From 430f10c9475a2210b7815a2444a9bdc8180bfcd1 Mon Sep 17 00:00:00 2001 From: Jan Mazak Date: Wed, 1 Nov 2023 14:14:32 +0100 Subject: [PATCH 1/3] make credential type generic --- src/errors/parseError.ts | 8 +-- src/txParsers.ts | 50 +++++++++---------- src/txSerializers.ts | 12 ++--- src/txValidators.ts | 6 +-- src/types.ts | 18 +++---- src/utils.ts | 6 +-- test/integration/__fixtures__/transactions.ts | 8 +-- test/unit/certificates.test.ts | 20 ++++---- 8 files changed, 63 insertions(+), 65 deletions(-) diff --git a/src/errors/parseError.ts b/src/errors/parseError.ts index 8c605da..82cc3f1 100644 --- a/src/errors/parseError.ts +++ b/src/errors/parseError.ts @@ -23,10 +23,10 @@ export enum ParseErrorReason { INVALID_TX_OUTPUT = 'Invalid transaction output', INVALID_CERTIFICATE_TYPE = 'Invalid certificate type', - INVALID_STAKE_CREDENTIAL_TYPE = 'Invalid stake credential type', - INVALID_STAKE_CREDENTIAL_KEY_HASH = 'Invalid stake credential key hash', - INVALID_STAKE_CREDENTIAL_SCRIPT_HASH = 'Invalid stake credential script hash', - INVALID_STAKE_CREDENTIAL = 'Invalid stake credential', + INVALID_CREDENTIAL_TYPE = 'Invalid stake credential type', + INVALID_CREDENTIAL_KEY_HASH = 'Invalid stake credential key hash', + INVALID_CREDENTIAL_SCRIPT_HASH = 'Invalid stake credential script hash', + INVALID_CREDENTIAL = 'Invalid stake credential', INVALID_POOL_KEY_HASH = 'Invalid pool key hash', INVALID_VRF_KEY_HASH = 'Invalid vrf key hash', INVALID_POOL_PARAMS = 'Invalid pool params', diff --git a/src/txParsers.ts b/src/txParsers.ts index 5f5a9a1..6b8b692 100644 --- a/src/txParsers.ts +++ b/src/txParsers.ts @@ -40,8 +40,8 @@ import type { RelaySingleHostAddress, RelaySingleHostName, RequiredSigner, - StakeCredentialKey, - StakeCredentialScript, + KeyCredential, + ScriptCredential, StakeDelegationCertificate, StakeDeregistrationCertificate, StakeRegistrationCertificate, @@ -70,7 +70,7 @@ import { REWARD_ACCOUNT_LENGTH, SCRIPT_DATA_HASH_LENGTH, SCRIPT_HASH_LENGTH, - StakeCredentialType, + CredentialType, TX_ID_HASH_LENGTH, TxOutputFormat, URL_MAX_LENGTH, @@ -291,46 +291,44 @@ export const parseWithdrawals = ( })) } -const parseStakeCredentialType = ( - unparsedStakeCredentialType: unknown, -): StakeCredentialType => { +const parseCredentialType = ( + unparsedCredentialType: unknown, +): CredentialType => { validate( - isNumber(unparsedStakeCredentialType), - ParseErrorReason.INVALID_STAKE_CREDENTIAL_TYPE, + isNumber(unparsedCredentialType), + ParseErrorReason.INVALID_CREDENTIAL_TYPE, ) validate( - unparsedStakeCredentialType in StakeCredentialType, - ParseErrorReason.INVALID_STAKE_CREDENTIAL_TYPE, + unparsedCredentialType in CredentialType, + ParseErrorReason.INVALID_CREDENTIAL_TYPE, ) - return unparsedStakeCredentialType + return unparsedCredentialType } -const parseStakeCredentialKey = ( - data: unknown[], -): WithoutType => ({ +const parseKeyCredential = (data: unknown[]): WithoutType => ({ hash: parseBufferOfLength( data[0], KEY_HASH_LENGTH, - ParseErrorReason.INVALID_STAKE_CREDENTIAL_KEY_HASH, + ParseErrorReason.INVALID_CREDENTIAL_KEY_HASH, ), }) -const parseStakeCredentialScript = ( +const parseScriptCredential = ( data: unknown[], -): WithoutType => ({ +): WithoutType => ({ hash: parseBufferOfLength( data[0], SCRIPT_HASH_LENGTH, - ParseErrorReason.INVALID_STAKE_CREDENTIAL_SCRIPT_HASH, + ParseErrorReason.INVALID_CREDENTIAL_SCRIPT_HASH, ), }) -const parseStakeCredential = createParser( +const parseCredential = createParser( parseBasedOnType, - ParseErrorReason.INVALID_STAKE_CREDENTIAL, - parseStakeCredentialType, - parseStakeCredentialKey, - parseStakeCredentialScript, + ParseErrorReason.INVALID_CREDENTIAL, + parseCredentialType, + parseKeyCredential, + parseScriptCredential, ) const parsePoolKeyHash = createParser( @@ -496,19 +494,19 @@ const parseCertificateType = ( const parseStakeRegistrationCertificate = ( data: unknown[], ): WithoutType => ({ - stakeCredential: parseStakeCredential(data[0]), + stakeCredential: parseCredential(data[0]), }) const parseStakeDeregistrationCertificate = ( data: unknown[], ): WithoutType => ({ - stakeCredential: parseStakeCredential(data[0]), + stakeCredential: parseCredential(data[0]), }) const parseStakeDelegationCertificate = ( data: unknown[], ): WithoutType => ({ - stakeCredential: parseStakeCredential(data[0]), + stakeCredential: parseCredential(data[0]), poolKeyHash: parsePoolKeyHash(data[1]), }) diff --git a/src/txSerializers.ts b/src/txSerializers.ts index 0f5d00e..f4c5413 100644 --- a/src/txSerializers.ts +++ b/src/txSerializers.ts @@ -15,7 +15,7 @@ import type { ReferenceScript, Relay, RewardAccount, - StakeCredential, + Credential, Transaction, TransactionBody, TransactionInput, @@ -145,9 +145,9 @@ const serializePoolParams = (poolParams: PoolParams) => [ poolParams.poolMetadata && serializePoolMetadata(poolParams.poolMetadata), ] -const serializeStakeCredential = (stakeCredential: StakeCredential) => [ - stakeCredential.type, - stakeCredential.hash, +const serializeCredential = (credential: Credential) => [ + credential.type, + credential.hash, ] const serializeCertificate = (certificate: Certificate) => { @@ -156,12 +156,12 @@ const serializeCertificate = (certificate: Certificate) => { case CertificateType.STAKE_DEREGISTRATION: return [ certificate.type, - serializeStakeCredential(certificate.stakeCredential), + serializeCredential(certificate.stakeCredential), ] case CertificateType.STAKE_DELEGATION: return [ certificate.type, - serializeStakeCredential(certificate.stakeCredential), + serializeCredential(certificate.stakeCredential), certificate.poolKeyHash, ] case CertificateType.POOL_REGISTRATION: diff --git a/src/txValidators.ts b/src/txValidators.ts index a28f218..c53a8a5 100644 --- a/src/txValidators.ts +++ b/src/txValidators.ts @@ -7,7 +7,7 @@ import type { Mint, Multiasset, RequiredSigner, - StakeCredentialType, + CredentialType, StakeDelegationCertificate, StakeDeregistrationCertificate, StakeRegistrationCertificate, @@ -263,8 +263,8 @@ function* validateStakeCredentials( certificates: Certificate[] | undefined, withdrawals: Withdrawal[] | undefined, ): ValidatorReturnType { - const certificateStakeCredentialTypes: Set = new Set() - const withdrawalStakeCredentialTypes: Set = new Set() + const certificateStakeCredentialTypes: Set = new Set() + const withdrawalStakeCredentialTypes: Set = new Set() if (certificates) { // We must first filter out the certificates that contain stake credentials diff --git a/src/types.ts b/src/types.ts index 117853a..12acefb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -129,36 +129,36 @@ export enum CertificateType { MOVE_INSTANTANEOUS_REWARDS_CERT = 6, } -export enum StakeCredentialType { +export enum CredentialType { KEY_HASH = 0, SCRIPT_HASH = 1, } -export type StakeCredentialKey = { - type: StakeCredentialType.KEY_HASH +export type KeyCredential = { + type: CredentialType.KEY_HASH hash: KeyHash } -export type StakeCredentialScript = { - type: StakeCredentialType.SCRIPT_HASH +export type ScriptCredential = { + type: CredentialType.SCRIPT_HASH hash: ScriptHash } -export type StakeCredential = StakeCredentialKey | StakeCredentialScript +export type Credential = KeyCredential | ScriptCredential export type StakeRegistrationCertificate = { type: CertificateType.STAKE_REGISTRATION - stakeCredential: StakeCredential + stakeCredential: Credential } export type StakeDeregistrationCertificate = { type: CertificateType.STAKE_DEREGISTRATION - stakeCredential: StakeCredential + stakeCredential: Credential } export type StakeDelegationCertificate = { type: CertificateType.STAKE_DELEGATION - stakeCredential: StakeCredential + stakeCredential: Credential poolKeyHash: FixLenBuffer } diff --git a/src/utils.ts b/src/utils.ts index 71362cc..5286044 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import blake2b from 'blake2b' import cbor from 'cbor' import type {FixLenBuffer, RewardAccount} from './types' -import {StakeCredentialType} from './types' +import {CredentialType} from './types' export function assert(cond: boolean, errMsg: string): asserts cond { const msg = errMsg ? `: ${errMsg}` : '' @@ -65,9 +65,9 @@ export const getRewardAccountStakeCredentialType = ( // eslint-disable-next-line no-bitwise switch ((rewardAccount[0] >> 4) & 1) { case 0: - return StakeCredentialType.KEY_HASH + return CredentialType.KEY_HASH case 1: - return StakeCredentialType.SCRIPT_HASH + return CredentialType.SCRIPT_HASH default: throw Error('Invalid reward account type') } diff --git a/test/integration/__fixtures__/transactions.ts b/test/integration/__fixtures__/transactions.ts index 9066ebf..7e01e56 100644 --- a/test/integration/__fixtures__/transactions.ts +++ b/test/integration/__fixtures__/transactions.ts @@ -9,7 +9,7 @@ import { CertificateType, DatumType, RelayType, - StakeCredentialType, + CredentialType, TxOutputFormat, } from '../../../src/types' import { @@ -267,7 +267,7 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ { type: CertificateType.STAKE_REGISTRATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, @@ -277,7 +277,7 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ { type: CertificateType.STAKE_DEREGISTRATION, stakeCredential: { - type: StakeCredentialType.SCRIPT_HASH, + type: CredentialType.SCRIPT_HASH, hash: toFixLenBuffer( 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', 28, @@ -287,7 +287,7 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ { type: CertificateType.STAKE_DELEGATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, diff --git a/test/unit/certificates.test.ts b/test/unit/certificates.test.ts index 89c4942..777703a 100644 --- a/test/unit/certificates.test.ts +++ b/test/unit/certificates.test.ts @@ -5,7 +5,7 @@ import { Port, CertificateType, RelayType, - StakeCredentialType, + CredentialType, } from '../../src/types' import { ipv4ToBuffer, @@ -26,7 +26,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_REGISTRATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, @@ -42,7 +42,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_REGISTRATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, @@ -52,7 +52,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_REGISTRATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', 28, @@ -68,7 +68,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_REGISTRATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, @@ -78,7 +78,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_DEREGISTRATION, stakeCredential: { - type: StakeCredentialType.SCRIPT_HASH, + type: CredentialType.SCRIPT_HASH, hash: toFixLenBuffer( 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', 28, @@ -88,7 +88,7 @@ const ValidCertificatesTestCases: ValidParseTestCase[] = [ { type: CertificateType.STAKE_DELEGATION, stakeCredential: { - type: StakeCredentialType.KEY_HASH, + type: CredentialType.KEY_HASH, hash: toFixLenBuffer( '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', 28, @@ -189,17 +189,17 @@ const InvalidCertificatesTestCases: InvalidParseTestCase[] = [ { testName: 'Invalid stake registration credential', cbor: '8182008100', - errMsg: ParseErrorReason.INVALID_STAKE_CREDENTIAL, + errMsg: ParseErrorReason.INVALID_CREDENTIAL, }, { testName: 'Invalid stake registration credential type', cbor: '8182008202581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - errMsg: ParseErrorReason.INVALID_STAKE_CREDENTIAL_TYPE, + errMsg: ParseErrorReason.INVALID_CREDENTIAL_TYPE, }, { testName: 'Invalid stake registration key hash', cbor: '8182008200581d2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1dff', - errMsg: ParseErrorReason.INVALID_STAKE_CREDENTIAL_KEY_HASH, + errMsg: ParseErrorReason.INVALID_CREDENTIAL_KEY_HASH, }, { testName: 'Invalid pool registration owner', From b84f78326225b47f1800e70c50ffb747cc04812d Mon Sep 17 00:00:00 2001 From: Jan Mazak Date: Wed, 1 Nov 2023 13:39:11 +0100 Subject: [PATCH 2/3] conway: add new tx elements, CDDL sets, fix tags --- .cspell.json | 4 +- package.json | 2 +- src/errors/parseError.ts | 82 +- src/errors/validationError.ts | 59 +- src/parsers.ts | 98 +- src/txParsers.ts | 865 ++++++++--- src/txSerializers.ts | 241 ++- src/txTransformers.ts | 68 +- src/txValidators.ts | 352 +++-- src/types.ts | 256 +++- src/utils.ts | 24 +- test/integration/__fixtures__/transactions.ts | 1302 +++++++++++++---- test/integration/transform.test.ts | 15 +- test/unit/certificates.test.ts | 484 ++++-- test/unit/inputs.test.ts | 79 +- 15 files changed, 3125 insertions(+), 806 deletions(-) diff --git a/.cspell.json b/.cspell.json index c39ae33..5aaab1c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -21,9 +21,11 @@ "bech", "cardano", "cbor", + "Deleg", "deregistration", - "ipfs", + "DREP", "fixability", + "ipfs", "multiasset", "multiassets", "reserialized", diff --git a/package.json b/package.json index 01294ef..f852d21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-hw-interop-lib", - "version": "2.0.2", + "version": "3.0.0", "files": [ "dist" ], diff --git a/src/errors/parseError.ts b/src/errors/parseError.ts index 82cc3f1..0362710 100644 --- a/src/errors/parseError.ts +++ b/src/errors/parseError.ts @@ -1,17 +1,14 @@ export enum ParseErrorReason { - INVALID_ADDRESS = 'Invalid address', - INVALID_REWARD_ACCOUNT = 'Invalid reward account', INVALID_COIN = 'Invalid coin', - INVALID_EPOCH = 'Invalid epoch', - - INVALID_POLICY_ID = 'Invalid policy id', - INVALID_ASSET_NAME = 'Invalid asset name', - INVALID_MULTIASSET = 'Invalid multiasset map', INVALID_TRANSACTION_ID = 'Invalid transaction id', INVALID_TX_INPUT_INDEX = 'Invalid transaction input index', INVALID_TX_INPUT = 'Invalid transaction input', + INVALID_TX_INPUTS = 'Invalid transaction inputs', + INVALID_POLICY_ID = 'Invalid policy id', + INVALID_ASSET_NAME = 'Invalid asset name', + INVALID_MULTIASSET = 'Invalid multiasset map', INVALID_OUTPUT_ADDRESS = 'Invalid output address', INVALID_OUTPUT_AMOUNT = 'Invalid output amount', INVALID_OUTPUT_MULTIASSET = 'Invalid output multiasset', @@ -21,12 +18,18 @@ export enum ParseErrorReason { INVALID_OUTPUT_DATUM = 'Invalid output datum', INVALID_OUTPUT_REFERENCE_SCRIPT = 'Invalid output reference script', INVALID_TX_OUTPUT = 'Invalid transaction output', + INVALID_TX_OUTPUTS = 'Invalid transaction outputs', + + INVALID_FEE = 'Invalid transaction fee', + + INVALID_TTL = 'Invalid transaction ttl', INVALID_CERTIFICATE_TYPE = 'Invalid certificate type', - INVALID_CREDENTIAL_TYPE = 'Invalid stake credential type', - INVALID_CREDENTIAL_KEY_HASH = 'Invalid stake credential key hash', - INVALID_CREDENTIAL_SCRIPT_HASH = 'Invalid stake credential script hash', - INVALID_CREDENTIAL = 'Invalid stake credential', + INVALID_CERTIFICATE = 'Invalid certificate', + INVALID_CREDENTIAL_TYPE = 'Invalid credential type', + INVALID_CREDENTIAL_KEY_HASH = 'Invalid credential key hash', + INVALID_CREDENTIAL_SCRIPT_HASH = 'Invalid credential script hash', + INVALID_CREDENTIAL = 'Invalid credential', INVALID_POOL_KEY_HASH = 'Invalid pool key hash', INVALID_VRF_KEY_HASH = 'Invalid vrf key hash', INVALID_POOL_PARAMS = 'Invalid pool params', @@ -42,40 +45,61 @@ export enum ParseErrorReason { INVALID_RELAY_DNS_NAME = 'Invalid relay DNS name', INVALID_RELAY = 'Invalid relay', INVALID_RELAYS = 'Invalid relays', - INVALID_POOL_METADATA_URL = 'Invalid pool metadata url', + INVALID_POOL_METADATA_URL = 'Invalid pool metadata URL', INVALID_POOL_METADATA_METADATA_HASH = 'Invalid pool metadata metadata hash', INVALID_POOL_METADATA = 'Invalid pool metadata', - INVALID_CERTIFICATE = 'Invalid certificate', + INVALID_EPOCH = 'Invalid epoch', + INVALID_DREP_TYPE = 'Invalid DRep type', + INVALID_DREP = 'Invalid DRep', + INVALID_ANCHOR = 'Invalid anchor', + INVALID_ANCHOR_URL = 'Invalid anchor URL', + INVALID_ANCHOR_DATA_HASH = 'Invalid anchor data hash', + INVALID_CERTIFICATES = 'Invalid transaction certificates', INVALID_WITHDRAWAL_AMOUNT = 'Invalid withdrawal amount', - - INVALID_MINT_AMOUNT = 'Invalid mint amount', - - INVALID_COLLATERAL_INPUT_INDEX = 'Invalid transaction collateral input index', - INVALID_COLLATERAL_INPUT = 'Invalid transaction collateral input', - - INVALID_REFERENCE_INPUT_INDEX = 'Invalid transaction reference input index', - INVALID_REFERENCE_INPUT = 'Invalid transaction reference input', - - INVALID_TX_BODY_CBOR = 'Invalid transaction body CBOR', - INVALID_TX_BODY_UNKNOWN_ITEMS = 'Transaction body contains unknown items', - INVALID_TX_INPUTS = 'Invalid transaction inputs', - INVALID_TX_OUTPUTS = 'Invalid transaction outputs', - INVALID_FEE = 'Invalid transaction fee', - INVALID_TTL = 'Invalid transaction ttl', - INVALID_CERTIFICATES = 'Invalid transaction certificates', + INVALID_REWARD_ACCOUNT = 'Invalid reward account', INVALID_WITHDRAWALS = 'Invalid transaction withdrawals', + INVALID_AUXILIARY_DATA_HASH = 'Invalid transaction auxiliary data hash', + INVALID_VALIDITY_INTERVAL_START = 'Invalid transaction validity interval start', + + INVALID_MINT_AMOUNT = 'Invalid mint amount', INVALID_MINT = 'Invalid transaction mint', + INVALID_SCRIPT_DATA_HASH = 'Invalid transaction script data hash', + + INVALID_COLLATERAL_INPUT_INDEX = 'Invalid transaction collateral input index', + INVALID_COLLATERAL_INPUT = 'Invalid transaction collateral input', INVALID_COLLATERAL_INPUTS = 'Invalid transaction collateral inputs', + INVALID_REQUIRED_SIGNERS = 'Invalid transaction required signers', + INVALID_NETWORK_ID = 'Invalid transaction network id', + INVALID_TOTAL_COLLATERAL = 'Invalid transaction total collateral', + + INVALID_REFERENCE_INPUT_INDEX = 'Invalid transaction reference input index', + INVALID_REFERENCE_INPUT = 'Invalid transaction reference input', INVALID_REFERENCE_INPUTS = 'Invalid transaction reference inputs', + INVALID_VOTING_PROCEDURES = 'Invalid transaction voting procedures', + INVALID_VOTING_PROCEDURES_EMPTY_MAP = 'Invalid transaction voting procedures --- an empty map included', + INVALID_VOTER = 'Invalid voter', + INVALID_GOV_ACTION_ID = 'Invalid governance action id', + INVALID_VOTE_OPTION = 'Invalid vote', + INVALID_VOTING_PROCEDURE = 'Invalid voting procedure', + + INVALID_PROPOSAL_PROCEDURES = 'Invalid proposal procedures', + INVALID_PROPOSAL_PROCEDURE = 'Invalid proposal procedure', + + INVALID_TREASURY = 'Invalid treasury', + + INVALID_DONATION = 'Invalid donation', + INVALID_TX_CBOR = 'Invalid transaction CBOR', + INVALID_TX_BODY_CBOR = 'Invalid transaction body CBOR', + INVALID_TX_BODY_UNKNOWN_ITEMS = 'Transaction body contains unknown items', } export class ParseError extends Error { diff --git a/src/errors/validationError.ts b/src/errors/validationError.ts index c40ed81..a9edbed 100644 --- a/src/errors/validationError.ts +++ b/src/errors/validationError.ts @@ -1,17 +1,31 @@ export enum ValidationErrorReason { // Unfixable validation errors UNSUPPORTED_TX_UPDATE = 'The transaction body entry update must not be included', + UNSUPPORTED_TX_PROPOSAL_PROCEDURES = 'The transaction body entry proposal_procedures must not be included', UNSUPPORTED_CERTIFICATE_GENESIS_KEY_DELEGATION = 'Certificate of type genesis_key_delegation is not supported and must not be included', UNSUPPORTED_CERTIFICATE_MOVE_INSTANTANEOUS_REWARDS_CERT = 'Certificate of type move_instantaneous_rewards_cert is not supported and must not be included', + UNSUPPORTED_CERTIFICATE_STAKE_VOTE_DELEG = 'Certificate of type stake_vote_deleg_cert is not supported and must not be included', + UNSUPPORTED_CERTIFICATE_STAKE_REG_DELEG = 'Certificate of type stake_reg_deleg_cert is not supported and must not be included', + UNSUPPORTED_CERTIFICATE_VOTE_REG_DELEG = 'Certificate of type vote_reg_deleg_cert is not supported and must not be included', + UNSUPPORTED_CERTIFICATE_STAKE_VOTE_REG_DELEG = 'Certificate of type stake_vote_reg_deleg_cert is not supported and must not be included', INTEGER_NOT_INT64 = 'Hardware wallets support integers up to int64, integers from -2^63 to 2^63-1', UNSIGNED_INTEGER_NOT_UINT64 = 'Hardware wallets support unsigned integers up to uint64, unsigned integers from 0 to 2^64-1', NUMBER_OF_ELEMENTS_EXCEEDS_UINT16 = 'The number of transaction elements individually must not exceed UINT16_MAX, i.e. 65535', - CERTIFICATES_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL = 'All certificates included in a transaction must have the same type of stake credential', - WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL = 'All withdrawals included in a transaction must have the same type of stake credential', - CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT = 'The stake credential type of certificates must be consistent with the type used for withdrawals', POOL_REGISTRATION_CERTIFICATE_WITH_OTHER_CERTIFICATES = 'If a transaction contains a pool registration certificate, then it must not contain any other certificate', POOL_REGISTRATION_CERTIFICATE_WITH_WITHDRAWALS = 'If a transaction contains a pool registration certificate, then it must not contain any withdrawal', POOL_REGISTRATION_CERTIFICATE_WITH_MINT_ENTRY = 'If a transaction contains a pool registration certificate, then it must not contain mint entry', + POOL_REGISTRATION_CERTIFICATE_WITH_PLUTUS_OUTPUTS = 'If a transaction contains a pool registration certificate, then it must not contain datums and reference scripts in outputs', + POOL_REGISTRATION_CERTIFICATE_WITH_SCRIPT_DATA_HASH = 'If a transaction contains a pool registration certificate, then it must not contain script data hash', + POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_INPUTS = 'If a transaction contains a pool registration certificate, then it must not contain collateral inputs', + POOL_REGISTRATION_CERTIFICATE_WITH_REQUIRED_SIGNERS = 'If a transaction contains a pool registration certificate, then it must not contain required signers', + POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_RETURN_OUTPUT = 'If a transaction contains a pool registration certificate, then it must not contain collateral return output', + POOL_REGISTRATION_CERTIFICATE_WITH_TOTAL_COLLATERAL = 'If a transaction contains a pool registration certificate, then it must not contain total collateral', + POOL_REGISTRATION_CERTIFICATE_WITH_REFERENCE_INPUTS = 'If a transaction contains a pool registration certificate, then it must not contain reference inputs', + POOL_REGISTRATION_CERTIFICATE_WITH_VOTING_PROCEDURES = 'If a transaction contains a pool registration certificate, then it must not contain voting procedures', + POOL_REGISTRATION_CERTIFICATE_WITH_TREASURY = 'If a transaction contains a pool registration certificate, then it must not contain treasury value entry', + POOL_REGISTRATION_CERTIFICATE_WITH_DONATION = 'If a transaction contains a pool registration certificate, then it must not contain treasury donation entry', + TOO_MANY_VOTERS_IN_VOTING_PROCEDURES = 'Only a single voter is allowed in voting procedures', + INVALID_NUMBER_OF_VOTING_PROCEDURES = 'There must be exactly one voting procedure per voter', // Fixable validation errors CBOR_IS_NOT_CANONICAL = 'CBOR is not canonical', @@ -21,32 +35,58 @@ export enum ValidationErrorReason { 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', + TX_INCONSISTENT_SET_TAGS = 'Inconsistent set tags 258 across the transaction body', } const FIXABLE = true const UNFIXABLE = false const validationErrorFixability: Record = { + // unfixable [ValidationErrorReason.UNSUPPORTED_TX_UPDATE]: UNFIXABLE, + [ValidationErrorReason.UNSUPPORTED_TX_PROPOSAL_PROCEDURES]: UNFIXABLE, [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_GENESIS_KEY_DELEGATION]: UNFIXABLE, [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_MOVE_INSTANTANEOUS_REWARDS_CERT]: UNFIXABLE, + [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_VOTE_DELEG]: UNFIXABLE, + [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_REG_DELEG]: UNFIXABLE, + [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_VOTE_REG_DELEG]: UNFIXABLE, + [ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_VOTE_REG_DELEG]: + UNFIXABLE, [ValidationErrorReason.INTEGER_NOT_INT64]: UNFIXABLE, [ValidationErrorReason.UNSIGNED_INTEGER_NOT_UINT64]: UNFIXABLE, [ValidationErrorReason.NUMBER_OF_ELEMENTS_EXCEEDS_UINT16]: UNFIXABLE, - [ValidationErrorReason.CERTIFICATES_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL]: - UNFIXABLE, - [ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL]: - UNFIXABLE, - [ValidationErrorReason.CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT]: - UNFIXABLE, [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_OTHER_CERTIFICATES]: UNFIXABLE, [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_WITHDRAWALS]: UNFIXABLE, [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_MINT_ENTRY]: UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_PLUTUS_OUTPUTS]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_SCRIPT_DATA_HASH]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_INPUTS]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_REQUIRED_SIGNERS]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_RETURN_OUTPUT]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_TOTAL_COLLATERAL]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_REFERENCE_INPUTS]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_VOTING_PROCEDURES]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_TREASURY]: + UNFIXABLE, + [ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_DONATION]: + UNFIXABLE, + [ValidationErrorReason.TOO_MANY_VOTERS_IN_VOTING_PROCEDURES]: UNFIXABLE, + [ValidationErrorReason.INVALID_NUMBER_OF_VOTING_PROCEDURES]: UNFIXABLE, + + // fixable [ValidationErrorReason.CBOR_IS_NOT_CANONICAL]: FIXABLE, [ValidationErrorReason.OPTIONAL_EMPTY_LISTS_AND_MAPS_MUST_NOT_BE_INCLUDED]: FIXABLE, @@ -58,6 +98,7 @@ const validationErrorFixability: Record = { [ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_DATUM]: FIXABLE, [ValidationErrorReason.COLLATERAL_RETURN_MUST_NOT_CONTAIN_REFERENCE_SCRIPT]: FIXABLE, + [ValidationErrorReason.TX_INCONSISTENT_SET_TAGS]: FIXABLE, } export type ValidationError = { diff --git a/src/parsers.ts b/src/parsers.ts index 9a4b4cc..b57380d 100644 --- a/src/parsers.ts +++ b/src/parsers.ts @@ -1,7 +1,12 @@ import {Tagged} from 'cbor' +import {Serializer} from './txSerializers' import {ParseErrorReason, ParseError} from './errors' import type { + CddlNonEmptyOrderedSet, + CddlNonEmptySet, + CddlSet, + CddlSetBase, FixLenBuffer, Int, MaxLenBuffer, @@ -9,7 +14,7 @@ import type { MaxSizeUint, Uint, } from './types' -import {CborTag} from './utils' +import {CborTag, encodeToCbor} from './utils' export function validate( cond: boolean, @@ -192,6 +197,75 @@ export const parseArray = ( return data.map((value) => parseEntry(value)) } +const areUnique = (items: T[], serialize: Serializer): boolean => { + const encoded: Buffer[] = items.map(serialize).map(encodeToCbor) + const s = new Set(encoded.map((buffer) => buffer.toString('hex'))) + return s.size === items.length +} + +// serializeEntry is needed to check uniqueness +const parseCddlSetBase = ( + data: unknown, + parseEntry: Parser, + serializeEntry: Serializer, + errMsg: ParseErrorReason, +): CddlSetBase => { + let result: CddlSetBase + if (data instanceof Tagged) { + validate(data.tag === CborTag.SET, errMsg) + validate(isArray(data.value), errMsg) + result = { + items: data.value.map((entry) => parseEntry(entry)), + hasTag: true, + } + } else { + validate(isArray(data), errMsg) + result = { + items: data.map((entry) => parseEntry(entry)), + hasTag: false, + } + } + validate(areUnique(result.items, serializeEntry), errMsg) + + return result +} + +export const parseCddlSet = ( + data: unknown, + parseEntry: Parser, + serializeEntry: Serializer, + errMsg: ParseErrorReason, +): CddlSet => { + return parseCddlSetBase( + data, + parseEntry, + serializeEntry, + errMsg, + ) as CddlSet +} + +export const parseCddlNonEmptySet = ( + data: unknown, + parseEntry: Parser, + serializeEntry: Serializer, + errMsg: ParseErrorReason, +): CddlNonEmptySet => { + const base = parseCddlSetBase(data, parseEntry, serializeEntry, errMsg) + validate(base.items.length > 0, errMsg) + return base as CddlNonEmptySet +} + +export const parseCddlNonEmptyOrderedSet = ( + data: unknown, + parseEntry: Parser, + serializeEntry: Serializer, + errMsg: ParseErrorReason, +): CddlNonEmptyOrderedSet => { + const base = parseCddlSetBase(data, parseEntry, serializeEntry, errMsg) + validate(base.items.length > 0, errMsg) + return base as CddlNonEmptyOrderedSet +} + /** * Parses the data as an array of length of the provided parsers. * If the number of provided parsers exceeds the length of parsed array, @@ -201,7 +275,7 @@ export const parseArray = ( * // returns [123N, -1N] * parseTuple([123, -1], InvalidDataReason.INVALID, parseUint64, parseInt64) */ -export const parseTuple = ( +export const parseTupleWithUndefined = ( data: unknown, errMsg: ParseErrorReason, ...parsers: {[K in keyof T]: Parser} @@ -212,6 +286,26 @@ export const parseTuple = ( return parsers.map((parser, index) => parser(data[index])) as T } +/** + * Parses the data as an array of length of the provided parsers. + * If the number of provided parsers exceeds the length of parsed array, + * an error is thrown. + * + * @example + * // returns [123N, -1N] + * parseTuple([123, -1], InvalidDataReason.INVALID, parseUint64, parseInt64) + */ +export const parseTuple = ( + data: unknown, + errMsg: ParseErrorReason, + ...parsers: {[K in keyof T]: Parser} +): T => { + validate(isArray(data), errMsg) + validate(data.length === parsers.length, errMsg) + + return parsers.map((parser, index) => parser(data[index])) as T +} + export const parseOptional = ( data: unknown, parser: Parser, diff --git a/src/txParsers.ts b/src/txParsers.ts index 6b8b692..1fd53fe 100644 --- a/src/txParsers.ts +++ b/src/txParsers.ts @@ -1,3 +1,10 @@ +import { + serializeTxInput, + serializeCertificate, + serializeCollateralInput, + serializeProposalProcedure, + identity, +} from './txSerializers' import {ParseErrorReason} from './errors' import type {Parser, WithoutType} from './parsers' import { @@ -12,17 +19,21 @@ import { parseBuffer, parseBufferOfLength, parseBufferOfMaxLength, + parseCddlSet, parseEmbeddedCborBytes, parseInt, parseMap, parseNullable, parseOptional, parseStringOfMaxLength, + parseTupleWithUndefined, parseTuple, parseUint, validate, + parseCddlNonEmptySet, + parseCddlNonEmptyOrderedSet, } from './parsers' -import type { +import { Amount, BabbageTransactionOutput, DatumHash, @@ -51,8 +62,25 @@ import type { TransactionOutput, Unparsed, Withdrawal, -} from './types' -import { + StakeRegistrationConwayCertificate, + StakeDeregistrationConwayCertificate, + VoteDelegationCertificate, + DRepType, + KeyHashDRep, + ScriptHashDRep, + AbstainDRep, + NoConfidenceDRep, + StakeAndVoteDelegationCertificate, + StakeRegistrationAndDelegationCertificate, + StakeRegistrationWithVoteDelegationCertificate, + StakeRegistrationWithStakeAndVoteDelegationCertificate, + ANCHOR_DATA_HASH_LENGTH, + Anchor, + AuthorizeCommitteeHotCertificate, + ResignCommitteeColdCertificate, + DRepRegistrationCertificate, + DRepDeregistrationCertificate, + DRepUpdateCertificate, AmountType, ASSET_NAME_MAX_LENGTH, AUXILIARY_DATA_HASH_LENGTH, @@ -75,22 +103,113 @@ import { TxOutputFormat, URL_MAX_LENGTH, VRF_KEY_HASH_LENGTH, + VoterVotes, + VoterType, + CommitteeKeyVoter, + CommitteeScriptVoter, + DRepKeyVoter, + StakePoolVoter, + DRepScriptVoter, + GovActionId, + VotingProcedure, + VoteOption, + Coin, + ProposalProcedure, + Int, + Uint, + UnitInterval, } from './types' import { BabbageTransactionOutputKeys, + CborTag, TransactionBodyKeys, undefinedOnlyAtTheEnd, } from './utils' +import {Tagged} from 'cbor' + +// ======================== universal parsers / parsers for CDDL data types + +// parsers for tx body elements and certain CDDL "datatypes" are exported const doNotParse: Parser = (data: unknown) => data +export const parseCoin = createParser(parseUint, ParseErrorReason.INVALID_COIN) + +const parseCredentialType = ( + unparsedCredentialType: unknown, +): CredentialType => { + validate( + isNumber(unparsedCredentialType), + ParseErrorReason.INVALID_CREDENTIAL_TYPE, + ) + validate( + unparsedCredentialType in CredentialType, + ParseErrorReason.INVALID_CREDENTIAL_TYPE, + ) + return unparsedCredentialType +} + +const parseKeyCredential = (data: unknown[]): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_CREDENTIAL) + return { + keyHash: parseBufferOfLength( + data[0], + KEY_HASH_LENGTH, + ParseErrorReason.INVALID_CREDENTIAL_KEY_HASH, + ), + } +} + +const parseScriptCredential = ( + data: unknown[], +): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_CREDENTIAL) + return { + scriptHash: parseBufferOfLength( + data[0], + SCRIPT_HASH_LENGTH, + ParseErrorReason.INVALID_CREDENTIAL_SCRIPT_HASH, + ), + } +} + +export const parseCredential = createParser( + parseBasedOnType, + ParseErrorReason.INVALID_CREDENTIAL, + parseCredentialType, + parseKeyCredential, + parseScriptCredential, +) + +// ======================== parsers for tx elements + +export const parseTxInput = (unparsedTxInput: unknown): TransactionInput => { + const [transactionId, index] = parseTuple( + unparsedTxInput, + ParseErrorReason.INVALID_TX_INPUT, + createParser( + parseBufferOfLength, + TX_ID_HASH_LENGTH, + ParseErrorReason.INVALID_TRANSACTION_ID, + ), + createParser(parseUint, ParseErrorReason.INVALID_TX_INPUT_INDEX), + ) + + return {transactionId, index} +} + +export const parseInputs = createParser( + parseCddlSet, + parseTxInput, + serializeTxInput, + ParseErrorReason.INVALID_TX_INPUTS, +) + const parseRewardAccount = createParser( parseBufferOfLength, REWARD_ACCOUNT_LENGTH, ParseErrorReason.INVALID_REWARD_ACCOUNT, ) -const parseCoin = createParser(parseUint, ParseErrorReason.INVALID_COIN) -const parseEpoch = createParser(parseUint, ParseErrorReason.INVALID_COIN) const parsePolicyId = createParser( parseBufferOfLength, @@ -102,8 +221,7 @@ const parseAssetName = createParser( ASSET_NAME_MAX_LENGTH, ParseErrorReason.INVALID_ASSET_NAME, ) - -const parseMultiasset = ( +export const parseMultiasset = ( unparsedMultiasset: unknown, parseAssetValue: Parser, errMsg: ParseErrorReason, @@ -124,26 +242,6 @@ const parseMultiasset = ( })) } -const parseTxInput = (unparsedTxInput: unknown): TransactionInput => { - const [transactionId, index] = parseTuple( - unparsedTxInput, - ParseErrorReason.INVALID_TX_INPUT, - createParser( - parseBufferOfLength, - TX_ID_HASH_LENGTH, - ParseErrorReason.INVALID_TRANSACTION_ID, - ), - createParser(parseUint, ParseErrorReason.INVALID_TX_INPUT_INDEX), - ) - - return {transactionId, index} -} - -const parseAddress = createParser( - parseBuffer, - ParseErrorReason.INVALID_OUTPUT_ADDRESS, -) - const parseAmountWithMultiasset = (unparsedAmount: unknown): Amount => { const [coin, multiasset] = parseTuple( unparsedAmount, @@ -164,7 +262,7 @@ const parseAmountWithoutMultiasset = (unparsedAmount: unknown): Amount => ({ coin: parseUint(unparsedAmount, ParseErrorReason.INVALID_OUTPUT_AMOUNT), }) -const parseAmount = (unparsedAmount: unknown): Amount => +export const parseAmount = (unparsedAmount: unknown): Amount => isUint(unparsedAmount) ? parseAmountWithoutMultiasset(unparsedAmount) : parseAmountWithMultiasset(unparsedAmount) @@ -183,10 +281,15 @@ const parseLegacyTxOutputDatumHash = ( } : undefined +const parseAddress = createParser( + parseBuffer, + ParseErrorReason.INVALID_OUTPUT_ADDRESS, +) + const parseLegacyTxOutput = ( unparsedTxOutput: unknown, ): LegacyTransactionOutput => { - const [address, amount, datumHash] = parseTuple( + const [address, amount, datumHash] = parseTupleWithUndefined( unparsedTxOutput, ParseErrorReason.INVALID_TX_OUTPUT, parseAddress, @@ -269,67 +372,21 @@ const parseBabbageTxOutput = ( } } -const parseTxOutput = (unparsedTxOutput: unknown): TransactionOutput => { +export const parseTxOutput = (unparsedTxOutput: unknown): TransactionOutput => { return isArray(unparsedTxOutput) ? parseLegacyTxOutput(unparsedTxOutput) : parseBabbageTxOutput(unparsedTxOutput) } -export const parseWithdrawals = ( - unparsedWithdrawals: unknown, -): Withdrawal[] => { - const withdrawalsMap = parseMap( - unparsedWithdrawals, - parseRewardAccount, - createParser(parseUint, ParseErrorReason.INVALID_WITHDRAWAL_AMOUNT), - ParseErrorReason.INVALID_WITHDRAWALS, - ) - - return Array.from(withdrawalsMap).map(([rewardAccount, amount]) => ({ - rewardAccount, - amount, - })) -} - -const parseCredentialType = ( - unparsedCredentialType: unknown, -): CredentialType => { - validate( - isNumber(unparsedCredentialType), - ParseErrorReason.INVALID_CREDENTIAL_TYPE, - ) - validate( - unparsedCredentialType in CredentialType, - ParseErrorReason.INVALID_CREDENTIAL_TYPE, - ) - return unparsedCredentialType -} - -const parseKeyCredential = (data: unknown[]): WithoutType => ({ - hash: parseBufferOfLength( - data[0], - KEY_HASH_LENGTH, - ParseErrorReason.INVALID_CREDENTIAL_KEY_HASH, - ), -}) +export const parseOutputs = createParser( + parseArray, + parseTxOutput, + ParseErrorReason.INVALID_TX_OUTPUTS, +) -const parseScriptCredential = ( - data: unknown[], -): WithoutType => ({ - hash: parseBufferOfLength( - data[0], - SCRIPT_HASH_LENGTH, - ParseErrorReason.INVALID_CREDENTIAL_SCRIPT_HASH, - ), -}) +export const parseFee = createParser(parseUint, ParseErrorReason.INVALID_FEE) -const parseCredential = createParser( - parseBasedOnType, - ParseErrorReason.INVALID_CREDENTIAL, - parseCredentialType, - parseKeyCredential, - parseScriptCredential, -) +export const parseTtl = createParser(parseUint, ParseErrorReason.INVALID_TTL) const parsePoolKeyHash = createParser( parseBufferOfLength, @@ -337,13 +394,22 @@ const parsePoolKeyHash = createParser( ParseErrorReason.INVALID_POOL_KEY_HASH, ) -const parseUnitInterval = createParser( +const parseUnitIntervalData = createParser( parseTuple, ParseErrorReason.INVALID_UNIT_INTERVAL, createParser(parseUint, ParseErrorReason.INVALID_UNIT_INTERVAL_START), createParser(parseUint, ParseErrorReason.INVALID_UNIT_INTERVAL_END), ) +const parseUnitInterval = (data: unknown): UnitInterval => { + validate(data instanceof Tagged, ParseErrorReason.INVALID_UNIT_INTERVAL) + validate( + data.tag === CborTag.UNIT_INTERVAL, + ParseErrorReason.INVALID_UNIT_INTERVAL, + ) + return parseUnitIntervalData(data.value) +} + const parsePort = (data: unknown): Port => { validate( isUintOfMaxSize(data, PORT_MAX_SIZE), @@ -366,38 +432,47 @@ const parseRelayType = (unparsedRelayType: unknown): RelayType => { const parseRelaySingleHostAddress = ( data: unknown[], -): WithoutType => ({ - port: parseNullable(data[0], parsePort), - ipv4: parseNullable( - data[1], - createParser( - parseBufferOfLength, - IPV4_LENGTH, - ParseErrorReason.INVALID_RELAY_IPV4, +): WithoutType => { + validate(data.length === 3, ParseErrorReason.INVALID_RELAY) + return { + port: parseNullable(data[0], parsePort), + ipv4: parseNullable( + data[1], + createParser( + parseBufferOfLength, + IPV4_LENGTH, + ParseErrorReason.INVALID_RELAY_IPV4, + ), ), - ), - ipv6: parseNullable( - data[2], - createParser( - parseBufferOfLength, - IPV6_LENGTH, - ParseErrorReason.INVALID_RELAY_IPV6, + ipv6: parseNullable( + data[2], + createParser( + parseBufferOfLength, + IPV6_LENGTH, + ParseErrorReason.INVALID_RELAY_IPV6, + ), ), - ), -}) + } +} const parseRelaySingleHostName = ( data: unknown[], -): WithoutType => ({ - port: parseNullable(data[0], parsePort), - dnsName: parseDnsName(data[1]), -}) +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_RELAY) + return { + port: parseNullable(data[0], parsePort), + dnsName: parseDnsName(data[1]), + } +} const parseRelayMultiHostName = ( data: unknown[], -): WithoutType => ({ - dnsName: parseDnsName(data[0]), -}) +): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_RELAY) + return { + dnsName: parseDnsName(data[0]), + } +} const parseRelay = createParser( parseBasedOnType, @@ -452,12 +527,13 @@ const parsePoolParams = (unparsedPoolParams: unknown): PoolParams => { parseUnitInterval, parseRewardAccount, createParser( - parseArray, + parseCddlSet, createParser( parseBufferOfLength, KEY_HASH_LENGTH, ParseErrorReason.INVALID_POOL_OWNER, ), + identity, ParseErrorReason.INVALID_POOL_OWNERS, ), createParser(parseArray, parseRelay, ParseErrorReason.INVALID_RELAYS), @@ -477,6 +553,76 @@ const parsePoolParams = (unparsedPoolParams: unknown): PoolParams => { } } +const parseDRepType = (unparsedType: unknown): DRepType => { + validate(isNumber(unparsedType), ParseErrorReason.INVALID_DREP_TYPE) + validate(unparsedType in DRepType, ParseErrorReason.INVALID_DREP_TYPE) + return unparsedType +} + +const parseKeyHashDRep = (data: unknown[]): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_DREP) + return { + keyHash: parseBufferOfLength( + data[0], + KEY_HASH_LENGTH, + ParseErrorReason.INVALID_DREP, + ), + } +} + +const parseScriptHashDRep = (data: unknown[]): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_DREP) + return { + scriptHash: parseBufferOfLength( + data[0], + SCRIPT_HASH_LENGTH, + ParseErrorReason.INVALID_DREP, + ), + } +} + +const parseAbstainDRep = (data: unknown[]): WithoutType => { + // nothing to parse + validate(data.length === 0, ParseErrorReason.INVALID_DREP) + return {} +} + +const parseNoConfidenceDRep = ( + data: unknown[], +): WithoutType => { + // nothing to parse + validate(data.length === 0, ParseErrorReason.INVALID_DREP) + return {} +} + +export const parseAnchor = (data: unknown): Anchor => { + const [url, dataHash] = parseTuple( + data, + ParseErrorReason.INVALID_ANCHOR, + createParser( + parseStringOfMaxLength, + URL_MAX_LENGTH, + ParseErrorReason.INVALID_ANCHOR_URL, + ), + createParser( + parseBufferOfLength, + ANCHOR_DATA_HASH_LENGTH, + ParseErrorReason.INVALID_ANCHOR_DATA_HASH, + ), + ) + return {url, dataHash} +} + +export const parseDRep = createParser( + parseBasedOnType, + ParseErrorReason.INVALID_DREP, + parseDRepType, + parseKeyHashDRep, + parseScriptHashDRep, + parseAbstainDRep, + parseNoConfidenceDRep, +) + const parseCertificateType = ( unparsedCertificateType: unknown, ): CertificateType => { @@ -493,22 +639,31 @@ const parseCertificateType = ( const parseStakeRegistrationCertificate = ( data: unknown[], -): WithoutType => ({ - stakeCredential: parseCredential(data[0]), -}) +): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + } +} const parseStakeDeregistrationCertificate = ( data: unknown[], -): WithoutType => ({ - stakeCredential: parseCredential(data[0]), -}) +): WithoutType => { + validate(data.length === 1, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + } +} const parseStakeDelegationCertificate = ( data: unknown[], -): WithoutType => ({ - stakeCredential: parseCredential(data[0]), - poolKeyHash: parsePoolKeyHash(data[1]), -}) +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + poolKeyHash: parsePoolKeyHash(data[1]), + } +} const parsePoolRegistrationCertificate = ( data: unknown[], @@ -516,26 +671,161 @@ const parsePoolRegistrationCertificate = ( poolParams: parsePoolParams(data), }) +const parseEpoch = createParser(parseUint, ParseErrorReason.INVALID_COIN) + const parsePoolRetirementCertificate = ( data: unknown[], -): WithoutType => ({ - poolKeyHash: parsePoolKeyHash(data[0]), - epoch: parseEpoch(data[1]), -}) +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + poolKeyHash: parsePoolKeyHash(data[0]), + epoch: parseEpoch(data[1]), + } +} +// removed in Conway, but we keep it here because it might appear +// in erroneous / previously-built transactions const parseGenesisKeyDelegation = ( data: unknown[], ): WithoutType => ({ restOfData: data, }) +// removed in Conway, but we keep it here because it might appear +// in erroneous / previously-built transactions const parseMoveInstantaneousRewardsCertificate = ( data: unknown[], ): WithoutType => ({ restOfData: data, }) -const parseCertificate = createParser( +const parseStakeRegistrationConwayCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + deposit: parseCoin(data[1]), + } +} + +const parseStakeDeregistrationConwayCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + deposit: parseCoin(data[1]), + } +} + +const parseVoteDelegationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + dRep: parseDRep(data[1]), + } +} + +const parseStakeAndVoteDelegationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 3, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + poolKeyHash: parsePoolKeyHash(data[1]), + dRep: parseDRep(data[2]), + } +} + +const parseStakeRegistrationAndDelegationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 3, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + poolKeyHash: parsePoolKeyHash(data[1]), + deposit: parseCoin(data[2]), + } +} + +const parseStakeRegistrationWithVoteDelegationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 3, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + dRep: parseDRep(data[1]), + deposit: parseCoin(data[2]), + } +} + +const parseStakeRegistrationWithStakeAndVoteDelegationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 4, ParseErrorReason.INVALID_CERTIFICATE) + return { + stakeCredential: parseCredential(data[0]), + poolKeyHash: parsePoolKeyHash(data[1]), + dRep: parseDRep(data[2]), + deposit: parseCoin(data[3]), + } +} + +const parseAuthorizeCommitteeHotCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + coldCredential: parseCredential(data[0]), + hotCredential: parseCredential(data[1]), + } +} + +const parseResignCommitteeColdCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + coldCredential: parseCredential(data[0]), + anchor: parseNullable(data[1], parseAnchor), + } +} + +const parseDRepRegistrationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 3, ParseErrorReason.INVALID_CERTIFICATE) + return { + dRepCredential: parseCredential(data[0]), + deposit: parseCoin(data[1]), + anchor: parseNullable(data[2], parseAnchor), + } +} + +const parseDRepDeregistrationCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + dRepCredential: parseCredential(data[0]), + deposit: parseCoin(data[1]), + } +} + +const parseDRepUpdateCertificate = ( + data: unknown[], +): WithoutType => { + validate(data.length === 2, ParseErrorReason.INVALID_CERTIFICATE) + return { + dRepCredential: parseCredential(data[0]), + anchor: parseNullable(data[1], parseAnchor), + } +} + +export const parseCertificate = createParser( parseBasedOnType, ParseErrorReason.INVALID_CERTIFICATE, parseCertificateType, @@ -546,9 +836,64 @@ const parseCertificate = createParser( parsePoolRetirementCertificate, parseGenesisKeyDelegation, parseMoveInstantaneousRewardsCertificate, + parseStakeRegistrationConwayCertificate, + parseStakeDeregistrationConwayCertificate, + parseVoteDelegationCertificate, + parseStakeAndVoteDelegationCertificate, + parseStakeRegistrationAndDelegationCertificate, + parseStakeRegistrationWithVoteDelegationCertificate, + parseStakeRegistrationWithStakeAndVoteDelegationCertificate, + parseAuthorizeCommitteeHotCertificate, + parseResignCommitteeColdCertificate, + parseDRepRegistrationCertificate, + parseDRepDeregistrationCertificate, + parseDRepUpdateCertificate, +) + +export const parseCertificates = createParser( + parseCddlNonEmptyOrderedSet, + parseCertificate, + serializeCertificate, + ParseErrorReason.INVALID_CERTIFICATES, ) -const parseCollateralInput = ( +export const parseWithdrawals = ( + unparsedWithdrawals: unknown, +): Withdrawal[] => { + const withdrawalsMap = parseMap( + unparsedWithdrawals, + parseRewardAccount, + createParser(parseUint, ParseErrorReason.INVALID_WITHDRAWAL_AMOUNT), + ParseErrorReason.INVALID_WITHDRAWALS, + ) + + return Array.from(withdrawalsMap).map(([rewardAccount, amount]) => ({ + rewardAccount, + amount, + })) +} + +export const parseAuxiliaryDataHash = createParser( + parseBufferOfLength, + AUXILIARY_DATA_HASH_LENGTH, + ParseErrorReason.INVALID_AUXILIARY_DATA_HASH, +) +export const parseValidityIntervalStart = createParser( + parseUint, + ParseErrorReason.INVALID_VALIDITY_INTERVAL_START, +) +export const parseMint = createParser( + parseMultiasset, + createParser(parseInt, ParseErrorReason.INVALID_MINT_AMOUNT), + ParseErrorReason.INVALID_MINT, +) +export const parseScriptDataHash = createParser( + parseBufferOfLength, + SCRIPT_DATA_HASH_LENGTH, + ParseErrorReason.INVALID_SCRIPT_DATA_HASH, +) + +export const parseCollateralInput = ( unparsedCollateralInput: unknown, ): TransactionInput => { const [transactionId, index] = parseTuple( @@ -565,14 +910,50 @@ const parseCollateralInput = ( return {transactionId, index} } -const parseRequiredSigner = (unparsedRequiredSigner: unknown): RequiredSigner => +export const parseCollateralInputs = createParser( + parseCddlNonEmptySet, + parseCollateralInput, + serializeCollateralInput, + ParseErrorReason.INVALID_COLLATERAL_INPUTS, +) + +export const parseRequiredSigner = ( + unparsedRequiredSigner: unknown, +): RequiredSigner => parseBufferOfLength( unparsedRequiredSigner, KEY_HASH_LENGTH, ParseErrorReason.INVALID_REQUIRED_SIGNERS, ) -const parseReferenceInput = ( +export const parseRequiredSigners = createParser( + parseCddlNonEmptySet, + parseRequiredSigner, + identity, + ParseErrorReason.INVALID_REQUIRED_SIGNERS, +) + +export const parseNetworkId = createParser( + parseUint, + ParseErrorReason.INVALID_NETWORK_ID, +) + +export const parseCollateralOutput = ( + unparsedTxOutput: unknown, +): TransactionOutput => { + // The reported error is somewhat inaccurate (does not refer to "collateral return output" + // at all, just "output"), but it is not worth rewriting the output parsing. + // There are some conditions on collateral return outputs + // that we do not verify here anyway (limitations on address type, datum hash presence etc.). + return parseTxOutput(unparsedTxOutput) +} + +export const parseTotalCollateral = createParser( + parseUint, + ParseErrorReason.INVALID_TOTAL_COLLATERAL, +) + +export const parseReferenceInput = ( unparsedReferenceInput: unknown, ): TransactionInput => { const [transactionId, index] = parseTuple( @@ -589,66 +970,176 @@ const parseReferenceInput = ( return {transactionId, index} } -export const parseInputs = createParser( - parseArray, - parseTxInput, - ParseErrorReason.INVALID_TX_INPUTS, -) -export const parseOutputs = createParser( - parseArray, - parseTxOutput, - ParseErrorReason.INVALID_TX_OUTPUTS, -) -export const parseFee = createParser(parseUint, ParseErrorReason.INVALID_FEE) -export const parseTtl = createParser(parseUint, ParseErrorReason.INVALID_TTL) -export const parseCertificates = createParser( - parseArray, - parseCertificate, - ParseErrorReason.INVALID_CERTIFICATES, +export const parseReferenceInputs = createParser( + parseCddlNonEmptySet, + parseReferenceInput, + serializeTxInput, + ParseErrorReason.INVALID_REFERENCE_INPUTS, ) -export const parseAuxiliaryDataHash = createParser( + +const parseVoterType = (unparsedType: unknown): VoterType => { + validate(isNumber(unparsedType), ParseErrorReason.INVALID_VOTER) + validate(unparsedType in VoterType, ParseErrorReason.INVALID_VOTER) + return unparsedType +} + +const parseKeyVoterHash = createParser( parseBufferOfLength, - AUXILIARY_DATA_HASH_LENGTH, - ParseErrorReason.INVALID_AUXILIARY_DATA_HASH, -) -export const parseValidityIntervalStart = createParser( - parseUint, - ParseErrorReason.INVALID_VALIDITY_INTERVAL_START, -) -export const parseMint = createParser( - parseMultiasset, - createParser(parseInt, ParseErrorReason.INVALID_MINT_AMOUNT), - ParseErrorReason.INVALID_MINT, + KEY_HASH_LENGTH, + ParseErrorReason.INVALID_VOTER, ) -const parseScriptDataHash = createParser( + +const parseScriptVoterHash = createParser( parseBufferOfLength, - SCRIPT_DATA_HASH_LENGTH, - ParseErrorReason.INVALID_SCRIPT_DATA_HASH, -) -const parseCollateralInputs = createParser( - parseArray, - parseCollateralInput, - ParseErrorReason.INVALID_COLLATERAL_INPUTS, + SCRIPT_HASH_LENGTH, + ParseErrorReason.INVALID_VOTER, ) -const parseRequiredSigners = createParser( - parseArray, - parseRequiredSigner, - ParseErrorReason.INVALID_REQUIRED_SIGNERS, + +const parseCommitteeKeyVoter = ( + data: unknown[], +): WithoutType => ({ + hash: parseKeyVoterHash(data[0]), +}) + +const parseCommitteeScriptVoter = ( + data: unknown[], +): WithoutType => ({ + hash: parseScriptVoterHash(data[0]), +}) + +const parseDRepKeyVoter = (data: unknown[]): WithoutType => ({ + hash: parseKeyVoterHash(data[0]), +}) + +const parseDRepScriptVoter = ( + data: unknown[], +): WithoutType => ({ + hash: parseScriptVoterHash(data[0]), +}) + +const parseStakePoolVoter = (data: unknown[]): WithoutType => ({ + hash: parseKeyVoterHash(data[0]), +}) + +const parseVoter = createParser( + parseBasedOnType, + ParseErrorReason.INVALID_VOTER, + parseVoterType, + parseCommitteeKeyVoter, + parseCommitteeScriptVoter, + parseDRepKeyVoter, + parseDRepScriptVoter, + parseStakePoolVoter, ) -const parseNetworkId = createParser( - parseUint, - ParseErrorReason.INVALID_NETWORK_ID, + +const parseGovActionId = (unparsed: unknown): GovActionId => { + const [transactionId, index] = parseTuple( + unparsed, + ParseErrorReason.INVALID_GOV_ACTION_ID, + createParser( + parseBufferOfLength, + TX_ID_HASH_LENGTH, + ParseErrorReason.INVALID_GOV_ACTION_ID, + ), + createParser(parseUint, ParseErrorReason.INVALID_GOV_ACTION_ID), + ) + + return {transactionId, index} +} + +const parseVoteOption = (unparsed: unknown): VoteOption => { + validate(isNumber(unparsed), ParseErrorReason.INVALID_VOTE_OPTION) + validate(unparsed in VoteOption, ParseErrorReason.INVALID_VOTE_OPTION) + return unparsed +} + +export const parseVotingProcedure = (unparsed: unknown): VotingProcedure => { + const [voteOption, anchor] = parseTuple( + unparsed, + ParseErrorReason.INVALID_VOTING_PROCEDURE, + parseVoteOption, + createParser( + parseNullable, + parseAnchor, + ParseErrorReason.INVALID_VOTING_PROCEDURE, + ), + ) + return { + voteOption, + anchor, + } +} + +export const parseVotingProcedures = (unparsed: unknown): VoterVotes[] => { + const voterVotesMap = parseMap( + unparsed, + parseVoter, + createParser( + parseMap, + parseGovActionId, + parseVotingProcedure, + ParseErrorReason.INVALID_VOTE_OPTION, + ), + ParseErrorReason.INVALID_VOTING_PROCEDURES, + ) + validate( + voterVotesMap.size > 0, + ParseErrorReason.INVALID_VOTING_PROCEDURES_EMPTY_MAP, + ) + for (const [, votes] of voterVotesMap) { + validate( + votes.size > 0, + ParseErrorReason.INVALID_VOTING_PROCEDURES_EMPTY_MAP, + ) + } + + return Array.from(voterVotesMap).map(([voter, votes]) => ({ + voter, + votes: Array.from(votes).map(([govActionId, votingProcedure]) => ({ + govActionId, + votingProcedure, + })), + })) +} + +export const parseProposalProcedure = ( + unparsedProcedure: unknown, +): ProposalProcedure => { + const [deposit, rewardAccount, govAction, anchor] = parseTuple( + unparsedProcedure, + ParseErrorReason.INVALID_PROPOSAL_PROCEDURE, + parseCoin, + parseRewardAccount, + doNotParse, + parseAnchor, + ) + + return { + deposit, + rewardAccount, + govAction, + anchor, + } +} + +export const parseProposalProcedures = createParser( + parseCddlNonEmptyOrderedSet, + parseProposalProcedure, + serializeProposalProcedure, + ParseErrorReason.INVALID_PROPOSAL_PROCEDURES, ) -const parseTotalCollateral = createParser( + +export const parseTreasury = createParser( parseUint, - ParseErrorReason.INVALID_TOTAL_COLLATERAL, -) -const parseReferenceInputs = createParser( - parseArray, - parseReferenceInput, - ParseErrorReason.INVALID_REFERENCE_INPUTS, + ParseErrorReason.INVALID_TREASURY, ) +export const parseDonation = (unparsed: unknown): Coin => { + const coin = parseUint(unparsed, ParseErrorReason.INVALID_DONATION) + validate(coin > 0, ParseErrorReason.INVALID_DONATION) + return coin +} + export const parseTxBody = (unparsedTxBody: unknown): TransactionBody => { validate( isMapWithKeysOfType(unparsedTxBody, isNumber), @@ -705,7 +1196,7 @@ export const parseTxBody = (unparsedTxBody: unknown): TransactionBody => { ), collateralReturnOutput: parseOptional( unparsedTxBody.get(TransactionBodyKeys.COLLATERAL_RETURN_OUTPUT), - parseTxOutput, + parseCollateralOutput, ), totalCollateral: parseOptional( unparsedTxBody.get(TransactionBodyKeys.TOTAL_COLLATERAL), @@ -715,11 +1206,27 @@ export const parseTxBody = (unparsedTxBody: unknown): TransactionBody => { unparsedTxBody.get(TransactionBodyKeys.REFERENCE_INPUTS), parseReferenceInputs, ), + votingProcedures: parseOptional( + unparsedTxBody.get(TransactionBodyKeys.VOTING_PROCEDURES), + parseVotingProcedures, + ), + proposalProcedures: parseOptional( + unparsedTxBody.get(TransactionBodyKeys.PROPOSAL_PROCEDURES), + parseProposalProcedures, + ), + treasury: parseOptional( + unparsedTxBody.get(TransactionBodyKeys.TREASURY), + parseTreasury, + ), + donation: parseOptional( + unparsedTxBody.get(TransactionBodyKeys.DONATION), + parseDonation, + ), } } export const parseTx = (unparsedTx: unknown): Transaction => { - const [body, ...otherItems] = parseTuple( + const [body, ...otherItems] = parseTupleWithUndefined( unparsedTx, ParseErrorReason.INVALID_TX_CBOR, parseTxBody, diff --git a/src/txSerializers.ts b/src/txSerializers.ts index f4c5413..86e2c43 100644 --- a/src/txSerializers.ts +++ b/src/txSerializers.ts @@ -1,6 +1,10 @@ import {Tagged} from 'cbor' - -import type { +import { + AmountType, + CertificateType, + DatumType, + RelayType, + TxOutputFormat, Amount, AssetName, BabbageTransactionOutput, @@ -21,13 +25,18 @@ import type { TransactionInput, TransactionOutput, Withdrawal, -} from './types' -import { - AmountType, - CertificateType, - DatumType, - RelayType, - TxOutputFormat, + DRep, + DRepType, + Anchor, + VoterVotes, + Voter, + GovActionId, + VotingProcedure, + ProposalProcedure, + Uint, + Int, + CddlSetBase, + CredentialType, } from './types' import { BabbageTransactionOutputKeys, @@ -37,14 +46,38 @@ import { unreachable, } from './utils' -const identity = (x: T): T => x +export const identity = (x: T): T => x + +export type Serializer = (data: T) => unknown + +export const serializeCddlSetBase = ( + set: CddlSetBase, + serializeEntry: Serializer, +) => { + const data = set.items.map(serializeEntry) + if (set.hasTag) { + return new Tagged(CborTag.SET, data) + } else { + return data + } +} + +export const serializeCddlSetBaseOrUndefined = ( + set: CddlSetBase | undefined, + serializeEntry: Serializer, +) => { + if (set === undefined) { + return undefined + } + return serializeCddlSetBase(set, serializeEntry) +} -const serializeTxInput = (input: TransactionInput) => [ +export const serializeTxInput = (input: TransactionInput) => [ input.transactionId, input.index, ] -const serializeMultiasset = ( +const serializeMultiasset = ( multiasset: Multiasset, ): Map> => new Map( @@ -138,19 +171,46 @@ const serializePoolParams = (poolParams: PoolParams) => [ poolParams.vrfKeyHash, poolParams.pledge, poolParams.cost, - new Tagged(CborTag.TUPLE, poolParams.margin), + new Tagged(CborTag.UNIT_INTERVAL, poolParams.margin), poolParams.rewardAccount, - poolParams.poolOwners, + serializeCddlSetBase(poolParams.poolOwners, identity), poolParams.relays.map(serializeRelay), poolParams.poolMetadata && serializePoolMetadata(poolParams.poolMetadata), ] -const serializeCredential = (credential: Credential) => [ - credential.type, - credential.hash, -] +const serializeCredential = (credential: Credential) => { + switch (credential.type) { + case CredentialType.KEY_HASH: + return [credential.type, credential.keyHash] + case CredentialType.SCRIPT_HASH: + return [credential.type, credential.scriptHash] + default: + unreachable(credential) + } +} + +const serializeDRep = (dRep: DRep) => { + switch (dRep.type) { + case DRepType.KEY_HASH: + return [dRep.type, dRep.keyHash] + case DRepType.SCRIPT_HASH: + return [dRep.type, dRep.scriptHash] + case DRepType.ABSTAIN: + case DRepType.NO_CONFIDENCE: + return [dRep.type] + default: + unreachable(dRep) + } +} + +const serializeAnchor = (anchor: Anchor | null) => { + if (anchor === null) { + return null + } + return [anchor.url, anchor.dataHash] +} -const serializeCertificate = (certificate: Certificate) => { +export const serializeCertificate = (certificate: Certificate) => { switch (certificate.type) { case CertificateType.STAKE_REGISTRATION: case CertificateType.STAKE_DEREGISTRATION: @@ -171,25 +231,136 @@ const serializeCertificate = (certificate: Certificate) => { case CertificateType.GENESIS_KEY_DELEGATION: case CertificateType.MOVE_INSTANTANEOUS_REWARDS_CERT: return [certificate.type, ...certificate.restOfData] + case CertificateType.STAKE_REGISTRATION_CONWAY: + case CertificateType.STAKE_DEREGISTRATION_CONWAY: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + certificate.deposit, + ] + case CertificateType.VOTE_DELEGATION: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + serializeDRep(certificate.dRep), + ] + case CertificateType.STAKE_AND_VOTE_DELEGATION: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + certificate.poolKeyHash, + serializeDRep(certificate.dRep), + ] + case CertificateType.STAKE_REGISTRATION_AND_DELEGATION: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + certificate.poolKeyHash, + certificate.deposit, + ] + case CertificateType.STAKE_REGISTRATION_WITH_VOTE_DELEGATION: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + serializeDRep(certificate.dRep), + certificate.deposit, + ] + case CertificateType.STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION: + return [ + certificate.type, + serializeCredential(certificate.stakeCredential), + certificate.poolKeyHash, + serializeDRep(certificate.dRep), + certificate.deposit, + ] + case CertificateType.AUTHORIZE_COMMITTEE_HOT: + return [ + certificate.type, + serializeCredential(certificate.coldCredential), + serializeCredential(certificate.hotCredential), + ] + case CertificateType.RESIGN_COMMITTEE_COLD: + return [ + certificate.type, + serializeCredential(certificate.coldCredential), + serializeAnchor(certificate.anchor), + ] + case CertificateType.DREP_REGISTRATION: + return [ + certificate.type, + serializeCredential(certificate.dRepCredential), + certificate.deposit, + serializeAnchor(certificate.anchor), + ] + case CertificateType.DREP_DEREGISTRATION: + return [ + certificate.type, + serializeCredential(certificate.dRepCredential), + certificate.deposit, + ] + case CertificateType.DREP_UPDATE: + return [ + certificate.type, + serializeCredential(certificate.dRepCredential), + serializeAnchor(certificate.anchor), + ] default: unreachable(certificate) } } -const serializeCollateralInput = (collateralInput: TransactionInput) => [ +export const serializeCollateralInput = (collateralInput: TransactionInput) => [ collateralInput.transactionId, collateralInput.index, ] +const serializeVoter = (voter: Voter) => [voter.type, voter.hash] + +const serializeGovActionId = (govActionId: GovActionId) => [ + govActionId.transactionId, + govActionId.index, +] + +const serializeVotingProcedure = (votingProcedure: VotingProcedure) => [ + votingProcedure.voteOption, + serializeAnchor(votingProcedure.anchor), +] + +const serializeVotingProcedures = (ballots: VoterVotes[]) => + new Map( + ballots.map(({voter, votes}) => [ + serializeVoter(voter), + new Map( + votes.map(({govActionId, votingProcedure}) => [ + serializeGovActionId(govActionId), + serializeVotingProcedure(votingProcedure), + ]), + ), + ]), + ) + +export const serializeProposalProcedure = (procedure: ProposalProcedure) => [ + procedure.deposit, + procedure.rewardAccount, + procedure.govAction, + serializeAnchor(procedure.anchor), +] + export const serializeTxBody = (txBody: TransactionBody) => filteredMap([ - [TransactionBodyKeys.INPUTS, txBody.inputs.map(serializeTxInput)], + [ + TransactionBodyKeys.INPUTS, + serializeCddlSetBase(txBody.inputs, serializeTxInput), + ], [TransactionBodyKeys.OUTPUTS, txBody.outputs.map(serializeTxOutput)], [TransactionBodyKeys.FEE, identity(txBody.fee)], [TransactionBodyKeys.TTL, identity(txBody.ttl)], [ TransactionBodyKeys.CERTIFICATES, - txBody.certificates?.map(serializeCertificate), + serializeCddlSetBaseOrUndefined( + txBody.certificates, + serializeCertificate, + ), ], [ TransactionBodyKeys.WITHDRAWALS, @@ -208,9 +379,15 @@ export const serializeTxBody = (txBody: TransactionBody) => [TransactionBodyKeys.SCRIPT_DATA_HASH, identity(txBody.scriptDataHash)], [ TransactionBodyKeys.COLLATERAL_INPUTS, - txBody.collateralInputs?.map(serializeCollateralInput), + serializeCddlSetBaseOrUndefined( + txBody.collateralInputs, + serializeCollateralInput, + ), + ], + [ + TransactionBodyKeys.REQUIRED_SIGNERS, + serializeCddlSetBaseOrUndefined(txBody.requiredSigners, identity), ], - [TransactionBodyKeys.REQUIRED_SIGNERS, identity(txBody.requiredSigners)], [TransactionBodyKeys.NETWORK_ID, identity(txBody.networkId)], [ TransactionBodyKeys.COLLATERAL_RETURN_OUTPUT, @@ -220,8 +397,22 @@ export const serializeTxBody = (txBody: TransactionBody) => [TransactionBodyKeys.TOTAL_COLLATERAL, identity(txBody.totalCollateral)], [ TransactionBodyKeys.REFERENCE_INPUTS, - txBody.referenceInputs?.map(serializeTxInput), + serializeCddlSetBaseOrUndefined(txBody.referenceInputs, serializeTxInput), + ], + [ + TransactionBodyKeys.VOTING_PROCEDURES, + txBody.votingProcedures && + serializeVotingProcedures(txBody.votingProcedures), + ], + [ + TransactionBodyKeys.PROPOSAL_PROCEDURES, + serializeCddlSetBaseOrUndefined( + txBody.proposalProcedures, + serializeProposalProcedure, + ), ], + [TransactionBodyKeys.TREASURY, identity(txBody.treasury)], + [TransactionBodyKeys.DONATION, identity(txBody.donation)], ]) export const serializeTx = (tx: Transaction) => { diff --git a/src/txTransformers.ts b/src/txTransformers.ts index 360d0e7..ae8cc2b 100644 --- a/src/txTransformers.ts +++ b/src/txTransformers.ts @@ -3,20 +3,23 @@ import type { AUXILIARY_DATA_HASH_LENGTH, Datum, FixLenBuffer, + Int, Multiasset, + PoolRegistrationCertificate, ReferenceScript, Transaction, TransactionBody, TransactionOutput, + Uint, Unparsed, } from './types' -import {AmountType, DatumType, TxOutputFormat} from './types' +import {AmountType, CertificateType, DatumType, TxOutputFormat} from './types' import {blake2b256, encodeToCbor, unreachable} from './utils' const transformOptionalList = (optionalList?: T[]): T[] | undefined => optionalList?.length === 0 ? undefined : optionalList -const transformMultiasset = ( +const transformMultiasset = ( multiasset?: Multiasset, ): Multiasset | undefined => multiasset === undefined @@ -118,25 +121,54 @@ const transformAuxiliaryDataHash = ( ? auxiliaryDataHash : blake2b256(encodeToCbor(auxiliaryData)) +// Add 258 tags everywhere if at least one is present. +// In the future, when the tags are mandatory, +// we should add them everywhere even if not present. +export const makeSetTagsConsistent = ( + txBody: TransactionBody, +): TransactionBody => { + const poolRegistrationCertificate = txBody.certificates?.items.find( + ({type}) => type === CertificateType.POOL_REGISTRATION, + ) as PoolRegistrationCertificate + const allSets = [ + txBody.inputs, + txBody.certificates, + txBody.collateralInputs, + txBody.requiredSigners, + txBody.referenceInputs, + txBody.proposalProcedures, + poolRegistrationCertificate?.poolParams.poolOwners, + ] + const tagIsPresent = allSets.some((s) => s !== undefined && s.hasTag) + if (tagIsPresent) { + allSets.map((s) => { + if (s !== undefined) { + s.hasTag = true + } + }) + } + return txBody +} + export const transformTxBody = ( txBody: TransactionBody, auxiliaryData: Unparsed, -): TransactionBody => ({ - ...txBody, - outputs: txBody.outputs.map(transformTxOutput), - certificates: transformOptionalList(txBody.certificates), - withdrawals: transformOptionalList(txBody.withdrawals), - collateralInputs: transformOptionalList(txBody.collateralInputs), - requiredSigners: transformOptionalList(txBody.requiredSigners), - collateralReturnOutput: - txBody.collateralReturnOutput && - transformTxOutput(txBody.collateralReturnOutput), - referenceInputs: transformOptionalList(txBody.referenceInputs), - auxiliaryDataHash: transformAuxiliaryDataHash( - txBody.auxiliaryDataHash, - auxiliaryData, - ), -}) +): TransactionBody => { + const transformedBody = { + ...txBody, + outputs: txBody.outputs.map(transformTxOutput), + withdrawals: transformOptionalList(txBody.withdrawals), + auxiliaryDataHash: transformAuxiliaryDataHash( + txBody.auxiliaryDataHash, + auxiliaryData, + ), + collateralReturnOutput: + txBody.collateralReturnOutput && + transformTxOutput(txBody.collateralReturnOutput), + votingProcedures: transformOptionalList(txBody.votingProcedures), + } + return makeSetTagsConsistent(transformedBody) +} export const transformTx = (tx: Transaction): Transaction => ({ ...tx, diff --git a/src/txValidators.ts b/src/txValidators.ts index c53a8a5..12abd6b 100644 --- a/src/txValidators.ts +++ b/src/txValidators.ts @@ -7,18 +7,19 @@ import type { Mint, Multiasset, RequiredSigner, - CredentialType, - StakeDelegationCertificate, - StakeDeregistrationCertificate, - StakeRegistrationCertificate, TransactionBody, TransactionInput, TransactionOutput, Uint, Withdrawal, + CddlSet, + CddlNonEmptySet, + CddlNonEmptyOrderedSet, + VoterVotes, + PoolRegistrationCertificate, } from './types' import {AmountType, CertificateType, DatumType, TxOutputFormat} from './types' -import {bind, getRewardAccountStakeCredentialType, unreachable} from './utils' +import {bind, unreachable} from './utils' const UINT16_MAX = 65535 const MAX_UINT_64_STR = '18446744073709551615' @@ -83,15 +84,21 @@ const validateInt64 = (n: Int, position: string) => err(ValidationErrorReason.INTEGER_NOT_INT64, position), ) -function* validateTxInputs(txInputs: TransactionInput[]): ValidatorReturnType { - yield* validateListConstraints(txInputs, 'transaction_body.inputs', true) +function* validateTxInputs( + txInputs: CddlSet, +): ValidatorReturnType { + yield* validateListConstraints( + txInputs.items, + 'transaction_body.inputs', + true, + ) - for (const [i, input] of txInputs.entries()) { + for (const [i, input] of txInputs.items.entries()) { yield* validateUint64(input.index, `transaction_body.inputs[${i}].index`) } } -function* validateMultiasset( +function* validateMultiasset( multiasset: Multiasset, validateAmount: (n: T, position: string) => ValidatorReturnType, position: string, @@ -186,15 +193,15 @@ function* validateTxOutputs(outputs: TransactionOutput[]): ValidatorReturnType { } function* validateCertificates( - certificates: Certificate[], + certificates: CddlNonEmptyOrderedSet, ): ValidatorReturnType { yield* validateListConstraints( - certificates, + certificates.items, 'transaction_body.certificates', false, ) - for (const [i, certificate] of certificates.entries()) { + for (const [i, certificate] of certificates.items.entries()) { switch (certificate.type) { case CertificateType.POOL_REGISTRATION: yield* validateUint64( @@ -238,6 +245,49 @@ function* validateCertificates( ), ) break + case CertificateType.STAKE_AND_VOTE_DELEGATION: + yield* validate( + false, + err( + ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_VOTE_DELEG, + `transaction_body.certificates[${i}]`, + ), + ) + break + case CertificateType.STAKE_REGISTRATION_AND_DELEGATION: + yield* validate( + false, + err( + ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_REG_DELEG, + `transaction_body.certificates[${i}]`, + ), + ) + break + case CertificateType.STAKE_REGISTRATION_WITH_VOTE_DELEGATION: + yield* validate( + false, + err( + ValidationErrorReason.UNSUPPORTED_CERTIFICATE_VOTE_REG_DELEG, + `transaction_body.certificates[${i}]`, + ), + ) + break + case CertificateType.STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION: + yield* validate( + false, + err( + ValidationErrorReason.UNSUPPORTED_CERTIFICATE_STAKE_VOTE_REG_DELEG, + `transaction_body.certificates[${i}]`, + ), + ) + break + case CertificateType.STAKE_REGISTRATION_CONWAY: + case CertificateType.STAKE_DEREGISTRATION_CONWAY: + yield* validateUint64( + certificate.deposit, + `transaction_body.certificates[${i}].deposit`, + ) + break default: break } @@ -259,90 +309,19 @@ function* validateWithdrawals(withdrawals: Withdrawal[]): ValidatorReturnType { } } -function* validateStakeCredentials( - certificates: Certificate[] | undefined, - withdrawals: Withdrawal[] | undefined, -): ValidatorReturnType { - const certificateStakeCredentialTypes: Set = new Set() - const withdrawalStakeCredentialTypes: Set = new Set() - - if (certificates) { - // We must first filter out the certificates that contain stake credentials - const certificatesWithStakeCredentials = certificates.filter( - ({type}) => - type === CertificateType.STAKE_REGISTRATION || - type === CertificateType.STAKE_DEREGISTRATION || - type === CertificateType.STAKE_DELEGATION, - ) as ( - | StakeRegistrationCertificate - | StakeDeregistrationCertificate - | StakeDelegationCertificate - )[] - certificatesWithStakeCredentials.forEach(({stakeCredential}) => - certificateStakeCredentialTypes.add(stakeCredential.type), - ) - // We check the set of stake credential types to be less or equal to one, - // because if there are 0 types it means there were no certificates with - // stake credentials which is possible and it shouldn't trigger this error - yield* validate( - certificateStakeCredentialTypes.size <= 1, - err( - ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL, - 'transaction_body.certificates', - ), - ) - } - - if (withdrawals) { - withdrawals.forEach(({rewardAccount}) => - withdrawalStakeCredentialTypes.add( - getRewardAccountStakeCredentialType(rewardAccount), - ), - ) - // Here we also check the number of stake credential types to be less or - // equal to one, because we could have been dealing with an empty array - // and that's a ValidationError that is caught elsewhere and shouldn't - // be caught by this check - yield* validate( - withdrawalStakeCredentialTypes.size <= 1, - err( - ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL, - 'transaction_body.withdrawals', - ), - ) - } - - if ( - certificateStakeCredentialTypes.size === 1 && - withdrawalStakeCredentialTypes.size === 1 - ) { - // We only trigger this check if both certificates and withdrawals have - // consistent stake credential types otherwise it is useless to check - // whether they are consistent in respect to each other. - yield* validate( - [...certificateStakeCredentialTypes][0] === - [...withdrawalStakeCredentialTypes][0], - err( - ValidationErrorReason.CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT, - 'transaction_body', - ), - ) - } -} - const validateMint = (mint: Mint) => validateMultiasset(mint, validateInt64, 'transaction_body.mint') function* validateCollateralInputs( - collateralInputs: TransactionInput[], + collateralInputs: CddlNonEmptySet, ): ValidatorReturnType { yield* validateListConstraints( - collateralInputs, + collateralInputs.items, 'transaction_body.collateral_inputs', false, ) - for (const [i, collateralInput] of collateralInputs.entries()) { + for (const [i, collateralInput] of collateralInputs.items.entries()) { yield* validateUint64( collateralInput.index, `transaction_body.collateral_inputs[${i}].index`, @@ -351,25 +330,25 @@ function* validateCollateralInputs( } function* validateRequiredSigners( - requiredSigners: RequiredSigner[], + requiredSigners: CddlNonEmptySet, ): ValidatorReturnType { yield* validateListConstraints( - requiredSigners, + requiredSigners.items, 'transaction_body.required_signers', false, ) } function* validateReferenceInputs( - referenceInputs: TransactionInput[], + referenceInputs: CddlNonEmptySet, ): ValidatorReturnType { yield* validateListConstraints( - referenceInputs, + referenceInputs.items, 'transaction_body.reference_inputs', false, ) - for (const [i, input] of referenceInputs.entries()) { + for (const [i, input] of referenceInputs.items.entries()) { yield* validateUint64( input.index, `transaction_body.reference_inputs[${i}].index`, @@ -404,8 +383,43 @@ function* validateTxCollateralReturnOutput( } } +function* validateVotingProcedures( + voterVotesArray: VoterVotes[], +): ValidatorReturnType { + yield* validateListConstraints( + voterVotesArray, + 'transaction_body.voting_procedures', + true, + ) + + yield* validate( + voterVotesArray.length <= 1, + err( + ValidationErrorReason.TOO_MANY_VOTERS_IN_VOTING_PROCEDURES, + `transaction_body.voting_procedures`, + ), + ) + + for (const [i, voterVotes] of voterVotesArray.entries()) { + yield* validate( + voterVotes.votes.length === 1, + err( + ValidationErrorReason.INVALID_NUMBER_OF_VOTING_PROCEDURES, + `transaction_body.voting_procedures[${i}].votes`, + ), + ) + + for (const [j, voterVote] of voterVotes.votes.entries()) { + yield* validateUint64( + voterVote.govActionId.index, + `transaction_body.voting_procedures[${i}].votes[${j}].govActionId.index`, + ) + } + } +} + /** - * Checks if a transaction contains pool registration certificate, if it does + * Checks if a transaction contains pool registration certificate; if it does, * runs a series of validators for pool registration transactions. */ function* validatePoolRegistrationTransaction( @@ -414,8 +428,8 @@ function* validatePoolRegistrationTransaction( // If the transaction doesn't contain pool registration certificates we can // skip all the validations. if ( - !txBody.certificates || - txBody.certificates.find( + txBody.certificates === undefined || + txBody.certificates.items.find( ({type}) => type === CertificateType.POOL_REGISTRATION, ) === undefined ) { @@ -423,12 +437,13 @@ function* validatePoolRegistrationTransaction( } yield* validate( - txBody.certificates.length === 1, + txBody.certificates.items.length === 1, err( ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_OTHER_CERTIFICATES, 'transaction_body.certificates', ), ) + // We consider the transaction to have no withdrawals if the field is not // present or if the array length is 0. Checking only whether the field is // not present is not sufficient because the transaction could contain an @@ -436,16 +451,17 @@ function* validatePoolRegistrationTransaction( // which is not fixable. This could lead to a scenario where a potentially // fixable transaction would have an unfixable validation error reported. yield* validate( - !txBody.withdrawals || txBody.withdrawals.length === 0, + txBody.withdrawals === undefined || txBody.withdrawals.length === 0, err( ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_WITHDRAWALS, 'transaction_body.withdrawals', ), ) + // The same applies here, but mint has a nested array for the tokens that // needs to be checked in a similar way yield* validate( - !txBody.mint || + txBody.mint === undefined || txBody.mint.length === 0 || txBody.mint.every(({tokens}) => tokens.length === 0), err( @@ -453,6 +469,106 @@ function* validatePoolRegistrationTransaction( 'transaction_body.mint', ), ) + + // no Plutus elements in tx outputs + yield* validate( + txBody.outputs.every((output) => { + switch (output.format) { + case TxOutputFormat.MAP_BABBAGE: + if (output.datum !== undefined) { + return false + } + if (output.referenceScript !== undefined) { + return false + } + break + case TxOutputFormat.ARRAY_LEGACY: + if (output.datumHash !== undefined) { + return false + } + break + default: + unreachable(output) + } + return true + }), + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_PLUTUS_OUTPUTS, + 'transaction_body.outputs', + ), + ) + + yield* validate( + txBody.scriptDataHash !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_SCRIPT_DATA_HASH, + 'transaction_body.script_data_hash', + ), + ) + + yield* validate( + txBody.collateralInputs !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_INPUTS, + 'transaction_body.collateral_inputs', + ), + ) + + yield* validate( + txBody.requiredSigners !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_REQUIRED_SIGNERS, + 'transaction_body.required_signers', + ), + ) + + yield* validate( + txBody.collateralReturnOutput !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_COLLATERAL_RETURN_OUTPUT, + 'transaction_body.collateral_return_output', + ), + ) + + yield* validate( + txBody.totalCollateral !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_TOTAL_COLLATERAL, + 'transaction_body.total_collateral', + ), + ) + + yield* validate( + txBody.referenceInputs !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_REFERENCE_INPUTS, + 'transaction_body.reference_inputs', + ), + ) + + yield* validate( + txBody.votingProcedures !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_VOTING_PROCEDURES, + 'transaction_body.voting_procedures', + ), + ) + + yield* validate( + txBody.treasury !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_TREASURY, + 'transaction_body.treasury', + ), + ) + + yield* validate( + txBody.donation !== undefined, + err( + ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_DONATION, + 'transaction_body.donation', + ), + ) } /** @@ -469,7 +585,6 @@ function* validateTxBody(txBody: TransactionBody): ValidatorReturnType { ) yield* validateOptional(txBody.certificates, validateCertificates) yield* validateOptional(txBody.withdrawals, validateWithdrawals) - yield* validateStakeCredentials(txBody.certificates, txBody.withdrawals) yield* validate( txBody.update === undefined, err(ValidationErrorReason.UNSUPPORTED_TX_UPDATE, 'transaction_body.update'), @@ -485,7 +600,6 @@ function* validateTxBody(txBody: TransactionBody): ValidatorReturnType { txBody.networkId, bind(validateUint64, 'transaction_body.network_id'), ) - yield* validatePoolRegistrationTransaction(txBody) yield* validateOptional( txBody.collateralReturnOutput, bind( @@ -498,6 +612,46 @@ function* validateTxBody(txBody: TransactionBody): ValidatorReturnType { bind(validateUint64, 'transaction_body.total_collateral'), ) yield* validateOptional(txBody.referenceInputs, validateReferenceInputs) + yield* validateOptional(txBody.votingProcedures, validateVotingProcedures) + yield* validate( + txBody.proposalProcedures === undefined, + err( + ValidationErrorReason.UNSUPPORTED_TX_PROPOSAL_PROCEDURES, + 'transaction_body.proposal_procedures', + ), + ) + yield* validateOptional( + txBody.treasury, + bind(validateUint64, 'transaction_body.treasury'), + ) + yield* validateOptional( + txBody.donation, + bind(validateUint64, 'transaction_body.donation'), + ) + + // extra checks for transactions containing stake pool registration certificates + yield* validatePoolRegistrationTransaction(txBody) + + // check for consistency of set tags + const poolRegistrationCertificate = txBody.certificates?.items.find( + ({type}) => type === CertificateType.POOL_REGISTRATION, + ) as PoolRegistrationCertificate + const allSets = [ + txBody.inputs, + txBody.certificates, + txBody.collateralInputs, + txBody.requiredSigners, + txBody.referenceInputs, + txBody.proposalProcedures, + poolRegistrationCertificate?.poolParams.poolOwners, + ] + const tagIsPresent = allSets.some((s) => s !== undefined && s.hasTag) + const tagIsAbsent = allSets.some((s) => s !== undefined && !s.hasTag) + const tagsAreInconsistent = tagIsPresent && tagIsAbsent + yield* validate( + !tagsAreInconsistent, + err(ValidationErrorReason.TX_INCONSISTENT_SET_TAGS, 'transaction_body'), + ) } /** diff --git a/src/types.ts b/src/types.ts index 12acefb..9761727 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,23 @@ export type FixLenBuffer = Buffer & {__length: N} export type MaxLenBuffer = Buffer & {__maxLength: N} export type MaxSizeUint = Uint & {__maxSize: N} +export type CddlSetBase = { + items: T[] + hasTag: boolean // 258 tag, existing since Conway, using the tag is optional in Conway +} +export type CddlSet = CddlSetBase & { + _nonEmpty: false + _ordered: false +} +export type CddlNonEmptySet = CddlSetBase & { + _nonEmpty: true + _ordered: false +} +export type CddlNonEmptyOrderedSet = CddlSetBase & { + _nonEmpty: true + _ordered: true +} + export const KEY_HASH_LENGTH = 28 export const SCRIPT_HASH_LENGTH = 28 export const GENESIS_DELEGATE_HASH_LENGTH = 28 @@ -20,6 +37,7 @@ export const AUXILIARY_DATA_HASH_LENGTH = 32 export const TX_ID_HASH_LENGTH = 32 export const DATUM_HASH_LENGTH = 32 export const SCRIPT_DATA_HASH_LENGTH = 32 +export const ANCHOR_DATA_HASH_LENGTH = 32 export const IPV4_LENGTH = 4 export const IPV6_LENGTH = 16 @@ -46,15 +64,15 @@ export type TransactionInput = { // Multiasset export type PolicyId = FixLenBuffer export type AssetName = MaxLenBuffer -export type Token = { +export type Token = { assetName: AssetName amount: T } -export type AssetGroup = { +export type AssetGroup = { policyId: PolicyId tokens: Token[] } -export type Multiasset = AssetGroup[] +export type Multiasset = AssetGroup[] // Amount export enum AmountType { @@ -125,8 +143,20 @@ export enum CertificateType { STAKE_DELEGATION = 2, POOL_REGISTRATION = 3, POOL_RETIREMENT = 4, - GENESIS_KEY_DELEGATION = 5, - MOVE_INSTANTANEOUS_REWARDS_CERT = 6, + GENESIS_KEY_DELEGATION = 5, // removed in Conway + MOVE_INSTANTANEOUS_REWARDS_CERT = 6, // removed in Conway + STAKE_REGISTRATION_CONWAY = 7, + STAKE_DEREGISTRATION_CONWAY = 8, + VOTE_DELEGATION = 9, + STAKE_AND_VOTE_DELEGATION = 10, + STAKE_REGISTRATION_AND_DELEGATION = 11, + STAKE_REGISTRATION_WITH_VOTE_DELEGATION = 12, + STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION = 13, + AUTHORIZE_COMMITTEE_HOT = 14, + RESIGN_COMMITTEE_COLD = 15, + DREP_REGISTRATION = 16, + DREP_DEREGISTRATION = 17, + DREP_UPDATE = 18, } export enum CredentialType { @@ -136,12 +166,12 @@ export enum CredentialType { export type KeyCredential = { type: CredentialType.KEY_HASH - hash: KeyHash + keyHash: KeyHash } export type ScriptCredential = { type: CredentialType.SCRIPT_HASH - hash: ScriptHash + scriptHash: ScriptHash } export type Credential = KeyCredential | ScriptCredential @@ -209,7 +239,7 @@ export type PoolParams = { cost: Coin margin: UnitInterval rewardAccount: RewardAccount - poolOwners: KeyHash[] + poolOwners: CddlSet relays: Relay[] poolMetadata: PoolMetadata | null } @@ -235,6 +265,116 @@ export type MoveInstantaneousRewardsCertificate = { restOfData: Unparsed[] } +export type StakeRegistrationConwayCertificate = { + type: CertificateType.STAKE_REGISTRATION_CONWAY + stakeCredential: Credential + deposit: Coin +} + +export type StakeDeregistrationConwayCertificate = { + type: CertificateType.STAKE_DEREGISTRATION_CONWAY + stakeCredential: Credential + deposit: Coin +} + +export enum DRepType { + KEY_HASH = 0, + SCRIPT_HASH = 1, + ABSTAIN = 2, + NO_CONFIDENCE = 3, +} + +export type KeyHashDRep = { + type: DRepType.KEY_HASH + keyHash: KeyHash +} + +export type ScriptHashDRep = { + type: DRepType.SCRIPT_HASH + scriptHash: ScriptHash +} + +export type AbstainDRep = { + type: DRepType.ABSTAIN +} + +export type NoConfidenceDRep = { + type: DRepType.NO_CONFIDENCE +} + +export type DRep = KeyHashDRep | ScriptHashDRep | AbstainDRep | NoConfidenceDRep + +export type VoteDelegationCertificate = { + type: CertificateType.VOTE_DELEGATION + stakeCredential: Credential + dRep: DRep +} + +export type StakeAndVoteDelegationCertificate = { + type: CertificateType.STAKE_AND_VOTE_DELEGATION + stakeCredential: Credential + poolKeyHash: FixLenBuffer + dRep: DRep +} + +export type StakeRegistrationAndDelegationCertificate = { + type: CertificateType.STAKE_REGISTRATION_AND_DELEGATION + stakeCredential: Credential + poolKeyHash: FixLenBuffer + deposit: Coin +} + +export type StakeRegistrationWithVoteDelegationCertificate = { + type: CertificateType.STAKE_REGISTRATION_WITH_VOTE_DELEGATION + stakeCredential: Credential + dRep: DRep + deposit: Coin +} + +export type StakeRegistrationWithStakeAndVoteDelegationCertificate = { + type: CertificateType.STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION + stakeCredential: Credential + poolKeyHash: FixLenBuffer + dRep: DRep + deposit: Coin +} + +export type Anchor = { + url: MaxLenString + dataHash: FixLenBuffer +} + +export type AuthorizeCommitteeHotCertificate = { + type: CertificateType.AUTHORIZE_COMMITTEE_HOT + coldCredential: Credential + hotCredential: Credential +} + +export type ResignCommitteeColdCertificate = { + type: CertificateType.RESIGN_COMMITTEE_COLD + coldCredential: Credential + anchor: Anchor | null +} + +export type DRepRegistrationCertificate = { + type: CertificateType.DREP_REGISTRATION + dRepCredential: Credential + deposit: Coin + anchor: Anchor | null +} + +export type DRepDeregistrationCertificate = { + type: CertificateType.DREP_DEREGISTRATION + dRepCredential: Credential + deposit: Coin +} + +export type DRepUpdateCertificate = { + type: CertificateType.DREP_UPDATE + dRepCredential: Credential + anchor: Anchor | null +} + export type Certificate = | StakeRegistrationCertificate | StakeDeregistrationCertificate @@ -243,6 +383,18 @@ export type Certificate = | PoolRetirementCertificate | GenesisKeyDelegation | MoveInstantaneousRewardsCertificate + | StakeRegistrationConwayCertificate + | StakeDeregistrationConwayCertificate + | VoteDelegationCertificate + | StakeAndVoteDelegationCertificate + | StakeRegistrationAndDelegationCertificate + | StakeRegistrationWithVoteDelegationCertificate + | StakeRegistrationWithStakeAndVoteDelegationCertificate + | AuthorizeCommitteeHotCertificate + | ResignCommitteeColdCertificate + | DRepRegistrationCertificate + | DRepDeregistrationCertificate + | DRepUpdateCertificate // Withdrawal export type Withdrawal = { @@ -256,25 +408,103 @@ export type Mint = Multiasset // Required signer export type RequiredSigner = FixLenBuffer +export enum VoterType { + COMMITTEE_KEY = 0, + COMMITTEE_SCRIPT = 1, + DREP_KEY = 2, + DREP_SCRIPT = 3, + STAKE_POOL = 4, +} + +export type CommitteeKeyVoter = { + type: VoterType.COMMITTEE_KEY + hash: KeyHash +} + +export type CommitteeScriptVoter = { + type: VoterType.COMMITTEE_SCRIPT + hash: ScriptHash +} + +export type DRepKeyVoter = { + type: VoterType.DREP_KEY + hash: KeyHash +} + +export type DRepScriptVoter = { + type: VoterType.DREP_SCRIPT + hash: ScriptHash +} + +export type StakePoolVoter = { + type: VoterType.STAKE_POOL + hash: KeyHash +} + +export type Voter = + | CommitteeKeyVoter + | CommitteeScriptVoter + | DRepKeyVoter + | DRepScriptVoter + | StakePoolVoter + +// Governance action id +export type GovActionId = { + transactionId: FixLenBuffer + index: Uint +} + +export enum VoteOption { + NO = 0, + YES = 1, + ABSTAIN = 2, +} + +export type VotingProcedure = { + voteOption: VoteOption + anchor: Anchor | null +} + +export type Vote = { + govActionId: GovActionId + votingProcedure: VotingProcedure +} + +export type VoterVotes = { + voter: Voter + votes: Vote[] +} + +export type ProposalProcedure = { + deposit: Coin + rewardAccount: RewardAccount + govAction: Unparsed + anchor: Anchor +} + // Transaction body export type TransactionBody = { - inputs: TransactionInput[] + inputs: CddlSet outputs: TransactionOutput[] fee: Coin ttl?: Uint - certificates?: Certificate[] + certificates?: CddlNonEmptyOrderedSet withdrawals?: Withdrawal[] update?: Unparsed auxiliaryDataHash?: FixLenBuffer validityIntervalStart?: Uint mint?: Mint scriptDataHash?: FixLenBuffer - collateralInputs?: TransactionInput[] - requiredSigners?: RequiredSigner[] + collateralInputs?: CddlNonEmptySet + requiredSigners?: CddlNonEmptySet networkId?: Uint collateralReturnOutput?: TransactionOutput totalCollateral?: Coin - referenceInputs?: TransactionInput[] + referenceInputs?: CddlNonEmptySet + votingProcedures?: VoterVotes[] + proposalProcedures?: CddlNonEmptyOrderedSet + treasury?: Coin + donation?: Coin } export type Transaction = { diff --git a/src/utils.ts b/src/utils.ts index 5286044..5011b5b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import blake2b from 'blake2b' -import cbor from 'cbor' +import cbor, {Tagged} from 'cbor' import type {FixLenBuffer, RewardAccount} from './types' import {CredentialType} from './types' @@ -15,22 +15,20 @@ export function unreachable(_val: never): never { export enum CborTag { ENCODED_CBOR = 24, - TUPLE = 30, + + // called unit interval in the CDDL, but means a fraction from [0, 1] + UNIT_INTERVAL = 30, + + // used for arrays interpreted as sets + SET = 258, } export const decodeCbor = (buffer: Buffer) => cbor.decode(buffer, { preventDuplicateKeys: true, tags: { - // Specifies that tag 30 should be parsed only as a tuple. For example - // the CDDL specifies unit_interval as: - // unit_interval = #6.30([uint, uint]) - [CborTag.TUPLE]: (v: unknown) => { - if (!Array.isArray(v) || v.length !== 2) { - throw new Error('Invalid tuple') - } - return v - }, + // we want to parse CDDL sets on our own + [CborTag.SET]: (x) => new Tagged(CborTag.SET, x), }, }) @@ -98,6 +96,10 @@ export enum TransactionBodyKeys { COLLATERAL_RETURN_OUTPUT = 16, TOTAL_COLLATERAL = 17, REFERENCE_INPUTS = 18, + VOTING_PROCEDURES = 19, + PROPOSAL_PROCEDURES = 20, + TREASURY = 21, + DONATION = 22, } export const blake2b256 = (data: unknown): FixLenBuffer<32> => diff --git a/test/integration/__fixtures__/transactions.ts b/test/integration/__fixtures__/transactions.ts index 7e01e56..a75f374 100644 --- a/test/integration/__fixtures__/transactions.ts +++ b/test/integration/__fixtures__/transactions.ts @@ -1,16 +1,26 @@ -import type { +import {ValidationError, ValidationErrorReason} from '../../../src/errors' +import { MaxLenBuffer, Port, Transaction, TransactionBody, -} from '../../../src/types' -import { + VoteOption, + VoterType, AmountType, CertificateType, DatumType, RelayType, CredentialType, TxOutputFormat, + DRepType, + CddlSet, + TransactionInput, + CddlNonEmptySet, + Certificate, + CddlNonEmptyOrderedSet, + KeyHash, + RequiredSigner, + ProposalProcedure, } from '../../../src/types' import { fromBech32, @@ -37,15 +47,68 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ testName: 'Simple tx body', cbor: 'a30081825820ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda700021a0001e240', txBody: { - inputs: [ + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, + outputs: [ { - transactionId: toFixLenBuffer( - 'ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de', - 32, + format: TxOutputFormat.ARRAY_LEGACY, + address: fromBech32( + 'addr_test1qqr585tvlc7ylnqvz8pyqwauzrdu0mxag3m7q56grgmgu7sxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknswgndm3', ), - index: toUint(0), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(0), + }, + datumHash: undefined, }, ], + fee: toUint(123456), + ttl: undefined, + certificates: undefined, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: undefined, + collateralInputs: undefined, + requiredSigners: undefined, + networkId: undefined, + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, + }, + }, + { + testName: 'Simple tx body with a 258 tag in inputs', + cbor: 'a300d9010281825820ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda700021a0001e240', + txBody: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de', + 32, + ), + index: toUint(0), + }, + ], + hasTag: true, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -74,21 +137,28 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { testName: 'Tx body with withdrawals', cbor: 'a4008182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda71a3dbb8b21021a0003ba1105a2581df0760fb6955d1217b1f1f208df6d45ab23c9e17b0c984a2d3a22bbbfb81a00bd979c581df0b494d35f236093e7caed75d2b99b1e523cde935a6f4a2d276b9fb4011a07b914d0', txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -130,21 +200,28 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { testName: 'Tx body with multiple outputs and multiassets', cbor: 'a40081825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee1bffffffffffffffff018282583930167f6dbf610ae030f043adb1f3af78754ed9595ad4ac1f7ed9ff6466760fb6955d1217b1f1f208df6d45ab23c9e17b0c984a2d3a22bbbfb8821a0001e91fa1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a146546f6b656e3101825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda7821a3dbb8b21a1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a246546f6b656e311a00155d9746546f6b656e321a00beeff1021a0003050309a1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a246546f6b656e313a0098967f46546f6b656e321b7fffffffffffffff', txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee', - 32, - ), - index: toUint('18446744073709551615'), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee', + 32, + ), + index: toUint('18446744073709551615'), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -233,21 +310,28 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { testName: 'Tx body with certificates', cbor: 'a40081825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee182a0181825839019b3a93b321ff8d65d6df1c6d845d54dbbf2cb34105fdb44ece1b7d312c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d01021a0012fc51048582008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d82018201581cc1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee5583028200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581c001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa3908a03581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8582067c5c0b45db55e8c82752263207b9a92c2d5fa6c671aceed9df451cad3fac7a31a0001e2401a05f5e100d81e82031819581de1d7d8a321633b3d1ab1651eeb258ad898ebcef1d348b54148f18e15da82581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581cf699c6400f85bdca54e44d0cad1f6141ce049a411c0d695fc30c3f7384840019029af650004706260000004700000000111100008301f676616464726573732e76616375756d6c6162732e636f6d8202781e616e6f746865722e616464726573732e76616375756d6c6162732e636f6d840019ffff447f0000fff682782468747470733a2f2f706f6f6c2d6d657461646174612e76616375756d6c6162732e636f6d5820e318d62e3d5cc3cc23ca1123438e439d7aac6c6c423320f670d159726ac9d11f8304581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a81a0001dfbe', txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee', - 32, - ), - index: toUint(42), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee', + 32, + ), + index: toUint(42), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -263,109 +347,115 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ ], fee: toUint(1244241), ttl: undefined, - certificates: [ - { - type: CertificateType.STAKE_REGISTRATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - 28, - ), + certificates: { + items: [ + { + type: CertificateType.STAKE_REGISTRATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, }, - }, - { - type: CertificateType.STAKE_DEREGISTRATION, - stakeCredential: { - type: CredentialType.SCRIPT_HASH, - hash: toFixLenBuffer( - 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', - 28, - ), + { + type: CertificateType.STAKE_DEREGISTRATION, + stakeCredential: { + type: CredentialType.SCRIPT_HASH, + scriptHash: toFixLenBuffer( + 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', + 28, + ), + }, }, - }, - { - type: CertificateType.STAKE_DELEGATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + { + type: CertificateType.STAKE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, + poolKeyHash: toFixLenBuffer( + '001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa390', 28, ), }, - poolKeyHash: toFixLenBuffer( - '001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa390', - 28, - ), - }, - { - type: CertificateType.POOL_REGISTRATION, - poolParams: { - operator: toFixLenBuffer( - '4DFBC0559B2E1D6AF62C447F0A0D6290A8B05E075EF08DB38C1B81A8', - 28, - ), - vrfKeyHash: toFixLenBuffer( - '67C5C0B45DB55E8C82752263207B9A92C2D5FA6C671ACEED9DF451CAD3FAC7A3', - 32, - ), - pledge: toUint(123456), - cost: toUint(100000000), - margin: [toUint(3), toUint(25)], - rewardAccount: rewardAccount( - 'stake1u8ta3gepvvan6x43v50wkfv2mzvwhnh36dyt2s2g7x8ptks528lzm', - ), - poolOwners: [ - toFixLenBuffer( - '2C049DFED8BC41EDEFBBC835CA0A739CAC961557950262EF48BCFF1D', - 28, - ), - toFixLenBuffer( - 'F699C6400F85BDCA54E44D0CAD1F6141CE049A411C0D695FC30C3F73', + { + type: CertificateType.POOL_REGISTRATION, + poolParams: { + operator: toFixLenBuffer( + '4DFBC0559B2E1D6AF62C447F0A0D6290A8B05E075EF08DB38C1B81A8', 28, ), - ], - relays: [ - { - type: RelayType.SINGLE_HOST_ADDRESS, - port: 666 as Port, - ipv4: null, - ipv6: toFixLenBuffer('00470626000000470000000011110000', 16), - }, - { - type: RelayType.SINGLE_HOST_NAME, - port: null, - dnsName: toMaxLenString('address.vacuumlabs.com', 64), - }, - { - type: RelayType.MULTI_HOST_NAME, - dnsName: toMaxLenString('another.address.vacuumlabs.com', 64), - }, - { - type: RelayType.SINGLE_HOST_ADDRESS, - port: 65535 as Port, - ipv4: ipv4ToBuffer('127.0.0.255'), - ipv6: null, - }, - ], - poolMetadata: { - url: toMaxLenString('https://pool-metadata.vacuumlabs.com', 64), - metadataHash: toFixLenBuffer( - 'E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', + vrfKeyHash: toFixLenBuffer( + '67C5C0B45DB55E8C82752263207B9A92C2D5FA6C671ACEED9DF451CAD3FAC7A3', 32, ), + pledge: toUint(123456), + cost: toUint(100000000), + margin: [toUint(3), toUint(25)], + rewardAccount: rewardAccount( + 'stake1u8ta3gepvvan6x43v50wkfv2mzvwhnh36dyt2s2g7x8ptks528lzm', + ), + poolOwners: { + items: [ + toFixLenBuffer( + '2C049DFED8BC41EDEFBBC835CA0A739CAC961557950262EF48BCFF1D', + 28, + ), + toFixLenBuffer( + 'F699C6400F85BDCA54E44D0CAD1F6141CE049A411C0D695FC30C3F73', + 28, + ), + ], + hasTag: false, + } as CddlSet, + relays: [ + { + type: RelayType.SINGLE_HOST_ADDRESS, + port: 666 as Port, + ipv4: null, + ipv6: toFixLenBuffer('00470626000000470000000011110000', 16), + }, + { + type: RelayType.SINGLE_HOST_NAME, + port: null, + dnsName: toMaxLenString('address.vacuumlabs.com', 64), + }, + { + type: RelayType.MULTI_HOST_NAME, + dnsName: toMaxLenString('another.address.vacuumlabs.com', 64), + }, + { + type: RelayType.SINGLE_HOST_ADDRESS, + port: 65535 as Port, + ipv4: ipv4ToBuffer('127.0.0.255'), + ipv6: null, + }, + ], + poolMetadata: { + url: toMaxLenString('https://pool-metadata.vacuumlabs.com', 64), + metadataHash: toFixLenBuffer( + 'E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', + 32, + ), + }, }, }, - }, - { - type: CertificateType.POOL_RETIREMENT, - poolKeyHash: toFixLenBuffer( - '4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8', - 28, - ), - epoch: toUint(122814), - }, - ], + { + type: CertificateType.POOL_RETIREMENT, + poolKeyHash: toFixLenBuffer( + '4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8', + 28, + ), + epoch: toUint(122814), + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, withdrawals: undefined, update: undefined, auxiliaryDataHash: undefined, @@ -378,6 +468,10 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { @@ -385,15 +479,18 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ 'Tx body with output datum hash, script data hash, collateral inputs, required signers and network id', cbor: 'a800818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018283583d105e2f080eb93bad86d401545e0ce5f2221096d6477e11e6643922fa8d2ed495234dc0d667c1316ff84e572310e265edb31330448b36b7179e28dd419e1a006ca7935820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce18883583930167f6dbf610ae030f043adb1f3af78754ed9595ad4ac1f7ed9ff6466760fb6955d1217b1f1f208df6d45ab23c9e17b0c984a2d3a22bbbfb8821a0001e91fa1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a146546f6b656e3101582000ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce102182a030a0b5820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce1880d818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7000e82581cfea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a581ceea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a0f01', txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -456,29 +553,39 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ 'ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce188', 32, ), - collateralInputs: [ - { - transactionId: toFixLenBuffer( - '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', - 32, + collateralInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlNonEmptySet, + requiredSigners: { + items: [ + toFixLenBuffer( + 'fea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, ), - index: toUint(0), - }, - ], - requiredSigners: [ - toFixLenBuffer( - 'fea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', - 28, - ), - toFixLenBuffer( - 'eea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', - 28, - ), - ], + toFixLenBuffer( + 'eea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, + ), + ], + hasTag: false, + } as CddlNonEmptySet, networkId: toUint(1), collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { @@ -486,15 +593,18 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ 'Tx body with inline datum, reference script, collateral return, total collateral and reference input', cbor: 'a8008182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d000181a4005839008b3303988371208dd0916cc4548c4eafc2fd3d6205ea8ec180c1b1d9e0820d5929d99bce8aa81e86195fd2b824e6550820a03af325f6ff220100028201d81841a003d8185846820158425840010000332233322222253353004333573466ebc00c00801801440204c98d4c01ccd5ce2481094e6f7420457175616c000084984880084880048004480048004102000b5820853cbe68f7fccdeeeb0fd7b711ea147912190c35ac52d9d94080ae82809b2f840d8182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d0110a2005839008b3303988371208dd0916cc4548c4eafc2fd3d6205ea8ec180c1b1d9e0820d5929d99bce8aa81e86195fd2b824e6550820a03af325f6ff220100110a128182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d02', txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.MAP_BABBAGE, @@ -527,15 +637,18 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ '853cbe68f7fccdeeeb0fd7b711ea147912190c35ac52d9d94080ae82809b2f84', 32, ), - collateralInputs: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(1), - }, - ], + collateralInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(1), + }, + ], + hasTag: false, + } as CddlNonEmptySet, requiredSigners: undefined, networkId: undefined, collateralReturnOutput: { @@ -551,15 +664,393 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ referenceScript: undefined, }, totalCollateral: toUint(10), - referenceInputs: [ + referenceInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(2), + }, + ], + hasTag: false, + } as CddlNonEmptySet, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, + }, + }, + { + // https://auspicious-fuchsia-2e9.notion.site/Transactions-in-SanchoNet-fbcf91c799404fe4b30a78e8e091df79#2fb761e130db4cc9954772cdf72d18e7 + testName: 'Conway tx body: voting procedures', + cbor: 'a400818258204547c077e8f3a9184438e36503f78b634eb416658c336c2d017d9912a7c493c7000181a20058390013ca2480e9651a5c504b36eda271ec171cdd404cfe349097524a48bd8bee57ce33c7c1f711bc5801986d89dd68078f5922b83812cc86f65f011b0000000253d3ae64021a0002a38913a18202581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8a1825820787142668a73c7c3ca6003571f429393f2d6dad8886bbcd0a9ba7aca07cc895e008201f6', + txBody: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '4547c077e8f3a9184438e36503f78b634eb416658c336c2d017d9912a7c493c7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, + outputs: [ + { + format: TxOutputFormat.MAP_BABBAGE, + address: toFixLenBuffer( + '0013ca2480e9651a5c504b36eda271ec171cdd404cfe349097524a48bd8bee57ce33c7c1f711bc5801986d89dd68078f5922b83812cc86f65f', + 57, + ), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(9996316260), + }, + datum: undefined, + referenceScript: undefined, + }, + ], + fee: toUint(172937), + ttl: undefined, + certificates: undefined, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: undefined, + collateralInputs: undefined, + requiredSigners: undefined, + networkId: undefined, + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: [ + { + voter: { + type: VoterType.DREP_KEY, + hash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + votes: [ + { + govActionId: { + transactionId: toFixLenBuffer( + '787142668a73c7c3ca6003571f429393f2d6dad8886bbcd0a9ba7aca07cc895e', + 32, + ), + index: toUint(0), + }, + votingProcedure: { + voteOption: VoteOption.YES, + anchor: null, + }, + }, + ], + }, + ], + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, + }, + }, + { + // https://auspicious-fuchsia-2e9.notion.site/Transactions-in-SanchoNet-fbcf91c799404fe4b30a78e8e091df79#a5ef58aa46e84d2f96d0b14f906e0417 + testName: 'Conway tx body: proposal procedures', + cbor: 'a40081825820017b91576a79a3602a02a65b600665ab71037ad14aa162538a26e64b3f5069fc000181a2005839002d745f050a8f7e263f4d0749a82284ed9cc065018c1f4f6a7c1b764882293a49e3ef29a4f9c32e4c18f202f5324182db7790f48dccf7a6dd011b0000000253d1efbc021a0002b3b11481841a000f4240581de082293a49e3ef29a4f9c32e4c18f202f5324182db7790f48dccf7a6dd8305f68282781968747470733a2f2f73686f727475726c2e61742f6173494a365820ee90ece16c47bf812b88edb89a01539e6683d6549a80b15383a4fb218ab9412df682781968747470733a2f2f73686f727475726c2e61742f784d53313558206f890de0c6e418e6526e2b1aa821850cb87aee94a6d77dc2a2e440116abc8e09', + txBody: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '017b91576a79a3602a02a65b600665ab71037ad14aa162538a26e64b3f5069fc', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, + outputs: [ + { + format: TxOutputFormat.MAP_BABBAGE, + address: toFixLenBuffer( + '002d745f050a8f7e263f4d0749a82284ed9cc065018c1f4f6a7c1b764882293a49e3ef29a4f9c32e4c18f202f5324182db7790f48dccf7a6dd', + 57, + ), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(9996201916), + }, + datum: undefined, + referenceScript: undefined, + }, + ], + fee: toUint(177073), + ttl: undefined, + certificates: undefined, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: undefined, + collateralInputs: undefined, + requiredSigners: undefined, + networkId: undefined, + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: { + items: [ + { + deposit: toUint(1000000), + rewardAccount: toFixLenBuffer( + 'e082293a49e3ef29a4f9c32e4c18f202f5324182db7790f48dccf7a6dd', + 29, + ), + govAction: [ + 5, + null, + [ + // constitution + [ + 'https://shorturl.at/asIJ6', + toFixLenBuffer( + 'ee90ece16c47bf812b88edb89a01539e6683d6549a80b15383a4fb218ab9412d', + 32, + ), + ], + null, + ], + ], + anchor: { + url: toMaxLenString('https://shorturl.at/xMS15', 64), + dataHash: toFixLenBuffer( + '6f890de0c6e418e6526e2b1aa821850cb87aee94a6d77dc2a2e440116abc8e09', + 32, + ), + }, + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + treasury: undefined, + donation: undefined, + }, + }, + { + // generated via https://ryun1.github.io/cip95-cardano-wallet-connector/ + NuFi by adding all the certs supported by the dApp + testName: 'Conway tx body: certificates', + cbor: 'a400818258205a70b318d72eb106568e6e9413e7d72573ef018307d189a92a6aea50ef779ee5010182825839006adb0971e4054c0da7ec23a0c276521f1cd8cecf1b15ad4822119cd186dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1011a000f4240825839006adb0971e4054c0da7ec23a0c276521f1cd8cecf1b15ad4822119cd186dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1011b0000000163d178ed021a00031f1d048883098200581c86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1018200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f84108200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f1a001e8480827268747470733a2f2f676f6f676c652e636f6d5820339ec66289039dbf9fa803be62b2b4f294b51adb719d7feef3f35619b3aee73e83128200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f827168747470733a2f2f7961686f6f2e636f6d5820a248769f4e4f23a2984548e7ee53cbe00c0596b54206bdac1023d485138a3e0783118200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f1a001e8480840a8200581c86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101581cdeadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1018200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f840b8200581c86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101581cdeadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1011a001e8480840c8200581c86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1018200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f1a001e8480850d8200581c86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101581cdeadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d1018200581c7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f1a001e8480', + txBody: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '5a70b318d72eb106568e6e9413e7d72573ef018307d189a92a6aea50ef779ee5', + 32, + ), + index: toUint(1), + }, + ], + hasTag: false, + } as CddlSet, + outputs: [ { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, + format: TxOutputFormat.ARRAY_LEGACY, + address: toFixLenBuffer( + '006adb0971e4054c0da7ec23a0c276521f1cd8cecf1b15ad4822119cd186dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 57, ), - index: toUint(2), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(1000000), + }, + datumHash: undefined, + }, + { + format: TxOutputFormat.ARRAY_LEGACY, + address: toFixLenBuffer( + '006adb0971e4054c0da7ec23a0c276521f1cd8cecf1b15ad4822119cd186dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 57, + ), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(5969639661), + }, + datumHash: undefined, }, ], + fee: toUint(204573), + ttl: undefined, + certificates: { + items: [ + { + type: CertificateType.VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + }, + dRep: { + type: DRepType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + }, + { + type: CertificateType.DREP_REGISTRATION, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + deposit: toUint(2000000), + anchor: { + url: toMaxLenString('https://google.com', 64), + dataHash: toFixLenBuffer( + '339ec66289039dbf9fa803be62b2b4f294b51adb719d7feef3f35619b3aee73e', + 32, + ), + }, + }, + { + type: CertificateType.DREP_UPDATE, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + anchor: { + url: toMaxLenString('https://yahoo.com', 64), + dataHash: toFixLenBuffer( + 'a248769f4e4f23a2984548e7ee53cbe00c0596b54206bdac1023d485138a3e07', + 32, + ), + }, + }, + { + type: CertificateType.DREP_DEREGISTRATION, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + deposit: toUint(2000000), + }, + { + type: CertificateType.STAKE_AND_VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + }, + poolKeyHash: toFixLenBuffer( + 'deadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + dRep: { + type: DRepType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + }, + { + type: CertificateType.STAKE_REGISTRATION_AND_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + }, + poolKeyHash: toFixLenBuffer( + 'deadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + deposit: toUint(2000000), + }, + { + type: CertificateType.STAKE_REGISTRATION_WITH_VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + }, + dRep: { + type: DRepType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + deposit: toUint(2000000), + }, + { + type: CertificateType.STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '86dcecee2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + }, + poolKeyHash: toFixLenBuffer( + 'deadbeef2ca5017ed3a8bef8386f4ea19411872975818b6c8e40d101', + 28, + ), + dRep: { + type: DRepType.KEY_HASH, + keyHash: toFixLenBuffer( + '7293814591e7543561361bafe399d9b5012d537c46cf70fa5e4faa9f', + 28, + ), + }, + deposit: toUint(2000000), + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: undefined, + collateralInputs: undefined, + requiredSigners: undefined, + networkId: undefined, + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, ] @@ -567,6 +1058,7 @@ export const ValidTransactionBodyTestCases: ValidTransactionBodyTestCase[] = [ type TransformTransactionBodyTestCase = { testName: string cbor: string + validationErrors: ValidationError[] auxiliaryData?: unknown txBody: TransactionBody } @@ -576,17 +1068,21 @@ export const TransformTransactionTestCases: TransformTransactionBodyTestCase[] = { testName: 'Simple tx body with canonical auxiliary data', cbor: 'a50081825820bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda71a0023583c021a00029b75031a01a3bd8f075820fb7099a47afd6efb4f9cccf9d0f8745331a19eb8b3f50548ffadae9de8551743', + validationErrors: [], auxiliaryData: CanonicalAuxiliaryData.data, txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -615,22 +1111,30 @@ export const TransformTransactionTestCases: TransformTransactionBodyTestCase[] = collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { testName: 'Simple tx body with non canonical auxiliary data', cbor: 'a50081825820bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda71a0023583c021a00029b75031a01a3bd8f075820fb7099a47afd6efb4f9cccf9d0f8745331a19eb8b3f50548ffadae9de8551743', + validationErrors: [], auxiliaryData: NonCanonicalAuxiliaryData.data, txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -659,22 +1163,30 @@ export const TransformTransactionTestCases: TransformTransactionBodyTestCase[] = collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, { testName: 'Simple tx body with auxiliary data hash but no auxiliary data', cbor: 'a50081825820bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda71a0023583c021a00029b75031a01a3bd8f075820fb7099a47afd6efb4f9cccf9d0f8745331a19eb8b3f50548ffadae9de8551743', + validationErrors: [], auxiliaryData: null, txBody: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'bc8bf52ea894fb8e442fe3eea628be87d0c9a37baef185b70eb00a5c8a849d3b', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -706,6 +1218,131 @@ export const TransformTransactionTestCases: TransformTransactionBodyTestCase[] = collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, + }, + }, + { + testName: + 'Tx body with tag 258 in collateral inputs and required signers, but not inputs', + cbor: 'a800818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018283583d105e2f080eb93bad86d401545e0ce5f2221096d6477e11e6643922fa8d2ed495234dc0d667c1316ff84e572310e265edb31330448b36b7179e28dd419e1a006ca7935820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce18883583930167f6dbf610ae030f043adb1f3af78754ed9595ad4ac1f7ed9ff6466760fb6955d1217b1f1f208df6d45ab23c9e17b0c984a2d3a22bbbfb8821a0001e91fa1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a146546f6b656e3101582000ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce102182a030a0b5820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce1880dd90102818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7000ed9010282581cfea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a581ceea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a0f01', + validationErrors: [ + { + fixable: true, + reason: ValidationErrorReason.TX_INCONSISTENT_SET_TAGS, + position: 'transaction_body', + }, + ], + txBody: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: true, + } as CddlSet, + outputs: [ + { + format: TxOutputFormat.ARRAY_LEGACY, + address: fromBech32( + 'addr_test1zp0z7zqwhya6mpk5q929ur897g3pp9kkgalpreny8y304rfw6j2jxnwq6enuzvt0lp89wgcsufj7mvcnxpzgkd4hz70z3h2pnc8lhq8r', + ), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(7120787), + }, + datumHash: { + type: DatumType.HASH, + hash: toFixLenBuffer( + 'ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce188', + 32, + ), + }, + }, + { + format: TxOutputFormat.ARRAY_LEGACY, + address: fromBech32( + 'addr_test1xqt87mdlvy9wqv8sgwkmrua00p65ak2ett22c8m7m8lkgenkp7mf2hgjz7clrusgmak5t2ere8shkrycfgkn5g4mh7uqvcq039', + ), + amount: { + type: AmountType.WITH_MULTIASSET, + coin: toUint(125215), + multiasset: [ + { + policyId: toFixLenBuffer( + 'd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2', + 28, + ), + tokens: [ + { + assetName: Buffer.from('Token1') as MaxLenBuffer<32>, + amount: toUint(1), + }, + ], + }, + ], + }, + datumHash: { + type: DatumType.HASH, + hash: toFixLenBuffer( + '00ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce1', + 32, + ), + }, + }, + ], + fee: toUint(42), + ttl: toUint(10), + certificates: undefined, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: toFixLenBuffer( + 'ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce188', + 32, + ), + collateralInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: true, + } as CddlNonEmptySet, + requiredSigners: { + items: [ + toFixLenBuffer( + 'fea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, + ), + toFixLenBuffer( + 'eea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, + ), + ], + hasTag: true, + } as CddlNonEmptySet, + networkId: toUint(1), + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, }, ] @@ -722,15 +1359,18 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ cbor: '83a30081825820ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de000181825839000743d16cfe3c4fcc0c11c2403bbc10dbc7ecdd4477e053481a368e7a06e2ae44dff6770dc0f4ada3cf4cf2605008e27aecdb332ad349fda700021a0001e240a10081825820abd0f26723a5de57c10eb483b14c0aec1c365d911d46ab38684c2b9b2fa4a4915840f2b04185587ed5af88cac6778b0a8392f1cd4d51e6c3722d96db62cae9d716f2d71a22aac6bde7ec097e1357b9e2ffa70eb9ab5d757d24180c843593fb302f09f6', tx: { body: { - inputs: [ - { - transactionId: toFixLenBuffer( - 'ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + 'ba638246bd9be05aa46e865320c354efea75cf5796e88b763faaa30c9fbb78de', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -759,6 +1399,10 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, witnessSet: new Map([ [ @@ -785,22 +1429,25 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ cbor: '83a3008282582014461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d0082582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d00018182583931550d0f8b591480fe57e832ab99d6c2fc387c8f417ab09399cb74b5e1f8ecfa2654cfe1dd931439db45e43f5d1a73129dcb7e4acc736c766a000200a20081825820abd0f26723a5de57c10eb483b14c0aec1c365d911d46ab38684c2b9b2fa4a4915840f2b04185587ed5af88cac6778b0a8392f1cd4d51e6c3722d96db62cae9d716f2d71a22aac6bde7ec097e1357b9e2ffa70eb9ab5d757d24180c843593fb302f0901828201828200581cc4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e03868200581ce01b7ece78d656ad5848362ded335254167378c1723cd94df336a6308200581c7ed7fe51d02aede226df3912f4f347bf9598138091801119a3dc7a1f82a11904d2a163666f6f6362617280', tx: { body: { - inputs: [ - { - transactionId: toFixLenBuffer( - '14461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '14461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -829,6 +1476,10 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, witnessSet: new Map([ [ @@ -905,29 +1556,32 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ cbor: '84a5008382582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d0082582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d0182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d02018182583931550d0f8b591480fe57e832ab99d6c2fc387c8f417ab09399cb74b5e1f8ecfa2654cfe1dd931439db45e43f5d1a73129dcb7e4acc736c766a0002000b58202d1c584b45751ab66d924c4a47f7fe7ea5831e0b485f8d9d290e69eb809c013b0d8182582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d03a50081825820abd0f26723a5de57c10eb483b14c0aec1c365d911d46ab38684c2b9b2fa4a4915840f2b04185587ed5af88cac6778b0a8392f1cd4d51e6c3722d96db62cae9d716f2d71a22aac6bde7ec097e1357b9e2ffa70eb9ab5d757d24180c843593fb302f0901828201828200581cc4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e03868200581ce01b7ece78d656ad5848362ded335254167378c1723cd94df336a6308200581c7ed7fe51d02aede226df3912f4f347bf9598138091801119a3dc7a1f038158425840010000332233322222253353004333573466ebc00c00801801440204c98d4c01ccd5ce2481094e6f7420457175616c000084984880084880048004480048004104814963686f636f6c61746505818400024963686f636f6c617465821a001e84801a3b9aca00f5f6', tx: { body: { - inputs: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(1), - }, - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(2), - }, - ], + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(1), + }, + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(2), + }, + ], + hasTag: false, + } as CddlSet, outputs: [ { format: TxOutputFormat.ARRAY_LEGACY, @@ -953,20 +1607,27 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ '2d1c584b45751ab66d924c4a47f7fe7ea5831e0b485f8d9d290e69eb809c013b', 32, ), - collateralInputs: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(3), - }, - ], + collateralInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(3), + }, + ], + hasTag: false, + } as CddlNonEmptySet, requiredSigners: undefined, networkId: undefined, collateralReturnOutput: undefined, totalCollateral: undefined, referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, }, witnessSet: new Map([ [ @@ -1041,4 +1702,123 @@ export const ValidTransactionTestCases: ValidTransactionTestCase[] = [ auxiliaryData: null, }, }, + { + testName: + 'Tx with tag 258 in collateral inputs and required signers, but not inputs', + cbor: '84a800818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018283583d105e2f080eb93bad86d401545e0ce5f2221096d6477e11e6643922fa8d2ed495234dc0d667c1316ff84e572310e265edb31330448b36b7179e28dd419e1a006ca7935820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce18883583930167f6dbf610ae030f043adb1f3af78754ed9595ad4ac1f7ed9ff6466760fb6955d1217b1f1f208df6d45ab23c9e17b0c984a2d3a22bbbfb8821a0001e91fa1581cd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2a146546f6b656e3101582000ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce102182a030a0b5820ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce1880dd90102818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7000ed9010282581cfea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a581ceea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a0f01a0f5f6', + tx: { + body: { + inputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: false, + } as CddlSet, + outputs: [ + { + format: TxOutputFormat.ARRAY_LEGACY, + address: fromBech32( + 'addr_test1zp0z7zqwhya6mpk5q929ur897g3pp9kkgalpreny8y304rfw6j2jxnwq6enuzvt0lp89wgcsufj7mvcnxpzgkd4hz70z3h2pnc8lhq8r', + ), + amount: { + type: AmountType.WITHOUT_MULTIASSET, + coin: toUint(7120787), + }, + datumHash: { + type: DatumType.HASH, + hash: toFixLenBuffer( + 'ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce188', + 32, + ), + }, + }, + { + format: TxOutputFormat.ARRAY_LEGACY, + address: fromBech32( + 'addr_test1xqt87mdlvy9wqv8sgwkmrua00p65ak2ett22c8m7m8lkgenkp7mf2hgjz7clrusgmak5t2ere8shkrycfgkn5g4mh7uqvcq039', + ), + amount: { + type: AmountType.WITH_MULTIASSET, + coin: toUint(125215), + multiasset: [ + { + policyId: toFixLenBuffer( + 'd7a7c6999786354b6dbee181a2f562a628a75fce126f4da40ce5d9b2', + 28, + ), + tokens: [ + { + assetName: Buffer.from('Token1') as MaxLenBuffer<32>, + amount: toUint(1), + }, + ], + }, + ], + }, + datumHash: { + type: DatumType.HASH, + hash: toFixLenBuffer( + '00ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce1', + 32, + ), + }, + }, + ], + fee: toUint(42), + ttl: toUint(10), + certificates: undefined, + withdrawals: undefined, + update: undefined, + auxiliaryDataHash: undefined, + validityIntervalStart: undefined, + mint: undefined, + scriptDataHash: toFixLenBuffer( + 'ffd4d009f554ba4fd8ed1f1d703244819861a9d34fd4753bcf3ff32f043ce188', + 32, + ), + collateralInputs: { + items: [ + { + transactionId: toFixLenBuffer( + '3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7', + 32, + ), + index: toUint(0), + }, + ], + hasTag: true, + } as CddlNonEmptySet, + requiredSigners: { + items: [ + toFixLenBuffer( + 'fea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, + ), + toFixLenBuffer( + 'eea6646c67fb467f8a5425e9c752e1e262b0420ba4b638f39514049a', + 28, + ), + ], + hasTag: true, + } as CddlNonEmptySet, + networkId: toUint(1), + collateralReturnOutput: undefined, + totalCollateral: undefined, + referenceInputs: undefined, + votingProcedures: undefined, + proposalProcedures: undefined, + treasury: undefined, + donation: undefined, + }, + witnessSet: {}, + scriptValidity: true, + auxiliaryData: null, + }, + }, ] diff --git a/test/integration/transform.test.ts b/test/integration/transform.test.ts index 66e24b5..5437761 100644 --- a/test/integration/transform.test.ts +++ b/test/integration/transform.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai' -import {decodeTxBody, transformTxBody} from '../../src/index' +import {decodeTxBody, transformTxBody, validateTxBody} from '../../src/index' import {TransformTransactionTestCases} from './__fixtures__/transactions' describe('Transform', () => { @@ -19,3 +19,16 @@ describe('Transform', () => { }) } }) + +describe('Validate', () => { + for (const { + testName, + cbor, + validationErrors, + } of TransformTransactionTestCases) { + it(testName, () => { + const errors = validateTxBody(Buffer.from(cbor, 'hex')) + expect(errors).to.deep.equal(validationErrors) + }) + } +}) diff --git a/test/unit/certificates.test.ts b/test/unit/certificates.test.ts index 777703a..2de4850 100644 --- a/test/unit/certificates.test.ts +++ b/test/unit/certificates.test.ts @@ -6,6 +6,10 @@ import { CertificateType, RelayType, CredentialType, + CddlSet, + KeyHash, + CddlNonEmptyOrderedSet, + DRepType, } from '../../src/types' import { ipv4ToBuffer, @@ -18,156 +22,385 @@ import { ValidParseTestCase, } from '../test_utils' -const ValidCertificatesTestCases: ValidParseTestCase[] = [ +const ValidCertificatesTestCases: ValidParseTestCase< + CddlNonEmptyOrderedSet +>[] = [ { - testName: 'One stake registration', + testName: 'One stake registration legacy', cbor: '8182008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - parsed: [ - { - type: CertificateType.STAKE_REGISTRATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - 28, - ), + parsed: { + items: [ + { + type: CertificateType.STAKE_REGISTRATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + { + testName: 'One stake registration Conway', + cbor: '8183078200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d06', + parsed: { + items: [ + { + type: CertificateType.STAKE_REGISTRATION_CONWAY, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, + deposit: toUint(6), }, - }, - ], + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, }, { testName: 'Two stake registrations', cbor: '8282008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d82008200581cc1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', - parsed: [ - { - type: CertificateType.STAKE_REGISTRATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - 28, - ), + parsed: { + items: [ + { + type: CertificateType.STAKE_REGISTRATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, }, - }, - { - type: CertificateType.STAKE_REGISTRATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', - 28, - ), + { + type: CertificateType.STAKE_REGISTRATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', + 28, + ), + }, }, - }, - ], + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, }, { - testName: 'Each certificate type once', - cbor: '8582008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d82018201581cc1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee5583028200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581c001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa3908a03581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8582067c5c0b45db55e8c82752263207b9a92c2d5fa6c671aceed9df451cad3fac7a31a0001e2401a05f5e100d81e82031819581de1d7d8a321633b3d1ab1651eeb258ad898ebcef1d348b54148f18e15da82581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581cf699c6400f85bdca54e44d0cad1f6141ce049a411c0d695fc30c3f7384840019029af650004706260000004700000000111100008301f676616464726573732e76616375756d6c6162732e636f6d8202781e616e6f746865722e616464726573732e76616375756d6c6162732e636f6d840019ffff447f0000fff682782468747470733a2f2f706f6f6c2d6d657461646174612e76616375756d6c6162732e636f6d5820e318d62e3d5cc3cc23ca1123438e439d7aac6c6c423320f670d159726ac9d11f8304581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a81a0001dfbe', - parsed: [ - { - type: CertificateType.STAKE_REGISTRATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', - 28, - ), + testName: 'Stake certificates', + cbor: '8383078200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d0683088201581cc1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee550683028200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581c001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa390', + parsed: { + items: [ + { + type: CertificateType.STAKE_REGISTRATION_CONWAY, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, + deposit: toUint(6), }, - }, - { - type: CertificateType.STAKE_DEREGISTRATION, - stakeCredential: { - type: CredentialType.SCRIPT_HASH, - hash: toFixLenBuffer( - 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', - 28, - ), + { + type: CertificateType.STAKE_DEREGISTRATION_CONWAY, + stakeCredential: { + type: CredentialType.SCRIPT_HASH, + scriptHash: toFixLenBuffer( + 'c1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee55', + 28, + ), + }, + deposit: toUint(6), }, - }, - { - type: CertificateType.STAKE_DELEGATION, - stakeCredential: { - type: CredentialType.KEY_HASH, - hash: toFixLenBuffer( - '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + { + type: CertificateType.STAKE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + 28, + ), + }, + poolKeyHash: toFixLenBuffer( + '001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa390', 28, ), }, - poolKeyHash: toFixLenBuffer( - '001337292eec9b3eefc6802f71cb34c21a7963eb12466d52836aa390', - 28, - ), - }, - { - type: CertificateType.POOL_REGISTRATION, - poolParams: { - operator: toFixLenBuffer( - '4DFBC0559B2E1D6AF62C447F0A0D6290A8B05E075EF08DB38C1B81A8', + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + { + testName: 'Stake pool certificates', + cbor: '828a03581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8582067c5c0b45db55e8c82752263207b9a92c2d5fa6c671aceed9df451cad3fac7a31a0001e2401a05f5e100d81e82031819581de1d7d8a321633b3d1ab1651eeb258ad898ebcef1d348b54148f18e15da82581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d581cf699c6400f85bdca54e44d0cad1f6141ce049a411c0d695fc30c3f7384840019029af650004706260000004700000000111100008301f676616464726573732e76616375756d6c6162732e636f6d8202781e616e6f746865722e616464726573732e76616375756d6c6162732e636f6d840019ffff447f0000fff682782468747470733a2f2f706f6f6c2d6d657461646174612e76616375756d6c6162732e636f6d5820e318d62e3d5cc3cc23ca1123438e439d7aac6c6c423320f670d159726ac9d11f8304581c4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a81a0001dfbe', + parsed: { + items: [ + { + type: CertificateType.POOL_REGISTRATION, + poolParams: { + operator: toFixLenBuffer( + '4DFBC0559B2E1D6AF62C447F0A0D6290A8B05E075EF08DB38C1B81A8', + 28, + ), + vrfKeyHash: toFixLenBuffer( + '67C5C0B45DB55E8C82752263207B9A92C2D5FA6C671ACEED9DF451CAD3FAC7A3', + 32, + ), + pledge: toUint(123456), + cost: toUint(100000000), + margin: [toUint(3), toUint(25)], + rewardAccount: rewardAccount( + 'stake1u8ta3gepvvan6x43v50wkfv2mzvwhnh36dyt2s2g7x8ptks528lzm', + ), + poolOwners: { + items: [ + toFixLenBuffer( + '2C049DFED8BC41EDEFBBC835CA0A739CAC961557950262EF48BCFF1D', + 28, + ), + toFixLenBuffer( + 'F699C6400F85BDCA54E44D0CAD1F6141CE049A411C0D695FC30C3F73', + 28, + ), + ], + hasTag: false, + } as CddlSet, + relays: [ + { + type: RelayType.SINGLE_HOST_ADDRESS, + port: 666 as Port, + ipv4: null, + ipv6: toFixLenBuffer('00470626000000470000000011110000', 16), + }, + { + type: RelayType.SINGLE_HOST_NAME, + port: null, + dnsName: toMaxLenString('address.vacuumlabs.com', 64), + }, + { + type: RelayType.MULTI_HOST_NAME, + dnsName: toMaxLenString('another.address.vacuumlabs.com', 64), + }, + { + type: RelayType.SINGLE_HOST_ADDRESS, + port: 65535 as Port, + ipv4: ipv4ToBuffer('127.0.0.255'), + ipv6: null, + }, + ], + poolMetadata: { + url: toMaxLenString('https://pool-metadata.vacuumlabs.com', 64), + metadataHash: toFixLenBuffer( + 'E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', + 32, + ), + }, + }, + }, + { + type: CertificateType.POOL_RETIREMENT, + poolKeyHash: toFixLenBuffer( + '4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8', 28, ), - vrfKeyHash: toFixLenBuffer( - '67C5C0B45DB55E8C82752263207B9A92C2D5FA6C671ACEED9DF451CAD3FAC7A3', - 32, - ), - pledge: toUint(123456), - cost: toUint(100000000), - margin: [toUint(3), toUint(25)], - rewardAccount: rewardAccount( - 'stake1u8ta3gepvvan6x43v50wkfv2mzvwhnh36dyt2s2g7x8ptks528lzm', - ), - poolOwners: [ - toFixLenBuffer( - '2C049DFED8BC41EDEFBBC835CA0A739CAC961557950262EF48BCFF1D', + epoch: toUint(122814), + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + { + testName: 'DRep certificates', + cbor: '8384108200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8008276616464726573732e76616375756d6c6162732e636f6d5820E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F83118200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce80083128200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce88276616464726573732e76616375756d6c6162732e636f6d5820E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', + parsed: { + items: [ + { + type: CertificateType.DREP_REGISTRATION, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', 28, ), - toFixLenBuffer( - 'F699C6400F85BDCA54E44D0CAD1F6141CE049A411C0D695FC30C3F73', + }, + deposit: toUint(0), + anchor: { + url: toMaxLenString('address.vacuumlabs.com', 64), + dataHash: toFixLenBuffer( + 'E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', + 32, + ), + }, + }, + { + type: CertificateType.DREP_DEREGISTRATION, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', 28, ), - ], - relays: [ - { - type: RelayType.SINGLE_HOST_ADDRESS, - port: 666 as Port, - ipv4: null, - ipv6: toFixLenBuffer('00470626000000470000000011110000', 16), - }, - { - type: RelayType.SINGLE_HOST_NAME, - port: null, - dnsName: toMaxLenString('address.vacuumlabs.com', 64), - }, - { - type: RelayType.MULTI_HOST_NAME, - dnsName: toMaxLenString('another.address.vacuumlabs.com', 64), - }, - { - type: RelayType.SINGLE_HOST_ADDRESS, - port: 65535 as Port, - ipv4: ipv4ToBuffer('127.0.0.255'), - ipv6: null, - }, - ], - poolMetadata: { - url: toMaxLenString('https://pool-metadata.vacuumlabs.com', 64), - metadataHash: toFixLenBuffer( + }, + deposit: toUint(0), + }, + { + type: CertificateType.DREP_UPDATE, + dRepCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + anchor: { + url: toMaxLenString('address.vacuumlabs.com', 64), + dataHash: toFixLenBuffer( 'E318D62E3D5CC3CC23CA1123438E439D7AAC6C6C423320F670D159726AC9D11F', 32, ), }, }, - }, - { - type: CertificateType.POOL_RETIREMENT, - poolKeyHash: toFixLenBuffer( - '4dfbc0559b2e1d6af62c447f0a0d6290a8b05e075ef08db38c1b81a8', - 28, - ), - epoch: toUint(122814), - }, - ], + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + { + testName: 'Vote delegation certificates', + cbor: '8483098200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce88200581cabcdefc7db733c057fed63fa085113dfb570566eb708d548d2f7cce883098201581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce88201581cabcdefc7db733c057fed63fa085113dfb570566eb708d548d2f7cce883098200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8810283098200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce88103', + parsed: { + items: [ + { + type: CertificateType.VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + dRep: { + type: DRepType.KEY_HASH, + keyHash: toFixLenBuffer( + 'abcdefc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + }, + { + type: CertificateType.VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.SCRIPT_HASH, + scriptHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + dRep: { + type: DRepType.SCRIPT_HASH, + scriptHash: toFixLenBuffer( + 'abcdefc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + }, + { + type: CertificateType.VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + dRep: { + type: DRepType.ABSTAIN, + }, + }, + { + type: CertificateType.VOTE_DELEGATION, + stakeCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + dRep: { + type: DRepType.NO_CONFIDENCE, + }, + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + { + testName: 'Constitutional committee certificates', + cbor: '82830e8200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce88200581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8830f8201581c1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8f6', + parsed: { + items: [ + { + type: CertificateType.AUTHORIZE_COMMITTEE_HOT, + coldCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + hotCredential: { + type: CredentialType.KEY_HASH, + keyHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + }, + { + type: CertificateType.RESIGN_COMMITTEE_COLD, + coldCredential: { + type: CredentialType.SCRIPT_HASH, + scriptHash: toFixLenBuffer( + '1033bbc7db733c057fed63fa085113dfb570566eb708d548d2f7cce8', + 28, + ), + }, + anchor: null, + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, + }, + /* TODO + { + testName: 'Combined certificates', + cbor: '80', + parsed: { + items: [ + { + type: CertificateType.STAKE_AND_VOTE_DELEGATION, + }, + { + type: CertificateType.STAKE_REGISTRATION_AND_DELEGATION, + }, + { + type: CertificateType.STAKE_REGISTRATION_WITH_VOTE_DELEGATION, + }, + { + type: CertificateType.STAKE_REGISTRATION_WITH_STAKE_AND_VOTE_DELEGATION, + }, + ], + hasTag: false, + } as CddlNonEmptyOrderedSet, }, +*/ ] const InvalidCertificatesTestCases: InvalidParseTestCase[] = [ @@ -176,6 +409,11 @@ const InvalidCertificatesTestCases: InvalidParseTestCase[] = [ cbor: 'a100f6', errMsg: ParseErrorReason.INVALID_CERTIFICATES, }, + { + testName: 'Items in array not unique', + cbor: '8482008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d82008200581cc1d58a7602c3bd8104cd2a871a2d1cb68f6f6669bd37a7688618ee5582008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d82008200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1e', + errMsg: ParseErrorReason.INVALID_CERTIFICATES, + }, { testName: 'Invalid certificate structure', cbor: '81818200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', @@ -183,7 +421,7 @@ const InvalidCertificatesTestCases: InvalidParseTestCase[] = [ }, { testName: 'Invalid certificate type', - cbor: '8182078200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', + cbor: '8182198200581c2c049dfed8bc41edefbbc835ca0a739cac961557950262ef48bcff1d', errMsg: ParseErrorReason.INVALID_CERTIFICATE_TYPE, }, { diff --git a/test/unit/inputs.test.ts b/test/unit/inputs.test.ts index 2d056fb..51b4506 100644 --- a/test/unit/inputs.test.ts +++ b/test/unit/inputs.test.ts @@ -1,49 +1,55 @@ import {ParseErrorReason} from '../../src/errors' import {parseInputs} from '../../src/txParsers' -import type {TransactionInput} from '../../src/types' +import type {CddlSet, TransactionInput} from '../../src/types' import type {InvalidParseTestCase, ValidParseTestCase} from '../test_utils' import {registerTests, toFixLenBuffer, toUint} from '../test_utils' -const ValidInputsTestCases: ValidParseTestCase[] = [ +const ValidInputsTestCases: ValidParseTestCase>[] = [ { testName: 'One input', cbor: '81825820B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE01', - parsed: [ - { - transactionId: toFixLenBuffer( - 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', - 32, - ), - index: toUint(1), - }, - ], + parsed: { + items: [ + { + transactionId: toFixLenBuffer( + 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', + 32, + ), + index: toUint(1), + }, + ], + hasTag: false, + } as CddlSet, }, { testName: 'Three inputs', cbor: '8382582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d00825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee00825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee1bffffffffffffffff', - parsed: [ - { - transactionId: toFixLenBuffer( - '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', - 32, - ), - index: toUint(0), - }, - { - transactionId: toFixLenBuffer( - 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', - 32, - ), - index: toUint(0), - }, - { - transactionId: toFixLenBuffer( - 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', - 32, - ), - index: toUint('18446744073709551615'), - }, - ], + parsed: { + items: [ + { + transactionId: toFixLenBuffer( + '94461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d', + 32, + ), + index: toUint(0), + }, + { + transactionId: toFixLenBuffer( + 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', + 32, + ), + index: toUint(0), + }, + { + transactionId: toFixLenBuffer( + 'B64AE44E1195B04663AB863B62337E626C65B0C9855A9FBB9EF4458F81A6F5EE', + 32, + ), + index: toUint('18446744073709551615'), + }, + ], + hasTag: false, + } as CddlSet, }, ] @@ -53,6 +59,11 @@ const InvalidInputsTestCases: InvalidParseTestCase[] = [ cbor: 'a100f6', errMsg: ParseErrorReason.INVALID_TX_INPUTS, }, + { + testName: 'Items in array not unique', + cbor: '84825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee1bffffffffffffffff82582094461e17271b4a108f679eb7b6947aea29573296a5edca635d583fb40785e05d00825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee00825820b64ae44e1195b04663ab863b62337e626c65b0c9855a9fbb9ef4458f81a6f5ee1bffffffffffffffff', + errMsg: ParseErrorReason.INVALID_TX_INPUTS, + }, { testName: 'Invalid input structure', cbor: '8183187b0102', From 616b05aad15fbbb44a33db270e0a7313ecc2ac47 Mon Sep 17 00:00:00 2001 From: Jan Mazak Date: Thu, 21 Dec 2023 23:44:58 +0100 Subject: [PATCH 3/3] add changelog --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e136fe8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [3.0.0] - [December 21th 2023] + +### Added + +- support for Conway transaction body elements +- optional 258 tags for sets + +### Changed + +- credential types are generalized + +### Fixed + +- some missing validations were added +- treatment of tag 30 (no longer removed when decoding CBOR) + + +## [2.0.0] - [December 9th 2022] + +### Added + +### Changed + +API related to raw transactions --- cardano-cli had moved away from its raw tx file format towards a CDDL-compliant one. + +### Fixed + + +## [1.0.0] - [August 12th 2022] + +First release supporting Babbage transaction elements. + +### Added + +### Changed + +### Fixed +