diff --git a/packages/ripple-binary-codec/README.md b/packages/ripple-binary-codec/README.md index 631dc841..e151433b 100644 --- a/packages/ripple-binary-codec/README.md +++ b/packages/ripple-binary-codec/README.md @@ -42,6 +42,11 @@ Encode a transaction object into a hex-string. '1100612200000000240000000125000000072D0000000055DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF6240000002540BE4008114D0F5430B66E06498D4CEEC816C7B3337F9982337' ``` +#### X-Address Compatibility + * ripple-binary-codec handles X-addresses by looking for a few specific files (Account/SourceTag, Destination/DestinationTag). + * If other fields (in the future) must to support X-addresses with tags, this library will need to be updated. + * When decoding rippled binary, the output will always output classic address + tag, with no X-addresses. X-address support only applies when encoding to binary. + ### encodeForSigning(json: object): string Encode the transaction object for signing. diff --git a/packages/ripple-binary-codec/src/types/account-id.ts b/packages/ripple-binary-codec/src/types/account-id.ts index 609f85e4..7bb351a4 100644 --- a/packages/ripple-binary-codec/src/types/account-id.ts +++ b/packages/ripple-binary-codec/src/types/account-id.ts @@ -1,6 +1,8 @@ import { decodeAccountID, encodeAccountID } from "ripple-address-codec"; import { Hash160 } from "./hash-160"; +const HEX_REGEX = /^[A-F0-9]{40}$/; + /** * Class defining how to encode and decode an AccountID */ @@ -27,9 +29,9 @@ class AccountID extends Hash160 { return new AccountID(); } - return /^r/.test(value) - ? this.fromBase58(value) - : new AccountID(Buffer.from(value, "hex")); + return HEX_REGEX.test(value) + ? new AccountID(Buffer.from(value, "hex")) + : this.fromBase58(value); } throw new Error("Cannot construct AccountID from value given"); diff --git a/packages/ripple-binary-codec/src/types/st-object.ts b/packages/ripple-binary-codec/src/types/st-object.ts index 578c0efa..791346fd 100644 --- a/packages/ripple-binary-codec/src/types/st-object.ts +++ b/packages/ripple-binary-codec/src/types/st-object.ts @@ -1,11 +1,53 @@ import { Field, FieldInstance } from "../enums"; import { SerializedType, JsonObject } from "./serialized-type"; +import { + xAddressToClassicAddress, + isValidXAddress, +} from "ripple-address-codec"; import { BinaryParser } from "../serdes/binary-parser"; import { BinarySerializer, BytesList } from "../serdes/binary-serializer"; -const OBJECT_END_MARKER = Buffer.from([0xe1]); -const OBJECT_END_MARKER_NAME = "ObjectEndMarker"; -const OBJECT_FIELD_TYPE_NAME = "STObject"; +const OBJECT_END_MARKER_BYTE = Buffer.from([0xe1]); +const OBJECT_END_MARKER = "ObjectEndMarker"; +const ST_OBJECT = "STObject"; +const DESTINATION = "Destination"; +const ACCOUNT = "Account"; +const SOURCE_TAG = "SourceTag"; +const DEST_TAG = "DestinationTag"; + +/** + * Break down an X-Address into an account and a tag + * + * @param field Name of field + * @param xAddress X-Address corresponding to the field + */ +function handleXAddress(field: string, xAddress: string): JsonObject { + const decoded = xAddressToClassicAddress(xAddress); + + let tagName; + if (field === DESTINATION) tagName = DEST_TAG; + else if (field === ACCOUNT) tagName = SOURCE_TAG; + else if (decoded.tag !== false) + throw new Error(`${field} cannot have an associated tag`); + + return decoded.tag !== false + ? { [field]: decoded.classicAddress, [tagName]: decoded.tag } + : { [field]: decoded.classicAddress }; +} + +/** + * Validate that two objects don't both have the same tag fields + * + * @param obj1 First object to check for tags + * @param obj2 Second object to check for tags + * @throws When both objects have SourceTag or DestinationTag + */ +function checkForDuplicateTags(obj1: JsonObject, obj2: JsonObject): void { + if (!(obj1[SOURCE_TAG] === undefined || obj2[SOURCE_TAG] === undefined)) + throw new Error("Cannot have Account X-Address and SourceTag"); + if (!(obj1[DEST_TAG] === undefined || obj2[DEST_TAG] === undefined)) + throw new Error("Cannot have Destination X-Address and DestinationTag"); +} /** * Class for Serializing/Deserializing objects @@ -23,15 +65,15 @@ class STObject extends SerializedType { while (!parser.end()) { const field = parser.readField(); - if (field.name === OBJECT_END_MARKER_NAME) { + if (field.name === OBJECT_END_MARKER) { break; } const associatedValue = parser.readFieldValue(field); bytes.writeFieldAndValue(field, associatedValue); - if (field.type.name === OBJECT_FIELD_TYPE_NAME) { - bytes.put(OBJECT_END_MARKER); + if (field.type.name === ST_OBJECT) { + bytes.put(OBJECT_END_MARKER_BYTE); } } @@ -56,7 +98,16 @@ class STObject extends SerializedType { const list: BytesList = new BytesList(); const bytes: BinarySerializer = new BinarySerializer(list); - let sorted = Object.keys(value) + const xAddressDecoded = Object.entries(value).reduce((acc, [key, val]) => { + let handled: JsonObject | undefined = undefined; + if (isValidXAddress(val)) { + handled = handleXAddress(key, val); + checkForDuplicateTags(handled, value as JsonObject); + } + return Object.assign(acc, handled ?? { [key]: val }); + }, {}); + + let sorted = Object.keys(xAddressDecoded) .map((f: string): FieldInstance => Field[f] as FieldInstance) .filter((f: FieldInstance): boolean => f !== undefined && f.isSerialized) .sort((a, b) => { @@ -68,11 +119,13 @@ class STObject extends SerializedType { } sorted.forEach((field) => { - const associatedValue = field.associatedType.from(value[field.name]); + const associatedValue = field.associatedType.from( + xAddressDecoded[field.name] + ); bytes.writeFieldAndValue(field, associatedValue); - if (field.type.name === OBJECT_FIELD_TYPE_NAME) { - bytes.put(OBJECT_END_MARKER); + if (field.type.name === ST_OBJECT) { + bytes.put(OBJECT_END_MARKER_BYTE); } }); @@ -90,7 +143,7 @@ class STObject extends SerializedType { while (!objectParser.end()) { const field = objectParser.readField(); - if (field.name === OBJECT_END_MARKER_NAME) { + if (field.name === OBJECT_END_MARKER) { break; } accumulator[field.name] = objectParser.readFieldValue(field).toJSON(); diff --git a/packages/ripple-binary-codec/test/fixtures/x-codec-fixtures.json b/packages/ripple-binary-codec/test/fixtures/x-codec-fixtures.json new file mode 100644 index 00000000..7012e4b5 --- /dev/null +++ b/packages/ripple-binary-codec/test/fixtures/x-codec-fixtures.json @@ -0,0 +1,188 @@ +{ + "transactions": [{ + "rjson": { + "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", + "TransactionType": "Payment", + "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", + "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", + "Amount": "10000000000", + "DestinationTag": 1010, + "SourceTag": 84854, + "Fee": "10", + "Flags": 0, + "Sequence": 62 + }, + "xjson": { + "Account": "X7tFPvjMH7nDxP8nTGkeeggcUpCZj8UbyT2QoiRHGDfjqrB", + "Destination": "XVYmGpJqHS95ir411XvanwY1xt5Z2314WsamHPVgUNABUGV", + "TransactionType": "Payment", + "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", + "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", + "Amount": "10000000000", + "Fee": "10", + "Flags": 0, + "Sequence": 62 + } + }, + { + "rjson": { + "Account": "r4DymtkgUAh2wqRxVfdd3Xtswzim6eC6c5", + "Amount": "199000000", + "Destination": "rsekGH9p9neiPxym2TMJhqaCzHFuokenTU", + "DestinationTag": 3663729509, + "Fee": "6335", + "Flags": 2147483648, + "LastLedgerSequence": 57313352, + "Sequence": 105791, + "SigningPubKey": "02053A627976CE1157461336AC65290EC1571CAAD1B327339980F7BF65EF776F83", + "TransactionType": "Payment", + "TxnSignature": "30440220086D3330CD6CE01D891A26BA0355D8D5A5D28A5C9A1D0C5E06E321C81A02318A0220027C3F6606E41FEA35103EDE5224CC489B6514ACFE27543185B0419DD02E301C" + }, + "xjson": { + "Account": "r4DymtkgUAh2wqRxVfdd3Xtswzim6eC6c5", + "Amount": "199000000", + "Destination": "X7cBoj6a5xSEfPCr6AStN9YPhbMAA2yaN2XYWwRJKAKb3y5", + "Fee": "6335", + "Flags": 2147483648, + "LastLedgerSequence": 57313352, + "Sequence": 105791, + "SigningPubKey": "02053A627976CE1157461336AC65290EC1571CAAD1B327339980F7BF65EF776F83", + "TransactionType": "Payment", + "TxnSignature": "30440220086D3330CD6CE01D891A26BA0355D8D5A5D28A5C9A1D0C5E06E321C81A02318A0220027C3F6606E41FEA35103EDE5224CC489B6514ACFE27543185B0419DD02E301C" + } + }, + { + "rjson": { + "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", + "Amount": "105302107", + "Destination": "r33hypJXDs47LVpmvta7hMW9pR8DYeBtkW", + "DestinationTag": 1658156118, + "Fee": "60000", + "Flags": 2147483648, + "LastLedgerSequence": 57313566, + "Sequence": 1113196, + "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", + "TransactionType": "Payment", + "TxnSignature": "3045022100FCA10FBAC65EA60C115A970CD52E6A526B1F9DDB6C4F843DA3DE7A97DFF9492D022037824D0FC6F663FB08BE0F2812CBADE1F61836528D44945FC37F10CC03215111" + }, + "xjson": { + "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", + "Amount": "105302107", + "Destination": "X7ikFY5asEwp6ikt2AJdTfBLALEs5JN35kkeqKVeT1GdvY1", + "Fee": "60000", + "Flags": 2147483648, + "LastLedgerSequence": 57313566, + "Sequence": 1113196, + "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", + "TransactionType": "Payment", + "TxnSignature": "3045022100FCA10FBAC65EA60C115A970CD52E6A526B1F9DDB6C4F843DA3DE7A97DFF9492D022037824D0FC6F663FB08BE0F2812CBADE1F61836528D44945FC37F10CC03215111" + } + }, + { + "rjson": { + "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", + "Amount": "3899911571", + "Destination": "rU2mEJSLqBRkYLVTv55rFTgQajkLTnT6mA", + "DestinationTag": 255406, + "Fee": "60000", + "Flags": 2147483648, + "LastLedgerSequence": 57313566, + "Sequence": 1113197, + "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", + "TransactionType": "Payment", + "TxnSignature": "3044022077642D94BB3C49BF3CB4C804255EC830D2C6009EA4995E38A84602D579B8AAD702206FAD977C49980226E8B495BF03C8D9767380F1546BBF5A4FD47D604C0D2CCF9B" + }, + "xjson": { + "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", + "Amount": "3899911571", + "Destination": "XVfH8gwNWVbB5Kft16jmTNgGTqgw1dzA8ZTBkNjSLw6JdXS", + "Fee": "60000", + "Flags": 2147483648, + "LastLedgerSequence": 57313566, + "Sequence": 1113197, + "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", + "TransactionType": "Payment", + "TxnSignature": "3044022077642D94BB3C49BF3CB4C804255EC830D2C6009EA4995E38A84602D579B8AAD702206FAD977C49980226E8B495BF03C8D9767380F1546BBF5A4FD47D604C0D2CCF9B" + } + }, + { + "rjson": { + "Account": "r4eEbLKZGbVSBHnSUBZW8i5XaMjGLdqT4a", + "Amount": "820370849", + "Destination": "rDhmyBh4JwDAtXyRZDarNgg52UcLLRoGje", + "DestinationTag": 2017780486, + "Fee": "6000", + "Flags": 2147483648, + "LastLedgerSequence": 57315579, + "Sequence": 234254, + "SigningPubKey": "038CF47114672A12B269AEE015BF7A8438609B994B0640E4B28B2F56E93D948B15", + "TransactionType": "Payment", + "TxnSignature": "3044022015004653B1CBDD5CCA1F7B38555F1B37FE3F811E9D5070281CCC6C8A93460D870220679E9899184901EA69750C8A9325768490B1B9C1A733842446727653FF3D1DC0" + }, + "xjson": { + "Account": "r4eEbLKZGbVSBHnSUBZW8i5XaMjGLdqT4a", + "Amount": "820370849", + "Destination": "XV31huWNJQXsAJFwgE6rnC8uf8jRx4H4waq4MyGUxz5CXzS", + "Fee": "6000", + "Flags": 2147483648, + "LastLedgerSequence": 57315579, + "Sequence": 234254, + "SigningPubKey": "038CF47114672A12B269AEE015BF7A8438609B994B0640E4B28B2F56E93D948B15", + "TransactionType": "Payment", + "TxnSignature": "3044022015004653B1CBDD5CCA1F7B38555F1B37FE3F811E9D5070281CCC6C8A93460D870220679E9899184901EA69750C8A9325768490B1B9C1A733842446727653FF3D1DC0" + } + }, + { + "rjson": { + "Account": "rsGeDwS4rpocUumu9smpXomzaaeG4Qyifz", + "Amount": "1500000000", + "Destination": "rDxfhNRgCDNDckm45zT5ayhKDC4Ljm7UoP", + "DestinationTag": 1000635172, + "Fee": "5000", + "Flags": 2147483648, + "Sequence": 55741075, + "SigningPubKey": "02ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C36", + "SourceTag": 1000635172, + "TransactionType": "Payment", + "TxnSignature": "304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A" + }, + "xjson": { + "Account": "rsGeDwS4rpocUumu9smpXomzaaeG4Qyifz", + "Amount": "1500000000", + "Destination": "XVBkK1yLutMqFGwTm6hykn7YXGDUrjsZSkpzMgRveZrMbHs", + "Fee": "5000", + "Flags": 2147483648, + "Sequence": 55741075, + "SigningPubKey": "02ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C36", + "SourceTag": 1000635172, + "TransactionType": "Payment", + "TxnSignature": "304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A" + } + }, + { + "rjson": { + "Account": "rHWcuuZoFvDS6gNbmHSdpb7u1hZzxvCoMt", + "Amount": "48918500000", + "Destination": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh", + "DestinationTag": 105959914, + "Fee": "10", + "Flags": 2147483648, + "Sequence": 32641, + "SigningPubKey": "02E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D33429", + "TransactionType": "Payment", + "TxnSignature": "304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A524" + }, + "xjson": { + "Account": "rHWcuuZoFvDS6gNbmHSdpb7u1hZzxvCoMt", + "Amount": "48918500000", + "Destination": "XVH3aqvbYGhRhrD1FYSzGooNuxdzbG3VR2fuM47oqbXxQr7", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 32641, + "SigningPubKey": "02E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D33429", + "TransactionType": "Payment", + "TxnSignature": "304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A524" + } + }] +} \ No newline at end of file diff --git a/packages/ripple-binary-codec/test/hash.test.js b/packages/ripple-binary-codec/test/hash.test.js index 41615a05..8d896ca9 100644 --- a/packages/ripple-binary-codec/test/hash.test.js +++ b/packages/ripple-binary-codec/test/hash.test.js @@ -16,10 +16,14 @@ describe('Hash160', function () { expect(h1.lt(h2)).toBe(true) expect(h3.lt(h2)).toBe(true) }) + test('throws when constructed from invalid hash length', () => { + expect(() => Hash160.from('10000000000000000000000000000000000000')).toThrow('Invalid Hash length 19') + expect(() => Hash160.from('100000000000000000000000000000000000000000')).toThrow('Invalid Hash length 21') + }) }) describe('Hash256', function () { - test('has a static width membmer', function () { + test('has a static width member', function () { expect(Hash256.width).toBe(32) }) test('has a ZERO_256 member', function () { diff --git a/packages/ripple-binary-codec/test/x-address.test.js b/packages/ripple-binary-codec/test/x-address.test.js new file mode 100644 index 00000000..2e4c6c7c --- /dev/null +++ b/packages/ripple-binary-codec/test/x-address.test.js @@ -0,0 +1,137 @@ +const { encode, decode } = require("./../dist/index"); +const fixtures = require('./fixtures/x-codec-fixtures.json') + +let json_x1 = { + OwnerCount: 0, + Account: "XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT", + PreviousTxnLgrSeq: 7, + LedgerEntryType: "AccountRoot", + PreviousTxnID: "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", + Flags: 0, + Sequence: 1, + Balance: "10000000000" + } + +let json_r1 = { + OwnerCount: 0, + Account: 'rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv', + PreviousTxnLgrSeq: 7, + LedgerEntryType: 'AccountRoot', + PreviousTxnID: 'DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF', + Flags: 0, + Sequence: 1, + Balance: '10000000000', + SourceTag: 12345, + } + +let json_null_x = { + "OwnerCount": 0, + "Account": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Destination": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Issuer": "XVXdn5wEVm5G4UhEHWDPqjvdeH361P4GETfNyyXGaoqBj71", + "PreviousTxnLgrSeq": 7, + "LedgerEntryType": "AccountRoot", + "PreviousTxnID": "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", + "Flags": 0, + "Sequence": 1, + "Balance": "10000000000" +} + +let json_invalid_x = { + "OwnerCount": 0, + "Account": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Destination": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Issuer": "XVXdn5wEVm5g4UhEHWDPqjvdeH361P4GETfNyyXGaoqBj71", + "PreviousTxnLgrSeq": 7, + "LedgerEntryType": "AccountRoot", + "PreviousTxnID": "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", + "Flags": 0, + "Sequence": 1, + "Balance": "10000000000" +} + +let json_null_r = { + "OwnerCount": 0, + "Account": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Destination": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Issuer": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "PreviousTxnLgrSeq": 7, + "LedgerEntryType": "AccountRoot", + "PreviousTxnID": "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", + "Flags": 0, + "Sequence": 1, + "Balance": "10000000000" +} + +let invalid_json_issuer_tagged = { + "OwnerCount": 0, + "Account": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Destination": "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", + "Issuer": "XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT", + "PreviousTxnLgrSeq": 7, + "LedgerEntryType": "AccountRoot", + "PreviousTxnID": "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", + "Flags": 0, + "Sequence": 1, + "Balance": "10000000000" +} + +let invalid_json_x_and_tagged = { + OwnerCount: 0, + Account: 'XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT', + PreviousTxnLgrSeq: 7, + LedgerEntryType: 'AccountRoot', + PreviousTxnID: 'DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF', + Flags: 0, + Sequence: 1, + Balance: '10000000000', + SourceTag: 12345, + } + +describe("X-Address Account is equivalent to a classic address w/ SourceTag", () => { + let encoded_x = encode(json_x1); + let encoded_r = encode(json_r1); + test("Can encode with x-Address", () => { + expect(encoded_x).toEqual(encoded_r); + }) + + test("decoded X-address is object w/ source and tag", () => { + let decoded_x = decode(encoded_x); + expect(decoded_x).toEqual(json_r1); + }) + + test("Encoding issuer X-Address w/ undefined destination tag", () => { + expect(encode(json_null_x)).toEqual(encode(json_null_r)); + }) + + test("Throws when X-Address is invalid", () => { + expect(() => encode(json_invalid_x)).toThrow("checksum_invalid"); + }) +}) + +describe("Invalid X-Address behavior", () => { + test("X-Address with tag throws value for invalid field",() => { + expect(() => encode(invalid_json_issuer_tagged)).toThrow(new Error("Issuer cannot have an associated tag")) + }) + + test("Throws when Account has both X-Addr and Destination Tag", () => { + expect(() => encode(invalid_json_x_and_tagged)).toThrow(new Error("Cannot have Account X-Address and SourceTag")); + }); +}) + +describe('ripple-binary-codec x-address test', function () { + function makeSuite (name, entries) { + describe(name, function () { + entries.forEach((t, testN) => { + test(`${name}[${testN}] encodes X-address json equivalent to classic address json`, + () => { + expect(encode(t.rjson)).toEqual(encode(t.xjson)) + }) + test(`${name}[${testN}] decodes X-address json equivalent to classic address json`, () => { + expect(decode(encode(t.xjson))).toEqual(t.rjson); + }) + }) + }) + } + makeSuite('transactions', fixtures.transactions) +}) \ No newline at end of file