Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(XRP): Move Ripple blockchain to Rust #4250

Merged
merged 32 commits into from
Feb 7, 2025
Merged
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
db78bef
refactor(ripple): Add Ripple blockchain skeleton
satoshiotomakan Jan 28, 2025
c1b997c
refactor(ripple): Add Classic and X addresses
satoshiotomakan Jan 28, 2025
81bd052
refactor(ripple): Add more XAddress tests
satoshiotomakan Jan 29, 2025
d1cf952
refactor(ripple): Start working on transaction binary encoding
satoshiotomakan Jan 31, 2025
24fc460
refactor(ripple): Remove `serde_with` dependency
satoshiotomakan Jan 31, 2025
1d60c8f
refactor(ripple): Simplify `Result::tw_err` method
satoshiotomakan Jan 31, 2025
d4be95f
refactor(ripple): Implement `Encodable` for `XRPLTypes`
satoshiotomakan Jan 31, 2025
f62f210
refactor(ripple): Finish `Encoder`
satoshiotomakan Feb 1, 2025
7ca99a3
refactor(ripple): Add Transaction encoding from JSON test
satoshiotomakan Feb 2, 2025
5c5d882
refactor(ripple): Add `TransactionSigner`, `TransactionBuilder` from …
satoshiotomakan Feb 3, 2025
603cc94
refactor(ripple): Add `TrustSet`
satoshiotomakan Feb 4, 2025
6ac06f6
refactor(ripple): Add more Payment and TokenPayment tests
satoshiotomakan Feb 4, 2025
10674b9
refactor(ripple): Add `EscrowCreate`
satoshiotomakan Feb 4, 2025
c98707e
refactor(ripple): Add more `EscrowCreate` tests
satoshiotomakan Feb 4, 2025
a2268e1
refactor(ripple): Add `EscrowCancel`
satoshiotomakan Feb 4, 2025
7e4e7e6
refactor(ripple): Add `EscrowFinish`
satoshiotomakan Feb 4, 2025
c9aa577
refactor(ripple): Add `NFTokenBurn`
satoshiotomakan Feb 4, 2025
670e050
refactor(ripple): Add `NFTokenCreateOffer`
satoshiotomakan Feb 4, 2025
8f5c8cd
refactor(ripple): Add `NFTokenAcceptOffer`
satoshiotomakan Feb 4, 2025
c1f1b20
refactor(ripple): Add `NFTokenCancelOffer`
satoshiotomakan Feb 4, 2025
0717be8
refactor(ripple): Add `SourceTag`
satoshiotomakan Feb 5, 2025
2b981e5
refactor(ripple): Add transaction pre-image hashing and compiling
satoshiotomakan Feb 5, 2025
111643c
refactor(ripple): Add additional checks for XRP and IssuedCurrency am…
satoshiotomakan Feb 6, 2025
55b9b0f
refactor(ripple): Replace C++ implementation
satoshiotomakan Feb 6, 2025
1dc34a2
refactor(ripple): Minor change
satoshiotomakan Feb 6, 2025
c533885
refactor(ripple): Do not set `destinationTag` if `destination` is XAd…
satoshiotomakan Feb 6, 2025
0944d36
Merge branch 'master' into s/rust-xrp
satoshiotomakan Feb 6, 2025
d7970d2
refactor(ripple): Add fuzz tests
satoshiotomakan Feb 6, 2025
e553eac
refactor(ripple): Improve code coverage
satoshiotomakan Feb 6, 2025
8a2c059
refactor(ripple): Avoid bigdecimal duplication
satoshiotomakan Feb 6, 2025
66dbe62
refactor(ripple): Code improvements
satoshiotomakan Feb 6, 2025
dcf0e15
refactor(ripple): PR review comments
satoshiotomakan Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(ripple): Add Transaction encoding from JSON test
satoshiotomakan committed Feb 2, 2025
commit 7ca99a3262dad8703d6b1d99e71ff1f789fdadcc
4 changes: 3 additions & 1 deletion rust/chains/tw_ripple/src/definitions.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tw_misc::serde::hashmap_as_tupple_list;

pub const DEFINITIONS_JSON: &str = include_str!("../definitions/definitions.json");

@@ -38,10 +39,11 @@ pub struct FieldInfo {
#[serde(rename_all = "UPPERCASE")]
pub struct Definitions {
pub types: HashMap<String, i16>,
#[serde(with = "hashmap_as_tupple_list")]
pub fields: HashMap<String, FieldInfo>,
pub transaction_types: HashMap<String, i16>,
}

fn parse_definitions_json() -> Definitions {
serde_json::from_str(DEFINITIONS_JSON).expect("registry.json expected to be valid")
serde_json::from_str(DEFINITIONS_JSON).expect("definitions.json expected to be valid")
}
106 changes: 53 additions & 53 deletions rust/chains/tw_ripple/src/encode/st_object.rs
Original file line number Diff line number Diff line change
@@ -2,15 +2,26 @@
//
// Copyright © 2017 Trust Wallet.

use crate::address::x_address::XAddress;
use crate::address::RippleAddress;
use crate::definitions::DEFINITIONS;
use crate::encode::encoder::Encoder;
use crate::encode::field_instance::FieldInstance;
use crate::encode::xrpl_types::XRPLTypes;
use serde_json::{Map as JsonMap, Value as Json};
use std::str::FromStr;
use tw_coin_entry::error::prelude::*;
use tw_memory::Data;

type PreProcessedObject = JsonMap<String, Json>;
type TransactionJson = JsonMap<String, Json>;

const DESTINATION_TAG: &str = "DestinationTag";
const DESTINATION: &str = "Destination";
const SOURCE_TAG: &str = "SourceTag";
const ACCOUNT: &str = "Account";
const ST_OBJECT: &str = "STObject";
const OBJECT_END_MARKER_BYTE: u8 = 0xE1;

/// Class for serializing/deserializing Indexmaps of objects.
///
@@ -95,12 +106,9 @@ impl STObject {
format!("Error encoding '{field_name}' field with '{associated_type}' type")
})?;

// TODO uncomment when nested STObject's supported.
// const ST_OBJECT: &str = "STObject";
// const OBJECT_END_MARKER_BYTE: u8 = 0xE1;
// if field_instance.associated_type == ST_OBJECT {
// encoder.push_byte(OBJECT_END_MARKER_BYTE);
// }
if field_instance.associated_type == ST_OBJECT {
encoder.push_byte(OBJECT_END_MARKER_BYTE);
}
}

Ok(STObject(encoder.finish()))
@@ -119,11 +127,10 @@ impl STObject {
continue;
};

// TODO consider uncommenting this to handle Source/Destination XAddresses
// when adding support for dapps.
// if let Ok(RippleAddress::X(x_addr)) = RippleAddress::from_str(value) {
// Self::pre_process_x_addr(&object, &mut pre_processed, field.to_string(), x_addr)?;
// }
if let Ok(RippleAddress::X(x_addr)) = RippleAddress::from_str(value) {
Self::pre_process_x_addr(&object, &mut pre_processed, field.to_string(), x_addr)?;
continue;
}

if field == "TransactionType" || field == "LedgerEntryType" {
let transaction_type_code = *DEFINITIONS
@@ -144,46 +151,39 @@ impl STObject {
Ok(pre_processed)
}

// TODO consider using it when adding support for dapps.
// type TransactionJson = JsonMap<String, Json>;
// const DESTINATION_TAG: &str = "DestinationTag";
// const DESTINATION: &str = "Destination";
// const SOURCE_TAG: &str = "SourceTag";
// const ACCOUNT: &str = "Account";
//
// fn pre_process_x_addr(
// tx_object: &TransactionJson,
// pre_processed: &mut PreProcessedObject,
// field: String,
// addr: XAddress,
// ) -> SigningResult<()> {
// let classic_addr = addr
// .to_classic()
// .into_tw()
// .context("Error converting an XAddress to Classic")?
// .to_string();
//
// let tag = Json::Number(addr.destination_tag().into());
// let tag_name = if field == DESTINATION {
// DESTINATION_TAG
// } else if field == ACCOUNT {
// SOURCE_TAG
// } else {
// return SigningError::err(SigningErrorType::Error_invalid_address).context(format!(
// "Field `{field}` is not allowed to have an associated tag."
// ));
// };
//
// // Check whether the Transaction object contains the tag already.
// // If so, it must be equal to the tag of a corresponding XAddress.
// if let Some(handled_tag) = tx_object.get(tag_name) {
// if handled_tag != &tag {
// return SigningError::err(SigningErrorType::Error_invalid_params).context("Cannot have mismatched Account/Destination X-Address and SourceTag/DestinationTag");
// }
// }
//
// pre_processed.insert(field, Json::String(classic_addr));
// pre_processed.insert(tag_name.to_string(), tag);
// Ok(())
// }
fn pre_process_x_addr(
tx_object: &TransactionJson,
pre_processed: &mut PreProcessedObject,
field: String,
addr: XAddress,
) -> SigningResult<()> {
let classic_addr = addr
.to_classic()
.into_tw()
.context("Error converting an XAddress to Classic")?
.to_string();

let tag = Json::Number(addr.destination_tag().into());
let tag_name = if field == DESTINATION {
DESTINATION_TAG
} else if field == ACCOUNT {
SOURCE_TAG
} else {
return SigningError::err(SigningErrorType::Error_invalid_address).context(format!(
"Field `{field}` is not allowed to have an associated tag."
));
};

// Check whether the Transaction object contains the tag already.
// If so, it must be equal to the tag of a corresponding XAddress.
if let Some(handled_tag) = tx_object.get(tag_name) {
if handled_tag != &tag {
return SigningError::err(SigningErrorType::Error_invalid_params).context("Cannot have mismatched Account/Destination X-Address and SourceTag/DestinationTag");
}
}

pre_processed.insert(field, Json::String(classic_addr));
pre_processed.insert(tag_name.to_string(), tag);
Ok(())
}
}
17 changes: 9 additions & 8 deletions rust/chains/tw_ripple/src/encode/xrpl_types.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,15 @@ use std::str::FromStr;
use tw_coin_entry::error::prelude::*;
use tw_hash::{Hash, H128, H160, H256};

/// Only supported types.
/// XRPL supported types.
/// Missing types:
/// * Issue
/// * Path
/// * PathSet
/// * PathStep
/// * STArray
/// * STObject
/// * XChainBridge
pub enum XRPLTypes {
AccountID(AccountId),
Amount(Amount),
@@ -23,18 +31,11 @@ pub enum XRPLTypes {
Hash128(H128),
Hash160(H160),
Hash256(H256),
// Issue(Issue),
// Path(Path),
// PathSet(PathSet),
// PathStep(PathStep),
Vector256(H256),
// STArray(STArray),
// STObject(STObject),
UInt8(u8),
UInt16(u16),
UInt32(u32),
UInt64(u64),
// XChainBridge(XChainBridge),
}

impl XRPLTypes {
256 changes: 256 additions & 0 deletions rust/chains/tw_ripple/tests/data/transactions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
[
{
"binary_no_signing": "120007220000000024000017B42A535A8CF164400000000608434065D48EEBE0B40E8000000000000000000000000000434E590000000000CED6E99370D5C00EF4EBF72567DA99F5661BFB3A68400000000000000C8114AD6E583D47F90F29FD8B23225E6F905602B0292E",
"json": {
"Account": "rGFpans8aW7XZNEcNky6RHKyEdLvXPMnUn",
"Expiration": 1398443249,
"Fee": "12",
"Flags": 0,
"Sequence": 6068,
"TakerGets": {
"currency": "CNY",
"issuer": "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y",
"value": "4.2"
},
"TakerPays": "101204800",
"TransactionType": "OfferCreate"
}
},
{
"binary_no_signing": "12000722000000002400005124201B005EE8EC64400000000014025165D4122B7C477B075F000000000000000000000000434E590000000000AA8114C65DA8EA1BE40849F974685289CC145CCF68400000000000000C8114D0B32295596E50017E246FE85FC5982A1BD89CE4",
"json": {
"Account": "rLpW9Reyn9YqZ8mxbq8nviXSp4TnHafVJQ",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 6220012,
"Sequence": 20772,
"TakerGets": {
"currency": "CNY",
"issuer": "rGYYWKxT1XgNipUJouCq4cKiyAdq8xBoE9",
"value": "0.05114362355976031"
},
"TakerPays": "1311313",
"TransactionType": "OfferCreate"
}
},
{
"binary_no_signing": "12000724000809172019000808DA64D4047BD23375F33500000000000000000000000042544300000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000003C3945C26840000000000000328114F4141D8B4EF33BC3EE224088CA418DFCD2847193",
"json": {
"Account": "rPEZyTnSyQyXBCwMVYyaafSVPL8oMtfG6a",
"Fee": "50",
"OfferSequence": 526554,
"Sequence": 526615,
"TakerGets": "1010386370",
"TakerPays": {
"currency": "BTC",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0.01262042643559221"
},
"TransactionType": "OfferCreate"
}
},
{
"binary_no_signing": "120000228000000024000054C761D54416B0AE3B00000000000000000000000000005244440000000000387B5123A1C93417271BA6DBBBD087E68E7445B268400000000000000A69D54417BCE6C800000000000000000000000000005244440000000000387B5123A1C93417271BA6DBBBD087E68E7445B28114F7B414E9D25EE050553D8A0BB27202F4249AD3288314B83EB506BBE5BCF3E89C638FDB185B1DEAC96584",
"json": {
"Account": "rP2jdgJhtY1pwDJQEMLfCixesg4cw8HcrW",
"Amount": {
"currency": "RDD",
"issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV",
"value": "1150.848"
},
"Destination": "rHoUTGMxWKbrTTF8tpAjysjpu8PWrbt1Wx",
"Fee": "10",
"Flags": 2147483648,
"SendMax": {
"currency": "RDD",
"issuer": "ra9eZxMbJrUcgV8ui7aPc161FgrqWScQxV",
"value": "1152"
},
"Sequence": 21703,
"TransactionType": "Payment"
}
},
{
"binary_no_signing": "120000220000000024000000026140000000017D784068400000000000000A81145CCB151F6E9D603F394AE778ACF10D3BECE874F68314E851BBBE79E328E43D68F43445368133DF5FBA5A",
"json": {
"Account": "r9TeThyi5xiuUUrFjtPKZiHcDxs7K9H6Rb",
"Amount": "25000000",
"Destination": "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C",
"Fee": "10",
"Flags": 0,
"Sequence": 2,
"TransactionType": "Payment"
}
},
{
"binary_no_signing": "12000022000000002400000090201B005EE9BA614000000000030D4068400000000000000F8114AA1BD19D9E87BE8069FDBF6843653C43837C03C6831467FE6EC28E0464DD24FB2D62A492AAC697CFAD02",
"json": {
"Account": "rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e",
"Amount": "200000",
"Destination": "rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ",
"Fee": "15",
"Flags": 0,
"LastLedgerSequence": 6220218,
"Sequence": 144,
"TransactionType": "Payment"
}
},
{
"binary_no_signing": "120000220000000024000000012EF72D50CA6140000000017D784068400000000000000C8114E851BBBE79E328E43D68F43445368133DF5FBA5A831476DAC5E814CD4AA74142C3AB45E69A900E637AA2",
"json": {
"Account": "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C",
"Amount": "25000000",
"Destination": "rBqSFEFg2B6GBMobtxnU1eLA1zbNC9NDGM",
"DestinationTag": 4146942154,
"Fee": "12",
"Flags": 0,
"Sequence": 1,
"TransactionType": "Payment"
}
},
{
"binary_no_signing": "12000023000A34F8240000888A61400000000007A120684000000000000014811408F41F116A1F60D60296B16907F0A041BF10619783146E2F0455C46CF5DF61A1E58419A89D45459045EA",
"json": {
"Account": "rFLiPGytDEwC5heoqFcFAZoqPPmKBzX1o",
"Amount": "500000",
"Destination": "rBsbetvMYuMkEeHZYizPMkpveCVH8EVQYd",
"Fee": "20",
"Sequence": 34954,
"SourceTag": 668920,
"TransactionType": "Payment"
}
},
{
"binary_no_signing": "1200082200000000240000512120190000511B201B005EE8E968400000000000000C8114D0B32295596E50017E246FE85FC5982A1BD89CE4",
"json": {
"Account": "rLpW9Reyn9YqZ8mxbq8nviXSp4TnHafVJQ",
"Fee": "12",
"Flags": 0,
"LastLedgerSequence": 6220009,
"OfferSequence": 20763,
"Sequence": 20769,
"TransactionType": "OfferCancel"
}
},
{
"binary_no_signing": "1200052280000000240000000368400000000000000A811448E143E2384A1B3C69A412789F2CA3FCE2F65F0B881448E143E2384A1B3C69A412789F2CA3FCE2F65F0B",
"json": {
"Account": "rfeMWWbSaGqc6Yth2dTetLBeKeUTTfE2pG",
"Fee": "10",
"Flags": 2147483648,
"RegularKey": "rfeMWWbSaGqc6Yth2dTetLBeKeUTTfE2pG",
"Sequence": 3,
"TransactionType": "SetRegularKey"
}
},
{
"binary_no_signing": "120005228000000024000000EE201B005EF94C68400000000000000C8114CB3F392892D0772FF5AD155D8D70404B1DB2ACFE8814F2F9A54D9CEBBE64342B52DE3450FFA0738C8D00",
"json": {
"Account": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz",
"Fee": "12",
"Flags": 2147483648,
"LastLedgerSequence": 6224204,
"RegularKey": "rP9jbfTepHAHWB4q9YjNkLyaZT15uvexiZ",
"Sequence": 238,
"TransactionType": "SetRegularKey"
}
},
{
"binary_no_signing": "1200142200020000240000002C63D6438D7EA4C680000000000000000000000000005743470000000000832297BEF589D59F9C03A84F920F8D9128CC1CE468400000000000000C8114BE6C30732AE33CF2AF3344CE8172A6B9300183E3",
"json": {
"Account": "rJMiz2rCMjZzEMijXNH1exNBryTQEjFd9S",
"Fee": "12",
"Flags": 131072,
"LimitAmount": {
"currency": "WCG",
"issuer": "rUx4xgE7bNWCCgGcXv1CCoQyTcCeZ275YG",
"value": "10000000"
},
"Sequence": 44,
"TransactionType": "TrustSet"
}
},
{
"binary_no_signing": "1200142280020000240000002B201B005EEAAF63D4838D7EA4C680000000000000000000000000004254430000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD368400000000000000C81148353C031DF5AA061A23535E6ABCEEEA23F152B1E",
"json": {
"Account": "rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN",
"Fee": "12",
"Flags": 2147614720,
"LastLedgerSequence": 6220463,
"LimitAmount": {
"currency": "BTC",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"value": "1"
},
"Sequence": 43,
"TransactionType": "TrustSet"
}
},
{
"binary_no_signing": "1200032200000000240000296668400000000000000A81140F3D0C7D2CFAB2EC8295451F0B3CA038E8E9CDCD",
"json": {
"Account": "rpP2GdsQwenNnFPefbXFgiTvEgJWQpq8Rw",
"Fee": "10",
"Flags": 0,
"Sequence": 10598,
"TransactionType": "AccountSet"
}
},
{
"binary_no_signing": "12000322000000002400000122201B005EECD668400000000000000F8114ABBD4A3AF95FDFD6D072F11421D8F107CAEA1852",
"json": {
"Account": "rGCnJuD31Kx4QGZJ2dX7xoje6T4Zr5s9EB",
"Fee": "15",
"Flags": 0,
"LastLedgerSequence": 6221014,
"Sequence": 290,
"TransactionType": "AccountSet"
}
},
{
"description": "Destination and Source are XAddress",
"binary_no_signing": "12000022000000002300014B76240000003E2E000003F26140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA",
"json": {
"Account": "X7tFPvjMH7nDxP8nTGkeeggcUpCZj8UbyT2QoiRHGDfjqrB",
"Destination": "XVYmGpJqHS95ir411XvanwY1xt5Z2314WsamHPVgUNABUGV",
"TransactionType": "Payment",
"TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639",
"SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E",
"Amount": "10000000000",
"Fee": "10",
"Flags": 0,
"Sequence": 62
}
},
{
"description": "Destination is XAddress, Source is classic, however SourceTag is specified",
"binary_no_signing": "1200002280000000233BA47B242403528A932E3BA47B24614000000059682F00684000000000001388732102ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C367446304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A811418E58D55BA0A0D22DEF988C22D6271CEBB5D2B1C83148E0D713E837D8C34420B42F2CE1698BDFFB27FA5",
"json": {
"Account": "rsGeDwS4rpocUumu9smpXomzaaeG4Qyifz",
"Amount": "1500000000",
"Destination": "XVBkK1yLutMqFGwTm6hykn7YXGDUrjsZSkpzMgRveZrMbHs",
"Fee": "5000",
"Flags": 2147483648,
"Sequence": 55741075,
"SigningPubKey": "02ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C36",
"SourceTag": 1000635172,
"TransactionType": "Payment",
"TxnSignature": "304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A"
}
},
{
"description": "Destination is XAddress, Source is classic, no tags provided",
"binary_no_signing": "12000022800000002400007F812E0650D1EA614000000B63C512A068400000000000000A732102E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D334297447304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A5248114B51C0DAC2EF22C5CD6FBCFA1E7778D20922D61E48314A025D8B3210251C94FA3AAC92E159673A140FAD9",
"json": {
"Account": "rHWcuuZoFvDS6gNbmHSdpb7u1hZzxvCoMt",
"Amount": "48918500000",
"Destination": "XVH3aqvbYGhRhrD1FYSzGooNuxdzbG3VR2fuM47oqbXxQr7",
"Fee": "10",
"Flags": 2147483648,
"Sequence": 32641,
"SigningPubKey": "02E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D33429",
"TransactionType": "Payment",
"TxnSignature": "304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A524"
}
}
]
25 changes: 25 additions & 0 deletions rust/chains/tw_ripple/tests/encode_json_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use serde_json::Value as Json;
use tw_encoding::hex::ToHex;
use tw_ripple::encode::st_object::STObject;

const TRANSACTION_CASES: &str = include_str!("data/transactions.json");

#[test]
fn test_encode_json_transaction() {
let input: Json = serde_json::from_str(TRANSACTION_CASES).unwrap();

for (idx, test) in input.as_array().unwrap().iter().enumerate() {
let tx_json = &test["json"];
let binary_no_signing = test["binary_no_signing"].as_str().unwrap();

let signing_only = false;
let serialized = STObject::try_from_value(tx_json.clone(), signing_only)
.expect(&format!("Error encoding transaction[{idx}]: {tx_json:?}"));

assert_eq!(serialized.0.to_hex().to_uppercase(), binary_no_signing);
}
}
65 changes: 65 additions & 0 deletions rust/tw_misc/src/serde.rs
Original file line number Diff line number Diff line change
@@ -44,6 +44,71 @@ macro_rules! deserialize_from_str {
};
}

pub mod hashmap_as_tupple_list {
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;

pub fn serialize<K, V, S>(map: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
where
K: Serialize,
V: Serialize,
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(map.len()))?;
for (k, v) in map {
seq.serialize_element(&(k, v))?;
}
seq.end()
}

/// Deserialize a value from a string.
pub fn deserialize<'de, D, K, V>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
where
D: Deserializer<'de>,
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
struct MapVisitor<K, V> {
marker: std::marker::PhantomData<fn() -> HashMap<K, V>>,
}

impl<'de, K, V> Visitor<'de> for MapVisitor<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
type Value = HashMap<K, V>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of key-value pairs")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut map = match seq.size_hint() {
Some(capacity) => HashMap::with_capacity(capacity),
None => HashMap::new(),
};
while let Some((k, v)) = seq.next_element()? {
map.insert(k, v);
}

Ok(map)
}
}

deserializer.deserialize_seq(MapVisitor {
marker: std::marker::PhantomData,
})
}
}

pub mod as_string {
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};