From 245efd011684495334f46a6cebc3eca8504860f2 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 13:14:19 -0700 Subject: [PATCH 01/11] Add ability to force a conversion type --- src/scval.js | 66 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/scval.js b/src/scval.js index 53597418..3302d078 100644 --- a/src/scval.js +++ b/src/scval.js @@ -54,7 +54,8 @@ import { ScInt, scValToBigInt } from './numbers/index'; * - number/bigint -> the smallest possible XDR integer type that will fit the * input value (if you want a specific type, use {@link ScInt}) * - * - {@link Address} -> scvAddress (for contracts and public keys) + * - {@link Address} or {@link Contract} -> scvAddress (for contracts and + * public keys) * * - Array -> scvVec after attempting to convert each item of type `T` to an * xdr.ScVal (recursively). note that all values must be the same type! @@ -67,12 +68,17 @@ import { ScInt, scValToBigInt } from './numbers/index'; * type which will force a particular interpretation of that value. * * Note that not all type specifications are compatible with all `ScVal`s, e.g. - * `toScVal("string", {type: i256})` will throw. + * `toScVal("a string", {type: "i256"})` will throw. * - * @param {any} val a native (or convertible) input value to wrap - * @param {object} [opts] an optional set of options to pass which allows you - * to specify a type when the native `val` is an integer-like (i.e. - * number|bigint) type + * @param {any} val - a native (or convertible) input value to wrap + * @param {object} [opts] - an optional set of hints around the type of + * conversion you'd like to see + * @param {string} [opts.type] - when `val` is an integer-like type (i.e. + * number|bigint), this will be forwarded to {@link ScInt}. otherwise, it can + * be 'string', 'symbol', 'bytes' to force a particular interpretation of + * `val`. for example, `nativeToScVal("hello", {type: 'symbol'})` will return + * an `scvSymbol`, whereas without the type it would have been an + * `scvString`. * * @returns {xdr.ScVal} a wrapped, smart, XDR version of the input value * @@ -82,9 +88,8 @@ import { ScInt, scValToBigInt } from './numbers/index'; * types, custom classes) * - the type of the input object (or some inner value of said object) cannot * be determined (via `typeof`) - * - * TODO: Allow users to force types that are not direct but can be translated, - * i.e. forcing a `Uint8Array` to be encoded as an ScSymbol or ScString. + * - the type you specified (via `opts.type`) is incompatible with the value + * you passed in (`val`), e.g. `nativeToScVal("a string", { type: 'i128' })`. */ export function nativeToScVal(val, opts = {}) { switch (typeof val) { @@ -101,8 +106,24 @@ export function nativeToScVal(val, opts = {}) { return val.toScVal(); } - if (val instanceof Uint8Array) { - return xdr.ScVal.scvBytes(Uint8Array.from(val)); + if (val instanceof Contract) { + return val.address().toScVal(); + } + + if (val instanceof Uint8Array || Buffer.isBuffer(val)) { + const copy = Uint8Array.from(val); + switch (opts?.type ?? 'bytes') { + case 'bytes': + return xdr.ScVal.scvBytes(copy); + case 'symbol': + return xdr.ScVal.scvSymbol(copy); + case 'string': + return xdr.ScVal.scvSymbol(copy); + default: + throw new TypeError( + `invalid type (${opts.type}) specified for bytes-like value` + ); + } } if (Array.isArray(val)) { @@ -129,10 +150,24 @@ export function nativeToScVal(val, opts = {}) { case 'number': case 'bigint': - return new ScInt(val, opts).toScVal(); + return new ScInt(val, { type: opts?.type }).toScVal(); case 'string': - return xdr.ScVal.scvString(val.toString()); + switch (opts?.type ?? 'string') { + case 'string': + return xdr.ScVal.scvString(val); + + case 'symbol': + return xdr.ScVal.scvSymbol(val); + + case 'bytes': + return xdr.ScVal.scvBytes(Uint8Array.from(val)); + + default: + throw new TypeError( + `invalid type (${opts.type}) specified for string value` + ); + } case 'boolean': return xdr.ScVal.scvBool(val); @@ -160,7 +195,7 @@ export function nativeToScVal(val, opts = {}) { * - map -> key-value object of any of the above (via recursion) * - bool -> boolean * - bytes -> Uint8Array - * - string, symbol -> string + * - string, symbol -> string|Buffer * * If no conversion can be made, this just "unwraps" the smart value to return * its underlying XDR value. @@ -214,8 +249,7 @@ export function scValToNative(scv) { case xdr.ScValType.scvString().value: case xdr.ScValType.scvSymbol().value: - // FIXME: Is this the right way to handle it being string|Buffer? - return String(scv.value()); + return scv.value(); // string|Buffer // these can be converted to bigint case xdr.ScValType.scvTimepoint().value: From 49de1d6c24b2306a95cc7d5acef2cae79b7962dc Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 13:19:31 -0700 Subject: [PATCH 02/11] Add examples, i/u32 support --- src/scval.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/scval.js b/src/scval.js index 3302d078..3ed77c42 100644 --- a/src/scval.js +++ b/src/scval.js @@ -90,6 +90,22 @@ import { ScInt, scValToBigInt } from './numbers/index'; * be determined (via `typeof`) * - the type you specified (via `opts.type`) is incompatible with the value * you passed in (`val`), e.g. `nativeToScVal("a string", { type: 'i128' })`. + * + * @example + * + * ```js + * nativeToScVal(1000); // gives ScValType === U64 + * nativeToScVal(1000n); // gives ScValType === U64 + * nativeToScVal(1n << 100n); // gives ScValType === U128 + * nativeToScVal(1000, { type: 'u32' }); // gives ScValType === U32 + * nativeToScVal(1000, { type: 'i125' }); // gives ScValType === I256 + * nativeToScVal("a string"); // gives ScValType === ScString + * nativeToScVal("a string", { type: 'symbol' }); // gives ScValType === ScSymbol + * nativeToScVal(new Uint8Array(5)); // gives ScValType === ScBytes + * nativeToScVal(new Uint8Array(5), { type: 'symbol' }); // gives ScValType === ScSymbol + * nativeToScVal(null); // gives ScValType === ScVoid + * nativeToScVal(null); // gives ScValType === ScVoid + * ``` */ export function nativeToScVal(val, opts = {}) { switch (typeof val) { @@ -99,7 +115,7 @@ export function nativeToScVal(val, opts = {}) { } if (val instanceof xdr.ScVal) { - return val; + return val; // should we copy? } if (val instanceof Address) { @@ -150,6 +166,14 @@ export function nativeToScVal(val, opts = {}) { case 'number': case 'bigint': + switch (opts?.type) { + case 'u32': + return ScVal.scvU32(val); + + case 'i32': + return ScVal.scvI32(val); + } + return new ScInt(val, { type: opts?.type }).toScVal(); case 'string': @@ -160,9 +184,6 @@ export function nativeToScVal(val, opts = {}) { case 'symbol': return xdr.ScVal.scvSymbol(val); - case 'bytes': - return xdr.ScVal.scvBytes(Uint8Array.from(val)); - default: throw new TypeError( `invalid type (${opts.type}) specified for string value` From 3e5955408455379a2b24143155a1ea39b27bc8a0 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 13:21:27 -0700 Subject: [PATCH 03/11] Add array typing support --- src/scval.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/scval.js b/src/scval.js index 3ed77c42..53abaa3f 100644 --- a/src/scval.js +++ b/src/scval.js @@ -3,6 +3,7 @@ * native JavaScript types. * * @example + * ```js * import { nativeToScVal, scValToNative, ScInt, xdr } from 'stellar-base'; * * let gigaMap = { @@ -32,6 +33,7 @@ * * // Similarly, the inverse should work: * scValToNative(scv) == gigaMap; // true + * ``` */ import xdr from './xdr'; @@ -104,7 +106,7 @@ import { ScInt, scValToBigInt } from './numbers/index'; * nativeToScVal(new Uint8Array(5)); // gives ScValType === ScBytes * nativeToScVal(new Uint8Array(5), { type: 'symbol' }); // gives ScValType === ScSymbol * nativeToScVal(null); // gives ScValType === ScVoid - * nativeToScVal(null); // gives ScValType === ScVoid + * nativeToScVal([1, 2, 3]); // gives ScValType === ScVoid * ``` */ export function nativeToScVal(val, opts = {}) { @@ -146,7 +148,7 @@ export function nativeToScVal(val, opts = {}) { if (val.length > 0 && val.some((v) => typeof v !== typeof v[0])) { throw new TypeError(`array value (${val}) must have a single type`); } - return xdr.ScVal.scvVec(val.map(nativeToScVal)); + return xdr.ScVal.scvVec(val.map(v => nativeToScVal(v, opts))); } if ((val.constructor?.name ?? '') !== 'Object') { From 24d95b80b1c407997552c1bcce84b8d40162b4a1 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 13:36:31 -0700 Subject: [PATCH 04/11] Add custom object interpretation --- src/scval.js | 82 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/src/scval.js b/src/scval.js index 53abaa3f..44d4f203 100644 --- a/src/scval.js +++ b/src/scval.js @@ -75,11 +75,27 @@ import { ScInt, scValToBigInt } from './numbers/index'; * @param {any} val - a native (or convertible) input value to wrap * @param {object} [opts] - an optional set of hints around the type of * conversion you'd like to see - * @param {string} [opts.type] - when `val` is an integer-like type (i.e. - * number|bigint), this will be forwarded to {@link ScInt}. otherwise, it can - * be 'string', 'symbol', 'bytes' to force a particular interpretation of - * `val`. for example, `nativeToScVal("hello", {type: 'symbol'})` will return - * an `scvSymbol`, whereas without the type it would have been an + * @param {string} [opts.type] - there is different behavior for different input + * types for `val`: + * + * - when `val` is an integer-like type (i.e. number|bigint), this will be + * forwarded to {@link ScInt} or forced to be u32/i32. + * + * - when `val` is an array type, this is forwarded to the recursion + * + * - when `val` is an object type (key-value entries), this should be an + * object in which each key has a pair of types (to represent forced types + * for the key and the value), where `null` (or a missing entry) indicates + * the default interpretation(s) (refer to the examples, below) + * + * - when `val` is a string type, this can be 'string' or 'symbol' to force + * a particular interpretation of `val`. + * + * - when `val` is a bytes-like type, this can be 'string', 'symbol', or + * 'bytes' to force a particular interpretation + * + * As a simple example, `nativeToScVal("hello", {type: 'symbol'})` will + * return an `scvSymbol`, whereas without the type it would have been an * `scvString`. * * @returns {xdr.ScVal} a wrapped, smart, XDR version of the input value @@ -96,17 +112,28 @@ import { ScInt, scValToBigInt } from './numbers/index'; * @example * * ```js - * nativeToScVal(1000); // gives ScValType === U64 - * nativeToScVal(1000n); // gives ScValType === U64 - * nativeToScVal(1n << 100n); // gives ScValType === U128 - * nativeToScVal(1000, { type: 'u32' }); // gives ScValType === U32 - * nativeToScVal(1000, { type: 'i125' }); // gives ScValType === I256 - * nativeToScVal("a string"); // gives ScValType === ScString - * nativeToScVal("a string", { type: 'symbol' }); // gives ScValType === ScSymbol - * nativeToScVal(new Uint8Array(5)); // gives ScValType === ScBytes - * nativeToScVal(new Uint8Array(5), { type: 'symbol' }); // gives ScValType === ScSymbol - * nativeToScVal(null); // gives ScValType === ScVoid - * nativeToScVal([1, 2, 3]); // gives ScValType === ScVoid + * nativeToScVal(1000); // gives ScValType === scvU64 + * nativeToScVal(1000n); // gives ScValType === scvU64 + * nativeToScVal(1n << 100n); // gives ScValType === scvU128 + * nativeToScVal(1000, { type: 'u32' }); // gives ScValType === scvU32 + * nativeToScVal(1000, { type: 'i125' }); // gives ScValType === scvI256 + * nativeToScVal("a string"); // gives ScValType === scvString + * nativeToScVal("a string", { type: 'symbol' }); // gives scvSymbol + * nativeToScVal(new Uint8Array(5)); // scvBytes + * nativeToScVal(new Uint8Array(5), { type: 'symbol' }); // scvSymbol + * nativeToScVal(null); // scvVoid + * nativeToScVal(true); // scvBool + * nativeToScVal([1, 2, 3]); // gives scvVec with each element as scvU64 + * nativeToScVal([1, 2, 3], { type: 'i128' }); // scvVec + * nativeToScVal({ 'hello': 1, 'world': [ true, false ] }, { + * type: { + * 'hello': [ 'symbol', 'i128' ], + * } + * }) + * // gives scvMap with entries: [ + * // [ scvSymbol, scvI128 ], + * // [ scvString, scvArray ] + * // ] * ``` */ export function nativeToScVal(val, opts = {}) { @@ -148,7 +175,7 @@ export function nativeToScVal(val, opts = {}) { if (val.length > 0 && val.some((v) => typeof v !== typeof v[0])) { throw new TypeError(`array value (${val}) must have a single type`); } - return xdr.ScVal.scvVec(val.map(v => nativeToScVal(v, opts))); + return xdr.ScVal.scvVec(val.map((v) => nativeToScVal(v, opts))); } if ((val.constructor?.name ?? '') !== 'Object') { @@ -160,20 +187,29 @@ export function nativeToScVal(val, opts = {}) { } return xdr.ScVal.scvMap( - Object.entries(val).map( - ([k, v]) => - new xdr.ScMapEntry({ key: nativeToScVal(k), val: nativeToScVal(v) }) - ) + Object.entries(val).map(([k, v]) => { + // the type can be specified with an entry for the key and the value, + // e.g. val = { 'hello': 1 } and opts.type = { hello: [ 'symbol', + // 'u128' ]} or you can use `null` for the default interpretation + const [keyType, valType] = opts?.type[k] ?? [null, null]; + const keyOpts = keyType ? { type: keyType } : {}; + const valOpts = valType ? { type: valType } : {}; + + return new xdr.ScMapEntry({ + key: nativeToScVal(k, keyOpts), + val: nativeToScVal(v, valOpts) + }); + }) ); case 'number': case 'bigint': switch (opts?.type) { case 'u32': - return ScVal.scvU32(val); + return xdr.ScVal.scvU32(val); case 'i32': - return ScVal.scvI32(val); + return xdr.ScVal.scvI32(val); } return new ScInt(val, { type: opts?.type }).toScVal(); From 92e264a9798b0538dbfcdd113c6696a891aba962 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 13:43:59 -0700 Subject: [PATCH 05/11] Fixup linting --- src/scval.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/scval.js b/src/scval.js index 44d4f203..90adce8f 100644 --- a/src/scval.js +++ b/src/scval.js @@ -39,6 +39,7 @@ import xdr from './xdr'; import { Address } from './address'; +import { Contract } from './contract'; import { ScInt, scValToBigInt } from './numbers/index'; /** @@ -210,6 +211,9 @@ export function nativeToScVal(val, opts = {}) { case 'i32': return xdr.ScVal.scvI32(val); + + default: + break; } return new ScInt(val, { type: opts?.type }).toScVal(); From 1f240706b30bd3a0a32a08bdba08929e4321f7e3 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 14:16:13 -0700 Subject: [PATCH 06/11] Tests and bugfixes for complex typespecs --- src/scval.js | 12 +++++--- test/unit/scval_test.js | 64 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/scval.js b/src/scval.js index 90adce8f..80275d01 100644 --- a/src/scval.js +++ b/src/scval.js @@ -164,7 +164,7 @@ export function nativeToScVal(val, opts = {}) { case 'symbol': return xdr.ScVal.scvSymbol(copy); case 'string': - return xdr.ScVal.scvSymbol(copy); + return xdr.ScVal.scvString(copy); default: throw new TypeError( `invalid type (${opts.type}) specified for bytes-like value` @@ -173,8 +173,12 @@ export function nativeToScVal(val, opts = {}) { } if (Array.isArray(val)) { - if (val.length > 0 && val.some((v) => typeof v !== typeof v[0])) { - throw new TypeError(`array value (${val}) must have a single type`); + if (val.length > 0 && val.some((v) => typeof v !== typeof val[0])) { + throw new TypeError( + `array values (${val}) must have the same type (types: ${val + .map((v) => typeof v) + .join(',')})` + ); } return xdr.ScVal.scvVec(val.map((v) => nativeToScVal(v, opts))); } @@ -192,7 +196,7 @@ export function nativeToScVal(val, opts = {}) { // the type can be specified with an entry for the key and the value, // e.g. val = { 'hello': 1 } and opts.type = { hello: [ 'symbol', // 'u128' ]} or you can use `null` for the default interpretation - const [keyType, valType] = opts?.type[k] ?? [null, null]; + const [keyType, valType] = (opts?.type ?? {})[k] ?? [null, null]; const keyOpts = keyType ? { type: keyType } : {}; const valOpts = valType ? { type: valType } : {}; diff --git a/test/unit/scval_test.js b/test/unit/scval_test.js index f994dc0e..00825f3b 100644 --- a/test/unit/scval_test.js +++ b/test/unit/scval_test.js @@ -110,8 +110,8 @@ describe('parsing and building ScVals', function () { ], [xdr.ScVal.scvString('hello there!'), 'hello there!'], [xdr.ScVal.scvSymbol('hello'), 'hello'], - [xdr.ScVal.scvSymbol(Buffer.from('hello')), 'hello'], - [xdr.ScVal.scvString(Buffer.alloc(32, '\xba')), '\xba'.repeat(16)], + [xdr.ScVal.scvString(Buffer.from('hello')), Buffer.from('hello')], // ensure no conversion + [xdr.ScVal.scvSymbol(Buffer.from('hello')), Buffer.from('hello')], // ensure no conversion [ new StellarBase.Address(kp.publicKey()).toScVal(), (actual) => actual.toString() === kp.publicKey() @@ -146,4 +146,64 @@ describe('parsing and building ScVals', function () { } }); }); + + it('converts native types with customized types', function () { + [ + [1, 'u32', 'scvU32'], + [1, 'i32', 'scvI32'], + [1, 'i64', 'scvI64'], + [1, 'i128', 'scvI128'], + [1, 'u256', 'scvU256'], + ['a', 'symbol', 'scvSymbol'], + ['a', undefined, 'scvString'], + [Buffer.from('abcdefg'), undefined, 'scvBytes'], + [Buffer.from('abcdefg'), 'string', 'scvString'], + [Buffer.from('abcdefg'), 'symbol', 'scvSymbol'] + ].forEach(([input, typeSpec, outType]) => { + let scv = nativeToScVal(input, { type: typeSpec }); + expect(scv.switch().name).to.equal( + outType, + `in: ${input}, ${typeSpec}\nout: ${JSON.stringify(scv, null, 2)}` + ); + }); + + let scv; + + scv = nativeToScVal(['a', 'b', 'c'], { type: 'symbol' }); + expect(scv.switch().name).to.equal('scvVec'); + scv.value().forEach((v) => { + expect(v.switch().name).to.equal('scvSymbol'); + }); + + scv = nativeToScVal( + { + hello: 'world', + goodbye: [1, 2, 3] + }, + { + type: { + hello: ['symbol', null], + goodbye: [null, 'i32'] + } + } + ); + let e; + expect(scv.switch().name).to.equal('scvMap'); + + e = scv.value()[0]; + expect(e.key().switch().name).to.equal('scvSymbol', `${JSON.stringify(e)}`); + expect(e.val().switch().name).to.equal('scvString', `${JSON.stringify(e)}`); + + e = scv.value()[1]; + expect(e.key().switch().name).to.equal('scvString', `${JSON.stringify(e)}`); + expect(e.val().switch().name).to.equal('scvVec', `${JSON.stringify(e)}`); + expect(e.val().value()[0].switch().name).to.equal( + 'scvI32', + `${JSON.stringify(e)}` + ); + }); + + it('throws on arrays with mixed types', function () { + expect(() => nativeToScVal([1, 'a', false])).to.throw(/same type/i); + }); }); From 19422da16fa250bca0ee89c9657b9095df1a68f2 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 14:39:07 -0700 Subject: [PATCH 07/11] Clean up some Buffer -> Uint8Array stuff --- src/numbers/index.js | 17 +++++++---------- src/scval.js | 9 +++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/numbers/index.js b/src/numbers/index.js index 68e6bbf6..65904e33 100644 --- a/src/numbers/index.js +++ b/src/numbers/index.js @@ -1,12 +1,9 @@ import { XdrLargeInt } from './xdr_large_int'; -import { Uint128 } from './uint128'; -import { Uint256 } from './uint256'; -import { Int128 } from './int128'; -import { Int256 } from './int256'; - -export { Uint256, Int256, Uint128, Int128 }; - +export { Uint128 } from './uint128'; +export { Uint256 } from './uint256'; +export { Int128 } from './int128'; +export { Int256 } from './int256'; export { ScInt } from './sc_int'; export { XdrLargeInt }; @@ -21,8 +18,8 @@ export { XdrLargeInt }; * let scv = contract.call("add", x, y); // assume it returns an xdr.ScVal * let bigi = scValToBigInt(scv); * - * new ScInt(bigi); // if you don't care about types, and - * new XdrLargeInt('i128', bigi); // if you do + * new ScInt(bigi); // if you don't care about types, and + * new XdrLargeInt('i128', bigi); // if you do * ``` * * @param {xdr.ScVal} scv - the raw XDR value to parse into an integer @@ -31,7 +28,7 @@ export { XdrLargeInt }; * @throws {TypeError} if the `scv` input value doesn't represent an integer */ export function scValToBigInt(scv) { - const type = scv.switch().name.slice(3).toLowerCase(); + const type = scv.switch().name.slice(3).toLowerCase(); // if it ain't broke... switch (scv.switch().name) { case 'scvU32': diff --git a/src/scval.js b/src/scval.js index 80275d01..0685eecf 100644 --- a/src/scval.js +++ b/src/scval.js @@ -262,7 +262,7 @@ export function nativeToScVal(val, opts = {}) { * - map -> key-value object of any of the above (via recursion) * - bool -> boolean * - bytes -> Uint8Array - * - string, symbol -> string|Buffer + * - string, symbol -> string|Uint8Array * * If no conversion can be made, this just "unwraps" the smart value to return * its underlying XDR value. @@ -316,7 +316,12 @@ export function scValToNative(scv) { case xdr.ScValType.scvString().value: case xdr.ScValType.scvSymbol().value: - return scv.value(); // string|Buffer + let v = scv.value(); // string|Buffer + if (Buffer.isBuffer(v)) { + // trying to avoid bubbling up problematic Buffer type + return Uint8Array.from(v); + } + return v; // string // these can be converted to bigint case xdr.ScValType.scvTimepoint().value: From bd7836a3f3d8f365a422c9e2ca3433d8ea3bfa22 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Fri, 30 Jun 2023 14:40:43 -0700 Subject: [PATCH 08/11] Linting fixup --- src/scval.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scval.js b/src/scval.js index 0685eecf..134fe549 100644 --- a/src/scval.js +++ b/src/scval.js @@ -315,13 +315,14 @@ export function scValToNative(scv) { return scv.value(); case xdr.ScValType.scvString().value: - case xdr.ScValType.scvSymbol().value: - let v = scv.value(); // string|Buffer + case xdr.ScValType.scvSymbol().value: { + const v = scv.value(); // string|Buffer if (Buffer.isBuffer(v)) { // trying to avoid bubbling up problematic Buffer type return Uint8Array.from(v); } return v; // string + } // these can be converted to bigint case xdr.ScValType.scvTimepoint().value: From 06b9adecc55f83d6dadf4949ce970a064434bd41 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Mon, 3 Jul 2023 13:11:03 -0700 Subject: [PATCH 09/11] Move type out of abstraction layer --- src/numbers/index.js | 10 +++++----- src/numbers/xdr_large_int.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/numbers/index.js b/src/numbers/index.js index 65904e33..08f45a69 100644 --- a/src/numbers/index.js +++ b/src/numbers/index.js @@ -1,4 +1,4 @@ -import { XdrLargeInt } from './xdr_large_int'; +import { XdrLargeInt, getType } from './xdr_large_int'; export { Uint128 } from './uint128'; export { Uint256 } from './uint256'; @@ -28,7 +28,7 @@ export { XdrLargeInt }; * @throws {TypeError} if the `scv` input value doesn't represent an integer */ export function scValToBigInt(scv) { - const type = scv.switch().name.slice(3).toLowerCase(); // if it ain't broke... + const scIntType = getType(scv.switch().name); switch (scv.switch().name) { case 'scvU32': @@ -37,18 +37,18 @@ export function scValToBigInt(scv) { case 'scvU64': case 'scvI64': - return new XdrLargeInt(type, scv.value()).toBigInt(); + return new XdrLargeInt(scIntType, scv.value()).toBigInt(); case 'scvU128': case 'scvI128': - return new XdrLargeInt(type, [ + return new XdrLargeInt(scIntType, [ scv.value().lo(), scv.value().hi() ]).toBigInt(); case 'scvU256': case 'scvI256': - return new XdrLargeInt(type, [ + return new XdrLargeInt(scIntType, [ scv.value().loLo(), scv.value().loHi(), scv.value().hiLo(), diff --git a/src/numbers/xdr_large_int.js b/src/numbers/xdr_large_int.js index 313e5532..843cc472 100644 --- a/src/numbers/xdr_large_int.js +++ b/src/numbers/xdr_large_int.js @@ -226,3 +226,15 @@ export class XdrLargeInt { } } } +/** + * Convert the raw `ScValType` string (e.g. 'scvI128', generated by the XDR) to + * an `ScIntType` (e.g. 'i128', used by this abstraction layer). + * + * @param {string} scvType - the `xdr.ScValType` as a string + * + * @returns {ScIntType} + */ + +export function getType(scvType) { + return scvType.slice(3).toLowerCase(); +} From c0f8d8ca8b7b53e463ecec7b9ad79e237fa8d754 Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Mon, 3 Jul 2023 13:11:50 -0700 Subject: [PATCH 10/11] [skip ci] Whitespace fixup --- src/numbers/xdr_large_int.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/numbers/xdr_large_int.js b/src/numbers/xdr_large_int.js index 843cc472..31c51a6e 100644 --- a/src/numbers/xdr_large_int.js +++ b/src/numbers/xdr_large_int.js @@ -226,6 +226,7 @@ export class XdrLargeInt { } } } + /** * Convert the raw `ScValType` string (e.g. 'scvI128', generated by the XDR) to * an `ScIntType` (e.g. 'i128', used by this abstraction layer). @@ -234,7 +235,6 @@ export class XdrLargeInt { * * @returns {ScIntType} */ - export function getType(scvType) { return scvType.slice(3).toLowerCase(); } From 2b1b010b9b6f410910bf80f5507628ef57f61e1f Mon Sep 17 00:00:00 2001 From: George Kudrayvtsev Date: Mon, 3 Jul 2023 13:27:34 -0700 Subject: [PATCH 11/11] Clarify documentation on exception throwing --- src/scval.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scval.js b/src/scval.js index 134fe549..3111cc56 100644 --- a/src/scval.js +++ b/src/scval.js @@ -108,7 +108,8 @@ import { ScInt, scValToBigInt } from './numbers/index'; * - the type of the input object (or some inner value of said object) cannot * be determined (via `typeof`) * - the type you specified (via `opts.type`) is incompatible with the value - * you passed in (`val`), e.g. `nativeToScVal("a string", { type: 'i128' })`. + * you passed in (`val`), e.g. `nativeToScVal("a string", { type: 'i128' })`, + * though this does not apply for types that ignore `opts` (e.g. addresses). * * @example *