Skip to content

Commit

Permalink
Implement review suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielKerekes committed Aug 9, 2022
1 parent d5a786b commit 760d20b
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 62 deletions.
12 changes: 10 additions & 2 deletions src/errors/validationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export enum ValidationErrorReason {
// Fixable validation errors
CBOR_IS_NOT_CANONICAL = 'CBOR is not canonical',
OPTIONAL_EMPTY_LISTS_AND_MAPS_MUST_NOT_BE_INCLUDED = 'Optional empty lists and maps must not be included as part of the transaction body or its elements',
OUTPUT_WITHOUT_TOKENS_MUST_BE_A_SIMPLE_TUPLE = 'Outputs containing no multi-asset tokens must be serialized as a simple tuple',
OUTPUT_AMOUNT_WITHOUT_TOKENS_MUST_NOT_BE_A_TUPLE = 'Output amount (value) without tokens must not be a tuple',
INLINE_DATUM_MUST_NOT_BE_EMPTY_IF_DEFINED = 'Inline datum must not be empty if defined',
REFERENCE_SCRIPT_MUST_NOT_BE_EMPTY_IF_DEFINED = 'Reference script must not be empty if defined',
COLLATERAL_RETURN_MUST_NOT_CONTAIN_DATUM = 'Collateral return must not contain datum',
COLLATERAL_RETURN_MUST_NOT_CONTAIN_REFERENCE_SCRIPT = 'Collateral return must not contain reference script',
}

const FIXABLE = true
Expand All @@ -37,7 +41,11 @@ const validationErrorFixability: Record<ValidationErrorReason, boolean> = {
[ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_MINT_ENTRY]: UNFIXABLE,
[ValidationErrorReason.CBOR_IS_NOT_CANONICAL]: FIXABLE,
[ValidationErrorReason.OPTIONAL_EMPTY_LISTS_AND_MAPS_MUST_NOT_BE_INCLUDED]: FIXABLE,
[ValidationErrorReason.OUTPUT_WITHOUT_TOKENS_MUST_BE_A_SIMPLE_TUPLE]: FIXABLE,
[ValidationErrorReason.OUTPUT_AMOUNT_WITHOUT_TOKENS_MUST_NOT_BE_A_TUPLE]: FIXABLE,
[ValidationErrorReason.INLINE_DATUM_MUST_NOT_BE_EMPTY_IF_DEFINED]: FIXABLE,
[ValidationErrorReason.REFERENCE_SCRIPT_MUST_NOT_BE_EMPTY_IF_DEFINED]: FIXABLE,
[ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_DATUM]: FIXABLE,
[ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_REFERENCE_SCRIPT]: FIXABLE,
}

export type ValidationError = {
Expand Down
38 changes: 19 additions & 19 deletions src/txParsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ParseErrorReason } from './errors'
import type { Parser, WithoutType } from './parsers'
import { createParser, isArray, isMapWithKeysOfType, isNumber, isUint, isUintOfMaxSize, parseArray, parseBasedOnType, parseBuffer, parseBufferOfLength, parseBufferOfMaxLength, parseEmbeddedCborBytes, parseInt, parseMap, parseNullable, parseOptional, parseStringOfMaxLength, parseTuple, parseUint, validate } from './parsers'
import type { Amount, BabbageTransactionOutput, DatumHash, DatumInline, GenesisKeyDelegation, LegacyTransactionOutput, MoveInstantaneousRewardsCertificate, Multiasset, PoolMetadata, PoolParams, PoolRegistrationCertificate, PoolRetirementCertificate, Port, RawTransaction, RelayMultiHostName, RelaySingleHostAddress, RelaySingleHostName, RequiredSigner, StakeCredentialKey, StakeCredentialScript, StakeDelegationCertificate, StakeDeregistrationCertificate, StakeRegistrationCertificate, Transaction, TransactionBody, TransactionInput, TransactionOutput, Unparsed, Withdrawal } from './types'
import { AmountType, ASSET_NAME_MAX_LENGTH, CertificateType, DATUM_HASH_LENGTH, DatumType, DNS_NAME_MAX_LENGTH, IPV4_LENGTH, IPV6_LENGTH, KEY_HASH_LENGTH, METADATA_HASH_LENGTH, OutputType, POOL_KEY_HASH_LENGTH, PORT_MAX_SIZE, RelayType, REWARD_ACCOUNT_LENGTH, SCRIPT_DATA_HASH_LENGTH, SCRIPT_HASH_LENGTH, StakeCredentialType, TX_ID_HASH_LENGTH, URL_MAX_LENGTH, VRF_KEY_HASH_LENGTH } from './types'
import { AmountType, ASSET_NAME_MAX_LENGTH, CertificateType, DATUM_HASH_LENGTH, DatumType, DNS_NAME_MAX_LENGTH, IPV4_LENGTH, IPV6_LENGTH, KEY_HASH_LENGTH, METADATA_HASH_LENGTH, POOL_KEY_HASH_LENGTH, PORT_MAX_SIZE, RelayType, REWARD_ACCOUNT_LENGTH, SCRIPT_DATA_HASH_LENGTH, SCRIPT_HASH_LENGTH, StakeCredentialType, TX_ID_HASH_LENGTH, TxOutputFormat, URL_MAX_LENGTH, VRF_KEY_HASH_LENGTH } from './types'
import { addIndefiniteLengthFlag, BabbageTransactionOutputKeys, TransactionBodyKeys, undefinedOnlyAtTheEnd } from './utils'

const dontParse: Parser<Unparsed> = (data: unknown) => data
Expand Down Expand Up @@ -75,6 +75,23 @@ const parseLegacyTxOutputDatumHash = (unparsedDatumHash: unknown): DatumHash | u
}
: undefined

const parseLegacyTxOutput = (unparsedTxOutput: unknown): LegacyTransactionOutput => {
const [address, amount, datumHash] = parseTuple(
unparsedTxOutput,
ParseErrorReason.INVALID_TX_OUTPUT,
parseAddress,
parseAmount,
parseLegacyTxOutputDatumHash,
)

return {
format: TxOutputFormat.ARRAY_LEGACY,
address,
amount,
datumHash,
}
}

const parseDatumType = (unparsedDatumType: unknown): DatumType => {
validate(isNumber(unparsedDatumType), ParseErrorReason.INVALID_OUTPUT_DATUM_TYPE)
validate(unparsedDatumType in DatumType, ParseErrorReason.INVALID_OUTPUT_DATUM_TYPE)
Expand All @@ -97,30 +114,13 @@ const parseDatum = createParser(
parseDatumInline,
)

const parseLegacyTxOutput = (unparsedTxOutput: unknown): LegacyTransactionOutput => {
const [address, amount, datumHash] = parseTuple(
unparsedTxOutput,
ParseErrorReason.INVALID_TX_OUTPUT,
parseAddress,
parseAmount,
parseLegacyTxOutputDatumHash,
)

return {
type: OutputType.ARRAY_LEGACY,
address,
amount,
datumHash,
}
}

const parseReferenceScript = createParser(parseEmbeddedCborBytes, ParseErrorReason.INVALID_OUTPUT_REFERENCE_SCRIPT)

const parseBabbageTxOutput = (unparsedTxOutput: unknown): BabbageTransactionOutput => {
validate(isMapWithKeysOfType(unparsedTxOutput, isNumber), ParseErrorReason.INVALID_TX_OUTPUT)

return {
type: OutputType.MAP_BABBAGE,
format: TxOutputFormat.MAP_BABBAGE,
address: parseAddress(unparsedTxOutput.get(BabbageTransactionOutputKeys.ADDRESS)),
amount: parseAmount(unparsedTxOutput.get(BabbageTransactionOutputKeys.AMOUNT)),
datum: parseOptional(unparsedTxOutput.get(BabbageTransactionOutputKeys.DATUM), parseDatum),
Expand Down
8 changes: 4 additions & 4 deletions src/txSerializers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tagged } from 'cbor'

import type { Amount, AssetName, BabbageTransactionOutput, Certificate, Coin, Datum, LegacyTransactionOutput, Multiasset, PolicyId, PoolMetadata, PoolParams, RawTransaction, ReferenceScript, Relay, RewardAccount, StakeCredential, Transaction, TransactionBody, TransactionInput, TransactionOutput, Withdrawal } from './types'
import { AmountType, CertificateType, DatumType, OutputType, RelayType } from './types'
import { AmountType, CertificateType, DatumType, RelayType,TxOutputFormat } from './types'
import { addIndefiniteLengthFlag, BabbageTransactionOutputKeys, CborTag, filteredMap, TransactionBodyKeys } from './utils'

const identity = <T>(x: T): T => x
Expand Down Expand Up @@ -52,10 +52,10 @@ const serializeBabbageTxOutput = (output: BabbageTransactionOutput) =>
])

const serializeTxOutput = (output: TransactionOutput) => {
switch (output.type) {
case OutputType.ARRAY_LEGACY:
switch (output.format) {
case TxOutputFormat.ARRAY_LEGACY:
return serializeLegacyTxOutput(output)
case OutputType.MAP_BABBAGE:
case TxOutputFormat.MAP_BABBAGE:
return serializeBabbageTxOutput(output)
}
}
Expand Down
46 changes: 40 additions & 6 deletions src/txTransformers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Amount, Multiasset, RawTransaction, Transaction, TransactionBody, TransactionOutput } from './types'
import { AmountType } from './types'
import type { Amount, Datum, Multiasset, RawTransaction, ReferenceScript, Transaction, TransactionBody, TransactionOutput } from './types'
import { AmountType, DatumType, TxOutputFormat } from './types'

const transformOptionalList = <T>(optionalList?: T[]): T[] | undefined =>
optionalList?.length === 0 ? undefined : optionalList
Expand Down Expand Up @@ -36,10 +36,44 @@ const transformAmount = (amount: Amount): Amount => {
}
}

const transformTxOutput = (output: TransactionOutput): TransactionOutput => ({
...output,
amount: transformAmount(output.amount),
})
const transformDatum = (datum: Datum | undefined): Datum | undefined => {
if (datum === undefined) return datum

switch (datum.type) {
case DatumType.HASH:
return datum
case DatumType.INLINE:
if (datum.bytes.length === 0) {
return undefined
}

return datum
}
}

const transformReferenceScript = (referenceScript: ReferenceScript | undefined): ReferenceScript | undefined =>
referenceScript === undefined
? undefined
: referenceScript.length === 0
? undefined
: referenceScript

const transformTxOutput = (output: TransactionOutput): TransactionOutput => {
switch (output.format) {
case TxOutputFormat.ARRAY_LEGACY:
return {
...output,
amount: transformAmount(output.amount),
}
case TxOutputFormat.MAP_BABBAGE:
return {
...output,
amount: transformAmount(output.amount),
datum: transformDatum(output.datum),
referenceScript: transformReferenceScript(output.referenceScript),
}
}
}

export const transformTxBody = (txBody: TransactionBody): TransactionBody => ({
...txBody,
Expand Down
37 changes: 31 additions & 6 deletions src/txValidators.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ValidationError } from './errors'
import { err, ValidationErrorReason } from './errors'
import type { Certificate, Int, Mint, Multiasset, RequiredSigner, StakeCredentialType, StakeDelegationCertificate, StakeDeregistrationCertificate, StakeRegistrationCertificate, TransactionBody, TransactionInput, TransactionOutput, Uint, Withdrawal } from './types'
import { AmountType, CertificateType } from './types'
import type { Amount, Certificate, Int, Mint, Multiasset, RequiredSigner, StakeCredentialType, StakeDelegationCertificate, StakeDeregistrationCertificate, StakeRegistrationCertificate, TransactionBody, TransactionInput, TransactionOutput, Uint, Withdrawal } from './types'
import { AmountType, CertificateType , DatumType, TxOutputFormat} from './types'
import { bind, getRewardAccountStakeCredentialType } from './utils'

const UINT16_MAX = 65535
Expand Down Expand Up @@ -65,7 +65,7 @@ function *validateMultiasset<T>(multiasset: Multiasset<T>, validateAmount: (n: T
}
}

function *validateTxOutput({amount}: TransactionOutput, position: string): ValidatorReturnType {
function *validateTxOutputAmount(amount: Amount, position: string): ValidatorReturnType {
switch (amount.type) {
case AmountType.WITHOUT_MULTIASSET:
yield* validateUint64(amount.coin, `${position}.amount`)
Expand All @@ -76,17 +76,30 @@ function *validateTxOutput({amount}: TransactionOutput, position: string): Valid
// function, this is a very specific check for the output format
// that it is okay that they are defacto preformed twice with
// different ValidationErrors, and both errors are marked as fixable
yield* validate(amount.multiasset.length > 0, err(ValidationErrorReason.OUTPUT_WITHOUT_TOKENS_MUST_BE_A_SIMPLE_TUPLE, `${position}.amount`))
yield* validate(amount.multiasset.length > 0, err(ValidationErrorReason.OUTPUT_AMOUNT_WITHOUT_TOKENS_MUST_NOT_BE_A_TUPLE, `${position}.amount`))
yield* validateMultiasset(amount.multiasset, validateUint64, `${position}`)
break
}
}

function *validateTxOutput(output: TransactionOutput, position: string): ValidatorReturnType {
yield* validateTxOutputAmount(output.amount, position)

switch (output.format) {
case TxOutputFormat.MAP_BABBAGE:
yield *validate(output.datum?.type !== DatumType.INLINE || output.datum.bytes.length > 0, err(ValidationErrorReason.INLINE_DATUM_MUST_NOT_BE_EMPTY_IF_DEFINED, `${position}.datum.bytes`))
yield *validate(output.referenceScript == null || output.referenceScript?.length > 0, err(ValidationErrorReason.REFERENCE_SCRIPT_MUST_NOT_BE_EMPTY_IF_DEFINED, `${position}.reference_script`))
break
default:
break
}
}

function *validateTxOutputs(outputs: TransactionOutput[]): ValidatorReturnType {
yield* validateListConstraints(outputs, 'transaction_body.outputs', true)

for (const [i, output] of outputs.entries()) {
validateTxOutput(output, `transaction_body.outputs[${i}]`)
yield *validateTxOutput(output, `transaction_body.outputs[${i}]`)
}
}

Expand Down Expand Up @@ -181,6 +194,18 @@ function *validateReferenceInputs(referenceInputs: TransactionInput[]): Validato
}
}

function *validateTxCollateralReturnOutput(output: TransactionOutput, position: string): ValidatorReturnType {
validateTxOutputAmount(output.amount, position)
switch (output.format) {
case TxOutputFormat.MAP_BABBAGE:
yield* validate(output.datum == null, err(ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_DATUM, `${position}.datum`))
yield* validate(output.referenceScript == null, err(ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_REFERENCE_SCRIPT, `${position}.reference_script`))
break
default:
break
}
}

/**
* Checks if a transaction contains pool registration certificate, if it does
* runs a series of validators for pool registration transactions.
Expand Down Expand Up @@ -224,7 +249,7 @@ function *validateTxBody(txBody: TransactionBody): ValidatorReturnType {
yield* validateOptional(txBody.requiredSigners, validateRequiredSigners)
yield* validateOptional(txBody.networkId, bind(validateUint64, 'transaction_body.network_id'))
yield* validatePoolRegistrationTransaction(txBody)
yield* validateOptional(txBody.collateralReturnOutput, bind(validateTxOutput, 'transaction_body.collateral_return_output'))
yield* validateOptional(txBody.collateralReturnOutput, bind(validateTxCollateralReturnOutput, 'transaction_body.collateral_return_output'))
yield* validateOptional(txBody.totalCollateral, bind(validateUint64, 'transaction_body.total_collateral'))
yield* validateOptional(txBody.referenceInputs, validateReferenceInputs)
}
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ export type DatumInline = {
export type Datum = DatumHash | DatumInline

// Transaction output
export enum OutputType {
export enum TxOutputFormat {
ARRAY_LEGACY,
MAP_BABBAGE,
}

export type LegacyTransactionOutput = {
type: OutputType.ARRAY_LEGACY,
format: TxOutputFormat.ARRAY_LEGACY,
address: Address,
amount: Amount,
datumHash?: DatumHash,
Expand All @@ -105,7 +105,7 @@ export type LegacyTransactionOutput = {
export type ReferenceScript = Buffer

export type BabbageTransactionOutput = {
type: OutputType.MAP_BABBAGE,
format: TxOutputFormat.MAP_BABBAGE,
address: Address,
amount: Amount,
datum?: Datum,
Expand Down
Loading

0 comments on commit 760d20b

Please sign in to comment.