diff --git a/docs/guides/error-handling.md b/docs/guides/error-handling.md index 9252f30f8f..1a1c41816f 100644 --- a/docs/guides/error-handling.md +++ b/docs/guides/error-handling.md @@ -79,7 +79,6 @@ BaseError │ ├── TransactionError │ ├── DecodeError -│ ├── EncodeError │ ├── PayloadLengthError │ ├── DryRunError │ ├── IllegalBidFeeError diff --git a/src/tx/builder/common.ts b/src/tx/builder/common.ts index 162bb09e2c..acf37e15f4 100644 --- a/src/tx/builder/common.ts +++ b/src/tx/builder/common.ts @@ -1,6 +1,8 @@ import { decode as rlpDecode, encode as rlpEncode } from 'rlp'; import { Field, BinaryData } from './field-types'; -import { ArgumentError, DecodeError, SchemaNotFoundError } from '../../utils/errors'; +import { + ArgumentError, DecodeError, SchemaNotFoundError, InternalError, +} from '../../utils/errors'; import { Encoding, Encoded, encode, decode, } from '../../utils/encoder'; @@ -8,7 +10,7 @@ import { readInt } from './helpers'; type Schemas = ReadonlyArray<{ tag: { constValue: number } & Field; - version: { constValue: number } & Field; + version: { constValue: number; constValueOptional: boolean } & Field; }>; export function getSchema( @@ -19,7 +21,11 @@ export function getSchema( ): Array<[string, Field]> { const subSchemas = schemas.filter((s) => s.tag.constValue === tag); if (subSchemas.length === 0) throw new SchemaNotFoundError(`${Tag[tag]} (${tag})`, 0); - version ??= Math.max(...subSchemas.map((s) => s.version.constValue)); + if (version == null) { + const defaultSchema = subSchemas.find((schema) => schema.version.constValueOptional); + if (defaultSchema == null) throw new InternalError(`Can't find default schema of ${Tag[tag]} (${tag})`); + version = defaultSchema.version.constValue; + } const schema = subSchemas.find((s) => s.version.constValue === version); if (schema == null) throw new SchemaNotFoundError(`${Tag[tag]} (${tag})`, version); return Object.entries(schema); diff --git a/src/tx/builder/field-types/index.ts b/src/tx/builder/field-types/index.ts index eb3240635a..908d83d58b 100644 --- a/src/tx/builder/field-types/index.ts +++ b/src/tx/builder/field-types/index.ts @@ -1,33 +1,33 @@ -import abiVersion from './abi-version'; -import address from './address'; -import array from './array'; -import boolean from './boolean'; -import coinAmount from './coin-amount'; -import ctVersion from './ct-version'; -import encoded from './encoded'; -import entry from './entry'; -import enumeration from './enumeration'; -import fee from './fee'; -import field from './field'; -import gasLimit from './gas-limit'; -import gasPrice from './gas-price'; -import map from './map'; -import mptree from './mptree'; -import name from './name'; -import nameFee from './name-fee'; -import nameId from './name-id'; -import nonce from './nonce'; -import pointers from './pointers'; -import queryFee from './query-fee'; -import raw from './raw'; -import shortUInt from './short-u-int'; -import shortUIntConst from './short-u-int-const'; -import string from './string'; -import ttl from './ttl'; -import uInt from './u-int'; -import withDefault from './with-default'; -import withFormatting from './with-formatting'; -import wrapped from './wrapped'; +export { default as abiVersion } from './abi-version'; +export { default as address } from './address'; +export { default as array } from './array'; +export { default as boolean } from './boolean'; +export { default as coinAmount } from './coin-amount'; +export { default as ctVersion } from './ct-version'; +export { default as encoded } from './encoded'; +export { default as entry } from './entry'; +export { default as enumeration } from './enumeration'; +export { default as fee } from './fee'; +export { default as field } from './field'; +export { default as gasLimit } from './gas-limit'; +export { default as gasPrice } from './gas-price'; +export { default as map } from './map'; +export { default as mptree } from './mptree'; +export { default as name } from './name'; +export { default as nameFee } from './name-fee'; +export { default as nameId } from './name-id'; +export { default as nonce } from './nonce'; +export { default as pointers } from './pointers'; +export { default as queryFee } from './query-fee'; +export { default as raw } from './raw'; +export { default as shortUInt } from './short-u-int'; +export { default as shortUIntConst } from './short-u-int-const'; +export { default as string } from './string'; +export { default as ttl } from './ttl'; +export { default as uInt } from './u-int'; +export { default as withDefault } from './with-default'; +export { default as withFormatting } from './with-formatting'; +export { default as wrapped } from './wrapped'; export type BinaryData = Buffer | Buffer[] | Buffer[][] | Array<[Buffer, Array<[Buffer, Buffer[]]>]>; @@ -37,36 +37,3 @@ export interface Field { deserialize: (value: BinaryData, options: any) => any; recursiveType?: boolean; } - -export { - abiVersion, - address, - array, - boolean, - coinAmount, - ctVersion, - encoded, - entry, - enumeration, - fee, - field, - gasLimit, - gasPrice, - map, - mptree, - name, - nameFee, - nameId, - nonce, - pointers, - queryFee, - raw, - shortUInt, - shortUIntConst, - string, - ttl, - uInt, - withDefault, - withFormatting, - wrapped, -}; diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index 4d03cbdc35..5ef23dbdb4 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -4,7 +4,7 @@ import nacl, { SignKeyPair } from 'tweetnacl'; import { blake2b } from 'blakejs/blake2b.js'; import { encode as varuintEncode } from 'varuint-bitcoin'; -import { concatBuffers } from './other'; +import { concatBuffers, isItemOfArray } from './other'; import { decode, encode, Encoded, Encoding, } from './encoder'; @@ -22,23 +22,29 @@ export function getAddressFromPriv(secret: string | Uint8Array): Encoded.Account } /** - * Check if address is valid - * @param address - Address - * @param prefix - Transaction prefix. Default: 'ak' - * @returns is valid + * Check if data is encoded in one of provided encodings + * @param maybeEncoded - Data to check + * @param encodings - Rest parameters with encodings to check against */ -export function isAddressValid( - address: string, - prefix: Encoding = Encoding.AccountAddress, -): boolean { +export function isAddressValid(maybeEncoded: string): maybeEncoded is Encoded.AccountAddress; +export function isAddressValid( + maybeEncoded: string, + ...encodings: E[] +): maybeEncoded is Encoded.Generic; +export function isAddressValid(maybeEncoded: string, ...encodings: Encoding[]): boolean { + if (encodings.length === 0) encodings = [Encoding.AccountAddress]; try { - decode(address as Encoded.Generic); - const actualPrefix = address.split('_')[0]; - if (actualPrefix !== prefix) { - throw new ArgumentError('Encoded string type', prefix, actualPrefix); + decode(maybeEncoded as Encoded.Any); + const encoding = maybeEncoded.split('_')[0]; + if (!isItemOfArray(encoding, encodings)) { + throw new ArgumentError( + 'Encoded string type', + encodings.length > 1 ? `one of ${encodings.join(', ')}` : encodings[0], + encoding, + ); } return true; - } catch (e) { + } catch (error) { return false; } } diff --git a/test/unit/crypto.ts b/test/unit/crypto.ts index 6690ed5a74..bc538ab039 100644 --- a/test/unit/crypto.ts +++ b/test/unit/crypto.ts @@ -4,7 +4,7 @@ import { assert, expect } from 'chai'; import { buildTxHash, decode, Encoded, generateKeyPair, getAddressFromPriv, verifyMessage, isValidKeypair, isAddressValid, hash, genSalt, - sign, verify, messageToHash, signMessage, + sign, verify, messageToHash, signMessage, Encoding, } from '../../src'; // These keys are fixations for the encryption lifecycle tests and will @@ -54,10 +54,33 @@ describe('crypto', () => { }); }); - it('isAddressValid', () => { - expect(isAddressValid('test')).to.be.equal(false); - expect(isAddressValid('th_11111111111111111111111111111111273Yts')).to.be.equal(false); - expect(isAddressValid('ak_11111111111111111111111111111111273Yts')).to.be.equal(true); + describe('isAddressValid', () => { + it('rejects invalid encoded data', () => { + expect(isAddressValid('test')).to.be.equal(false); + expect(isAddressValid('th_11111111111111111111111111111111273Yts')).to.be.equal(false); + expect(isAddressValid('ak_11111111111111111111111111111111273Yts', Encoding.TxHash)) + .to.be.equal(false); + }); + + it('returns true for a valid address', () => { + const maybeValue: string = 'ak_11111111111111111111111111111111273Yts'; + const result = isAddressValid(maybeValue); + expect(result).to.be.equal(true); + // @ts-expect-error `result` is not chcked yet + let value: Encoded.AccountAddress = maybeValue; + if (result) value = maybeValue; + expect(value); + }); + + it('correctly checks against multiple encodings', () => { + const maybeValue: string = 'th_HZMNgTvEiyKeATpauJjjeWwZcyHapKG8bDgy2S1sCUEUQnbwK'; + const result = isAddressValid(maybeValue, Encoding.Name, Encoding.TxHash); + expect(result).to.be.equal(true); + // @ts-expect-error `result` is not chcked yet + let value: Encoded.Name | Encoded.TxHash = maybeValue; + if (result) value = maybeValue; + expect(value); + }); }); describe('sign', () => {