From 3cadbd8d3977f4ebe23d92a21cb1daee86015234 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 May 2023 11:31:36 +0200 Subject: [PATCH 001/113] make GitHub workflows work with dev branch instead of develop --- .github/workflows/audit_test.yml | 2 +- .github/workflows/quality_test.yml | 2 +- .github/workflows/unit_test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/audit_test.yml b/.github/workflows/audit_test.yml index ed37267a..cf4334cc 100644 --- a/.github/workflows/audit_test.yml +++ b/.github/workflows/audit_test.yml @@ -1,7 +1,7 @@ on: push: branches: - - develop + - dev pull_request: branches: - main diff --git a/.github/workflows/quality_test.yml b/.github/workflows/quality_test.yml index f553fea0..1e01d77c 100644 --- a/.github/workflows/quality_test.yml +++ b/.github/workflows/quality_test.yml @@ -1,7 +1,7 @@ on: push: branches: - - develop + - dev pull_request: branches: - main diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 0d2e2857..4dfe9a0c 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -1,7 +1,7 @@ on: push: branches: - - develop + - dev pull_request: branches: - main From c45a271997520ec7ae06d0c4812f28f29b4e7dc1 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 3 Aug 2023 20:45:19 +0200 Subject: [PATCH 002/113] add code examples --- examples/std/.gitignore | 20 +++++++++++ examples/std/Cargo.toml | 33 +++++++++++++++++++ .../std/src/bin/tokio/net/send_request.rs | 4 +++ .../src/bin/transaction/sign_transaction.rs | 4 +++ .../std/src/bin/wallet/generate_wallet.rs | 7 ++++ .../std/src/bin/wallet/wallet_from_seed.rs | 9 +++++ 6 files changed, 77 insertions(+) create mode 100644 examples/std/.gitignore create mode 100644 examples/std/Cargo.toml create mode 100644 examples/std/src/bin/tokio/net/send_request.rs create mode 100644 examples/std/src/bin/transaction/sign_transaction.rs create mode 100644 examples/std/src/bin/wallet/generate_wallet.rs create mode 100644 examples/std/src/bin/wallet/wallet_from_seed.rs diff --git a/examples/std/.gitignore b/examples/std/.gitignore new file mode 100644 index 00000000..1ec67b52 --- /dev/null +++ b/examples/std/.gitignore @@ -0,0 +1,20 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Added by cargo +/target + +# VSCode +.vscode +.idea + +# Additional +src/main.rs diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml new file mode 100644 index 00000000..1e4a813c --- /dev/null +++ b/examples/std/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "std" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +xrpl-rust = { path = "../.." } + +[[bin]] +name = "wallet_from_seed" +path = "src/bin/wallet/wallet_from_seed.rs" +required-features = [ +] + +[[bin]] +name = "generate_wallet" +path = "src/bin/wallet/generate_wallet.rs" +required-features = [ +] + +[[bin]] +name = "send_request" +path = "src/bin/tokio/net/send_request.rs" +required-features = [ +] + +[[bin]] +name = "send_request" +path = "src/bin/transaction/sign_transaction.rs" +required-features = [ +] diff --git a/examples/std/src/bin/tokio/net/send_request.rs b/examples/std/src/bin/tokio/net/send_request.rs new file mode 100644 index 00000000..dc017cce --- /dev/null +++ b/examples/std/src/bin/tokio/net/send_request.rs @@ -0,0 +1,4 @@ +// TODO: add as soon as `AsyncWebsocketClient` is implemented +fn main() { + todo!() +} diff --git a/examples/std/src/bin/transaction/sign_transaction.rs b/examples/std/src/bin/transaction/sign_transaction.rs new file mode 100644 index 00000000..170c7df8 --- /dev/null +++ b/examples/std/src/bin/transaction/sign_transaction.rs @@ -0,0 +1,4 @@ +// TODO: add as soon as `sign` is implemented +fn main() { + todo!() +} diff --git a/examples/std/src/bin/wallet/generate_wallet.rs b/examples/std/src/bin/wallet/generate_wallet.rs new file mode 100644 index 00000000..4af7da74 --- /dev/null +++ b/examples/std/src/bin/wallet/generate_wallet.rs @@ -0,0 +1,7 @@ +use xrpl::wallet::Wallet; + +fn main() { + let wallet = Wallet::create(None).expect("Failed to generate new wallet"); + + println!("Wallet: {:?}", wallet); +} diff --git a/examples/std/src/bin/wallet/wallet_from_seed.rs b/examples/std/src/bin/wallet/wallet_from_seed.rs new file mode 100644 index 00000000..c561ecfd --- /dev/null +++ b/examples/std/src/bin/wallet/wallet_from_seed.rs @@ -0,0 +1,9 @@ +use xrpl::wallet::Wallet; + +fn main() { + let wallet = Wallet::new("sEdVWgwiHxBmFoMGJBoPZf6H1XSLLGd", 0) + .expect("Failed to create wallet from seed"); + + assert_eq!(wallet.classic_address, "rsAhdjbE7YXqQtubcaSwb6xHn6mU2bSFHY"); + println!("Wallet: {:?}", wallet); +} From 21154e2f2007bdeb11b039b9e09a8a3e679d1226 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 3 Aug 2023 20:46:56 +0200 Subject: [PATCH 003/113] fix model constructor to public; wallet scope public --- src/models/requests/account_channels.rs | 2 +- src/models/requests/account_currencies.rs | 2 +- src/models/requests/account_info.rs | 2 +- src/models/requests/account_lines.rs | 2 +- src/models/requests/account_nfts.rs | 7 ++++++- src/models/requests/account_objects.rs | 2 +- src/models/requests/account_offers.rs | 2 +- src/models/requests/account_tx.rs | 2 +- src/models/requests/book_offers.rs | 2 +- src/models/requests/channel_authorize.rs | 2 +- src/models/requests/channel_verify.rs | 2 +- src/models/requests/deposit_authorize.rs | 2 +- src/models/requests/fee.rs | 2 +- src/models/requests/gateway_balances.rs | 2 +- src/models/requests/ledger.rs | 2 +- src/models/requests/ledger_closed.rs | 2 +- src/models/requests/ledger_current.rs | 2 +- src/models/requests/ledger_data.rs | 2 +- src/models/requests/ledger_entry.rs | 2 +- src/models/requests/manifest.rs | 2 +- src/models/requests/nft_buy_offers.rs | 2 +- src/models/requests/nft_sell_offers.rs | 2 +- src/models/requests/no_ripple_check.rs | 2 +- src/models/requests/path_find.rs | 2 +- src/models/requests/ping.rs | 2 +- src/models/requests/random.rs | 2 +- src/models/requests/ripple_path_find.rs | 2 +- src/models/requests/server_info.rs | 2 +- src/models/requests/server_state.rs | 2 +- src/models/requests/submit.rs | 2 +- src/models/requests/submit_multisigned.rs | 2 +- src/models/requests/subscribe.rs | 2 +- src/models/requests/transaction_entry.rs | 2 +- src/models/requests/tx.rs | 2 +- src/models/requests/unsubscribe.rs | 2 +- src/models/transactions/account_delete.rs | 2 +- src/models/transactions/account_set.rs | 2 +- src/models/transactions/check_cancel.rs | 2 +- src/models/transactions/check_cash.rs | 2 +- src/models/transactions/check_create.rs | 2 +- src/models/transactions/deposit_preauth.rs | 2 +- src/models/transactions/escrow_cancel.rs | 2 +- src/models/transactions/escrow_create.rs | 2 +- src/models/transactions/escrow_finish.rs | 2 +- src/models/transactions/nftoken_accept_offer.rs | 2 +- src/models/transactions/nftoken_burn.rs | 2 +- src/models/transactions/nftoken_cancel_offer.rs | 2 +- src/models/transactions/nftoken_create_offer.rs | 2 +- src/models/transactions/nftoken_mint.rs | 2 +- src/models/transactions/offer_cancel.rs | 2 +- src/models/transactions/offer_create.rs | 2 +- src/models/transactions/payment.rs | 2 +- src/models/transactions/payment_channel_claim.rs | 2 +- src/models/transactions/payment_channel_create.rs | 2 +- src/models/transactions/payment_channel_fund.rs | 2 +- .../transactions/pseudo_transactions/enable_amendment.rs | 2 +- src/models/transactions/pseudo_transactions/set_fee.rs | 2 +- src/models/transactions/pseudo_transactions/unl_modify.rs | 2 +- src/models/transactions/set_regular_key.rs | 2 +- src/models/transactions/signer_list_set.rs | 2 +- src/models/transactions/ticket_create.rs | 2 +- src/models/transactions/trust_set.rs | 2 +- src/wallet/mod.rs | 3 ++- 63 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index 705d5cd2..a6f0ed97 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -76,7 +76,7 @@ impl<'a> Default for AccountChannels<'a> { impl<'a> Model for AccountChannels<'a> {} impl<'a> AccountChannels<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index 35a91428..1edc9008 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -50,7 +50,7 @@ impl<'a> Default for AccountCurrencies<'a> { impl<'a> Model for AccountCurrencies<'a> {} impl<'a> AccountCurrencies<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 4f2ca2d7..038b06b4 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -59,7 +59,7 @@ impl<'a> Default for AccountInfo<'a> { impl<'a> Model for AccountInfo<'a> {} impl<'a> AccountInfo<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index d08e88f1..8ca47b56 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -56,7 +56,7 @@ impl<'a> Default for AccountLines<'a> { impl<'a> Model for AccountLines<'a> {} impl<'a> AccountLines<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index 2cf7fb3a..faa26391 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -41,7 +41,12 @@ impl<'a> Default for AccountNfts<'a> { impl<'a> Model for AccountNfts<'a> {} impl<'a> AccountNfts<'a> { - fn new(account: &'a str, id: Option<&'a str>, limit: Option, marker: Option) -> Self { + pub fn new( + account: &'a str, + id: Option<&'a str>, + limit: Option, + marker: Option, + ) -> Self { Self { account, id, diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index 0868a200..24a7a0ff 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -79,7 +79,7 @@ impl<'a> Default for AccountObjects<'a> { impl<'a> Model for AccountObjects<'a> {} impl<'a> AccountObjects<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 90500ebf..d310d540 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -55,7 +55,7 @@ impl<'a> Default for AccountOffers<'a> { impl<'a> Model for AccountOffers<'a> {} impl<'a> AccountOffers<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index d3c63107..25827af4 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -71,7 +71,7 @@ impl<'a> Default for AccountTx<'a> { impl<'a> Model for AccountTx<'a> {} impl<'a> AccountTx<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index 75551e4a..e470a70c 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -61,7 +61,7 @@ impl<'a> Default for BookOffers<'a> { impl<'a> Model for BookOffers<'a> {} impl<'a> BookOffers<'a> { - fn new( + pub fn new( taker_gets: Currency<'a>, taker_pays: Currency<'a>, id: Option<&'a str>, diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 348eb7b1..bd21f346 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -120,7 +120,7 @@ impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { } impl<'a> ChannelAuthorize<'a> { - fn new( + pub fn new( channel_id: &'a str, amount: &'a str, id: Option<&'a str>, diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 4355e07f..73fb1dc3 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -43,7 +43,7 @@ impl<'a> Default for ChannelVerify<'a> { impl<'a> Model for ChannelVerify<'a> {} impl<'a> ChannelVerify<'a> { - fn new( + pub fn new( channel_id: &'a str, amount: &'a str, public_key: &'a str, diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 21a4af44..087a00db 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -43,7 +43,7 @@ impl<'a> Default for DepositAuthorized<'a> { impl<'a> Model for DepositAuthorized<'a> {} impl<'a> DepositAuthorized<'a> { - fn new( + pub fn new( source_account: &'a str, destination_account: &'a str, id: Option<&'a str>, diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index a5507b32..8416a2ef 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -32,7 +32,7 @@ impl<'a> Default for Fee<'a> { impl<'a> Model for Fee<'a> {} impl<'a> Fee<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::Fee, diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index 122ff42c..ade93092 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -50,7 +50,7 @@ impl<'a> Default for GatewayBalances<'a> { impl<'a> Model for GatewayBalances<'a> {} impl<'a> GatewayBalances<'a> { - fn new( + pub fn new( account: &'a str, id: Option<&'a str>, strict: Option, diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index 76d22253..bc1eabe0 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -76,7 +76,7 @@ impl<'a> Default for Ledger<'a> { impl<'a> Model for Ledger<'a> {} impl<'a> Ledger<'a> { - fn new( + pub fn new( id: Option<&'a str>, ledger_hash: Option<&'a str>, ledger_index: Option<&'a str>, diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index 1b76d328..e7b5d309 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -31,7 +31,7 @@ impl<'a> Default for LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} impl<'a> LedgerClosed<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::LedgerClosed, diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 93e6bfac..6ccfd04d 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -31,7 +31,7 @@ impl<'a> Default for LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} impl<'a> LedgerCurrent<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::LedgerCurrent, diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index e5197671..362e4291 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -50,7 +50,7 @@ impl<'a> Default for LedgerData<'a> { impl<'a> Model for LedgerData<'a> {} impl<'a> LedgerData<'a> { - fn new( + pub fn new( id: Option<&'a str>, ledger_hash: Option<&'a str>, ledger_index: Option<&'a str>, diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index a21bd5a6..a7e851bb 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -178,7 +178,7 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } impl<'a> LedgerEntry<'a> { - fn new( + pub fn new( id: Option<&'a str>, index: Option<&'a str>, account_root: Option<&'a str>, diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index 9705a6f3..7a02e289 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -37,7 +37,7 @@ impl<'a> Default for Manifest<'a> { impl<'a> Model for Manifest<'a> {} impl<'a> Manifest<'a> { - fn new(public_key: &'a str, id: Option<&'a str>) -> Self { + pub fn new(public_key: &'a str, id: Option<&'a str>) -> Self { Self { public_key, id, diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index b34308d3..3ecfa1ef 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -42,7 +42,7 @@ impl<'a> Default for NftBuyOffers<'a> { impl<'a> Model for NftBuyOffers<'a> {} impl<'a> NftBuyOffers<'a> { - fn new( + pub fn new( nft_id: &'a str, ledger_hash: Option<&'a str>, ledger_index: Option<&'a str>, diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 35273506..354cafda 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -26,7 +26,7 @@ impl<'a> Default for NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} impl<'a> NftSellOffers<'a> { - fn new(nft_id: &'a str) -> Self { + pub fn new(nft_id: &'a str) -> Self { Self { nft_id, command: RequestMethod::NftSellOffers, diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index 5c52ef61..37f5f0bc 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -73,7 +73,7 @@ impl<'a> Default for NoRippleCheck<'a> { impl<'a> Model for NoRippleCheck<'a> {} impl<'a> NoRippleCheck<'a> { - fn new( + pub fn new( account: &'a str, role: NoRippleCheckRole, id: Option<&'a str>, diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index 57bc751b..0eedc299 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -105,7 +105,7 @@ impl<'a> Default for PathFind<'a> { impl<'a> Model for PathFind<'a> {} impl<'a> PathFind<'a> { - fn new( + pub fn new( subcommand: PathFindSubcommand, source_account: &'a str, destination_account: &'a str, diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 24e1c68b..9c100442 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -30,7 +30,7 @@ impl<'a> Default for Ping<'a> { impl<'a> Model for Ping<'a> {} impl<'a> Ping<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::Ping, diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 8cf99192..0ecbfbdc 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -31,7 +31,7 @@ impl<'a> Default for Random<'a> { impl<'a> Model for Random<'a> {} impl<'a> Random<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::Random, diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index cbafd6ed..77e4ce06 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -79,7 +79,7 @@ impl<'a> Default for RipplePathFind<'a> { impl<'a> Model for RipplePathFind<'a> {} impl<'a> RipplePathFind<'a> { - fn new( + pub fn new( source_account: &'a str, destination_account: &'a str, destination_amount: Currency<'a>, diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 0308fbd5..f869a4ae 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -31,7 +31,7 @@ impl<'a> Default for ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} impl<'a> ServerInfo<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::ServerInfo, diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 2b7942c6..8f1997b8 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -36,7 +36,7 @@ impl<'a> Default for ServerState<'a> { impl<'a> Model for ServerState<'a> {} impl<'a> ServerState<'a> { - fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option<&'a str>) -> Self { Self { id, command: RequestMethod::ServerState, diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index 2fabd316..537125d7 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -60,7 +60,7 @@ impl<'a> Default for Submit<'a> { impl<'a> Model for Submit<'a> {} impl<'a> Submit<'a> { - fn new(tx_blob: &'a str, id: Option<&'a str>, fail_hard: Option) -> Self { + pub fn new(tx_blob: &'a str, id: Option<&'a str>, fail_hard: Option) -> Self { Self { tx_blob, id, diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 368207a3..bb1d0cf6 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -40,7 +40,7 @@ impl<'a> Default for SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} impl<'a> SubmitMultisigned<'a> { - fn new(id: Option<&'a str>, fail_hard: Option) -> Self { + pub fn new(id: Option<&'a str>, fail_hard: Option) -> Self { Self { id, fail_hard, diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index 5c1efd05..60adf24d 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -93,7 +93,7 @@ impl<'a> Default for Subscribe<'a> { impl<'a> Model for Subscribe<'a> {} impl<'a> Subscribe<'a> { - fn new( + pub fn new( id: Option<&'a str>, books: Option>>, streams: Option>, diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index cf0cf33c..b6687476 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -43,7 +43,7 @@ impl<'a> Default for TransactionEntry<'a> { impl<'a> Model for TransactionEntry<'a> {} impl<'a> TransactionEntry<'a> { - fn new( + pub fn new( tx_hash: &'a str, id: Option<&'a str>, ledger_hash: Option<&'a str>, diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 9d3056f9..2425536b 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -46,7 +46,7 @@ impl<'a> Default for Tx<'a> { impl<'a> Model for Tx<'a> {} impl<'a> Tx<'a> { - fn new( + pub fn new( id: Option<&'a str>, binary: Option, min_ledger: Option, diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index d8ac3d99..8ffc8210 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -75,7 +75,7 @@ impl<'a> Default for Unsubscribe<'a> { impl<'a> Model for Unsubscribe<'a> {} impl<'a> Unsubscribe<'a> { - fn new( + pub fn new( id: Option<&'a str>, books: Option>>, streams: Option>, diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 6623b401..e30e1a05 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -121,7 +121,7 @@ impl<'a> Transaction for AccountDelete<'a> { } impl<'a> AccountDelete<'a> { - fn new( + pub fn new( account: &'a str, destination: &'a str, fee: Option>, diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 8b37c14a..43f40478 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -387,7 +387,7 @@ impl<'a> AccountSetError for AccountSet<'a> { } impl<'a> AccountSet<'a> { - fn new( + pub fn new( account: &'a str, fee: Option>, sequence: Option, diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 26a81743..48bef9c1 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -114,7 +114,7 @@ impl<'a> Transaction for CheckCancel<'a> { } impl<'a> CheckCancel<'a> { - fn new( + pub fn new( account: &'a str, check_id: &'a str, fee: Option>, diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 58648d39..53816f65 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -147,7 +147,7 @@ impl<'a> CheckCashError for CheckCash<'a> { } impl<'a> CheckCash<'a> { - fn new( + pub fn new( account: &'a str, check_id: &'a str, fee: Option>, diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 772bbd11..b5c1f83a 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -121,7 +121,7 @@ impl<'a> Transaction for CheckCreate<'a> { } impl<'a> CheckCreate<'a> { - fn new( + pub fn new( account: &'a str, destination: &'a str, send_max: Amount<'a>, diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index ceb42a5e..5e32517b 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -141,7 +141,7 @@ impl<'a> DepositPreauthError for DepositPreauth<'a> { } impl<'a> DepositPreauth<'a> { - fn new( + pub fn new( account: &'a str, fee: Option>, sequence: Option, diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 5bef7a5b..42eadca2 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -112,7 +112,7 @@ impl<'a> Transaction for EscrowCancel<'a> { } impl<'a> EscrowCancel<'a> { - fn new( + pub fn new( account: &'a str, owner: &'a str, offer_sequence: u32, diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index f5e3b102..d0eacaba 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -152,7 +152,7 @@ impl<'a> EscrowCreateError for EscrowCreate<'a> { } impl<'a> EscrowCreate<'a> { - fn new( + pub fn new( account: &'a str, amount: XRPAmount<'a>, destination: &'a str, diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 7f5f75e2..e049edcd 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -144,7 +144,7 @@ impl<'a> EscrowFinishError for EscrowFinish<'a> { } impl<'a> EscrowFinish<'a> { - fn new( + pub fn new( account: &'a str, owner: &'a str, offer_sequence: u32, diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 0b8f785c..dcfcc532 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -174,7 +174,7 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { } impl<'a> NFTokenAcceptOffer<'a> { - fn new( + pub fn new( account: &'a str, fee: Option>, sequence: Option, diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 457d2382..e3320432 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -114,7 +114,7 @@ impl<'a> Transaction for NFTokenBurn<'a> { } impl<'a> NFTokenBurn<'a> { - fn new( + pub fn new( account: &'a str, nftoken_id: &'a str, fee: Option>, diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 5c850ccd..3d18160d 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -139,7 +139,7 @@ impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { } impl<'a> NFTokenCancelOffer<'a> { - fn new( + pub fn new( account: &'a str, nftoken_offers: Vec<&'a str>, fee: Option>, diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 658a061a..44cc792f 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -255,7 +255,7 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { } impl<'a> NFTokenCreateOffer<'a> { - fn new( + pub fn new( account: &'a str, nftoken_id: &'a str, amount: Amount<'a>, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index e61dbc08..dbe29528 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -237,7 +237,7 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { } impl<'a> NFTokenMint<'a> { - fn new( + pub fn new( account: &'a str, nftoken_taxon: u32, fee: Option>, diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 643252a2..560b62f2 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -110,7 +110,7 @@ impl<'a> Transaction for OfferCancel<'a> { } impl<'a> OfferCancel<'a> { - fn new( + pub fn new( account: &'a str, offer_sequence: u32, fee: Option>, diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 1acd33de..13b2175e 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -174,7 +174,7 @@ impl<'a> Transaction for OfferCreate<'a> { } impl<'a> OfferCreate<'a> { - fn new( + pub fn new( account: &'a str, taker_gets: Amount<'a>, taker_pays: Amount<'a>, diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 41c757a0..6cd3ef0d 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -260,7 +260,7 @@ impl<'a> PaymentError for Payment<'a> { } impl<'a> Payment<'a> { - fn new( + pub fn new( account: &'a str, amount: Amount<'a>, destination: &'a str, diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 61ccbd34..4a30a21b 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -174,7 +174,7 @@ impl<'a> Transaction for PaymentChannelClaim<'a> { } impl<'a> PaymentChannelClaim<'a> { - fn new( + pub fn new( account: &'a str, channel: &'a str, fee: Option>, diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 9188e8a2..0b444b5e 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -120,7 +120,7 @@ impl<'a> Transaction for PaymentChannelCreate<'a> { } impl<'a> PaymentChannelCreate<'a> { - fn new( + pub fn new( account: &'a str, amount: XRPAmount<'a>, destination: &'a str, diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 4f9c3a34..7870a26e 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -115,7 +115,7 @@ impl<'a> Transaction for PaymentChannelFund<'a> { } impl<'a> PaymentChannelFund<'a> { - fn new( + pub fn new( account: &'a str, channel: &'a str, amount: XRPAmount<'a>, diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index a7476a73..463ea2b1 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -103,7 +103,7 @@ impl<'a> Transaction for EnableAmendment<'a> { } impl<'a> EnableAmendment<'a> { - fn new( + pub fn new( account: &'a str, amendment: &'a str, ledger_sequence: u32, diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index b1aeced5..f3782328 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -69,7 +69,7 @@ impl<'a> Transaction for SetFee<'a> { } impl<'a> SetFee<'a> { - fn new( + pub fn new( account: &'a str, base_fee: XRPAmount<'a>, reference_fee_units: u32, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index b9a3d7fd..8ce72482 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -78,7 +78,7 @@ impl<'a> Transaction for UNLModify<'a> { } impl<'a> UNLModify<'a> { - fn new( + pub fn new( account: &'a str, ledger_sequence: u32, unlmodify_disabling: UNLModifyDisabling, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 38697431..d651dc48 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -114,7 +114,7 @@ impl<'a> Transaction for SetRegularKey<'a> { } impl<'a> SetRegularKey<'a> { - fn new( + pub fn new( account: &'a str, fee: Option>, sequence: Option, diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 8223c03e..75519cad 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -226,7 +226,7 @@ impl<'a> SignerListSetError for SignerListSet<'a> { } impl<'a> SignerListSet<'a> { - fn new( + pub fn new( account: &'a str, signer_quorum: u32, fee: Option>, diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 2efb723d..3779eab2 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -110,7 +110,7 @@ impl<'a> Transaction for TicketCreate<'a> { } impl<'a> TicketCreate<'a> { - fn new( + pub fn new( account: &'a str, ticket_count: u32, fee: Option>, diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 5b0727d8..3165cd3c 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -162,7 +162,7 @@ impl<'a> Transaction for TrustSet<'a> { } impl<'a> TrustSet<'a> { - fn new( + pub fn new( account: &'a str, limit_amount: IssuedCurrencyAmount<'a>, fee: Option>, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 0d36f11c..acc3bbb8 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -18,7 +18,8 @@ use zeroize::Zeroize; /// /// See Cryptographic Keys: /// `` -struct Wallet { +#[derive(Debug)] +pub struct Wallet { /// The seed from which the public and private keys /// are derived. pub seed: String, From 156ddaad9ea714bffd263e8fb31ba1563bfa4640 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 3 Aug 2023 20:54:26 +0200 Subject: [PATCH 004/113] add changes to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c88321c..3cf681dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Performance Benchmarks ## [[Unreleased]] +- Examples + - Wallet from seed + - New wallet generation ## [[v0.2.0-beta]] ### Added From 765e5b222761c32ac811e2376e2f2d8709bc0432 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 3 Aug 2023 21:02:57 +0200 Subject: [PATCH 005/113] add changes to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cf681dd..15c526bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples - Wallet from seed - New wallet generation +- make `new` methods of models public ## [[v0.2.0-beta]] ### Added From 5582ad14d46d39da434aca97610b3e15f8bc7bba Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 9 Aug 2023 22:51:51 +0200 Subject: [PATCH 006/113] run tests on pull requests to the dev branch --- .github/workflows/unit_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 4dfe9a0c..7fd6a512 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -5,6 +5,7 @@ on: pull_request: branches: - main + - dev name: Unit From 26a7f81d1b8cc0675adaae7641b024e603809caa Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:10:42 +0200 Subject: [PATCH 007/113] add AsyncWebsocketClient --- src/asynch/clients/async_websocket_client.rs | 284 +++++++++++++++++++ src/asynch/clients/exceptions.rs | 40 +++ src/asynch/clients/mod.rs | 6 + src/asynch/mod.rs | 2 + src/lib.rs | 1 + 5 files changed, 333 insertions(+) create mode 100644 src/asynch/clients/async_websocket_client.rs create mode 100644 src/asynch/clients/exceptions.rs create mode 100644 src/asynch/clients/mod.rs create mode 100644 src/asynch/mod.rs diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs new file mode 100644 index 00000000..4368dd9a --- /dev/null +++ b/src/asynch/clients/async_websocket_client.rs @@ -0,0 +1,284 @@ +use super::exceptions::XRPLWebsocketException; +use crate::Err; +use alloc::string::ToString; +use anyhow::Result; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; +use embedded_websocket::{ + framer_async::Framer as EmbeddedWebsocketFramer, Client as EmbeddedWebsocketClient, + WebSocket as EmbeddedWebsocket, +}; +use futures::{Sink, Stream}; +use rand_core::RngCore; +// Exports +#[cfg(feature = "tungstenite")] +pub use tungstenite::*; + +pub use embedded_websocket::{ + framer_async::{ + FramerError as EmbeddedWebsocketFramerError, ReadResult as EmbeddedWebsocketReadMessageType, + }, + Error as EmbeddedWebsocketError, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, + WebSocketOptions as EmbeddedWebsocketOptions, + WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, + WebSocketState as EmbeddedWebsocketState, +}; + +pub type AsyncWebsocketClientEmbeddedWebsocket = + AsyncWebsocketClient, Status>; + +pub struct WebsocketOpen; +pub struct WebsocketClosed; + +pub struct AsyncWebsocketClient { + inner: T, + status: PhantomData, +} + +impl AsyncWebsocketClient { + pub fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +#[cfg(feature = "tungstenite")] +mod tungstenite { + use super::*; + use core::fmt::Display; + use core::{pin::Pin, task::Poll}; + use tokio::net::TcpStream; + pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; + use tokio_tungstenite::{ + connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, + WebSocketStream as TungsteniteWebsocketStream, + }; + use url::Url; + + pub type AsyncWebsocketClientTungstenite = AsyncWebsocketClient< + TungsteniteWebsocketStream>, + Status, + >; + + impl Sink for AsyncWebsocketClient + where + T: Sink + Unpin, + >::Error: Display, + I: serde::Serialize, + { + type Error = anyhow::Error; + + fn poll_ready( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send( + mut self: core::pin::Pin<&mut Self>, + item: I, + ) -> core::result::Result<(), Self::Error> { + match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text( + serde_json::to_string(&item).unwrap(), + )) { + // TODO: unwrap + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } + + fn poll_flush( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_flush(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn poll_close( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_close(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + } + + impl Stream for AsyncWebsocketClient + where + T: Stream + Unpin, + { + type Item = ::Item; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll> { + match Pin::new(&mut self.inner).poll_next(cx) { + Poll::Ready(Some(item)) => Poll::Ready(Some(item)), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } + } + + impl + AsyncWebsocketClient< + TungsteniteWebsocketStream>, + WebsocketClosed, + > + { + pub async fn open( + uri: Url, + ) -> Result< + AsyncWebsocketClient< + TungsteniteWebsocketStream>, + WebsocketOpen, + >, + > { + let (websocket_stream, _) = tungstenite_connect_async(uri).await.unwrap(); // TODO: unwrap + + Ok(AsyncWebsocketClient { + inner: websocket_stream, + status: PhantomData::, + }) + } + } +} + +impl + AsyncWebsocketClient, WebsocketClosed> +where + Rng: RngCore, +{ + pub async fn open<'a, B, E>( + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + rng: Rng, + websocket_options: &EmbeddedWebsocketOptions<'_>, + ) -> Result< + AsyncWebsocketClient, WebsocketOpen>, + > + where + B: AsRef<[u8]>, + E: Debug, + { + let websocket = EmbeddedWebsocket::::new_client(rng); + let mut framer = EmbeddedWebsocketFramer::new(websocket); + framer + .connect(stream, buffer, websocket_options) + .await + .unwrap(); // TODO: unwrap + + Ok(AsyncWebsocketClient { + inner: framer, + status: PhantomData::, + }) + } +} + +impl AsyncWebsocketClient, WebsocketOpen> +where + Rng: RngCore, +{ + pub fn encode( + &mut self, + message_type: EmbeddedWebsocketSendMessageType, + end_of_message: bool, + from: &[u8], + to: &mut [u8], + ) -> Result + where + E: Debug, + { + let len = self + .inner + .encode::(message_type, end_of_message, from, to) + .unwrap(); // TODO: unwrap + + Ok(len) + } + + pub async fn send<'b, E, R: serde::Serialize>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + end_of_message: bool, + frame_buf: R, + ) -> Result<()> + where + E: Debug, + { + self.inner + .write( + stream, + stream_buf, + EmbeddedWebsocketSendMessageType::Binary, + end_of_message, + serde_json::to_vec(&frame_buf).unwrap().as_slice(), + ) // TODO: unwrap + .await + .unwrap(); // TODO: unwrap + + Ok(()) + } + + pub async fn close<'b, E>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + close_status: EmbeddedWebsocketCloseStatusCode, + status_description: Option<&str>, + ) -> Result<()> + where + E: Debug, + { + self.inner + .close(stream, stream_buf, close_status, status_description) + .await + .unwrap(); // TODO: unwrap + + Ok(()) + } + + pub async fn next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Option>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Some(Ok(read_result)), + Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), + None => None, + } + } + + pub async fn try_next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Result>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Ok(Some(read_result)), + Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), + None => Ok(None), + } + } +} diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs new file mode 100644 index 00000000..635a3e7a --- /dev/null +++ b/src/asynch/clients/exceptions.rs @@ -0,0 +1,40 @@ +use core::fmt::Debug; +use core::str::Utf8Error; +use embedded_websocket::framer_async::FramerError; +use thiserror_no_std::Error; + +#[derive(Debug, PartialEq, Eq, Error)] +pub enum XRPLWebsocketException { + // FramerError + #[error("I/O error: {0:?}")] + Io(E), + #[error("Frame too large (size: {0:?})")] + FrameTooLarge(usize), + #[error("Failed to interpret u8 to string (error: {0:?})")] + Utf8(Utf8Error), + #[error("Invalid HTTP header")] + HttpHeader, + #[error("Websocket error: {0:?}")] + WebSocket(embedded_websocket::Error), + #[error("Disconnected")] + Disconnected, + #[error("Read buffer is too small (size: {0:?})")] + RxBufferTooSmall(usize), +} + +impl From> for XRPLWebsocketException { + fn from(value: FramerError) -> Self { + match value { + FramerError::Io(e) => XRPLWebsocketException::Io(e), + FramerError::FrameTooLarge(e) => XRPLWebsocketException::FrameTooLarge(e), + FramerError::Utf8(e) => XRPLWebsocketException::Utf8(e), + FramerError::HttpHeader(_) => XRPLWebsocketException::HttpHeader, + FramerError::WebSocket(e) => XRPLWebsocketException::WebSocket(e), + FramerError::Disconnected => XRPLWebsocketException::Disconnected, + FramerError::RxBufferTooSmall(e) => XRPLWebsocketException::RxBufferTooSmall(e), + } + } +} + +#[cfg(feature = "std")] +impl alloc::error::Error for XRPLWebsocketException {} diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs new file mode 100644 index 00000000..b5dbb46e --- /dev/null +++ b/src/asynch/clients/mod.rs @@ -0,0 +1,6 @@ +mod async_json_rpc_client; +mod async_websocket_client; +mod exceptions; + +pub use async_json_rpc_client::*; +pub use async_websocket_client::*; diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs new file mode 100644 index 00000000..286b8f61 --- /dev/null +++ b/src/asynch/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "net")] +pub mod clients; diff --git a/src/lib.rs b/src/lib.rs index 58f3a239..481b4225 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std as alloc; +pub mod asynch; pub mod constants; #[cfg(feature = "core")] pub mod core; From c9b1b07b0b43f9905f0ff7dcd214a34e68e78a8e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:11:03 +0200 Subject: [PATCH 008/113] add AsyncWebsocketClient example --- .../std/src/bin/tokio/net/send_request.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/examples/std/src/bin/tokio/net/send_request.rs b/examples/std/src/bin/tokio/net/send_request.rs index dc017cce..4270b701 100644 --- a/examples/std/src/bin/tokio/net/send_request.rs +++ b/examples/std/src/bin/tokio/net/send_request.rs @@ -1,4 +1,32 @@ +use xrpl::asynch::client::{AsyncWebsocketClientTungstenite, TungsteniteMessage}; +use xrpl::models::requests::AccountInfo; + // TODO: add as soon as `AsyncWebsocketClient` is implemented -fn main() { - todo!() +#[tokio::main] +async fn main() { + let websocket = + AsyncWebsocketClientTungstenite::open("wss://xrplcluster.com/".parse().unwrap()) + .await + .unwrap(); + assert!(websocket.is_open()); + + let account_info = AccountInfo::new( + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + None, + None, + None, + None, + None, + None, + ); + + websocket.send(&account_info).await.unwrap(); + + while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { + let account_info_echo: AccountInfo = serde_json::from_str(response.as_str()).unwrap(); + println!("account_info_echo: {:?}", account_info_echo); + + websocket.close().await.unwrap(); + break; + } } From d88b0b17de8723df4acf007984c59f0738c27d5e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:11:27 +0200 Subject: [PATCH 009/113] add AsyncWebsocketClient tests --- tests/common.rs | 3 -- tests/common/codec.rs | 35 ++++++++++++++ tests/common/constants.rs | 2 + tests/common/mod.rs | 43 +++++++++++++++++ tests/integration/clients/mod.rs | 81 ++++++++++++++++++++++++++++++++ tests/integration/mod.rs | 3 ++ tests/integration_tests.rs | 4 ++ 7 files changed, 168 insertions(+), 3 deletions(-) delete mode 100644 tests/common.rs create mode 100644 tests/common/codec.rs create mode 100644 tests/common/constants.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/integration/clients/mod.rs create mode 100644 tests/integration/mod.rs create mode 100644 tests/integration_tests.rs diff --git a/tests/common.rs b/tests/common.rs deleted file mode 100644 index 7db0151d..00000000 --- a/tests/common.rs +++ /dev/null @@ -1,3 +0,0 @@ -/// Setup common testing prerequisites here such as connecting a client -/// to a server or creating required files/directories if needed. -pub fn setup() {} diff --git a/tests/common/codec.rs b/tests/common/codec.rs new file mode 100644 index 00000000..7e074866 --- /dev/null +++ b/tests/common/codec.rs @@ -0,0 +1,35 @@ +use bytes::{BufMut, BytesMut}; +use std::io; +use tokio_util::codec::{Decoder, Encoder}; + +pub struct Codec {} + +impl Codec { + pub fn new() -> Self { + Codec {} + } +} + +impl Decoder for Codec { + type Item = BytesMut; + type Error = io::Error; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, io::Error> { + if !buf.is_empty() { + let len = buf.len(); + Ok(Some(buf.split_to(len))) + } else { + Ok(None) + } + } +} + +impl Encoder<&[u8]> for Codec { + type Error = io::Error; + + fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { + buf.reserve(data.len()); + buf.put(data); + Ok(()) + } +} diff --git a/tests/common/constants.rs b/tests/common/constants.rs new file mode 100644 index 00000000..fb33485a --- /dev/null +++ b/tests/common/constants.rs @@ -0,0 +1,2 @@ +pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror/"; +pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 00000000..0312307c --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,43 @@ +pub mod codec; +#[cfg(feature = "tungstenite")] +use xrpl::asynch::clients::AsyncWebsocketClientTungstenite; +use xrpl::asynch::clients::{ + AsyncWebsocketClientEmbeddedWebsocket, EmbeddedWebsocketOptions, WebsocketOpen, +}; + +use tokio::net::TcpStream; +use tokio_util::codec::Framed; + +mod constants; +pub use constants::*; + +#[cfg(feature = "tungstenite")] +pub async fn connect_to_wss_tungstinite_echo() -> AsyncWebsocketClientTungstenite { + let websocket = AsyncWebsocketClientTungstenite::open(ECHO_WSS_SERVER.parse().unwrap()) + .await + .unwrap(); + assert!(websocket.is_open()); + + websocket +} + +pub async fn connect_to_ws_embedded_websocket_tokio_echo( + stream: &mut Framed, + buffer: &mut [u8], +) -> AsyncWebsocketClientEmbeddedWebsocket { + let rng = rand::thread_rng(); + let websocket_options = EmbeddedWebsocketOptions { + path: "/mirror", + host: "ws.vi-server.org", + origin: "http://ws.vi-server.org:80", + sub_protocols: None, + additional_headers: None, + }; + + let websocket = + AsyncWebsocketClientEmbeddedWebsocket::open(stream, buffer, rng, &websocket_options) + .await + .unwrap(); + + websocket +} diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs new file mode 100644 index 00000000..80321e4f --- /dev/null +++ b/tests/integration/clients/mod.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "tungstenite")] +use super::common::connect_to_wss_tungstinite_echo; +use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; +#[cfg(feature = "tungstenite")] +use futures::{SinkExt, TryStreamExt}; +use tokio_util::codec::Framed; +use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; + +#[cfg(feature = "tungstenite")] +use xrpl::asynch::clients::TungsteniteMessage; +use xrpl::models::requests::AccountInfo; + +#[cfg(feature = "tungstenite")] +#[tokio::test] +async fn test_websocket_tungstenite_echo() { + let mut websocket = connect_to_wss_tungstinite_echo().await; + let account_info = AccountInfo::new( + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + None, + None, + None, + None, + None, + None, + ); + + websocket.send(&account_info).await.unwrap(); + + while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { + let account_info_echo: AccountInfo = serde_json::from_str(response.as_str()).unwrap(); + println!("account_info_echo: {:?}", account_info_echo); + assert_eq!(account_info, account_info_echo); + + break; + } +} + +#[tokio::test] +async fn test_embedded_websocket_echo() { + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let mut framed = Framed::new(tcp_stream, Codec::new()); + let mut buffer = [0u8; 4096]; + let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await; + let account_info = AccountInfo::new( + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + None, + None, + None, + None, + None, + None, + ); + websocket + .send(&mut framed, &mut buffer, false, &account_info) + .await + .unwrap(); + + let mut ping_counter = 0; + loop { + let message = websocket + .try_next(&mut framed, &mut buffer) + .await + .unwrap() + .unwrap(); + match message { + EmbeddedWebsocketReadMessageType::Binary(msg) => { + assert_eq!(serde_json::to_vec(&account_info).unwrap().as_slice(), msg); + break; + } + EmbeddedWebsocketReadMessageType::Ping(_) => { + ping_counter += 1; + if ping_counter > 1 { + panic!("Expected only one ping"); + } + } + _ => panic!("Expected binary message"), + } + } +} diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs new file mode 100644 index 00000000..d9510731 --- /dev/null +++ b/tests/integration/mod.rs @@ -0,0 +1,3 @@ +use super::common; + +mod clients; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 00000000..8732ebce --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,4 @@ +#![allow(dead_code)] // Remove eventually +mod common; + +mod integration; From 8c8e667c3cb0ad1fc38fbdceb3321abb54ed4670 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:12:12 +0200 Subject: [PATCH 010/113] add networking dependencies/update dependencies --- Cargo.toml | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11b261e2..ab5119e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,14 +29,14 @@ ed25519-dalek = "1.0.1" secp256k1 = { version = "0.27.0", default-features = false, features = [ "alloc", ] } -bs58 = { version = "0.4.0", default-features = false, features = [ +bs58 = { version = "0.5.0", default-features = false, features = [ "check", "alloc", ] } -indexmap = { version = "1.7.0", features = ["serde"] } +indexmap = { version = "2.0.0", features = ["serde"] } regex = { version = "1.5.4", default-features = false } -strum = { version = "0.24.1", default-features = false } -strum_macros = { version = "0.24.2", default-features = false } +strum = { version = "0.25.0", default-features = false } +strum_macros = { version = "0.25.2", default-features = false } crypto-bigint = { version = "0.5.1" } rust_decimal = { version = "1.17.0", default-features = false, features = [ "serde", @@ -51,33 +51,63 @@ serde = { version = "1.0.130", default-features = false, features = ["derive"] } serde_json = { version = "1.0.68", default-features = false, features = [ "alloc", ] } -serde_with = "2.3.1" +serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.13.2", default-features = false, features = ["serde"] } +hashbrown = { version = "0.14.0", default-features = false, features = ["serde"] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version ="1.0.69", default-features = false } +tokio = { version = "1.28.0", default-features = false, optional = true } +url = { version = "2.2.2", default-features = false, optional = true } +futures = { version = "0.3.28", default-features = false, optional = true } +rand_core = { version = "0.6.4", default-features = false } +tokio-tungstenite = { version = "0.20.0", optional = true } + +[dependencies.embedded-websocket] +# git version needed to use `framer_async` +git = "https://github.com/ninjasource/embedded-websocket" +version = "0.9.2" +rev = "8d87d46f46fa0c75e099ca8aad37e8d00c8854f8" +default-features = false [dev-dependencies] -criterion = "0.4.0" +criterion = "0.5.1" cargo-husky = { version = "1.5.0", default-features = false, features = [ "user-hooks", ] } +tokio = { version = "1.28.2", features = ["full"] } +tokio-util = { version = "0.7.7", features = ["codec"] } +bytes = { version = "1.4.0", default-features = false } [[bench]] name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils"] +default = ["std", "core", "models", "utils", "net", "tungstenite"] models = ["core", "transactions", "requests", "ledger"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] +net = ["url", "futures"] +tungstenite = ["tokio/net", "tokio-tungstenite/native-tls"] core = ["utils"] utils = [] -std = ["rand/std", "regex/std", "chrono/std", "rand/std_rng", "hex/std", "rust_decimal/std", "bs58/std", "serde/std", "indexmap/std", "secp256k1/std"] +std = [ + "embedded-websocket/std", + "futures/std", + "rand/std", + "regex/std", + "chrono/std", + "rand/std_rng", + "hex/std", + "rust_decimal/std", + "bs58/std", + "serde/std", + "indexmap/std", + "secp256k1/std", +] From 33e85ef8e55a5ee144cea0158cdc8773e4e00bb2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:12:26 +0200 Subject: [PATCH 011/113] adjust git workflow --- .github/workflows/unit_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 7fd6a512..907c18c4 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --no-default-features --features core,models + args: --release --no-default-features --features core,models,net,tungstenite - uses: actions-rs/cargo@v1 with: command: test @@ -33,4 +33,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models + args: --no-default-features --features core,models,net,tungstenite From f6b9a02c506484534f34053afe0ed12ae406b5c3 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:12:37 +0200 Subject: [PATCH 012/113] add changes to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c526bc..971c1e36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Examples - Wallet from seed - New wallet generation + - `AsyncWebsocketClient` requests - make `new` methods of models public +- add `AsyncWebsocketClient` +- update dependencies ## [[v0.2.0-beta]] ### Added From 9dea852cc11a9d29f32d3ca9b06798974877bd3d Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:34:56 +0200 Subject: [PATCH 013/113] try fix git worklow error --- rust-toolchain | 1 + 1 file changed, 1 insertion(+) create mode 100644 rust-toolchain diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 00000000..07ade694 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file From ba998531d5982d0615bc6df3de1581bd67d27118 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 14 Aug 2023 22:38:30 +0200 Subject: [PATCH 014/113] remove async_json_rpc_client module --- src/asynch/clients/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index b5dbb46e..c30c2d4c 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,6 +1,4 @@ -mod async_json_rpc_client; mod async_websocket_client; mod exceptions; -pub use async_json_rpc_client::*; pub use async_websocket_client::*; From aa4edfd8a3fb97b4704555414e80c93050a906ab Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 15 Aug 2023 21:59:52 +0200 Subject: [PATCH 015/113] resolve unwraps --- src/asynch/clients/async_websocket_client.rs | 97 +++++++++++--------- src/asynch/clients/exceptions.rs | 5 +- tests/integration/clients/mod.rs | 21 ++++- 3 files changed, 75 insertions(+), 48 deletions(-) diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs index 4368dd9a..76d7ef1a 100644 --- a/src/asynch/clients/async_websocket_client.rs +++ b/src/asynch/clients/async_websocket_client.rs @@ -69,7 +69,7 @@ mod tungstenite { fn poll_ready( mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { match Pin::new(&mut self.inner).poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -77,15 +77,14 @@ mod tungstenite { } } - fn start_send( - mut self: core::pin::Pin<&mut Self>, - item: I, - ) -> core::result::Result<(), Self::Error> { - match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text( - serde_json::to_string(&item).unwrap(), - )) { - // TODO: unwrap - Ok(()) => Ok(()), + fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { + match serde_json::to_string(&item) { + Ok(json) => { + match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } Err(error) => Err!(error), } } @@ -93,7 +92,7 @@ mod tungstenite { fn poll_flush( mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { match Pin::new(&mut self.inner).poll_flush(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -104,7 +103,7 @@ mod tungstenite { fn poll_close( mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { + ) -> core::task::Poll> { match Pin::new(&mut self.inner).poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -145,12 +144,17 @@ mod tungstenite { WebsocketOpen, >, > { - let (websocket_stream, _) = tungstenite_connect_async(uri).await.unwrap(); // TODO: unwrap - - Ok(AsyncWebsocketClient { - inner: websocket_stream, - status: PhantomData::, - }) + match tungstenite_connect_async(uri).await { + Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + inner: websocket_stream, + status: PhantomData::, + }), + Err(error) => { + Err!(XRPLWebsocketException::UnableToConnect::( + error + )) + } + } } } } @@ -174,10 +178,11 @@ where { let websocket = EmbeddedWebsocket::::new_client(rng); let mut framer = EmbeddedWebsocketFramer::new(websocket); - framer - .connect(stream, buffer, websocket_options) - .await - .unwrap(); // TODO: unwrap + match framer.connect(stream, buffer, websocket_options).await { + Ok(Some(_)) => {} + Ok(None) => {} + Err(error) => return Err!(XRPLWebsocketException::from(error)), + } Ok(AsyncWebsocketClient { inner: framer, @@ -200,12 +205,13 @@ where where E: Debug, { - let len = self + match self .inner .encode::(message_type, end_of_message, from, to) - .unwrap(); // TODO: unwrap - - Ok(len) + { + Ok(bytes_written) => Ok(bytes_written), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } } pub async fn send<'b, E, R: serde::Serialize>( @@ -218,18 +224,23 @@ where where E: Debug, { - self.inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Binary, - end_of_message, - serde_json::to_vec(&frame_buf).unwrap().as_slice(), - ) // TODO: unwrap - .await - .unwrap(); // TODO: unwrap - - Ok(()) + match serde_json::to_vec(&frame_buf) { + Ok(frame_buf) => match self + .inner + .write( + stream, + stream_buf, + EmbeddedWebsocketSendMessageType::Text, + end_of_message, + frame_buf.as_slice(), + ) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + }, + Err(error) => Err!(error), + } } pub async fn close<'b, E>( @@ -242,12 +253,14 @@ where where E: Debug, { - self.inner + match self + .inner .close(stream, stream_buf, close_status, status_description) .await - .unwrap(); // TODO: unwrap - - Ok(()) + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } } pub async fn next<'a, B: Deref, E>( diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index 635a3e7a..c017bd5e 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -3,8 +3,11 @@ use core::str::Utf8Error; use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; -#[derive(Debug, PartialEq, Eq, Error)] +#[derive(Debug, Error)] pub enum XRPLWebsocketException { + #[cfg(feature = "tungstenite")] + #[error("Unable to connect to websocket")] + UnableToConnect(tokio_tungstenite::tungstenite::Error), // FramerError #[error("I/O error: {0:?}")] Io(E), diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 80321e4f..2d5cf594 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -65,17 +65,28 @@ async fn test_embedded_websocket_echo() { .unwrap() .unwrap(); match message { - EmbeddedWebsocketReadMessageType::Binary(msg) => { - assert_eq!(serde_json::to_vec(&account_info).unwrap().as_slice(), msg); - break; - } EmbeddedWebsocketReadMessageType::Ping(_) => { ping_counter += 1; if ping_counter > 1 { panic!("Expected only one ping"); } } - _ => panic!("Expected binary message"), + EmbeddedWebsocketReadMessageType::Text(text) => { + assert_eq!( + serde_json::from_str::(text).unwrap(), + account_info + ); + break; + } + EmbeddedWebsocketReadMessageType::Binary(_) => { + panic!("Expected text message found binary") + } + EmbeddedWebsocketReadMessageType::Pong(_) => { + panic!("Expected text message found pong") + } + EmbeddedWebsocketReadMessageType::Close(_) => { + panic!("Expected text message found close") + } } } } From f3dbcefb161922e45349e97b9d8999eaf834b163 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 18 Aug 2023 23:51:10 +0200 Subject: [PATCH 016/113] improve features --- src/asynch/clients/async_websocket_client.rs | 287 ++++++++++--------- src/asynch/clients/mod.rs | 6 +- tests/common/mod.rs | 15 +- tests/integration/clients/mod.rs | 23 +- 4 files changed, 177 insertions(+), 154 deletions(-) diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs index 76d7ef1a..0d58f01e 100644 --- a/src/asynch/clients/async_websocket_client.rs +++ b/src/asynch/clients/async_websocket_client.rs @@ -1,17 +1,12 @@ use super::exceptions::XRPLWebsocketException; use crate::Err; -use alloc::string::ToString; -use anyhow::Result; -use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use embedded_websocket::{ - framer_async::Framer as EmbeddedWebsocketFramer, Client as EmbeddedWebsocketClient, - WebSocket as EmbeddedWebsocket, -}; +use core::marker::PhantomData; use futures::{Sink, Stream}; -use rand_core::RngCore; // Exports +#[cfg(feature = "embedded-websocket")] +pub use embedded_websocket_impl::*; #[cfg(feature = "tungstenite")] -pub use tungstenite::*; +pub use tungstenite_impl::*; pub use embedded_websocket::{ framer_async::{ @@ -23,9 +18,6 @@ pub use embedded_websocket::{ WebSocketState as EmbeddedWebsocketState, }; -pub type AsyncWebsocketClientEmbeddedWebsocket = - AsyncWebsocketClient, Status>; - pub struct WebsocketOpen; pub struct WebsocketClosed; @@ -41,9 +33,9 @@ impl AsyncWebsocketClient { } #[cfg(feature = "tungstenite")] -mod tungstenite { +mod tungstenite_impl { use super::*; - use core::fmt::Display; + use anyhow::Result; use core::{pin::Pin, task::Poll}; use tokio::net::TcpStream; pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; @@ -58,10 +50,14 @@ mod tungstenite { Status, >; - impl Sink for AsyncWebsocketClient + impl Sink + for AsyncWebsocketClient< + TungsteniteWebsocketStream>, + WebsocketOpen, + > where - T: Sink + Unpin, - >::Error: Display, + // T: Sink + Unpin, + // >::Error: Display, I: serde::Serialize, { type Error = anyhow::Error; @@ -112,11 +108,16 @@ mod tungstenite { } } - impl Stream for AsyncWebsocketClient - where - T: Stream + Unpin, + impl Stream + for AsyncWebsocketClient< + TungsteniteWebsocketStream>, + WebsocketOpen, + > + // where + // T: Stream + Unpin, { - type Item = ::Item; + type Item = + > as Stream>::Item; fn poll_next( mut self: Pin<&mut Self>, @@ -159,139 +160,157 @@ mod tungstenite { } } -impl - AsyncWebsocketClient, WebsocketClosed> -where - Rng: RngCore, -{ - pub async fn open<'a, B, E>( - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - rng: Rng, - websocket_options: &EmbeddedWebsocketOptions<'_>, - ) -> Result< - AsyncWebsocketClient, WebsocketOpen>, - > - where - B: AsRef<[u8]>, - E: Debug, - { - let websocket = EmbeddedWebsocket::::new_client(rng); - let mut framer = EmbeddedWebsocketFramer::new(websocket); - match framer.connect(stream, buffer, websocket_options).await { - Ok(Some(_)) => {} - Ok(None) => {} - Err(error) => return Err!(XRPLWebsocketException::from(error)), - } +#[cfg(feature = "embedded-websocket")] +mod embedded_websocket_impl { + use super::*; + use anyhow::Result; + use core::{fmt::Debug, ops::Deref}; + use embedded_websocket::{ + framer_async::Framer as EmbeddedWebsocketFramer, Client as EmbeddedWebsocketClient, + WebSocket as EmbeddedWebsocket, + }; + use rand_core::RngCore; - Ok(AsyncWebsocketClient { - inner: framer, - status: PhantomData::, - }) - } -} + pub type AsyncWebsocketClientEmbeddedWebsocket = + AsyncWebsocketClient, Status>; -impl AsyncWebsocketClient, WebsocketOpen> -where - Rng: RngCore, -{ - pub fn encode( - &mut self, - message_type: EmbeddedWebsocketSendMessageType, - end_of_message: bool, - from: &[u8], - to: &mut [u8], - ) -> Result + impl + AsyncWebsocketClient, WebsocketClosed> where - E: Debug, + Rng: RngCore, { - match self - .inner - .encode::(message_type, end_of_message, from, to) + pub async fn open<'a, B, E>( + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + rng: Rng, + websocket_options: &EmbeddedWebsocketOptions<'_>, + ) -> Result< + AsyncWebsocketClient< + EmbeddedWebsocketFramer, + WebsocketOpen, + >, + > + where + B: AsRef<[u8]>, + E: Debug, { - Ok(bytes_written) => Ok(bytes_written), - Err(error) => Err!(XRPLWebsocketException::from(error)), + let websocket = EmbeddedWebsocket::::new_client(rng); + let mut framer = EmbeddedWebsocketFramer::new(websocket); + match framer.connect(stream, buffer, websocket_options).await { + Ok(Some(_)) => {} + Ok(None) => {} + Err(error) => return Err!(XRPLWebsocketException::from(error)), + } + + Ok(AsyncWebsocketClient { + inner: framer, + status: PhantomData::, + }) } } - pub async fn send<'b, E, R: serde::Serialize>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - end_of_message: bool, - frame_buf: R, - ) -> Result<()> + impl AsyncWebsocketClient, WebsocketOpen> where - E: Debug, + Rng: RngCore, { - match serde_json::to_vec(&frame_buf) { - Ok(frame_buf) => match self + pub fn encode( + &mut self, + message_type: EmbeddedWebsocketSendMessageType, + end_of_message: bool, + from: &[u8], + to: &mut [u8], + ) -> Result + where + E: Debug, + { + match self .inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Text, - end_of_message, - frame_buf.as_slice(), - ) - .await + .encode::(message_type, end_of_message, from, to) { - Ok(()) => Ok(()), + Ok(bytes_written) => Ok(bytes_written), Err(error) => Err!(XRPLWebsocketException::from(error)), - }, - Err(error) => Err!(error), + } } - } - pub async fn close<'b, E>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - close_status: EmbeddedWebsocketCloseStatusCode, - status_description: Option<&str>, - ) -> Result<()> - where - E: Debug, - { - match self - .inner - .close(stream, stream_buf, close_status, status_description) - .await + pub async fn send<'b, E, R: serde::Serialize>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + end_of_message: bool, + frame_buf: R, + ) -> Result<()> + where + E: Debug, { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), + match serde_json::to_vec(&frame_buf) { + Ok(frame_buf) => match self + .inner + .write( + stream, + stream_buf, + EmbeddedWebsocketSendMessageType::Text, + end_of_message, + frame_buf.as_slice(), + ) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + }, + Err(error) => Err!(error), + } } - } - pub async fn next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Option>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Some(Ok(read_result)), - Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), - None => None, + pub async fn close<'b, E>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + close_status: EmbeddedWebsocketCloseStatusCode, + status_description: Option<&str>, + ) -> Result<()> + where + E: Debug, + { + match self + .inner + .close(stream, stream_buf, close_status, status_description) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } } - } - pub async fn try_next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Result>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Ok(Some(read_result)), - Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), - None => Ok(None), + pub async fn next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Option>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Some(Ok(read_result)), + Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), + None => None, + } + } + + pub async fn try_next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Result>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Ok(Some(read_result)), + Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), + None => Ok(None), + } } } } diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index c30c2d4c..f4f3a0a0 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,4 +1,2 @@ -mod async_websocket_client; -mod exceptions; - -pub use async_websocket_client::*; +pub mod async_websocket_client; +pub mod exceptions; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 0312307c..1bf4146c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,12 +1,16 @@ pub mod codec; + +use xrpl::asynch::clients::async_websocket_client::WebsocketOpen; #[cfg(feature = "tungstenite")] -use xrpl::asynch::clients::AsyncWebsocketClientTungstenite; -use xrpl::asynch::clients::{ - AsyncWebsocketClientEmbeddedWebsocket, EmbeddedWebsocketOptions, WebsocketOpen, +use xrpl::asynch::clients::async_websocket_client::AsyncWebsocketClientTungstenite; +#[cfg(feature = "embedded-websocket")] +use xrpl::asynch::clients::async_websocket_client::{ + AsyncWebsocketClientEmbeddedWebsocket, EmbeddedWebsocketOptions, }; - -use tokio::net::TcpStream; +#[cfg(feature = "embedded-websocket")] use tokio_util::codec::Framed; +#[cfg(feature = "embedded-websocket")] +use tokio::net::TcpStream; mod constants; pub use constants::*; @@ -21,6 +25,7 @@ pub async fn connect_to_wss_tungstinite_echo() -> AsyncWebsocketClientTungstenit websocket } +#[cfg(feature = "embedded-websocket")] pub async fn connect_to_ws_embedded_websocket_tokio_echo( stream: &mut Framed, buffer: &mut [u8], diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 2d5cf594..4e0d9d55 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,18 +1,13 @@ -#[cfg(feature = "tungstenite")] -use super::common::connect_to_wss_tungstinite_echo; -use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; -#[cfg(feature = "tungstenite")] -use futures::{SinkExt, TryStreamExt}; -use tokio_util::codec::Framed; -use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; - -#[cfg(feature = "tungstenite")] -use xrpl::asynch::clients::TungsteniteMessage; use xrpl::models::requests::AccountInfo; -#[cfg(feature = "tungstenite")] #[tokio::test] +#[cfg(feature = "tungstenite")] async fn test_websocket_tungstenite_echo() { + use super::*; + use super::super::common::connect_to_wss_tungstinite_echo; + use xrpl::asynch::clients::async_websocket_client::TungsteniteMessage; + use futures::{SinkExt, TryStreamExt}; + let mut websocket = connect_to_wss_tungstinite_echo().await; let account_info = AccountInfo::new( "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", @@ -36,7 +31,13 @@ async fn test_websocket_tungstenite_echo() { } #[tokio::test] +#[cfg(feature = "embedded-websocket")] async fn test_embedded_websocket_echo() { + use super::*; + use super::super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; + use xrpl::asynch::clients::async_websocket_client::EmbeddedWebsocketReadMessageType; + use tokio_util::codec::Framed; + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); From 6c36a645952bf2091238667e0a1235f6761c7380 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 18 Aug 2023 23:51:29 +0200 Subject: [PATCH 017/113] improve features --- Cargo.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab5119e9..8f473c2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,13 +71,13 @@ git = "https://github.com/ninjasource/embedded-websocket" version = "0.9.2" rev = "8d87d46f46fa0c75e099ca8aad37e8d00c8854f8" default-features = false +optional = true [dev-dependencies] criterion = "0.5.1" cargo-husky = { version = "1.5.0", default-features = false, features = [ "user-hooks", ] } -tokio = { version = "1.28.2", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } bytes = { version = "1.4.0", default-features = false } @@ -86,15 +86,16 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "net", "tungstenite"] +default = ["std", "core", "models", "utils", "net", "tungstenite", "embedded-websocket"] models = ["core", "transactions", "requests", "ledger"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -net = ["url", "futures"] -tungstenite = ["tokio/net", "tokio-tungstenite/native-tls"] +net = ["dep:url", "dep:futures"] +tungstenite = ["tokio/full", "tokio-tungstenite/native-tls"] +embedded-websocket = ["dep:embedded-websocket"] core = ["utils"] utils = [] std = [ From 625708be0445e0763ec56a08feb3d82ad979aa56 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 18 Aug 2023 23:52:03 +0200 Subject: [PATCH 018/113] remove unneeded ToString --- src/_anyhow/mod.rs | 2 ++ src/models/requests/channel_authorize.rs | 2 -- src/models/requests/ledger_entry.rs | 2 -- src/models/transactions/account_set.rs | 2 -- src/models/transactions/check_cash.rs | 2 -- src/models/transactions/deposit_preauth.rs | 2 -- src/models/transactions/escrow_create.rs | 2 -- src/models/transactions/escrow_finish.rs | 2 -- src/models/transactions/nftoken_accept_offer.rs | 2 -- src/models/transactions/nftoken_cancel_offer.rs | 2 -- src/models/transactions/nftoken_create_offer.rs | 2 -- src/models/transactions/nftoken_mint.rs | 2 -- src/models/transactions/payment.rs | 1 - src/models/transactions/signer_list_set.rs | 2 -- 14 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/_anyhow/mod.rs b/src/_anyhow/mod.rs index 345a3a8b..7b4ab3ec 100644 --- a/src/_anyhow/mod.rs +++ b/src/_anyhow/mod.rs @@ -2,6 +2,8 @@ #[macro_export] macro_rules! Err { ($err:expr $(,)?) => {{ + use alloc::string::ToString; + let error = $err.to_string().replace("\"", ""); let boxed_error = ::alloc::boxed::Box::new(error); let leaked_error: &'static str = ::alloc::boxed::Box::leak(boxed_error); diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index bd21f346..8024c20a 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -3,8 +3,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::requests::XRPLChannelAuthorizeException; use crate::{ constants::CryptoAlgorithm, diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index a7e851bb..78856383 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -3,8 +3,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::requests::XRPLLedgerEntryException; use crate::models::{requests::RequestMethod, Model}; diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 43f40478..3c9fad90 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -5,8 +5,6 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; -use alloc::string::ToString; - use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLAccountSetException; use crate::{ diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 53816f65..c51951fc 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -4,8 +4,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLCheckCashException; use crate::models::{ diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 5e32517b..0376f24c 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -4,8 +4,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLDepositPreauthException; use crate::models::{ diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index d0eacaba..32905e47 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -4,8 +4,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLEscrowCreateException; use crate::models::{ diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index e049edcd..2ba50e6e 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -4,8 +4,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::transactions::XRPLEscrowFinishException; use crate::models::{ amount::XRPAmount, diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index dcfcc532..68f3e5ff 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -6,8 +6,6 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenAcceptOfferException; diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 3d18160d..205a5312 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -4,8 +4,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenCancelOfferException; use crate::models::{ diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 44cc792f..b2b53c5e 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -7,8 +7,6 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; -use alloc::string::ToString; - use crate::models::{ model::Model, transactions::{Flag, Memo, Signer, Transaction, TransactionType}, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index dbe29528..b6fbe7b1 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -5,8 +5,6 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; -use alloc::string::ToString; - use crate::{ constants::{MAX_TRANSFER_FEE, MAX_URI_LENGTH}, models::{ diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 6cd3ef0d..3358fc46 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -11,7 +11,6 @@ use crate::models::{ transactions::{Flag, Memo, Signer, Transaction, TransactionType}, PathStep, }; -use alloc::string::ToString; use crate::Err; use crate::_serde::txn_flags; diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 75519cad..961269b9 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -5,8 +5,6 @@ use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_with::skip_serializing_none; -use alloc::string::ToString; - use crate::models::transactions::XRPLSignerListSetException; use crate::models::{ amount::XRPAmount, From 2fdfc8d260d291ed7bf83e5a222e7508211de637 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 19 Aug 2023 00:07:24 +0200 Subject: [PATCH 019/113] resolve git workflow error --- src/asynch/clients/async_websocket_client.rs | 24 ++++++++------------ tests/integration/clients/mod.rs | 5 ++-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs index 0d58f01e..d292a141 100644 --- a/src/asynch/clients/async_websocket_client.rs +++ b/src/asynch/clients/async_websocket_client.rs @@ -8,16 +8,6 @@ pub use embedded_websocket_impl::*; #[cfg(feature = "tungstenite")] pub use tungstenite_impl::*; -pub use embedded_websocket::{ - framer_async::{ - FramerError as EmbeddedWebsocketFramerError, ReadResult as EmbeddedWebsocketReadMessageType, - }, - Error as EmbeddedWebsocketError, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, - WebSocketOptions as EmbeddedWebsocketOptions, - WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, - WebSocketState as EmbeddedWebsocketState, -}; - pub struct WebsocketOpen; pub struct WebsocketClosed; @@ -34,7 +24,7 @@ impl AsyncWebsocketClient { #[cfg(feature = "tungstenite")] mod tungstenite_impl { - use super::*; + use super::{WebsocketOpen, WebsocketClosed, AsyncWebsocketClient, Sink, Stream, PhantomData, Err, XRPLWebsocketException}; use anyhow::Result; use core::{pin::Pin, task::Poll}; use tokio::net::TcpStream; @@ -162,12 +152,18 @@ mod tungstenite_impl { #[cfg(feature = "embedded-websocket")] mod embedded_websocket_impl { - use super::*; + use super::{WebsocketOpen, WebsocketClosed, AsyncWebsocketClient, Sink, Stream, PhantomData, Err, XRPLWebsocketException}; use anyhow::Result; use core::{fmt::Debug, ops::Deref}; - use embedded_websocket::{ - framer_async::Framer as EmbeddedWebsocketFramer, Client as EmbeddedWebsocketClient, + pub use embedded_websocket::{ + framer_async::{ + FramerError as EmbeddedWebsocketFramerError, ReadResult as EmbeddedWebsocketReadMessageType, Framer as EmbeddedWebsocketFramer, + }, Client as EmbeddedWebsocketClient, WebSocket as EmbeddedWebsocket, + Error as EmbeddedWebsocketError, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, + WebSocketOptions as EmbeddedWebsocketOptions, + WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, + WebSocketState as EmbeddedWebsocketState, }; use rand_core::RngCore; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 4e0d9d55..7f3dc89f 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,10 +1,9 @@ -use xrpl::models::requests::AccountInfo; #[tokio::test] #[cfg(feature = "tungstenite")] async fn test_websocket_tungstenite_echo() { - use super::*; use super::super::common::connect_to_wss_tungstinite_echo; + use xrpl::models::requests::AccountInfo; use xrpl::asynch::clients::async_websocket_client::TungsteniteMessage; use futures::{SinkExt, TryStreamExt}; @@ -33,8 +32,8 @@ async fn test_websocket_tungstenite_echo() { #[tokio::test] #[cfg(feature = "embedded-websocket")] async fn test_embedded_websocket_echo() { - use super::*; use super::super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; + use xrpl::models::requests::AccountInfo; use xrpl::asynch::clients::async_websocket_client::EmbeddedWebsocketReadMessageType; use tokio_util::codec::Framed; From a8b4031acaf35010445832e45b270800defa26c3 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 19 Aug 2023 00:11:09 +0200 Subject: [PATCH 020/113] resolve git workflow error --- src/asynch/clients/exceptions.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index c017bd5e..b6aef7f9 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -1,5 +1,6 @@ use core::fmt::Debug; use core::str::Utf8Error; +#[cfg(feature = "embedded-websocket")] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; @@ -17,6 +18,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, + #[cfg(feature = "embedded-websocket")] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] @@ -25,6 +27,7 @@ pub enum XRPLWebsocketException { RxBufferTooSmall(usize), } +#[cfg(feature = "embedded-websocket")] impl From> for XRPLWebsocketException { fn from(value: FramerError) -> Self { match value { From 9dea91be59c82ce2bca4bbae86a1d5f4fa9eec9f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 23 Aug 2023 14:54:59 +0200 Subject: [PATCH 021/113] handle unwraps --- .../std/src/bin/tokio/net/send_request.rs | 1 - rust-toolchain | 2 +- tests/common/mod.rs | 48 +++++---- tests/integration/clients/mod.rs | 101 ++++++++++-------- 4 files changed, 85 insertions(+), 67 deletions(-) diff --git a/examples/std/src/bin/tokio/net/send_request.rs b/examples/std/src/bin/tokio/net/send_request.rs index 4270b701..21bacd00 100644 --- a/examples/std/src/bin/tokio/net/send_request.rs +++ b/examples/std/src/bin/tokio/net/send_request.rs @@ -1,7 +1,6 @@ use xrpl::asynch::client::{AsyncWebsocketClientTungstenite, TungsteniteMessage}; use xrpl::models::requests::AccountInfo; -// TODO: add as soon as `AsyncWebsocketClient` is implemented #[tokio::main] async fn main() { let websocket = diff --git a/rust-toolchain b/rust-toolchain index 07ade694..bf867e0a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly \ No newline at end of file +nightly diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1bf4146c..64ba2c26 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,35 +1,43 @@ pub mod codec; -use xrpl::asynch::clients::async_websocket_client::WebsocketOpen; +use anyhow::anyhow; +use anyhow::Result; + +#[cfg(feature = "embedded-websocket")] +use tokio::net::TcpStream; +#[cfg(feature = "embedded-websocket")] +use tokio_util::codec::Framed; #[cfg(feature = "tungstenite")] use xrpl::asynch::clients::async_websocket_client::AsyncWebsocketClientTungstenite; +use xrpl::asynch::clients::async_websocket_client::WebsocketOpen; #[cfg(feature = "embedded-websocket")] use xrpl::asynch::clients::async_websocket_client::{ AsyncWebsocketClientEmbeddedWebsocket, EmbeddedWebsocketOptions, }; -#[cfg(feature = "embedded-websocket")] -use tokio_util::codec::Framed; -#[cfg(feature = "embedded-websocket")] -use tokio::net::TcpStream; mod constants; pub use constants::*; #[cfg(feature = "tungstenite")] -pub async fn connect_to_wss_tungstinite_echo() -> AsyncWebsocketClientTungstenite { - let websocket = AsyncWebsocketClientTungstenite::open(ECHO_WSS_SERVER.parse().unwrap()) - .await - .unwrap(); - assert!(websocket.is_open()); - - websocket +pub async fn connect_to_wss_tungstinite_echo( +) -> Result> { + match ECHO_WSS_SERVER.parse() { + Ok(url) => match AsyncWebsocketClientTungstenite::open(url).await { + Ok(websocket) => { + assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + }, + Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), + } } #[cfg(feature = "embedded-websocket")] pub async fn connect_to_ws_embedded_websocket_tokio_echo( stream: &mut Framed, buffer: &mut [u8], -) -> AsyncWebsocketClientEmbeddedWebsocket { +) -> Result> { let rng = rand::thread_rng(); let websocket_options = EmbeddedWebsocketOptions { path: "/mirror", @@ -39,10 +47,12 @@ pub async fn connect_to_ws_embedded_websocket_tokio_echo( additional_headers: None, }; - let websocket = - AsyncWebsocketClientEmbeddedWebsocket::open(stream, buffer, rng, &websocket_options) - .await - .unwrap(); - - websocket + match AsyncWebsocketClientEmbeddedWebsocket::open(stream, buffer, rng, &websocket_options).await + { + Ok(websocket) => { + assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + } } diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 7f3dc89f..7fb0d06e 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,13 +1,15 @@ +use anyhow::anyhow; +use anyhow::Result; #[tokio::test] #[cfg(feature = "tungstenite")] -async fn test_websocket_tungstenite_echo() { - use super::super::common::connect_to_wss_tungstinite_echo; - use xrpl::models::requests::AccountInfo; - use xrpl::asynch::clients::async_websocket_client::TungsteniteMessage; +async fn test_websocket_tungstenite_echo() -> Result<()> { + use super::common::connect_to_wss_tungstinite_echo; use futures::{SinkExt, TryStreamExt}; + use xrpl::asynch::clients::async_websocket_client::TungsteniteMessage; + use xrpl::models::requests::AccountInfo; - let mut websocket = connect_to_wss_tungstinite_echo().await; + let mut websocket = connect_to_wss_tungstinite_echo().await?; let account_info = AccountInfo::new( "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", None, @@ -18,31 +20,37 @@ async fn test_websocket_tungstenite_echo() { None, ); - websocket.send(&account_info).await.unwrap(); - + websocket.send(&account_info).await?; while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - let account_info_echo: AccountInfo = serde_json::from_str(response.as_str()).unwrap(); - println!("account_info_echo: {:?}", account_info_echo); - assert_eq!(account_info, account_info_echo); - - break; + match serde_json::from_str::(response.as_str()) { + Ok(account_info_echo) => { + assert_eq!(account_info, account_info_echo); + return Ok(()); + } + Err(err) => { + return Err(anyhow!("Error parsing response: {:?}", err)); + } + }; } + + Ok(()) } #[tokio::test] #[cfg(feature = "embedded-websocket")] -async fn test_embedded_websocket_echo() { - use super::super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; - use xrpl::models::requests::AccountInfo; - use xrpl::asynch::clients::async_websocket_client::EmbeddedWebsocketReadMessageType; +async fn test_embedded_websocket_echo() -> Result<()> { + use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; use tokio_util::codec::Framed; + use xrpl::asynch::clients::async_websocket_client::EmbeddedWebsocketReadMessageType; + use xrpl::models::requests::AccountInfo; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); let mut framed = Framed::new(tcp_stream, Codec::new()); let mut buffer = [0u8; 4096]; - let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await; + let mut websocket = + connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; let account_info = AccountInfo::new( "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", None, @@ -54,39 +62,40 @@ async fn test_embedded_websocket_echo() { ); websocket .send(&mut framed, &mut buffer, false, &account_info) - .await - .unwrap(); + .await?; let mut ping_counter = 0; loop { - let message = websocket - .try_next(&mut framed, &mut buffer) - .await - .unwrap() - .unwrap(); - match message { - EmbeddedWebsocketReadMessageType::Ping(_) => { - ping_counter += 1; - if ping_counter > 1 { - panic!("Expected only one ping"); + match websocket.try_next(&mut framed, &mut buffer).await? { + Some(message) => match message { + EmbeddedWebsocketReadMessageType::Ping(_) => { + ping_counter += 1; + if ping_counter > 1 { + panic!("Expected only one ping"); + } } - } - EmbeddedWebsocketReadMessageType::Text(text) => { - assert_eq!( - serde_json::from_str::(text).unwrap(), - account_info - ); - break; - } - EmbeddedWebsocketReadMessageType::Binary(_) => { - panic!("Expected text message found binary") - } - EmbeddedWebsocketReadMessageType::Pong(_) => { - panic!("Expected text message found pong") - } - EmbeddedWebsocketReadMessageType::Close(_) => { - panic!("Expected text message found close") - } + EmbeddedWebsocketReadMessageType::Text(text) => { + match serde_json::from_str::(text) { + Ok(account_info_echo) => { + assert_eq!(account_info, account_info_echo); + return Ok(()); + } + Err(err) => { + return Err(anyhow!("Error parsing response: {:?}", err)); + } + } + } + EmbeddedWebsocketReadMessageType::Binary(_) => { + panic!("Expected text message found binary") + } + EmbeddedWebsocketReadMessageType::Pong(_) => { + panic!("Expected text message found pong") + } + EmbeddedWebsocketReadMessageType::Close(_) => { + panic!("Expected text message found close") + } + }, + None => return Err(anyhow!("No message received")), } } } From edc560233d70e97d8ca014bb919cbe4429305e46 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 26 Aug 2023 11:52:10 +0200 Subject: [PATCH 022/113] fix examples --- examples/std/Cargo.toml | 12 +++++++++--- .../tokio/net/{send_request.rs => tungstenite.rs} | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) rename examples/std/src/bin/tokio/net/{send_request.rs => tungstenite.rs} (84%) diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 1e4a813c..9d84bc4c 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" [dependencies] xrpl-rust = { path = "../.." } +tokio = { version = "1.12.0", features = ["full"], optional = true } +futures = { version = "0.3.19", optional = true } +serde_json = { version = "1.0.70", optional = true } [[bin]] name = "wallet_from_seed" @@ -21,13 +24,16 @@ required-features = [ ] [[bin]] -name = "send_request" -path = "src/bin/tokio/net/send_request.rs" +name = "tungstenite" +path = "src/bin/tokio/net/tungstenite.rs" required-features = [ + "tokio", + "futures", + "serde_json", ] [[bin]] -name = "send_request" +name = "sign_request" path = "src/bin/transaction/sign_transaction.rs" required-features = [ ] diff --git a/examples/std/src/bin/tokio/net/send_request.rs b/examples/std/src/bin/tokio/net/tungstenite.rs similarity index 84% rename from examples/std/src/bin/tokio/net/send_request.rs rename to examples/std/src/bin/tokio/net/tungstenite.rs index 21bacd00..fd64fb8d 100644 --- a/examples/std/src/bin/tokio/net/send_request.rs +++ b/examples/std/src/bin/tokio/net/tungstenite.rs @@ -1,6 +1,10 @@ -use xrpl::asynch::client::{AsyncWebsocketClientTungstenite, TungsteniteMessage}; +use xrpl::asynch::clients::async_websocket_client::{ + AsyncWebsocketClientTungstenite, TungsteniteMessage, +}; use xrpl::models::requests::AccountInfo; +use futures::{SinkExt, TryStreamExt}; + #[tokio::main] async fn main() { let websocket = From acc641a6ef09d073a04305ef6d775aaa45b76776 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 26 Aug 2023 11:52:28 +0200 Subject: [PATCH 023/113] fix deprecation warnings --- src/utils/time_conversion.rs | 77 +++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/src/utils/time_conversion.rs b/src/utils/time_conversion.rs index f20d403d..62e92e04 100644 --- a/src/utils/time_conversion.rs +++ b/src/utils/time_conversion.rs @@ -105,6 +105,8 @@ pub fn posix_to_ripple_time(timestamp: i64) -> Result datetime_to_ripple_time(dt), + _ => Err(XRPLTimeRangeException::InvalidLocalTime), + }; + assert_eq!(Ok(0_i64), actual); } #[test] @@ -144,34 +147,52 @@ mod test { } #[test] - fn accept_datetime_round_trip() { - let current_time: DateTime = Utc.timestamp(Utc::now().timestamp(), 0); + fn accept_datetime_round_trip() -> Result<()> { + let current_time: DateTime = match Utc.timestamp_opt(Utc::now().timestamp(), 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; let ripple_time: i64 = datetime_to_ripple_time(current_time).unwrap(); let round_trip_time = ripple_time_to_datetime(ripple_time); assert_eq!(Ok(current_time), round_trip_time); + + Ok(()) } #[test] - fn accept_ripple_epoch() { - assert_eq!( - Ok(Utc.ymd(2000, 1, 1).and_hms(0, 0, 0)), - ripple_time_to_datetime(0) - ); + fn accept_ripple_epoch() -> Result<()> { + let expected = match Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; + assert_eq!(Ok(expected), ripple_time_to_datetime(0)); + + Ok(()) } /// "Ripple Epoch" time starts in the year 2000 #[test] - fn accept_datetime_underflow() { - let datetime: DateTime = Utc.ymd(1999, 1, 1).and_hms(0, 0, 0); - assert!(datetime_to_ripple_time(datetime).is_err()) + fn accept_datetime_underflow() -> Result<()> { + let datetime: DateTime = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; + assert!(datetime_to_ripple_time(datetime).is_err()); + + Ok(()) } /// "Ripple Epoch" time starts in the year 2000 #[test] - fn accept_posix_underflow() { - let datetime: DateTime = Utc.ymd(1999, 1, 1).and_hms(0, 0, 0); - assert!(posix_to_ripple_time(datetime.timestamp()).is_err()) + fn accept_posix_underflow() -> Result<()> { + let datetime: DateTime = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; + assert!(posix_to_ripple_time(datetime.timestamp()).is_err()); + + Ok(()) } /// "Ripple Epoch" time's equivalent to the @@ -180,14 +201,24 @@ mod test { /// starting 30 years after UNIX time's signed /// 32-bit int. #[test] - fn accept_datetime_overflow() { - let datetime: DateTime = Utc.ymd(2137, 1, 1).and_hms(0, 0, 0); - assert!(datetime_to_ripple_time(datetime).is_err()) + fn accept_datetime_overflow() -> Result<()> { + let datetime: DateTime = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; + assert!(datetime_to_ripple_time(datetime).is_err()); + + Ok(()) } #[test] - fn accept_posix_overflow() { - let datetime: DateTime = Utc.ymd(2137, 1, 1).and_hms(0, 0, 0); - assert!(posix_to_ripple_time(datetime.timestamp()).is_err()) + fn accept_posix_overflow() -> Result<()> { + let datetime: DateTime = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) { + LocalResult::Single(dt) => dt, + _ => return Err(anyhow!("Invalid local time")), + }; + assert!(posix_to_ripple_time(datetime.timestamp()).is_err()); + + Ok(()) } } From 0ae81eafa57f2e73f6ab72d2e1a54aeaefede44c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 26 Aug 2023 11:53:15 +0200 Subject: [PATCH 024/113] handle test unwraps --- tests/integration/clients/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 7fb0d06e..72dfe997 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -46,7 +46,7 @@ async fn test_embedded_websocket_echo() -> Result<()> { let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await - .unwrap(); + .map_err(|_| anyhow!("Error connecting to websocket"))?; let mut framed = Framed::new(tcp_stream, Codec::new()); let mut buffer = [0u8; 4096]; let mut websocket = @@ -71,7 +71,7 @@ async fn test_embedded_websocket_echo() -> Result<()> { EmbeddedWebsocketReadMessageType::Ping(_) => { ping_counter += 1; if ping_counter > 1 { - panic!("Expected only one ping"); + return Err(anyhow!("Expected only one ping")); } } EmbeddedWebsocketReadMessageType::Text(text) => { From 7a3293e9e9ae360d83c8c464c253f40f238fbadf Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 26 Aug 2023 11:53:52 +0200 Subject: [PATCH 025/113] add embedded-websocket documentation --- src/asynch/clients/async_websocket_client.rs | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs index d292a141..f057c494 100644 --- a/src/asynch/clients/async_websocket_client.rs +++ b/src/asynch/clients/async_websocket_client.rs @@ -24,7 +24,10 @@ impl AsyncWebsocketClient { #[cfg(feature = "tungstenite")] mod tungstenite_impl { - use super::{WebsocketOpen, WebsocketClosed, AsyncWebsocketClient, Sink, Stream, PhantomData, Err, XRPLWebsocketException}; + use super::{ + AsyncWebsocketClient, Err, PhantomData, Sink, Stream, WebsocketClosed, WebsocketOpen, + XRPLWebsocketException, + }; use anyhow::Result; use core::{pin::Pin, task::Poll}; use tokio::net::TcpStream; @@ -46,8 +49,6 @@ mod tungstenite_impl { WebsocketOpen, > where - // T: Sink + Unpin, - // >::Error: Display, I: serde::Serialize, { type Error = anyhow::Error; @@ -103,8 +104,6 @@ mod tungstenite_impl { TungsteniteWebsocketStream>, WebsocketOpen, > - // where - // T: Stream + Unpin, { type Item = > as Stream>::Item; @@ -152,15 +151,20 @@ mod tungstenite_impl { #[cfg(feature = "embedded-websocket")] mod embedded_websocket_impl { - use super::{WebsocketOpen, WebsocketClosed, AsyncWebsocketClient, Sink, Stream, PhantomData, Err, XRPLWebsocketException}; + use super::{ + AsyncWebsocketClient, Err, PhantomData, Sink, Stream, WebsocketClosed, WebsocketOpen, + XRPLWebsocketException, + }; use anyhow::Result; use core::{fmt::Debug, ops::Deref}; pub use embedded_websocket::{ framer_async::{ - FramerError as EmbeddedWebsocketFramerError, ReadResult as EmbeddedWebsocketReadMessageType, Framer as EmbeddedWebsocketFramer, - }, Client as EmbeddedWebsocketClient, + Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, + ReadResult as EmbeddedWebsocketReadMessageType, + }, + Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, WebSocket as EmbeddedWebsocket, - Error as EmbeddedWebsocketError, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, + WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, WebSocketOptions as EmbeddedWebsocketOptions, WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, WebSocketState as EmbeddedWebsocketState, @@ -175,6 +179,7 @@ mod embedded_websocket_impl { where Rng: RngCore, { + /// Open a websocket connection. pub async fn open<'a, B, E>( stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), buffer: &'a mut [u8], @@ -209,6 +214,7 @@ mod embedded_websocket_impl { where Rng: RngCore, { + /// Encode a message to be sent over the websocket. pub fn encode( &mut self, message_type: EmbeddedWebsocketSendMessageType, @@ -228,6 +234,7 @@ mod embedded_websocket_impl { } } + /// Send a message over the websocket. pub async fn send<'b, E, R: serde::Serialize>( &mut self, stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), @@ -257,6 +264,7 @@ mod embedded_websocket_impl { } } + /// Close the websocket connection. pub async fn close<'b, E>( &mut self, stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), @@ -277,6 +285,7 @@ mod embedded_websocket_impl { } } + /// Read a message from the websocket. pub async fn next<'a, B: Deref, E>( &'a mut self, stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), @@ -293,6 +302,9 @@ mod embedded_websocket_impl { } } + /// Read a message from the websocket. + /// + /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. pub async fn try_next<'a, B: Deref, E>( &'a mut self, stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), From fe9ab929f74b6467a2d2f8c08f185162feb8e1c2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 18 Sep 2023 17:02:45 +0200 Subject: [PATCH 026/113] use String instead of Cow<'a, str> for structs using serde_with_tag macro due to lifetime issues when deserializing --- src/models/ledger/objects/amendments.rs | 16 ++-- src/models/ledger/objects/amm.rs | 23 ++--- src/models/ledger/objects/negative_unl.rs | 14 +-- src/models/ledger/objects/signer_list.rs | 20 ++-- src/models/transactions/account_delete.rs | 4 +- src/models/transactions/account_set.rs | 4 +- src/models/transactions/check_cancel.rs | 4 +- src/models/transactions/check_cash.rs | 4 +- src/models/transactions/check_create.rs | 4 +- src/models/transactions/deposit_preauth.rs | 4 +- src/models/transactions/escrow_cancel.rs | 4 +- src/models/transactions/escrow_create.rs | 4 +- src/models/transactions/escrow_finish.rs | 4 +- src/models/transactions/exceptions.rs | 33 +++---- src/models/transactions/mod.rs | 9 +- .../transactions/nftoken_accept_offer.rs | 9 +- src/models/transactions/nftoken_burn.rs | 4 +- .../transactions/nftoken_cancel_offer.rs | 4 +- .../transactions/nftoken_create_offer.rs | 4 +- src/models/transactions/nftoken_mint.rs | 9 +- src/models/transactions/offer_cancel.rs | 4 +- src/models/transactions/offer_create.rs | 4 +- src/models/transactions/payment.rs | 4 +- .../transactions/payment_channel_claim.rs | 4 +- .../transactions/payment_channel_create.rs | 4 +- .../transactions/payment_channel_fund.rs | 4 +- src/models/transactions/set_regular_key.rs | 4 +- src/models/transactions/signer_list_set.rs | 92 +++++++++---------- src/models/transactions/ticket_create.rs | 4 +- src/models/transactions/trust_set.rs | 4 +- 30 files changed, 156 insertions(+), 153 deletions(-) diff --git a/src/models/ledger/objects/amendments.rs b/src/models/ledger/objects/amendments.rs index c2f03c5d..daa06395 100644 --- a/src/models/ledger/objects/amendments.rs +++ b/src/models/ledger/objects/amendments.rs @@ -1,6 +1,6 @@ use crate::models::ledger::LedgerEntryType; use crate::models::Model; -use alloc::borrow::Cow; +use alloc::{borrow::Cow, string::String}; use alloc::vec::Vec; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; @@ -11,9 +11,9 @@ use serde_with::skip_serializing_none; serde_with_tag! { /// `` #[derive(Debug, PartialEq, Eq, Clone, new, Default)] - pub struct Majority<'a> { + pub struct Majority { /// The Amendment ID of the pending amendment. - pub amendment: Cow<'a, str>, + pub amendment: String, /// The `close_time` field of the ledger version where this amendment most recently gained a /// majority. pub close_time: u32, @@ -43,8 +43,7 @@ pub struct Amendments<'a> { pub amendments: Option>>, /// Array of objects describing the status of amendments that have majority support but are not /// yet enabled. If omitted, there are no pending amendments with majority support. - #[serde(borrow = "'a")] - pub majorities: Option>>, + pub majorities: Option>, } impl<'a> Model for Amendments<'a> {} @@ -65,7 +64,7 @@ impl<'a> Amendments<'a> { pub fn new( index: Cow<'a, str>, amendments: Option>>, - majorities: Option>>, + majorities: Option>, ) -> Self { Self { ledger_entry_type: LedgerEntryType::Amendments, @@ -80,6 +79,7 @@ impl<'a> Amendments<'a> { #[cfg(test)] mod test_serde { use crate::models::ledger::{Amendments, Majority}; + use alloc::string::ToString; use alloc::borrow::Cow; use alloc::vec; @@ -94,9 +94,7 @@ mod test_serde { Cow::from("740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11"), ]), Some(vec![Majority { - amendment: Cow::from( - "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146", - ), + amendment: "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146".to_string(), close_time: 535589001, }]), ); diff --git a/src/models/ledger/objects/amm.rs b/src/models/ledger/objects/amm.rs index 15b6bf14..5ece326e 100644 --- a/src/models/ledger/objects/amm.rs +++ b/src/models/ledger/objects/amm.rs @@ -1,6 +1,7 @@ use crate::models::ledger::LedgerEntryType; use crate::models::{amount::Amount, Currency, Model}; use alloc::borrow::Cow; +use alloc::string::String; use alloc::vec::Vec; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; @@ -10,8 +11,8 @@ use serde_with::skip_serializing_none; serde_with_tag! { #[derive(Debug, PartialEq, Eq, Clone, new, Default)] - pub struct AuthAccount<'a> { - pub account: Cow<'a, str>, + pub struct AuthAccount { + pub account: String, } } @@ -31,14 +32,13 @@ pub struct AuctionSlot<'a> { pub price: Amount<'a>, /// A list of at most 4 additional accounts that are authorized to trade at the discounted fee /// for this AMM instance. - #[serde(borrow = "'a")] - pub auth_accounts: Option>>, + pub auth_accounts: Option>, } serde_with_tag! { #[derive(Debug, PartialEq, Eq, Clone, new, Default)] - pub struct VoteEntry<'a> { - pub account: Cow<'a, str>, + pub struct VoteEntry { + pub account: String, pub trading_fee: u16, pub vote_weight: u32, } @@ -81,7 +81,7 @@ pub struct AMM<'a> { #[serde(borrow = "'a")] pub auction_slot: Option>, /// A list of vote objects, representing votes on the pool's trading fee. - pub vote_slots: Option>>, + pub vote_slots: Option>, } impl<'a> Default for AMM<'a> { @@ -112,7 +112,7 @@ impl<'a> AMM<'a> { lptoken_balance: Amount<'a>, trading_fee: u16, auction_slot: Option>, - vote_slots: Option>>, + vote_slots: Option>, ) -> Self { Self { ledger_entry_type: LedgerEntryType::AMM, @@ -134,6 +134,7 @@ mod test_serde { use crate::models::amount::{Amount, IssuedCurrencyAmount}; use crate::models::currency::{Currency, IssuedCurrency, XRP}; use crate::models::ledger::amm::{AuctionSlot, AuthAccount, VoteEntry, AMM}; + use alloc::string::ToString; use alloc::borrow::Cow; use alloc::vec; @@ -163,12 +164,12 @@ mod test_serde { "0.8696263565463045".into(), )), Some(vec![ - AuthAccount::new(Cow::from("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg")), - AuthAccount::new(Cow::from("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv")), + AuthAccount::new("rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg".to_string()), + AuthAccount::new("rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv".to_string()), ]), )), Some(vec![VoteEntry::new( - Cow::from("rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm"), + "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm".to_string(), 600, 100000, )]), diff --git a/src/models/ledger/objects/negative_unl.rs b/src/models/ledger/objects/negative_unl.rs index 7e954136..08dfe1f3 100644 --- a/src/models/ledger/objects/negative_unl.rs +++ b/src/models/ledger/objects/negative_unl.rs @@ -1,7 +1,7 @@ use crate::models::ledger::LedgerEntryType; use crate::models::Model; use alloc::borrow::Cow; - +use alloc::string::String; use alloc::vec::Vec; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; @@ -12,11 +12,11 @@ use serde_with::skip_serializing_none; serde_with_tag! { /// Each `DisabledValidator` object represents one disabled validator. #[derive(Debug, PartialEq, Eq, Clone, new, Default)] - pub struct DisabledValidator<'a> { + pub struct DisabledValidator { /// The ledger index when the validator was added to the Negative UNL. pub first_ledger_sequence: u32, /// The master public key of the validator, in hexadecimal. - pub public_key: Cow<'a, str>, + pub public_key: String, } } @@ -40,8 +40,7 @@ pub struct NegativeUNL<'a> { pub index: Cow<'a, str>, /// A list of `DisabledValidator` objects (see below), each representing a trusted validator /// that is currently disabled. - #[serde(borrow = "'a")] - pub disabled_validators: Option>>, + pub disabled_validators: Option>, /// The public key of a trusted validator that is scheduled to be disabled in the /// next flag ledger. pub validator_to_disable: Option>, @@ -68,7 +67,7 @@ impl<'a> Model for NegativeUNL<'a> {} impl<'a> NegativeUNL<'a> { pub fn new( index: Cow<'a, str>, - disabled_validators: Option>>, + disabled_validators: Option>, validator_to_disable: Option>, validator_to_re_enable: Option>, ) -> Self { @@ -86,6 +85,7 @@ impl<'a> NegativeUNL<'a> { #[cfg(test)] mod test_serde { use super::*; + use alloc::string::ToString; use alloc::vec; #[test] @@ -94,7 +94,7 @@ mod test_serde { Cow::from("2E8A59AA9D3B5B186B0B9E0F62E6C02587CA74A4D778938E957B6357D364B244"), Some(vec![DisabledValidator::new( 1609728, - Cow::from("ED6629D456285AE3613B285F65BBFF168D695BA3921F309949AFCD2CA7AFEC16FE"), + "ED6629D456285AE3613B285F65BBFF168D695BA3921F309949AFCD2CA7AFEC16FE".to_string(), )]), None, None, diff --git a/src/models/ledger/objects/signer_list.rs b/src/models/ledger/objects/signer_list.rs index 53c0a93a..85d4908e 100644 --- a/src/models/ledger/objects/signer_list.rs +++ b/src/models/ledger/objects/signer_list.rs @@ -2,7 +2,7 @@ use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; use crate::models::Model; use alloc::borrow::Cow; - +use alloc::string::String; use alloc::vec::Vec; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; @@ -26,14 +26,14 @@ serde_with_tag! { /// /// `` #[derive(Debug, PartialEq, Eq, Clone, new, Default)] - pub struct SignerEntry<'a>{ + pub struct SignerEntry { /// An XRP Ledger address whose signature contributes to the multi-signature. - pub account: Cow<'a, str>, + pub account: String, /// The weight of a signature from this signer. pub signer_weight: u16, /// Arbitrary hexadecimal data. This can be used to identify the signer or for /// other, related purposes. - pub wallet_locator: Option>, + pub wallet_locator: Option, } } @@ -66,8 +66,7 @@ pub struct SignerList<'a> { pub previous_txn_lgr_seq: u32, /// An array of Signer Entry objects representing the parties who are part of this /// signer list. - #[serde(borrow = "'a")] - pub signer_entries: Vec>, + pub signer_entries: Vec, /// An ID for this signer list. Currently always set to 0. #[serde(rename = "SignerListID")] pub signer_list_id: u32, @@ -102,7 +101,7 @@ impl<'a> SignerList<'a> { owner_node: Cow<'a, str>, previous_txn_id: Cow<'a, str>, previous_txn_lgr_seq: u32, - signer_entries: Vec>, + signer_entries: Vec, signer_list_id: u32, signer_quorum: u32, ) -> Self { @@ -123,6 +122,7 @@ impl<'a> SignerList<'a> { #[cfg(test)] mod test_serde { use super::*; + use alloc::string::ToString; use alloc::vec; #[test] @@ -134,9 +134,9 @@ mod test_serde { Cow::from("5904C0DC72C58A83AEFED2FFC5386356AA83FCA6A88C89D00646E51E687CDBE4"), 16061435, vec![ - SignerEntry::new(Cow::from("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), 2, None), - SignerEntry::new(Cow::from("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n"), 1, None), - SignerEntry::new(Cow::from("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), 1, None), + SignerEntry::new("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), 2, None), + SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1, None), + SignerEntry::new("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), 1, None), ], 0, 3, diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index e30e1a05..51769077 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -70,7 +70,7 @@ pub struct AccountDelete<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -132,7 +132,7 @@ impl<'a> AccountDelete<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, destination_tag: Option, ) -> Self { diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 3c9fad90..14303958 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -126,7 +126,7 @@ pub struct AccountSet<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -396,7 +396,7 @@ impl<'a> AccountSet<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, clear_flag: Option, domain: Option<&'a str>, diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 48bef9c1..30ff1535 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -70,7 +70,7 @@ pub struct CheckCancel<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -125,7 +125,7 @@ impl<'a> CheckCancel<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, ) -> Self { Self { diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index c51951fc..eb773825 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -74,7 +74,7 @@ pub struct CheckCash<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -156,7 +156,7 @@ impl<'a> CheckCash<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, amount: Option>, deliver_min: Option>, diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index b5c1f83a..cf1c6149 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -69,7 +69,7 @@ pub struct CheckCreate<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -133,7 +133,7 @@ impl<'a> CheckCreate<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, destination_tag: Option, expiration: Option, diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 0376f24c..a5a2daf2 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -71,7 +71,7 @@ pub struct DepositPreauth<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -149,7 +149,7 @@ impl<'a> DepositPreauth<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, authorize: Option<&'a str>, unauthorize: Option<&'a str>, diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 42eadca2..87f012cb 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -67,7 +67,7 @@ pub struct EscrowCancel<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -124,7 +124,7 @@ impl<'a> EscrowCancel<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, ) -> Self { Self { diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 32905e47..dec31b98 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -70,7 +70,7 @@ pub struct EscrowCreate<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -162,7 +162,7 @@ impl<'a> EscrowCreate<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, destination_tag: Option, cancel_after: Option, diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 2ba50e6e..a21e3af8 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -70,7 +70,7 @@ pub struct EscrowFinish<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -154,7 +154,7 @@ impl<'a> EscrowFinish<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, condition: Option<&'a str>, fulfillment: Option<&'a str>, diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index 8fef9623..9ebf9caf 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -1,6 +1,7 @@ use crate::models::transactions::{AccountSetFlag, PaymentFlag}; use strum_macros::Display; use thiserror_no_std::Error; +use alloc::borrow::Cow; #[derive(Debug, Clone, PartialEq, Eq, Display)] pub enum XRPLTransactionException<'a> { @@ -274,53 +275,53 @@ pub enum XRPLSignerListSetException<'a> { /// A field was defined that another field definition would delete. #[error("The value of the field `{field1:?}` can not be defined with the field `{field2:?}` because it would cause the deletion of `{field1:?}`. For more information see: {resource:?}")] ValueCausesValueDeletion { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, /// A field is expected to have a certain value to be deleted. #[error("The field `{field:?}` has the wrong value to be deleted (expected {expected:?}, found {found:?}). For more information see: {resource:?}")] InvalidValueForValueDeletion { - field: &'a str, + field: Cow<'a, str>, expected: u32, found: u32, - resource: &'a str, + resource: Cow<'a, str>, }, /// A collection has too few items in it. #[error("The value of the field `{field:?}` has too few items in it (min {min:?}, found {found:?}). For more information see: {resource:?}")] CollectionTooFewItems { - field: &'a str, + field: Cow<'a, str>, min: usize, found: usize, - resource: &'a str, + resource: Cow<'a, str>, }, /// A collection has too many items in it. #[error("The value of the field `{field:?}` has too many items in it (max {max:?}, found {found:?}). For more information see: {resource:?}")] CollectionTooManyItems { - field: &'a str, + field: Cow<'a, str>, max: usize, found: usize, - resource: &'a str, + resource: Cow<'a, str>, }, /// A collection is not allowed to have duplicates in it. #[error("The value of the field `{field:?}` has a duplicate in it (found {found:?}). For more information see: {resource:?}")] CollectionItemDuplicate { - field: &'a str, - found: &'a str, - resource: &'a str, + field: Cow<'a, str>, + found: Cow<'a, str>, + resource: Cow<'a, str>, }, /// A collection contains an invalid value. #[error("The field `{field:?}` contains an invalid value (found {found:?}). For more information see: {resource:?}")] CollectionInvalidItem { - field: &'a str, - found: &'a str, - resource: &'a str, + field: Cow<'a, str>, + found: Cow<'a, str>, + resource: Cow<'a, str>, }, #[error("The field `signer_quorum` must be below or equal to the sum of `signer_weight` in `signer_entries`. For more information see: {resource:?}")] SignerQuorumExceedsSignerWeight { max: u32, found: u32, - resource: &'a str, + resource: Cow<'a, str>, }, } diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 1add1bb1..328918a7 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -53,6 +53,7 @@ pub use ticket_create::*; pub use trust_set::*; use crate::serde_with_tag; +use alloc::string::String; use derive_new::new; use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; @@ -190,10 +191,10 @@ serde_with_tag! { /// `` // `#[derive(Serialize)]` is defined in the macro #[derive(Debug, PartialEq, Eq, Default, Clone, new)] -pub struct Memo<'a> { - pub memo_data: Option<&'a str>, - pub memo_format: Option<&'a str>, - pub memo_type: Option<&'a str>, +pub struct Memo { + pub memo_data: Option, + pub memo_format: Option, + pub memo_type: Option, } } diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 68f3e5ff..e6efac02 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -74,7 +74,7 @@ pub struct NFTokenAcceptOffer<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -182,7 +182,7 @@ impl<'a> NFTokenAcceptOffer<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, nftoken_sell_offer: Option<&'a str>, nftoken_buy_offer: Option<&'a str>, @@ -284,6 +284,7 @@ mod test_nftoken_accept_offer_error { #[cfg(test)] mod test_serde { use alloc::vec; + use alloc::string::ToString; use super::*; @@ -300,7 +301,7 @@ mod test_serde { None, None, Some(vec![Memo::new( - Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437"), + Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437".to_string()), None, None, )]), @@ -330,7 +331,7 @@ mod test_serde { None, None, Some(vec![Memo::new( - Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437"), + Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437".to_string()), None, None, )]), diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index e3320432..1c10bdb3 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -68,7 +68,7 @@ pub struct NFTokenBurn<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -125,7 +125,7 @@ impl<'a> NFTokenBurn<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, owner: Option<&'a str>, ) -> Self { diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 205a5312..b60480a8 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -70,7 +70,7 @@ pub struct NFTokenCancelOffer<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -148,7 +148,7 @@ impl<'a> NFTokenCancelOffer<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, ) -> Self { Self { diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index b2b53c5e..ac2790d8 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -96,7 +96,7 @@ pub struct NFTokenCreateOffer<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -266,7 +266,7 @@ impl<'a> NFTokenCreateOffer<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, owner: Option<&'a str>, expiration: Option, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index b6fbe7b1..f244ea59 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -102,7 +102,7 @@ pub struct NFTokenMint<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -247,7 +247,7 @@ impl<'a> NFTokenMint<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, issuer: Option<&'a str>, transfer_fee: Option, @@ -376,6 +376,7 @@ mod test_nftoken_mint_error { #[cfg(test)] mod test_serde { + use alloc::string::ToString; use alloc::vec; use super::*; @@ -394,7 +395,7 @@ mod test_serde { None, None, Some(vec![NFTokenMintFlag::TfTransferable]), - Some(vec![Memo::new(Some("72656E74"), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963"))]), + Some(vec![Memo::new(Some("72656E74".to_string()), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963".to_string()))]), None, None, Some(314), @@ -422,7 +423,7 @@ mod test_serde { None, None, Some(vec![NFTokenMintFlag::TfTransferable]), - Some(vec![Memo::new(Some("72656E74"), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963"))]), + Some(vec![Memo::new(Some("72656E74".to_string()), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963".to_string()))]), None, None, Some(314), diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 560b62f2..57e27bd5 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -67,7 +67,7 @@ pub struct OfferCancel<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -121,7 +121,7 @@ impl<'a> OfferCancel<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, ) -> Self { Self { diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 13b2175e..0b75e9c8 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -105,7 +105,7 @@ pub struct OfferCreate<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -187,7 +187,7 @@ impl<'a> OfferCreate<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, expiration: Option, offer_sequence: Option, diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 3358fc46..0aa62c75 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -102,7 +102,7 @@ pub struct Payment<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -272,7 +272,7 @@ impl<'a> Payment<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, destination_tag: Option, invoice_id: Option, diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 4a30a21b..1318065e 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -101,7 +101,7 @@ pub struct PaymentChannelClaim<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -186,7 +186,7 @@ impl<'a> PaymentChannelClaim<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, balance: Option<&'a str>, amount: Option<&'a str>, diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 0b444b5e..0402f7e3 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -67,7 +67,7 @@ pub struct PaymentChannelCreate<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -134,7 +134,7 @@ impl<'a> PaymentChannelCreate<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, cancel_after: Option, destination_tag: Option, diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 7870a26e..b79927d8 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -68,7 +68,7 @@ pub struct PaymentChannelFund<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -127,7 +127,7 @@ impl<'a> PaymentChannelFund<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, expiration: Option, ) -> Self { diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index d651dc48..ccebf804 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -71,7 +71,7 @@ pub struct SetRegularKey<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -124,7 +124,7 @@ impl<'a> SetRegularKey<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, regular_key: Option<&'a str>, ) -> Self { diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 961269b9..43f317bd 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -1,4 +1,5 @@ -use alloc::borrow::Cow; +use alloc::string::ToString; +use alloc::string::String; use alloc::vec::Vec; use anyhow::Result; use derive_new::new; @@ -17,7 +18,7 @@ serde_with_tag! { #[derive(Debug, PartialEq, Eq, Default, Clone, new)] #[skip_serializing_none] pub struct SignerEntry { - pub account: Cow<'static, str>, + pub account: String, pub signer_weight: u16, } } @@ -84,7 +85,7 @@ pub struct SignerListSet<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -143,23 +144,23 @@ impl<'a> SignerListSetError for SignerListSet<'a> { if let Some(signer_entries) = &self.signer_entries { if self.signer_quorum == 0 { Err(XRPLSignerListSetException::ValueCausesValueDeletion { - field1: "signer_entries", - field2: "signer_quorum", - resource: "", + field1: "signer_entries".into(), + field2: "signer_quorum".into(), + resource: "".into(), }) } else if signer_entries.is_empty() { Err(XRPLSignerListSetException::CollectionTooFewItems { - field: "signer_entries", + field: "signer_entries".into(), min: 1_usize, found: signer_entries.len(), - resource: "", + resource: "".into(), }) } else if signer_entries.len() > 8 { Err(XRPLSignerListSetException::CollectionTooManyItems { - field: "signer_entries", + field: "signer_entries".into(), max: 8_usize, found: signer_entries.len(), - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -174,7 +175,7 @@ impl<'a> SignerListSetError for SignerListSet<'a> { let mut signer_weight_sum: u32 = 0; if self.signer_entries.is_some() { for signer_entry in self.signer_entries.as_ref().unwrap() { - accounts.push(&signer_entry.account); + accounts.push(signer_entry.account.clone()); let weight: u32 = signer_entry.signer_weight.into(); signer_weight_sum += weight; } @@ -184,27 +185,27 @@ impl<'a> SignerListSetError for SignerListSet<'a> { for account in accounts.clone() { if check_account.contains(&account) { return Err(XRPLSignerListSetException::CollectionItemDuplicate { - field: "signer_entries", - found: account, - resource: "", + field: "signer_entries".into(), + found: account.into(), + resource: "".into(), }); } else { check_account.push(account); } } if let Some(_signer_entries) = &self.signer_entries { - if accounts.contains(&&Cow::Borrowed(self.account)) { + if accounts.contains(&self.account.to_string()) { Err(XRPLSignerListSetException::CollectionInvalidItem { - field: "signer_entries", - found: self.account, - resource: "", + field: "signer_entries".into(), + found: self.account.into(), + resource: "".into(), }) } else if self.signer_quorum > signer_weight_sum { Err( XRPLSignerListSetException::SignerQuorumExceedsSignerWeight { max: signer_weight_sum, found: self.signer_quorum, - resource: "", + resource: "".into(), }, ) } else { @@ -212,10 +213,10 @@ impl<'a> SignerListSetError for SignerListSet<'a> { } } else if self.signer_quorum != 0 { Err(XRPLSignerListSetException::InvalidValueForValueDeletion { - field: "signer_quorum", + field: "signer_quorum".into(), expected: 0, found: self.signer_quorum, - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -235,7 +236,7 @@ impl<'a> SignerListSet<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, signer_entries: Option>, ) -> Self { @@ -266,7 +267,6 @@ pub trait SignerListSetError { #[cfg(test)] mod test_signer_list_set_error { - use alloc::borrow::Cow::Borrowed; use alloc::string::ToString; use alloc::vec; @@ -292,7 +292,7 @@ mod test_signer_list_set_error { signers: None, signer_quorum: 0, signer_entries: Some(vec![SignerEntry { - account: Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), + account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 2, }]), }; @@ -338,39 +338,39 @@ mod test_signer_list_set_error { signer_list_set.signer_entries = Some(vec![ SignerEntry { - account: Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), + account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 1, }, SignerEntry { - account: Borrowed("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), + account: "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), signer_weight: 1, }, SignerEntry { - account: Borrowed("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), + account: "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), signer_weight: 2, }, SignerEntry { - account: Borrowed("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"), + account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".to_string(), signer_weight: 2, }, SignerEntry { - account: Borrowed("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"), + account: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".to_string(), signer_weight: 1, }, SignerEntry { - account: Borrowed("rXTZ5g8X7mrAYEe7iFeM9fiS4ccueyurG"), + account: "rXTZ5g8X7mrAYEe7iFeM9fiS4ccueyurG".to_string(), signer_weight: 1, }, SignerEntry { - account: Borrowed("rPbMHxs7vy5t6e19tYfqG7XJ6Fog8EPZLk"), + account: "rPbMHxs7vy5t6e19tYfqG7XJ6Fog8EPZLk".to_string(), signer_weight: 2, }, SignerEntry { - account: Borrowed("r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W"), + account: "r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W".to_string(), signer_weight: 3, }, SignerEntry { - account: Borrowed("rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL"), + account: "rchGBxcD1A1C2tdxF6papQYZ8kjRKMYcL".to_string(), signer_weight: 2, }, ]); @@ -382,15 +382,15 @@ mod test_signer_list_set_error { signer_list_set.signer_entries = Some(vec![ SignerEntry { - account: Borrowed("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb"), + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".to_string(), signer_weight: 1, }, SignerEntry { - account: Borrowed("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), + account: "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), signer_weight: 2, }, SignerEntry { - account: Borrowed("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"), + account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".to_string(), signer_weight: 2, }, ]); @@ -401,7 +401,7 @@ mod test_signer_list_set_error { ); signer_list_set.signer_entries = Some(vec![SignerEntry { - account: Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), + account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 3, }]); signer_list_set.signer_quorum = 10; @@ -413,11 +413,11 @@ mod test_signer_list_set_error { signer_list_set.signer_entries = Some(vec![ SignerEntry { - account: Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), + account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 3, }, SignerEntry { - account: Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), + account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 2, }, ]); @@ -432,7 +432,7 @@ mod test_signer_list_set_error { #[cfg(test)] mod test_serde { - use alloc::borrow::Cow::Borrowed; + use alloc::string::ToString; use alloc::vec; use super::*; @@ -453,9 +453,9 @@ mod test_serde { None, None, Some(vec![ - SignerEntry::new(Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), 2), - SignerEntry::new(Borrowed("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), 1), - SignerEntry::new(Borrowed("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n"), 1), + SignerEntry::new("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), 2), + SignerEntry::new("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), 1), + SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1), ]), ); let default_json = r#"{"TransactionType":"SignerListSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","Sequence":null,"LastLedgerSequence":null,"AccountTxnID":null,"SigningPubKey":null,"SourceTag":null,"TicketSequence":null,"TxnSignature":null,"Flags":null,"Memos":null,"Signers":null,"SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; @@ -482,9 +482,9 @@ mod test_serde { None, None, Some(vec![ - SignerEntry::new(Borrowed("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"), 2), - SignerEntry::new(Borrowed("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v"), 1), - SignerEntry::new(Borrowed("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n"), 1), + SignerEntry::new("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), 2), + SignerEntry::new("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), 1), + SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1), ]), ); let default_json = r#"{"TransactionType":"SignerListSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 3779eab2..ebbe5cf7 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -67,7 +67,7 @@ pub struct TicketCreate<'a> { /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -121,7 +121,7 @@ impl<'a> TicketCreate<'a> { source_tag: Option, ticket_sequence: Option, txn_signature: Option<&'a str>, - memos: Option>>, + memos: Option>, signers: Option>>, ) -> Self { Self { diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 3165cd3c..081d2eaa 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -96,7 +96,7 @@ pub struct TrustSet<'a> { #[serde(with = "txn_flags")] pub flags: Option>, /// Additional arbitrary information used to identify this transaction. - pub memos: Option>>, + pub memos: 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 @@ -174,7 +174,7 @@ impl<'a> TrustSet<'a> { ticket_sequence: Option, txn_signature: Option<&'a str>, flags: Option>, - memos: Option>>, + memos: Option>, signers: Option>>, quality_in: Option, quality_out: Option, From b74af282dd928595fef772d5c22ebc37df87826c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 18 Sep 2023 18:13:29 +0200 Subject: [PATCH 027/113] utilize Cow for models --- src/models/mod.rs | 9 +- src/models/requests/account_channels.rs | 23 ++-- src/models/requests/account_currencies.rs | 19 +-- src/models/requests/account_info.rs | 19 +-- src/models/requests/account_lines.rs | 23 ++-- src/models/requests/account_nfts.rs | 11 +- src/models/requests/account_objects.rs | 19 +-- src/models/requests/account_offers.rs | 19 +-- src/models/requests/account_tx.rs | 19 +-- src/models/requests/book_offers.rs | 17 +-- src/models/requests/channel_authorize.rs | 53 +++---- src/models/requests/channel_verify.rs | 29 ++-- src/models/requests/deposit_authorize.rs | 25 ++-- src/models/requests/exceptions.rs | 33 ++--- src/models/requests/fee.rs | 5 +- src/models/requests/gateway_balances.rs | 23 ++-- src/models/requests/ledger.rs | 13 +- src/models/requests/ledger_closed.rs | 5 +- src/models/requests/ledger_current.rs | 5 +- src/models/requests/ledger_data.rs | 13 +- src/models/requests/ledger_entry.rs | 97 ++++++------- src/models/requests/manifest.rs | 9 +- src/models/requests/nft_buy_offers.rs | 15 +- src/models/requests/nft_sell_offers.rs | 7 +- src/models/requests/no_ripple_check.rs | 19 +-- src/models/requests/path_find.rs | 17 +-- src/models/requests/ping.rs | 5 +- src/models/requests/random.rs | 5 +- src/models/requests/ripple_path_find.rs | 25 ++-- src/models/requests/server_info.rs | 5 +- src/models/requests/server_state.rs | 5 +- src/models/requests/submit.rs | 9 +- src/models/requests/submit_multisigned.rs | 5 +- src/models/requests/subscribe.rs | 27 ++-- src/models/requests/transaction_entry.rs | 19 +-- src/models/requests/tx.rs | 5 +- src/models/requests/unsubscribe.rs | 17 +-- src/models/transactions/account_delete.rs | 29 ++-- src/models/transactions/account_set.rs | 107 +++++++------- src/models/transactions/check_cancel.rs | 29 ++-- src/models/transactions/check_cash.rs | 39 +++--- src/models/transactions/check_create.rs | 37 ++--- src/models/transactions/deposit_preauth.rs | 41 +++--- src/models/transactions/escrow_cancel.rs | 29 ++-- src/models/transactions/escrow_create.rs | 47 +++---- src/models/transactions/escrow_finish.rs | 57 ++++---- src/models/transactions/exceptions.rs | 130 +++++++++--------- src/models/transactions/mod.rs | 7 +- .../transactions/nftoken_accept_offer.rs | 24 ++-- src/models/transactions/nftoken_burn.rs | 37 ++--- .../transactions/nftoken_cancel_offer.rs | 29 ++-- .../transactions/nftoken_create_offer.rs | 87 ++++++------ src/models/transactions/nftoken_mint.rs | 61 ++++---- src/models/transactions/offer_cancel.rs | 21 +-- src/models/transactions/offer_create.rs | 25 ++-- src/models/transactions/payment.rs | 81 +++++------ .../transactions/payment_channel_claim.rs | 61 ++++---- .../transactions/payment_channel_create.rs | 37 ++--- .../transactions/payment_channel_fund.rs | 29 ++-- .../pseudo_transactions/enable_amendment.rs | 17 +-- .../pseudo_transactions/set_fee.rs | 13 +- .../pseudo_transactions/unl_modify.rs | 17 +-- src/models/transactions/set_regular_key.rs | 29 ++-- src/models/transactions/signer_list_set.rs | 27 ++-- src/models/transactions/ticket_create.rs | 21 +-- src/models/transactions/trust_set.rs | 21 +-- tests/integration/clients/mod.rs | 4 +- 67 files changed, 965 insertions(+), 901 deletions(-) diff --git a/src/models/mod.rs b/src/models/mod.rs index b3c6a22d..92958f0e 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -28,6 +28,7 @@ use derive_new::new; pub use model::Model; use crate::models::currency::{Currency, XRP}; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use strum_macros::Display; @@ -52,11 +53,11 @@ pub enum AccountObjectType { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, new)] #[serde(rename_all = "PascalCase")] pub struct PathStep<'a> { - account: Option<&'a str>, - currency: Option<&'a str>, - issuer: Option<&'a str>, + account: Option>, + currency: Option>, + issuer: Option>, r#type: Option, - type_hex: Option<&'a str>, + type_hex: Option>, } /// Returns a Currency as XRP for the currency, without a value. diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index a6f0ed97..b322633e 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -35,21 +36,21 @@ pub struct AccountChannels<'a> { /// The unique identifier of an account, typically the /// account's Address. The request returns channels where /// this account is the channel's owner/source. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Limit the number of transactions to retrieve. Cannot /// be less than 10 or more than 400. The default is 200. pub limit: Option, /// The unique identifier of an account, typically the /// account's Address. If provided, filter results to /// payment channels whose destination is this account. - pub destination_account: Option<&'a str>, + pub destination_account: Option>, /// Value from a previous paginated response. /// Resume retrieving data where that response left off. pub marker: Option, @@ -61,7 +62,7 @@ pub struct AccountChannels<'a> { impl<'a> Default for AccountChannels<'a> { fn default() -> Self { AccountChannels { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -77,12 +78,12 @@ impl<'a> Model for AccountChannels<'a> {} impl<'a> AccountChannels<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, limit: Option, - destination_account: Option<&'a str>, + destination_account: Option>, marker: Option, ) -> Self { Self { diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index 1edc9008..af18eddd 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -15,14 +16,14 @@ use crate::models::{default_false, requests::RequestMethod, Model}; pub struct AccountCurrencies<'a> { /// A unique identifier for the account, most commonly /// the account's Address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If true, then the account field only accepts a public /// key or XRP Ledger address. Otherwise, account can be /// a secret or passphrase (not recommended). @@ -37,7 +38,7 @@ pub struct AccountCurrencies<'a> { impl<'a> Default for AccountCurrencies<'a> { fn default() -> Self { AccountCurrencies { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -51,10 +52,10 @@ impl<'a> Model for AccountCurrencies<'a> {} impl<'a> AccountCurrencies<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, strict: Option, ) -> Self { Self { diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 038b06b4..1f1230d2 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -14,14 +15,14 @@ use crate::models::{requests::RequestMethod, Model}; pub struct AccountInfo<'a> { /// A unique identifier for the account, most commonly the /// account's Address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If true, then the account field only accepts a public /// key or XRP Ledger address. Otherwise, account can be /// a secret or passphrase (not recommended). @@ -44,7 +45,7 @@ pub struct AccountInfo<'a> { impl<'a> Default for AccountInfo<'a> { fn default() -> Self { AccountInfo { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -60,10 +61,10 @@ impl<'a> Model for AccountInfo<'a> {} impl<'a> AccountInfo<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, strict: Option, queue: Option, signer_lists: Option, diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index 8ca47b56..de572052 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -15,21 +16,21 @@ use crate::models::{requests::RequestMethod, Model}; pub struct AccountLines<'a> { /// A unique identifier for the account, most commonly the /// account's Address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Limit the number of trust lines to retrieve. The server /// is not required to honor this value. Must be within the /// inclusive range 10 to 400. pub limit: Option, /// The Address of a second account. If provided, show only /// lines of trust connecting the two accounts. - pub peer: Option<&'a str>, + pub peer: Option>, /// Value from a previous paginated response. Resume retrieving /// data where that response left off. pub marker: Option, @@ -41,7 +42,7 @@ pub struct AccountLines<'a> { impl<'a> Default for AccountLines<'a> { fn default() -> Self { AccountLines { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -57,12 +58,12 @@ impl<'a> Model for AccountLines<'a> {} impl<'a> AccountLines<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, limit: Option, - peer: Option<&'a str>, + peer: Option>, marker: Option, ) -> Self { Self { diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index faa26391..960782e6 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -11,9 +12,9 @@ pub struct AccountNfts<'a> { /// The unique identifier of an account, typically the /// account's Address. The request returns a list of /// NFTs owned by this account. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// Limit the number of token pages to retrieve. Each page /// can contain up to 32 NFTs. The limit value cannot be /// lower than 20 or more than 400. The default is 100. @@ -29,7 +30,7 @@ pub struct AccountNfts<'a> { impl<'a> Default for AccountNfts<'a> { fn default() -> Self { AccountNfts { - account: "", + account: "".into(), id: None, limit: None, marker: None, @@ -42,8 +43,8 @@ impl<'a> Model for AccountNfts<'a> {} impl<'a> AccountNfts<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, limit: Option, marker: Option, ) -> Self { diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index 24a7a0ff..306f4742 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use strum_macros::Display; @@ -32,14 +33,14 @@ pub enum AccountObjectType { pub struct AccountObjects<'a> { /// A unique identifier for the account, most commonly the /// account's address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If included, filter results to include only this type /// of ledger object. The valid types are: check, deposit_preauth, /// escrow, offer, payment_channel, signer_list, ticket, @@ -63,7 +64,7 @@ pub struct AccountObjects<'a> { impl<'a> Default for AccountObjects<'a> { fn default() -> Self { AccountObjects { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -80,10 +81,10 @@ impl<'a> Model for AccountObjects<'a> {} impl<'a> AccountObjects<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, r#type: Option, deletion_blockers_only: Option, limit: Option, diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index d310d540..0c76872d 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,14 +14,14 @@ use crate::models::{requests::RequestMethod, Model}; pub struct AccountOffers<'a> { /// A unique identifier for the account, most commonly the /// account's Address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string identifying the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or "current", /// "closed", or "validated" to select a ledger dynamically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Limit the number of transactions to retrieve. The server is /// not required to honor this value. Must be within the inclusive /// range 10 to 400. @@ -40,7 +41,7 @@ pub struct AccountOffers<'a> { impl<'a> Default for AccountOffers<'a> { fn default() -> Self { AccountOffers { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -56,10 +57,10 @@ impl<'a> Model for AccountOffers<'a> {} impl<'a> AccountOffers<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, limit: Option, strict: Option, marker: Option, diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 25827af4..4f0e114a 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,13 +14,13 @@ use crate::models::{requests::RequestMethod, Model}; pub struct AccountTx<'a> { /// A unique identifier for the account, most commonly the /// account's address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// Use to look for transactions from a single ledger only. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// Use to look for transactions from a single ledger only. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Defaults to false. If set to true, returns transactions /// as hex strings instead of JSON. pub binary: Option, @@ -53,7 +54,7 @@ pub struct AccountTx<'a> { impl<'a> Default for AccountTx<'a> { fn default() -> Self { AccountTx { - account: "", + account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -72,10 +73,10 @@ impl<'a> Model for AccountTx<'a> {} impl<'a> AccountTx<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, binary: Option, forward: Option, ledger_index_min: Option, diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index e470a70c..64e4e5ba 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -22,12 +23,12 @@ pub struct BookOffers<'a> { /// like currency amounts. pub taker_pays: Currency<'a>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If provided, the server does not provide more than /// this many offers in the results. The total number of /// results returned may be fewer than the limit, @@ -37,7 +38,7 @@ pub struct BookOffers<'a> { /// Unfunded offers placed by this account are always /// included in the response. (You can use this to look /// up your own orders to cancel them.) - pub taker: Option<&'a str>, + pub taker: Option>, /// The request method. #[serde(default = "RequestMethod::book_offers")] pub command: RequestMethod, @@ -64,11 +65,11 @@ impl<'a> BookOffers<'a> { pub fn new( taker_gets: Currency<'a>, taker_pays: Currency<'a>, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, limit: Option, - taker: Option<&'a str>, + taker: Option>, ) -> Self { Self { taker_gets, diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 8024c20a..9a3d0f7c 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -31,37 +32,37 @@ use crate::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ChannelAuthorize<'a> { /// The unique ID of the payment channel to use. - pub channel_id: &'a str, + pub channel_id: Cow<'a, str>, /// Cumulative amount of XRP, in drops, to authorize. /// If the destination has already received a lesser amount /// of XRP from this channel, the signature created by this /// method can be redeemed for the difference. - pub amount: &'a str, + pub amount: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The secret key to use to sign the claim. This must be /// the same key pair as the public key specified in the /// channel. Cannot be used with seed, seed_hex, or passphrase. - pub secret: Option<&'a str>, + pub secret: Option>, /// The secret seed to use to sign the claim. This must be /// the same key pair as the public key specified in the channel. /// Must be in the XRP Ledger's base58 format. If provided, /// you must also specify the key_type. Cannot be used with /// secret, seed_hex, or passphrase. - pub seed: Option<&'a str>, + pub seed: Option>, /// The secret seed to use to sign the claim. This must be the /// same key pair as the public key specified in the channel. /// Must be in hexadecimal format. If provided, you must also /// specify the key_type. Cannot be used with secret, seed, /// or passphrase. - pub seed_hex: Option<&'a str>, + pub seed_hex: Option>, /// A string passphrase to use to sign the claim. This must be /// the same key pair as the public key specified in the channel. /// The key derived from this passphrase must match the public /// key specified in the channel. If provided, you must also /// specify the key_type. Cannot be used with secret, seed, /// or seed_hex. - pub passphrase: Option<&'a str>, + pub passphrase: Option>, /// The signing algorithm of the cryptographic key pair provided. /// Valid types are secp256k1 or ed25519. The default is secp256k1. pub key_type: Option, @@ -73,8 +74,8 @@ pub struct ChannelAuthorize<'a> { impl<'a> Default for ChannelAuthorize<'a> { fn default() -> Self { ChannelAuthorize { - channel_id: "", - amount: "", + channel_id: "".into(), + amount: "".into(), id: None, secret: None, seed: None, @@ -98,18 +99,18 @@ impl<'a> Model for ChannelAuthorize<'a> { impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { fn _get_field_error(&self) -> Result<(), XRPLChannelAuthorizeException> { let mut signing_methods = Vec::new(); - for method in [self.secret, self.seed, self.seed_hex, self.passphrase] { + for method in [self.secret.clone(), self.seed.clone(), self.seed_hex.clone(), self.passphrase.clone()] { if method.is_some() { signing_methods.push(method) } } if signing_methods.len() != 1 { Err(XRPLChannelAuthorizeException::DefineExactlyOneOf { - field1: "secret", - field2: "seed", - field3: "seed_hex", - field4: "passphrase", - resource: "", + field1: "secret".into(), + field2: "seed".into(), + field3: "seed_hex".into(), + field4: "passphrase".into(), + resource: "".into(), }) } else { Ok(()) @@ -119,13 +120,13 @@ impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { impl<'a> ChannelAuthorize<'a> { pub fn new( - channel_id: &'a str, - amount: &'a str, - id: Option<&'a str>, - secret: Option<&'a str>, - seed: Option<&'a str>, - seed_hex: Option<&'a str>, - passphrase: Option<&'a str>, + channel_id: Cow<'a, str>, + amount: Cow<'a, str>, + id: Option>, + secret: Option>, + seed: Option>, + seed_hex: Option>, + passphrase: Option>, key_type: Option, ) -> Self { Self { @@ -158,12 +159,12 @@ mod test_channel_authorize_errors { fn test_fields_error() { let channel_authorize = ChannelAuthorize { command: RequestMethod::ChannelAuthorize, - channel_id: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3", - amount: "1000000", + channel_id: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3".into(), + amount: "1000000".into(), id: None, secret: None, - seed: Some(""), - seed_hex: Some(""), + seed: Some("".into()), + seed_hex: Some("".into()), passphrase: None, key_type: Some(CryptoAlgorithm::SECP256K1), }; diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 73fb1dc3..d041d715 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -11,17 +12,17 @@ use crate::models::{requests::RequestMethod, Model}; pub struct ChannelVerify<'a> { /// The Channel ID of the channel that provides the XRP. /// This is a 64-character hexadecimal string. - pub channel_id: &'a str, + pub channel_id: Cow<'a, str>, /// The amount of XRP, in drops, the provided signature authorizes. - pub amount: &'a str, + pub amount: Cow<'a, str>, /// The public key of the channel and the key pair that was used to /// create the signature, in hexadecimal or the XRP Ledger's /// base58 format. - pub public_key: &'a str, + pub public_key: Cow<'a, str>, /// The signature to verify, in hexadecimal. - pub signature: &'a str, + pub signature: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::channel_verify")] pub command: RequestMethod, @@ -30,10 +31,10 @@ pub struct ChannelVerify<'a> { impl<'a> Default for ChannelVerify<'a> { fn default() -> Self { ChannelVerify { - channel_id: "", - amount: "", - public_key: "", - signature: "", + channel_id: "".into(), + amount: "".into(), + public_key: "".into(), + signature: "".into(), id: None, command: RequestMethod::ChannelVerify, } @@ -44,11 +45,11 @@ impl<'a> Model for ChannelVerify<'a> {} impl<'a> ChannelVerify<'a> { pub fn new( - channel_id: &'a str, - amount: &'a str, - public_key: &'a str, - signature: &'a str, - id: Option<&'a str>, + channel_id: Cow<'a, str>, + amount: Cow<'a, str>, + public_key: Cow<'a, str>, + signature: Cow<'a, str>, + id: Option>, ) -> Self { Self { channel_id, diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 087a00db..973aa720 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -12,16 +13,16 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct DepositAuthorized<'a> { /// The sender of a possible payment. - pub source_account: &'a str, + pub source_account: Cow<'a, str>, /// The recipient of a possible payment. - pub destination_account: &'a str, + pub destination_account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// The request method. #[serde(default = "RequestMethod::deposit_authorization")] pub command: RequestMethod, @@ -30,8 +31,8 @@ pub struct DepositAuthorized<'a> { impl<'a> Default for DepositAuthorized<'a> { fn default() -> Self { DepositAuthorized { - source_account: "", - destination_account: "", + source_account: "".into(), + destination_account: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -44,11 +45,11 @@ impl<'a> Model for DepositAuthorized<'a> {} impl<'a> DepositAuthorized<'a> { pub fn new( - source_account: &'a str, - destination_account: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + source_account: Cow<'a, str>, + destination_account: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, ) -> Self { Self { source_account, diff --git a/src/models/requests/exceptions.rs b/src/models/requests/exceptions.rs index 2e43eecc..1114cd4f 100644 --- a/src/models/requests/exceptions.rs +++ b/src/models/requests/exceptions.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use strum_macros::Display; use thiserror_no_std::Error; @@ -18,11 +19,11 @@ pub enum XRPLChannelAuthorizeException<'a> { /// A field cannot be defined with other fields. #[error("The field `{field1:?}` can not be defined with `{field2:?}`, `{field3:?}`, `{field4:?}`. Define exactly one of them. For more information see: {resource:?}")] DefineExactlyOneOf { - field1: &'a str, - field2: &'a str, - field3: &'a str, - field4: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + field3: Cow<'a, str>, + field4: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -40,17 +41,17 @@ pub enum XRPLLedgerEntryException<'a> { /// A field cannot be defined with other fields. #[error("Define one of: `{field1:?}`, `{field2:?}`, `{field3:?}`, `{field4:?}`, `{field5:?}`, `{field6:?}`, `{field7:?}`, `{field8:?}`, `{field9:?}`, `{field10:?}`. Define exactly one of them. For more information see: {resource:?}")] DefineExactlyOneOf { - field1: &'a str, - field2: &'a str, - field3: &'a str, - field4: &'a str, - field5: &'a str, - field6: &'a str, - field7: &'a str, - field8: &'a str, - field9: &'a str, - field10: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + field3: Cow<'a, str>, + field4: Cow<'a, str>, + field5: Cow<'a, str>, + field6: Cow<'a, str>, + field7: Cow<'a, str>, + field8: Cow<'a, str>, + field9: Cow<'a, str>, + field10: Cow<'a, str>, + resource: Cow<'a, str>, }, } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 8416a2ef..2ce491a2 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -14,7 +15,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Fee<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::fee")] pub command: RequestMethod, @@ -32,7 +33,7 @@ impl<'a> Default for Fee<'a> { impl<'a> Model for Fee<'a> {} impl<'a> Fee<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::Fee, diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index ade93092..b5923e9e 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -14,20 +15,20 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct GatewayBalances<'a> { /// The Address to check. This should be the issuing address. - pub account: &'a str, + pub account: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// If true, only accept an address or public key for the /// account parameter. Defaults to false. pub strict: Option, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger version to use, or a /// shortcut string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// An operational address to exclude from the balances /// issued, or an array of such addresses. - pub hotwallet: Option>, + pub hotwallet: Option>>, /// The request method. #[serde(default = "RequestMethod::deposit_authorization")] pub command: RequestMethod, @@ -36,7 +37,7 @@ pub struct GatewayBalances<'a> { impl<'a> Default for GatewayBalances<'a> { fn default() -> Self { GatewayBalances { - account: "", + account: "".into(), id: None, strict: None, ledger_hash: None, @@ -51,12 +52,12 @@ impl<'a> Model for GatewayBalances<'a> {} impl<'a> GatewayBalances<'a> { pub fn new( - account: &'a str, - id: Option<&'a str>, + account: Cow<'a, str>, + id: Option>, strict: Option, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, - hotwallet: Option>, + ledger_hash: Option>, + ledger_index: Option>, + hotwallet: Option>>, ) -> Self { Self { account, diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index bc1eabe0..3ddc5fe8 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -11,12 +12,12 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Ledger<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Admin required. If true, return full information on /// the entire ledger. Ignored if you did not specify a /// ledger version. Defaults to false. (Equivalent to @@ -77,9 +78,9 @@ impl<'a> Model for Ledger<'a> {} impl<'a> Ledger<'a> { pub fn new( - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, full: Option, accounts: Option, transactions: Option, diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index e7b5d309..4b8f8288 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,7 +14,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerClosed<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::ledger_closed")] pub command: RequestMethod, @@ -31,7 +32,7 @@ impl<'a> Default for LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} impl<'a> LedgerClosed<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::LedgerClosed, diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 6ccfd04d..3b72251d 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,7 +14,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerCurrent<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::ledger_current")] pub command: RequestMethod, @@ -31,7 +32,7 @@ impl<'a> Default for LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} impl<'a> LedgerCurrent<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::LedgerCurrent, diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index 362e4291..78a21c77 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,12 +14,12 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerData<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If set to true, return ledger objects as hashed hex /// strings instead of JSON. pub binary: Option, @@ -51,9 +52,9 @@ impl<'a> Model for LedgerData<'a> {} impl<'a> LedgerData<'a> { pub fn new( - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, binary: Option, limit: Option, marker: Option, diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 78856383..672a893a 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -1,5 +1,6 @@ use crate::Err; use anyhow::Result; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -10,8 +11,8 @@ use crate::models::{requests::RequestMethod, Model}; /// querying by object ID. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct DepositPreauth<'a> { - pub owner: &'a str, - pub authorized: &'a str, + pub owner: Cow<'a, str>, + pub authorized: Cow<'a, str>, } /// Required fields for requesting a DirectoryNode if not @@ -19,8 +20,8 @@ pub struct DepositPreauth<'a> { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Directory<'a> { - pub owner: &'a str, - pub dir_root: &'a str, + pub owner: Cow<'a, str>, + pub dir_root: Cow<'a, str>, pub sub_index: Option, } @@ -28,7 +29,7 @@ pub struct Directory<'a> { /// by object ID. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Escrow<'a> { - pub owner: &'a str, + pub owner: Cow<'a, str>, pub seq: u64, } @@ -36,7 +37,7 @@ pub struct Escrow<'a> { /// by object ID. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Offer<'a> { - pub account: &'a str, + pub account: Cow<'a, str>, pub seq: u64, } @@ -44,15 +45,15 @@ pub struct Offer<'a> { /// querying by object ID. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Ticket<'a> { - pub owner: &'a str, + pub owner: Cow<'a, str>, pub ticket_sequence: u64, } /// Required fields for requesting a RippleState. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct RippleState<'a> { - pub account: &'a str, - pub currency: &'a str, + pub account: Cow<'a, str>, + pub currency: Cow<'a, str>, } /// The ledger_entry method returns a single ledger object @@ -69,11 +70,11 @@ pub struct RippleState<'a> { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerEntry<'a> { /// The unique request id. - pub id: Option<&'a str>, - pub index: Option<&'a str>, - pub account_root: Option<&'a str>, - pub check: Option<&'a str>, - pub payment_channel: Option<&'a str>, + pub id: Option>, + pub index: Option>, + pub account_root: Option>, + pub check: Option>, + pub payment_channel: Option>, pub deposit_preauth: Option>, pub directory: Option>, pub escrow: Option>, @@ -85,11 +86,11 @@ pub struct LedgerEntry<'a> { /// data in JSON format. The default is false. pub binary: Option, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut string /// (e.g. "validated" or "closed" or "current") to choose a ledger /// automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// The request method. #[serde(default = "RequestMethod::ledger_entry")] pub command: RequestMethod, @@ -129,7 +130,7 @@ impl<'a: 'static> Model for LedgerEntry<'a> { impl<'a> LedgerEntryError for LedgerEntry<'a> { fn _get_field_error(&self) -> Result<(), XRPLLedgerEntryException> { let mut signing_methods: u32 = 0; - for method in [self.index, self.account_root, self.check] { + for method in [self.index.clone(), self.account_root.clone(), self.check.clone()] { if method.is_some() { signing_methods += 1 } @@ -157,17 +158,17 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } if signing_methods != 1 { Err(XRPLLedgerEntryException::DefineExactlyOneOf { - field1: "index", - field2: "account_root", - field3: "check", - field4: "directory", - field5: "offer", - field6: "ripple_state", - field7: "escrow", - field8: "payment_channel", - field9: "deposit_preauth", - field10: "ticket", - resource: "", + field1: "index".into(), + field2: "account_root".into(), + field3: "check".into(), + field4: "directory".into(), + field5: "offer".into(), + field6: "ripple_state".into(), + field7: "escrow".into(), + field8: "payment_channel".into(), + field9: "deposit_preauth".into(), + field10: "ticket".into(), + resource: "".into(), }) } else { Ok(()) @@ -177,11 +178,11 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { impl<'a> LedgerEntry<'a> { pub fn new( - id: Option<&'a str>, - index: Option<&'a str>, - account_root: Option<&'a str>, - check: Option<&'a str>, - payment_channel: Option<&'a str>, + id: Option>, + index: Option>, + account_root: Option>, + check: Option>, + payment_channel: Option>, deposit_preauth: Option>, directory: Option>, escrow: Option>, @@ -189,8 +190,8 @@ impl<'a> LedgerEntry<'a> { ripple_state: Option>, ticket: Option>, binary: Option, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + ledger_hash: Option>, + ledger_index: Option>, ) -> Self { Self { id, @@ -231,14 +232,14 @@ mod test_ledger_entry_errors { command: RequestMethod::LedgerEntry, id: None, index: None, - account_root: Some("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"), + account_root: Some("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into()), check: None, payment_channel: None, deposit_preauth: None, directory: None, escrow: None, offer: Some(Offer { - account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), seq: 359, }), ripple_state: None, @@ -248,17 +249,17 @@ mod test_ledger_entry_errors { ledger_index: None, }; let _expected = XRPLLedgerEntryException::DefineExactlyOneOf { - field1: "index", - field2: "account_root", - field3: "check", - field4: "directory", - field5: "offer", - field6: "ripple_state", - field7: "escrow", - field8: "payment_channel", - field9: "deposit_preauth", - field10: "ticket", - resource: "", + field1: "index".into(), + field2: "account_root".into(), + field3: "check".into(), + field4: "directory".into(), + field5: "offer".into(), + field6: "ripple_state".into(), + field7: "escrow".into(), + field8: "payment_channel".into(), + field9: "deposit_preauth".into(), + field10: "ticket".into(), + resource: "".into(), }; assert_eq!( ledger_entry.validate().unwrap_err().to_string().as_str(), diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index 7a02e289..2cfbb85e 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -16,9 +17,9 @@ pub struct Manifest<'a> { /// The base58-encoded public key of the validator /// to look up. This can be the master public key or /// ephemeral public key. - pub public_key: &'a str, + pub public_key: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::manifest")] pub command: RequestMethod, @@ -27,7 +28,7 @@ pub struct Manifest<'a> { impl<'a> Default for Manifest<'a> { fn default() -> Self { Manifest { - public_key: "", + public_key: "".into(), id: None, command: RequestMethod::Manifest, } @@ -37,7 +38,7 @@ impl<'a> Default for Manifest<'a> { impl<'a> Model for Manifest<'a> {} impl<'a> Manifest<'a> { - pub fn new(public_key: &'a str, id: Option<&'a str>) -> Self { + pub fn new(public_key: Cow<'a, str>, id: Option>) -> Self { Self { public_key, id, diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index 3ecfa1ef..1bde682d 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -8,12 +9,12 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct NftBuyOffers<'a> { /// The unique identifier of a NFToken object. - pub nft_id: &'a str, + pub nft_id: Cow<'a, str>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Limit the number of NFT buy offers to retrieve. /// This value cannot be lower than 50 or more than 500. /// The default is 250. @@ -29,7 +30,7 @@ pub struct NftBuyOffers<'a> { impl<'a> Default for NftBuyOffers<'a> { fn default() -> Self { NftBuyOffers { - nft_id: "", + nft_id: "".into(), ledger_hash: None, ledger_index: None, limit: None, @@ -43,9 +44,9 @@ impl<'a> Model for NftBuyOffers<'a> {} impl<'a> NftBuyOffers<'a> { pub fn new( - nft_id: &'a str, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + nft_id: Cow<'a, str>, + ledger_hash: Option>, + ledger_index: Option>, limit: Option, marker: Option, ) -> Self { diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 354cafda..1edb8d11 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -8,7 +9,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct NftSellOffers<'a> { /// The unique identifier of a NFToken object. - pub nft_id: &'a str, + pub nft_id: Cow<'a, str>, /// The request method. #[serde(default = "RequestMethod::nft_sell_offers")] pub command: RequestMethod, @@ -17,7 +18,7 @@ pub struct NftSellOffers<'a> { impl<'a> Default for NftSellOffers<'a> { fn default() -> Self { NftSellOffers { - nft_id: "", + nft_id: "".into(), command: RequestMethod::NftSellOffers, } } @@ -26,7 +27,7 @@ impl<'a> Default for NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} impl<'a> NftSellOffers<'a> { - pub fn new(nft_id: &'a str) -> Self { + pub fn new(nft_id: Cow<'a, str>) -> Self { Self { nft_id, command: RequestMethod::NftSellOffers, diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index 37f5f0bc..83410605 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use strum_macros::Display; @@ -29,7 +30,7 @@ pub enum NoRippleCheckRole { pub struct NoRippleCheck<'a> { /// A unique identifier for the account, most commonly the /// account's address. - pub account: &'a str, + pub account: Cow<'a, str>, /// Whether the address refers to a gateway or user. /// Recommendations depend on the role of the account. /// Issuers must have Default Ripple enabled and must disable @@ -37,12 +38,12 @@ pub struct NoRippleCheck<'a> { /// disabled, and should enable No Ripple on all trust lines. pub role: NoRippleCheckRole, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut string /// to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// If true, include an array of suggested transactions, as JSON /// objects, that you can sign and submit to fix the problems. /// Defaults to false. @@ -58,7 +59,7 @@ pub struct NoRippleCheck<'a> { impl<'a> Default for NoRippleCheck<'a> { fn default() -> Self { NoRippleCheck { - account: "", + account: "".into(), role: Default::default(), id: None, ledger_hash: None, @@ -74,11 +75,11 @@ impl<'a> Model for NoRippleCheck<'a> {} impl<'a> NoRippleCheck<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, role: NoRippleCheckRole, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, transactions: Option, limit: Option, ) -> Self { diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index 0eedc299..41426053 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -61,10 +62,10 @@ pub struct PathFind<'a> { /// Unique address of the account to find a path /// from. (In other words, the account that would /// be sending a payment.) - pub source_account: &'a str, + pub source_account: Cow<'a, str>, /// Unique address of the account to find a path to. /// (In other words, the account that would receive a payment.) - pub destination_account: &'a str, + pub destination_account: Cow<'a, str>, /// Currency Amount that the destination account would /// receive in a transaction. Special case: New in: rippled 0.30.0 /// You can specify "-1" (for XRP) or provide -1 as the contents of @@ -73,7 +74,7 @@ pub struct PathFind<'a> { /// the amount specified in send_max (if provided). pub destination_amount: Currency<'a>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// Currency Amount that would be spent in the transaction. /// Not compatible with source_currencies. pub send_max: Option>, @@ -91,8 +92,8 @@ impl<'a> Default for PathFind<'a> { fn default() -> Self { PathFind { subcommand: Default::default(), - source_account: "", - destination_account: "", + source_account: "".into(), + destination_account: "".into(), destination_amount: Currency::XRP(XRP::new()), id: None, send_max: None, @@ -107,10 +108,10 @@ impl<'a> Model for PathFind<'a> {} impl<'a> PathFind<'a> { pub fn new( subcommand: PathFindSubcommand, - source_account: &'a str, - destination_account: &'a str, + source_account: Cow<'a, str>, + destination_account: Cow<'a, str>, destination_amount: Currency<'a>, - id: Option<&'a str>, + id: Option>, send_max: Option>, paths: Option>>>, ) -> Self { diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 9c100442..753ffdeb 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -12,7 +13,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Ping<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::ping")] pub command: RequestMethod, @@ -30,7 +31,7 @@ impl<'a> Default for Ping<'a> { impl<'a> Model for Ping<'a> {} impl<'a> Ping<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::Ping, diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 0ecbfbdc..4b53e8f8 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,7 +14,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Random<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::random")] pub command: RequestMethod, @@ -31,7 +32,7 @@ impl<'a> Default for Random<'a> { impl<'a> Model for Random<'a> {} impl<'a> Random<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::Random, diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index 77e4ce06..6c9d3ad5 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -26,10 +27,10 @@ use crate::models::{currency::Currency, requests::RequestMethod, Model}; pub struct RipplePathFind<'a> { /// Unique address of the account that would send funds /// in a transaction. - pub source_account: &'a str, + pub source_account: Cow<'a, str>, /// Unique address of the account that would receive funds /// in a transaction. - pub destination_account: &'a str, + pub destination_account: Cow<'a, str>, /// Currency Amount that the destination account would /// receive in a transaction. Special case: New in: rippled 0.30.0 /// You can specify "-1" (for XRP) or provide -1 as the contents @@ -38,12 +39,12 @@ pub struct RipplePathFind<'a> { /// than the amount specified in send_max (if provided). pub destination_amount: Currency<'a>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// Currency Amount that would be spent in the transaction. /// Cannot be used with source_currencies. pub send_max: Option>, @@ -63,8 +64,8 @@ pub struct RipplePathFind<'a> { impl<'a> Default for RipplePathFind<'a> { fn default() -> Self { RipplePathFind { - source_account: "", - destination_account: "", + source_account: "".into(), + destination_account: "".into(), destination_amount: Currency::XRP(XRP::new()), id: None, ledger_hash: None, @@ -80,12 +81,12 @@ impl<'a> Model for RipplePathFind<'a> {} impl<'a> RipplePathFind<'a> { pub fn new( - source_account: &'a str, - destination_account: &'a str, + source_account: Cow<'a, str>, + destination_account: Cow<'a, str>, destination_amount: Currency<'a>, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, send_max: Option>, source_currencies: Option>>, ) -> Self { diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index f869a4ae..1b1d4ab1 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,7 +14,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ServerInfo<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request info. #[serde(default = "RequestMethod::server_info")] pub command: RequestMethod, @@ -31,7 +32,7 @@ impl<'a> Default for ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} impl<'a> ServerInfo<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::ServerInfo, diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 8f1997b8..8f6d3b4a 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -18,7 +19,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ServerState<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// The request method. #[serde(default = "RequestMethod::server_state")] pub command: RequestMethod, @@ -36,7 +37,7 @@ impl<'a> Default for ServerState<'a> { impl<'a> Model for ServerState<'a> {} impl<'a> ServerState<'a> { - pub fn new(id: Option<&'a str>) -> Self { + pub fn new(id: Option>) -> Self { Self { id, command: RequestMethod::ServerState, diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index 537125d7..d4301b7d 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -35,9 +36,9 @@ use crate::models::{requests::RequestMethod, Model}; pub struct Submit<'a> { /// Hex representation of the signed transaction to submit. /// This can also be a multi-signed transaction. - pub tx_blob: &'a str, + pub tx_blob: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// If true, and the transaction fails locally, do not retry /// or relay the transaction to other servers pub fail_hard: Option, @@ -49,7 +50,7 @@ pub struct Submit<'a> { impl<'a> Default for Submit<'a> { fn default() -> Self { Submit { - tx_blob: "", + tx_blob: "".into(), id: None, fail_hard: None, command: RequestMethod::Submit, @@ -60,7 +61,7 @@ impl<'a> Default for Submit<'a> { impl<'a> Model for Submit<'a> {} impl<'a> Submit<'a> { - pub fn new(tx_blob: &'a str, id: Option<&'a str>, fail_hard: Option) -> Self { + pub fn new(tx_blob: Cow<'a, str>, id: Option>, fail_hard: Option) -> Self { Self { tx_blob, id, diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index bb1d0cf6..95558242 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -18,7 +19,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct SubmitMultisigned<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// If true, and the transaction fails locally, do not /// retry or relay the transaction to other servers. pub fail_hard: Option, @@ -40,7 +41,7 @@ impl<'a> Default for SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} impl<'a> SubmitMultisigned<'a> { - pub fn new(id: Option<&'a str>, fail_hard: Option) -> Self { + pub fn new(id: Option>, fail_hard: Option) -> Self { Self { id, fail_hard, diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index 60adf24d..dcc6ab89 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -14,7 +15,7 @@ use crate::models::{currency::Currency, default_false, requests::RequestMethod, pub struct SubscribeBook<'a> { pub taker_gets: Currency<'a>, pub taker_pays: Currency<'a>, - pub taker: &'a str, + pub taker: Cow<'a, str>, #[serde(default = "default_false")] pub snapshot: Option, #[serde(default = "default_false")] @@ -47,7 +48,7 @@ pub enum StreamParameter { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Subscribe<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// Array of objects defining order books to monitor for /// updates, as detailed below. pub books: Option>>, @@ -57,17 +58,17 @@ pub struct Subscribe<'a> { /// for validated transactions. The addresses must be in the /// XRP Ledger's base58 format. The server sends a notification /// for any transaction that affects at least one of these accounts. - pub accounts: Option>, + pub accounts: Option>>, /// Like accounts, but include transactions that are not /// yet finalized. - pub accounts_proposed: Option>, + pub accounts_proposed: Option>>, /// (Optional for Websocket; Required otherwise) URL where the server /// sends a JSON-RPC callbacks for each event. Admin-only. - pub url: Option<&'a str>, + pub url: Option>, /// Username to provide for basic authentication at the callback URL. - pub url_username: Option<&'a str>, + pub url_username: Option>, /// Password to provide for basic authentication at the callback URL. - pub url_password: Option<&'a str>, + pub url_password: Option>, /// The request method. // #[serde(skip_serializing)] #[serde(default = "RequestMethod::subscribe")] @@ -94,14 +95,14 @@ impl<'a> Model for Subscribe<'a> {} impl<'a> Subscribe<'a> { pub fn new( - id: Option<&'a str>, + id: Option>, books: Option>>, streams: Option>, - accounts: Option>, - accounts_proposed: Option>, - url: Option<&'a str>, - url_username: Option<&'a str>, - url_password: Option<&'a str>, + accounts: Option>>, + accounts_proposed: Option>>, + url: Option>, + url_username: Option>, + url_password: Option>, ) -> Self { Self { id, diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index b6687476..1ec1844c 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -15,14 +16,14 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct TransactionEntry<'a> { /// Unique hash of the transaction you are looking up. - pub tx_hash: &'a str, + pub tx_hash: Cow<'a, str>, /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option<&'a str>, + pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. - pub ledger_index: Option<&'a str>, + pub ledger_index: Option>, /// The request method. #[serde(default = "RequestMethod::transaction_entry")] pub command: RequestMethod, @@ -31,7 +32,7 @@ pub struct TransactionEntry<'a> { impl<'a> Default for TransactionEntry<'a> { fn default() -> Self { TransactionEntry { - tx_hash: "", + tx_hash: "".into(), id: None, ledger_hash: None, ledger_index: None, @@ -44,10 +45,10 @@ impl<'a> Model for TransactionEntry<'a> {} impl<'a> TransactionEntry<'a> { pub fn new( - tx_hash: &'a str, - id: Option<&'a str>, - ledger_hash: Option<&'a str>, - ledger_index: Option<&'a str>, + tx_hash: Cow<'a, str>, + id: Option>, + ledger_hash: Option>, + ledger_index: Option>, ) -> Self { Self { tx_hash, diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 2425536b..13e4de6e 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -11,7 +12,7 @@ use crate::models::{requests::RequestMethod, Model}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Tx<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// If true, return transaction data and metadata as binary /// serialized to hexadecimal strings. If false, return /// transaction data and metadata as JSON. The default is false. @@ -47,7 +48,7 @@ impl<'a> Model for Tx<'a> {} impl<'a> Tx<'a> { pub fn new( - id: Option<&'a str>, + id: Option>, binary: Option, min_ledger: Option, max_ledger: Option, diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index 8ffc8210..4d6a9f3c 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -34,7 +35,7 @@ pub struct UnsubscribeBook<'a> { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Unsubscribe<'a> { /// The unique request id. - pub id: Option<&'a str>, + pub id: Option>, /// Array of objects defining order books to unsubscribe /// from, as explained below. pub books: Option>>, @@ -47,12 +48,12 @@ pub struct Unsubscribe<'a> { /// those messages if you previously subscribed to those accounts /// specifically. You cannot use this to filter accounts out of /// the general transactions stream.) - pub accounts: Option>, + pub accounts: Option>>, /// Like accounts, but for accounts_proposed subscriptions that /// included not-yet-validated transactions. - pub accounts_proposed: Option>, + pub accounts_proposed: Option>>, #[serde(skip_serializing)] - pub broken: Option<&'a str>, + pub broken: Option>, /// The request method. #[serde(default = "RequestMethod::unsubscribe")] pub command: RequestMethod, @@ -76,12 +77,12 @@ impl<'a> Model for Unsubscribe<'a> {} impl<'a> Unsubscribe<'a> { pub fn new( - id: Option<&'a str>, + id: Option>, books: Option>>, streams: Option>, - accounts: Option>, - accounts_proposed: Option>, - broken: Option<&'a str>, + accounts: Option>>, + accounts_proposed: Option>>, + broken: Option>, ) -> Self { Self { id, diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 51769077..6a7d57ca 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -30,7 +31,7 @@ pub struct AccountDelete<'a> { #[serde(default = "TransactionType::account_set")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -50,11 +51,11 @@ pub struct AccountDelete<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -66,7 +67,7 @@ pub struct AccountDelete<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -83,7 +84,7 @@ pub struct AccountDelete<'a> { /// The address of an account to receive any leftover XRP after /// deleting the sending account. Must be a funded account in /// the ledger, and must not be the sending account. - pub destination: &'a str, + pub destination: Cow<'a, str>, /// Arbitrary destination tag that identifies a hosted /// recipient or other information for the recipient /// of the deleted account's leftover XRP. @@ -122,16 +123,16 @@ impl<'a> Transaction for AccountDelete<'a> { impl<'a> AccountDelete<'a> { pub fn new( - account: &'a str, - destination: &'a str, + account: Cow<'a, str>, + destination: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, destination_tag: Option, @@ -163,8 +164,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = AccountDelete::new( - "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm", - "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe", + "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm".into(), + "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), Some("2000000".into()), Some(2470665), None, @@ -188,8 +189,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = AccountDelete::new( - "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm", - "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe", + "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm".into(), + "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), Some("2000000".into()), Some(2470665), None, diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 14303958..3e5399bb 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -84,7 +85,7 @@ pub struct AccountSet<'a> { #[serde(default = "TransactionType::account_set")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -104,11 +105,11 @@ pub struct AccountSet<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -120,7 +121,7 @@ pub struct AccountSet<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -141,22 +142,22 @@ pub struct AccountSet<'a> { /// The domain that owns this account, as a string of hex /// representing the ASCII for the domain in lowercase. /// Cannot be more than 256 bytes in length. - pub domain: Option<&'a str>, + pub domain: Option>, /// Hash of an email address to be used for generating an /// avatar image. Conventionally, clients use Gravatar /// to display this image. - pub email_hash: Option<&'a str>, + pub email_hash: Option>, /// Public key for sending encrypted messages to this account. /// To set the key, it must be exactly 33 bytes, with the /// first byte indicating the key type: 0x02 or 0x03 for /// secp256k1 keys, 0xED for Ed25519 keys. To remove the /// key, use an empty value. - pub message_key: Option<&'a str>, + pub message_key: Option>, /// Sets an alternate account that is allowed to mint NFTokens /// on this account's behalf using NFTokenMint's Issuer field. /// This field is part of the experimental XLS-20 standard /// for non-fungible tokens. - pub nftoken_minter: Option<&'a str>, + pub nftoken_minter: Option>, /// Flag to enable for this account. pub set_flag: Option, /// The fee to charge when users transfer this account's tokens, @@ -261,17 +262,17 @@ impl<'a> AccountSetError for AccountSet<'a> { if let Some(tick_size) = self.tick_size { if tick_size > MAX_TICK_SIZE { Err(XRPLAccountSetException::ValueTooHigh { - field: "tick_size", + field: "tick_size".into(), max: MAX_TICK_SIZE, found: tick_size, - resource: "", + resource: "".into(), }) } else if tick_size < MIN_TICK_SIZE && tick_size != DISABLE_TICK_SIZE { Err(XRPLAccountSetException::ValueTooLow { - field: "tick_size", + field: "tick_size".into(), min: MIN_TICK_SIZE, found: tick_size, - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -285,19 +286,19 @@ impl<'a> AccountSetError for AccountSet<'a> { if let Some(transfer_rate) = self.transfer_rate { if transfer_rate > MAX_TRANSFER_RATE { Err(XRPLAccountSetException::ValueTooHigh { - field: "transfer_rate", + field: "transfer_rate".into(), max: MAX_TRANSFER_RATE, found: transfer_rate, - resource: "", + resource: "".into(), }) } else if transfer_rate < MIN_TRANSFER_RATE && transfer_rate != SPECIAL_CASE_TRANFER_RATE { Err(XRPLAccountSetException::ValueTooLow { - field: "transfer_rate", + field: "transfer_rate".into(), min: MIN_TRANSFER_RATE, found: transfer_rate, - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -308,20 +309,20 @@ impl<'a> AccountSetError for AccountSet<'a> { } fn _get_domain_error(&self) -> Result<(), XRPLAccountSetException> { - if let Some(domain) = self.domain { + if let Some(domain) = self.domain.clone() { if domain.to_lowercase().as_str() != domain { Err(XRPLAccountSetException::InvalidValueFormat { - field: "domain", + field: "domain".into(), found: domain, - format: "lowercase", - resource: "", + format: "lowercase".into(), + resource: "".into(), }) } else if domain.len() > MAX_DOMAIN_LENGTH { Err(XRPLAccountSetException::ValueTooLong { - field: "domain", + field: "domain".into(), max: MAX_DOMAIN_LENGTH, found: domain.len(), - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -336,7 +337,7 @@ impl<'a> AccountSetError for AccountSet<'a> { { Err(XRPLAccountSetException::SetAndUnsetSameFlag { found: self.clear_flag.clone().unwrap(), - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -344,24 +345,24 @@ impl<'a> AccountSetError for AccountSet<'a> { } fn _get_nftoken_minter_error(&self) -> Result<(), XRPLAccountSetException> { - if let Some(_nftoken_minter) = self.nftoken_minter { + if let Some(_nftoken_minter) = self.nftoken_minter.clone() { if self.set_flag.is_none() { if let Some(clear_flag) = &self.clear_flag { match clear_flag { AccountSetFlag::AsfAuthorizedNFTokenMinter => { Err(XRPLAccountSetException::SetFieldWhenUnsetRequiredFlag { - field: "nftoken_minter", + field: "nftoken_minter".into(), flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, - resource: "", + resource: "".into(), }) } _ => Ok(()), } } else { Err(XRPLAccountSetException::FieldRequiresFlag { - field: "set_flag", + field: "set_flag".into(), flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, - resource: "", + resource: "".into(), }) } } else { @@ -372,8 +373,8 @@ impl<'a> AccountSetError for AccountSet<'a> { AccountSetFlag::AsfAuthorizedNFTokenMinter => { Err(XRPLAccountSetException::FlagRequiresField { flag: AccountSetFlag::AsfAuthorizedNFTokenMinter, - field: "nftoken_minter", - resource: "", + field: "nftoken_minter".into(), + resource: "".into(), }) } _ => Ok(()), @@ -386,26 +387,26 @@ impl<'a> AccountSetError for AccountSet<'a> { impl<'a> AccountSet<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, clear_flag: Option, - domain: Option<&'a str>, - email_hash: Option<&'a str>, - message_key: Option<&'a str>, + domain: Option>, + email_hash: Option>, + message_key: Option>, set_flag: Option, transfer_rate: Option, tick_size: Option, - nftoken_minter: Option<&'a str>, + nftoken_minter: Option>, ) -> Self { Self { transaction_type: TransactionType::AccountSet, @@ -453,7 +454,7 @@ mod test_account_set_errors { fn test_tick_size_error() { let mut account_set = AccountSet { transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -495,7 +496,7 @@ mod test_account_set_errors { fn test_transfer_rate_error() { let mut account_set = AccountSet { transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -537,7 +538,7 @@ mod test_account_set_errors { fn test_domain_error() { let mut account_set = AccountSet { transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -558,7 +559,7 @@ mod test_account_set_errors { tick_size: None, nftoken_minter: None, }; - let domain_not_lowercase = Some("https://Example.com/"); + let domain_not_lowercase = Some("https://Example.com/".into()); account_set.domain = domain_not_lowercase; assert_eq!( @@ -566,7 +567,7 @@ mod test_account_set_errors { "The value of the field `domain` does not have the correct format (expected lowercase, found https://Example.com/). For more information see: " ); - let domain_too_long = Some("https://example.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + let domain_too_long = Some("https://example.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()); account_set.domain = domain_too_long; assert_eq!( @@ -579,7 +580,7 @@ mod test_account_set_errors { fn test_flag_error() { let account_set = AccountSet { transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -611,7 +612,7 @@ mod test_account_set_errors { fn test_asf_authorized_nftoken_minter_error() { let mut account_set = AccountSet { transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -632,7 +633,7 @@ mod test_account_set_errors { tick_size: None, nftoken_minter: None, }; - account_set.nftoken_minter = Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK"); + account_set.nftoken_minter = Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()); assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), @@ -648,7 +649,7 @@ mod test_account_set_errors { ); account_set.set_flag = None; - account_set.nftoken_minter = Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK"); + account_set.nftoken_minter = Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()); account_set.clear_flag = Some(AccountSetFlag::AsfAuthorizedNFTokenMinter); assert_eq!( @@ -665,7 +666,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = AccountSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), Some(5), None, @@ -678,9 +679,9 @@ mod test_serde { None, None, None, - Some("6578616D706C652E636F6D"), + Some("6578616D706C652E636F6D".into()), None, - Some("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"), + Some("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB".into()), Some(AccountSetFlag::AsfAccountTxnID), None, None, @@ -697,7 +698,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = AccountSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), Some(5), None, @@ -710,9 +711,9 @@ mod test_serde { None, None, None, - Some("6578616D706C652E636F6D"), + Some("6578616D706C652E636F6D".into()), None, - Some("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"), + Some("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB".into()), Some(AccountSetFlag::AsfAccountTxnID), None, None, diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 30ff1535..d11fd120 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -30,7 +31,7 @@ pub struct CheckCancel<'a> { #[serde(default = "TransactionType::check_cancel")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -50,11 +51,11 @@ pub struct CheckCancel<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -66,7 +67,7 @@ pub struct CheckCancel<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -81,7 +82,7 @@ pub struct CheckCancel<'a> { // See CheckCancel fields: // `` #[serde(rename = "CheckID")] - pub check_id: &'a str, + pub check_id: Cow<'a, str>, } impl<'a> Default for CheckCancel<'a> { @@ -115,16 +116,16 @@ impl<'a> Transaction for CheckCancel<'a> { impl<'a> CheckCancel<'a> { pub fn new( - account: &'a str, - check_id: &'a str, + account: Cow<'a, str>, + check_id: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, ) -> Self { @@ -154,8 +155,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = CheckCancel::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo", - "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0", + "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), + "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), Some("12".into()), None, None, @@ -178,8 +179,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = CheckCancel::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo", - "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0", + "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), + "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), Some("12".into()), None, None, diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index eb773825..fffca38b 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -1,5 +1,6 @@ use crate::Err; use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -34,7 +35,7 @@ pub struct CheckCash<'a> { #[serde(default = "TransactionType::check_cash")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -54,11 +55,11 @@ pub struct CheckCash<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -70,7 +71,7 @@ pub struct CheckCash<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -85,7 +86,7 @@ pub struct CheckCash<'a> { /// See CheckCash fields: /// `` #[serde(rename = "CheckID")] - pub check_id: &'a str, + pub check_id: Cow<'a, str>, pub amount: Option>, pub deliver_min: Option>, } @@ -134,9 +135,9 @@ impl<'a> CheckCashError for CheckCash<'a> { || (self.amount.is_some() && self.deliver_min.is_some()) { Err(XRPLCheckCashException::DefineExactlyOneOf { - field1: "amount", - field2: "deliver_min", - resource: "", + field1: "amount".into(), + field2: "deliver_min".into(), + resource: "".into(), }) } else { Ok(()) @@ -146,16 +147,16 @@ impl<'a> CheckCashError for CheckCash<'a> { impl<'a> CheckCash<'a> { pub fn new( - account: &'a str, - check_id: &'a str, + account: Cow<'a, str>, + check_id: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, amount: Option>, @@ -197,7 +198,7 @@ mod test_check_cash_error { fn test_amount_and_deliver_min_error() { let check_cash = CheckCash { transaction_type: TransactionType::CheckCash, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -209,7 +210,7 @@ mod test_check_cash_error { flags: None, memos: None, signers: None, - check_id: "", + check_id: "".into(), amount: None, deliver_min: None, }; @@ -230,8 +231,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = CheckCash::new( - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy", - "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334", + "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), + "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334".into(), Some("12".into()), None, None, @@ -256,8 +257,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = CheckCash::new( - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy", - "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334", + "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), + "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334".into(), Some("12".into()), None, None, diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index cf1c6149..6b861be6 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -29,7 +30,7 @@ pub struct CheckCreate<'a> { #[serde(default = "TransactionType::check_create")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -49,11 +50,11 @@ pub struct CheckCreate<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -65,7 +66,7 @@ pub struct CheckCreate<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -79,12 +80,12 @@ pub struct CheckCreate<'a> { /// /// See CheckCreate fields: /// `` - pub destination: &'a str, + pub destination: Cow<'a, str>, pub send_max: Amount<'a>, pub destination_tag: Option, pub expiration: Option, #[serde(rename = "InvoiceID")] - pub invoice_id: Option<&'a str>, + pub invoice_id: Option>, } impl<'a> Default for CheckCreate<'a> { @@ -122,22 +123,22 @@ impl<'a> Transaction for CheckCreate<'a> { impl<'a> CheckCreate<'a> { pub fn new( - account: &'a str, - destination: &'a str, + account: Cow<'a, str>, + destination: Cow<'a, str>, send_max: Amount<'a>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, destination_tag: Option, expiration: Option, - invoice_id: Option<&'a str>, + invoice_id: Option>, ) -> Self { Self { transaction_type: TransactionType::CheckCreate, @@ -171,8 +172,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = CheckCreate::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo", - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy", + "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), + "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), Amount::XRPAmount(XRPAmount::from("100000000")), Some("12".into()), None, @@ -186,7 +187,7 @@ mod test_serde { None, Some(1), Some(570113521), - Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"), + Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B".into()), ); let default_json = r#"{"TransactionType":"CheckCreate","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Fee":"12","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"}"#; @@ -199,8 +200,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = CheckCreate::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo", - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy", + "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), + "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), Amount::XRPAmount(XRPAmount::from("100000000")), Some("12".into()), None, @@ -214,7 +215,7 @@ mod test_serde { None, Some(1), Some(570113521), - Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"), + Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B".into()), ); let default_json = r#"{"TransactionType":"CheckCreate","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B","DestinationTag":1,"Fee":"12"}"#; diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index a5a2daf2..a5e85944 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -1,5 +1,6 @@ use crate::Err; use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -31,7 +32,7 @@ pub struct DepositPreauth<'a> { #[serde(default = "TransactionType::deposit_preauth")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -51,11 +52,11 @@ pub struct DepositPreauth<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -67,7 +68,7 @@ pub struct DepositPreauth<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -81,8 +82,8 @@ pub struct DepositPreauth<'a> { /// /// See DepositPreauth fields: /// `` - pub authorize: Option<&'a str>, - pub unauthorize: Option<&'a str>, + pub authorize: Option>, + pub unauthorize: Option>, } impl<'a> Default for DepositPreauth<'a> { @@ -128,9 +129,9 @@ impl<'a> DepositPreauthError for DepositPreauth<'a> { || (self.authorize.is_some() && self.unauthorize.is_some()) { Err(XRPLDepositPreauthException::DefineExactlyOneOf { - field1: "authorize", - field2: "unauthorize", - resource: "", + field1: "authorize".into(), + field2: "unauthorize".into(), + resource: "".into(), }) } else { Ok(()) @@ -140,19 +141,19 @@ impl<'a> DepositPreauthError for DepositPreauth<'a> { impl<'a> DepositPreauth<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, - authorize: Option<&'a str>, - unauthorize: Option<&'a str>, + authorize: Option>, + unauthorize: Option>, ) -> Self { Self { transaction_type: TransactionType::DepositPreauth, @@ -190,7 +191,7 @@ mod test_deposit_preauth_exception { fn test_authorize_and_unauthorize_error() { let deposit_preauth = DepositPreauth { transaction_type: TransactionType::DepositPreauth, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -220,7 +221,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = DepositPreauth::new( - "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8", + "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8".into(), Some("10".into()), Some(2), None, @@ -231,7 +232,7 @@ mod test_serde { None, None, None, - Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"), + Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de".into()), None, ); let default_json = r#"{"TransactionType":"DepositPreauth","Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","Fee":"10","Sequence":2,"Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"}"#; @@ -245,7 +246,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = DepositPreauth::new( - "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8", + "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8".into(), Some("10".into()), Some(2), None, @@ -256,7 +257,7 @@ mod test_serde { None, None, None, - Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"), + Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de".into()), None, ); let default_json = r#"{"TransactionType":"DepositPreauth","Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de","Fee":"10","Sequence":2}"#; diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 87f012cb..047e031c 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -27,7 +28,7 @@ pub struct EscrowCancel<'a> { #[serde(default = "TransactionType::escrow_cancel")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -47,11 +48,11 @@ pub struct EscrowCancel<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -63,7 +64,7 @@ pub struct EscrowCancel<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -77,7 +78,7 @@ pub struct EscrowCancel<'a> { /// /// See EscrowCancel fields: /// `` - pub owner: &'a str, + pub owner: Cow<'a, str>, pub offer_sequence: u32, } @@ -113,17 +114,17 @@ impl<'a> Transaction for EscrowCancel<'a> { impl<'a> EscrowCancel<'a> { pub fn new( - account: &'a str, - owner: &'a str, + account: Cow<'a, str>, + owner: Cow<'a, str>, offer_sequence: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, ) -> Self { @@ -154,8 +155,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = EscrowCancel::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, None, None, @@ -179,8 +180,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = EscrowCancel::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, None, None, diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index dec31b98..ae9329ad 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -1,5 +1,6 @@ use crate::Err; use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -30,7 +31,7 @@ pub struct EscrowCreate<'a> { #[serde(default = "TransactionType::escrow_create")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -50,11 +51,11 @@ pub struct EscrowCreate<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -66,7 +67,7 @@ pub struct EscrowCreate<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -81,11 +82,11 @@ pub struct EscrowCreate<'a> { /// See EscrowCreate fields: /// `` pub amount: XRPAmount<'a>, - pub destination: &'a str, + pub destination: Cow<'a, str>, pub destination_tag: Option, pub cancel_after: Option, pub finish_after: Option, - pub condition: Option<&'a str>, + pub condition: Option>, } impl<'a> Default for EscrowCreate<'a> { @@ -134,11 +135,11 @@ impl<'a> EscrowCreateError for EscrowCreate<'a> { if let (Some(finish_after), Some(cancel_after)) = (self.finish_after, self.cancel_after) { if finish_after >= cancel_after { Err(XRPLEscrowCreateException::ValueBelowValue { - field1: "cancel_after", - field2: "finish_after", + field1: "cancel_after".into(), + field2: "finish_after".into(), field1_val: cancel_after, field2_val: finish_after, - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -151,23 +152,23 @@ impl<'a> EscrowCreateError for EscrowCreate<'a> { impl<'a> EscrowCreate<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, amount: XRPAmount<'a>, - destination: &'a str, + destination: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, destination_tag: Option, cancel_after: Option, finish_after: Option, - condition: Option<&'a str>, + condition: Option>, ) -> Self { Self { transaction_type: TransactionType::EscrowCreate, @@ -211,7 +212,7 @@ mod test_escrow_create_errors { fn test_cancel_after_error() { let escrow_create = EscrowCreate { transaction_type: TransactionType::EscrowCreate, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -224,7 +225,7 @@ mod test_escrow_create_errors { memos: None, signers: None, amount: XRPAmount::from("100000000"), - destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), destination_tag: None, cancel_after: Some(13298498), finish_after: Some(14359039), @@ -245,9 +246,9 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = EscrowCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), None, None, None, @@ -261,7 +262,7 @@ mod test_serde { Some(23480), Some(533257958), Some(533171558), - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"), + Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), ); let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; @@ -274,9 +275,9 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = EscrowCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), None, None, None, @@ -290,7 +291,7 @@ mod test_serde { Some(23480), Some(533257958), Some(533171558), - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"), + Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), ); let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","DestinationTag":23480,"SourceTag":11747}"#; diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index a21e3af8..db5e7d46 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -1,4 +1,5 @@ use crate::Err; +use alloc::borrow::Cow; use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -30,7 +31,7 @@ pub struct EscrowFinish<'a> { #[serde(default = "TransactionType::escrow_finish")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -50,11 +51,11 @@ pub struct EscrowFinish<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -66,7 +67,7 @@ pub struct EscrowFinish<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -80,10 +81,10 @@ pub struct EscrowFinish<'a> { /// /// See EscrowFinish fields: /// `` - pub owner: &'a str, + pub owner: Cow<'a, str>, pub offer_sequence: u32, - pub condition: Option<&'a str>, - pub fulfillment: Option<&'a str>, + pub condition: Option>, + pub fulfillment: Option>, } impl<'a> Default for EscrowFinish<'a> { @@ -131,9 +132,9 @@ impl<'a> EscrowFinishError for EscrowFinish<'a> { || (self.condition.is_none() && self.condition.is_some()) { Err(XRPLEscrowFinishException::FieldRequiresField { - field1: "condition", - field2: "fulfillment", - resource: "", + field1: "condition".into(), + field2: "fulfillment".into(), + resource: "".into(), }) } else { Ok(()) @@ -143,21 +144,21 @@ impl<'a> EscrowFinishError for EscrowFinish<'a> { impl<'a> EscrowFinish<'a> { pub fn new( - account: &'a str, - owner: &'a str, + account: Cow<'a, str>, + owner: Cow<'a, str>, offer_sequence: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, - condition: Option<&'a str>, - fulfillment: Option<&'a str>, + condition: Option>, + fulfillment: Option>, ) -> Self { Self { transaction_type: TransactionType::EscrowFinish, @@ -197,7 +198,7 @@ mod test_escrow_finish_errors { fn test_condition_and_fulfillment_error() { let escrow_finish = EscrowFinish { transaction_type: TransactionType::EscrowCancel, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -209,10 +210,10 @@ mod test_escrow_finish_errors { flags: None, memos: None, signers: None, - owner: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + owner: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), offer_sequence: 10, condition: Some( - "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100", + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into(), ), fulfillment: None, }; @@ -231,8 +232,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = EscrowFinish::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, None, None, @@ -244,8 +245,8 @@ mod test_serde { None, None, None, - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"), - Some("A0028000"), + Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some("A0028000".into()), ); let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; @@ -258,8 +259,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = EscrowFinish::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, None, None, @@ -271,8 +272,8 @@ mod test_serde { None, None, None, - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"), - Some("A0028000"), + Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some("A0028000".into()), ); let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index 9ebf9caf..eefd58fd 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -26,61 +26,61 @@ pub enum XRPLAccountSetException<'a> { /// A fields value exceeds its maximum value. #[error("The value of the field `{field:?}` is defined above its maximum (max {max:?}, found {found:?}). For more information see: {resource:?}")] ValueTooHigh { - field: &'a str, + field: Cow<'a, str>, max: u32, found: u32, - resource: &'a str, + resource: Cow<'a, str>, }, /// A fields value exceeds its minimum value. #[error("The value of the field `{field:?}` is defined below its minimum (min {min:?}, found {found:?}). For more information see: {resource:?}")] ValueTooLow { - field: &'a str, + field: Cow<'a, str>, min: u32, found: u32, - resource: &'a str, + resource: Cow<'a, str>, }, /// A fields value exceeds its maximum character length. #[error("The value of the field `{field:?}` exceeds its maximum length of characters (max {max:?}, found {found:?}). For more information see: {resource:?}")] ValueTooLong { - field: &'a str, + field: Cow<'a, str>, max: usize, found: usize, - resource: &'a str, + resource: Cow<'a, str>, }, /// A fields value doesn't match its required format. #[error("The value of the field `{field:?}` does not have the correct format (expected {format:?}, found {found:?}). For more information see: {resource:?}")] InvalidValueFormat { - field: &'a str, - format: &'a str, - found: &'a str, - resource: &'a str, + field: Cow<'a, str>, + format: Cow<'a, str>, + found: Cow<'a, str>, + resource: Cow<'a, str>, }, /// A field can only be defined if a transaction flag is set. #[error("For the field `{field:?}` to be defined it is required to set the flag `{flag:?}`. For more information see: {resource:?}")] FieldRequiresFlag { - field: &'a str, + field: Cow<'a, str>, flag: AccountSetFlag, - resource: &'a str, + resource: Cow<'a, str>, }, /// An account set flag can only be set if a field is defined. #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`. For more information see: {resource:?}")] FlagRequiresField { flag: AccountSetFlag, - field: &'a str, - resource: &'a str, + field: Cow<'a, str>, + resource: Cow<'a, str>, }, /// Am account set flag can not be set and unset at the same time. #[error("A flag cannot be set and unset at the same time (found {found:?}). For more information see: {resource:?}")] SetAndUnsetSameFlag { found: AccountSetFlag, - resource: &'a str, + resource: Cow<'a, str>, }, /// A field was defined and an account set flag that is required for that field was unset. #[error("The field `{field:?}` cannot be defined if its required flag `{flag:?}` is being unset. For more information see: {resource:?}")] SetFieldWhenUnsetRequiredFlag { - field: &'a str, + field: Cow<'a, str>, flag: AccountSetFlag, - resource: &'a str, + resource: Cow<'a, str>, }, } @@ -92,9 +92,9 @@ pub enum XRPLCheckCashException<'a> { /// A field cannot be defined with other fields. #[error("The field `{field1:?}` can not be defined with `{field2:?}`. Define exactly one of them. For more information see: {resource:?}")] DefineExactlyOneOf { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -103,9 +103,9 @@ pub enum XRPLDepositPreauthException<'a> { /// A field cannot be defined with other fields. #[error("The field `{field1:?}` can not be defined with `{field2:?}`. Define exactly one of them. For more information see: {resource:?}")] DefineExactlyOneOf { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -117,11 +117,11 @@ pub enum XRPLEscrowCreateException<'a> { /// A fields value cannot be below another fields value. #[error("The value of the field `{field1:?}` is not allowed to be below the value of the field `{field2:?}` (max {field2_val:?}, found {field1_val:?}). For more information see: {resource:?}")] ValueBelowValue { - field1: &'a str, - field2: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, field1_val: u32, field2_val: u32, - resource: &'a str, + resource: Cow<'a, str>, }, } @@ -133,9 +133,9 @@ pub enum XRPLEscrowFinishException<'a> { /// For a field to be defined it also needs another field to be defined. #[error("For the field `{field1:?}` to be defined it is required to also define the field `{field2:?}`. For more information see: {resource:?}")] FieldRequiresField { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -147,13 +147,13 @@ pub enum XRPLNFTokenAcceptOfferException<'a> { /// Define at least one of the fields. #[error("Define at least one of the fields `{field1:?}` and `{field2:?}`. For more information see: {resource:?}")] DefineOneOf { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, /// The value can not be zero. #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { field: &'a str, resource: &'a str }, + ValueZero { field: Cow<'a, str>, resource: Cow<'a, str> }, } #[cfg(feature = "std")] @@ -164,9 +164,9 @@ pub enum XRPLNFTokenCancelOfferException<'a> { /// A collection was defined to be empty. #[error("The value of the field `{field:?}` is not allowed to be empty (type `{r#type:?}`). If the field is optional, define it to be `None`. For more information see: {resource:?}")] CollectionEmpty { - field: &'a str, - r#type: &'a str, - resource: &'a str, + field: Cow<'a, str>, + r#type: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -177,27 +177,27 @@ impl<'a> alloc::error::Error for XRPLNFTokenCancelOfferException<'a> {} pub enum XRPLNFTokenCreateOfferException<'a> { /// The value can not be zero. #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { field: &'a str, resource: &'a str }, + ValueZero { field: Cow<'a, str>, resource: Cow<'a, str> }, /// A fields value is not allowed to be the same as another fields value. #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`. For more information see: {resource:?}")] ValueEqualsValue { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, /// An optional value must be defined in a certain context. #[error("The optional field `{field:?}` is required to be defined for {context:?}. For more information see: {resource:?}")] OptionRequired { - field: &'a str, - context: &'a str, - resource: &'a str, + field: Cow<'a, str>, + context: Cow<'a, str>, + resource: Cow<'a, str>, }, /// An optional value is not allowed to be defined in a certain context. #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}. For more information see: {resource:?}")] IllegalOption { - field: &'a str, - context: &'a str, - resource: &'a str, + field: Cow<'a, str>, + context: Cow<'a, str>, + resource: Cow<'a, str>, }, } @@ -209,25 +209,25 @@ pub enum XRPLNFTokenMintException<'a> { /// A fields value is not allowed to be the same as another fields value. #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`. For more information see: {resource:?}")] ValueEqualsValue { - field1: &'a str, - field2: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + resource: Cow<'a, str>, }, /// A fields value exceeds its maximum value. #[error("The field `{field:?}` exceeds its maximum value (max {max:?}, found {found:?}). For more information see: {resource:?}")] ValueTooHigh { - field: &'a str, + field: Cow<'a, str>, max: u32, found: u32, - resource: &'a str, + resource: Cow<'a, str>, }, /// A fields value exceeds its maximum character length. #[error("The value of the field `{field:?}` exceeds its maximum length of characters (max {max:?}, found {found:?}). For more information see: {resource:?}")] ValueTooLong { - field: &'a str, + field: Cow<'a, str>, max: usize, found: usize, - resource: &'a str, + resource: Cow<'a, str>, }, } @@ -239,31 +239,31 @@ pub enum XRPLPaymentException<'a> { /// An optional value must be defined in a certain context. #[error("The optional field `{field:?}` is required to be defined for {context:?}. For more information see: {resource:?}")] OptionRequired { - field: &'a str, - context: &'a str, - resource: &'a str, + field: Cow<'a, str>, + context: Cow<'a, str>, + resource: Cow<'a, str>, }, /// An optional value is not allowed to be defined in a certain context. #[error("The optional field `{field:?}` is not allowed to be defined for {context:?}.For more information see: {resource:?}")] IllegalOption { - field: &'a str, - context: &'a str, - resource: &'a str, + field: Cow<'a, str>, + context: Cow<'a, str>, + resource: Cow<'a, str>, }, /// A fields value is not allowed to be the same as another fields value, in a certain context. #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`, for {context:?}. For more information see: {resource:?}")] ValueEqualsValueInContext { - field1: &'a str, - field2: &'a str, - context: &'a str, - resource: &'a str, + field1: Cow<'a, str>, + field2: Cow<'a, str>, + context: Cow<'a, str>, + resource: Cow<'a, str>, }, /// An account set flag can only be set if a field is defined. #[error("For the flag `{flag:?}` to be set it is required to define the field `{field:?}`. For more information see: {resource:?}")] FlagRequiresField { flag: PaymentFlag, - field: &'a str, - resource: &'a str, + field: Cow<'a, str>, + resource: Cow<'a, str>, }, } diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 328918a7..f8db8807 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -53,6 +53,7 @@ pub use ticket_create::*; pub use trust_set::*; use crate::serde_with_tag; +use alloc::borrow::Cow; use alloc::string::String; use derive_new::new; use serde::ser::SerializeMap; @@ -207,9 +208,9 @@ pub struct Memo { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, new)] #[serde(rename_all = "PascalCase")] pub struct Signer<'a> { - account: &'a str, - txn_signature: &'a str, - signing_pub_key: &'a str, + account: Cow<'a, str>, + txn_signature: Cow<'a, str>, + signing_pub_key: Cow<'a, str>, } /// Standard functions for transactions. diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index e6efac02..ebe0de06 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -140,9 +140,9 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { && self.nftoken_buy_offer.is_none() { Err(XRPLNFTokenAcceptOfferException::DefineOneOf { - field1: "nftoken_sell_offer", - field2: "nftoken_buy_offer", - resource: "", + field1: "nftoken_sell_offer".into(), + field2: "nftoken_buy_offer".into(), + resource: "".into(), }) } else { Ok(()) @@ -156,8 +156,8 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { Ok(nftoken_broker_fee_dec) => { if nftoken_broker_fee_dec.is_zero() { Err!(XRPLNFTokenAcceptOfferException::ValueZero { - field: "nftoken_broker_fee", - resource: "", + field: "nftoken_broker_fee".into(), + resource: "".into(), }) } else { Ok(()) @@ -230,7 +230,7 @@ mod test_nftoken_accept_offer_error { fn test_brokered_mode_error() { let nftoken_accept_offer = NFTokenAcceptOffer { transaction_type: TransactionType::NFTokenAcceptOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -257,7 +257,7 @@ mod test_nftoken_accept_offer_error { fn test_broker_fee_error() { let nftoken_accept_offer = NFTokenAcceptOffer { transaction_type: TransactionType::NFTokenAcceptOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -269,7 +269,7 @@ mod test_nftoken_accept_offer_error { flags: None, memos: None, signers: None, - nftoken_sell_offer: Some(""), + nftoken_sell_offer: Some("".into()), nftoken_buy_offer: None, nftoken_broker_fee: Some(Amount::XRPAmount(XRPAmount::from("0"))), }; @@ -291,7 +291,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = NFTokenAcceptOffer::new( - "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG", + "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG".into(), Some("12".into()), Some(68549302), Some(75447550), @@ -306,7 +306,7 @@ mod test_serde { None, )]), None, - Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"), + Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77".into()), None, None, ); @@ -321,7 +321,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = NFTokenAcceptOffer::new( - "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG", + "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG".into(), Some("12".into()), Some(68549302), Some(75447550), @@ -336,7 +336,7 @@ mod test_serde { None, )]), None, - Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"), + Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77".into()), None, None, ); diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 1c10bdb3..2a2b936b 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -28,7 +29,7 @@ pub struct NFTokenBurn<'a> { #[serde(default = "TransactionType::nftoken_burn")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -48,11 +49,11 @@ pub struct NFTokenBurn<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -64,7 +65,7 @@ pub struct NFTokenBurn<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -79,8 +80,8 @@ pub struct NFTokenBurn<'a> { /// See NFTokenBurn fields: /// `` #[serde(rename = "NFTokenID")] - pub nftoken_id: &'a str, - pub owner: Option<&'a str>, + pub nftoken_id: Cow<'a, str>, + pub owner: Option>, } impl<'a> Default for NFTokenBurn<'a> { @@ -115,19 +116,19 @@ impl<'a> Transaction for NFTokenBurn<'a> { impl<'a> NFTokenBurn<'a> { pub fn new( - account: &'a str, - nftoken_id: &'a str, + account: Cow<'a, str>, + nftoken_id: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, - owner: Option<&'a str>, + owner: Option>, ) -> Self { Self { transaction_type: TransactionType::NFTokenBurn, @@ -156,8 +157,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = NFTokenBurn::new( - "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2", - "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65", + "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2".into(), + "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), Some("10".into()), None, None, @@ -168,7 +169,7 @@ mod test_serde { None, None, None, - Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"), + Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), ); let default_json = r#"{"TransactionType":"NFTokenBurn","Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"}"#; @@ -181,8 +182,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = NFTokenBurn::new( - "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2", - "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65", + "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2".into(), + "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), Some("10".into()), None, None, @@ -193,7 +194,7 @@ mod test_serde { None, None, None, - Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"), + Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), ); let default_json = r#"{"TransactionType":"NFTokenBurn","Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"}"#; diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index b60480a8..fb738c23 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -1,6 +1,7 @@ use crate::Err; use alloc::vec::Vec; use anyhow::Result; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -30,7 +31,7 @@ pub struct NFTokenCancelOffer<'a> { #[serde(default = "TransactionType::nftoken_cancel_offer")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -50,11 +51,11 @@ pub struct NFTokenCancelOffer<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -66,7 +67,7 @@ pub struct NFTokenCancelOffer<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -126,9 +127,9 @@ impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { fn _get_nftoken_offers_error(&self) -> Result<(), XRPLNFTokenCancelOfferException> { if self.nftoken_offers.is_empty() { Err(XRPLNFTokenCancelOfferException::CollectionEmpty { - field: "nftoken_offers", - r#type: stringify!(Vec), - resource: "", + field: "nftoken_offers".into(), + r#type: stringify!(Vec).into(), + resource: "".into(), }) } else { Ok(()) @@ -138,16 +139,16 @@ impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { impl<'a> NFTokenCancelOffer<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, nftoken_offers: Vec<&'a str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, ) -> Self { @@ -187,7 +188,7 @@ mod test_nftoken_cancel_offer_error { fn test_nftoken_offer_error() { let nftoken_cancel_offer = NFTokenCancelOffer { transaction_type: TransactionType::NFTokenCancelOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -218,7 +219,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = NFTokenCancelOffer::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"], None, None, @@ -242,7 +243,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = NFTokenCancelOffer::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"], None, None, diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index ac2790d8..16886839 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -2,6 +2,7 @@ use alloc::vec::Vec; use anyhow::Result; use core::convert::TryInto; use rust_decimal::Decimal; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -54,7 +55,7 @@ pub struct NFTokenCreateOffer<'a> { #[serde(default = "TransactionType::nftoken_create_offer")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -74,11 +75,11 @@ pub struct NFTokenCreateOffer<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -90,7 +91,7 @@ pub struct NFTokenCreateOffer<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -107,11 +108,11 @@ pub struct NFTokenCreateOffer<'a> { /// See NFTokenCreateOffer fields: /// `` #[serde(rename = "NFTokenID")] - pub nftoken_id: &'a str, + pub nftoken_id: Cow<'a, str>, pub amount: Amount<'a>, - pub owner: Option<&'a str>, + pub owner: Option>, pub expiration: Option, - pub destination: Option<&'a str>, + pub destination: Option>, } impl<'a> Default for NFTokenCreateOffer<'a> { @@ -190,8 +191,8 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { )) && amount.is_zero() { Err!(XRPLNFTokenCreateOfferException::ValueZero { - field: "amount", - resource: "", + field: "amount".into(), + resource: "".into(), }) } else { Ok(()) @@ -204,12 +205,12 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { } fn _get_destination_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { - if let Some(destination) = self.destination { + if let Some(destination) = self.destination.clone() { if destination == self.account { Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { - field1: "destination", - field2: "account", - resource: "", + field1: "destination".into(), + field2: "account".into(), + resource: "".into(), }) } else { Ok(()) @@ -220,20 +221,20 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { } fn _get_owner_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { - if let Some(owner) = self.owner { + if let Some(owner) = self.owner.clone() { if self.has_flag(&Flag::NFTokenCreateOffer( NFTokenCreateOfferFlag::TfSellOffer, )) { Err(XRPLNFTokenCreateOfferException::IllegalOption { - field: "owner", - context: "NFToken sell offers", - resource: "", + field: "owner".into(), + context: "NFToken sell offers".into(), + resource: "".into(), }) } else if owner == self.account { Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { - field1: "owner", - field2: "account", - resource: "", + field1: "owner".into(), + field2: "account".into(), + resource: "".into(), }) } else { Ok(()) @@ -242,9 +243,9 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { NFTokenCreateOfferFlag::TfSellOffer, )) { Err(XRPLNFTokenCreateOfferException::OptionRequired { - field: "owner", - context: "NFToken buy offers", - resource: "", + field: "owner".into(), + context: "NFToken buy offers".into(), + resource: "".into(), }) } else { Ok(()) @@ -254,23 +255,23 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { impl<'a> NFTokenCreateOffer<'a> { pub fn new( - account: &'a str, - nftoken_id: &'a str, + account: Cow<'a, str>, + nftoken_id: Cow<'a, str>, amount: Amount<'a>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, - owner: Option<&'a str>, + owner: Option>, expiration: Option, - destination: Option<&'a str>, + destination: Option>, ) -> Self { Self { transaction_type: TransactionType::NFTokenCreateOffer, @@ -317,7 +318,7 @@ mod test_nftoken_create_offer_error { fn test_amount_error() { let nftoken_create_offer = NFTokenCreateOffer { transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -329,7 +330,7 @@ mod test_nftoken_create_offer_error { flags: None, memos: None, signers: None, - nftoken_id: "", + nftoken_id: "".into(), amount: Amount::XRPAmount(XRPAmount::from("0")), owner: None, expiration: None, @@ -350,7 +351,7 @@ mod test_nftoken_create_offer_error { fn test_destination_error() { let nftoken_create_offer = NFTokenCreateOffer { transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -362,11 +363,11 @@ mod test_nftoken_create_offer_error { flags: None, memos: None, signers: None, - nftoken_id: "", + nftoken_id: "".into(), amount: Amount::XRPAmount(XRPAmount::from("1")), owner: None, expiration: None, - destination: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb"), + destination: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), }; assert_eq!( @@ -379,7 +380,7 @@ mod test_nftoken_create_offer_error { fn test_owner_error() { let mut nftoken_create_offer = NFTokenCreateOffer { transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -391,9 +392,9 @@ mod test_nftoken_create_offer_error { flags: None, memos: None, signers: None, - nftoken_id: "", + nftoken_id: "".into(), amount: Amount::XRPAmount(XRPAmount::from("1")), - owner: Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK"), + owner: Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()), expiration: None, destination: None, }; @@ -413,7 +414,7 @@ mod test_nftoken_create_offer_error { "The optional field `owner` is required to be defined for NFToken buy offers. For more information see: " ); - nftoken_create_offer.owner = Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb"); + nftoken_create_offer.owner = Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()); assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), @@ -432,8 +433,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = NFTokenCreateOffer::new( - "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX", - "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007", + "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX".into(), + "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007".into(), Amount::XRPAmount(XRPAmount::from("1000000")), None, None, @@ -461,8 +462,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = NFTokenCreateOffer::new( - "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX", - "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007", + "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX".into(), + "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007".into(), Amount::XRPAmount(XRPAmount::from("1000000")), None, None, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index f244ea59..ddc16ee8 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; use anyhow::Result; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -60,7 +61,7 @@ pub struct NFTokenMint<'a> { #[serde(default = "TransactionType::nftoken_mint")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -80,11 +81,11 @@ pub struct NFTokenMint<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -96,7 +97,7 @@ pub struct NFTokenMint<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -114,10 +115,10 @@ pub struct NFTokenMint<'a> { /// `` #[serde(rename = "NFTokenTaxon")] pub nftoken_taxon: u32, - pub issuer: Option<&'a str>, + pub issuer: Option>, pub transfer_fee: Option, #[serde(rename = "URI")] - pub uri: Option<&'a str>, + pub uri: Option>, } impl<'a> Default for NFTokenMint<'a> { @@ -184,12 +185,12 @@ impl<'a> Transaction for NFTokenMint<'a> { impl<'a> NFTokenMintError for NFTokenMint<'a> { fn _get_issuer_error(&self) -> Result<(), XRPLNFTokenMintException> { - if let Some(issuer) = self.issuer { + if let Some(issuer) = self.issuer.clone() { if issuer == self.account { Err(XRPLNFTokenMintException::ValueEqualsValue { - field1: "issuer", - field2: "account", - resource: "", + field1: "issuer".into(), + field2: "account".into(), + resource: "".into(), }) } else { Ok(()) @@ -203,10 +204,10 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { if let Some(transfer_fee) = self.transfer_fee { if transfer_fee > MAX_TRANSFER_FEE { Err(XRPLNFTokenMintException::ValueTooHigh { - field: "transfer_fee", + field: "transfer_fee".into(), max: MAX_TRANSFER_FEE, found: transfer_fee, - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -217,13 +218,13 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { } fn _get_uri_error(&self) -> Result<(), XRPLNFTokenMintException> { - if let Some(uri) = self.uri { + if let Some(uri) = self.uri.clone() { if uri.len() > MAX_URI_LENGTH { Err(XRPLNFTokenMintException::ValueTooLong { - field: "uri", + field: "uri".into(), max: MAX_URI_LENGTH, found: uri.len(), - resource: "", + resource: "".into(), }) } else { Ok(()) @@ -236,22 +237,22 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { impl<'a> NFTokenMint<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, nftoken_taxon: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, - issuer: Option<&'a str>, + issuer: Option>, transfer_fee: Option, - uri: Option<&'a str>, + uri: Option>, ) -> Self { Self { transaction_type: TransactionType::NFTokenMint, @@ -293,7 +294,7 @@ mod test_nftoken_mint_error { fn test_issuer_error() { let nftoken_mint = NFTokenMint { transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -306,7 +307,7 @@ mod test_nftoken_mint_error { memos: None, signers: None, nftoken_taxon: 0, - issuer: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb"), + issuer: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), transfer_fee: None, uri: None, }; @@ -321,7 +322,7 @@ mod test_nftoken_mint_error { fn test_transfer_fee_error() { let nftoken_mint = NFTokenMint { transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -349,7 +350,7 @@ mod test_nftoken_mint_error { fn test_uri_error() { let nftoken_mint = NFTokenMint { transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -364,7 +365,7 @@ mod test_nftoken_mint_error { nftoken_taxon: 0, issuer: None, transfer_fee: None, - uri: Some("wss://xrplcluster.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + uri: Some("wss://xrplcluster.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()), }; assert_eq!( @@ -384,7 +385,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = NFTokenMint::new( - "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), 0, Some("10".into()), None, @@ -399,7 +400,7 @@ mod test_serde { None, None, Some(314), - Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"), + Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469".into()), ); let default_json = r#"{"TransactionType":"NFTokenMint","Account":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","Fee":"10","Flags":8,"Memos":[{"Memo":{"MemoData":"72656E74","MemoFormat":null,"MemoType":"687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963"}}],"NFTokenTaxon":0,"TransferFee":314,"URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"}"#; @@ -412,7 +413,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = NFTokenMint::new( - "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), 0, Some("10".into()), None, @@ -427,7 +428,7 @@ mod test_serde { None, None, Some(314), - Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"), + Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469".into()), ); let default_json = r#"{"TransactionType":"NFTokenMint","Account":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","TransferFee":314,"NFTokenTaxon":0,"Flags":8,"Fee":"10","URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469","Memos":[{"Memo":{"MemoType":"687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963","MemoFormat":null,"MemoData":"72656E74"}}]}"#; diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 57e27bd5..4a7f049b 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -27,7 +28,7 @@ pub struct OfferCancel<'a> { #[serde(default = "TransactionType::offer_cancel")] transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -47,11 +48,11 @@ pub struct OfferCancel<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -63,7 +64,7 @@ pub struct OfferCancel<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -111,16 +112,16 @@ impl<'a> Transaction for OfferCancel<'a> { impl<'a> OfferCancel<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, offer_sequence: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, ) -> Self { @@ -150,7 +151,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = OfferCancel::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), 6, Some("12".into()), Some(7), @@ -174,7 +175,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = OfferCancel::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), 6, Some("12".into()), Some(7), diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 0b75e9c8..3710618d 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -63,7 +64,7 @@ pub struct OfferCreate<'a> { #[serde(default = "TransactionType::offer_create")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -83,11 +84,11 @@ pub struct OfferCreate<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -99,7 +100,7 @@ pub struct OfferCreate<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -175,17 +176,17 @@ impl<'a> Transaction for OfferCreate<'a> { impl<'a> OfferCreate<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, taker_gets: Amount<'a>, taker_pays: Amount<'a>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, @@ -225,7 +226,7 @@ mod test { fn test_has_flag() { let txn: OfferCreate = OfferCreate { transaction_type: TransactionType::OfferCreate, - account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe", + account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), fee: Some("10".into()), sequence: Some(1), last_ledger_sequence: Some(72779837), @@ -254,7 +255,7 @@ mod test { fn test_get_transaction_type() { let txn: OfferCreate = OfferCreate { transaction_type: TransactionType::OfferCreate, - account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe", + account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), fee: Some("10".into()), sequence: Some(1), last_ledger_sequence: Some(72779837), @@ -290,7 +291,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = OfferCreate::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), Amount::XRPAmount(XRPAmount::from("6000000")), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "GKO".into(), @@ -322,7 +323,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = OfferCreate::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), Amount::XRPAmount(XRPAmount::from("6000000")), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "GKO".into(), diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 0aa62c75..1c3cb6e9 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -60,7 +61,7 @@ pub struct Payment<'a> { #[serde(default = "TransactionType::payment")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -80,11 +81,11 @@ pub struct Payment<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -96,7 +97,7 @@ pub struct Payment<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -113,7 +114,7 @@ pub struct Payment<'a> { /// See Payment fields: /// `` pub amount: Amount<'a>, - pub destination: &'a str, + pub destination: Cow<'a, str>, pub destination_tag: Option, pub invoice_id: Option, pub paths: Option>>>, @@ -191,16 +192,16 @@ impl<'a> PaymentError for Payment<'a> { if self.amount.is_xrp() && self.send_max.is_none() { if self.paths.is_some() { Err(XRPLPaymentException::IllegalOption { - field: "paths", - context: "XRP to XRP payments", - resource: "", + field: "paths".into(), + context: "XRP to XRP payments".into(), + resource: "".into(), }) } else if self.account == self.destination { Err(XRPLPaymentException::ValueEqualsValueInContext { - field1: "account", - field2: "destination", - context: "XRP to XRP Payments", - resource: "", + field1: "account".into(), + field2: "destination".into(), + context: "XRP to XRP Payments".into(), + resource: "".into(), }) } else { Ok(()) @@ -217,9 +218,9 @@ impl<'a> PaymentError for Payment<'a> { && self.amount.is_xrp() { Err(XRPLPaymentException::IllegalOption { - field: "send_max", - context: "XRP to XRP non-partial payments", - resource: "", + field: "send_max".into(), + context: "XRP to XRP non-partial payments".into(), + resource: "".into(), }) } else { Ok(()) @@ -227,15 +228,15 @@ impl<'a> PaymentError for Payment<'a> { } else if self.has_flag(&Flag::Payment(PaymentFlag::TfPartialPayment)) { Err(XRPLPaymentException::FlagRequiresField { flag: PaymentFlag::TfPartialPayment, - field: "send_max", - resource: "", + field: "send_max".into(), + resource: "".into(), }) } else if !self.has_flag(&Flag::Payment(PaymentFlag::TfPartialPayment)) { if let Some(_deliver_min) = &self.deliver_min { Err(XRPLPaymentException::IllegalOption { - field: "deliver_min", - context: "XRP to XRP non-partial payments", - resource: "", + field: "deliver_min".into(), + context: "XRP to XRP non-partial payments".into(), + resource: "".into(), }) } else { Ok(()) @@ -248,9 +249,9 @@ impl<'a> PaymentError for Payment<'a> { fn _get_exchange_error(&self) -> Result<(), XRPLPaymentException> { if self.account == self.destination && self.send_max.is_none() { return Err(XRPLPaymentException::OptionRequired { - field: "send_max", - context: "exchanges", - resource: "", + field: "send_max".into(), + context: "exchanges".into(), + resource: "".into(), }); } @@ -260,17 +261,17 @@ impl<'a> PaymentError for Payment<'a> { impl<'a> Payment<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, amount: Amount<'a>, - destination: &'a str, + destination: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, @@ -327,7 +328,7 @@ mod test_payment_error { fn test_xrp_to_xrp_error() { let mut payment = Payment { transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -340,11 +341,11 @@ mod test_payment_error { memos: None, signers: None, amount: Amount::XRPAmount(XRPAmount::from("1000000")), - destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK", + destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), destination_tag: None, invoice_id: None, paths: Some(vec![vec![PathStep { - account: Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"), + account: Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), currency: None, issuer: None, r#type: None, @@ -368,7 +369,7 @@ mod test_payment_error { ); payment.send_max = None; - payment.destination = "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb"; + payment.destination = "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(); assert_eq!( payment.validate().unwrap_err().to_string().as_str(), @@ -380,7 +381,7 @@ mod test_payment_error { fn test_partial_payments_eror() { let mut payment = Payment { transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -393,7 +394,7 @@ mod test_payment_error { memos: None, signers: None, amount: Amount::XRPAmount("1000000".into()), - destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK", + destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), destination_tag: None, invoice_id: None, paths: None, @@ -420,7 +421,7 @@ mod test_payment_error { fn test_exchange_error() { let payment = Payment { transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -437,7 +438,7 @@ mod test_payment_error { "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), "10".into(), )), - destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), destination_tag: None, invoice_id: None, paths: None, @@ -463,13 +464,13 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = Payment::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), "1".into(), )), - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), Some("12".into()), Some(2), None, @@ -498,13 +499,13 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = Payment::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), "1".into(), )), - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), Some("12".into()), Some(2), None, diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 1318065e..37615917 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -59,7 +60,7 @@ pub struct PaymentChannelClaim<'a> { #[serde(default = "TransactionType::payment_channel_claim")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -79,11 +80,11 @@ pub struct PaymentChannelClaim<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -95,7 +96,7 @@ pub struct PaymentChannelClaim<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -111,11 +112,11 @@ pub struct PaymentChannelClaim<'a> { /// /// See PaymentChannelClaim fields: /// `` - pub channel: &'a str, - pub balance: Option<&'a str>, - pub amount: Option<&'a str>, - pub signature: Option<&'a str>, - pub public_key: Option<&'a str>, + pub channel: Cow<'a, str>, + pub balance: Option>, + pub amount: Option>, + pub signature: Option>, + pub public_key: Option>, } impl<'a> Default for PaymentChannelClaim<'a> { @@ -175,23 +176,23 @@ impl<'a> Transaction for PaymentChannelClaim<'a> { impl<'a> PaymentChannelClaim<'a> { pub fn new( - account: &'a str, - channel: &'a str, + account: Cow<'a, str>, + channel: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, - balance: Option<&'a str>, - amount: Option<&'a str>, - signature: Option<&'a str>, - public_key: Option<&'a str>, + balance: Option>, + amount: Option>, + signature: Option>, + public_key: Option>, ) -> Self { Self { transaction_type: TransactionType::PaymentChannelClaim, @@ -223,8 +224,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = PaymentChannelClaim::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), + "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), None, None, None, @@ -236,10 +237,10 @@ mod test_serde { None, None, None, - Some("1000000"), - Some("1000000"), - Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B"), - Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"), + Some("1000000".into()), + Some("1000000".into()), + Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), + Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into()), ); let default_json = r#"{"TransactionType":"PaymentChannelClaim","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; @@ -252,8 +253,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = PaymentChannelClaim::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), + "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), None, None, None, @@ -265,10 +266,10 @@ mod test_serde { None, None, None, - Some("1000000"), - Some("1000000"), - Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B"), - Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"), + Some("1000000".into()), + Some("1000000".into()), + Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), + Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into()), ); let default_json = r#"{"TransactionType":"PaymentChannelClaim","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 0402f7e3..f5a133b4 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -27,7 +28,7 @@ pub struct PaymentChannelCreate<'a> { #[serde(default = "TransactionType::payment_channel_create")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -47,11 +48,11 @@ pub struct PaymentChannelCreate<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -63,7 +64,7 @@ pub struct PaymentChannelCreate<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -78,9 +79,9 @@ pub struct PaymentChannelCreate<'a> { /// See PaymentChannelCreate fields: /// `` pub amount: XRPAmount<'a>, - pub destination: &'a str, + pub destination: Cow<'a, str>, pub settle_delay: u32, - pub public_key: &'a str, + pub public_key: Cow<'a, str>, pub cancel_after: Option, pub destination_tag: Option, } @@ -121,19 +122,19 @@ impl<'a> Transaction for PaymentChannelCreate<'a> { impl<'a> PaymentChannelCreate<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, amount: XRPAmount<'a>, - destination: &'a str, + destination: Cow<'a, str>, settle_delay: u32, - public_key: &'a str, + public_key: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, cancel_after: Option, @@ -170,11 +171,11 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = PaymentChannelCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), 86400, - "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A", + "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into(), None, None, None, @@ -199,11 +200,11 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = PaymentChannelCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), 86400, - "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A", + "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into(), None, None, None, diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index b79927d8..24067a43 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -28,7 +29,7 @@ pub struct PaymentChannelFund<'a> { #[serde(default = "TransactionType::payment_channel_fund")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -48,11 +49,11 @@ pub struct PaymentChannelFund<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -64,7 +65,7 @@ pub struct PaymentChannelFund<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -79,7 +80,7 @@ pub struct PaymentChannelFund<'a> { /// See PaymentChannelFund fields: /// `` pub amount: XRPAmount<'a>, - pub channel: &'a str, + pub channel: Cow<'a, str>, pub expiration: Option, } @@ -116,17 +117,17 @@ impl<'a> Transaction for PaymentChannelFund<'a> { impl<'a> PaymentChannelFund<'a> { pub fn new( - account: &'a str, - channel: &'a str, + account: Cow<'a, str>, + channel: Cow<'a, str>, amount: XRPAmount<'a>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, expiration: Option, @@ -161,8 +162,8 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = PaymentChannelFund::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), XRPAmount::from("200000"), None, None, @@ -187,8 +188,8 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = PaymentChannelFund::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), XRPAmount::from("200000"), None, None, diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 463ea2b1..6037054f 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -1,4 +1,5 @@ use crate::_serde::txn_flags; +use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -41,7 +42,7 @@ pub struct EnableAmendment<'a> { #[serde(default = "TransactionType::enable_amendment")] transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -55,7 +56,7 @@ pub struct EnableAmendment<'a> { /// 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<&'a str>, + 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 @@ -63,7 +64,7 @@ pub struct EnableAmendment<'a> { pub source_tag: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -72,7 +73,7 @@ pub struct EnableAmendment<'a> { /// /// See EnableAmendment fields: /// `` - pub amendment: &'a str, + pub amendment: Cow<'a, str>, pub ledger_sequence: u32, } @@ -104,14 +105,14 @@ impl<'a> Transaction for EnableAmendment<'a> { impl<'a> EnableAmendment<'a> { pub fn new( - account: &'a str, - amendment: &'a str, + account: Cow<'a, str>, + amendment: Cow<'a, str>, ledger_sequence: u32, fee: Option>, sequence: Option, - signing_pub_key: Option<&'a str>, + signing_pub_key: Option>, source_tag: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, ) -> Self { Self { diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index f3782328..19c3c678 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -24,7 +25,7 @@ pub struct SetFee<'a> { #[serde(default = "TransactionType::set_fee")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -38,7 +39,7 @@ pub struct SetFee<'a> { /// 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<&'a str>, + 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 @@ -46,7 +47,7 @@ pub struct SetFee<'a> { pub source_tag: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// The custom fields for the SetFee model. @@ -70,7 +71,7 @@ impl<'a> Transaction for SetFee<'a> { impl<'a> SetFee<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, base_fee: XRPAmount<'a>, reference_fee_units: u32, reserve_base: u32, @@ -78,9 +79,9 @@ impl<'a> SetFee<'a> { ledger_sequence: u32, fee: Option>, sequence: Option, - signing_pub_key: Option<&'a str>, + signing_pub_key: Option>, source_tag: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, ) -> Self { Self { transaction_type: TransactionType::SetFee, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 8ce72482..57941a22 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -1,3 +1,4 @@ +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -35,7 +36,7 @@ pub struct UNLModify<'a> { #[serde(default = "TransactionType::unl_modify")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -49,7 +50,7 @@ pub struct UNLModify<'a> { /// 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<&'a str>, + 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 @@ -57,7 +58,7 @@ pub struct UNLModify<'a> { pub source_tag: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// The custom fields for the UNLModify model. @@ -66,7 +67,7 @@ pub struct UNLModify<'a> { /// `` pub ledger_sequence: u32, pub unlmodify_disabling: UNLModifyDisabling, - pub unlmodify_validator: &'a str, + pub unlmodify_validator: Cow<'a, str>, } impl<'a> Model for UNLModify<'a> {} @@ -79,15 +80,15 @@ impl<'a> Transaction for UNLModify<'a> { impl<'a> UNLModify<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, ledger_sequence: u32, unlmodify_disabling: UNLModifyDisabling, - unlmodify_validator: &'a str, + unlmodify_validator: Cow<'a, str>, fee: Option>, sequence: Option, - signing_pub_key: Option<&'a str>, + signing_pub_key: Option>, source_tag: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, ) -> Self { Self { transaction_type: TransactionType::UNLModify, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index ccebf804..78d30f15 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -31,7 +32,7 @@ pub struct SetRegularKey<'a> { #[serde(default = "TransactionType::set_regular_key")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -51,11 +52,11 @@ pub struct SetRegularKey<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -67,7 +68,7 @@ pub struct SetRegularKey<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -81,7 +82,7 @@ pub struct SetRegularKey<'a> { /// /// See SetRegularKey fields: /// `` - pub regular_key: Option<&'a str>, + pub regular_key: Option>, } impl<'a> Default for SetRegularKey<'a> { @@ -115,18 +116,18 @@ impl<'a> Transaction for SetRegularKey<'a> { impl<'a> SetRegularKey<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, - regular_key: Option<&'a str>, + regular_key: Option>, ) -> Self { Self { transaction_type: TransactionType::SetRegularKey, @@ -154,7 +155,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = SetRegularKey::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), None, None, @@ -165,7 +166,7 @@ mod test_serde { None, None, None, - Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"), + Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD".into()), ); let default_json = r#"{"TransactionType":"SetRegularKey","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; @@ -178,7 +179,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = SetRegularKey::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), None, None, @@ -189,7 +190,7 @@ mod test_serde { None, None, None, - Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"), + Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD".into()), ); let default_json = r#"{"TransactionType":"SetRegularKey","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 43f317bd..62a424bf 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -1,5 +1,6 @@ use alloc::string::ToString; use alloc::string::String; +use alloc::borrow::Cow; use alloc::vec::Vec; use anyhow::Result; use derive_new::new; @@ -45,7 +46,7 @@ pub struct SignerListSet<'a> { #[serde(default = "TransactionType::signer_list_set")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -65,11 +66,11 @@ pub struct SignerListSet<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -81,7 +82,7 @@ pub struct SignerListSet<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -197,7 +198,7 @@ impl<'a> SignerListSetError for SignerListSet<'a> { if accounts.contains(&self.account.to_string()) { Err(XRPLSignerListSetException::CollectionInvalidItem { field: "signer_entries".into(), - found: self.account.into(), + found: self.account.clone(), resource: "".into(), }) } else if self.signer_quorum > signer_weight_sum { @@ -226,16 +227,16 @@ impl<'a> SignerListSetError for SignerListSet<'a> { impl<'a> SignerListSet<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, signer_quorum: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, signer_entries: Option>, @@ -278,7 +279,7 @@ mod test_signer_list_set_error { fn test_signer_list_deleted_error() { let mut signer_list_set = SignerListSet { transaction_type: TransactionType::SignerListSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -315,7 +316,7 @@ mod test_signer_list_set_error { fn test_signer_entries_error() { let mut signer_list_set = SignerListSet { transaction_type: TransactionType::SignerListSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb", + account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), fee: None, sequence: None, last_ledger_sequence: None, @@ -440,7 +441,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = SignerListSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 3, Some("12".into()), None, @@ -469,7 +470,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = SignerListSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 3, Some("12".into()), None, diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index ebbe5cf7..b696a141 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -27,7 +28,7 @@ pub struct TicketCreate<'a> { #[serde(default = "TransactionType::ticket_create")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -47,11 +48,11 @@ pub struct TicketCreate<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -63,7 +64,7 @@ pub struct TicketCreate<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -111,16 +112,16 @@ impl<'a> Transaction for TicketCreate<'a> { impl<'a> TicketCreate<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, ticket_count: u32, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, ) -> Self { @@ -150,7 +151,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = TicketCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 10, Some("10".into()), Some(381), @@ -174,7 +175,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = TicketCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 10, Some("10".into()), Some(381), diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 081d2eaa..43d28dc4 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -54,7 +55,7 @@ pub struct TrustSet<'a> { #[serde(default = "TransactionType::trust_set")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -74,11 +75,11 @@ pub struct TrustSet<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -90,7 +91,7 @@ pub struct TrustSet<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. #[serde(default)] #[serde(with = "txn_flags")] @@ -163,16 +164,16 @@ impl<'a> Transaction for TrustSet<'a> { impl<'a> TrustSet<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, limit_amount: IssuedCurrencyAmount<'a>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, flags: Option>, memos: Option>, signers: Option>>, @@ -208,7 +209,7 @@ mod test_serde { #[test] fn test_serialize() { let default_txn = TrustSet::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), IssuedCurrencyAmount::new( "USD".into(), "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc".into(), @@ -239,7 +240,7 @@ mod test_serde { #[test] fn test_deserialize() { let default_txn = TrustSet::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), IssuedCurrencyAmount::new( "USD".into(), "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc".into(), diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 72dfe997..65a73d16 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -11,7 +11,7 @@ async fn test_websocket_tungstenite_echo() -> Result<()> { let mut websocket = connect_to_wss_tungstinite_echo().await?; let account_info = AccountInfo::new( - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, None, None, @@ -52,7 +52,7 @@ async fn test_embedded_websocket_echo() -> Result<()> { let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; let account_info = AccountInfo::new( - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, None, None, From 738e2e162ea6156fe9df8e797f5598a7df670b9c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 18 Sep 2023 18:13:43 +0200 Subject: [PATCH 028/113] utilize Cow for models --- .../transactions/nftoken_accept_offer.rs | 25 ++++++++++--------- .../transactions/nftoken_cancel_offer.rs | 8 +++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index ebe0de06..5fba6980 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -1,5 +1,6 @@ use crate::Err; use alloc::vec::Vec; +use alloc::borrow::Cow; use anyhow::Result; use core::convert::TryInto; use rust_decimal::Decimal; @@ -34,7 +35,7 @@ pub struct NFTokenAcceptOffer<'a> { #[serde(default = "TransactionType::nftoken_accept_offer")] pub transaction_type: TransactionType, /// The unique address of the account that initiated the transaction. - pub account: &'a str, + pub account: Cow<'a, str>, /// Integer amount of XRP, in drops, to be destroyed as a cost /// for distributing this transaction to the network. Some /// transaction types have different minimum requirements. @@ -54,11 +55,11 @@ pub struct NFTokenAcceptOffer<'a> { /// transaction is only valid if the sending account's /// previously-sent transaction matches the provided hash. #[serde(rename = "AccountTxnID")] - pub account_txn_id: Option<&'a str>, + pub account_txn_id: 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<&'a str>, + 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 @@ -70,7 +71,7 @@ pub struct NFTokenAcceptOffer<'a> { pub ticket_sequence: Option, /// The signature that verifies this transaction as originating /// from the account it says it is from. - pub txn_signature: Option<&'a str>, + pub txn_signature: Option>, /// Set of bit-flags for this transaction. pub flags: Option, /// Additional arbitrary information used to identify this transaction. @@ -85,9 +86,9 @@ pub struct NFTokenAcceptOffer<'a> { /// See NFTokenAcceptOffer fields: /// `` #[serde(rename = "NFTokenSellOffer")] - pub nftoken_sell_offer: Option<&'a str>, + pub nftoken_sell_offer: Option>, #[serde(rename = "NFTokenBuyOffer")] - pub nftoken_buy_offer: Option<&'a str>, + pub nftoken_buy_offer: Option>, #[serde(rename = "NFTokenBrokerFee")] pub nftoken_broker_fee: Option>, } @@ -173,19 +174,19 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { impl<'a> NFTokenAcceptOffer<'a> { pub fn new( - account: &'a str, + account: Cow<'a, str>, fee: Option>, sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option<&'a str>, - signing_pub_key: Option<&'a str>, + account_txn_id: Option>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, - txn_signature: Option<&'a str>, + txn_signature: Option>, memos: Option>, signers: Option>>, - nftoken_sell_offer: Option<&'a str>, - nftoken_buy_offer: Option<&'a str>, + nftoken_sell_offer: Option>, + nftoken_buy_offer: Option>, nftoken_broker_fee: Option>, ) -> Self { Self { diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index fb738c23..178376a0 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -84,7 +84,7 @@ pub struct NFTokenCancelOffer<'a> { /// Lifetime issue #[serde(borrow)] #[serde(rename = "NFTokenOffers")] - pub nftoken_offers: Vec<&'a str>, + pub nftoken_offers: Vec>, } impl<'a> Default for NFTokenCancelOffer<'a> { @@ -140,7 +140,7 @@ impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { impl<'a> NFTokenCancelOffer<'a> { pub fn new( account: Cow<'a, str>, - nftoken_offers: Vec<&'a str>, + nftoken_offers: Vec>, fee: Option>, sequence: Option, last_ledger_sequence: Option, @@ -220,7 +220,7 @@ mod test_serde { fn test_serialize() { let default_txn = NFTokenCancelOffer::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"], + vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D".into()], None, None, None, @@ -244,7 +244,7 @@ mod test_serde { fn test_deserialize() { let default_txn = NFTokenCancelOffer::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"], + vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D".into()], None, None, None, From 49a2efe06afa3e10018616ca885af9bdab0ea904 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 18 Sep 2023 18:29:05 +0200 Subject: [PATCH 029/113] cargo fmt --- Cargo.toml | 16 +++++++++++++--- src/models/ledger/objects/amendments.rs | 7 ++++--- src/models/ledger/objects/amm.rs | 2 +- src/models/requests/channel_authorize.rs | 9 +++++++-- src/models/requests/ledger_entry.rs | 8 ++++++-- src/models/transactions/account_delete.rs | 2 +- src/models/transactions/account_set.rs | 2 +- src/models/transactions/check_cancel.rs | 2 +- src/models/transactions/check_cash.rs | 2 +- src/models/transactions/check_create.rs | 2 +- src/models/transactions/deposit_preauth.rs | 2 +- src/models/transactions/escrow_cancel.rs | 2 +- src/models/transactions/escrow_create.rs | 12 +++++++++--- src/models/transactions/escrow_finish.rs | 13 ++++++++++--- src/models/transactions/exceptions.rs | 12 +++++++++--- src/models/transactions/nftoken_accept_offer.rs | 14 ++++++++++---- src/models/transactions/nftoken_burn.rs | 2 +- src/models/transactions/nftoken_cancel_offer.rs | 2 +- src/models/transactions/nftoken_create_offer.rs | 2 +- src/models/transactions/nftoken_mint.rs | 2 +- src/models/transactions/offer_cancel.rs | 2 +- src/models/transactions/offer_create.rs | 2 +- src/models/transactions/payment.rs | 2 +- src/models/transactions/payment_channel_claim.rs | 2 +- .../transactions/payment_channel_create.rs | 2 +- src/models/transactions/payment_channel_fund.rs | 2 +- src/models/transactions/set_regular_key.rs | 2 +- src/models/transactions/signer_list_set.rs | 4 ++-- src/models/transactions/ticket_create.rs | 2 +- src/models/transactions/trust_set.rs | 2 +- 30 files changed, 91 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f473c2d..43f6d1b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,11 +54,13 @@ serde_json = { version = "1.0.68", default-features = false, features = [ serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.14.0", default-features = false, features = ["serde"] } +hashbrown = { version = "0.14.0", default-features = false, features = [ + "serde", +] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" -anyhow = { version ="1.0.69", default-features = false } +anyhow = { version = "1.0.69", default-features = false } tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, optional = true } @@ -86,7 +88,15 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "net", "tungstenite", "embedded-websocket"] +default = [ + "std", + "core", + "models", + "utils", + "net", + "tungstenite", + "embedded-websocket", +] models = ["core", "transactions", "requests", "ledger"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] diff --git a/src/models/ledger/objects/amendments.rs b/src/models/ledger/objects/amendments.rs index daa06395..e2a34d14 100644 --- a/src/models/ledger/objects/amendments.rs +++ b/src/models/ledger/objects/amendments.rs @@ -1,7 +1,7 @@ use crate::models::ledger::LedgerEntryType; use crate::models::Model; -use alloc::{borrow::Cow, string::String}; use alloc::vec::Vec; +use alloc::{borrow::Cow, string::String}; use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; @@ -79,8 +79,8 @@ impl<'a> Amendments<'a> { #[cfg(test)] mod test_serde { use crate::models::ledger::{Amendments, Majority}; - use alloc::string::ToString; use alloc::borrow::Cow; + use alloc::string::ToString; use alloc::vec; #[test] @@ -94,7 +94,8 @@ mod test_serde { Cow::from("740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11"), ]), Some(vec![Majority { - amendment: "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146".to_string(), + amendment: "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146" + .to_string(), close_time: 535589001, }]), ); diff --git a/src/models/ledger/objects/amm.rs b/src/models/ledger/objects/amm.rs index 5ece326e..d971552e 100644 --- a/src/models/ledger/objects/amm.rs +++ b/src/models/ledger/objects/amm.rs @@ -134,8 +134,8 @@ mod test_serde { use crate::models::amount::{Amount, IssuedCurrencyAmount}; use crate::models::currency::{Currency, IssuedCurrency, XRP}; use crate::models::ledger::amm::{AuctionSlot, AuthAccount, VoteEntry, AMM}; - use alloc::string::ToString; use alloc::borrow::Cow; + use alloc::string::ToString; use alloc::vec; #[test] diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 9a3d0f7c..93ef01d3 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -99,7 +99,12 @@ impl<'a> Model for ChannelAuthorize<'a> { impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { fn _get_field_error(&self) -> Result<(), XRPLChannelAuthorizeException> { let mut signing_methods = Vec::new(); - for method in [self.secret.clone(), self.seed.clone(), self.seed_hex.clone(), self.passphrase.clone()] { + for method in [ + self.secret.clone(), + self.seed.clone(), + self.seed_hex.clone(), + self.passphrase.clone(), + ] { if method.is_some() { signing_methods.push(method) } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 672a893a..7a80e0bb 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -1,6 +1,6 @@ use crate::Err; -use anyhow::Result; use alloc::borrow::Cow; +use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -130,7 +130,11 @@ impl<'a: 'static> Model for LedgerEntry<'a> { impl<'a> LedgerEntryError for LedgerEntry<'a> { fn _get_field_error(&self) -> Result<(), XRPLLedgerEntryException> { let mut signing_methods: u32 = 0; - for method in [self.index.clone(), self.account_root.clone(), self.check.clone()] { + for method in [ + self.index.clone(), + self.account_root.clone(), + self.check.clone(), + ] { if method.is_some() { signing_methods += 1 } diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 6a7d57ca..56d220f6 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 3e5399bb..c04ccb12 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index d11fd120..21650480 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index fffca38b..875bafe3 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -1,6 +1,6 @@ use crate::Err; -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 6b861be6..ee961512 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index a5e85944..41e236e7 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -1,6 +1,6 @@ use crate::Err; -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 047e031c..fdb863fa 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index ae9329ad..6cc81345 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -1,6 +1,6 @@ use crate::Err; -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -262,7 +262,10 @@ mod test_serde { Some(23480), Some(533257958), Some(533171558), - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some( + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + .into(), + ), ); let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; @@ -291,7 +294,10 @@ mod test_serde { Some(23480), Some(533257958), Some(533171558), - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some( + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + .into(), + ), ); let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","DestinationTag":23480,"SourceTag":11747}"#; diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index db5e7d46..facb5416 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -213,7 +213,8 @@ mod test_escrow_finish_errors { owner: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), offer_sequence: 10, condition: Some( - "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into(), + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + .into(), ), fulfillment: None, }; @@ -245,7 +246,10 @@ mod test_serde { None, None, None, - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some( + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + .into(), + ), Some("A0028000".into()), ); let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; @@ -272,7 +276,10 @@ mod test_serde { None, None, None, - Some("A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100".into()), + Some( + "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" + .into(), + ), Some("A0028000".into()), ); let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index eefd58fd..becceea9 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -1,7 +1,7 @@ use crate::models::transactions::{AccountSetFlag, PaymentFlag}; +use alloc::borrow::Cow; use strum_macros::Display; use thiserror_no_std::Error; -use alloc::borrow::Cow; #[derive(Debug, Clone, PartialEq, Eq, Display)] pub enum XRPLTransactionException<'a> { @@ -153,7 +153,10 @@ pub enum XRPLNFTokenAcceptOfferException<'a> { }, /// The value can not be zero. #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { field: Cow<'a, str>, resource: Cow<'a, str> }, + ValueZero { + field: Cow<'a, str>, + resource: Cow<'a, str>, + }, } #[cfg(feature = "std")] @@ -177,7 +180,10 @@ impl<'a> alloc::error::Error for XRPLNFTokenCancelOfferException<'a> {} pub enum XRPLNFTokenCreateOfferException<'a> { /// The value can not be zero. #[error("The value of the field `{field:?}` is not allowed to be zero. For more information see: {resource:?}")] - ValueZero { field: Cow<'a, str>, resource: Cow<'a, str> }, + ValueZero { + field: Cow<'a, str>, + resource: Cow<'a, str>, + }, /// A fields value is not allowed to be the same as another fields value. #[error("The value of the field `{field1:?}` is not allowed to be the same as the value of the field `{field2:?}`. For more information see: {resource:?}")] ValueEqualsValue { diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 5fba6980..4e2759e9 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -1,6 +1,6 @@ use crate::Err; -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use core::convert::TryInto; use rust_decimal::Decimal; @@ -284,8 +284,8 @@ mod test_nftoken_accept_offer_error { #[cfg(test)] mod test_serde { - use alloc::vec; use alloc::string::ToString; + use alloc::vec; use super::*; @@ -302,7 +302,10 @@ mod test_serde { None, None, Some(vec![Memo::new( - Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437".to_string()), + Some( + "61356534373538372D633134322D346663382D616466362D393666383562356435386437" + .to_string(), + ), None, None, )]), @@ -332,7 +335,10 @@ mod test_serde { None, None, Some(vec![Memo::new( - Some("61356534373538372D633134322D346663382D616466362D393666383562356435386437".to_string()), + Some( + "61356534373538372D633134322D346663382D616466362D393666383562356435386437" + .to_string(), + ), None, None, )]), diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 2a2b936b..8b9e12a8 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 178376a0..5dbd3b98 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -1,7 +1,7 @@ use crate::Err; +use alloc::borrow::Cow; use alloc::vec::Vec; use anyhow::Result; -use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 16886839..3561773e 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -1,8 +1,8 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use anyhow::Result; use core::convert::TryInto; use rust_decimal::Decimal; -use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index ddc16ee8..f88e80c1 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -1,6 +1,6 @@ +use alloc::borrow::Cow; use alloc::vec::Vec; use anyhow::Result; -use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 4a7f049b..7e596cc6 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 3710618d..678f9bca 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 1c3cb6e9..901e1ca0 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 37615917..b4af1204 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index f5a133b4..c0274764 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 24067a43..6278f12d 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 78d30f15..0dca531a 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 62a424bf..97b6dd6a 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -1,6 +1,6 @@ -use alloc::string::ToString; -use alloc::string::String; use alloc::borrow::Cow; +use alloc::string::String; +use alloc::string::ToString; use alloc::vec::Vec; use anyhow::Result; use derive_new::new; diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index b696a141..157a6cc9 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 43d28dc4..10a5cd65 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -1,5 +1,5 @@ -use alloc::vec::Vec; use alloc::borrow::Cow; +use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; From 8c1855bd2e0ceea93580e1e39fa05a35e9baf85e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 8 Jan 2024 20:26:43 +0000 Subject: [PATCH 030/113] add devcontainer --- .devcontainer/Dockerfile | 5 ++++ .devcontainer/devcontainer.json | 41 ++++++++++++++++++++++++++++++++ .devcontainer/docker-compose.yml | 10 ++++++++ 3 files changed, 56 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..f56adafc --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +FROM mcr.microsoft.com/devcontainers/rust:1.0-1-bullseye + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..2239d3d1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/rust +{ + "name": "xrpl-rust", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + // Use 'mounts' to make the cargo cache persistent in a Docker Volume. + // "mounts": [ + // { + // "source": "devcontainer-cargo-cache-${devcontainerId}", + // "target": "/usr/local/cargo", + // "type": "volume" + // } + // ] + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "rustup --version", + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "Gydunhn.vsc-essentials", + "GitHub.copilot", + "swellaby.rust-pack", + "panicbit.cargo", + "vadimcn.vscode-lldb", + "tamasfe.even-better-toml" + ], + "settings": { + "files.autoSave": "onFocusChange", + "files.eol": "\n", + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true + } + } + } + } + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..d0342a5c --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" + +services: + app: + build: + context: . + dockerfile: Dockerfile + command: sleep infinity + volumes: + - ../..:/workspaces:cached \ No newline at end of file From 259c8576698d5cede1607f0b49f1cd17bee3fc9b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 14 Jan 2024 20:11:10 +0000 Subject: [PATCH 031/113] fix async client tests --- .cargo-husky/hooks/pre-commit | 2 + Cargo.toml | 22 +- src/asynch/clients/async_websocket_client.rs | 324 ------------------- src/asynch/clients/embedded_websocket.rs | 168 ++++++++++ src/asynch/clients/exceptions.rs | 8 +- src/asynch/clients/mod.rs | 14 +- src/asynch/clients/tokio_tungstenite.rs | 112 +++++++ src/asynch/mod.rs | 2 +- src/models/ledger/objects/mod.rs | 1 - tests/common/mod.rs | 30 +- tests/integration/clients/mod.rs | 19 +- tests/integration/mod.rs | 2 +- tests/integration_tests.rs | 10 + 13 files changed, 343 insertions(+), 371 deletions(-) delete mode 100644 src/asynch/clients/async_websocket_client.rs create mode 100644 src/asynch/clients/embedded_websocket.rs create mode 100644 src/asynch/clients/tokio_tungstenite.rs diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index c2e5f7bb..d4a3108b 100755 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -4,6 +4,8 @@ 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 --all-features cargo clippy --fix --allow-staged cargo doc --no-deps diff --git a/Cargo.toml b/Cargo.toml index 43f6d1b6..d8893d3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,9 @@ criterion = "0.5.1" cargo-husky = { version = "1.5.0", default-features = false, features = [ "user-hooks", ] } +tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } +futures-util = "0.3.30" bytes = { version = "1.4.0", default-features = false } [[bench]] @@ -88,24 +90,20 @@ name = "benchmarks" harness = false [features] -default = [ - "std", - "core", - "models", - "utils", - "net", - "tungstenite", - "embedded-websocket", -] +default = ["std", "core", "models", "utils", "tungstenite"] models = ["core", "transactions", "requests", "ledger"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -net = ["dep:url", "dep:futures"] -tungstenite = ["tokio/full", "tokio-tungstenite/native-tls"] -embedded-websocket = ["dep:embedded-websocket"] +tungstenite = [ + "dep:url", + "dep:futures", + "tokio/full", + "tokio-tungstenite/native-tls", +] +embedded-ws = ["dep:url", "dep:futures", "dep:embedded-websocket"] core = ["utils"] utils = [] std = [ diff --git a/src/asynch/clients/async_websocket_client.rs b/src/asynch/clients/async_websocket_client.rs deleted file mode 100644 index f057c494..00000000 --- a/src/asynch/clients/async_websocket_client.rs +++ /dev/null @@ -1,324 +0,0 @@ -use super::exceptions::XRPLWebsocketException; -use crate::Err; -use core::marker::PhantomData; -use futures::{Sink, Stream}; -// Exports -#[cfg(feature = "embedded-websocket")] -pub use embedded_websocket_impl::*; -#[cfg(feature = "tungstenite")] -pub use tungstenite_impl::*; - -pub struct WebsocketOpen; -pub struct WebsocketClosed; - -pub struct AsyncWebsocketClient { - inner: T, - status: PhantomData, -} - -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} - -#[cfg(feature = "tungstenite")] -mod tungstenite_impl { - use super::{ - AsyncWebsocketClient, Err, PhantomData, Sink, Stream, WebsocketClosed, WebsocketOpen, - XRPLWebsocketException, - }; - use anyhow::Result; - use core::{pin::Pin, task::Poll}; - use tokio::net::TcpStream; - pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, - }; - use url::Url; - - pub type AsyncWebsocketClientTungstenite = AsyncWebsocketClient< - TungsteniteWebsocketStream>, - Status, - >; - - impl Sink - for AsyncWebsocketClient< - TungsteniteWebsocketStream>, - WebsocketOpen, - > - where - I: serde::Serialize, - { - type Error = anyhow::Error; - - fn poll_ready( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { - match serde_json::to_string(&item) { - Ok(json) => { - match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } - } - Err(error) => Err!(error), - } - } - - fn poll_flush( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_flush(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn poll_close( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_close(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - } - - impl Stream - for AsyncWebsocketClient< - TungsteniteWebsocketStream>, - WebsocketOpen, - > - { - type Item = - > as Stream>::Item; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll> { - match Pin::new(&mut self.inner).poll_next(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(item)), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } - } - - impl - AsyncWebsocketClient< - TungsteniteWebsocketStream>, - WebsocketClosed, - > - { - pub async fn open( - uri: Url, - ) -> Result< - AsyncWebsocketClient< - TungsteniteWebsocketStream>, - WebsocketOpen, - >, - > { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - inner: websocket_stream, - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } - } - } -} - -#[cfg(feature = "embedded-websocket")] -mod embedded_websocket_impl { - use super::{ - AsyncWebsocketClient, Err, PhantomData, Sink, Stream, WebsocketClosed, WebsocketOpen, - XRPLWebsocketException, - }; - use anyhow::Result; - use core::{fmt::Debug, ops::Deref}; - pub use embedded_websocket::{ - framer_async::{ - Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, - ReadResult as EmbeddedWebsocketReadMessageType, - }, - Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, - WebSocket as EmbeddedWebsocket, - WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, - WebSocketOptions as EmbeddedWebsocketOptions, - WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, - WebSocketState as EmbeddedWebsocketState, - }; - use rand_core::RngCore; - - pub type AsyncWebsocketClientEmbeddedWebsocket = - AsyncWebsocketClient, Status>; - - impl - AsyncWebsocketClient, WebsocketClosed> - where - Rng: RngCore, - { - /// Open a websocket connection. - pub async fn open<'a, B, E>( - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - rng: Rng, - websocket_options: &EmbeddedWebsocketOptions<'_>, - ) -> Result< - AsyncWebsocketClient< - EmbeddedWebsocketFramer, - WebsocketOpen, - >, - > - where - B: AsRef<[u8]>, - E: Debug, - { - let websocket = EmbeddedWebsocket::::new_client(rng); - let mut framer = EmbeddedWebsocketFramer::new(websocket); - match framer.connect(stream, buffer, websocket_options).await { - Ok(Some(_)) => {} - Ok(None) => {} - Err(error) => return Err!(XRPLWebsocketException::from(error)), - } - - Ok(AsyncWebsocketClient { - inner: framer, - status: PhantomData::, - }) - } - } - - impl AsyncWebsocketClient, WebsocketOpen> - where - Rng: RngCore, - { - /// Encode a message to be sent over the websocket. - pub fn encode( - &mut self, - message_type: EmbeddedWebsocketSendMessageType, - end_of_message: bool, - from: &[u8], - to: &mut [u8], - ) -> Result - where - E: Debug, - { - match self - .inner - .encode::(message_type, end_of_message, from, to) - { - Ok(bytes_written) => Ok(bytes_written), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Send a message over the websocket. - pub async fn send<'b, E, R: serde::Serialize>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - end_of_message: bool, - frame_buf: R, - ) -> Result<()> - where - E: Debug, - { - match serde_json::to_vec(&frame_buf) { - Ok(frame_buf) => match self - .inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Text, - end_of_message, - frame_buf.as_slice(), - ) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - }, - Err(error) => Err!(error), - } - } - - /// Close the websocket connection. - pub async fn close<'b, E>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - close_status: EmbeddedWebsocketCloseStatusCode, - status_description: Option<&str>, - ) -> Result<()> - where - E: Debug, - { - match self - .inner - .close(stream, stream_buf, close_status, status_description) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Read a message from the websocket. - pub async fn next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Option>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Some(Ok(read_result)), - Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), - None => None, - } - } - - /// Read a message from the websocket. - /// - /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. - pub async fn try_next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Result>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Ok(Some(read_result)), - Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), - None => Ok(None), - } - } - } -} diff --git a/src/asynch/clients/embedded_websocket.rs b/src/asynch/clients/embedded_websocket.rs new file mode 100644 index 00000000..76106dee --- /dev/null +++ b/src/asynch/clients/embedded_websocket.rs @@ -0,0 +1,168 @@ +use super::{ + exceptions::XRPLWebsocketException, + {WebsocketClosed, WebsocketOpen}, +}; +use crate::Err; +use anyhow::Result; +use core::marker::PhantomData; +use core::{fmt::Debug, ops::Deref}; +pub use embedded_websocket::{ + framer_async::{ + Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, + ReadResult as EmbeddedWebsocketReadMessageType, + }, + Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, + WebSocket as EmbeddedWebsocket, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, + WebSocketOptions as EmbeddedWebsocketOptions, + WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, + WebSocketState as EmbeddedWebsocketState, +}; +use futures::{Sink, Stream}; +use rand_core::RngCore; + +pub struct AsyncWebsocketClient { + inner: EmbeddedWebsocketFramer, + status: PhantomData, +} + +impl AsyncWebsocketClient { + pub fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +impl AsyncWebsocketClient { + /// Open a websocket connection. + pub async fn open<'a, B, E>( + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + rng: Rng, + websocket_options: &EmbeddedWebsocketOptions<'_>, + ) -> Result> + where + B: AsRef<[u8]>, + E: Debug, + { + let websocket = EmbeddedWebsocket::::new_client(rng); + let mut framer = EmbeddedWebsocketFramer::new(websocket); + match framer.connect(stream, buffer, websocket_options).await { + Ok(Some(_)) => {} + Ok(None) => {} + Err(error) => return Err!(XRPLWebsocketException::from(error)), + } + + Ok(AsyncWebsocketClient { + inner: framer, + status: PhantomData::, + }) + } +} + +impl AsyncWebsocketClient { + /// Encode a message to be sent over the websocket. + pub fn encode( + &mut self, + message_type: EmbeddedWebsocketSendMessageType, + end_of_message: bool, + from: &[u8], + to: &mut [u8], + ) -> Result + where + E: Debug, + { + match self + .inner + .encode::(message_type, end_of_message, from, to) + { + Ok(bytes_written) => Ok(bytes_written), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } + } + + /// Send a message over the websocket. + pub async fn send<'b, E, R: serde::Serialize>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + end_of_message: bool, + frame_buf: R, + ) -> Result<()> + where + E: Debug, + { + match serde_json::to_vec(&frame_buf) { + Ok(frame_buf) => match self + .inner + .write( + stream, + stream_buf, + EmbeddedWebsocketSendMessageType::Text, + end_of_message, + frame_buf.as_slice(), + ) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + }, + Err(error) => Err!(error), + } + } + + /// Close the websocket connection. + pub async fn close<'b, E>( + &mut self, + stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), + stream_buf: &'b mut [u8], + close_status: EmbeddedWebsocketCloseStatusCode, + status_description: Option<&str>, + ) -> Result<()> + where + E: Debug, + { + match self + .inner + .close(stream, stream_buf, close_status, status_description) + .await + { + Ok(()) => Ok(()), + Err(error) => Err!(XRPLWebsocketException::from(error)), + } + } + + /// Read a message from the websocket. + pub async fn next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Option>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Some(Ok(read_result)), + Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), + None => None, + } + } + + /// Read a message from the websocket. + /// + /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. + pub async fn try_next<'a, B: Deref, E>( + &'a mut self, + stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), + buffer: &'a mut [u8], + ) -> Result>> + // TODO: Change to Response as soon as implemented + where + E: Debug, + { + match self.inner.read(stream, buffer).await { + Some(Ok(read_result)) => Ok(Some(read_result)), + Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), + None => Ok(None), + } + } +} diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/exceptions.rs index b6aef7f9..220084cb 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/exceptions.rs @@ -1,12 +1,12 @@ use core::fmt::Debug; use core::str::Utf8Error; -#[cfg(feature = "embedded-websocket")] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum XRPLWebsocketException { - #[cfg(feature = "tungstenite")] + #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] #[error("Unable to connect to websocket")] UnableToConnect(tokio_tungstenite::tungstenite::Error), // FramerError @@ -18,7 +18,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, - #[cfg(feature = "embedded-websocket")] + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] @@ -27,7 +27,7 @@ pub enum XRPLWebsocketException { RxBufferTooSmall(usize), } -#[cfg(feature = "embedded-websocket")] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] impl From> for XRPLWebsocketException { fn from(value: FramerError) -> Self { match value { diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index f4f3a0a0..23038986 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,2 +1,14 @@ -pub mod async_websocket_client; pub mod exceptions; + +pub struct WebsocketOpen; +pub struct WebsocketClosed; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +mod embedded_websocket; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +mod tokio_tungstenite; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub use embedded_websocket::*; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tokio_tungstenite::*; diff --git a/src/asynch/clients/tokio_tungstenite.rs b/src/asynch/clients/tokio_tungstenite.rs new file mode 100644 index 00000000..c03bf281 --- /dev/null +++ b/src/asynch/clients/tokio_tungstenite.rs @@ -0,0 +1,112 @@ +use super::{ + exceptions::XRPLWebsocketException, + {WebsocketClosed, WebsocketOpen}, +}; +use crate::Err; + +use anyhow::Result; +use core::marker::PhantomData; +use core::{pin::Pin, task::Poll}; +use futures::{Sink, Stream}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, + WebSocketStream as TungsteniteWebsocketStream, +}; +use url::Url; + +pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; + +pub struct AsyncWebsocketClient { + inner: TungsteniteWebsocketStream>, + status: PhantomData, +} + +impl AsyncWebsocketClient { + pub fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +impl Sink for AsyncWebsocketClient +where + I: serde::Serialize, +{ + type Error = anyhow::Error; + + fn poll_ready( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { + match serde_json::to_string(&item) { + Ok(json) => { + match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } + Err(error) => Err!(error), + } + } + + fn poll_flush( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_flush(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn poll_close( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + match Pin::new(&mut self.inner).poll_close(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } +} + +impl Stream for AsyncWebsocketClient { + type Item = > as Stream>::Item; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll> { + match Pin::new(&mut self.inner).poll_next(cx) { + Poll::Ready(Some(item)) => Poll::Ready(Some(item)), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl AsyncWebsocketClient { + pub async fn open(uri: Url) -> Result> { + match tungstenite_connect_async(uri).await { + Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + inner: websocket_stream, + status: PhantomData::, + }), + Err(error) => { + Err!(XRPLWebsocketException::UnableToConnect::( + error + )) + } + } + } +} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 286b8f61..70076baf 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,2 +1,2 @@ -#[cfg(feature = "net")] +#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] pub mod clients; diff --git a/src/models/ledger/objects/mod.rs b/src/models/ledger/objects/mod.rs index e0f64908..04a8baf2 100644 --- a/src/models/ledger/objects/mod.rs +++ b/src/models/ledger/objects/mod.rs @@ -31,7 +31,6 @@ pub use nftoken_page::*; pub use offer::*; pub use pay_channel::*; pub use ripple_state::*; -pub use ripple_state::*; pub use ticket::*; use serde::{Deserialize, Serialize}; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 64ba2c26..1c406bcd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -3,26 +3,23 @@ pub mod codec; use anyhow::anyhow; use anyhow::Result; -#[cfg(feature = "embedded-websocket")] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio::net::TcpStream; -#[cfg(feature = "embedded-websocket")] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio_util::codec::Framed; -#[cfg(feature = "tungstenite")] -use xrpl::asynch::clients::async_websocket_client::AsyncWebsocketClientTungstenite; -use xrpl::asynch::clients::async_websocket_client::WebsocketOpen; -#[cfg(feature = "embedded-websocket")] -use xrpl::asynch::clients::async_websocket_client::{ - AsyncWebsocketClientEmbeddedWebsocket, EmbeddedWebsocketOptions, -}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use xrpl::asynch::clients::AsyncWebsocketClient; +use xrpl::asynch::clients::WebsocketOpen; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use xrpl::asynch::clients::{AsyncWebsocketClient, EmbeddedWebsocketOptions}; mod constants; pub use constants::*; -#[cfg(feature = "tungstenite")] -pub async fn connect_to_wss_tungstinite_echo( -) -> Result> { +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn connect_to_wss_tungstinite_echo() -> Result> { match ECHO_WSS_SERVER.parse() { - Ok(url) => match AsyncWebsocketClientTungstenite::open(url).await { + Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { assert!(websocket.is_open()); Ok(websocket) @@ -33,11 +30,11 @@ pub async fn connect_to_wss_tungstinite_echo( } } -#[cfg(feature = "embedded-websocket")] +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub async fn connect_to_ws_embedded_websocket_tokio_echo( stream: &mut Framed, buffer: &mut [u8], -) -> Result> { +) -> Result> { let rng = rand::thread_rng(); let websocket_options = EmbeddedWebsocketOptions { path: "/mirror", @@ -47,8 +44,7 @@ pub async fn connect_to_ws_embedded_websocket_tokio_echo( additional_headers: None, }; - match AsyncWebsocketClientEmbeddedWebsocket::open(stream, buffer, rng, &websocket_options).await - { + match AsyncWebsocketClient::open(stream, buffer, rng, &websocket_options).await { Ok(websocket) => { assert!(websocket.is_open()); Ok(websocket) diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 65a73d16..54989551 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,12 +1,11 @@ use anyhow::anyhow; use anyhow::Result; -#[tokio::test] -#[cfg(feature = "tungstenite")] -async fn test_websocket_tungstenite_echo() -> Result<()> { +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn test_websocket_tungstenite_echo() -> Result<()> { use super::common::connect_to_wss_tungstinite_echo; - use futures::{SinkExt, TryStreamExt}; - use xrpl::asynch::clients::async_websocket_client::TungsteniteMessage; + use futures_util::{SinkExt, TryStreamExt}; + use xrpl::asynch::clients::TungsteniteMessage; use xrpl::models::requests::AccountInfo; let mut websocket = connect_to_wss_tungstinite_echo().await?; @@ -22,7 +21,8 @@ async fn test_websocket_tungstenite_echo() -> Result<()> { websocket.send(&account_info).await?; while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - match serde_json::from_str::(response.as_str()) { + let account_info_echo = serde_json::from_str::(response.as_str()); + match account_info_echo { Ok(account_info_echo) => { assert_eq!(account_info, account_info_echo); return Ok(()); @@ -36,12 +36,11 @@ async fn test_websocket_tungstenite_echo() -> Result<()> { Ok(()) } -#[tokio::test] -#[cfg(feature = "embedded-websocket")] -async fn test_embedded_websocket_echo() -> Result<()> { +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub async fn test_embedded_websocket_echo() -> Result<()> { use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; use tokio_util::codec::Framed; - use xrpl::asynch::clients::async_websocket_client::EmbeddedWebsocketReadMessageType; + use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; use xrpl::models::requests::AccountInfo; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index d9510731..eb5264ce 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1,3 +1,3 @@ use super::common; -mod clients; +pub mod clients; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 8732ebce..1e58c815 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -2,3 +2,13 @@ mod common; mod integration; + +use anyhow::Result; + +#[tokio::test] +async fn test_asynch_clients() -> Result<()> { + #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + return integration::clients::test_websocket_tungstenite_echo().await; + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + return integration::clients::test_embedded_websocket_echo().await; +} From 9d9ce07488892a77770bf4c88b33429a1d8000fe Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 14 Jan 2024 20:14:44 +0000 Subject: [PATCH 032/113] fix github workflow --- .github/workflows/unit_test.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 907c18c4..a0a38090 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --no-default-features --features core,models,net,tungstenite + args: --release --no-default-features --features core,models,tungstenite - uses: actions-rs/cargo@v1 with: command: test @@ -33,4 +33,8 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models,net,tungstenite + args: --no-default-features --features core,models,tungstenite + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features core,models,embedded-ws From 613a64c016d7d1ad280e1ff10873fbb0698f582f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 15 Jan 2024 10:57:18 +0000 Subject: [PATCH 033/113] fix tests with --all-features --- tests/integration_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 1e58c815..179d20bf 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -11,4 +11,6 @@ async fn test_asynch_clients() -> Result<()> { return integration::clients::test_websocket_tungstenite_echo().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; + #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] + Ok(()) } From b975c4663be393ed8ecdb8f49048cf2311a11b99 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 15 Jan 2024 18:22:17 +0000 Subject: [PATCH 034/113] fix --no-default-features tests with embedded-ws --- .devcontainer/devcontainer.json | 3 ++- Cargo.toml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2239d3d1..c28ff7f6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,7 +24,8 @@ "swellaby.rust-pack", "panicbit.cargo", "vadimcn.vscode-lldb", - "tamasfe.even-better-toml" + "tamasfe.even-better-toml", + "Swellaby.vscode-rust-test-adapter" ], "settings": { "files.autoSave": "onFocusChange", diff --git a/Cargo.toml b/Cargo.toml index d8893d3c..67e2e023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,11 @@ tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } futures-util = "0.3.30" bytes = { version = "1.4.0", default-features = false } +rand = { version = "0.8.4", default-features = false, features = [ + "getrandom", + "std", + "std_rng", +] } [[bench]] name = "benchmarks" From 788c528b9f34b0a1b3474d4fa75f92c6ab26d0e6 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Jan 2024 13:18:05 +0000 Subject: [PATCH 035/113] try fix colliding embebedded-websocket feature with dep: syntax --- Cargo.toml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67e2e023..6e0d190a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,13 +102,8 @@ requests = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -tungstenite = [ - "dep:url", - "dep:futures", - "tokio/full", - "tokio-tungstenite/native-tls", -] -embedded-ws = ["dep:url", "dep:futures", "dep:embedded-websocket"] +tungstenite = ["url", "futures", "tokio/full", "tokio-tungstenite/native-tls"] +embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] utils = [] std = [ From e889d31c0ba98ad776107d35812ad83d16701608 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 28 Jan 2024 13:21:30 +0000 Subject: [PATCH 036/113] fix ambiguous name error --- src/asynch/clients/{embedded_websocket.rs => embedded_ws.rs} | 0 src/asynch/clients/mod.rs | 4 ++-- src/asynch/clients/{tokio_tungstenite.rs => tungstenite.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/asynch/clients/{embedded_websocket.rs => embedded_ws.rs} (100%) rename src/asynch/clients/{tokio_tungstenite.rs => tungstenite.rs} (100%) diff --git a/src/asynch/clients/embedded_websocket.rs b/src/asynch/clients/embedded_ws.rs similarity index 100% rename from src/asynch/clients/embedded_websocket.rs rename to src/asynch/clients/embedded_ws.rs diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 23038986..54eb0495 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -6,9 +6,9 @@ pub struct WebsocketClosed; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] mod embedded_websocket; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tokio_tungstenite; +mod tungstenite; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub use embedded_websocket::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tokio_tungstenite::*; +pub use tungstenite::*; diff --git a/src/asynch/clients/tokio_tungstenite.rs b/src/asynch/clients/tungstenite.rs similarity index 100% rename from src/asynch/clients/tokio_tungstenite.rs rename to src/asynch/clients/tungstenite.rs From b8698cb6b35b6fa0242477692d4592540400ae88 Mon Sep 17 00:00:00 2001 From: LimpidCrypto <97235361+LimpidCrypto@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:23:15 +0100 Subject: [PATCH 037/113] Revise Models (#74) * current state * initial * initial * add to changelog * impl LedgerObject trait * remove .DS_Store * add documentation * cargo fix * add Default trait to FlagCollection * refactoring of models * fix serde tests * fix serde tests * Delete src/models/.DS_Store * remove dafault_none * resolve comments --- .devcontainer/devcontainer.json | 2 +- .gitignore | 2 + CHANGELOG.md | 1 + src/_serde/mod.rs | 67 +-- src/models/amount/mod.rs | 6 + src/models/exceptions.rs | 10 + src/models/ledger/objects/account_root.rs | 94 ++-- src/models/ledger/objects/amendments.rs | 67 +-- src/models/ledger/objects/amm.rs | 66 +-- src/models/ledger/objects/check.rs | 80 ++-- src/models/ledger/objects/deposit_preauth.rs | 76 +-- src/models/ledger/objects/directory_node.rs | 88 ++-- src/models/ledger/objects/escrow.rs | 78 ++- src/models/ledger/objects/fee_settings.rs | 75 +-- src/models/ledger/objects/ledger_hashes.rs | 71 +-- src/models/ledger/objects/mod.rs | 59 +++ src/models/ledger/objects/negative_unl.rs | 73 +-- src/models/ledger/objects/nftoken_offer.rs | 84 ++-- src/models/ledger/objects/nftoken_page.rs | 83 ++-- src/models/ledger/objects/offer.rs | 85 ++-- src/models/ledger/objects/pay_channel.rs | 83 ++-- src/models/ledger/objects/ripple_state.rs | 86 ++-- src/models/ledger/objects/signer_list.rs | 79 ++-- src/models/ledger/objects/ticket.rs | 73 ++- src/models/mod.rs | 97 +++- src/models/requests/account_channels.rs | 37 +- src/models/requests/account_currencies.rs | 35 +- src/models/requests/account_info.rs | 37 +- src/models/requests/account_lines.rs | 42 +- src/models/requests/account_nfts.rs | 34 +- src/models/requests/account_objects.rs | 38 +- src/models/requests/account_offers.rs | 37 +- src/models/requests/account_tx.rs | 40 +- src/models/requests/book_offers.rs | 65 ++- src/models/requests/channel_authorize.rs | 80 ++-- src/models/requests/channel_verify.rs | 43 +- src/models/requests/deposit_authorize.rs | 41 +- src/models/requests/fee.rs | 29 +- src/models/requests/gateway_balances.rs | 52 +- src/models/requests/ledger.rs | 88 ++-- src/models/requests/ledger_closed.rs | 29 +- src/models/requests/ledger_current.rs | 29 +- src/models/requests/ledger_data.rs | 42 +- src/models/requests/ledger_entry.rs | 150 +++--- src/models/requests/manifest.rs | 32 +- src/models/requests/mod.rs | 132 +----- src/models/requests/nft_buy_offers.rs | 31 +- src/models/requests/nft_sell_offers.rs | 28 +- src/models/requests/no_ripple_check.rs | 45 +- src/models/requests/path_find.rs | 63 ++- src/models/requests/ping.rs | 29 +- src/models/requests/random.rs | 29 +- src/models/requests/ripple_path_find.rs | 49 +- src/models/requests/server_info.rs | 29 +- src/models/requests/server_state.rs | 29 +- src/models/requests/submit.rs | 33 +- src/models/requests/submit_multisigned.rs | 30 +- src/models/requests/subscribe.rs | 66 ++- src/models/requests/transaction_entry.rs | 34 +- src/models/requests/tx.rs | 44 +- src/models/requests/unsubscribe.rs | 55 +-- src/models/transactions/account_delete.rs | 177 ++----- src/models/transactions/account_set.rs | 444 ++++++------------ src/models/transactions/check_cancel.rs | 178 ++----- src/models/transactions/check_cash.rs | 224 +++------ src/models/transactions/check_create.rs | 206 +++----- src/models/transactions/deposit_preauth.rs | 212 +++------ src/models/transactions/escrow_cancel.rs | 188 ++------ src/models/transactions/escrow_create.rs | 262 ++++------- src/models/transactions/escrow_finish.rs | 219 +++------ src/models/transactions/exceptions.rs | 1 + src/models/transactions/mod.rs | 226 +++++---- .../transactions/nftoken_accept_offer.rs | 252 +++------- src/models/transactions/nftoken_burn.rs | 178 ++----- .../transactions/nftoken_cancel_offer.rs | 204 +++----- .../transactions/nftoken_create_offer.rs | 362 +++++--------- src/models/transactions/nftoken_mint.rs | 332 +++++-------- src/models/transactions/offer_cancel.rs | 176 ++----- src/models/transactions/offer_create.rs | 294 ++++-------- src/models/transactions/payment.rs | 375 +++++---------- .../transactions/payment_channel_claim.rs | 230 +++------ .../transactions/payment_channel_create.rs | 204 +++----- .../transactions/payment_channel_fund.rs | 186 ++------ .../pseudo_transactions/enable_amendment.rs | 93 ++-- .../pseudo_transactions/set_fee.rs | 72 ++- .../pseudo_transactions/unl_modify.rs | 72 ++- src/models/transactions/set_regular_key.rs | 170 ++----- src/models/transactions/signer_list_set.rs | 248 +++------- src/models/transactions/ticket_create.rs | 174 ++----- src/models/transactions/trust_set.rs | 216 +++------ tests/integration/clients/mod.rs | 4 +- 91 files changed, 3482 insertions(+), 5988 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c28ff7f6..fd1443fd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,7 @@ // } // ] // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "rustup --version", + "postStartCommand": "rustup --version && rustup component add rustfmt && rustup component add clippy", // Configure tool-specific properties. "customizations": { "vscode": { diff --git a/.gitignore b/.gitignore index 1ec67b52..c59e98f8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ Cargo.lock # Additional src/main.rs + +**/.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 971c1e36..377fb081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - make `new` methods of models public - add `AsyncWebsocketClient` - update dependencies +- add devcontainer ## [[v0.2.0-beta]] ### Added diff --git a/src/_serde/mod.rs b/src/_serde/mod.rs index 8a7584e3..fbba3413 100644 --- a/src/_serde/mod.rs +++ b/src/_serde/mod.rs @@ -1,9 +1,10 @@ //! Serde functionalities -use alloc::string::String; +use crate::models::FlagCollection; +use alloc::format; use alloc::vec::Vec; -use core::fmt::Debug; use core::hash::BuildHasherDefault; +use core::{convert::TryFrom, fmt::Debug}; use fnv::FnvHasher; use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -11,9 +12,9 @@ use strum::IntoEnumIterator; pub type HashMap = hashbrown::HashMap>; -fn serialize_flag(flags: &Vec, s: S) -> Result +fn serialize_flag(flags: &FlagCollection, s: S) -> Result where - F: Serialize, + F: Serialize + IntoEnumIterator, S: Serializer, { let flags_value_result: Result = serde_json::to_value(flags); @@ -35,40 +36,18 @@ where } } -fn deserialize_flags<'de, D, F>(d: D) -> Result, D::Error> +fn deserialize_flags<'de, D, F>(d: D) -> Result, D::Error> where F: Serialize + IntoEnumIterator + Debug, D: Deserializer<'de>, { let flags_u32 = u32::deserialize(d)?; - - let mut flags_vec = Vec::new(); - for flag in F::iter() { - let check_flag_string_result: Result = - serde_json::to_string(&flag); - match check_flag_string_result { - Ok(check_flag_string) => { - let check_flag_u32_result = check_flag_string.parse::(); - match check_flag_u32_result { - Ok(check_flag) => { - if check_flag & flags_u32 == check_flag { - flags_vec.push(flag); - } else { - continue; - } - } - Err(_) => { - return Err(de::Error::custom("SerdeIntermediateStepError: Failed to turn flag into `u32` during deserialization")); - } - }; - } - Err(_) => { - return Err(de::Error::custom("SerdeIntermediateStepError: Failed to turn flag into `String` during deserialization")); - } - }; - } - - Ok(flags_vec) + FlagCollection::::try_from(flags_u32).map_err(|_e| { + de::Error::custom(format!( + "SerdeIntermediateStepError: Failed to turn `u32` into `FlagCollection<{}>` during deserialization", + core::any::type_name::() + )) + }) } /// A `mod` to be used on transaction `flags` fields. It serializes the `Vec` into a `u32`, @@ -77,15 +56,15 @@ pub(crate) mod txn_flags { use core::fmt::Debug; use crate::_serde::{deserialize_flags, serialize_flag}; - use alloc::vec::Vec; use serde::{Deserializer, Serialize, Serializer}; + use crate::models::FlagCollection; use strum::IntoEnumIterator; - pub fn serialize(flags: &Option>, s: S) -> Result + pub fn serialize(flags: &Option>, s: S) -> Result where - F: Serialize, + F: Serialize + IntoEnumIterator, S: Serializer, { if let Some(f) = flags { @@ -95,15 +74,15 @@ pub(crate) mod txn_flags { } } - pub fn deserialize<'de, F, D>(d: D) -> Result>, D::Error> + pub fn deserialize<'de, F, D>(d: D) -> Result>, D::Error> where F: Serialize + IntoEnumIterator + Debug, D: Deserializer<'de>, { - let flags_vec_result: Result, D::Error> = deserialize_flags(d); + let flags_vec_result: Result, D::Error> = deserialize_flags(d); match flags_vec_result { Ok(flags_vec) => { - if flags_vec.is_empty() { + if flags_vec.0.is_empty() { Ok(None) } else { Ok(Some(flags_vec)) @@ -118,23 +97,23 @@ pub(crate) mod lgr_obj_flags { use core::fmt::Debug; use crate::_serde::{deserialize_flags, serialize_flag}; - use alloc::vec::Vec; + use crate::models::FlagCollection; use serde::{Deserializer, Serialize, Serializer}; use strum::IntoEnumIterator; - pub fn serialize(flags: &Vec, s: S) -> Result + pub fn serialize(flags: &FlagCollection, s: S) -> Result where - F: Serialize, + F: Serialize + IntoEnumIterator, S: Serializer, { - if !flags.is_empty() { + if !flags.0.is_empty() { serialize_flag(flags, s) } else { s.serialize_u32(0) } } - pub fn deserialize<'de, F, D>(d: D) -> Result, D::Error> + pub fn deserialize<'de, F, D>(d: D) -> Result, D::Error> where F: Serialize + IntoEnumIterator + Debug, D: Deserializer<'de>, diff --git a/src/models/amount/mod.rs b/src/models/amount/mod.rs index e690ac9a..85430986 100644 --- a/src/models/amount/mod.rs +++ b/src/models/amount/mod.rs @@ -62,3 +62,9 @@ impl<'a> From> for Amount<'a> { Self::XRPAmount(value) } } + +impl<'a> From<&'a str> for Amount<'a> { + fn from(value: &'a str) -> Self { + Self::XRPAmount(value.into()) + } +} diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index 45856761..50f3183d 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -5,6 +5,7 @@ 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] @@ -22,3 +23,12 @@ pub struct JSONRPCException { #[cfg(feature = "std")] impl<'a> alloc::error::Error for XRPLModelException<'a> {} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Error)] +pub enum XRPLFlagsException { + #[error("Cannot convert flag to u32")] + CannotConvertFlagToU32, +} + +#[cfg(feature = "std")] +impl<'a> alloc::error::Error for XRPLFlagsException {} diff --git a/src/models/ledger/objects/account_root.rs b/src/models/ledger/objects/account_root.rs index b71cc418..56180386 100644 --- a/src/models/ledger/objects/account_root.rs +++ b/src/models/ledger/objects/account_root.rs @@ -1,13 +1,15 @@ -use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::{amount::XRPAmount, Model}; use alloc::borrow::Cow; -use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; +use super::{CommonFields, LedgerObject}; + /// There are several options which can be either enabled or disabled for an account. /// These options can be changed with an `AccountSet` transaction. /// @@ -50,16 +52,16 @@ pub enum AccountRootFlag { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct AccountRoot<'a> { - /// The value `0x0061`, mapped to the string `AccountRoot`, indicates that this is an `AccountRoot` - /// object. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this account. - #[serde(with = "lgr_obj_flags")] - pub flags: Vec, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, AccountRootFlag>, + // The custom fields for the AccountRoot model. + // + // See AccountRoot fields: + // `` /// The identifying (classic) address of this account. pub account: Cow<'a, str>, /// The number of objects this account owns in the ledger, which contributes to its owner @@ -117,41 +119,19 @@ pub struct AccountRoot<'a> { pub wallet_size: Option, } -impl<'a> Default for AccountRoot<'a> { - fn default() -> Self { - Self { - flags: Default::default(), - index: Default::default(), - account: Default::default(), - ledger_entry_type: LedgerEntryType::AccountRoot, - owner_count: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - sequence: Default::default(), - account_txn_id: Default::default(), - balance: Default::default(), - burned_nftokens: Default::default(), - domain: Default::default(), - email_hash: Default::default(), - message_key: Default::default(), - minted_nftokens: Default::default(), - nftoken_minter: Default::default(), - regular_key: Default::default(), - ticket_count: Default::default(), - tick_size: Default::default(), - transfer_rate: Default::default(), - wallet_locator: Default::default(), - wallet_size: Default::default(), - } +impl<'a> Model for AccountRoot<'a> {} + +impl<'a> LedgerObject for AccountRoot<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for AccountRoot<'a> {} - impl<'a> AccountRoot<'a> { pub fn new( - flags: Vec, - index: Cow<'a, str>, + flags: FlagCollection, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, owner_count: u32, previous_txn_id: Cow<'a, str>, @@ -173,9 +153,12 @@ impl<'a> AccountRoot<'a> { wallet_size: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::AccountRoot, - flags, - index, + common_fields: CommonFields::new( + flags, + LedgerEntryType::AccountRoot, + index, + ledger_index, + ), account, owner_count, previous_txn_id, @@ -200,16 +183,19 @@ impl<'a> AccountRoot<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::borrow::Cow; use alloc::vec; #[test] - fn test_serialize() { + fn test_account_root_serde() { let account_root = AccountRoot::new( - vec![AccountRootFlag::LsfDefaultRipple], - Cow::from("13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8"), + vec![AccountRootFlag::LsfDefaultRipple].into(), + Some(Cow::from( + "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", + )), + None, Cow::from("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"), 3, Cow::from("0D5FB50FA65C9FE1538FD7E398FFFE9D1908DFA4576D8D7A020040686F93C77D"), @@ -232,12 +218,10 @@ mod test_serde { None, None, ); - let account_root_json = serde_json::to_string(&account_root).unwrap(); - let actual = account_root_json.as_str(); - let expected = r#"{"LedgerEntryType":"AccountRoot","Flags":8388608,"index":"13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OwnerCount":3,"PreviousTxnID":"0D5FB50FA65C9FE1538FD7E398FFFE9D1908DFA4576D8D7A020040686F93C77D","PreviousTxnLgrSeq":14091160,"Sequence":336,"AccountTxnID":"0D5FB50FA65C9FE1538FD7E398FFFE9D1908DFA4576D8D7A020040686F93C77D","Balance":"148446663","Domain":"6D64756F31332E636F6D","EmailHash":"98B4375E1D753E5B91627516F6D70977","MessageKey":"0000000000000000000000070000000300","TransferRate":1004999999}"#; + let serialized = serde_json::to_string(&account_root).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: AccountRoot = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(account_root, deserialized); + } } diff --git a/src/models/ledger/objects/amendments.rs b/src/models/ledger/objects/amendments.rs index e2a34d14..6740200b 100644 --- a/src/models/ledger/objects/amendments.rs +++ b/src/models/ledger/objects/amendments.rs @@ -1,5 +1,7 @@ use crate::models::ledger::LedgerEntryType; -use crate::models::Model; + +use crate::models::FlagCollection; +use crate::models::{Model, NoFlags}; use alloc::vec::Vec; use alloc::{borrow::Cow, string::String}; use derive_new::new; @@ -8,6 +10,8 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; use crate::serde_with_tag; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + serde_with_tag! { /// `` #[derive(Debug, PartialEq, Eq, Clone, new, Default)] @@ -28,16 +32,16 @@ serde_with_tag! { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Amendments<'a> { - /// The value `0x0066`, mapped to the string `Amendments`, indicates that this object describes - /// the status of `amendments` to the XRP Ledger. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no flags - /// for `Amendments` objects. The value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the Amendments model. + // + // See Amendments fields: + // `` /// Array of 256-bit amendment IDs for all currently enabled amendments. If omitted, there are /// no enabled amendments. pub amendments: Option>>, @@ -48,28 +52,26 @@ pub struct Amendments<'a> { impl<'a> Model for Amendments<'a> {} -impl<'a> Default for Amendments<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::Amendments, - flags: Default::default(), - index: Default::default(), - amendments: Default::default(), - majorities: Default::default(), - } +impl<'a> LedgerObject for Amendments<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } impl<'a> Amendments<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, amendments: Option>>, majorities: Option>, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::Amendments, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::Amendments, + index, + ledger_index, + }, amendments, majorities, } @@ -77,16 +79,19 @@ impl<'a> Amendments<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use crate::models::ledger::{Amendments, Majority}; use alloc::borrow::Cow; use alloc::string::ToString; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let amendments = Amendments::new( - Cow::from("7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4"), + Some(Cow::from( + "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4", + )), + None, Some(vec![ Cow::from("42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE"), Cow::from("4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373"), @@ -99,11 +104,11 @@ mod test_serde { close_time: 535589001, }]), ); - let amendments_json = serde_json::to_string(&amendments).unwrap(); - let actual = amendments_json.as_str(); - let expected = r#"{"LedgerEntryType":"Amendments","Flags":0,"index":"7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4","Amendments":["42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE","4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373","6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC","740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11"],"Majorities":[{"Majority":{"Amendment":"1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146","CloseTime":535589001}}]}"#; + let serialized = serde_json::to_string(&amendments).unwrap(); + + let deserialized: Amendments = serde_json::from_str(&serialized).unwrap(); - assert_eq!(expected, actual); + assert_eq!(amendments, deserialized); } // TODO: test_deserialize diff --git a/src/models/ledger/objects/amm.rs b/src/models/ledger/objects/amm.rs index d971552e..48f965df 100644 --- a/src/models/ledger/objects/amm.rs +++ b/src/models/ledger/objects/amm.rs @@ -1,4 +1,6 @@ use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; +use crate::models::NoFlags; use crate::models::{amount::Amount, Currency, Model}; use alloc::borrow::Cow; use alloc::string::String; @@ -9,6 +11,8 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; use crate::serde_with_tag; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + serde_with_tag! { #[derive(Debug, PartialEq, Eq, Clone, new, Default)] pub struct AuthAccount { @@ -51,14 +55,16 @@ serde_with_tag! { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct AMM<'a> { - /// The value `0x0079`, mapped to the string `AMM`, indicates that this is an `AMM` object. - pub ledger_entry_type: LedgerEntryType, - /// Currently there are no flags for the AMM ledger object - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the AMM model. + // + // See AMM fields: + // `` /// The address of the special account that holds this `AMM's` assets. #[serde(rename = "AMMAccount")] pub amm_account: Cow<'a, str>, @@ -84,28 +90,18 @@ pub struct AMM<'a> { pub vote_slots: Option>, } -impl<'a> Default for AMM<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::AMM, - flags: Default::default(), - index: Default::default(), - amm_account: Default::default(), - asset: Default::default(), - asset2: Default::default(), - lptoken_balance: Default::default(), - trading_fee: Default::default(), - auction_slot: Default::default(), - vote_slots: Default::default(), - } +impl<'a> Model for AMM<'a> {} + +impl<'a> LedgerObject for AMM<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for AMM<'a> {} - impl<'a> AMM<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, amm_account: Cow<'a, str>, asset: Currency<'a>, asset2: Currency<'a>, @@ -115,9 +111,12 @@ impl<'a> AMM<'a> { vote_slots: Option>, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::AMM, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::AMM, + index, + ledger_index, + }, amm_account, asset, asset2, @@ -141,7 +140,8 @@ mod test_serde { #[test] fn test_serialize() { let amm = AMM::new( - Cow::from("ForTest"), + Some(Cow::from("ForTest")), + None, Cow::from("rE54zDvgnghAoPopCgvtiqWNq3dU5y836S"), Currency::XRP(XRP::new()), Currency::IssuedCurrency(IssuedCurrency::new( @@ -174,11 +174,11 @@ mod test_serde { 100000, )]), ); - let amm_json = serde_json::to_string(&amm).unwrap(); - let actual = amm_json.as_str(); - let expected = r#"{"LedgerEntryType":"AMM","Flags":0,"index":"ForTest","AMMAccount":"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S","Asset":{"currency":"XRP"},"Asset2":{"currency":"TST","issuer":"rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"},"LPTokenBalance":{"currency":"039C99CD9AB0B70B32ECDA51EAAE471625608EA2","issuer":"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S","value":"71150.53584131501"},"TradingFee":600,"AuctionSlot":{"Account":"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm","DiscountedFee":0,"Expiration":721870180,"Price":{"currency":"039C99CD9AB0B70B32ECDA51EAAE471625608EA2","issuer":"rE54zDvgnghAoPopCgvtiqWNq3dU5y836S","value":"0.8696263565463045"},"AuthAccounts":[{"AuthAccount":{"Account":"rMKXGCbJ5d8LbrqthdG46q3f969MVK2Qeg"}},{"AuthAccount":{"Account":"rBepJuTLFJt3WmtLXYAxSjtBWAeQxVbncv"}}]},"VoteSlots":[{"VoteEntry":{"Account":"rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm","TradingFee":600,"VoteWeight":100000}}]}"#; + let serialized = serde_json::to_string(&amm).unwrap(); + + let deserialized: AMM = serde_json::from_str(&serialized).unwrap(); - assert_eq!(expected, actual); + assert_eq!(amm, deserialized); } // TODO: test_deserialize diff --git a/src/models/ledger/objects/check.rs b/src/models/ledger/objects/check.rs index 41ceefb1..3800ea0e 100644 --- a/src/models/ledger/objects/check.rs +++ b/src/models/ledger/objects/check.rs @@ -1,4 +1,6 @@ use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; +use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; @@ -6,6 +8,8 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// A Check object describes a check, similar to a paper personal check, which can be cashed by its /// destination to get money from its sender. /// @@ -14,15 +18,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Check<'a> { - /// The value `0x0043`, mapped to the string `Check`, indicates that this object is a `Check` object. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no flags - /// for `Check` objects. The value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the Check model. + // + // See Check fields: + // `` /// The sender of the `Check`. Cashing the `Check` debits this address's balance. pub account: Cow<'a, str>, /// The intended recipient of the `Check`. Only this address can cash the `Check`, using a @@ -57,33 +62,18 @@ pub struct Check<'a> { pub source_tag: Option, } -impl<'a> Default for Check<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::Check, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - destination: Default::default(), - destination_node: Default::default(), - destination_tag: Default::default(), - expiration: Default::default(), - invoice_id: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - send_max: Default::default(), - sequence: Default::default(), - source_tag: Default::default(), - } +impl<'a> Model for Check<'a> {} + +impl<'a> LedgerObject for Check<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for Check<'a> {} - impl<'a> Check<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, destination: Cow<'a, str>, owner_node: Cow<'a, str>, @@ -98,9 +88,12 @@ impl<'a> Check<'a> { source_tag: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::Check, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::Check, + index, + ledger_index, + }, account, destination, owner_node, @@ -118,14 +111,17 @@ impl<'a> Check<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::borrow::Cow; #[test] - fn test_serialize() { + fn test_serde() { let check = Check::new( - Cow::from("49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0"), + Some(Cow::from( + "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0", + )), + None, Cow::from("rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo"), Cow::from("rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy"), Cow::from("0000000000000000"), @@ -141,12 +137,10 @@ mod test_serde { )), None, ); - let check_json = serde_json::to_string(&check).unwrap(); - let actual = check_json.as_str(); - let expected = r#"{"LedgerEntryType":"Check","Flags":0,"index":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","OwnerNode":"0000000000000000","PreviousTxnID":"5463C6E08862A1FAE5EDAC12D70ADB16546A1F674930521295BC082494B62924","PreviousTxnLgrSeq":6,"SendMax":"100000000","Sequence":2,"DestinationNode":"0000000000000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"46060241FABCF692D4D934BA2A6C4427CD4279083E38C77CBE642243E43BE291"}"#; + let serialized = serde_json::to_string(&check).unwrap(); - assert_eq!(expected, actual) - } + let deserialized: Check = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(check, deserialized); + } } diff --git a/src/models/ledger/objects/deposit_preauth.rs b/src/models/ledger/objects/deposit_preauth.rs index 2ace6388..926a0c08 100644 --- a/src/models/ledger/objects/deposit_preauth.rs +++ b/src/models/ledger/objects/deposit_preauth.rs @@ -1,10 +1,14 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// A `DepositPreauth` object tracks a preauthorization from one account to another. /// `DepositPreauth` transactions create these objects. /// @@ -13,16 +17,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct DepositPreauth<'a> { - /// The value `0x0070`, mapped to the string `DepositPreauth`, indicates that this is a - /// `DepositPreauth` object. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no flags - /// for `DepositPreauth` objects. The value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the DepositPreauth model. + // + // See DepositPreauth fields: + // `` /// The account that granted the preauthorization. pub account: Cow<'a, str>, /// The account that received the preauthorization. @@ -37,26 +41,18 @@ pub struct DepositPreauth<'a> { pub previous_txn_lgr_seq: u32, } -impl<'a> Default for DepositPreauth<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::DepositPreauth, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - authorize: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - } +impl<'a> Model for DepositPreauth<'a> {} + +impl<'a> LedgerObject for DepositPreauth<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for DepositPreauth<'a> {} - impl<'a> DepositPreauth<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, authorize: Cow<'a, str>, owner_node: Cow<'a, str>, @@ -64,9 +60,12 @@ impl<'a> DepositPreauth<'a> { previous_txn_lgr_seq: u32, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::DepositPreauth, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::DepositPreauth, + index, + ledger_index, + }, account, authorize, owner_node, @@ -77,25 +76,26 @@ impl<'a> DepositPreauth<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let deposit_preauth = DepositPreauth::new( - Cow::from("4A255038CC3ADCC1A9C91509279B59908251728D0DAADB248FFE297D0F7E068C"), + Some(Cow::from( + "4A255038CC3ADCC1A9C91509279B59908251728D0DAADB248FFE297D0F7E068C", + )), + None, Cow::from("rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8"), Cow::from("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"), Cow::from("0000000000000000"), Cow::from("3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702"), 7, ); - let deposit_preauth_json = serde_json::to_string(&deposit_preauth).unwrap(); - let actual = deposit_preauth_json.as_str(); - let expected = r#"{"LedgerEntryType":"DepositPreauth","Flags":0,"index":"4A255038CC3ADCC1A9C91509279B59908251728D0DAADB248FFE297D0F7E068C","Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de","OwnerNode":"0000000000000000","PreviousTxnID":"3E8964D5A86B3CD6B9ECB33310D4E073D64C865A5B866200AD2B7E29F8326702","PreviousTxnLgrSeq":7}"#; + let serialized = serde_json::to_string(&deposit_preauth).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: DepositPreauth = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(deposit_preauth, deserialized); + } } diff --git a/src/models/ledger/objects/directory_node.rs b/src/models/ledger/objects/directory_node.rs index 09dfe038..b8cad877 100644 --- a/src/models/ledger/objects/directory_node.rs +++ b/src/models/ledger/objects/directory_node.rs @@ -1,11 +1,14 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `DirectoryNode` object type provides a list of links to other objects in the ledger's state /// tree. A single conceptual Directory takes the form of a doubly linked list, with one or more /// `DirectoryNode` objects each containing up to 32 IDs of other objects. The first object is called @@ -23,20 +26,22 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct DirectoryNode<'a> { - /// The value 0x0064, mapped to the string `DirectoryNode`, indicates that this object is part - /// of a Directory. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no flags - /// for `DirectoryNode` objects. The value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the DirectoryNode model. + // + // See DirectoryNode fields: + // `` /// (`Offer` Directories only) DEPRECATED. Do not use. pub exchange_rate: Option>, /// The contents of this `Directory`: an array of IDs of other objects. pub indexes: Vec>, + /// The ID of root object for this directory. + pub root_index: Cow<'a, str>, /// If this `Directory` consists of multiple pages, this ID links to the next object in the chain, /// wrapping around at the end. pub index_next: Option, @@ -45,15 +50,13 @@ pub struct DirectoryNode<'a> { pub index_previous: Option, /// (Owner Directories only) The address of the account that owns the objects in this directory. pub owner: Option>, - /// The ID of root object for this directory. - pub root_index: Cow<'a, str>, /// (`Offer` `Directories` only) The currency code of the `TakerGets` amount from the offers in this /// directory. pub taker_gets_currency: Option>, + /// (`Offer` `Directories` only) The currency code of the `TakerPays` amount from the offers in this /// (`Offer` `Directories` only) The issuer of the `TakerGets` amount from the offers in this /// directory. pub taker_gets_issuer: Option>, - /// (`Offer` `Directories` only) The currency code of the `TakerPays` amount from the offers in this /// directory. pub taker_pays_currency: Option>, /// (`Offer` `Directories` only) The issuer of the `TakerPays` amount from the offers in this @@ -61,31 +64,18 @@ pub struct DirectoryNode<'a> { pub taker_pays_issuer: Option>, } -impl<'a> Default for DirectoryNode<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::DirectoryNode, - flags: Default::default(), - index: Default::default(), - exchange_rate: Default::default(), - indexes: Default::default(), - index_next: Default::default(), - index_previous: Default::default(), - owner: Default::default(), - root_index: Default::default(), - taker_gets_currency: Default::default(), - taker_gets_issuer: Default::default(), - taker_pays_currency: Default::default(), - taker_pays_issuer: Default::default(), - } +impl<'a> Model for DirectoryNode<'a> {} + +impl<'a> LedgerObject for DirectoryNode<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for DirectoryNode<'a> {} - impl<'a> DirectoryNode<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, indexes: Vec>, root_index: Cow<'a, str>, exchange_rate: Option>, @@ -98,15 +88,18 @@ impl<'a> DirectoryNode<'a> { taker_pays_issuer: Option>, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::DirectoryNode, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::DirectoryNode, + index, + ledger_index, + }, exchange_rate, indexes, + root_index, index_next, index_previous, owner, - root_index, taker_gets_currency, taker_gets_issuer, taker_pays_currency, @@ -116,14 +109,17 @@ impl<'a> DirectoryNode<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let directory_node = DirectoryNode::new( - Cow::from("1BBEF97EDE88D40CEE2ADE6FEF121166AFE80D99EBADB01A4F069BA8FF484000"), + Some(Cow::from( + "1BBEF97EDE88D40CEE2ADE6FEF121166AFE80D99EBADB01A4F069BA8FF484000", + )), + None, vec![Cow::from( "AD7EAE148287EF12D213A251015F86E6D4BD34B3C4A0A1ED9A17198373F908AD", )], @@ -137,12 +133,10 @@ mod test_serde { Some(Cow::from("0000000000000000000000004A50590000000000")), Some(Cow::from("5BBC0F22F61D9224A110650CFE21CC0C4BE13098")), ); - let directory_node_json = serde_json::to_string(&directory_node).unwrap(); - let actual = directory_node_json.as_str(); - let expected = r#"{"LedgerEntryType":"DirectoryNode","Flags":0,"index":"1BBEF97EDE88D40CEE2ADE6FEF121166AFE80D99EBADB01A4F069BA8FF484000","ExchangeRate":"4F069BA8FF484000","Indexes":["AD7EAE148287EF12D213A251015F86E6D4BD34B3C4A0A1ED9A17198373F908AD"],"RootIndex":"1BBEF97EDE88D40CEE2ADE6FEF121166AFE80D99EBADB01A4F069BA8FF484000","TakerGetsCurrency":"0000000000000000000000000000000000000000","TakerGetsIssuer":"0000000000000000000000000000000000000000","TakerPaysCurrency":"0000000000000000000000004A50590000000000","TakerPaysIssuer":"5BBC0F22F61D9224A110650CFE21CC0C4BE13098"}"#; + let serialized = serde_json::to_string(&directory_node).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: DirectoryNode = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(directory_node, deserialized); + } } diff --git a/src/models/ledger/objects/escrow.rs b/src/models/ledger/objects/escrow.rs index 58f07061..9ab60cb5 100644 --- a/src/models/ledger/objects/escrow.rs +++ b/src/models/ledger/objects/escrow.rs @@ -1,10 +1,15 @@ use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; +use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `Escrow` object type represents a held payment of XRP waiting to be executed or canceled. /// An `EscrowCreate` transaction creates an `Escrow` object in the ledger. A successful `EscrowFinish` /// or `EscrowCancel` transaction deletes the object. If the `Escrow` object has a crypto-condition, @@ -24,16 +29,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Escrow<'a> { - /// The value `0x0075`, mapped to the string `Escrow`, indicates that this object is an - /// `Escrow` object. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no - /// flags for `Escrow` objects. The value is always `0`. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the Escrow model. + // + // See Escrow fields: + // `` /// The address of the owner (sender) of this held payment. This is the account that provided /// the XRP, and gets it back if the held payment is canceled. pub account: Cow<'a, str>, @@ -72,33 +77,18 @@ pub struct Escrow<'a> { pub source_tag: Option, } -impl<'a> Default for Escrow<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::Escrow, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - amount: Default::default(), - cancel_after: Default::default(), - condition: Default::default(), - destination: Default::default(), - destination_node: Default::default(), - destination_tag: Default::default(), - finish_after: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - source_tag: Default::default(), - } +impl<'a> Model for Escrow<'a> {} + +impl<'a> LedgerObject for Escrow<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for Escrow<'a> {} - impl<'a> Escrow<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, amount: Amount<'a>, destination: Cow<'a, str>, @@ -113,9 +103,12 @@ impl<'a> Escrow<'a> { source_tag: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::Escrow, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::Escrow, + index, + ledger_index, + }, account, amount, destination, @@ -140,7 +133,10 @@ mod test_serde { #[test] fn test_serialize() { let escrow = Escrow::new( - Cow::from("DC5F3851D8A1AB622F957761E5963BC5BD439D5C24AC6AD7AC4523F0640244AC"), + Some(Cow::from( + "DC5F3851D8A1AB622F957761E5963BC5BD439D5C24AC6AD7AC4523F0640244AC", + )), + None, Cow::from("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"), Amount::XRPAmount("10000".into()), Cow::from("ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"), @@ -156,12 +152,10 @@ mod test_serde { Some(545354132), Some(11747), ); - let escrow_json = serde_json::to_string(&escrow).unwrap(); - let actual = escrow_json.as_str(); - let expected = r#"{"LedgerEntryType":"Escrow","Flags":0,"index":"DC5F3851D8A1AB622F957761E5963BC5BD439D5C24AC6AD7AC4523F0640244AC","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Amount":"10000","Destination":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","OwnerNode":"0000000000000000","PreviousTxnID":"C44F2EB84196B9AD820313DBEBA6316A15C9A2D35787579ED172B87A30131DA7","PreviousTxnLgrSeq":28991004,"CancelAfter":545440232,"Condition":"A0258020A82A88B2DF843A54F58772E4A3861866ECDB4157645DD9AE528C1D3AEEDABAB6810120","DestinationNode":"0000000000000000","DestinationTag":23480,"FinishAfter":545354132,"SourceTag":11747}"#; + let serialized = serde_json::to_string(&escrow).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: Escrow = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(escrow, deserialized); + } } diff --git a/src/models/ledger/objects/fee_settings.rs b/src/models/ledger/objects/fee_settings.rs index 52bf1db1..67ef763a 100644 --- a/src/models/ledger/objects/fee_settings.rs +++ b/src/models/ledger/objects/fee_settings.rs @@ -1,10 +1,14 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `FeeSettings` object type contains the current base transaction cost and reserve amounts /// as determined by fee voting. Each ledger version contains at most one `FeeSettings` object. /// @@ -13,16 +17,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct FeeSettings<'a> { - /// The value `0x0073`, mapped to the string `FeeSettings`, indicates that this object contains - /// the ledger's fee settings. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines no flags - /// for `FeeSettings` objects. The value is always `0`. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the FeeSettings model. + // + // See FeeSettings fields: + // `` /// The transaction cost of the "reference transaction" in drops of XRP as hexadecimal. pub base_fee: Cow<'a, str>, /// The BaseFee translated into "fee units". @@ -33,34 +37,30 @@ pub struct FeeSettings<'a> { pub reserve_increment: u32, } -impl<'a> Default for FeeSettings<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::FeeSettings, - flags: Default::default(), - index: Default::default(), - base_fee: Default::default(), - reference_fee_units: Default::default(), - reserve_base: Default::default(), - reserve_increment: Default::default(), - } +impl<'a> Model for FeeSettings<'a> {} + +impl<'a> LedgerObject for FeeSettings<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for FeeSettings<'a> {} - impl<'a> FeeSettings<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, base_fee: Cow<'a, str>, reference_fee_units: u32, reserve_base: u32, reserve_increment: u32, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::FeeSettings, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::FeeSettings, + index, + ledger_index, + }, base_fee, reference_fee_units, reserve_base, @@ -70,24 +70,25 @@ impl<'a> FeeSettings<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let fee_settings = FeeSettings::new( - Cow::from("4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651"), + Some(Cow::from( + "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651", + )), + None, Cow::from("000000000000000A"), 10, 20000000, 5000000, ); - let fee_settings_json = serde_json::to_string(&fee_settings).unwrap(); - let actual = fee_settings_json.as_str(); - let expected = r#"{"LedgerEntryType":"FeeSettings","Flags":0,"index":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651","BaseFee":"000000000000000A","ReferenceFeeUnits":10,"ReserveBase":20000000,"ReserveIncrement":5000000}"#; + let serialized = serde_json::to_string(&fee_settings).unwrap(); - assert_eq!(expected, actual) - } + let deserialized: FeeSettings = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(fee_settings, deserialized); + } } diff --git a/src/models/ledger/objects/ledger_hashes.rs b/src/models/ledger/objects/ledger_hashes.rs index 70331f3e..db47a071 100644 --- a/src/models/ledger/objects/ledger_hashes.rs +++ b/src/models/ledger/objects/ledger_hashes.rs @@ -1,11 +1,14 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `LedgerHashes` object type contains a history of prior ledgers that led up to this /// ledger version, in the form of their hashes. Objects of this ledger type are modified /// automatically when closing a ledger. The `LedgerHashes` objects exist to make it possible @@ -23,14 +26,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct LedgerHashes<'a> { - /// The value `0x0068`, mapped to the string `LedgerHashes`, indicates that this object is a - /// list of ledger hashes. - pub ledger_entry_type: LedgerEntryType, - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the LedgerHashes model. + // + // See LedgerHashes fields: + // `` /// **DEPRECATED** Do not use. pub first_ledger_sequence: u32, /// An array of up to 256 ledger hashes. The contents depend on which sub-type of `LedgerHashes` @@ -40,32 +45,29 @@ pub struct LedgerHashes<'a> { pub last_ledger_sequence: u32, } -impl<'a> Default for LedgerHashes<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::LedgerHashes, - flags: Default::default(), - index: Default::default(), - first_ledger_sequence: Default::default(), - hashes: Default::default(), - last_ledger_sequence: Default::default(), - } +impl<'a> Model for LedgerHashes<'a> {} + +impl<'a> LedgerObject for LedgerHashes<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for LedgerHashes<'a> {} - impl<'a> LedgerHashes<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, first_ledger_sequence: u32, hashes: Vec>, last_ledger_sequence: u32, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::LedgerHashes, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::LedgerHashes, + index, + ledger_index, + }, first_ledger_sequence, hashes, last_ledger_sequence, @@ -74,14 +76,17 @@ impl<'a> LedgerHashes<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let ledger_hashes = LedgerHashes::new( - Cow::from("B4979A36CDC7F3D3D5C31A4EAE2AC7D7209DDA877588B9AFC66799692AB0D66B"), + Some(Cow::from( + "B4979A36CDC7F3D3D5C31A4EAE2AC7D7209DDA877588B9AFC66799692AB0D66B", + )), + None, 2, vec![ Cow::from("D638208ADBD04CBB10DE7B645D3AB4BA31489379411A3A347151702B6401AA78"), @@ -92,12 +97,10 @@ mod test_serde { ], 33872029, ); - let ledger_hashes_json = serde_json::to_string(&ledger_hashes).unwrap(); - let actual = ledger_hashes_json.as_str(); - let expected = r#"{"LedgerEntryType":"LedgerHashes","Flags":0,"index":"B4979A36CDC7F3D3D5C31A4EAE2AC7D7209DDA877588B9AFC66799692AB0D66B","FirstLedgerSequence":2,"Hashes":["D638208ADBD04CBB10DE7B645D3AB4BA31489379411A3A347151702B6401AA78","254D690864E418DDD9BCAC93F41B1F53B1AE693FC5FE667CE40205C322D1BE3B","A2B31D28905E2DEF926362822BC412B12ABF6942B73B72A32D46ED2ABB7ACCFA","AB4014846DF818A4B43D6B1686D0DE0644FE711577C5AB6F0B2A21CCEE280140","3383784E82A8BA45F4DD5EF4EE90A1B2D3B4571317DBAC37B859836ADDE644C1"],"LastLedgerSequence":33872029}"#; + let serialized = serde_json::to_string(&ledger_hashes).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: LedgerHashes = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(ledger_hashes, deserialized); + } } diff --git a/src/models/ledger/objects/mod.rs b/src/models/ledger/objects/mod.rs index 04a8baf2..1800162a 100644 --- a/src/models/ledger/objects/mod.rs +++ b/src/models/ledger/objects/mod.rs @@ -33,9 +33,17 @@ pub use pay_channel::*; pub use ripple_state::*; pub use ticket::*; +use derive_new::new; +use strum::IntoEnumIterator; + +use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use strum_macros::Display; +use crate::_serde::lgr_obj_flags; +use crate::models::FlagCollection; + #[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq, Eq)] pub enum LedgerEntryType { AccountRoot = 0x0061, @@ -56,3 +64,54 @@ pub enum LedgerEntryType { SignerList = 0x0053, Ticket = 0x0054, } + +/// The base fields for all ledger object models. +/// +/// See Ledger Object Common Fields: +/// `` +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] +#[serde(rename_all = "PascalCase")] +pub struct CommonFields<'a, F> +where + F: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + /// A bit-map of boolean flags enabled for this account. + #[serde(with = "lgr_obj_flags")] + pub flags: FlagCollection, + /// The type of the ledger object. + pub ledger_entry_type: LedgerEntryType, + /// The object ID of a single object to retrieve from the ledger, as a + /// 64-character (256-bit) hexadecimal string. + #[serde(rename = "index")] + pub index: Option>, + /// The object ID in transaction metadata of a single object to retrieve from the ledger, as a + /// 64-character (256-bit) hexadecimal string. + pub ledger_index: Option>, +} + +impl<'a, T> LedgerObject for CommonFields<'a, T> +where + T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, +{ + fn has_flag(&self, flag: &T) -> bool { + self.flags.0.contains(flag) + } + + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.ledger_entry_type.clone() + } +} + +/// Standard functions for ledger objects. +pub trait LedgerObject +where + T: IntoEnumIterator + Serialize, +{ + fn has_flag(&self, flag: &T) -> bool { + let _txn_flag = flag; + false + } + + fn get_ledger_entry_type(&self) -> LedgerEntryType; +} diff --git a/src/models/ledger/objects/negative_unl.rs b/src/models/ledger/objects/negative_unl.rs index 08dfe1f3..b6d2eeae 100644 --- a/src/models/ledger/objects/negative_unl.rs +++ b/src/models/ledger/objects/negative_unl.rs @@ -1,5 +1,6 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; @@ -9,6 +10,8 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; use crate::serde_with_tag; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + serde_with_tag! { /// Each `DisabledValidator` object represents one disabled validator. #[derive(Debug, PartialEq, Eq, Clone, new, Default)] @@ -28,16 +31,16 @@ serde_with_tag! { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct NegativeUNL<'a> { - /// The value `0x004E`, mapped to the string `NegativeUNL`, indicates that this object is the - /// Negative UNL. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags. No flags are defined for the NegativeUNL object type, so this - /// value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the NegativeUNL model. + // + // See NegativeUNL fields: + // `` /// A list of `DisabledValidator` objects (see below), each representing a trusted validator /// that is currently disabled. pub disabled_validators: Option>, @@ -49,32 +52,29 @@ pub struct NegativeUNL<'a> { pub validator_to_re_enable: Option>, } -impl<'a> Default for NegativeUNL<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::NegativeUNL, - flags: Default::default(), - index: Default::default(), - disabled_validators: Default::default(), - validator_to_disable: Default::default(), - validator_to_re_enable: Default::default(), - } +impl<'a> Model for NegativeUNL<'a> {} + +impl<'a> LedgerObject for NegativeUNL<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for NegativeUNL<'a> {} - impl<'a> NegativeUNL<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, disabled_validators: Option>, validator_to_disable: Option>, validator_to_re_enable: Option>, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::NegativeUNL, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::NegativeUNL, + index, + ledger_index, + }, disabled_validators, validator_to_disable, validator_to_re_enable, @@ -83,15 +83,18 @@ impl<'a> NegativeUNL<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::string::ToString; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let negative_unl = NegativeUNL::new( - Cow::from("2E8A59AA9D3B5B186B0B9E0F62E6C02587CA74A4D778938E957B6357D364B244"), + Some(Cow::from( + "2E8A59AA9D3B5B186B0B9E0F62E6C02587CA74A4D778938E957B6357D364B244", + )), + None, Some(vec![DisabledValidator::new( 1609728, "ED6629D456285AE3613B285F65BBFF168D695BA3921F309949AFCD2CA7AFEC16FE".to_string(), @@ -99,12 +102,10 @@ mod test_serde { None, None, ); - let negative_unl_json = serde_json::to_string(&negative_unl).unwrap(); - let actual = negative_unl_json.as_str(); - let expected = r#"{"LedgerEntryType":"NegativeUNL","Flags":0,"index":"2E8A59AA9D3B5B186B0B9E0F62E6C02587CA74A4D778938E957B6357D364B244","DisabledValidators":[{"DisabledValidator":{"FirstLedgerSequence":1609728,"PublicKey":"ED6629D456285AE3613B285F65BBFF168D695BA3921F309949AFCD2CA7AFEC16FE"}}]}"#; + let serialized = serde_json::to_string(&negative_unl).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: NegativeUNL = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(negative_unl, deserialized); + } } diff --git a/src/models/ledger/objects/nftoken_offer.rs b/src/models/ledger/objects/nftoken_offer.rs index e7498089..224982dd 100644 --- a/src/models/ledger/objects/nftoken_offer.rs +++ b/src/models/ledger/objects/nftoken_offer.rs @@ -1,16 +1,16 @@ -use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; -use alloc::vec::Vec; - use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; +use super::{CommonFields, LedgerObject}; + #[derive( Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, )] @@ -28,16 +28,16 @@ pub enum NFTokenOfferFlag { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct NFTokenOffer<'a> { - /// The value `0x0037`, mapped to the string `NFTokenOffer`, indicates that this is an offer - /// to trade a `NFToken`. - pub ledger_entry_type: LedgerEntryType, - /// A set of flags associated with this object, used to specify various options or settings. - #[serde(with = "lgr_obj_flags")] - pub flags: Vec, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NFTokenOfferFlag>, + // The custom fields for the NFTokenOffer model. + // + // See NFTokenOffer fields: + // `` /// Amount expected or offered for the `NFToken`. If the token has the `lsfOnlyXRP` flag set, /// the amount must be specified in XRP. Sell offers that specify assets other than XRP /// must specify a non-zero amount. Sell offers that specify XRP can be 'free' @@ -71,31 +71,19 @@ pub struct NFTokenOffer<'a> { pub owner_node: Option>, } -impl<'a> Default for NFTokenOffer<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::NFTokenOffer, - flags: Default::default(), - index: Default::default(), - amount: Default::default(), - nftoken_id: Default::default(), - owner: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - destination: Default::default(), - expiration: Default::default(), - nftoken_offer_node: Default::default(), - owner_node: Default::default(), - } +impl<'a> Model for NFTokenOffer<'a> {} + +impl<'a> LedgerObject for NFTokenOffer<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for NFTokenOffer<'a> {} - impl<'a> NFTokenOffer<'a> { pub fn new( - flags: Vec, - index: Cow<'a, str>, + flags: FlagCollection, + index: Option>, + ledger_index: Option>, amount: Amount<'a>, nftoken_id: Cow<'a, str>, owner: Cow<'a, str>, @@ -107,9 +95,12 @@ impl<'a> NFTokenOffer<'a> { owner_node: Option>, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::NFTokenOffer, - flags, - index, + common_fields: CommonFields { + flags, + ledger_entry_type: LedgerEntryType::NFTokenOffer, + index, + ledger_index, + }, amount, nftoken_id, owner, @@ -124,16 +115,19 @@ impl<'a> NFTokenOffer<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::borrow::Cow; use alloc::vec; #[test] - fn test_serialization() { + fn test_serde() { let nftoken_offer = NFTokenOffer::new( - vec![NFTokenOfferFlag::LsfSellNFToken], - Cow::from("AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3"), + vec![NFTokenOfferFlag::LsfSellNFToken].into(), + Some(Cow::from( + "AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3", + )), + None, Amount::XRPAmount("1000000".into()), Cow::from("00081B5825A08C22787716FA031B432EBBC1B101BB54875F0002D2A400000000"), Cow::from("rhRxL3MNvuKEjWjL7TBbZSDacb8PmzAd7m"), @@ -144,12 +138,10 @@ mod test_serde { Some(Cow::from("0")), Some(Cow::from("17")), ); - let nftoken_offer_json = serde_json::to_string(&nftoken_offer).unwrap(); - let actual = nftoken_offer_json.as_str(); - let expected = r#"{"LedgerEntryType":"NFTokenOffer","Flags":1,"index":"AEBABA4FAC212BF28E0F9A9C3788A47B085557EC5D1429E7A8266FB859C863B3","Amount":"1000000","NFTokenID":"00081B5825A08C22787716FA031B432EBBC1B101BB54875F0002D2A400000000","Owner":"rhRxL3MNvuKEjWjL7TBbZSDacb8PmzAd7m","PreviousTxnID":"BFA9BE27383FA315651E26FDE1FA30815C5A5D0544EE10EC33D3E92532993769","PreviousTxnLgrSeq":75443565,"NFTokenOfferNode":"0","OwnerNode":"17"}"#; + let serialized = serde_json::to_string(&nftoken_offer).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: NFTokenOffer = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(nftoken_offer, deserialized); + } } diff --git a/src/models/ledger/objects/nftoken_page.rs b/src/models/ledger/objects/nftoken_page.rs index ff86133d..750ed97e 100644 --- a/src/models/ledger/objects/nftoken_page.rs +++ b/src/models/ledger/objects/nftoken_page.rs @@ -1,5 +1,6 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use derive_new::new; @@ -7,6 +8,8 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new, Default)] #[serde(rename_all = "PascalCase")] pub struct NFToken<'a> { @@ -23,24 +26,24 @@ pub struct NFToken<'a> { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct NFTokenPage<'a> { - /// The value `0x0050`, mapped to the string `NFTokenPage`, indicates that this is a page - /// containing `NFToken` objects. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags. No flags are defined for the NegativeUNL object type, so this - /// value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the NFTokenPage model. + // + // See NFTokenPage fields: + // `` + /// The locator of the next page, if any. Details about this field and how it should be + /// used are outlined below. + pub next_page_min: Option>, /// The collection of NFToken objects contained in this `NFTokenPage` object. /// This specification places an upper bound of 32 `NFToken` objects per page. /// Objects are sorted from low to high with the `NFTokenID` used as the sorting parameter. #[serde(rename = "NFTokens")] pub nftokens: Vec>, - /// The locator of the next page, if any. Details about this field and how it should be - /// used are outlined below. - pub next_page_min: Option>, /// The locator of the previous page, if any. Details about this field and how it should /// be used are outlined below. pub previous_page_min: Option>, @@ -53,36 +56,31 @@ pub struct NFTokenPage<'a> { pub previous_txn_lgr_seq: Option, } -impl<'a> Default for NFTokenPage<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::NFTokenPage, - flags: Default::default(), - index: Default::default(), - nftokens: Default::default(), - next_page_min: Default::default(), - previous_page_min: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - } +impl<'a> Model for NFTokenPage<'a> {} + +impl<'a> LedgerObject for NFTokenPage<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for NFTokenPage<'a> {} - impl<'a> NFTokenPage<'a> { pub fn new( - index: Cow<'a, str>, - nftokens: Vec>, + index: Option>, + ledger_index: Option>, next_page_min: Option>, + nftokens: Vec>, previous_page_min: Option>, previous_txn_id: Option>, previous_txn_lgr_seq: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::NFTokenPage, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::NFTokenPage, + index, + ledger_index, + }, nftokens, next_page_min, previous_page_min, @@ -93,29 +91,28 @@ impl<'a> NFTokenPage<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let nftoken_page = NFTokenPage::new( - Cow::from("ForTest"), + Some(Cow::from("ForTest")), + None, + Some(Cow::from("598EDFD7CF73460FB8C695d6a9397E9073781BA3B78198904F659AAA252A")), vec![NFToken::new( Cow::from("000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"), Cow::from("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469") )], - Some(Cow::from("598EDFD7CF73460FB8C695d6a9397E9073781BA3B78198904F659AAA252A")), Some(Cow::from("598EDFD7CF73460FB8C695d6a9397E907378C8A841F7204C793DCBEF5406")), Some(Cow::from("95C8761B22894E328646F7A70035E9DFBECC90EDD83E43B7B973F626D21A0822")), Some(42891441), ); - let nftoken_page_json = serde_json::to_string(&nftoken_page).unwrap(); - let actual = nftoken_page_json.as_str(); - let expected = r#"{"LedgerEntryType":"NFTokenPage","Flags":0,"index":"ForTest","NFTokens":[{"NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"}],"NextPageMin":"598EDFD7CF73460FB8C695d6a9397E9073781BA3B78198904F659AAA252A","PreviousPageMin":"598EDFD7CF73460FB8C695d6a9397E907378C8A841F7204C793DCBEF5406","PreviousTxnID":"95C8761B22894E328646F7A70035E9DFBECC90EDD83E43B7B973F626D21A0822","PreviousTxnLgrSeq":42891441}"#; + let serialized = serde_json::to_string(&nftoken_page).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: NFTokenPage = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(nftoken_page, deserialized); + } } diff --git a/src/models/ledger/objects/offer.rs b/src/models/ledger/objects/offer.rs index d3db408c..fc3c9fc9 100644 --- a/src/models/ledger/objects/offer.rs +++ b/src/models/ledger/objects/offer.rs @@ -1,16 +1,16 @@ -use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; -use alloc::vec::Vec; - use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use strum_macros::{AsRefStr, Display, EnumIter}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + #[derive( Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, )] @@ -32,16 +32,16 @@ pub enum OfferFlag { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Offer<'a> { - /// The value `0x006F`, mapped to the string `Offer`, indicates that this object - /// describes an `Offer`. - ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this offer. - #[serde(with = "lgr_obj_flags")] - flags: Vec, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, OfferFlag>, + // The custom fields for the Offer model. + // + // See Offer fields: + // `` /// The address of the account that owns this `Offer`. pub account: Cow<'a, str>, /// The ID of the `Offer Directory` that links to this Offer. @@ -69,32 +69,19 @@ pub struct Offer<'a> { pub expiration: Option, } -impl<'a> Default for Offer<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::Offer, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - book_directory: Default::default(), - book_node: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - sequence: Default::default(), - taker_gets: Default::default(), - taker_pays: Default::default(), - expiration: Default::default(), - } +impl<'a> Model for Offer<'a> {} + +impl<'a> LedgerObject for Offer<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for Offer<'a> {} - impl<'a> Offer<'a> { pub fn new( - flags: Vec, - index: Cow<'a, str>, + flags: FlagCollection, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, book_directory: Cow<'a, str>, book_node: Cow<'a, str>, @@ -107,9 +94,12 @@ impl<'a> Offer<'a> { expiration: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::Offer, - flags, - index, + common_fields: CommonFields { + flags, + ledger_entry_type: LedgerEntryType::Offer, + index, + ledger_index, + }, account, book_directory, book_node, @@ -125,17 +115,20 @@ impl<'a> Offer<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use crate::models::amount::IssuedCurrencyAmount; use alloc::borrow::Cow; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let offer = Offer::new( - vec![OfferFlag::LsfSell], - Cow::from("96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797"), + vec![OfferFlag::LsfSell].into(), + Some(Cow::from( + "96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797", + )), + None, Cow::from("rBqb89MRQJnMPq8wTwEbtz4kvxrEDfcYvt"), Cow::from("ACC27DE91DBA86FC509069EAF4BC511D73128B780F2E54BF5E07A369E2446000"), Cow::from("0000000000000000"), @@ -151,12 +144,10 @@ mod test_serde { Amount::XRPAmount("79550000000".into()), None, ); - let offer_json = serde_json::to_string(&offer).unwrap(); - let actual = offer_json.as_str(); - let expected = r#"{"LedgerEntryType":"Offer","Flags":131072,"index":"96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797","Account":"rBqb89MRQJnMPq8wTwEbtz4kvxrEDfcYvt","BookDirectory":"ACC27DE91DBA86FC509069EAF4BC511D73128B780F2E54BF5E07A369E2446000","BookNode":"0000000000000000","OwnerNode":"0000000000000000","PreviousTxnID":"F0AB71E777B2DA54B86231E19B82554EF1F8211F92ECA473121C655BFC5329BF","PreviousTxnLgrSeq":14524914,"Sequence":866,"TakerGets":{"currency":"XAG","issuer":"r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH","value":"37"},"TakerPays":"79550000000"}"#; + let serialized = serde_json::to_string(&offer).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: Offer = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(offer, deserialized); + } } diff --git a/src/models/ledger/objects/pay_channel.rs b/src/models/ledger/objects/pay_channel.rs index d334cfb8..95fe2d3b 100644 --- a/src/models/ledger/objects/pay_channel.rs +++ b/src/models/ledger/objects/pay_channel.rs @@ -1,4 +1,6 @@ use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; +use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; @@ -6,6 +8,8 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `PayChannel` object type represents a payment channel. Payment channels enable small, /// rapid off-ledger payments of XRP that can be later reconciled with the consensus ledger. /// @@ -14,16 +18,16 @@ use serde_with::skip_serializing_none; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct PayChannel<'a> { - /// The value `0x0078`, mapped to the string `PayChannel`, indicates that this object is a - /// payment channel object. - ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines - /// no flags for PayChannel objects. The value is always 0. - flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the PayChannel model. + // + // See PayChannel fields: + // `` /// The source address that owns this payment channel. pub account: Cow<'a, str>, /// Total XRP, in drops, that has been allocated to this channel. This includes XRP @@ -66,35 +70,18 @@ pub struct PayChannel<'a> { pub source_tag: Option, } -impl<'a> Default for PayChannel<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::PayChannel, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - amount: Default::default(), - balance: Default::default(), - destination: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - public_key: Default::default(), - settle_delay: Default::default(), - cancel_after: Default::default(), - destination_tag: Default::default(), - destination_node: Default::default(), - expiration: Default::default(), - source_tag: Default::default(), - } +impl<'a> Model for PayChannel<'a> {} + +impl<'a> LedgerObject for PayChannel<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for PayChannel<'a> {} - impl<'a> PayChannel<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, amount: Amount<'a>, balance: Amount<'a>, @@ -111,9 +98,12 @@ impl<'a> PayChannel<'a> { source_tag: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::PayChannel, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::PayChannel, + index, + ledger_index, + }, account, amount, balance, @@ -133,14 +123,17 @@ impl<'a> PayChannel<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::borrow::Cow; #[test] - fn test_serialize() { + fn test_serde() { let pay_channel = PayChannel::new( - Cow::from("96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797"), + Some(Cow::from( + "96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797", + )), + None, Cow::from("rBqb89MRQJnMPq8wTwEbtz4kvxrEDfcYvt"), Amount::XRPAmount("4325800".into()), Amount::XRPAmount("2323423".into()), @@ -156,12 +149,10 @@ mod test_serde { Some(536027313), Some(0), ); - let pay_channel_json = serde_json::to_string(&pay_channel).unwrap(); - let actual = pay_channel_json.as_str(); - let expected = r#"{"LedgerEntryType":"PayChannel","Flags":0,"index":"96F76F27D8A327FC48753167EC04A46AA0E382E6F57F32FD12274144D00F1797","Account":"rBqb89MRQJnMPq8wTwEbtz4kvxrEDfcYvt","Amount":"4325800","Balance":"2323423","Destination":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OwnerNode":"0000000000000000","PreviousTxnID":"F0AB71E777B2DA54B86231E19B82554EF1F8211F92ECA473121C655BFC5329BF","PreviousTxnLgrSeq":14524914,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","SettleDelay":3600,"CancelAfter":536891313,"DestinationTag":1002341,"DestinationNode":"0000000000000000","Expiration":536027313,"SourceTag":0}"#; + let serialized = serde_json::to_string(&pay_channel).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: PayChannel = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(pay_channel, deserialized); + } } diff --git a/src/models/ledger/objects/ripple_state.rs b/src/models/ledger/objects/ripple_state.rs index 269572ba..ac207c4d 100644 --- a/src/models/ledger/objects/ripple_state.rs +++ b/src/models/ledger/objects/ripple_state.rs @@ -1,14 +1,16 @@ -use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; -use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use strum_macros::{AsRefStr, Display, EnumIter}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + #[derive( Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, )] @@ -42,16 +44,16 @@ pub enum RippleStateFlag { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct RippleState<'a> { - /// The value 0x0072, mapped to the string RippleState, indicates that this object - /// is a RippleState object. - ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean options enabled for this object. - #[serde(with = "lgr_obj_flags")] - flags: Vec, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, RippleStateFlag>, + // The custom fields for the RippleState model. + // + // See RippleState fields: + // `` /// The balance of the trust line, from the perspective of the low account. A negative /// balance indicates that the high account holds tokens issued by the low account. pub balance: Amount<'a>, @@ -87,33 +89,19 @@ pub struct RippleState<'a> { pub low_quality_out: Option, } -impl<'a> Default for RippleState<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::RippleState, - flags: Default::default(), - index: Default::default(), - balance: Default::default(), - high_limit: Default::default(), - high_node: Default::default(), - low_limit: Default::default(), - low_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - high_quality_in: Default::default(), - high_quality_out: Default::default(), - low_quality_in: Default::default(), - low_quality_out: Default::default(), - } +impl<'a> Model for RippleState<'a> {} + +impl<'a> LedgerObject for RippleState<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for RippleState<'a> {} - impl<'a> RippleState<'a> { pub fn new( - flags: Vec, - index: Cow<'a, str>, + flags: FlagCollection, + index: Option>, + ledger_index: Option>, balance: Amount<'a>, high_limit: Amount<'a>, high_node: Cow<'a, str>, @@ -127,9 +115,12 @@ impl<'a> RippleState<'a> { low_quality_out: Option, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::RippleState, - flags, - index, + common_fields: CommonFields { + flags, + ledger_entry_type: LedgerEntryType::RippleState, + index, + ledger_index, + }, balance, high_limit, high_node, @@ -146,16 +137,19 @@ impl<'a> RippleState<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use crate::models::amount::IssuedCurrencyAmount; use alloc::{borrow::Cow, vec}; #[test] - fn test_serialize() { + fn test_serde() { let ripple_state = RippleState::new( - vec![RippleStateFlag::LsfHighReserve, RippleStateFlag::LsfLowAuth], - Cow::from("9CA88CDEDFF9252B3DE183CE35B038F57282BC9503CDFA1923EF9A95DF0D6F7B"), + vec![RippleStateFlag::LsfHighReserve, RippleStateFlag::LsfLowAuth].into(), + Some(Cow::from( + "9CA88CDEDFF9252B3DE183CE35B038F57282BC9503CDFA1923EF9A95DF0D6F7B", + )), + None, Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rrrrrrrrrrrrrrrrrrrrBZbvji".into(), @@ -180,12 +174,10 @@ mod test_serde { None, None, ); - let ripple_state_json = serde_json::to_string(&ripple_state).unwrap(); - let actual = ripple_state_json.as_str(); - let expected = r#"{"LedgerEntryType":"RippleState","Flags":393216,"index":"9CA88CDEDFF9252B3DE183CE35B038F57282BC9503CDFA1923EF9A95DF0D6F7B","Balance":{"currency":"USD","issuer":"rrrrrrrrrrrrrrrrrrrrBZbvji","value":"-10"},"HighLimit":{"currency":"USD","issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","value":"110"},"HighNode":"0000000000000000","LowLimit":{"currency":"USD","issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","value":"0"},"LowNode":"0000000000000000","PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879","PreviousTxnLgrSeq":14090896}"#; + let serialized = serde_json::to_string(&ripple_state).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: RippleState = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(ripple_state, deserialized); + } } diff --git a/src/models/ledger/objects/signer_list.rs b/src/models/ledger/objects/signer_list.rs index 85d4908e..f95cbfdd 100644 --- a/src/models/ledger/objects/signer_list.rs +++ b/src/models/ledger/objects/signer_list.rs @@ -1,5 +1,5 @@ -use crate::_serde::lgr_obj_flags; use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; use alloc::borrow::Cow; use alloc::string::String; @@ -12,6 +12,8 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::serde_with_tag; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + #[derive( Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, )] @@ -45,16 +47,16 @@ serde_with_tag! { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct SignerList<'a> { - /// The value 0x0053, mapped to the string SignerList, indicates that this object is a - /// SignerList object. - ledger_entry_type: LedgerEntryType, - /// A bit-map of Boolean flags enabled for this signer list. - #[serde(with = "lgr_obj_flags")] - flags: Vec, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, SignerListFlag>, + // The custom fields for the SignerList model. + // + // See SignerList fields: + // `` /// A hint indicating which page of the owner directory links to this object, in case /// the directory consists of multiple pages. pub owner_node: Cow<'a, str>, @@ -76,28 +78,19 @@ pub struct SignerList<'a> { pub signer_quorum: u32, } -impl<'a> Default for SignerList<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::SignerList, - flags: Default::default(), - index: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - signer_entries: Default::default(), - signer_list_id: Default::default(), - signer_quorum: Default::default(), - } +impl<'a> Model for SignerList<'a> {} + +impl<'a> LedgerObject for SignerList<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for SignerList<'a> {} - impl<'a> SignerList<'a> { pub fn new( - flags: Vec, - index: Cow<'a, str>, + flags: FlagCollection, + index: Option>, + ledger_index: Option>, owner_node: Cow<'a, str>, previous_txn_id: Cow<'a, str>, previous_txn_lgr_seq: u32, @@ -106,9 +99,12 @@ impl<'a> SignerList<'a> { signer_quorum: u32, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::SignerList, - flags, - index, + common_fields: CommonFields { + flags, + ledger_entry_type: LedgerEntryType::SignerList, + index, + ledger_index, + }, owner_node, previous_txn_id, previous_txn_lgr_seq, @@ -120,16 +116,19 @@ impl<'a> SignerList<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::string::ToString; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let signer_list = SignerList::new( - vec![], - Cow::from("A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7"), + vec![].into(), + Some(Cow::from( + "A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7", + )), + None, Cow::from("0000000000000000"), Cow::from("5904C0DC72C58A83AEFED2FFC5386356AA83FCA6A88C89D00646E51E687CDBE4"), 16061435, @@ -141,12 +140,10 @@ mod test_serde { 0, 3, ); - let signer_list_json = serde_json::to_string(&signer_list).unwrap(); - let actual = signer_list_json.as_str(); - let expected = r#"{"LedgerEntryType":"SignerList","Flags":0,"index":"A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7","OwnerNode":"0000000000000000","PreviousTxnID":"5904C0DC72C58A83AEFED2FFC5386356AA83FCA6A88C89D00646E51E687CDBE4","PreviousTxnLgrSeq":16061435,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2,"WalletLocator":null}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1,"WalletLocator":null}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1,"WalletLocator":null}}],"SignerListID":0,"SignerQuorum":3}"#; + let serialized = serde_json::to_string(&signer_list).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: SignerList = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(signer_list, deserialized); + } } diff --git a/src/models/ledger/objects/ticket.rs b/src/models/ledger/objects/ticket.rs index 09e9d10d..0f85331c 100644 --- a/src/models/ledger/objects/ticket.rs +++ b/src/models/ledger/objects/ticket.rs @@ -1,27 +1,30 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::FlagCollection; use crate::models::Model; +use crate::models::{ledger::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::{CommonFields, LedgerObject}; + /// The `Ticket` object type represents a `Ticket`, which tracks an account sequence number that /// has been set aside for future use. You can create new tickets with a `TicketCreate` transaction. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Ticket<'a> { - /// The value 0x0054, mapped to the string Ticket, indicates that this object - /// is a Ticket object. - pub ledger_entry_type: LedgerEntryType, - /// A bit-map of boolean flags enabled for this object. Currently, the protocol defines - /// no flags for Ticket objects. The value is always 0. - pub flags: u32, - /// The object ID of a single object to retrieve from the ledger, as a - /// 64-character (256-bit) hexadecimal string. - #[serde(rename = "index")] - pub index: Cow<'a, str>, + /// The base fields for all ledger object models. + /// + /// See Ledger Object Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the Ticket model. + // + // See Ticket fields: + // `` /// The account that owns this Ticket. pub account: Cow<'a, str>, /// A hint indicating which page of the owner directory links to this object, in case the @@ -37,26 +40,18 @@ pub struct Ticket<'a> { pub ticket_sequence: u32, } -impl<'a> Default for Ticket<'a> { - fn default() -> Self { - Self { - ledger_entry_type: LedgerEntryType::Ticket, - flags: Default::default(), - index: Default::default(), - account: Default::default(), - owner_node: Default::default(), - previous_txn_id: Default::default(), - previous_txn_lgr_seq: Default::default(), - ticket_sequence: Default::default(), - } +impl<'a> Model for Ticket<'a> {} + +impl<'a> LedgerObject for Ticket<'a> { + fn get_ledger_entry_type(&self) -> LedgerEntryType { + self.common_fields.get_ledger_entry_type() } } -impl<'a> Model for Ticket<'a> {} - impl<'a> Ticket<'a> { pub fn new( - index: Cow<'a, str>, + index: Option>, + ledger_index: Option>, account: Cow<'a, str>, owner_node: Cow<'a, str>, previous_txn_id: Cow<'a, str>, @@ -64,9 +59,12 @@ impl<'a> Ticket<'a> { ticket_sequence: u32, ) -> Self { Self { - ledger_entry_type: LedgerEntryType::Ticket, - flags: 0, - index, + common_fields: CommonFields { + flags: FlagCollection::default(), + ledger_entry_type: LedgerEntryType::Ticket, + index, + ledger_index, + }, account, owner_node, previous_txn_id, @@ -77,25 +75,24 @@ impl<'a> Ticket<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let ticket = Ticket::new( - Cow::from("ForTest"), + Some(Cow::from("ForTest")), + None, Cow::from("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"), Cow::from("0000000000000000"), Cow::from("F19AD4577212D3BEACA0F75FE1BA1644F2E854D46E8D62E9C95D18E9708CBFB1"), 4, 3, ); - let ticket_json = serde_json::to_string(&ticket).unwrap(); - let actual = ticket_json.as_str(); - let expected = r#"{"LedgerEntryType":"Ticket","Flags":0,"index":"ForTest","Account":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de","OwnerNode":"0000000000000000","PreviousTxnID":"F19AD4577212D3BEACA0F75FE1BA1644F2E854D46E8D62E9C95D18E9708CBFB1","PreviousTxnLgrSeq":4,"TicketSequence":3}"#; + let serialized = serde_json::to_string(&ticket).unwrap(); - assert_eq!(expected, actual); - } + let deserialized: Ticket = serde_json::from_str(&serialized).unwrap(); - // TODO: test_deserialize + assert_eq!(ticket, deserialized); + } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 92958f0e..7cd5e1dc 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -24,13 +24,19 @@ pub mod amount; pub mod currency; pub mod utils; +use core::convert::TryFrom; + use derive_new::new; pub use model::Model; +use strum::IntoEnumIterator; use crate::models::currency::{Currency, XRP}; -use alloc::borrow::Cow; +use crate::models::exceptions::XRPLFlagsException; +use crate::Err; +use alloc::{borrow::Cow, vec::Vec}; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use strum_macros::Display; +use strum_macros::{AsRefStr, Display, EnumIter}; /// Represents the object types that an AccountObjects /// Request can ask for. @@ -49,6 +55,12 @@ pub enum AccountObjectType { Ticket, } +/// Represents the type of flags when the XRPL model has no flags. +#[derive( + Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr, EnumIter, Copy, +)] +pub enum NoFlags {} + /// A PathStep represents an individual step along a Path. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, new)] #[serde(rename_all = "PascalCase")] @@ -95,6 +107,87 @@ fn default_fee_div_max() -> Option { Some(1) } +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, new)] +pub struct FlagCollection(pub(crate) Vec) +where + T: IntoEnumIterator; + +impl Iterator for FlagCollection +where + T: IntoEnumIterator, +{ + type Item = T; + + fn next(&mut self) -> Option { + self.0.pop() + } +} + +impl Default for FlagCollection +where + T: IntoEnumIterator, +{ + fn default() -> Self { + FlagCollection(Vec::new()) + } +} + +impl From> for FlagCollection +where + T: IntoEnumIterator, +{ + fn from(flags: Vec) -> Self { + FlagCollection(flags) + } +} + +impl TryFrom for FlagCollection +where + T: IntoEnumIterator + Serialize, +{ + type Error = anyhow::Error; + + fn try_from(flags: u32) -> Result { + let mut flag_collection = Vec::new(); + for flag in T::iter() { + let flag_as_u32 = flag_to_u32(&flag)?; + if flags & flag_as_u32 == flag_as_u32 { + flag_collection.push(flag); + } + } + Ok(FlagCollection::new(flag_collection)) + } +} + +impl TryFrom> for u32 +where + T: IntoEnumIterator + Serialize, +{ + type Error = anyhow::Error; + + fn try_from(flag_collection: FlagCollection) -> Result { + let mut flags = 0; + for flag in flag_collection { + let flag_as_u32 = flag_to_u32(&flag)?; + flags |= flag_as_u32; + } + Ok(flags) + } +} + +fn flag_to_u32(flag: &T) -> Result +where + T: Serialize, +{ + match serde_json::to_string(flag) { + Ok(flag_as_string) => match flag_as_string.parse::() { + Ok(flag_as_u32) => Ok(flag_as_u32), + Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), + }, + Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), + } +} + // pub trait SignAndSubmitError { // fn _get_field_error(&self) -> Result<(), XRPLSignAndSubmitException>; // fn _get_key_type_error(&self) -> Result<(), XRPLSignAndSubmitException>; diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index b322633e..5965eb52 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::CommonFields; + /// This request returns information about an account's Payment /// Channels. This includes only channels where the specified /// account is the channel's source, not the destination. @@ -21,7 +23,7 @@ use crate::models::{requests::RequestMethod, Model}; /// ``` /// use xrpl::models::requests::AccountChannels; /// -/// let json = r#"{"account":"rH6ZiHU1PGamME2LvVTxrgvfjQpppWKGmr","marker":12345678,"command":"account_channels"}"#.to_string(); +/// let json = r#"{"command":"account_channels","account":"rH6ZiHU1PGamME2LvVTxrgvfjQpppWKGmr","marker":12345678}"#.to_string(); /// let model: AccountChannels = serde_json::from_str(&json).expect(""); /// let revert: Option = match serde_json::to_string(&model) { /// Ok(model) => Some(model), @@ -33,12 +35,13 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountChannels<'a> { + /// Common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The unique identifier of an account, typically the /// account's Address. The request returns channels where /// this account is the channel's owner/source. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -54,47 +57,31 @@ pub struct AccountChannels<'a> { /// Value from a previous paginated response. /// Resume retrieving data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_channels")] - pub command: RequestMethod, -} - -impl<'a> Default for AccountChannels<'a> { - fn default() -> Self { - AccountChannels { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - limit: None, - destination_account: None, - marker: None, - command: RequestMethod::AccountChannels, - } - } } impl<'a> Model for AccountChannels<'a> {} impl<'a> AccountChannels<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, + destination_account: Option>, ledger_hash: Option>, ledger_index: Option>, limit: Option, - destination_account: Option>, marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountChannels, + id, + }, account, - id, ledger_hash, ledger_index, limit, destination_account, marker, - command: RequestMethod::AccountChannels, } } } diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index af18eddd..c6307543 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{default_false, requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request retrieves a list of currencies that an account /// can send or receive, based on its trust lines. This is not /// a thoroughly confirmed list, but it can be used to populate @@ -14,11 +16,12 @@ use crate::models::{default_false, requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountCurrencies<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly /// the account's Address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -30,41 +33,33 @@ pub struct AccountCurrencies<'a> { /// The default is false. #[serde(default = "default_false")] pub strict: Option, - /// The request method. - #[serde(default = "RequestMethod::account_currencies")] - pub command: RequestMethod, } -impl<'a> Default for AccountCurrencies<'a> { - fn default() -> Self { - AccountCurrencies { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - strict: None, - command: RequestMethod::AccountCurrencies, - } +impl<'a> Model for AccountCurrencies<'a> {} + +impl<'a> Request for AccountCurrencies<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountCurrencies<'a> {} - impl<'a> AccountCurrencies<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, strict: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountCurrencies, + id, + }, account, - id, ledger_hash, ledger_index, strict, - command: RequestMethod::AccountCurrencies, } } } diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 1f1230d2..1db06aa4 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request retrieves information about an account, its /// activity, and its XRP balance. All information retrieved /// is relative to a particular version of the ledger. @@ -13,11 +15,12 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountInfo<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's Address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -37,32 +40,20 @@ pub struct AccountInfo<'a> { /// If true, and the MultiSign amendment is enabled, also /// returns any SignerList objects associated with this account. pub signer_lists: Option, - /// The request method. - #[serde(default = "RequestMethod::account_info")] - pub command: RequestMethod, } -impl<'a> Default for AccountInfo<'a> { - fn default() -> Self { - AccountInfo { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - strict: None, - queue: None, - signer_lists: None, - command: RequestMethod::AccountInfo, - } +impl<'a> Model for AccountInfo<'a> {} + +impl<'a> Request for AccountInfo<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountInfo<'a> {} - impl<'a> AccountInfo<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, strict: Option, @@ -70,14 +61,16 @@ impl<'a> AccountInfo<'a> { signer_lists: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountInfo, + id, + }, account, - id, ledger_hash, ledger_index, strict, queue, signer_lists, - command: RequestMethod::AccountInfo, } } } diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index de572052..432f74d4 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request returns information about an account's trust /// lines, including balances in all non-XRP currencies and /// assets. All information retrieved is relative to a particular @@ -14,11 +16,12 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountLines<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's Address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -31,50 +34,35 @@ pub struct AccountLines<'a> { /// The Address of a second account. If provided, show only /// lines of trust connecting the two accounts. pub peer: Option>, - /// Value from a previous paginated response. Resume retrieving - /// data where that response left off. - pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_lines")] - pub command: RequestMethod, } -impl<'a> Default for AccountLines<'a> { - fn default() -> Self { - AccountLines { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - limit: None, - peer: None, - marker: None, - command: RequestMethod::AccountLines, - } +impl<'a> Model for AccountLines<'a> {} + +impl<'a> Request for AccountLines<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountLines<'a> {} - impl<'a> AccountLines<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, limit: Option, peer: Option>, - marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountLines, + id, + }, account, - id, ledger_hash, ledger_index, limit, peer, - marker, - command: RequestMethod::AccountLines, } } } diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index 960782e6..2447f3af 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -4,17 +4,20 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This method retrieves all of the NFTs currently owned /// by the specified account. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountNfts<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The unique identifier of an account, typically the /// account's Address. The request returns a list of /// NFTs owned by this account. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// Limit the number of token pages to retrieve. Each page /// can contain up to 32 NFTs. The limit value cannot be /// lower than 20 or more than 400. The default is 100. @@ -22,38 +25,31 @@ pub struct AccountNfts<'a> { /// Value from a previous paginated response. Resume /// retrieving data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_nfts")] - pub command: RequestMethod, } -impl<'a> Default for AccountNfts<'a> { - fn default() -> Self { - AccountNfts { - account: "".into(), - id: None, - limit: None, - marker: None, - command: RequestMethod::AccountNfts, - } +impl<'a> Model for AccountNfts<'a> {} + +impl<'a> Request for AccountNfts<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountNfts<'a> {} - impl<'a> AccountNfts<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, limit: Option, marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountNfts, + id, + }, account, - id, limit, marker, - command: RequestMethod::AccountNfts, } } } diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index 306f4742..c51bcc4f 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -5,6 +5,8 @@ use strum_macros::Display; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// Represents the object types that an AccountObjects /// Request can ask for. #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display)] @@ -31,11 +33,12 @@ pub enum AccountObjectType { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountObjects<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -56,33 +59,20 @@ pub struct AccountObjects<'a> { /// Value from a previous paginated response. Resume retrieving /// data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_objects")] - pub command: RequestMethod, } -impl<'a> Default for AccountObjects<'a> { - fn default() -> Self { - AccountObjects { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - r#type: None, - deletion_blockers_only: None, - limit: None, - marker: None, - command: RequestMethod::AccountObjects, - } +impl<'a> Model for AccountObjects<'a> {} + +impl<'a> Request for AccountObjects<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountObjects<'a> {} - impl<'a> AccountObjects<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, r#type: Option, @@ -91,15 +81,17 @@ impl<'a> AccountObjects<'a> { marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountObjects, + id, + }, account, - id, ledger_hash, ledger_index, r#type, deletion_blockers_only, limit, marker, - command: RequestMethod::AccountObjects, } } } diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 0c76872d..5e2cfb02 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request retrieves a list of offers made by a given account /// that are outstanding as of a particular ledger version. /// @@ -12,11 +14,12 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountOffers<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's Address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string identifying the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or "current", @@ -33,32 +36,20 @@ pub struct AccountOffers<'a> { /// Value from a previous paginated response. Resume retrieving /// data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_offers")] - pub command: RequestMethod, } -impl<'a> Default for AccountOffers<'a> { - fn default() -> Self { - AccountOffers { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - limit: None, - strict: None, - marker: None, - command: RequestMethod::AccountOffers, - } +impl<'a> Model for AccountOffers<'a> {} + +impl<'a> Request for AccountOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountOffers<'a> {} - impl<'a> AccountOffers<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, limit: Option, @@ -66,14 +57,16 @@ impl<'a> AccountOffers<'a> { marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountOffers, + id, + }, account, - id, ledger_hash, ledger_index, limit, strict, marker, - command: RequestMethod::AccountOffers, } } } diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 4f0e114a..48da5afd 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request retrieves from the ledger a list of /// transactions that involved the specified account. /// @@ -12,11 +14,12 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct AccountTx<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// Use to look for transactions from a single ledger only. pub ledger_hash: Option>, /// Use to look for transactions from a single ledger only. @@ -46,35 +49,20 @@ pub struct AccountTx<'a> { /// if there is a change in the server's range of available /// ledgers. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::account_tx")] - pub command: RequestMethod, } -impl<'a> Default for AccountTx<'a> { - fn default() -> Self { - AccountTx { - account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - binary: None, - forward: None, - ledger_index_min: None, - ledger_index_max: None, - limit: None, - marker: None, - command: RequestMethod::AccountTx, - } +impl<'a> Model for AccountTx<'a> {} + +impl<'a> Request for AccountTx<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for AccountTx<'a> {} - impl<'a> AccountTx<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, + account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, binary: Option, @@ -85,8 +73,11 @@ impl<'a> AccountTx<'a> { marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::AccountTx, + id, + }, account, - id, ledger_hash, ledger_index, binary, @@ -95,7 +86,6 @@ impl<'a> AccountTx<'a> { ledger_index_max, limit, marker, - command: RequestMethod::AccountTx, } } } diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index 64e4e5ba..57887d38 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{currency::Currency, requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The book_offers method retrieves a list of offers, also known /// as the order book, between two currencies. /// @@ -12,6 +14,9 @@ use crate::models::{currency::Currency, requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct BookOffers<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Specification of which currency the account taking /// the offer would receive, as an object with currency /// and issuer fields (omit issuer for XRP), @@ -22,8 +27,6 @@ pub struct BookOffers<'a> { /// issuer fields (omit issuer for XRP), /// like currency amounts. pub taker_pays: Currency<'a>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -39,47 +42,37 @@ pub struct BookOffers<'a> { /// included in the response. (You can use this to look /// up your own orders to cancel them.) pub taker: Option>, - /// The request method. - #[serde(default = "RequestMethod::book_offers")] - pub command: RequestMethod, } -impl<'a> Default for BookOffers<'a> { - fn default() -> Self { - BookOffers { - taker_gets: Default::default(), - taker_pays: Default::default(), - id: None, - ledger_hash: None, - ledger_index: None, - limit: None, - taker: None, - command: RequestMethod::BookOffers, - } +impl<'a> Model for BookOffers<'a> {} + +impl<'a> Request for BookOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for BookOffers<'a> {} - impl<'a> BookOffers<'a> { pub fn new( + id: Option>, taker_gets: Currency<'a>, taker_pays: Currency<'a>, - id: Option>, ledger_hash: Option>, ledger_index: Option>, limit: Option, taker: Option>, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::BookOffers, + id, + }, taker_gets, taker_pays, - id, ledger_hash, ledger_index, limit, taker, - command: RequestMethod::BookOffers, } } } @@ -92,21 +85,19 @@ mod test { #[test] fn test_serde() { - let req = BookOffers { - taker_gets: Currency::IssuedCurrency(IssuedCurrency::new( - "EUR".into(), - "rTestIssuer".into(), - )), - taker_pays: Currency::XRP(XRP::new()), - ..Default::default() - }; - let req_as_string = serde_json::to_string(&req).unwrap(); - let req_json = req_as_string.as_str(); - let expected_json = r#"{"taker_gets":{"currency":"EUR","issuer":"rTestIssuer"},"taker_pays":{"currency":"XRP"},"command":"book_offers"}"#; - let deserialized_req: BookOffers = serde_json::from_str(req_json).unwrap(); + let req = BookOffers::new( + None, + Currency::IssuedCurrency(IssuedCurrency::new("EUR".into(), "rTestIssuer".into())), + Currency::XRP(XRP::new()), + None, + None, + None, + None, + ); + let serialized = serde_json::to_string(&req).unwrap(); + + let deserialized: BookOffers = serde_json::from_str(&serialized).unwrap(); - assert_eq!(req_json, expected_json); - assert_eq!(req, deserialized_req); - assert_eq!(Currency::XRP(XRP::new()), deserialized_req.taker_pays); + assert_eq!(req, deserialized); } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 93ef01d3..835cdd7e 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -11,6 +11,8 @@ use crate::{ Err, }; +use super::{CommonFields, Request}; + /// The channel_authorize method creates a signature that can be /// used to redeem a specific amount of XRP from a payment channel. /// @@ -31,6 +33,9 @@ use crate::{ #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ChannelAuthorize<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The unique ID of the payment channel to use. pub channel_id: Cow<'a, str>, /// Cumulative amount of XRP, in drops, to authorize. @@ -38,8 +43,6 @@ pub struct ChannelAuthorize<'a> { /// of XRP from this channel, the signature created by this /// method can be redeemed for the difference. pub amount: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// The secret key to use to sign the claim. This must be /// the same key pair as the public key specified in the /// channel. Cannot be used with seed, seed_hex, or passphrase. @@ -66,25 +69,6 @@ pub struct ChannelAuthorize<'a> { /// The signing algorithm of the cryptographic key pair provided. /// Valid types are secp256k1 or ed25519. The default is secp256k1. pub key_type: Option, - /// The request method. - #[serde(default = "RequestMethod::channel_authorize")] - pub command: RequestMethod, -} - -impl<'a> Default for ChannelAuthorize<'a> { - fn default() -> Self { - ChannelAuthorize { - channel_id: "".into(), - amount: "".into(), - id: None, - secret: None, - seed: None, - seed_hex: None, - passphrase: None, - key_type: None, - command: RequestMethod::ChannelAuthorize, - } - } } impl<'a> Model for ChannelAuthorize<'a> { @@ -96,6 +80,12 @@ impl<'a> Model for ChannelAuthorize<'a> { } } +impl<'a> Request for ChannelAuthorize<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() + } +} + impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { fn _get_field_error(&self) -> Result<(), XRPLChannelAuthorizeException> { let mut signing_methods = Vec::new(); @@ -125,9 +115,9 @@ impl<'a> ChannelAuthorizeError for ChannelAuthorize<'a> { impl<'a> ChannelAuthorize<'a> { pub fn new( + id: Option>, channel_id: Cow<'a, str>, amount: Cow<'a, str>, - id: Option>, secret: Option>, seed: Option>, seed_hex: Option>, @@ -135,15 +125,17 @@ impl<'a> ChannelAuthorize<'a> { key_type: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::ChannelAuthorize, + id, + }, channel_id, amount, - id, secret, seed, seed_hex, passphrase, key_type, - command: RequestMethod::ChannelAuthorize, } } } @@ -162,21 +154,39 @@ mod test_channel_authorize_errors { #[test] fn test_fields_error() { - let channel_authorize = ChannelAuthorize { - command: RequestMethod::ChannelAuthorize, - channel_id: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3".into(), - amount: "1000000".into(), - id: None, - secret: None, - seed: Some("".into()), - seed_hex: Some("".into()), - passphrase: None, - key_type: Some(CryptoAlgorithm::SECP256K1), - }; + let channel_authorize = ChannelAuthorize::new( + None, + "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3".into(), + "1000000".into(), + None, + Some("".into()), + Some("".into()), + None, + Some(CryptoAlgorithm::SECP256K1), + ); assert_eq!( channel_authorize.validate().unwrap_err().to_string().as_str(), "The field `secret` can not be defined with `seed`, `seed_hex`, `passphrase`. Define exactly one of them. For more information see: " ); } + + #[test] + fn test_serde() { + let req = ChannelAuthorize::new( + None, + "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3".into(), + "1000000".into(), + None, + Some("".into()), + None, + None, + Some(CryptoAlgorithm::SECP256K1), + ); + let serialized = serde_json::to_string(&req).unwrap(); + + let deserialized: ChannelAuthorize = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(req, deserialized); + } } diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index d041d715..949b6ad1 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -2,7 +2,9 @@ use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::{requests::RequestMethod, Model}; +use crate::models::{amount::XRPAmount, requests::RequestMethod, Model}; + +use super::{CommonFields, Request}; /// The channel_verify method checks the validity of a signature /// that can be used to redeem a specific amount of XRP from a @@ -10,54 +12,47 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ChannelVerify<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, + /// The amount of XRP, in drops, the provided signature authorizes. + pub amount: XRPAmount<'a>, /// The Channel ID of the channel that provides the XRP. /// This is a 64-character hexadecimal string. pub channel_id: Cow<'a, str>, - /// The amount of XRP, in drops, the provided signature authorizes. - pub amount: Cow<'a, str>, /// The public key of the channel and the key pair that was used to /// create the signature, in hexadecimal or the XRP Ledger's /// base58 format. pub public_key: Cow<'a, str>, /// The signature to verify, in hexadecimal. pub signature: Cow<'a, str>, - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::channel_verify")] - pub command: RequestMethod, } -impl<'a> Default for ChannelVerify<'a> { - fn default() -> Self { - ChannelVerify { - channel_id: "".into(), - amount: "".into(), - public_key: "".into(), - signature: "".into(), - id: None, - command: RequestMethod::ChannelVerify, - } +impl<'a> Model for ChannelVerify<'a> {} + +impl<'a> Request for ChannelVerify<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for ChannelVerify<'a> {} - impl<'a> ChannelVerify<'a> { pub fn new( + id: Option>, + amount: XRPAmount<'a>, channel_id: Cow<'a, str>, - amount: Cow<'a, str>, public_key: Cow<'a, str>, signature: Cow<'a, str>, - id: Option>, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::ChannelVerify, + id, + }, channel_id, amount, public_key, signature, - id, - command: RequestMethod::ChannelVerify, } } } diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 973aa720..5204bc69 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The deposit_authorized command indicates whether one account /// is authorized to send payments directly to another. /// @@ -12,52 +14,45 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct DepositAuthorized<'a> { - /// The sender of a possible payment. - pub source_account: Cow<'a, str>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The recipient of a possible payment. pub destination_account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, + /// The sender of a possible payment. + pub source_account: Cow<'a, str>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. pub ledger_index: Option>, - /// The request method. - #[serde(default = "RequestMethod::deposit_authorization")] - pub command: RequestMethod, } -impl<'a> Default for DepositAuthorized<'a> { - fn default() -> Self { - DepositAuthorized { - source_account: "".into(), - destination_account: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - command: RequestMethod::DepositAuthorized, - } +impl<'a> Model for DepositAuthorized<'a> {} + +impl<'a> Request for DepositAuthorized<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for DepositAuthorized<'a> {} - impl<'a> DepositAuthorized<'a> { pub fn new( - source_account: Cow<'a, str>, - destination_account: Cow<'a, str>, id: Option>, + destination_account: Cow<'a, str>, + source_account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::DepositAuthorized, + id, + }, source_account, destination_account, - id, ledger_hash, ledger_index, - command: RequestMethod::DepositAuthorized, } } } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 2ce491a2..2ab0ee8a 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The fee command reports the current state of the open-ledger /// requirements for the transaction cost. This requires the /// FeeEscalation amendment to be enabled. This is a public @@ -14,29 +16,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Fee<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::fee")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for Fee<'a> { - fn default() -> Self { - Fee { - id: None, - command: RequestMethod::Fee, - } +impl<'a> Model for Fee<'a> {} + +impl<'a> Request for Fee<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Fee<'a> {} - impl<'a> Fee<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::Fee, + common_fields: CommonFields { + command: RequestMethod::Fee, + id, + }, } } } diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index b5923e9e..58dc61e7 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -5,6 +5,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This request calculates the total balances issued by a /// given account, optionally excluding amounts held by /// operational addresses. @@ -14,59 +16,51 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct GatewayBalances<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The Address to check. This should be the issuing address. pub account: Cow<'a, str>, - /// The unique request id. - pub id: Option>, - /// If true, only accept an address or public key for the - /// account parameter. Defaults to false. - pub strict: Option, + /// An operational address to exclude from the balances + /// issued, or an array of such addresses. + pub hotwallet: Option>>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger version to use, or a /// shortcut string to choose a ledger automatically. pub ledger_index: Option>, - /// An operational address to exclude from the balances - /// issued, or an array of such addresses. - pub hotwallet: Option>>, - /// The request method. - #[serde(default = "RequestMethod::deposit_authorization")] - pub command: RequestMethod, + /// If true, only accept an address or public key for the + /// account parameter. Defaults to false. + pub strict: Option, } -impl<'a> Default for GatewayBalances<'a> { - fn default() -> Self { - GatewayBalances { - account: "".into(), - id: None, - strict: None, - ledger_hash: None, - ledger_index: None, - hotwallet: None, - command: RequestMethod::GatewayBalances, - } +impl<'a> Model for GatewayBalances<'a> {} + +impl<'a> Request for GatewayBalances<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for GatewayBalances<'a> {} - impl<'a> GatewayBalances<'a> { pub fn new( - account: Cow<'a, str>, id: Option>, - strict: Option, + account: Cow<'a, str>, + hotwallet: Option>>, ledger_hash: Option>, ledger_index: Option>, - hotwallet: Option>>, + strict: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::GatewayBalances, + id, + }, account, - id, strict, ledger_hash, ledger_index, hotwallet, - command: RequestMethod::GatewayBalances, } } } diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index 3ddc5fe8..7ba5faec 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// Retrieve information about the public ledger. /// /// See Ledger Data: @@ -11,86 +13,75 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Ledger<'a> { - /// The unique request id. - pub id: Option>, - /// A 20-byte hex string for the ledger version to use. - pub ledger_hash: Option>, - /// The ledger index of the ledger to use, or a shortcut - /// string to choose a ledger automatically. - pub ledger_index: Option>, - /// Admin required. If true, return full information on - /// the entire ledger. Ignored if you did not specify a - /// ledger version. Defaults to false. (Equivalent to - /// enabling transactions, accounts, and expand.) - /// Caution: This is a very large amount of data -- on - /// the order of several hundred megabytes! - pub full: Option, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Admin required. If true, return information on accounts /// in the ledger. Ignored if you did not specify a ledger /// version. Defaults to false. Caution: This returns a very /// large amount of data! pub accounts: Option, - /// If true, return information on transactions in the - /// specified ledger version. Defaults to false. Ignored if - /// you did not specify a ledger version. - pub transactions: Option, + /// If true, and transactions and expand are both also true, + /// return transaction information in binary format + /// (hexadecimal string) instead of JSON format. + pub binary: Option, /// Provide full JSON-formatted information for /// transaction/account information instead of only hashes. /// Defaults to false. Ignored unless you request transactions, /// accounts, or both. pub expand: Option, + /// Admin required. If true, return full information on + /// the entire ledger. Ignored if you did not specify a + /// ledger version. Defaults to false. (Equivalent to + /// enabling transactions, accounts, and expand.) + /// Caution: This is a very large amount of data -- on + /// the order of several hundred megabytes! + pub full: Option, + /// A 20-byte hex string for the ledger version to use. + pub ledger_hash: Option>, + /// The ledger index of the ledger to use, or a shortcut + /// string to choose a ledger automatically. + pub ledger_index: Option>, /// If true, include owner_funds field in the metadata of /// OfferCreate transactions in the response. Defaults to /// false. Ignored unless transactions are included and /// expand is true. pub owner_funds: Option, - /// If true, and transactions and expand are both also true, - /// return transaction information in binary format - /// (hexadecimal string) instead of JSON format. - pub binary: Option, /// If true, and the command is requesting the current ledger, /// includes an array of queued transactions in the results. pub queue: Option, - /// The request method. - #[serde(default = "RequestMethod::ledger")] - pub command: RequestMethod, + /// If true, return information on transactions in the + /// specified ledger version. Defaults to false. Ignored if + /// you did not specify a ledger version. + pub transactions: Option, } -impl<'a> Default for Ledger<'a> { - fn default() -> Self { - Ledger { - id: None, - ledger_hash: None, - ledger_index: None, - full: None, - accounts: None, - transactions: None, - expand: None, - owner_funds: None, - binary: None, - queue: None, - command: RequestMethod::Ledger, - } +impl<'a> Model for Ledger<'a> {} + +impl<'a> Request for Ledger<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Ledger<'a> {} - impl<'a> Ledger<'a> { pub fn new( id: Option>, - ledger_hash: Option>, - ledger_index: Option>, - full: Option, accounts: Option, - transactions: Option, + binary: Option, expand: Option, + full: Option, + ledger_hash: Option>, + ledger_index: Option>, owner_funds: Option, - binary: Option, queue: Option, + transactions: Option, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::Ledger, + id, + }, ledger_hash, ledger_index, full, @@ -100,7 +91,6 @@ impl<'a> Ledger<'a> { owner_funds, binary, queue, - command: RequestMethod::Ledger, } } } diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index 4b8f8288..c65f486c 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The ledger_closed method returns the unique identifiers of /// the most recently closed ledger. (This ledger is not /// necessarily validated and immutable yet.) @@ -13,29 +15,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerClosed<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::ledger_closed")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for LedgerClosed<'a> { - fn default() -> Self { - LedgerClosed { - id: None, - command: RequestMethod::LedgerClosed, - } +impl<'a> Model for LedgerClosed<'a> {} + +impl<'a> Request for LedgerClosed<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for LedgerClosed<'a> {} - impl<'a> LedgerClosed<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::LedgerClosed, + common_fields: CommonFields { + command: RequestMethod::LedgerClosed, + id, + }, } } } diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 3b72251d..9b408ee6 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The ledger_closed method returns the unique identifiers of /// the most recently closed ledger. (This ledger is not /// necessarily validated and immutable yet.) @@ -13,29 +15,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerCurrent<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::ledger_current")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for LedgerCurrent<'a> { - fn default() -> Self { - LedgerCurrent { - id: None, - command: RequestMethod::LedgerCurrent, - } +impl<'a> Model for LedgerCurrent<'a> {} + +impl<'a> Request for LedgerCurrent<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for LedgerCurrent<'a> {} - impl<'a> LedgerCurrent<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::LedgerCurrent, + common_fields: CommonFields { + command: RequestMethod::LedgerCurrent, + id, + }, } } } diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index 78a21c77..c71e012c 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The ledger_data method retrieves contents of the specified /// ledger. You can iterate through several calls to retrieve /// the entire contents of a single ledger version. @@ -13,60 +15,52 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerData<'a> { - /// The unique request id. - pub id: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, + /// If set to true, return ledger objects as hashed hex + /// strings instead of JSON. + pub binary: Option, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. pub ledger_index: Option>, - /// If set to true, return ledger objects as hashed hex - /// strings instead of JSON. - pub binary: Option, /// Limit the number of ledger objects to retrieve. /// The server is not required to honor this value. pub limit: Option, /// Value from a previous paginated response. /// Resume retrieving data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::ledger_data")] - pub command: RequestMethod, } -impl<'a> Default for LedgerData<'a> { - fn default() -> Self { - LedgerData { - id: None, - ledger_hash: None, - ledger_index: None, - binary: None, - limit: None, - marker: None, - command: RequestMethod::LedgerData, - } +impl<'a> Model for LedgerData<'a> {} + +impl<'a> Request for LedgerData<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for LedgerData<'a> {} - impl<'a> LedgerData<'a> { pub fn new( id: Option>, + binary: Option, ledger_hash: Option>, ledger_index: Option>, - binary: Option, limit: Option, marker: Option, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::LedgerData, + id, + }, ledger_hash, ledger_index, binary, limit, marker, - command: RequestMethod::LedgerData, } } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 7a80e0bb..1dbc4c1d 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -1,33 +1,36 @@ use crate::Err; use alloc::borrow::Cow; use anyhow::Result; +use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::requests::XRPLLedgerEntryException; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// Required fields for requesting a DepositPreauth if not /// querying by object ID. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct DepositPreauth<'a> { - pub owner: Cow<'a, str>, pub authorized: Cow<'a, str>, + pub owner: Cow<'a, str>, } /// Required fields for requesting a DirectoryNode if not /// querying by object ID. #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct Directory<'a> { - pub owner: Cow<'a, str>, pub dir_root: Cow<'a, str>, + pub owner: Cow<'a, str>, pub sub_index: Option, } /// Required fields for requesting a Escrow if not querying /// by object ID. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct Escrow<'a> { pub owner: Cow<'a, str>, pub seq: u64, @@ -35,7 +38,7 @@ pub struct Escrow<'a> { /// Required fields for requesting a Escrow if not querying /// by object ID. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct Offer<'a> { pub account: Cow<'a, str>, pub seq: u64, @@ -43,14 +46,14 @@ pub struct Offer<'a> { /// Required fields for requesting a Ticket, if not /// querying by object ID. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct Ticket<'a> { pub owner: Cow<'a, str>, pub ticket_sequence: u64, } /// Required fields for requesting a RippleState. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] pub struct RippleState<'a> { pub account: Cow<'a, str>, pub currency: Cow<'a, str>, @@ -69,53 +72,29 @@ pub struct RippleState<'a> { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct LedgerEntry<'a> { - /// The unique request id. - pub id: Option>, - pub index: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, pub account_root: Option>, - pub check: Option>, - pub payment_channel: Option>, - pub deposit_preauth: Option>, - pub directory: Option>, - pub escrow: Option>, - pub offer: Option>, - pub ripple_state: Option>, - pub ticket: Option>, /// If true, return the requested ledger object's contents as a /// hex string in the XRP Ledger's binary format. Otherwise, return /// data in JSON format. The default is false. pub binary: Option, + pub check: Option>, + pub deposit_preauth: Option>, + pub directory: Option>, + pub escrow: Option>, + pub index: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut string /// (e.g. "validated" or "closed" or "current") to choose a ledger /// automatically. pub ledger_index: Option>, - /// The request method. - #[serde(default = "RequestMethod::ledger_entry")] - pub command: RequestMethod, -} - -impl<'a> Default for LedgerEntry<'a> { - fn default() -> Self { - LedgerEntry { - id: None, - index: None, - account_root: None, - check: None, - payment_channel: None, - deposit_preauth: None, - directory: None, - escrow: None, - offer: None, - ripple_state: None, - ticket: None, - binary: None, - ledger_hash: None, - ledger_index: None, - command: RequestMethod::LedgerEntry, - } - } + pub offer: Option>, + pub payment_channel: Option>, + pub ripple_state: Option>, + pub ticket: Option>, } impl<'a: 'static> Model for LedgerEntry<'a> { @@ -180,25 +159,34 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } } +impl<'a> Request for LedgerEntry<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() + } +} + impl<'a> LedgerEntry<'a> { pub fn new( id: Option>, - index: Option>, account_root: Option>, + binary: Option, check: Option>, - payment_channel: Option>, deposit_preauth: Option>, directory: Option>, escrow: Option>, + index: Option>, + ledger_hash: Option>, + ledger_index: Option>, offer: Option>, + payment_channel: Option>, ripple_state: Option>, ticket: Option>, - binary: Option, - ledger_hash: Option>, - ledger_index: Option>, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::LedgerEntry, + id, + }, index, account_root, check, @@ -212,7 +200,6 @@ impl<'a> LedgerEntry<'a> { binary, ledger_hash, ledger_index, - command: RequestMethod::LedgerData, } } } @@ -232,26 +219,25 @@ mod test_ledger_entry_errors { #[test] fn test_fields_error() { - let ledger_entry = LedgerEntry { - command: RequestMethod::LedgerEntry, - id: None, - index: None, - account_root: Some("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into()), - check: None, - payment_channel: None, - deposit_preauth: None, - directory: None, - escrow: None, - offer: Some(Offer { + let ledger_entry = LedgerEntry::new( + None, + Some("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into()), + None, + None, + None, + None, + None, + None, + None, + None, + Some(Offer { account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), seq: 359, }), - ripple_state: None, - ticket: None, - binary: None, - ledger_hash: None, - ledger_index: None, - }; + None, + None, + None, + ); let _expected = XRPLLedgerEntryException::DefineExactlyOneOf { field1: "index".into(), field2: "account_root".into(), @@ -270,4 +256,32 @@ mod test_ledger_entry_errors { "Define one of: `index`, `account_root`, `check`, `directory`, `offer`, `ripple_state`, `escrow`, `payment_channel`, `deposit_preauth`, `ticket`. Define exactly one of them. For more information see: " ); } + + #[test] + fn test_serde() { + let req = LedgerEntry::new( + None, + Some("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into()), + None, + None, + None, + None, + None, + None, + None, + None, + Some(Offer { + account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + seq: 359, + }), + None, + None, + None, + ); + let serialized = serde_json::to_string(&req).unwrap(); + + let deserialized: LedgerEntry = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(req, deserialized); + } } diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index 2cfbb85e..d2615195 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The manifest method reports the current "manifest" /// information for a given validator public key. The /// "manifest" is the public portion of that validator's @@ -14,35 +16,31 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Manifest<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The base58-encoded public key of the validator /// to look up. This can be the master public key or /// ephemeral public key. pub public_key: Cow<'a, str>, - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::manifest")] - pub command: RequestMethod, } -impl<'a> Default for Manifest<'a> { - fn default() -> Self { - Manifest { - public_key: "".into(), - id: None, - command: RequestMethod::Manifest, - } +impl<'a> Model for Manifest<'a> {} + +impl<'a> Request for Manifest<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Manifest<'a> {} - impl<'a> Manifest<'a> { - pub fn new(public_key: Cow<'a, str>, id: Option>) -> Self { + pub fn new(id: Option>, public_key: Cow<'a, str>) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::Manifest, + id, + }, public_key, - id, - command: RequestMethod::Manifest, } } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 9473949d..860b7504 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -43,10 +43,12 @@ pub use account_nfts::*; pub use account_objects::*; pub use account_offers::*; pub use account_tx::*; +use alloc::borrow::Cow; pub use book_offers::*; pub use channel_authorize::*; pub use channel_verify::*; pub use deposit_authorize::*; +use derive_new::new; pub use exceptions::*; pub use fee::*; pub use gateway_balances::*; @@ -63,6 +65,7 @@ pub use path_find::*; pub use ping::*; pub use random::*; pub use ripple_path_find::*; +use serde_with::skip_serializing_none; pub use server_info::*; pub use server_state::*; pub use submit::*; @@ -135,115 +138,24 @@ pub enum RequestMethod { Random, } -/// For use with serde defaults. -/// TODO Find a better way -impl RequestMethod { - fn account_channels() -> Self { - RequestMethod::AccountChannels - } - fn account_currencies() -> Self { - RequestMethod::AccountCurrencies - } - fn account_info() -> Self { - RequestMethod::AccountInfo - } - fn account_lines() -> Self { - RequestMethod::AccountLines - } - fn account_nfts() -> Self { - RequestMethod::AccountNfts - } - fn account_objects() -> Self { - RequestMethod::AccountObjects - } - fn account_offers() -> Self { - RequestMethod::AccountOffers - } - fn account_tx() -> Self { - RequestMethod::AccountTx - } - fn book_offers() -> Self { - RequestMethod::BookOffers - } - fn channel_authorize() -> Self { - RequestMethod::ChannelAuthorize - } - fn channel_verify() -> Self { - RequestMethod::ChannelVerify - } - fn deposit_authorization() -> Self { - RequestMethod::DepositAuthorized - } - fn fee() -> Self { - RequestMethod::Fee - } - fn ledger_closed() -> Self { - RequestMethod::LedgerClosed - } - fn ledger_current() -> Self { - RequestMethod::LedgerCurrent - } - fn ledger_data() -> Self { - RequestMethod::LedgerData - } - fn ledger_entry() -> Self { - RequestMethod::LedgerEntry - } - fn ledger() -> Self { - RequestMethod::Ledger - } - fn manifest() -> Self { - RequestMethod::Manifest - } - fn nft_buy_offers() -> Self { - RequestMethod::NftBuyOffers - } - fn nft_sell_offers() -> Self { - RequestMethod::NftSellOffers - } - fn no_ripple_check() -> Self { - RequestMethod::NoRippleCheck - } - fn path_find() -> Self { - RequestMethod::PathFind - } - fn ripple_path_find() -> Self { - RequestMethod::RipplePathFind - } - fn ping() -> Self { - RequestMethod::Ping - } - fn random() -> Self { - RequestMethod::Random - } - fn server_info() -> Self { - RequestMethod::ServerInfo - } - fn server_state() -> Self { - RequestMethod::ServerState - } - fn submit() -> Self { - RequestMethod::Submit - } - fn sign_for() -> Self { - RequestMethod::SignFor - } - fn sign() -> Self { - RequestMethod::Sign - } - fn submit_multisigned() -> Self { - RequestMethod::SubmitMultisigned - } - fn subscribe() -> Self { - RequestMethod::Subscribe - } - fn unsubscribe() -> Self { - RequestMethod::Unsubscribe - } - fn transaction_entry() -> Self { - RequestMethod::TransactionEntry - } - fn tx() -> Self { - RequestMethod::Tx +/// The base fields for all request models. +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] +pub struct CommonFields<'a> { + /// The request method. + pub command: RequestMethod, + /// The unique request id. + pub id: Option>, +} + +impl Request for CommonFields<'_> { + fn get_command(&self) -> RequestMethod { + self.command.clone() } } + +/// The base trait for all request models. +/// Used to identify the model as a request. +pub trait Request { + fn get_command(&self) -> RequestMethod; +} diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index 1bde682d..3e9ce276 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -4,10 +4,15 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This method retrieves all of buy offers for the specified NFToken. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct NftBuyOffers<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The unique identifier of a NFToken object. pub nft_id: Cow<'a, str>, /// A 20-byte hex string for the ledger version to use. @@ -22,28 +27,19 @@ pub struct NftBuyOffers<'a> { /// Value from a previous paginated response. /// Resume retrieving data where that response left off. pub marker: Option, - /// The request method. - #[serde(default = "RequestMethod::nft_buy_offers")] - pub command: RequestMethod, } -impl<'a> Default for NftBuyOffers<'a> { - fn default() -> Self { - NftBuyOffers { - nft_id: "".into(), - ledger_hash: None, - ledger_index: None, - limit: None, - marker: None, - command: RequestMethod::NftBuyOffers, - } +impl<'a> Model for NftBuyOffers<'a> {} + +impl<'a> Request for NftBuyOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for NftBuyOffers<'a> {} - impl<'a> NftBuyOffers<'a> { pub fn new( + id: Option>, nft_id: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, @@ -51,12 +47,15 @@ impl<'a> NftBuyOffers<'a> { marker: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::NftBuyOffers, + id, + }, nft_id, ledger_hash, ledger_index, limit, marker, - command: RequestMethod::NftBuyOffers, } } } diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 1edb8d11..7404d59c 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -4,33 +4,35 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// This method retrieves all of sell offers for the specified NFToken. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct NftSellOffers<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// The unique identifier of a NFToken object. pub nft_id: Cow<'a, str>, - /// The request method. - #[serde(default = "RequestMethod::nft_sell_offers")] - pub command: RequestMethod, } -impl<'a> Default for NftSellOffers<'a> { - fn default() -> Self { - NftSellOffers { - nft_id: "".into(), - command: RequestMethod::NftSellOffers, - } +impl<'a> Model for NftSellOffers<'a> {} + +impl<'a> Request for NftSellOffers<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for NftSellOffers<'a> {} - impl<'a> NftSellOffers<'a> { - pub fn new(nft_id: Cow<'a, str>) -> Self { + pub fn new(id: Option>, nft_id: Cow<'a, str>) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::NftSellOffers, + id, + }, nft_id, - command: RequestMethod::NftSellOffers, } } } diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index 83410605..f2467efa 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -5,6 +5,8 @@ use strum_macros::Display; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// Enum representing the options for the address role in /// a NoRippleCheckRequest. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display)] @@ -28,6 +30,9 @@ pub enum NoRippleCheckRole { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct NoRippleCheck<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// A unique identifier for the account, most commonly the /// account's address. pub account: Cow<'a, str>, @@ -37,61 +42,49 @@ pub struct NoRippleCheck<'a> { /// No Ripple on all trust lines. Users should have Default Ripple /// disabled, and should enable No Ripple on all trust lines. pub role: NoRippleCheckRole, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut string /// to choose a ledger automatically. pub ledger_index: Option>, + /// The maximum number of trust line problems to include in the + /// results. Defaults to 300. + pub limit: Option, /// If true, include an array of suggested transactions, as JSON /// objects, that you can sign and submit to fix the problems. /// Defaults to false. pub transactions: Option, - /// The maximum number of trust line problems to include in the - /// results. Defaults to 300. - pub limit: Option, - /// The request method. - #[serde(default = "RequestMethod::no_ripple_check")] - pub command: RequestMethod, } -impl<'a> Default for NoRippleCheck<'a> { - fn default() -> Self { - NoRippleCheck { - account: "".into(), - role: Default::default(), - id: None, - ledger_hash: None, - ledger_index: None, - transactions: None, - limit: None, - command: RequestMethod::NoRippleCheck, - } +impl<'a> Model for NoRippleCheck<'a> {} + +impl<'a> Request for NoRippleCheck<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for NoRippleCheck<'a> {} - impl<'a> NoRippleCheck<'a> { pub fn new( + id: Option>, account: Cow<'a, str>, role: NoRippleCheckRole, - id: Option>, ledger_hash: Option>, ledger_index: Option>, - transactions: Option, limit: Option, + transactions: Option, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::NoRippleCheck, + id, + }, account, role, - id, ledger_hash, ledger_index, transactions, limit, - command: RequestMethod::NoRippleCheck, } } } diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index 41426053..e2ab7074 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -3,9 +3,11 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::currency::{Currency, XRP}; +use crate::models::currency::Currency; use crate::models::{requests::RequestMethod, Model, PathStep}; +use super::{CommonFields, Request}; + /// A path is an array. Each member of a path is an object that specifies a step on that path. pub type Path<'a> = Vec>; @@ -57,12 +59,9 @@ pub enum PathFindSubcommand { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct PathFind<'a> { - /// Use "create" to send the create sub-command. - pub subcommand: PathFindSubcommand, - /// Unique address of the account to find a path - /// from. (In other words, the account that would - /// be sending a payment.) - pub source_account: Cow<'a, str>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Unique address of the account to find a path to. /// (In other words, the account that would receive a payment.) pub destination_account: Cow<'a, str>, @@ -73,57 +72,51 @@ pub struct PathFind<'a> { /// to deliver as much as possible, while spending no more than /// the amount specified in send_max (if provided). pub destination_amount: Currency<'a>, - /// The unique request id. - pub id: Option>, - /// Currency Amount that would be spent in the transaction. - /// Not compatible with source_currencies. - pub send_max: Option>, + /// Unique address of the account to find a path + /// from. (In other words, the account that would + /// be sending a payment.) + pub source_account: Cow<'a, str>, + /// Use "create" to send the create sub-command. + pub subcommand: PathFindSubcommand, /// Array of arrays of objects, representing payment paths to check. /// You can use this to keep updated on changes to particular paths /// you already know about, or to check the overall cost to make a /// payment along a certain path. pub paths: Option>>, - /// The request method. - #[serde(default = "RequestMethod::path_find")] - pub command: RequestMethod, + /// Currency Amount that would be spent in the transaction. + /// Not compatible with source_currencies. + pub send_max: Option>, } -impl<'a> Default for PathFind<'a> { - fn default() -> Self { - PathFind { - subcommand: Default::default(), - source_account: "".into(), - destination_account: "".into(), - destination_amount: Currency::XRP(XRP::new()), - id: None, - send_max: None, - paths: None, - command: RequestMethod::PathFind, - } +impl<'a> Model for PathFind<'a> {} + +impl<'a> Request for PathFind<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for PathFind<'a> {} - impl<'a> PathFind<'a> { pub fn new( - subcommand: PathFindSubcommand, - source_account: Cow<'a, str>, + id: Option>, destination_account: Cow<'a, str>, destination_amount: Currency<'a>, - id: Option>, - send_max: Option>, + source_account: Cow<'a, str>, + subcommand: PathFindSubcommand, paths: Option>>>, + send_max: Option>, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::PathFind, + id, + }, subcommand, source_account, destination_account, destination_amount, - id, send_max, paths, - command: RequestMethod::PathFind, } } } diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 753ffdeb..079b81be 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The ping command returns an acknowledgement, so that /// clients can test the connection status and latency. /// @@ -12,29 +14,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Ping<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::ping")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for Ping<'a> { - fn default() -> Self { - Ping { - id: None, - command: RequestMethod::Ping, - } +impl<'a> Model for Ping<'a> {} + +impl<'a> Request for Ping<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Ping<'a> {} - impl<'a> Ping<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::Ping, + common_fields: CommonFields { + command: RequestMethod::Ping, + id, + }, } } } diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 4b53e8f8..8bc0029f 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The random command provides a random number to be used /// as a source of entropy for random number generation /// by clients. @@ -13,29 +15,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Random<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::random")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for Random<'a> { - fn default() -> Self { - Random { - id: None, - command: RequestMethod::Random, - } +impl<'a> Model for Random<'a> {} + +impl<'a> Request for Random<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Random<'a> {} - impl<'a> Random<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::Random, + common_fields: CommonFields { + command: RequestMethod::Random, + id, + }, } } } diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index 6c9d3ad5..ac923963 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -3,9 +3,10 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::currency::XRP; use crate::models::{currency::Currency, requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The ripple_path_find method is a simpl<'a>ified version of /// the path_find method that provides a single response with /// a payment path you can use right away. It is available in @@ -25,9 +26,9 @@ use crate::models::{currency::Currency, requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct RipplePathFind<'a> { - /// Unique address of the account that would send funds - /// in a transaction. - pub source_account: Cow<'a, str>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Unique address of the account that would receive funds /// in a transaction. pub destination_account: Cow<'a, str>, @@ -38,8 +39,9 @@ pub struct RipplePathFind<'a> { /// path to deliver as much as possible, while spending no more /// than the amount specified in send_max (if provided). pub destination_amount: Currency<'a>, - /// The unique request id. - pub id: Option>, + /// Unique address of the account that would send funds + /// in a transaction. + pub source_account: Cow<'a, str>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut @@ -56,50 +58,39 @@ pub struct RipplePathFind<'a> { /// currencies available up to a maximum of 88 different /// currency/issuer pairs. pub source_currencies: Option>>, - /// The request method. - #[serde(default = "RequestMethod::ripple_path_find")] - pub command: RequestMethod, } -impl<'a> Default for RipplePathFind<'a> { - fn default() -> Self { - RipplePathFind { - source_account: "".into(), - destination_account: "".into(), - destination_amount: Currency::XRP(XRP::new()), - id: None, - ledger_hash: None, - ledger_index: None, - send_max: None, - source_currencies: None, - command: RequestMethod::RipplePathFind, - } +impl<'a> Model for RipplePathFind<'a> {} + +impl<'a> Request for RipplePathFind<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for RipplePathFind<'a> {} - impl<'a> RipplePathFind<'a> { pub fn new( - source_account: Cow<'a, str>, + id: Option>, destination_account: Cow<'a, str>, destination_amount: Currency<'a>, - id: Option>, + source_account: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, send_max: Option>, source_currencies: Option>>, ) -> Self { Self { - source_account, + common_fields: CommonFields { + command: RequestMethod::RipplePathFind, + id, + }, destination_account, destination_amount, - id, + source_account, ledger_hash, ledger_index, send_max, source_currencies, - command: RequestMethod::RipplePathFind, } } } diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 1b1d4ab1..378fc0f0 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The server_info command asks the server for a /// human-readable version of various information about the /// rippled server being queried. @@ -13,29 +15,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ServerInfo<'a> { - /// The unique request id. - pub id: Option>, - /// The request info. - #[serde(default = "RequestMethod::server_info")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for ServerInfo<'a> { - fn default() -> Self { - ServerInfo { - id: None, - command: RequestMethod::ServerInfo, - } +impl<'a> Model for ServerInfo<'a> {} + +impl<'a> Request for ServerInfo<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for ServerInfo<'a> {} - impl<'a> ServerInfo<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::ServerInfo, + common_fields: CommonFields { + command: RequestMethod::ServerInfo, + id, + }, } } } diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 8f6d3b4a..4a0b7306 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The server_state command asks the server for various /// machine-readable information about the rippled server's /// current state. The response is almost the same as the @@ -18,29 +20,26 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ServerState<'a> { - /// The unique request id. - pub id: Option>, - /// The request method. - #[serde(default = "RequestMethod::server_state")] - pub command: RequestMethod, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, } -impl<'a> Default for ServerState<'a> { - fn default() -> Self { - ServerState { - id: None, - command: RequestMethod::ServerState, - } +impl<'a> Model for ServerState<'a> {} + +impl<'a> Request for ServerState<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for ServerState<'a> {} - impl<'a> ServerState<'a> { pub fn new(id: Option>) -> Self { Self { - id, - command: RequestMethod::ServerState, + common_fields: CommonFields { + command: RequestMethod::ServerState, + id, + }, } } } diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index d4301b7d..132a9910 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The submit method applies a transaction and sends it to /// the network to be confirmed and included in future ledgers. /// @@ -34,39 +36,34 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Submit<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Hex representation of the signed transaction to submit. /// This can also be a multi-signed transaction. pub tx_blob: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// If true, and the transaction fails locally, do not retry /// or relay the transaction to other servers pub fail_hard: Option, - /// The request method. - #[serde(default = "RequestMethod::submit")] - pub command: RequestMethod, } -impl<'a> Default for Submit<'a> { - fn default() -> Self { - Submit { - tx_blob: "".into(), - id: None, - fail_hard: None, - command: RequestMethod::Submit, - } +impl<'a> Model for Submit<'a> {} + +impl<'a> Request for Submit<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Submit<'a> {} - impl<'a> Submit<'a> { - pub fn new(tx_blob: Cow<'a, str>, id: Option>, fail_hard: Option) -> Self { + pub fn new(id: Option>, tx_blob: Cow<'a, str>, fail_hard: Option) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::Submit, + id, + }, tx_blob, - id, fail_hard, - command: RequestMethod::Submit, } } } diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 95558242..82fdc1cc 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The server_state command asks the server for various /// machine-readable information about the rippled server's /// current state. The response is almost the same as the @@ -18,34 +20,30 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct SubmitMultisigned<'a> { - /// The unique request id. - pub id: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// If true, and the transaction fails locally, do not /// retry or relay the transaction to other servers. pub fail_hard: Option, - /// The request method. - #[serde(default = "RequestMethod::submit_multisigned")] - pub command: RequestMethod, } -impl<'a> Default for SubmitMultisigned<'a> { - fn default() -> Self { - SubmitMultisigned { - id: None, - fail_hard: None, - command: RequestMethod::SubmitMultisigned, - } +impl<'a> Model for SubmitMultisigned<'a> {} + +impl<'a> Request for SubmitMultisigned<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for SubmitMultisigned<'a> {} - impl<'a> SubmitMultisigned<'a> { pub fn new(id: Option>, fail_hard: Option) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::SubmitMultisigned, + id, + }, fail_hard, - command: RequestMethod::SubmitMultisigned, } } } diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index dcc6ab89..0487ca22 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -1,25 +1,28 @@ use alloc::borrow::Cow; use alloc::vec::Vec; +use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use strum_macros::Display; use crate::models::{currency::Currency, default_false, requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// Format for elements in the `books` array for Subscribe only. /// /// See Subscribe: /// `` -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] #[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))] pub struct SubscribeBook<'a> { + pub taker: Cow<'a, str>, pub taker_gets: Currency<'a>, pub taker_pays: Currency<'a>, - pub taker: Cow<'a, str>, - #[serde(default = "default_false")] - pub snapshot: Option, #[serde(default = "default_false")] pub both: Option, + #[serde(default = "default_false")] + pub snapshot: Option, } /// Represents possible values of the streams query param @@ -47,13 +50,9 @@ pub enum StreamParameter { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Subscribe<'a> { - /// The unique request id. - pub id: Option>, - /// Array of objects defining order books to monitor for - /// updates, as detailed below. - pub books: Option>>, - /// Array of string names of generic streams to subscribe to. - pub streams: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Array with the unique addresses of accounts to monitor /// for validated transactions. The addresses must be in the /// XRP Ledger's base58 format. The server sends a notification @@ -62,50 +61,44 @@ pub struct Subscribe<'a> { /// Like accounts, but include transactions that are not /// yet finalized. pub accounts_proposed: Option>>, + /// Array of objects defining order books to monitor for + /// updates, as detailed below. + pub books: Option>>, + /// Array of string names of generic streams to subscribe to. + pub streams: Option>, /// (Optional for Websocket; Required otherwise) URL where the server /// sends a JSON-RPC callbacks for each event. Admin-only. pub url: Option>, - /// Username to provide for basic authentication at the callback URL. - pub url_username: Option>, /// Password to provide for basic authentication at the callback URL. pub url_password: Option>, - /// The request method. - // #[serde(skip_serializing)] - #[serde(default = "RequestMethod::subscribe")] - pub command: RequestMethod, + /// Username to provide for basic authentication at the callback URL. + pub url_username: Option>, } -impl<'a> Default for Subscribe<'a> { - fn default() -> Self { - Subscribe { - id: None, - books: None, - streams: None, - accounts: None, - accounts_proposed: None, - url: None, - url_username: None, - url_password: None, - command: RequestMethod::Subscribe, - } +impl<'a> Model for Subscribe<'a> {} + +impl<'a> Request for Subscribe<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Subscribe<'a> {} - impl<'a> Subscribe<'a> { pub fn new( id: Option>, - books: Option>>, - streams: Option>, accounts: Option>>, accounts_proposed: Option>>, + books: Option>>, + streams: Option>, url: Option>, - url_username: Option>, url_password: Option>, + url_username: Option>, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::Subscribe, + id, + }, books, streams, accounts, @@ -113,7 +106,6 @@ impl<'a> Subscribe<'a> { url, url_username, url_password, - command: RequestMethod::Subscribe, } } } diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index 1ec1844c..24ad02cb 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The transaction_entry method retrieves information on a /// single transaction from a specific ledger version. /// (The tx method, by contrast, searches all ledgers for @@ -15,47 +17,41 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct TransactionEntry<'a> { + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Unique hash of the transaction you are looking up. pub tx_hash: Cow<'a, str>, - /// The unique request id. - pub id: Option>, /// A 20-byte hex string for the ledger version to use. pub ledger_hash: Option>, /// The ledger index of the ledger to use, or a shortcut /// string to choose a ledger automatically. pub ledger_index: Option>, - /// The request method. - #[serde(default = "RequestMethod::transaction_entry")] - pub command: RequestMethod, } -impl<'a> Default for TransactionEntry<'a> { - fn default() -> Self { - TransactionEntry { - tx_hash: "".into(), - id: None, - ledger_hash: None, - ledger_index: None, - command: RequestMethod::TransactionEntry, - } +impl<'a> Model for TransactionEntry<'a> {} + +impl<'a> Request for TransactionEntry<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for TransactionEntry<'a> {} - impl<'a> TransactionEntry<'a> { pub fn new( - tx_hash: Cow<'a, str>, id: Option>, + tx_hash: Cow<'a, str>, ledger_hash: Option>, ledger_index: Option>, ) -> Self { Self { + common_fields: CommonFields { + command: RequestMethod::TransactionEntry, + id, + }, tx_hash, - id, ledger_hash, ledger_index, - command: RequestMethod::TransactionEntry, } } } diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 13e4de6e..0dd547ee 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -4,6 +4,8 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; +use super::{CommonFields, Request}; + /// The tx method retrieves information on a single transaction. /// /// See Tx: @@ -11,54 +13,48 @@ use crate::models::{requests::RequestMethod, Model}; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Tx<'a> { - /// The unique request id. - pub id: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// If true, return transaction data and metadata as binary /// serialized to hexadecimal strings. If false, return /// transaction data and metadata as JSON. The default is false. pub binary: Option, - /// Use this with max_ledger to specify a range of up to 1000 - /// ledger indexes, starting with this ledger (inclusive). If - /// the server cannot find the transaction, it confirms whether - /// it was able to search all the ledgers in this range. - pub min_ledger: Option, /// Use this with min_ledger to specify a range of up to 1000 /// ledger indexes, ending with this ledger (inclusive). If the /// server cannot find the transaction, it confirms whether it /// was able to search all the ledgers in the requested range. pub max_ledger: Option, - /// The request method. - #[serde(default = "RequestMethod::tx")] - pub command: RequestMethod, + /// Use this with max_ledger to specify a range of up to 1000 + /// ledger indexes, starting with this ledger (inclusive). If + /// the server cannot find the transaction, it confirms whether + /// it was able to search all the ledgers in this range. + pub min_ledger: Option, } -impl<'a> Default for Tx<'a> { - fn default() -> Self { - Tx { - id: None, - binary: None, - min_ledger: None, - max_ledger: None, - command: RequestMethod::Tx, - } +impl<'a> Model for Tx<'a> {} + +impl<'a> Request for Tx<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Tx<'a> {} - impl<'a> Tx<'a> { pub fn new( id: Option>, binary: Option, - min_ledger: Option, max_ledger: Option, + min_ledger: Option, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::Tx, + id, + }, binary, min_ledger, max_ledger, - command: RequestMethod::Tx, } } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index 4d6a9f3c..90f123d0 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -1,5 +1,6 @@ use alloc::borrow::Cow; use alloc::vec::Vec; +use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -10,11 +11,13 @@ use crate::models::{ Model, }; +use super::{CommonFields, Request}; + /// Format for elements in the `books` array for Unsubscribe only. /// /// See Unsubscribe: /// `` -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] #[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))] pub struct UnsubscribeBook<'a> { pub taker_gets: Currency<'a>, @@ -34,15 +37,9 @@ pub struct UnsubscribeBook<'a> { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Unsubscribe<'a> { - /// The unique request id. - pub id: Option>, - /// Array of objects defining order books to unsubscribe - /// from, as explained below. - pub books: Option>>, - /// Array of string names of generic streams to unsubscribe - /// from, including ledger, server, transactions, - /// and transactions_proposed. - pub streams: Option>, + /// The common fields shared by all requests. + #[serde(flatten)] + pub common_fields: CommonFields<'a>, /// Array of unique account addresses to stop receiving updates /// for, in the XRP Ledger's base58 format. (This only stops /// those messages if you previously subscribed to those accounts @@ -52,46 +49,44 @@ pub struct Unsubscribe<'a> { /// Like accounts, but for accounts_proposed subscriptions that /// included not-yet-validated transactions. pub accounts_proposed: Option>>, + /// Array of objects defining order books to unsubscribe + /// from, as explained below. + pub books: Option>>, #[serde(skip_serializing)] pub broken: Option>, - /// The request method. - #[serde(default = "RequestMethod::unsubscribe")] - pub command: RequestMethod, + /// Array of string names of generic streams to unsubscribe + /// from, including ledger, server, transactions, + /// and transactions_proposed. + pub streams: Option>, } -impl<'a> Default for Unsubscribe<'a> { - fn default() -> Self { - Unsubscribe { - id: None, - books: None, - streams: None, - accounts: None, - accounts_proposed: None, - broken: None, - command: RequestMethod::Unsubscribe, - } +impl<'a> Model for Unsubscribe<'a> {} + +impl<'a> Request for Unsubscribe<'a> { + fn get_command(&self) -> RequestMethod { + self.common_fields.command.clone() } } -impl<'a> Model for Unsubscribe<'a> {} - impl<'a> Unsubscribe<'a> { pub fn new( id: Option>, - books: Option>>, - streams: Option>, accounts: Option>>, accounts_proposed: Option>>, + books: Option>>, broken: Option>, + streams: Option>, ) -> Self { Self { - id, + common_fields: CommonFields { + command: RequestMethod::Unsubscribe, + id, + }, books, streams, accounts, accounts_proposed, broken, - command: RequestMethod::Unsubscribe, } } } diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 56d220f6..26a95147 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -4,11 +4,15 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::transactions::CommonFields; +use crate::models::NoFlags; use crate::models::{ model::Model, - transactions::{Memo, Signer, Transaction, TransactionType}, + transactions::{Transaction, TransactionType}, }; +use super::{Memo, Signer}; + /// An AccountDelete transaction deletes an account and any objects it /// owns in the XRP Ledger, if possible, sending the account's remaining /// XRP to a specified destination account. See Deletion of Accounts for @@ -20,63 +24,12 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct AccountDelete<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::account_set")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, + /// The base fields for all transaction models. + /// + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, // The custom fields for the AccountDelete model. // // See AccountDelete fields: @@ -91,66 +44,42 @@ pub struct AccountDelete<'a> { pub destination_tag: Option, } -impl<'a> Default for AccountDelete<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::AccountDelete, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - destination: Default::default(), - destination_tag: Default::default(), - } - } -} - impl<'a> Model for AccountDelete<'a> {} -impl<'a> Transaction for AccountDelete<'a> { +impl<'a> Transaction for AccountDelete<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } impl<'a> AccountDelete<'a> { pub fn new( account: Cow<'a, str>, - destination: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + destination: Cow<'a, str>, destination_tag: Option, ) -> Self { Self { - transaction_type: TransactionType::AccountDelete, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::AccountDelete, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, destination, destination_tag, } @@ -165,48 +94,26 @@ mod test_serde { fn test_serialize() { let default_txn = AccountDelete::new( "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm".into(), - "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), - Some("2000000".into()), - Some(2470665), - None, - None, None, - None, - None, - None, - None, - None, - Some(13), - ); - let default_json = r#"{"TransactionType":"AccountDelete","Account":"rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm","Fee":"2000000","Sequence":2470665,"Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","DestinationTag":13}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = AccountDelete::new( - "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm".into(), - "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), Some("2000000".into()), - Some(2470665), - None, - None, - None, None, None, + Some(2470665), None, None, None, + "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), Some(13), ); - let default_json = r#"{"TransactionType":"AccountDelete","Account":"rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm","Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","DestinationTag":13,"Fee":"2000000","Sequence":2470665}"#; - - let txn_as_obj: AccountDelete = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm","TransactionType":"AccountDelete","Fee":"2000000","Sequence":2470665,"Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","DestinationTag":13}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: AccountDelete = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index c04ccb12..0377406f 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -7,27 +7,28 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLAccountSetException; +use crate::models::transactions::{CommonFields, XRPLAccountSetException}; use crate::{ - _serde::txn_flags, constants::{ DISABLE_TICK_SIZE, MAX_DOMAIN_LENGTH, MAX_TICK_SIZE, MAX_TRANSFER_RATE, MIN_TICK_SIZE, MIN_TRANSFER_RATE, SPECIAL_CASE_TRANFER_RATE, }, models::{ model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }, Err, }; +use super::FlagCollection; + /// Transactions of the AccountSet type support additional values /// in the Flags field. This enum represents those options. /// /// See AccountSet flags: /// `` #[derive( - Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, + Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, Copy, )] #[repr(u32)] pub enum AccountSetFlag { @@ -74,65 +75,12 @@ pub enum AccountSetFlag { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct AccountSet<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::account_set")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, + /// The base fields for all transaction models. + /// + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, AccountSetFlag>, // The custom fields for the AccountSet model. // // See AccountSet fields: @@ -172,34 +120,6 @@ pub struct AccountSet<'a> { pub tick_size: Option, } -impl<'a> Default for AccountSet<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::AccountSet, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - clear_flag: Default::default(), - domain: Default::default(), - email_hash: Default::default(), - message_key: Default::default(), - nftoken_minter: Default::default(), - set_flag: Default::default(), - transfer_rate: Default::default(), - tick_size: Default::default(), - } - } -} - impl<'a: 'static> Model for AccountSet<'a> { fn get_errors(&self) -> Result<()> { match self._get_tick_size_error() { @@ -221,39 +141,13 @@ impl<'a: 'static> Model for AccountSet<'a> { } } -impl<'a> Transaction for AccountSet<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::AccountSet(account_set_flag) => match account_set_flag { - AccountSetFlag::AsfAccountTxnID => flags.contains(&AccountSetFlag::AsfAccountTxnID), - AccountSetFlag::AsfAuthorizedNFTokenMinter => { - flags.contains(&AccountSetFlag::AsfAuthorizedNFTokenMinter) - } - AccountSetFlag::AsfDefaultRipple => { - flags.contains(&AccountSetFlag::AsfDefaultRipple) - } - AccountSetFlag::AsfDepositAuth => flags.contains(&AccountSetFlag::AsfDepositAuth), - AccountSetFlag::AsfDisableMaster => { - flags.contains(&AccountSetFlag::AsfDisableMaster) - } - AccountSetFlag::AsfDisallowXRP => flags.contains(&AccountSetFlag::AsfDisallowXRP), - AccountSetFlag::AsfGlobalFreeze => flags.contains(&AccountSetFlag::AsfGlobalFreeze), - AccountSetFlag::AsfNoFreeze => flags.contains(&AccountSetFlag::AsfNoFreeze), - AccountSetFlag::AsfRequireAuth => flags.contains(&AccountSetFlag::AsfRequireAuth), - AccountSetFlag::AsfRequireDest => flags.contains(&AccountSetFlag::AsfRequireDest), - }, - _ => false, - } +impl<'a> Transaction for AccountSet<'a> { + fn has_flag(&self, flag: &AccountSetFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } @@ -388,17 +282,15 @@ impl<'a> AccountSetError for AccountSet<'a> { impl<'a> AccountSet<'a> { pub fn new( account: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, clear_flag: Option, domain: Option>, email_hash: Option>, @@ -409,19 +301,19 @@ impl<'a> AccountSet<'a> { nftoken_minter: Option>, ) -> Self { Self { - transaction_type: TransactionType::AccountSet, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::AccountSet, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, clear_flag, domain, email_hash, @@ -452,29 +344,26 @@ mod test_account_set_errors { #[test] fn test_tick_size_error() { - let mut account_set = AccountSet { - transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - clear_flag: None, - domain: None, - email_hash: None, - message_key: None, - set_flag: None, - transfer_rate: None, - tick_size: None, - nftoken_minter: None, - }; + let mut account_set = AccountSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); let tick_size_too_low = Some(2); account_set.tick_size = tick_size_too_low; @@ -494,29 +383,26 @@ mod test_account_set_errors { #[test] fn test_transfer_rate_error() { - let mut account_set = AccountSet { - transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - clear_flag: None, - domain: None, - email_hash: None, - message_key: None, - set_flag: None, - transfer_rate: None, - tick_size: None, - nftoken_minter: None, - }; + let mut account_set = AccountSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); let tick_size_too_low = Some(999999999); account_set.transfer_rate = tick_size_too_low; @@ -536,29 +422,26 @@ mod test_account_set_errors { #[test] fn test_domain_error() { - let mut account_set = AccountSet { - transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - clear_flag: None, - domain: None, - email_hash: None, - message_key: None, - set_flag: None, - transfer_rate: None, - tick_size: None, - nftoken_minter: None, - }; + let mut account_set = AccountSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); let domain_not_lowercase = Some("https://Example.com/".into()); account_set.domain = domain_not_lowercase; @@ -578,29 +461,26 @@ mod test_account_set_errors { #[test] fn test_flag_error() { - let account_set = AccountSet { - transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - clear_flag: Some(AccountSetFlag::AsfDisallowXRP), - domain: None, - email_hash: None, - message_key: None, - set_flag: Some(AccountSetFlag::AsfDisallowXRP), - transfer_rate: None, - tick_size: None, - nftoken_minter: None, - }; + let account_set = AccountSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(AccountSetFlag::AsfDisallowXRP), + None, + None, + None, + Some(AccountSetFlag::AsfDisallowXRP), + None, + None, + None, + ); assert_eq!( account_set.validate().unwrap_err().to_string().as_str(), @@ -610,29 +490,26 @@ mod test_account_set_errors { #[test] fn test_asf_authorized_nftoken_minter_error() { - let mut account_set = AccountSet { - transaction_type: TransactionType::AccountSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - clear_flag: None, - domain: None, - email_hash: None, - message_key: None, - set_flag: None, - transfer_rate: None, - tick_size: None, - nftoken_minter: None, - }; + let mut account_set = AccountSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); account_set.nftoken_minter = Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()); assert_eq!( @@ -660,53 +537,19 @@ mod test_account_set_errors { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = AccountSet::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - Some("12".into()), - Some(5), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - Some("6578616D706C652E636F6D".into()), - None, - Some("03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB".into()), - Some(AccountSetFlag::AsfAccountTxnID), - None, - None, None, - ); - let default_json = r#"{"TransactionType":"AccountSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","Sequence":5,"Domain":"6578616D706C652E636F6D","MessageKey":"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB","SetFlag":5}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = AccountSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), - Some(5), - None, - None, - None, None, None, None, + Some(5), None, None, None, @@ -719,10 +562,15 @@ mod test_serde { None, None, ); - let default_json = r#"{"TransactionType":"AccountSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","Sequence":5,"Domain":"6578616D706C652E636F6D","MessageKey":"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB","SetFlag":5}"#; - - let txn_as_obj: AccountSet = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"AccountSet","Fee":"12","Sequence":5,"Domain":"6578616D706C652E636F6D","MessageKey":"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB","SetFlag":5}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: AccountSet = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 21650480..1f9aa015 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -4,11 +4,15 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::transactions::CommonFields; +use crate::models::NoFlags; use crate::models::{ model::Model, - transactions::{Memo, Signer, Transaction, TransactionType}, + transactions::{Transaction, TransactionType}, }; +use super::{Memo, Signer}; + /// Cancels an unredeemed Check, removing it from the ledger without /// sending any money. The source or the destination of the check can /// cancel a Check at any time using this transaction type. If the Check @@ -20,167 +24,70 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct CheckCancel<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::check_cancel")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, + /// The base fields for all transaction models. + /// + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, // The custom fields for the CheckCancel model. // // See CheckCancel fields: // `` + /// The ID of the Check ledger object to cancel, as a 64-character hexadecimal string. #[serde(rename = "CheckID")] pub check_id: Cow<'a, str>, } -impl<'a> Default for CheckCancel<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::CheckCancel, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - check_id: Default::default(), - } - } -} - impl<'a> Model for CheckCancel<'a> {} -impl<'a> Transaction for CheckCancel<'a> { +impl<'a> Transaction for CheckCancel<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } impl<'a> CheckCancel<'a> { pub fn new( account: Cow<'a, str>, - check_id: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + check_id: Cow<'a, str>, ) -> Self { Self { - transaction_type: TransactionType::CheckCancel, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::CheckCancel, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, check_id, } } } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = CheckCancel::new( "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), - "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), - Some("12".into()), - None, None, - None, - None, - None, - None, - None, - None, - None, - ); - let default_json = r#"{"TransactionType":"CheckCancel","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Fee":"12","CheckID":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = CheckCancel::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), - "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), Some("12".into()), None, None, @@ -188,14 +95,17 @@ mod test_serde { None, None, None, - None, - None, - None, + "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), ); - let default_json = r#"{"TransactionType":"CheckCancel","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","CheckID":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0","Fee":"12"}"#; - - let txn_as_obj: CheckCancel = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCancel","Fee":"12","CheckID":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: CheckCancel = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 875bafe3..83a7485f 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLCheckCashException; +use crate::models::transactions::{CommonFields, XRPLCheckCashException}; +use crate::models::NoFlags; use crate::models::{ amount::Amount, model::Model, @@ -24,96 +25,28 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct CheckCash<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::check_cash")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the CheckCash model. + /// The base fields for all transaction models. /// - /// See CheckCash fields: - /// `` + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the CheckCash model. + // + // See CheckCash fields: + // `` + /// The ID of the Check ledger object to cash, as a 64-character hexadecimal string. #[serde(rename = "CheckID")] pub check_id: Cow<'a, str>, + /// Redeem the Check for exactly this amount, if possible. The currency must match that of the + /// SendMax of the corresponding CheckCreate transaction. You must provide either this field or DeliverMin. pub amount: Option>, + /// Redeem the Check for at least this amount and for as much as possible. The currency must + /// match that of the SendMax of the corresponding CheckCreate transaction. You must provide + /// either this field or Amount. pub deliver_min: Option>, } -impl<'a> Default for CheckCash<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::CheckCash, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - check_id: Default::default(), - amount: Default::default(), - deliver_min: Default::default(), - } - } -} - impl<'a: 'static> Model for CheckCash<'a> { fn get_errors(&self) -> Result<()> { match self._get_amount_and_deliver_min_error() { @@ -123,9 +56,9 @@ impl<'a: 'static> Model for CheckCash<'a> { } } -impl<'a> Transaction for CheckCash<'a> { +impl<'a> Transaction for CheckCash<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } @@ -148,34 +81,32 @@ impl<'a> CheckCashError for CheckCash<'a> { impl<'a> CheckCash<'a> { pub fn new( account: Cow<'a, str>, - check_id: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + check_id: Cow<'a, str>, amount: Option>, deliver_min: Option>, ) -> Self { Self { - transaction_type: TransactionType::CheckCash, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::CheckCash, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, check_id, amount, deliver_min, @@ -196,44 +127,8 @@ mod test_check_cash_error { #[test] fn test_amount_and_deliver_min_error() { - let check_cash = CheckCash { - transaction_type: TransactionType::CheckCash, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - check_id: "".into(), - amount: None, - deliver_min: None, - }; - - assert_eq!( - check_cash.validate().unwrap_err().to_string().as_str(), - "The field `amount` can not be defined with `deliver_min`. Define exactly one of them. For more information see: " - ); - } -} - -#[cfg(test)] -mod test_serde { - use crate::models::amount::XRPAmount; - - use super::*; - - #[test] - fn test_serialize() { - let default_txn = CheckCash::new( - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), - "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334".into(), - Some("12".into()), + let check_cash = CheckCash::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), None, None, None, @@ -242,40 +137,47 @@ mod test_serde { None, None, None, + "".into(), None, - Some(Amount::XRPAmount(XRPAmount::from("100000000"))), None, ); - let default_json = r#"{"TransactionType":"CheckCash","Account":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","Fee":"12","CheckID":"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334","Amount":"100000000"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - assert_eq!(txn_json, default_json); + assert_eq!( + check_cash.validate().unwrap_err().to_string().as_str(), + "The field `amount` can not be defined with `deliver_min`. Define exactly one of them. For more information see: " + ); } +} + +#[cfg(test)] +mod tests { + use super::*; #[test] - fn test_deserialize() { + fn test_serde() { let default_txn = CheckCash::new( "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), - "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334".into(), - Some("12".into()), - None, - None, None, + Some("12".into()), None, None, None, None, None, None, - Some(Amount::XRPAmount(XRPAmount::from("100000000"))), + "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334".into(), + Some("100000000".into()), None, ); - let default_json = r#"{"Account":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","TransactionType":"CheckCash","Amount":"100000000","CheckID":"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334","Fee":"12"}"#; - - let txn_as_obj: CheckCash = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","TransactionType":"CheckCash","Fee":"12","CheckID":"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334","Amount":"100000000"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: CheckCash = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index ee961512..8f2b23f6 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -1,15 +1,20 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::transactions::CommonFields; +use crate::models::NoFlags; use crate::models::{ amount::Amount, model::Model, - transactions::{Memo, Signer, Transaction, TransactionType}, + transactions::{Transaction, TransactionType}, }; +use super::{Memo, Signer}; + /// Create a Check object in the ledger, which is a deferred /// payment that can be cashed by its intended destination. /// @@ -19,141 +24,71 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct CheckCreate<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::check_create")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the CheckCreate model. + /// The base fields for all transaction models. /// - /// See CheckCreate fields: - /// `` + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the CheckCreate model. + // + // See CheckCreate fields: + // `` + /// The unique address of the account that can cash the Check. pub destination: Cow<'a, str>, + /// Maximum amount of source currency the Check is allowed to debit the sender, + /// including transfer fees on non-XRP currencies. The Check can only credit + /// the destination with the same currency (from the same issuer, for non-XRP + /// currencies). For non-XRP amounts, the nested field names MUST be lower-case. pub send_max: Amount<'a>, + /// Arbitrary tag that identifies the reason for the Check, or a hosted recipient to pay. pub destination_tag: Option, + /// Time after which the Check is no longer valid, in seconds since the Ripple Epoch. pub expiration: Option, + /// Arbitrary 256-bit hash representing a specific reason or identifier for this Check. #[serde(rename = "InvoiceID")] pub invoice_id: Option>, } -impl<'a> Default for CheckCreate<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::CheckCreate, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - destination: Default::default(), - send_max: Default::default(), - destination_tag: Default::default(), - expiration: Default::default(), - invoice_id: Default::default(), - } - } -} - impl<'a> Model for CheckCreate<'a> {} -impl<'a> Transaction for CheckCreate<'a> { +impl<'a> Transaction for CheckCreate<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } impl<'a> CheckCreate<'a> { pub fn new( account: Cow<'a, str>, - destination: Cow<'a, str>, - send_max: Amount<'a>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + destination: Cow<'a, str>, + send_max: Amount<'a>, destination_tag: Option, expiration: Option, invoice_id: Option>, ) -> Self { Self { - transaction_type: TransactionType::CheckCreate, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::CheckCreate, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, destination, send_max, destination_tag, @@ -164,45 +99,14 @@ impl<'a> CheckCreate<'a> { } #[cfg(test)] -mod test_serde { - use crate::models::amount::XRPAmount; - +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = CheckCreate::new( "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), - Amount::XRPAmount(XRPAmount::from("100000000")), - Some("12".into()), None, - None, - None, - None, - None, - None, - None, - None, - None, - Some(1), - Some(570113521), - Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B".into()), - ); - let default_json = r#"{"TransactionType":"CheckCreate","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Fee":"12","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = CheckCreate::new( - "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo".into(), - "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), - Amount::XRPAmount(XRPAmount::from("100000000")), Some("12".into()), None, None, @@ -210,17 +114,21 @@ mod test_serde { None, None, None, - None, - None, - None, + "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy".into(), + "100000000".into(), Some(1), Some(570113521), Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B".into()), ); - let default_json = r#"{"TransactionType":"CheckCreate","Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B","DestinationTag":1,"Fee":"12"}"#; - - let txn_as_obj: CheckCreate = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCreate","Fee":"12","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: CheckCreate = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 41e236e7..76857280 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLDepositPreauthException; +use crate::models::transactions::{CommonFields, XRPLDepositPreauthException}; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, @@ -21,93 +22,22 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct DepositPreauth<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::deposit_preauth")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the DepositPreauth model. + /// The base fields for all transaction models. /// - /// See DepositPreauth fields: - /// `` + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the DepositPreauth model. + // + // See DepositPreauth fields: + // `` + /// The XRP Ledger address of the sender to preauthorize. pub authorize: Option>, + /// The XRP Ledger address of a sender whose preauthorization should be revoked. pub unauthorize: Option>, } -impl<'a> Default for DepositPreauth<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::DepositPreauth, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - authorize: Default::default(), - unauthorize: Default::default(), - } - } -} - impl<'a: 'static> Model for DepositPreauth<'a> { fn get_errors(&self) -> Result<()> { match self._get_authorize_and_unauthorize_error() { @@ -117,9 +47,9 @@ impl<'a: 'static> Model for DepositPreauth<'a> { } } -impl<'a> Transaction for DepositPreauth<'a> { +impl<'a> Transaction for DepositPreauth<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } @@ -142,33 +72,31 @@ impl<'a> DepositPreauthError for DepositPreauth<'a> { impl<'a> DepositPreauth<'a> { pub fn new( account: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, authorize: Option>, unauthorize: Option>, ) -> Self { Self { - transaction_type: TransactionType::DepositPreauth, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::DepositPreauth, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, authorize, unauthorize, } @@ -189,41 +117,9 @@ mod test_deposit_preauth_exception { #[test] fn test_authorize_and_unauthorize_error() { - let deposit_preauth = DepositPreauth { - transaction_type: TransactionType::DepositPreauth, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - authorize: None, - unauthorize: None, - }; - - assert_eq!( - deposit_preauth.validate().unwrap_err().to_string().as_str(), - "The field `authorize` can not be defined with `unauthorize`. Define exactly one of them. For more information see: " - ); - } -} - -#[cfg(test)] -mod test_serde { - use super::*; - - #[test] - fn test_serialize() { - let default_txn = DepositPreauth::new( - "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8".into(), - Some("10".into()), - Some(2), + let deposit_preauth = DepositPreauth::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, None, None, None, @@ -232,38 +128,44 @@ mod test_serde { None, None, None, - Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de".into()), None, ); - let default_json = r#"{"TransactionType":"DepositPreauth","Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","Fee":"10","Sequence":2,"Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - assert_eq!(txn_json, default_json); + assert_eq!( + deposit_preauth.validate().unwrap_err().to_string().as_str(), + "The field `authorize` can not be defined with `unauthorize`. Define exactly one of them. For more information see: " + ); } +} + +#[cfg(test)] +mod tests { + use super::*; #[test] - fn test_deserialize() { + fn test_serde() { let default_txn = DepositPreauth::new( "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8".into(), - Some("10".into()), - Some(2), - None, - None, None, + Some("10".into()), None, None, + Some(2), None, None, None, Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de".into()), None, ); - let default_json = r#"{"TransactionType":"DepositPreauth","Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de","Fee":"10","Sequence":2}"#; - - let txn_as_obj: DepositPreauth = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","TransactionType":"DepositPreauth","Fee":"10","Sequence":2,"Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: DepositPreauth = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index fdb863fa..69ddbd04 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -1,14 +1,19 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::transactions::CommonFields; +use crate::models::NoFlags; use crate::models::{ model::Model, - transactions::{Memo, Signer, Transaction, TransactionType}, + transactions::{Transaction, TransactionType}, }; +use super::{Memo, Signer}; + /// Cancels an Escrow and returns escrowed XRP to the sender. /// /// See EscrowCancel: @@ -17,131 +22,58 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct EscrowCancel<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::escrow_cancel")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the EscrowCancel model. + /// The base fields for all transaction models. /// - /// See EscrowCancel fields: - /// `` + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the EscrowCancel model. + // + // See EscrowCancel fields: + // `` + /// Address of the source account that funded the escrow payment. pub owner: Cow<'a, str>, + /// Transaction sequence (or Ticket number) of EscrowCreate transaction that created the escrow to cancel. pub offer_sequence: u32, } -impl<'a> Default for EscrowCancel<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::EscrowCancel, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - owner: Default::default(), - offer_sequence: Default::default(), - } - } -} - impl<'a> Model for EscrowCancel<'a> {} -impl<'a> Transaction for EscrowCancel<'a> { +impl<'a> Transaction for EscrowCancel<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } impl<'a> EscrowCancel<'a> { pub fn new( account: Cow<'a, str>, - owner: Cow<'a, str>, - offer_sequence: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + owner: Cow<'a, str>, + offer_sequence: u32, ) -> Self { Self { - transaction_type: TransactionType::EscrowCancel, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::EscrowCancel, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, owner, offer_sequence, } @@ -149,17 +81,13 @@ impl<'a> EscrowCancel<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = EscrowCancel::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 7, - None, - None, None, None, None, @@ -168,36 +96,18 @@ mod test_serde { None, None, None, - ); - let default_json = r#"{"TransactionType":"EscrowCancel","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = EscrowCancel::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, ); - let default_json = r#"{"TransactionType":"EscrowCancel","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7}"#; - - let txn_as_obj: EscrowCancel = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCancel","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: EscrowCancel = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 6cc81345..379873e1 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -6,13 +6,14 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLEscrowCreateException; +use crate::models::transactions::{CommonFields, XRPLEscrowCreateException}; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; -/// Creates an Escrow, which sequests XRP until the escrow process either finishes or is canceled. +/// Creates an Escrow, which requests XRP until the escrow process either finishes or is canceled. /// /// See EscrowCreate: /// `` @@ -20,101 +21,41 @@ use crate::models::{ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "PascalCase")] pub struct EscrowCreate<'a> { - // The base fields for all transaction models. - // - // See Transaction Types: - // `` - // - // See Transaction Common Fields: - // `` - /// The type of transaction. - #[serde(default = "TransactionType::escrow_create")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the EscrowCreate model. + /// The base fields for all transaction models. /// - /// See EscrowCreate fields: - /// `` + /// See Transaction Common Fields: + /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the EscrowCreate model. + // + // See EscrowCreate fields: + // `` + /// Amount of XRP, in drops, to deduct from the sender's balance and escrow. + /// Once escrowed, the XRP can either go to the Destination address + /// (after the FinishAfter time) or returned to the sender (after the CancelAfter time). pub amount: XRPAmount<'a>, + /// Address to receive escrowed XRP. pub destination: Cow<'a, str>, + /// Arbitrary tag to further specify the destination for this escrowed + /// payment, such as a hosted recipient at the destination address. pub destination_tag: Option, + /// The time, in seconds since the Ripple Epoch, when this + /// escrow expires. This value is immutable; the funds can + /// only be returned to the sender after this time. pub cancel_after: Option, + /// The time, in seconds since the Ripple Epoch, when the escrowed XRP + /// can be released to the recipient. This value is immutable, and the + /// funds can't be accessed until this time. pub finish_after: Option, + /// Hex value representing a PREIMAGE-SHA-256 crypto-condition. + /// The funds can only be delivered to the recipient if this + /// condition is fulfilled. If the condition is not fulfilled + /// before the expiration time specified in the CancelAfter + /// field, the XRP can only revert to the sender. pub condition: Option>, } -impl<'a> Default for EscrowCreate<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::EscrowCreate, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - amount: Default::default(), - destination: Default::default(), - destination_tag: Default::default(), - cancel_after: Default::default(), - finish_after: Default::default(), - condition: Default::default(), - } - } -} - impl<'a: 'static> Model for EscrowCreate<'a> { fn get_errors(&self) -> Result<()> { match self._get_finish_after_error() { @@ -124,9 +65,9 @@ impl<'a: 'static> Model for EscrowCreate<'a> { } } -impl<'a> Transaction for EscrowCreate<'a> { +impl<'a> Transaction for EscrowCreate<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } @@ -153,37 +94,35 @@ impl<'a> EscrowCreateError for EscrowCreate<'a> { impl<'a> EscrowCreate<'a> { pub fn new( account: Cow<'a, str>, - amount: XRPAmount<'a>, - destination: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, - destination_tag: Option, + source_tag: Option, + ticket_sequence: Option, + amount: XRPAmount<'a>, + destination: Cow<'a, str>, cancel_after: Option, - finish_after: Option, condition: Option>, + destination_tag: Option, + finish_after: Option, ) -> Self { Self { - transaction_type: TransactionType::EscrowCreate, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::EscrowCreate, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, amount, destination, destination_tag, @@ -210,99 +149,66 @@ mod test_escrow_create_errors { #[test] fn test_cancel_after_error() { - let escrow_create = EscrowCreate { - transaction_type: TransactionType::EscrowCreate, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - amount: XRPAmount::from("100000000"), - destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - destination_tag: None, - cancel_after: Some(13298498), - finish_after: Some(14359039), - condition: None, - }; - - assert_eq!( - escrow_create.validate().unwrap_err().to_string().as_str(), - "The value of the field `cancel_after` is not allowed to be below the value of the field `finish_after` (max 14359039, found 13298498). For more information see: " - ); - } -} - -#[cfg(test)] -mod test_serde { - use super::*; - - #[test] - fn test_serialize() { - let default_txn = EscrowCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), + let escrow_create = EscrowCreate::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), None, None, None, None, None, - Some(11747), None, None, None, + XRPAmount::from("100000000"), + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + Some(13298498), None, - Some(23480), - Some(533257958), - Some(533171558), - Some( - "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" - .into(), - ), + None, + Some(14359039), ); - let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - assert_eq!(txn_json, default_json); + assert_eq!( + escrow_create.validate().unwrap_err().to_string().as_str(), + "The value of the field `cancel_after` is not allowed to be below the value of the field `finish_after` (max 14359039, found 13298498). For more information see: " + ); } +} + +#[cfg(test)] +mod tests { + use super::*; #[test] - fn test_deserialize() { + fn test_serde() { let default_txn = EscrowCreate::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), - None, None, None, None, None, - Some(11747), - None, None, None, + Some(11747), None, - Some(23480), + XRPAmount::from("10000"), + "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), Some(533257958), - Some(533171558), Some( "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" .into(), ), + Some(23480), + Some(533171558), ); - let default_json = r#"{"TransactionType":"EscrowCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","DestinationTag":23480,"SourceTag":11747}"#; - - let txn_as_obj: EscrowCreate = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCreate","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: EscrowCreate = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index facb5416..36d4b9a1 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -6,12 +6,15 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::transactions::XRPLEscrowFinishException; +use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Finishes an Escrow and delivers XRP from a held payment to the recipient. /// /// See EscrowFinish: @@ -28,89 +31,22 @@ pub struct EscrowFinish<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::escrow_finish")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the EscrowFinish model. - /// - /// See EscrowFinish fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the EscrowFinish model. + // + // See EscrowFinish fields: + // `` + /// Address of the source account that funded the held payment. pub owner: Cow<'a, str>, + /// Transaction sequence of EscrowCreate transaction that created the held payment to finish. pub offer_sequence: u32, + /// Hex value matching the previously-supplied PREIMAGE-SHA-256 crypto-condition of the held payment. pub condition: Option>, + /// Hex value of the PREIMAGE-SHA-256 crypto-condition fulfillment matching the held payment's Condition. pub fulfillment: Option>, } -impl<'a> Default for EscrowFinish<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::EscrowFinish, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - owner: Default::default(), - offer_sequence: Default::default(), - condition: Default::default(), - fulfillment: Default::default(), - } - } -} - impl<'a: 'static> Model for EscrowFinish<'a> { fn get_errors(&self) -> Result<()> { match self._get_condition_and_fulfillment_error() { @@ -120,9 +56,9 @@ impl<'a: 'static> Model for EscrowFinish<'a> { } } -impl<'a> Transaction for EscrowFinish<'a> { +impl<'a> Transaction for EscrowFinish<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -145,35 +81,33 @@ impl<'a> EscrowFinishError for EscrowFinish<'a> { impl<'a> EscrowFinish<'a> { pub fn new( account: Cow<'a, str>, - owner: Cow<'a, str>, - offer_sequence: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + owner: Cow<'a, str>, + offer_sequence: u32, condition: Option>, fulfillment: Option>, ) -> Self { Self { - transaction_type: TransactionType::EscrowFinish, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::EscrowFinish, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, owner, offer_sequence, condition, @@ -196,48 +130,8 @@ mod test_escrow_finish_errors { #[test] fn test_condition_and_fulfillment_error() { - let escrow_finish = EscrowFinish { - transaction_type: TransactionType::EscrowCancel, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - owner: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - offer_sequence: 10, - condition: Some( - "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" - .into(), - ), - fulfillment: None, - }; - - assert_eq!( - escrow_finish.validate().unwrap_err().to_string().as_str(), - "For the field `condition` to be defined it is required to also define the field `fulfillment`. For more information see: " - ); - } -} - -#[cfg(test)] -mod test_serde { - use super::*; - - #[test] - fn test_serialize() { - let default_txn = EscrowFinish::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 7, - None, - None, + let escrow_finish = EscrowFinish::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), None, None, None, @@ -246,28 +140,30 @@ mod test_serde { None, None, None, + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + 10, Some( "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" .into(), ), - Some("A0028000".into()), + None, ); - let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); + assert_eq!( + escrow_finish.validate().unwrap_err().to_string().as_str(), + "For the field `condition` to be defined it is required to also define the field `fulfillment`. For more information see: " + ); } +} + +#[cfg(test)] +mod tests { + use super::*; #[test] - fn test_deserialize() { + fn test_serde() { let default_txn = EscrowFinish::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 7, - None, - None, None, None, None, @@ -276,16 +172,23 @@ mod test_serde { None, None, None, + "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), + 7, Some( "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100" .into(), ), Some("A0028000".into()), ); - let default_json = r#"{"TransactionType":"EscrowFinish","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; - - let txn_as_obj: EscrowFinish = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: EscrowFinish = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index becceea9..a16503c0 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -1,5 +1,6 @@ use crate::models::transactions::{AccountSetFlag, PaymentFlag}; use alloc::borrow::Cow; +use core::fmt::Debug; use strum_macros::Display; use thiserror_no_std::Error; diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index f8db8807..57ad9163 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -47,19 +47,30 @@ pub use payment_channel_claim::*; pub use payment_channel_create::*; pub use payment_channel_fund::*; pub use pseudo_transactions::*; + pub use set_regular_key::*; pub use signer_list_set::*; pub use ticket_create::*; pub use trust_set::*; -use crate::serde_with_tag; +use crate::models::amount::XRPAmount; +use crate::{ + _serde::txn_flags, + serde_with_tag, +}; use alloc::borrow::Cow; use alloc::string::String; +use alloc::vec::Vec; +use anyhow::Result; use derive_new::new; use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use strum::IntoEnumIterator; use strum_macros::{AsRefStr, Display}; +use super::FlagCollection; + /// Enum containing the different Transaction types. #[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq, Eq)] pub enum TransactionType { @@ -94,89 +105,132 @@ pub enum TransactionType { UNLModify, } -/// For use with serde defaults. -/// TODO Find a better way -impl TransactionType { - fn account_delete() -> Self { - TransactionType::AccountDelete - } - fn account_set() -> Self { - TransactionType::AccountSet - } - fn check_cancel() -> Self { - TransactionType::CheckCancel - } - fn check_cash() -> Self { - TransactionType::CheckCash - } - fn check_create() -> Self { - TransactionType::CheckCreate - } - fn deposit_preauth() -> Self { - TransactionType::DepositPreauth - } - fn escrow_cancel() -> Self { - TransactionType::EscrowCancel - } - fn escrow_create() -> Self { - TransactionType::EscrowCreate - } - fn escrow_finish() -> Self { - TransactionType::EscrowFinish - } - fn nftoken_accept_offer() -> Self { - TransactionType::NFTokenAcceptOffer - } - fn nftoken_burn() -> Self { - TransactionType::NFTokenBurn - } - fn nftoken_cancel_offer() -> Self { - TransactionType::NFTokenCancelOffer - } - fn nftoken_create_offer() -> Self { - TransactionType::NFTokenCreateOffer - } - fn nftoken_mint() -> Self { - TransactionType::NFTokenMint - } - fn offer_cancel() -> Self { - TransactionType::OfferCancel - } - fn offer_create() -> Self { - TransactionType::OfferCreate - } - fn payment() -> Self { - TransactionType::Payment - } - fn payment_channel_claim() -> Self { - TransactionType::PaymentChannelClaim - } - fn payment_channel_create() -> Self { - TransactionType::PaymentChannelCreate - } - fn payment_channel_fund() -> Self { - TransactionType::PaymentChannelFund - } - fn set_regular_key() -> Self { - TransactionType::SetRegularKey - } - fn signer_list_set() -> Self { - TransactionType::SignerListSet - } - fn ticket_create() -> Self { - TransactionType::TicketCreate - } - fn trust_set() -> Self { - TransactionType::TrustSet - } - fn enable_amendment() -> Self { - TransactionType::EnableAmendment +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] +#[serde(rename_all = "PascalCase")] +pub struct PreparedTransaction<'a, T> { + #[serde(flatten)] + pub transaction: T, + /// 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: Cow<'a, str>, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] +#[serde(rename_all = "PascalCase")] +pub struct SignedTransaction<'a, T> { + #[serde(flatten)] + pub prepared_transaction: PreparedTransaction<'a, T>, + /// The signature that verifies this transaction as originating + /// from the account it says it is from. + pub txn_signature: Cow<'a, str>, +} + +/// The base fields for all transaction models. +/// +/// See Transaction Common Fields: +/// `` +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct CommonFields<'a, F> +where + F: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + /// The unique address of the account that initiated the transaction. + pub account: Cow<'a, str>, + /// The type of transaction. + /// + /// See Transaction Types: + /// `` + pub transaction_type: TransactionType, + /// Hash value identifying another transaction. If provided, this + /// transaction is only valid if the sending account's + /// previously-sent transaction matches the provided hash. + #[serde(rename = "AccountTxnID")] + pub account_txn_id: Option>, + /// Integer amount of XRP, in drops, to be destroyed as a cost + /// for distributing this transaction to the network. Some + /// transaction types have different minimum requirements. + /// See Transaction Cost for details. + pub fee: Option>, + /// Set of bit-flags for this transaction. + #[serde(with = "txn_flags")] + pub flags: Option>, + /// Highest ledger index this transaction can appear in. + /// Specifying this field places a strict upper limit on how long + /// the transaction can wait to be validated or rejected. + /// See Reliable Transaction Submission for more details. + pub last_ledger_sequence: Option, + /// Additional arbitrary information used to identify this transaction. + pub memos: 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. + /// The special case 0 means the transaction is using a Ticket instead. + pub sequence: 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 + /// payment's SourceTag as the refund payment's DestinationTag. + pub signers: 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 + /// payment's SourceTag as the refund payment's DestinationTag. + pub source_tag: Option, + /// The sequence number of the ticket to use in place + /// of a Sequence number. If this is provided, Sequence must + /// be 0. Cannot be used with AccountTxnID. + pub ticket_sequence: Option, +} + +impl<'a, T> CommonFields<'a, T> +where + T: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + pub fn new( + account: Cow<'a, str>, + transaction_type: TransactionType, + account_txn_id: Option>, + fee: Option>, + flags: Option>, + last_ledger_sequence: Option, + memos: Option>, + sequence: Option, + signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + ) -> Self { + CommonFields { + account, + transaction_type, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + } } - fn set_fee() -> Self { - TransactionType::SetFee +} + +impl<'a, T> Transaction for CommonFields<'a, T> +where + T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, +{ + fn has_flag(&self, flag: &T) -> bool { + match &self.flags { + Some(flag_collection) => flag_collection.0.contains(flag), + None => false, + } } - fn unl_modify() -> Self { - TransactionType::UNLModify + + fn get_transaction_type(&self) -> TransactionType { + self.transaction_type.clone() } } @@ -214,9 +268,11 @@ pub struct Signer<'a> { } /// Standard functions for transactions. -pub trait Transaction { - // TODO: use generic type - fn has_flag(&self, flag: &Flag) -> bool { +pub trait Transaction +where + T: IntoEnumIterator + Serialize, +{ + fn has_flag(&self, flag: &T) -> bool { let _txn_flag = flag; false } diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 4e2759e9..2346f031 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -10,12 +10,15 @@ 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; use crate::models::{ amount::Amount, model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Accept offers to buy or sell an NFToken. /// /// See NFTokenAcceptOffer: @@ -32,90 +35,29 @@ pub struct NFTokenAcceptOffer<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::nftoken_accept_offer")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the NFTokenAcceptOffer model. - /// - /// See NFTokenAcceptOffer fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the NFTokenAcceptOffer model. + // + // See NFTokenAcceptOffer fields: + // `` + /// Identifies the NFTokenOffer that offers to sell the NFToken. #[serde(rename = "NFTokenSellOffer")] pub nftoken_sell_offer: Option>, + /// Identifies the NFTokenOffer that offers to buy the NFToken. #[serde(rename = "NFTokenBuyOffer")] pub nftoken_buy_offer: Option>, #[serde(rename = "NFTokenBrokerFee")] + /// This field is only valid in brokered mode, and specifies the + /// amount that the broker keeps as part of their fee for bringing + /// the two offers together; the remaining amount is sent to the + /// seller of the NFToken being bought. If specified, the fee must + /// be such that, before applying the transfer fee, the amount that + /// the seller would receive is at least as much as the amount + /// indicated in the sell offer. pub nftoken_broker_fee: Option>, } -impl<'a> Default for NFTokenAcceptOffer<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::NFTokenAcceptOffer, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - nftoken_sell_offer: Default::default(), - nftoken_buy_offer: Default::default(), - nftoken_broker_fee: Default::default(), - } - } -} - impl<'a: 'static> Model for NFTokenAcceptOffer<'a> { fn get_errors(&self) -> Result<()> { match self._get_brokered_mode_error() { @@ -128,9 +70,9 @@ impl<'a: 'static> Model for NFTokenAcceptOffer<'a> { } } -impl<'a> Transaction for NFTokenAcceptOffer<'a> { +impl<'a> Transaction for NFTokenAcceptOffer<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -175,34 +117,32 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { impl<'a> NFTokenAcceptOffer<'a> { pub fn new( account: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, nftoken_sell_offer: Option>, nftoken_buy_offer: Option>, nftoken_broker_fee: Option>, ) -> Self { Self { - transaction_type: TransactionType::NFTokenAcceptOffer, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::NFTokenAcceptOffer, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, nftoken_sell_offer, nftoken_buy_offer, nftoken_broker_fee, @@ -229,24 +169,20 @@ mod test_nftoken_accept_offer_error { #[test] fn test_brokered_mode_error() { - let nftoken_accept_offer = NFTokenAcceptOffer { - transaction_type: TransactionType::NFTokenAcceptOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_sell_offer: None, - nftoken_buy_offer: None, - nftoken_broker_fee: Some(Amount::XRPAmount(XRPAmount::from("100"))), - }; + let nftoken_accept_offer = NFTokenAcceptOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(Amount::XRPAmount(XRPAmount::from("100"))), + ); assert_eq!( nftoken_accept_offer.validate().unwrap_err().to_string().as_str(), @@ -256,24 +192,20 @@ mod test_nftoken_accept_offer_error { #[test] fn test_broker_fee_error() { - let nftoken_accept_offer = NFTokenAcceptOffer { - transaction_type: TransactionType::NFTokenAcceptOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_sell_offer: Some("".into()), - nftoken_buy_offer: None, - nftoken_broker_fee: Some(Amount::XRPAmount(XRPAmount::from("0"))), - }; + let nftoken_accept_offer = NFTokenAcceptOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + Some("".into()), + None, + Some(Amount::XRPAmount(XRPAmount::from("0"))), + ); assert_eq!( nftoken_accept_offer.validate().unwrap_err().to_string().as_str(), @@ -283,24 +215,19 @@ mod test_nftoken_accept_offer_error { } #[cfg(test)] -mod test_serde { +mod tests { use alloc::string::ToString; use alloc::vec; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = NFTokenAcceptOffer::new( "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG".into(), + None, Some("12".into()), - Some(68549302), Some(75447550), - None, - None, - None, - None, - None, Some(vec![Memo::new( Some( "61356534373538372D633134322D346663382D616466362D393666383562356435386437" @@ -309,48 +236,23 @@ mod test_serde { None, None, )]), - None, - Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77".into()), - None, - None, - ); - let default_json = r#"{"TransactionType":"NFTokenAcceptOffer","Account":"r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG","Fee":"12","Sequence":68549302,"LastLedgerSequence":75447550,"Memos":[{"Memo":{"MemoData":"61356534373538372D633134322D346663382D616466362D393666383562356435386437","MemoFormat":null,"MemoType":null}}],"NFTokenSellOffer":"68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = NFTokenAcceptOffer::new( - "r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG".into(), - Some("12".into()), Some(68549302), - Some(75447550), None, None, None, - None, - None, - Some(vec![Memo::new( - Some( - "61356534373538372D633134322D346663382D616466362D393666383562356435386437" - .to_string(), - ), - None, - None, - )]), - None, Some("68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77".into()), None, None, ); - let default_json = r#"{"TransactionType":"NFTokenAcceptOffer","Account":"r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG","Fee":"12","LastLedgerSequence":75447550,"Memos":[{"Memo":{"MemoData":"61356534373538372D633134322D346663382D616466362D393666383562356435386437","MemoFormat":null,"MemoType":null}}],"NFTokenSellOffer":"68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77","Sequence":68549302}"#; - - let txn_as_obj: NFTokenAcceptOffer = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG","TransactionType":"NFTokenAcceptOffer","Fee":"12","LastLedgerSequence":75447550,"Memos":[{"Memo":{"MemoData":"61356534373538372D633134322D346663382D616466362D393666383562356435386437","MemoFormat":null,"MemoType":null}}],"Sequence":68549302,"NFTokenSellOffer":"68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: NFTokenAcceptOffer = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 8b9e12a8..c6e32e5e 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -1,14 +1,18 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Removes a NFToken object from the NFTokenPage in which it is being held, /// effectively removing the token from the ledger (burning it). /// @@ -26,124 +30,58 @@ pub struct NFTokenBurn<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::nftoken_burn")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the NFTokenBurn model. - /// - /// See NFTokenBurn fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the NFTokenBurn model. + // + // See NFTokenBurn fields: + // `` #[serde(rename = "NFTokenID")] + /// The NFToken to be removed by this transaction. pub nftoken_id: Cow<'a, str>, + /// The owner of the NFToken to burn. Only used if that owner is + /// different than the account sending this transaction. The + /// issuer or authorized minter can use this field to burn NFTs + /// that have the lsfBurnable flag enabled. pub owner: Option>, } -impl<'a> Default for NFTokenBurn<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::NFTokenBurn, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - nftoken_id: Default::default(), - owner: Default::default(), - } - } -} - impl<'a> Model for NFTokenBurn<'a> {} -impl<'a> Transaction for NFTokenBurn<'a> { +impl<'a> Transaction for NFTokenBurn<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> NFTokenBurn<'a> { pub fn new( account: Cow<'a, str>, - nftoken_id: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + nftoken_id: Cow<'a, str>, owner: Option>, ) -> Self { Self { - transaction_type: TransactionType::NFTokenBurn, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::NFTokenBurn, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, nftoken_id, owner, } @@ -151,39 +89,14 @@ impl<'a> NFTokenBurn<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = NFTokenBurn::new( "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2".into(), - "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), - Some("10".into()), - None, - None, None, - None, - None, - None, - None, - None, - None, - Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), - ); - let default_json = r#"{"TransactionType":"NFTokenBurn","Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = NFTokenBurn::new( - "rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2".into(), - "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), Some("10".into()), None, None, @@ -191,15 +104,18 @@ mod test_serde { None, None, None, - None, - None, - None, + "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), ); - let default_json = r#"{"TransactionType":"NFTokenBurn","Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"}"#; - - let txn_as_obj: NFTokenBurn = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","TransactionType":"NFTokenBurn","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: NFTokenBurn = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 5dbd3b98..8078e572 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -7,11 +7,14 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenCancelOfferException; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Cancels existing token offers created using NFTokenCreateOffer. /// /// See NFTokenCancelOffer: @@ -28,86 +31,22 @@ pub struct NFTokenCancelOffer<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::nftoken_cancel_offer")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the NFTokenCancelOffer model. - /// - /// See NFTokenCancelOffer fields: - /// `` - /// Lifetime issue + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the NFTokenCancelOffer model. + // + // See NFTokenCancelOffer fields: + // `` + // Lifetime issue + /// An array of IDs of the NFTokenOffer objects to cancel (not the IDs of NFToken + /// objects, but the IDs of the NFTokenOffer objects). Each entry must be a + /// different object ID of an NFTokenOffer object; the transaction is invalid + /// if the array contains duplicate entries. #[serde(borrow)] #[serde(rename = "NFTokenOffers")] pub nftoken_offers: Vec>, } -impl<'a> Default for NFTokenCancelOffer<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::NFTokenCancelOffer, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - nftoken_offers: Default::default(), - } - } -} - impl<'a: 'static> Model for NFTokenCancelOffer<'a> { fn get_errors(&self) -> Result<()> { match self._get_nftoken_offers_error() { @@ -117,9 +56,9 @@ impl<'a: 'static> Model for NFTokenCancelOffer<'a> { } } -impl<'a> Transaction for NFTokenCancelOffer<'a> { +impl<'a> Transaction for NFTokenCancelOffer<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -140,32 +79,30 @@ impl<'a> NFTokenCancelOfferError for NFTokenCancelOffer<'a> { impl<'a> NFTokenCancelOffer<'a> { pub fn new( account: Cow<'a, str>, - nftoken_offers: Vec>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + nftoken_offers: Vec>, ) -> Self { Self { - transaction_type: TransactionType::NFTokenCancelOffer, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::NFTokenCancelOffer, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, nftoken_offers, } } @@ -186,22 +123,18 @@ mod test_nftoken_cancel_offer_error { #[test] fn test_nftoken_offer_error() { - let nftoken_cancel_offer = NFTokenCancelOffer { - transaction_type: TransactionType::NFTokenCancelOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_offers: Vec::new(), - }; + let nftoken_cancel_offer = NFTokenCancelOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + Vec::new(), + ); assert_eq!( nftoken_cancel_offer.validate().unwrap_err().to_string().as_str(), @@ -211,17 +144,15 @@ mod test_nftoken_cancel_offer_error { } #[cfg(test)] -mod test_serde { +mod tests { use alloc::vec; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = NFTokenCancelOffer::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D".into()], - None, None, None, None, @@ -230,36 +161,17 @@ mod test_serde { None, None, None, - None, - ); - let default_json = r#"{"TransactionType":"NFTokenCancelOffer","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","NFTokenOffers":["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"]}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = NFTokenCancelOffer::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D".into()], - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, ); - let default_json = r#"{"TransactionType":"NFTokenCancelOffer","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","NFTokenOffers":["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"]}"#; - - let txn_as_obj: NFTokenCancelOffer = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"NFTokenCancelOffer","NFTokenOffers":["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"]}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: NFTokenCancelOffer = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 3561773e..09ad25c2 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -10,14 +10,15 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::Err; -use crate::_serde::txn_flags; use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::{Amount, XRPAmount}; use crate::models::transactions::XRPLNFTokenCreateOfferException; +use crate::Err; + +use super::{CommonFields, FlagCollection}; /// Transactions of the NFTokenCreateOffer type support additional values /// in the Flags field. This enum represents those options. @@ -52,94 +53,34 @@ pub struct NFTokenCreateOffer<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::nftoken_create_offer")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the NFTokenCreateOffer model. - /// - /// See NFTokenCreateOffer fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NFTokenCreateOfferFlag>, + // The custom fields for the NFTokenCreateOffer model. + // + // See NFTokenCreateOffer fields: + // `` + /// Identifies the NFToken object that the offer references. #[serde(rename = "NFTokenID")] pub nftoken_id: Cow<'a, str>, + /// Indicates the amount expected or offered for the corresponding NFToken. + /// The amount must be non-zero, except where this is an offer to sell and the + /// asset is XRP; then, it is legal to specify an amount of zero, which means + /// that the current owner of the token is giving it away, gratis, either to + /// anyone at all, or to the account identified by the Destination field. pub amount: Amount<'a>, + /// Who owns the corresponding NFToken. If the offer is to buy a token, this field + /// must be present and it must be different than the Account field (since an offer + /// to buy a token one already holds is meaningless). If the offer is to sell a token, + /// this field must not be present, as the owner is, implicitly, the same as the + /// Account (since an offer to sell a token one doesn't already hold is meaningless) pub owner: Option>, + /// Time after which the offer is no longer active, in seconds since the Ripple Epoch. pub expiration: Option, + /// If present, indicates that this offer may only be accepted by the specified account. + /// Attempts by other accounts to accept this offer MUST fail. pub destination: Option>, } -impl<'a> Default for NFTokenCreateOffer<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::NFTokenCreateOffer, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - nftoken_id: Default::default(), - amount: Default::default(), - owner: Default::default(), - expiration: Default::default(), - destination: Default::default(), - } - } -} - impl<'a: 'static> Model for NFTokenCreateOffer<'a> { fn get_errors(&self) -> Result<()> { match self._get_amount_error() { @@ -155,28 +96,13 @@ impl<'a: 'static> Model for NFTokenCreateOffer<'a> { } } -impl<'a> Transaction for NFTokenCreateOffer<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::NFTokenCreateOffer(nftoken_create_offer_flag) => { - match nftoken_create_offer_flag { - NFTokenCreateOfferFlag::TfSellOffer => { - flags.contains(&NFTokenCreateOfferFlag::TfSellOffer) - } - } - } - _ => false, - } +impl<'a> Transaction for NFTokenCreateOffer<'a> { + fn has_flag(&self, flag: &NFTokenCreateOfferFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -186,10 +112,7 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { self.amount.clone().try_into(); match amount_into_decimal { Ok(amount) => { - if !self.has_flag(&Flag::NFTokenCreateOffer( - NFTokenCreateOfferFlag::TfSellOffer, - )) && amount.is_zero() - { + if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount.is_zero() { Err!(XRPLNFTokenCreateOfferException::ValueZero { field: "amount".into(), resource: "".into(), @@ -206,7 +129,7 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { fn _get_destination_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { if let Some(destination) = self.destination.clone() { - if destination == self.account { + if destination == self.common_fields.account { Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { field1: "destination".into(), field2: "account".into(), @@ -222,15 +145,13 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { fn _get_owner_error(&self) -> Result<(), XRPLNFTokenCreateOfferException> { if let Some(owner) = self.owner.clone() { - if self.has_flag(&Flag::NFTokenCreateOffer( - NFTokenCreateOfferFlag::TfSellOffer, - )) { + if self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) { Err(XRPLNFTokenCreateOfferException::IllegalOption { field: "owner".into(), context: "NFToken sell offers".into(), resource: "".into(), }) - } else if owner == self.account { + } else if owner == self.common_fields.account { Err(XRPLNFTokenCreateOfferException::ValueEqualsValue { field1: "owner".into(), field2: "account".into(), @@ -239,9 +160,7 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { } else { Ok(()) } - } else if !self.has_flag(&Flag::NFTokenCreateOffer( - NFTokenCreateOfferFlag::TfSellOffer, - )) { + } else if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) { Err(XRPLNFTokenCreateOfferException::OptionRequired { field: "owner".into(), context: "NFToken buy offers".into(), @@ -256,37 +175,35 @@ impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { impl<'a> NFTokenCreateOffer<'a> { pub fn new( account: Cow<'a, str>, - nftoken_id: Cow<'a, str>, - amount: Amount<'a>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, - owner: Option>, - expiration: Option, + source_tag: Option, + ticket_sequence: Option, + amount: Amount<'a>, + nftoken_id: Cow<'a, str>, destination: Option>, + expiration: Option, + owner: Option>, ) -> Self { Self { - transaction_type: TransactionType::NFTokenCreateOffer, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::NFTokenCreateOffer, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, nftoken_id, amount, owner, @@ -316,26 +233,23 @@ mod test_nftoken_create_offer_error { #[test] fn test_amount_error() { - let nftoken_create_offer = NFTokenCreateOffer { - transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_id: "".into(), - amount: Amount::XRPAmount(XRPAmount::from("0")), - owner: None, - expiration: None, - destination: None, - }; + let nftoken_create_offer = NFTokenCreateOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("0")), + "".into(), + None, + None, + None, + ); assert_eq!( nftoken_create_offer @@ -349,26 +263,23 @@ mod test_nftoken_create_offer_error { #[test] fn test_destination_error() { - let nftoken_create_offer = NFTokenCreateOffer { - transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_id: "".into(), - amount: Amount::XRPAmount(XRPAmount::from("1")), - owner: None, - expiration: None, - destination: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), - }; + let nftoken_create_offer = NFTokenCreateOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("1")), + "".into(), + Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), + None, + None, + ); assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), @@ -378,35 +289,32 @@ mod test_nftoken_create_offer_error { #[test] fn test_owner_error() { - let mut nftoken_create_offer = NFTokenCreateOffer { - transaction_type: TransactionType::NFTokenCreateOffer, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_id: "".into(), - amount: Amount::XRPAmount(XRPAmount::from("1")), - owner: Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()), - expiration: None, - destination: None, - }; + let mut nftoken_create_offer = NFTokenCreateOffer::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("1")), + "".into(), + None, + None, + Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()), + ); let sell_flag = vec![NFTokenCreateOfferFlag::TfSellOffer]; - nftoken_create_offer.flags = Some(sell_flag); + nftoken_create_offer.common_fields.flags = Some(sell_flag.into()); assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), "The optional field `owner` is not allowed to be defined for NFToken sell offers. For more information see: " ); - nftoken_create_offer.flags = None; + nftoken_create_offer.common_fields.flags = None; nftoken_create_offer.owner = None; assert_eq!( @@ -424,66 +332,40 @@ mod test_nftoken_create_offer_error { } #[cfg(test)] -mod test_serde { +mod tests { use crate::models::amount::XRPAmount; use alloc::vec; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = NFTokenCreateOffer::new( "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX".into(), - "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007".into(), - Amount::XRPAmount(XRPAmount::from("1000000")), - None, - None, - None, None, None, + Some(vec![NFTokenCreateOfferFlag::TfSellOffer].into()), None, None, None, - Some(vec![NFTokenCreateOfferFlag::TfSellOffer]), None, None, None, - None, - None, - ); - let default_json = r#"{"TransactionType":"NFTokenCreateOffer","Account":"rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX","Flags":1,"NFTokenID":"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007","Amount":"1000000"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = NFTokenCreateOffer::new( - "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX".into(), - "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007".into(), Amount::XRPAmount(XRPAmount::from("1000000")), - None, - None, - None, - None, - None, - None, - None, - None, - Some(vec![NFTokenCreateOfferFlag::TfSellOffer]), - None, - None, + "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007".into(), None, None, None, ); - let default_json = r#"{"TransactionType":"NFTokenCreateOffer","Account":"rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX","NFTokenID":"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007","Amount":"1000000","Flags":1}"#; - - let txn_as_obj: NFTokenCreateOffer = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX","TransactionType":"NFTokenCreateOffer","Flags":1,"NFTokenID":"000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007","Amount":"1000000"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: NFTokenCreateOffer = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index f88e80c1..ab097b55 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -10,15 +10,16 @@ use crate::{ constants::{MAX_TRANSFER_FEE, MAX_URI_LENGTH}, models::{ model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }, Err, }; -use crate::_serde::txn_flags; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenMintException; +use super::{CommonFields, FlagCollection}; + /// Transactions of the NFTokenMint type support additional values /// in the Flags field. This enum represents those options. /// @@ -57,94 +58,38 @@ pub struct NFTokenMint<'a> { // // See Transaction Common Fields: // `` - /// The type of transaction. - #[serde(default = "TransactionType::nftoken_mint")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the NFTokenMint model. - /// - /// See NFTokenMint fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NFTokenMintFlag>, + // The custom fields for the NFTokenMint model. + // + // See NFTokenMint fields: + // `` + /// An arbitrary taxon, or shared identifier, for a series or collection of related NFTs. + /// To mint a series of NFTs, give them all the same taxon. #[serde(rename = "NFTokenTaxon")] pub nftoken_taxon: u32, + /// The issuer of the token, if the sender of the account is issuing it on behalf of + /// another account. This field must be omitted if the account sending the transaction + /// is the issuer of the NFToken. If provided, the issuer's AccountRoot object must have + /// the NFTokenMinter field set to the sender of this transaction (this transaction's + /// Account field). pub issuer: Option>, + /// The value specifies the fee charged by the issuer for secondary sales of the NFToken, + /// if such sales are allowed. Valid values for this field are between 0 and 50000 + /// inclusive, allowing transfer rates of between 0.00% and 50.00% in increments of + /// 0.001. If this field is provided, the transaction MUST have the tfTransferable + /// flag enabled. pub transfer_fee: Option, + /// Up to 256 bytes of arbitrary data. In JSON, this should be encoded as a string of + /// hexadecimal. You can use the xrpl.convertStringToHex utility to convert a URI to + /// its hexadecimal equivalent. This is intended to be a URI that points to the data or + /// metadata associated with the NFT. The contents could decode to an HTTP or HTTPS URL, + /// an IPFS URI, a magnet link, immediate data encoded as an RFC 2379 "data" URL , or + /// even an issuer-specific encoding. The URI is NOT checked for validity. #[serde(rename = "URI")] pub uri: Option>, } -impl<'a> Default for NFTokenMint<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::NFTokenMint, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - nftoken_taxon: Default::default(), - issuer: Default::default(), - transfer_fee: Default::default(), - uri: Default::default(), - } - } -} - impl<'a: 'static> Model for NFTokenMint<'a> { fn get_errors(&self) -> Result<()> { match self._get_issuer_error() { @@ -160,33 +105,20 @@ impl<'a: 'static> Model for NFTokenMint<'a> { } } -impl<'a> Transaction for NFTokenMint<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::NFTokenMint(nftoken_mint_flag) => match nftoken_mint_flag { - NFTokenMintFlag::TfBurnable => flags.contains(&NFTokenMintFlag::TfBurnable), - NFTokenMintFlag::TfOnlyXRP => flags.contains(&NFTokenMintFlag::TfOnlyXRP), - NFTokenMintFlag::TfTransferable => flags.contains(&NFTokenMintFlag::TfTransferable), - }, - _ => false, - } +impl<'a> Transaction for NFTokenMint<'a> { + fn has_flag(&self, flag: &NFTokenMintFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> NFTokenMintError for NFTokenMint<'a> { fn _get_issuer_error(&self) -> Result<(), XRPLNFTokenMintException> { if let Some(issuer) = self.issuer.clone() { - if issuer == self.account { + if issuer == self.common_fields.account { Err(XRPLNFTokenMintException::ValueEqualsValue { field1: "issuer".into(), field2: "account".into(), @@ -238,36 +170,34 @@ impl<'a> NFTokenMintError for NFTokenMint<'a> { impl<'a> NFTokenMint<'a> { pub fn new( account: Cow<'a, str>, - nftoken_taxon: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + nftoken_taxon: u32, issuer: Option>, transfer_fee: Option, uri: Option>, ) -> Self { Self { - transaction_type: TransactionType::NFTokenMint, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::NFTokenMint, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, nftoken_taxon, issuer, transfer_fee, @@ -292,25 +222,22 @@ mod test_nftoken_mint_error { #[test] fn test_issuer_error() { - let nftoken_mint = NFTokenMint { - transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_taxon: 0, - issuer: Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), - transfer_fee: None, - uri: None, - }; + let nftoken_mint = NFTokenMint::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + 0, + Some("rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into()), + None, + None, + ); assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), @@ -320,25 +247,22 @@ mod test_nftoken_mint_error { #[test] fn test_transfer_fee_error() { - let nftoken_mint = NFTokenMint { - transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_taxon: 0, - issuer: None, - transfer_fee: Some(50001), - uri: None, - }; + let nftoken_mint = NFTokenMint::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + 0, + None, + Some(50001), + None, + ); assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), @@ -348,25 +272,22 @@ mod test_nftoken_mint_error { #[test] fn test_uri_error() { - let nftoken_mint = NFTokenMint { - transaction_type: TransactionType::NFTokenMint, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - nftoken_taxon: 0, - issuer: None, - transfer_fee: None, - uri: Some("wss://xrplcluster.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()), - }; + let nftoken_mint = NFTokenMint::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + 0, + None, + None, + Some("wss://xrplcluster.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into()), + ); assert_eq!( nftoken_mint.validate().unwrap_err().to_string().as_str(), @@ -376,64 +297,39 @@ mod test_nftoken_mint_error { } #[cfg(test)] -mod test_serde { +mod tests { use alloc::string::ToString; use alloc::vec; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = NFTokenMint::new( "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), - 0, - Some("10".into()), - None, - None, - None, - None, - None, None, - None, - Some(vec![NFTokenMintFlag::TfTransferable]), - Some(vec![Memo::new(Some("72656E74".to_string()), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963".to_string()))]), - None, - None, - Some(314), - Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469".into()), - ); - let default_json = r#"{"TransactionType":"NFTokenMint","Account":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","Fee":"10","Flags":8,"Memos":[{"Memo":{"MemoData":"72656E74","MemoFormat":null,"MemoType":"687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963"}}],"NFTokenTaxon":0,"TransferFee":314,"URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = NFTokenMint::new( - "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), - 0, Some("10".into()), + Some(vec![NFTokenMintFlag::TfTransferable].into()), None, + Some(vec![Memo::new(Some("72656E74".to_string()), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963".to_string()))]), None, None, None, None, - None, - None, - Some(vec![NFTokenMintFlag::TfTransferable]), - Some(vec![Memo::new(Some("72656E74".to_string()), None, Some("687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963".to_string()))]), - None, + 0, None, Some(314), Some("697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469".into()), ); - let default_json = r#"{"TransactionType":"NFTokenMint","Account":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","TransferFee":314,"NFTokenTaxon":0,"Flags":8,"Fee":"10","URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469","Memos":[{"Memo":{"MemoType":"687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963","MemoFormat":null,"MemoData":"72656E74"}}]}"#; - - let txn_as_obj: NFTokenMint = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B","TransactionType":"NFTokenMint","Fee":"10","Flags":8,"Memos":[{"Memo":{"MemoData":"72656E74","MemoFormat":null,"MemoType":"687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963"}}],"NFTokenTaxon":0,"TransferFee":314,"URI":"697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: NFTokenMint = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 7e596cc6..c0471cb3 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -1,14 +1,18 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Removes an Offer object from the XRP Ledger. /// /// See OfferCancel: @@ -25,173 +29,85 @@ pub struct OfferCancel<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::offer_cancel")] - transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the OfferCancel model. - /// - /// See OfferCancel fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the OfferCancel model. + // + // See OfferCancel fields: + // `` + /// The sequence number (or Ticket number) of a previous OfferCreate transaction. + /// If specified, cancel any offer object in the ledger that was created by that + /// transaction. It is not considered an error if the offer specified does not exist. pub offer_sequence: u32, } -impl<'a> Default for OfferCancel<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::OfferCancel, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - offer_sequence: Default::default(), - } - } -} - impl<'a> Model for OfferCancel<'a> {} -impl<'a> Transaction for OfferCancel<'a> { +impl<'a> Transaction for OfferCancel<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> OfferCancel<'a> { pub fn new( account: Cow<'a, str>, - offer_sequence: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + offer_sequence: u32, ) -> Self { Self { - transaction_type: TransactionType::OfferCancel, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::OfferCancel, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, offer_sequence, } } } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = OfferCancel::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - 6, - Some("12".into()), - Some(7), - Some(7108629), None, - None, - None, - None, - None, - None, - None, - ); - let default_json = r#"{"TransactionType":"OfferCancel","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","Sequence":7,"LastLedgerSequence":7108629,"OfferSequence":6}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = OfferCancel::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - 6, Some("12".into()), - Some(7), Some(7108629), None, + Some(7), None, None, None, - None, - None, - None, + 6, ); - let default_json = r#"{"TransactionType":"OfferCancel","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","LastLedgerSequence":7108629,"OfferSequence":6,"Sequence":7}"#; - - let txn_as_obj: OfferCancel = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCancel","Fee":"12","LastLedgerSequence":7108629,"Sequence":7,"OfferSequence":6}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: OfferCancel = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 678f9bca..2a356ba5 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -1,5 +1,6 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -8,12 +9,13 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::_serde::txn_flags; use crate::models::amount::XRPAmount; +use super::{CommonFields, FlagCollection}; + /// Transactions of the OfferCreate type support additional values /// in the Flags field. This enum represents those options. /// @@ -61,152 +63,65 @@ pub struct OfferCreate<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::offer_create")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the OfferCreate model. - /// - /// See OfferCreate fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, OfferCreateFlag>, + // The custom fields for the OfferCreate model. + // + // See OfferCreate fields: + // `` + /// The amount and type of currency being sold. pub taker_gets: Amount<'a>, + /// The amount and type of currency being bought. pub taker_pays: Amount<'a>, + /// Time after which the Offer is no longer active, in seconds since the Ripple Epoch. pub expiration: Option, + /// An Offer to delete first, specified in the same way as OfferCancel. pub offer_sequence: Option, } -impl<'a> Default for OfferCreate<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::OfferCreate, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - taker_gets: Default::default(), - taker_pays: Default::default(), - expiration: Default::default(), - offer_sequence: Default::default(), - } - } -} - impl<'a> Model for OfferCreate<'a> {} -impl<'a> Transaction for OfferCreate<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::OfferCreate(offer_create_flag) => match offer_create_flag { - OfferCreateFlag::TfFillOrKill => flags.contains(&OfferCreateFlag::TfFillOrKill), - OfferCreateFlag::TfImmediateOrCancel => { - flags.contains(&OfferCreateFlag::TfImmediateOrCancel) - } - OfferCreateFlag::TfPassive => flags.contains(&OfferCreateFlag::TfPassive), - OfferCreateFlag::TfSell => flags.contains(&OfferCreateFlag::TfSell), - }, - _ => false, - } +impl<'a> Transaction for OfferCreate<'a> { + fn has_flag(&self, flag: &OfferCreateFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> OfferCreate<'a> { pub fn new( account: Cow<'a, str>, - taker_gets: Amount<'a>, - taker_pays: Amount<'a>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + taker_gets: Amount<'a>, + taker_pays: Amount<'a>, expiration: Option, offer_sequence: Option, ) -> Self { Self { - transaction_type: TransactionType::OfferCreate, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::OfferCreate, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, taker_gets, taker_pays, expiration, @@ -224,58 +139,52 @@ mod test { #[test] fn test_has_flag() { - let txn: OfferCreate = OfferCreate { - transaction_type: TransactionType::OfferCreate, - account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), - fee: Some("10".into()), - sequence: Some(1), - last_ledger_sequence: Some(72779837), - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: Some(vec![OfferCreateFlag::TfImmediateOrCancel]), - memos: None, - signers: None, - taker_gets: Amount::XRPAmount(XRPAmount::from("1000000")), - taker_pays: Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( + let txn: OfferCreate = OfferCreate::new( + "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), + None, + Some("10".into()), + Some(vec![OfferCreateFlag::TfImmediateOrCancel].into()), + Some(72779837), + None, + Some(1), + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("1000000")), + Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), "0.3".into(), )), - expiration: None, - offer_sequence: None, - }; - assert!(txn.has_flag(&Flag::OfferCreate(OfferCreateFlag::TfImmediateOrCancel))); - assert!(!txn.has_flag(&Flag::OfferCreate(OfferCreateFlag::TfPassive))); + None, + None, + ); + assert!(txn.has_flag(&OfferCreateFlag::TfImmediateOrCancel)); + assert!(!txn.has_flag(&OfferCreateFlag::TfPassive)); } #[test] fn test_get_transaction_type() { - let txn: OfferCreate = OfferCreate { - transaction_type: TransactionType::OfferCreate, - account: "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), - fee: Some("10".into()), - sequence: Some(1), - last_ledger_sequence: Some(72779837), - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: Some(vec![OfferCreateFlag::TfImmediateOrCancel]), - memos: None, - signers: None, - taker_gets: Amount::XRPAmount(XRPAmount::from("1000000")), - taker_pays: Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( + let txn: OfferCreate = OfferCreate::new( + "rpXhhWmCvDwkzNtRbm7mmD1vZqdfatQNEe".into(), + None, + Some("10".into()), + Some(vec![OfferCreateFlag::TfImmediateOrCancel].into()), + Some(72779837), + None, + Some(1), + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("1000000")), + Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), "0.3".into(), )), - expiration: None, - offer_sequence: None, - }; + None, + None, + ); let actual = txn.get_transaction_type(); let expect = TransactionType::OfferCreate; assert_eq!(actual, expect) @@ -283,71 +192,42 @@ mod test { } #[cfg(test)] -mod test_serde { +mod tests { use crate::models::amount::{IssuedCurrencyAmount, XRPAmount}; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = OfferCreate::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - Amount::XRPAmount(XRPAmount::from("6000000")), - Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( - "GKO".into(), - "ruazs5h1qEsqpke88pcqnaseXdm6od2xc".into(), - "2".into(), - )), - Some("12".into()), - Some(8), - Some(7108682), - None, - None, - None, - None, None, + Some("12".into()), None, + Some(7108682), None, + Some(8), None, None, None, - ); - let default_json = r#"{"TransactionType":"OfferCreate","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","Sequence":8,"LastLedgerSequence":7108682,"TakerGets":"6000000","TakerPays":{"currency":"GKO","issuer":"ruazs5h1qEsqpke88pcqnaseXdm6od2xc","value":"2"}}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = OfferCreate::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), Amount::XRPAmount(XRPAmount::from("6000000")), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "GKO".into(), "ruazs5h1qEsqpke88pcqnaseXdm6od2xc".into(), "2".into(), )), - Some("12".into()), - Some(8), - Some(7108682), - None, - None, - None, - None, - None, - None, - None, - None, None, None, ); - let default_json = r#"{"TransactionType":"OfferCreate","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","Sequence":8,"LastLedgerSequence":7108682,"TakerGets":"6000000","TakerPays":{"value":"2","currency":"GKO","issuer":"ruazs5h1qEsqpke88pcqnaseXdm6od2xc"}}"#; - - let txn_as_obj: OfferCreate = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCreate","Fee":"12","LastLedgerSequence":7108682,"Sequence":8,"TakerGets":"6000000","TakerPays":{"currency":"GKO","issuer":"ruazs5h1qEsqpke88pcqnaseXdm6od2xc","value":"2"}}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: OfferCreate = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 901e1ca0..15e73b28 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -9,14 +9,15 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, PathStep, }; -use crate::Err; -use crate::_serde::txn_flags; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLPaymentException; +use crate::Err; + +use super::{CommonFields, FlagCollection}; /// Transactions of the Payment type support additional values /// in the Flags field. This enum represents those options. @@ -58,97 +59,37 @@ pub struct Payment<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::payment")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the Payment model. - /// - /// See Payment fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, PaymentFlag>, + // The custom fields for the Payment model. + // + // See Payment fields: + // `` + /// The amount of currency to deliver. For non-XRP amounts, the nested field names + /// MUST be lower-case. If the tfPartialPayment flag is set, deliver up to this + /// amount instead. pub amount: Amount<'a>, + /// The unique address of the account receiving the payment. pub destination: Cow<'a, str>, + /// Arbitrary tag that identifies the reason for the payment to the destination, + /// or a hosted recipient to pay. pub destination_tag: Option, + /// Arbitrary 256-bit hash representing a specific reason or identifier for this payment. pub invoice_id: Option, + /// Array of payment paths to be used for this transaction. Must be omitted for + /// XRP-to-XRP transactions. pub paths: Option>>>, + /// Highest amount of source currency this transaction is allowed to cost, including + /// transfer fees, exchange rates, and slippage . Does not include the XRP destroyed + /// as a cost for submitting the transaction. For non-XRP amounts, the nested field + /// names MUST be lower-case. Must be supplied for cross-currency/cross-issue payments. + /// Must be omitted for XRP-to-XRP payments. pub send_max: Option>, + /// Minimum amount of destination currency this transaction should deliver. Only valid + /// if this is a partial payment. For non-XRP amounts, the nested field names are lower-case. pub deliver_min: Option>, } -impl<'a> Default for Payment<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::Payment, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - amount: Default::default(), - destination: Default::default(), - destination_tag: Default::default(), - invoice_id: Default::default(), - paths: Default::default(), - send_max: Default::default(), - deliver_min: Default::default(), - } - } -} - impl<'a: 'static> Model for Payment<'a> { fn get_errors(&self) -> Result<()> { match self._get_xrp_transaction_error() { @@ -164,26 +105,13 @@ impl<'a: 'static> Model for Payment<'a> { } } -impl<'a> Transaction for Payment<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::Payment(payment_flag) => match payment_flag { - PaymentFlag::TfLimitQuality => flags.contains(&PaymentFlag::TfLimitQuality), - PaymentFlag::TfNoDirectRipple => flags.contains(&PaymentFlag::TfNoDirectRipple), - PaymentFlag::TfPartialPayment => flags.contains(&PaymentFlag::TfPartialPayment), - }, - _ => false, - } +impl<'a> Transaction for Payment<'a> { + fn has_flag(&self, flag: &PaymentFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -196,7 +124,7 @@ impl<'a> PaymentError for Payment<'a> { context: "XRP to XRP payments".into(), resource: "".into(), }) - } else if self.account == self.destination { + } else if self.common_fields.account == self.destination { Err(XRPLPaymentException::ValueEqualsValueInContext { field1: "account".into(), field2: "destination".into(), @@ -213,7 +141,7 @@ impl<'a> PaymentError for Payment<'a> { fn _get_partial_payment_error(&self) -> Result<(), XRPLPaymentException> { if let Some(send_max) = &self.send_max { - if !self.has_flag(&Flag::Payment(PaymentFlag::TfPartialPayment)) + if !self.has_flag(&PaymentFlag::TfPartialPayment) && send_max.is_xrp() && self.amount.is_xrp() { @@ -225,13 +153,13 @@ impl<'a> PaymentError for Payment<'a> { } else { Ok(()) } - } else if self.has_flag(&Flag::Payment(PaymentFlag::TfPartialPayment)) { + } else if self.has_flag(&PaymentFlag::TfPartialPayment) { Err(XRPLPaymentException::FlagRequiresField { flag: PaymentFlag::TfPartialPayment, field: "send_max".into(), resource: "".into(), }) - } else if !self.has_flag(&Flag::Payment(PaymentFlag::TfPartialPayment)) { + } else if !self.has_flag(&PaymentFlag::TfPartialPayment) { if let Some(_deliver_min) = &self.deliver_min { Err(XRPLPaymentException::IllegalOption { field: "deliver_min".into(), @@ -247,7 +175,7 @@ impl<'a> PaymentError for Payment<'a> { } fn _get_exchange_error(&self) -> Result<(), XRPLPaymentException> { - if self.account == self.destination && self.send_max.is_none() { + if self.common_fields.account == self.destination && self.send_max.is_none() { return Err(XRPLPaymentException::OptionRequired { field: "send_max".into(), context: "exchanges".into(), @@ -262,39 +190,37 @@ impl<'a> PaymentError for Payment<'a> { impl<'a> Payment<'a> { pub fn new( account: Cow<'a, str>, - amount: Amount<'a>, - destination: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + amount: Amount<'a>, + destination: Cow<'a, str>, + deliver_min: Option>, destination_tag: Option, invoice_id: Option, paths: Option>>>, send_max: Option>, - deliver_min: Option>, ) -> Self { Self { - transaction_type: TransactionType::Payment, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::Payment, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, amount, destination, destination_tag, @@ -326,34 +252,31 @@ mod test_payment_error { #[test] fn test_xrp_to_xrp_error() { - let mut payment = Payment { - transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - amount: Amount::XRPAmount(XRPAmount::from("1000000")), - destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), - destination_tag: None, - invoice_id: None, - paths: Some(vec![vec![PathStep { + let mut payment = Payment::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::XRPAmount(XRPAmount::from("1000000")), + "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), + None, + None, + None, + Some(vec![vec![PathStep { account: Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), currency: None, issuer: None, r#type: None, type_hex: None, }]]), - send_max: None, - deliver_min: None, - }; + None, + ); assert_eq!( payment.validate().unwrap_err().to_string().as_str(), @@ -379,36 +302,33 @@ mod test_payment_error { #[test] fn test_partial_payments_eror() { - let mut payment = Payment { - transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - amount: Amount::XRPAmount("1000000".into()), - destination: "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), - destination_tag: None, - invoice_id: None, - paths: None, - send_max: None, - deliver_min: None, - }; - payment.flags = Some(vec![PaymentFlag::TfPartialPayment]); + let mut payment = Payment::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::XRPAmount("1000000".into()), + "rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into(), + None, + None, + None, + None, + None, + ); + payment.common_fields.flags = Some(vec![PaymentFlag::TfPartialPayment].into()); assert_eq!( payment.validate().unwrap_err().to_string().as_str(), "For the flag `TfPartialPayment` to be set it is required to define the field `send_max`. For more information see: " ); - payment.flags = None; + payment.common_fields.flags = None; payment.deliver_min = Some(Amount::XRPAmount("99999".into())); assert_eq!( @@ -419,32 +339,29 @@ mod test_payment_error { #[test] fn test_exchange_error() { - let payment = Payment { - transaction_type: TransactionType::Payment, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - amount: Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( + let payment = Payment::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(), "10".into(), )), - destination: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - destination_tag: None, - invoice_id: None, - paths: None, - send_max: None, - deliver_min: None, - }; + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + ); assert_eq!( payment.validate().unwrap_err().to_string().as_str(), @@ -454,7 +371,7 @@ mod test_payment_error { } #[cfg(test)] -mod test_serde { +mod tests { use alloc::vec; use crate::models::amount::{Amount, IssuedCurrencyAmount}; @@ -462,71 +379,39 @@ mod test_serde { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = Payment::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( - "USD".into(), - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "1".into(), - )), - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - Some("12".into()), - Some(2), - None, - None, - None, - None, - None, - None, - Some(vec![PaymentFlag::TfPartialPayment]), - None, None, + Some("12".into()), + Some(vec![PaymentFlag::TfPartialPayment].into()), None, None, + Some(2), None, None, None, - ); - let default_json = r#"{"TransactionType":"Payment","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","Sequence":2,"Flags":131072,"Amount":{"currency":"USD","issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","value":"1"},"Destination":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = Payment::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new( "USD".into(), "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), "1".into(), )), "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - Some("12".into()), - Some(2), - None, - None, - None, - None, - None, - None, - Some(vec![PaymentFlag::TfPartialPayment]), - None, - None, None, None, None, None, None, ); - let default_json = r#"{"TransactionType":"Payment","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Destination":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Amount":{"currency":"USD","value":"1","issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"},"Fee":"12","Flags":131072,"Sequence":2}"#; - - let txn_as_obj: Payment = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"Payment","Fee":"12","Flags":131072,"Sequence":2,"Amount":{"currency":"USD","issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","value":"1"},"Destination":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: Payment = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index b4af1204..c961c086 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -1,5 +1,6 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -7,12 +8,13 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::_serde::txn_flags; use crate::models::amount::XRPAmount; +use super::{CommonFields, FlagCollection}; + /// Transactions of the PaymentChannelClaim type support additional values /// in the Flags field. This enum represents those options. /// @@ -57,157 +59,79 @@ pub struct PaymentChannelClaim<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::payment_channel_claim")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the PaymentChannelClaim model. - /// - /// See PaymentChannelClaim fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, PaymentChannelClaimFlag>, + // The custom fields for the PaymentChannelClaim model. + // + // See PaymentChannelClaim fields: + // `` + /// The unique ID of the channel, as a 64-character hexadecimal string. pub channel: Cow<'a, str>, + /// otal amount of XRP, in drops, delivered by this channel after processing this claim. + /// Required to deliver XRP. Must be more than the total amount delivered by the channel + /// so far, but not greater than the Amount of the signed claim. Must be provided except + /// when closing the channel. pub balance: Option>, + /// The amount of XRP, in drops, authorized by the Signature. This must match the amount + /// in the signed message. This is the cumulative amount of XRP that can be dispensed by + /// the channel, including XRP previously redeemed. pub amount: Option>, + /// The signature of this claim, as hexadecimal. The signed message contains the channel + /// ID and the amount of the claim. Required unless the sender of the transaction is the + /// source address of the channel. pub signature: Option>, + /// The public key used for the signature, as hexadecimal. This must match the PublicKey + /// stored in the ledger for the channel. Required unless the sender of the transaction + /// is the source address of the channel and the Signature field is omitted. (The transaction + /// includes the public key so that rippled can check the validity of the signature before + /// trying to apply the transaction to the ledger.) pub public_key: Option>, } -impl<'a> Default for PaymentChannelClaim<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::PaymentChannelClaim, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - channel: Default::default(), - balance: Default::default(), - amount: Default::default(), - signature: Default::default(), - public_key: Default::default(), - } - } -} - impl<'a> Model for PaymentChannelClaim<'a> {} -impl<'a> Transaction for PaymentChannelClaim<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::PaymentChannelClaim(payment_channel_claim_flag) => { - match payment_channel_claim_flag { - PaymentChannelClaimFlag::TfClose => { - flags.contains(&PaymentChannelClaimFlag::TfClose) - } - PaymentChannelClaimFlag::TfRenew => { - flags.contains(&PaymentChannelClaimFlag::TfRenew) - } - } - } - _ => false, - } +impl<'a> Transaction for PaymentChannelClaim<'a> { + fn has_flag(&self, flag: &PaymentChannelClaimFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> PaymentChannelClaim<'a> { pub fn new( account: Cow<'a, str>, - channel: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, - balance: Option>, + source_tag: Option, + ticket_sequence: Option, + channel: Cow<'a, str>, amount: Option>, - signature: Option>, + balance: Option>, public_key: Option>, + signature: Option>, ) -> Self { Self { - transaction_type: TransactionType::PaymentChannelClaim, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::PaymentChannelClaim, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, channel, balance, amount, @@ -218,15 +142,13 @@ impl<'a> PaymentChannelClaim<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = PaymentChannelClaim::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), - None, None, None, None, @@ -236,45 +158,21 @@ mod test_serde { None, None, None, - None, - Some("1000000".into()), - Some("1000000".into()), - Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), - Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into()), - ); - let default_json = r#"{"TransactionType":"PaymentChannelClaim","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = PaymentChannelClaim::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, Some("1000000".into()), Some("1000000".into()), - Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into()), + Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), ); - let default_json = r#"{"TransactionType":"PaymentChannelClaim","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; - - let txn_as_obj: PaymentChannelClaim = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"PaymentChannelClaim","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: PaymentChannelClaim = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index c0274764..6a68935f 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -1,14 +1,18 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Create a unidirectional channel and fund it with XRP. /// /// See PaymentChannelCreate fields: @@ -25,135 +29,75 @@ pub struct PaymentChannelCreate<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::payment_channel_create")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the PaymentChannelCreate model. - /// - /// See PaymentChannelCreate fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the PaymentChannelCreate model. + // + // See PaymentChannelCreate fields: + // `` + /// Amount of XRP, in drops, to deduct from the sender's balance and set aside in this channel. + /// While the channel is open, the XRP can only go to the Destination address. When the channel + /// closes, any unclaimed XRP is returned to the source address's balance. pub amount: XRPAmount<'a>, + /// Address to receive XRP claims against this channel. This is also known as the + /// "destination address" for the channel. Cannot be the same as the sender (Account). pub destination: Cow<'a, str>, + /// Amount of time the source address must wait before closing the channel if it has unclaimed XRP. pub settle_delay: u32, + /// The 33-byte public key of the key pair the source will use to sign claims against this channel, + /// in hexadecimal. This can be any secp256k1 or Ed25519 public key. For more information on key + /// pairs, see Key Derivation pub public_key: Cow<'a, str>, + /// The time, in seconds since the Ripple Epoch, when this channel expires. Any transaction that + /// would modify the channel after this time closes the channel without otherwise affecting it. + /// This value is immutable; the channel can be closed earlier than this time but cannot remain + /// open after this time. pub cancel_after: Option, + /// Arbitrary tag to further specify the destination for this payment channel, such as a hosted + /// recipient at the destination address. pub destination_tag: Option, } -impl<'a> Default for PaymentChannelCreate<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::PaymentChannelCreate, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - amount: Default::default(), - destination: Default::default(), - settle_delay: Default::default(), - public_key: Default::default(), - cancel_after: Default::default(), - destination_tag: Default::default(), - } - } -} - impl<'a> Model for PaymentChannelCreate<'a> {} -impl<'a> Transaction for PaymentChannelCreate<'a> { +impl<'a> Transaction for PaymentChannelCreate<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> PaymentChannelCreate<'a> { pub fn new( account: Cow<'a, str>, - amount: XRPAmount<'a>, - destination: Cow<'a, str>, - settle_delay: u32, - public_key: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + amount: XRPAmount<'a>, + destination: Cow<'a, str>, + public_key: Cow<'a, str>, + settle_delay: u32, cancel_after: Option, destination_tag: Option, ) -> Self { Self { - transaction_type: TransactionType::PaymentChannelCreate, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::PaymentChannelCreate, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, amount, destination, settle_delay, @@ -165,63 +109,37 @@ impl<'a> PaymentChannelCreate<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = PaymentChannelCreate::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - XRPAmount::from("10000"), - "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), - 86400, - "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into(), None, None, None, None, None, - Some(11747), - None, - None, None, + Some(11747), None, - Some(533171558), - Some(23480), - ); - let default_json = r#"{"TransactionType":"PaymentChannelCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SettleDelay":86400,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","CancelAfter":533171558,"DestinationTag":23480}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = PaymentChannelCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), XRPAmount::from("10000"), "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".into(), - 86400, "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into(), - None, - None, - None, - None, - None, - Some(11747), - None, - None, - None, - None, + 86400, Some(533171558), Some(23480), ); - let default_json = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelCreate","Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SettleDelay":86400,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","CancelAfter":533171558,"DestinationTag":23480,"SourceTag":11747}"#; - - let txn_as_obj: PaymentChannelCreate = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelCreate","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SettleDelay":86400,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","CancelAfter":533171558,"DestinationTag":23480}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: PaymentChannelCreate = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 6278f12d..5a33dd8c 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -1,14 +1,18 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Add additional XRP to an open payment channel, /// and optionally update the expiration time of the channel. /// @@ -26,126 +30,63 @@ pub struct PaymentChannelFund<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::payment_channel_fund")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the PaymentChannelFund model. - /// - /// See PaymentChannelFund fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the PaymentChannelFund model. + // + // See PaymentChannelFund fields: + // `` + /// Amount of XRP, in drops to add to the channel. Must be a positive amount of XRP. pub amount: XRPAmount<'a>, + /// The unique ID of the channel to fund, as a 64-character hexadecimal string. pub channel: Cow<'a, str>, + /// New Expiration time to set for the channel, in seconds since the Ripple Epoch. + /// This must be later than either the current time plus the SettleDelay of the + /// channel, or the existing Expiration of the channel. After the Expiration time, + /// any transaction that would access the channel closes the channel without + /// taking its normal action. Any unspent XRP is returned to the source address when + /// the channel closes. (Expiration is separate from the channel's immutable + /// CancelAfter time.) For more information, see the PayChannel ledger object type. pub expiration: Option, } -impl<'a> Default for PaymentChannelFund<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::PaymentChannelFund, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - amount: Default::default(), - channel: Default::default(), - expiration: Default::default(), - } - } -} - impl<'a> Model for PaymentChannelFund<'a> {} -impl<'a> Transaction for PaymentChannelFund<'a> { +impl<'a> Transaction for PaymentChannelFund<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> PaymentChannelFund<'a> { pub fn new( account: Cow<'a, str>, - channel: Cow<'a, str>, - amount: XRPAmount<'a>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + amount: XRPAmount<'a>, + channel: Cow<'a, str>, expiration: Option, ) -> Self { Self { - transaction_type: TransactionType::PaymentChannelFund, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::PaymentChannelFund, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, amount, channel, expiration, @@ -154,18 +95,15 @@ impl<'a> PaymentChannelFund<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use crate::models::amount::XRPAmount; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = PaymentChannelFund::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), - XRPAmount::from("200000"), - None, None, None, None, @@ -174,39 +112,19 @@ mod test_serde { None, None, None, - None, - Some(543171558), - ); - let default_json = r#"{"TransactionType":"PaymentChannelFund","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Amount":"200000","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Expiration":543171558}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = PaymentChannelFund::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), XRPAmount::from("200000"), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), Some(543171558), ); - let default_json = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelFund","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Amount":"200000","Expiration":543171558}"#; - - let txn_as_obj: PaymentChannelFund = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelFund","Amount":"200000","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Expiration":543171558}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: PaymentChannelFund = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 6037054f..3a949eab 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -1,15 +1,16 @@ -use crate::_serde::txn_flags; use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; +use crate::models::transactions::{CommonFields, FlagCollection, Memo, Signer}; use crate::models::{ model::Model, - transactions::{Flag, Transaction, TransactionType}, + transactions::{Transaction, TransactionType}, }; #[derive( @@ -39,36 +40,8 @@ pub struct EnableAmendment<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::enable_amendment")] - transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The signature that verifies this transaction as originating - /// from the account it says it is from. - pub txn_signature: Option>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, + #[serde(flatten)] + pub common_fields: CommonFields<'a, EnableAmendmentFlag>, /// The custom fields for the EnableAmendment model. /// /// See EnableAmendment fields: @@ -79,51 +52,45 @@ pub struct EnableAmendment<'a> { impl<'a> Model for EnableAmendment<'a> {} -impl<'a> Transaction for EnableAmendment<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - match flag { - Flag::EnableAmendment(enable_amendment_flag) => match enable_amendment_flag { - EnableAmendmentFlag::TfGotMajority => self - .flags - .as_ref() - .unwrap() - .contains(&EnableAmendmentFlag::TfGotMajority), - EnableAmendmentFlag::TfLostMajority => self - .flags - .as_ref() - .unwrap() - .contains(&EnableAmendmentFlag::TfLostMajority), - }, - _ => false, - } +impl<'a> Transaction for EnableAmendment<'a> { + fn has_flag(&self, flag: &EnableAmendmentFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> EnableAmendment<'a> { pub fn new( account: Cow<'a, str>, - amendment: Cow<'a, str>, - ledger_sequence: u32, + account_txn_id: Option>, fee: Option>, + flags: Option>, + last_ledger_sequence: Option, + memos: Option>, sequence: Option, - signing_pub_key: Option>, + signers: Option>>, source_tag: Option, - txn_signature: Option>, - flags: Option>, + ticket_sequence: Option, + amendment: Cow<'a, str>, + ledger_sequence: u32, ) -> Self { Self { - transaction_type: TransactionType::EnableAmendment, - account, - fee, - sequence, - signing_pub_key, - source_tag, - txn_signature, - flags, + common_fields: CommonFields { + account, + transaction_type: TransactionType::EnableAmendment, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, amendment, ledger_sequence, } diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 19c3c678..2ec56e47 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -1,8 +1,12 @@ use alloc::borrow::Cow; +use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::transactions::{CommonFields, Memo, Signer}; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Transaction, TransactionType}, @@ -22,34 +26,8 @@ pub struct SetFee<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::set_fee")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The signature that verifies this transaction as originating - /// from the account it says it is from. - pub txn_signature: Option>, - /// Set of bit-flags for this transaction. - pub flags: Option, + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, /// The custom fields for the SetFee model. /// /// See SetFee fields: @@ -63,35 +41,43 @@ pub struct SetFee<'a> { impl<'a> Model for SetFee<'a> {} -impl<'a> Transaction for SetFee<'a> { +impl<'a> Transaction for SetFee<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> SetFee<'a> { pub fn new( account: Cow<'a, str>, + account_txn_id: Option>, + fee: Option>, + last_ledger_sequence: Option, + memos: Option>, + sequence: Option, + signers: Option>>, + source_tag: Option, + ticket_sequence: Option, base_fee: XRPAmount<'a>, reference_fee_units: u32, reserve_base: u32, reserve_increment: u32, ledger_sequence: u32, - fee: Option>, - sequence: Option, - signing_pub_key: Option>, - source_tag: Option, - txn_signature: Option>, ) -> Self { Self { - transaction_type: TransactionType::SetFee, - account, - fee, - sequence, - signing_pub_key, - source_tag, - txn_signature, - flags: None, + common_fields: CommonFields { + account, + transaction_type: TransactionType::SetFee, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, base_fee, reference_fee_units, reserve_base, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 57941a22..3bceedc6 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -1,9 +1,13 @@ use alloc::borrow::Cow; +use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; +use crate::models::transactions::{CommonFields, Memo, Signer}; +use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, model::Model, @@ -33,34 +37,8 @@ pub struct UNLModify<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::unl_modify")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The signature that verifies this transaction as originating - /// from the account it says it is from. - pub txn_signature: Option>, - /// Set of bit-flags for this transaction. - pub flags: Option, + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, /// The custom fields for the UNLModify model. /// /// See UNLModify fields: @@ -72,33 +50,41 @@ pub struct UNLModify<'a> { impl<'a> Model for UNLModify<'a> {} -impl<'a> Transaction for UNLModify<'a> { +impl<'a> Transaction for UNLModify<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> UNLModify<'a> { pub fn new( account: Cow<'a, str>, - ledger_sequence: u32, - unlmodify_disabling: UNLModifyDisabling, - unlmodify_validator: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, + last_ledger_sequence: Option, + memos: Option>, sequence: Option, - signing_pub_key: Option>, + signers: Option>>, source_tag: Option, - txn_signature: Option>, + ticket_sequence: Option, + ledger_sequence: u32, + unlmodify_disabling: UNLModifyDisabling, + unlmodify_validator: Cow<'a, str>, ) -> Self { Self { - transaction_type: TransactionType::UNLModify, - account, - fee, - sequence, - signing_pub_key, - source_tag, - txn_signature, - flags: None, + common_fields: CommonFields { + account, + transaction_type: TransactionType::UNLModify, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, ledger_sequence, unlmodify_disabling, unlmodify_validator, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 0dca531a..0fdae169 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -1,14 +1,18 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// You can protect your account by assigning a regular key pair to /// it and using it instead of the master key pair to sign transactions /// whenever possible. If your regular key pair is compromised, but @@ -29,157 +33,67 @@ pub struct SetRegularKey<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::set_regular_key")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the SetRegularKey model. - /// - /// See SetRegularKey fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the SetRegularKey model. + // + // See SetRegularKey fields: + // `` + /// A base-58-encoded Address that indicates the regular key pair to be + /// assigned to the account. If omitted, removes any existing regular key + /// pair from the account. Must not match the master key pair for the address. pub regular_key: Option>, } -impl<'a> Default for SetRegularKey<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::SetRegularKey, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - regular_key: Default::default(), - } - } -} - impl<'a> Model for SetRegularKey<'a> {} -impl<'a> Transaction for SetRegularKey<'a> { +impl<'a> Transaction for SetRegularKey<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> SetRegularKey<'a> { pub fn new( account: Cow<'a, str>, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, regular_key: Option>, ) -> Self { Self { - transaction_type: TransactionType::SetRegularKey, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::SetRegularKey, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, regular_key, } } } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = SetRegularKey::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - Some("12".into()), - None, - None, None, - None, - None, - None, - None, - None, - None, - Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD".into()), - ); - let default_json = r#"{"TransactionType":"SetRegularKey","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = SetRegularKey::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), Some("12".into()), None, None, @@ -187,15 +101,17 @@ mod test_serde { None, None, None, - None, - None, - None, Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD".into()), ); - let default_json = r#"{"TransactionType":"SetRegularKey","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; - - let txn_as_obj: SetRegularKey = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SetRegularKey","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: SetRegularKey = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 97b6dd6a..837963ea 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -8,6 +8,7 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::transactions::XRPLSignerListSetException; +use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, model::Model, @@ -15,6 +16,8 @@ use crate::models::{ }; use crate::{serde_with_tag, Err}; +use super::CommonFields; + serde_with_tag! { #[derive(Debug, PartialEq, Eq, Default, Clone, new)] #[skip_serializing_none] @@ -43,85 +46,22 @@ pub struct SignerListSet<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::signer_list_set")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the TicketCreate model. - /// - /// See TicketCreate fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the TicketCreate model. + // + // See TicketCreate fields: + // `` + /// A target number for the signer weights. A multi-signature from this list + /// is valid only if the sum weights of the signatures provided is greater + /// than or equal to this value. To delete a signer list, use the value 0. pub signer_quorum: u32, + /// A target number for the signer weights. A multi-signature from this list is + /// valid only if the sum weights of the signatures provided is greater than + /// or equal to this value. To delete a signer list, use the value 0. pub signer_entries: Option>, } -impl<'a> Default for SignerListSet<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::SignerListSet, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - signer_quorum: Default::default(), - signer_entries: Default::default(), - } - } -} - impl<'a> Model for SignerListSet<'a> { fn get_errors(&self) -> Result<()> { match self._get_signer_entries_error() { @@ -134,9 +74,9 @@ impl<'a> Model for SignerListSet<'a> { } } -impl<'a> Transaction for SignerListSet<'a> { +impl<'a> Transaction for SignerListSet<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } @@ -195,10 +135,10 @@ impl<'a> SignerListSetError for SignerListSet<'a> { } } if let Some(_signer_entries) = &self.signer_entries { - if accounts.contains(&self.account.to_string()) { + if accounts.contains(&self.common_fields.account.to_string()) { Err(XRPLSignerListSetException::CollectionInvalidItem { field: "signer_entries".into(), - found: self.account.clone(), + found: self.common_fields.account.clone(), resource: "".into(), }) } else if self.signer_quorum > signer_weight_sum { @@ -228,33 +168,31 @@ impl<'a> SignerListSetError for SignerListSet<'a> { impl<'a> SignerListSet<'a> { pub fn new( account: Cow<'a, str>, - signer_quorum: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + signer_quorum: u32, signer_entries: Option>, ) -> Self { Self { - transaction_type: TransactionType::SignerListSet, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::SignerListSet, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, signer_quorum, signer_entries, } @@ -277,26 +215,22 @@ mod test_signer_list_set_error { #[test] fn test_signer_list_deleted_error() { - let mut signer_list_set = SignerListSet { - transaction_type: TransactionType::SignerListSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - signer_quorum: 0, - signer_entries: Some(vec![SignerEntry { + let mut signer_list_set = SignerListSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + 0, + Some(vec![SignerEntry { account: "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), signer_weight: 2, }]), - }; + ); assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), @@ -314,23 +248,19 @@ mod test_signer_list_set_error { #[test] fn test_signer_entries_error() { - let mut signer_list_set = SignerListSet { - transaction_type: TransactionType::SignerListSet, - account: "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), - fee: None, - sequence: None, - last_ledger_sequence: None, - account_txn_id: None, - signing_pub_key: None, - source_tag: None, - ticket_sequence: None, - txn_signature: None, - flags: None, - memos: None, - signers: None, - signer_quorum: 3, - signer_entries: Some(vec![]), - }; + let mut signer_list_set = SignerListSet::new( + "rU4EE1FskCPJw5QkLx1iGgdWiJa6HeqYyb".into(), + None, + None, + None, + None, + None, + None, + None, + None, + 3, + Some(vec![]), + ); assert_eq!( signer_list_set.validate().unwrap_err().to_string().as_str(), @@ -432,46 +362,17 @@ mod test_signer_list_set_error { } #[cfg(test)] -mod test_serde { +mod tests { use alloc::string::ToString; use alloc::vec; use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = SignerListSet::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 3, - Some("12".into()), - None, - None, - None, - None, None, - None, - None, - None, - None, - Some(vec![ - SignerEntry::new("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), 2), - SignerEntry::new("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), 1), - SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1), - ]), - ); - let default_json = r#"{"TransactionType":"SignerListSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","Sequence":null,"LastLedgerSequence":null,"AccountTxnID":null,"SigningPubKey":null,"SourceTag":null,"TicketSequence":null,"TxnSignature":null,"Flags":null,"Memos":null,"Signers":null,"SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = SignerListSet::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 3, Some("12".into()), None, None, @@ -479,19 +380,22 @@ mod test_serde { None, None, None, - None, - None, - None, + 3, Some(vec![ SignerEntry::new("rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW".to_string(), 2), SignerEntry::new("rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v".to_string(), 1), SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1), ]), ); - let default_json = r#"{"TransactionType":"SignerListSet","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"12","SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; - - let txn_as_obj: SignerListSet = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SignerListSet","Fee":"12","SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: SignerListSet = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 157a6cc9..77ececd3 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -4,11 +4,14 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; +use crate::models::NoFlags; use crate::models::{ model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; +use super::CommonFields; + /// Sets aside one or more sequence numbers as Tickets. /// /// See TicketCreate: @@ -25,173 +28,84 @@ pub struct TicketCreate<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::ticket_create")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - pub flags: Option, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the TicketCreate model. - /// - /// See TicketCreate fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, NoFlags>, + // The custom fields for the TicketCreate model. + // + // See TicketCreate fields: + // `` + /// How many Tickets to create. This must be a positive number and cannot cause + /// the account to own more than 250 Tickets after executing this transaction. pub ticket_count: u32, } -impl<'a> Default for TicketCreate<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::TicketCreate, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - ticket_count: Default::default(), - } - } -} - impl<'a> Model for TicketCreate<'a> {} -impl<'a> Transaction for TicketCreate<'a> { +impl<'a> Transaction for TicketCreate<'a> { fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.get_transaction_type() } } impl<'a> TicketCreate<'a> { pub fn new( account: Cow<'a, str>, - ticket_count: u32, + account_txn_id: Option>, fee: Option>, - sequence: Option, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + ticket_count: u32, ) -> Self { Self { - transaction_type: TransactionType::TicketCreate, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags: None, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::TicketCreate, + account_txn_id, + fee, + flags: None, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, ticket_count, } } } #[cfg(test)] -mod test_serde { +mod tests { use super::*; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = TicketCreate::new( "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 10, - Some("10".into()), - Some(381), - None, - None, None, - None, - None, - None, - None, - None, - ); - let default_json = r#"{"TransactionType":"TicketCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"10","Sequence":381,"TicketCount":10}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = TicketCreate::new( - "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), - 10, Some("10".into()), - Some(381), - None, - None, - None, None, None, + Some(381), None, None, None, + 10, ); - let default_json = r#"{"TransactionType":"TicketCreate","Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","Fee":"10","Sequence":381,"TicketCount":10}"#; - - let txn_as_obj: TicketCreate = serde_json::from_str(default_json).unwrap(); + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"TicketCreate","Fee":"10","Sequence":381,"TicketCount":10}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); - assert_eq!(txn_as_obj, default_txn); + // Deserialize + let deserialized: TicketCreate = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 10a5cd65..86a2f2c9 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -1,5 +1,6 @@ use alloc::borrow::Cow; use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_with::skip_serializing_none; @@ -7,12 +8,13 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ model::Model, - transactions::{Flag, Memo, Signer, Transaction, TransactionType}, + transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::_serde::txn_flags; use crate::models::amount::{IssuedCurrencyAmount, XRPAmount}; +use super::{CommonFields, FlagCollection}; + /// Transactions of the TrustSet type support additional values /// in the Flags field. This enum represents those options. /// @@ -52,148 +54,64 @@ pub struct TrustSet<'a> { // See Transaction Common Fields: // `` /// The type of transaction. - #[serde(default = "TransactionType::trust_set")] - pub transaction_type: TransactionType, - /// The unique address of the account that initiated the transaction. - pub account: Cow<'a, str>, - /// Integer amount of XRP, in drops, to be destroyed as a cost - /// for distributing this transaction to the network. Some - /// transaction types have different minimum requirements. - /// See Transaction Cost for details. - pub fee: 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. - /// The special case 0 means the transaction is using a Ticket instead. - pub sequence: Option, - /// Highest ledger index this transaction can appear in. - /// Specifying this field places a strict upper limit on how long - /// the transaction can wait to be validated or rejected. - /// See Reliable Transaction Submission for more details. - pub last_ledger_sequence: Option, - /// Hash value identifying another transaction. If provided, this - /// transaction is only valid if the sending account's - /// previously-sent transaction matches the provided hash. - #[serde(rename = "AccountTxnID")] - pub account_txn_id: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub source_tag: Option, - /// The sequence number of the ticket to use in place - /// 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>, - /// Set of bit-flags for this transaction. - #[serde(default)] - #[serde(with = "txn_flags")] - pub flags: Option>, - /// Additional arbitrary information used to identify this transaction. - pub memos: 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 - /// payment's SourceTag as the refund payment's DestinationTag. - pub signers: Option>>, - /// The custom fields for the TrustSet model. - /// - /// See TrustSet fields: - /// `` + #[serde(flatten)] + pub common_fields: CommonFields<'a, TrustSetFlag>, + // The custom fields for the TrustSet model. + // + // See TrustSet fields: + // `` + /// Object defining the trust line to create or modify, in the format of a Currency Amount. pub limit_amount: IssuedCurrencyAmount<'a>, + /// Value incoming balances on this trust line at the ratio of this number per + /// 1,000,000,000 units. A value of 0 is shorthand for treating balances at face value. pub quality_in: Option, + /// Value outgoing balances on this trust line at the ratio of this number per + /// 1,000,000,000 units. A value of 0 is shorthand for treating balances at face value. pub quality_out: Option, } -impl<'a> Default for TrustSet<'a> { - fn default() -> Self { - Self { - transaction_type: TransactionType::TrustSet, - account: Default::default(), - fee: Default::default(), - sequence: Default::default(), - last_ledger_sequence: Default::default(), - account_txn_id: Default::default(), - signing_pub_key: Default::default(), - source_tag: Default::default(), - ticket_sequence: Default::default(), - txn_signature: Default::default(), - flags: Default::default(), - memos: Default::default(), - signers: Default::default(), - limit_amount: Default::default(), - quality_in: Default::default(), - quality_out: Default::default(), - } - } -} - impl<'a> Model for TrustSet<'a> {} -impl<'a> Transaction for TrustSet<'a> { - fn has_flag(&self, flag: &Flag) -> bool { - let mut flags = &Vec::new(); - - if let Some(flag_set) = self.flags.as_ref() { - flags = flag_set; - } - - match flag { - Flag::TrustSet(trust_set_flag) => match trust_set_flag { - TrustSetFlag::TfClearFreeze => flags.contains(&TrustSetFlag::TfClearFreeze), - TrustSetFlag::TfClearNoRipple => flags.contains(&TrustSetFlag::TfClearNoRipple), - TrustSetFlag::TfSetAuth => flags.contains(&TrustSetFlag::TfSetAuth), - TrustSetFlag::TfSetFreeze => flags.contains(&TrustSetFlag::TfSetFreeze), - TrustSetFlag::TfSetNoRipple => flags.contains(&TrustSetFlag::TfSetNoRipple), - }, - _ => false, - } +impl<'a> Transaction for TrustSet<'a> { + fn has_flag(&self, flag: &TrustSetFlag) -> bool { + self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.transaction_type.clone() + self.common_fields.transaction_type.clone() } } impl<'a> TrustSet<'a> { pub fn new( account: Cow<'a, str>, - limit_amount: IssuedCurrencyAmount<'a>, + account_txn_id: Option>, fee: Option>, - sequence: Option, + flags: Option>, last_ledger_sequence: Option, - account_txn_id: Option>, - signing_pub_key: Option>, - source_tag: Option, - ticket_sequence: Option, - txn_signature: Option>, - flags: Option>, memos: Option>, + sequence: Option, signers: Option>>, + source_tag: Option, + ticket_sequence: Option, + limit_amount: IssuedCurrencyAmount<'a>, quality_in: Option, quality_out: Option, ) -> Self { Self { - transaction_type: TransactionType::TrustSet, - account, - fee, - sequence, - last_ledger_sequence, - account_txn_id, - signing_pub_key, - source_tag, - ticket_sequence, - txn_signature, - flags, - memos, - signers, + common_fields: CommonFields { + account, + transaction_type: TransactionType::TrustSet, + account_txn_id, + fee, + flags, + last_ledger_sequence, + memos, + sequence, + signers, + source_tag, + ticket_sequence, + }, limit_amount, quality_in, quality_out, @@ -202,68 +120,40 @@ impl<'a> TrustSet<'a> { } #[cfg(test)] -mod test_serde { +mod tests { use super::*; use alloc::vec; #[test] - fn test_serialize() { + fn test_serde() { let default_txn = TrustSet::new( "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), - IssuedCurrencyAmount::new( - "USD".into(), - "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc".into(), - "100".into(), - ), + None, Some("12".into()), - Some(12), + Some(vec![TrustSetFlag::TfClearNoRipple].into()), Some(8007750), None, + Some(12), None, None, None, - None, - Some(vec![TrustSetFlag::TfClearNoRipple]), - None, - None, - None, - None, - ); - let default_json = r#"{"TransactionType":"TrustSet","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","Sequence":12,"LastLedgerSequence":8007750,"Flags":262144,"LimitAmount":{"currency":"USD","issuer":"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc","value":"100"}}"#; - - let txn_as_string = serde_json::to_string(&default_txn).unwrap(); - let txn_json = txn_as_string.as_str(); - - assert_eq!(txn_json, default_json); - } - - #[test] - fn test_deserialize() { - let default_txn = TrustSet::new( - "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX".into(), IssuedCurrencyAmount::new( "USD".into(), "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc".into(), "100".into(), ), - Some("12".into()), - Some(12), - Some(8007750), - None, - None, - None, - None, - None, - Some(vec![TrustSetFlag::TfClearNoRipple]), - None, - None, None, None, ); - let default_json = r#"{"TransactionType":"TrustSet","Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","Fee":"12","Flags":262144,"LastLedgerSequence":8007750,"LimitAmount":{"currency":"USD","issuer":"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc","value":"100"},"Sequence":12}"#; - - let txn_as_obj: TrustSet = serde_json::from_str(default_json).unwrap(); - - assert_eq!(txn_as_obj, default_txn); + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"TrustSet","Fee":"12","Flags":262144,"LastLedgerSequence":8007750,"Sequence":12,"LimitAmount":{"currency":"USD","issuer":"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc","value":"100"}}"#; + // Serialize + let default_json_value = serde_json::to_value(default_json_str).unwrap(); + let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + assert_eq!(serialized_value, default_json_value); + + // Deserialize + let deserialized: TrustSet = serde_json::from_str(default_json_str).unwrap(); + assert_eq!(default_txn, deserialized); } } diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 54989551..9a76a87d 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -10,8 +10,8 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { let mut websocket = connect_to_wss_tungstinite_echo().await?; let account_info = AccountInfo::new( - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, None, None, @@ -51,8 +51,8 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; let account_info = AccountInfo::new( - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, + "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), None, None, None, From e18673c35818c598edd260339a6766d5dd72dbcc Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:10:24 +0000 Subject: [PATCH 038/113] revise dependencies --- Cargo.toml | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e0d190a..398f49da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,7 @@ serde_json = { version = "1.0.68", default-features = false, features = [ serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.14.0", default-features = false, features = [ - "serde", -] } +hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" @@ -66,14 +64,15 @@ url = { version = "2.2.2", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } - -[dependencies.embedded-websocket] -# git version needed to use `framer_async` -git = "https://github.com/ninjasource/embedded-websocket" -version = "0.9.2" -rev = "8d87d46f46fa0c75e099ca8aad37e8d00c8854f8" -default-features = false -optional = true +embassy-sync = { version = "0.6.0", default-features = false } +embedded-io-async = "0.6.1" +futures-sink = { version = "0.3.30", default-features = false } +futures-core = { version = "0.3.30", default-features = false } +futures-util = { version = "0.3.30", optional = true } +tokio-util = { version = "0.7.7", features = ["codec"], optional = true } +bytes = { version = "1.4.0", default-features = false } +embassy-futures = "0.1.1" +embedded-websocket = { version = "0.9.3", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -82,8 +81,6 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -futures-util = "0.3.30" -bytes = { version = "1.4.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = [ "getrandom", "std", @@ -96,13 +93,20 @@ harness = false [features] default = ["std", "core", "models", "utils", "tungstenite"] -models = ["core", "transactions", "requests", "ledger"] +models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] +results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -tungstenite = ["url", "futures", "tokio/full", "tokio-tungstenite/native-tls"] +tungstenite = [ + "url", + "futures", + "tokio/net", + "tokio-tungstenite/native-tls", + "futures-util", +] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] utils = [] @@ -119,4 +123,5 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", + "dep:tokio-util", ] From fb8586248676f24b232efa594a1db340a3c96fd7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:11:11 +0000 Subject: [PATCH 039/113] remove old clients --- src/asynch/clients/embedded_ws.rs | 168 ------------------------------ src/asynch/clients/tungstenite.rs | 112 -------------------- 2 files changed, 280 deletions(-) delete mode 100644 src/asynch/clients/embedded_ws.rs delete mode 100644 src/asynch/clients/tungstenite.rs diff --git a/src/asynch/clients/embedded_ws.rs b/src/asynch/clients/embedded_ws.rs deleted file mode 100644 index 76106dee..00000000 --- a/src/asynch/clients/embedded_ws.rs +++ /dev/null @@ -1,168 +0,0 @@ -use super::{ - exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, -}; -use crate::Err; -use anyhow::Result; -use core::marker::PhantomData; -use core::{fmt::Debug, ops::Deref}; -pub use embedded_websocket::{ - framer_async::{ - Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, - ReadResult as EmbeddedWebsocketReadMessageType, - }, - Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, - WebSocket as EmbeddedWebsocket, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, - WebSocketOptions as EmbeddedWebsocketOptions, - WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, - WebSocketState as EmbeddedWebsocketState, -}; -use futures::{Sink, Stream}; -use rand_core::RngCore; - -pub struct AsyncWebsocketClient { - inner: EmbeddedWebsocketFramer, - status: PhantomData, -} - -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} - -impl AsyncWebsocketClient { - /// Open a websocket connection. - pub async fn open<'a, B, E>( - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - rng: Rng, - websocket_options: &EmbeddedWebsocketOptions<'_>, - ) -> Result> - where - B: AsRef<[u8]>, - E: Debug, - { - let websocket = EmbeddedWebsocket::::new_client(rng); - let mut framer = EmbeddedWebsocketFramer::new(websocket); - match framer.connect(stream, buffer, websocket_options).await { - Ok(Some(_)) => {} - Ok(None) => {} - Err(error) => return Err!(XRPLWebsocketException::from(error)), - } - - Ok(AsyncWebsocketClient { - inner: framer, - status: PhantomData::, - }) - } -} - -impl AsyncWebsocketClient { - /// Encode a message to be sent over the websocket. - pub fn encode( - &mut self, - message_type: EmbeddedWebsocketSendMessageType, - end_of_message: bool, - from: &[u8], - to: &mut [u8], - ) -> Result - where - E: Debug, - { - match self - .inner - .encode::(message_type, end_of_message, from, to) - { - Ok(bytes_written) => Ok(bytes_written), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Send a message over the websocket. - pub async fn send<'b, E, R: serde::Serialize>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - end_of_message: bool, - frame_buf: R, - ) -> Result<()> - where - E: Debug, - { - match serde_json::to_vec(&frame_buf) { - Ok(frame_buf) => match self - .inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Text, - end_of_message, - frame_buf.as_slice(), - ) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - }, - Err(error) => Err!(error), - } - } - - /// Close the websocket connection. - pub async fn close<'b, E>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - close_status: EmbeddedWebsocketCloseStatusCode, - status_description: Option<&str>, - ) -> Result<()> - where - E: Debug, - { - match self - .inner - .close(stream, stream_buf, close_status, status_description) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Read a message from the websocket. - pub async fn next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Option>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Some(Ok(read_result)), - Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), - None => None, - } - } - - /// Read a message from the websocket. - /// - /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. - pub async fn try_next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Result>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Ok(Some(read_result)), - Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), - None => Ok(None), - } - } -} diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs deleted file mode 100644 index c03bf281..00000000 --- a/src/asynch/clients/tungstenite.rs +++ /dev/null @@ -1,112 +0,0 @@ -use super::{ - exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, -}; -use crate::Err; - -use anyhow::Result; -use core::marker::PhantomData; -use core::{pin::Pin, task::Poll}; -use futures::{Sink, Stream}; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, -}; -use url::Url; - -pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - -pub struct AsyncWebsocketClient { - inner: TungsteniteWebsocketStream>, - status: PhantomData, -} - -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} - -impl Sink for AsyncWebsocketClient -where - I: serde::Serialize, -{ - type Error = anyhow::Error; - - fn poll_ready( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { - match serde_json::to_string(&item) { - Ok(json) => { - match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } - } - Err(error) => Err!(error), - } - } - - fn poll_flush( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_flush(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn poll_close( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_close(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } -} - -impl Stream for AsyncWebsocketClient { - type Item = > as Stream>::Item; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll> { - match Pin::new(&mut self.inner).poll_next(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(item)), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -impl AsyncWebsocketClient { - pub async fn open(uri: Url) -> Result> { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - inner: websocket_stream, - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } - } -} From 4ab8dfc841ad8d245d5c9c6db247dfec76a797d7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:11:39 +0000 Subject: [PATCH 040/113] add new clients --- .../clients/websocket/embedded_websocket.rs | 253 ++++++++++++++++++ src/asynch/clients/websocket/tungstenite.rs | 213 +++++++++++++++ 2 files changed, 466 insertions(+) create mode 100644 src/asynch/clients/websocket/embedded_websocket.rs create mode 100644 src/asynch/clients/websocket/tungstenite.rs diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs new file mode 100644 index 00000000..83fef699 --- /dev/null +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -0,0 +1,253 @@ +use core::{ + fmt::{Debug, Display}, + iter::FromIterator, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use alloc::{ + dbg, + string::{String, ToString}, + sync::Arc, +}; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_io_async::{ErrorType, Read, Write}; +use embedded_websocket::{ + framer_async::Framer, framer_async::ReadResult, Client, WebSocketClient, WebSocketOptions, + WebSocketSendMessageType, +}; +use futures_core::Stream; +use futures_sink::Sink; +use rand_core::RngCore; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use crate::{ + asynch::clients::{ + client::Client as ClientTrait, + websocket::websocket_base::{MessageHandler, WebsocketBase}, + }, + models::requests::Request, +}; + +use super::exceptions::XRPLWebsocketException; + +pub struct AsyncWebsocketClient< + const BUF: usize, + Tcp, + B, + E, + Rng: RngCore, + M = SingleExecutorMutex, + Status = WebsocketClosed, +> where + M: RawMutex, + B: Deref + AsRef<[u8]>, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + tcp: Arc>, + websocket: Arc>>, + tx_buffer: [u8; BUF], + websocket_base: Arc>>, + status: PhantomData, +} + +impl + AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + pub async fn open( + rng: Rng, + tcp: Tcp, + url: Url, + ) -> Result< + AsyncWebsocketClient, + XRPLWebsocketException, + > { + // replace the scheme with http or https + let scheme = match url.scheme() { + "wss" => "https", + "ws" => "http", + _ => url.scheme(), + }; + let port = match url.port() { + Some(port) => port, + None => match url.scheme() { + "wss" => 443, + "ws" => 80, + _ => 80, + }, + } + .to_string(); + let path = url.path(); + let host = url.host_str().unwrap(); + let origin = scheme.to_string() + "://" + host + ":" + &port + path; + let websocket_options = WebSocketOptions { + path, + host, + origin: &origin, + sub_protocols: None, + additional_headers: None, + }; + // dbg!(&websocket_options.path); + // dbg!(&websocket_options.host); + // dbg!(&websocket_options.origin); + // panic!(); + let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); + let tcp = Arc::new(Mutex::new(tcp)); + let mut buffer = [0; BUF]; + websocket + .lock() + .await + .connect( + tcp.lock().await.deref_mut(), + &mut buffer, + &websocket_options, + ) + .await + .unwrap(); + + Ok(AsyncWebsocketClient { + tcp, + websocket, + tx_buffer: [0; BUF], + websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + status: PhantomData::, + }) + } +} + +impl + AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn do_write(&self, buf: &[u8]) -> Result::Error> { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + let mut buffer = self.tx_buffer; + match inner + .write( + tcp.deref_mut(), + &mut buffer, + WebSocketSendMessageType::Text, + false, + buf, + ) + .await + { + Ok(()) => Ok(buf.len()), + Err(error) => Err(XRPLWebsocketException::::from(error)), + } + } +} + +impl ErrorType + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + type Error = XRPLWebsocketException; +} + +impl Write + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + self.do_write(buf).await + } +} + +impl Read + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + match inner.read(tcp.deref_mut(), buf).await { + Some(Ok(ReadResult::Text(t))) => Ok(t.len()), + Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), + Some(Ok(ReadResult::Ping(_))) => Ok(0), + Some(Ok(_)) => Err(XRPLWebsocketException::::UnexpectedMessageType), + Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), + None => Err(XRPLWebsocketException::::Disconnected), + } + } +} + +impl MessageHandler + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn setup_request_future(&mut self, id: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.setup_request_future(id).await; + } + + async fn handle_message(&mut self, message: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.handle_message(message).await; + } + + async fn pop_message(&mut self) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.pop_message().await + } + + async fn request_impl(&mut self, id: String) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.request_impl(id).await + } +} + +impl ClientTrait + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + mut request: Req, + ) -> anyhow::Result> { + let request_id = self.set_request_id::(&mut request); + self.do_write(serde_json::to_string(&request).unwrap().as_bytes()) + .await + .unwrap(); + let mut websocket_base = self.websocket_base.lock().await; + let message = websocket_base.request_impl(request_id.to_string()).await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } +} diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs new file mode 100644 index 00000000..ee4660fc --- /dev/null +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -0,0 +1,213 @@ +use super::exceptions::XRPLWebsocketException; +use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use crate::asynch::clients::client::Client; +use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; +use crate::models::requests::Request; +use crate::models::results::XRPLResponse; +use crate::Err; + +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use anyhow::Result; +use core::marker::PhantomData; +use core::{pin::Pin, task::Poll}; +use embassy_futures::block_on; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use futures::{Sink, Stream}; +use futures_util::SinkExt; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, + WebSocketStream as TungsteniteWebsocketStream, +}; +use url::Url; + +pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; + +pub type AsyncWebsocketConnection = + Arc>>>; + +pub struct AsyncWebsocketClient +where + M: RawMutex, +{ + websocket: AsyncWebsocketConnection, + websocket_base: Arc>>, + status: PhantomData, +} + +impl Sink for AsyncWebsocketClient +where + M: RawMutex, + Self: Unpin, +{ + type Error = anyhow::Error; + + fn poll_ready( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { + let mut guard = block_on(self.websocket.lock()); + // let _ = self.websocket.lock().then(|mut guard| async move { + match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + // }); + // Ok(()) + } + + fn poll_flush( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_flush(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn poll_close( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_close(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } +} + +impl Stream for AsyncWebsocketClient +where + M: RawMutex, +{ + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_next(cx) { + Poll::Ready(Some(item)) => match item { + Ok(message) => match message { + TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), + TungsteniteMessage::Binary(response) => { + let response_string = String::from_utf8(response).unwrap(); + Poll::Ready(Some(Ok(response_string))) + } + _ => Poll::Ready(Some(Err!( + XRPLWebsocketException::::UnexpectedMessageType + ))), + }, + Err(error) => Poll::Ready(Some(Err!(error))), + }, + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl AsyncWebsocketClient +where + M: RawMutex, +{ + pub async fn open(uri: Url) -> Result> { + match tungstenite_connect_async(uri).await { + Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + websocket: Arc::new(Mutex::new(websocket_stream)), + websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + status: PhantomData::, + }), + Err(error) => { + Err!(XRPLWebsocketException::UnableToConnect::( + error + )) + } + } + } +} + +impl MessageHandler for AsyncWebsocketClient +where + M: RawMutex, +{ + async fn setup_request_future(&mut self, id: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.setup_request_future(id).await; + } + + async fn handle_message(&mut self, message: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.handle_message(message).await; + } + + async fn pop_message(&mut self) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.pop_message().await + } + + async fn request_impl(&mut self, id: String) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.request_impl(id).await + } +} + +impl Client for AsyncWebsocketClient +where + M: RawMutex, +{ + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + mut request: Req, + ) -> Result> { + let request_id = self.set_request_id::(&mut request); + let mut websocket = self.websocket.lock().await; + websocket + .send(TungsteniteMessage::Text( + serde_json::to_string(&request).unwrap(), + )) + .await + .unwrap(); + let mut websocket_base = self.websocket_base.lock().await; + let message = websocket_base.request_impl(request_id.to_string()).await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } + + // async fn get_common_fields(&self) -> Result> { + // let server_state = self + // .request::(requests::ServerState::new(None)) + // .await?; + // let state = server_state.result.state; + // let common_fields = CommonFields { + // network_id: state.network_id, + // build_version: Some(state.build_version), + // }; + + // Ok(common_fields) + // } +} From a676991394e4f746d8d0c94043c9b5e542547b77 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:12:30 +0000 Subject: [PATCH 041/113] add codec for std embedded websocket --- src/asynch/clients/websocket/codec.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/asynch/clients/websocket/codec.rs diff --git a/src/asynch/clients/websocket/codec.rs b/src/asynch/clients/websocket/codec.rs new file mode 100644 index 00000000..95e9b09b --- /dev/null +++ b/src/asynch/clients/websocket/codec.rs @@ -0,0 +1,30 @@ +use alloc::{io, vec::Vec}; +use bytes::{BufMut, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +pub struct Codec; + +impl Decoder for Codec { + type Item = Vec; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result>, io::Error> { + if !src.is_empty() { + let len = src.len(); + let data = src.split_to(len).to_vec(); + Ok(Some(data)) + } else { + Ok(None) + } + } +} + +impl Encoder<&[u8]> for Codec { + type Error = io::Error; + + fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { + buf.reserve(data.len()); + buf.put(data); + Ok(()) + } +} From 16252c82ec524d8e4e40298bcc2ef99bc29972af Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:13:47 +0000 Subject: [PATCH 042/113] move websocket exceptions --- .../clients/{ => websocket}/exceptions.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) rename src/asynch/clients/{ => websocket}/exceptions.rs (76%) diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs similarity index 76% rename from src/asynch/clients/exceptions.rs rename to src/asynch/clients/websocket/exceptions.rs index 220084cb..ac587c52 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -1,6 +1,8 @@ use core::fmt::Debug; use core::str::Utf8Error; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; @@ -18,13 +20,17 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), + #[error("Unexpected result type")] + UnexpectedMessageType, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[error("Embedded I/O error: {0:?}")] + EmbeddedIoError(ErrorKind), } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -42,5 +48,15 @@ impl From> for XRPLWebsocketException { } } +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl EmbeddedIoError for XRPLWebsocketException { + fn kind(&self) -> ErrorKind { + match self { + XRPLWebsocketException::EmbeddedIoError(e) => e.kind(), + _ => ErrorKind::Other, + } + } +} + #[cfg(feature = "std")] impl alloc::error::Error for XRPLWebsocketException {} From ff9342b4ccd6ffb0cca3d4b2a497a3e29c95b432 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:14:08 +0000 Subject: [PATCH 043/113] add WebsocketBase --- .../clients/websocket/websocket_base.rs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/asynch/clients/websocket/websocket_base.rs diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs new file mode 100644 index 00000000..6f01ffcb --- /dev/null +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -0,0 +1,81 @@ +use alloc::string::{String, ToString}; +use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; +use futures::channel::oneshot::{self, Receiver, Sender}; +use hashbrown::HashMap; + +const _MAX_CHANNEL_MSG_CNT: usize = 10; + +/// A struct that handles futures of websocket messages. +pub struct WebsocketBase +where + M: RawMutex, +{ + /// The messages the user requests, which means he is waiting for a specific `id`. + pending_requests: HashMap>, + request_senders: HashMap>, + /// The messages the user waits for when sending and receiving normally. + messages: Channel, +} + +impl WebsocketBase +where + M: RawMutex, +{ + pub fn new() -> Self { + Self { + pending_requests: HashMap::new(), + request_senders: HashMap::new(), + messages: Channel::new(), + } + } +} + +pub(crate) trait MessageHandler { + /// Setup an empty future for a request. + async fn setup_request_future(&mut self, id: String); + async fn handle_message(&mut self, message: String); + async fn pop_message(&mut self) -> String; + async fn request_impl(&mut self, id: String) -> String; +} + +impl MessageHandler for WebsocketBase +where + M: RawMutex, +{ + async fn setup_request_future(&mut self, id: String) { + let (sender, receiver) = oneshot::channel::(); + self.pending_requests.insert(id.clone(), receiver); + self.request_senders.insert(id, sender); + } + + async fn handle_message(&mut self, message: String) { + let message_value = serde_json::to_value(&message).unwrap(); + let id = match message_value.get("id") { + Some(id) => { + let id = id.as_str().unwrap().to_string(); + if id == String::new() { + todo!("`id` must not be an empty string") + } + id + } + None => String::new(), + }; + if let Some(_receiver) = self.pending_requests.get(&id) { + let sender = self.request_senders.remove(&id).unwrap(); + sender.send(message).unwrap(); + } else { + self.messages.send(message).await; + } + } + + async fn pop_message(&mut self) -> String { + self.messages.receive().await + } + + async fn request_impl(&mut self, id: String) -> String { + self.setup_request_future(id.clone()).await; + let fut = self.pending_requests.remove(&id).unwrap(); + let message = fut.await.unwrap(); + serde_json::from_str(&message).unwrap() + } +} From 289fc4c70af4a7b55d19f6feb0368bd9a9b00b69 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:14:38 +0000 Subject: [PATCH 044/113] add Client and AsyncClient traits --- src/asynch/clients/async_client.rs | 18 +++++++++++++++ src/asynch/clients/client.rs | 36 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/asynch/clients/async_client.rs create mode 100644 src/asynch/clients/client.rs diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs new file mode 100644 index 00000000..0b01e8c5 --- /dev/null +++ b/src/asynch/clients/async_client.rs @@ -0,0 +1,18 @@ +use super::client::Client; +use crate::models::{requests::Request, results::XRPLResponse}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +pub trait AsyncClient: Client { + async fn request< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: Req, + ) -> Result> { + self.request_impl(request).await + } +} + +impl AsyncClient for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs new file mode 100644 index 00000000..c1ce3c24 --- /dev/null +++ b/src/asynch/clients/client.rs @@ -0,0 +1,36 @@ +use crate::{ + models::{requests::Request, results::XRPLResponse}, + utils::get_random_id, +}; +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +pub(crate) trait Client { + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: Req, + ) -> Result>; + fn set_request_id< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: &mut Req, + ) -> Cow<'_, str> { + let common_fields = request.get_common_fields(); + let request_id: Cow<'_, str> = match common_fields.id.clone() { + Some(id) => id, + None => { + #[cfg(feature = "std")] + let mut rng = rand::thread_rng(); + Cow::Owned(get_random_id(&mut rng)) + } + }; + request.get_common_fields_mut().id = Some(request_id.clone()); + request_id + } +} From d631691d1cf63d6e7e65ee2a7bae54ed2e5ed606 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:15:33 +0000 Subject: [PATCH 045/113] add XRPLWebsocketIO as standardized trait --- src/asynch/clients/websocket/mod.rs | 124 ++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/asynch/clients/websocket/mod.rs diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs new file mode 100644 index 00000000..6ef8fe55 --- /dev/null +++ b/src/asynch/clients/websocket/mod.rs @@ -0,0 +1,124 @@ +use core::fmt::{Debug, Display}; + +use crate::{models::results::XRPLResponse, Err}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use alloc::string::String; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use alloc::string::ToString; +use anyhow::Result; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use futures::{Sink, SinkExt, Stream, StreamExt}; +use serde::{Deserialize, Serialize}; + +mod websocket_base; +use websocket_base::MessageHandler; + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub mod codec; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +mod embedded_websocket; +pub mod exceptions; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +mod tungstenite; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub use embedded_websocket::AsyncWebsocketClient; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite::AsyncWebsocketClient; + +pub struct WebsocketOpen; +pub struct WebsocketClosed; + +pub type MultiExecutorMutex = CriticalSectionRawMutex; +pub type SingleExecutorMutex = NoopRawMutex; + +#[allow(async_fn_in_trait)] +pub trait XRPLWebsocketIO { + async fn xrpl_send(&mut self, message: Req) -> Result<()>; + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result>; +} + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl XRPLWebsocketIO for T +where + ::Error: Display, +{ + async fn xrpl_send(&mut self, message: Req) -> Result<()> { + let message = serde_json::to_string(&message).unwrap(); + let message_buffer = message.as_bytes(); + match self.write(message_buffer).await { + Ok(_) => Ok(()), + Err(e) => Err!(e), + } + } + + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result> { + let mut buffer = [0; 1024]; + loop { + match self.read(&mut buffer).await { + Ok(u_size) => { + // If the buffer is empty, continue to the next iteration. + if u_size == 0 { + continue; + } + let response_str = core::str::from_utf8(&buffer[..u_size]).unwrap(); + self.handle_message(response_str.to_string()).await; + let message = self.pop_message().await; + dbg!(&message); + let response = serde_json::from_str(&message).unwrap(); + return Ok(response); + } + Err(error) => return Err!(error), + } + } + } +} + +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +impl XRPLWebsocketIO for T +where + T: Stream> + Sink + MessageHandler + Unpin, + >::Error: Debug + Display, +{ + async fn xrpl_send(&mut self, message: Req) -> Result<()> { + let message = serde_json::to_string(&message).unwrap(); + match self.send(message).await { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } + + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result> { + match self.next().await { + Some(Ok(item)) => { + let xrpl_response = serde_json::from_str(&item).unwrap(); + self.handle_message(xrpl_response).await; + let message = self.pop_message().await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } + Some(Err(error)) => Err!(error), + None => { + todo!() + } + } + } +} From df60e5a952ca9d7087afdfb60168f629c4cf8245 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:16:19 +0000 Subject: [PATCH 046/113] adjust clients mod --- src/asynch/clients/mod.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 54eb0495..746be0ce 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,14 +1,5 @@ -pub mod exceptions; +mod async_client; +mod client; +mod websocket; -pub struct WebsocketOpen; -pub struct WebsocketClosed; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -mod embedded_websocket; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tungstenite; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub use embedded_websocket::*; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tungstenite::*; +pub use websocket::*; From a731d4ac016f854e39cab36641ea8a8d2d4c315f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:17:03 +0000 Subject: [PATCH 047/113] add XRPLResponse --- src/models/mod.rs | 2 ++ src/models/results/mod.rs | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/models/results/mod.rs diff --git a/src/models/mod.rs b/src/models/mod.rs index 7cd5e1dc..6d9c5ebd 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -14,6 +14,8 @@ pub mod model; #[cfg(feature = "requests")] #[allow(clippy::too_many_arguments)] pub mod requests; +#[cfg(feature = "results")] +pub mod results; #[cfg(feature = "transactions")] #[allow(clippy::too_many_arguments)] pub mod transactions; diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs new file mode 100644 index 00000000..d7bc4d20 --- /dev/null +++ b/src/models/results/mod.rs @@ -0,0 +1,46 @@ +use alloc::{borrow::Cow, vec::Vec}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ResponseStatus { + Success, + Error, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum ResponseType { + Response, + LedgerClosed, + Transaction, +} + +/// TODO: Because everything is optional, the deserializing always succeds without returning an error on false data. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLResponse<'a, Res, Req> { + pub id: Option>, + pub error: Option>, + pub error_code: Option, + pub error_message: Option>, + pub forwarded: Option, + pub request: Option, + pub result: Option, + pub status: Option, + pub r#type: Option, + pub warning: Option>, + pub warnings: Option>>, +} + +impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { + pub fn is_success(&self) -> bool { + self.status == Some(ResponseStatus::Success) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLWarning<'a> { + pub id: Cow<'a, str>, + pub message: Cow<'a, str>, + pub forwarded: Option, +} From e838975150468d8e4e9845404b11dd23e015e15f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:18:50 +0000 Subject: [PATCH 048/113] revise the Request trait to have methods to get a resut models common fields --- src/models/requests/account_channels.rs | 12 +++++++++++- src/models/requests/account_currencies.rs | 10 +++++++--- src/models/requests/account_info.rs | 10 +++++++--- src/models/requests/account_lines.rs | 10 +++++++--- src/models/requests/account_nfts.rs | 10 +++++++--- src/models/requests/account_objects.rs | 10 +++++++--- src/models/requests/account_offers.rs | 10 +++++++--- src/models/requests/account_tx.rs | 10 +++++++--- src/models/requests/book_offers.rs | 10 +++++++--- src/models/requests/channel_authorize.rs | 10 +++++++--- src/models/requests/channel_verify.rs | 10 +++++++--- src/models/requests/deposit_authorize.rs | 10 +++++++--- src/models/requests/fee.rs | 10 +++++++--- src/models/requests/gateway_balances.rs | 10 +++++++--- src/models/requests/ledger.rs | 10 +++++++--- src/models/requests/ledger_closed.rs | 10 +++++++--- src/models/requests/ledger_current.rs | 10 +++++++--- src/models/requests/ledger_data.rs | 10 +++++++--- src/models/requests/ledger_entry.rs | 10 +++++++--- src/models/requests/manifest.rs | 10 +++++++--- src/models/requests/mod.rs | 11 +++-------- src/models/requests/nft_buy_offers.rs | 10 +++++++--- src/models/requests/nft_sell_offers.rs | 10 +++++++--- src/models/requests/no_ripple_check.rs | 10 +++++++--- src/models/requests/path_find.rs | 10 +++++++--- src/models/requests/ping.rs | 10 +++++++--- src/models/requests/random.rs | 10 +++++++--- src/models/requests/ripple_path_find.rs | 10 +++++++--- src/models/requests/server_info.rs | 10 +++++++--- src/models/requests/server_state.rs | 10 +++++++--- src/models/requests/submit.rs | 10 +++++++--- src/models/requests/submit_multisigned.rs | 10 +++++++--- src/models/requests/subscribe.rs | 10 +++++++--- src/models/requests/transaction_entry.rs | 10 +++++++--- src/models/requests/tx.rs | 10 +++++++--- src/models/requests/unsubscribe.rs | 10 +++++++--- 36 files changed, 252 insertions(+), 111 deletions(-) diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index 5965eb52..df0753ed 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -4,7 +4,7 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; -use super::CommonFields; +use super::{CommonFields, Request}; /// This request returns information about an account's Payment /// Channels. This includes only channels where the specified @@ -85,3 +85,13 @@ impl<'a> AccountChannels<'a> { } } } + +impl<'a> Request<'a> for AccountChannels<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields + } +} diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index c6307543..4ff2e373 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -37,9 +37,13 @@ pub struct AccountCurrencies<'a> { impl<'a> Model for AccountCurrencies<'a> {} -impl<'a> Request for AccountCurrencies<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountCurrencies<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 1db06aa4..84d8f42d 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -44,9 +44,13 @@ pub struct AccountInfo<'a> { impl<'a> Model for AccountInfo<'a> {} -impl<'a> Request for AccountInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index 432f74d4..3571cf2a 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -38,9 +38,13 @@ pub struct AccountLines<'a> { impl<'a> Model for AccountLines<'a> {} -impl<'a> Request for AccountLines<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountLines<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index 2447f3af..a9e79047 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -29,9 +29,13 @@ pub struct AccountNfts<'a> { impl<'a> Model for AccountNfts<'a> {} -impl<'a> Request for AccountNfts<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountNfts<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index c51bcc4f..d78c4381 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -63,9 +63,13 @@ pub struct AccountObjects<'a> { impl<'a> Model for AccountObjects<'a> {} -impl<'a> Request for AccountObjects<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountObjects<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 5e2cfb02..647d3f3e 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -40,9 +40,13 @@ pub struct AccountOffers<'a> { impl<'a> Model for AccountOffers<'a> {} -impl<'a> Request for AccountOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 48da5afd..1f16e80a 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -53,9 +53,13 @@ pub struct AccountTx<'a> { impl<'a> Model for AccountTx<'a> {} -impl<'a> Request for AccountTx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountTx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index 57887d38..ca27a3d5 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -46,9 +46,13 @@ pub struct BookOffers<'a> { impl<'a> Model for BookOffers<'a> {} -impl<'a> Request for BookOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for BookOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 835cdd7e..466fa425 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -80,9 +80,13 @@ impl<'a> Model for ChannelAuthorize<'a> { } } -impl<'a> Request for ChannelAuthorize<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelAuthorize<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 949b6ad1..59e814a9 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -30,9 +30,13 @@ pub struct ChannelVerify<'a> { impl<'a> Model for ChannelVerify<'a> {} -impl<'a> Request for ChannelVerify<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelVerify<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 5204bc69..2c58e473 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -30,9 +30,13 @@ pub struct DepositAuthorized<'a> { impl<'a> Model for DepositAuthorized<'a> {} -impl<'a> Request for DepositAuthorized<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for DepositAuthorized<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 2ab0ee8a..05052d96 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -23,9 +23,13 @@ pub struct Fee<'a> { impl<'a> Model for Fee<'a> {} -impl<'a> Request for Fee<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Fee<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index 58dc61e7..e5f38386 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -36,9 +36,13 @@ pub struct GatewayBalances<'a> { impl<'a> Model for GatewayBalances<'a> {} -impl<'a> Request for GatewayBalances<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for GatewayBalances<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index 7ba5faec..d567cd41 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -58,9 +58,13 @@ pub struct Ledger<'a> { impl<'a> Model for Ledger<'a> {} -impl<'a> Request for Ledger<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ledger<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index c65f486c..7b601ea6 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -22,9 +22,13 @@ pub struct LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} -impl<'a> Request for LedgerClosed<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerClosed<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 9b408ee6..aec482f6 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -22,9 +22,13 @@ pub struct LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} -impl<'a> Request for LedgerCurrent<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerCurrent<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index c71e012c..c30bb753 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -36,9 +36,13 @@ pub struct LedgerData<'a> { impl<'a> Model for LedgerData<'a> {} -impl<'a> Request for LedgerData<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerData<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 1dbc4c1d..0022437c 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -159,9 +159,13 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } } -impl<'a> Request for LedgerEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index d2615195..e9f38d94 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -27,9 +27,13 @@ pub struct Manifest<'a> { impl<'a> Model for Manifest<'a> {} -impl<'a> Request for Manifest<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Manifest<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 860b7504..3d81058f 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -148,14 +148,9 @@ pub struct CommonFields<'a> { pub id: Option>, } -impl Request for CommonFields<'_> { - fn get_command(&self) -> RequestMethod { - self.command.clone() - } -} - /// The base trait for all request models. /// Used to identify the model as a request. -pub trait Request { - fn get_command(&self) -> RequestMethod; +pub trait Request<'a> { + fn get_common_fields(&self) -> &CommonFields<'a>; + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; } diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index 3e9ce276..d8a4c4c5 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -31,9 +31,13 @@ pub struct NftBuyOffers<'a> { impl<'a> Model for NftBuyOffers<'a> {} -impl<'a> Request for NftBuyOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftBuyOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 7404d59c..fa4dd6f9 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -19,9 +19,13 @@ pub struct NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} -impl<'a> Request for NftSellOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftSellOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index f2467efa..21bffb7f 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -58,9 +58,13 @@ pub struct NoRippleCheck<'a> { impl<'a> Model for NoRippleCheck<'a> {} -impl<'a> Request for NoRippleCheck<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NoRippleCheck<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index e2ab7074..55b34865 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -90,9 +90,13 @@ pub struct PathFind<'a> { impl<'a> Model for PathFind<'a> {} -impl<'a> Request for PathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for PathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 079b81be..5c70e863 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -21,9 +21,13 @@ pub struct Ping<'a> { impl<'a> Model for Ping<'a> {} -impl<'a> Request for Ping<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ping<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 8bc0029f..19c7e986 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -22,9 +22,13 @@ pub struct Random<'a> { impl<'a> Model for Random<'a> {} -impl<'a> Request for Random<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Random<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index ac923963..4f7f5cfd 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -62,9 +62,13 @@ pub struct RipplePathFind<'a> { impl<'a> Model for RipplePathFind<'a> {} -impl<'a> Request for RipplePathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for RipplePathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 378fc0f0..7044ee5d 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -22,9 +22,13 @@ pub struct ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} -impl<'a> Request for ServerInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 4a0b7306..85c910c5 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -27,9 +27,13 @@ pub struct ServerState<'a> { impl<'a> Model for ServerState<'a> {} -impl<'a> Request for ServerState<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerState<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index 132a9910..e68dec41 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -49,9 +49,13 @@ pub struct Submit<'a> { impl<'a> Model for Submit<'a> {} -impl<'a> Request for Submit<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Submit<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 82fdc1cc..913539c9 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -30,9 +30,13 @@ pub struct SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} -impl<'a> Request for SubmitMultisigned<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for SubmitMultisigned<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index 0487ca22..fb501f87 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -77,9 +77,13 @@ pub struct Subscribe<'a> { impl<'a> Model for Subscribe<'a> {} -impl<'a> Request for Subscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Subscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index 24ad02cb..8f3c9885 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -31,9 +31,13 @@ pub struct TransactionEntry<'a> { impl<'a> Model for TransactionEntry<'a> {} -impl<'a> Request for TransactionEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for TransactionEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 0dd547ee..29b88373 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -34,9 +34,13 @@ pub struct Tx<'a> { impl<'a> Model for Tx<'a> {} -impl<'a> Request for Tx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Tx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index 90f123d0..b056b688 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -62,9 +62,13 @@ pub struct Unsubscribe<'a> { impl<'a> Model for Unsubscribe<'a> {} -impl<'a> Request for Unsubscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Unsubscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } From 7e8984f3f2d29d30e8a3d6515cba0b277359004c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:19:08 +0000 Subject: [PATCH 049/113] cargo fmt --- src/models/transactions/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 57ad9163..647dc3b5 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -54,10 +54,7 @@ pub use ticket_create::*; pub use trust_set::*; use crate::models::amount::XRPAmount; -use crate::{ - _serde::txn_flags, - serde_with_tag, -}; +use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; From 1fb4489d104236c52cbd1709e56bdc33c75ea07c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:19:40 +0000 Subject: [PATCH 050/113] add get_random_id utility function --- src/utils/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 41508f7c..9fff8a72 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,7 +8,10 @@ pub use self::time_conversion::*; pub use self::xrpl_conversion::*; use crate::constants::*; +use alloc::string::String; +use alloc::string::ToString; use alloc::vec::Vec; +use rand::Rng; use regex::Regex; /// Determine if the address string is a hex address. @@ -65,6 +68,12 @@ pub fn is_iso_hex(value: &str) -> bool { regex.is_match(value) } +/// Generate a random id. +pub fn get_random_id(rng: &mut T) -> String { + let id: u32 = rng.gen(); + id.to_string() +} + /// Converter to byte array with endianness. pub trait ToBytes { /// Return the byte array of self. From 7c64058415fff470c1e3bcd8416fcd73a3103fd2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:20:07 +0000 Subject: [PATCH 051/113] add xrpl testnet uri --- tests/common/constants.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/common/constants.rs b/tests/common/constants.rs index fb33485a..a2ba78a8 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,2 +1,4 @@ -pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror/"; -pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror/"; +pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; +pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; + +pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; From 08621098b37b0b31701e23926404467ee0ecc087 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:20:42 +0000 Subject: [PATCH 052/113] revise the tests to work with the changes --- tests/common/mod.rs | 46 ++++++++--------- tests/integration/clients/mod.rs | 84 +++++++++----------------------- 2 files changed, 46 insertions(+), 84 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1c406bcd..dc7e14be 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,27 +1,27 @@ -pub mod codec; - use anyhow::anyhow; use anyhow::Result; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use std::io; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio::net::TcpStream; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio_util::codec::Framed; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -use xrpl::asynch::clients::AsyncWebsocketClient; -use xrpl::asynch::clients::WebsocketOpen; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use xrpl::asynch::clients::{AsyncWebsocketClient, EmbeddedWebsocketOptions}; +use xrpl::asynch::clients::codec::Codec; +use xrpl::asynch::clients::AsyncWebsocketClient; +use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; mod constants; pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_echo() -> Result> { +pub async fn connect_to_wss_tungstinite_echo( +) -> Result> { match ECHO_WSS_SERVER.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { - assert!(websocket.is_open()); + // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), @@ -32,21 +32,23 @@ pub async fn connect_to_wss_tungstinite_echo() -> Result, - buffer: &mut [u8], -) -> Result> { - let rng = rand::thread_rng(); - let websocket_options = EmbeddedWebsocketOptions { - path: "/mirror", - host: "ws.vi-server.org", - origin: "http://ws.vi-server.org:80", - sub_protocols: None, - additional_headers: None, - }; - - match AsyncWebsocketClient::open(stream, buffer, rng, &websocket_options).await { + stream: Framed, +) -> Result< + AsyncWebsocketClient< + 4096, + Framed, + Vec, + io::Error, + rand_core::OsRng, + SingleExecutorMutex, + WebsocketOpen, + >, +> { + let rng = rand_core::OsRng; + let url = XRPL_TEST_NET.parse().unwrap(); + match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { - assert!(websocket.is_open()); + // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 9a76a87d..8dc535e5 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,12 +1,9 @@ -use anyhow::anyhow; use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_echo() -> Result<()> { use super::common::connect_to_wss_tungstinite_echo; - use futures_util::{SinkExt, TryStreamExt}; - use xrpl::asynch::clients::TungsteniteMessage; - use xrpl::models::requests::AccountInfo; + use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::AccountInfo}; let mut websocket = connect_to_wss_tungstinite_echo().await?; let account_info = AccountInfo::new( @@ -19,37 +16,29 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { None, ); - websocket.send(&account_info).await?; - while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - let account_info_echo = serde_json::from_str::(response.as_str()); - match account_info_echo { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - }; + websocket.xrpl_send(account_info).await.unwrap(); + loop { + let t = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + dbg!("{:?}", t); } - - Ok(()) } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; + use super::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; - use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::XRPLWebsocketIO; use xrpl::models::requests::AccountInfo; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await - .map_err(|_| anyhow!("Error connecting to websocket"))?; - let mut framed = Framed::new(tcp_stream, Codec::new()); - let mut buffer = [0u8; 4096]; - let mut websocket = - connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; + .unwrap(); + let mut framed = Framed::new(tcp_stream, Codec); + let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let account_info = AccountInfo::new( None, "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), @@ -59,42 +48,13 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { None, None, ); - websocket - .send(&mut framed, &mut buffer, false, &account_info) - .await?; + websocket.xrpl_send(account_info).await?; - let mut ping_counter = 0; - loop { - match websocket.try_next(&mut framed, &mut buffer).await? { - Some(message) => match message { - EmbeddedWebsocketReadMessageType::Ping(_) => { - ping_counter += 1; - if ping_counter > 1 { - return Err(anyhow!("Expected only one ping")); - } - } - EmbeddedWebsocketReadMessageType::Text(text) => { - match serde_json::from_str::(text) { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - } - } - EmbeddedWebsocketReadMessageType::Binary(_) => { - panic!("Expected text message found binary") - } - EmbeddedWebsocketReadMessageType::Pong(_) => { - panic!("Expected text message found pong") - } - EmbeddedWebsocketReadMessageType::Close(_) => { - panic!("Expected text message found close") - } - }, - None => return Err(anyhow!("No message received")), - } - } + let t = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + dbg!("{:?}", t); + panic!(); + Ok(()) } From c18f77a63f40f18f5188ddd321c8dc4258dc844a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 9 Jun 2024 09:43:22 +0000 Subject: [PATCH 053/113] error handling --- .../clients/websocket/embedded_websocket.rs | 62 +++++++++++-------- src/asynch/clients/websocket/exceptions.rs | 6 ++ src/asynch/clients/websocket/mod.rs | 40 +++++++----- src/asynch/clients/websocket/tungstenite.rs | 40 ++++++++---- .../clients/websocket/websocket_base.rs | 49 ++++++++++----- tests/common/constants.rs | 1 + tests/common/mod.rs | 4 +- tests/integration/clients/mod.rs | 20 +++--- 8 files changed, 138 insertions(+), 84 deletions(-) diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 83fef699..9bc43fcb 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -1,21 +1,20 @@ use core::{ fmt::{Debug, Display}, - iter::FromIterator, marker::PhantomData, ops::{Deref, DerefMut}, }; use alloc::{ - dbg, string::{String, ToString}, sync::Arc, }; +use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; use embedded_websocket::{ - framer_async::Framer, framer_async::ReadResult, Client, WebSocketClient, WebSocketOptions, - WebSocketSendMessageType, + framer_async::{Framer, FramerError, ReadResult}, + Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; use futures_core::Stream; use futures_sink::Sink; @@ -29,7 +28,8 @@ use crate::{ client::Client as ClientTrait, websocket::websocket_base::{MessageHandler, WebsocketBase}, }, - models::requests::Request, + models::{requests::Request, results::XRPLResponse}, + Err, }; use super::exceptions::XRPLWebsocketException; @@ -66,10 +66,7 @@ where rng: Rng, tcp: Tcp, url: Url, - ) -> Result< - AsyncWebsocketClient, - XRPLWebsocketException, - > { + ) -> Result> { // replace the scheme with http or https let scheme = match url.scheme() { "wss" => "https", @@ -86,7 +83,10 @@ where } .to_string(); let path = url.path(); - let host = url.host_str().unwrap(); + let host = match url.host_str() { + Some(host) => host, + None => return Err!(XRPLWebsocketException::::Disconnected), + }; let origin = scheme.to_string() + "://" + host + ":" + &port + path; let websocket_options = WebSocketOptions { path, @@ -95,14 +95,10 @@ where sub_protocols: None, additional_headers: None, }; - // dbg!(&websocket_options.path); - // dbg!(&websocket_options.host); - // dbg!(&websocket_options.origin); - // panic!(); let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); let tcp = Arc::new(Mutex::new(tcp)); let mut buffer = [0; BUF]; - websocket + if let Err(error) = websocket .lock() .await .connect( @@ -111,12 +107,19 @@ where &websocket_options, ) .await - .unwrap(); + { + match error { + // FramerError::WebSocket(embedded_websocket::Error::HttpResponseCodeInvalid( + // Some(308), + // )) => (), + error => return Err!(XRPLWebsocketException::from(error)), + } + } Ok(AsyncWebsocketClient { tcp, websocket, - tx_buffer: [0; BUF], + tx_buffer: buffer, websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), status: PhantomData::, }) @@ -210,9 +213,9 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) { + async fn handle_message(&mut self, message: String) -> Result<()> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await; + websocket_base.handle_message(message).await } async fn pop_message(&mut self) -> String { @@ -220,7 +223,7 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { let mut websocket_base = self.websocket_base.lock().await; websocket_base.request_impl(id).await } @@ -240,14 +243,21 @@ where >( &self, mut request: Req, - ) -> anyhow::Result> { + ) -> Result> { let request_id = self.set_request_id::(&mut request); - self.do_write(serde_json::to_string(&request).unwrap().as_bytes()) - .await - .unwrap(); + let request_string = match serde_json::to_string(&request) { + Ok(request_string) => request_string, + Err(error) => return Err!(error), + }; + if let Err(error) = self.do_write(request_string.as_bytes()).await { + return Err!(error); + } let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await; - let response = serde_json::from_str(&message).unwrap(); + let message = websocket_base.request_impl(request_id.to_string()).await?; + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; Ok(response) } } diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index ac587c52..47102697 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -31,6 +31,12 @@ pub enum XRPLWebsocketException { #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Embedded I/O error: {0:?}")] EmbeddedIoError(ErrorKind), + #[error("Missing request channel sender.")] + MissingRequestSender, + #[error("Missing request channel receiver.")] + MissingRequestReceiver, + #[error("Invalid message.")] + InvalidMessage, } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 6ef8fe55..a4aebbd5 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,6 +9,7 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -52,7 +53,10 @@ where ::Error: Display, { async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = serde_json::to_string(&message).unwrap(); + let message = match serde_json::to_string(&message) { + Ok(message) => message, + Err(error) => return Err!(error), + }; let message_buffer = message.as_bytes(); match self.write(message_buffer).await { Ok(_) => Ok(()), @@ -74,12 +78,18 @@ where if u_size == 0 { continue; } - let response_str = core::str::from_utf8(&buffer[..u_size]).unwrap(); - self.handle_message(response_str.to_string()).await; + let response_str = match core::str::from_utf8(&buffer[..u_size]) { + Ok(response_str) => response_str, + Err(error) => { + return Err!(XRPLWebsocketException::::Utf8(error)) + } + }; + self.handle_message(response_str.to_string()).await?; let message = self.pop_message().await; - dbg!(&message); - let response = serde_json::from_str(&message).unwrap(); - return Ok(response); + match serde_json::from_str(&message) { + Ok(response) => return Ok(response), + Err(error) => return Err!(error), + } } Err(error) => return Err!(error), } @@ -94,7 +104,10 @@ where >::Error: Debug + Display, { async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = serde_json::to_string(&message).unwrap(); + let message = match serde_json::to_string(&message) { + Ok(message) => message, + Err(error) => return Err!(error), + }; match self.send(message).await { Ok(()) => Ok(()), Err(error) => Err!(error), @@ -109,16 +122,15 @@ where ) -> Result> { match self.next().await { Some(Ok(item)) => { - let xrpl_response = serde_json::from_str(&item).unwrap(); - self.handle_message(xrpl_response).await; + self.handle_message(item).await?; let message = self.pop_message().await; - let response = serde_json::from_str(&message).unwrap(); - Ok(response) + match serde_json::from_str(&message) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } } Some(Err(error)) => Err!(error), - None => { - todo!() - } + None => Err!(XRPLWebsocketException::::Disconnected), } } } diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index ee4660fc..cef65c97 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -113,7 +113,16 @@ where Ok(message) => match message { TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), TungsteniteMessage::Binary(response) => { - let response_string = String::from_utf8(response).unwrap(); + let response_string = match String::from_utf8(response) { + Ok(string) => string, + Err(error) => { + return Poll::Ready(Some(Err!(XRPLWebsocketException::< + anyhow::Error, + >::Utf8( + error.utf8_error() + )))); + } + }; Poll::Ready(Some(Ok(response_string))) } _ => Poll::Ready(Some(Err!( @@ -157,9 +166,9 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) { + async fn handle_message(&mut self, message: String) -> Result<()> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await; + websocket_base.handle_message(message).await } async fn pop_message(&mut self) -> String { @@ -167,7 +176,7 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { let mut websocket_base = self.websocket_base.lock().await; websocket_base.request_impl(id).await } @@ -186,16 +195,23 @@ where ) -> Result> { let request_id = self.set_request_id::(&mut request); let mut websocket = self.websocket.lock().await; - websocket - .send(TungsteniteMessage::Text( - serde_json::to_string(&request).unwrap(), - )) + let request_string = match serde_json::to_string(&request) { + Ok(request_string) => request_string, + Err(error) => return Err!(error), + }; + match websocket + .send(TungsteniteMessage::Text(request_string)) .await - .unwrap(); + { + Ok(()) => (), + Err(error) => return Err!(error), + } let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await; - let response = serde_json::from_str(&message).unwrap(); - Ok(response) + let message = websocket_base.request_impl(request_id.to_string()).await?; + match serde_json::from_str(&message) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } } // async fn get_common_fields(&self) -> Result> { diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index 6f01ffcb..a8be7076 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -1,8 +1,11 @@ use alloc::string::{String, ToString}; +use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; +use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; + const _MAX_CHANNEL_MSG_CNT: usize = 10; /// A struct that handles futures of websocket messages. @@ -33,9 +36,9 @@ where pub(crate) trait MessageHandler { /// Setup an empty future for a request. async fn setup_request_future(&mut self, id: String); - async fn handle_message(&mut self, message: String); + async fn handle_message(&mut self, message: String) -> Result<()>; async fn pop_message(&mut self) -> String; - async fn request_impl(&mut self, id: String) -> String; + async fn request_impl(&mut self, id: String) -> Result; } impl MessageHandler for WebsocketBase @@ -48,34 +51,46 @@ where self.request_senders.insert(id, sender); } - async fn handle_message(&mut self, message: String) { - let message_value = serde_json::to_value(&message).unwrap(); + async fn handle_message(&mut self, message: String) -> Result<()> { + let message_value = match serde_json::to_value(&message) { + Ok(value) => value, + Err(error) => return Err!(error), + }; let id = match message_value.get("id") { - Some(id) => { - let id = id.as_str().unwrap().to_string(); - if id == String::new() { - todo!("`id` must not be an empty string") - } - id - } + Some(id) => match id.as_str() { + Some(id) => id.to_string(), + None => return Err!(XRPLWebsocketException::::InvalidMessage), + }, None => String::new(), }; if let Some(_receiver) = self.pending_requests.get(&id) { - let sender = self.request_senders.remove(&id).unwrap(); - sender.send(message).unwrap(); + let sender = match self.request_senders.remove(&id) { + Some(sender) => sender, + None => return Err!(XRPLWebsocketException::::MissingRequestSender), + }; + match sender.send(message) { + Ok(()) => (), + Err(error) => return Err!(error), + }; } else { self.messages.send(message).await; } + Ok(()) } async fn pop_message(&mut self) -> String { self.messages.receive().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { self.setup_request_future(id.clone()).await; - let fut = self.pending_requests.remove(&id).unwrap(); - let message = fut.await.unwrap(); - serde_json::from_str(&message).unwrap() + let fut = match self.pending_requests.remove(&id) { + Some(fut) => fut, + None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), + }; + match fut.await { + Ok(message) => Ok(message), + Err(error) => return Err!(error), + } } } diff --git a/tests/common/constants.rs b/tests/common/constants.rs index a2ba78a8..97f48f57 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,4 +1,5 @@ pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; +// pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index dc7e14be..d79995fb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,7 +18,7 @@ pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn connect_to_wss_tungstinite_echo( ) -> Result> { - match ECHO_WSS_SERVER.parse() { + match XRPL_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { // assert!(websocket.is_open()); @@ -45,7 +45,7 @@ pub async fn connect_to_ws_embedded_websocket_tokio_echo( >, > { let rng = rand_core::OsRng; - let url = XRPL_TEST_NET.parse().unwrap(); + let url = ECHO_WS_SERVER.parse().unwrap(); match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { // assert!(websocket.is_open()); diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 8dc535e5..3e0dda43 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -17,13 +17,11 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { ); websocket.xrpl_send(account_info).await.unwrap(); - loop { - let t = websocket - .xrpl_receive::, AccountInfo<'_>>() - .await - .unwrap(); - dbg!("{:?}", t); - } + let _ = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + Ok(()) } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -49,12 +47,8 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { None, ); websocket.xrpl_send(account_info).await?; - - let t = websocket + let _ = websocket .xrpl_receive::, AccountInfo<'_>>() - .await - .unwrap(); - dbg!("{:?}", t); - panic!(); + .await?; Ok(()) } From c8a50c114c65ac2f0508b2a052aa3ff88471fccb Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 9 Jun 2024 11:37:43 +0000 Subject: [PATCH 054/113] improve testing --- .../clients/websocket/embedded_websocket.rs | 4 +- src/asynch/clients/websocket/exceptions.rs | 2 +- src/models/results/fee.rs | 16 +++++ src/models/results/mod.rs | 65 +++++++++++++++++-- tests/common/constants.rs | 3 +- tests/common/mod.rs | 2 +- tests/integration/clients/mod.rs | 41 +++++------- 7 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 src/models/results/fee.rs diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 9bc43fcb..53beb6a6 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -5,6 +5,7 @@ use core::{ }; use alloc::{ + dbg, panic, string::{String, ToString}, sync::Arc, }; @@ -193,7 +194,8 @@ where Some(Ok(ReadResult::Text(t))) => Ok(t.len()), Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), Some(Ok(ReadResult::Ping(_))) => Ok(0), - Some(Ok(_)) => Err(XRPLWebsocketException::::UnexpectedMessageType), + Some(Ok(ReadResult::Pong(_))) => Ok(0), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), None => Err(XRPLWebsocketException::::Disconnected), } diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 47102697..664ec36e 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -26,7 +26,7 @@ pub enum XRPLWebsocketException { Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), - #[error("Unexpected result type")] + #[error("Unexpected message type")] UnexpectedMessageType, #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Embedded I/O error: {0:?}")] diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs new file mode 100644 index 00000000..59284566 --- /dev/null +++ b/src/models/results/fee.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::amount::XRPAmount; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Fee<'a> { + pub drops: Drops<'a>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Drops<'a> { + pub base_fee: XRPAmount<'a>, + pub median_fee: XRPAmount<'a>, + pub minimum_fee: XRPAmount<'a>, + pub open_ledger_fee: XRPAmount<'a>, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index d7bc4d20..5c74b564 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,5 +1,8 @@ -use alloc::{borrow::Cow, vec::Vec}; -use serde::{Deserialize, Serialize}; +use alloc::{borrow::Cow, string::ToString, vec::Vec}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +mod fee; +pub use fee::{Fee as FeeResult, *}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -16,8 +19,7 @@ pub enum ResponseType { Transaction, } -/// TODO: Because everything is optional, the deserializing always succeds without returning an error on false data. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct XRPLResponse<'a, Res, Req> { pub id: Option>, pub error: Option>, @@ -32,6 +34,61 @@ pub struct XRPLResponse<'a, Res, Req> { pub warnings: Option>>, } +impl<'a, 'de, Res, Req> Deserialize<'de> for XRPLResponse<'a, Res, Req> +where + Res: DeserializeOwned, + Req: DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // TODO: add validation for fields that can not coexist in the same response + let mut map = serde_json::Map::deserialize(deserializer)?; + if map.is_empty() { + return Err(serde::de::Error::custom("Empty response")); + } + Ok(XRPLResponse { + id: map.remove("id").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error: map.remove("error").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error_code: map + .remove("error_code") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + error_message: map.remove("error_message").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), + request: map + .remove("request") + .map(|v| serde_json::from_value(v).unwrap()), + result: map + .remove("result") + .map(|v| serde_json::from_value(v).unwrap()), + status: map + .remove("status") + .map(|v| serde_json::from_value(v).unwrap()), + r#type: map + .remove("type") + .map(|v| serde_json::from_value(v).unwrap()), + warning: map.remove("warning").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + warnings: map + .remove("warnings") + .and_then(|v| serde_json::from_value(v).ok()), + }) + } +} + impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) diff --git a/tests/common/constants.rs b/tests/common/constants.rs index 97f48f57..732059d2 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -2,4 +2,5 @@ pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; // pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; -pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; +pub const XRPL_WSS_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; +pub const XRPL_WS_TEST_NET: &'static str = "wss://s.altnet.rippletest.net:51233/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d79995fb..1c13ded5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,7 +18,7 @@ pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn connect_to_wss_tungstinite_echo( ) -> Result> { - match XRPL_TEST_NET.parse() { + match XRPL_WSS_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { // assert!(websocket.is_open()); diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 3e0dda43..390ac84b 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -3,24 +3,19 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_echo() -> Result<()> { use super::common::connect_to_wss_tungstinite_echo; - use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::AccountInfo}; + use xrpl::{ + asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, + }; let mut websocket = connect_to_wss_tungstinite_echo().await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); + let fee = Fee::new(None); - websocket.xrpl_send(account_info).await.unwrap(); - let _ = websocket - .xrpl_receive::, AccountInfo<'_>>() + websocket.xrpl_send(fee).await.unwrap(); + let message = websocket + .xrpl_receive::, Fee<'_>>() .await .unwrap(); + assert!(message.result.is_some()); Ok(()) } @@ -30,25 +25,19 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::XRPLWebsocketIO; - use xrpl::models::requests::AccountInfo; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); let mut framed = Framed::new(tcp_stream, Codec); let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); - websocket.xrpl_send(account_info).await?; + let fee = Fee::new(None); + websocket.xrpl_send(fee).await?; let _ = websocket - .xrpl_receive::, AccountInfo<'_>>() - .await?; + .xrpl_receive::, Fee<'_>>() + .await + .unwrap(); Ok(()) } From e607e4c7b2cb4370b953110020b80aaefbada515 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:02:09 +0000 Subject: [PATCH 055/113] implement websocket client request --- src/asynch/clients/async_client.rs | 9 ++- src/asynch/clients/client.rs | 13 ++-- src/asynch/clients/mod.rs | 1 + src/asynch/clients/websocket/mod.rs | 7 +- src/asynch/clients/websocket/tungstenite.rs | 78 ++++++++++++++----- .../clients/websocket/websocket_base.rs | 32 ++++++-- tests/common/mod.rs | 2 +- tests/integration/clients/mod.rs | 19 ++++- tests/integration_tests.rs | 12 ++- 9 files changed, 124 insertions(+), 49 deletions(-) diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 0b01e8c5..57a776ab 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -3,16 +3,17 @@ use crate::models::{requests::Request, results::XRPLResponse}; use anyhow::Result; use serde::{Deserialize, Serialize}; -pub trait AsyncClient: Client { +#[allow(async_fn_in_trait)] +pub trait AsyncClient<'a>: Client<'a> { async fn request< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: Req, ) -> Result> { self.request_impl(request).await } } -impl AsyncClient for T {} +impl<'a, T: Client<'a>> AsyncClient<'a> for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index c1ce3c24..86d6e13f 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -6,19 +6,20 @@ use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; -pub(crate) trait Client { +#[allow(async_fn_in_trait)] +pub trait Client<'a> { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: Req, ) -> Result>; fn set_request_id< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: &mut Req, ) -> Cow<'_, str> { let common_fields = request.get_common_fields(); @@ -27,6 +28,8 @@ pub(crate) trait Client { None => { #[cfg(feature = "std")] let mut rng = rand::thread_rng(); + #[cfg(not(feature = "std"))] + unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); Cow::Owned(get_random_id(&mut rng)) } }; diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 746be0ce..591a459f 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -2,4 +2,5 @@ mod async_client; mod client; mod websocket; +pub use async_client::*; pub use websocket::*; diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index a4aebbd5..66bff880 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,7 +9,6 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -44,7 +43,7 @@ pub trait XRPLWebsocketIO { Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result>; + ) -> Result>>; } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -119,7 +118,7 @@ where Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result> { + ) -> Result>> { match self.next().await { Some(Ok(item)) => { self.handle_message(item).await?; @@ -130,7 +129,7 @@ where } } Some(Err(error)) => Err!(error), - None => Err!(XRPLWebsocketException::::Disconnected), + None => Ok(None), } } } diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index cef65c97..0f85ceba 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -14,7 +14,7 @@ use core::{pin::Pin, task::Poll}; use embassy_futures::block_on; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; -use futures::{Sink, Stream}; +use futures::{Sink, Stream, StreamExt}; use futures_util::SinkExt; use serde::{Deserialize, Serialize}; use tokio::net::TcpStream; @@ -50,7 +50,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -60,13 +59,10 @@ where fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { let mut guard = block_on(self.websocket.lock()); - // let _ = self.websocket.lock().then(|mut guard| async move { match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { Ok(()) => Ok(()), Err(error) => Err!(error), } - // }); - // Ok(()) } fn poll_flush( @@ -74,7 +70,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_flush(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -87,7 +82,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -107,7 +101,6 @@ where cx: &mut core::task::Context<'_>, ) -> Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_next(cx) { Poll::Ready(Some(item)) => match item { Ok(message) => match message { @@ -125,6 +118,9 @@ where }; Poll::Ready(Some(Ok(response_string))) } + TungsteniteMessage::Close(_) => Poll::Ready(Some(Err!( + XRPLWebsocketException::::Disconnected + ))), _ => Poll::Ready(Some(Err!( XRPLWebsocketException::::UnexpectedMessageType ))), @@ -176,41 +172,81 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> Result { + async fn try_recv_request(&mut self, id: String) -> Result> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.request_impl(id).await + websocket_base.try_recv_request(id).await } } -impl Client for AsyncWebsocketClient +impl<'a, M> Client<'a> for AsyncWebsocketClient where M: RawMutex, { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, mut request: Req, ) -> Result> { + // setup request future let request_id = self.set_request_id::(&mut request); + let mut websocket_base = self.websocket_base.lock().await; + websocket_base + .setup_request_future(request_id.to_string()) + .await; + // send request let mut websocket = self.websocket.lock().await; let request_string = match serde_json::to_string(&request) { Ok(request_string) => request_string, Err(error) => return Err!(error), }; - match websocket + if let Err(error) = websocket .send(TungsteniteMessage::Text(request_string)) .await { - Ok(()) => (), - Err(error) => return Err!(error), + return Err!(error); } - let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await?; - match serde_json::from_str(&message) { - Ok(response) => Ok(response), - Err(error) => Err!(error), + // wait for response + loop { + let message = websocket.next().await; + match message { + Some(Ok(TungsteniteMessage::Text(message))) => { + websocket_base.handle_message(message).await?; + let message_opt = websocket_base + .try_recv_request(request_id.to_string()) + .await?; + if let Some(message) = message_opt { + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; + return Ok(response); + } + } + Some(Ok(TungsteniteMessage::Binary(response))) => { + let message = match String::from_utf8(response) { + Ok(string) => string, + Err(error) => { + return Err!(XRPLWebsocketException::::Utf8( + error.utf8_error() + )); + } + }; + match serde_json::from_str(&message) { + Ok(response) => return Ok(response), + Err(error) => return Err!(error), + } + } + Some(Ok(TungsteniteMessage::Close(_))) => { + return Err!(XRPLWebsocketException::::Disconnected) + } + Some(Ok(_)) => { + return Err!(XRPLWebsocketException::::UnexpectedMessageType); + } + Some(Err(error)) => return Err!(error), + None => continue, + } } } diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index a8be7076..ab43e3a8 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -3,6 +3,7 @@ use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; +use serde_json::Value; use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; @@ -31,6 +32,12 @@ where messages: Channel::new(), } } + + pub fn close(&mut self) { + self.pending_requests.clear(); + self.request_senders.clear(); + self.messages.clear(); + } } pub(crate) trait MessageHandler { @@ -38,7 +45,7 @@ pub(crate) trait MessageHandler { async fn setup_request_future(&mut self, id: String); async fn handle_message(&mut self, message: String) -> Result<()>; async fn pop_message(&mut self) -> String; - async fn request_impl(&mut self, id: String) -> Result; + async fn try_recv_request(&mut self, id: String) -> Result>; } impl MessageHandler for WebsocketBase @@ -46,13 +53,16 @@ where M: RawMutex, { async fn setup_request_future(&mut self, id: String) { + if self.pending_requests.contains_key(&id) { + return; + } let (sender, receiver) = oneshot::channel::(); self.pending_requests.insert(id.clone(), receiver); self.request_senders.insert(id, sender); } async fn handle_message(&mut self, message: String) -> Result<()> { - let message_value = match serde_json::to_value(&message) { + let message_value: Value = match serde_json::from_str(&message) { Ok(value) => value, Err(error) => return Err!(error), }; @@ -82,15 +92,21 @@ where self.messages.receive().await } - async fn request_impl(&mut self, id: String) -> Result { - self.setup_request_future(id.clone()).await; - let fut = match self.pending_requests.remove(&id) { + async fn try_recv_request(&mut self, id: String) -> Result> { + let fut = match self.pending_requests.get_mut(&id) { Some(fut) => fut, None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), }; - match fut.await { - Ok(message) => Ok(message), - Err(error) => return Err!(error), + match fut.try_recv() { + Ok(Some(message)) => { + // Remove the future from the hashmap. + self.pending_requests.remove(&id); + Ok(Some(message)) + } + Ok(None) => Ok(None), + Err(error) => { + return Err!(error); + } } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1c13ded5..25d97d5e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -16,7 +16,7 @@ mod constants; pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_echo( +pub async fn connect_to_wss_tungstinite_test_net( ) -> Result> { match XRPL_WSS_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 390ac84b..d8c1d805 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,13 +1,13 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn test_websocket_tungstenite_echo() -> Result<()> { - use super::common::connect_to_wss_tungstinite_echo; +pub async fn test_websocket_tungstenite_test_net() -> Result<()> { + use super::common::connect_to_wss_tungstinite_test_net; use xrpl::{ asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, }; - let mut websocket = connect_to_wss_tungstinite_echo().await?; + let mut websocket = connect_to_wss_tungstinite_test_net().await?; let fee = Fee::new(None); websocket.xrpl_send(fee).await.unwrap(); @@ -15,6 +15,19 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { .xrpl_receive::, Fee<'_>>() .await .unwrap(); + assert!(message.unwrap().result.is_some()); + Ok(()) +} + +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn test_websocket_tungstenite_request() -> Result<()> { + use super::common::connect_to_wss_tungstinite_test_net; + use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; + + let websocket = connect_to_wss_tungstinite_test_net().await?; + let fee = Fee::new(None); + + let message = websocket.request::, _>(fee).await.unwrap(); assert!(message.result.is_some()); Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 179d20bf..49db24a0 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -8,9 +8,15 @@ use anyhow::Result; #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] - return integration::clients::test_websocket_tungstenite_echo().await; + return integration::clients::test_websocket_tungstenite_test_net().await; + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + return integration::clients::test_embedded_websocket_echo().await; +} + +#[tokio::test] +async fn test_asynch_clients_request() -> Result<()> { + #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; - #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] - Ok(()) } From 0f2a1e000c0a6492b6ff9b47b3d68de624520cf1 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:33:31 +0000 Subject: [PATCH 056/113] implement websocket client request for embedded websocket --- Cargo.toml | 2 +- .../clients/websocket/embedded_websocket.rs | 81 +++++++++++++------ src/asynch/clients/websocket/mod.rs | 4 +- tests/integration/clients/mod.rs | 21 ++++- tests/integration_tests.rs | 2 +- 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 398f49da..322ac67a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "tungstenite"] +default = ["std", "core", "models", "utils", "embedded-ws"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 53beb6a6..ec5a3707 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -153,6 +153,20 @@ where Err(error) => Err(XRPLWebsocketException::::from(error)), } } + + async fn do_read(&self, buf: &mut [u8]) -> Result::Error> { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + match inner.read(tcp.deref_mut(), buf).await { + Some(Ok(ReadResult::Text(t))) => Ok(t.len()), + Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), + Some(Ok(ReadResult::Ping(_))) => Ok(0), + Some(Ok(ReadResult::Pong(_))) => Ok(0), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), + Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), + None => Err(XRPLWebsocketException::::Disconnected), + } + } } impl ErrorType @@ -188,17 +202,7 @@ where Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, { async fn read(&mut self, buf: &mut [u8]) -> Result { - let mut inner = self.websocket.lock().await; - let mut tcp = self.tcp.lock().await; - match inner.read(tcp.deref_mut(), buf).await { - Some(Ok(ReadResult::Text(t))) => Ok(t.len()), - Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), - Some(Ok(ReadResult::Ping(_))) => Ok(0), - Some(Ok(ReadResult::Pong(_))) => Ok(0), - Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), - Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), - None => Err(XRPLWebsocketException::::Disconnected), - } + self.do_read(buf).await } } @@ -225,28 +229,34 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> Result { + async fn try_recv_request(&mut self, id: String) -> Result> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.request_impl(id).await + websocket_base.try_recv_request(id).await } } -impl ClientTrait +impl<'a, const BUF: usize, M, Tcp, B, E, Rng: RngCore> ClientTrait<'a> for AsyncWebsocketClient where M: RawMutex, B: Deref + AsRef<[u8]>, E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, mut request: Req, ) -> Result> { + // setup request future let request_id = self.set_request_id::(&mut request); + let mut websocket_base = self.websocket_base.lock().await; + websocket_base + .setup_request_future(request_id.to_string()) + .await; + // send request let request_string = match serde_json::to_string(&request) { Ok(request_string) => request_string, Err(error) => return Err!(error), @@ -254,12 +264,35 @@ where if let Err(error) = self.do_write(request_string.as_bytes()).await { return Err!(error); } - let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await?; - let response = match serde_json::from_str(&message) { - Ok(response) => response, - Err(error) => return Err!(error), - }; - Ok(response) + // wait for response + loop { + let mut rx_buffer = [0; 1024]; + match self.do_read(&mut rx_buffer).await { + Ok(u_size) => { + // If the buffer is empty, continue to the next iteration. + if u_size == 0 { + continue; + } + let message_str = match core::str::from_utf8(&rx_buffer[..u_size]) { + Ok(response_str) => response_str, + Err(error) => return Err!(XRPLWebsocketException::::Utf8(error)), + }; + websocket_base + .handle_message(message_str.to_string()) + .await?; + let message_opt = websocket_base + .try_recv_request(request_id.to_string()) + .await?; + if let Some(message) = message_opt { + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; + return Ok(response); + } + } + Err(error) => return Err!(error), + } + } } } diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 66bff880..6cc1d398 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,6 +9,8 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -68,7 +70,7 @@ where Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result> { + ) -> Result>> { let mut buffer = [0; 1024]; loop { match self.read(&mut buffer).await { diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d8c1d805..e81de7c0 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -44,7 +44,7 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); - let mut framed = Framed::new(tcp_stream, Codec); + let framed = Framed::new(tcp_stream, Codec); let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let fee = Fee::new(None); websocket.xrpl_send(fee).await?; @@ -54,3 +54,22 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { .unwrap(); Ok(()) } + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub async fn test_embedded_websocket_request() -> Result<()> { + use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::AsyncClient; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; + + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let framed = Framed::new(tcp_stream, Codec); + let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; + let fee = Fee::new(None); + let _res = websocket.request::(fee).await?; + Ok(()) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 49db24a0..19114a74 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -18,5 +18,5 @@ async fn test_asynch_clients_request() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] - return integration::clients::test_embedded_websocket_echo().await; + return integration::clients::test_embedded_websocket_request().await; } From 856fc1cd7b4897f32d44592131fc20b57384e8d2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:41:04 +0000 Subject: [PATCH 057/113] update rand trying to get rid of cargo check: OsRng: rand_core::RngCore is not satisfied error --- Cargo.toml | 4 ++-- src/asynch/clients/websocket/embedded_websocket.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 322ac67a..9c651d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ chrono = { version = "0.4.19", default-features = false, features = [ "clock", ] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -rand = { version = "0.8.4", default-features = false, features = ["getrandom"] } +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } serde = { version = "1.0.130", default-features = false, features = ["derive"] } serde_json = { version = "1.0.68", default-features = false, features = [ "alloc", @@ -81,7 +81,7 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -rand = { version = "0.8.4", default-features = false, features = [ +rand = { version = "0.8.5", default-features = false, features = [ "getrandom", "std", "std_rng", diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index ec5a3707..77c48dd8 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -5,7 +5,6 @@ use core::{ }; use alloc::{ - dbg, panic, string::{String, ToString}, sync::Arc, }; @@ -14,7 +13,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; use embedded_websocket::{ - framer_async::{Framer, FramerError, ReadResult}, + framer_async::{Framer, ReadResult}, Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; use futures_core::Stream; From d40648afe1b2484cc3bed3386d8034bfcc071946 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:24:21 +0000 Subject: [PATCH 058/113] fix github workflow build and tests --- Cargo.toml | 4 +++- src/asynch/clients/client.rs | 6 ++++-- src/asynch/clients/websocket/exceptions.rs | 1 + src/models/transactions/escrow_finish.rs | 8 +++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c651d92..76853166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,9 @@ thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.28", default-features = false, optional = true } +futures = { version = "0.3.28", default-features = false, features = [ + "alloc", +], optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } embassy-sync = { version = "0.6.0", default-features = false } diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 86d6e13f..ff45730e 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -27,10 +27,12 @@ pub trait Client<'a> { Some(id) => id, None => { #[cfg(feature = "std")] - let mut rng = rand::thread_rng(); + { + let mut rng = rand::thread_rng(); + Cow::Owned(get_random_id(&mut rng)) + } #[cfg(not(feature = "std"))] unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); - Cow::Owned(get_random_id(&mut rng)) } }; request.get_common_fields_mut().id = Some(request_id.clone()); diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 664ec36e..88b1161a 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -20,6 +20,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 36d4b9a1..2648498a 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -158,6 +158,8 @@ mod test_escrow_finish_errors { #[cfg(test)] mod tests { + use serde_json::Value; + use super::*; #[test] @@ -182,9 +184,9 @@ mod tests { ); let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; // Serialize - let default_json_value = serde_json::to_value(default_json_str).unwrap(); - let serialized_string = serde_json::to_string(&default_txn).unwrap(); - let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + let default_json_value: Value = serde_json::from_str(default_json_str).unwrap(); + // let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&default_txn).unwrap(); assert_eq!(serialized_value, default_json_value); // Deserialize From 5e3159be8e28dff9053d92de55b6c19f35134413 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:25:10 +0000 Subject: [PATCH 059/113] fix github workflow build and tests --- Cargo.toml | 2 +- src/asynch/clients/client.rs | 7 +-- src/models/transactions/mod.rs | 8 +++ tests/common/mod.rs | 98 +++++++++++++++++--------------- tests/integration/clients/mod.rs | 12 ++-- tests/integration/mod.rs | 2 - tests/integration_tests.rs | 10 +++- 7 files changed, 78 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76853166..6c7b105e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "embedded-ws"] +default = ["std", "core", "models", "utils", "tungstenite"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index ff45730e..7b7cd241 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -1,7 +1,6 @@ -use crate::{ - models::{requests::Request, results::XRPLResponse}, - utils::get_random_id, -}; +use crate::models::{requests::Request, results::XRPLResponse}; +#[cfg(feature = "std")] +use crate::utils::get_random_id; use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 647dc3b5..2ee57b1b 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -153,6 +153,7 @@ where pub fee: Option>, /// Set of bit-flags for this transaction. #[serde(with = "txn_flags")] + #[serde(default = "optional_flag_collection_default")] pub flags: Option>, /// Highest ledger index this transaction can appear in. /// Specifying this field places a strict upper limit on how long @@ -231,6 +232,13 @@ where } } +fn optional_flag_collection_default() -> Option> +where + T: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + None +} + serde_with_tag! { /// An arbitrary piece of data attached to a transaction. A /// transaction can have multiple Memo objects as an array diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 25d97d5e..4c451809 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,56 +1,64 @@ -use anyhow::anyhow; -use anyhow::Result; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use std::io; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio::net::TcpStream; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio_util::codec::Framed; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use xrpl::asynch::clients::codec::Codec; -use xrpl::asynch::clients::AsyncWebsocketClient; -use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; - mod constants; -pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_test_net( -) -> Result> { - match XRPL_WSS_TEST_NET.parse() { - Ok(url) => match AsyncWebsocketClient::open(url).await { +mod tungstenite_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use xrpl::asynch::clients::AsyncWebsocketClient; + use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; + + pub async fn connect_to_wss_tungstinite_test_net( + ) -> Result> { + match XRPL_WSS_TEST_NET.parse() { + Ok(url) => match AsyncWebsocketClient::open(url).await { + Ok(websocket) => { + // assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + }, + Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), + } + } +} + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +mod embedded_ws_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use std::io; + use tokio::net::TcpStream; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + + pub async fn connect_to_ws_embedded_websocket_tokio_echo( + stream: Framed, + ) -> Result< + AsyncWebsocketClient< + 4096, + Framed, + Vec, + io::Error, + rand_core::OsRng, + SingleExecutorMutex, + WebsocketOpen, + >, + > { + let rng = rand_core::OsRng; + let url = ECHO_WS_SERVER.parse().unwrap(); + match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - }, - Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), - } -} - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub async fn connect_to_ws_embedded_websocket_tokio_echo( - stream: Framed, -) -> Result< - AsyncWebsocketClient< - 4096, - Framed, - Vec, - io::Error, - rand_core::OsRng, - SingleExecutorMutex, - WebsocketOpen, - >, -> { - let rng = rand_core::OsRng; - let url = ECHO_WS_SERVER.parse().unwrap(); - match AsyncWebsocketClient::open(rng, stream, url).await { - Ok(websocket) => { - // assert!(websocket.is_open()); - Ok(websocket) } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), } } + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub use embedded_ws_clients::*; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite_clients::*; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e81de7c0..d66e9d9d 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -2,7 +2,7 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_test_net() -> Result<()> { - use super::common::connect_to_wss_tungstinite_test_net; + use crate::common::connect_to_wss_tungstinite_test_net; use xrpl::{ asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, }; @@ -21,7 +21,7 @@ pub async fn test_websocket_tungstenite_test_net() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_request() -> Result<()> { - use super::common::connect_to_wss_tungstinite_test_net; + use crate::common::connect_to_wss_tungstinite_test_net; use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; let websocket = connect_to_wss_tungstinite_test_net().await?; @@ -32,9 +32,9 @@ pub async fn test_websocket_tungstenite_request() -> Result<()> { Ok(()) } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::XRPLWebsocketIO; @@ -55,9 +55,9 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { Ok(()) } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_request() -> Result<()> { - use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::AsyncClient; diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index eb5264ce..705f46db 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1,3 +1 @@ -use super::common; - pub mod clients; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 19114a74..48352339 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,18 +5,22 @@ mod integration; use anyhow::Result; +#[cfg(any(feature = "tungstenite", all(feature = "embedded-ws", feature = "std")))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; + Ok(()) } +#[cfg(any(feature = "tungstenite", feature = "embedded-ws", feature = "std"))] #[tokio::test] async fn test_asynch_clients_request() -> Result<()> { - #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + #[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_request().await; + Ok(()) } From 30f11e30254fcdc74436769bc67c7fbb49926ae7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:28:01 +0000 Subject: [PATCH 060/113] run linters --- tests/integration_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 48352339..73d75eeb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -12,6 +12,7 @@ async fn test_asynch_clients() -> Result<()> { return integration::clients::test_websocket_tungstenite_test_net().await; #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; + #[allow(unreachable_code)] Ok(()) } @@ -22,5 +23,6 @@ async fn test_asynch_clients_request() -> Result<()> { return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_request().await; + #[allow(unreachable_code)] Ok(()) } From 6070296cda1b2b1d24c5d27d19d79d97e54b149d Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 12 Jun 2024 19:35:36 +0000 Subject: [PATCH 061/113] add json rpc client --- Cargo.toml | 5 +- src/asynch/clients/json_rpc/exceptions.rs | 7 ++ src/asynch/clients/json_rpc/mod.rs | 86 +++++++++++++++++++ src/asynch/clients/mod.rs | 7 ++ .../clients/websocket/embedded_websocket.rs | 3 +- src/asynch/clients/websocket/mod.rs | 9 +- src/asynch/clients/websocket/tungstenite.rs | 3 +- .../clients/websocket/websocket_base.rs | 3 +- tests/integration/clients/mod.rs | 17 ++++ tests/integration_tests.rs | 9 ++ 10 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 src/asynch/clients/json_rpc/exceptions.rs create mode 100644 src/asynch/clients/json_rpc/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6c7b105e..5bc65da3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,8 @@ tokio-util = { version = "0.7.7", features = ["codec"], optional = true } bytes = { version = "1.4.0", default-features = false } embassy-futures = "0.1.1" embedded-websocket = { version = "0.9.3", optional = true } +reqwless = { version = "0.12.0", optional = true } +embedded-nal-async = "0.7.1" [dev-dependencies] criterion = "0.5.1" @@ -94,7 +96,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "tungstenite"] +default = ["std", "core", "models", "utils", "tungstenite", "json-rpc"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] @@ -102,6 +104,7 @@ results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] +json-rpc = ["reqwless"] tungstenite = [ "url", "futures", diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs new file mode 100644 index 00000000..406f82b4 --- /dev/null +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -0,0 +1,7 @@ +use strum_macros::Display; +use thiserror_no_std::Error; + +#[derive(Debug, Error, Display)] +pub enum XRPLJsonRpcException { + ReqwlessError(reqwless::Error), +} diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs new file mode 100644 index 00000000..318be698 --- /dev/null +++ b/src/asynch/clients/json_rpc/mod.rs @@ -0,0 +1,86 @@ +use alloc::{borrow::Cow, sync::Arc}; +use anyhow::Result; +use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; +use embedded_nal_async::{Dns, TcpConnect}; +use reqwless::{ + client::HttpClient, + headers::ContentType, + request::{Method, RequestBuilder}, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + models::{requests::Request, results::XRPLResponse}, + Err, +}; + +mod exceptions; +pub use exceptions::XRPLJsonRpcException; + +use super::{client::Client, SingleExecutorMutex}; + +pub struct AsyncJsonRpcClient<'a, const BUF: usize, T, D, M = SingleExecutorMutex> +where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, +{ + url: Cow<'a, str>, + client: Arc>>, +} + +impl<'a, const BUF: usize, T, D, M> AsyncJsonRpcClient<'a, BUF, T, D, M> +where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, +{ + pub fn new(url: Cow<'a, str>, tcp: &'a T, dns: &'a D) -> Self { + Self { + url, + client: Arc::new(Mutex::new(HttpClient::new(tcp, dns))), + } + } +} + +impl<'a, const BUF: usize, T, D, M> Client<'a> for AsyncJsonRpcClient<'a, BUF, T, D, M> +where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, +{ + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: Req, + ) -> Result> { + let request_buf = match serde_json::to_vec(&request) { + Ok(request) => request, + Err(error) => return Err!(error), + }; + let mut rx_buffer = [0; BUF]; + let mut client = self.client.lock().await; + let response = match client.request(Method::POST, &self.url).await { + Ok(client) => { + if let Err(error) = client + .body(request_buf.as_slice()) + .content_type(ContentType::TextPlain) + .send(&mut rx_buffer) + .await + { + Err!(XRPLJsonRpcException::ReqwlessError(error)) + } else { + match serde_json::from_slice::>(&rx_buffer) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } + } + } + Err(error) => Err!(XRPLJsonRpcException::ReqwlessError(error)), + }; + + response + } +} diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 591a459f..9f0fba41 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,6 +1,13 @@ mod async_client; mod client; +mod json_rpc; mod websocket; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + +pub type MultiExecutorMutex = CriticalSectionRawMutex; +pub type SingleExecutorMutex = NoopRawMutex; + pub use async_client::*; +pub use json_rpc::*; pub use websocket::*; diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 77c48dd8..cd53eaca 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -22,7 +22,8 @@ use rand_core::RngCore; use serde::{Deserialize, Serialize}; use url::Url; -use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use super::{WebsocketClosed, WebsocketOpen}; +use crate::asynch::clients::SingleExecutorMutex; use crate::{ asynch::clients::{ client::Client as ClientTrait, diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 6cc1d398..0f4bdc82 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -6,11 +6,8 @@ use alloc::string::String; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use alloc::string::ToString; use anyhow::Result; -use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -22,7 +19,8 @@ use websocket_base::MessageHandler; pub mod codec; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] mod embedded_websocket; -pub mod exceptions; +mod exceptions; +pub use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] mod tungstenite; @@ -34,9 +32,6 @@ pub use tungstenite::AsyncWebsocketClient; pub struct WebsocketOpen; pub struct WebsocketClosed; -pub type MultiExecutorMutex = CriticalSectionRawMutex; -pub type SingleExecutorMutex = NoopRawMutex; - #[allow(async_fn_in_trait)] pub trait XRPLWebsocketIO { async fn xrpl_send(&mut self, message: Req) -> Result<()>; diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index 0f85ceba..acb0c4a3 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -1,7 +1,8 @@ use super::exceptions::XRPLWebsocketException; -use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use super::{WebsocketClosed, WebsocketOpen}; use crate::asynch::clients::client::Client; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; +use crate::asynch::clients::SingleExecutorMutex; use crate::models::requests::Request; use crate::models::results::XRPLResponse; use crate::Err; diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index ab43e3a8..b4e44ae8 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -5,7 +5,8 @@ use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; use serde_json::Value; -use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; +use super::exceptions::XRPLWebsocketException; +use crate::Err; const _MAX_CHANNEL_MSG_CNT: usize = 10; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d66e9d9d..e1400980 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -73,3 +73,20 @@ pub async fn test_embedded_websocket_request() -> Result<()> { let _res = websocket.request::(fee).await?; Ok(()) } + +pub async fn test_json_rpc() -> Result<()> { + use xrpl::{ + asynch::clients::AsyncJsonRpcClient, models::requests::Fee, models::results::FeeResult, + }; + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let mut client = AsyncJsonRpcClient::new( + "https://s.altnet.rippletest.net:51234".into(), + &tcp_stream, + &reqwest::Client::new(), + ); + let server_state = client.request::(Fee::new(None)).await?; + assert!(server_state.result.state.is_some()); + Ok(()) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 73d75eeb..5f82ac54 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -26,3 +26,12 @@ async fn test_asynch_clients_request() -> Result<()> { #[allow(unreachable_code)] Ok(()) } + +#[cfg(feature = "json-rpc")] +#[tokio::test] +async fn test_asynch_clients_json_rpc() -> Result<()> { + #[cfg(feature = "json-rpc")] + return integration::clients::test_json_rpc().await; + #[allow(unreachable_code)] + Ok(()) +} From c2713368bb8fac1ec0cf951ad772e197219fc282 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 13 Jun 2024 16:24:52 +0000 Subject: [PATCH 062/113] add json rpc for std and tests --- Cargo.toml | 8 +- src/asynch/clients/json_rpc/exceptions.rs | 7 +- src/asynch/clients/json_rpc/mod.rs | 222 ++++++++++++++++------ tests/integration/clients/mod.rs | 20 +- tests/integration_tests.rs | 6 +- 5 files changed, 183 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5bc65da3..be380d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,8 @@ bytes = { version = "1.4.0", default-features = false } embassy-futures = "0.1.1" embedded-websocket = { version = "0.9.3", optional = true } reqwless = { version = "0.12.0", optional = true } -embedded-nal-async = "0.7.1" +embedded-nal-async = { version = "0.7.1", optional = true } +reqwest = { version = "0.12.4", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" @@ -96,7 +97,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "tungstenite", "json-rpc"] +default = ["std", "core", "models", "utils", "tungstenite", "json-rpc-std"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] @@ -104,7 +105,8 @@ results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -json-rpc = ["reqwless"] +json-rpc = ["reqwless", "embedded-nal-async"] +json-rpc-std = ["reqwest"] tungstenite = [ "url", "futures", diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs index 406f82b4..95e7a47a 100644 --- a/src/asynch/clients/json_rpc/exceptions.rs +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -1,7 +1,8 @@ -use strum_macros::Display; use thiserror_no_std::Error; -#[derive(Debug, Error, Display)] +#[derive(Debug, Error)] pub enum XRPLJsonRpcException { - ReqwlessError(reqwless::Error), + #[cfg(feature = "json-rpc")] + #[error("Reqwless error")] + ReqwlessError, } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 318be698..c41930ab 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,12 +1,6 @@ -use alloc::{borrow::Cow, sync::Arc}; +use alloc::{string::String, sync::Arc}; use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; -use embedded_nal_async::{Dns, TcpConnect}; -use reqwless::{ - client::HttpClient, - headers::ContentType, - request::{Method, RequestBuilder}, -}; use serde::{Deserialize, Serialize}; use crate::{ @@ -19,68 +13,174 @@ pub use exceptions::XRPLJsonRpcException; use super::{client::Client, SingleExecutorMutex}; -pub struct AsyncJsonRpcClient<'a, const BUF: usize, T, D, M = SingleExecutorMutex> -where - M: RawMutex, - T: TcpConnect + 'a, - D: Dns + 'a, -{ - url: Cow<'a, str>, - client: Arc>>, +/// Renames the requests field `command` to `method` for JSON-RPC. +fn request_to_json_rpc(request: &impl Serialize) -> Result { + let mut request = match serde_json::to_value(request) { + Ok(request) => request, + Err(error) => return Err!(error), + }; + if let Some(command) = request.get_mut("command") { + let method = command.take(); + request["method"] = method; + } + match serde_json::to_string(&request) { + Ok(request) => Ok(request), + Err(error) => Err!(error), + } } -impl<'a, const BUF: usize, T, D, M> AsyncJsonRpcClient<'a, BUF, T, D, M> -where - M: RawMutex, - T: TcpConnect + 'a, - D: Dns + 'a, -{ - pub fn new(url: Cow<'a, str>, tcp: &'a T, dns: &'a D) -> Self { - Self { - url, - client: Arc::new(Mutex::new(HttpClient::new(tcp, dns))), +#[cfg(feature = "json-rpc-std")] +mod std_client { + use super::*; + use reqwest::Client as HttpClient; + use url::Url; + + pub struct AsyncJsonRpcClient + where + M: RawMutex, + { + url: Url, + client: Arc>, + } + + impl AsyncJsonRpcClient + where + M: RawMutex, + { + pub fn new(url: Url) -> Self { + Self { + url, + client: Arc::new(Mutex::new(HttpClient::new())), + } + } + } + + impl AsyncJsonRpcClient + where + M: RawMutex, + { + fn from(url: Url, client: HttpClient) -> Self { + Self { + url, + client: Arc::new(Mutex::new(client)), + } + } + } + + impl<'a, M> Client<'a> for AsyncJsonRpcClient + where + M: RawMutex, + { + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: Req, + ) -> Result> { + let client = self.client.lock().await; + let response = match client + .post(self.url.as_ref()) + .body(request_to_json_rpc(&request)?) + .send() + .await + { + Ok(response) => match response.json().await { + Ok(response) => Ok(response), + Err(error) => Err!(error), + }, + Err(error) => Err!(error), + }; + + response } } } -impl<'a, const BUF: usize, T, D, M> Client<'a> for AsyncJsonRpcClient<'a, BUF, T, D, M> -where - M: RawMutex, - T: TcpConnect + 'a, - D: Dns + 'a, -{ - async fn request_impl< - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &'a self, - request: Req, - ) -> Result> { - let request_buf = match serde_json::to_vec(&request) { - Ok(request) => request, - Err(error) => return Err!(error), - }; - let mut rx_buffer = [0; BUF]; - let mut client = self.client.lock().await; - let response = match client.request(Method::POST, &self.url).await { - Ok(client) => { - if let Err(error) = client - .body(request_buf.as_slice()) - .content_type(ContentType::TextPlain) - .send(&mut rx_buffer) - .await - { - Err!(XRPLJsonRpcException::ReqwlessError(error)) - } else { - match serde_json::from_slice::>(&rx_buffer) { - Ok(response) => Ok(response), - Err(error) => Err!(error), +#[cfg(feature = "json-rpc")] +mod no_std_client { + use super::*; + use embedded_nal_async::{Dns, TcpConnect}; + use reqwless::{ + client::{HttpClient, TlsConfig}, + headers::ContentType, + request::{Method, RequestBuilder}, + }; + use url::Url; + + pub struct AsyncJsonRpcClient<'a, const BUF: usize, T, D, M = SingleExecutorMutex> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + url: Url, + client: Arc>>, + } + + impl<'a, const BUF: usize, T, D, M> AsyncJsonRpcClient<'a, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + pub fn new(url: Url, tcp: &'a T, dns: &'a D) -> Self { + Self { + url, + client: Arc::new(Mutex::new(HttpClient::new(tcp, dns))), + } + } + + pub fn new_with_tls(url: Url, tcp: &'a T, dns: &'a D, tls: TlsConfig<'a>) -> Self { + Self { + url, + client: Arc::new(Mutex::new(HttpClient::new_with_tls(tcp, dns, tls))), + } + } + } + + impl<'a, const BUF: usize, T, D, M> Client<'a> for AsyncJsonRpcClient<'a, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, + >( + &'a self, + request: Req, + ) -> Result> { + let request_json_rpc = request_to_json_rpc(&request)?; + let request_buf = request_json_rpc.as_bytes(); + let mut rx_buffer = [0; BUF]; + let mut client = self.client.lock().await; + let response = match client.request(Method::POST, self.url.as_str()).await { + Ok(client) => { + if let Err(_error) = client + .body(request_buf) + .content_type(ContentType::TextPlain) + .send(&mut rx_buffer) + .await + { + Err!(XRPLJsonRpcException::ReqwlessError) + } else { + match serde_json::from_slice::>(&rx_buffer) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } } } - } - Err(error) => Err!(XRPLJsonRpcException::ReqwlessError(error)), - }; + Err(_error) => Err!(XRPLJsonRpcException::ReqwlessError), + }; - response + response + } } } + +#[cfg(feature = "json-rpc")] +pub use no_std_client::AsyncJsonRpcClient; +#[cfg(feature = "json-rpc-std")] +pub use std_client::AsyncJsonRpcClient; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e1400980..d02d7d64 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -74,19 +74,19 @@ pub async fn test_embedded_websocket_request() -> Result<()> { Ok(()) } -pub async fn test_json_rpc() -> Result<()> { +#[cfg(feature = "json-rpc-std")] +pub async fn test_json_rpc_std() -> Result<()> { use xrpl::{ - asynch::clients::AsyncJsonRpcClient, models::requests::Fee, models::results::FeeResult, + asynch::clients::{AsyncClient, AsyncJsonRpcClient, SingleExecutorMutex}, + models::requests::Fee, + models::results::FeeResult, }; - let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + let client: AsyncJsonRpcClient = + AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); + let fee_result = client + .request::(Fee::new(None)) .await .unwrap(); - let mut client = AsyncJsonRpcClient::new( - "https://s.altnet.rippletest.net:51234".into(), - &tcp_stream, - &reqwest::Client::new(), - ); - let server_state = client.request::(Fee::new(None)).await?; - assert!(server_state.result.state.is_some()); + assert!(fee_result.result.is_some()); Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5f82ac54..e752f833 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -27,11 +27,11 @@ async fn test_asynch_clients_request() -> Result<()> { Ok(()) } -#[cfg(feature = "json-rpc")] +#[cfg(feature = "json-rpc-std")] #[tokio::test] async fn test_asynch_clients_json_rpc() -> Result<()> { - #[cfg(feature = "json-rpc")] - return integration::clients::test_json_rpc().await; + #[cfg(feature = "json-rpc-std")] + return integration::clients::test_json_rpc_std().await; #[allow(unreachable_code)] Ok(()) } From 65d9b214ae08bced8d3fac3e9953961ba108c5f8 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 13 Jun 2024 16:25:31 +0000 Subject: [PATCH 063/113] cargo fmt --- src/asynch/clients/json_rpc/mod.rs | 4 ++-- tests/integration/clients/mod.rs | 2 +- tests/integration_tests.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index c41930ab..f70a03be 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -180,7 +180,7 @@ mod no_std_client { } } -#[cfg(feature = "json-rpc")] +#[cfg(all(feature = "json-rpc", not(feature = "json-rpc-std")))] pub use no_std_client::AsyncJsonRpcClient; -#[cfg(feature = "json-rpc-std")] +#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] pub use std_client::AsyncJsonRpcClient; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d02d7d64..d105f12f 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -74,7 +74,7 @@ pub async fn test_embedded_websocket_request() -> Result<()> { Ok(()) } -#[cfg(feature = "json-rpc-std")] +#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] pub async fn test_json_rpc_std() -> Result<()> { use xrpl::{ asynch::clients::{AsyncClient, AsyncJsonRpcClient, SingleExecutorMutex}, diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e752f833..23d401ff 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -27,10 +27,10 @@ async fn test_asynch_clients_request() -> Result<()> { Ok(()) } -#[cfg(feature = "json-rpc-std")] +#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] #[tokio::test] async fn test_asynch_clients_json_rpc() -> Result<()> { - #[cfg(feature = "json-rpc-std")] + #[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] return integration::clients::test_json_rpc_std().await; #[allow(unreachable_code)] Ok(()) From e27479e93a92856a07ef4e6cc0284975bc25425b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 13 Jun 2024 16:32:27 +0000 Subject: [PATCH 064/113] add pre-commit test with json-rpc-std --- .cargo-husky/hooks/pre-commit | 1 + 1 file changed, 1 insertion(+) diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index d4a3108b..3dfe18e5 100755 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -6,6 +6,7 @@ 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 core,models,utils,json-rpc-std cargo test --all-features cargo clippy --fix --allow-staged cargo doc --no-deps From 7f9284f4e9fa3158405146455cce66d45ac7303e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 15 Jun 2024 19:10:40 +0000 Subject: [PATCH 065/113] cargo fmt --- src/asynch/clients/json_rpc/mod.rs | 6 ++---- src/asynch/clients/websocket/websocket_base.rs | 2 +- src/models/transactions/account_set.rs | 2 +- src/wallet/mod.rs | 7 ++----- tests/common/constants.rs | 8 ++++---- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index f70a03be..72bc61f9 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -79,7 +79,7 @@ mod std_client { request: Req, ) -> Result> { let client = self.client.lock().await; - let response = match client + match client .post(self.url.as_ref()) .body(request_to_json_rpc(&request)?) .send() @@ -90,9 +90,7 @@ mod std_client { Err(error) => Err!(error), }, Err(error) => Err!(error), - }; - - response + } } } } diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index b4e44ae8..649eb1fc 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -106,7 +106,7 @@ where } Ok(None) => Ok(None), Err(error) => { - return Err!(error); + Err!(error) } } } diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 0377406f..15ae14b8 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -230,7 +230,7 @@ impl<'a> AccountSetError for AccountSet<'a> { if self.clear_flag.is_some() && self.set_flag.is_some() && self.clear_flag == self.set_flag { Err(XRPLAccountSetException::SetAndUnsetSameFlag { - found: self.clear_flag.clone().unwrap(), + found: self.clear_flag.unwrap(), resource: "".into(), }) } else { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index acc3bbb8..aff3b7a9 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -10,7 +10,6 @@ use crate::core::keypairs::generate_seed; use alloc::format; use alloc::string::String; use alloc::string::ToString; -use alloc::vec; use zeroize::Zeroize; /// The cryptographic keys needed to control an @@ -88,11 +87,9 @@ impl Wallet { impl ToString for Wallet { /// Returns a string representation of a Wallet. fn to_string(&self) -> String { - let string_list = vec![ - format!("public_key: {}", self.public_key), + let string_list = [format!("public_key: {}", self.public_key), format!("private_key: {}", "-HIDDEN-"), - format!("classic_address: {}", self.classic_address), - ]; + format!("classic_address: {}", self.classic_address)]; string_list.join("-") } diff --git a/tests/common/constants.rs b/tests/common/constants.rs index 732059d2..95299ecb 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,6 +1,6 @@ -pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; -pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; +pub const ECHO_WS_SERVER: &str = "ws://ws.vi-server.org/mirror"; +pub const ECHO_WSS_SERVER: &str = "wss://ws.vi-server.org/mirror"; // pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; -pub const XRPL_WSS_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; -pub const XRPL_WS_TEST_NET: &'static str = "wss://s.altnet.rippletest.net:51233/"; +pub const XRPL_WSS_TEST_NET: &str = "wss://testnet.xrpl-labs.com/"; +pub const XRPL_WS_TEST_NET: &str = "wss://s.altnet.rippletest.net:51233/"; From d20d01124f42f54571ee584b08b0323a11f03022 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 15 Jun 2024 19:13:13 +0000 Subject: [PATCH 066/113] cargo fix --- src/wallet/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index aff3b7a9..00e2443d 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -87,9 +87,11 @@ impl Wallet { impl ToString for Wallet { /// Returns a string representation of a Wallet. fn to_string(&self) -> String { - let string_list = [format!("public_key: {}", self.public_key), + let string_list = [ + format!("public_key: {}", self.public_key), format!("private_key: {}", "-HIDDEN-"), - format!("classic_address: {}", self.classic_address)]; + format!("classic_address: {}", self.classic_address), + ]; string_list.join("-") } From 9e573f64ea00f91f3981e41f0fdde79b50466d7e Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 28 Jun 2024 12:54:56 +0000 Subject: [PATCH 067/113] improve client code --- Cargo.toml | 40 ++++------ examples/std/Cargo.toml | 21 ++---- examples/std/src/bin/tokio/net/tungstenite.rs | 35 ++++----- src/asynch/clients/async_client.rs | 10 ++- src/asynch/clients/client.rs | 14 ++-- src/asynch/clients/json_rpc/mod.rs | 20 +++-- src/asynch/clients/mod.rs | 12 +-- .../clients/websocket/embedded_websocket.rs | 12 +-- src/asynch/clients/websocket/mod.rs | 10 ++- src/asynch/clients/websocket/tungstenite.rs | 73 ++++++------------- .../clients/websocket/websocket_base.rs | 3 +- src/asynch/mod.rs | 7 +- tests/common/mod.rs | 36 ++++++--- 13 files changed, 140 insertions(+), 153 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be380d70..c29c8621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,18 +59,14 @@ fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } -tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.28", default-features = false, features = [ +futures = { version = "0.3.30", default-features = false, features = [ "alloc", ], optional = true } rand_core = { version = "0.6.4", default-features = false } -tokio-tungstenite = { version = "0.20.0", optional = true } +tokio-tungstenite = { version = "0.23.1", optional = true } embassy-sync = { version = "0.6.0", default-features = false } embedded-io-async = "0.6.1" -futures-sink = { version = "0.3.30", default-features = false } -futures-core = { version = "0.3.30", default-features = false } -futures-util = { version = "0.3.30", optional = true } tokio-util = { version = "0.7.7", features = ["codec"], optional = true } bytes = { version = "1.4.0", default-features = false } embassy-futures = "0.1.1" @@ -81,16 +77,16 @@ reqwest = { version = "0.12.4", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" -cargo-husky = { version = "1.5.0", default-features = false, features = [ - "user-hooks", -] } -tokio = { version = "1.28.0", features = ["full"] } -tokio-util = { version = "0.7.7", features = ["codec"] } -rand = { version = "0.8.5", default-features = false, features = [ - "getrandom", - "std", - "std_rng", -] } +tokio = { version = "1.38.0", features = ["full"] } +# cargo-husky = { version = "1.5.0", default-features = false, features = [ +# "user-hooks", +# ] } +# tokio-util = { version = "0.7.7", features = ["codec"] } +# rand = { version = "0.8.5", default-features = false, features = [ +# "getrandom", +# "std", +# "std_rng", +# ] } [[bench]] name = "benchmarks" @@ -105,15 +101,9 @@ results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -json-rpc = ["reqwless", "embedded-nal-async"] -json-rpc-std = ["reqwest"] -tungstenite = [ - "url", - "futures", - "tokio/net", - "tokio-tungstenite/native-tls", - "futures-util", -] +json-rpc = ["url", "reqwless", "embedded-nal-async"] +json-rpc-std = ["url", "reqwest"] +tungstenite = ["url", "futures", "tokio-tungstenite/native-tls"] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] utils = [] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 9d84bc4c..7d976969 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -7,33 +7,28 @@ edition = "2021" [dependencies] xrpl-rust = { path = "../.." } -tokio = { version = "1.12.0", features = ["full"], optional = true } -futures = { version = "0.3.19", optional = true } +tokio = { version = "1.38.0", features = ["full"], optional = true } serde_json = { version = "1.0.70", optional = true } +tokio-tungstenite = { version = "0.23.1", features = [ + "native-tls", +], optional = true } [[bin]] name = "wallet_from_seed" path = "src/bin/wallet/wallet_from_seed.rs" -required-features = [ -] +required-features = [] [[bin]] name = "generate_wallet" path = "src/bin/wallet/generate_wallet.rs" -required-features = [ -] +required-features = [] [[bin]] name = "tungstenite" path = "src/bin/tokio/net/tungstenite.rs" -required-features = [ - "tokio", - "futures", - "serde_json", -] +required-features = ["tokio", "serde_json", "tokio-tungstenite"] [[bin]] name = "sign_request" path = "src/bin/transaction/sign_transaction.rs" -required-features = [ -] +required-features = [] diff --git a/examples/std/src/bin/tokio/net/tungstenite.rs b/examples/std/src/bin/tokio/net/tungstenite.rs index fd64fb8d..975a3b96 100644 --- a/examples/std/src/bin/tokio/net/tungstenite.rs +++ b/examples/std/src/bin/tokio/net/tungstenite.rs @@ -1,35 +1,28 @@ -use xrpl::asynch::clients::async_websocket_client::{ - AsyncWebsocketClientTungstenite, TungsteniteMessage, +use serde_json::Value; +use tokio_tungstenite::connect_async; +use xrpl::asynch::clients::{ + AsyncWebsocketClient, SingleExecutorMutex, WebsocketOpen, XRPLWebsocketIO, }; -use xrpl::models::requests::AccountInfo; - -use futures::{SinkExt, TryStreamExt}; +use xrpl::models::requests::{StreamParameter, Subscribe}; #[tokio::main] async fn main() { - let websocket = - AsyncWebsocketClientTungstenite::open("wss://xrplcluster.com/".parse().unwrap()) - .await - .unwrap(); - assert!(websocket.is_open()); - - let account_info = AccountInfo::new( - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH", + let stream = connect_async("wss://xrplcluster.com/").await.unwrap().0; + let mut websocket: AsyncWebsocketClient<_, SingleExecutorMutex, WebsocketOpen> = + AsyncWebsocketClient::open(stream).await.unwrap(); + let subscribe = Subscribe::new( + None, None, None, None, + Some(vec![StreamParameter::Ledger]), None, None, None, ); - - websocket.send(&account_info).await.unwrap(); - - while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - let account_info_echo: AccountInfo = serde_json::from_str(response.as_str()).unwrap(); - println!("account_info_echo: {:?}", account_info_echo); - - websocket.close().await.unwrap(); + websocket.xrpl_send(subscribe).await.unwrap(); + while let Ok(Some(account_info_echo)) = websocket.xrpl_receive::().await { + println!("subscription message: {:?}", account_info_echo); break; } } diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 57a776ab..8479d42e 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -4,16 +4,18 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; #[allow(async_fn_in_trait)] -pub trait AsyncClient<'a>: Client<'a> { +pub trait AsyncClient: Client { async fn request< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { self.request_impl(request).await } } -impl<'a, T: Client<'a>> AsyncClient<'a> for T {} +impl AsyncClient for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 7b7cd241..42391654 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -6,21 +6,25 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; #[allow(async_fn_in_trait)] -pub trait Client<'a> { +pub trait Client { async fn request_impl< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result>; + ) -> Result>; fn set_request_id< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: &mut Req, - ) -> Cow<'_, str> { + ) -> Cow<'b, str> { let common_fields = request.get_common_fields(); let request_id: Cow<'_, str> = match common_fields.id.clone() { Some(id) => id, diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 72bc61f9..2e97b12f 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -67,17 +67,19 @@ mod std_client { } } - impl<'a, M> Client<'a> for AsyncJsonRpcClient + impl Client for AsyncJsonRpcClient where M: RawMutex, { async fn request_impl< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { let client = self.client.lock().await; match client .post(self.url.as_ref()) @@ -137,19 +139,21 @@ mod no_std_client { } } - impl<'a, const BUF: usize, T, D, M> Client<'a> for AsyncJsonRpcClient<'a, BUF, T, D, M> + impl Client for AsyncJsonRpcClient<'_, BUF, T, D, M> where M: RawMutex, - T: TcpConnect + 'a, - D: Dns + 'a, + T: TcpConnect, + D: Dns, { async fn request_impl< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, request: Req, - ) -> Result> { + ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; let request_buf = request_json_rpc.as_bytes(); let mut rx_buffer = [0; BUF]; diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 9f0fba41..4558c658 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,13 +1,15 @@ -mod async_client; -mod client; -mod json_rpc; -mod websocket; +pub mod async_client; +pub mod client; +pub mod json_rpc; +#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] +pub mod websocket; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; - pub type MultiExecutorMutex = CriticalSectionRawMutex; pub type SingleExecutorMutex = NoopRawMutex; pub use async_client::*; +pub use client::*; pub use json_rpc::*; +#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] pub use websocket::*; diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index cd53eaca..dcc8a2ee 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -16,8 +16,8 @@ use embedded_websocket::{ framer_async::{Framer, ReadResult}, Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; -use futures_core::Stream; -use futures_sink::Sink; +use futures::Sink; +use futures::Stream; use rand_core::RngCore; use serde::{Deserialize, Serialize}; use url::Url; @@ -235,7 +235,7 @@ where } } -impl<'a, const BUF: usize, M, Tcp, B, E, Rng: RngCore> ClientTrait<'a> +impl ClientTrait for AsyncWebsocketClient where M: RawMutex, @@ -244,12 +244,14 @@ where Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { async fn request_impl< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, mut request: Req, - ) -> Result> { + ) -> Result> { // setup request future let request_id = self.set_request_id::(&mut request); let mut websocket_base = self.websocket_base.lock().await; diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 0f4bdc82..4159742e 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -1,4 +1,4 @@ -use core::fmt::{Debug, Display}; +use core::fmt::Debug; use crate::{models::results::XRPLResponse, Err}; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] @@ -7,6 +7,8 @@ use alloc::string::String; use alloc::string::ToString; use anyhow::Result; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use core::fmt::Display; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; @@ -35,6 +37,7 @@ pub struct WebsocketClosed; #[allow(async_fn_in_trait)] pub trait XRPLWebsocketIO { async fn xrpl_send(&mut self, message: Req) -> Result<()>; + async fn xrpl_receive< Res: Serialize + for<'de> Deserialize<'de> + Debug, Req: Serialize + for<'de> Deserialize<'de> + Debug, @@ -94,10 +97,9 @@ where } #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -impl XRPLWebsocketIO for T +impl XRPLWebsocketIO for T where - T: Stream> + Sink + MessageHandler + Unpin, - >::Error: Debug + Display, + T: Stream> + Sink + MessageHandler + Unpin, { async fn xrpl_send(&mut self, message: Req) -> Result<()> { let message = match serde_json::to_string(&message) { diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index acb0c4a3..f7681b46 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -15,34 +15,23 @@ use core::{pin::Pin, task::Poll}; use embassy_futures::block_on; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; -use futures::{Sink, Stream, StreamExt}; -use futures_util::SinkExt; +use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, -}; -use url::Url; +use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; -pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - -pub type AsyncWebsocketConnection = - Arc>>>; - -pub struct AsyncWebsocketClient +pub struct AsyncWebsocketClient where M: RawMutex, { - websocket: AsyncWebsocketConnection, + websocket: Arc>, websocket_base: Arc>>, status: PhantomData, } -impl Sink for AsyncWebsocketClient +impl Sink for AsyncWebsocketClient where + T: Sink + Unpin, M: RawMutex, - Self: Unpin, { type Error = anyhow::Error; @@ -91,8 +80,9 @@ where } } -impl Stream for AsyncWebsocketClient +impl Stream for AsyncWebsocketClient where + T: Stream> + Unpin, M: RawMutex, { type Item = Result; @@ -134,27 +124,20 @@ where } } -impl AsyncWebsocketClient +impl AsyncWebsocketClient where M: RawMutex, { - pub async fn open(uri: Url) -> Result> { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - websocket: Arc::new(Mutex::new(websocket_stream)), - websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } + pub async fn open(stream: T) -> Result> { + Ok(AsyncWebsocketClient { + websocket: Arc::new(Mutex::new(stream)), + websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + status: PhantomData::, + }) } } -impl MessageHandler for AsyncWebsocketClient +impl MessageHandler for AsyncWebsocketClient where M: RawMutex, { @@ -179,17 +162,22 @@ where } } -impl<'a, M> Client<'a> for AsyncWebsocketClient +impl Client for AsyncWebsocketClient where + T: Stream> + + Sink + + Unpin, M: RawMutex, { async fn request_impl< + 'a: 'b, + 'b, Res: Serialize + for<'de> Deserialize<'de>, Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &'a self, + &self, mut request: Req, - ) -> Result> { + ) -> Result> { // setup request future let request_id = self.set_request_id::(&mut request); let mut websocket_base = self.websocket_base.lock().await; @@ -250,17 +238,4 @@ where } } } - - // async fn get_common_fields(&self) -> Result> { - // let server_state = self - // .request::(requests::ServerState::new(None)) - // .await?; - // let state = server_state.result.state; - // let common_fields = CommonFields { - // network_id: state.network_id, - // build_version: Some(state.build_version), - // }; - - // Ok(common_fields) - // } } diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index 649eb1fc..b8bc4307 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -41,7 +41,8 @@ where } } -pub(crate) trait MessageHandler { +#[allow(async_fn_in_trait)] +pub trait MessageHandler { /// Setup an empty future for a request. async fn setup_request_future(&mut self, id: String); async fn handle_message(&mut self, message: String) -> Result<()>; diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 70076baf..55b0c5f3 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,2 +1,7 @@ -#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] +#[cfg(any( + feature = "tungstenite", + feature = "embedded-ws", + feature = "json-rpc-std", + feature = "json-rpc" +))] pub mod clients; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4c451809..be43e04e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -5,20 +5,30 @@ mod tungstenite_clients { use super::constants::*; use anyhow::anyhow; use anyhow::Result; + use tokio::net::TcpStream; + use tokio_tungstenite::connect_async; + use tokio_tungstenite::MaybeTlsStream; + use tokio_tungstenite::WebSocketStream; use xrpl::asynch::clients::AsyncWebsocketClient; use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; - pub async fn connect_to_wss_tungstinite_test_net( - ) -> Result> { - match XRPL_WSS_TEST_NET.parse() { - Ok(url) => match AsyncWebsocketClient::open(url).await { - Ok(websocket) => { - // assert!(websocket.is_open()); - Ok(websocket) - } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - }, - Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), + pub async fn connect_to_wss_tungstinite_test_net<'a>() -> Result< + AsyncWebsocketClient< + WebSocketStream>, + SingleExecutorMutex, + WebsocketOpen, + >, + > { + let stream = connect_async(XRPL_WSS_TEST_NET.to_string()) + .await + .unwrap() + .0; + match AsyncWebsocketClient::open(stream).await { + Ok(websocket) => { + // assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), } } } @@ -31,7 +41,9 @@ mod embedded_ws_clients { use std::io; use tokio::net::TcpStream; use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::{ + codec::Codec, AsyncWebsocketClient, SingleExecutorMutex, WebsocketOpen, + }; pub async fn connect_to_ws_embedded_websocket_tokio_echo( stream: Framed, From a7105e7316923be22ee7a0a6e6ab20687755a96b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 16 Jul 2024 19:14:37 +0200 Subject: [PATCH 068/113] refactor features --- Cargo.toml | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c29c8621..eb90d848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,41 +59,43 @@ fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } + +# networking url = { version = "2.2.2", default-features = false, optional = true } futures = { version = "0.3.30", default-features = false, features = [ "alloc", ], optional = true } -rand_core = { version = "0.6.4", default-features = false } -tokio-tungstenite = { version = "0.23.1", optional = true } embassy-sync = { version = "0.6.0", default-features = false } -embedded-io-async = "0.6.1" -tokio-util = { version = "0.7.7", features = ["codec"], optional = true } -bytes = { version = "1.4.0", default-features = false } -embassy-futures = "0.1.1" +rand_core = { version = "0.6.4", default-features = false } +# std websocket +embassy-futures = { version = "0.1.1", optional = true } +tokio-tungstenite = { version = "0.23.1", features = [ + "native-tls", +], optional = true } +tungstenite = { version = "0.23.0", optional = true } +tokio-util = { version = "0.7.11", features = ["codec"], optional = true } +tokio = { version = "1.0", features = ["full"], optional = true } +# no-std websocket +bytes = { version = "1.4.0", default-features = false, optional = true } +embedded-io-async = { version = "0.6.1", optional = true } embedded-websocket = { version = "0.9.3", optional = true } +# std json-rpc +reqwest = { version = "0.12.5", features = ["json"], optional = true } +# no-std json-rpc reqwless = { version = "0.12.0", optional = true } embedded-nal-async = { version = "0.7.1", optional = true } -reqwest = { version = "0.12.4", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" -tokio = { version = "1.38.0", features = ["full"] } -# cargo-husky = { version = "1.5.0", default-features = false, features = [ -# "user-hooks", -# ] } -# tokio-util = { version = "0.7.7", features = ["codec"] } -# rand = { version = "0.8.5", default-features = false, features = [ -# "getrandom", -# "std", -# "std_rng", -# ] } +tokio = { version = "1.0", features = ["full"] } +tokio-util = { version = "0.7.11", features = ["codec"] } [[bench]] name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "tungstenite", "json-rpc-std"] +default = ["std", "core", "models", "utils", "websocket-std"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] @@ -103,8 +105,16 @@ amounts = ["core"] currencies = ["core"] json-rpc = ["url", "reqwless", "embedded-nal-async"] json-rpc-std = ["url", "reqwest"] -tungstenite = ["url", "futures", "tokio-tungstenite/native-tls"] -embedded-ws = ["url", "futures", "embedded-websocket"] +websocket = ["url", "futures", "embedded-websocket", "embedded-io-async"] +websocket-codec = ["bytes", "tokio-util"] +websocket-std = [ + "url", + "futures", + "tungstenite", + "tokio", + "tokio-tungstenite", + "embassy-futures", +] core = ["utils"] utils = [] std = [ @@ -120,5 +130,4 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", - "dep:tokio-util", ] From 0c87335d9e2b6bf5837fb4703505803f97b84363 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 19 Jul 2024 16:44:53 +0200 Subject: [PATCH 069/113] fix tungstenite example --- examples/std/Cargo.toml | 9 +++------ examples/std/src/bin/tokio/net/tungstenite.rs | 17 +++++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 7d976969..27aa2801 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -7,11 +7,8 @@ edition = "2021" [dependencies] xrpl-rust = { path = "../.." } -tokio = { version = "1.38.0", features = ["full"], optional = true } -serde_json = { version = "1.0.70", optional = true } -tokio-tungstenite = { version = "0.23.1", features = [ - "native-tls", -], optional = true } +tokio = { version = "1.0", features = ["full"], optional = true } +tokio-tungstenite = "0.23.1" [[bin]] name = "wallet_from_seed" @@ -26,7 +23,7 @@ required-features = [] [[bin]] name = "tungstenite" path = "src/bin/tokio/net/tungstenite.rs" -required-features = ["tokio", "serde_json", "tokio-tungstenite"] +required-features = ["tokio"] [[bin]] name = "sign_request" diff --git a/examples/std/src/bin/tokio/net/tungstenite.rs b/examples/std/src/bin/tokio/net/tungstenite.rs index 975a3b96..2f1ff5e0 100644 --- a/examples/std/src/bin/tokio/net/tungstenite.rs +++ b/examples/std/src/bin/tokio/net/tungstenite.rs @@ -1,4 +1,3 @@ -use serde_json::Value; use tokio_tungstenite::connect_async; use xrpl::asynch::clients::{ AsyncWebsocketClient, SingleExecutorMutex, WebsocketOpen, XRPLWebsocketIO, @@ -7,11 +6,12 @@ use xrpl::models::requests::{StreamParameter, Subscribe}; #[tokio::main] async fn main() { - let stream = connect_async("wss://xrplcluster.com/").await.unwrap().0; - let mut websocket: AsyncWebsocketClient<_, SingleExecutorMutex, WebsocketOpen> = - AsyncWebsocketClient::open(stream).await.unwrap(); + let mut websocket: AsyncWebsocketClient = + AsyncWebsocketClient::open("wss://xrplcluster.com/".parse().unwrap()) + .await + .unwrap(); let subscribe = Subscribe::new( - None, + Some("my_id".into()), None, None, None, @@ -20,9 +20,10 @@ async fn main() { None, None, ); - websocket.xrpl_send(subscribe).await.unwrap(); - while let Ok(Some(account_info_echo)) = websocket.xrpl_receive::().await { + websocket.xrpl_send(subscribe.into()).await.unwrap(); + loop { + let account_info_echo = websocket.xrpl_receive().await.unwrap().unwrap(); println!("subscription message: {:?}", account_info_echo); - break; + // break; } } From ce9aeb6a9824bcb3e3a936edf5936186c6212437 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 19 Jul 2024 16:47:13 +0200 Subject: [PATCH 070/113] fix clients --- src/asynch/clients/async_client.rs | 13 +- src/asynch/clients/client.rs | 26 +- src/asynch/clients/json_rpc/mod.rs | 39 +- src/asynch/clients/mod.rs | 6 +- .../{embedded_websocket.rs => _no_std.rs} | 18 +- .../websocket/{tungstenite.rs => _std.rs} | 93 +++-- src/asynch/clients/websocket/exceptions.rs | 14 +- src/asynch/clients/websocket/mod.rs | 67 ++-- src/asynch/mod.rs | 4 +- src/models/requests/mod.rs | 332 ++++++++++++++++++ src/models/results/fee.rs | 4 +- src/models/results/mod.rs | 138 +++++--- 12 files changed, 548 insertions(+), 206 deletions(-) rename src/asynch/clients/websocket/{embedded_websocket.rs => _no_std.rs} (95%) rename src/asynch/clients/websocket/{tungstenite.rs => _std.rs} (73%) diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 8479d42e..f9007f07 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -1,19 +1,10 @@ use super::client::Client; -use crate::models::{requests::Request, results::XRPLResponse}; +use crate::models::{requests::XRPLRequest, results::XRPLResponse}; use anyhow::Result; -use serde::{Deserialize, Serialize}; #[allow(async_fn_in_trait)] pub trait AsyncClient: Client { - async fn request< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &self, - request: Req, - ) -> Result> { + async fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { self.request_impl(request).await } } diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 42391654..ebbb609b 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -1,30 +1,16 @@ -use crate::models::{requests::Request, results::XRPLResponse}; +use crate::models::{ + requests::{Request, XRPLRequest}, + results::XRPLResponse, +}; #[cfg(feature = "std")] use crate::utils::get_random_id; use alloc::borrow::Cow; use anyhow::Result; -use serde::{Deserialize, Serialize}; #[allow(async_fn_in_trait)] pub trait Client { - async fn request_impl< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &self, - request: Req, - ) -> Result>; - fn set_request_id< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( - &self, - request: &mut Req, - ) -> Cow<'b, str> { + async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + fn set_request_id<'a: 'b, 'b>(&self, request: &mut XRPLRequest<'a>) -> Cow<'b, str> { let common_fields = request.get_common_fields(); let request_id: Cow<'_, str> = match common_fields.id.clone() { Some(id) => id, diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 2e97b12f..348c87df 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,12 +1,9 @@ use alloc::{string::String, sync::Arc}; use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; -use crate::{ - models::{requests::Request, results::XRPLResponse}, - Err, -}; +use crate::{models::results::XRPLResponse, Err}; mod exceptions; pub use exceptions::XRPLJsonRpcException; @@ -30,7 +27,9 @@ fn request_to_json_rpc(request: &impl Serialize) -> Result { } #[cfg(feature = "json-rpc-std")] -mod std_client { +mod _std { + use crate::models::requests::XRPLRequest; + use super::*; use reqwest::Client as HttpClient; use url::Url; @@ -71,15 +70,10 @@ mod std_client { where M: RawMutex, { - async fn request_impl< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( + async fn request_impl<'a: 'b, 'b>( &self, - request: Req, - ) -> Result> { + request: XRPLRequest<'a>, + ) -> Result> { let client = self.client.lock().await; match client .post(self.url.as_ref()) @@ -98,7 +92,9 @@ mod std_client { } #[cfg(feature = "json-rpc")] -mod no_std_client { +mod _no_std { + use crate::models::requests::XRPLRequest; + use super::*; use embedded_nal_async::{Dns, TcpConnect}; use reqwless::{ @@ -145,14 +141,9 @@ mod no_std_client { T: TcpConnect, D: Dns, { - async fn request_impl< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( + async fn request_impl<'a: 'b, 'b>( &self, - request: Req, + request: XRPLRequest<'a>, ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; let request_buf = request_json_rpc.as_bytes(); @@ -183,6 +174,6 @@ mod no_std_client { } #[cfg(all(feature = "json-rpc", not(feature = "json-rpc-std")))] -pub use no_std_client::AsyncJsonRpcClient; +pub use _no_std::AsyncJsonRpcClient; #[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] -pub use std_client::AsyncJsonRpcClient; +pub use _std::AsyncJsonRpcClient; diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 4558c658..1d151377 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,7 +1,8 @@ pub mod async_client; pub mod client; +#[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))] pub mod json_rpc; -#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] +#[cfg(any(feature = "websocket-std", feature = "websocket"))] pub mod websocket; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; @@ -10,6 +11,7 @@ pub type SingleExecutorMutex = NoopRawMutex; pub use async_client::*; pub use client::*; +#[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))] pub use json_rpc::*; -#[cfg(any(feature = "tungstenite", feature = "embedded-ws"))] +#[cfg(any(feature = "websocket-std", feature = "websocket"))] pub use websocket::*; diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/_no_std.rs similarity index 95% rename from src/asynch/clients/websocket/embedded_websocket.rs rename to src/asynch/clients/websocket/_no_std.rs index dcc8a2ee..1e5d9838 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -19,17 +19,16 @@ use embedded_websocket::{ use futures::Sink; use futures::Stream; use rand_core::RngCore; -use serde::{Deserialize, Serialize}; use url::Url; use super::{WebsocketClosed, WebsocketOpen}; -use crate::asynch::clients::SingleExecutorMutex; +use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; use crate::{ asynch::clients::{ client::Client as ClientTrait, websocket::websocket_base::{MessageHandler, WebsocketBase}, }, - models::{requests::Request, results::XRPLResponse}, + models::results::XRPLResponse, Err, }; @@ -243,17 +242,12 @@ where E: Debug + Display, Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { - async fn request_impl< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( + async fn request_impl<'a: 'b, 'b>( &self, - mut request: Req, - ) -> Result> { + mut request: XRPLRequest<'a>, + ) -> Result> { // setup request future - let request_id = self.set_request_id::(&mut request); + let request_id = self.set_request_id(&mut request); let mut websocket_base = self.websocket_base.lock().await; websocket_base .setup_request_future(request_id.to_string()) diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/_std.rs similarity index 73% rename from src/asynch/clients/websocket/tungstenite.rs rename to src/asynch/clients/websocket/_std.rs index f7681b46..35fbce05 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -3,7 +3,7 @@ use super::{WebsocketClosed, WebsocketOpen}; use crate::asynch::clients::client::Client; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; use crate::asynch::clients::SingleExecutorMutex; -use crate::models::requests::Request; +use crate::models::requests::XRPLRequest; use crate::models::results::XRPLResponse; use crate::Err; @@ -16,21 +16,25 @@ use embassy_futures::block_on; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use futures::{Sink, SinkExt, Stream, StreamExt}; -use serde::{Deserialize, Serialize}; -use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; +use tokio::net::TcpStream; +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +use url::Url; -pub struct AsyncWebsocketClient +use tokio_tungstenite::connect_async as tokio_tungstenite_connect_async; + +type TokioTungsteniteMaybeTlsStream = WebSocketStream>; + +pub struct AsyncWebsocketClient where M: RawMutex, { - websocket: Arc>, + websocket: Arc>, websocket_base: Arc>>, status: PhantomData, } -impl Sink for AsyncWebsocketClient +impl Sink for AsyncWebsocketClient where - T: Sink + Unpin, M: RawMutex, { type Error = anyhow::Error; @@ -49,7 +53,7 @@ where fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { let mut guard = block_on(self.websocket.lock()); - match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { + match Pin::new(&mut *guard).start_send(tungstenite::Message::Text(item)) { Ok(()) => Ok(()), Err(error) => Err!(error), } @@ -80,9 +84,8 @@ where } } -impl Stream for AsyncWebsocketClient +impl Stream for AsyncWebsocketClient where - T: Stream> + Unpin, M: RawMutex, { type Item = Result; @@ -95,8 +98,8 @@ where match Pin::new(&mut *guard).poll_next(cx) { Poll::Ready(Some(item)) => match item { Ok(message) => match message { - TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), - TungsteniteMessage::Binary(response) => { + tungstenite::Message::Text(response) => Poll::Ready(Some(Ok(response))), + tungstenite::Message::Binary(response) => { let response_string = match String::from_utf8(response) { Ok(string) => string, Err(error) => { @@ -109,7 +112,7 @@ where }; Poll::Ready(Some(Ok(response_string))) } - TungsteniteMessage::Close(_) => Poll::Ready(Some(Err!( + tungstenite::Message::Close(_) => Poll::Ready(Some(Err!( XRPLWebsocketException::::Disconnected ))), _ => Poll::Ready(Some(Err!( @@ -118,17 +121,23 @@ where }, Err(error) => Poll::Ready(Some(Err!(error))), }, - Poll::Ready(None) => Poll::Ready(None), + Poll::Ready(None) => Poll::Ready(Some(Err!( + XRPLWebsocketException::::Disconnected + ))), Poll::Pending => Poll::Pending, } } } -impl AsyncWebsocketClient +impl AsyncWebsocketClient where M: RawMutex, { - pub async fn open(stream: T) -> Result> { + pub async fn open(uri: Url) -> Result> { + let stream = match tokio_tungstenite_connect_async(uri.to_string()).await { + Ok((stream, _)) => stream, + Err(error) => return Err!(error), + }; Ok(AsyncWebsocketClient { websocket: Arc::new(Mutex::new(stream)), websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), @@ -137,7 +146,31 @@ where } } -impl MessageHandler for AsyncWebsocketClient +impl AsyncWebsocketClient +where + M: RawMutex, +{ + pub async fn close(&self) -> Result<()> { + let mut websocket = self.websocket.lock().await; + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.close(); + match websocket.close(None).await { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } +} + +impl AsyncWebsocketClient +where + M: RawMutex, +{ + pub fn is_open(&self) -> bool { + core::any::type_name::() == core::any::type_name::() + } +} + +impl MessageHandler for AsyncWebsocketClient where M: RawMutex, { @@ -162,24 +195,16 @@ where } } -impl Client for AsyncWebsocketClient +impl Client for AsyncWebsocketClient where - T: Stream> - + Sink - + Unpin, M: RawMutex, { - async fn request_impl< - 'a: 'b, - 'b, - Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, - >( + async fn request_impl<'a: 'b, 'b>( &self, - mut request: Req, - ) -> Result> { + mut request: XRPLRequest<'a>, + ) -> Result> { // setup request future - let request_id = self.set_request_id::(&mut request); + let request_id = self.set_request_id(&mut request); let mut websocket_base = self.websocket_base.lock().await; websocket_base .setup_request_future(request_id.to_string()) @@ -191,7 +216,7 @@ where Err(error) => return Err!(error), }; if let Err(error) = websocket - .send(TungsteniteMessage::Text(request_string)) + .send(tungstenite::Message::Text(request_string)) .await { return Err!(error); @@ -200,7 +225,7 @@ where loop { let message = websocket.next().await; match message { - Some(Ok(TungsteniteMessage::Text(message))) => { + Some(Ok(tungstenite::Message::Text(message))) => { websocket_base.handle_message(message).await?; let message_opt = websocket_base .try_recv_request(request_id.to_string()) @@ -213,7 +238,7 @@ where return Ok(response); } } - Some(Ok(TungsteniteMessage::Binary(response))) => { + Some(Ok(tungstenite::Message::Binary(response))) => { let message = match String::from_utf8(response) { Ok(string) => string, Err(error) => { @@ -227,7 +252,7 @@ where Err(error) => return Err!(error), } } - Some(Ok(TungsteniteMessage::Close(_))) => { + Some(Ok(tungstenite::Message::Close(_))) => { return Err!(XRPLWebsocketException::::Disconnected) } Some(Ok(_)) => { diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 88b1161a..63052bb9 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -1,14 +1,14 @@ use core::fmt::Debug; use core::str::Utf8Error; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum XRPLWebsocketException { - #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + #[cfg(all(feature = "websocket-std", not(feature = "websocket")))] #[error("Unable to connect to websocket")] UnableToConnect(tokio_tungstenite::tungstenite::Error), // FramerError @@ -20,7 +20,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "websocket", not(feature = "websocket-std")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] @@ -29,7 +29,7 @@ pub enum XRPLWebsocketException { RxBufferTooSmall(usize), #[error("Unexpected message type")] UnexpectedMessageType, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "websocket", not(feature = "websocket-std")))] #[error("Embedded I/O error: {0:?}")] EmbeddedIoError(ErrorKind), #[error("Missing request channel sender.")] @@ -40,7 +40,7 @@ pub enum XRPLWebsocketException { InvalidMessage, } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] impl From> for XRPLWebsocketException { fn from(value: FramerError) -> Self { match value { @@ -55,7 +55,7 @@ impl From> for XRPLWebsocketException { } } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] impl EmbeddedIoError for XRPLWebsocketException { fn kind(&self) -> ErrorKind { match self { diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 4159742e..bd068976 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -1,57 +1,52 @@ -use core::fmt::Debug; - -use crate::{models::results::XRPLResponse, Err}; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use crate::{ + models::{requests::XRPLRequest, results::XRPLResponse}, + Err, +}; +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] use alloc::string::String; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] use alloc::string::ToString; use anyhow::Result; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] use core::fmt::Display; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] use futures::{Sink, SinkExt, Stream, StreamExt}; -use serde::{Deserialize, Serialize}; mod websocket_base; use websocket_base::MessageHandler; -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +mod _no_std; +#[cfg(feature = "websocket-codec")] pub mod codec; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -mod embedded_websocket; mod exceptions; pub use exceptions::XRPLWebsocketException; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tungstenite; +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +mod _std; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub use embedded_websocket::AsyncWebsocketClient; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tungstenite::AsyncWebsocketClient; +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +pub use _no_std::*; +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +pub use _std::*; pub struct WebsocketOpen; pub struct WebsocketClosed; #[allow(async_fn_in_trait)] pub trait XRPLWebsocketIO { - async fn xrpl_send(&mut self, message: Req) -> Result<()>; + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()>; - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>>; + async fn xrpl_receive(&mut self) -> Result>>; } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] impl XRPLWebsocketIO for T where ::Error: Display, { - async fn xrpl_send(&mut self, message: Req) -> Result<()> { + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { let message = match serde_json::to_string(&message) { Ok(message) => message, Err(error) => return Err!(error), @@ -63,12 +58,7 @@ where } } - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>> { + async fn xrpl_receive(&mut self) -> Result>> { let mut buffer = [0; 1024]; loop { match self.read(&mut buffer).await { @@ -96,12 +86,12 @@ where } } -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] impl XRPLWebsocketIO for T where T: Stream> + Sink + MessageHandler + Unpin, { - async fn xrpl_send(&mut self, message: Req) -> Result<()> { + async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { let message = match serde_json::to_string(&message) { Ok(message) => message, Err(error) => return Err!(error), @@ -112,12 +102,7 @@ where } } - async fn xrpl_receive< - Res: Serialize + for<'de> Deserialize<'de> + Debug, - Req: Serialize + for<'de> Deserialize<'de> + Debug, - >( - &mut self, - ) -> Result>> { + async fn xrpl_receive(&mut self) -> Result>> { match self.next().await { Some(Ok(item)) => { self.handle_message(item).await?; diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 55b0c5f3..92562b27 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,6 +1,6 @@ #[cfg(any( - feature = "tungstenite", - feature = "embedded-ws", + feature = "websocket-std", + feature = "websocket", feature = "json-rpc-std", feature = "json-rpc" ))] diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 3d81058f..e77e91c8 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -138,6 +138,338 @@ pub enum RequestMethod { Random, } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(untagged)] +pub enum XRPLRequest<'a> { + AccountChannels(AccountChannels<'a>), + AccountCurrencies(AccountCurrencies<'a>), + AccountInfo(AccountInfo<'a>), + AccountLines(AccountLines<'a>), + AccountNfts(AccountNfts<'a>), + AccountObjects(AccountObjects<'a>), + AccountOffers(AccountOffers<'a>), + AccountTx(AccountTx<'a>), + GatewayBalances(GatewayBalances<'a>), + NoRippleCheck(NoRippleCheck<'a>), + Submit(Submit<'a>), + SubmitMultisigned(SubmitMultisigned<'a>), + TransactionEntry(TransactionEntry<'a>), + Tx(Tx<'a>), + ChannelAuthorize(ChannelAuthorize<'a>), + ChannelVerify(ChannelVerify<'a>), + BookOffers(BookOffers<'a>), + DepositAuthorized(DepositAuthorized<'a>), + NftBuyOffers(NftBuyOffers<'a>), + NftSellOffers(NftSellOffers<'a>), + PathFind(PathFind<'a>), + RipplePathFind(RipplePathFind<'a>), + Ledger(Ledger<'a>), + LedgerClosed(LedgerClosed<'a>), + LedgerCurrent(LedgerCurrent<'a>), + LedgerData(LedgerData<'a>), + LedgerEntry(LedgerEntry<'a>), + Subscribe(Subscribe<'a>), + Unsubscribe(Unsubscribe<'a>), + Fee(Fee<'a>), + Manifest(Manifest<'a>), + ServerInfo(ServerInfo<'a>), + ServerState(ServerState<'a>), + Ping(Ping<'a>), + Random(Random<'a>), +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountChannels<'a>) -> Self { + XRPLRequest::AccountChannels(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountCurrencies<'a>) -> Self { + XRPLRequest::AccountCurrencies(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountInfo<'a>) -> Self { + XRPLRequest::AccountInfo(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountLines<'a>) -> Self { + XRPLRequest::AccountLines(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountNfts<'a>) -> Self { + XRPLRequest::AccountNfts(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountObjects<'a>) -> Self { + XRPLRequest::AccountObjects(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountOffers<'a>) -> Self { + XRPLRequest::AccountOffers(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: AccountTx<'a>) -> Self { + XRPLRequest::AccountTx(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: GatewayBalances<'a>) -> Self { + XRPLRequest::GatewayBalances(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: NoRippleCheck<'a>) -> Self { + XRPLRequest::NoRippleCheck(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Submit<'a>) -> Self { + XRPLRequest::Submit(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: SubmitMultisigned<'a>) -> Self { + XRPLRequest::SubmitMultisigned(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: TransactionEntry<'a>) -> Self { + XRPLRequest::TransactionEntry(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Tx<'a>) -> Self { + XRPLRequest::Tx(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ChannelAuthorize<'a>) -> Self { + XRPLRequest::ChannelAuthorize(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ChannelVerify<'a>) -> Self { + XRPLRequest::ChannelVerify(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: BookOffers<'a>) -> Self { + XRPLRequest::BookOffers(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: DepositAuthorized<'a>) -> Self { + XRPLRequest::DepositAuthorized(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: NftBuyOffers<'a>) -> Self { + XRPLRequest::NftBuyOffers(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: NftSellOffers<'a>) -> Self { + XRPLRequest::NftSellOffers(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: PathFind<'a>) -> Self { + XRPLRequest::PathFind(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: RipplePathFind<'a>) -> Self { + XRPLRequest::RipplePathFind(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Ledger<'a>) -> Self { + XRPLRequest::Ledger(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: LedgerClosed<'a>) -> Self { + XRPLRequest::LedgerClosed(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: LedgerCurrent<'a>) -> Self { + XRPLRequest::LedgerCurrent(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: LedgerData<'a>) -> Self { + XRPLRequest::LedgerData(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: LedgerEntry<'a>) -> Self { + XRPLRequest::LedgerEntry(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Subscribe<'a>) -> Self { + XRPLRequest::Subscribe(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Unsubscribe<'a>) -> Self { + XRPLRequest::Unsubscribe(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Fee<'a>) -> Self { + XRPLRequest::Fee(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Manifest<'a>) -> Self { + XRPLRequest::Manifest(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ServerInfo<'a>) -> Self { + XRPLRequest::ServerInfo(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ServerState<'a>) -> Self { + XRPLRequest::ServerState(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Ping<'a>) -> Self { + XRPLRequest::Ping(request) + } +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: Random<'a>) -> Self { + XRPLRequest::Random(request) + } +} + +impl<'a> Request<'a> for XRPLRequest<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + match self { + XRPLRequest::AccountChannels(request) => request.get_common_fields(), + XRPLRequest::AccountCurrencies(request) => request.get_common_fields(), + XRPLRequest::AccountInfo(request) => request.get_common_fields(), + XRPLRequest::AccountLines(request) => request.get_common_fields(), + XRPLRequest::AccountNfts(request) => request.get_common_fields(), + XRPLRequest::AccountObjects(request) => request.get_common_fields(), + XRPLRequest::AccountOffers(request) => request.get_common_fields(), + XRPLRequest::AccountTx(request) => request.get_common_fields(), + XRPLRequest::GatewayBalances(request) => request.get_common_fields(), + XRPLRequest::NoRippleCheck(request) => request.get_common_fields(), + XRPLRequest::Submit(request) => request.get_common_fields(), + XRPLRequest::SubmitMultisigned(request) => request.get_common_fields(), + XRPLRequest::TransactionEntry(request) => request.get_common_fields(), + XRPLRequest::Tx(request) => request.get_common_fields(), + XRPLRequest::ChannelAuthorize(request) => request.get_common_fields(), + XRPLRequest::ChannelVerify(request) => request.get_common_fields(), + XRPLRequest::BookOffers(request) => request.get_common_fields(), + XRPLRequest::DepositAuthorized(request) => request.get_common_fields(), + XRPLRequest::NftBuyOffers(request) => request.get_common_fields(), + XRPLRequest::NftSellOffers(request) => request.get_common_fields(), + XRPLRequest::PathFind(request) => request.get_common_fields(), + XRPLRequest::RipplePathFind(request) => request.get_common_fields(), + XRPLRequest::Ledger(request) => request.get_common_fields(), + XRPLRequest::LedgerClosed(request) => request.get_common_fields(), + XRPLRequest::LedgerCurrent(request) => request.get_common_fields(), + XRPLRequest::LedgerData(request) => request.get_common_fields(), + XRPLRequest::LedgerEntry(request) => request.get_common_fields(), + XRPLRequest::Subscribe(request) => request.get_common_fields(), + XRPLRequest::Unsubscribe(request) => request.get_common_fields(), + XRPLRequest::Fee(request) => request.get_common_fields(), + XRPLRequest::Manifest(request) => request.get_common_fields(), + XRPLRequest::ServerInfo(request) => request.get_common_fields(), + XRPLRequest::ServerState(request) => request.get_common_fields(), + XRPLRequest::Ping(request) => request.get_common_fields(), + XRPLRequest::Random(request) => request.get_common_fields(), + } + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + match self { + XRPLRequest::AccountChannels(request) => request.get_common_fields_mut(), + XRPLRequest::AccountCurrencies(request) => request.get_common_fields_mut(), + XRPLRequest::AccountInfo(request) => request.get_common_fields_mut(), + XRPLRequest::AccountLines(request) => request.get_common_fields_mut(), + XRPLRequest::AccountNfts(request) => request.get_common_fields_mut(), + XRPLRequest::AccountObjects(request) => request.get_common_fields_mut(), + XRPLRequest::AccountOffers(request) => request.get_common_fields_mut(), + XRPLRequest::AccountTx(request) => request.get_common_fields_mut(), + XRPLRequest::GatewayBalances(request) => request.get_common_fields_mut(), + XRPLRequest::NoRippleCheck(request) => request.get_common_fields_mut(), + XRPLRequest::Submit(request) => request.get_common_fields_mut(), + XRPLRequest::SubmitMultisigned(request) => request.get_common_fields_mut(), + XRPLRequest::TransactionEntry(request) => request.get_common_fields_mut(), + XRPLRequest::Tx(request) => request.get_common_fields_mut(), + XRPLRequest::ChannelAuthorize(request) => request.get_common_fields_mut(), + XRPLRequest::ChannelVerify(request) => request.get_common_fields_mut(), + XRPLRequest::BookOffers(request) => request.get_common_fields_mut(), + XRPLRequest::DepositAuthorized(request) => request.get_common_fields_mut(), + XRPLRequest::NftBuyOffers(request) => request.get_common_fields_mut(), + XRPLRequest::NftSellOffers(request) => request.get_common_fields_mut(), + XRPLRequest::PathFind(request) => request.get_common_fields_mut(), + XRPLRequest::RipplePathFind(request) => request.get_common_fields_mut(), + XRPLRequest::Ledger(request) => request.get_common_fields_mut(), + XRPLRequest::LedgerClosed(request) => request.get_common_fields_mut(), + XRPLRequest::LedgerCurrent(request) => request.get_common_fields_mut(), + XRPLRequest::LedgerData(request) => request.get_common_fields_mut(), + XRPLRequest::LedgerEntry(request) => request.get_common_fields_mut(), + XRPLRequest::Subscribe(request) => request.get_common_fields_mut(), + XRPLRequest::Unsubscribe(request) => request.get_common_fields_mut(), + XRPLRequest::Fee(request) => request.get_common_fields_mut(), + XRPLRequest::Manifest(request) => request.get_common_fields_mut(), + XRPLRequest::ServerInfo(request) => request.get_common_fields_mut(), + XRPLRequest::ServerState(request) => request.get_common_fields_mut(), + XRPLRequest::Ping(request) => request.get_common_fields_mut(), + XRPLRequest::Random(request) => request.get_common_fields_mut(), + } + } +} + /// The base fields for all request models. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, new)] diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs index 59284566..7ee878a1 100644 --- a/src/models/results/fee.rs +++ b/src/models/results/fee.rs @@ -2,12 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::models::amount::XRPAmount; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Fee<'a> { pub drops: Drops<'a>, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Drops<'a> { pub base_fee: XRPAmount<'a>, pub median_fee: XRPAmount<'a>, diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 5c74b564..17770767 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,8 +1,27 @@ -use alloc::{borrow::Cow, string::ToString, vec::Vec}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use alloc::{ + borrow::Cow, + string::{String, ToString}, + vec::Vec, +}; +use serde::{Deserialize, Serialize}; mod fee; -pub use fee::{Fee as FeeResult, *}; +pub use fee::{Fee, *}; +use serde_json::{Map, Value}; + +use super::requests::XRPLRequest; +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum XRPLResult<'a> { + Fee(Fee<'a>), + Custom(Value), +} + +impl<'a> From> for XRPLResult<'a> { + fn from(fee: Fee<'a>) -> Self { + XRPLResult::Fee(fee) + } +} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -20,26 +39,26 @@ pub enum ResponseType { } #[derive(Debug, Clone, Serialize)] -pub struct XRPLResponse<'a, Res, Req> { +pub struct XRPLResponse<'a> { pub id: Option>, pub error: Option>, pub error_code: Option, pub error_message: Option>, pub forwarded: Option, - pub request: Option, - pub result: Option, + pub request: Option>, + pub result: Option>, pub status: Option, pub r#type: Option, pub warning: Option>, pub warnings: Option>>, } -impl<'a, 'de, Res, Req> Deserialize<'de> for XRPLResponse<'a, Res, Req> -where - Res: DeserializeOwned, - Req: DeserializeOwned, -{ - fn deserialize(deserializer: D) -> Result +fn is_subscription_stream_item(item: &Map) -> bool { + item.get("result").is_none() && item.get("error_code").is_none() +} + +impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { + fn deserialize(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -48,48 +67,65 @@ where if map.is_empty() { return Err(serde::de::Error::custom("Empty response")); } - Ok(XRPLResponse { - id: map.remove("id").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - error: map.remove("error").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - error_code: map - .remove("error_code") - .and_then(|v| v.as_i64()) - .map(|v| v as i32), - error_message: map.remove("error_message").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), - request: map - .remove("request") - .map(|v| serde_json::from_value(v).unwrap()), - result: map - .remove("result") - .map(|v| serde_json::from_value(v).unwrap()), - status: map - .remove("status") - .map(|v| serde_json::from_value(v).unwrap()), - r#type: map - .remove("type") - .map(|v| serde_json::from_value(v).unwrap()), - warning: map.remove("warning").map(|item| match item.as_str() { - Some(item_str) => Cow::Owned(item_str.to_string()), - None => Cow::Borrowed(""), - }), - warnings: map - .remove("warnings") - .and_then(|v| serde_json::from_value(v).ok()), - }) + if is_subscription_stream_item(&map) { + let map_as_value = Value::Object(map); + Ok(XRPLResponse { + id: None, + error: None, + error_code: None, + error_message: None, + forwarded: None, + request: None, + result: serde_json::from_value(map_as_value).map_err(serde::de::Error::custom)?, + status: None, + r#type: None, + warning: None, + warnings: None, + }) + } else { + Ok(XRPLResponse { + id: map.remove("id").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error: map.remove("error").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error_code: map + .remove("error_code") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + error_message: map.remove("error_message").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), + request: map + .remove("request") + .map(|v| serde_json::from_value(v).unwrap()), + result: map + .remove("result") + .map(|v| serde_json::from_value(v).unwrap()), + status: map + .remove("status") + .map(|v| serde_json::from_value(v).unwrap()), + r#type: map + .remove("type") + .map(|v| serde_json::from_value(v).unwrap()), + warning: map.remove("warning").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + warnings: map + .remove("warnings") + .and_then(|v| serde_json::from_value(v).ok()), + }) + } } } -impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { +impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) } From 15395b9d08519cf24d33e872e5a28c027ab620ae Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 19 Jul 2024 16:47:26 +0200 Subject: [PATCH 071/113] fix client tests --- tests/common/mod.rs | 29 +++++++---------------- tests/integration/clients/mod.rs | 40 +++++++++++--------------------- tests/integration_tests.rs | 27 ++++++++++++++++----- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index be43e04e..d3fe32d6 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,31 +1,18 @@ mod constants; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] mod tungstenite_clients { use super::constants::*; use anyhow::anyhow; use anyhow::Result; - use tokio::net::TcpStream; - use tokio_tungstenite::connect_async; - use tokio_tungstenite::MaybeTlsStream; - use tokio_tungstenite::WebSocketStream; use xrpl::asynch::clients::AsyncWebsocketClient; use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; - pub async fn connect_to_wss_tungstinite_test_net<'a>() -> Result< - AsyncWebsocketClient< - WebSocketStream>, - SingleExecutorMutex, - WebsocketOpen, - >, - > { - let stream = connect_async(XRPL_WSS_TEST_NET.to_string()) - .await - .unwrap() - .0; - match AsyncWebsocketClient::open(stream).await { + pub async fn connect_to_wss_tungstinite_test_net<'a>( + ) -> Result> { + match AsyncWebsocketClient::open(XRPL_WSS_TEST_NET.parse().unwrap()).await { Ok(websocket) => { - // assert!(websocket.is_open()); + assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), @@ -33,7 +20,7 @@ mod tungstenite_clients { } } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] mod embedded_ws_clients { use super::constants::*; use anyhow::anyhow; @@ -70,7 +57,7 @@ mod embedded_ws_clients { } } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] pub use embedded_ws_clients::*; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] pub use tungstenite_clients::*; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d105f12f..a116b0f4 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,45 +1,39 @@ use anyhow::Result; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] pub async fn test_websocket_tungstenite_test_net() -> Result<()> { use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{ - asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, - }; + use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::Fee}; let mut websocket = connect_to_wss_tungstinite_test_net().await?; let fee = Fee::new(None); - websocket.xrpl_send(fee).await.unwrap(); - let message = websocket - .xrpl_receive::, Fee<'_>>() - .await - .unwrap(); + websocket.xrpl_send(fee.into()).await.unwrap(); + let message = websocket.xrpl_receive().await.unwrap(); assert!(message.unwrap().result.is_some()); Ok(()) } -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] pub async fn test_websocket_tungstenite_request() -> Result<()> { use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; + use xrpl::{asynch::clients::AsyncClient, models::requests::Fee}; let websocket = connect_to_wss_tungstinite_test_net().await?; let fee = Fee::new(None); - let message = websocket.request::, _>(fee).await.unwrap(); + let message = websocket.request(fee.into()).await.unwrap(); assert!(message.result.is_some()); Ok(()) } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] pub async fn test_embedded_websocket_echo() -> Result<()> { use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::XRPLWebsocketIO; use xrpl::models::requests::Fee; - use xrpl::models::results::FeeResult; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await @@ -47,22 +41,18 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { let framed = Framed::new(tcp_stream, Codec); let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let fee = Fee::new(None); - websocket.xrpl_send(fee).await?; - let _ = websocket - .xrpl_receive::, Fee<'_>>() - .await - .unwrap(); + websocket.xrpl_send(fee.into()).await?; + let _ = websocket.xrpl_receive().await.unwrap(); Ok(()) } -#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] pub async fn test_embedded_websocket_request() -> Result<()> { use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::AsyncClient; use xrpl::models::requests::Fee; - use xrpl::models::results::FeeResult; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await @@ -70,7 +60,7 @@ pub async fn test_embedded_websocket_request() -> Result<()> { let framed = Framed::new(tcp_stream, Codec); let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let fee = Fee::new(None); - let _res = websocket.request::(fee).await?; + let _res = websocket.request(fee.into()).await?; Ok(()) } @@ -79,14 +69,10 @@ pub async fn test_json_rpc_std() -> Result<()> { use xrpl::{ asynch::clients::{AsyncClient, AsyncJsonRpcClient, SingleExecutorMutex}, models::requests::Fee, - models::results::FeeResult, }; let client: AsyncJsonRpcClient = AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); - let fee_result = client - .request::(Fee::new(None)) - .await - .unwrap(); + let fee_result = client.request(Fee::new(None).into()).await.unwrap(); assert!(fee_result.result.is_some()); Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 23d401ff..5b604d0a 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,23 +5,38 @@ mod integration; use anyhow::Result; -#[cfg(any(feature = "tungstenite", all(feature = "embedded-ws", feature = "std")))] +#[cfg(any( + feature = "websocket-std", + all(feature = "websocket", feature = "std") +))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { - #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + #[cfg(all(feature = "websocket-std", not(feature = "websocket")))] return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] + #[cfg(all( + feature = "websocket", + feature = "std", + not(feature = "websocket-std") + ))] return integration::clients::test_embedded_websocket_echo().await; #[allow(unreachable_code)] Ok(()) } -#[cfg(any(feature = "tungstenite", feature = "embedded-ws", feature = "std"))] +#[cfg(any(feature = "websocket-std", feature = "websocket", feature = "std"))] #[tokio::test] async fn test_asynch_clients_request() -> Result<()> { - #[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] + #[cfg(all( + feature = "websocket-std", + feature = "std", + not(feature = "websocket") + ))] return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] + #[cfg(all( + feature = "websocket", + feature = "std", + not(feature = "websocket-std") + ))] return integration::clients::test_embedded_websocket_request().await; #[allow(unreachable_code)] Ok(()) From bbc2066bf4165c4dfafff432d2b8e72d45d6f7aa Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 19 Jul 2024 17:06:33 +0200 Subject: [PATCH 072/113] fix tests --- src/asynch/clients/json_rpc/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 348c87df..95a8c4f2 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -144,7 +144,7 @@ mod _no_std { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, - ) -> Result> { + ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; let request_buf = request_json_rpc.as_bytes(); let mut rx_buffer = [0; BUF]; @@ -159,7 +159,7 @@ mod _no_std { { Err!(XRPLJsonRpcException::ReqwlessError) } else { - match serde_json::from_slice::>(&rx_buffer) { + match serde_json::from_slice::>(&rx_buffer) { Ok(response) => Ok(response), Err(error) => Err!(error), } From 3dc8cf06ebcda26e67ec646780216928b5a27d49 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 19 Jul 2024 17:43:55 +0200 Subject: [PATCH 073/113] rename features in github workflow --- .github/workflows/unit_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index a0a38090..c4fb65d4 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --no-default-features --features core,models,tungstenite + args: --release --no-default-features --features core,models,websocket-std - uses: actions-rs/cargo@v1 with: command: test @@ -33,8 +33,8 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models,tungstenite + args: --no-default-features --features core,models,websocket-std - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models,embedded-ws + args: --no-default-features --features core,models,websocket From 4dfcc101b40d583c806e01253a2045a0e45aa2d3 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 9 Aug 2024 16:32:44 +0200 Subject: [PATCH 074/113] update dependencies; add no_std websocket client example; fix some feature issues --- Cargo.toml | 91 +++++++++++---------- examples/std/Cargo.toml | 9 +- examples/std/src/bin/tokio/net/websocket.rs | 43 ++++++++++ rust-toolchain | 1 - src/asynch/clients/websocket/_no_std.rs | 2 +- src/asynch/clients/websocket/_std.rs | 2 +- src/asynch/clients/websocket/mod.rs | 2 +- src/core/keypairs/algorithms.rs | 45 +++++----- src/lib.rs | 12 ++- src/utils/mod.rs | 2 +- tests/common/mod.rs | 5 +- 11 files changed, 138 insertions(+), 76 deletions(-) create mode 100644 examples/std/src/bin/tokio/net/websocket.rs delete mode 100644 rust-toolchain diff --git a/Cargo.toml b/Cargo.toml index eb90d848..96a48766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,11 @@ proc-macro = true [dependencies] lazy_static = "1.4.0" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } rand_hc = "0.3.1" ripemd = "0.1.1" -ed25519-dalek = "1.0.1" -secp256k1 = { version = "0.27.0", default-features = false, features = [ +ed25519-dalek = { version = "2.1.1" } +secp256k1 = { version = "0.29.0", default-features = false, features = [ "alloc", ] } bs58 = { version = "0.5.0", default-features = false, features = [ @@ -35,8 +35,8 @@ bs58 = { version = "0.5.0", default-features = false, features = [ ] } indexmap = { version = "2.0.0", features = ["serde"] } regex = { version = "1.5.4", default-features = false } -strum = { version = "0.25.0", default-features = false } -strum_macros = { version = "0.25.2", default-features = false } +strum = { version = "0.26.3", default-features = false } +strum_macros = { version = "0.26.4", default-features = false } crypto-bigint = { version = "0.5.1" } rust_decimal = { version = "1.17.0", default-features = false, features = [ "serde", @@ -56,70 +56,75 @@ serde_repr = "0.1" zeroize = "1.5.7" hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } -derive-new = { version = "0.5.9", default-features = false } +derive-new = { version = "0.6.0", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } # networking -url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.30", default-features = false, features = [ - "alloc", -], optional = true } -embassy-sync = { version = "0.6.0", default-features = false } -rand_core = { version = "0.6.4", default-features = false } -# std websocket +url = { version = "2.2.1", default-features = false, optional = true } +# websocket +embassy-sync = { version = "0.6.0", optional = true } +embedded-io-async = { version = "0.6.1", optional = true } +embedded-websocket = { version = "0.9.3", default-features = false, optional = true } +futures = { version = "0.3.30", optional = true } +# websocket-codec +bytes = { version = "1.7.1", default-features = false, optional = true } +tokio-util = { version = "0.7.11", features = ["codec"], optional = true } +# websocket-std embassy-futures = { version = "0.1.1", optional = true } -tokio-tungstenite = { version = "0.23.1", features = [ - "native-tls", +tokio = { version = "1.0", default-features = false, features = [ + "net", ], optional = true } -tungstenite = { version = "0.23.0", optional = true } -tokio-util = { version = "0.7.11", features = ["codec"], optional = true } -tokio = { version = "1.0", features = ["full"], optional = true } -# no-std websocket -bytes = { version = "1.4.0", default-features = false, optional = true } -embedded-io-async = { version = "0.6.1", optional = true } -embedded-websocket = { version = "0.9.3", optional = true } -# std json-rpc -reqwest = { version = "0.12.5", features = ["json"], optional = true } -# no-std json-rpc -reqwless = { version = "0.12.0", optional = true } +tokio-tungstenite = { version = "0.23.1", optional = true } embedded-nal-async = { version = "0.7.1", optional = true } +# json-rpc +reqwless = { version = "0.12.1", optional = true } +# json-rpc-std +reqwest = { version = "0.12.5", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } -tokio-util = { version = "0.7.11", features = ["codec"] } [[bench]] name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "websocket-std"] -models = ["core", "transactions", "requests", "ledger", "results"] -transactions = ["core", "amounts", "currencies"] -requests = ["core", "amounts", "currencies"] -results = ["core", "amounts", "currencies"] -ledger = ["core", "amounts", "currencies"] -amounts = ["core"] -currencies = ["core"] -json-rpc = ["url", "reqwless", "embedded-nal-async"] -json-rpc-std = ["url", "reqwest"] -websocket = ["url", "futures", "embedded-websocket", "embedded-io-async"] -websocket-codec = ["bytes", "tokio-util"] +default = ["std", "core", "models", "websocket", "websocket-codec"] +models = ["transactions", "ledger", "requests", "results"] +transactions = ["amounts", "currencies"] +requests = ["amounts", "currencies"] +results = ["amounts", "currencies"] +ledger = ["amounts", "currencies"] +amounts = [] +currencies = [] +json-rpc = ["requests", "results", "url"] +json-rpc-std = ["requests", "results", "url"] +websocket = [ + "requests", + "results", + "url", + "embassy-sync", + "embedded-io-async", + "embedded-websocket", + "futures", +] +websocket-codec = ["requests", "results", "bytes", "tokio-util"] websocket-std = [ + "requests", + "results", "url", + "embassy-sync", "futures", - "tungstenite", + "embassy-futures", "tokio", "tokio-tungstenite", - "embassy-futures", ] core = ["utils"] +wallet = ["core"] utils = [] std = [ - "embedded-websocket/std", - "futures/std", "rand/std", "regex/std", "chrono/std", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 27aa2801..43a4fc85 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -7,8 +7,9 @@ edition = "2021" [dependencies] xrpl-rust = { path = "../.." } -tokio = { version = "1.0", features = ["full"], optional = true } -tokio-tungstenite = "0.23.1" +tokio = { version = "1.0", features = ["full"] } +rand = "0.8.5" +tokio-util = { version = "0.7.11", features = ["codec"] } [[bin]] name = "wallet_from_seed" @@ -25,6 +26,10 @@ name = "tungstenite" path = "src/bin/tokio/net/tungstenite.rs" required-features = ["tokio"] +[[bin]] +name = "websocket" +path = "src/bin/tokio/net/websocket.rs" + [[bin]] name = "sign_request" path = "src/bin/transaction/sign_transaction.rs" diff --git a/examples/std/src/bin/tokio/net/websocket.rs b/examples/std/src/bin/tokio/net/websocket.rs new file mode 100644 index 00000000..6abf15df --- /dev/null +++ b/examples/std/src/bin/tokio/net/websocket.rs @@ -0,0 +1,43 @@ +/// Utilizing the no_std `AsyncWebsocketClient` of xrpl-rust with a tokio tcp stream. +use rand::rngs::OsRng; +use tokio::net::TcpStream; +use tokio_util::codec::Framed; +use xrpl::asynch::clients::{websocket::codec::Codec, AsyncWebsocketClient, SingleExecutorMutex}; +use xrpl::models::requests::{StreamParameter, Subscribe}; + +#[tokio::main] +async fn main() { + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let framed_stream = Framed::new(tcp_stream, Codec); + let rng = OsRng; + let url = "wss://xrplcluster.com/".parse().unwrap(); + let mut websocket = AsyncWebsocketClient::< + 4096, + Framed, + _, + _, + OsRng, + SingleExecutorMutex, + >::open(rng, framed_stream, url) + .await + .unwrap(); + + let subscribe = Subscribe::new( + Some("my_id".into()), + None, + None, + None, + Some(vec![StreamParameter::Ledger]), + None, + None, + None, + ); + websocket.xrpl_send(subscribe.into()).await.unwrap(); + loop { + let account_info_echo = websocket.xrpl_receive().await.unwrap().unwrap(); + println!("subscription message: {:?}", account_info_echo); + // break; + } +} diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index bf867e0a..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 1e5d9838..7552b3a2 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -18,7 +18,7 @@ use embedded_websocket::{ }; use futures::Sink; use futures::Stream; -use rand_core::RngCore; +use rand::RngCore; use url::Url; use super::{WebsocketClosed, WebsocketOpen}; diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index 35fbce05..dcb906ac 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -17,7 +17,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use futures::{Sink, SinkExt, Stream, StreamExt}; use tokio::net::TcpStream; -use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +use tokio_tungstenite::{tungstenite, MaybeTlsStream, WebSocketStream}; use url::Url; use tokio_tungstenite::connect_async as tokio_tungstenite_connect_async; 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/core/keypairs/algorithms.rs b/src/core/keypairs/algorithms.rs index f905cbe2..72284424 100644 --- a/src/core/keypairs/algorithms.rs +++ b/src/core/keypairs/algorithms.rs @@ -18,7 +18,9 @@ use core::convert::TryInto; use core::str::FromStr; use crypto_bigint::Encoding; use crypto_bigint::U256; +use ed25519_dalek::ed25519::signature::SignerMut; use ed25519_dalek::Verifier; +use ed25519_dalek::SECRET_KEY_LENGTH; use secp256k1::ecdsa; use secp256k1::Scalar; @@ -58,8 +60,8 @@ impl Secp256k1 { } /// Hash the message to prevent insecure signing. - fn _get_message(message: &[u8]) -> Result { - secp256k1::Message::from_slice(&sha512_first_half(message)) + fn _get_message(message: &[u8]) -> secp256k1::Message { + secp256k1::Message::from_digest(sha512_first_half(message)) } /// Determing if the provided secret key is valid. @@ -143,7 +145,7 @@ impl Ed25519 { } /// Hex encode the public key. - fn _public_key_to_str(key: ed25519_dalek::PublicKey) -> String { + fn _public_key_to_str(key: ed25519_dalek::VerifyingKey) -> String { hex::encode(key.as_ref()) } @@ -155,7 +157,7 @@ impl Ed25519 { /// Format the public and private keys. fn _format_keys( - public: ed25519_dalek::PublicKey, + public: ed25519_dalek::VerifyingKey, private: ed25519_dalek::SecretKey, ) -> (String, String) { ( @@ -265,7 +267,7 @@ impl CryptoImplementation for Secp256k1 { private_key: &str, ) -> Result, XRPLKeypairsException> { let secp = secp256k1::Secp256k1::::signing_only(); - let message = Self::_get_message(message_bytes)?; + let message = Self::_get_message(message_bytes); let trimmed_key = private_key.trim_start_matches(SECP256K1_PREFIX); let private = secp256k1::SecretKey::from_str(trimmed_key)?; let signature = secp.sign_ecdsa(&message, &private); @@ -307,8 +309,8 @@ impl CryptoImplementation for Secp256k1 { let sig = ecdsa::Signature::from_der(&value); let public = secp256k1::PublicKey::from_str(public_key); - if let (&Ok(m), &Ok(s), &Ok(p)) = (&msg.as_ref(), &sig.as_ref(), &public.as_ref()) { - secp.verify_ecdsa(m, s, p).is_ok() + if let (&Ok(s), &Ok(p)) = (&sig.as_ref(), &public.as_ref()) { + secp.verify_ecdsa(&msg, s, p).is_ok() } else { false } @@ -367,8 +369,9 @@ impl CryptoImplementation for Ed25519 { }) } else { let raw_private = sha512_first_half(decoded_seed); - let private = ed25519_dalek::SecretKey::from_bytes(&raw_private)?; - let public = ed25519_dalek::PublicKey::from(&private); + let private: [u8; SECRET_KEY_LENGTH] = ed25519_dalek::SecretKey::from(raw_private); + let signing_key: ed25519_dalek::SigningKey = private.into(); + let public = (&signing_key).into(); Ok(Ed25519::_format_keys(public, private)) } @@ -398,25 +401,22 @@ impl CryptoImplementation for Ed25519 { /// 182, 130, 224, 208, 104, 247, 55,73, 117, 14, /// ]; /// - /// let signing: Option> = match Ed25519.sign( + /// let signing: Option> = Some(Ed25519.sign( /// message, /// private_key, - /// ) { - /// Ok(signature) => Some(signature), - /// Err(e) => match e { - /// XRPLKeypairsException::ED25519Error => None, - /// _ => None, - /// }, - /// }; + /// ).unwrap()); /// /// assert_eq!(Some(signature), signing); /// ``` fn sign(&self, message: &[u8], private_key: &str) -> Result, XRPLKeypairsException> { let raw_private = hex::decode(&private_key[ED25519_PREFIX.len()..])?; - let private = ed25519_dalek::SecretKey::from_bytes(&raw_private)?; - let expanded_private = ed25519_dalek::ExpandedSecretKey::from(&private); - let public = ed25519_dalek::PublicKey::from(&private); - let signature: ed25519_dalek::Signature = expanded_private.sign(message, &public); + let raw_private_slice: &[u8; SECRET_KEY_LENGTH] = raw_private + .as_slice() + .try_into() + .map_err(|_| XRPLKeypairsException::InvalidSecret)?; + let private: ed25519_dalek::SecretKey = (*raw_private_slice).into(); + let mut signing_key: ed25519_dalek::SigningKey = private.into(); + let signature = signing_key.sign(message); Ok(signature.to_bytes().to_vec()) } @@ -455,7 +455,8 @@ impl CryptoImplementation for Ed25519 { }; if let (Ok(rpub), Ok(dsig)) = (raw_public, decoded_sig) { - let public = ed25519_dalek::PublicKey::from_bytes(&rpub); + let rpub = rpub.as_slice().try_into().unwrap(); + let public = ed25519_dalek::VerifyingKey::from_bytes(rpub); if dsig.len() != ED25519_SIGNATURE_LENGTH { return false; diff --git a/src/lib.rs b/src/lib.rs index 481b4225..5c81de38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,13 +30,21 @@ pub mod constants; #[cfg(feature = "core")] pub mod core; pub mod macros; -#[cfg(feature = "models")] +#[cfg(any( + feature = "currencies", + feature = "amounts", + feature = "requests", + feature = "results", + feature = "ledger", + feature = "transactions", +))] pub mod models; #[cfg(feature = "utils")] pub mod utils; +#[cfg(feature = "wallet")] pub mod wallet; -pub extern crate indexmap; +// pub extern crate indexmap; pub extern crate serde_json; mod _anyhow; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9fff8a72..3a076c45 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -69,7 +69,7 @@ pub fn is_iso_hex(value: &str) -> bool { } /// Generate a random id. -pub fn get_random_id(rng: &mut T) -> String { +pub fn get_random_id(rng: &mut T) -> String { let id: u32 = rng.gen(); id.to_string() } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d3fe32d6..e6ad0d4d 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -25,6 +25,7 @@ mod embedded_ws_clients { use super::constants::*; use anyhow::anyhow; use anyhow::Result; + use rand::rngs::OsRng; use std::io; use tokio::net::TcpStream; use tokio_util::codec::Framed; @@ -40,12 +41,12 @@ mod embedded_ws_clients { Framed, Vec, io::Error, - rand_core::OsRng, + OsRng, SingleExecutorMutex, WebsocketOpen, >, > { - let rng = rand_core::OsRng; + let rng = OsRng {}; let url = ECHO_WS_SERVER.parse().unwrap(); match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { From 5241758c4add9d034d81e94d5723d34e9eca839f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 9 Aug 2024 16:51:20 +0200 Subject: [PATCH 075/113] fix default features --- Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96a48766..d1164f66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,10 @@ lazy_static = "1.4.0" sha2 = { version = "0.10.2", default-features = false } rand_hc = "0.3.1" ripemd = "0.1.1" -ed25519-dalek = { version = "2.1.1" } +ed25519-dalek = { version = "2.1.1", default-features = false, features = [ + "alloc", + "zeroize", +] } secp256k1 = { version = "0.29.0", default-features = false, features = [ "alloc", ] } @@ -91,7 +94,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "websocket", "websocket-codec"] +default = ["std", "utils", "wallet", "models", "websocket"] models = ["transactions", "ledger", "requests", "results"] transactions = ["amounts", "currencies"] requests = ["amounts", "currencies"] From 571495b30e2a3d10015485a77227984f0aecabfe 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 076/113] add transaction auto filling as in #76 (#80) --- .cargo-husky/hooks/pre-commit | 5 +- .github/workflows/unit_test.yml | 6 +- Cargo.toml | 99 +++--- 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/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 +- 52 files changed, 1290 insertions(+), 179 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 d1164f66..ed8569c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,11 @@ proc-macro = true [dependencies] lazy_static = "1.4.0" -sha2 = { version = "0.10.2", default-features = false } +sha2 = "0.10.2" rand_hc = "0.3.1" ripemd = "0.1.1" -ed25519-dalek = { version = "2.1.1", default-features = false, features = [ - "alloc", - "zeroize", -] } -secp256k1 = { version = "0.29.0", default-features = false, features = [ +ed25519-dalek = "1.0.1" +secp256k1 = { version = "0.27.0", default-features = false, features = [ "alloc", ] } bs58 = { version = "0.5.0", default-features = false, features = [ @@ -38,8 +35,8 @@ bs58 = { version = "0.5.0", default-features = false, features = [ ] } indexmap = { version = "2.0.0", features = ["serde"] } regex = { version = "1.5.4", default-features = false } -strum = { version = "0.26.3", default-features = false } -strum_macros = { version = "0.26.4", default-features = false } +strum = { version = "0.25.0", default-features = false } +strum_macros = { version = "0.25.2", default-features = false } crypto-bigint = { version = "0.5.1" } rust_decimal = { version = "1.17.0", default-features = false, features = [ "serde", @@ -59,75 +56,81 @@ serde_repr = "0.1" zeroize = "1.5.7" hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } -derive-new = { version = "0.6.0", default-features = false } +derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } # networking -url = { version = "2.2.1", default-features = false, optional = true } -# websocket -embassy-sync = { version = "0.6.0", optional = true } -embedded-io-async = { version = "0.6.1", optional = true } -embedded-websocket = { version = "0.9.3", default-features = false, optional = true } -futures = { version = "0.3.30", optional = true } -# websocket-codec -bytes = { version = "1.7.1", default-features = false, optional = true } -tokio-util = { version = "0.7.11", features = ["codec"], optional = true } -# websocket-std +url = { version = "2.2.2", default-features = false, optional = true } +futures = { version = "0.3.30", default-features = false, features = [ + "alloc", +], optional = true } +embassy-sync = { version = "0.6.0", default-features = false } +rand_core = { version = "0.6.4", default-features = false } +# std websocket embassy-futures = { version = "0.1.1", optional = true } -tokio = { version = "1.0", default-features = false, features = [ - "net", +tokio-tungstenite = { version = "0.23.1", features = [ + "native-tls", ], optional = true } -tokio-tungstenite = { version = "0.23.1", optional = true } -embedded-nal-async = { version = "0.7.1", optional = true } -# json-rpc -reqwless = { version = "0.12.1", optional = true } -# json-rpc-std +tungstenite = { version = "0.23.0", optional = true } +tokio-util = { version = "0.7.11", features = ["codec"], optional = true } +tokio = { version = "1.0", features = ["full"], optional = true } +# no-std websocket +bytes = { version = "1.4.0", default-features = false, optional = true } +embedded-io-async = { version = "0.6.1", optional = true } +embedded-websocket = { version = "0.9.3", optional = true } +# std json-rpc reqwest = { version = "0.12.5", features = ["json"], optional = true } +# no-std json-rpc +reqwless = { version = "0.12.0", optional = true } +embedded-nal-async = { version = "0.7.1", optional = true } [dev-dependencies] criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } +tokio-util = { version = "0.7.11", features = ["codec"] } [[bench]] name = "benchmarks" harness = false [features] -default = ["std", "utils", "wallet", "models", "websocket"] -models = ["transactions", "ledger", "requests", "results"] -transactions = ["amounts", "currencies"] -requests = ["amounts", "currencies"] -results = ["amounts", "currencies"] -ledger = ["amounts", "currencies"] -amounts = [] -currencies = [] -json-rpc = ["requests", "results", "url"] -json-rpc-std = ["requests", "results", "url"] -websocket = [ +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", - "url", - "embassy-sync", - "embedded-io-async", - "embedded-websocket", - "futures", + "transactions", + "ledger", ] -websocket-codec = ["requests", "results", "bytes", "tokio-util"] +amounts = ["core"] +currencies = ["core"] +json-rpc = ["url", "reqwless", "embedded-nal-async"] +json-rpc-std = ["url", "reqwest"] +websocket = ["url", "futures", "embedded-websocket", "embedded-io-async"] +websocket-codec = ["bytes", "tokio-util"] websocket-std = [ - "requests", - "results", "url", - "embassy-sync", "futures", - "embassy-futures", + "tungstenite", "tokio", "tokio-tungstenite", + "embassy-futures", ] core = ["utils"] -wallet = ["core"] utils = [] std = [ + "embedded-websocket/std", + "futures/std", "rand/std", "regex/std", "chrono/std", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 43a4fc85..d6b0dd90 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -22,7 +22,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/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 5c81de38..90792129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,20 +31,19 @@ pub mod constants; pub mod core; pub mod macros; #[cfg(any( - feature = "currencies", feature = "amounts", + feature = "currencies", + feature = "ledger", feature = "requests", feature = "results", - feature = "ledger", - feature = "transactions", + feature = "transactions" ))] pub mod models; #[cfg(feature = "utils")] pub mod utils; -#[cfg(feature = "wallet")] pub mod wallet; -// pub extern crate indexmap; +pub extern crate indexmap; pub extern crate serde_json; mod _anyhow; 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(()) From abc1fc43007a670bbd620ec4ed075c00498c3ef6 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 15 Aug 2024 14:53:36 +0000 Subject: [PATCH 077/113] fix testing --- Cargo.toml | 101 ++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ed8569c1..e11ad5a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,14 @@ proc-macro = true [dependencies] lazy_static = "1.4.0" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } rand_hc = "0.3.1" ripemd = "0.1.1" -ed25519-dalek = "1.0.1" -secp256k1 = { version = "0.27.0", default-features = false, features = [ +ed25519-dalek = { version = "2.1.1", default-features = false, features = [ + "alloc", + "zeroize", +] } +secp256k1 = { version = "0.29.0", default-features = false, features = [ "alloc", ] } bs58 = { version = "0.5.0", default-features = false, features = [ @@ -35,8 +38,8 @@ bs58 = { version = "0.5.0", default-features = false, features = [ ] } indexmap = { version = "2.0.0", features = ["serde"] } regex = { version = "1.5.4", default-features = false } -strum = { version = "0.25.0", default-features = false } -strum_macros = { version = "0.25.2", default-features = false } +strum = { version = "0.26.3", default-features = false } +strum_macros = { version = "0.26.4", default-features = false } crypto-bigint = { version = "0.5.1" } rust_decimal = { version = "1.17.0", default-features = false, features = [ "serde", @@ -56,81 +59,77 @@ serde_repr = "0.1" zeroize = "1.5.7" hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } -derive-new = { version = "0.5.9", default-features = false } +derive-new = { version = "0.6.0", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } # networking -url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.30", default-features = false, features = [ - "alloc", -], optional = true } -embassy-sync = { version = "0.6.0", default-features = false } -rand_core = { version = "0.6.4", default-features = false } -# std websocket +url = { version = "2.2.1", default-features = false, optional = true } +# websocket +embassy-sync = { version = "0.6.0", optional = true } +embedded-io-async = { version = "0.6.1", optional = true } +embedded-websocket = { version = "0.9.3", default-features = false, optional = true } +futures = { version = "0.3.30", optional = true } +# websocket-codec +bytes = { version = "1.7.1", default-features = false, optional = true } +tokio-util = { version = "0.7.11", features = ["codec"], optional = true } +# websocket-std embassy-futures = { version = "0.1.1", optional = true } -tokio-tungstenite = { version = "0.23.1", features = [ - "native-tls", +tokio = { version = "1.0", default-features = false, features = [ + "net", ], optional = true } -tungstenite = { version = "0.23.0", optional = true } -tokio-util = { version = "0.7.11", features = ["codec"], optional = true } -tokio = { version = "1.0", features = ["full"], optional = true } -# no-std websocket -bytes = { version = "1.4.0", default-features = false, optional = true } -embedded-io-async = { version = "0.6.1", optional = true } -embedded-websocket = { version = "0.9.3", optional = true } -# std json-rpc -reqwest = { version = "0.12.5", features = ["json"], optional = true } -# no-std json-rpc -reqwless = { version = "0.12.0", optional = true } +tokio-tungstenite = { version = "0.23.1", optional = true, features = [ + "native-tls", +] } embedded-nal-async = { version = "0.7.1", optional = true } +# json-rpc +reqwless = { version = "0.12.1", optional = true } +# json-rpc-std +reqwest = { version = "0.12.5", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } -tokio-util = { version = "0.7.11", features = ["codec"] } [[bench]] name = "benchmarks" harness = false [features] -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", +default = ["std", "utils", "wallet", "models", "websocket-std"] +models = ["transactions", "ledger", "requests", "results"] +transactions = ["amounts", "currencies"] +requests = ["amounts", "currencies"] +results = ["amounts", "currencies"] +ledger = ["amounts", "currencies"] +amounts = [] +currencies = [] +json-rpc = ["requests", "results", "url"] +json-rpc-std = ["requests", "results", "url"] +websocket = [ "requests", "results", - "transactions", - "ledger", + "url", + "embassy-sync", + "embedded-io-async", + "embedded-websocket", + "futures", ] -amounts = ["core"] -currencies = ["core"] -json-rpc = ["url", "reqwless", "embedded-nal-async"] -json-rpc-std = ["url", "reqwest"] -websocket = ["url", "futures", "embedded-websocket", "embedded-io-async"] -websocket-codec = ["bytes", "tokio-util"] +websocket-codec = ["requests", "results", "bytes", "tokio-util"] websocket-std = [ + "requests", + "results", "url", + "embassy-sync", "futures", - "tungstenite", + "embassy-futures", "tokio", "tokio-tungstenite", - "embassy-futures", ] core = ["utils"] +wallet = ["core"] utils = [] std = [ - "embedded-websocket/std", - "futures/std", "rand/std", "regex/std", "chrono/std", From 5573dbe4c877ef0c7ba4660db6d54eaeb95eb8de Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 15 Aug 2024 15:15:27 +0000 Subject: [PATCH 078/113] fix testing --- Cargo.toml | 57 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e11ad5a5..3f14a787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,40 +96,57 @@ name = "benchmarks" harness = false [features] -default = ["std", "utils", "wallet", "models", "websocket-std"] -models = ["transactions", "ledger", "requests", "results"] -transactions = ["amounts", "currencies"] -requests = ["amounts", "currencies"] -results = ["amounts", "currencies"] -ledger = ["amounts", "currencies"] -amounts = [] -currencies = [] -json-rpc = ["requests", "results", "url"] -json-rpc-std = ["requests", "results", "url"] -websocket = [ +default = [ + "std", + "core", + "wallet", + "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"] +json-rpc-std = ["url", "reqwest"] +wallet = ["core"] +websocket = [ "url", - "embassy-sync", - "embedded-io-async", - "embedded-websocket", "futures", + "embedded-websocket", + "embedded-io-async", + "embassy-sync", ] -websocket-codec = ["requests", "results", "bytes", "tokio-util"] +websocket-codec = ["bytes", "tokio-util"] websocket-std = [ - "requests", - "results", "url", - "embassy-sync", "futures", - "embassy-futures", "tokio", "tokio-tungstenite", + "embassy-futures", + "embassy-sync", ] core = ["utils"] -wallet = ["core"] utils = [] std = [ + "embedded-websocket/std", + "futures/std", "rand/std", "regex/std", "chrono/std", From f41da81cd8433f747891a07aaa8bc575cc34a7a3 Mon Sep 17 00:00:00 2001 From: LimpidCrypto <97235361+LimpidCrypto@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:00:10 +0200 Subject: [PATCH 079/113] Transaction signing (#71) * move BinarySerializer and BinaryParser * add STObject * current state of txn signing * current state of sign * current state of sign * use String instead of Cow<'a, str> for structs using serde_with_tag macro due to lifetime issues when deserializing * utilize Cow for models * utilize Cow for models * cargo fmt * add simple test * add devcontainer * fix async client tests * fix github workflow * fix tests with --all-features * fix --no-default-features tests with embedded-ws * try fix colliding embebedded-websocket feature with dep: syntax * fix ambiguous name error * Revise Models (#74) * current state * initial * initial * add to changelog * impl LedgerObject trait * remove .DS_Store * add documentation * cargo fix * add Default trait to FlagCollection * refactoring of models * fix serde tests * fix serde tests * Delete src/models/.DS_Store * remove dafault_none * resolve comments * revise dependencies * remove old clients * add new clients * add codec for std embedded websocket * move websocket exceptions * add WebsocketBase * add Client and AsyncClient traits * add XRPLWebsocketIO as standardized trait * adjust clients mod * add XRPLResponse * revise the Request trait to have methods to get a resut models common fields * cargo fmt * add get_random_id utility function * add xrpl testnet uri * revise the tests to work with the changes * error handling * improve testing * implement websocket client request * implement websocket client request for embedded websocket * update rand trying to get rid of cargo check: OsRng: rand_core::RngCore is not satisfied error * fix github workflow build and tests * fix github workflow build and tests * run linters * add json rpc client * add json rpc for std and tests * cargo fmt * add pre-commit test with json-rpc-std * cargo fmt * cargo fix * improve client code * refactor features * fix tungstenite example * fix clients * fix client tests * fix tests * rename features in github workflow * add transaction auto filling as in #76 (#80) * current state of txn signing * current state of sign * current state of sign * add simple test * resolve errors caused by updating branch * resolve errors caused by updating branch * add autofill_and_sign * make tx signing work * improve sign test * handle unwraps * add autofill_and_sign * add sign and submit * fix errors caused by solving merge conflicts * rename SerializedDict and SerializedList to STObject and STArray * rename SerializedDict and SerializedList to STObject and STArray * handle unwraps * fix tests * fix tests * fix tests * add multisigning * add multisigning * fix tests * fix tests --------- Co-authored-by: LimpidCrypto --- Cargo.toml | 5 +- rustc-ice-2024-08-27T11_36_40-17775.txt | 59 ++ src/asynch/clients/client.rs | 15 +- src/asynch/clients/websocket/_no_std.rs | 10 +- src/asynch/clients/websocket/_std.rs | 5 +- src/asynch/transaction/exceptions.rs | 8 + src/asynch/transaction/mod.rs | 375 +++++++++- src/core/binarycodec/binary_wrappers.rs | 811 ++++++++++++++++++++ src/core/binarycodec/exceptions.rs | 3 + src/core/binarycodec/mod.rs | 855 ++-------------------- src/core/definitions/definitions.json | 61 +- src/core/definitions/mod.rs | 6 +- src/core/definitions/types.rs | 6 +- src/core/mod.rs | 6 +- src/core/test_data/data-driven-tests.json | 42 +- src/core/types/exceptions.rs | 28 + src/core/types/mod.rs | 461 +++++++++++- src/lib.rs | 2 + src/models/amount/xrp_amount.rs | 8 + src/models/results/mod.rs | 30 + src/models/results/submit.rs | 42 ++ src/models/transactions/exceptions.rs | 10 + src/models/transactions/mod.rs | 21 +- src/models/transactions/payment.rs | 2 +- src/transaction/exceptions.rs | 7 + src/transaction/mod.rs | 4 + src/transaction/multisign.rs | 80 ++ src/utils/mod.rs | 3 +- src/utils/transactions.rs | 83 +++ tests/common/mod.rs | 5 +- 30 files changed, 2153 insertions(+), 900 deletions(-) create mode 100644 rustc-ice-2024-08-27T11_36_40-17775.txt create mode 100644 src/core/binarycodec/binary_wrappers.rs create mode 100644 src/models/results/submit.rs create mode 100644 src/transaction/exceptions.rs create mode 100644 src/transaction/mod.rs create mode 100644 src/transaction/multisign.rs create mode 100644 src/utils/transactions.rs diff --git a/Cargo.toml b/Cargo.toml index 3f14a787..deda7588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,12 +64,13 @@ thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } # networking -url = { version = "2.2.1", default-features = false, optional = true } +url = { version = "2.2.2", default-features = false, optional = true } # websocket embassy-sync = { version = "0.6.0", optional = true } embedded-io-async = { version = "0.6.1", optional = true } embedded-websocket = { version = "0.9.3", default-features = false, optional = true } futures = { version = "0.3.30", optional = true } +rand_core = { version = "0.6.4", default-features = false } # websocket-codec bytes = { version = "1.7.1", default-features = false, optional = true } tokio-util = { version = "0.7.11", features = ["codec"], optional = true } @@ -90,6 +91,7 @@ reqwest = { version = "0.12.5", features = ["json"], optional = true } [dev-dependencies] criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } +tokio-util = { version = "0.7.11", features = ["codec"] } [[bench]] name = "benchmarks" @@ -114,6 +116,7 @@ helpers = ["account-helpers", "ledger-helpers", "transaction-helpers"] account-helpers = ["amounts", "currencies", "requests", "results"] ledger-helpers = ["amounts", "currencies", "requests", "results"] transaction-helpers = [ + "wallet", "amounts", "currencies", "requests", diff --git a/rustc-ice-2024-08-27T11_36_40-17775.txt b/rustc-ice-2024-08-27T11_36_40-17775.txt new file mode 100644 index 00000000..d13252de --- /dev/null +++ b/rustc-ice-2024-08-27T11_36_40-17775.txt @@ -0,0 +1,59 @@ +thread 'rustc' panicked at /rustc/80eb5a8e910e5185d47cdefe3732d839c78a5e7e/compiler/rustc_query_system/src/query/plumbing.rs:727:9: +Found unstable fingerprints for evaluate_obligation(9acaf53e42db4106-b70f9be4bd000590): Ok(EvaluatedToAmbig) +stack backtrace: + 0: 0x7f2385fa3de5 - std::backtrace::Backtrace::create::h5fc94655595d7e27 + 1: 0x7f2384710fe5 - std::backtrace::Backtrace::force_capture::h3ae8cd2c99c0732c + 2: 0x7f23838b111e - std[edc1ba278773f0d3]::panicking::update_hook::>::{closure#0} + 3: 0x7f2384728e27 - std::panicking::rust_panic_with_hook::h9e59a429b59b11d4 + 4: 0x7f2384728ae7 - std::panicking::begin_panic_handler::{{closure}}::h97d6e0922a833e87 + 5: 0x7f23847262e9 - std::sys::backtrace::__rust_end_short_backtrace::ha09625205ff6b122 + 6: 0x7f23847287b4 - rust_begin_unwind + 7: 0x7f2381922f53 - core::panicking::panic_fmt::h089121e4cbbc3220 + 8: 0x7f23841d9c41 - rustc_query_system[cb6aa6728468b543]::query::plumbing::incremental_verify_ich_failed:: + 9: 0x7f238558c6af - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, rustc_middle[354bbee2ce4c460]::query::erase::Erased<[u8; 2usize]>>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> + 10: 0x7f238558ab62 - rustc_query_impl[2dfeb81381ece16c]::query_impl::evaluate_obligation::get_query_incr::__rust_end_short_backtrace + 11: 0x7f238246b63e - ::evaluate_obligation_no_overflow + 12: 0x7f2381749e92 - , ::consider_candidates::{closure#0}>, ::consider_candidates::{closure#1}> as core[8c2a069408211e69]::iter::traits::iterator::Iterator>::next + 13: 0x7f238552a7b4 - ::pick_method + 14: 0x7f2385529ed4 - ::pick_core + 15: 0x7f2382485c0e - ::lookup_probe + 16: 0x7f2385931fec - ::check_expr_with_expectation_and_args + 17: 0x7f23859345ae - ::check_expr_with_expectation_and_args + 18: 0x7f2385935734 - ::check_expr_with_expectation_and_args + 19: 0x7f2385935734 - ::check_expr_with_expectation_and_args + 20: 0x7f2385933b44 - ::check_expr_with_expectation_and_args + 21: 0x7f238593362e - ::check_expr_with_expectation_and_args + 22: 0x7f238592aa16 - ::check_block_with_expected + 23: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args + 24: 0x7f238593376d - ::check_expr_with_expectation_and_args + 25: 0x7f238592aa16 - ::check_block_with_expected + 26: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args + 27: 0x7f23851bac37 - rustc_hir_typeck[d8d14d8a3ea8603d]::check::check_fn + 28: 0x7f23852c6edf - rustc_hir_typeck[d8d14d8a3ea8603d]::typeck + 29: 0x7f23852c6933 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> + 30: 0x7f2385251b4a - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> + 31: 0x7f2385334054 - rustc_query_impl[2dfeb81381ece16c]::query_impl::typeck::get_query_incr::__rust_end_short_backtrace + 32: 0x7f238524da5b - ::par_body_owners::::{closure#0} + 33: 0x7f238524b7a4 - rustc_hir_analysis[618f1997a162b265]::check_crate + 34: 0x7f238572bcbf - rustc_interface[fc21679c26087701]::passes::run_required_analyses + 35: 0x7f2385a97e5e - rustc_interface[fc21679c26087701]::passes::analysis + 36: 0x7f2385a97e31 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> + 37: 0x7f2385f83c0d - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> + 38: 0x7f2385f838ba - rustc_query_impl[2dfeb81381ece16c]::query_impl::analysis::get_query_incr::__rust_end_short_backtrace + 39: 0x7f2385d12229 - rustc_interface[fc21679c26087701]::interface::run_compiler::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1} + 40: 0x7f2385de6f44 - std[edc1ba278773f0d3]::sys::backtrace::__rust_begin_short_backtrace::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>> + 41: 0x7f2385de75b0 - <::spawn_unchecked_, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#1} as core[8c2a069408211e69]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x7f2385de792b - std::sys::pal::unix::thread::Thread::new::thread_start::he6ae6a1223d421a8 + 43: 0x7f2380330ea7 - start_thread + 44: 0x7f2380250a6f - clone + 45: 0x0 - + + +rustc version: 1.82.0-nightly (80eb5a8e9 2024-08-13) +platform: x86_64-unknown-linux-gnu + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `models::transactions::payment::Payment<'a>: models::transactions::Transaction<'b, ^1_2>` +#1 [typeck] type-checking `models::transactions::payment::::_get_partial_payment_error` +#2 [analysis] running analysis passes on this crate +end of query stack diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index ebbb609b..0fec4d50 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -4,27 +4,26 @@ use crate::models::{ }; #[cfg(feature = "std")] use crate::utils::get_random_id; -use alloc::borrow::Cow; +use alloc::borrow::{Cow, ToOwned}; use anyhow::Result; #[allow(async_fn_in_trait)] pub trait Client { async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; - fn set_request_id<'a: 'b, 'b>(&self, request: &mut XRPLRequest<'a>) -> Cow<'b, str> { - let common_fields = request.get_common_fields(); - let request_id: Cow<'_, str> = match common_fields.id.clone() { - Some(id) => id, + + fn set_request_id(&self, request: &mut XRPLRequest<'_>) -> () { + let common_fields = request.get_common_fields_mut(); + common_fields.id = match &common_fields.id { + Some(id) => Some(id.to_owned()), None => { #[cfg(feature = "std")] { let mut rng = rand::thread_rng(); - Cow::Owned(get_random_id(&mut rng)) + Some(Cow::Owned(get_random_id(&mut rng))) } #[cfg(not(feature = "std"))] unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); } }; - request.get_common_fields_mut().id = Some(request_id.clone()); - request_id } } diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 7552b3a2..99bbc4d5 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -18,11 +18,14 @@ use embedded_websocket::{ }; use futures::Sink; use futures::Stream; -use rand::RngCore; +use rand_core::RngCore; use url::Url; use super::{WebsocketClosed, WebsocketOpen}; -use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; +use crate::{ + asynch::clients::SingleExecutorMutex, + models::requests::{Request, XRPLRequest}, +}; use crate::{ asynch::clients::{ client::Client as ClientTrait, @@ -247,7 +250,8 @@ where mut request: XRPLRequest<'a>, ) -> Result> { // setup request future - let request_id = self.set_request_id(&mut request); + self.set_request_id(&mut request); + let request_id = request.get_common_fields().id.as_ref().unwrap(); let mut websocket_base = self.websocket_base.lock().await; websocket_base .setup_request_future(request_id.to_string()) diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index dcb906ac..a2935fce 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -3,7 +3,7 @@ use super::{WebsocketClosed, WebsocketOpen}; use crate::asynch::clients::client::Client; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; use crate::asynch::clients::SingleExecutorMutex; -use crate::models::requests::XRPLRequest; +use crate::models::requests::{Request, XRPLRequest}; use crate::models::results::XRPLResponse; use crate::Err; @@ -204,7 +204,8 @@ where mut request: XRPLRequest<'a>, ) -> Result> { // setup request future - let request_id = self.set_request_id(&mut request); + self.set_request_id(&mut request); + let request_id = request.get_common_fields().id.as_ref().unwrap(); let mut websocket_base = self.websocket_base.lock().await; websocket_base .setup_request_future(request_id.to_string()) diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 92e8d8f9..0b4456c3 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -14,3 +14,11 @@ pub enum XRPLTransactionException<'a> { #[error("Invalid rippled version: {0}")] InvalidRippledVersion(Cow<'a, str>), } + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum XRPLSignTransactionException<'a> { + #[error("{0:?} value does not match X-Address tag")] + TagFieldMismatch(&'a str), + #[error("Fee value of {0:?} is likely entered incorrectly, since it is much larger than the typical XRP transaction cost. If this is intentional, use `check_fee=Some(false)`.")] + FeeTooHigh(Cow<'a, str>), +} diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 315e81aa..607c07dd 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,44 +1,59 @@ -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; +pub mod exceptions; + +use crate::{ + asynch::{ + account::get_next_valid_seq_number, + clients::{AsyncClient, CommonFields}, + ledger::{get_fee, get_latest_validated_ledger_sequence}, + transaction::exceptions::XRPLSignTransactionException, + }, + core::{ + addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, + binarycodec::{encode, encode_for_multisigning, encode_for_signing}, + keypairs::sign as keypairs_sign, + }, + models::{ + amount::XRPAmount, + exceptions::XRPLModelException, + requests::{ServerState, Submit}, + results::{ServerState as ServerStateResult, Submit as SubmitResult}, + transactions::{Signer, Transaction, TransactionType, XRPLTransactionFieldException}, + Model, + }, + utils::transactions::{ + get_transaction_field_value, set_transaction_field_value, validate_transaction_has_field, + }, + wallet::Wallet, + Err, +}; + use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; +use alloc::{borrow::Cow, vec}; use anyhow::Result; use core::convert::TryInto; use core::fmt::Debug; use exceptions::XRPLTransactionException; use rust_decimal::Decimal; use serde::Serialize; +use serde::{de::DeserializeOwned, Deserialize}; 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>( +pub async fn autofill<'a, 'b, F, T, C>( transaction: &mut T, - client: &'a impl AsyncClient, + client: &'b C, signers_count: Option, ) -> Result<()> where - 'a: 'b, - T: Transaction<'b, F> + Model + Clone, + T: Transaction<'a, F> + Model + Clone, F: IntoEnumIterator + Serialize + Debug + PartialEq, + C: AsyncClient, { let txn = transaction.clone(); let txn_common_fields = transaction.get_mut_common_fields(); @@ -52,7 +67,7 @@ where } if txn_common_fields.fee.is_none() { txn_common_fields.fee = - Some(calculate_fee_per_transaction_type(txn, Some(client), signers_count).await?); + 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?; @@ -62,15 +77,15 @@ where Ok(()) } -pub async fn calculate_fee_per_transaction_type<'a, 'b, T, F>( - transaction: T, - client: Option<&'a impl AsyncClient>, +pub async fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( + transaction: &T, + client: Option<&'b C>, signers_count: Option, -) -> Result> +) -> Result> where - 'a: 'b, - T: Transaction<'b, F>, + T: Transaction<'a, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, + C: AsyncClient, { let mut net_fee = XRPAmount::from("10"); let base_fee; @@ -122,10 +137,10 @@ async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result( +fn calculate_base_fee_for_escrow_finish<'a: 'b, 'b>( net_fee: XRPAmount<'a>, fulfillment: Option>, -) -> Result> { +) -> Result> { if let Some(fulfillment) = fulfillment { calculate_based_on_fulfillment(fulfillment, net_fee) } else { @@ -239,6 +254,224 @@ fn is_not_later_rippled_version<'a>( } } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +enum AccountFieldType { + Account, + Destination, +} + +pub async fn sign_and_submit<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + autofill: bool, + check_fee: bool, +) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if autofill { + autofill_and_sign(transaction, client, wallet, check_fee).await?; + } else { + if check_fee { + check_txn_fee(transaction, client).await?; + } + sign(transaction, wallet, false)?; + } + submit(transaction, client).await +} + +pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + if multisign { + let serialized_for_signing = + encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; + let serialized_bytes = hex::decode(serialized_for_signing).unwrap(); + let signature = keypairs_sign(&serialized_bytes, &wallet.private_key).unwrap(); + let signer = Signer::new( + wallet.classic_address.clone().into(), + signature.into(), + wallet.public_key.clone().into(), + ); + transaction.get_mut_common_fields().signers = Some(vec![signer]); + + Ok(()) + } else { + prepare_transaction(transaction, wallet)?; + let serialized_for_signing = encode_for_signing(transaction)?; + let serialized_bytes = match hex::decode(serialized_for_signing) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { + Ok(signature) => signature, + Err(e) => return Err!(e), + }; + transaction.get_mut_common_fields().txn_signature = Some(signature.into()); + + Ok(()) + } +} + +pub async fn autofill_and_sign<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + check_fee: bool, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if check_fee { + check_txn_fee(transaction, client).await?; + } + autofill(transaction, client, None).await?; + sign(transaction, wallet, false)?; + + Ok(()) +} + +pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + let txn_blob = encode(transaction)?; + let req = Submit::new(None, txn_blob.into(), None); + let res = client.request(req.into()).await?; + match res.try_into_result::>() { + Ok(value) => { + let submit_result = SubmitResult::from(value); + Ok(submit_result) + } + Err(e) => Err!(e), + } +} + +async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone, + C: AsyncClient, +{ + // max of xrp_to_drops(0.1) and calculate_fee_per_transaction_type + let expected_fee = XRPAmount::from("100000") + .max(calculate_fee_per_transaction_type(transaction, Some(client), None).await?); + let transaction_fee = transaction + .get_common_fields() + .fee + .clone() + .unwrap_or(XRPAmount::from("0")); + if transaction_fee > expected_fee { + return Err!(XRPLSignTransactionException::FeeTooHigh( + transaction_fee.try_into()? + )); + } + Ok(()) +} + +fn prepare_transaction<'a, T, F>(transaction: &mut T, wallet: &Wallet) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, +{ + let commond_fields = transaction.get_mut_common_fields(); + commond_fields.signing_pub_key = Some(wallet.public_key.clone().into()); + + validate_account_xaddress(transaction, AccountFieldType::Account)?; + if validate_transaction_has_field(transaction, "Destination").is_ok() { + validate_account_xaddress(transaction, AccountFieldType::Destination)?; + } + + let _ = convert_to_classic_address(transaction, "Unauthorize"); + let _ = convert_to_classic_address(transaction, "Authorize"); + // EscrowCancel, EscrowFinish + let _ = convert_to_classic_address(transaction, "Owner"); + // SetRegularKey + + let _ = convert_to_classic_address(transaction, "RegularKey"); + + Ok(()) +} + +fn validate_account_xaddress<'a, T, F>( + prepared_transaction: &mut T, + account_field: AccountFieldType, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, +{ + let (account_field_name, tag_field_name) = match serde_json::to_string(&account_field) { + Ok(name) => { + let name_str = name.as_str().trim(); + if name_str == "\"Account\"" { + ("Account", "SourceTag") + } else if name_str == "\"Destination\"" { + ("Destination", "DestinationTag") + } else { + return Err!(XRPLTransactionFieldException::UnknownAccountField(name_str)); + } + } + Err(error) => return Err!(error), + }; + let account_address = match account_field { + AccountFieldType::Account => prepared_transaction.get_common_fields().account.clone(), + AccountFieldType::Destination => { + get_transaction_field_value(prepared_transaction, "Destination")? + } + }; + + if is_valid_xaddress(&account_address) { + let (address, tag, _) = match xaddress_to_classic_address(&account_address) { + Ok(t) => t, + Err(error) => return Err!(error), + }; + validate_transaction_has_field(prepared_transaction, &account_field_name)?; + set_transaction_field_value(prepared_transaction, &account_field_name, address)?; + + if validate_transaction_has_field(prepared_transaction, &tag_field_name).is_ok() + && get_transaction_field_value(prepared_transaction, &tag_field_name).unwrap_or(Some(0)) + != tag + { + Err!(XRPLSignTransactionException::TagFieldMismatch( + &tag_field_name + )) + } else { + set_transaction_field_value(prepared_transaction, &tag_field_name, tag)?; + + Ok(()) + } + } else { + Ok(()) + } +} + +fn convert_to_classic_address<'a, T, F>(transaction: &mut T, field_name: &str) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone, +{ + let address = get_transaction_field_value::(transaction, field_name)?; + if is_valid_xaddress(&address) { + let classic_address = match xaddress_to_classic_address(&address) { + Ok(t) => t.0, + Err(error) => return Err!(error), + }; + set_transaction_field_value(transaction, field_name, classic_address) + } else { + Ok(()) + } +} + #[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] #[cfg(test)] mod test_autofill { @@ -255,7 +488,7 @@ mod test_autofill { #[tokio::test] async fn test_autofill_txn() -> Result<()> { let mut txn = OfferCreate::new( - "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), + "r9mhdWo1NXVZr2pDnCtC1xwxE85kFtSzYR".into(), None, None, None, @@ -290,3 +523,85 @@ mod test_autofill { Ok(()) } } + +#[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] +#[cfg(test)] +mod test_sign { + use alloc::borrow::Cow; + + use crate::{ + asynch::{ + clients::{AsyncWebsocketClient, SingleExecutorMutex}, + transaction::{autofill_and_sign, sign}, + }, + models::transactions::{AccountSet, Transaction}, + wallet::Wallet, + }; + + #[test] + fn test_sign() { + let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut tx = AccountSet::new( + Cow::from(wallet.classic_address.clone()), + None, + Some("10".into()), + None, + None, + None, + Some(227234), + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // "example.com" + None, + None, + None, + None, + None, + None, + ); + sign(&mut tx, &wallet, false).unwrap(); + let expected_signature: Cow = + "B310792432B0242C2542C2B46CA234C87F4AE3FFC33226797AF72A92D9295ED20BD05A85D0\ + C13760B653AE9B8C0D74B9BBD310B09524F63B41D1776E7F2BB609" + .into(); + let actual_signature = tx.get_common_fields().txn_signature.as_ref().unwrap(); + assert_eq!(expected_signature, *actual_signature); + } + + #[tokio::test] + async fn test_autofill_and_sign() { + let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut tx = AccountSet::new( + Cow::from(wallet.classic_address.clone()), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // "example.com" + None, + None, + None, + None, + None, + None, + ); + let client = AsyncWebsocketClient::::open( + "wss://testnet.xrpl-labs.com/".parse().unwrap(), + ) + .await + .unwrap(); + autofill_and_sign(&mut tx, &client, &wallet, true) + .await + .unwrap(); + assert!(tx.get_common_fields().sequence.is_some()); + assert!(tx.get_common_fields().txn_signature.is_some()); + } +} diff --git a/src/core/binarycodec/binary_wrappers.rs b/src/core/binarycodec/binary_wrappers.rs new file mode 100644 index 00000000..d8085bd7 --- /dev/null +++ b/src/core/binarycodec/binary_wrappers.rs @@ -0,0 +1,811 @@ +use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; +use crate::core::binarycodec::utils::*; +use crate::core::definitions::*; +use crate::core::types::TryFromParser; +use crate::utils::ToBytes; +use alloc::borrow::ToOwned; +use alloc::vec; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::convert::TryInto; + +/// Serializes JSON to XRPL binary format. +pub type BinarySerializer = Vec; + +/// Deserializes from hex-encoded XRPL binary format to +/// serde JSON fields and values. +/// +/// # Examples +/// +/// ## Basic usage +/// +/// ``` +/// use xrpl::core::binarycodec::BinaryParser; +/// use xrpl::core::Parser; +/// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; +/// +/// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; +/// let binary_parser: BinaryParser = BinaryParser::from(test_bytes); +/// +/// assert_eq!(binary_parser, test_bytes[..]); +/// ``` +#[derive(Debug, Clone)] +pub struct BinaryParser(Vec); + +/// Helper function for length-prefixed fields including +/// Blob types and some AccountID types. Calculates the +/// prefix of variable length bytes. +/// +/// The length of the prefix is 1-3 bytes depending on the +/// length of the contents: +/// Content length <= 192 bytes: prefix is 1 byte +/// 192 bytes < Content length <= 12480 bytes: prefix is 2 bytes +/// 12480 bytes < Content length <= 918744 bytes: prefix is 3 bytes +/// +/// See Length Prefixing: +/// `` +fn _encode_variable_length_prefix(length: &usize) -> Result, XRPLBinaryCodecException> { + if length <= &MAX_SINGLE_BYTE_LENGTH { + Ok([*length as u8].to_vec()) + } else if length < &MAX_DOUBLE_BYTE_LENGTH { + let mut bytes = vec![]; + let b_length = *length - (MAX_SINGLE_BYTE_LENGTH + 1); + let val_a: u8 = ((b_length >> 8) + (MAX_SINGLE_BYTE_LENGTH + 1)).try_into()?; + let val_b: u8 = (b_length & 0xFF).try_into()?; + + bytes.extend_from_slice(&[val_a]); + bytes.extend_from_slice(&[val_b]); + + Ok(bytes) + } else if length <= &MAX_LENGTH_VALUE { + let mut bytes = vec![]; + let b_length = *length - MAX_DOUBLE_BYTE_LENGTH; + let val_a: u8 = ((MAX_SECOND_BYTE_VALUE + 1) + (b_length >> 16)).try_into()?; + let val_b: u8 = ((b_length >> 8) & 0xFF).try_into()?; + let val_c: u8 = (b_length & 0xFF).try_into()?; + + bytes.extend_from_slice(&[val_a]); + bytes.extend_from_slice(&[val_b]); + bytes.extend_from_slice(&[val_c]); + + Ok(bytes) + } else { + Err(XRPLBinaryCodecException::InvalidVariableLengthTooLarge { + max: MAX_LENGTH_VALUE, + }) + } +} + +pub trait Parser { + /// Peek the first byte of the BinaryParser. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// let first_byte: Option<[u8; 1]> = binary_parser.peek(); + /// + /// assert_eq!(Some([test_bytes[0]; 1]), first_byte); + /// ``` + fn peek(&self) -> Option<[u8; 1]>; + + /// Consume the first n bytes of the BinaryParser. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.skip_bytes(4) { + /// Ok(parser) => assert_eq!(*parser, test_bytes[4..]), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// ``` + fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException>; + + /// Consume and return the first n bytes of the BinaryParser. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.read(5) { + /// Ok(data) => assert_eq!(test_bytes[..5], data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// ``` + fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException>; + + /// Read 1 byte from parser and return as unsigned int. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.read_uint8() { + /// Ok(data) => assert_eq!(0, data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// ``` + fn read_uint8(&mut self) -> Result; + + /// Read 2 bytes from parser and return as unsigned int. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.read_uint16() { + /// Ok(data) => assert_eq!(17, data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// ``` + fn read_uint16(&mut self) -> Result; + + /// Read 4 bytes from parser and return as unsigned int. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.read_uint32() { + /// Ok(data) => assert_eq!(1122867, data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// ``` + fn read_uint32(&mut self) -> Result; + + /// Returns whether the binary parser has finished + /// parsing (e.g. there is nothing left in the buffer + /// that needs to be processed). + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// extern crate alloc; + /// use alloc::vec; + /// + /// let empty: &[u8] = &[]; + /// let mut buffer: Vec = vec![]; + /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// while !binary_parser.is_end(None) { + /// match binary_parser.read(1) { + /// Ok(data) => buffer.extend_from_slice(&data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + /// max: _, + /// found: _, + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + /// } + /// + /// assert_eq!(test_bytes, &buffer[..]); + /// // The BinaryParser is emptied as it is read. + /// assert_eq!(binary_parser, empty[..]); + /// + /// ``` + fn is_end(&self, custom_end: Option) -> bool; + + /// Reads a variable length encoding prefix and returns + /// the encoded length. The formula for decoding a length + /// prefix is described in Length Prefixing. + /// + /// See Length Prefixing: + /// `` + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinaryParser; + /// use xrpl::core::Parser; + /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; + /// + /// let test_bytes: &[u8] = &[6, 17, 34, 51, 68, 85, 102]; + /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); + /// + /// match binary_parser.read_length_prefix() { + /// Ok(data) => assert_eq!(6, data), + /// Err(e) => match e { + /// XRPLBinaryCodecException::UnexpectedLengthPrefixRange { + /// min: _, max: _ + /// } => assert!(false), + /// _ => assert!(false) + /// } + /// } + fn read_length_prefix(&mut self) -> Result; + + /// Reads field ID from BinaryParser and returns as + /// a FieldHeader object. + fn read_field_header(&mut self) -> Result; + + /// Read the field ordinal at the head of the + /// BinaryParser and return a FieldInstance object + /// representing information about the field + /// containedin the following bytes. + fn read_field(&mut self) -> Result; + + /// Read next bytes from BinaryParser as the given type. + fn read_type(&mut self) -> Result; + + /// Read value of the type specified by field from + /// the BinaryParser. + fn read_field_value(&mut self, field: &FieldInstance) -> Result + where + T::Error: From; +} + +pub trait Serialization { + /// Write given bytes to this BinarySerializer. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinarySerializer; + /// use xrpl::core::binarycodec::Serialization; + /// + /// let mut test_bytes: Vec = [0, 17, 34, 51, 68, 85, 102].to_vec(); + /// let mut serializer: BinarySerializer = BinarySerializer::new(); + /// + /// serializer.append(&mut test_bytes.to_owned()); + /// assert_eq!(test_bytes, serializer); + /// ``` + fn append(&mut self, bytes: &[u8]) -> &Self; + + /// Write a variable length encoded value to + /// the BinarySerializer. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinarySerializer; + /// use xrpl::core::binarycodec::Serialization; + /// + /// let expected: Vec = [3, 0, 17, 34].to_vec(); + /// let mut test_bytes: Vec = [0, 17, 34].to_vec(); + /// let mut serializer: BinarySerializer = BinarySerializer::new(); + /// + /// serializer.write_length_encoded(&mut test_bytes, true); + /// assert_eq!(expected, serializer); + /// ``` + fn write_length_encoded(&mut self, value: &[u8], encode_value: bool) -> &Self; + + /// Write field and value to the buffer. + /// + /// # Examples + /// + /// ## Basic usage + /// + /// ``` + /// use xrpl::core::binarycodec::BinarySerializer; + /// use xrpl::core::binarycodec::Serialization; + /// use xrpl::core::definitions::FieldInstance; + /// use xrpl::core::definitions::FieldInfo; + /// use xrpl::core::definitions::FieldHeader; + /// + /// let field_header: FieldHeader = FieldHeader { + /// type_code: -2, + /// field_code: 0, + /// }; + /// + /// let field_info: FieldInfo = FieldInfo { + /// nth: 0, + /// is_vl_encoded: false, + /// is_serialized: false, + /// is_signing_field: false, + /// r#type: "Unknown".to_string(), + /// }; + /// + /// let field_instance = FieldInstance::new(&field_info, "Generic", field_header); + /// let expected: Vec = [224, 0, 17, 34].to_vec(); + /// let test_bytes: Vec = [0, 17, 34].to_vec(); + /// let mut serializer: BinarySerializer = BinarySerializer::new(); + /// + /// serializer.write_field_and_value(field_instance, &test_bytes, false); + /// assert_eq!(expected, serializer); + /// ``` + fn write_field_and_value( + &mut self, + field: FieldInstance, + value: &[u8], + is_unl_modify_workaround: bool, + ) -> &Self; +} + +impl Serialization for BinarySerializer { + fn append(&mut self, bytes: &[u8]) -> &Self { + self.extend_from_slice(bytes); + self + } + + fn write_length_encoded(&mut self, value: &[u8], encode_value: bool) -> &Self { + let mut byte_object: Vec = Vec::new(); + if encode_value { + // write value to byte_object + byte_object.extend_from_slice(value); + } + // TODO Handle unwrap better + let length_prefix = _encode_variable_length_prefix(&byte_object.len()).unwrap(); + + self.extend_from_slice(&length_prefix); + self.extend_from_slice(&byte_object); + + self + } + + fn write_field_and_value( + &mut self, + field: FieldInstance, + value: &[u8], + is_unl_modify_workaround: bool, + ) -> &Self { + self.extend_from_slice(&field.header.to_bytes()); + + if field.is_vl_encoded { + self.write_length_encoded(value, !is_unl_modify_workaround); + } else { + self.extend_from_slice(value); + } + + self + } +} + +/// Peek the first byte of the BinaryParser. +impl Parser for BinaryParser { + fn peek(&self) -> Option<[u8; 1]> { + if !self.0.is_empty() { + Some(self.0[0].to_be_bytes()) + } else { + None + } + } + + fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException> { + if n > self.0.len() { + Err(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { + max: self.0.len(), + found: n, + }) + } else { + self.0 = self.0[n..].to_vec(); + Ok(self) + } + } + + fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException> { + let first_n_bytes = self.0[..n].to_owned(); + + self.skip_bytes(n)?; + Ok(first_n_bytes) + } + + fn read_uint8(&mut self) -> Result { + let result = self.read(1)?; + Ok(u8::from_be_bytes(result.try_into().or(Err( + XRPLBinaryCodecException::InvalidReadFromBytesValue, + ))?)) + } + + fn read_uint16(&mut self) -> Result { + let result = self.read(2)?; + Ok(u16::from_be_bytes(result.try_into().or(Err( + XRPLBinaryCodecException::InvalidReadFromBytesValue, + ))?)) + } + + fn read_uint32(&mut self) -> Result { + let result = self.read(4)?; + Ok(u32::from_be_bytes(result.try_into().or(Err( + XRPLBinaryCodecException::InvalidReadFromBytesValue, + ))?)) + } + + fn is_end(&self, custom_end: Option) -> bool { + if let Some(end) = custom_end { + self.0.len() <= end + } else { + self.0.is_empty() + } + } + + fn read_length_prefix(&mut self) -> Result { + let byte1: usize = self.read_uint8()? as usize; + + match byte1 { + // If the field contains 0 to 192 bytes of data, + // the first byte defines the length of the contents. + x if x <= MAX_SINGLE_BYTE_LENGTH => Ok(byte1), + // If the field contains 193 to 12480 bytes of data, + // the first two bytes indicate the length of the + // field with the following formula: + // 193 + ((byte1 - 193) * 256) + byte2 + x if x <= MAX_SECOND_BYTE_VALUE => { + let byte2: usize = self.read_uint8()? as usize; + Ok((MAX_SINGLE_BYTE_LENGTH + 1) + + ((byte1 - (MAX_SINGLE_BYTE_LENGTH + 1)) * MAX_BYTE_VALUE) + + byte2) + } + // If the field contains 12481 to 918744 bytes of data, + // the first three bytes indicate the length of the + // field with the following formula: + // 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3 + x if x <= 254 => { + let byte2: usize = self.read_uint8()? as usize; + let byte3: usize = self.read_uint8()? as usize; + + Ok(MAX_DOUBLE_BYTE_LENGTH + + ((byte1 - (MAX_SECOND_BYTE_VALUE + 1)) * MAX_DOUBLE_BYTE_VALUE) + + (byte2 * MAX_BYTE_VALUE) + + byte3) + } + _ => Err(XRPLBinaryCodecException::UnexpectedLengthPrefixRange { min: 1, max: 3 }), + } + } + + fn read_field_header(&mut self) -> Result { + let mut type_code: i16 = self.read_uint8()? as i16; + let mut field_code: i16 = type_code & 15; + + type_code >>= 4; + + if type_code == 0 { + type_code = self.read_uint8()? as i16; + + if type_code == 0 || type_code < 16 { + return Err(XRPLBinaryCodecException::UnexpectedTypeCodeRange { min: 1, max: 16 }); + }; + }; + + if field_code == 0 { + field_code = self.read_uint8()? as i16; + + if field_code == 0 || field_code < 16 { + return Err(XRPLBinaryCodecException::UnexpectedFieldCodeRange { min: 1, max: 16 }); + }; + }; + + Ok(FieldHeader { + type_code, + field_code, + }) + } + + fn read_field(&mut self) -> Result { + let field_header = self.read_field_header()?; + let field_name = get_field_name_from_header(&field_header); + + if let Some(name) = field_name { + if let Some(instance) = get_field_instance(name) { + return Ok(instance); + }; + }; + + Err(XRPLBinaryCodecException::UnknownFieldName) + } + + fn read_type(&mut self) -> Result { + T::from_parser(self, None) + } + + fn read_field_value(&mut self, field: &FieldInstance) -> Result + where + T::Error: From, + { + if field.is_vl_encoded { + let length = self.read_length_prefix()?; + T::from_parser(self, Some(length)) + } else { + T::from_parser(self, None) + } + } +} + +impl From<&[u8]> for BinaryParser { + fn from(hex_bytes: &[u8]) -> Self { + BinaryParser(hex_bytes.to_vec()) + } +} + +impl From> for BinaryParser { + fn from(hex_bytes: Vec) -> Self { + BinaryParser(hex_bytes) + } +} + +impl TryFrom<&str> for BinaryParser { + type Error = XRPLBinaryCodecException; + + fn try_from(hex_bytes: &str) -> Result { + Ok(BinaryParser(hex::decode(hex_bytes)?)) + } +} + +impl PartialEq<[u8]> for BinaryParser { + fn eq(&self, bytes: &[u8]) -> bool { + self.0 == bytes + } +} + +impl PartialEq> for BinaryParser { + fn eq(&self, bytes: &Vec) -> bool { + &self.0 == bytes + } +} + +impl ExactSizeIterator for BinaryParser { + fn len(&self) -> usize { + self.0.len() + } +} + +impl Iterator for BinaryParser { + type Item = u8; + + fn next(&mut self) -> Option { + if self.is_end(None) { + None + } else { + Some(self.read_uint8().expect("BinaryParser::next")) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::alloc::string::ToString; + use alloc::string::String; + + const TEST_HEX: &str = "00112233445566"; + + #[test] + fn test_binaryparser_from() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let ref_bytes: &[u8] = test_bytes.as_ref(); + let slice_parser = BinaryParser::from(ref_bytes); + let vec_parser = BinaryParser::from(test_bytes.to_owned()); + + assert_eq!(slice_parser, test_bytes[..]); + assert_eq!(vec_parser, test_bytes[..]); + } + + #[test] + fn test_binaryparser_try_from() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let string_parser = BinaryParser::try_from(TEST_HEX).unwrap(); + + assert_eq!(string_parser, test_bytes[..]); + } + + #[test] + fn test_peek() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let binary_parser = BinaryParser::from(test_bytes.as_ref()); + + assert_eq!(binary_parser.peek(), Some([test_bytes[0]; 1])); + } + + #[test] + fn test_skip_bytes() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + + assert!(binary_parser.skip_bytes(4).is_ok()); + assert_eq!(binary_parser, test_bytes[4..]); + } + + #[test] + fn test_read() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + let result = binary_parser.read(5); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), test_bytes[..5]); + } + + #[test] + fn test_read_uint8() { + let test_hex: &str = "01000200000003"; + let test_bytes: Vec = hex::decode(test_hex).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + let result = binary_parser.read_uint8(); + + assert!(result.is_ok()); + assert_eq!(result, Ok(1)); + } + + #[test] + fn test_read_uint16() { + let test_hex: &str = "000200000003"; + let test_bytes: Vec = hex::decode(test_hex).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + let result = binary_parser.read_uint16(); + + assert!(result.is_ok()); + assert_eq!(result, Ok(2)); + } + + #[test] + fn test_read_uint32() { + let test_hex: &str = "00000003"; + let test_bytes: Vec = hex::decode(test_hex).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + let result = binary_parser.read_uint32(); + + assert!(result.is_ok()); + assert_eq!(result, Ok(3)); + } + + #[test] + fn test_read_length_prefix() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + let result = binary_parser.read_length_prefix(); + + assert!(result.is_ok()); + assert_eq!(result, Ok(0)); + } + + // TODO Finish tests + #[test] + fn test_read_field_header() {} + + #[test] + fn test_read_field_value() {} + + #[test] + fn test_read_field_and_value() {} + + #[test] + fn test_read_type() {} + + #[test] + fn accept_peek_skip_read() { + let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); + let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); + + assert_eq!(binary_parser.peek(), Some([test_bytes[0]; 1])); + assert!(binary_parser.skip_bytes(3).is_ok()); + assert_eq!(binary_parser, test_bytes[3..]); + + let result = binary_parser.read(2); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), test_bytes[3..5]); + } + + #[test] + fn test_binaryserializer_write_field_and_value() { + let field_header = FieldHeader { + type_code: -2, + field_code: 0, + }; + + let field_info = FieldInfo { + nth: 0, + is_vl_encoded: false, + is_serialized: false, + is_signing_field: false, + r#type: "Unknown".to_string(), + }; + + let field_instance = FieldInstance::new(&field_info, "Generic", field_header); + let expected: Vec = [224, 0, 17, 34].to_vec(); + let test_bytes: Vec = [0, 17, 34].to_vec(); + let mut serializer: BinarySerializer = BinarySerializer::new(); + + serializer.write_field_and_value(field_instance, &test_bytes, false); + assert_eq!(expected, serializer); + } + + /// This is currently a sanity check for private + /// [`_encode_variable_length_prefix`], which is called by + /// BinarySerializer.write_length_encoded. + #[test] + fn test_encode_variable_length_prefix() { + for case in [100_usize, 1000, 20_000] { + let blob = (0..case).map(|_| "A2").collect::(); + let mut binary_serializer: BinarySerializer = BinarySerializer::new(); + + binary_serializer.write_length_encoded(&hex::decode(blob).expect(""), true); + + let mut binary_parser: BinaryParser = BinaryParser::from(binary_serializer.as_ref()); + let decoded_length = binary_parser.read_length_prefix(); + + assert!(decoded_length.is_ok()); + assert_eq!(decoded_length, Ok(case)); + } + } +} diff --git a/src/core/binarycodec/exceptions.rs b/src/core/binarycodec/exceptions.rs index fd1a6931..d88eccd8 100644 --- a/src/core/binarycodec/exceptions.rs +++ b/src/core/binarycodec/exceptions.rs @@ -26,6 +26,9 @@ pub enum XRPLBinaryCodecException { SerdeJsonError(serde_json::error::Category), DecimalError(rust_decimal::Error), ISOCodeError(ISOCodeException), + FieldHasNoAssiciatedTag, + XAddressTagMismatch, + FieldIsNotAccountOrDestination, } impl From for XRPLBinaryCodecException { diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index ff4c02d3..d28b44fb 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -1,802 +1,93 @@ //! Functions for encoding objects into the XRP Ledger's //! canonical binary format and decoding them. -pub mod exceptions; -pub(crate) mod test_cases; -pub mod utils; - -use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; -use crate::core::binarycodec::utils::*; -use crate::core::definitions::*; -use crate::core::types::TryFromParser; -use crate::utils::ToBytes; -use alloc::borrow::ToOwned; -use alloc::vec; -use alloc::vec::Vec; -use core::convert::TryFrom; -use core::convert::TryInto; - -/// Serializes JSON to XRPL binary format. -pub type BinarySerializer = Vec; - -/// Deserializes from hex-encoded XRPL binary format to -/// serde JSON fields and values. -/// -/// # Examples -/// -/// ## Basic usage -/// -/// ``` -/// use xrpl::core::binarycodec::BinaryParser; -/// use xrpl::core::Parser; -/// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; -/// -/// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; -/// let binary_parser: BinaryParser = BinaryParser::from(test_bytes); -/// -/// assert_eq!(binary_parser, test_bytes[..]); -/// ``` -#[derive(Debug, Clone)] -pub struct BinaryParser(Vec); - -/// Helper function for length-prefixed fields including -/// Blob types and some AccountID types. Calculates the -/// prefix of variable length bytes. -/// -/// The length of the prefix is 1-3 bytes depending on the -/// length of the contents: -/// Content length <= 192 bytes: prefix is 1 byte -/// 192 bytes < Content length <= 12480 bytes: prefix is 2 bytes -/// 12480 bytes < Content length <= 918744 bytes: prefix is 3 bytes -/// -/// See Length Prefixing: -/// `` -fn _encode_variable_length_prefix(length: &usize) -> Result, XRPLBinaryCodecException> { - if length <= &MAX_SINGLE_BYTE_LENGTH { - Ok([*length as u8].to_vec()) - } else if length < &MAX_DOUBLE_BYTE_LENGTH { - let mut bytes = vec![]; - let b_length = *length - (MAX_SINGLE_BYTE_LENGTH + 1); - let val_a: u8 = ((b_length >> 8) + (MAX_SINGLE_BYTE_LENGTH + 1)).try_into()?; - let val_b: u8 = (b_length & 0xFF).try_into()?; - - bytes.extend_from_slice(&[val_a]); - bytes.extend_from_slice(&[val_b]); - - Ok(bytes) - } else if length <= &MAX_LENGTH_VALUE { - let mut bytes = vec![]; - let b_length = *length - MAX_DOUBLE_BYTE_LENGTH; - let val_a: u8 = ((MAX_SECOND_BYTE_VALUE + 1) + (b_length >> 16)).try_into()?; - let val_b: u8 = ((b_length >> 8) & 0xFF).try_into()?; - let val_c: u8 = (b_length & 0xFF).try_into()?; - - bytes.extend_from_slice(&[val_a]); - bytes.extend_from_slice(&[val_b]); - bytes.extend_from_slice(&[val_c]); - - Ok(bytes) - } else { - Err(XRPLBinaryCodecException::InvalidVariableLengthTooLarge { - max: MAX_LENGTH_VALUE, - }) - } -} - -pub trait Parser { - /// Peek the first byte of the BinaryParser. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// let first_byte: Option<[u8; 1]> = binary_parser.peek(); - /// - /// assert_eq!(Some([test_bytes[0]; 1]), first_byte); - /// ``` - fn peek(&self) -> Option<[u8; 1]>; - - /// Consume the first n bytes of the BinaryParser. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.skip_bytes(4) { - /// Ok(parser) => assert_eq!(*parser, test_bytes[4..]), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// ``` - fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException>; - - /// Consume and return the first n bytes of the BinaryParser. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.read(5) { - /// Ok(data) => assert_eq!(test_bytes[..5], data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// ``` - fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException>; - - /// Read 1 byte from parser and return as unsigned int. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.read_uint8() { - /// Ok(data) => assert_eq!(0, data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// ``` - fn read_uint8(&mut self) -> Result; - - /// Read 2 bytes from parser and return as unsigned int. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.read_uint16() { - /// Ok(data) => assert_eq!(17, data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// ``` - fn read_uint16(&mut self) -> Result; - - /// Read 4 bytes from parser and return as unsigned int. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.read_uint32() { - /// Ok(data) => assert_eq!(1122867, data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// ``` - fn read_uint32(&mut self) -> Result; - - /// Returns whether the binary parser has finished - /// parsing (e.g. there is nothing left in the buffer - /// that needs to be processed). - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// extern crate alloc; - /// use alloc::vec; - /// - /// let empty: &[u8] = &[]; - /// let mut buffer: Vec = vec![]; - /// let test_bytes: &[u8] = &[0, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// while !binary_parser.is_end(None) { - /// match binary_parser.read(1) { - /// Ok(data) => buffer.extend_from_slice(&data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - /// max: _, - /// found: _, - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - /// } - /// - /// assert_eq!(test_bytes, &buffer[..]); - /// // The BinaryParser is emptied as it is read. - /// assert_eq!(binary_parser, empty[..]); - /// - /// ``` - fn is_end(&self, custom_end: Option) -> bool; - - /// Reads a variable length encoding prefix and returns - /// the encoded length. The formula for decoding a length - /// prefix is described in Length Prefixing. - /// - /// See Length Prefixing: - /// `` - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinaryParser; - /// use xrpl::core::Parser; - /// use xrpl::core::binarycodec::exceptions::XRPLBinaryCodecException; - /// - /// let test_bytes: &[u8] = &[6, 17, 34, 51, 68, 85, 102]; - /// let mut binary_parser: BinaryParser = BinaryParser::from(test_bytes); - /// - /// match binary_parser.read_length_prefix() { - /// Ok(data) => assert_eq!(6, data), - /// Err(e) => match e { - /// XRPLBinaryCodecException::UnexpectedLengthPrefixRange { - /// min: _, max: _ - /// } => assert!(false), - /// _ => assert!(false) - /// } - /// } - fn read_length_prefix(&mut self) -> Result; - - /// Reads field ID from BinaryParser and returns as - /// a FieldHeader object. - fn read_field_header(&mut self) -> Result; - - /// Read the field ordinal at the head of the - /// BinaryParser and return a FieldInstance object - /// representing information about the field - /// containedin the following bytes. - fn read_field(&mut self) -> Result; - - /// Read next bytes from BinaryParser as the given type. - fn read_type(&mut self) -> Result; - - /// Read value of the type specified by field from - /// the BinaryParser. - fn read_field_value(&mut self, field: &FieldInstance) -> Result - where - T::Error: From; -} - -pub trait Serialization { - /// Write given bytes to this BinarySerializer. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinarySerializer; - /// use xrpl::core::binarycodec::Serialization; - /// - /// let mut test_bytes: Vec = [0, 17, 34, 51, 68, 85, 102].to_vec(); - /// let mut serializer: BinarySerializer = BinarySerializer::new(); - /// - /// serializer.append(&mut test_bytes.to_owned()); - /// assert_eq!(test_bytes, serializer); - /// ``` - fn append(&mut self, bytes: &[u8]) -> &Self; - - /// Write a variable length encoded value to - /// the BinarySerializer. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinarySerializer; - /// use xrpl::core::binarycodec::Serialization; - /// - /// let expected: Vec = [3, 0, 17, 34].to_vec(); - /// let mut test_bytes: Vec = [0, 17, 34].to_vec(); - /// let mut serializer: BinarySerializer = BinarySerializer::new(); - /// - /// serializer.write_length_encoded(&mut test_bytes); - /// assert_eq!(expected, serializer); - /// ``` - fn write_length_encoded(&mut self, value: &[u8]) -> &Self; - - /// Write field and value to the buffer. - /// - /// # Examples - /// - /// ## Basic usage - /// - /// ``` - /// use xrpl::core::binarycodec::BinarySerializer; - /// use xrpl::core::binarycodec::Serialization; - /// use xrpl::core::definitions::FieldInstance; - /// use xrpl::core::definitions::FieldInfo; - /// use xrpl::core::definitions::FieldHeader; - /// - /// let field_header: FieldHeader = FieldHeader { - /// type_code: -2, - /// field_code: 0, - /// }; - /// - /// let field_info: FieldInfo = FieldInfo { - /// nth: 0, - /// is_vl_encoded: false, - /// is_serialized: false, - /// is_signing_field: false, - /// r#type: "Unknown".to_string(), - /// }; - /// - /// let field_instance = FieldInstance::new(&field_info, "Generic", field_header); - /// let expected: Vec = [255, 224, 0, 17, 34].to_vec(); - /// let test_bytes: Vec = [0, 17, 34].to_vec(); - /// let mut serializer: BinarySerializer = BinarySerializer::new(); - /// - /// serializer.write_field_and_value(field_instance, &test_bytes); - /// assert_eq!(expected, serializer); - /// ``` - fn write_field_and_value(&mut self, field: FieldInstance, value: &[u8]) -> &Self; -} - -impl Serialization for BinarySerializer { - fn append(&mut self, bytes: &[u8]) -> &Self { - self.extend_from_slice(bytes); - self - } - - fn write_length_encoded(&mut self, value: &[u8]) -> &Self { - let length_prefix = _encode_variable_length_prefix(&value.len()); - - // TODO Handle unwrap better - self.extend_from_slice(&length_prefix.unwrap()); - self.extend_from_slice(value); - - self - } - - fn write_field_and_value(&mut self, field: FieldInstance, value: &[u8]) -> &Self { - self.extend_from_slice(&field.header.to_bytes()); - - if field.is_vl_encoded { - self.write_length_encoded(value); - } else { - self.extend_from_slice(value); - } - - self - } -} - -/// Peek the first byte of the BinaryParser. -impl Parser for BinaryParser { - fn peek(&self) -> Option<[u8; 1]> { - if !self.0.is_empty() { - Some(self.0[0].to_be_bytes()) - } else { - None - } - } - - fn skip_bytes(&mut self, n: usize) -> Result<&Self, XRPLBinaryCodecException> { - if n > self.0.len() { - Err(XRPLBinaryCodecException::UnexpectedParserSkipOverflow { - max: self.0.len(), - found: n, - }) - } else { - self.0 = self.0[n..].to_vec(); - Ok(self) - } - } - - fn read(&mut self, n: usize) -> Result, XRPLBinaryCodecException> { - let first_n_bytes = self.0[..n].to_owned(); - - self.skip_bytes(n)?; - Ok(first_n_bytes) - } - - fn read_uint8(&mut self) -> Result { - let result = self.read(1)?; - Ok(u8::from_be_bytes(result.try_into().or(Err( - XRPLBinaryCodecException::InvalidReadFromBytesValue, - ))?)) - } - - fn read_uint16(&mut self) -> Result { - let result = self.read(2)?; - Ok(u16::from_be_bytes(result.try_into().or(Err( - XRPLBinaryCodecException::InvalidReadFromBytesValue, - ))?)) - } - - fn read_uint32(&mut self) -> Result { - let result = self.read(4)?; - Ok(u32::from_be_bytes(result.try_into().or(Err( - XRPLBinaryCodecException::InvalidReadFromBytesValue, - ))?)) - } - - fn is_end(&self, custom_end: Option) -> bool { - if let Some(end) = custom_end { - self.0.len() <= end - } else { - self.0.is_empty() - } - } - - fn read_length_prefix(&mut self) -> Result { - let byte1: usize = self.read_uint8()? as usize; - - match byte1 { - // If the field contains 0 to 192 bytes of data, - // the first byte defines the length of the contents. - x if x <= MAX_SINGLE_BYTE_LENGTH => Ok(byte1), - // If the field contains 193 to 12480 bytes of data, - // the first two bytes indicate the length of the - // field with the following formula: - // 193 + ((byte1 - 193) * 256) + byte2 - x if x <= MAX_SECOND_BYTE_VALUE => { - let byte2: usize = self.read_uint8()? as usize; - Ok((MAX_SINGLE_BYTE_LENGTH + 1) - + ((byte1 - (MAX_SINGLE_BYTE_LENGTH + 1)) * MAX_BYTE_VALUE) - + byte2) - } - // If the field contains 12481 to 918744 bytes of data, - // the first three bytes indicate the length of the - // field with the following formula: - // 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3 - x if x <= 254 => { - let byte2: usize = self.read_uint8()? as usize; - let byte3: usize = self.read_uint8()? as usize; - - Ok(MAX_DOUBLE_BYTE_LENGTH - + ((byte1 - (MAX_SECOND_BYTE_VALUE + 1)) * MAX_DOUBLE_BYTE_VALUE) - + (byte2 * MAX_BYTE_VALUE) - + byte3) - } - _ => Err(XRPLBinaryCodecException::UnexpectedLengthPrefixRange { min: 1, max: 3 }), - } - } - - fn read_field_header(&mut self) -> Result { - let mut type_code: i16 = self.read_uint8()? as i16; - let mut field_code: i16 = type_code & 15; - - type_code >>= 4; - - if type_code == 0 { - type_code = self.read_uint8()? as i16; - - if type_code == 0 || type_code < 16 { - return Err(XRPLBinaryCodecException::UnexpectedTypeCodeRange { min: 1, max: 16 }); - }; - }; - - if field_code == 0 { - field_code = self.read_uint8()? as i16; - - if field_code == 0 || field_code < 16 { - return Err(XRPLBinaryCodecException::UnexpectedFieldCodeRange { min: 1, max: 16 }); - }; - }; - Ok(FieldHeader { - type_code, - field_code, - }) - } - - fn read_field(&mut self) -> Result { - let field_header = self.read_field_header()?; - let field_name = get_field_name_from_header(&field_header); - - if let Some(name) = field_name { - if let Some(instance) = get_field_instance(name) { - return Ok(instance); - }; - }; - - Err(XRPLBinaryCodecException::UnknownFieldName) - } - - fn read_type(&mut self) -> Result { - T::from_parser(self, None) - } - - fn read_field_value(&mut self, field: &FieldInstance) -> Result - where - T::Error: From, - { - if field.is_vl_encoded { - let length = self.read_length_prefix()?; - T::from_parser(self, Some(length)) - } else { - T::from_parser(self, None) - } - } -} +use super::types::{AccountId, STObject}; +use crate::{models::transactions::Transaction, Err}; -impl From<&[u8]> for BinaryParser { - fn from(hex_bytes: &[u8]) -> Self { - BinaryParser(hex_bytes.to_vec()) - } -} +use alloc::{borrow::Cow, string::String, vec::Vec}; +use anyhow::Result; +use core::{convert::TryFrom, fmt::Debug}; +use hex::ToHex; +use serde::{de::DeserializeOwned, Serialize}; +use strum::IntoEnumIterator; -impl From> for BinaryParser { - fn from(hex_bytes: Vec) -> Self { - BinaryParser(hex_bytes) - } -} +pub mod binary_wrappers; +pub mod exceptions; +pub(crate) mod test_cases; +pub mod utils; -impl TryFrom<&str> for BinaryParser { - type Error = XRPLBinaryCodecException; +pub use binary_wrappers::*; - fn try_from(hex_bytes: &str) -> Result { - Ok(BinaryParser(hex::decode(hex_bytes)?)) - } -} +const TRANSACTION_SIGNATURE_PREFIX: i32 = 0x53545800; +const TRANSACTION_MULTISIG_PREFIX: i32 = 0x534D5400; -impl PartialEq<[u8]> for BinaryParser { - fn eq(&self, bytes: &[u8]) -> bool { - self.0 == bytes - } +pub fn encode<'a, T, F>(signed_transaction: &T) -> Result +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + serialize_json(signed_transaction, None, None, false) } -impl PartialEq> for BinaryParser { - fn eq(&self, bytes: &Vec) -> bool { - &self.0 == bytes - } +pub fn encode_for_signing<'a, T, F>(prepared_transaction: &T) -> Result +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + serialize_json( + prepared_transaction, + Some(TRANSACTION_SIGNATURE_PREFIX.to_be_bytes().as_ref()), + None, + true, + ) } -impl ExactSizeIterator for BinaryParser { - fn len(&self) -> usize { - self.0.len() - } +pub fn encode_for_multisigning<'a, T, F>( + prepared_transaction: &T, + signing_account: Cow<'a, str>, +) -> Result +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + let signing_account_id = AccountId::try_from(signing_account.as_ref()).unwrap(); + + serialize_json( + prepared_transaction, + Some(TRANSACTION_MULTISIG_PREFIX.to_be_bytes().as_ref()), + Some(signing_account_id.as_ref()), + true, + ) } -impl Iterator for BinaryParser { - type Item = u8; - - fn next(&mut self) -> Option { - if self.is_end(None) { - None - } else { - Some(self.read_uint8().expect("BinaryParser::next")) +fn serialize_json<'a, T, F>( + prepared_transaction: &T, + prefix: Option<&[u8]>, + suffix: Option<&[u8]>, + signing_only: bool, +) -> Result +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + let mut buffer = Vec::new(); + if let Some(p) = prefix { + buffer.extend(p); + } + let json_value = match serde_json::to_value(prepared_transaction) { + Ok(v) => v, + Err(e) => { + return Err!(e); } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::alloc::string::ToString; - use alloc::string::String; + }; + let st_object = STObject::try_from_value(json_value, signing_only)?; - const TEST_HEX: &str = "00112233445566"; - - #[test] - fn test_binaryparser_from() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let ref_bytes: &[u8] = test_bytes.as_ref(); - let slice_parser = BinaryParser::from(ref_bytes); - let vec_parser = BinaryParser::from(test_bytes.to_owned()); - - assert_eq!(slice_parser, test_bytes[..]); - assert_eq!(vec_parser, test_bytes[..]); - } - - #[test] - fn test_binaryparser_try_from() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let string_parser = BinaryParser::try_from(TEST_HEX).unwrap(); - - assert_eq!(string_parser, test_bytes[..]); + buffer.extend(st_object.as_ref()); + if let Some(s) = suffix { + buffer.extend(s); } - #[test] - fn test_peek() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let binary_parser = BinaryParser::from(test_bytes.as_ref()); + let hex_string = buffer.encode_hex_upper::(); - assert_eq!(binary_parser.peek(), Some([test_bytes[0]; 1])); - } - - #[test] - fn test_skip_bytes() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - - assert!(binary_parser.skip_bytes(4).is_ok()); - assert_eq!(binary_parser, test_bytes[4..]); - } - - #[test] - fn test_read() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - let result = binary_parser.read(5); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), test_bytes[..5]); - } - - #[test] - fn test_read_uint8() { - let test_hex: &str = "01000200000003"; - let test_bytes: Vec = hex::decode(test_hex).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - let result = binary_parser.read_uint8(); - - assert!(result.is_ok()); - assert_eq!(result, Ok(1)); - } - - #[test] - fn test_read_uint16() { - let test_hex: &str = "000200000003"; - let test_bytes: Vec = hex::decode(test_hex).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - let result = binary_parser.read_uint16(); - - assert!(result.is_ok()); - assert_eq!(result, Ok(2)); - } - - #[test] - fn test_read_uint32() { - let test_hex: &str = "00000003"; - let test_bytes: Vec = hex::decode(test_hex).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - let result = binary_parser.read_uint32(); - - assert!(result.is_ok()); - assert_eq!(result, Ok(3)); - } - - #[test] - fn test_read_length_prefix() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - let result = binary_parser.read_length_prefix(); - - assert!(result.is_ok()); - assert_eq!(result, Ok(0)); - } - - // TODO Finish tests - #[test] - fn test_read_field_header() {} - - #[test] - fn test_read_field_value() {} - - #[test] - fn test_read_field_and_value() {} - - #[test] - fn test_read_type() {} - - #[test] - fn accept_peek_skip_read() { - let test_bytes: Vec = hex::decode(TEST_HEX).expect(""); - let mut binary_parser = BinaryParser::from(test_bytes.as_ref()); - - assert_eq!(binary_parser.peek(), Some([test_bytes[0]; 1])); - assert!(binary_parser.skip_bytes(3).is_ok()); - assert_eq!(binary_parser, test_bytes[3..]); - - let result = binary_parser.read(2); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), test_bytes[3..5]); - } - - #[test] - fn test_binaryserializer_write_field_and_value() { - let field_header = FieldHeader { - type_code: -2, - field_code: 0, - }; - - let field_info = FieldInfo { - nth: 0, - is_vl_encoded: false, - is_serialized: false, - is_signing_field: false, - r#type: "Unknown".to_string(), - }; - - let field_instance = FieldInstance::new(&field_info, "Generic", field_header); - let expected: Vec = [255, 224, 0, 17, 34].to_vec(); - let test_bytes: Vec = [0, 17, 34].to_vec(); - let mut serializer: BinarySerializer = BinarySerializer::new(); - - serializer.write_field_and_value(field_instance, &test_bytes); - assert_eq!(expected, serializer); - } - - /// This is currently a sanity check for private - /// [`_encode_variable_length_prefix`], which is called by - /// BinarySerializer.write_length_encoded. - #[test] - fn test_encode_variable_length_prefix() { - for case in [100_usize, 1000, 20_000] { - let blob = (0..case).map(|_| "A2").collect::(); - let mut binary_serializer: BinarySerializer = BinarySerializer::new(); - - binary_serializer.write_length_encoded(&hex::decode(blob).expect("")); - - let mut binary_parser: BinaryParser = BinaryParser::from(binary_serializer.as_ref()); - let decoded_length = binary_parser.read_length_prefix(); - - assert!(decoded_length.is_ok()); - assert_eq!(decoded_length, Ok(case)); - } - } + Ok(hex_string) } diff --git a/src/core/definitions/definitions.json b/src/core/definitions/definitions.json index 9a4f4453..1ffda30c 100644 --- a/src/core/definitions/definitions.json +++ b/src/core/definitions/definitions.json @@ -9,7 +9,7 @@ "Hash256": 5, "UInt8": 16, "Vector256": 19, - "SerializedDict": 14, + "STObject": 14, "Unknown": -2, "Transaction": 10001, "Hash160": 17, @@ -19,7 +19,7 @@ "NotPresent": 0, "UInt64": 3, "UInt32": 2, - "SerializedList": 15 + "STArray": 15 }, "LEDGER_ENTRY_TYPES": { "Any": -3, @@ -1141,7 +1141,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1151,7 +1151,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1161,7 +1161,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1171,7 +1171,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1181,7 +1181,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1191,7 +1191,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1201,7 +1201,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1211,7 +1211,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1221,7 +1221,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1231,7 +1231,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1241,7 +1241,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1251,7 +1251,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1261,7 +1261,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1271,7 +1271,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedDict" + "type": "STObject" } ], [ @@ -1281,7 +1281,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1291,7 +1291,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": false, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1301,7 +1301,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1311,7 +1311,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1321,7 +1321,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1331,7 +1331,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1341,7 +1341,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1351,7 +1351,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1361,7 +1361,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1371,7 +1371,7 @@ "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "SerializedList" + "type": "STArray" } ], [ @@ -1619,7 +1619,6 @@ "telCAN_NOT_QUEUE_BLOCKED": -389, "telCAN_NOT_QUEUE_FEE": -388, "telCAN_NOT_QUEUE_FULL": -387, - "temMALFORMED": -299, "temBAD_AMOUNT": -298, "temBAD_CURRENCY": -297, @@ -1655,7 +1654,6 @@ "temCANNOT_PREAUTH_SELF": -267, "temUNCERTAIN": -266, "temUNKNOWN": -265, - "tefFAILURE": -199, "tefALREADY": -198, "tefBAD_ADD_AUTH": -197, @@ -1675,7 +1673,6 @@ "tefBAD_AUTH_MASTER": -183, "tefINVARIANT_FAILED": -182, "tefTOO_BIG": -181, - "terRETRY": -99, "terFUNDS_SPENT": -98, "terINSUF_FEE_B": -97, @@ -1687,9 +1684,7 @@ "terLAST": -91, "terNO_RIPPLE": -90, "terQUEUED": -89, - "tesSUCCESS": 0, - "tecCLAIM": 100, "tecPATH_PARTIAL": 101, "tecUNFUNDED_ADD": 102, @@ -1731,7 +1726,6 @@ }, "TRANSACTION_TYPES": { "Invalid": -1, - "Payment": 0, "EscrowCreate": 1, "EscrowFinish": 2, @@ -1754,9 +1748,8 @@ "DepositPreauth": 19, "TrustSet": 20, "AccountDelete": 21, - "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 } -} +} \ No newline at end of file diff --git a/src/core/definitions/mod.rs b/src/core/definitions/mod.rs index 12dfe556..554c0269 100644 --- a/src/core/definitions/mod.rs +++ b/src/core/definitions/mod.rs @@ -65,7 +65,7 @@ pub struct FieldHeader { /// let field_instance: FieldInstance = /// FieldInstance::new(&field_info, "Generic", field_header); /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FieldInstance { pub nth: i16, pub is_vl_encoded: bool, @@ -146,10 +146,10 @@ impl ToBytes for FieldHeader { if self.type_code < 16 { if self.field_code < 16 { - let shift = self.type_code << 4 | self.field_code; + let shift = (self.type_code << 4 | self.field_code) as u8; header_bytes.extend_from_slice(&shift.to_be_bytes()); } else { - let shift = self.type_code << 4; + let shift = (self.type_code << 4) as u8; header_bytes.extend_from_slice(&shift.to_be_bytes()); header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); diff --git a/src/core/definitions/types.rs b/src/core/definitions/types.rs index e03c9844..8a374c89 100644 --- a/src/core/definitions/types.rs +++ b/src/core/definitions/types.rs @@ -36,7 +36,8 @@ pub struct Types { pub hash_256: i16, pub u_int_8: i16, pub vector_256: i16, - pub serialized_dict: i16, + #[serde(rename = "STObject")] + pub st_object: i16, pub unknown: i16, pub transaction: i16, pub hash_160: i16, @@ -46,7 +47,8 @@ pub struct Types { pub not_present: i16, pub u_int_64: i16, pub u_int_32: i16, - pub serialized_list: i16, + #[serde(rename = "STArray")] + pub st_array: i16, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/src/core/mod.rs b/src/core/mod.rs index 0a16f755..a5d6073b 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,7 +6,7 @@ pub mod definitions; pub mod keypairs; pub mod types; -pub use self::binarycodec::BinaryParser; -pub use self::binarycodec::BinarySerializer; -pub use self::binarycodec::Parser; +pub use self::binarycodec::binary_wrappers::BinaryParser; +pub use self::binarycodec::binary_wrappers::BinarySerializer; +pub use self::binarycodec::binary_wrappers::Parser; pub use self::definitions::load_definition_map; diff --git a/src/core/test_data/data-driven-tests.json b/src/core/test_data/data-driven-tests.json index 5f27bf2f..017624dd 100644 --- a/src/core/test_data/data-driven-tests.json +++ b/src/core/test_data/data-driven-tests.json @@ -33,11 +33,11 @@ "ordinal": 8 }, { - "name": "SerializedDict", + "name": "STObject", "ordinal": 14 }, { - "name": "SerializedList", + "name": "STArray", "ordinal": 15 }, { @@ -736,126 +736,126 @@ "expected_hex": "88" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "ObjectEndMarker", "nth_of_type": 1, "type": 14, "expected_hex": "E1" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "TransactionMetaData", "nth_of_type": 2, "type": 14, "expected_hex": "E2" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "CreatedNode", "nth_of_type": 3, "type": 14, "expected_hex": "E3" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "DeletedNode", "nth_of_type": 4, "type": 14, "expected_hex": "E4" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "ModifiedNode", "nth_of_type": 5, "type": 14, "expected_hex": "E5" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "PreviousFields", "nth_of_type": 6, "type": 14, "expected_hex": "E6" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "FinalFields", "nth_of_type": 7, "type": 14, "expected_hex": "E7" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "NewFields", "nth_of_type": 8, "type": 14, "expected_hex": "E8" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "TemplateEntry", "nth_of_type": 9, "type": 14, "expected_hex": "E9" }, { - "type_name": "SerializedDict", + "type_name": "STObject", "name": "Memo", "nth_of_type": 10, "type": 14, "expected_hex": "EA" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "ArrayEndMarker", "nth_of_type": 1, "type": 15, "expected_hex": "F1" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "Signers", "nth_of_type": 3, "type": 15, "expected_hex": "F3" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "SignerEntries", "nth_of_type": 4, "type": 15, "expected_hex": "F4" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "Template", "nth_of_type": 5, "type": 15, "expected_hex": "F5" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "Necessary", "nth_of_type": 6, "type": 15, "expected_hex": "F6" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "Sufficient", "nth_of_type": 7, "type": 15, "expected_hex": "F7" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "AffectedNodes", "nth_of_type": 8, "type": 15, "expected_hex": "F8" }, { - "type_name": "SerializedList", + "type_name": "STArray", "name": "Memos", "nth_of_type": 9, "type": 15, @@ -3948,4 +3948,4 @@ "expected_hex": "8D" } ] -} +} \ No newline at end of file diff --git a/src/core/types/exceptions.rs b/src/core/types/exceptions.rs index 06f6aec0..94e1edb3 100644 --- a/src/core/types/exceptions.rs +++ b/src/core/types/exceptions.rs @@ -6,6 +6,7 @@ use crate::utils::exceptions::ISOCodeException; use crate::utils::exceptions::JSONParseException; use crate::utils::exceptions::XRPRangeException; use strum_macros::Display; +use thiserror_no_std::Error; #[derive(Debug, Clone, PartialEq, Display)] #[non_exhaustive] @@ -17,6 +18,33 @@ pub enum XRPLTypeException { XRPLRangeError(XRPRangeException), DecimalError(rust_decimal::Error), JSONParseError(JSONParseException), + UnknownXRPLType, +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum XRPLSerializeArrayException { + #[error("Expected `Value` to be an array.")] + ExpectedArray, + #[error("Expected `Value` to be an array of objects.")] + ExpectedObjectArray, +} + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum XRPLSerializeMapException<'a> { + #[error("Expected `Value` to be an object.")] + ExpectedObject, + #[error("Field `{field}` is not allowed to have an associated tag.")] + DisallowedTag { field: &'a str }, + #[error("Cannot have mismatched Account X-Address and SourceTag")] + AccountMismatchingTags, + #[error("Cannot have mismatched Destination X-Address and DestinationTag")] + DestinationMismatchingTags, + #[error("Unknown transaction type: {0}")] + UnknownTransactionType(&'a str), + #[error("Unknown transaction result: {0}")] + UnknownTransactionResult(&'a str), + #[error("Unknown ledger entry type: {0}")] + UnknownLedgerEntryType(&'a str), } #[derive(Debug, Clone, PartialEq, Display)] diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index 91d2ec80..dac7bbb8 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -11,6 +11,12 @@ pub(crate) mod test_cases; pub mod utils; pub mod vector256; +use core::convert::TryFrom; +use core::convert::TryInto; +use core::fmt::Debug; +use core::fmt::Display; +use core::iter::FromIterator; + pub use self::account_id::AccountId; pub use self::amount::Amount; pub use self::blob::Blob; @@ -24,11 +30,154 @@ pub use self::paths::PathSet; pub use self::paths::PathStep; pub use self::vector256::Vector256; +use crate::core::binarycodec::binary_wrappers::Serialization; +use crate::core::definitions::get_field_instance; +use crate::core::definitions::get_transaction_result_code; +use crate::core::definitions::get_transaction_type_code; +use crate::core::definitions::FieldInstance; use crate::core::BinaryParser; +use crate::Err; +use alloc::borrow::Cow; +use alloc::borrow::ToOwned; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec; use alloc::vec::Vec; +use amount::IssuedCurrency; +use anyhow::Result; +use serde_json::Map; +use serde_json::Value; + +use super::addresscodec::is_valid_xaddress; +use super::addresscodec::xaddress_to_classic_address; +use super::BinarySerializer; + +const ACCOUNT: &str = "Account"; +const SOURCE_TAG: &str = "SourceTag"; +const DESTINATION: &str = "Destination"; +const DESTINATION_TAG: &str = "DestinationTag"; +const UNL_MODIFY_TX_TYPE: &str = "0066"; +const ST_OBJECT: &str = "STObject"; +const OBJECT_END_MARKER_BYTES: [u8; 1] = [0xE1]; +const ARRAY_END_MARKER: [u8; 1] = [0xF1]; -/// Contains a serialized buffer of a Serializer type. #[derive(Debug)] +pub enum XRPLTypes { + AccountID(AccountId), + Amount(Amount), + Blob(Blob), + Currency(Currency), + Hash128(Hash128), + Hash160(Hash160), + Hash256(Hash256), + Path(Path), + PathSet(PathSet), + PathStep(PathStep), + Vector256(Vector256), + STArray(STArray), + STObject(STObject), + UInt8(u8), + UInt16(u16), + UInt32(u32), + UInt64(u64), + Unknown, +} + +impl XRPLTypes { + pub fn from_value(name: &str, value: Value) -> Result { + if let Some(value) = value.as_str() { + match name { + "AccountID" => Ok(XRPLTypes::AccountID(Self::type_from_str(value)?)), + "Amount" => Ok(XRPLTypes::Amount(Self::type_from_str(value)?)), + "Blob" => Ok(XRPLTypes::Blob(Self::type_from_str(value)?)), + "Currency" => Ok(XRPLTypes::Currency(Self::type_from_str(value)?)), + "Hash128" => Ok(XRPLTypes::Hash128(Self::type_from_str(value)?)), + "Hash160" => Ok(XRPLTypes::Hash160(Self::type_from_str(value)?)), + "Hash256" => Ok(XRPLTypes::Hash256(Self::type_from_str(value)?)), + _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + } + } else if let Some(value) = value.as_u64() { + match name { + "UInt8" => Ok(XRPLTypes::UInt8(value as u8)), + "UInt16" => Ok(XRPLTypes::UInt16(value as u16)), + "UInt32" => Ok(XRPLTypes::UInt32(value as u32)), + "UInt64" => Ok(XRPLTypes::UInt64(value as u64)), + _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + } + } else if let Some(value) = value.as_object() { + match name { + "Amount" => Ok(XRPLTypes::Amount(Self::amount_from_map(value.to_owned())?)), + "STObject" => Ok(XRPLTypes::STObject(STObject::try_from_value( + Value::Object(value.to_owned()), + false, + )?)), + _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + } + } else if let Some(value) = value.as_array() { + match name { + "STArray" => Ok(XRPLTypes::STArray(STArray::try_from_value(Value::Array( + value.to_owned(), + ))?)), + _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), + } + } else { + Err!(exceptions::XRPLTypeException::UnknownXRPLType) + } + } + + fn type_from_str<'a, T>(value: &'a str) -> Result + where + T: TryFrom<&'a str>, + >::Error: Display, + { + match value.try_into() { + Ok(value) => Ok(value), + Err(error) => Err!(error), + } + } + + fn amount_from_map(value: Map) -> Result + where + T: TryFrom, + >::Error: Display, + { + match IssuedCurrency::try_from(Value::Object(value)) { + Ok(value) => match value.try_into() { + Ok(value) => Ok(value), + Err(error) => Err!(error), + }, + Err(error) => Err!(error), + } + } +} + +impl Into for XRPLTypes { + fn into(self) -> SerializedType { + match self { + XRPLTypes::AccountID(account_id) => SerializedType::from(account_id), + XRPLTypes::Amount(amount) => SerializedType::from(amount), + XRPLTypes::Blob(blob) => SerializedType::from(blob), + XRPLTypes::Currency(currency) => SerializedType::from(currency), + XRPLTypes::Hash128(hash128) => SerializedType::from(hash128), + XRPLTypes::Hash160(hash160) => SerializedType::from(hash160), + XRPLTypes::Hash256(hash256) => SerializedType::from(hash256), + XRPLTypes::Path(path) => SerializedType::from(path), + XRPLTypes::PathSet(path_set) => SerializedType::from(path_set), + XRPLTypes::PathStep(path_step) => SerializedType::from(path_step), + XRPLTypes::Vector256(vector256) => SerializedType::from(vector256), + XRPLTypes::STArray(st_array) => st_array.0, + XRPLTypes::STObject(st_object) => st_object.0, + XRPLTypes::UInt8(value) => SerializedType(value.to_be_bytes().to_vec()), + XRPLTypes::UInt16(value) => SerializedType(value.to_be_bytes().to_vec()), + XRPLTypes::UInt32(value) => SerializedType(value.to_be_bytes().to_vec()), + XRPLTypes::UInt64(value) => SerializedType(value.to_be_bytes().to_vec()), + XRPLTypes::Unknown => SerializedType(vec![]), + } + } +} + +/// Contains a serialized buffer of a Serializer type. +#[derive(Debug, Clone)] pub struct SerializedType(Vec); /// Class for serializing and deserializing Lists of objects. @@ -36,14 +185,299 @@ pub struct SerializedType(Vec); /// See Array Fields: /// `` #[derive(Debug)] -pub struct SerializedList(SerializedType); +pub struct STArray(SerializedType); + +impl STArray { + /// Create a SerializedArray from a serde_json::Value. + /// + /// ``` + /// use xrpl::core::types::STArray; + /// use serde_json::Value; + /// use hex::ToHex; + /// + /// let array_end_marker = [0xF1]; + /// let memo = r#"{ + /// "Memo": { + /// "MemoType": "687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963", + /// "MemoData": "72656E74" + /// } + /// }"#; + /// let memo_hex = "EA7C1F687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E657269637D0472656E74E1"; + /// let expected_json = Value::Array(vec![serde_json::from_str(memo).unwrap(), serde_json::from_str(memo).unwrap()]); + /// let expected_hex = memo_hex.to_owned() + memo_hex + &array_end_marker.to_vec().encode_hex_upper::(); + /// let st_array = STArray::try_from_value(expected_json).unwrap(); + /// let actual_hex = hex::encode_upper(st_array.as_ref()); + /// + /// assert_eq!(actual_hex, expected_hex); + /// ``` + pub fn try_from_value(value: Value) -> Result { + if let Some(array) = value.as_array() { + if !array.is_empty() && array.iter().filter(|v| v.is_object()).count() != array.len() { + Err!(exceptions::XRPLSerializeArrayException::ExpectedObjectArray) + } else { + let mut serializer = BinarySerializer::new(); + for object in array { + let obj = match object { + Value::Object(map) => map, + _ => { + return Err!( + exceptions::XRPLSerializeArrayException::ExpectedObjectArray + ) + } + }; + let transaction = STObject::try_from_value(Value::Object(obj.clone()), false)?; + serializer.append(transaction.as_ref().to_vec().as_mut()); + } + serializer.append(ARRAY_END_MARKER.to_vec().as_mut()); + Ok(STArray(serializer.into())) + } + } else { + Err!(exceptions::XRPLSerializeArrayException::ExpectedArray) + } + } +} + +impl XRPLType for STArray { + type Error = anyhow::Error; + + fn new(buffer: Option<&[u8]>) -> Result { + if let Some(data) = buffer { + Ok(STArray(SerializedType(data.to_vec()))) + } else { + Ok(STArray(SerializedType(vec![]))) + } + } +} + +impl AsRef<[u8]> for STArray { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} /// Class for serializing/deserializing Indexmaps of objects. /// /// See Object Fields: /// `` #[derive(Debug)] -pub struct SerializedMap(SerializedType); +pub struct STObject(SerializedType); + +impl STObject { + /// Create a SerializedMap from a serde_json::Value. + /// + /// ``` + /// use xrpl::core::types::STObject; + /// + /// let expected_json = r#"{ + /// "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", + /// "Fee": "10", + /// "Flags": 0, + /// "Sequence": 103929, + /// "SigningPubKey": "028472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F418D6A7166", + /// "TakerGets": { + /// "value": "1694.768", + /// "currency": "ILS", + /// "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9" + /// }, + /// "TakerPays": "98957503520", + /// "TransactionType": "OfferCreate", + /// "TxnSignature": "304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C0591CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C" + /// }"#; + /// + /// let buffer = "120007220000000024000195F964400000170A53AC2065D5460561E\ + /// C9DE000000000000000000000000000494C53000000000092D70596\ + /// 8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210\ + /// 28472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F\ + /// 418D6A71667447304502202ABE08D5E78D1E74A4C18F2714F64E87B\ + /// 8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C059\ + /// 1CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C811439408\ + /// A69F0895E62149CFCC006FB89FA7D1E6E5D"; + /// let value = serde_json::from_str(expected_json).unwrap(); + /// let serialized_map = STObject::try_from_value(value, false).unwrap(); + /// let hex = hex::encode_upper(serialized_map.as_ref()); + /// assert_eq!(hex, buffer); + /// ``` + pub fn try_from_value(value: Value, signing_only: bool) -> Result { + let object = match value { + Value::Object(map) => map, + _ => return Err!(exceptions::XRPLSerializeMapException::ExpectedObject), + }; + let mut serializer = BinarySerializer::new(); + let mut value_xaddress_handled = Map::new(); + for (field, value) in &object { + if let Some(value) = value.as_str() { + if is_valid_xaddress(value) { + let handled_xaddress = handle_xaddress(field.into(), value.into())?; + if let Some(handled_tag) = handled_xaddress.get(SOURCE_TAG) { + if let Some(object_tag) = object.get(SOURCE_TAG) { + if handled_tag != object_tag { + return Err!( + exceptions::XRPLSerializeMapException::AccountMismatchingTags + ); + } + } + } + if let Some(handled_tag) = handled_xaddress.get(DESTINATION_TAG) { + if let Some(object_tag) = object.get(DESTINATION_TAG) { + if handled_tag != object_tag { + return Err!( + exceptions::XRPLSerializeMapException::DestinationMismatchingTags + ); + } + } + } + value_xaddress_handled.extend(handled_xaddress); + } else if field == "TransactionType" { + let transaction_type_code = match get_transaction_type_code(value) { + Some(code) => code, + None => { + return Err!( + exceptions::XRPLSerializeMapException::UnknownTransactionType( + value + ) + ) + } + }; + value_xaddress_handled.insert( + field.to_owned(), + Value::Number(transaction_type_code.to_owned().into()), + ); + } else if field == "TransactionResult" { + let transaction_result_code = match get_transaction_result_code(value) { + Some(code) => code, + None => { + return Err!( + exceptions::XRPLSerializeMapException::UnknownTransactionResult( + value + ) + ) + } + }; + value_xaddress_handled.insert( + field.to_owned(), + Value::Number(transaction_result_code.to_owned().into()), + ); + } else if field == "LedgerEntryType" { + let ledger_entry_type_code = match get_transaction_type_code(value) { + Some(code) => code, + None => { + return Err!( + exceptions::XRPLSerializeMapException::UnknownLedgerEntryType( + value + ) + ) + } + }; + value_xaddress_handled.insert( + field.to_owned(), + Value::Number(ledger_entry_type_code.to_owned().into()), + ); + } else { + value_xaddress_handled + .insert(field.to_owned(), Value::String(value.to_owned())); + } + } else { + value_xaddress_handled.insert(field.to_owned(), value.clone()); + } + } + + let mut sorted_keys: Vec = Vec::new(); + for (field, _) in &value_xaddress_handled { + let field_instance = get_field_instance(&field); + if let Some(field_instance) = field_instance { + if value_xaddress_handled.contains_key(&field_instance.name) + && field_instance.is_serialized + { + sorted_keys.push(field_instance); + } + } + } + sorted_keys.sort_by_key(|k| k.ordinal); + if signing_only { + sorted_keys.retain(|k| k.is_signing); + } + let mut is_unl_modify = false; + + for field_instance in &sorted_keys { + let associated_value = match value_xaddress_handled.get(&field_instance.name) { + Some(value) => value, + None => Err(anyhow::anyhow!( + "Error prossessing field: {}", + field_instance.name + ))?, + }; + let associated_value = XRPLTypes::from_value( + &field_instance.associated_type, + associated_value.to_owned(), + )?; + let associated_value: SerializedType = associated_value.into(); + if field_instance.name == "TransactionType" + && associated_value.to_string() == UNL_MODIFY_TX_TYPE + { + is_unl_modify = true; + } + let is_unl_modify_workaround = field_instance.name == "Account" && is_unl_modify; + + serializer.write_field_and_value( + field_instance.to_owned(), + associated_value.as_ref(), + is_unl_modify_workaround, + ); + if field_instance.associated_type == ST_OBJECT { + serializer.append(OBJECT_END_MARKER_BYTES.to_vec().as_mut()); + } + } + + Ok(STObject(serializer.into())) + } +} + +impl XRPLType for STObject { + type Error = anyhow::Error; + + fn new(buffer: Option<&[u8]>) -> Result { + if let Some(data) = buffer { + Ok(STObject(SerializedType(data.to_vec()))) + } else { + Ok(STObject(SerializedType(vec![]))) + } + } +} + +impl AsRef<[u8]> for STObject { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +fn handle_xaddress(field: Cow, xaddress: Cow) -> Result> { + let (classic_address, tag, _is_test_net) = match xaddress_to_classic_address(&xaddress) { + Ok((classic_address, tag, is_test_net)) => (classic_address, tag, is_test_net), + Err(e) => return Err!(e), + }; + if let Some(tag) = tag { + if &field == DESTINATION { + let tag_name = DESTINATION_TAG; + Ok(Map::from_iter(vec![ + (field.to_string(), Value::String(classic_address)), + (tag_name.to_string(), Value::Number(tag.into())), + ])) + } else if &field == ACCOUNT { + let tag_name = SOURCE_TAG; + Ok(Map::from_iter(vec![ + (field.to_string(), Value::String(classic_address)), + (tag_name.to_string(), Value::Number(tag.into())), + ])) + } else { + Err!(exceptions::XRPLSerializeMapException::DisallowedTag { field: &field }) + } + } else { + Ok(Map::from_iter(vec![( + field.to_string(), + Value::String(classic_address), + )])) + } +} /// An XRPL Type will implement this trait. /// @@ -114,6 +548,27 @@ pub trait TryFromParser { Self: Sized; } +impl ToString for SerializedType { + /// Get the hex representation of the SerializedType bytes. + fn to_string(&self) -> String { + hex::encode_upper(self.0.as_slice()) + } +} + +impl From> for SerializedType { + /// Create a SerializedType from a Vec. + fn from(buffer: Vec) -> Self { + SerializedType(buffer) + } +} + +impl AsRef<[u8]> for SerializedType { + /// Get a reference of the byte representation. + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + impl From for SerializedType where T: XRPLType + AsRef<[u8]>, diff --git a/src/lib.rs b/src/lib.rs index 90792129..8e527e20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,8 @@ pub mod macros; feature = "transactions" ))] pub mod models; +#[cfg(feature = "transaction-helpers")] +pub mod transaction; #[cfg(feature = "utils")] pub mod utils; pub mod wallet; diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index b4e6a6a5..91c3d6a1 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -111,6 +111,14 @@ impl<'a> TryInto for XRPAmount<'a> { } } +impl<'a> TryInto> for XRPAmount<'a> { + type Error = anyhow::Error; + + fn try_into(self) -> Result, Self::Error> { + Ok(self.0) + } +} + impl<'a> PartialOrd for XRPAmount<'a> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.0.cmp(&other.0)) diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 200b8b2b..a52d7406 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -16,11 +16,13 @@ pub mod exceptions; pub mod fee; pub mod ledger; pub mod server_state; +pub mod submit; pub use account_info::*; pub use fee::*; pub use ledger::*; pub use server_state::*; +pub use submit::*; use crate::Err; @@ -32,6 +34,7 @@ pub enum XRPLResult<'a> { AccountInfo(AccountInfo<'a>), Ledger(Ledger<'a>), ServerState(ServerState<'a>), + Submit(Submit<'a>), Other(Value), } @@ -59,6 +62,32 @@ impl<'a> From> for XRPLResult<'a> { } } +impl<'a> From> for XRPLResult<'a> { + fn from(submit: Submit<'a>) -> Self { + XRPLResult::Submit(submit) + } +} + +impl<'a> From for XRPLResult<'a> { + fn from(value: Value) -> Self { + XRPLResult::Other(value) + } +} + +impl<'a> TryInto for XRPLResult<'a> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match self { + XRPLResult::Other(value) => Ok(value), + res => match serde_json::to_value(res) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + } + } +} + impl XRPLResult<'_> { pub(crate) fn get_name(&self) -> String { match self { @@ -66,6 +95,7 @@ impl XRPLResult<'_> { XRPLResult::AccountInfo(_) => "AccountInfo".to_string(), XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), + XRPLResult::Submit(_) => "Submit".to_string(), XRPLResult::Other(_) => "Other".to_string(), } } diff --git a/src/models/results/submit.rs b/src/models/results/submit.rs new file mode 100644 index 00000000..3afd25c8 --- /dev/null +++ b/src/models/results/submit.rs @@ -0,0 +1,42 @@ +use core::convert::TryFrom; + +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{models::results::exceptions::XRPLResultException, Err}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Submit<'a> { + engine_result: Cow<'a, str>, + engine_result_code: i32, + engine_result_message: Cow<'a, str>, + tx_blob: Cow<'a, str>, + tx_json: Value, + accepted: Option, + account_sequence_available: Option, + account_sequence_next: Option, + applied: Option, + broadcast: Option, + kept: Option, + queued: Option, + open_ledger_cost: Option>, + validated_ledger_index: Option, +} + +impl<'a> TryFrom> for Submit<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::Submit(server_state) => Ok(server_state), + res => Err!(XRPLResultException::UnexpectedResultType( + "Submit".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index a16503c0..bbee7124 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -22,6 +22,16 @@ pub enum XRPLTransactionException<'a> { #[cfg(feature = "std")] impl<'a> alloc::error::Error for XRPLTransactionException<'a> {} +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum XRPLTransactionFieldException<'a> { + #[error("Transaction is missing common field `{0:?}`")] + FieldMissing(&'a str), + #[error("There is no transaction common field `{0:?}`")] + InvalidCommonField(&'a str), + #[error("There is no account field named `{0:?}`")] + UnknownAccountField(&'a str), +} + #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum XRPLAccountSetException<'a> { /// A fields value exceeds its maximum value. diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 4c2254b1..ff72c422 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -236,6 +236,21 @@ where } } +impl CommonFields<'_, T> +where + T: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + pub fn is_signed(&self) -> bool { + if let Some(signers) = &self.signers { + signers + .iter() + .all(|signer| signer.txn_signature.len() > 0 && signer.signing_pub_key.len() > 0) + } else { + self.txn_signature.is_some() && self.signing_pub_key.is_some() + } + } +} + impl<'a, T> Transaction<'a, T> for CommonFields<'a, T> where T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, @@ -295,9 +310,9 @@ pub struct Memo { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, new)] #[serde(rename_all = "PascalCase")] pub struct Signer<'a> { - account: Cow<'a, str>, - txn_signature: Cow<'a, str>, - signing_pub_key: Cow<'a, str>, + pub account: Cow<'a, str>, + pub txn_signature: Cow<'a, str>, + pub signing_pub_key: Cow<'a, str>, } /// Standard functions for transactions. diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 6be16a46..e4dab26c 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -119,7 +119,7 @@ impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { } fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, PaymentFlag> { - self.common_fields.get_mut_common_fields() + &mut self.common_fields } } diff --git a/src/transaction/exceptions.rs b/src/transaction/exceptions.rs new file mode 100644 index 00000000..d0c0298a --- /dev/null +++ b/src/transaction/exceptions.rs @@ -0,0 +1,7 @@ +use thiserror_no_std::Error; + +#[derive(Debug, PartialEq, Error)] +pub enum XRPLMultisignException { + #[error("No signers set in the transaction. Use `sign` function with `multisign = true`.")] + NoSigners, +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs new file mode 100644 index 00000000..71b662ed --- /dev/null +++ b/src/transaction/mod.rs @@ -0,0 +1,4 @@ +pub mod exceptions; +mod multisign; + +pub use multisign::*; diff --git a/src/transaction/multisign.rs b/src/transaction/multisign.rs new file mode 100644 index 00000000..8159bdb1 --- /dev/null +++ b/src/transaction/multisign.rs @@ -0,0 +1,80 @@ +use core::fmt::Debug; + +use alloc::vec::Vec; +use anyhow::Result; +use serde::Serialize; +use strum::IntoEnumIterator; + +use crate::{ + core::addresscodec::decode_classic_address, models::transactions::Transaction, + transaction::exceptions::XRPLMultisignException, Err, +}; + +pub fn multisign<'a, T, F>(transaction: &mut T, tx_list: &'a Vec) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq + 'a, + T: Transaction<'a, F>, +{ + let mut decoded_tx_signers = Vec::new(); + for tx in tx_list { + let tx_signers = match tx.get_common_fields().signers.as_ref() { + Some(signers) => signers, + None => return Err!(XRPLMultisignException::NoSigners), + }; + let tx_signer = match tx_signers.get(0) { + Some(signer) => signer, + None => return Err!(XRPLMultisignException::NoSigners), + }; + decoded_tx_signers.push(tx_signer.clone()); + } + decoded_tx_signers + .sort_by_key(|signer| decode_classic_address(signer.account.as_ref()).unwrap()); + transaction.get_mut_common_fields().signers = Some(decoded_tx_signers); + + Ok(()) +} + +#[cfg(test)] +mod test { + use alloc::borrow::Cow; + + use super::*; + use crate::asynch::transaction::sign; + use crate::models::transactions::AccountSet; + use crate::wallet::Wallet; + + #[tokio::test] + async fn test_multisign() { + let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let wallet1 = Wallet::create(None).unwrap(); + let wallet2 = Wallet::create(None).unwrap(); + let mut multi_signed_tx = AccountSet::new( + Cow::from(wallet.classic_address.clone()), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // "example.com" + None, + None, + None, + None, + None, + None, + ); + let mut tx_1 = multi_signed_tx.clone(); + sign(&mut tx_1, &wallet1, true).unwrap(); + let mut tx_2 = multi_signed_tx.clone(); + sign(&mut tx_2, &wallet2, true).unwrap(); + let tx_list = [tx_1.clone(), tx_2.clone()].to_vec(); + + multisign(&mut multi_signed_tx, &tx_list).unwrap(); + assert!(multi_signed_tx.get_common_fields().is_signed()); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3a076c45..7f06707e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,6 +2,7 @@ pub mod exceptions; pub mod time_conversion; +pub(crate) mod transactions; pub mod xrpl_conversion; pub use self::time_conversion::*; @@ -69,7 +70,7 @@ pub fn is_iso_hex(value: &str) -> bool { } /// Generate a random id. -pub fn get_random_id(rng: &mut T) -> String { +pub fn get_random_id(rng: &mut T) -> String { let id: u32 = rng.gen(); id.to_string() } diff --git a/src/utils/transactions.rs b/src/utils/transactions.rs new file mode 100644 index 00000000..739954f5 --- /dev/null +++ b/src/utils/transactions.rs @@ -0,0 +1,83 @@ +use core::fmt::Debug; + +use anyhow::Result; +use serde::{de::DeserializeOwned, Serialize}; +use strum::IntoEnumIterator; + +use crate::{ + models::transactions::{Transaction, XRPLTransactionFieldException}, + Err, +}; + +pub fn get_transaction_field_value<'a, F, T, R>(transaction: &T, field_name: &str) -> Result +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize, + R: DeserializeOwned, +{ + match serde_json::to_value(transaction) { + Ok(transaction_json) => match transaction_json.get(field_name) { + Some(common_field_value) => { + match serde_json::from_value::(common_field_value.clone()) { + Ok(val) => Ok(val), + Err(error) => Err!(error), + } + } + None => Err!(XRPLTransactionFieldException::FieldMissing(field_name)), + }, + Err(error) => Err!(error), + } +} + +pub fn set_transaction_field_value<'a, F, T, V>( + transaction: &mut T, + field_name: &str, + field_value: V, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned, + V: Serialize, +{ + match serde_json::to_value(&mut *transaction) { + Ok(mut transaction_json) => { + transaction_json[field_name] = match serde_json::to_value(field_value) { + Ok(json_value) => json_value, + Err(error) => return Err!(error), + }; + match serde_json::from_value::(transaction_json) { + Ok(val) => { + *transaction = val; + Ok(()) + } + Err(error) => Err!(error), + } + } + Err(error) => Err!(error), + } +} + +pub fn validate_transaction_has_field<'a, T, F>(transaction: &T, field_name: &str) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize, +{ + match serde_json::to_value(transaction) { + Ok(transaction_json) => match transaction_json.get(field_name) { + Some(_) => Ok(()), + None => Err!(XRPLTransactionFieldException::FieldMissing(field_name)), + }, + Err(error) => Err!(error), + } +} + +pub fn validate_common_fied(common_field_name: &str) -> Result<()> { + match common_field_name { + "Account" | "TransactionType" | "Fee" | "Sequence" | "AccountTxnID" | "Flags" + | "LastLedgerSequence" | "Memos" | "NetworkID" | "Signers" | "SourceTag" + | "SigningPubKey" | "TicketSequence" | "TxnSignature" => Ok(()), + _ => Err!(XRPLTransactionFieldException::InvalidCommonField( + common_field_name + )), + } +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e6ad0d4d..d3fe32d6 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -25,7 +25,6 @@ mod embedded_ws_clients { use super::constants::*; use anyhow::anyhow; use anyhow::Result; - use rand::rngs::OsRng; use std::io; use tokio::net::TcpStream; use tokio_util::codec::Framed; @@ -41,12 +40,12 @@ mod embedded_ws_clients { Framed, Vec, io::Error, - OsRng, + rand_core::OsRng, SingleExecutorMutex, WebsocketOpen, >, > { - let rng = OsRng {}; + let rng = rand_core::OsRng; let url = ECHO_WS_SERVER.parse().unwrap(); match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { From 95aab63446ac79329e8651b757fe8d41484d48c8 Mon Sep 17 00:00:00 2001 From: LimpidCrypto <97235361+LimpidCrypto@users.noreply.github.com> Date: Sun, 1 Sep 2024 08:46:30 +0200 Subject: [PATCH 080/113] Account helpers (#81) * add remaining account helpers --------- Co-authored-by: LimpidCrypto --- src/asynch/account/mod.rs | 75 ++++++++++++++++++++++++++++++-- src/models/results/account_tx.rs | 35 +++++++++++++++ src/models/results/mod.rs | 26 +++++++---- 3 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 src/models/results/account_tx.rs diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 53917f69..e392853b 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -3,12 +3,31 @@ use anyhow::Result; use crate::{ core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, - models::{ledger::AccountRoot, requests::AccountInfo, results}, + models::{ + amount::XRPAmount, + ledger::AccountRoot, + requests::{AccountInfo, AccountTx}, + results, + }, Err, }; use super::clients::AsyncClient; +pub async fn does_account_exist( + address: Cow<'_, str>, + client: &C, + ledger_index: Option>, +) -> Result +where + C: AsyncClient, +{ + match get_account_root(address, client, ledger_index.unwrap_or("validated".into())).await { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } +} + pub async fn get_next_valid_seq_number( address: Cow<'_, str>, client: &impl AsyncClient, @@ -19,11 +38,30 @@ pub async fn get_next_valid_seq_number( Ok(account_info.sequence) } -pub async fn get_account_root<'a>( +pub async fn get_xrp_balance<'a: 'b, 'b, C>( address: Cow<'a, str>, - client: &impl AsyncClient, + client: &C, + ledger_index: Option>, +) -> Result> +where + C: AsyncClient, +{ + let account_info = + get_account_root(address, client, ledger_index.unwrap_or("validated".into())).await?; + match account_info.balance { + Some(balance) => Ok(balance), + None => Ok(0.into()), + } +} + +pub async fn get_account_root<'a: 'b, 'b, C>( + address: Cow<'a, str>, + client: &C, ledger_index: Cow<'a, str>, -) -> Result> { +) -> Result> +where + C: AsyncClient, +{ let mut classic_address = address; if is_valid_xaddress(&classic_address) { classic_address = match xaddress_to_classic_address(&classic_address) { @@ -50,3 +88,32 @@ pub async fn get_account_root<'a>( .try_into_result::>()? .account_data) } + +pub async fn get_latest_transaction<'a: 'b, 'b, C>( + mut address: Cow<'a, str>, + client: &C, +) -> Result> +where + C: AsyncClient, +{ + if is_valid_xaddress(&address) { + address = match xaddress_to_classic_address(&address) { + Ok((address, _, _)) => address.into(), + Err(e) => return Err!(e), + }; + } + let account_tx = AccountTx::new( + None, + address, + None, + Some("validated".into()), + None, + None, + None, + None, + Some(1), + None, + ); + let response = client.request(account_tx.into()).await?; + response.try_into_result::>() +} diff --git a/src/models/results/account_tx.rs b/src/models/results/account_tx.rs new file mode 100644 index 00000000..35b49834 --- /dev/null +++ b/src/models/results/account_tx.rs @@ -0,0 +1,35 @@ +use core::convert::TryFrom; + +use alloc::{borrow::Cow, vec::Vec}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{models::results::exceptions::XRPLResultException, Err}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct AccountTx<'a> { + pub account: Cow<'a, str>, + pub ledger_index_min: Option, + pub ledger_index_max: Option, + pub limit: Option, + pub marker: Option, + pub transactions: Vec, + pub validated: Option, +} + +impl<'a> TryFrom> for AccountTx<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::AccountTx(account_tx) => Ok(account_tx), + res => Err!(XRPLResultException::UnexpectedResultType( + "AccountTx".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index a52d7406..adbf2c79 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; pub mod account_info; +pub mod account_tx; pub mod exceptions; pub mod fee; pub mod ledger; @@ -19,6 +20,7 @@ pub mod server_state; pub mod submit; pub use account_info::*; +pub use account_tx::*; pub use fee::*; pub use ledger::*; pub use server_state::*; @@ -30,26 +32,33 @@ use super::requests::XRPLRequest; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { - Fee(Fee<'a>), AccountInfo(AccountInfo<'a>), + AccountTx(AccountTx<'a>), + Fee(Fee<'a>), Ledger(Ledger<'a>), ServerState(ServerState<'a>), Submit(Submit<'a>), Other(Value), } -impl<'a> From> for XRPLResult<'a> { - fn from(fee: Fee<'a>) -> Self { - XRPLResult::Fee(fee) - } -} - 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(account_tx: AccountTx<'a>) -> Self { + XRPLResult::AccountTx(account_tx) + } +} + +impl<'a> From> for XRPLResult<'a> { + fn from(fee: Fee<'a>) -> Self { + XRPLResult::Fee(fee) + } +} + impl<'a> From> for XRPLResult<'a> { fn from(ledger: Ledger<'a>) -> Self { XRPLResult::Ledger(ledger) @@ -91,8 +100,9 @@ impl<'a> TryInto for XRPLResult<'a> { impl XRPLResult<'_> { pub(crate) fn get_name(&self) -> String { match self { - XRPLResult::Fee(_) => "Fee".to_string(), XRPLResult::AccountInfo(_) => "AccountInfo".to_string(), + XRPLResult::AccountTx(_) => "AccountTx".to_string(), + XRPLResult::Fee(_) => "Fee".to_string(), XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), From f17ee2451fa03762fe9d68e69f89cdf5b75e3c1a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 1 Sep 2024 08:46:29 +0000 Subject: [PATCH 081/113] add get_latest_open_ledger_sequence --- src/asynch/ledger/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index e4c5f830..cd03be65 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -35,6 +35,30 @@ pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> .ledger_index) } +pub async fn get_latest_open_ledger_sequence(client: &impl AsyncClient) -> Result { + let ledger_response = client + .request( + Ledger::new( + None, + None, + None, + None, + None, + None, + Some("open".into()), + None, + None, + None, + ) + .into(), + ) + .await?; + + Ok(ledger_response + .try_into_result::>()? + .ledger_index) +} + pub enum FeeType { Open, Minimum, From f6777893e7404f12ec66b953dfda132784e11db3 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 4 Sep 2024 16:04:17 +0000 Subject: [PATCH 082/113] add submit_and_wait --- .gitignore | 2 + Cargo.toml | 4 + rustc-ice-2024-08-27T11_36_40-17775.txt | 59 ------- src/asynch/transaction/exceptions.rs | 16 ++ src/asynch/transaction/mod.rs | 205 +++++++++++----------- src/asynch/transaction/submit_and_wait.rs | 204 +++++++++++++++++++++ src/core/binarycodec/mod.rs | 4 +- src/core/definitions/mod.rs | 10 +- src/core/types/mod.rs | 3 + src/models/requests/tx.rs | 4 + src/models/results/exceptions.rs | 8 + src/models/results/mod.rs | 156 +++++++++++++++- src/models/results/submit.rs | 28 +-- src/models/results/tx.rs | 40 +++++ src/models/transactions/exceptions.rs | 1 + src/models/transactions/mod.rs | 73 +++++++- 16 files changed, 630 insertions(+), 187 deletions(-) delete mode 100644 rustc-ice-2024-08-27T11_36_40-17775.txt create mode 100644 src/asynch/transaction/submit_and_wait.rs create mode 100644 src/models/results/tx.rs diff --git a/.gitignore b/.gitignore index c59e98f8..e3e4c984 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ Cargo.lock src/main.rs **/.DS_Store + +rustc-ice* diff --git a/Cargo.toml b/Cargo.toml index deda7588..52af12d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ embedded-nal-async = { version = "0.7.1", optional = true } reqwless = { version = "0.12.1", optional = true } # json-rpc-std reqwest = { version = "0.12.5", features = ["json"], optional = true } +embassy-time = { version = "0.3.2", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -123,6 +124,7 @@ transaction-helpers = [ "results", "transactions", "ledger", + "embassy-time", ] amounts = ["core"] currencies = ["core"] @@ -160,4 +162,6 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", + "embassy-time/std", ] +embassy-time = ["dep:embassy-time"] diff --git a/rustc-ice-2024-08-27T11_36_40-17775.txt b/rustc-ice-2024-08-27T11_36_40-17775.txt deleted file mode 100644 index d13252de..00000000 --- a/rustc-ice-2024-08-27T11_36_40-17775.txt +++ /dev/null @@ -1,59 +0,0 @@ -thread 'rustc' panicked at /rustc/80eb5a8e910e5185d47cdefe3732d839c78a5e7e/compiler/rustc_query_system/src/query/plumbing.rs:727:9: -Found unstable fingerprints for evaluate_obligation(9acaf53e42db4106-b70f9be4bd000590): Ok(EvaluatedToAmbig) -stack backtrace: - 0: 0x7f2385fa3de5 - std::backtrace::Backtrace::create::h5fc94655595d7e27 - 1: 0x7f2384710fe5 - std::backtrace::Backtrace::force_capture::h3ae8cd2c99c0732c - 2: 0x7f23838b111e - std[edc1ba278773f0d3]::panicking::update_hook::>::{closure#0} - 3: 0x7f2384728e27 - std::panicking::rust_panic_with_hook::h9e59a429b59b11d4 - 4: 0x7f2384728ae7 - std::panicking::begin_panic_handler::{{closure}}::h97d6e0922a833e87 - 5: 0x7f23847262e9 - std::sys::backtrace::__rust_end_short_backtrace::ha09625205ff6b122 - 6: 0x7f23847287b4 - rust_begin_unwind - 7: 0x7f2381922f53 - core::panicking::panic_fmt::h089121e4cbbc3220 - 8: 0x7f23841d9c41 - rustc_query_system[cb6aa6728468b543]::query::plumbing::incremental_verify_ich_failed:: - 9: 0x7f238558c6af - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, rustc_middle[354bbee2ce4c460]::query::erase::Erased<[u8; 2usize]>>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 10: 0x7f238558ab62 - rustc_query_impl[2dfeb81381ece16c]::query_impl::evaluate_obligation::get_query_incr::__rust_end_short_backtrace - 11: 0x7f238246b63e - ::evaluate_obligation_no_overflow - 12: 0x7f2381749e92 - , ::consider_candidates::{closure#0}>, ::consider_candidates::{closure#1}> as core[8c2a069408211e69]::iter::traits::iterator::Iterator>::next - 13: 0x7f238552a7b4 - ::pick_method - 14: 0x7f2385529ed4 - ::pick_core - 15: 0x7f2382485c0e - ::lookup_probe - 16: 0x7f2385931fec - ::check_expr_with_expectation_and_args - 17: 0x7f23859345ae - ::check_expr_with_expectation_and_args - 18: 0x7f2385935734 - ::check_expr_with_expectation_and_args - 19: 0x7f2385935734 - ::check_expr_with_expectation_and_args - 20: 0x7f2385933b44 - ::check_expr_with_expectation_and_args - 21: 0x7f238593362e - ::check_expr_with_expectation_and_args - 22: 0x7f238592aa16 - ::check_block_with_expected - 23: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args - 24: 0x7f238593376d - ::check_expr_with_expectation_and_args - 25: 0x7f238592aa16 - ::check_block_with_expected - 26: 0x7f2385931ed3 - ::check_expr_with_expectation_and_args - 27: 0x7f23851bac37 - rustc_hir_typeck[d8d14d8a3ea8603d]::check::check_fn - 28: 0x7f23852c6edf - rustc_hir_typeck[d8d14d8a3ea8603d]::typeck - 29: 0x7f23852c6933 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> - 30: 0x7f2385251b4a - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 31: 0x7f2385334054 - rustc_query_impl[2dfeb81381ece16c]::query_impl::typeck::get_query_incr::__rust_end_short_backtrace - 32: 0x7f238524da5b - ::par_body_owners::::{closure#0} - 33: 0x7f238524b7a4 - rustc_hir_analysis[618f1997a162b265]::check_crate - 34: 0x7f238572bcbf - rustc_interface[fc21679c26087701]::passes::run_required_analyses - 35: 0x7f2385a97e5e - rustc_interface[fc21679c26087701]::passes::analysis - 36: 0x7f2385a97e31 - rustc_query_impl[2dfeb81381ece16c]::plumbing::__rust_begin_short_backtrace::> - 37: 0x7f2385f83c0d - rustc_query_system[cb6aa6728468b543]::query::plumbing::try_execute_query::>, false, false, false>, rustc_query_impl[2dfeb81381ece16c]::plumbing::QueryCtxt, true> - 38: 0x7f2385f838ba - rustc_query_impl[2dfeb81381ece16c]::query_impl::analysis::get_query_incr::__rust_end_short_backtrace - 39: 0x7f2385d12229 - rustc_interface[fc21679c26087701]::interface::run_compiler::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1} - 40: 0x7f2385de6f44 - std[edc1ba278773f0d3]::sys::backtrace::__rust_begin_short_backtrace::, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>> - 41: 0x7f2385de75b0 - <::spawn_unchecked_, rustc_driver_impl[734e2c9770122f8]::run_compiler::{closure#0}>::{closure#1}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[8c2a069408211e69]::result::Result<(), rustc_span[e1b931742f0b02c1]::ErrorGuaranteed>>::{closure#1} as core[8c2a069408211e69]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x7f2385de792b - std::sys::pal::unix::thread::Thread::new::thread_start::he6ae6a1223d421a8 - 43: 0x7f2380330ea7 - start_thread - 44: 0x7f2380250a6f - clone - 45: 0x0 - - - -rustc version: 1.82.0-nightly (80eb5a8e9 2024-08-13) -platform: x86_64-unknown-linux-gnu - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `models::transactions::payment::Payment<'a>: models::transactions::Transaction<'b, ^1_2>` -#1 [typeck] type-checking `models::transactions::payment::::_get_partial_payment_error` -#2 [analysis] running analysis passes on this crate -end of query stack diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 0b4456c3..4e37a1ca 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -21,4 +21,20 @@ pub enum XRPLSignTransactionException<'a> { TagFieldMismatch(&'a str), #[error("Fee value of {0:?} is likely entered incorrectly, since it is much larger than the typical XRP transaction cost. If this is intentional, use `check_fee=Some(false)`.")] FeeTooHigh(Cow<'a, str>), + #[error("Wallet is required to sign transaction")] + WalletRequired, +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum XRPLSubmitAndWaitException<'a> { + #[error("Transaction submission failed: {0}")] + SubmissionFailed(Cow<'a, str>), + #[error("The latest validated ledger sequence {validated_ledger_sequence} is greater than the LastLedgerSequence {last_ledger_sequence} in the Transaction. Prelim result: {prelim_result}")] + SubmissionTimeout { + last_ledger_sequence: u32, + validated_ledger_sequence: u32, + prelim_result: Cow<'a, str>, + }, + #[error("Expected field in the transaction metadata: {0}")] + ExpectedFieldInTxMeta(Cow<'a, str>), } diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 607c07dd..c23b63ce 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -1,4 +1,7 @@ pub mod exceptions; +mod submit_and_wait; + +pub use submit_and_wait::*; use crate::{ asynch::{ @@ -45,6 +48,70 @@ const RESTRICTED_NETWORKS: u16 = 1024; const REQUIRED_NETWORKID_VERSION: &str = "1.11.0"; const LEDGER_OFFSET: u8 = 20; +pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, +{ + if multisign { + let serialized_for_signing = + encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; + let serialized_bytes = match hex::decode(serialized_for_signing) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { + Ok(signature) => signature, + Err(e) => return Err!(e), + }; + let signer = Signer::new( + wallet.classic_address.clone().into(), + signature.into(), + wallet.public_key.clone().into(), + ); + transaction.get_mut_common_fields().signers = Some(vec![signer]); + + Ok(()) + } else { + prepare_transaction(transaction, wallet)?; + let serialized_for_signing = encode_for_signing(transaction)?; + let serialized_bytes = match hex::decode(serialized_for_signing) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { + Ok(signature) => signature, + Err(e) => return Err!(e), + }; + transaction.get_mut_common_fields().txn_signature = Some(signature.into()); + + Ok(()) + } +} + +pub async fn sign_and_submit<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + autofill: bool, + check_fee: bool, +) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if autofill { + autofill_and_sign(transaction, client, wallet, check_fee).await?; + } else { + if check_fee { + check_txn_fee(transaction, client).await?; + } + sign(transaction, wallet, false)?; + } + submit(transaction, client).await +} + pub async fn autofill<'a, 'b, F, T, C>( transaction: &mut T, client: &'b C, @@ -77,6 +144,44 @@ where Ok(()) } +pub async fn autofill_and_sign<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + check_fee: bool, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + if check_fee { + check_txn_fee(transaction, client).await?; + } + autofill(transaction, client, None).await?; + sign(transaction, wallet, false)?; + + Ok(()) +} + +pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + C: AsyncClient, +{ + let txn_blob = encode(transaction)?; + let req = Submit::new(None, txn_blob.into(), None); + let res = client.request(req.into()).await?; + match res.try_into_result::>() { + Ok(value) => { + let submit_result = SubmitResult::from(value); + Ok(submit_result) + } + Err(e) => Err!(e), + } +} + pub async fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( transaction: &T, client: Option<&'b C>, @@ -260,102 +365,6 @@ enum AccountFieldType { Destination, } -pub async fn sign_and_submit<'a, 'b, T, F, C>( - transaction: &mut T, - client: &'b C, - wallet: &Wallet, - autofill: bool, - check_fee: bool, -) -> Result> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - if autofill { - autofill_and_sign(transaction, client, wallet, check_fee).await?; - } else { - if check_fee { - check_txn_fee(transaction, client).await?; - } - sign(transaction, wallet, false)?; - } - submit(transaction, client).await -} - -pub fn sign<'a, T, F>(transaction: &mut T, wallet: &Wallet, multisign: bool) -> Result<()> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, -{ - if multisign { - let serialized_for_signing = - encode_for_multisigning(transaction, wallet.classic_address.clone().into())?; - let serialized_bytes = hex::decode(serialized_for_signing).unwrap(); - let signature = keypairs_sign(&serialized_bytes, &wallet.private_key).unwrap(); - let signer = Signer::new( - wallet.classic_address.clone().into(), - signature.into(), - wallet.public_key.clone().into(), - ); - transaction.get_mut_common_fields().signers = Some(vec![signer]); - - Ok(()) - } else { - prepare_transaction(transaction, wallet)?; - let serialized_for_signing = encode_for_signing(transaction)?; - let serialized_bytes = match hex::decode(serialized_for_signing) { - Ok(bytes) => bytes, - Err(e) => return Err!(e), - }; - let signature = match keypairs_sign(&serialized_bytes, &wallet.private_key) { - Ok(signature) => signature, - Err(e) => return Err!(e), - }; - transaction.get_mut_common_fields().txn_signature = Some(signature.into()); - - Ok(()) - } -} - -pub async fn autofill_and_sign<'a, 'b, T, F, C>( - transaction: &mut T, - client: &'b C, - wallet: &Wallet, - check_fee: bool, -) -> Result<()> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - if check_fee { - check_txn_fee(transaction, client).await?; - } - autofill(transaction, client, None).await?; - sign(transaction, wallet, false)?; - - Ok(()) -} - -pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> -where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, -{ - let txn_blob = encode(transaction)?; - let req = Submit::new(None, txn_blob.into(), None); - let res = client.request(req.into()).await?; - match res.try_into_result::>() { - Ok(value) => { - let submit_result = SubmitResult::from(value); - Ok(submit_result) - } - Err(e) => Err!(e), - } -} - async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> Result<()> where F: IntoEnumIterator + Serialize + Debug + PartialEq, @@ -538,8 +547,8 @@ mod test_sign { wallet::Wallet, }; - #[test] - fn test_sign() { + #[tokio::test] + async fn test_sign() { let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); let mut tx = AccountSet::new( Cow::from(wallet.classic_address.clone()), diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs new file mode 100644 index 00000000..3cf0fd5d --- /dev/null +++ b/src/asynch/transaction/submit_and_wait.rs @@ -0,0 +1,204 @@ +use core::fmt::Debug; + +use alloc::{borrow::Cow, format}; +use anyhow::{Ok, Result}; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value; +use strum::IntoEnumIterator; + +use crate::{ + asynch::{ + clients::AsyncClient, + ledger::get_latest_validated_ledger_sequence, + transaction::{ + autofill, check_txn_fee, + exceptions::{XRPLSignTransactionException, XRPLSubmitAndWaitException}, + sign, submit, + }, + }, + models::{requests, results, transactions::Transaction, Model}, + wallet::Wallet, + Err, +}; + +pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( + transaction: &'b mut T, + client: &C, + wallet: Option<&Wallet>, + check_fee: Option, + autofill: Option, +) -> Result> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, + C: AsyncClient, +{ + get_signed_transaction(transaction, client, wallet, check_fee, autofill).await?; + send_reliable_submission(transaction, client).await +} + +async fn send_reliable_submission<'a: 'b, 'b, T, F, C>( + transaction: &'b mut T, + client: &C, +) -> Result> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, + C: AsyncClient, +{ + let tx_hash = transaction.get_hash()?; + let submit_response = submit(transaction, client).await?; + let prelim_result = submit_response.engine_result; + if &prelim_result[0..3] == "tem" { + let message = format!( + "{}: {}", + prelim_result, submit_response.engine_result_message + ); + Err!(XRPLSubmitAndWaitException::SubmissionFailed(message.into())) + } else { + wait_for_final_transaction_result( + tx_hash, + client, + transaction + .get_common_fields() + .last_ledger_sequence + .unwrap(), // safe to unwrap because we autofilled the transaction + ) + .await + } +} + +async fn wait_for_final_transaction_result<'a: 'b, 'b, C>( + tx_hash: Cow<'a, str>, + client: &C, + last_ledger_sequence: u32, +) -> Result> +where + C: AsyncClient, +{ + let mut validated_ledger_sequence = 0; + while validated_ledger_sequence < last_ledger_sequence { + validated_ledger_sequence = get_latest_validated_ledger_sequence(client).await?; + // sleep for 1 second + // embassy_time::Timer::after_secs(1).await; + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + let response = client + .request(requests::Tx::new(None, None, None, None, Some(tx_hash.clone())).into()) + .await?; + if response.is_success() { + if let Some(error) = response.error { + if error == "txnNotFound" { + continue; + } else { + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + format!("{}: {}", error, response.error_message.unwrap_or("".into())) + .into() + )); + } + } else { + let opt_result = response.try_into_opt_result::()?; + let validated = opt_result.try_get_typed("validated")?; + if validated { + let result = opt_result.try_into_result()?; + let return_code = match result.meta.get("TransactionResult") { + Some(Value::String(s)) => s, + _ => { + return Err!(XRPLSubmitAndWaitException::ExpectedFieldInTxMeta( + "TransactionResult".into() + )); + } + }; + if return_code != "tesSUCCESS" { + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + return_code.into() + )); + } else { + return Ok(result); + } + } + } + } + } + return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + "Transaction not included in ledger".into() + )); +} + +async fn get_signed_transaction<'a, T, F, C>( + transaction: &mut T, + client: &C, + wallet: Option<&Wallet>, + do_check_fee: Option, + do_autofill: Option, +) -> Result<()> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone, + C: AsyncClient, +{ + if transaction.get_common_fields().is_signed() { + return Ok(()); + } + if let Some(wallet) = wallet { + if let Some(check_fee) = do_check_fee { + if check_fee { + check_txn_fee(transaction, client).await?; + } + } + if let Some(do_autofill) = do_autofill { + if do_autofill { + autofill(transaction, client, None).await?; + } + } + if transaction.get_common_fields().signers.as_ref().is_some() { + sign(transaction, wallet, true) + } else { + sign(transaction, wallet, false) + } + } else { + Err!(XRPLSignTransactionException::WalletRequired) + } +} + +#[cfg(test)] +mod test_submit_and_wait { + use super::*; + use crate::{ + asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, + models::transactions::AccountSet, + wallet::Wallet, + }; + + #[tokio::test] + async fn test_submit_and_wait() { + let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut tx = AccountSet::new( + Cow::from(wallet.classic_address.clone()), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // "example.com" + None, + None, + None, + None, + None, + None, + ); + let client = AsyncWebsocketClient::::open( + "wss://testnet.xrpl-labs.com/".parse().unwrap(), + ) + .await + .unwrap(); + submit_and_wait(&mut tx, &client, Some(&wallet), Some(true), Some(true)) + .await + .unwrap(); + } +} diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index d28b44fb..713872fd 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -74,6 +74,7 @@ where if let Some(p) = prefix { buffer.extend(p); } + let json_value = match serde_json::to_value(prepared_transaction) { Ok(v) => v, Err(e) => { @@ -81,12 +82,11 @@ where } }; let st_object = STObject::try_from_value(json_value, signing_only)?; - buffer.extend(st_object.as_ref()); + if let Some(s) = suffix { buffer.extend(s); } - let hex_string = buffer.encode_hex_upper::(); Ok(hex_string) diff --git a/src/core/definitions/mod.rs b/src/core/definitions/mod.rs index 554c0269..42ad99ed 100644 --- a/src/core/definitions/mod.rs +++ b/src/core/definitions/mod.rs @@ -152,15 +152,15 @@ impl ToBytes for FieldHeader { let shift = (self.type_code << 4) as u8; header_bytes.extend_from_slice(&shift.to_be_bytes()); - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); } } else if self.field_code < 16 { - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); - header_bytes.extend_from_slice(&self.type_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); + header_bytes.extend_from_slice(&(self.type_code as u8).to_be_bytes()); } else { header_bytes.extend_from_slice(&[0]); - header_bytes.extend_from_slice(&self.type_code.to_be_bytes()); - header_bytes.extend_from_slice(&self.field_code.to_be_bytes()); + header_bytes.extend_from_slice(&(self.type_code as u8).to_be_bytes()); + header_bytes.extend_from_slice(&(self.field_code as u8).to_be_bytes()); } header_bytes diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index dac7bbb8..a34da2c6 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -298,6 +298,7 @@ impl STObject { /// assert_eq!(hex, buffer); /// ``` pub fn try_from_value(value: Value, signing_only: bool) -> Result { + // dbg!(&value); let object = match value { Value::Object(map) => map, _ => return Err!(exceptions::XRPLSerializeMapException::ExpectedObject), @@ -381,6 +382,7 @@ impl STObject { } } + // dbg!(&value_xaddress_handled); let mut sorted_keys: Vec = Vec::new(); for (field, _) in &value_xaddress_handled { let field_instance = get_field_instance(&field); @@ -411,6 +413,7 @@ impl STObject { associated_value.to_owned(), )?; let associated_value: SerializedType = associated_value.into(); + // dbg!(&field_instance, &associated_value.to_string(),); if field_instance.name == "TransactionType" && associated_value.to_string() == UNL_MODIFY_TX_TYPE { diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 29b88373..ce21e3c4 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -30,6 +30,8 @@ pub struct Tx<'a> { /// the server cannot find the transaction, it confirms whether /// it was able to search all the ledgers in this range. pub min_ledger: Option, + /// The 256-bit hash of the transaction to look up, as hexadecimal. + pub transaction: Option>, } impl<'a> Model for Tx<'a> {} @@ -50,6 +52,7 @@ impl<'a> Tx<'a> { binary: Option, max_ledger: Option, min_ledger: Option, + transaction: Option>, ) -> Self { Self { common_fields: CommonFields { @@ -59,6 +62,7 @@ impl<'a> Tx<'a> { binary, min_ledger, max_ledger, + transaction, } } } diff --git a/src/models/results/exceptions.rs b/src/models/results/exceptions.rs index 6f5dd316..1900e349 100644 --- a/src/models/results/exceptions.rs +++ b/src/models/results/exceptions.rs @@ -1,6 +1,8 @@ use alloc::string::String; use thiserror_no_std::Error; +use super::XRPLOtherResult; + #[derive(Debug, Error)] pub enum XRPLResultException { #[error("Response error: {0}")] @@ -9,4 +11,10 @@ pub enum XRPLResultException { ExpectedResultOrError, #[error("Unexpected result type (expected {0:?}, got {1:?}).")] UnexpectedResultType(String, String), + #[error("Index not found.")] + IndexNotFound, + #[error("Called unwrap on `XRPLOtherResult`.")] + UnwrapOnOther, + #[error("Expected a XRPL Result model but got `XRPLOtherResult`: {0:?}.")] + ExpectedResult(XRPLOtherResult), } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index adbf2c79..678f9547 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,15 +1,15 @@ use core::convert::{TryFrom, TryInto}; use alloc::{ - borrow::Cow, + borrow::{Cow, ToOwned}, format, string::{String, ToString}, vec::Vec, }; use anyhow::Result; use exceptions::XRPLResultException; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{value::Index, Map, Value}; pub mod account_info; pub mod account_tx; @@ -18,6 +18,7 @@ pub mod fee; pub mod ledger; pub mod server_state; pub mod submit; +pub mod tx; pub use account_info::*; pub use account_tx::*; @@ -25,10 +26,108 @@ pub use fee::*; pub use ledger::*; pub use server_state::*; pub use submit::*; +pub use tx::*; use crate::Err; use super::requests::XRPLRequest; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum XRPLOptionalResult { + Result(T), + Other(XRPLOtherResult), +} + +impl XRPLOptionalResult { + pub fn unwrap(self) -> T { + match self { + XRPLOptionalResult::Result(result) => result, + XRPLOptionalResult::Other(_) => { + panic!("{}", XRPLResultException::UnwrapOnOther.to_string()) + } + } + } + + /// Try to convert the result into an expected XRPL result. + pub fn try_into_result(self) -> Result { + match self { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } + + /// Get a value from the result by index. + pub fn try_get_typed(&self, index: I) -> Result + where + T: Serialize, + I: Index, + U: DeserializeOwned, + { + match self { + XRPLOptionalResult::Result(result) => match serde_json::to_value(result) { + Ok(value) => match value.get(index) { + Some(value) => match serde_json::from_value(value.to_owned()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + }, + Err(e) => Err!(e), + }, + XRPLOptionalResult::Other(other) => other.try_get_typed(index), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct XRPLOtherResult(Value); + +impl TryFrom> for XRPLOtherResult { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult) -> Result { + match result { + XRPLResult::Other(value) => Ok(value), + res => Err!(XRPLResultException::UnexpectedResultType( + "Other".to_string(), + res.get_name() + )), + } + } +} + +impl From for XRPLOtherResult { + fn from(value: Value) -> Self { + XRPLOtherResult(value) + } +} + +impl Into for XRPLOtherResult { + fn into(self) -> Value { + self.0 + } +} + +impl XRPLOtherResult { + pub fn get(&self, index: impl Index) -> Option<&Value> { + self.0.get(index) + } + + pub fn try_get_typed(&self, index: I) -> Result + where + I: Index, + T: DeserializeOwned, + { + match self.0.get(index) { + Some(value) => match serde_json::from_value(value.clone()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { @@ -38,7 +137,8 @@ pub enum XRPLResult<'a> { Ledger(Ledger<'a>), ServerState(ServerState<'a>), Submit(Submit<'a>), - Other(Value), + Tx(Tx<'a>), + Other(XRPLOtherResult), } impl<'a> From> for XRPLResult<'a> { @@ -77,9 +177,21 @@ impl<'a> From> for XRPLResult<'a> { } } +impl<'a> From> for XRPLResult<'a> { + fn from(tx: Tx<'a>) -> Self { + XRPLResult::Tx(tx) + } +} + impl<'a> From for XRPLResult<'a> { fn from(value: Value) -> Self { - XRPLResult::Other(value) + XRPLResult::Other(XRPLOtherResult(value)) + } +} + +impl<'a> From for XRPLResult<'a> { + fn from(other: XRPLOtherResult) -> Self { + XRPLResult::Other(other) } } @@ -88,7 +200,7 @@ impl<'a> TryInto for XRPLResult<'a> { fn try_into(self) -> Result { match self { - XRPLResult::Other(value) => Ok(value), + XRPLResult::Other(XRPLOtherResult(value)) => Ok(value), res => match serde_json::to_value(res) { Ok(value) => Ok(value), Err(e) => Err!(e), @@ -106,6 +218,7 @@ impl XRPLResult<'_> { XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), + XRPLResult::Tx(_) => "Tx".to_string(), XRPLResult::Other(_) => "Other".to_string(), } } @@ -213,14 +326,31 @@ impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { } } +impl TryInto for XRPLResponse<'_> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match serde_json::to_value(self) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + } + } +} + 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 { + pub fn try_into_opt_result(self) -> Result> + where + T: TryFrom, Error = anyhow::Error>, + { match self.result { - Some(result) => result.try_into(), + Some(result) => match result.clone().try_into() { + Ok(result) => Ok(XRPLOptionalResult::Result(result)), + Err(_) => Ok(XRPLOptionalResult::Other(result.try_into()?)), + }, None => { if let Some(error) = self.error { Err!(XRPLResultException::ResponseError(format!( @@ -234,6 +364,16 @@ impl<'a> XRPLResponse<'a> { } } } + + pub fn try_into_result(self) -> Result + where + T: TryFrom, Error = anyhow::Error>, + { + match self.try_into_opt_result()? { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/models/results/submit.rs b/src/models/results/submit.rs index 3afd25c8..1b55706b 100644 --- a/src/models/results/submit.rs +++ b/src/models/results/submit.rs @@ -11,20 +11,20 @@ use super::XRPLResult; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Submit<'a> { - engine_result: Cow<'a, str>, - engine_result_code: i32, - engine_result_message: Cow<'a, str>, - tx_blob: Cow<'a, str>, - tx_json: Value, - accepted: Option, - account_sequence_available: Option, - account_sequence_next: Option, - applied: Option, - broadcast: Option, - kept: Option, - queued: Option, - open_ledger_cost: Option>, - validated_ledger_index: Option, + pub engine_result: Cow<'a, str>, + pub engine_result_code: i32, + pub engine_result_message: Cow<'a, str>, + pub tx_blob: Cow<'a, str>, + pub tx_json: Value, + pub accepted: Option, + pub account_sequence_available: Option, + pub account_sequence_next: Option, + pub applied: Option, + pub broadcast: Option, + pub kept: Option, + pub queued: Option, + pub open_ledger_cost: Option>, + pub validated_ledger_index: Option, } impl<'a> TryFrom> for Submit<'a> { diff --git a/src/models/results/tx.rs b/src/models/results/tx.rs new file mode 100644 index 00000000..cecdf63b --- /dev/null +++ b/src/models/results/tx.rs @@ -0,0 +1,40 @@ +use core::convert::TryFrom; + +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{models::results::exceptions::XRPLResultException, Err}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Tx<'a> { + pub ctid: Cow<'a, str>, + pub date: u32, + pub hash: Cow<'a, str>, + pub ledger_index: u32, + pub meta: Value, + /// Various fields of the transaction + #[serde(flatten)] + pub various: Value, + pub validated: Option, + /// (Deprecated) Alias for `ledger_index` + #[serde(rename = "inLedger")] + pub in_ledger: Option, +} + +impl<'a> TryFrom> for Tx<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::Tx(tx) => Ok(tx), + res => Err!(XRPLResultException::UnexpectedResultType( + "Tx".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index bbee7124..c36bc9b6 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -17,6 +17,7 @@ pub enum XRPLTransactionException<'a> { XRPLNFTokenMintError(XRPLNFTokenMintException<'a>), XRPLPaymentError(XRPLPaymentException<'a>), XRPLSignerListSetError(XRPLSignerListSetException<'a>), + TxMustBeSigned, } #[cfg(feature = "std")] diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index ff72c422..58fda72d 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -29,6 +29,7 @@ use core::fmt::Debug; pub use account_delete::*; pub use account_set::*; +use alloc::format; pub use check_cancel::*; pub use check_cash::*; pub use check_create::*; @@ -50,11 +51,14 @@ pub use payment_channel_create::*; pub use payment_channel_fund::*; pub use pseudo_transactions::*; +use serde::de::DeserializeOwned; pub use set_regular_key::*; +use sha2::{Digest, Sha512}; pub use signer_list_set::*; pub use ticket_create::*; pub use trust_set::*; +use crate::core::binarycodec::encode; use crate::models::amount::XRPAmount; use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; @@ -71,6 +75,8 @@ use strum_macros::{AsRefStr, Display}; use super::FlagCollection; +const TRANSACTION_HASH_PREFIX: u32 = 0x54584E00; + /// Enum containing the different Transaction types. #[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq, Eq)] pub enum TransactionType { @@ -238,7 +244,7 @@ where impl CommonFields<'_, T> where - T: IntoEnumIterator + Serialize + core::fmt::Debug, + T: IntoEnumIterator + Serialize + Debug + PartialEq + Clone, { pub fn is_signed(&self) -> bool { if let Some(signers) = &self.signers { @@ -338,6 +344,31 @@ where Err(e) => Err!(e), } } + + /// Hashes the Transaction object as the ledger does. Only valid for signed + /// Transaction objects. + fn get_hash(&self) -> Result> + where + Self: Serialize + DeserializeOwned + Debug + Clone, + { + // if !self.is_signed() { + // return Err!(XRPLTransactionException::TxMustBeSigned); + // } + let prefix = format!("{:X}", TRANSACTION_HASH_PREFIX); + let encoded_tx = encode(self)?; + let encoded = prefix + &encoded_tx; + let encoded_bytes = match hex::decode(&encoded) { + Ok(bytes) => bytes, + Err(e) => return Err!(e), + }; + let mut hasher = Sha512::new(); + hasher.update(&encoded_bytes); + let hash = hasher.finalize(); + let hex_string = hex::encode_upper(hash); + let result = hex_string[..64].to_string(); + + Ok(result.into()) + } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] @@ -351,3 +382,43 @@ pub enum Flag { TrustSet(TrustSetFlag), EnableAmendment(EnableAmendmentFlag), } + +#[cfg(test)] +mod test_tx_common_fields { + use super::*; + use crate::{ + asynch::transaction::sign, + models::{amount::IssuedCurrencyAmount, transactions::OfferCreate}, + wallet::Wallet, + }; + + #[tokio::test] + async fn test_get_hash() { + let mut wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let mut txn = OfferCreate::new( + "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), + None, + Some("10".into()), + Some(FlagCollection::default()), + Some(16409087), + None, + Some(16409064), + None, + None, + None, + "13100000".into(), + IssuedCurrencyAmount::new( + "USD".into(), + "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), + "10".into(), + ) + .into(), + None, + None, + ); + sign(&mut txn, &mut wallet, false).unwrap(); + let expected_hash = "39530980D3D6F848E619BF05A57988D42A62075289B99C5728CBDE0D1710284B"; + + assert_eq!(&txn.get_hash().unwrap(), expected_hash); + } +} From a7fba6ae9846111c91d5d42768e71ce5621f4db4 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:15:19 +0000 Subject: [PATCH 083/113] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 3cf0fd5d..1b4b147b 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -160,6 +160,11 @@ where } } +#[cfg(all( + feature = "websocket-std", + feature = "transactions", + feature = "wallet" +))] #[cfg(test)] mod test_submit_and_wait { use super::*; From 4759b0076b41b7808d0ac66ed3f384fc8fab75d9 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:19:30 +0000 Subject: [PATCH 084/113] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 1b4b147b..0f9b456f 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -162,6 +162,7 @@ where #[cfg(all( feature = "websocket-std", + not(feature = "websocket"), feature = "transactions", feature = "wallet" ))] From d2773e00ce9e492980a75ddd7fb600c3d31cc18c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:24:20 +0000 Subject: [PATCH 085/113] try fix tests --- src/models/transactions/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 58fda72d..a6b109b8 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -383,6 +383,14 @@ pub enum Flag { EnableAmendment(EnableAmendmentFlag), } +#[cfg(all( + feature = "websocket-std", + not(feature = "websocket"), + feature = "transactions", + feature = "transaction-helpers", + feature = "amounts", + feature = "wallet" +))] #[cfg(test)] mod test_tx_common_fields { use super::*; From 43d2c8a53123c216038717dffcd5de4dad6ceaca Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:30:43 +0000 Subject: [PATCH 086/113] try fix tests --- .github/workflows/unit_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 6f2bb2d0..c8feaf76 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -41,4 +41,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features websocket-std,helpers + args: --no-default-features --features std,websocket-std,helpers From 32bd35074ab3cb7338b2ed51ce30babcbed4309c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:13:28 +0000 Subject: [PATCH 087/113] add faucet wallet generation --- Cargo.toml | 14 +- src/asynch/account/mod.rs | 27 ++-- src/asynch/clients/client.rs | 3 + src/asynch/clients/json_rpc/exceptions.rs | 4 + src/asynch/clients/json_rpc/mod.rs | 146 +++++++++++++------- src/asynch/clients/websocket/_std.rs | 6 + src/asynch/mod.rs | 2 + src/asynch/wallet/exceptions.rs | 13 ++ src/asynch/wallet/mod.rs | 152 +++++++++++++++++++++ src/models/amount/xrp_amount.rs | 16 ++- src/models/requests/mod.rs | 11 ++ src/models/results/mod.rs | 159 ++-------------------- tests/integration/clients/mod.rs | 2 +- 13 files changed, 333 insertions(+), 222 deletions(-) create mode 100644 src/asynch/wallet/exceptions.rs create mode 100644 src/asynch/wallet/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 52af12d6..fd1ddbea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,16 +106,22 @@ default = [ "models", "utils", "helpers", - "websocket-std", + "json-rpc-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"] +helpers = [ + "account-helpers", + "ledger-helpers", + "transaction-helpers", + "wallet-helpers", +] account-helpers = ["amounts", "currencies", "requests", "results"] ledger-helpers = ["amounts", "currencies", "requests", "results"] +wallet-helpers = ["requests", "results"] transaction-helpers = [ "wallet", "amounts", @@ -128,8 +134,8 @@ transaction-helpers = [ ] amounts = ["core"] currencies = ["core"] -json-rpc = ["url", "reqwless", "embedded-nal-async"] -json-rpc-std = ["url", "reqwest"] +json-rpc = ["url", "reqwless", "embassy-sync", "embedded-nal-async"] +json-rpc-std = ["url", "reqwest", "embassy-sync", "tokio"] wallet = ["core"] websocket = [ "url", diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index e392853b..8111972f 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -1,4 +1,4 @@ -use alloc::borrow::Cow; +use alloc::{borrow::Cow, dbg}; use anyhow::Result; use crate::{ @@ -69,20 +69,17 @@ where Err(e) => return Err!(e), }; } - let account_info = client - .request( - AccountInfo::new( - None, - classic_address, - None, - Some(ledger_index), - None, - None, - None, - ) - .into(), - ) - .await?; + let request = AccountInfo::new( + None, + classic_address, + None, + Some(ledger_index), + None, + Some(true), + None, + ) + .into(); + let account_info = client.request(request).await?; Ok(account_info .try_into_result::>()? diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 0fec4d50..7a678ae2 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -6,11 +6,14 @@ use crate::models::{ use crate::utils::get_random_id; use alloc::borrow::{Cow, ToOwned}; use anyhow::Result; +use url::Url; #[allow(async_fn_in_trait)] pub trait Client { async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + fn get_host(&self) -> Url; + fn set_request_id(&self, request: &mut XRPLRequest<'_>) -> () { let common_fields = request.get_common_fields_mut(); common_fields.id = match &common_fields.id { diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs index 95e7a47a..21fc86f5 100644 --- a/src/asynch/clients/json_rpc/exceptions.rs +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -1,3 +1,4 @@ +use reqwest::Response; use thiserror_no_std::Error; #[derive(Debug, Error)] @@ -5,4 +6,7 @@ pub enum XRPLJsonRpcException { #[cfg(feature = "json-rpc")] #[error("Reqwless error")] ReqwlessError, + #[cfg(feature = "json-rpc-std")] + #[error("Request error: {0:?}")] + RequestError(Response), } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index 95a8c4f2..c0a21fd5 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,93 +1,139 @@ -use alloc::{string::String, sync::Arc}; +use alloc::{ + dbg, + string::{String, ToString}, + sync::Arc, + vec, +}; use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use serde::Serialize; +use serde_json::{Map, Value}; +use url::Url; -use crate::{models::results::XRPLResponse, Err}; +use crate::{ + asynch::wallet::get_faucet_url, + models::{requests::FundFaucet, results::XRPLResponse}, + Err, +}; mod exceptions; pub use exceptions::XRPLJsonRpcException; -use super::{client::Client, SingleExecutorMutex}; +use super::client::Client; + +pub trait XRPLFaucet: Client { + fn get_faucet_url(&self, url: Option) -> Result + where + Self: Sized + Client, + { + get_faucet_url(self, url) + } + + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; +} /// Renames the requests field `command` to `method` for JSON-RPC. -fn request_to_json_rpc(request: &impl Serialize) -> Result { +fn request_to_json_rpc(request: &impl Serialize) -> Result { + let mut json_rpc_request = Map::new(); let mut request = match serde_json::to_value(request) { - Ok(request) => request, + Ok(request) => match request.as_object().cloned() { + Some(request) => request, + None => todo!("Handle non-object requests"), + }, Err(error) => return Err!(error), }; - if let Some(command) = request.get_mut("command") { - let method = command.take(); - request["method"] = method; - } - match serde_json::to_string(&request) { - Ok(request) => Ok(request), - Err(error) => Err!(error), + if let Some(command) = request.remove("command") { + json_rpc_request.insert("method".to_string(), command); + json_rpc_request.insert( + "params".to_string(), + serde_json::Value::Array(vec![Value::Object(request)]), + ); } + + Ok(Value::Object(json_rpc_request)) } #[cfg(feature = "json-rpc-std")] mod _std { - use crate::models::requests::XRPLRequest; + use crate::models::requests::{FundFaucet, XRPLRequest}; use super::*; + use alloc::{dbg, format, string::ToString}; use reqwest::Client as HttpClient; use url::Url; - pub struct AsyncJsonRpcClient - where - M: RawMutex, - { + pub struct AsyncJsonRpcClient { url: Url, - client: Arc>, } - impl AsyncJsonRpcClient - where - M: RawMutex, - { - pub fn new(url: Url) -> Self { - Self { - url, - client: Arc::new(Mutex::new(HttpClient::new())), - } + impl AsyncJsonRpcClient { + pub fn connect(url: Url) -> Self { + Self { url } } } - impl AsyncJsonRpcClient - where - M: RawMutex, - { - fn from(url: Url, client: HttpClient) -> Self { - Self { - url, - client: Arc::new(Mutex::new(client)), - } - } - } - - impl Client for AsyncJsonRpcClient - where - M: RawMutex, - { + impl Client for AsyncJsonRpcClient { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, ) -> Result> { - let client = self.client.lock().await; - match client + let client = HttpClient::new(); + let request_json_rpc = request_to_json_rpc(&request)?; + dbg!(&request_json_rpc); + let response = client .post(self.url.as_ref()) - .body(request_to_json_rpc(&request)?) + .json(&request_json_rpc) .send() - .await - { - Ok(response) => match response.json().await { - Ok(response) => Ok(response), + .await; + dbg!(&response); + match response { + Ok(response) => match response.text().await { + Ok(response) => { + dbg!(&response); + Ok(serde_json::from_str::>(&response).unwrap()) + } Err(error) => Err!(error), }, Err(error) => Err!(error), } } + + fn get_host(&self) -> Url { + self.url.clone() + } + } + + impl XRPLFaucet for AsyncJsonRpcClient { + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + let faucet_url = self.get_faucet_url(url)?; + let client = HttpClient::new(); + let request_json_rpc = serde_json::to_value(&request).unwrap(); + dbg!(&request_json_rpc); + let response = client + .post(&faucet_url.to_string()) + .json(&request_json_rpc) + .send() + .await; + dbg!(&response); + match response { + Ok(response) => { + if response.status().is_success() { + dbg!("Success"); + dbg!(&response); + Ok(()) + } else { + dbg!("Error"); + dbg!(&response); + todo!() + // Err!(XRPLJsonRpcException::RequestError()) + } + } + Err(error) => { + dbg!("req Error"); + Err!(error) + } + } + } } } diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index a2935fce..ef05d70c 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -30,6 +30,7 @@ where { websocket: Arc>, websocket_base: Arc>>, + uri: Url, status: PhantomData, } @@ -141,6 +142,7 @@ where Ok(AsyncWebsocketClient { websocket: Arc::new(Mutex::new(stream)), websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + uri, status: PhantomData::, }) } @@ -199,6 +201,10 @@ impl Client for AsyncWebsocketClient where M: RawMutex, { + fn get_host(&self) -> Url { + self.uri.clone() + } + async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>, diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index cc98777f..fc5acf16 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -11,3 +11,5 @@ pub mod clients; pub mod ledger; #[cfg(feature = "transaction-helpers")] pub mod transaction; +#[cfg(feature = "wallet-helpers")] +pub mod wallet; diff --git a/src/asynch/wallet/exceptions.rs b/src/asynch/wallet/exceptions.rs new file mode 100644 index 00000000..9571cd29 --- /dev/null +++ b/src/asynch/wallet/exceptions.rs @@ -0,0 +1,13 @@ +use thiserror_no_std::Error; + +#[derive(Error, Debug)] +pub enum XRPLFaucetException { + #[error( + "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." + )] + CannotFundSidechainAccount, + #[error("Cannot derive a faucet URL from the client host.")] + CannotDeriveFaucetUrl, + #[error("Funding request timed out.")] + FundingTimeout, +} diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs new file mode 100644 index 00000000..b21b3a99 --- /dev/null +++ b/src/asynch/wallet/mod.rs @@ -0,0 +1,152 @@ +pub mod exceptions; + +use alloc::{borrow::Cow, dbg}; +use anyhow::Result; +use exceptions::XRPLFaucetException; +use url::Url; + +use crate::{ + asynch::account::get_next_valid_seq_number, + models::{amount::XRPAmount, requests::FundFaucet}, + wallet::Wallet, + Err, +}; + +use super::{ + account::get_xrp_balance, + clients::{AsyncClient, Client, XRPLFaucet}, +}; + +const TEST_FAUCET_URL: &'static str = "https://faucet.altnet.rippletest.net/accounts"; +const DEV_FAUCET_URL: &'static str = "https://faucet.devnet.rippletest.net/accounts"; + +const TIMEOUT_SECS: u8 = 40; + +pub async fn generate_faucet_wallet<'a, C>( + client: &C, + wallet: Option, + faucet_host: Option, + usage_context: Option>, + user_agent: Option>, +) -> Result +where + C: XRPLFaucet + Client, +{ + let faucet_url = get_faucet_url(client, faucet_host)?; + let wallet = match wallet { + Some(wallet) => wallet, + None => match Wallet::create(None) { + Ok(wallet) => wallet, + Err(error) => return Err!(error), + }, + }; + let address = &wallet.classic_address; + dbg!(address); + let starting_balance = 0.into(); // check_balance(client, address.into()).await; + let user_agent = user_agent.unwrap_or("xrpl-rust".into()); + fund_wallet( + client, + faucet_url, + address.into(), + usage_context, + Some(user_agent), + ) + .await?; + let mut is_funded = false; + for _ in 0..TIMEOUT_SECS { + // wait 1 second + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + if !is_funded { + let balance = check_balance(client, address.into()).await; + dbg!(&balance); + dbg!(&starting_balance); + if balance > starting_balance { + is_funded = true; + } + } else { + // wait until the ledger knows about the wallets existence + match get_next_valid_seq_number(address.into(), client, None).await { + Ok(_sequence) => { + return Ok(wallet); + } + Err(_) => continue, + } + } + } + + Err!(XRPLFaucetException::FundingTimeout) +} + +pub fn get_faucet_url(client: &C, url: Option) -> Result +where + C: Client, +{ + if let Some(url) = url { + Ok(url) + } else { + let host = client.get_host(); + let host_str = host.host_str().unwrap(); + if host_str.contains("altnet") || host_str.contains("testnet") { + match Url::parse(TEST_FAUCET_URL) { + Ok(url) => return Ok(url), + Err(error) => return Err!(error), + } + } else if host_str.contains("devnet") { + match Url::parse(DEV_FAUCET_URL) { + Ok(url) => Ok(url), + Err(error) => Err!(error), + } + } else if host_str.contains("sidechain-net2") { + Err!(XRPLFaucetException::CannotFundSidechainAccount) + } else { + Err!(XRPLFaucetException::CannotDeriveFaucetUrl) + } + } +} + +async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPAmount<'b> +where + C: Client, +{ + get_xrp_balance(address, client, None).await.unwrap() + // .unwrap_or(XRPAmount::default()) +} + +async fn fund_wallet<'a: 'b, 'b, C>( + client: &C, + faucet_url: Url, + address: Cow<'a, str>, + usage_context: Option>, + user_agent: Option>, +) -> Result<()> +where + C: XRPLFaucet + Client, +{ + let request = FundFaucet { + destination: address, + usage_context, + user_agent, + }; + client.request_funding(Some(faucet_url), request).await?; + + Ok(()) +} + +#[cfg(test)] +mod test_faucet_wallet_generation { + use super::*; + use crate::asynch::clients::json_rpc::AsyncJsonRpcClient; + use alloc::dbg; + use url::Url; + + #[tokio::test] + async fn test_generate_faucet_wallet() { + let client = + AsyncJsonRpcClient::connect(Url::parse("https://testnet.xrpl-labs.com/").unwrap()); + let wallet = generate_faucet_wallet(&client, None, None, None, None) + .await + .unwrap(); + dbg!(&wallet); + assert_eq!(wallet.classic_address.len(), 34); + } +} diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 91c3d6a1..17add31e 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -12,11 +12,17 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// Represents an amount of XRP in Drops. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct XRPAmount<'a>(pub Cow<'a, str>); impl<'a> Model for XRPAmount<'a> {} +impl Default for XRPAmount<'_> { + fn default() -> Self { + Self("0".into()) + } +} + // 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> @@ -121,12 +127,16 @@ impl<'a> TryInto> for XRPAmount<'a> { impl<'a> PartialOrd for XRPAmount<'a> { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.0.cmp(&other.0)) + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.clone().try_into().unwrap(); + Some(self_decimal.cmp(&other_decimal)) } } impl<'a> Ord for XRPAmount<'a> { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.cmp(&other.0) + let self_decimal: Decimal = self.clone().try_into().unwrap(); + let other_decimal: Decimal = other.clone().try_into().unwrap(); + self_decimal.cmp(&other_decimal) } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index e77e91c8..1c4f318a 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -486,3 +486,14 @@ pub trait Request<'a> { fn get_common_fields(&self) -> &CommonFields<'a>; fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +#[skip_serializing_none] +pub struct FundFaucet<'a> { + pub destination: Cow<'a, str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub usage_context: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option>, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 678f9547..517ca7d3 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,15 +1,15 @@ use core::convert::{TryFrom, TryInto}; use alloc::{ - borrow::{Cow, ToOwned}, - format, + borrow::Cow, + dbg, format, string::{String, ToString}, vec::Vec, }; use anyhow::Result; use exceptions::XRPLResultException; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::{value::Index, Map, Value}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; pub mod account_info; pub mod account_tx; @@ -18,7 +18,6 @@ pub mod fee; pub mod ledger; pub mod server_state; pub mod submit; -pub mod tx; pub use account_info::*; pub use account_tx::*; @@ -26,108 +25,10 @@ pub use fee::*; pub use ledger::*; pub use server_state::*; pub use submit::*; -pub use tx::*; use crate::Err; use super::requests::XRPLRequest; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum XRPLOptionalResult { - Result(T), - Other(XRPLOtherResult), -} - -impl XRPLOptionalResult { - pub fn unwrap(self) -> T { - match self { - XRPLOptionalResult::Result(result) => result, - XRPLOptionalResult::Other(_) => { - panic!("{}", XRPLResultException::UnwrapOnOther.to_string()) - } - } - } - - /// Try to convert the result into an expected XRPL result. - pub fn try_into_result(self) -> Result { - match self { - XRPLOptionalResult::Result(result) => Ok(result), - XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), - } - } - - /// Get a value from the result by index. - pub fn try_get_typed(&self, index: I) -> Result - where - T: Serialize, - I: Index, - U: DeserializeOwned, - { - match self { - XRPLOptionalResult::Result(result) => match serde_json::to_value(result) { - Ok(value) => match value.get(index) { - Some(value) => match serde_json::from_value(value.to_owned()) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - }, - None => Err!(XRPLResultException::IndexNotFound), - }, - Err(e) => Err!(e), - }, - XRPLOptionalResult::Other(other) => other.try_get_typed(index), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct XRPLOtherResult(Value); - -impl TryFrom> for XRPLOtherResult { - type Error = anyhow::Error; - - fn try_from(result: XRPLResult) -> Result { - match result { - XRPLResult::Other(value) => Ok(value), - res => Err!(XRPLResultException::UnexpectedResultType( - "Other".to_string(), - res.get_name() - )), - } - } -} - -impl From for XRPLOtherResult { - fn from(value: Value) -> Self { - XRPLOtherResult(value) - } -} - -impl Into for XRPLOtherResult { - fn into(self) -> Value { - self.0 - } -} - -impl XRPLOtherResult { - pub fn get(&self, index: impl Index) -> Option<&Value> { - self.0.get(index) - } - - pub fn try_get_typed(&self, index: I) -> Result - where - I: Index, - T: DeserializeOwned, - { - match self.0.get(index) { - Some(value) => match serde_json::from_value(value.clone()) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - }, - None => Err!(XRPLResultException::IndexNotFound), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { @@ -137,8 +38,7 @@ pub enum XRPLResult<'a> { Ledger(Ledger<'a>), ServerState(ServerState<'a>), Submit(Submit<'a>), - Tx(Tx<'a>), - Other(XRPLOtherResult), + Other(Value), } impl<'a> From> for XRPLResult<'a> { @@ -177,21 +77,9 @@ impl<'a> From> for XRPLResult<'a> { } } -impl<'a> From> for XRPLResult<'a> { - fn from(tx: Tx<'a>) -> Self { - XRPLResult::Tx(tx) - } -} - impl<'a> From for XRPLResult<'a> { fn from(value: Value) -> Self { - XRPLResult::Other(XRPLOtherResult(value)) - } -} - -impl<'a> From for XRPLResult<'a> { - fn from(other: XRPLOtherResult) -> Self { - XRPLResult::Other(other) + XRPLResult::Other(value) } } @@ -200,7 +88,7 @@ impl<'a> TryInto for XRPLResult<'a> { fn try_into(self) -> Result { match self { - XRPLResult::Other(XRPLOtherResult(value)) => Ok(value), + XRPLResult::Other(value) => Ok(value), res => match serde_json::to_value(res) { Ok(value) => Ok(value), Err(e) => Err!(e), @@ -218,7 +106,6 @@ impl XRPLResult<'_> { XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), - XRPLResult::Tx(_) => "Tx".to_string(), XRPLResult::Other(_) => "Other".to_string(), } } @@ -326,31 +213,15 @@ impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { } } -impl TryInto for XRPLResponse<'_> { - type Error = anyhow::Error; - - fn try_into(self) -> Result { - match serde_json::to_value(self) { - Ok(value) => Ok(value), - Err(e) => Err!(e), - } - } -} - impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) } - pub fn try_into_opt_result(self) -> Result> - where - T: TryFrom, Error = anyhow::Error>, - { + pub fn try_into_result, Error = anyhow::Error>>(self) -> Result { + dbg!(self.result.clone()); match self.result { - Some(result) => match result.clone().try_into() { - Ok(result) => Ok(XRPLOptionalResult::Result(result)), - Err(_) => Ok(XRPLOptionalResult::Other(result.try_into()?)), - }, + Some(result) => result.try_into(), None => { if let Some(error) = self.error { Err!(XRPLResultException::ResponseError(format!( @@ -364,16 +235,6 @@ impl<'a> XRPLResponse<'a> { } } } - - pub fn try_into_result(self) -> Result - where - T: TryFrom, Error = anyhow::Error>, - { - match self.try_into_opt_result()? { - XRPLOptionalResult::Result(result) => Ok(result), - XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), - } - } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index a116b0f4..e4b68521 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -71,7 +71,7 @@ pub async fn test_json_rpc_std() -> Result<()> { models::requests::Fee, }; let client: AsyncJsonRpcClient = - AsyncJsonRpcClient::new("https://s1.ripple.com:51234/".parse().unwrap()); + AsyncJsonRpcClient::connect("https://s1.ripple.com:51234/".parse().unwrap()); let fee_result = client.request(Fee::new(None).into()).await.unwrap(); assert!(fee_result.result.is_some()); Ok(()) From f4abba100f21ee1b0b59ef1fc06ed2c18bf62af9 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 4 Sep 2024 16:04:17 +0000 Subject: [PATCH 088/113] add submit_and_wait --- src/asynch/transaction/submit_and_wait.rs | 6 - src/models/results/mod.rs | 159 ++++++++++++++++++++-- src/models/transactions/mod.rs | 8 -- 3 files changed, 149 insertions(+), 24 deletions(-) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 0f9b456f..3cf0fd5d 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -160,12 +160,6 @@ where } } -#[cfg(all( - feature = "websocket-std", - not(feature = "websocket"), - feature = "transactions", - feature = "wallet" -))] #[cfg(test)] mod test_submit_and_wait { use super::*; diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 517ca7d3..678f9547 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,15 +1,15 @@ use core::convert::{TryFrom, TryInto}; use alloc::{ - borrow::Cow, - dbg, format, + borrow::{Cow, ToOwned}, + format, string::{String, ToString}, vec::Vec, }; use anyhow::Result; use exceptions::XRPLResultException; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{value::Index, Map, Value}; pub mod account_info; pub mod account_tx; @@ -18,6 +18,7 @@ pub mod fee; pub mod ledger; pub mod server_state; pub mod submit; +pub mod tx; pub use account_info::*; pub use account_tx::*; @@ -25,10 +26,108 @@ pub use fee::*; pub use ledger::*; pub use server_state::*; pub use submit::*; +pub use tx::*; use crate::Err; use super::requests::XRPLRequest; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum XRPLOptionalResult { + Result(T), + Other(XRPLOtherResult), +} + +impl XRPLOptionalResult { + pub fn unwrap(self) -> T { + match self { + XRPLOptionalResult::Result(result) => result, + XRPLOptionalResult::Other(_) => { + panic!("{}", XRPLResultException::UnwrapOnOther.to_string()) + } + } + } + + /// Try to convert the result into an expected XRPL result. + pub fn try_into_result(self) -> Result { + match self { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } + + /// Get a value from the result by index. + pub fn try_get_typed(&self, index: I) -> Result + where + T: Serialize, + I: Index, + U: DeserializeOwned, + { + match self { + XRPLOptionalResult::Result(result) => match serde_json::to_value(result) { + Ok(value) => match value.get(index) { + Some(value) => match serde_json::from_value(value.to_owned()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + }, + Err(e) => Err!(e), + }, + XRPLOptionalResult::Other(other) => other.try_get_typed(index), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct XRPLOtherResult(Value); + +impl TryFrom> for XRPLOtherResult { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult) -> Result { + match result { + XRPLResult::Other(value) => Ok(value), + res => Err!(XRPLResultException::UnexpectedResultType( + "Other".to_string(), + res.get_name() + )), + } + } +} + +impl From for XRPLOtherResult { + fn from(value: Value) -> Self { + XRPLOtherResult(value) + } +} + +impl Into for XRPLOtherResult { + fn into(self) -> Value { + self.0 + } +} + +impl XRPLOtherResult { + pub fn get(&self, index: impl Index) -> Option<&Value> { + self.0.get(index) + } + + pub fn try_get_typed(&self, index: I) -> Result + where + I: Index, + T: DeserializeOwned, + { + match self.0.get(index) { + Some(value) => match serde_json::from_value(value.clone()) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + }, + None => Err!(XRPLResultException::IndexNotFound), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { @@ -38,7 +137,8 @@ pub enum XRPLResult<'a> { Ledger(Ledger<'a>), ServerState(ServerState<'a>), Submit(Submit<'a>), - Other(Value), + Tx(Tx<'a>), + Other(XRPLOtherResult), } impl<'a> From> for XRPLResult<'a> { @@ -77,9 +177,21 @@ impl<'a> From> for XRPLResult<'a> { } } +impl<'a> From> for XRPLResult<'a> { + fn from(tx: Tx<'a>) -> Self { + XRPLResult::Tx(tx) + } +} + impl<'a> From for XRPLResult<'a> { fn from(value: Value) -> Self { - XRPLResult::Other(value) + XRPLResult::Other(XRPLOtherResult(value)) + } +} + +impl<'a> From for XRPLResult<'a> { + fn from(other: XRPLOtherResult) -> Self { + XRPLResult::Other(other) } } @@ -88,7 +200,7 @@ impl<'a> TryInto for XRPLResult<'a> { fn try_into(self) -> Result { match self { - XRPLResult::Other(value) => Ok(value), + XRPLResult::Other(XRPLOtherResult(value)) => Ok(value), res => match serde_json::to_value(res) { Ok(value) => Ok(value), Err(e) => Err!(e), @@ -106,6 +218,7 @@ impl XRPLResult<'_> { XRPLResult::Ledger(_) => "Ledger".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), + XRPLResult::Tx(_) => "Tx".to_string(), XRPLResult::Other(_) => "Other".to_string(), } } @@ -213,15 +326,31 @@ impl<'a, 'de> Deserialize<'de> for XRPLResponse<'a> { } } +impl TryInto for XRPLResponse<'_> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match serde_json::to_value(self) { + Ok(value) => Ok(value), + Err(e) => Err!(e), + } + } +} + 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 { - dbg!(self.result.clone()); + pub fn try_into_opt_result(self) -> Result> + where + T: TryFrom, Error = anyhow::Error>, + { match self.result { - Some(result) => result.try_into(), + Some(result) => match result.clone().try_into() { + Ok(result) => Ok(XRPLOptionalResult::Result(result)), + Err(_) => Ok(XRPLOptionalResult::Other(result.try_into()?)), + }, None => { if let Some(error) = self.error { Err!(XRPLResultException::ResponseError(format!( @@ -235,6 +364,16 @@ impl<'a> XRPLResponse<'a> { } } } + + pub fn try_into_result(self) -> Result + where + T: TryFrom, Error = anyhow::Error>, + { + match self.try_into_opt_result()? { + XRPLOptionalResult::Result(result) => Ok(result), + XRPLOptionalResult::Other(other) => Err!(XRPLResultException::ExpectedResult(other)), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index a6b109b8..58fda72d 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -383,14 +383,6 @@ pub enum Flag { EnableAmendment(EnableAmendmentFlag), } -#[cfg(all( - feature = "websocket-std", - not(feature = "websocket"), - feature = "transactions", - feature = "transaction-helpers", - feature = "amounts", - feature = "wallet" -))] #[cfg(test)] mod test_tx_common_fields { use super::*; From 23a0c2659a7c720841233bf6bb568bacbcaf107f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:15:19 +0000 Subject: [PATCH 089/113] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 3cf0fd5d..1b4b147b 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -160,6 +160,11 @@ where } } +#[cfg(all( + feature = "websocket-std", + feature = "transactions", + feature = "wallet" +))] #[cfg(test)] mod test_submit_and_wait { use super::*; From a50981129e81789979c3d310a164de458c39bedc Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:19:30 +0000 Subject: [PATCH 090/113] try fix tests --- src/asynch/transaction/submit_and_wait.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 1b4b147b..0f9b456f 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -162,6 +162,7 @@ where #[cfg(all( feature = "websocket-std", + not(feature = "websocket"), feature = "transactions", feature = "wallet" ))] From aa689d030820c937f04c1dba04450a7647cb20bb Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 19:24:20 +0000 Subject: [PATCH 091/113] try fix tests --- src/models/transactions/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 58fda72d..a6b109b8 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -383,6 +383,14 @@ pub enum Flag { EnableAmendment(EnableAmendmentFlag), } +#[cfg(all( + feature = "websocket-std", + not(feature = "websocket"), + feature = "transactions", + feature = "transaction-helpers", + feature = "amounts", + feature = "wallet" +))] #[cfg(test)] mod test_tx_common_fields { use super::*; From bb3a44f71f1b1ae016e86aaf5b2acaa7992555a6 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 6 Sep 2024 20:12:38 +0000 Subject: [PATCH 092/113] finalize --- src/asynch/account/mod.rs | 4 ++-- src/asynch/clients/json_rpc/mod.rs | 21 +++------------------ src/asynch/wallet/mod.rs | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 8111972f..0a629024 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, dbg}; +use alloc::borrow::Cow; use anyhow::Result; use crate::{ @@ -75,7 +75,7 @@ where None, Some(ledger_index), None, - Some(true), + None, None, ) .into(); diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index c0a21fd5..b0290b4e 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -1,11 +1,5 @@ -use alloc::{ - dbg, - string::{String, ToString}, - sync::Arc, - vec, -}; +use alloc::{string::ToString, vec}; use anyhow::Result; -use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use serde::Serialize; use serde_json::{Map, Value}; use url::Url; @@ -21,6 +15,7 @@ pub use exceptions::XRPLJsonRpcException; use super::client::Client; +#[allow(async_fn_in_trait)] pub trait XRPLFaucet: Client { fn get_faucet_url(&self, url: Option) -> Result where @@ -58,7 +53,7 @@ mod _std { use crate::models::requests::{FundFaucet, XRPLRequest}; use super::*; - use alloc::{dbg, format, string::ToString}; + use alloc::string::ToString; use reqwest::Client as HttpClient; use url::Url; @@ -79,17 +74,14 @@ mod _std { ) -> Result> { let client = HttpClient::new(); let request_json_rpc = request_to_json_rpc(&request)?; - dbg!(&request_json_rpc); let response = client .post(self.url.as_ref()) .json(&request_json_rpc) .send() .await; - dbg!(&response); match response { Ok(response) => match response.text().await { Ok(response) => { - dbg!(&response); Ok(serde_json::from_str::>(&response).unwrap()) } Err(error) => Err!(error), @@ -108,28 +100,21 @@ mod _std { let faucet_url = self.get_faucet_url(url)?; let client = HttpClient::new(); let request_json_rpc = serde_json::to_value(&request).unwrap(); - dbg!(&request_json_rpc); let response = client .post(&faucet_url.to_string()) .json(&request_json_rpc) .send() .await; - dbg!(&response); match response { Ok(response) => { if response.status().is_success() { - dbg!("Success"); - dbg!(&response); Ok(()) } else { - dbg!("Error"); - dbg!(&response); todo!() // Err!(XRPLJsonRpcException::RequestError()) } } Err(error) => { - dbg!("req Error"); Err!(error) } } diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index b21b3a99..99330cde 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -1,6 +1,6 @@ pub mod exceptions; -use alloc::{borrow::Cow, dbg}; +use alloc::borrow::Cow; use anyhow::Result; use exceptions::XRPLFaucetException; use url::Url; @@ -14,7 +14,7 @@ use crate::{ use super::{ account::get_xrp_balance, - clients::{AsyncClient, Client, XRPLFaucet}, + clients::{Client, XRPLFaucet}, }; const TEST_FAUCET_URL: &'static str = "https://faucet.altnet.rippletest.net/accounts"; @@ -41,8 +41,7 @@ where }, }; let address = &wallet.classic_address; - dbg!(address); - let starting_balance = 0.into(); // check_balance(client, address.into()).await; + let starting_balance = check_balance(client, address.into()).await; let user_agent = user_agent.unwrap_or("xrpl-rust".into()); fund_wallet( client, @@ -58,8 +57,6 @@ where tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; if !is_funded { let balance = check_balance(client, address.into()).await; - dbg!(&balance); - dbg!(&starting_balance); if balance > starting_balance { is_funded = true; } @@ -108,8 +105,9 @@ async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPA where C: Client, { - get_xrp_balance(address, client, None).await.unwrap() - // .unwrap_or(XRPAmount::default()) + get_xrp_balance(address, client, None) + .await + .unwrap_or(XRPAmount::default()) } async fn fund_wallet<'a: 'b, 'b, C>( @@ -132,11 +130,11 @@ where Ok(()) } +#[cfg(all(feature = "json-rpc-std", feature = "helpers", feature = "models"))] #[cfg(test)] mod test_faucet_wallet_generation { use super::*; use crate::asynch::clients::json_rpc::AsyncJsonRpcClient; - use alloc::dbg; use url::Url; #[tokio::test] @@ -146,7 +144,9 @@ mod test_faucet_wallet_generation { let wallet = generate_faucet_wallet(&client, None, None, None, None) .await .unwrap(); - dbg!(&wallet); - assert_eq!(wallet.classic_address.len(), 34); + let balance = get_xrp_balance(wallet.classic_address.clone().into(), &client, None) + .await + .unwrap(); + assert!(balance > 0.into()); } } From c178c61f1347db3c897b90efe9ebf1b431f96881 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:39:50 +0000 Subject: [PATCH 093/113] fix github tests --- Cargo.toml | 2 +- src/asynch/clients/json_rpc/mod.rs | 11 +++++++++-- src/asynch/mod.rs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd1ddbea..0a9c4cc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ default = [ "models", "utils", "helpers", - "json-rpc-std", + "websocket-std", ] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index b0290b4e..dd1a2623 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -124,9 +124,11 @@ mod _std { #[cfg(feature = "json-rpc")] mod _no_std { - use crate::models::requests::XRPLRequest; + use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; use super::*; + use alloc::sync::Arc; + use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use embedded_nal_async::{Dns, TcpConnect}; use reqwless::{ client::{HttpClient, TlsConfig}, @@ -177,7 +179,8 @@ mod _no_std { request: XRPLRequest<'a>, ) -> Result> { let request_json_rpc = request_to_json_rpc(&request)?; - let request_buf = request_json_rpc.as_bytes(); + let request_string = request_json_rpc.to_string(); + let request_buf = request_string.as_bytes(); let mut rx_buffer = [0; BUF]; let mut client = self.client.lock().await; let response = match client.request(Method::POST, self.url.as_str()).await { @@ -201,6 +204,10 @@ mod _no_std { response } + + fn get_host(&self) -> Url { + self.url.clone() + } } } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index fc5acf16..b045b44c 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -11,5 +11,5 @@ pub mod clients; pub mod ledger; #[cfg(feature = "transaction-helpers")] pub mod transaction; -#[cfg(feature = "wallet-helpers")] +#[cfg(all(feature = "wallet-helpers", feature = "json-rpc-std"))] pub mod wallet; From 2a17e0c184e55990df4d8df09c5650fc3677be1f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:43:32 +0000 Subject: [PATCH 094/113] fix github tests --- src/asynch/wallet/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 99330cde..9e26c6ab 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -130,7 +130,12 @@ where Ok(()) } -#[cfg(all(feature = "json-rpc-std", feature = "helpers", feature = "models"))] +#[cfg(all( + feature = "json-rpc-std", + not(feature = "json-rpc"), + feature = "helpers", + feature = "models" +))] #[cfg(test)] mod test_faucet_wallet_generation { use super::*; From 50692630134a90f130d02c3193ad9d8f49c5a87c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 7 Sep 2024 08:50:51 +0000 Subject: [PATCH 095/113] fix github tests --- src/asynch/clients/websocket/_no_std.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 99bbc4d5..2f3926d2 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -54,6 +54,7 @@ pub struct AsyncWebsocketClient< websocket: Arc>>, tx_buffer: [u8; BUF], websocket_base: Arc>>, + uri: Url, status: PhantomData, } @@ -124,6 +125,7 @@ where websocket, tx_buffer: buffer, websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + uri: url, status: PhantomData::, }) } @@ -245,6 +247,10 @@ where E: Debug + Display, Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { + fn get_host(&self) -> Url { + self.uri.clone() + } + async fn request_impl<'a: 'b, 'b>( &self, mut request: XRPLRequest<'a>, From e23a72cbe09a536bd81947f9811366ae3179f27a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 8 Sep 2024 11:50:49 +0000 Subject: [PATCH 096/113] refactoring --- .github/workflows/unit_test.yml | 47 +++- Cargo.toml | 100 ++++--- src/asynch/account/mod.rs | 13 +- src/asynch/clients/async_client.rs | 4 +- src/asynch/clients/client.rs | 15 +- src/asynch/clients/json_rpc/exceptions.rs | 6 +- src/asynch/clients/json_rpc/mod.rs | 84 ++++-- src/asynch/clients/mod.rs | 36 ++- src/asynch/clients/websocket/_no_std.rs | 115 ++++---- src/asynch/clients/websocket/_std.rs | 26 +- src/asynch/clients/websocket/codec.rs | 30 --- src/asynch/clients/websocket/exceptions.rs | 15 +- src/asynch/clients/websocket/mod.rs | 30 +-- src/asynch/ledger/mod.rs | 6 +- src/asynch/mod.rs | 27 +- src/asynch/transaction/exceptions.rs | 2 +- src/asynch/transaction/mod.rs | 30 +-- src/asynch/transaction/submit_and_wait.rs | 45 ++-- src/asynch/wallet/mod.rs | 17 +- src/core/binarycodec/mod.rs | 23 +- src/core/types/mod.rs | 3 - src/lib.rs | 27 +- src/models/amount/mod.rs | 11 +- src/models/exceptions.rs | 8 - src/models/flag_collection.rs | 97 +++++++ src/models/ledger/mod.rs | 1 - src/models/ledger/objects/account_root.rs | 2 +- src/models/ledger/objects/amendments.rs | 4 +- src/models/ledger/objects/amm.rs | 4 +- src/models/ledger/objects/check.rs | 2 +- src/models/ledger/objects/deposit_preauth.rs | 2 +- src/models/ledger/objects/directory_node.rs | 2 +- src/models/ledger/objects/escrow.rs | 2 +- src/models/ledger/objects/fee_settings.rs | 2 +- src/models/ledger/objects/ledger_hashes.rs | 2 +- src/models/ledger/objects/negative_unl.rs | 2 +- src/models/ledger/objects/nftoken_offer.rs | 2 +- src/models/ledger/objects/nftoken_page.rs | 2 +- src/models/ledger/objects/offer.rs | 2 +- src/models/ledger/objects/pay_channel.rs | 2 +- src/models/ledger/objects/ripple_state.rs | 2 +- src/models/ledger/objects/signer_list.rs | 2 +- src/models/ledger/objects/ticket.rs | 2 +- src/models/mod.rs | 185 ++----------- src/models/model.rs | 8 - src/models/requests/account_channels.rs | 2 +- src/models/requests/channel_authorize.rs | 2 +- src/models/requests/ledger_entry.rs | 4 +- src/models/requests/mod.rs | 253 ++++++++---------- src/models/requests/unsubscribe.rs | 2 +- src/models/response.rs | 1 - src/models/results/account_info.rs | 2 +- src/models/results/ledger.rs | 2 +- src/models/results/mod.rs | 93 +++---- src/models/transactions/account_delete.rs | 2 +- src/models/transactions/account_set.rs | 4 +- src/models/transactions/check_cancel.rs | 2 +- src/models/transactions/check_cash.rs | 4 +- src/models/transactions/check_create.rs | 2 +- src/models/transactions/deposit_preauth.rs | 4 +- src/models/transactions/escrow_cancel.rs | 2 +- src/models/transactions/escrow_create.rs | 4 +- src/models/transactions/escrow_finish.rs | 4 +- src/models/transactions/exceptions.rs | 2 +- src/models/transactions/mod.rs | 65 ++--- .../transactions/nftoken_accept_offer.rs | 4 +- src/models/transactions/nftoken_burn.rs | 2 +- .../transactions/nftoken_cancel_offer.rs | 4 +- .../transactions/nftoken_create_offer.rs | 4 +- src/models/transactions/nftoken_mint.rs | 4 +- src/models/transactions/offer_cancel.rs | 2 +- src/models/transactions/offer_create.rs | 2 +- src/models/transactions/payment.rs | 5 +- .../transactions/payment_channel_claim.rs | 2 +- .../transactions/payment_channel_create.rs | 2 +- .../transactions/payment_channel_fund.rs | 2 +- .../pseudo_transactions/enable_amendment.rs | 2 +- .../pseudo_transactions/set_fee.rs | 2 +- .../pseudo_transactions/unl_modify.rs | 2 +- src/models/transactions/set_regular_key.rs | 2 +- src/models/transactions/signer_list_set.rs | 4 +- src/models/transactions/ticket_create.rs | 2 +- src/models/transactions/trust_set.rs | 2 +- src/models/utils.rs | 22 -- src/transaction/multisign.rs | 2 +- src/utils/mod.rs | 10 +- src/utils/transactions.rs | 2 +- src/wallet/wallet_generation.rs | 0 tests/common/mod.rs | 86 ++---- tests/integration/clients/mod.rs | 78 ------ tests/integration_tests.rs | 33 --- tests/test_utils.rs | 3 + 92 files changed, 762 insertions(+), 1028 deletions(-) delete mode 100644 src/asynch/clients/websocket/codec.rs create mode 100644 src/models/flag_collection.rs delete mode 100644 src/models/response.rs delete mode 100644 src/models/utils.rs delete mode 100644 src/wallet/wallet_generation.rs diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index c8feaf76..84b268c5 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -19,26 +19,57 @@ jobs: with: toolchain: stable - uses: actions-rs/cargo@v1 + name: Build with default features with: command: build - args: --release --all-features + args: --release - uses: actions-rs/cargo@v1 + name: Build with no default features with: command: build - args: --release --no-default-features --features core,models,websocket-std + args: --release --no-default-features - uses: actions-rs/cargo@v1 + name: Build for no_std with: - command: test - args: --all-features + command: build + args: --release --no-default-features --features embassy-rt,core,wallet,models,helpers,websocket,json-rpc - uses: actions-rs/cargo@v1 + name: Build only core with: - command: test - args: --no-default-features --features core,models,websocket-std + command: build + args: --release --no-default-features --features core + - uses: actions-rs/cargo@v1 + name: Build only wallet + with: + command: build + args: --release --no-default-features --features wallet + - uses: actions-rs/cargo@v1 + name: Build only models + with: + command: build + args: --release --no-default-features --features models + - uses: actions-rs/cargo@v1 + name: Build only helpers + with: + command: build + args: --release --no-default-features --features helpers,tokio-rt + - uses: actions-rs/cargo@v1 + name: Build only websocket + with: + command: build + args: --release --no-default-features --features websocket + - uses: actions-rs/cargo@v1 + name: Build only core + with: + command: build + args: --release --no-default-features --features json-rpc - uses: actions-rs/cargo@v1 + name: Test with default features with: command: test - args: --no-default-features --features std,models,websocket,websocket-codec + args: --release - uses: actions-rs/cargo@v1 + name: Test for no_std with: command: test - args: --no-default-features --features std,websocket-std,helpers + args: --release --no-default-features --features embassy-rt,core,wallet,models,helpers,websocket,json-rpc diff --git a/Cargo.toml b/Cargo.toml index 0a9c4cc6..6f46ef3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ tag-name = "{{version}}" [lib] name = "xrpl" crate-type = ["lib"] -proc-macro = true [dependencies] lazy_static = "1.4.0" @@ -62,37 +61,29 @@ fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.6.0", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } +embassy-sync = "0.6.0" # networking -url = { version = "2.2.2", default-features = false, optional = true } -# websocket -embassy-sync = { version = "0.6.0", optional = true } +url = { version = "2.2.2", default-features = false } embedded-io-async = { version = "0.6.1", optional = true } -embedded-websocket = { version = "0.9.3", default-features = false, optional = true } futures = { version = "0.3.30", optional = true } -rand_core = { version = "0.6.4", default-features = false } -# websocket-codec -bytes = { version = "1.7.1", default-features = false, optional = true } -tokio-util = { version = "0.7.11", features = ["codec"], optional = true } -# websocket-std -embassy-futures = { version = "0.1.1", optional = true } -tokio = { version = "1.0", default-features = false, features = [ - "net", -], optional = true } +tokio = { version = "1.0", optional = true } +embassy-time = { version = "0.3.2", optional = true } +embedded-websocket = { git = "https://github.com/LimpidCrypto/embedded-websocket", branch = "embedded-io-async", optional = true, default-features = false, features = [ + "embedded-io-async", +] } +reqwless = { version = "0.12.1", optional = true } +reqwest = { version = "0.12.7", optional = true, features = ["json"] } tokio-tungstenite = { version = "0.23.1", optional = true, features = [ "native-tls", ] } +embassy-futures = { version = "0.1.1", optional = true } embedded-nal-async = { version = "0.7.1", optional = true } -# json-rpc -reqwless = { version = "0.12.1", optional = true } -# json-rpc-std -reqwest = { version = "0.12.5", features = ["json"], optional = true } -embassy-time = { version = "0.3.2", optional = true } [dev-dependencies] criterion = "0.5.1" tokio = { version = "1.0", features = ["full"] } -tokio-util = { version = "0.7.11", features = ["codec"] } +embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } [[bench]] name = "benchmarks" @@ -101,63 +92,56 @@ harness = false [features] default = [ "std", + "tokio-rt", "core", "wallet", "models", "utils", "helpers", - "websocket-std", + "websocket", + "json-rpc", +] +models = [ + "transaction-models", + "request-models", + "ledger-models", + "result-models", ] -models = ["core", "transactions", "requests", "ledger", "results"] -transactions = ["core", "amounts", "currencies"] -requests = ["core", "amounts", "currencies"] -results = ["core", "amounts", "currencies"] -ledger = ["core", "amounts", "currencies"] +transaction-models = ["core"] +request-models = [] +result-models = ["request-models", "ledger-models"] +ledger-models = [] helpers = [ "account-helpers", "ledger-helpers", "transaction-helpers", "wallet-helpers", ] -account-helpers = ["amounts", "currencies", "requests", "results"] -ledger-helpers = ["amounts", "currencies", "requests", "results"] -wallet-helpers = ["requests", "results"] +account-helpers = ["core", "request-models", "result-models"] +ledger-helpers = ["request-models", "result-models"] +wallet-helpers = ["wallet", "request-models", "result-models"] transaction-helpers = [ "wallet", - "amounts", - "currencies", - "requests", - "results", - "transactions", - "ledger", - "embassy-time", + "account-helpers", + "ledger-helpers", + "request-models", + "result-models", + "transaction-models", + "ledger-models", ] -amounts = ["core"] -currencies = ["core"] -json-rpc = ["url", "reqwless", "embassy-sync", "embedded-nal-async"] -json-rpc-std = ["url", "reqwest", "embassy-sync", "tokio"] wallet = ["core"] +json-rpc = ["reqwless", "embedded-nal-async"] websocket = [ - "url", + "request-models", + "result-models", "futures", - "embedded-websocket", "embedded-io-async", - "embassy-sync", -] -websocket-codec = ["bytes", "tokio-util"] -websocket-std = [ - "url", - "futures", - "tokio", - "tokio-tungstenite", "embassy-futures", - "embassy-sync", + "embedded-websocket", ] core = ["utils"] utils = [] std = [ - "embedded-websocket/std", - "futures/std", "rand/std", "regex/std", "chrono/std", @@ -168,6 +152,12 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", - "embassy-time/std", + "embedded-websocket/std", + "reqwest", + "tokio", + "tokio-tungstenite", + "futures/std", ] -embassy-time = ["dep:embassy-time"] +# runtimes +tokio-rt = ["tokio"] +embassy-rt = ["embassy-time"] diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 0a629024..0ebc5cf9 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -4,10 +4,9 @@ use anyhow::Result; use crate::{ core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, models::{ - amount::XRPAmount, - ledger::AccountRoot, - requests::{AccountInfo, AccountTx}, - results, + ledger::objects::AccountRoot, + requests::{account_info::AccountInfo, account_tx::AccountTx}, + results, XRPAmount, }, Err, }; @@ -82,14 +81,14 @@ where let account_info = client.request(request).await?; Ok(account_info - .try_into_result::>()? + .try_into_result::>()? .account_data) } pub async fn get_latest_transaction<'a: 'b, 'b, C>( mut address: Cow<'a, str>, client: &C, -) -> Result> +) -> Result> where C: AsyncClient, { @@ -112,5 +111,5 @@ where None, ); let response = client.request(account_tx.into()).await?; - response.try_into_result::>() + response.try_into_result::>() } diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index af7e8eaf..6a0433a8 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -1,7 +1,7 @@ use super::{client::Client, CommonFields}; use crate::models::{ - requests::{ServerState, XRPLRequest}, - results::{ServerState as ServerStateResult, XRPLResponse}, + requests::{server_state::ServerState, XRPLRequest}, + results::{server_state::ServerState as ServerStateResult, XRPLResponse}, }; use anyhow::Result; diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 7a678ae2..5ccde6f6 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -2,12 +2,20 @@ use crate::models::{ requests::{Request, XRPLRequest}, results::XRPLResponse, }; -#[cfg(feature = "std")] -use crate::utils::get_random_id; -use alloc::borrow::{Cow, ToOwned}; +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, +}; use anyhow::Result; +use rand::Rng; use url::Url; +/// Generate a random id. +pub fn get_random_id(rng: &mut T) -> String { + let id: u32 = rng.gen(); + id.to_string() +} + #[allow(async_fn_in_trait)] pub trait Client { async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; @@ -21,6 +29,7 @@ pub trait Client { None => { #[cfg(feature = "std")] { + use alloc::borrow::Cow; let mut rng = rand::thread_rng(); Some(Cow::Owned(get_random_id(&mut rng))) } diff --git a/src/asynch/clients/json_rpc/exceptions.rs b/src/asynch/clients/json_rpc/exceptions.rs index 21fc86f5..bc10b2d7 100644 --- a/src/asynch/clients/json_rpc/exceptions.rs +++ b/src/asynch/clients/json_rpc/exceptions.rs @@ -1,12 +1,10 @@ -use reqwest::Response; use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum XRPLJsonRpcException { - #[cfg(feature = "json-rpc")] #[error("Reqwless error")] ReqwlessError, - #[cfg(feature = "json-rpc-std")] + #[cfg(feature = "std")] #[error("Request error: {0:?}")] - RequestError(Response), + RequestError(reqwest::Response), } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index dd1a2623..b403d35c 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -2,31 +2,14 @@ use alloc::{string::ToString, vec}; use anyhow::Result; use serde::Serialize; use serde_json::{Map, Value}; -use url::Url; -use crate::{ - asynch::wallet::get_faucet_url, - models::{requests::FundFaucet, results::XRPLResponse}, - Err, -}; +use crate::{models::results::XRPLResponse, Err}; mod exceptions; pub use exceptions::XRPLJsonRpcException; use super::client::Client; -#[allow(async_fn_in_trait)] -pub trait XRPLFaucet: Client { - fn get_faucet_url(&self, url: Option) -> Result - where - Self: Sized + Client, - { - get_faucet_url(self, url) - } - - async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; -} - /// Renames the requests field `command` to `method` for JSON-RPC. fn request_to_json_rpc(request: &impl Serialize) -> Result { let mut json_rpc_request = Map::new(); @@ -48,9 +31,12 @@ fn request_to_json_rpc(request: &impl Serialize) -> Result { Ok(Value::Object(json_rpc_request)) } -#[cfg(feature = "json-rpc-std")] +#[cfg(all(feature = "json-rpc", feature = "std"))] mod _std { - use crate::models::requests::{FundFaucet, XRPLRequest}; + use crate::{ + asynch::clients::XRPLFaucet, + models::requests::{FundFaucet, XRPLRequest}, + }; use super::*; use alloc::string::ToString; @@ -122,9 +108,12 @@ mod _std { } } -#[cfg(feature = "json-rpc")] +#[cfg(all(feature = "json-rpc", not(feature = "std")))] mod _no_std { - use crate::{asynch::clients::SingleExecutorMutex, models::requests::XRPLRequest}; + use crate::{ + asynch::clients::{SingleExecutorMutex, XRPLFaucet}, + models::requests::{FundFaucet, XRPLRequest}, + }; use super::*; use alloc::sync::Arc; @@ -153,7 +142,7 @@ mod _no_std { T: TcpConnect + 'a, D: Dns + 'a, { - pub fn new(url: Url, tcp: &'a T, dns: &'a D) -> Self { + pub fn connect(url: Url, tcp: &'a T, dns: &'a D) -> Self { Self { url, client: Arc::new(Mutex::new(HttpClient::new(tcp, dns))), @@ -187,7 +176,7 @@ mod _no_std { Ok(client) => { if let Err(_error) = client .body(request_buf) - .content_type(ContentType::TextPlain) + .content_type(ContentType::ApplicationJson) .send(&mut rx_buffer) .await { @@ -209,9 +198,52 @@ mod _no_std { self.url.clone() } } + + impl<'a, const BUF: usize, T, D, M> XRPLFaucet for AsyncJsonRpcClient<'a, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + let faucet_url = self.get_faucet_url(url)?; + let request_json_rpc = serde_json::to_value(&request).unwrap(); + let request_string = request_json_rpc.to_string(); + let request_buf = request_string.as_bytes(); + let mut rx_buffer = [0; BUF]; + let mut client = self.client.lock().await; + let response = match client.request(Method::POST, faucet_url.as_str()).await { + Ok(client) => { + if let Err(_error) = client + .body(request_buf) + .content_type(ContentType::ApplicationJson) + .send(&mut rx_buffer) + .await + { + Err!(XRPLJsonRpcException::ReqwlessError) + } else { + if let Ok(response) = serde_json::from_slice::>(&rx_buffer) + { + if response.is_success() { + Ok(()) + } else { + todo!() + // Err!(XRPLJsonRpcException::RequestError()) + } + } else { + Err!(XRPLJsonRpcException::ReqwlessError) + } + } + } + Err(_error) => Err!(XRPLJsonRpcException::ReqwlessError), + }; + + response + } + } } -#[cfg(all(feature = "json-rpc", not(feature = "json-rpc-std")))] +#[cfg(all(feature = "json-rpc", not(feature = "std")))] pub use _no_std::AsyncJsonRpcClient; -#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] +#[cfg(all(feature = "json-rpc", feature = "std"))] pub use _std::AsyncJsonRpcClient; diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 0843eb30..fdf420b1 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,23 +1,41 @@ pub mod async_client; pub mod client; -#[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))] -pub mod json_rpc; -#[cfg(any(feature = "websocket-std", feature = "websocket"))] -pub mod websocket; +#[cfg(feature = "json-rpc")] +mod json_rpc; +#[cfg(feature = "websocket")] +mod websocket; use alloc::borrow::Cow; +use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; -pub type MultiExecutorMutex = CriticalSectionRawMutex; -pub type SingleExecutorMutex = NoopRawMutex; +use serde::{Deserialize, Serialize}; +use url::Url; pub use async_client::*; pub use client::*; -#[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))] +#[cfg(feature = "json-rpc")] pub use json_rpc::*; -use serde::{Deserialize, Serialize}; -#[cfg(any(feature = "websocket-std", feature = "websocket"))] +#[cfg(feature = "websocket")] pub use websocket::*; +pub type MultiExecutorMutex = CriticalSectionRawMutex; +pub type SingleExecutorMutex = NoopRawMutex; + +#[cfg(feature = "wallet-helpers")] +use crate::{asynch::wallet::get_faucet_url, models::requests::FundFaucet}; +#[allow(async_fn_in_trait)] +#[cfg(feature = "wallet-helpers")] +pub trait XRPLFaucet: Client { + fn get_faucet_url(&self, url: Option) -> Result + where + Self: Sized + Client, + { + get_faucet_url(self, url) + } + + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommonFields<'a> { pub build_version: Option>, diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 2f3926d2..8baef20c 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -1,8 +1,4 @@ -use core::{ - fmt::{Debug, Display}, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; +use core::{marker::PhantomData, ops::DerefMut}; use alloc::{ string::{String, ToString}, @@ -16,12 +12,10 @@ use embedded_websocket::{ framer_async::{Framer, ReadResult}, Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; -use futures::Sink; -use futures::Stream; -use rand_core::RngCore; +use rand::RngCore; use url::Url; -use super::{WebsocketClosed, WebsocketOpen}; +use super::{WebSocketClosed, WebSocketOpen}; use crate::{ asynch::clients::SingleExecutorMutex, models::requests::{Request, XRPLRequest}, @@ -37,18 +31,15 @@ use crate::{ use super::exceptions::XRPLWebsocketException; -pub struct AsyncWebsocketClient< +pub struct AsyncWebSocketClient< const BUF: usize, Tcp, - B, - E, Rng: RngCore, M = SingleExecutorMutex, - Status = WebsocketClosed, + Status = WebSocketClosed, > where M: RawMutex, - B: Deref + AsRef<[u8]>, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { tcp: Arc>, websocket: Arc>>, @@ -58,19 +49,18 @@ pub struct AsyncWebsocketClient< status: PhantomData, } -impl - AsyncWebsocketClient +impl AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { pub async fn open( - rng: Rng, tcp: Tcp, url: Url, - ) -> Result> { + rng: Rng, + sub_protocols: Option<&[&str]>, + additional_headers: Option<&[&str]>, + ) -> Result> { // replace the scheme with http or https let scheme = match url.scheme() { "wss" => "https", @@ -89,15 +79,15 @@ where let path = url.path(); let host = match url.host_str() { Some(host) => host, - None => return Err!(XRPLWebsocketException::::Disconnected), + None => return Err!(XRPLWebsocketException::::Disconnected), }; let origin = scheme.to_string() + "://" + host + ":" + &port + path; let websocket_options = WebSocketOptions { path, host, origin: &origin, - sub_protocols: None, - additional_headers: None, + sub_protocols, + additional_headers, }; let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); let tcp = Arc::new(Mutex::new(tcp)); @@ -120,24 +110,30 @@ where } } - Ok(AsyncWebsocketClient { + Ok(AsyncWebSocketClient { tcp, websocket, tx_buffer: buffer, websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), uri: url, - status: PhantomData::, + status: PhantomData::, }) } } -impl - AsyncWebsocketClient +impl ErrorType + for AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, +{ + type Error = XRPLWebsocketException<::Error>; +} + +impl AsyncWebSocketClient +where + M: RawMutex, + Tcp: Read + Write + Unpin, { async fn do_write(&self, buf: &[u8]) -> Result::Error> { let mut inner = self.websocket.lock().await; @@ -154,7 +150,7 @@ where .await { Ok(()) => Ok(buf.len()), - Err(error) => Err(XRPLWebsocketException::::from(error)), + Err(error) => Err(XRPLWebsocketException::from(error)), } } @@ -166,57 +162,40 @@ where Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), Some(Ok(ReadResult::Ping(_))) => Ok(0), Some(Ok(ReadResult::Pong(_))) => Ok(0), - Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), - Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), - None => Err(XRPLWebsocketException::::Disconnected), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::Disconnected), + Some(Err(error)) => Err(XRPLWebsocketException::from(error)), + None => Err(XRPLWebsocketException::Disconnected), } } } -impl ErrorType - for AsyncWebsocketClient -where - M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, -{ - type Error = XRPLWebsocketException; -} - -impl Write - for AsyncWebsocketClient +impl Write + for AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { async fn write(&mut self, buf: &[u8]) -> Result { self.do_write(buf).await } } -impl Read - for AsyncWebsocketClient +impl Read + for AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { async fn read(&mut self, buf: &mut [u8]) -> Result { self.do_read(buf).await } } -impl MessageHandler - for AsyncWebsocketClient +impl MessageHandler + for AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { async fn setup_request_future(&mut self, id: String) { let mut websocket_base = self.websocket_base.lock().await; @@ -239,13 +218,11 @@ where } } -impl ClientTrait - for AsyncWebsocketClient +impl ClientTrait + for AsyncWebSocketClient where M: RawMutex, - B: Deref + AsRef<[u8]>, - E: Debug + Display, - Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, + Tcp: Read + Write + Unpin, { fn get_host(&self) -> Url { self.uri.clone() @@ -281,7 +258,9 @@ where } let message_str = match core::str::from_utf8(&rx_buffer[..u_size]) { Ok(response_str) => response_str, - Err(error) => return Err!(XRPLWebsocketException::::Utf8(error)), + Err(error) => { + return Err!(XRPLWebsocketException::::Utf8(error)) + } }; websocket_base .handle_message(message_str.to_string()) diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index ef05d70c..78688688 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -1,5 +1,5 @@ use super::exceptions::XRPLWebsocketException; -use super::{WebsocketClosed, WebsocketOpen}; +use super::{WebSocketClosed, WebSocketOpen}; use crate::asynch::clients::client::Client; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; use crate::asynch::clients::SingleExecutorMutex; @@ -24,7 +24,7 @@ use tokio_tungstenite::connect_async as tokio_tungstenite_connect_async; type TokioTungsteniteMaybeTlsStream = WebSocketStream>; -pub struct AsyncWebsocketClient +pub struct AsyncWebSocketClient where M: RawMutex, { @@ -34,7 +34,7 @@ where status: PhantomData, } -impl Sink for AsyncWebsocketClient +impl Sink for AsyncWebSocketClient where M: RawMutex, { @@ -85,7 +85,7 @@ where } } -impl Stream for AsyncWebsocketClient +impl Stream for AsyncWebSocketClient where M: RawMutex, { @@ -130,25 +130,25 @@ where } } -impl AsyncWebsocketClient +impl AsyncWebSocketClient where M: RawMutex, { - pub async fn open(uri: Url) -> Result> { + pub async fn open(uri: Url) -> Result> { let stream = match tokio_tungstenite_connect_async(uri.to_string()).await { Ok((stream, _)) => stream, Err(error) => return Err!(error), }; - Ok(AsyncWebsocketClient { + Ok(AsyncWebSocketClient { websocket: Arc::new(Mutex::new(stream)), websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), uri, - status: PhantomData::, + status: PhantomData::, }) } } -impl AsyncWebsocketClient +impl AsyncWebSocketClient where M: RawMutex, { @@ -163,16 +163,16 @@ where } } -impl AsyncWebsocketClient +impl AsyncWebSocketClient where M: RawMutex, { pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() + core::any::type_name::() == core::any::type_name::() } } -impl MessageHandler for AsyncWebsocketClient +impl MessageHandler for AsyncWebSocketClient where M: RawMutex, { @@ -197,7 +197,7 @@ where } } -impl Client for AsyncWebsocketClient +impl Client for AsyncWebSocketClient where M: RawMutex, { diff --git a/src/asynch/clients/websocket/codec.rs b/src/asynch/clients/websocket/codec.rs deleted file mode 100644 index 95e9b09b..00000000 --- a/src/asynch/clients/websocket/codec.rs +++ /dev/null @@ -1,30 +0,0 @@ -use alloc::{io, vec::Vec}; -use bytes::{BufMut, BytesMut}; -use tokio_util::codec::{Decoder, Encoder}; - -pub struct Codec; - -impl Decoder for Codec { - type Item = Vec; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result>, io::Error> { - if !src.is_empty() { - let len = src.len(); - let data = src.split_to(len).to_vec(); - Ok(Some(data)) - } else { - Ok(None) - } - } -} - -impl Encoder<&[u8]> for Codec { - type Error = io::Error; - - fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { - buf.reserve(data.len()); - buf.put(data); - Ok(()) - } -} diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 63052bb9..f659efb3 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -1,16 +1,13 @@ use core::fmt::Debug; use core::str::Utf8Error; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum XRPLWebsocketException { - #[cfg(all(feature = "websocket-std", not(feature = "websocket")))] - #[error("Unable to connect to websocket")] - UnableToConnect(tokio_tungstenite::tungstenite::Error), // FramerError #[error("I/O error: {0:?}")] Io(E), @@ -20,7 +17,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, - #[cfg(all(feature = "websocket", not(feature = "websocket-std")))] + #[cfg(all(feature = "websocket", not(feature = "std")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] @@ -29,7 +26,7 @@ pub enum XRPLWebsocketException { RxBufferTooSmall(usize), #[error("Unexpected message type")] UnexpectedMessageType, - #[cfg(all(feature = "websocket", not(feature = "websocket-std")))] + #[cfg(all(feature = "websocket", not(feature = "std")))] #[error("Embedded I/O error: {0:?}")] EmbeddedIoError(ErrorKind), #[error("Missing request channel sender.")] @@ -40,7 +37,7 @@ pub enum XRPLWebsocketException { InvalidMessage, } -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] impl From> for XRPLWebsocketException { fn from(value: FramerError) -> Self { match value { @@ -55,7 +52,7 @@ impl From> for XRPLWebsocketException { } } -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] impl EmbeddedIoError for XRPLWebsocketException { fn kind(&self) -> ErrorKind { match self { diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 9fc26d28..04520815 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -2,37 +2,35 @@ use crate::{ models::{requests::XRPLRequest, results::XRPLResponse}, Err, }; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +#[cfg(feature = "std")] use alloc::string::String; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(not(feature = "std"))] use alloc::string::ToString; use anyhow::Result; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(not(feature = "std"))] use core::fmt::Display; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(not(feature = "std"))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +#[cfg(feature = "std")] use futures::{Sink, SinkExt, Stream, StreamExt}; mod websocket_base; use websocket_base::MessageHandler; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] mod _no_std; -#[cfg(all(feature = "websocket-codec", feature = "std"))] -pub mod codec; mod exceptions; -pub use exceptions::XRPLWebsocketException; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +pub use exceptions::*; +#[cfg(all(feature = "websocket", feature = "std"))] mod _std; -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(all(feature = "websocket", not(feature = "std")))] pub use _no_std::*; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +#[cfg(all(feature = "websocket", feature = "std"))] pub use _std::*; -pub struct WebsocketOpen; -pub struct WebsocketClosed; +pub struct WebSocketOpen; +pub struct WebSocketClosed; #[allow(async_fn_in_trait)] pub trait XRPLWebsocketIO { @@ -41,7 +39,7 @@ pub trait XRPLWebsocketIO { async fn xrpl_receive(&mut self) -> Result>>; } -#[cfg(all(feature = "websocket", not(feature = "websocket-std")))] +#[cfg(not(feature = "std"))] impl XRPLWebsocketIO for T where ::Error: Display, @@ -86,7 +84,7 @@ where } } -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] +#[cfg(feature = "std")] impl XRPLWebsocketIO for T where T: Stream> + Sink + MessageHandler + Unpin, diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index cd03be65..1ff7b40a 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -4,9 +4,9 @@ use alloc::string::ToString; use anyhow::Result; use crate::models::{ - amount::XRPAmount, - requests::{Fee, Ledger}, - results::{Drops, Fee as FeeResult, Ledger as LedgerResult}, + requests::{fee::Fee, ledger::Ledger}, + results::{fee::Drops, fee::Fee as FeeResult, ledger::Ledger as LedgerResult}, + XRPAmount, }; use super::clients::AsyncClient; diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index b045b44c..b94b9b75 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,15 +1,22 @@ -#[cfg(feature = "account-helpers")] -pub mod account; -#[cfg(any( - feature = "websocket-std", - feature = "websocket", - feature = "json-rpc-std", - feature = "json-rpc" +#[cfg(all( + feature = "account-helpers", + any(feature = "websocket", feature = "json-rpc") ))] +pub mod account; +#[cfg(any(feature = "websocket", feature = "json-rpc"))] pub mod clients; -#[cfg(feature = "ledger-helpers")] +#[cfg(all( + feature = "ledger-helpers", + any(feature = "websocket", feature = "json-rpc") +))] pub mod ledger; -#[cfg(feature = "transaction-helpers")] +#[cfg(all( + feature = "transaction-helpers", + any(feature = "websocket", feature = "json-rpc") +))] pub mod transaction; -#[cfg(all(feature = "wallet-helpers", feature = "json-rpc-std"))] +#[cfg(all( + feature = "wallet-helpers", + any(feature = "websocket", feature = "json-rpc") +))] pub mod wallet; diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs index 4e37a1ca..49ccb3fc 100644 --- a/src/asynch/transaction/exceptions.rs +++ b/src/asynch/transaction/exceptions.rs @@ -3,7 +3,7 @@ use core::num::ParseIntError; use alloc::borrow::Cow; use thiserror_no_std::Error; -use crate::models::amount::XRPAmount; +use crate::models::XRPAmount; #[derive(Error, Debug, PartialEq)] pub enum XRPLTransactionException<'a> { diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index c23b63ce..f6b28519 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -16,12 +16,12 @@ use crate::{ keypairs::sign as keypairs_sign, }, models::{ - amount::XRPAmount, - exceptions::XRPLModelException, - requests::{ServerState, Submit}, - results::{ServerState as ServerStateResult, Submit as SubmitResult}, - transactions::{Signer, Transaction, TransactionType, XRPLTransactionFieldException}, - Model, + requests::{server_state::ServerState, submit::Submit}, + results::{server_state::ServerState as ServerStateResult, submit::Submit as SubmitResult}, + transactions::{ + exceptions::XRPLTransactionFieldException, Signer, Transaction, TransactionType, + }, + Model, XRPAmount, XRPLModelException, }, utils::transactions::{ get_transaction_field_value, set_transaction_field_value, validate_transaction_has_field, @@ -481,15 +481,15 @@ where } } -#[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] +#[cfg(all(feature = "websocket", feature = "std"))] #[cfg(test)] mod test_autofill { use super::autofill; use crate::{ - asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, + asynch::clients::{AsyncWebSocketClient, SingleExecutorMutex}, models::{ - amount::{IssuedCurrencyAmount, XRPAmount}, - transactions::{OfferCreate, Transaction}, + transactions::{offer_create::OfferCreate, Transaction}, + IssuedCurrencyAmount, XRPAmount, }, }; use anyhow::Result; @@ -517,7 +517,7 @@ mod test_autofill { None, None, ); - let client = AsyncWebsocketClient::::open( + let client = AsyncWebSocketClient::::open( "wss://testnet.xrpl-labs.com/".parse().unwrap(), ) .await @@ -533,17 +533,17 @@ mod test_autofill { } } -#[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] +#[cfg(all(feature = "websocket", feature = "std"))] #[cfg(test)] mod test_sign { use alloc::borrow::Cow; use crate::{ asynch::{ - clients::{AsyncWebsocketClient, SingleExecutorMutex}, + clients::{AsyncWebSocketClient, SingleExecutorMutex}, transaction::{autofill_and_sign, sign}, }, - models::transactions::{AccountSet, Transaction}, + models::transactions::{account_set::AccountSet, Transaction}, wallet::Wallet, }; @@ -602,7 +602,7 @@ mod test_sign { None, None, ); - let client = AsyncWebsocketClient::::open( + let client = AsyncWebSocketClient::::open( "wss://testnet.xrpl-labs.com/".parse().unwrap(), ) .await diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 0f9b456f..4f260239 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -27,7 +27,7 @@ pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( wallet: Option<&Wallet>, check_fee: Option, autofill: Option, -) -> Result> +) -> Result> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, @@ -40,7 +40,7 @@ where async fn send_reliable_submission<'a: 'b, 'b, T, F, C>( transaction: &'b mut T, client: &C, -) -> Result> +) -> Result> where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, @@ -72,18 +72,28 @@ async fn wait_for_final_transaction_result<'a: 'b, 'b, C>( tx_hash: Cow<'a, str>, client: &C, last_ledger_sequence: u32, -) -> Result> +) -> Result> where C: AsyncClient, { let mut validated_ledger_sequence = 0; + let mut c = 0; while validated_ledger_sequence < last_ledger_sequence { + c += 1; + if c > 20 { + panic!() + } validated_ledger_sequence = get_latest_validated_ledger_sequence(client).await?; // sleep for 1 second - // embassy_time::Timer::after_secs(1).await; + #[cfg(feature = "embassy-rt")] + embassy_time::Timer::after_secs(1).await; + #[cfg(any( + feature = "tokio-rt", + all(feature = "embassy-rt", feature = "tokio-rt") + ))] tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; let response = client - .request(requests::Tx::new(None, None, None, None, Some(tx_hash.clone())).into()) + .request(requests::tx::Tx::new(None, None, None, None, Some(tx_hash.clone())).into()) .await?; if response.is_success() { if let Some(error) = response.error { @@ -96,7 +106,7 @@ where )); } } else { - let opt_result = response.try_into_opt_result::()?; + let opt_result = response.try_into_opt_result::()?; let validated = opt_result.try_get_typed("validated")?; if validated { let result = opt_result.try_into_result()?; @@ -161,23 +171,25 @@ where } #[cfg(all( - feature = "websocket-std", - not(feature = "websocket"), - feature = "transactions", - feature = "wallet" + feature = "std", + feature = "json-rpc", + feature = "wallet-helpers", + feature = "tokio-rt" ))] #[cfg(test)] mod test_submit_and_wait { use super::*; use crate::{ - asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, - models::transactions::AccountSet, - wallet::Wallet, + asynch::{clients::AsyncJsonRpcClient, wallet::generate_faucet_wallet}, + models::transactions::account_set::AccountSet, }; #[tokio::test] async fn test_submit_and_wait() { - let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let client = AsyncJsonRpcClient::connect("https://testnet.xrpl-labs.com/".parse().unwrap()); + let wallet = generate_faucet_wallet(&client, None, None, None, None) + .await + .unwrap(); let mut tx = AccountSet::new( Cow::from(wallet.classic_address.clone()), None, @@ -198,11 +210,6 @@ mod test_submit_and_wait { None, None, ); - let client = AsyncWebsocketClient::::open( - "wss://testnet.xrpl-labs.com/".parse().unwrap(), - ) - .await - .unwrap(); submit_and_wait(&mut tx, &client, Some(&wallet), Some(true), Some(true)) .await .unwrap(); diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 9e26c6ab..5a9e54c3 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -7,7 +7,7 @@ use url::Url; use crate::{ asynch::account::get_next_valid_seq_number, - models::{amount::XRPAmount, requests::FundFaucet}, + models::{requests::FundFaucet, XRPAmount}, wallet::Wallet, Err, }; @@ -54,6 +54,12 @@ where let mut is_funded = false; for _ in 0..TIMEOUT_SECS { // wait 1 second + #[cfg(all(feature = "embassy-rt", not(feature = "tokio-rt")))] + embassy_time::Timer::after_secs(1).await; + #[cfg(any( + feature = "tokio-rt", + all(feature = "embassy-rt", feature = "tokio-rt") + ))] tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; if !is_funded { let balance = check_balance(client, address.into()).await; @@ -130,16 +136,11 @@ where Ok(()) } -#[cfg(all( - feature = "json-rpc-std", - not(feature = "json-rpc"), - feature = "helpers", - feature = "models" -))] +#[cfg(all(feature = "json-rpc", feature = "std"))] #[cfg(test)] mod test_faucet_wallet_generation { use super::*; - use crate::asynch::clients::json_rpc::AsyncJsonRpcClient; + use crate::asynch::clients::AsyncJsonRpcClient; use url::Url; #[tokio::test] diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index 713872fd..e619421b 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -2,14 +2,13 @@ //! canonical binary format and decoding them. use super::types::{AccountId, STObject}; -use crate::{models::transactions::Transaction, Err}; +use crate::Err; use alloc::{borrow::Cow, string::String, vec::Vec}; use anyhow::Result; use core::{convert::TryFrom, fmt::Debug}; use hex::ToHex; use serde::{de::DeserializeOwned, Serialize}; -use strum::IntoEnumIterator; pub mod binary_wrappers; pub mod exceptions; @@ -21,18 +20,16 @@ pub use binary_wrappers::*; const TRANSACTION_SIGNATURE_PREFIX: i32 = 0x53545800; const TRANSACTION_MULTISIG_PREFIX: i32 = 0x534D5400; -pub fn encode<'a, T, F>(signed_transaction: &T) -> Result +pub fn encode<'a, T>(signed_transaction: &T) -> Result where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Serialize + DeserializeOwned + Clone + Debug, { serialize_json(signed_transaction, None, None, false) } -pub fn encode_for_signing<'a, T, F>(prepared_transaction: &T) -> Result +pub fn encode_for_signing<'a, T>(prepared_transaction: &T) -> Result where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Serialize + DeserializeOwned + Clone + Debug, { serialize_json( prepared_transaction, @@ -42,13 +39,12 @@ where ) } -pub fn encode_for_multisigning<'a, T, F>( +pub fn encode_for_multisigning<'a, T>( prepared_transaction: &T, signing_account: Cow<'a, str>, ) -> Result where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Serialize + DeserializeOwned + Clone + Debug, { let signing_account_id = AccountId::try_from(signing_account.as_ref()).unwrap(); @@ -60,15 +56,14 @@ where ) } -fn serialize_json<'a, T, F>( +fn serialize_json<'a, T>( prepared_transaction: &T, prefix: Option<&[u8]>, suffix: Option<&[u8]>, signing_only: bool, ) -> Result where - F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Serialize + DeserializeOwned + Clone + Debug, { let mut buffer = Vec::new(); if let Some(p) = prefix { diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index a34da2c6..dac7bbb8 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -298,7 +298,6 @@ impl STObject { /// assert_eq!(hex, buffer); /// ``` pub fn try_from_value(value: Value, signing_only: bool) -> Result { - // dbg!(&value); let object = match value { Value::Object(map) => map, _ => return Err!(exceptions::XRPLSerializeMapException::ExpectedObject), @@ -382,7 +381,6 @@ impl STObject { } } - // dbg!(&value_xaddress_handled); let mut sorted_keys: Vec = Vec::new(); for (field, _) in &value_xaddress_handled { let field_instance = get_field_instance(&field); @@ -413,7 +411,6 @@ impl STObject { associated_value.to_owned(), )?; let associated_value: SerializedType = associated_value.into(); - // dbg!(&field_instance, &associated_value.to_string(),); if field_instance.name == "TransactionType" && associated_value.to_string() == UNL_MODIFY_TX_TYPE { diff --git a/src/lib.rs b/src/lib.rs index 8e527e20..98486805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,28 +25,41 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std as alloc; +#[cfg(all(feature = "request-models", feature = "result-models"))] pub mod asynch; pub mod constants; #[cfg(feature = "core")] pub mod core; pub mod macros; #[cfg(any( - feature = "amounts", - feature = "currencies", - feature = "ledger", - feature = "requests", - feature = "results", - feature = "transactions" + feature = "ledger-models", + feature = "request-models", + feature = "result-models", + feature = "transaction-models" ))] pub mod models; #[cfg(feature = "transaction-helpers")] pub mod transaction; #[cfg(feature = "utils")] pub mod utils; +#[cfg(feature = "wallet")] pub mod wallet; -pub extern crate indexmap; pub extern crate serde_json; mod _anyhow; +#[cfg(any( + feature = "ledger-models", + feature = "request-models", + feature = "result-models", + feature = "transaction-models" +))] mod _serde; + +#[cfg(all(feature = "embassy-rt", feature = "tokio-rt"))] +compile_error!("Cannot enable both `embassy-rt` and `tokio-rt` features at the same time."); +#[cfg(all( + any(feature = "transaction-helpers", feature = "wallet-helpers"), + not(any(feature = "embassy-rt", feature = "tokio-rt")) +))] +compile_error!("Cannot enable `transaction-helpers` or `wallet-helpers` without enabling either `embassy-rt` or `tokio-rt`."); diff --git a/src/models/amount/mod.rs b/src/models/amount/mod.rs index 313e7107..25893015 100644 --- a/src/models/amount/mod.rs +++ b/src/models/amount/mod.rs @@ -1,13 +1,14 @@ -pub mod exceptions; -pub mod issued_currency_amount; -pub mod xrp_amount; +mod exceptions; +mod issued_currency_amount; +mod xrp_amount; -use core::convert::TryInto; +pub use exceptions::*; pub use issued_currency_amount::*; -use rust_decimal::Decimal; pub use xrp_amount::*; use crate::models::Model; +use core::convert::TryInto; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use strum_macros::Display; diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index 1acd1b47..b1f317e7 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -1,19 +1,11 @@ //! General XRPL Model Exception. -use crate::models::requests::XRPLRequestException; -use crate::models::transactions::XRPLTransactionException; use alloc::string::String; use serde::{Deserialize, Serialize}; use thiserror_no_std::Error; #[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), } diff --git a/src/models/flag_collection.rs b/src/models/flag_collection.rs new file mode 100644 index 00000000..38d35bb0 --- /dev/null +++ b/src/models/flag_collection.rs @@ -0,0 +1,97 @@ +use core::convert::TryFrom; + +use alloc::vec::Vec; +use anyhow::Result; +use derive_new::new; +use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; +use strum_macros::{AsRefStr, Display, EnumIter}; + +use crate::{models::XRPLFlagsException, Err}; + +/// Represents the type of flags when the XRPL model has no flags. +#[derive( + Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr, EnumIter, Copy, +)] +pub enum NoFlags {} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, new)] +pub struct FlagCollection(pub(crate) Vec) +where + T: IntoEnumIterator; + +impl Iterator for FlagCollection +where + T: IntoEnumIterator, +{ + type Item = T; + + fn next(&mut self) -> Option { + self.0.pop() + } +} + +impl Default for FlagCollection +where + T: IntoEnumIterator, +{ + fn default() -> Self { + FlagCollection(Vec::new()) + } +} + +impl From> for FlagCollection +where + T: IntoEnumIterator, +{ + fn from(flags: Vec) -> Self { + FlagCollection(flags) + } +} + +impl TryFrom for FlagCollection +where + T: IntoEnumIterator + Serialize, +{ + type Error = anyhow::Error; + + fn try_from(flags: u32) -> Result { + let mut flag_collection = Vec::new(); + for flag in T::iter() { + let flag_as_u32 = flag_to_u32(&flag)?; + if flags & flag_as_u32 == flag_as_u32 { + flag_collection.push(flag); + } + } + Ok(FlagCollection::new(flag_collection)) + } +} + +impl TryFrom> for u32 +where + T: IntoEnumIterator + Serialize, +{ + type Error = anyhow::Error; + + fn try_from(flag_collection: FlagCollection) -> Result { + let mut flags = 0; + for flag in flag_collection { + let flag_as_u32 = flag_to_u32(&flag)?; + flags |= flag_as_u32; + } + Ok(flags) + } +} + +fn flag_to_u32(flag: &T) -> Result +where + T: Serialize, +{ + match serde_json::to_string(flag) { + Ok(flag_as_string) => match flag_as_string.parse::() { + Ok(flag_as_u32) => Ok(flag_as_u32), + Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), + }, + Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), + } +} diff --git a/src/models/ledger/mod.rs b/src/models/ledger/mod.rs index bcfa65b2..15ee7f6c 100644 --- a/src/models/ledger/mod.rs +++ b/src/models/ledger/mod.rs @@ -1,2 +1 @@ pub mod objects; -pub use objects::*; diff --git a/src/models/ledger/objects/account_root.rs b/src/models/ledger/objects/account_root.rs index 56180386..021ef523 100644 --- a/src/models/ledger/objects/account_root.rs +++ b/src/models/ledger/objects/account_root.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::{amount::XRPAmount, Model}; use alloc::borrow::Cow; diff --git a/src/models/ledger/objects/amendments.rs b/src/models/ledger/objects/amendments.rs index 6740200b..3bf17097 100644 --- a/src/models/ledger/objects/amendments.rs +++ b/src/models/ledger/objects/amendments.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::{Model, NoFlags}; @@ -80,7 +80,7 @@ impl<'a> Amendments<'a> { #[cfg(test)] mod tests { - use crate::models::ledger::{Amendments, Majority}; + use crate::models::ledger::objects::{Amendments, Majority}; use alloc::borrow::Cow; use alloc::string::ToString; use alloc::vec; diff --git a/src/models/ledger/objects/amm.rs b/src/models/ledger/objects/amm.rs index 48f965df..910621db 100644 --- a/src/models/ledger/objects/amm.rs +++ b/src/models/ledger/objects/amm.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::NoFlags; use crate::models::{amount::Amount, Currency, Model}; @@ -132,7 +132,7 @@ impl<'a> AMM<'a> { mod test_serde { use crate::models::amount::{Amount, IssuedCurrencyAmount}; use crate::models::currency::{Currency, IssuedCurrency, XRP}; - use crate::models::ledger::amm::{AuctionSlot, AuthAccount, VoteEntry, AMM}; + use crate::models::ledger::objects::amm::{AuctionSlot, AuthAccount, VoteEntry, AMM}; use alloc::borrow::Cow; use alloc::string::ToString; use alloc::vec; diff --git a/src/models/ledger/objects/check.rs b/src/models/ledger/objects/check.rs index 3800ea0e..ce2dba34 100644 --- a/src/models/ledger/objects/check.rs +++ b/src/models/ledger/objects/check.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; diff --git a/src/models/ledger/objects/deposit_preauth.rs b/src/models/ledger/objects/deposit_preauth.rs index 926a0c08..4d390fbc 100644 --- a/src/models/ledger/objects/deposit_preauth.rs +++ b/src/models/ledger/objects/deposit_preauth.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; diff --git a/src/models/ledger/objects/directory_node.rs b/src/models/ledger/objects/directory_node.rs index b8cad877..68d24c1d 100644 --- a/src/models/ledger/objects/directory_node.rs +++ b/src/models/ledger/objects/directory_node.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; diff --git a/src/models/ledger/objects/escrow.rs b/src/models/ledger/objects/escrow.rs index 9ab60cb5..a0eb9144 100644 --- a/src/models/ledger/objects/escrow.rs +++ b/src/models/ledger/objects/escrow.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; diff --git a/src/models/ledger/objects/fee_settings.rs b/src/models/ledger/objects/fee_settings.rs index 67ef763a..58e10f45 100644 --- a/src/models/ledger/objects/fee_settings.rs +++ b/src/models/ledger/objects/fee_settings.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; diff --git a/src/models/ledger/objects/ledger_hashes.rs b/src/models/ledger/objects/ledger_hashes.rs index db47a071..c5d292af 100644 --- a/src/models/ledger/objects/ledger_hashes.rs +++ b/src/models/ledger/objects/ledger_hashes.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; diff --git a/src/models/ledger/objects/negative_unl.rs b/src/models/ledger/objects/negative_unl.rs index b6d2eeae..3f4e7542 100644 --- a/src/models/ledger/objects/negative_unl.rs +++ b/src/models/ledger/objects/negative_unl.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; diff --git a/src/models/ledger/objects/nftoken_offer.rs b/src/models/ledger/objects/nftoken_offer.rs index 224982dd..b139e876 100644 --- a/src/models/ledger/objects/nftoken_offer.rs +++ b/src/models/ledger/objects/nftoken_offer.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; diff --git a/src/models/ledger/objects/nftoken_page.rs b/src/models/ledger/objects/nftoken_page.rs index 750ed97e..1489da22 100644 --- a/src/models/ledger/objects/nftoken_page.rs +++ b/src/models/ledger/objects/nftoken_page.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use alloc::vec::Vec; use derive_new::new; diff --git a/src/models/ledger/objects/offer.rs b/src/models/ledger/objects/offer.rs index fc3c9fc9..ecc9d558 100644 --- a/src/models/ledger/objects/offer.rs +++ b/src/models/ledger/objects/offer.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; diff --git a/src/models/ledger/objects/pay_channel.rs b/src/models/ledger/objects/pay_channel.rs index 95fe2d3b..6043603f 100644 --- a/src/models/ledger/objects/pay_channel.rs +++ b/src/models/ledger/objects/pay_channel.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::NoFlags; use crate::models::{amount::Amount, Model}; diff --git a/src/models/ledger/objects/ripple_state.rs b/src/models/ledger/objects/ripple_state.rs index ac207c4d..49074554 100644 --- a/src/models/ledger/objects/ripple_state.rs +++ b/src/models/ledger/objects/ripple_state.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::{amount::Amount, Model}; use alloc::borrow::Cow; diff --git a/src/models/ledger/objects/signer_list.rs b/src/models/ledger/objects/signer_list.rs index f95cbfdd..3b1f8e09 100644 --- a/src/models/ledger/objects/signer_list.rs +++ b/src/models/ledger/objects/signer_list.rs @@ -1,4 +1,4 @@ -use crate::models::ledger::LedgerEntryType; +use crate::models::ledger::objects::LedgerEntryType; use crate::models::FlagCollection; use crate::models::Model; use alloc::borrow::Cow; diff --git a/src/models/ledger/objects/ticket.rs b/src/models/ledger/objects/ticket.rs index 0f85331c..d1778b47 100644 --- a/src/models/ledger/objects/ticket.rs +++ b/src/models/ledger/objects/ticket.rs @@ -1,6 +1,6 @@ use crate::models::FlagCollection; use crate::models::Model; -use crate::models::{ledger::LedgerEntryType, NoFlags}; +use crate::models::{ledger::objects::LedgerEntryType, NoFlags}; use alloc::borrow::Cow; use serde::{Deserialize, Serialize}; diff --git a/src/models/mod.rs b/src/models/mod.rs index 6d9c5ebd..3b75ca02 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -7,61 +7,32 @@ //! 4. Required specific fields in alphabetical order //! 5. Optional specific fields in alphabetical order -pub mod exceptions; -#[cfg(feature = "ledger")] +#[cfg(feature = "ledger-models")] pub mod ledger; -pub mod model; -#[cfg(feature = "requests")] +#[cfg(feature = "request-models")] #[allow(clippy::too_many_arguments)] pub mod requests; -#[cfg(feature = "results")] +#[cfg(feature = "result-models")] pub mod results; -#[cfg(feature = "transactions")] +#[cfg(feature = "transaction-models")] #[allow(clippy::too_many_arguments)] pub mod transactions; -#[cfg(feature = "amounts")] -pub mod amount; -#[cfg(feature = "currencies")] -pub mod currency; -pub mod utils; +mod amount; +mod currency; +mod exceptions; +mod flag_collection; +mod model; -use core::convert::TryFrom; +pub use amount::*; +pub use currency::*; +pub use exceptions::*; +pub use flag_collection::*; +pub use model::*; +use alloc::borrow::Cow; use derive_new::new; -pub use model::Model; -use strum::IntoEnumIterator; - -use crate::models::currency::{Currency, XRP}; -use crate::models::exceptions::XRPLFlagsException; -use crate::Err; -use alloc::{borrow::Cow, vec::Vec}; -use anyhow::Result; use serde::{Deserialize, Serialize}; -use strum_macros::{AsRefStr, Display, EnumIter}; - -/// Represents the object types that an AccountObjects -/// Request can ask for. -#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display)] -#[strum(serialize_all = "snake_case")] -#[serde(rename_all = "snake_case")] -#[serde(tag = "type")] -pub enum AccountObjectType { - Check, - DepositPreauth, - Escrow, - Offer, - PaymentChannel, - SignerList, - RippleState, - Ticket, -} - -/// Represents the type of flags when the XRPL model has no flags. -#[derive( - Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr, EnumIter, Copy, -)] -pub enum NoFlags {} /// A PathStep represents an individual step along a Path. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default, Clone, new)] @@ -74,133 +45,7 @@ pub struct PathStep<'a> { type_hex: Option>, } -/// Returns a Currency as XRP for the currency, without a value. -fn default_xrp_currency<'a>() -> Currency<'a> { - Currency::XRP(XRP::new()) -} - -/// For use with serde defaults. -fn default_true() -> Option { - Some(true) -} - /// For use with serde defaults. fn default_false() -> Option { Some(false) } - -/// For use with serde defaults. -fn default_limit_200() -> Option { - Some(200) -} - -/// For use with serde defaults. -fn default_limit_300() -> Option { - Some(300) -} - -/// For use with serde defaults. -fn default_fee_mult_max() -> Option { - Some(10) -} - -/// For use with serde defaults. -fn default_fee_div_max() -> Option { - Some(1) -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, new)] -pub struct FlagCollection(pub(crate) Vec) -where - T: IntoEnumIterator; - -impl Iterator for FlagCollection -where - T: IntoEnumIterator, -{ - type Item = T; - - fn next(&mut self) -> Option { - self.0.pop() - } -} - -impl Default for FlagCollection -where - T: IntoEnumIterator, -{ - fn default() -> Self { - FlagCollection(Vec::new()) - } -} - -impl From> for FlagCollection -where - T: IntoEnumIterator, -{ - fn from(flags: Vec) -> Self { - FlagCollection(flags) - } -} - -impl TryFrom for FlagCollection -where - T: IntoEnumIterator + Serialize, -{ - type Error = anyhow::Error; - - fn try_from(flags: u32) -> Result { - let mut flag_collection = Vec::new(); - for flag in T::iter() { - let flag_as_u32 = flag_to_u32(&flag)?; - if flags & flag_as_u32 == flag_as_u32 { - flag_collection.push(flag); - } - } - Ok(FlagCollection::new(flag_collection)) - } -} - -impl TryFrom> for u32 -where - T: IntoEnumIterator + Serialize, -{ - type Error = anyhow::Error; - - fn try_from(flag_collection: FlagCollection) -> Result { - let mut flags = 0; - for flag in flag_collection { - let flag_as_u32 = flag_to_u32(&flag)?; - flags |= flag_as_u32; - } - Ok(flags) - } -} - -fn flag_to_u32(flag: &T) -> Result -where - T: Serialize, -{ - match serde_json::to_string(flag) { - Ok(flag_as_string) => match flag_as_string.parse::() { - Ok(flag_as_u32) => Ok(flag_as_u32), - Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), - }, - Err(_error) => Err!(XRPLFlagsException::CannotConvertFlagToU32), - } -} - -// pub trait SignAndSubmitError { -// fn _get_field_error(&self) -> Result<(), XRPLSignAndSubmitException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignAndSubmitException>; -// } -// -// pub trait SignForError { -// fn _get_field_error(&self) -> Result<(), XRPLSignForException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignForException>; -// } -// -// pub trait SignError { -// fn _get_field_error(&self) -> Result<(), XRPLSignException>; -// fn _get_key_type_error(&self) -> Result<(), XRPLSignException>; -// } diff --git a/src/models/model.rs b/src/models/model.rs index a4325db2..7271c0de 100644 --- a/src/models/model.rs +++ b/src/models/model.rs @@ -16,12 +16,4 @@ pub trait Model { Err(error) => Err(error), } } - - /// Returns whether the structure is valid. - fn is_valid(&self) -> bool { - match self.get_errors() { - Ok(_no_error) => true, - Err(_error) => false, - } - } } diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index df0753ed..7c8903bb 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -21,7 +21,7 @@ use super::{CommonFields, Request}; /// ## Basic usage /// /// ``` -/// use xrpl::models::requests::AccountChannels; +/// use xrpl::models::requests::account_channels::AccountChannels; /// /// let json = r#"{"command":"account_channels","account":"rH6ZiHU1PGamME2LvVTxrgvfjQpppWKGmr","marker":12345678}"#.to_string(); /// let model: AccountChannels = serde_json::from_str(&json).expect(""); diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 466fa425..4b84c5c5 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -4,7 +4,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::requests::XRPLChannelAuthorizeException; +use crate::models::requests::exceptions::XRPLChannelAuthorizeException; use crate::{ constants::CryptoAlgorithm, models::{requests::RequestMethod, Model}, diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 0022437c..2cf0cad5 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -5,7 +5,7 @@ use derive_new::new; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::requests::XRPLLedgerEntryException; +use crate::models::requests::exceptions::XRPLLedgerEntryException; use crate::models::{requests::RequestMethod, Model}; use super::{CommonFields, Request}; @@ -215,7 +215,7 @@ pub trait LedgerEntryError { #[cfg(test)] mod test_ledger_entry_errors { use super::Offer; - use crate::models::requests::XRPLLedgerEntryException; + use crate::models::requests::exceptions::XRPLLedgerEntryException; use crate::models::Model; use alloc::string::ToString; diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 1c4f318a..5df5ee31 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -35,47 +35,10 @@ pub mod transaction_entry; pub mod tx; pub mod unsubscribe; -pub use account_channels::*; -pub use account_currencies::*; -pub use account_info::*; -pub use account_lines::*; -pub use account_nfts::*; -pub use account_objects::*; -pub use account_offers::*; -pub use account_tx::*; use alloc::borrow::Cow; -pub use book_offers::*; -pub use channel_authorize::*; -pub use channel_verify::*; -pub use deposit_authorize::*; use derive_new::new; -pub use exceptions::*; -pub use fee::*; -pub use gateway_balances::*; -pub use ledger::*; -pub use ledger_closed::*; -pub use ledger_current::*; -pub use ledger_data::*; -pub use ledger_entry::*; -pub use manifest::*; -pub use nft_buy_offers::*; -pub use nft_sell_offers::*; -pub use no_ripple_check::*; -pub use path_find::*; -pub use ping::*; -pub use random::*; -pub use ripple_path_find::*; -use serde_with::skip_serializing_none; -pub use server_info::*; -pub use server_state::*; -pub use submit::*; -pub use submit_multisigned::*; -pub use subscribe::*; -pub use transaction_entry::*; -pub use tx::*; -pub use unsubscribe::*; - use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use strum_macros::Display; /// Represents the different options for the `method` @@ -141,249 +104,249 @@ pub enum RequestMethod { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(untagged)] pub enum XRPLRequest<'a> { - AccountChannels(AccountChannels<'a>), - AccountCurrencies(AccountCurrencies<'a>), - AccountInfo(AccountInfo<'a>), - AccountLines(AccountLines<'a>), - AccountNfts(AccountNfts<'a>), - AccountObjects(AccountObjects<'a>), - AccountOffers(AccountOffers<'a>), - AccountTx(AccountTx<'a>), - GatewayBalances(GatewayBalances<'a>), - NoRippleCheck(NoRippleCheck<'a>), - Submit(Submit<'a>), - SubmitMultisigned(SubmitMultisigned<'a>), - TransactionEntry(TransactionEntry<'a>), - Tx(Tx<'a>), - ChannelAuthorize(ChannelAuthorize<'a>), - ChannelVerify(ChannelVerify<'a>), - BookOffers(BookOffers<'a>), - DepositAuthorized(DepositAuthorized<'a>), - NftBuyOffers(NftBuyOffers<'a>), - NftSellOffers(NftSellOffers<'a>), - PathFind(PathFind<'a>), - RipplePathFind(RipplePathFind<'a>), - Ledger(Ledger<'a>), - LedgerClosed(LedgerClosed<'a>), - LedgerCurrent(LedgerCurrent<'a>), - LedgerData(LedgerData<'a>), - LedgerEntry(LedgerEntry<'a>), - Subscribe(Subscribe<'a>), - Unsubscribe(Unsubscribe<'a>), - Fee(Fee<'a>), - Manifest(Manifest<'a>), - ServerInfo(ServerInfo<'a>), - ServerState(ServerState<'a>), - Ping(Ping<'a>), - Random(Random<'a>), -} - -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountChannels<'a>) -> Self { + AccountChannels(account_channels::AccountChannels<'a>), + AccountCurrencies(account_currencies::AccountCurrencies<'a>), + AccountInfo(account_info::AccountInfo<'a>), + AccountLines(account_lines::AccountLines<'a>), + AccountNfts(account_nfts::AccountNfts<'a>), + AccountObjects(account_objects::AccountObjects<'a>), + AccountOffers(account_offers::AccountOffers<'a>), + AccountTx(account_tx::AccountTx<'a>), + GatewayBalances(gateway_balances::GatewayBalances<'a>), + NoRippleCheck(no_ripple_check::NoRippleCheck<'a>), + Submit(submit::Submit<'a>), + SubmitMultisigned(submit_multisigned::SubmitMultisigned<'a>), + TransactionEntry(transaction_entry::TransactionEntry<'a>), + Tx(tx::Tx<'a>), + ChannelAuthorize(channel_authorize::ChannelAuthorize<'a>), + ChannelVerify(channel_verify::ChannelVerify<'a>), + BookOffers(book_offers::BookOffers<'a>), + DepositAuthorized(deposit_authorize::DepositAuthorized<'a>), + NftBuyOffers(nft_buy_offers::NftBuyOffers<'a>), + NftSellOffers(nft_sell_offers::NftSellOffers<'a>), + PathFind(path_find::PathFind<'a>), + RipplePathFind(ripple_path_find::RipplePathFind<'a>), + Ledger(ledger::Ledger<'a>), + LedgerClosed(ledger_closed::LedgerClosed<'a>), + LedgerCurrent(ledger_current::LedgerCurrent<'a>), + LedgerData(ledger_data::LedgerData<'a>), + LedgerEntry(ledger_entry::LedgerEntry<'a>), + Subscribe(subscribe::Subscribe<'a>), + Unsubscribe(unsubscribe::Unsubscribe<'a>), + Fee(fee::Fee<'a>), + Manifest(manifest::Manifest<'a>), + ServerInfo(server_info::ServerInfo<'a>), + ServerState(server_state::ServerState<'a>), + Ping(ping::Ping<'a>), + Random(random::Random<'a>), +} + +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_channels::AccountChannels<'a>) -> Self { XRPLRequest::AccountChannels(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountCurrencies<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_currencies::AccountCurrencies<'a>) -> Self { XRPLRequest::AccountCurrencies(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountInfo<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_info::AccountInfo<'a>) -> Self { XRPLRequest::AccountInfo(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountLines<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_lines::AccountLines<'a>) -> Self { XRPLRequest::AccountLines(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountNfts<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_nfts::AccountNfts<'a>) -> Self { XRPLRequest::AccountNfts(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountObjects<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_objects::AccountObjects<'a>) -> Self { XRPLRequest::AccountObjects(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountOffers<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_offers::AccountOffers<'a>) -> Self { XRPLRequest::AccountOffers(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: AccountTx<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: account_tx::AccountTx<'a>) -> Self { XRPLRequest::AccountTx(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: GatewayBalances<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: gateway_balances::GatewayBalances<'a>) -> Self { XRPLRequest::GatewayBalances(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: NoRippleCheck<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: no_ripple_check::NoRippleCheck<'a>) -> Self { XRPLRequest::NoRippleCheck(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Submit<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: submit::Submit<'a>) -> Self { XRPLRequest::Submit(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: SubmitMultisigned<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: submit_multisigned::SubmitMultisigned<'a>) -> Self { XRPLRequest::SubmitMultisigned(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: TransactionEntry<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: transaction_entry::TransactionEntry<'a>) -> Self { XRPLRequest::TransactionEntry(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Tx<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: tx::Tx<'a>) -> Self { XRPLRequest::Tx(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: ChannelAuthorize<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: channel_authorize::ChannelAuthorize<'a>) -> Self { XRPLRequest::ChannelAuthorize(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: ChannelVerify<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: channel_verify::ChannelVerify<'a>) -> Self { XRPLRequest::ChannelVerify(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: BookOffers<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: book_offers::BookOffers<'a>) -> Self { XRPLRequest::BookOffers(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: DepositAuthorized<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: deposit_authorize::DepositAuthorized<'a>) -> Self { XRPLRequest::DepositAuthorized(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: NftBuyOffers<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: nft_buy_offers::NftBuyOffers<'a>) -> Self { XRPLRequest::NftBuyOffers(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: NftSellOffers<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: nft_sell_offers::NftSellOffers<'a>) -> Self { XRPLRequest::NftSellOffers(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: PathFind<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: path_find::PathFind<'a>) -> Self { XRPLRequest::PathFind(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: RipplePathFind<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ripple_path_find::RipplePathFind<'a>) -> Self { XRPLRequest::RipplePathFind(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Ledger<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ledger::Ledger<'a>) -> Self { XRPLRequest::Ledger(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: LedgerClosed<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ledger_closed::LedgerClosed<'a>) -> Self { XRPLRequest::LedgerClosed(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: LedgerCurrent<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ledger_current::LedgerCurrent<'a>) -> Self { XRPLRequest::LedgerCurrent(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: LedgerData<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ledger_data::LedgerData<'a>) -> Self { XRPLRequest::LedgerData(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: LedgerEntry<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ledger_entry::LedgerEntry<'a>) -> Self { XRPLRequest::LedgerEntry(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Subscribe<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: subscribe::Subscribe<'a>) -> Self { XRPLRequest::Subscribe(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Unsubscribe<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: unsubscribe::Unsubscribe<'a>) -> Self { XRPLRequest::Unsubscribe(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Fee<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: fee::Fee<'a>) -> Self { XRPLRequest::Fee(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Manifest<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: manifest::Manifest<'a>) -> Self { XRPLRequest::Manifest(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: ServerInfo<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: server_info::ServerInfo<'a>) -> Self { XRPLRequest::ServerInfo(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: ServerState<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: server_state::ServerState<'a>) -> Self { XRPLRequest::ServerState(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Ping<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: ping::Ping<'a>) -> Self { XRPLRequest::Ping(request) } } -impl<'a> From> for XRPLRequest<'a> { - fn from(request: Random<'a>) -> Self { +impl<'a> From> for XRPLRequest<'a> { + fn from(request: random::Random<'a>) -> Self { XRPLRequest::Random(request) } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index b056b688..8ea56076 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -7,7 +7,7 @@ use serde_with::skip_serializing_none; use crate::models::{ currency::Currency, default_false, - requests::{RequestMethod, StreamParameter}, + requests::{subscribe::StreamParameter, RequestMethod}, Model, }; diff --git a/src/models/response.rs b/src/models/response.rs deleted file mode 100644 index a6ef23c5..00000000 --- a/src/models/response.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: Add `Response` model diff --git a/src/models/results/account_info.rs b/src/models/results/account_info.rs index 0a33d78f..4372e084 100644 --- a/src/models/results/account_info.rs +++ b/src/models/results/account_info.rs @@ -4,7 +4,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use crate::{ - models::{ledger::AccountRoot, results::exceptions::XRPLResultException}, + models::{ledger::objects::AccountRoot, results::exceptions::XRPLResultException}, Err, }; diff --git a/src/models/results/ledger.rs b/src/models/results/ledger.rs index 0cf0dd69..b81456db 100644 --- a/src/models/results/ledger.rs +++ b/src/models/results/ledger.rs @@ -41,7 +41,7 @@ impl<'a> TryFrom> for Ledger<'a> { match result { XRPLResult::Ledger(ledger) => Ok(ledger), res => Err!(XRPLResultException::UnexpectedResultType( - "Ledger".to_string(), + "ledger-models".to_string(), res.get_name() )), } diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 678f9547..434e7463 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,5 +1,14 @@ -use core::convert::{TryFrom, TryInto}; +pub mod account_info; +pub mod account_tx; +pub mod exceptions; +pub mod fee; +pub mod ledger; +pub mod server_state; +pub mod submit; +pub mod tx; +use super::requests::XRPLRequest; +use crate::Err; use alloc::{ borrow::{Cow, ToOwned}, format, @@ -7,31 +16,11 @@ use alloc::{ vec::Vec, }; use anyhow::Result; +use core::convert::{TryFrom, TryInto}; use exceptions::XRPLResultException; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{value::Index, Map, Value}; -pub mod account_info; -pub mod account_tx; -pub mod exceptions; -pub mod fee; -pub mod ledger; -pub mod server_state; -pub mod submit; -pub mod tx; - -pub use account_info::*; -pub use account_tx::*; -pub use fee::*; -pub use ledger::*; -pub use server_state::*; -pub use submit::*; -pub use tx::*; - -use crate::Err; - -use super::requests::XRPLRequest; - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum XRPLOptionalResult { Result(T), @@ -131,54 +120,54 @@ impl XRPLOtherResult { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { - AccountInfo(AccountInfo<'a>), - AccountTx(AccountTx<'a>), - Fee(Fee<'a>), - Ledger(Ledger<'a>), - ServerState(ServerState<'a>), - Submit(Submit<'a>), - Tx(Tx<'a>), + AccountInfo(account_info::AccountInfo<'a>), + AccountTx(account_tx::AccountTx<'a>), + Fee(fee::Fee<'a>), + Ledger(ledger::Ledger<'a>), + ServerState(server_state::ServerState<'a>), + Submit(submit::Submit<'a>), + Tx(tx::Tx<'a>), Other(XRPLOtherResult), } -impl<'a> From> for XRPLResult<'a> { - fn from(account_info: AccountInfo<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(account_info: account_info::AccountInfo<'a>) -> Self { XRPLResult::AccountInfo(account_info) } } -impl<'a> From> for XRPLResult<'a> { - fn from(account_tx: AccountTx<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(account_tx: account_tx::AccountTx<'a>) -> Self { XRPLResult::AccountTx(account_tx) } } -impl<'a> From> for XRPLResult<'a> { - fn from(fee: Fee<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(fee: fee::Fee<'a>) -> Self { XRPLResult::Fee(fee) } } -impl<'a> From> for XRPLResult<'a> { - fn from(ledger: Ledger<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(ledger: ledger::Ledger<'a>) -> Self { XRPLResult::Ledger(ledger) } } -impl<'a> From> for XRPLResult<'a> { - fn from(server_state: ServerState<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(server_state: server_state::ServerState<'a>) -> Self { XRPLResult::ServerState(server_state) } } -impl<'a> From> for XRPLResult<'a> { - fn from(submit: Submit<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(submit: submit::Submit<'a>) -> Self { XRPLResult::Submit(submit) } } -impl<'a> From> for XRPLResult<'a> { - fn from(tx: Tx<'a>) -> Self { +impl<'a> From> for XRPLResult<'a> { + fn from(tx: tx::Tx<'a>) -> Self { XRPLResult::Tx(tx) } } @@ -215,7 +204,7 @@ impl XRPLResult<'_> { XRPLResult::AccountInfo(_) => "AccountInfo".to_string(), XRPLResult::AccountTx(_) => "AccountTx".to_string(), XRPLResult::Fee(_) => "Fee".to_string(), - XRPLResult::Ledger(_) => "Ledger".to_string(), + XRPLResult::Ledger(_) => "ledger-models".to_string(), XRPLResult::ServerState(_) => "ServerState".to_string(), XRPLResult::Submit(_) => "Submit".to_string(), XRPLResult::Tx(_) => "Tx".to_string(), @@ -339,7 +328,21 @@ impl TryInto for XRPLResponse<'_> { impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { - self.status == Some(ResponseStatus::Success) + if let Some(status) = &self.status { + status == &ResponseStatus::Success + } else { + if let Some(result) = &self.result { + match serde_json::to_value(result) { + Ok(value) => match value.get("status") { + Some(Value::String(status)) => status == "success", + _ => false, + }, + _ => false, + } + } else { + false + } + } } pub fn try_into_opt_result(self) -> Result> diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 53036f76..3c70ae2b 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -7,7 +7,7 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 2984bc77..b8560c92 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -7,15 +7,15 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; -use crate::models::transactions::{CommonFields, XRPLAccountSetException}; +use crate::models::transactions::{exceptions::XRPLAccountSetException, CommonFields}; use crate::{ constants::{ DISABLE_TICK_SIZE, MAX_DOMAIN_LENGTH, MAX_TICK_SIZE, MAX_TRANSFER_RATE, MIN_TICK_SIZE, MIN_TRANSFER_RATE, SPECIAL_CASE_TRANFER_RATE, }, models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }, Err, }; diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index adfc0603..07354092 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -7,7 +7,7 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index eb7c37fe..dd73dedc 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{CommonFields, XRPLCheckCashException}; +use crate::models::transactions::{exceptions::XRPLCheckCashException, CommonFields}; use crate::models::NoFlags; use crate::models::{ amount::Amount, - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; /// Cancels an unredeemed Check, removing it from the ledger without diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 1b3dc2ed..171139a0 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -9,8 +9,8 @@ use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ amount::Amount, - model::Model, transactions::{Transaction, TransactionType}, + Model, }; use super::{Memo, Signer}; diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 0a2c808e..ad19e4eb 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -6,11 +6,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{CommonFields, XRPLDepositPreauthException}; +use crate::models::transactions::{exceptions::XRPLDepositPreauthException, CommonFields}; use crate::models::NoFlags; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; /// A DepositPreauth transaction gives another account pre-approval diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 149e93de..7c7e39c6 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -8,7 +8,7 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 3e206002..134c8659 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -6,11 +6,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::{CommonFields, XRPLEscrowCreateException}; +use crate::models::transactions::{exceptions::XRPLEscrowCreateException, CommonFields}; use crate::models::NoFlags; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; /// Creates an Escrow, which requests XRP until the escrow process either finishes or is canceled. diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index e3355f94..eada6dd2 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -5,12 +5,12 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::transactions::XRPLEscrowFinishException; +use crate::models::transactions::exceptions::XRPLEscrowFinishException; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/exceptions.rs b/src/models/transactions/exceptions.rs index c36bc9b6..c13198fb 100644 --- a/src/models/transactions/exceptions.rs +++ b/src/models/transactions/exceptions.rs @@ -1,4 +1,4 @@ -use crate::models::transactions::{AccountSetFlag, PaymentFlag}; +use crate::models::transactions::{account_set::AccountSetFlag, payment::PaymentFlag}; use alloc::borrow::Cow; use core::fmt::Debug; use strum_macros::Display; diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index a6b109b8..bb00ea68 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -25,56 +25,26 @@ 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::*; -use alloc::format; -pub use check_cancel::*; -pub use check_cash::*; -pub use check_create::*; -pub use deposit_preauth::*; -pub use escrow_cancel::*; -pub use escrow_create::*; -pub use escrow_finish::*; -pub use exceptions::*; -pub use nftoken_accept_offer::*; -pub use nftoken_burn::*; -pub use nftoken_cancel_offer::*; -pub use nftoken_create_offer::*; -pub use nftoken_mint::*; -pub use offer_cancel::*; -pub use offer_create::*; -pub use payment::*; -pub use payment_channel_claim::*; -pub use payment_channel_create::*; -pub use payment_channel_fund::*; -pub use pseudo_transactions::*; - -use serde::de::DeserializeOwned; -pub use set_regular_key::*; -use sha2::{Digest, Sha512}; -pub use signer_list_set::*; -pub use ticket_create::*; -pub use trust_set::*; - +use super::FlagCollection; use crate::core::binarycodec::encode; use crate::models::amount::XRPAmount; use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; +use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; use anyhow::Result; +use core::fmt::Debug; use derive_new::new; +use serde::de::DeserializeOwned; use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use sha2::{Digest, Sha512}; use strum::IntoEnumIterator; use strum_macros::{AsRefStr, Display}; -use super::FlagCollection; - const TRANSACTION_HASH_PREFIX: u32 = 0x54584E00; /// Enum containing the different Transaction types. @@ -373,22 +343,21 @@ where #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] pub enum Flag { - AccountSet(AccountSetFlag), - NFTokenCreateOffer(NFTokenCreateOfferFlag), - NFTokenMint(NFTokenMintFlag), - OfferCreate(OfferCreateFlag), - Payment(PaymentFlag), - PaymentChannelClaim(PaymentChannelClaimFlag), - TrustSet(TrustSetFlag), - EnableAmendment(EnableAmendmentFlag), + AccountSet(account_set::AccountSetFlag), + NFTokenCreateOffer(nftoken_create_offer::NFTokenCreateOfferFlag), + NFTokenMint(nftoken_mint::NFTokenMintFlag), + OfferCreate(offer_create::OfferCreateFlag), + Payment(payment::PaymentFlag), + PaymentChannelClaim(payment_channel_claim::PaymentChannelClaimFlag), + TrustSet(trust_set::TrustSetFlag), + EnableAmendment(pseudo_transactions::enable_amendment::EnableAmendmentFlag), } #[cfg(all( - feature = "websocket-std", - not(feature = "websocket"), - feature = "transactions", + feature = "std", + feature = "websocket", + feature = "transaction-models", feature = "transaction-helpers", - feature = "amounts", feature = "wallet" ))] #[cfg(test)] @@ -396,7 +365,7 @@ mod test_tx_common_fields { use super::*; use crate::{ asynch::transaction::sign, - models::{amount::IssuedCurrencyAmount, transactions::OfferCreate}, + models::{amount::IssuedCurrencyAmount, transactions::offer_create::OfferCreate}, wallet::Wallet, }; diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 103bc94a..4a26e8de 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -8,12 +8,12 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLNFTokenAcceptOfferException; +use crate::models::transactions::exceptions::XRPLNFTokenAcceptOfferException; use crate::models::NoFlags; use crate::models::{ amount::Amount, - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 6d260dbb..4764cb54 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -7,7 +7,7 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index f2d13729..a0043370 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -6,11 +6,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLNFTokenCancelOfferException; +use crate::models::transactions::exceptions::XRPLNFTokenCancelOfferException; use crate::models::NoFlags; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 5922a12a..e8858896 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -9,12 +9,12 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use crate::models::amount::{Amount, XRPAmount}; -use crate::models::transactions::XRPLNFTokenCreateOfferException; +use crate::models::transactions::exceptions::XRPLNFTokenCreateOfferException; use crate::Err; use super::{CommonFields, FlagCollection}; diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index 1477d014..6c803ab1 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -9,14 +9,14 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::{ constants::{MAX_TRANSFER_FEE, MAX_URI_LENGTH}, models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }, Err, }; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLNFTokenMintException; +use crate::models::transactions::exceptions::XRPLNFTokenMintException; use super::{CommonFields, FlagCollection}; diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 7834ad68..8c7775d8 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -7,7 +7,7 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 2e71f511..c4111f8f 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -8,7 +8,7 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index e4dab26c..6cd0dba4 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -8,13 +8,12 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, - PathStep, + Model, PathStep, }; use crate::models::amount::XRPAmount; -use crate::models::transactions::XRPLPaymentException; +use crate::models::transactions::exceptions::XRPLPaymentException; use crate::Err; use super::{CommonFields, FlagCollection}; diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 68d55bc6..229c0511 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use crate::models::amount::XRPAmount; diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 68950884..8268fffa 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -7,7 +7,7 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 85b2528e..c1058ad8 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -7,7 +7,7 @@ use serde_with::skip_serializing_none; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 9d7db323..4de63555 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -9,7 +9,7 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; use crate::models::transactions::{CommonFields, FlagCollection, Memo, Signer}; use crate::models::{ - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index f861c39b..e9f10f29 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -8,7 +8,7 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::{CommonFields, Memo, Signer}; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 259b8979..a8a50f06 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -10,7 +10,7 @@ use crate::models::transactions::{CommonFields, Memo, Signer}; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - model::Model, + Model, transactions::{Transaction, TransactionType}, }; diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index f0bec596..f3ee7d06 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 3b34833a..4538fbaa 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -7,12 +7,12 @@ use derive_new::new; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::transactions::XRPLSignerListSetException; +use crate::models::transactions::exceptions::XRPLSignerListSetException; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use crate::{serde_with_tag, Err}; diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 3ae9bddb..304bbd2d 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -6,7 +6,7 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - model::Model, + Model, transactions::{Memo, Signer, Transaction, TransactionType}, }; diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 31397e7d..bd8287f0 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ - model::Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use crate::models::amount::{IssuedCurrencyAmount, XRPAmount}; diff --git a/src/models/utils.rs b/src/models/utils.rs deleted file mode 100644 index cf1c177c..00000000 --- a/src/models/utils.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Helper util functions for the models module. -use crate::models::exceptions::JSONRPCException; -use alloc::string::String; -use serde::{Deserialize, Serialize}; - -/// JSONRPC Request -#[derive(Debug, Clone, Serialize)] -pub struct Request { - pub method: String, - pub params: Option, - pub id: serde_json::Value, - pub jsonrpc: Option, -} - -/// JSONRPC Response -#[derive(Debug, Clone, Deserialize)] -pub struct Response { - pub id: serde_json::Value, - pub result: Option, - pub error: Option, - pub jsonrpc: Option, -} diff --git a/src/transaction/multisign.rs b/src/transaction/multisign.rs index 8159bdb1..d2f5eda2 100644 --- a/src/transaction/multisign.rs +++ b/src/transaction/multisign.rs @@ -40,7 +40,7 @@ mod test { use super::*; use crate::asynch::transaction::sign; - use crate::models::transactions::AccountSet; + use crate::models::transactions::account_set::AccountSet; use crate::wallet::Wallet; #[tokio::test] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7f06707e..953b6e98 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,6 +2,7 @@ pub mod exceptions; pub mod time_conversion; +#[cfg(feature = "transaction-models")] pub(crate) mod transactions; pub mod xrpl_conversion; @@ -9,10 +10,7 @@ pub use self::time_conversion::*; pub use self::xrpl_conversion::*; use crate::constants::*; -use alloc::string::String; -use alloc::string::ToString; use alloc::vec::Vec; -use rand::Rng; use regex::Regex; /// Determine if the address string is a hex address. @@ -69,12 +67,6 @@ pub fn is_iso_hex(value: &str) -> bool { regex.is_match(value) } -/// Generate a random id. -pub fn get_random_id(rng: &mut T) -> String { - let id: u32 = rng.gen(); - id.to_string() -} - /// Converter to byte array with endianness. pub trait ToBytes { /// Return the byte array of self. diff --git a/src/utils/transactions.rs b/src/utils/transactions.rs index 739954f5..c932b913 100644 --- a/src/utils/transactions.rs +++ b/src/utils/transactions.rs @@ -5,7 +5,7 @@ use serde::{de::DeserializeOwned, Serialize}; use strum::IntoEnumIterator; use crate::{ - models::transactions::{Transaction, XRPLTransactionFieldException}, + models::transactions::{exceptions::XRPLTransactionFieldException, Transaction}, Err, }; diff --git a/src/wallet/wallet_generation.rs b/src/wallet/wallet_generation.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d3fe32d6..6c55fb26 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,63 +1,35 @@ -mod constants; +use anyhow::Result; +#[cfg(all(feature = "websocket", not(feature = "std")))] +use embedded_io_adapters::tokio_1::FromTokio; +#[cfg(all(feature = "websocket", not(feature = "std")))] +use rand::rngs::OsRng; +#[cfg(all(feature = "websocket", not(feature = "std")))] +use tokio::net::TcpStream; +use url::Url; +#[cfg(feature = "websocket")] +use xrpl::asynch::clients::{AsyncWebSocketClient, SingleExecutorMutex, WebSocketOpen}; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] -mod tungstenite_clients { - use super::constants::*; - use anyhow::anyhow; - use anyhow::Result; - use xrpl::asynch::clients::AsyncWebsocketClient; - use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; +#[cfg(all(feature = "websocket", not(feature = "std")))] +pub async fn open_websocket( + uri: Url, +) -> Result< + AsyncWebSocketClient<4096, FromTokio, OsRng, SingleExecutorMutex, WebSocketOpen>, +> { + let port = uri.port().unwrap_or(80); + let url = format!("{}:{}", uri.host_str().unwrap(), port); - pub async fn connect_to_wss_tungstinite_test_net<'a>( - ) -> Result> { - match AsyncWebsocketClient::open(XRPL_WSS_TEST_NET.parse().unwrap()).await { - Ok(websocket) => { - assert!(websocket.is_open()); - Ok(websocket) - } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - } + let tcp = TcpStream::connect(&url).await.unwrap(); + let stream = FromTokio::new(tcp); + let rng = OsRng; + match AsyncWebSocketClient::open(stream, uri, rng, None, None).await { + Ok(client) => Ok(client), + Err(e) => Err(e), } } -#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] -mod embedded_ws_clients { - use super::constants::*; - use anyhow::anyhow; - use anyhow::Result; - use std::io; - use tokio::net::TcpStream; - use tokio_util::codec::Framed; - use xrpl::asynch::clients::{ - codec::Codec, AsyncWebsocketClient, SingleExecutorMutex, WebsocketOpen, - }; - - pub async fn connect_to_ws_embedded_websocket_tokio_echo( - stream: Framed, - ) -> Result< - AsyncWebsocketClient< - 4096, - Framed, - Vec, - io::Error, - rand_core::OsRng, - SingleExecutorMutex, - WebsocketOpen, - >, - > { - let rng = rand_core::OsRng; - let url = ECHO_WS_SERVER.parse().unwrap(); - match AsyncWebsocketClient::open(rng, stream, url).await { - Ok(websocket) => { - // assert!(websocket.is_open()); - Ok(websocket) - } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - } - } +#[cfg(all(feature = "websocket", feature = "std"))] +pub async fn open_websocket( + uri: Url, +) -> Result> { + AsyncWebSocketClient::open(uri).await } - -#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] -pub use embedded_ws_clients::*; -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] -pub use tungstenite_clients::*; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e4b68521..e69de29b 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,78 +0,0 @@ -use anyhow::Result; - -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] -pub async fn test_websocket_tungstenite_test_net() -> Result<()> { - use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::Fee}; - - let mut websocket = connect_to_wss_tungstinite_test_net().await?; - let fee = Fee::new(None); - - websocket.xrpl_send(fee.into()).await.unwrap(); - let message = websocket.xrpl_receive().await.unwrap(); - assert!(message.unwrap().result.is_some()); - Ok(()) -} - -#[cfg(all(feature = "websocket-std", not(feature = "websocket")))] -pub async fn test_websocket_tungstenite_request() -> Result<()> { - use crate::common::connect_to_wss_tungstinite_test_net; - use xrpl::{asynch::clients::AsyncClient, models::requests::Fee}; - - let websocket = connect_to_wss_tungstinite_test_net().await?; - let fee = Fee::new(None); - - let message = websocket.request(fee.into()).await.unwrap(); - assert!(message.result.is_some()); - Ok(()) -} - -#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] -pub async fn test_embedded_websocket_echo() -> Result<()> { - use crate::common::connect_to_ws_embedded_websocket_tokio_echo; - use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; - use xrpl::asynch::clients::XRPLWebsocketIO; - use xrpl::models::requests::Fee; - - let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") - .await - .unwrap(); - let framed = Framed::new(tcp_stream, Codec); - let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let fee = Fee::new(None); - websocket.xrpl_send(fee.into()).await?; - let _ = websocket.xrpl_receive().await.unwrap(); - Ok(()) -} - -#[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] -pub async fn test_embedded_websocket_request() -> Result<()> { - use crate::common::connect_to_ws_embedded_websocket_tokio_echo; - use tokio_util::codec::Framed; - use xrpl::asynch::clients::codec::Codec; - use xrpl::asynch::clients::AsyncClient; - use xrpl::models::requests::Fee; - - let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") - .await - .unwrap(); - let framed = Framed::new(tcp_stream, Codec); - let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let fee = Fee::new(None); - let _res = websocket.request(fee.into()).await?; - Ok(()) -} - -#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] -pub async fn test_json_rpc_std() -> Result<()> { - use xrpl::{ - asynch::clients::{AsyncClient, AsyncJsonRpcClient, SingleExecutorMutex}, - models::requests::Fee, - }; - let client: AsyncJsonRpcClient = - AsyncJsonRpcClient::connect("https://s1.ripple.com:51234/".parse().unwrap()); - let fee_result = client.request(Fee::new(None).into()).await.unwrap(); - assert!(fee_result.result.is_some()); - Ok(()) -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c243b8c0..8732ebce 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -2,36 +2,3 @@ mod common; mod integration; - -use anyhow::Result; - -#[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")))] - return integration::clients::test_embedded_websocket_echo().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[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")))] - return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] - return integration::clients::test_embedded_websocket_request().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] -#[tokio::test] -async fn test_asynch_clients_json_rpc() -> Result<()> { - #[cfg(all(feature = "json-rpc-std", not(feature = "json-rpc")))] - return integration::clients::test_json_rpc_std().await; - #[allow(unreachable_code)] - Ok(()) -} diff --git a/tests/test_utils.rs b/tests/test_utils.rs index eca718e9..51f1d6ba 100644 --- a/tests/test_utils.rs +++ b/tests/test_utils.rs @@ -1,11 +1,14 @@ +#[cfg(feature = "utils")] use xrpl::utils::{posix_to_ripple_time, ripple_time_to_posix}; #[test] +#[cfg(feature = "utils")] fn it_converts_posix_to_ripple_time() { assert_eq!(posix_to_ripple_time(1660187459), Ok(713502659_i64)); } #[test] +#[cfg(feature = "utils")] fn it_converts_ripple_time_to_posix() { assert_eq!(ripple_time_to_posix(713502659), Ok(1660187459)); } From 8ec4ef4aaa8f4d578b907d7bb2b55426c7e91665 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 8 Sep 2024 12:00:44 +0000 Subject: [PATCH 097/113] cargo fmt --- src/models/transactions/account_delete.rs | 2 +- src/models/transactions/check_cancel.rs | 2 +- src/models/transactions/escrow_cancel.rs | 2 +- src/models/transactions/nftoken_burn.rs | 2 +- src/models/transactions/offer_cancel.rs | 2 +- src/models/transactions/offer_create.rs | 2 +- src/models/transactions/payment_channel_create.rs | 2 +- src/models/transactions/payment_channel_fund.rs | 2 +- src/models/transactions/pseudo_transactions/enable_amendment.rs | 2 +- src/models/transactions/pseudo_transactions/set_fee.rs | 2 +- src/models/transactions/pseudo_transactions/unl_modify.rs | 2 +- src/models/transactions/ticket_create.rs | 2 +- tests/integration/clients/mod.rs | 1 + 13 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 3c70ae2b..4fbae6cc 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -7,8 +7,8 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Transaction, TransactionType}, + Model, }; use super::{Memo, Signer}; diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 07354092..ae7026d1 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -7,8 +7,8 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Transaction, TransactionType}, + Model, }; use super::{Memo, Signer}; diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 7c7e39c6..696084ff 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -8,8 +8,8 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Transaction, TransactionType}, + Model, }; use super::{Memo, Signer}; diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index 4764cb54..dc3458af 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 8c7775d8..8416573a 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index c4111f8f..fb973671 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -8,8 +8,8 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::{ amount::Amount, - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use crate::models::amount::XRPAmount; diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 8268fffa..43ce7eea 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index c1058ad8..d46aab24 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -7,8 +7,8 @@ use serde_with::skip_serializing_none; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 4de63555..bb309c97 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -9,8 +9,8 @@ use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::amount::XRPAmount; use crate::models::transactions::{CommonFields, FlagCollection, Memo, Signer}; use crate::models::{ - Model, transactions::{Transaction, TransactionType}, + Model, }; #[derive( diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index e9f10f29..c573fa7f 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -8,8 +8,8 @@ use crate::models::amount::XRPAmount; use crate::models::transactions::{CommonFields, Memo, Signer}; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Transaction, TransactionType}, + Model, }; /// See SetFee: diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index a8a50f06..e3c081a5 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -10,8 +10,8 @@ use crate::models::transactions::{CommonFields, Memo, Signer}; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, - Model, transactions::{Transaction, TransactionType}, + Model, }; #[derive( diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 304bbd2d..1dc6e9a8 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -6,8 +6,8 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::NoFlags; use crate::models::{ - Model, transactions::{Memo, Signer, Transaction, TransactionType}, + Model, }; use super::CommonFields; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e69de29b..8b137891 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -0,0 +1 @@ + From d7829e0f5052351d89d9814916221d0b8a9adc42 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 8 Sep 2024 12:31:26 +0000 Subject: [PATCH 098/113] clippy fix --- src/asynch/clients/client.rs | 2 +- src/asynch/clients/json_rpc/mod.rs | 2 +- src/asynch/transaction/mod.rs | 14 ++++++------- src/asynch/transaction/submit_and_wait.rs | 4 ++-- src/asynch/wallet/mod.rs | 8 ++++---- src/core/binarycodec/mod.rs | 4 ++-- src/core/keypairs/algorithms.rs | 2 +- src/core/types/mod.rs | 10 +++++----- src/models/exceptions.rs | 2 +- src/models/mod.rs | 2 ++ src/models/results/mod.rs | 24 +++++++++++------------ src/transaction/multisign.rs | 2 +- src/wallet/mod.rs | 20 +++++++++---------- 13 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 5ccde6f6..060fb22d 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -22,7 +22,7 @@ pub trait Client { fn get_host(&self) -> Url; - fn set_request_id(&self, request: &mut XRPLRequest<'_>) -> () { + fn set_request_id(&self, request: &mut XRPLRequest<'_>) { let common_fields = request.get_common_fields_mut(); common_fields.id = match &common_fields.id { Some(id) => Some(id.to_owned()), diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index b403d35c..db0cb844 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -87,7 +87,7 @@ mod _std { let client = HttpClient::new(); let request_json_rpc = serde_json::to_value(&request).unwrap(); let response = client - .post(&faucet_url.to_string()) + .post(faucet_url.to_string()) .json(&request_json_rpc) .send() .await; diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index f6b28519..374cd4e5 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -175,7 +175,7 @@ where let res = client.request(req.into()).await?; match res.try_into_result::>() { Ok(value) => { - let submit_result = SubmitResult::from(value); + let submit_result = value; Ok(submit_result) } Err(e) => Err!(e), @@ -444,18 +444,18 @@ where Ok(t) => t, Err(error) => return Err!(error), }; - validate_transaction_has_field(prepared_transaction, &account_field_name)?; - set_transaction_field_value(prepared_transaction, &account_field_name, address)?; + validate_transaction_has_field(prepared_transaction, account_field_name)?; + set_transaction_field_value(prepared_transaction, account_field_name, address)?; - if validate_transaction_has_field(prepared_transaction, &tag_field_name).is_ok() - && get_transaction_field_value(prepared_transaction, &tag_field_name).unwrap_or(Some(0)) + if validate_transaction_has_field(prepared_transaction, tag_field_name).is_ok() + && get_transaction_field_value(prepared_transaction, tag_field_name).unwrap_or(Some(0)) != tag { Err!(XRPLSignTransactionException::TagFieldMismatch( - &tag_field_name + tag_field_name )) } else { - set_transaction_field_value(prepared_transaction, &tag_field_name, tag)?; + set_transaction_field_value(prepared_transaction, tag_field_name, tag)?; Ok(()) } diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 4f260239..210dfefd 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -129,9 +129,9 @@ where } } } - return Err!(XRPLSubmitAndWaitException::SubmissionFailed( + Err!(XRPLSubmitAndWaitException::SubmissionFailed( "Transaction not included in ledger".into() - )); + )) } async fn get_signed_transaction<'a, T, F, C>( diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 5a9e54c3..13b27168 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -17,8 +17,8 @@ use super::{ clients::{Client, XRPLFaucet}, }; -const TEST_FAUCET_URL: &'static str = "https://faucet.altnet.rippletest.net/accounts"; -const DEV_FAUCET_URL: &'static str = "https://faucet.devnet.rippletest.net/accounts"; +const TEST_FAUCET_URL: &str = "https://faucet.altnet.rippletest.net/accounts"; +const DEV_FAUCET_URL: &str = "https://faucet.devnet.rippletest.net/accounts"; const TIMEOUT_SECS: u8 = 40; @@ -91,8 +91,8 @@ where let host_str = host.host_str().unwrap(); if host_str.contains("altnet") || host_str.contains("testnet") { match Url::parse(TEST_FAUCET_URL) { - Ok(url) => return Ok(url), - Err(error) => return Err!(error), + Ok(url) => Ok(url), + Err(error) => Err!(error), } } else if host_str.contains("devnet") { match Url::parse(DEV_FAUCET_URL) { diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index e619421b..47d1cf86 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -39,9 +39,9 @@ where ) } -pub fn encode_for_multisigning<'a, T>( +pub fn encode_for_multisigning( prepared_transaction: &T, - signing_account: Cow<'a, str>, + signing_account: Cow<'_, str>, ) -> Result where T: Serialize + DeserializeOwned + Clone + Debug, diff --git a/src/core/keypairs/algorithms.rs b/src/core/keypairs/algorithms.rs index 72284424..0dc47a03 100644 --- a/src/core/keypairs/algorithms.rs +++ b/src/core/keypairs/algorithms.rs @@ -414,7 +414,7 @@ impl CryptoImplementation for Ed25519 { .as_slice() .try_into() .map_err(|_| XRPLKeypairsException::InvalidSecret)?; - let private: ed25519_dalek::SecretKey = (*raw_private_slice).into(); + let private: ed25519_dalek::SecretKey = *raw_private_slice; let mut signing_key: ed25519_dalek::SigningKey = private.into(); let signature = signing_key.sign(message); diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index dac7bbb8..a454dc99 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -101,7 +101,7 @@ impl XRPLTypes { "UInt8" => Ok(XRPLTypes::UInt8(value as u8)), "UInt16" => Ok(XRPLTypes::UInt16(value as u16)), "UInt32" => Ok(XRPLTypes::UInt32(value as u32)), - "UInt64" => Ok(XRPLTypes::UInt64(value as u64)), + "UInt64" => Ok(XRPLTypes::UInt64(value)), _ => Err!(exceptions::XRPLTypeException::UnknownXRPLType), } } else if let Some(value) = value.as_object() { @@ -151,9 +151,9 @@ impl XRPLTypes { } } -impl Into for XRPLTypes { - fn into(self) -> SerializedType { - match self { +impl From for SerializedType { + fn from(val: XRPLTypes) -> Self { + match val { XRPLTypes::AccountID(account_id) => SerializedType::from(account_id), XRPLTypes::Amount(amount) => SerializedType::from(amount), XRPLTypes::Blob(blob) => SerializedType::from(blob), @@ -383,7 +383,7 @@ impl STObject { let mut sorted_keys: Vec = Vec::new(); for (field, _) in &value_xaddress_handled { - let field_instance = get_field_instance(&field); + let field_instance = get_field_instance(field); if let Some(field_instance) = field_instance { if value_xaddress_handled.contains_key(&field_instance.name) && field_instance.is_serialized diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index b1f317e7..c1f3089a 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -26,4 +26,4 @@ pub enum XRPLFlagsException { } #[cfg(feature = "std")] -impl<'a> alloc::error::Error for XRPLFlagsException {} +impl alloc::error::Error for XRPLFlagsException {} diff --git a/src/models/mod.rs b/src/models/mod.rs index 3b75ca02..2e402d77 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -8,11 +8,13 @@ //! 5. Optional specific fields in alphabetical order #[cfg(feature = "ledger-models")] +#[allow(clippy::too_many_arguments)] pub mod ledger; #[cfg(feature = "request-models")] #[allow(clippy::too_many_arguments)] pub mod requests; #[cfg(feature = "result-models")] +#[allow(clippy::too_many_arguments)] pub mod results; #[cfg(feature = "transaction-models")] #[allow(clippy::too_many_arguments)] diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 434e7463..fd2e19ca 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -91,9 +91,9 @@ impl From for XRPLOtherResult { } } -impl Into for XRPLOtherResult { - fn into(self) -> Value { - self.0 +impl From for Value { + fn from(val: XRPLOtherResult) -> Self { + val.0 } } @@ -330,18 +330,16 @@ impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { if let Some(status) = &self.status { status == &ResponseStatus::Success - } else { - if let Some(result) = &self.result { - match serde_json::to_value(result) { - Ok(value) => match value.get("status") { - Some(Value::String(status)) => status == "success", - _ => false, - }, + } else if let Some(result) = &self.result { + match serde_json::to_value(result) { + Ok(value) => match value.get("status") { + Some(Value::String(status)) => status == "success", _ => false, - } - } else { - false + }, + _ => false, } + } else { + false } } diff --git a/src/transaction/multisign.rs b/src/transaction/multisign.rs index d2f5eda2..437629a6 100644 --- a/src/transaction/multisign.rs +++ b/src/transaction/multisign.rs @@ -21,7 +21,7 @@ where Some(signers) => signers, None => return Err!(XRPLMultisignException::NoSigners), }; - let tx_signer = match tx_signers.get(0) { + let tx_signer = match tx_signers.first() { Some(signer) => signer, None => return Err!(XRPLMultisignException::NoSigners), }; diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 00e2443d..7b84f314 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,5 +1,7 @@ //! Methods for working with XRPL wallets. +use core::fmt::Display; + use crate::constants::CryptoAlgorithm; use crate::core::addresscodec::classic_address_to_xaddress; use crate::core::addresscodec::exceptions::XRPLAddressCodecException; @@ -7,9 +9,7 @@ use crate::core::keypairs::derive_classic_address; use crate::core::keypairs::derive_keypair; use crate::core::keypairs::exceptions::XRPLKeypairsException; use crate::core::keypairs::generate_seed; -use alloc::format; use alloc::string::String; -use alloc::string::ToString; use zeroize::Zeroize; /// The cryptographic keys needed to control an @@ -84,15 +84,13 @@ impl Wallet { } } -impl ToString for Wallet { +impl Display for Wallet { /// Returns a string representation of a Wallet. - fn to_string(&self) -> String { - let string_list = [ - format!("public_key: {}", self.public_key), - format!("private_key: {}", "-HIDDEN-"), - format!("classic_address: {}", self.classic_address), - ]; - - string_list.join("-") + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "Wallet {{ public_key: {}, private_key: -HIDDEN-, classic_address: {} }}", + self.public_key, self.classic_address + ) } } From 60568cf33d4988065f3dbf377fed87c32279f13f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 9 Sep 2024 18:22:11 +0000 Subject: [PATCH 099/113] add sync clients and helpers; add examples; fix clippy warnings --- .cargo-husky/hooks/pre-commit | 0 CHANGELOG.md | 26 +- CODEOWNERS | 1 + Cargo.toml | 13 +- examples/rp2040/.gitignore | 20 ++ examples/rp2040/Cargo.toml | 17 + examples/rp2040/src/bin/asynch/json_rpc.rs | 1 + examples/rp2040/src/bin/asynch/websocket.rs | 1 + examples/std/Cargo.toml | 20 +- .../std/src/bin/asynch/clients/json_rpc.rs | 20 ++ .../clients/websocket.rs} | 15 +- examples/std/src/bin/clients/json_rpc.rs | 19 ++ examples/std/src/bin/clients/websocket.rs | 28 ++ examples/std/src/bin/tokio/net/websocket.rs | 43 --- src/account/mod.rs | 75 +++++ src/asynch/account/mod.rs | 12 +- src/asynch/clients/async_client.rs | 6 +- src/asynch/clients/client.rs | 34 +- src/asynch/clients/json_rpc/mod.rs | 8 +- src/asynch/clients/mod.rs | 33 +- src/asynch/clients/websocket/_no_std.rs | 5 +- src/asynch/clients/websocket/_std.rs | 4 +- src/asynch/clients/websocket/mod.rs | 6 +- src/asynch/ledger/mod.rs | 8 +- src/asynch/mod.rs | 51 +++ src/asynch/transaction/mod.rs | 36 +-- src/asynch/transaction/submit_and_wait.rs | 19 +- src/asynch/wallet/exceptions.rs | 13 - src/asynch/wallet/mod.rs | 47 +-- src/clients/mod.rs | 291 ++++++++++++++++++ src/core/addresscodec/mod.rs | 2 +- src/core/binarycodec/mod.rs | 18 +- src/core/definitions/mod.rs | 9 +- src/core/types/account_id.rs | 14 +- src/core/types/amount.rs | 7 +- src/core/types/blob.rs | 8 +- src/core/types/currency.rs | 14 +- src/core/types/hash.rs | 20 +- src/core/types/mod.rs | 10 +- src/core/types/vector256.rs | 8 +- src/ledger/mod.rs | 41 +++ src/lib.rs | 19 +- src/models/amount/issued_currency_amount.rs | 2 +- src/models/amount/xrp_amount.rs | 4 +- src/models/requests/ledger_entry.rs | 1 + src/transaction/mod.rs | 125 ++++++++ src/wallet/faucet_generation.rs | 30 ++ src/wallet/mod.rs | 5 +- 48 files changed, 954 insertions(+), 255 deletions(-) mode change 100755 => 100644 .cargo-husky/hooks/pre-commit create mode 100644 examples/rp2040/.gitignore create mode 100644 examples/rp2040/Cargo.toml create mode 100644 examples/rp2040/src/bin/asynch/json_rpc.rs create mode 100644 examples/rp2040/src/bin/asynch/websocket.rs create mode 100644 examples/std/src/bin/asynch/clients/json_rpc.rs rename examples/std/src/bin/{tokio/net/tungstenite.rs => asynch/clients/websocket.rs} (55%) create mode 100644 examples/std/src/bin/clients/json_rpc.rs create mode 100644 examples/std/src/bin/clients/websocket.rs delete mode 100644 examples/std/src/bin/tokio/net/websocket.rs create mode 100644 src/account/mod.rs delete mode 100644 src/asynch/wallet/exceptions.rs create mode 100644 src/clients/mod.rs create mode 100644 src/ledger/mod.rs create mode 100644 src/wallet/faucet_generation.rs diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit old mode 100755 new mode 100644 diff --git a/CHANGELOG.md b/CHANGELOG.md index 377fb081..e9c865bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,31 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [[Incomplete]] -- JSONRPC -- Websockets + - Models - Integration Tests - Performance Benchmarks ## [[Unreleased]] + - Examples - Wallet from seed - New wallet generation - - `AsyncWebsocketClient` requests + - Client requests - make `new` methods of models public -- add `AsyncWebsocketClient` +- add `AsyncWebSocketClient` and `WebSocketClient` +- add `AsyncJsonRpcClient` and `JsonRpcClient` - update dependencies - add devcontainer +- add transaction helpers and signing +- add account helpers +- add ledger helpers +- add wallet helpers + +--- ## [[v0.2.0-beta]] + ### Added + - Request models - Transaction models - Ledger models - Utilize `anyhow` and `thiserror` for models - Utilities regarding `serde` crate - Utilities regarding `anyhow` crate + ### Changed + - Use `serde_with` to reduce repetitive serialization skip attribute tags - Use `strum_macros::Display` instead of manual `core::fmt::Display` - Use `strum_macros::Display` for `CryptoAlgorithm` enum @@ -41,13 +52,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - crypto-bigint - serde_with - criterion + ### Fixed + - Broken documentation link - Flatten hex exceptions missed from previous pass --- ## [v0.1.1] - 2021-10-28 + Initial core release. - ### Added + +### Added + - All Core functionality working with unit tests diff --git a/CODEOWNERS b/CODEOWNERS index 6d1bffed..6232c3b0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ * @sephynox +* @LimpidCrypto diff --git a/Cargo.toml b/Cargo.toml index 6f46ef3a..fb58463e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ serde_repr = "0.1" zeroize = "1.5.7" hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } -derive-new = { version = "0.6.0", default-features = false } +derive-new = { version = "0.7.0", default-features = false } thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } embassy-sync = "0.6.0" @@ -79,6 +79,11 @@ tokio-tungstenite = { version = "0.23.1", optional = true, features = [ ] } embassy-futures = { version = "0.1.1", optional = true } embedded-nal-async = { version = "0.7.1", optional = true } +actix-rt = { version = "2.10.0", optional = true } +async-std = { version = "1.13.0", optional = true } +futures-executor = { version = "0.3.30", optional = true } +futures-timer = { version = "3.0.3", optional = true } +smol = { version = "2.0.2", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -160,4 +165,8 @@ std = [ ] # runtimes tokio-rt = ["tokio"] -embassy-rt = ["embassy-time"] +embassy-rt = ["dep:embassy-time"] +actix-rt = ["dep:actix-rt"] +async-std-rt = ["dep:async-std"] +futures-rt = ["dep:futures-timer"] +smol-rt = ["dep:smol"] diff --git a/examples/rp2040/.gitignore b/examples/rp2040/.gitignore new file mode 100644 index 00000000..1ec67b52 --- /dev/null +++ b/examples/rp2040/.gitignore @@ -0,0 +1,20 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Added by cargo +/target + +# VSCode +.vscode +.idea + +# Additional +src/main.rs diff --git a/examples/rp2040/Cargo.toml b/examples/rp2040/Cargo.toml new file mode 100644 index 00000000..4c995f9d --- /dev/null +++ b/examples/rp2040/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rp2040" +version = "0.1.0" +edition = "2021" + +[dependencies] +xrpl-rust = { path = "../..", default-features = false, features = ["websocket", "json-rpc"] } +# embassy-executor = { version = "0.6.0", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +# embassy-rp = { version = "0.2.0", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } + +[[bin]] +name = "async_websocket" +path = "src/bin/asynch/websocket.rs" + +[[bin]] +name = "async_json_rpc" +path = "src/bin/asynch/json_rpc.rs" diff --git a/examples/rp2040/src/bin/asynch/json_rpc.rs b/examples/rp2040/src/bin/asynch/json_rpc.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/examples/rp2040/src/bin/asynch/json_rpc.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/examples/rp2040/src/bin/asynch/websocket.rs b/examples/rp2040/src/bin/asynch/websocket.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/examples/rp2040/src/bin/asynch/websocket.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index d6b0dd90..fc44fbb8 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" xrpl-rust = { path = "../.." } tokio = { version = "1.0", features = ["full"] } rand = "0.8.5" -tokio-util = { version = "0.7.11", features = ["codec"] } [[bin]] name = "wallet_from_seed" @@ -22,13 +21,24 @@ path = "src/bin/wallet/generate_wallet.rs" required-features = [] [[bin]] -name = "websocket-std" -path = "src/bin/tokio/net/tungstenite.rs" -required-features = ["tokio"] +name = "async_websocket" +path = "src/bin/asynch/clients/websocket.rs" +required-features = [] + +[[bin]] +name = "async_json_rpc" +path = "src/bin/asynch/clients/json_rpc.rs" +required-features = [] [[bin]] name = "websocket" -path = "src/bin/tokio/net/websocket.rs" +path = "src/bin/clients/websocket.rs" +required-features = [] + +[[bin]] +name = "json_rpc" +path = "src/bin/clients/json_rpc.rs" +required-features = [] [[bin]] name = "sign_request" diff --git a/examples/std/src/bin/asynch/clients/json_rpc.rs b/examples/std/src/bin/asynch/clients/json_rpc.rs new file mode 100644 index 00000000..b0938f80 --- /dev/null +++ b/examples/std/src/bin/asynch/clients/json_rpc.rs @@ -0,0 +1,20 @@ +use xrpl::asynch::clients::{AsyncJsonRpcClient, XRPLAsyncClient}; +use xrpl::models::requests::account_info::AccountInfo; + +#[tokio::main] +async fn main() { + // connect to a XRP Ledger node + let client = AsyncJsonRpcClient::connect("https://xrplcluster.com/".parse().unwrap()); + // request account info + let account_info = AccountInfo::new( + None, + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59".into(), + None, + None, + None, + None, + None, + ); + let response = client.request(account_info.into()).await.unwrap(); + println!("account info: {:?}", response); +} diff --git a/examples/std/src/bin/tokio/net/tungstenite.rs b/examples/std/src/bin/asynch/clients/websocket.rs similarity index 55% rename from examples/std/src/bin/tokio/net/tungstenite.rs rename to examples/std/src/bin/asynch/clients/websocket.rs index 2f1ff5e0..a41be9f9 100644 --- a/examples/std/src/bin/tokio/net/tungstenite.rs +++ b/examples/std/src/bin/asynch/clients/websocket.rs @@ -1,17 +1,18 @@ -use tokio_tungstenite::connect_async; use xrpl::asynch::clients::{ - AsyncWebsocketClient, SingleExecutorMutex, WebsocketOpen, XRPLWebsocketIO, + AsyncWebSocketClient, SingleExecutorMutex, WebSocketOpen, XRPLAsyncWebsocketIO, }; -use xrpl::models::requests::{StreamParameter, Subscribe}; +use xrpl::models::requests::subscribe::{StreamParameter, Subscribe}; #[tokio::main] async fn main() { - let mut websocket: AsyncWebsocketClient = - AsyncWebsocketClient::open("wss://xrplcluster.com/".parse().unwrap()) + // open a websocket connection to a XRP Ledger node + let mut websocket: AsyncWebSocketClient = + AsyncWebSocketClient::open("wss://xrplcluster.com/".parse().unwrap()) .await .unwrap(); + // subscribe to the ledger stream let subscribe = Subscribe::new( - Some("my_id".into()), + None, None, None, None, @@ -21,9 +22,9 @@ async fn main() { None, ); websocket.xrpl_send(subscribe.into()).await.unwrap(); + // listen for messages loop { let account_info_echo = websocket.xrpl_receive().await.unwrap().unwrap(); println!("subscription message: {:?}", account_info_echo); - // break; } } diff --git a/examples/std/src/bin/clients/json_rpc.rs b/examples/std/src/bin/clients/json_rpc.rs new file mode 100644 index 00000000..3d7e7f39 --- /dev/null +++ b/examples/std/src/bin/clients/json_rpc.rs @@ -0,0 +1,19 @@ +use xrpl::clients::{json_rpc::JsonRpcClient, XRPLSyncClient}; +use xrpl::models::requests::account_info::AccountInfo; + +fn main() { + // connect to a XRP Ledger node + let client = JsonRpcClient::connect("https://xrplcluster.com/".parse().unwrap()); + // request account info + let account_info = AccountInfo::new( + None, + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59".into(), + None, + None, + None, + None, + None, + ); + let response = client.request(account_info.into()).unwrap(); + println!("account info: {:?}", response); +} diff --git a/examples/std/src/bin/clients/websocket.rs b/examples/std/src/bin/clients/websocket.rs new file mode 100644 index 00000000..42785184 --- /dev/null +++ b/examples/std/src/bin/clients/websocket.rs @@ -0,0 +1,28 @@ +use xrpl::clients::{ + websocket::{WebSocketClient, WebSocketOpen}, + SingleExecutorMutex, XRPLSyncWebsocketIO, +}; +use xrpl::models::requests::subscribe::{StreamParameter, Subscribe}; + +fn main() { + // open a websocket connection to a XRP Ledger node + let mut websocket: WebSocketClient = + WebSocketClient::open("wss://xrplcluster.com/".parse().unwrap()).unwrap(); + // subscribe to the ledger stream + let subscribe = Subscribe::new( + None, + None, + None, + None, + Some(vec![StreamParameter::Ledger]), + None, + None, + None, + ); + websocket.xrpl_send(subscribe.into()).unwrap(); + // listen for messages + loop { + let response = websocket.xrpl_receive().unwrap().unwrap(); + println!("subscription message: {:?}", response); + } +} diff --git a/examples/std/src/bin/tokio/net/websocket.rs b/examples/std/src/bin/tokio/net/websocket.rs deleted file mode 100644 index 6abf15df..00000000 --- a/examples/std/src/bin/tokio/net/websocket.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// Utilizing the no_std `AsyncWebsocketClient` of xrpl-rust with a tokio tcp stream. -use rand::rngs::OsRng; -use tokio::net::TcpStream; -use tokio_util::codec::Framed; -use xrpl::asynch::clients::{websocket::codec::Codec, AsyncWebsocketClient, SingleExecutorMutex}; -use xrpl::models::requests::{StreamParameter, Subscribe}; - -#[tokio::main] -async fn main() { - let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") - .await - .unwrap(); - let framed_stream = Framed::new(tcp_stream, Codec); - let rng = OsRng; - let url = "wss://xrplcluster.com/".parse().unwrap(); - let mut websocket = AsyncWebsocketClient::< - 4096, - Framed, - _, - _, - OsRng, - SingleExecutorMutex, - >::open(rng, framed_stream, url) - .await - .unwrap(); - - let subscribe = Subscribe::new( - Some("my_id".into()), - None, - None, - None, - Some(vec![StreamParameter::Ledger]), - None, - None, - None, - ); - websocket.xrpl_send(subscribe.into()).await.unwrap(); - loop { - let account_info_echo = websocket.xrpl_receive().await.unwrap().unwrap(); - println!("subscription message: {:?}", account_info_echo); - // break; - } -} diff --git a/src/account/mod.rs b/src/account/mod.rs new file mode 100644 index 00000000..69a102c7 --- /dev/null +++ b/src/account/mod.rs @@ -0,0 +1,75 @@ +use alloc::borrow::Cow; +use anyhow::Result; +use embassy_futures::block_on; + +use crate::{ + asynch::{ + account::{ + does_account_exist as async_does_account_exist, + get_account_root as async_get_account_root, + get_latest_transaction as async_get_latest_transaction, + get_next_valid_seq_number as async_get_next_valid_seq_number, + get_xrp_balance as async_get_xrp_balance, + }, + clients::XRPLClient, + }, + models::{ledger::objects::AccountRoot, results::account_tx::AccountTx, XRPAmount}, +}; + +pub fn does_account_exist( + address: Cow<'_, str>, + client: &C, + ledger_index: Option>, +) -> Result +where + C: XRPLClient, +{ + block_on(async_does_account_exist(address, client, ledger_index)) +} + +pub fn get_next_valid_seq_number( + address: Cow<'_, str>, + client: &C, + ledger_index: Option>, +) -> Result +where + C: XRPLClient, +{ + block_on(async_get_next_valid_seq_number( + address, + client, + ledger_index, + )) +} + +pub fn get_xrp_balance<'a: 'b, 'b, C>( + address: Cow<'a, str>, + client: &C, + ledger_index: Option>, +) -> Result> +where + C: XRPLClient, +{ + block_on(async_get_xrp_balance(address, client, ledger_index)) +} + +pub fn get_account_root<'a: 'b, 'b, C>( + address: Cow<'a, str>, + client: &C, + ledger_index: Cow<'a, str>, +) -> Result> +where + C: XRPLClient, +{ + block_on(async_get_account_root(address, client, ledger_index)) +} + +pub fn get_latest_transaction<'a: 'b, 'b, C>( + address: Cow<'a, str>, + client: &C, +) -> Result> +where + C: XRPLClient, +{ + block_on(async_get_latest_transaction(address, client)) +} diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs index 0ebc5cf9..a29a1c10 100644 --- a/src/asynch/account/mod.rs +++ b/src/asynch/account/mod.rs @@ -11,7 +11,7 @@ use crate::{ Err, }; -use super::clients::AsyncClient; +use super::clients::XRPLAsyncClient; pub async fn does_account_exist( address: Cow<'_, str>, @@ -19,7 +19,7 @@ pub async fn does_account_exist( ledger_index: Option>, ) -> Result where - C: AsyncClient, + C: XRPLAsyncClient, { match get_account_root(address, client, ledger_index.unwrap_or("validated".into())).await { Ok(_) => Ok(true), @@ -29,7 +29,7 @@ where pub async fn get_next_valid_seq_number( address: Cow<'_, str>, - client: &impl AsyncClient, + client: &impl XRPLAsyncClient, ledger_index: Option>, ) -> Result { let account_info = @@ -43,7 +43,7 @@ pub async fn get_xrp_balance<'a: 'b, 'b, C>( ledger_index: Option>, ) -> Result> where - C: AsyncClient, + C: XRPLAsyncClient, { let account_info = get_account_root(address, client, ledger_index.unwrap_or("validated".into())).await?; @@ -59,7 +59,7 @@ pub async fn get_account_root<'a: 'b, 'b, C>( ledger_index: Cow<'a, str>, ) -> Result> where - C: AsyncClient, + C: XRPLAsyncClient, { let mut classic_address = address; if is_valid_xaddress(&classic_address) { @@ -90,7 +90,7 @@ pub async fn get_latest_transaction<'a: 'b, 'b, C>( client: &C, ) -> Result> where - C: AsyncClient, + C: XRPLAsyncClient, { if is_valid_xaddress(&address) { address = match xaddress_to_classic_address(&address) { diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 6a0433a8..420ee328 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -1,4 +1,4 @@ -use super::{client::Client, CommonFields}; +use super::{client::XRPLClient, CommonFields}; use crate::models::{ requests::{server_state::ServerState, XRPLRequest}, results::{server_state::ServerState as ServerStateResult, XRPLResponse}, @@ -6,7 +6,7 @@ use crate::models::{ use anyhow::Result; #[allow(async_fn_in_trait)] -pub trait AsyncClient: Client { +pub trait XRPLAsyncClient: XRPLClient { async fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { self.request_impl(request).await } @@ -25,4 +25,4 @@ pub trait AsyncClient: Client { } } -impl AsyncClient for T {} +impl XRPLAsyncClient for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 060fb22d..8b2aaa5f 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -2,22 +2,12 @@ use crate::models::{ requests::{Request, XRPLRequest}, results::XRPLResponse, }; -use alloc::{ - borrow::ToOwned, - string::{String, ToString}, -}; +use alloc::borrow::Cow; use anyhow::Result; -use rand::Rng; use url::Url; -/// Generate a random id. -pub fn get_random_id(rng: &mut T) -> String { - let id: u32 = rng.gen(); - id.to_string() -} - #[allow(async_fn_in_trait)] -pub trait Client { +pub trait XRPLClient { async fn request_impl<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; fn get_host(&self) -> Url; @@ -25,17 +15,27 @@ pub trait Client { fn set_request_id(&self, request: &mut XRPLRequest<'_>) { let common_fields = request.get_common_fields_mut(); common_fields.id = match &common_fields.id { - Some(id) => Some(id.to_owned()), + Some(id) => Some(id.clone()), None => { #[cfg(feature = "std")] { - use alloc::borrow::Cow; - let mut rng = rand::thread_rng(); - Some(Cow::Owned(get_random_id(&mut rng))) + Some(self.get_random_id()) } #[cfg(not(feature = "std"))] - unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); + unimplemented!( + "Random ID generation is not supported in no_std. Please provide an ID." + ) } }; } + + /// Generate a random id. + #[cfg(feature = "std")] + fn get_random_id<'a>(&self) -> Cow<'a, str> { + use alloc::string::ToString; + + let random_id = rand::random::().to_string(); + + Cow::Owned(random_id) + } } diff --git a/src/asynch/clients/json_rpc/mod.rs b/src/asynch/clients/json_rpc/mod.rs index db0cb844..3431a2ea 100644 --- a/src/asynch/clients/json_rpc/mod.rs +++ b/src/asynch/clients/json_rpc/mod.rs @@ -8,7 +8,7 @@ use crate::{models::results::XRPLResponse, Err}; mod exceptions; pub use exceptions::XRPLJsonRpcException; -use super::client::Client; +use super::client::XRPLClient; /// Renames the requests field `command` to `method` for JSON-RPC. fn request_to_json_rpc(request: &impl Serialize) -> Result { @@ -53,7 +53,7 @@ mod _std { } } - impl Client for AsyncJsonRpcClient { + impl XRPLClient for AsyncJsonRpcClient { async fn request_impl<'a: 'b, 'b>( &self, request: XRPLRequest<'a>, @@ -149,7 +149,7 @@ mod _no_std { } } - pub fn new_with_tls(url: Url, tcp: &'a T, dns: &'a D, tls: TlsConfig<'a>) -> Self { + pub fn connect_with_tls(url: Url, tcp: &'a T, dns: &'a D, tls: TlsConfig<'a>) -> Self { Self { url, client: Arc::new(Mutex::new(HttpClient::new_with_tls(tcp, dns, tls))), @@ -157,7 +157,7 @@ mod _no_std { } } - impl Client for AsyncJsonRpcClient<'_, BUF, T, D, M> + impl XRPLClient for AsyncJsonRpcClient<'_, BUF, T, D, M> where M: RawMutex, T: TcpConnect, diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index fdf420b1..c80e2088 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -21,16 +21,37 @@ pub use websocket::*; pub type MultiExecutorMutex = CriticalSectionRawMutex; pub type SingleExecutorMutex = NoopRawMutex; -#[cfg(feature = "wallet-helpers")] -use crate::{asynch::wallet::get_faucet_url, models::requests::FundFaucet}; +const TEST_FAUCET_URL: &str = "https://faucet.altnet.rippletest.net/accounts"; +const DEV_FAUCET_URL: &str = "https://faucet.devnet.rippletest.net/accounts"; + +use crate::{asynch::XRPLFaucetException, models::requests::FundFaucet, Err}; #[allow(async_fn_in_trait)] -#[cfg(feature = "wallet-helpers")] -pub trait XRPLFaucet: Client { +pub trait XRPLFaucet: XRPLClient { fn get_faucet_url(&self, url: Option) -> Result where - Self: Sized + Client, + Self: Sized + XRPLClient, { - get_faucet_url(self, url) + if let Some(url) = url { + Ok(url) + } else { + let host = self.get_host(); + let host_str = host.host_str().unwrap(); + if host_str.contains("altnet") || host_str.contains("testnet") { + match Url::parse(TEST_FAUCET_URL) { + Ok(url) => Ok(url), + Err(error) => Err!(error), + } + } else if host_str.contains("devnet") { + match Url::parse(DEV_FAUCET_URL) { + Ok(url) => Ok(url), + Err(error) => Err!(error), + } + } else if host_str.contains("sidechain-net2") { + Err!(XRPLFaucetException::CannotFundSidechainAccount) + } else { + Err!(XRPLFaucetException::CannotDeriveFaucetUrl) + } + } } async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()>; diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 8baef20c..97bb92b6 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -22,7 +22,7 @@ use crate::{ }; use crate::{ asynch::clients::{ - client::Client as ClientTrait, + client::XRPLClient as ClientTrait, websocket::websocket_base::{MessageHandler, WebsocketBase}, }, models::results::XRPLResponse, @@ -218,11 +218,12 @@ where } } -impl ClientTrait +impl ClientTrait for AsyncWebSocketClient where M: RawMutex, Tcp: Read + Write + Unpin, + Rng: RngCore, { fn get_host(&self) -> Url { self.uri.clone() diff --git a/src/asynch/clients/websocket/_std.rs b/src/asynch/clients/websocket/_std.rs index 78688688..ba5d550c 100644 --- a/src/asynch/clients/websocket/_std.rs +++ b/src/asynch/clients/websocket/_std.rs @@ -1,6 +1,6 @@ use super::exceptions::XRPLWebsocketException; use super::{WebSocketClosed, WebSocketOpen}; -use crate::asynch::clients::client::Client; +use crate::asynch::clients::client::XRPLClient; use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; use crate::asynch::clients::SingleExecutorMutex; use crate::models::requests::{Request, XRPLRequest}; @@ -197,7 +197,7 @@ where } } -impl Client for AsyncWebSocketClient +impl XRPLClient for AsyncWebSocketClient where M: RawMutex, { diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 04520815..06924e03 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -33,14 +33,14 @@ pub struct WebSocketOpen; pub struct WebSocketClosed; #[allow(async_fn_in_trait)] -pub trait XRPLWebsocketIO { +pub trait XRPLAsyncWebsocketIO { async fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()>; async fn xrpl_receive(&mut self) -> Result>>; } #[cfg(not(feature = "std"))] -impl XRPLWebsocketIO for T +impl XRPLAsyncWebsocketIO for T where ::Error: Display, { @@ -85,7 +85,7 @@ where } #[cfg(feature = "std")] -impl XRPLWebsocketIO for T +impl XRPLAsyncWebsocketIO for T where T: Stream> + Sink + MessageHandler + Unpin, { diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs index 1ff7b40a..61eea2ea 100644 --- a/src/asynch/ledger/mod.rs +++ b/src/asynch/ledger/mod.rs @@ -9,9 +9,9 @@ use crate::models::{ XRPAmount, }; -use super::clients::AsyncClient; +use super::clients::XRPLAsyncClient; -pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> Result { +pub async fn get_latest_validated_ledger_sequence(client: &impl XRPLAsyncClient) -> Result { let ledger_response = client .request( Ledger::new( @@ -35,7 +35,7 @@ pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> .ledger_index) } -pub async fn get_latest_open_ledger_sequence(client: &impl AsyncClient) -> Result { +pub async fn get_latest_open_ledger_sequence(client: &impl XRPLAsyncClient) -> Result { let ledger_response = client .request( Ledger::new( @@ -66,7 +66,7 @@ pub enum FeeType { } pub async fn get_fee( - client: &impl AsyncClient, + client: &impl XRPLAsyncClient, max_fee: Option, fee_type: Option, ) -> Result> { diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index b94b9b75..8db200d2 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -20,3 +20,54 @@ pub mod transaction; any(feature = "websocket", feature = "json-rpc") ))] pub mod wallet; + +use thiserror_no_std::Error; + +#[derive(Error, Debug)] +pub enum XRPLFaucetException { + #[error( + "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." + )] + CannotFundSidechainAccount, + #[error("Cannot derive a faucet URL from the client host.")] + CannotDeriveFaucetUrl, + #[error("Funding request timed out.")] + FundingTimeout, +} + +#[allow(unused_imports)] +#[allow(clippy::needless_return)] +async fn wait_seconds(seconds: u64) { + use core::time::Duration; + + #[cfg(feature = "tokio-rt")] + { + tokio::time::sleep(tokio::time::Duration::from_secs(seconds)).await; + return; + } + #[cfg(feature = "embassy-rt")] + { + embassy_time::Timer::after_secs(1).await; + return; + } + #[cfg(feature = "actix-rt")] + { + actix_rt::time::sleep(Duration::from_secs(seconds)).await; + return; + } + #[cfg(feature = "async-std-rt")] + { + async_std::task::sleep(Duration::from_secs(seconds)).await; + return; + } + #[cfg(feature = "futures-rt")] + { + futures_timer::Delay::new(Duration::from_secs(seconds)).await; + return; + } + #[cfg(feature = "smol-rt")] + { + smol::Timer::after(Duration::from_secs(seconds)).await; + return; + } +} diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 374cd4e5..a0d98b77 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -6,7 +6,7 @@ pub use submit_and_wait::*; use crate::{ asynch::{ account::get_next_valid_seq_number, - clients::{AsyncClient, CommonFields}, + clients::{CommonFields, XRPLAsyncClient}, ledger::{get_fee, get_latest_validated_ledger_sequence}, transaction::exceptions::XRPLSignTransactionException, }, @@ -99,7 +99,7 @@ pub async fn sign_and_submit<'a, 'b, T, F, C>( where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, + C: XRPLAsyncClient, { if autofill { autofill_and_sign(transaction, client, wallet, check_fee).await?; @@ -120,7 +120,7 @@ pub async fn autofill<'a, 'b, F, T, C>( where T: Transaction<'a, F> + Model + Clone, F: IntoEnumIterator + Serialize + Debug + PartialEq, - C: AsyncClient, + C: XRPLAsyncClient, { let txn = transaction.clone(); let txn_common_fields = transaction.get_mut_common_fields(); @@ -153,7 +153,7 @@ pub async fn autofill_and_sign<'a, 'b, T, F, C>( where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, + C: XRPLAsyncClient, { if check_fee { check_txn_fee(transaction, client).await?; @@ -168,7 +168,7 @@ pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result + Serialize + DeserializeOwned + Clone + Debug, - C: AsyncClient, + C: XRPLAsyncClient, { let txn_blob = encode(transaction)?; let req = Submit::new(None, txn_blob.into(), None); @@ -190,7 +190,7 @@ pub async fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( where T: Transaction<'a, F>, F: IntoEnumIterator + Serialize + Debug + PartialEq, - C: AsyncClient, + C: XRPLAsyncClient, { let mut net_fee = XRPAmount::from("10"); let base_fee; @@ -230,7 +230,7 @@ where Ok(base_fee_decimal.ceil().into()) } -async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result> { +async fn get_owner_reserve_from_response(client: &impl XRPLAsyncClient) -> Result> { let owner_reserve_response = client.request(ServerState::new(None).into()).await?; match owner_reserve_response .try_into_result::>()? @@ -348,9 +348,9 @@ fn is_not_later_rippled_version<'a>( )? { Ok(source_patch[1] < target_patch[1]) } else if source_patch[1].starts_with('b') { - Ok(&source_patch[1][1..] < &target_patch[1][1..]) + Ok(source_patch[1][1..] < target_patch[1][1..]) } else { - Ok(&source_patch[1][2..] < &target_patch[1][2..]) + Ok(source_patch[1][2..] < target_patch[1][2..]) } } else { Ok(false) @@ -369,7 +369,7 @@ async fn check_txn_fee<'a, 'b, T, F, C>(transaction: &mut T, client: &'b C) -> R where F: IntoEnumIterator + Serialize + Debug + PartialEq, T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone, - C: AsyncClient, + C: XRPLAsyncClient, { // max of xrp_to_drops(0.1) and calculate_fee_per_transaction_type let expected_fee = XRPAmount::from("100000") @@ -536,12 +536,13 @@ mod test_autofill { #[cfg(all(feature = "websocket", feature = "std"))] #[cfg(test)] mod test_sign { - use alloc::borrow::Cow; + use alloc::{borrow::Cow, dbg}; use crate::{ asynch::{ - clients::{AsyncWebSocketClient, SingleExecutorMutex}, + clients::AsyncJsonRpcClient, transaction::{autofill_and_sign, sign}, + wallet::generate_faucet_wallet, }, models::transactions::{account_set::AccountSet, Transaction}, wallet::Wallet, @@ -581,7 +582,11 @@ mod test_sign { #[tokio::test] async fn test_autofill_and_sign() { - let wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); + let client = AsyncJsonRpcClient::connect("wss://testnet.xrpl-labs.com/".parse().unwrap()); + let wallet = generate_faucet_wallet(&client, None, None, None, None) + .await + .unwrap(); + dbg!(&wallet); let mut tx = AccountSet::new( Cow::from(wallet.classic_address.clone()), None, @@ -602,11 +607,6 @@ mod test_sign { None, None, ); - let client = AsyncWebSocketClient::::open( - "wss://testnet.xrpl-labs.com/".parse().unwrap(), - ) - .await - .unwrap(); autofill_and_sign(&mut tx, &client, &wallet, true) .await .unwrap(); diff --git a/src/asynch/transaction/submit_and_wait.rs b/src/asynch/transaction/submit_and_wait.rs index 210dfefd..f7d7825b 100644 --- a/src/asynch/transaction/submit_and_wait.rs +++ b/src/asynch/transaction/submit_and_wait.rs @@ -8,13 +8,14 @@ use strum::IntoEnumIterator; use crate::{ asynch::{ - clients::AsyncClient, + clients::XRPLAsyncClient, ledger::get_latest_validated_ledger_sequence, transaction::{ autofill, check_txn_fee, exceptions::{XRPLSignTransactionException, XRPLSubmitAndWaitException}, sign, submit, }, + wait_seconds, }, models::{requests, results, transactions::Transaction, Model}, wallet::Wallet, @@ -31,7 +32,7 @@ pub async fn submit_and_wait<'a: 'b, 'b, T, F, C>( where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, - C: AsyncClient, + C: XRPLAsyncClient, { get_signed_transaction(transaction, client, wallet, check_fee, autofill).await?; send_reliable_submission(transaction, client).await @@ -44,7 +45,7 @@ async fn send_reliable_submission<'a: 'b, 'b, T, F, C>( where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, - C: AsyncClient, + C: XRPLAsyncClient, { let tx_hash = transaction.get_hash()?; let submit_response = submit(transaction, client).await?; @@ -74,7 +75,7 @@ async fn wait_for_final_transaction_result<'a: 'b, 'b, C>( last_ledger_sequence: u32, ) -> Result> where - C: AsyncClient, + C: XRPLAsyncClient, { let mut validated_ledger_sequence = 0; let mut c = 0; @@ -85,13 +86,7 @@ where } validated_ledger_sequence = get_latest_validated_ledger_sequence(client).await?; // sleep for 1 second - #[cfg(feature = "embassy-rt")] - embassy_time::Timer::after_secs(1).await; - #[cfg(any( - feature = "tokio-rt", - all(feature = "embassy-rt", feature = "tokio-rt") - ))] - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + wait_seconds(1).await; let response = client .request(requests::tx::Tx::new(None, None, None, None, Some(tx_hash.clone())).into()) .await?; @@ -144,7 +139,7 @@ async fn get_signed_transaction<'a, T, F, C>( where T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone, - C: AsyncClient, + C: XRPLAsyncClient, { if transaction.get_common_fields().is_signed() { return Ok(()); diff --git a/src/asynch/wallet/exceptions.rs b/src/asynch/wallet/exceptions.rs deleted file mode 100644 index 9571cd29..00000000 --- a/src/asynch/wallet/exceptions.rs +++ /dev/null @@ -1,13 +0,0 @@ -use thiserror_no_std::Error; - -#[derive(Error, Debug)] -pub enum XRPLFaucetException { - #[error( - "Cannot fund an account on an issuing chain. Accounts must be created via the bridge." - )] - CannotFundSidechainAccount, - #[error("Cannot derive a faucet URL from the client host.")] - CannotDeriveFaucetUrl, - #[error("Funding request timed out.")] - FundingTimeout, -} diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 13b27168..0767d3d4 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -1,12 +1,9 @@ -pub mod exceptions; - use alloc::borrow::Cow; use anyhow::Result; -use exceptions::XRPLFaucetException; use url::Url; use crate::{ - asynch::account::get_next_valid_seq_number, + asynch::{account::get_next_valid_seq_number, wait_seconds, XRPLFaucetException}, models::{requests::FundFaucet, XRPAmount}, wallet::Wallet, Err, @@ -14,12 +11,9 @@ use crate::{ use super::{ account::get_xrp_balance, - clients::{Client, XRPLFaucet}, + clients::{XRPLClient, XRPLFaucet}, }; -const TEST_FAUCET_URL: &str = "https://faucet.altnet.rippletest.net/accounts"; -const DEV_FAUCET_URL: &str = "https://faucet.devnet.rippletest.net/accounts"; - const TIMEOUT_SECS: u8 = 40; pub async fn generate_faucet_wallet<'a, C>( @@ -30,7 +24,7 @@ pub async fn generate_faucet_wallet<'a, C>( user_agent: Option>, ) -> Result where - C: XRPLFaucet + Client, + C: XRPLFaucet + XRPLClient, { let faucet_url = get_faucet_url(client, faucet_host)?; let wallet = match wallet { @@ -54,12 +48,7 @@ where let mut is_funded = false; for _ in 0..TIMEOUT_SECS { // wait 1 second - #[cfg(all(feature = "embassy-rt", not(feature = "tokio-rt")))] - embassy_time::Timer::after_secs(1).await; - #[cfg(any( - feature = "tokio-rt", - all(feature = "embassy-rt", feature = "tokio-rt") - ))] + wait_seconds(1).await; tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; if !is_funded { let balance = check_balance(client, address.into()).await; @@ -82,34 +71,14 @@ where pub fn get_faucet_url(client: &C, url: Option) -> Result where - C: Client, + C: XRPLFaucet + XRPLClient, { - if let Some(url) = url { - Ok(url) - } else { - let host = client.get_host(); - let host_str = host.host_str().unwrap(); - if host_str.contains("altnet") || host_str.contains("testnet") { - match Url::parse(TEST_FAUCET_URL) { - Ok(url) => Ok(url), - Err(error) => Err!(error), - } - } else if host_str.contains("devnet") { - match Url::parse(DEV_FAUCET_URL) { - Ok(url) => Ok(url), - Err(error) => Err!(error), - } - } else if host_str.contains("sidechain-net2") { - Err!(XRPLFaucetException::CannotFundSidechainAccount) - } else { - Err!(XRPLFaucetException::CannotDeriveFaucetUrl) - } - } + client.get_faucet_url(url) } async fn check_balance<'a: 'b, 'b, C>(client: &C, address: Cow<'a, str>) -> XRPAmount<'b> where - C: Client, + C: XRPLClient, { get_xrp_balance(address, client, None) .await @@ -124,7 +93,7 @@ async fn fund_wallet<'a: 'b, 'b, C>( user_agent: Option>, ) -> Result<()> where - C: XRPLFaucet + Client, + C: XRPLFaucet + XRPLClient, { let request = FundFaucet { destination: address, diff --git a/src/clients/mod.rs b/src/clients/mod.rs new file mode 100644 index 00000000..03a932dc --- /dev/null +++ b/src/clients/mod.rs @@ -0,0 +1,291 @@ +use anyhow::Result; + +use crate::{ + asynch::clients::{CommonFields, XRPLClient}, + models::{requests::XRPLRequest, results::XRPLResponse}, +}; + +pub use crate::asynch::clients::{SingleExecutorMutex, XRPLFaucet}; + +pub trait XRPLSyncClient: XRPLClient { + fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result>; + + fn get_common_fields(&self) -> Result>; +} + +#[cfg(all(feature = "json-rpc", feature = "std"))] +pub mod json_rpc { + use anyhow::Result; + use tokio::runtime::Runtime; + use url::Url; + + use crate::{ + asynch::clients::{ + AsyncJsonRpcClient, CommonFields, XRPLAsyncClient, XRPLClient, XRPLFaucet, + }, + models::{ + requests::{FundFaucet, XRPLRequest}, + results::XRPLResponse, + }, + Err, + }; + + use super::XRPLSyncClient; + + pub struct JsonRpcClient(AsyncJsonRpcClient); + impl JsonRpcClient { + pub fn connect(url: Url) -> Self { + Self(AsyncJsonRpcClient::connect(url)) + } + } + + impl XRPLClient for JsonRpcClient { + async fn request_impl<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> Result> { + self.0.request_impl(request).await + } + + fn get_host(&self) -> Url { + self.0.get_host() + } + + fn get_random_id<'a>(&self) -> alloc::borrow::Cow<'a, str> { + self.0.get_random_id() + } + } + + impl XRPLSyncClient for JsonRpcClient { + fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { + match Runtime::new() { + Ok(rt) => rt.block_on(self.0.request_impl(request)), + Err(e) => Err!(e), + } + } + + fn get_common_fields(&self) -> Result> { + match Runtime::new() { + Ok(rt) => rt.block_on(self.0.get_common_fields()), + Err(e) => Err!(e), + } + } + } + + impl XRPLFaucet for JsonRpcClient { + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + self.0.request_funding(url, request).await + } + } +} + +#[cfg(all(feature = "json-rpc", not(feature = "std")))] +pub mod json_rpc { + use anyhow::Result; + use embassy_sync::blocking_mutex::raw::RawMutex; + use embedded_nal_async::{Dns, TcpConnect}; + use url::Url; + + use crate::{ + asynch::clients::{AsyncJsonRpcClient, XRPLClient, XRPLFaucet}, + models::{ + requests::{FundFaucet, XRPLRequest}, + results::XRPLResponse, + }, + }; + + pub struct JsonRpcClient<'a, const BUF: usize, T, D, M>( + pub(crate) AsyncJsonRpcClient<'a, BUF, T, D, M>, + ) + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a; + + impl<'a, const BUF: usize, T, D, M> JsonRpcClient<'a, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + pub fn connect(url: Url, tcp: &'a T, dns: &'a D) -> Self { + Self(AsyncJsonRpcClient::connect(url, tcp, dns)) + } + } + + impl XRPLClient for JsonRpcClient<'_, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect, + D: Dns, + { + async fn request_impl<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> Result> { + self.0.request_impl(request).await + } + + fn get_host(&self) -> Url { + self.0.get_host() + } + } + + impl<'a, const BUF: usize, T, D, M> XRPLFaucet for JsonRpcClient<'a, BUF, T, D, M> + where + M: RawMutex, + T: TcpConnect + 'a, + D: Dns + 'a, + { + async fn request_funding(&self, url: Option, request: FundFaucet<'_>) -> Result<()> { + self.0.request_funding(url, request).await + } + } +} + +pub trait XRPLSyncWebsocketIO { + fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()>; + + fn xrpl_receive(&mut self) -> Result>>; +} + +#[cfg(all(feature = "websocket", feature = "std"))] +pub mod websocket { + use anyhow::Result; + use embassy_sync::blocking_mutex::raw::RawMutex; + use tokio::runtime::Runtime; + use url::Url; + + use super::{XRPLSyncClient, XRPLSyncWebsocketIO}; + use crate::{ + asynch::clients::{ + AsyncWebSocketClient, CommonFields, XRPLAsyncClient, XRPLAsyncWebsocketIO, XRPLClient, + }, + models::{requests::XRPLRequest, results::XRPLResponse}, + Err, + }; + + pub use crate::asynch::clients::{WebSocketClosed, WebSocketOpen}; + + pub struct WebSocketClient { + pub(crate) inner: AsyncWebSocketClient, + rt: Runtime, + } + + impl WebSocketClient { + pub fn open(url: Url) -> Result> { + match Runtime::new() { + Ok(rt) => { + let client: AsyncWebSocketClient = + rt.block_on(AsyncWebSocketClient::open(url))?; + + Ok(WebSocketClient { inner: client, rt }) + } + Err(e) => Err!(e), + } + } + } + + impl XRPLClient for WebSocketClient + where + M: RawMutex, + { + fn get_host(&self) -> Url { + self.inner.get_host() + } + + async fn request_impl<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> Result> { + match Runtime::new() { + Ok(rt) => rt.block_on(self.inner.request_impl(request)), + Err(e) => Err!(e), + } + } + } + + impl XRPLSyncClient for WebSocketClient + where + M: RawMutex, + { + fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { + self.rt.block_on(self.inner.request_impl(request)) + } + + fn get_common_fields(&self) -> Result> { + self.rt.block_on(self.inner.get_common_fields()) + } + } + + impl XRPLSyncWebsocketIO for WebSocketClient + where + M: RawMutex, + { + fn xrpl_send(&mut self, message: XRPLRequest<'_>) -> Result<()> { + self.rt.block_on(self.inner.xrpl_send(message)) + } + + fn xrpl_receive(&mut self) -> Result>> { + self.rt.block_on(self.inner.xrpl_receive()) + } + } +} + +#[cfg(all(feature = "websocket", not(feature = "std")))] +pub mod websocket { + use super::XRPLSyncWebsocketIO; + use anyhow::Result; + use embassy_futures::block_on; + use embassy_sync::blocking_mutex::raw::RawMutex; + use embedded_io_async::{Read, Write}; + use rand::RngCore; + use url::Url; + + use crate::{ + asynch::clients::{AsyncWebSocketClient, WebSocketOpen, XRPLAsyncWebsocketIO, XRPLClient}, + models::{requests::XRPLRequest, results::XRPLResponse}, + }; + + pub struct WebSocketClient( + pub(crate) AsyncWebSocketClient, + ) + where + Tcp: Read + Write + Unpin, + Rng: RngCore, + M: RawMutex; + + impl XRPLClient for WebSocketClient + where + Tcp: Read + Write + Unpin, + Rng: RngCore, + M: RawMutex, + { + fn get_host(&self) -> Url { + self.0.get_host() + } + + async fn request_impl<'a: 'b, 'b>( + &self, + request: XRPLRequest<'a>, + ) -> Result> { + block_on(self.0.request_impl(request)) + } + } + + impl XRPLSyncWebsocketIO + for WebSocketClient + where + Tcp: Read + Write + Unpin, + Rng: RngCore, + M: RawMutex, + { + fn xrpl_send(&mut self, message: crate::models::requests::XRPLRequest<'_>) -> Result<()> { + block_on(self.0.xrpl_send(message)) + } + + fn xrpl_receive(&mut self) -> Result>> { + block_on(self.0.xrpl_receive()) + } + } +} diff --git a/src/core/addresscodec/mod.rs b/src/core/addresscodec/mod.rs index ddfb525c..7aa8ed8a 100644 --- a/src/core/addresscodec/mod.rs +++ b/src/core/addresscodec/mod.rs @@ -201,7 +201,7 @@ pub fn classic_address_to_xaddress( Err(XRPLAddressCodecException::InvalidCAddressIdLength { length: CLASSIC_ADDRESS_ID_LENGTH, }) - } else if tag.is_some() && tag > Some(u32::max_value().into()) { + } else if tag.is_some() && tag > Some(u32::MAX.into()) { Err(XRPLAddressCodecException::InvalidCAddressTag) } else { if let Some(tval) = tag { diff --git a/src/core/binarycodec/mod.rs b/src/core/binarycodec/mod.rs index 47d1cf86..0045c57f 100644 --- a/src/core/binarycodec/mod.rs +++ b/src/core/binarycodec/mod.rs @@ -6,9 +6,9 @@ use crate::Err; use alloc::{borrow::Cow, string::String, vec::Vec}; use anyhow::Result; -use core::{convert::TryFrom, fmt::Debug}; +use core::convert::TryFrom; use hex::ToHex; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; pub mod binary_wrappers; pub mod exceptions; @@ -20,16 +20,16 @@ pub use binary_wrappers::*; const TRANSACTION_SIGNATURE_PREFIX: i32 = 0x53545800; const TRANSACTION_MULTISIG_PREFIX: i32 = 0x534D5400; -pub fn encode<'a, T>(signed_transaction: &T) -> Result +pub fn encode(signed_transaction: &T) -> Result where - T: Serialize + DeserializeOwned + Clone + Debug, + T: Serialize, { serialize_json(signed_transaction, None, None, false) } -pub fn encode_for_signing<'a, T>(prepared_transaction: &T) -> Result +pub fn encode_for_signing(prepared_transaction: &T) -> Result where - T: Serialize + DeserializeOwned + Clone + Debug, + T: Serialize, { serialize_json( prepared_transaction, @@ -44,7 +44,7 @@ pub fn encode_for_multisigning( signing_account: Cow<'_, str>, ) -> Result where - T: Serialize + DeserializeOwned + Clone + Debug, + T: Serialize, { let signing_account_id = AccountId::try_from(signing_account.as_ref()).unwrap(); @@ -56,14 +56,14 @@ where ) } -fn serialize_json<'a, T>( +fn serialize_json( prepared_transaction: &T, prefix: Option<&[u8]>, suffix: Option<&[u8]>, signing_only: bool, ) -> Result where - T: Serialize + DeserializeOwned + Clone + Debug, + T: Serialize, { let mut buffer = Vec::new(); if let Some(p) = prefix { diff --git a/src/core/definitions/mod.rs b/src/core/definitions/mod.rs index 42ad99ed..15880922 100644 --- a/src/core/definitions/mod.rs +++ b/src/core/definitions/mod.rs @@ -3,10 +3,11 @@ pub mod types; +use core::fmt::Display; + pub use self::types::*; use crate::utils::ToBytes; -use alloc::format; use alloc::string::String; use alloc::string::ToString; use alloc::vec; @@ -132,10 +133,10 @@ impl FieldInstance { } } -impl ToString for FieldHeader { +impl Display for FieldHeader { /// Convert the FieldHeader to a String. - fn to_string(&self) -> String { - format!("{}_{}", self.type_code, self.field_code) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}_{}", self.type_code, self.field_code) } } diff --git a/src/core/types/account_id.rs b/src/core/types/account_id.rs index 0aa7c247..bbaa8e31 100644 --- a/src/core/types/account_id.rs +++ b/src/core/types/account_id.rs @@ -8,8 +8,6 @@ use crate::core::types::exceptions::XRPLHashException; use crate::core::types::*; use crate::core::BinaryParser; use crate::utils::is_hex_address; -use alloc::string::String; -use alloc::string::ToString; use core::convert::TryFrom; use serde::ser::Error; use serde::Serializer; @@ -86,10 +84,14 @@ impl TryFrom<&str> for AccountId { } } -impl ToString for AccountId { - /// Get the classic address of the AccountId bytes. - fn to_string(&self) -> String { - encode_classic_address(self.as_ref()).expect("to_string") +impl Display for AccountId { + /// Return the classic address of the AccountId bytes. + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "{}", + encode_classic_address(self.as_ref()).expect("Could not encode address to string") + ) } } diff --git a/src/core/types/amount.rs b/src/core/types/amount.rs index 4e6c1039..5632f040 100644 --- a/src/core/types/amount.rs +++ b/src/core/types/amount.rs @@ -343,10 +343,9 @@ impl TryFrom for IssuedCurrency { } } -impl ToString for Amount { - /// Get the hex representation of the Amount bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) +impl Display for Amount { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Display::fmt(&hex::encode_upper(self.as_ref()), f) } } diff --git a/src/core/types/blob.rs b/src/core/types/blob.rs index 5515bddd..90954396 100644 --- a/src/core/types/blob.rs +++ b/src/core/types/blob.rs @@ -5,8 +5,6 @@ use crate::core::binarycodec::exceptions::XRPLBinaryCodecException; use crate::core::types::*; -use alloc::string::String; -use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; @@ -51,10 +49,10 @@ impl TryFrom<&str> for Blob { } } -impl ToString for Blob { +impl Display for Blob { /// Get the hex representation of the Blob bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + Display::fmt(&hex::encode_upper(self.as_ref()), f) } } diff --git a/src/core/types/currency.rs b/src/core/types/currency.rs index 2cf2bf32..f7af7d2e 100644 --- a/src/core/types/currency.rs +++ b/src/core/types/currency.rs @@ -115,20 +115,24 @@ impl TryFrom<&str> for Currency { } } -impl ToString for Currency { +impl Display for Currency { /// Get the ISO or hex representation of the Currency bytes. - fn to_string(&self) -> String { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let buffer = self.0.as_ref(); if hex::encode_upper(buffer) == NATIVE_HEX_CODE { - NATIVE_CODE.to_string() + write!(f, "{}", NATIVE_CODE) } else { let iso = _iso_code_from_hex(buffer); if let Ok(code) = iso { - code.or_else(|| Some(hex::encode_upper(buffer))).unwrap() + write!( + f, + "{}", + code.or_else(|| Some(hex::encode_upper(buffer))).unwrap() + ) } else { - hex::encode_upper(buffer) + write!(f, "{}", hex::encode_upper(buffer)) } } } diff --git a/src/core/types/hash.rs b/src/core/types/hash.rs index 35ae0803..cfffd4b8 100644 --- a/src/core/types/hash.rs +++ b/src/core/types/hash.rs @@ -8,8 +8,6 @@ use crate::core::types::utils::*; use crate::core::types::*; use crate::core::BinaryParser; use crate::core::Parser; -use alloc::string::String; -use alloc::string::ToString; use alloc::vec::Vec; use core::convert::TryFrom; use serde::Deserialize; @@ -280,24 +278,24 @@ impl TryFrom<&str> for Hash256 { } } -impl ToString for Hash128 { +impl Display for Hash128 { /// Get the hex representation of the Hash128 bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", hex::encode_upper(self.as_ref())) } } -impl ToString for Hash160 { +impl Display for Hash160 { /// Get the hex representation of the Hash160 bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", hex::encode_upper(self.as_ref())) } } -impl ToString for Hash256 { +impl Display for Hash256 { /// Get the hex representation of the Hash256 bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", hex::encode_upper(self.as_ref())) } } diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index a454dc99..5ccb3765 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -456,13 +456,13 @@ fn handle_xaddress(field: Cow, xaddress: Cow) -> Result return Err!(e), }; if let Some(tag) = tag { - if &field == DESTINATION { + if field == DESTINATION { let tag_name = DESTINATION_TAG; Ok(Map::from_iter(vec![ (field.to_string(), Value::String(classic_address)), (tag_name.to_string(), Value::Number(tag.into())), ])) - } else if &field == ACCOUNT { + } else if field == ACCOUNT { let tag_name = SOURCE_TAG; Ok(Map::from_iter(vec![ (field.to_string(), Value::String(classic_address)), @@ -548,10 +548,10 @@ pub trait TryFromParser { Self: Sized; } -impl ToString for SerializedType { +impl Display for SerializedType { /// Get the hex representation of the SerializedType bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.0.as_slice()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", hex::encode_upper(self.0.as_slice())) } } diff --git a/src/core/types/vector256.rs b/src/core/types/vector256.rs index bc9a06bc..d1d56128 100644 --- a/src/core/types/vector256.rs +++ b/src/core/types/vector256.rs @@ -5,8 +5,6 @@ use crate::core::types::exceptions::XRPLVectorException; use crate::core::types::hash::Hash256; use crate::core::types::*; use crate::core::BinaryParser; -use alloc::string::String; -use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; @@ -96,10 +94,10 @@ impl TryFrom> for Vector256 { } } -impl ToString for Vector256 { +impl Display for Vector256 { /// Get the hex representation of the Vector256 bytes. - fn to_string(&self) -> String { - hex::encode_upper(self.as_ref()) + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + Display::fmt(&hex::encode_upper(self.as_ref()), f) } } diff --git a/src/ledger/mod.rs b/src/ledger/mod.rs new file mode 100644 index 00000000..ccbc04a0 --- /dev/null +++ b/src/ledger/mod.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use embassy_futures::block_on; + +use crate::{ + asynch::{ + clients::XRPLClient, + ledger::{ + get_fee as async_get_fee, + get_latest_open_ledger_sequence as async_get_latest_open_ledger_sequence, + get_latest_validated_ledger_sequence as async_get_latest_validated_ledger_sequence, + }, + }, + models::XRPAmount, +}; + +pub use crate::asynch::ledger::FeeType; + +pub fn get_latest_validated_ledger_sequence(client: &C) -> Result +where + C: XRPLClient, +{ + block_on(async_get_latest_validated_ledger_sequence(client)) +} + +pub fn get_latest_open_ledger_sequence(client: &C) -> Result +where + C: XRPLClient, +{ + block_on(async_get_latest_open_ledger_sequence(client)) +} + +pub fn get_fee( + client: &C, + max_fee: Option, + fee_type: Option, +) -> Result> +where + C: XRPLClient, +{ + block_on(async_get_fee(client, max_fee, fee_type)) +} diff --git a/src/lib.rs b/src/lib.rs index 98486805..00b87254 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,11 +25,17 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std as alloc; +#[cfg(feature = "account-helpers")] +pub mod account; #[cfg(all(feature = "request-models", feature = "result-models"))] pub mod asynch; +#[cfg(any(feature = "json-rpc", feature = "websocket"))] +pub mod clients; pub mod constants; #[cfg(feature = "core")] pub mod core; +#[cfg(feature = "ledger-helpers")] +pub mod ledger; pub mod macros; #[cfg(any( feature = "ledger-models", @@ -56,10 +62,15 @@ mod _anyhow; ))] mod _serde; -#[cfg(all(feature = "embassy-rt", feature = "tokio-rt"))] -compile_error!("Cannot enable both `embassy-rt` and `tokio-rt` features at the same time."); #[cfg(all( any(feature = "transaction-helpers", feature = "wallet-helpers"), - not(any(feature = "embassy-rt", feature = "tokio-rt")) + not(any( + feature = "tokio-rt", + feature = "embassy-rt", + feature = "actix-rt", + feature = "async-std-rt", + feature = "futures-rt", + feature = "smol-rt" + )) ))] -compile_error!("Cannot enable `transaction-helpers` or `wallet-helpers` without enabling either `embassy-rt` or `tokio-rt`."); +compile_error!("Cannot enable `transaction-helpers` or `wallet-helpers` without enabling a runtime feature (\"*-rt\"). This is required for sleeping between retries internally."); diff --git a/src/models/amount/issued_currency_amount.rs b/src/models/amount/issued_currency_amount.rs index 6c1d183c..90343415 100644 --- a/src/models/amount/issued_currency_amount.rs +++ b/src/models/amount/issued_currency_amount.rs @@ -38,7 +38,7 @@ impl<'a> TryInto for IssuedCurrencyAmount<'a> { impl<'a> PartialOrd for IssuedCurrencyAmount<'a> { fn partial_cmp(&self, other: &Self) -> Option { - self.value.partial_cmp(&other.value) + Some(self.cmp(other)) } } diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 17add31e..dea52afe 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -127,9 +127,7 @@ impl<'a> TryInto> for XRPAmount<'a> { impl<'a> PartialOrd for XRPAmount<'a> { fn partial_cmp(&self, other: &Self) -> Option { - let self_decimal: Decimal = self.clone().try_into().unwrap(); - let other_decimal: Decimal = other.clone().try_into().unwrap(); - Some(self_decimal.cmp(&other_decimal)) + Some(self.cmp(other)) } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 2cf0cad5..836def71 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -209,6 +209,7 @@ impl<'a> LedgerEntry<'a> { } pub trait LedgerEntryError { + #[allow(clippy::result_large_err)] fn _get_field_error(&self) -> Result<(), XRPLLedgerEntryException>; } diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 71b662ed..273d0a95 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -1,4 +1,129 @@ pub mod exceptions; mod multisign; +use core::fmt::Debug; + +use crate::{ + asynch::{ + clients::XRPLAsyncClient, + transaction::{ + autofill as async_autofill, autofill_and_sign as async_autofill_and_sign, + calculate_fee_per_transaction_type as async_calculate_fee_per_transaction_type, + sign_and_submit as async_sign_and_submit, submit as async_submit, + submit_and_wait as async_submit_and_wait, + }, + }, + models::{ + results::{submit::Submit, tx::Tx}, + transactions::Transaction, + Model, XRPAmount, + }, + wallet::Wallet, +}; +use anyhow::Result; +use embassy_futures::block_on; +use serde::{de::DeserializeOwned, Serialize}; +use strum::IntoEnumIterator; + +pub use crate::asynch::transaction::sign; pub use multisign::*; + +pub fn sign_and_submit<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + autofill: bool, + check_fee: bool, +) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: XRPLAsyncClient, +{ + block_on(async_sign_and_submit( + transaction, + client, + wallet, + autofill, + check_fee, + )) +} + +pub fn autofill<'a, 'b, F, T, C>( + transaction: &mut T, + client: &'b C, + signers_count: Option, +) -> Result<()> +where + T: Transaction<'a, F> + Model + Clone, + F: IntoEnumIterator + Serialize + Debug + PartialEq, + C: XRPLAsyncClient, +{ + block_on(async_autofill(transaction, client, signers_count)) +} + +pub fn autofill_and_sign<'a, 'b, T, F, C>( + transaction: &mut T, + client: &'b C, + wallet: &Wallet, + check_fee: bool, +) -> Result<()> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, + C: XRPLAsyncClient, +{ + block_on(async_autofill_and_sign( + transaction, + client, + wallet, + check_fee, + )) +} + +pub fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> +where + F: IntoEnumIterator + Serialize + Debug + PartialEq, + T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + C: XRPLAsyncClient, +{ + block_on(async_submit(transaction, client)) +} + +pub fn submit_and_wait<'a: 'b, 'b, T, F, C>( + transaction: &'b mut T, + client: &C, + wallet: Option<&Wallet>, + check_fee: Option, + autofill: Option, +) -> Result> +where + T: Transaction<'a, F> + Model + Clone + DeserializeOwned + Debug, + F: IntoEnumIterator + Serialize + Debug + PartialEq + Debug + Clone + 'a, + C: XRPLAsyncClient, +{ + block_on(async_submit_and_wait( + transaction, + client, + wallet, + check_fee, + autofill, + )) +} + +pub fn calculate_fee_per_transaction_type<'a, 'b, 'c, T, F, C>( + transaction: &T, + client: Option<&'b C>, + signers_count: Option, +) -> Result> +where + T: Transaction<'a, F>, + F: IntoEnumIterator + Serialize + Debug + PartialEq, + C: XRPLAsyncClient, +{ + block_on(async_calculate_fee_per_transaction_type( + transaction, + client, + signers_count, + )) +} diff --git a/src/wallet/faucet_generation.rs b/src/wallet/faucet_generation.rs new file mode 100644 index 00000000..c35c50e8 --- /dev/null +++ b/src/wallet/faucet_generation.rs @@ -0,0 +1,30 @@ +use super::Wallet; +use crate::asynch::{ + clients::{XRPLAsyncClient, XRPLFaucet}, + wallet::generate_faucet_wallet as async_generate_faucet_wallet, +}; +use alloc::borrow::Cow; +use anyhow::Result; +use embassy_futures::block_on; +use url::Url; + +pub use crate::asynch::wallet::get_faucet_url; + +pub fn generate_faucet_wallet<'a, C>( + client: &C, + wallet: Option, + faucet_host: Option, + usage_context: Option>, + user_agent: Option>, +) -> Result +where + C: XRPLFaucet + XRPLAsyncClient, +{ + block_on(async_generate_faucet_wallet( + client, + wallet, + faucet_host, + usage_context, + user_agent, + )) +} diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 7b84f314..1f367fe7 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,6 +1,6 @@ //! Methods for working with XRPL wallets. -use core::fmt::Display; +mod faucet_generation; use crate::constants::CryptoAlgorithm; use crate::core::addresscodec::classic_address_to_xaddress; @@ -10,8 +10,11 @@ use crate::core::keypairs::derive_keypair; use crate::core::keypairs::exceptions::XRPLKeypairsException; use crate::core::keypairs::generate_seed; use alloc::string::String; +use core::fmt::Display; use zeroize::Zeroize; +pub use faucet_generation::*; + /// The cryptographic keys needed to control an /// XRP Ledger account. /// From f5929ec592731bc09fd48d8af5dec85ee16e6e92 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 9 Sep 2024 18:29:16 +0000 Subject: [PATCH 100/113] fix gh tests --- .cargo-husky/hooks/pre-commit | 10 +++------- src/asynch/wallet/mod.rs | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index eb096f90..bbdf313d 100644 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -3,12 +3,8 @@ 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 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 test --release +cargo test --release --no-default-features --features embassy-rt,core,wallet,models,helpers,websocket,json-rpc cargo clippy --fix --allow-staged cargo doc --no-deps -#cargo audit +cargo audit diff --git a/src/asynch/wallet/mod.rs b/src/asynch/wallet/mod.rs index 0767d3d4..39f2d197 100644 --- a/src/asynch/wallet/mod.rs +++ b/src/asynch/wallet/mod.rs @@ -49,7 +49,6 @@ where for _ in 0..TIMEOUT_SECS { // wait 1 second wait_seconds(1).await; - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; if !is_funded { let balance = check_balance(client, address.into()).await; if balance > starting_balance { From a7838e6bdfeab9dab7fc1af8c80f4896a839d159 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 9 Sep 2024 18:32:42 +0000 Subject: [PATCH 101/113] fix gh tests --- src/wallet/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 1f367fe7..36fa2c36 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,5 +1,6 @@ //! Methods for working with XRPL wallets. +#[cfg(feature = "wallet-helpers")] mod faucet_generation; use crate::constants::CryptoAlgorithm; @@ -13,6 +14,7 @@ use alloc::string::String; use core::fmt::Display; use zeroize::Zeroize; +#[cfg(feature = "wallet-helpers")] pub use faucet_generation::*; /// The cryptographic keys needed to control an From 6ac9ed46085a9152eea953e5ee07740457a30fb9 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Mon, 9 Sep 2024 19:12:41 +0000 Subject: [PATCH 102/113] fix gh tests --- .github/workflows/unit_test.yml | 2 +- Cargo.toml | 5 ++--- src/lib.rs | 9 ++++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 84b268c5..4b360698 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -52,7 +52,7 @@ jobs: name: Build only helpers with: command: build - args: --release --no-default-features --features helpers,tokio-rt + args: --release --no-default-features --features websocket,json-rpc,helpers,tokio-rt - uses: actions-rs/cargo@v1 name: Build only websocket with: diff --git a/Cargo.toml b/Cargo.toml index fb58463e..5d2e5c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ reqwest = { version = "0.12.7", optional = true, features = ["json"] } tokio-tungstenite = { version = "0.23.1", optional = true, features = [ "native-tls", ] } -embassy-futures = { version = "0.1.1", optional = true } +embassy-futures = { version = "0.1.1" } embedded-nal-async = { version = "0.7.1", optional = true } actix-rt = { version = "2.10.0", optional = true } async-std = { version = "1.13.0", optional = true } @@ -135,13 +135,12 @@ transaction-helpers = [ "ledger-models", ] wallet = ["core"] -json-rpc = ["reqwless", "embedded-nal-async"] +json-rpc = ["request-models", "result-models", "reqwless", "embedded-nal-async"] websocket = [ "request-models", "result-models", "futures", "embedded-io-async", - "embassy-futures", "embedded-websocket", ] core = ["utils"] diff --git a/src/lib.rs b/src/lib.rs index 00b87254..53e83cef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,14 @@ extern crate std as alloc; #[cfg(feature = "account-helpers")] pub mod account; -#[cfg(all(feature = "request-models", feature = "result-models"))] +#[cfg(any( + feature = "json-rpc", + feature = "websocket", + feature = "account-helpers", + feature = "ledger-helpers", + feature = "transaction-helpers", + feature = "wallet-helpers" +))] pub mod asynch; #[cfg(any(feature = "json-rpc", feature = "websocket"))] pub mod clients; From 51d1bf7322c4a9bcd0650351b1dc4ccf435b1c79 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 10 Sep 2024 19:17:00 +0000 Subject: [PATCH 103/113] add sign_and_submit example --- examples/std/Cargo.toml | 7 +++- .../bin/asynch/transaction/sign_and_submit.rs | 40 +++++++++++++++++++ .../src/bin/transaction/sign_transaction.rs | 32 ++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 examples/std/src/bin/asynch/transaction/sign_and_submit.rs diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index fc44fbb8..043947ff 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -41,6 +41,11 @@ path = "src/bin/clients/json_rpc.rs" required-features = [] [[bin]] -name = "sign_request" +name = "sign_transaction" path = "src/bin/transaction/sign_transaction.rs" required-features = [] + +[[bin]] +name = "sign_and_submit" +path = "src/bin/asynch/transaction/sign_and_submit.rs" +required-features = [] diff --git a/examples/std/src/bin/asynch/transaction/sign_and_submit.rs b/examples/std/src/bin/asynch/transaction/sign_and_submit.rs new file mode 100644 index 00000000..f05dc6d5 --- /dev/null +++ b/examples/std/src/bin/asynch/transaction/sign_and_submit.rs @@ -0,0 +1,40 @@ +use xrpl::asynch::clients::AsyncJsonRpcClient; +use xrpl::asynch::transaction::sign_and_submit; +use xrpl::asynch::wallet::generate_faucet_wallet; +use xrpl::models::transactions::account_set::AccountSet; + +#[tokio::main] +async fn main() { + let client = AsyncJsonRpcClient::connect("https://testnet.xrpl-labs.com/".parse().unwrap()); + // Create a new wallet we can use to sign the transaction + let wallet = generate_faucet_wallet(&client, None, None, None, None) + .await + .unwrap(); + // Define the transaction we want to sign + let mut account_set = AccountSet::new( + wallet.classic_address.clone().into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // example.com + None, + None, + None, + None, + None, + None, + ); + println!("AccountSet transaction before signing: {:?}", account_set); + // Sign and submit the transaction + sign_and_submit(&mut account_set, &client, &wallet, true, true) + .await + .unwrap(); + println!("AccountSet transaction after signing: {:?}", account_set); +} diff --git a/examples/std/src/bin/transaction/sign_transaction.rs b/examples/std/src/bin/transaction/sign_transaction.rs index 170c7df8..8ffaa245 100644 --- a/examples/std/src/bin/transaction/sign_transaction.rs +++ b/examples/std/src/bin/transaction/sign_transaction.rs @@ -1,4 +1,32 @@ -// TODO: add as soon as `sign` is implemented +use xrpl::asynch::transaction::sign; +use xrpl::models::transactions::account_set::AccountSet; +use xrpl::wallet::Wallet; + fn main() { - todo!() + // Create a new wallet we can use to sign the transaction + let wallet = Wallet::create(None).unwrap(); + // Define the transaction we want to sign + let mut account_set = AccountSet::new( + wallet.classic_address.clone().into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some("6578616d706c652e636f6d".into()), // example.com + None, + None, + None, + None, + None, + None, + ); + println!("AccountSet transaction before signing: {:?}", account_set); + sign(&mut account_set, &wallet, false).unwrap(); + println!("AccountSet transaction after signing: {:?}", account_set); } From 4cb5a7464375f4d54e9c69334ab868245182b8eb Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Wed, 11 Sep 2024 10:49:27 +0000 Subject: [PATCH 104/113] always serialize flags --- src/_serde/mod.rs | 16 +-- src/asynch/transaction/mod.rs | 6 +- src/models/flag_collection.rs | 14 +++ src/models/transactions/account_delete.rs | 6 +- src/models/transactions/account_set.rs | 4 +- src/models/transactions/check_cancel.rs | 6 +- src/models/transactions/check_cash.rs | 6 +- src/models/transactions/check_create.rs | 6 +- src/models/transactions/deposit_preauth.rs | 6 +- src/models/transactions/escrow_cancel.rs | 6 +- src/models/transactions/escrow_create.rs | 6 +- src/models/transactions/escrow_finish.rs | 6 +- src/models/transactions/mod.rs | 106 ++++++++++-------- .../transactions/nftoken_accept_offer.rs | 6 +- src/models/transactions/nftoken_burn.rs | 6 +- .../transactions/nftoken_cancel_offer.rs | 6 +- .../transactions/nftoken_create_offer.rs | 6 +- src/models/transactions/nftoken_mint.rs | 2 +- src/models/transactions/offer_cancel.rs | 6 +- src/models/transactions/offer_create.rs | 4 +- src/models/transactions/payment.rs | 6 +- .../transactions/payment_channel_claim.rs | 4 +- .../transactions/payment_channel_create.rs | 6 +- .../transactions/payment_channel_fund.rs | 6 +- .../pseudo_transactions/enable_amendment.rs | 2 +- .../pseudo_transactions/set_fee.rs | 4 +- .../pseudo_transactions/unl_modify.rs | 4 +- src/models/transactions/set_regular_key.rs | 6 +- src/models/transactions/signer_list_set.rs | 5 +- src/models/transactions/ticket_create.rs | 6 +- src/models/transactions/trust_set.rs | 2 +- 31 files changed, 156 insertions(+), 125 deletions(-) diff --git a/src/_serde/mod.rs b/src/_serde/mod.rs index fbba3413..3b314bfe 100644 --- a/src/_serde/mod.rs +++ b/src/_serde/mod.rs @@ -62,19 +62,19 @@ pub(crate) mod txn_flags { use crate::models::FlagCollection; use strum::IntoEnumIterator; - pub fn serialize(flags: &Option>, s: S) -> Result + pub fn serialize(flags: &FlagCollection, s: S) -> Result where - F: Serialize + IntoEnumIterator, + F: Serialize + IntoEnumIterator + Debug, S: Serializer, { - if let Some(f) = flags { - serialize_flag(f, s) - } else { + if flags.0.is_empty() { s.serialize_u32(0) + } else { + serialize_flag(flags, s) } } - pub fn deserialize<'de, F, D>(d: D) -> Result>, D::Error> + pub fn deserialize<'de, F, D>(d: D) -> Result, D::Error> where F: Serialize + IntoEnumIterator + Debug, D: Deserializer<'de>, @@ -83,9 +83,9 @@ pub(crate) mod txn_flags { match flags_vec_result { Ok(flags_vec) => { if flags_vec.0.is_empty() { - Ok(None) + Ok(FlagCollection::::default()) } else { - Ok(Some(flags_vec)) + Ok(flags_vec) } } Err(error) => Err(error), diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index a0d98b77..437f390f 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -573,8 +573,8 @@ mod test_sign { ); sign(&mut tx, &wallet, false).unwrap(); let expected_signature: Cow = - "B310792432B0242C2542C2B46CA234C87F4AE3FFC33226797AF72A92D9295ED20BD05A85D0\ - C13760B653AE9B8C0D74B9BBD310B09524F63B41D1776E7F2BB609" + "C3F435CFBFAE996FE297F3A71BEAB68FF5322CBF039E41A9615BC48A59FB4EC\ + 5A55F8D4EC0225D47056E02ECCCDF7E8FF5F8B7FAA1EBBCBF7D0491FCB2D98807" .into(); let actual_signature = tx.get_common_fields().txn_signature.as_ref().unwrap(); assert_eq!(expected_signature, *actual_signature); @@ -582,7 +582,7 @@ mod test_sign { #[tokio::test] async fn test_autofill_and_sign() { - let client = AsyncJsonRpcClient::connect("wss://testnet.xrpl-labs.com/".parse().unwrap()); + let client = AsyncJsonRpcClient::connect("https://testnet.xrpl-labs.com/".parse().unwrap()); let wallet = generate_faucet_wallet(&client, None, None, None, None) .await .unwrap(); diff --git a/src/models/flag_collection.rs b/src/models/flag_collection.rs index 38d35bb0..17331e26 100644 --- a/src/models/flag_collection.rs +++ b/src/models/flag_collection.rs @@ -83,6 +83,20 @@ where } } +impl core::fmt::Display for FlagCollection +where + T: IntoEnumIterator + Serialize, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut flags = 0; + for flag in &self.0 { + let flag_as_u32 = flag_to_u32(flag).unwrap(); + flags |= flag_as_u32; + } + write!(f, "{}", flags) + } +} + fn flag_to_u32(flag: &T) -> Result where T: Serialize, diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 4fbae6cc..4665611b 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -5,11 +5,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; -use crate::models::NoFlags; use crate::models::{ transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::{Memo, Signer}; @@ -80,7 +80,7 @@ impl<'a> AccountDelete<'a> { transaction_type: TransactionType::AccountDelete, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -116,7 +116,7 @@ mod test_serde { "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".into(), Some(13), ); - let default_json_str = r#"{"Account":"rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm","TransactionType":"AccountDelete","Fee":"2000000","Sequence":2470665,"Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","DestinationTag":13}"#; + let default_json_str = r#"{"Account":"rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm","TransactionType":"AccountDelete","Fee":"2000000","Flags":0,"Sequence":2470665,"Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","DestinationTag":13}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index b8560c92..60558e2a 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -314,7 +314,7 @@ impl<'a> AccountSet<'a> { transaction_type: TransactionType::AccountSet, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, @@ -573,7 +573,7 @@ mod tests { None, None, ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"AccountSet","Fee":"12","Sequence":5,"Domain":"6578616D706C652E636F6D","MessageKey":"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB","SetFlag":5}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"AccountSet","Fee":"12","Flags":0,"Sequence":5,"Domain":"6578616D706C652E636F6D","MessageKey":"03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB","SetFlag":5}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index ae7026d1..13c5a5ac 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -5,11 +5,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; -use crate::models::NoFlags; use crate::models::{ transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::{Memo, Signer}; @@ -74,7 +74,7 @@ impl<'a> CheckCancel<'a> { transaction_type: TransactionType::CheckCancel, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -108,7 +108,7 @@ mod tests { None, "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0".into(), ); - let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCancel","Fee":"12","CheckID":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0"}"#; + let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCancel","Fee":"12","Flags":0,"CheckID":"49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index dd73dedc..3f4fdab6 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -7,12 +7,12 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::{exceptions::XRPLCheckCashException, CommonFields}; -use crate::models::NoFlags; use crate::models::{ amount::Amount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; /// Cancels an unredeemed Check, removing it from the ledger without /// sending any money. The source or the destination of the check can @@ -107,7 +107,7 @@ impl<'a> CheckCash<'a> { transaction_type: TransactionType::CheckCash, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -180,7 +180,7 @@ mod tests { Some("100000000".into()), None, ); - let default_json_str = r#"{"Account":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","TransactionType":"CheckCash","Fee":"12","CheckID":"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334","Amount":"100000000"}"#; + let default_json_str = r#"{"Account":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","TransactionType":"CheckCash","Fee":"12","Flags":0,"CheckID":"838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334","Amount":"100000000"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 171139a0..9d5fe7fc 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -6,12 +6,12 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; -use crate::models::NoFlags; use crate::models::{ amount::Amount, transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::{Memo, Signer}; @@ -89,7 +89,7 @@ impl<'a> CheckCreate<'a> { transaction_type: TransactionType::CheckCreate, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -131,7 +131,7 @@ mod tests { Some(570113521), Some("6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B".into()), ); - let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCreate","Fee":"12","Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"}"#; + let default_json_str = r#"{"Account":"rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo","TransactionType":"CheckCreate","Fee":"12","Flags":0,"Destination":"rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy","SendMax":"100000000","DestinationTag":1,"Expiration":570113521,"InvoiceID":"6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index ad19e4eb..d61dd999 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -7,11 +7,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::{exceptions::XRPLDepositPreauthException, CommonFields}; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; /// A DepositPreauth transaction gives another account pre-approval /// to deliver payments to the sender of this transaction. @@ -97,7 +97,7 @@ impl<'a> DepositPreauth<'a> { transaction_type: TransactionType::DepositPreauth, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -168,7 +168,7 @@ mod tests { Some("rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de".into()), None, ); - let default_json_str = r#"{"Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","TransactionType":"DepositPreauth","Fee":"10","Sequence":2,"Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"}"#; + let default_json_str = r#"{"Account":"rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8","TransactionType":"DepositPreauth","Fee":"10","Flags":0,"Sequence":2,"Authorize":"rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 696084ff..6abda066 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -6,11 +6,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::CommonFields; -use crate::models::NoFlags; use crate::models::{ transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::{Memo, Signer}; @@ -74,7 +74,7 @@ impl<'a> EscrowCancel<'a> { transaction_type: TransactionType::EscrowCancel, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -110,7 +110,7 @@ mod tests { "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn".into(), 7, ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCancel","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCancel","Flags":0,"Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 134c8659..006ddafe 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -7,11 +7,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::{exceptions::XRPLEscrowCreateException, CommonFields}; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; /// Creates an Escrow, which requests XRP until the escrow process either finishes or is canceled. /// @@ -123,7 +123,7 @@ impl<'a> EscrowCreate<'a> { transaction_type: TransactionType::EscrowCreate, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -211,7 +211,7 @@ mod tests { Some(23480), Some(533171558), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCreate","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowCreate","Flags":0,"SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","DestinationTag":23480,"CancelAfter":533257958,"FinishAfter":533171558,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index eada6dd2..268a973a 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::transactions::exceptions::XRPLEscrowFinishException; -use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -108,7 +108,7 @@ impl<'a> EscrowFinish<'a> { transaction_type: TransactionType::EscrowFinish, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -193,7 +193,7 @@ mod tests { ), Some("A0028000".into()), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Flags":0,"Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; // Serialize let default_json_value: Value = serde_json::from_str(default_json_str).unwrap(); // let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index bb00ea68..25e4dd06 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -37,6 +37,7 @@ use alloc::vec::Vec; use anyhow::Result; use core::fmt::Debug; use derive_new::new; +use exceptions::XRPLTransactionException; use serde::de::DeserializeOwned; use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; @@ -132,8 +133,8 @@ where pub fee: Option>, /// Set of bit-flags for this transaction. #[serde(with = "txn_flags")] - #[serde(default = "optional_flag_collection_default")] - pub flags: Option>, + #[serde(default = "flag_collection_default")] + pub flags: FlagCollection, /// Highest ledger index this transaction can appear in. /// Specifying this field places a strict upper limit on how long /// the transaction can wait to be validated or rejected. @@ -198,7 +199,7 @@ where transaction_type, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, network_id, @@ -232,10 +233,7 @@ where T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, { fn has_flag(&self, flag: &T) -> bool { - match &self.flags { - Some(flag_collection) => flag_collection.0.contains(flag), - None => false, - } + self.flags.0.contains(flag) } fn get_transaction_type(&self) -> TransactionType { @@ -251,11 +249,11 @@ where } } -fn optional_flag_collection_default() -> Option> +fn flag_collection_default() -> FlagCollection where T: IntoEnumIterator + Serialize + core::fmt::Debug, { - None + FlagCollection::::default() } serde_with_tag! { @@ -315,24 +313,31 @@ where } } + fn is_signed(&self) -> bool { + self.get_common_fields().txn_signature.is_some() + && self.get_common_fields().signing_pub_key.is_some() + } + /// Hashes the Transaction object as the ledger does. Only valid for signed /// Transaction objects. fn get_hash(&self) -> Result> where Self: Serialize + DeserializeOwned + Debug + Clone, { - // if !self.is_signed() { - // return Err!(XRPLTransactionException::TxMustBeSigned); - // } + if self.get_common_fields().txn_signature.is_none() + && self.get_common_fields().signers.is_none() + { + return Err!(XRPLTransactionException::TxMustBeSigned); + } let prefix = format!("{:X}", TRANSACTION_HASH_PREFIX); - let encoded_tx = encode(self)?; - let encoded = prefix + &encoded_tx; - let encoded_bytes = match hex::decode(&encoded) { + let tx_hex = encode(self)?; + let tx_hex = prefix + &tx_hex; + let tx_bytes = match hex::decode(&tx_hex) { Ok(bytes) => bytes, Err(e) => return Err!(e), }; let mut hasher = Sha512::new(); - hasher.update(&encoded_bytes); + hasher.update(&tx_bytes); let hash = hasher.finalize(); let hex_string = hex::encode_upper(hash); let result = hex_string[..64].to_string(); @@ -363,39 +368,50 @@ pub enum Flag { #[cfg(test)] mod test_tx_common_fields { use super::*; - use crate::{ - asynch::transaction::sign, - models::{amount::IssuedCurrencyAmount, transactions::offer_create::OfferCreate}, - wallet::Wallet, - }; + use account_set::AccountSet; + use alloc::dbg; + use offer_create::OfferCreate; #[tokio::test] async fn test_get_hash() { - let mut wallet = Wallet::new("sEdT7wHTCLzDG7ueaw4hroSTBvH7Mk5", 0).unwrap(); - let mut txn = OfferCreate::new( - "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), - None, - Some("10".into()), - Some(FlagCollection::default()), - Some(16409087), - None, - Some(16409064), - None, - None, - None, - "13100000".into(), - IssuedCurrencyAmount::new( - "USD".into(), - "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3".into(), - "10".into(), - ) - .into(), - None, - None, - ); - sign(&mut txn, &mut wallet, false).unwrap(); - let expected_hash = "39530980D3D6F848E619BF05A57988D42A62075289B99C5728CBDE0D1710284B"; + let txn_json = r#"{ + "Account": "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3", + "Fee": "10", + "Flags": 0, + "LastLedgerSequence": 16409087, + "Sequence": 16409064, + "SigningPubKey": "ED93BFA583E83331E9DC498DE4558CE4861ACFAB9385EBBC43BC56A0D9845A1DF2", + "TakerGets": "13100000", + "TakerPays": { + "currency": "USD", + "issuer": "rLyttXLh7Ttca9CMUaD3exVoXY2fn2zwj3", + "value": "10" + }, + "TransactionType": "OfferCreate", + "TxnSignature": "71135999783658A0CB4EBCF02E59ACD94C4D06D5BF909E05E6B97588155482BBA598535AD4728ACA1F90C4DE73FFC741B0A6AB87141BDA8BCC2F2DF9CD8C3703" + }"#; + let expected_hash = "66F3D6158CAB6E53405F8C264DB39F07D8D0454433A63DDFB98218ED1BC99B60"; + let txn: OfferCreate = serde_json::from_str(txn_json).unwrap(); assert_eq!(&txn.get_hash().unwrap(), expected_hash); } + + #[test] + fn test_txn_hash() { + let tx_json_str = r#"{ + "Account": "rEbY5Tr5B6AjyjuVRhajpnvCWLGkYk5z6", + "Domain": "6578616d706c652e636f6d", + "Fee": "10", + "Flags": 0, + "LastLedgerSequence": 596447, + "Sequence": 596427, + "SigningPubKey": "EDAF73A0E6745EA9C17A2F4EB7043134A055213116CFF6F7888BBFF557B002874F", + "TransactionType": "AccountSet", + "TxnSignature": "8666A7E6AF0D6A4B4F19F25D315FA1C31D132FB2E974686C415D5499D43710384FF851C75CCC4E57972DE5C5354289F574B2F604B6AF15E2DADA6BB9F1330A07" + }"#; + let expected_hash = "5B765D6C6058CF54F5DBF6230A7F51E23295004FCC043660A77D73AA8537737B"; + let tx: AccountSet = serde_json::from_str(tx_json_str).unwrap(); + dbg!(&tx); + assert_eq!(tx.get_hash().unwrap(), expected_hash); + } } diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 4a26e8de..c82219ae 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -9,12 +9,12 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::exceptions::XRPLNFTokenAcceptOfferException; -use crate::models::NoFlags; use crate::models::{ amount::Amount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -136,7 +136,7 @@ impl<'a> NFTokenAcceptOffer<'a> { transaction_type: TransactionType::NFTokenAcceptOffer, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -248,7 +248,7 @@ mod tests { None, None, ); - let default_json_str = r#"{"Account":"r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG","TransactionType":"NFTokenAcceptOffer","Fee":"12","LastLedgerSequence":75447550,"Memos":[{"Memo":{"MemoData":"61356534373538372D633134322D346663382D616466362D393666383562356435386437","MemoFormat":null,"MemoType":null}}],"Sequence":68549302,"NFTokenSellOffer":"68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"}"#; + let default_json_str = r#"{"Account":"r9spUPhPBfB6kQeF6vPhwmtFwRhBh2JUCG","TransactionType":"NFTokenAcceptOffer","Fee":"12","Flags":0,"LastLedgerSequence":75447550,"Memos":[{"Memo":{"MemoData":"61356534373538372D633134322D346663382D616466362D393666383562356435386437","MemoFormat":null,"MemoType":null}}],"Sequence":68549302,"NFTokenSellOffer":"68CD1F6F906494EA08C9CB5CAFA64DFA90D4E834B7151899B73231DE5A0C3B77"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index dc3458af..44c1aa7a 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -82,7 +82,7 @@ impl<'a> NFTokenBurn<'a> { transaction_type: TransactionType::NFTokenBurn, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -118,7 +118,7 @@ mod tests { "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65".into(), Some("rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into()), ); - let default_json_str = r#"{"Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","TransactionType":"NFTokenBurn","Fee":"10","NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"}"#; + let default_json_str = r#"{"Account":"rNCFjv8Ek5oDrNiMJ3pw6eLLFtMjZLJnf2","TransactionType":"NFTokenBurn","Fee":"10","Flags":0,"NFTokenID":"000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65","Owner":"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index a0043370..6fb637e6 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -7,11 +7,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::exceptions::XRPLNFTokenCancelOfferException; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -103,7 +103,7 @@ impl<'a> NFTokenCancelOffer<'a> { transaction_type: TransactionType::NFTokenCancelOffer, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -174,7 +174,7 @@ mod tests { None, vec!["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D".into()], ); - let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"NFTokenCancelOffer","NFTokenOffers":["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"]}"#; + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"NFTokenCancelOffer","Flags":0,"NFTokenOffers":["9C92E061381C1EF37A8CDE0E8FC35188BFC30B1883825042A64309AC09F4C36D"]}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index e8858896..a9315866 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -195,7 +195,7 @@ impl<'a> NFTokenCreateOffer<'a> { transaction_type: TransactionType::NFTokenCreateOffer, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, @@ -309,14 +309,14 @@ mod test_nftoken_create_offer_error { Some("rLSn6Z3T8uCxbcd1oxwfGQN1Fdn5CyGujK".into()), ); let sell_flag = vec![NFTokenCreateOfferFlag::TfSellOffer]; - nftoken_create_offer.common_fields.flags = Some(sell_flag.into()); + nftoken_create_offer.common_fields.flags = sell_flag.into(); assert_eq!( nftoken_create_offer.validate().unwrap_err().to_string().as_str(), "The optional field `owner` is not allowed to be defined for NFToken sell offers. For more information see: " ); - nftoken_create_offer.common_fields.flags = None; + nftoken_create_offer.common_fields.flags = FlagCollection::default(); nftoken_create_offer.owner = None; assert_eq!( diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index 6c803ab1..1c491105 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -198,7 +198,7 @@ impl<'a> NFTokenMint<'a> { transaction_type: TransactionType::NFTokenMint, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index 8416573a..2fd1534b 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -76,7 +76,7 @@ impl<'a> OfferCancel<'a> { transaction_type: TransactionType::OfferCancel, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -110,7 +110,7 @@ mod tests { None, 6, ); - let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCancel","Fee":"12","LastLedgerSequence":7108629,"Sequence":7,"OfferSequence":6}"#; + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCancel","Fee":"12","Flags":0,"LastLedgerSequence":7108629,"Sequence":7,"OfferSequence":6}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index fb973671..b822eb5d 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -122,7 +122,7 @@ impl<'a> OfferCreate<'a> { transaction_type: TransactionType::OfferCreate, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, @@ -230,7 +230,7 @@ mod tests { None, None, ); - let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCreate","Fee":"12","LastLedgerSequence":7108682,"Sequence":8,"TakerGets":"6000000","TakerPays":{"currency":"GKO","issuer":"ruazs5h1qEsqpke88pcqnaseXdm6od2xc","value":"2"}}"#; + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"OfferCreate","Fee":"12","Flags":0,"LastLedgerSequence":7108682,"Sequence":8,"TakerGets":"6000000","TakerPays":{"currency":"GKO","issuer":"ruazs5h1qEsqpke88pcqnaseXdm6od2xc","value":"2"}}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 6cd0dba4..ee13ce4b 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -220,7 +220,7 @@ impl<'a> Payment<'a> { transaction_type: TransactionType::Payment, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, @@ -331,14 +331,14 @@ mod test_payment_error { None, None, ); - payment.common_fields.flags = Some(vec![PaymentFlag::TfPartialPayment].into()); + payment.common_fields.flags = vec![PaymentFlag::TfPartialPayment].into(); assert_eq!( payment.validate().unwrap_err().to_string().as_str(), "For the flag `TfPartialPayment` to be set it is required to define the field `send_max`. For more information see: " ); - payment.common_fields.flags = None; + payment.common_fields.flags = FlagCollection::default(); payment.deliver_min = Some(Amount::XRPAmount("99999".into())); assert_eq!( diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index 229c0511..acb1aa8e 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -132,7 +132,7 @@ impl<'a> PaymentChannelClaim<'a> { transaction_type: TransactionType::PaymentChannelClaim, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, @@ -175,7 +175,7 @@ mod tests { Some("32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A".into()), Some("30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B".into()), ); - let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"PaymentChannelClaim","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; + let default_json_str = r#"{"Account":"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX","TransactionType":"PaymentChannelClaim","Flags":0,"Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Balance":"1000000","Amount":"1000000","Signature":"30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B","PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 43ce7eea..15b8cf95 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -98,7 +98,7 @@ impl<'a> PaymentChannelCreate<'a> { transaction_type: TransactionType::PaymentChannelCreate, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -142,7 +142,7 @@ mod tests { Some(533171558), Some(23480), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelCreate","SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SettleDelay":86400,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","CancelAfter":533171558,"DestinationTag":23480}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelCreate","Flags":0,"SourceTag":11747,"Amount":"10000","Destination":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SettleDelay":86400,"PublicKey":"32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A","CancelAfter":533171558,"DestinationTag":23480}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index d46aab24..69eff5a9 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -4,12 +4,12 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -87,7 +87,7 @@ impl<'a> PaymentChannelFund<'a> { transaction_type: TransactionType::PaymentChannelFund, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -127,7 +127,7 @@ mod tests { "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198".into(), Some(543171558), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelFund","Amount":"200000","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Expiration":543171558}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"PaymentChannelFund","Flags":0,"Amount":"200000","Channel":"C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198","Expiration":543171558}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index bb309c97..be7f2f32 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -91,7 +91,7 @@ impl<'a> EnableAmendment<'a> { transaction_type: TransactionType::EnableAmendment, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index c573fa7f..cb412b84 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -6,11 +6,11 @@ use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; use crate::models::transactions::{CommonFields, Memo, Signer}; -use crate::models::NoFlags; use crate::models::{ transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; /// See SetFee: /// `` @@ -78,7 +78,7 @@ impl<'a> SetFee<'a> { transaction_type: TransactionType::SetFee, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index e3c081a5..9d6e2035 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -7,12 +7,12 @@ use serde_with::skip_serializing_none; use strum_macros::{AsRefStr, Display, EnumIter}; use crate::models::transactions::{CommonFields, Memo, Signer}; -use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, transactions::{Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; #[derive( Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter, @@ -85,7 +85,7 @@ impl<'a> UNLModify<'a> { transaction_type: TransactionType::UNLModify, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index f3ee7d06..020b1032 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -80,7 +80,7 @@ impl<'a> SetRegularKey<'a> { transaction_type: TransactionType::SetRegularKey, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -114,7 +114,7 @@ mod tests { None, Some("rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD".into()), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SetRegularKey","Fee":"12","RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SetRegularKey","Fee":"12","Flags":0,"RegularKey":"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 4538fbaa..8bd09ece 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -8,6 +8,7 @@ use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::transactions::exceptions::XRPLSignerListSetException; +use crate::models::FlagCollection; use crate::models::NoFlags; use crate::models::{ amount::XRPAmount, @@ -193,7 +194,7 @@ impl<'a> SignerListSet<'a> { transaction_type: TransactionType::SignerListSet, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -398,7 +399,7 @@ mod tests { SignerEntry::new("raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n".to_string(), 1), ]), ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SignerListSet","Fee":"12","SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"SignerListSet","Fee":"12","Flags":0,"SignerQuorum":3,"SignerEntries":[{"SignerEntry":{"Account":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW","SignerWeight":2}},{"SignerEntry":{"Account":"rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v","SignerWeight":1}},{"SignerEntry":{"Account":"raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n","SignerWeight":1}}]}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 1dc6e9a8..c2b7df3c 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -4,11 +4,11 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use crate::models::amount::XRPAmount; -use crate::models::NoFlags; use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, Model, }; +use crate::models::{FlagCollection, NoFlags}; use super::CommonFields; @@ -74,7 +74,7 @@ impl<'a> TicketCreate<'a> { transaction_type: TransactionType::TicketCreate, account_txn_id, fee, - flags: None, + flags: FlagCollection::default(), last_ledger_sequence, memos, sequence, @@ -108,7 +108,7 @@ mod tests { None, 10, ); - let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"TicketCreate","Fee":"10","Sequence":381,"TicketCount":10}"#; + let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"TicketCreate","Fee":"10","Flags":0,"Sequence":381,"TicketCount":10}"#; // Serialize let default_json_value = serde_json::to_value(default_json_str).unwrap(); let serialized_string = serde_json::to_string(&default_txn).unwrap(); diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index bd8287f0..c4914dc2 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -112,7 +112,7 @@ impl<'a> TrustSet<'a> { transaction_type: TransactionType::TrustSet, account_txn_id, fee, - flags, + flags: flags.unwrap_or_default(), last_ledger_sequence, memos, sequence, From 81d5d2a0023945bb21a204879bc85931e5ebf35f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 17:28:36 +0000 Subject: [PATCH 105/113] set version for embedded-websocket --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5d2e5c9d..5e9a6336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ embedded-io-async = { version = "0.6.1", optional = true } futures = { version = "0.3.30", optional = true } tokio = { version = "1.0", optional = true } embassy-time = { version = "0.3.2", optional = true } -embedded-websocket = { git = "https://github.com/LimpidCrypto/embedded-websocket", branch = "embedded-io-async", optional = true, default-features = false, features = [ +embedded-websocket = { git = "https://github.com/LimpidCrypto/embedded-websocket", version = "0.9.3", branch = "embedded-io-async", optional = true, default-features = false, features = [ "embedded-io-async", ] } reqwless = { version = "0.12.1", optional = true } From a19e0f96dac7e05748f8201cc0518a7227c36864 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 17:29:37 +0000 Subject: [PATCH 106/113] validate before submitting --- src/asynch/transaction/mod.rs | 3 ++- src/transaction/mod.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs index 437f390f..daaac60e 100644 --- a/src/asynch/transaction/mod.rs +++ b/src/asynch/transaction/mod.rs @@ -167,9 +167,10 @@ where pub async fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> where F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, C: XRPLAsyncClient, { + transaction.validate()?; let txn_blob = encode(transaction)?; let req = Submit::new(None, txn_blob.into(), None); let res = client.request(req.into()).await?; diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 273d0a95..c5afd560 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -84,7 +84,7 @@ where pub fn submit<'a, T, F, C>(transaction: &T, client: &C) -> Result> where F: IntoEnumIterator + Serialize + Debug + PartialEq, - T: Transaction<'a, F> + Serialize + DeserializeOwned + Clone + Debug, + T: Transaction<'a, F> + Model + Serialize + DeserializeOwned + Clone + Debug, C: XRPLAsyncClient, { block_on(async_submit(transaction, client)) From 6c480aca7350cd542c6afb73f905aed1df4ca02b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 17:52:31 +0000 Subject: [PATCH 107/113] update package --- Cargo.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e9a6336..d88128a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,15 @@ [package] name = "xrpl-rust" -version = "0.2.0" -edition = "2018" -authors = ["Tanveer Wahid "] +version = "0.3.0" +edition = "2021" +authors = [ + "Tanveer Wahid ", + "Steffen Konermann ", +] description = "A 100% Rust library to interact with the XRPL" readme = "README.md" license = "ISC" -repository = "https://github.com/589labs/xrpl-rust" +repository = "https://github.com/sephynox/xrpl-rust" keywords = ["xrpl", "no_std"] categories = ["no-std"] From 537e7bcff633d1b2c5bc15207b85a566fa8256ef Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 17:58:25 +0000 Subject: [PATCH 108/113] fix tests by adding rt-multi-thread feature for tokio --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d88128a1..3473ab05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ embassy-sync = "0.6.0" url = { version = "2.2.2", default-features = false } embedded-io-async = { version = "0.6.1", optional = true } futures = { version = "0.3.30", optional = true } -tokio = { version = "1.0", optional = true } +tokio = { version = "1.0", optional = true, features = ["rt-multi-thread"] } embassy-time = { version = "0.3.2", optional = true } embedded-websocket = { git = "https://github.com/LimpidCrypto/embedded-websocket", version = "0.9.3", branch = "embedded-io-async", optional = true, default-features = false, features = [ "embedded-io-async", From 55db1df0c35e96b2a4083029e5d93354c3eed53a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 18:03:25 +0000 Subject: [PATCH 109/113] fix tests by enabling time feature for tokio --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3473ab05..da23c293 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -166,7 +166,7 @@ std = [ "futures/std", ] # runtimes -tokio-rt = ["tokio"] +tokio-rt = ["tokio/time"] embassy-rt = ["dep:embassy-time"] actix-rt = ["dep:actix-rt"] async-std-rt = ["dep:async-std"] From cb7a80ca51c6f418de60973fda183a94a56c828d Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Thu, 12 Sep 2024 19:17:34 +0000 Subject: [PATCH 110/113] use embedded-websocket-embedded-io instead of embedded-websocket --- Cargo.toml | 6 +++--- src/asynch/clients/websocket/_no_std.rs | 4 ++-- src/asynch/clients/websocket/exceptions.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da23c293..c6e48271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ embedded-io-async = { version = "0.6.1", optional = true } futures = { version = "0.3.30", optional = true } tokio = { version = "1.0", optional = true, features = ["rt-multi-thread"] } embassy-time = { version = "0.3.2", optional = true } -embedded-websocket = { git = "https://github.com/LimpidCrypto/embedded-websocket", version = "0.9.3", branch = "embedded-io-async", optional = true, default-features = false, features = [ +embedded-websocket-embedded-io = { version = "0.1.0", optional = true, default-features = false, features = [ "embedded-io-async", ] } reqwless = { version = "0.12.1", optional = true } @@ -144,7 +144,7 @@ websocket = [ "result-models", "futures", "embedded-io-async", - "embedded-websocket", + "embedded-websocket-embedded-io", ] core = ["utils"] utils = [] @@ -159,7 +159,7 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", - "embedded-websocket/std", + "embedded-websocket-embedded-io/std", "reqwest", "tokio", "tokio-tungstenite", diff --git a/src/asynch/clients/websocket/_no_std.rs b/src/asynch/clients/websocket/_no_std.rs index 97bb92b6..306dccbd 100644 --- a/src/asynch/clients/websocket/_no_std.rs +++ b/src/asynch/clients/websocket/_no_std.rs @@ -8,7 +8,7 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; -use embedded_websocket::{ +use embedded_websocket_embedded_io::{ framer_async::{Framer, ReadResult}, Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; @@ -103,7 +103,7 @@ where .await { match error { - // FramerError::WebSocket(embedded_websocket::Error::HttpResponseCodeInvalid( + // FramerError::WebSocket(embedded_websocket_embedded_io::Error::HttpResponseCodeInvalid( // Some(308), // )) => (), error => return Err!(XRPLWebsocketException::from(error)), diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index f659efb3..491811e0 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -3,7 +3,7 @@ use core::str::Utf8Error; #[cfg(all(feature = "websocket", not(feature = "std")))] use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; #[cfg(all(feature = "websocket", not(feature = "std")))] -use embedded_websocket::framer_async::FramerError; +use embedded_websocket_embedded_io::framer_async::FramerError; use thiserror_no_std::Error; #[derive(Debug, Error)] @@ -19,7 +19,7 @@ pub enum XRPLWebsocketException { HttpHeader, #[cfg(all(feature = "websocket", not(feature = "std")))] #[error("Websocket error: {0:?}")] - WebSocket(embedded_websocket::Error), + WebSocket(embedded_websocket_embedded_io::Error), #[error("Disconnected")] Disconnected, #[error("Read buffer is too small (size: {0:?})")] From 31198726aa3dfa62a929a86c63815edb62cf63f8 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 13 Sep 2024 07:35:22 +0000 Subject: [PATCH 111/113] change version back to 0.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c6e48271..d977acca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xrpl-rust" -version = "0.3.0" +version = "0.2.0" edition = "2021" authors = [ "Tanveer Wahid ", From 3506bc5482f58ab8ec703bd91f45853b6b9099bd Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 13 Sep 2024 08:02:02 +0000 Subject: [PATCH 112/113] only include src and Cargo.toml in build --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d977acca..8b8b9969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ readme = "README.md" license = "ISC" repository = "https://github.com/sephynox/xrpl-rust" +include = ["src/**/*", "Cargo.toml"] + keywords = ["xrpl", "no_std"] categories = ["no-std"] From 25a07e1eafe4ae305e17efd71fc16c5086decbed Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Fri, 13 Sep 2024 09:15:10 +0000 Subject: [PATCH 113/113] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9c865bb..288b77e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [[Unreleased]] +## [[v0.2.0-beta.1]] + - Examples - Wallet from seed - New wallet generation