Skip to content

Commit

Permalink
Add mint field to transaction and functions for parsing Int64
Browse files Browse the repository at this point in the history
  • Loading branch information
balintklement authored and janmazak committed Jul 25, 2021
1 parent 5b419ad commit d36bce6
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 14 deletions.
22 changes: 11 additions & 11 deletions src/errors/invalidDataReason.ts
Expand Up @@ -18,17 +18,17 @@ export enum InvalidDataReason {

OUTPUTS_NOT_ARRAY = "outputs not an array",

OUTPUT_INVALID_AMOUNT = "invalid amount in an output",
OUTPUT_INVALID_POLICY_NAME = "invalid policy id in the token bundle in an output ",
OUTPUT_INVALID_TOKEN_BUNDLE_NOT_ARRAY = "invalid token bundle in an output - asset groups not an array",
OUTPUT_INVALID_TOKEN_BUNDLE_TOO_LARGE = "invalid token bundle in an output - too many asset groups",
OUTPUT_INVALID_TOKEN_BUNDLE_ORDERING = "invalid token bundle in an output - incorrect ordering of asset groups",
OUTPUT_INVALID_TOKEN_BUNDLE_NOT_UNIQUE = "invalid token bundle in an output - policyIds not unique",
OUTPUT_INVALID_ASSET_NAME = "invalid asset name in the token bundle in an output",
OUTPUT_INVALID_ASSET_GROUP_NOT_ARRAY = "invalid asset group in the token bundle in an output - tokens not an array",
OUTPUT_INVALID_ASSET_GROUP_TOO_LARGE = "invalid asset group in the token bundle in an output - too many tokens",
OUTPUT_INVALID_ASSET_GROUP_ORDERING = "invalid asset group in the token bundle in an output - incorrect ordering of tokens",
OUTPUT_INVALID_ASSET_GROUP_NOT_UNIQUE = "invalid asset group in the token bundle in an output - token names not unique",
OUTPUT_INVALID_AMOUNT = "invalid amount in an output (or mint field)",
OUTPUT_INVALID_POLICY_NAME = "invalid policy id in the token bundle in an output (or mint field)",
OUTPUT_INVALID_TOKEN_BUNDLE_NOT_ARRAY = "invalid token bundle in an output (or mint field) - asset groups not an array",
OUTPUT_INVALID_TOKEN_BUNDLE_TOO_LARGE = "invalid token bundle in an output (or mint field) - too many asset groups",
OUTPUT_INVALID_TOKEN_BUNDLE_ORDERING = "invalid token bundle in an output (or mint field) - incorrect ordering of asset groups",
OUTPUT_INVALID_TOKEN_BUNDLE_NOT_UNIQUE = "invalid token bundle in an output (or mint field) - policyIds not unique",
OUTPUT_INVALID_ASSET_NAME = "invalid asset name in the token bundle in an output (or mint field)",
OUTPUT_INVALID_ASSET_GROUP_NOT_ARRAY = "invalid asset group in the token bundle in an output (or mint field) - tokens not an array",
OUTPUT_INVALID_ASSET_GROUP_TOO_LARGE = "invalid asset group in the token bundle in an output (or mint field) - too many tokens",
OUTPUT_INVALID_ASSET_GROUP_ORDERING = "invalid asset group in the token bundle in an output (or mint field) - incorrect ordering of tokens",
OUTPUT_INVALID_ASSET_GROUP_NOT_UNIQUE = "invalid asset group in the token bundle in an output (or mint field) - token names not unique",

OUTPUT_INVALID_ADDRESS = "invalid address in an output",

Expand Down
9 changes: 8 additions & 1 deletion src/parsing/transaction.ts
Expand Up @@ -24,7 +24,7 @@ import {
} from "../types/public"
import { unreachable } from "../utils/assert"
import { isArray, parseBIP32Path, validate } from "../utils/parse"
import { parseHexString, parseHexStringOfLength, parseUint32_t, parseUint64_str } from "../utils/parse"
import { parseHexString, parseHexStringOfLength, parseInt64_str, parseUint32_t, parseUint64_str } from "../utils/parse"
import { parseAddress } from "./address"
import { parseCertificate } from "./certificate"
import { ASSET_GROUPS_MAX, MAX_LOVELACE_SUPPLY_STR, TOKENS_IN_GROUP_MAX } from "./constants"
Expand Down Expand Up @@ -129,6 +129,12 @@ export function parseTransaction(tx: Transaction): ParsedTransaction {
? null
: parseUint64_str(tx.validityIntervalStart, {}, InvalidDataReason.VALIDITY_INTERVAL_START_INVALID)

// mint instructions
validate(isArray(tx.mintInstructions ?? []), InvalidDataReason.OUTPUT_INVALID_TOKEN_BUNDLE_NOT_ARRAY)
const mintInstructions = tx.mintInstructions == null
? null
: parseTokenBundle(tx.mintInstructions, parseInt64_str)

return {
network,
inputs,
Expand All @@ -139,6 +145,7 @@ export function parseTransaction(tx: Transaction): ParsedTransaction {
withdrawals,
certificates,
fee,
mintInstructions,
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/types/internal.ts
Expand Up @@ -14,6 +14,10 @@ export type Uint32_t = number & { __type: 'uint32_t' }
export type Uint16_t = number & { __type: 'uint16_t' }
export type Uint8_t = number & { __type: 'uint8_t' }

export type Int64_str = string & { __type: 'int64_t' }
export type _Int64_num = number & { __type: 'int64_t' }
export type _Int64_bigint = bigint & { __type: 'int64_t' }

// Reexport blockchain spec
export { AddressType, CertificateType, RelayType, PoolKeyType, PoolOwnerType, PoolRewardAccountType, TransactionSigningMode, TxAuxiliaryDataType, TxOutputDestinationType }
export { Version, DeviceCompatibility } from './public'
Expand Down Expand Up @@ -95,6 +99,7 @@ export type ParsedTransaction = {
withdrawals: ParsedWithdrawal[]
auxiliaryData: ParsedTxAuxiliaryData | null
validityIntervalStart: Uint64_str | null
mintInstructions: Array<ParsedAssetGroup<Int64_str>> | null
}

export type ParsedSigningRequest = {
Expand Down
33 changes: 32 additions & 1 deletion src/types/public.ts
Expand Up @@ -715,6 +715,33 @@ export type Withdrawal = {
amount: bigint_like,
};

/**
* Describes a single token to be minted or burned during the transaction
* @category ~Mary~ --> todo?
* @see [[MintAssetGroup]]
*/
export type MintToken = {
assetNameHex: string,
/** Note: device does not know the number of decimal places the token uses */
amount: bigint_like,
};

/**
* Describes a group of assets defined in the same policy to be minted or burned during the transaction
* @category ~Mary~ --> todo?
* @see [[Transaction]]
*/
export type MintAssetGroup = {
policyIdHex: string,
/**
* The keys must be sorted lowest value to highest to reflect a valid canonical CBOR.
* The sorting rules (as described in the [CBOR RFC](https://datatracker.ietf.org/doc/html/rfc7049#section-3.9)) are:
* * if two keys have different lengths, the shorter one sorts earlier;
* * if two keys have the same length, the one with the lower value in lexical order sorts earlier.
*/
tokens: Array<MintToken>,
};

/**
* Device app flags
* @category Basic types
Expand Down Expand Up @@ -995,7 +1022,11 @@ export type Transaction = {
* Validity start (block height) if any.
* Transaction becomes valid only starting from this block height.
*/
validityIntervalStart?: bigint_like | null
validityIntervalStart?: bigint_like | null,
/**
* Mint or burn instructions (if any)
*/
mintInstructions?: Array<MintAssetGroup> | null,
}

/**
Expand Down
56 changes: 55 additions & 1 deletion src/utils/parse.ts
@@ -1,8 +1,10 @@
import { InvalidData } from "../errors"
import type { InvalidDataReason } from "../errors/index"
import type { _Uint64_bigint, _Uint64_num, FixlenHexString, HexString, Uint8_t, Uint16_t, Uint32_t, Uint64_str, ValidBIP32Path, VarlenAsciiString } from "../types/internal"
import type { _Int64_bigint, _Int64_num, _Uint64_bigint, _Uint64_num, FixlenHexString, HexString, Int64_str, Uint8_t, Uint16_t, Uint32_t, Uint64_str, ValidBIP32Path, VarlenAsciiString } from "../types/internal"

export const MAX_UINT_64_STR = "18446744073709551615"
export const MIN_INT_64_STR = "-9223372036854775808"
export const MAX_INT_64_STR = "9223372036854775807"

export const isString = (data: unknown): data is string =>
typeof data === "string"
Expand Down Expand Up @@ -64,6 +66,41 @@ export const isUintStr = (data: unknown, constraints: { min?: string, max?: stri
)
}


export const isInt64str = (data: unknown): data is Int64_str =>
isIntStr(data, {})

export const isInt64Number = (data: unknown): data is _Int64_num =>
isInteger(data) && data >= Number.MIN_SAFE_INTEGER && data <= Number.MAX_SAFE_INTEGER

export const isInt64Bigint = (data: unknown): data is _Int64_bigint =>
(typeof data === 'bigint') && isInt64str(data.toString())

export const isIntStr = (data: unknown, constraints: { min?: string, max?: string }): data is string => {
const min = constraints.min ?? MIN_INT_64_STR
const max = constraints.max ?? MAX_INT_64_STR

let hasValidFormat = isString(data)
// check format via RegExp
&& /^-?[0-9]*$/.test(data)
// Length checks
&& data.length > 0
&& data.length <= Math.max(min.length, max.length)

let isValidNegativeNumber = isString(data) && data.startsWith("-") &&
// leading zeros
(data.length === 2 || data[1] !== "0") &&
// if number is negative: greater or equal than min value (Note: this is string comparison!)
(data.length < min.length || data <= min)
let isValidPositiveNumber = isString(data) && !data.startsWith("-") &&
// leading zeros
(data.length === 1 || data[0] !== "0") &&
// if number is positive: less or equal than max value (Note: this is string comparison!)
(data.length < max.length || data <= max)

return hasValidFormat && (isValidNegativeNumber || isValidPositiveNumber)
}

export function validate(cond: boolean, errMsg: InvalidDataReason): asserts cond {
if (!cond) throw new InvalidData(errMsg)
}
Expand All @@ -89,6 +126,23 @@ export function parseHexStringOfLength<L extends number>(str: unknown, length: L
return str
}


export function parseInt64_str(val: unknown, constraints: { min?: string, max?: string}, errMsg: InvalidDataReason): Int64_str {
switch (typeof val) {
case 'string':
validate(isInt64str(val) && isIntStr(val, constraints), errMsg)
return val
case 'number':
validate(isInt64Number(val) && isIntStr(val.toString(), constraints), errMsg)
return val.toString() as Int64_str
case 'bigint':
validate(isInt64Bigint(val) && isIntStr(val.toString(), constraints), errMsg)
return val.toString() as Int64_str
default:
validate(false, errMsg)
}
}

export function parseUint64_str(val: unknown, constraints: { min?: string, max?: string }, errMsg: InvalidDataReason): Uint64_str {
switch (typeof val) {
case 'string':
Expand Down

0 comments on commit d36bce6

Please sign in to comment.