diff --git a/src/Lib/AssocMap.ts b/src/Lib/AssocMap.ts index f4acf2c..fc688e8 100644 --- a/src/Lib/AssocMap.ts +++ b/src/Lib/AssocMap.ts @@ -1,5 +1,5 @@ /** - * TypeScript implementation for {@link *https://github.com/input-output-hk/plutus/blob/1.16.0.0/plutus-tx/src/PlutusTx/AssocMap.hs } + * TypeScript implementation for {@link https://github.com/input-output-hk/plutus/blob/1.16.0.0/plutus-tx/src/PlutusTx/AssocMap.hs } * * @example * ```ts diff --git a/src/Lib/PlutusData.ts b/src/Lib/PlutusData.ts index 5e4d707..d6b4083 100644 --- a/src/Lib/PlutusData.ts +++ b/src/Lib/PlutusData.ts @@ -19,7 +19,7 @@ import * as LbHex from "./Hex.js"; /** * {@link PlutusData} is a generic "data" type. * - * @see {@link https://github.com/input-output-hk/plutus/blob/1.16.0.0/plutus-core/plutus-core/src/PlutusCore/Data.hs#L33-L48 | `Data`} + * @see {@link https://github.com/input-output-hk/plutus/blob/1.16.0.0/plutus-core/plutus-core/src/PlutusCore/Data.hs#L33-L48 } */ export type PlutusData = | { name: "Constr"; fields: [Integer, List] } diff --git a/src/Lib/V1.ts b/src/Lib/V1.ts index 28477b6..9d85b13 100644 --- a/src/Lib/V1.ts +++ b/src/Lib/V1.ts @@ -19,4 +19,6 @@ export * from "./V1/Scripts.js"; export * from "./V1/Time.js"; export * from "./V1/Tx.js"; export * from "./V1/Value.js"; +export * from "./V1/DCert.js"; +export * from "./V1/Contexts.js"; export * from "./Prelude/Instances.js"; diff --git a/src/Lib/V1/Contexts.ts b/src/Lib/V1/Contexts.ts new file mode 100644 index 0000000..f318775 --- /dev/null +++ b/src/Lib/V1/Contexts.ts @@ -0,0 +1,381 @@ +/** + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Contexts.hs} + */ + +import type { Eq, Integer, Json } from "prelude"; +import * as Prelude from "prelude"; +import * as PreludeInstances from "../Prelude/Instances.js"; + +import type { TxId, TxOut, TxOutRef } from "./Tx.js"; +import * as LbTx from "./Tx.js"; + +import type { Value } from "./Value.js"; +import * as LbValue from "./Value.js"; + +import type { StakingCredential } from "./Credential.js"; +import * as LbCredential from "./Credential.js"; + +import { IsPlutusDataError } from "../PlutusData.js"; +import type { IsPlutusData } from "../PlutusData.js"; + +import type { DCert } from "./DCert.js"; +import * as LbDCert from "./DCert.js"; + +import type { POSIXTimeRange } from "./Time.js"; +import * as LbTime from "./Time.js"; + +import type { Datum, DatumHash } from "./Scripts.js"; +import * as LbScripts from "./Scripts.js"; + +import * as LbCrypto from "./Crypto.js"; +import type { PubKeyHash } from "./Crypto.js"; + +/** + * An input of a pending transaction. + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Contexts.hs#L66-L70} + */ +export type TxInInfo = { + txInInfoOutRef: TxOutRef; + txInInfoResolved: TxOut; +}; + +/** + * {@link Eq} instance for {@link TxInInfo} + */ +export const eqTxInInfo: Eq = { + eq: (l, r) => { + return LbTx.eqTxOutRef.eq(l.txInInfoOutRef, r.txInInfoOutRef) && + LbTx.eqTxOut.eq(l.txInInfoResolved, r.txInInfoResolved); + }, + neq: (l, r) => { + return LbTx.eqTxOutRef.neq(l.txInInfoOutRef, r.txInInfoOutRef) || + LbTx.eqTxOut.neq(l.txInInfoResolved, r.txInInfoResolved); + }, +}; + +/** + * {@link Json} instance for {@link TxInInfo} + */ +export const jsonTxInInfo: Json = { + toJson: (txInInfo) => { + return { + "output": LbTx.jsonTxOut.toJson(txInInfo.txInInfoResolved), + "reference": LbTx.jsonTxOutRef.toJson(txInInfo.txInInfoOutRef), + }; + }, + fromJson: (value) => { + const txInInfoOutRef = Prelude.caseFieldWithValue( + "reference", + LbTx.jsonTxOutRef.fromJson, + value, + ); + const txInInfoResolved = Prelude.caseFieldWithValue( + "output", + LbTx.jsonTxOut.fromJson, + value, + ); + return { txInInfoOutRef, txInInfoResolved }; + }, +}; + +/** + * {@link IsPlutusData} instance for {@link TxInInfo} + */ +export const isPlutusDataTxInInfo: IsPlutusData = { + toData: (txInInfo) => { + return { + fields: [0n, [ + LbTx.isPlutusDataTxOutRef.toData(txInInfo.txInInfoOutRef), + LbTx.isPlutusDataTxOut.toData(txInInfo.txInInfoResolved), + ]], + name: "Constr", + }; + }, + + fromData: (plutusData) => { + switch (plutusData.name) { + case "Constr": { + if (plutusData.fields[0] === 0n && plutusData.fields[1].length === 2) { + return { + txInInfoOutRef: LbTx.isPlutusDataTxOutRef.fromData( + plutusData.fields[1][0]!, + ), + txInInfoResolved: LbTx.isPlutusDataTxOut.fromData( + plutusData.fields[1][1]!, + ), + }; + } else { + break; + } + } + default: + break; + } + throw new IsPlutusDataError("Unexpected data"); + }, +}; + +/** + * A pending transaction. This is the view as seen by validator scripts, so + * some details are stripped out. + * + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Contexts.hs#L96-L108} + */ +export type TxInfo = { + txInfoInputs: TxInInfo[]; + txInfoOutputs: TxOut[]; + txInfoFee: Value; + txInfoMint: Value; + txInfoDCert: DCert[]; + txInfoWdrl: [StakingCredential, Integer][]; + txInfoValidRange: POSIXTimeRange; + txInfoSignatories: PubKeyHash[]; + txInfoData: [DatumHash, Datum][]; + txInfoId: TxId; +}; + +/** + * {@link Eq} instance for {@link TxInfo} + */ +export const eqTxInfo: Eq = { + eq: (l, r) => { + return Prelude.eqList(eqTxInInfo).eq(l.txInfoInputs, r.txInfoInputs) && + Prelude.eqList(LbTx.eqTxOut).eq(l.txInfoOutputs, r.txInfoOutputs) && + LbValue.eqValue.eq(l.txInfoFee, r.txInfoFee) && + LbValue.eqValue.eq(l.txInfoMint, r.txInfoMint) && + Prelude.eqList(LbDCert.eqDCert).eq(l.txInfoDCert, r.txInfoDCert) && + Prelude.eqList( + Prelude.eqPair(LbCredential.eqStakingCredential, Prelude.eqInteger), + ).eq(l.txInfoWdrl, r.txInfoWdrl) && + LbTime.eqPOSIXTimeRange.eq(l.txInfoValidRange, r.txInfoValidRange) && + Prelude.eqList(LbCrypto.eqPubKeyHash).eq( + l.txInfoSignatories, + r.txInfoSignatories, + ) && + Prelude.eqList(Prelude.eqPair(LbScripts.eqDatumHash, LbScripts.eqDatum)) + .eq(l.txInfoData, r.txInfoData) && + LbTx.eqTxId.eq(l.txInfoId, r.txInfoId); + }, + neq: (l, r) => { + return Prelude.eqList(eqTxInInfo).neq(l.txInfoInputs, r.txInfoInputs) || + Prelude.eqList(LbTx.eqTxOut).neq(l.txInfoOutputs, r.txInfoOutputs) || + LbValue.eqValue.neq(l.txInfoFee, r.txInfoFee) || + LbValue.eqValue.neq(l.txInfoMint, r.txInfoMint) || + Prelude.eqList(LbDCert.eqDCert).neq(l.txInfoDCert, r.txInfoDCert) || + Prelude.eqList( + Prelude.eqPair(LbCredential.eqStakingCredential, Prelude.eqInteger), + ).neq(l.txInfoWdrl, r.txInfoWdrl) || + LbTime.eqPOSIXTimeRange.neq(l.txInfoValidRange, r.txInfoValidRange) || + Prelude.eqList(LbCrypto.eqPubKeyHash).neq( + l.txInfoSignatories, + r.txInfoSignatories, + ) || + Prelude.eqList(Prelude.eqPair(LbScripts.eqDatumHash, LbScripts.eqDatum)) + .neq(l.txInfoData, r.txInfoData) || + LbTx.eqTxId.neq(l.txInfoId, r.txInfoId); + }, +}; + +/** + * {@link Json} instance for {@link TxInfo} + */ +export const jsonTxInfo: Json = { + toJson: (txInfo) => { + return { + inputs: Prelude.jsonList(jsonTxInInfo).toJson(txInfo.txInfoInputs), + outputs: Prelude.jsonList(LbTx.jsonTxOut).toJson(txInfo.txInfoOutputs), + fee: LbValue.jsonValue.toJson(txInfo.txInfoFee), + mint: LbValue.jsonValue.toJson(txInfo.txInfoMint), + d_cert: Prelude.jsonList(LbDCert.jsonDCert).toJson(txInfo.txInfoDCert), + wdrl: Prelude.jsonList( + Prelude.jsonPair( + LbCredential.jsonStakingCredential, + Prelude.jsonInteger, + ), + ).toJson(txInfo.txInfoWdrl), + valid_range: LbTime.jsonPOSIXTimeRange.toJson(txInfo.txInfoValidRange), + signatories: Prelude.jsonList(LbCrypto.jsonPubKeyHash).toJson( + txInfo.txInfoSignatories, + ), + datums: Prelude.jsonList( + Prelude.jsonPair(LbScripts.jsonDatumHash, LbScripts.jsonDatum), + ).toJson(txInfo.txInfoData), + id: LbTx.jsonTxId.toJson(txInfo.txInfoId), + }; + }, + fromJson: (value) => { + const txInfoInputs = Prelude.caseFieldWithValue( + "inputs", + Prelude.jsonList(jsonTxInInfo).fromJson, + value, + ); + const txInfoOutputs = Prelude.caseFieldWithValue( + "outputs", + Prelude.jsonList(LbTx.jsonTxOut).fromJson, + value, + ); + const txInfoFee = Prelude.caseFieldWithValue( + "fee", + LbValue.jsonValue.fromJson, + value, + ); + const txInfoMint = Prelude.caseFieldWithValue( + "mint", + LbValue.jsonValue.fromJson, + value, + ); + const txInfoDCert = Prelude.caseFieldWithValue( + "d_cert", + Prelude.jsonList(LbDCert.jsonDCert).fromJson, + value, + ); + const txInfoWdrl = Prelude.caseFieldWithValue( + "wdrl", + Prelude.jsonList( + Prelude.jsonPair( + LbCredential.jsonStakingCredential, + Prelude.jsonInteger, + ), + ).fromJson, + value, + ); + const txInfoValidRange = Prelude.caseFieldWithValue( + "valid_range", + LbTime.jsonPOSIXTimeRange.fromJson, + value, + ); + const txInfoSignatories = Prelude.caseFieldWithValue( + "signatories", + Prelude.jsonList(LbCrypto.jsonPubKeyHash).fromJson, + value, + ); + const txInfoData = Prelude.caseFieldWithValue( + "datums", + Prelude.jsonList( + Prelude.jsonPair(LbScripts.jsonDatumHash, LbScripts.jsonDatum), + ).fromJson, + value, + ); + const txInfoId = Prelude.caseFieldWithValue( + "id", + LbTx.jsonTxId.fromJson, + value, + ); + + return { + txInfoInputs, + txInfoOutputs, + txInfoFee, + txInfoMint, + txInfoDCert, + txInfoWdrl, + txInfoValidRange, + txInfoSignatories, + txInfoData, + txInfoId, + }; + }, +}; + +/** + * {@link IsPlutusData} instance for {@link TxInfo} + */ +export const isPlutusDataTxInfo: IsPlutusData = { + toData: (txInfo) => { + return { + fields: [0n, [ + PreludeInstances.isPlutusDataList(isPlutusDataTxInInfo).toData( + txInfo.txInfoInputs, + ), + PreludeInstances.isPlutusDataList(LbTx.isPlutusDataTxOut).toData( + txInfo.txInfoOutputs, + ), + LbValue.isPlutusDataValue.toData(txInfo.txInfoFee), + LbValue.isPlutusDataValue.toData(txInfo.txInfoMint), + PreludeInstances.isPlutusDataList(LbDCert.isPlutusDataDCert).toData( + txInfo.txInfoDCert, + ), + PreludeInstances.isPlutusDataList( + PreludeInstances.isPlutusDataPairWithTag( + LbCredential.isPlutusDataStakingCredential, + PreludeInstances.isPlutusDataInteger, + ), + ).toData(txInfo.txInfoWdrl), + LbTime.isPlutusDataPOSIXTimeRange.toData(txInfo.txInfoValidRange), + PreludeInstances.isPlutusDataList(LbCrypto.isPlutusDataPubKeyHash) + .toData(txInfo.txInfoSignatories), + PreludeInstances.isPlutusDataList( + PreludeInstances.isPlutusDataPairWithTag( + LbScripts.isPlutusDataDatumHash, + LbScripts.isPlutusDataDatum, + ), + ).toData(txInfo.txInfoData), + LbTx.isPlutusDataTxId.toData(txInfo.txInfoId), + ]], + name: "Constr", + }; + }, + + fromData: (plutusData) => { + switch (plutusData.name) { + case "Constr": { + if (plutusData.fields[0] === 0n && plutusData.fields[1].length === 10) { + const txInfoInputs = PreludeInstances.isPlutusDataList( + isPlutusDataTxInInfo, + ).fromData(plutusData.fields[1][0]!); + const txInfoOutputs = PreludeInstances.isPlutusDataList( + LbTx.isPlutusDataTxOut, + ).fromData(plutusData.fields[1][1]!); + const txInfoFee = LbValue.isPlutusDataValue.fromData( + plutusData.fields[1][2]!, + ); + const txInfoMint = LbValue.isPlutusDataValue.fromData( + plutusData.fields[1][3]!, + ); + const txInfoDCert = PreludeInstances.isPlutusDataList( + LbDCert.isPlutusDataDCert, + ).fromData(plutusData.fields[1][4]!); + const txInfoWdrl = PreludeInstances.isPlutusDataList( + PreludeInstances.isPlutusDataPairWithTag( + LbCredential.isPlutusDataStakingCredential, + PreludeInstances.isPlutusDataInteger, + ), + ).fromData(plutusData.fields[1][5]!); + const txInfoValidRange = LbTime.isPlutusDataPOSIXTimeRange.fromData( + plutusData.fields[1][6]!, + ); + const txInfoSignatories = PreludeInstances.isPlutusDataList( + LbCrypto.isPlutusDataPubKeyHash, + ).fromData(plutusData.fields[1][7]!); + const txInfoData = PreludeInstances.isPlutusDataList( + PreludeInstances.isPlutusDataPairWithTag( + LbScripts.isPlutusDataDatumHash, + LbScripts.isPlutusDataDatum, + ), + ).fromData(plutusData.fields[1][8]!); + const txInfoId = LbTx.isPlutusDataTxId.fromData( + plutusData.fields[1][9]!, + ); + + return { + txInfoInputs, + txInfoOutputs, + txInfoFee, + txInfoMint, + txInfoDCert, + txInfoWdrl, + txInfoValidRange, + txInfoSignatories, + txInfoData, + txInfoId, + }; + } else { + break; + } + } + default: + break; + } + throw new IsPlutusDataError("Unexpected data"); + }, +}; diff --git a/src/Lib/V1/DCert.ts b/src/Lib/V1/DCert.ts new file mode 100644 index 0000000..791e244 --- /dev/null +++ b/src/Lib/V1/DCert.ts @@ -0,0 +1,355 @@ +/** + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/DCert.hs} + */ + +import * as LbCredential from "./Credential.js"; +import type { StakingCredential } from "./Credential.js"; +import * as LbCrypto from "./Crypto.js"; +import type { PubKeyHash } from "./Crypto.js"; +import type { Eq, Integer, Json } from "prelude"; +import { JsonError } from "prelude"; +import { IsPlutusDataError } from "../PlutusData.js"; +import type { IsPlutusData } from "../PlutusData.js"; +import * as Prelude from "prelude"; +import * as PJson from "prelude/Json.js"; +import * as PreludeInstances from "../Prelude/Instances.js"; + +/** + * {@link DCert} a representation of the ledger DCert. + * + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/DCert.hs#L22-L46} + */ +export type DCert = + | { name: "DCertDelegRegKey"; fields: StakingCredential } + | { name: "DCertDelegDeRegKey"; fields: StakingCredential } + | { name: "DCertDelegDelegate"; fields: [StakingCredential, PubKeyHash] } + | { name: "DCertPoolRegister"; fields: [PubKeyHash, PubKeyHash] } + | { name: "DCertPoolRetire"; fields: [PubKeyHash, Integer] } + | { name: "DCertGenesis" } + | { name: "DCertMir" }; + +/** + * {@link Eq} instance for {@link DCert} + */ +export const eqDCert: Eq = { + eq: (l, r) => { + if (l.name === "DCertDelegRegKey" && r.name === "DCertDelegRegKey") { + return LbCredential.eqStakingCredential.eq(l.fields, r.fields); + } else if ( + l.name === "DCertDelegDeRegKey" && r.name === "DCertDelegDeRegKey" + ) { + return LbCredential.eqStakingCredential.eq(l.fields, r.fields); + } else if ( + l.name === "DCertDelegDelegate" && r.name === "DCertDelegDelegate" + ) { + return LbCredential.eqStakingCredential.eq(l.fields[0], r.fields[0]) && + LbCrypto.eqPubKeyHash.eq(l.fields[1], r.fields[1]); + } else if ( + l.name === "DCertPoolRegister" && r.name === "DCertPoolRegister" + ) { + return LbCrypto.eqPubKeyHash.eq(l.fields[0], r.fields[0]) && + LbCrypto.eqPubKeyHash.eq(l.fields[1], r.fields[1]); + } else if (l.name === "DCertPoolRetire" && r.name === "DCertPoolRetire") { + return LbCrypto.eqPubKeyHash.eq(l.fields[0], r.fields[0]) && + Prelude.eqInteger.eq(l.fields[1], r.fields[1]); + } else if (l.name === "DCertGenesis" && r.name === "DCertGenesis") { + return true; + } else if (l.name === "DCertMir" && r.name === "DCertMir") { + return true; + } else { + return false; + } + }, + neq: (l, r) => { + if (l.name === "DCertDelegRegKey" && r.name === "DCertDelegRegKey") { + return LbCredential.eqStakingCredential.neq(l.fields, r.fields); + } else if ( + l.name === "DCertDelegDeRegKey" && r.name === "DCertDelegDeRegKey" + ) { + return LbCredential.eqStakingCredential.neq(l.fields, r.fields); + } else if ( + l.name === "DCertDelegDelegate" && r.name === "DCertDelegDelegate" + ) { + return LbCredential.eqStakingCredential.neq(l.fields[0], r.fields[0]) || + LbCrypto.eqPubKeyHash.neq(l.fields[1], r.fields[1]); + } else if ( + l.name === "DCertPoolRegister" && r.name === "DCertPoolRegister" + ) { + return LbCrypto.eqPubKeyHash.neq(l.fields[0], r.fields[0]) || + LbCrypto.eqPubKeyHash.neq(l.fields[1], r.fields[1]); + } else if (l.name === "DCertPoolRetire" && r.name === "DCertPoolRetire") { + return LbCrypto.eqPubKeyHash.neq(l.fields[0], r.fields[0]) || + Prelude.eqInteger.neq(l.fields[1], r.fields[1]); + } else if (l.name === "DCertGenesis" && r.name === "DCertGenesis") { + return false; + } else if (l.name === "DCertMir" && r.name === "DCertMir") { + return false; + } else { + return true; + } + }, +}; + +/** + * {@link Json} instance for {@link DCert} + */ +export const jsonDCert: Json = { + toJson: (dcert) => { + if (dcert.name === `DCertDelegRegKey`) { + return PJson.jsonConstructor(dcert.name, [ + LbCredential.jsonStakingCredential.toJson(dcert.fields), + ]); + } else if (dcert.name === `DCertDelegDeRegKey`) { + return PJson.jsonConstructor(dcert.name, [ + LbCredential.jsonStakingCredential.toJson(dcert.fields), + ]); + } else if (dcert.name === `DCertDelegDelegate`) { + return PJson.jsonConstructor(dcert.name, [ + LbCredential.jsonStakingCredential.toJson(dcert.fields[0]), + LbCrypto.jsonPubKeyHash.toJson(dcert.fields[1]), + ]); + } else if (dcert.name === `DCertPoolRegister`) { + return PJson.jsonConstructor(dcert.name, [ + LbCrypto.jsonPubKeyHash.toJson(dcert.fields[0]), + LbCrypto.jsonPubKeyHash.toJson(dcert.fields[1]), + ]); + } else if (dcert.name === `DCertPoolRetire`) { + return PJson.jsonConstructor(dcert.name, [ + LbCrypto.jsonPubKeyHash.toJson(dcert.fields[0]), + Prelude.jsonInteger.toJson(dcert.fields[1]), + ]); + } else if (dcert.name === `DCertGenesis`) { + return PJson.jsonConstructor(dcert.name, []); + } else { + // else if (dcert.name === `DCertMir`) { + return PJson.jsonConstructor(dcert.name, []); + } + }, + fromJson: (value) => { + return PJson.caseJsonConstructor("DCert", { + "DCertDelegRegKey": (ctorFields) => { + if (ctorFields.length === 1) { + return { + fields: LbCredential.jsonStakingCredential.fromJson(ctorFields[0]!), + name: "DCertDelegRegKey", + }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + "DCertDelegDeRegKey": (ctorFields) => { + if (ctorFields.length === 1) { + return { + fields: LbCredential.jsonStakingCredential.fromJson(ctorFields[0]!), + name: "DCertDelegDeRegKey", + }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + "DCertDelegDelegate": (ctorFields) => { + if (ctorFields.length === 2) { + return { + fields: [ + LbCredential.jsonStakingCredential.fromJson(ctorFields[0]!), + LbCrypto.jsonPubKeyHash.fromJson(ctorFields[1]!), + ], + name: "DCertDelegDelegate", + }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + + "DCertPoolRegister": (ctorFields) => { + if (ctorFields.length === 2) { + return { + fields: [ + LbCrypto.jsonPubKeyHash.fromJson(ctorFields[0]!), + LbCrypto.jsonPubKeyHash.fromJson(ctorFields[1]!), + ], + name: "DCertPoolRegister", + }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + + "DCertPoolRetire": (ctorFields) => { + if (ctorFields.length === 2) { + return { + fields: [ + LbCrypto.jsonPubKeyHash.fromJson(ctorFields[0]!), + Prelude.jsonInteger.fromJson(ctorFields[1]!), + ], + name: "DCertPoolRetire", + }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + "DCertGenesis": (ctorFields) => { + if (ctorFields.length === 0) { + return { name: "DCertGenesis" }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + "DCertMir": (ctorFields) => { + if (ctorFields.length === 0) { + return { name: "DCertMir" }; + } else { + throw new JsonError( + "Expected JSON Array with 1 fields but got" + + PJson.stringify(value), + ); + } + }, + }, value); + }, +}; + +/** + * {@link IsPlutusData} instance for {@link DCert} + */ +export const isPlutusDataDCert: IsPlutusData = { + toData: (dcert) => { + if (dcert.name === `DCertDelegRegKey`) { + return { + fields: [0n, [ + LbCredential.isPlutusDataStakingCredential.toData(dcert.fields), + ]], + name: "Constr", + }; + } else if (dcert.name === `DCertDelegDeRegKey`) { + return { + fields: [1n, [ + LbCredential.isPlutusDataStakingCredential.toData(dcert.fields), + ]], + name: "Constr", + }; + } else if (dcert.name === `DCertDelegDelegate`) { + return { + fields: [2n, [ + LbCredential.isPlutusDataStakingCredential.toData(dcert.fields[0]), + LbCrypto.isPlutusDataPubKeyHash.toData(dcert.fields[1]), + ]], + name: "Constr", + }; + } else if (dcert.name === `DCertPoolRegister`) { + return { + fields: [3n, [ + LbCrypto.isPlutusDataPubKeyHash.toData(dcert.fields[0]), + LbCrypto.isPlutusDataPubKeyHash.toData(dcert.fields[1]), + ]], + name: "Constr", + }; + } else if (dcert.name === `DCertPoolRetire`) { + return { + fields: [4n, [ + LbCrypto.isPlutusDataPubKeyHash.toData(dcert.fields[0]), + PreludeInstances.isPlutusDataInteger.toData(dcert.fields[1]), + ]], + name: "Constr", + }; + } else if (dcert.name === `DCertGenesis`) { + return { fields: [5n, []], name: "Constr" }; + } else { + // else if (dcert.name === `DCertMir`) { + return { fields: [6n, []], name: "Constr" }; + } + }, + + fromData: (plutusData) => { + switch (plutusData.name) { + case "Constr": + if (plutusData.fields[0] === 0n && plutusData.fields[1].length === 1) { + return { + fields: LbCredential.isPlutusDataStakingCredential.fromData( + plutusData.fields[1][0]!, + ), + name: "DCertDelegRegKey", + }; + } else if ( + plutusData.fields[0] === 1n && plutusData.fields[1].length === 1 + ) { + return { + fields: LbCredential.isPlutusDataStakingCredential.fromData( + plutusData.fields[1][0]!, + ), + name: "DCertDelegDeRegKey", + }; + } else if ( + plutusData.fields[0] === 2n && plutusData.fields[1].length === 2 + ) { + return { + fields: [ + LbCredential.isPlutusDataStakingCredential.fromData( + plutusData.fields[1][0]!, + ), + LbCrypto.isPlutusDataPubKeyHash.fromData( + plutusData.fields[1][1]!, + ), + ], + name: "DCertDelegDelegate", + }; + } else if ( + plutusData.fields[0] === 3n && plutusData.fields[1].length === 2 + ) { + return { + fields: [ + LbCrypto.isPlutusDataPubKeyHash.fromData( + plutusData.fields[1][0]!, + ), + LbCrypto.isPlutusDataPubKeyHash.fromData( + plutusData.fields[1][1]!, + ), + ], + name: "DCertPoolRegister", + }; + } else if ( + plutusData.fields[0] === 4n && plutusData.fields[1].length === 2 + ) { + return { + fields: [ + LbCrypto.isPlutusDataPubKeyHash.fromData( + plutusData.fields[1][0]!, + ), + PreludeInstances.isPlutusDataInteger.fromData( + plutusData.fields[1][1]!, + ), + ], + name: "DCertPoolRetire", + }; + } else if ( + plutusData.fields[0] === 5n && plutusData.fields[1].length === 0 + ) { + return { name: "DCertGenesis" }; + } else if ( + plutusData.fields[0] === 6n && plutusData.fields[1].length === 0 + ) { + return { name: "DCertMir" }; + } + break; + } + throw new IsPlutusDataError("Expected Constr but got " + plutusData); + }, +}; diff --git a/src/Lib/V1/Tx.ts b/src/Lib/V1/Tx.ts index 4b0e062..8d2857e 100644 --- a/src/Lib/V1/Tx.ts +++ b/src/Lib/V1/Tx.ts @@ -10,6 +10,14 @@ import * as Prelude from "prelude"; import { JsonError } from "prelude"; import * as LbBytes from "./Bytes.js"; +import type { Value } from "./Value.js"; +import type { DatumHash } from "./Scripts.js"; +import type { Address } from "./Address.js"; + +import * as LbValue from "./Value.js"; +import * as LbScripts from "./Scripts.js"; +import * as LbAddress from "./Address.js"; + /** * {@link TxId} is a transaction id i.e., the hash of a transaction. 32 bytes. * @@ -157,3 +165,110 @@ export const isPlutusDataTxOutRef: IsPlutusData = { throw new IsPlutusDataError("Invalid data"); }, }; + +/** + * {@link TxOut} a transaction output consisting of a target address, a value, + * and optionally a datum hash. + * + * @see {@link https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Tx.hs#L102-L110} + */ +export type TxOut = { + txOutAddress: Address; + txOutValue: Value; + txOutDatumHash: Maybe; +}; + +/** + * {@link Eq} instance for {@link TxOut} + */ +export const eqTxOut: Eq = { + eq: (l, r) => { + return LbAddress.eqAddress.eq(l.txOutAddress, r.txOutAddress) && + LbValue.eqValue.eq(l.txOutValue, r.txOutValue) && + Prelude.eqMaybe(LbScripts.eqDatumHash).eq( + l.txOutDatumHash, + r.txOutDatumHash, + ); + }, + neq: (l, r) => { + return LbAddress.eqAddress.neq(l.txOutAddress, r.txOutAddress) || + LbValue.eqValue.neq(l.txOutValue, r.txOutValue) || + Prelude.eqMaybe(LbScripts.eqDatumHash).neq( + l.txOutDatumHash, + r.txOutDatumHash, + ); + }, +}; + +/** + * {@link Json} instance for {@link TxOut} + */ +export const jsonTxOut: Json = { + toJson: (txOut) => { + return { + "address": LbAddress.jsonAddress.toJson(txOut.txOutAddress), + "datum_hash": Prelude.jsonMaybe(LbScripts.jsonDatumHash).toJson( + txOut.txOutDatumHash, + ), + "value": LbValue.jsonValue.toJson(txOut.txOutValue), + }; + }, + fromJson: (value) => { + const txOutAddress = Prelude.caseFieldWithValue( + "address", + LbAddress.jsonAddress.fromJson, + value, + ); + const txOutValue = Prelude.caseFieldWithValue( + "value", + LbValue.jsonValue.fromJson, + value, + ); + + const txOutDatumHash = Prelude.caseFieldWithValue( + "datum_hash", + Prelude.jsonMaybe(LbScripts.jsonDatumHash).fromJson, + value, + ); + return { txOutAddress, txOutDatumHash, txOutValue }; + }, +}; + +/** + * {@link IsPlutusData} instance for {@link TxOut} + */ +export const isPlutusDataTxOut: IsPlutusData = { + toData: (txOut) => { + return { + fields: [0n, [ + LbAddress.isPlutusDataAddress.toData(txOut.txOutAddress), + LbValue.isPlutusDataValue.toData(txOut.txOutValue), + PreludeInstances.isPlutusDataMaybe(LbScripts.isPlutusDataDatumHash) + .toData(txOut.txOutDatumHash), + ]], + name: "Constr", + }; + }, + fromData: (plutusData) => { + switch (plutusData.name) { + case "Constr": + if (plutusData.fields[0] === 0n && plutusData.fields[1].length === 3) { + return { + txOutAddress: LbAddress.isPlutusDataAddress.fromData( + plutusData.fields[1][0]!, + ), + txOutDatumHash: PreludeInstances.isPlutusDataMaybe( + LbScripts.isPlutusDataDatumHash, + ).fromData(plutusData.fields[1][2]!), + txOutValue: LbValue.isPlutusDataValue.fromData( + plutusData.fields[1][1]!, + ), + }; + } + break; + default: + break; + } + throw new IsPlutusDataError("Invalid data"); + }, +}; diff --git a/src/Tests/BoolInstances-test.ts b/src/Tests/Prelude/BoolInstances-test.ts similarity index 92% rename from src/Tests/BoolInstances-test.ts rename to src/Tests/Prelude/BoolInstances-test.ts index ed7310a..b8ea47a 100644 --- a/src/Tests/BoolInstances-test.ts +++ b/src/Tests/Prelude/BoolInstances-test.ts @@ -1,10 +1,10 @@ // // Tests for the instances for `Bool` import * as Prelude from "prelude"; -import * as PreludeInstances from "../Lib/Prelude/Instances.js"; +import * as PreludeInstances from "../../Lib/Prelude/Instances.js"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; describe("Bool tests", () => { diff --git a/src/Tests/IntegerInstances-test.ts b/src/Tests/Prelude/IntegerInstances-test.ts similarity index 93% rename from src/Tests/IntegerInstances-test.ts rename to src/Tests/Prelude/IntegerInstances-test.ts index e7f41f6..0aca364 100644 --- a/src/Tests/IntegerInstances-test.ts +++ b/src/Tests/Prelude/IntegerInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `Integer` import * as Prelude from "prelude"; -import * as PreludeInstances from "../Lib/Prelude/Instances.js"; +import * as PreludeInstances from "../../Lib/Prelude/Instances.js"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; describe("Integer tests", () => { diff --git a/src/Tests/MaybeInstances-test.ts b/src/Tests/Prelude/MaybeInstances-test.ts similarity index 96% rename from src/Tests/MaybeInstances-test.ts rename to src/Tests/Prelude/MaybeInstances-test.ts index 512d403..68c5915 100644 --- a/src/Tests/MaybeInstances-test.ts +++ b/src/Tests/Prelude/MaybeInstances-test.ts @@ -1,10 +1,10 @@ // // Tests for the instances for `Maybe` import * as Prelude from "prelude"; -import * as PreludeInstances from "../Lib/Prelude/Instances.js"; +import * as PreludeInstances from "../../Lib/Prelude/Instances.js"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; export function fcMaybe( diff --git a/src/Tests/AddressInstances-test.ts b/src/Tests/V1/AddressInstances-test.ts similarity index 98% rename from src/Tests/AddressInstances-test.ts rename to src/Tests/V1/AddressInstances-test.ts index f60038c..7673159 100644 --- a/src/Tests/AddressInstances-test.ts +++ b/src/Tests/V1/AddressInstances-test.ts @@ -1,15 +1,15 @@ // Tests for the instances for `Address` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; import * as TestCredential from "./CredentialInstances-test.js"; import * as TestStakingCredential from "./StakingCredentialInstances-test.js"; -import * as TestMaybe from "./MaybeInstances-test.js"; +import * as TestMaybe from "../Prelude/MaybeInstances-test.js"; const pubKeyHash1 = Prelude.fromJust( V1.pubKeyHashFromBytes( diff --git a/src/Tests/CredentialInstances-test.ts b/src/Tests/V1/CredentialInstances-test.ts similarity index 98% rename from src/Tests/CredentialInstances-test.ts rename to src/Tests/V1/CredentialInstances-test.ts index 8d5467e..22f4aa7 100644 --- a/src/Tests/CredentialInstances-test.ts +++ b/src/Tests/V1/CredentialInstances-test.ts @@ -1,10 +1,10 @@ // // Tests for the instances for `Credential` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; import * as TestPubKeyHash from "./PubKeyHashInstances-test.js"; diff --git a/src/Tests/V1/DCertInstances-test.ts b/src/Tests/V1/DCertInstances-test.ts new file mode 100644 index 0000000..f1aedc2 --- /dev/null +++ b/src/Tests/V1/DCertInstances-test.ts @@ -0,0 +1,113 @@ +// Tests for the instances for `DCert` +import * as V1 from "../../Lib/V1.js"; +import { describe, it } from "node:test"; +import * as TestUtils from "../TestUtils.js"; +import fc from "fast-check"; + +import * as TestStakingCredential from "./StakingCredentialInstances-test.js"; +import * as TestPubKeyHash from "./PubKeyHashInstances-test.js"; + +export function fcDCert(): fc.Arbitrary { + return fc.oneof( + fc.record( + { + name: fc.constant("DCertDelegRegKey"), + fields: TestStakingCredential.fcStakingCredential(), + }, + ), + fc.record( + { + name: fc.constant("DCertDelegDeRegKey"), + fields: TestStakingCredential.fcStakingCredential(), + }, + ), + fc.record( + { + name: fc.constant("DCertDelegDelegate"), + fields: fc.tuple( + TestStakingCredential.fcStakingCredential(), + TestPubKeyHash.fcPubKeyHash(), + ), + }, + ), + fc.record( + { + name: fc.constant("DCertPoolRegister"), + fields: fc.tuple( + TestPubKeyHash.fcPubKeyHash(), + TestPubKeyHash.fcPubKeyHash(), + ), + }, + ), + fc.record( + { + name: fc.constant("DCertPoolRetire"), + fields: fc.tuple(TestPubKeyHash.fcPubKeyHash(), fc.bigInt()), + }, + ), + fc.record( + { name: fc.constant("DCertGenesis") }, + ), + fc.record( + { name: fc.constant("DCertMir") }, + ), + ) as fc.Arbitrary; +} + +describe("DCert tests", () => { + describe("Eq Credential tests", () => { + const dict = V1.eqDCert; + + // TODO(jaredponn): put some hard coded unit tests in + + it(`eq is not neq property based tests`, () => { + fc.assert( + fc.property( + fcDCert(), + fcDCert(), + (l, r) => { + TestUtils.negationTest(dict, l, r); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("Json DCert tests", () => { + it(`toJson/fromJson property based tests`, () => { + fc.assert( + fc.property( + fcDCert(), + (data) => { + TestUtils.toJsonFromJsonRoundTrip(V1.jsonDCert, data); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("IsPlutusData DCert tests", () => { + it(`toData/fromData property based tests`, () => { + fc.assert( + fc.property( + fcDCert(), + (data) => { + TestUtils.isPlutusDataRoundTrip( + V1.isPlutusDataDCert, + data, + ); + }, + ), + { + examples: [], + }, + ); + }); + }); +}); diff --git a/src/Tests/DatumHashInstances-test.ts b/src/Tests/V1/DatumHashInstances-test.ts similarity index 97% rename from src/Tests/DatumHashInstances-test.ts rename to src/Tests/V1/DatumHashInstances-test.ts index 4a84ecd..40d8c44 100644 --- a/src/Tests/DatumHashInstances-test.ts +++ b/src/Tests/V1/DatumHashInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `DatumHash` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; // `datumHash1` is distinct from `datumHash2` diff --git a/src/Tests/DatumInstances-test.ts b/src/Tests/V1/DatumInstances-test.ts similarity index 67% rename from src/Tests/DatumInstances-test.ts rename to src/Tests/V1/DatumInstances-test.ts index 54c7067..cfe43a5 100644 --- a/src/Tests/DatumInstances-test.ts +++ b/src/Tests/V1/DatumInstances-test.ts @@ -1,5 +1,5 @@ // Alias the generator for `Datum` since `Datum` is a type alias for // `PlutusData` -import * as TestPlutusData from "./PlutusDataInstances-test.js"; +import * as TestPlutusData from "../PlutusDataInstances-test.js"; export const fcDatum = TestPlutusData.fcPlutusData; diff --git a/src/Tests/IntervalInstances-test.ts b/src/Tests/V1/IntervalInstances-test.ts similarity index 99% rename from src/Tests/IntervalInstances-test.ts rename to src/Tests/V1/IntervalInstances-test.ts index a966e08..9662ca1 100644 --- a/src/Tests/IntervalInstances-test.ts +++ b/src/Tests/V1/IntervalInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for various types in the `V1/Interval.js` file -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; export function fcExtended( diff --git a/src/Tests/LedgerBytesInstances-test.ts b/src/Tests/V1/LedgerBytesInstances-test.ts similarity index 96% rename from src/Tests/LedgerBytesInstances-test.ts rename to src/Tests/V1/LedgerBytesInstances-test.ts index 911b89e..972b787 100644 --- a/src/Tests/LedgerBytesInstances-test.ts +++ b/src/Tests/V1/LedgerBytesInstances-test.ts @@ -1,9 +1,9 @@ // Tests for the instances for `LedgerBytes` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; describe("LedgerBytes tests", () => { diff --git a/src/Tests/PubKeyHashInstances-test.ts b/src/Tests/V1/PubKeyHashInstances-test.ts similarity index 97% rename from src/Tests/PubKeyHashInstances-test.ts rename to src/Tests/V1/PubKeyHashInstances-test.ts index 068a054..0f886b0 100644 --- a/src/Tests/PubKeyHashInstances-test.ts +++ b/src/Tests/V1/PubKeyHashInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `PubKeyHash` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; // `pubKeyHash1` is distinct from `pubKeyHash2` diff --git a/src/Tests/ScriptHashInstances-test.ts b/src/Tests/V1/ScriptHashInstances-test.ts similarity index 97% rename from src/Tests/ScriptHashInstances-test.ts rename to src/Tests/V1/ScriptHashInstances-test.ts index 5488d1d..1250484 100644 --- a/src/Tests/ScriptHashInstances-test.ts +++ b/src/Tests/V1/ScriptHashInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `ScriptHash` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; // `scriptHash1` is distinct from `scriptHash2` diff --git a/src/Tests/StakingCredentialInstances-test.ts b/src/Tests/V1/StakingCredentialInstances-test.ts similarity index 98% rename from src/Tests/StakingCredentialInstances-test.ts rename to src/Tests/V1/StakingCredentialInstances-test.ts index cddf59b..1cd03f4 100644 --- a/src/Tests/StakingCredentialInstances-test.ts +++ b/src/Tests/V1/StakingCredentialInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `StakingCredential` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; import * as TestCredential from "./CredentialInstances-test.js"; diff --git a/src/Tests/TxIdInstances-test.ts b/src/Tests/V1/TxIdInstances-test.ts similarity index 97% rename from src/Tests/TxIdInstances-test.ts rename to src/Tests/V1/TxIdInstances-test.ts index d3ba6b3..d1277a8 100644 --- a/src/Tests/TxIdInstances-test.ts +++ b/src/Tests/V1/TxIdInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `TxId` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; // `txId1` is distinct from `txId2` diff --git a/src/Tests/V1/TxInInfoInstances-test.ts b/src/Tests/V1/TxInInfoInstances-test.ts new file mode 100644 index 0000000..e732c9d --- /dev/null +++ b/src/Tests/V1/TxInInfoInstances-test.ts @@ -0,0 +1,75 @@ +// Tests for the instances for `DCert` +import * as V1 from "../../Lib/V1.js"; +import { describe, it } from "node:test"; +import * as TestUtils from "../TestUtils.js"; +import fc from "fast-check"; + +import * as TestTxOutRef from "./TxOutRefInstances-test.js"; +import * as TestTxOut from "./TxOutInstances-test.js"; + +export function fcTxInInfo(): fc.Arbitrary { + return fc.record( + { + txInInfoOutRef: TestTxOutRef.fcTxOutRef(), + txInInfoResolved: TestTxOut.fcTxOut(), + }, + ) as fc.Arbitrary; +} + +describe("TxInInfo tests", () => { + describe("Eq TxInInfo tests", () => { + const dict = V1.eqTxInInfo; + + // TODO(jaredponn): put some hard coded unit tests in + + it(`eq is not neq property based tests`, () => { + fc.assert( + fc.property( + fcTxInInfo(), + fcTxInInfo(), + (l, r) => { + TestUtils.negationTest(dict, l, r); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("Json TxInInfo tests", () => { + it(`toJson/fromJson property based tests`, () => { + fc.assert( + fc.property( + fcTxInInfo(), + (data) => { + TestUtils.toJsonFromJsonRoundTrip(V1.jsonTxInInfo, data); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("IsPlutusData TxInInfo tests", () => { + it(`toData/fromData property based tests`, () => { + fc.assert( + fc.property( + fcTxInInfo(), + (data) => { + TestUtils.isPlutusDataRoundTrip( + V1.isPlutusDataTxInInfo, + data, + ); + }, + ), + { + examples: [], + }, + ); + }); + }); +}); diff --git a/src/Tests/V1/TxInfoInstances-test.ts b/src/Tests/V1/TxInfoInstances-test.ts new file mode 100644 index 0000000..4ac74de --- /dev/null +++ b/src/Tests/V1/TxInfoInstances-test.ts @@ -0,0 +1,95 @@ +// Tests for the instances for `TxInfo` +import * as V1 from "../../Lib/V1.js"; +import { describe, it } from "node:test"; +import * as TestUtils from "../TestUtils.js"; +import fc from "fast-check"; + +import * as TestTxInInfo from "./TxInInfoInstances-test.js"; +import * as TestValue from "./ValueInstances-test.js"; +import * as TestDCert from "./DCertInstances-test.js"; +import * as TestStakingCredential from "./StakingCredentialInstances-test.js"; +import * as TestInterval from "./IntervalInstances-test.js"; +import * as TestPubKeyHash from "./PubKeyHashInstances-test.js"; +import * as TestDatumHash from "./DatumHashInstances-test.js"; +import * as TestDatum from "./DatumInstances-test.js"; +import * as TestTxId from "./TxIdInstances-test.js"; +import * as TestTxOut from "./TxOutInstances-test.js"; + +export function fcTxInfo(): fc.Arbitrary { + return fc.record( + { + txInfoInputs: fc.array(TestTxInInfo.fcTxInInfo()), + txInfoOutputs: fc.array(TestTxOut.fcTxOut()), + txInfoFee: TestValue.fcValue(), + txInfoMint: TestValue.fcValue(), + txInfoDCert: fc.array(TestDCert.fcDCert()), + txInfoWdrl: fc.array( + fc.tuple(TestStakingCredential.fcStakingCredential(), fc.bigInt()), + ), + txInfoValidRange: TestInterval.fcInterval(fc.bigInt()), + txInfoSignatories: fc.array(TestPubKeyHash.fcPubKeyHash()), + txInfoData: fc.array( + fc.tuple(TestDatumHash.fcDatumHash(), TestDatum.fcDatum()), + ), + txInfoId: TestTxId.fcTxId(), + }, + ); +} + +describe("TxInfo tests", () => { + describe("Eq TxInfo tests", () => { + const dict = V1.eqTxInfo; + + // TODO(jaredponn): put some hard coded unit tests in + + it(`eq is not neq property based tests`, () => { + fc.assert( + fc.property( + fcTxInfo(), + fcTxInfo(), + (l, r) => { + TestUtils.negationTest(dict, l, r); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("Json TxInfo tests", () => { + it(`toJson/fromJson property based tests`, () => { + fc.assert( + fc.property( + fcTxInfo(), + (data) => { + TestUtils.toJsonFromJsonRoundTrip(V1.jsonTxInfo, data); + }, + ), + { + examples: [], + }, + ); + }); + }); + + describe("IsPlutusData TxInfo tests", () => { + it(`toData/fromData property based tests`, () => { + fc.assert( + fc.property( + fcTxInfo(), + (data) => { + TestUtils.isPlutusDataRoundTrip( + V1.isPlutusDataTxInfo, + data, + ); + }, + ), + { + examples: [], + }, + ); + }); + }); +}); diff --git a/src/Tests/V1/TxOutInstances-test.ts b/src/Tests/V1/TxOutInstances-test.ts new file mode 100644 index 0000000..7560228 --- /dev/null +++ b/src/Tests/V1/TxOutInstances-test.ts @@ -0,0 +1,187 @@ +// Tests for the instances for `TxOut` +import * as LbAssocMap from "../../Lib/AssocMap.js"; +import * as V1 from "../../Lib/V1.js"; +import * as Prelude from "prelude"; +import * as PreludeInstances from "../../Lib/Prelude/Instances.js"; + +import { describe, it } from "node:test"; + +import * as TestUtils from "../TestUtils.js"; +import fc from "fast-check"; + +import * as TestAddress from "./AddressInstances-test.js"; +import * as TestValue from "./ValueInstances-test.js"; +import * as TestMaybe from "../Prelude/MaybeInstances-test.js"; +import * as TestDatumHash from "./DatumHashInstances-test.js"; + +export function fcTxOut(): fc.Arbitrary { + return fc.record({ + txOutAddress: TestAddress.fcAddress(), + txOutValue: TestValue.fcValue(), + txOutDatumHash: TestMaybe.fcMaybe(TestDatumHash.fcDatumHash()), + }) as fc.Arbitrary; +} + +const pubKeyHash1 = Prelude.fromJust( + V1.pubKeyHashFromBytes( + Uint8Array.from([ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + ]), + ), +); + +const credential1: V1.Credential = { + name: "PubKeyCredential", + fields: pubKeyHash1, +}; +const address1: V1.Address = { + addressCredential: credential1, + addressStakingCredential: { name: "Nothing" }, +}; + +const value1: V1.Value = LbAssocMap.fromList([]); + +const maybeDatumHash1: Prelude.Maybe = { + name: `Just`, + fields: TestDatumHash.datumHash1, +}; +const maybeDatumHash2: Prelude.Maybe = { name: `Nothing` }; + +const txOut1: V1.TxOut = { + txOutAddress: address1, + txOutValue: value1, + txOutDatumHash: maybeDatumHash1, +}; + +const txOut2: V1.TxOut = { + txOutAddress: address1, + txOutValue: value1, + txOutDatumHash: maybeDatumHash2, +}; + +describe("TxOut tests", () => { + describe("Eq TxOut tests", () => { + const dict = V1.eqTxOut; + TestUtils.eqIt(dict, txOut1, txOut1, true); + TestUtils.neqIt(dict, txOut1, txOut1, false); + + TestUtils.eqIt(dict, txOut2, txOut2, true); + TestUtils.neqIt(dict, txOut2, txOut2, false); + + TestUtils.eqIt(dict, txOut2, txOut1, false); + TestUtils.neqIt(dict, txOut2, txOut1, true); + + it(`eq is not neq property based tests`, () => { + fc.assert( + fc.property( + fcTxOut(), + fcTxOut(), + (l, r) => { + TestUtils.negationTest(dict, l, r); + }, + ), + { examples: [] }, + ); + }); + }); + + describe("Json TxOut tests", () => { + TestUtils.toJsonAndFromJsonIt(V1.jsonTxOut, txOut1, { + address: V1.jsonAddress.toJson(address1), + datum_hash: Prelude.jsonMaybe(V1.jsonDatumHash).toJson(maybeDatumHash1), + value: [], + }); + + TestUtils.toJsonAndFromJsonIt(V1.jsonTxOut, txOut2, { + address: V1.jsonAddress.toJson(address1), + datum_hash: Prelude.jsonMaybe(V1.jsonDatumHash).toJson(maybeDatumHash2), + value: [], + }); + + it(`toJson/fromJson property based tests`, () => { + fc.assert( + fc.property( + fcTxOut(), + (data) => { + TestUtils.toJsonFromJsonRoundTrip(V1.jsonTxOut, data); + }, + ), + { examples: [] }, + ); + }); + }); + + describe("IsPlutusData TxOut tests", () => { + TestUtils.isPlutusDataIt( + V1.isPlutusDataTxOut, + txOut1, + { + name: "Constr", + fields: [0n, [ + V1.isPlutusDataAddress.toData(address1), + V1.isPlutusDataValue.toData(value1), + PreludeInstances.isPlutusDataMaybe(V1.isPlutusDataDatumHash).toData( + maybeDatumHash1, + ), + ]], + }, + ); + + TestUtils.isPlutusDataIt( + V1.isPlutusDataTxOut, + txOut2, + { + name: "Constr", + fields: [0n, [ + V1.isPlutusDataAddress.toData(address1), + V1.isPlutusDataValue.toData(value1), + PreludeInstances.isPlutusDataMaybe(V1.isPlutusDataDatumHash).toData( + maybeDatumHash2, + ), + ]], + }, + ); + + it(`toData/fromData property based tests`, () => { + fc.assert( + fc.property( + fcTxOut(), + (data) => { + TestUtils.isPlutusDataRoundTrip( + V1.isPlutusDataTxOut, + data, + ); + }, + ), + { examples: [] }, + ); + }); + }); +}); diff --git a/src/Tests/TxOutRefInstances-test.ts b/src/Tests/V1/TxOutRefInstances-test.ts similarity index 96% rename from src/Tests/TxOutRefInstances-test.ts rename to src/Tests/V1/TxOutRefInstances-test.ts index 8c47c20..9cafbe7 100644 --- a/src/Tests/TxOutRefInstances-test.ts +++ b/src/Tests/V1/TxOutRefInstances-test.ts @@ -1,10 +1,10 @@ // Tests for the instances for `TxOutRef` -import * as V1 from "../Lib/V1.js"; +import * as V1 from "../../Lib/V1.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; import * as TestTxId from "./TxIdInstances-test.js"; @@ -102,7 +102,7 @@ export function fcTxOutRef(): fc.Arbitrary { } describe("TxOutRef tests", () => { - describe("Eq Credential tests", () => { + describe("Eq TxOutRef tests", () => { const dict = V1.eqTxOutRef; // Same address diff --git a/src/Tests/ValueInstances-test.ts b/src/Tests/V1/ValueInstances-test.ts similarity index 98% rename from src/Tests/ValueInstances-test.ts rename to src/Tests/V1/ValueInstances-test.ts index 3acbe8a..fbc88cd 100644 --- a/src/Tests/ValueInstances-test.ts +++ b/src/Tests/V1/ValueInstances-test.ts @@ -1,12 +1,12 @@ // Tests for the instances for various types in the `V1/Value.js` file -import * as V1 from "../Lib/V1.js"; -import * as LbAssocMap from "../Lib/AssocMap.js"; +import * as V1 from "../../Lib/V1.js"; +import * as LbAssocMap from "../../Lib/AssocMap.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; -import * as TestAssocMap from "./AssocMap-test.js"; +import * as TestUtils from "../TestUtils.js"; +import * as TestAssocMap from "../AssocMap-test.js"; import fc from "fast-check"; // `currencySymbol1` is distinct from `currencySymbol2` diff --git a/src/Tests/OutputDatumInstances-test.ts b/src/Tests/V2/OutputDatumInstances-test.ts similarity index 94% rename from src/Tests/OutputDatumInstances-test.ts rename to src/Tests/V2/OutputDatumInstances-test.ts index cb32bae..9378c64 100644 --- a/src/Tests/OutputDatumInstances-test.ts +++ b/src/Tests/V2/OutputDatumInstances-test.ts @@ -1,16 +1,16 @@ // Tests for the instances for `OutputDatum` -import * as V1 from "../Lib/V1.js"; -import * as V2 from "../Lib/V2.js"; -import * as LbPlutusData from "../Lib/PlutusData.js"; +import * as V1 from "../../Lib/V1.js"; +import * as V2 from "../../Lib/V2.js"; +import * as LbPlutusData from "../../Lib/PlutusData.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; -import * as TestDatum from "./DatumInstances-test.js"; -import * as TestDatumHash from "./DatumHashInstances-test.js"; +import * as TestDatum from "../V1/DatumInstances-test.js"; +import * as TestDatumHash from "../V1/DatumHashInstances-test.js"; export const datumHash1: V1.DatumHash = Prelude.fromJust( V1.datumHashFromBytes( diff --git a/src/Tests/TxInInfoInstances-test.ts b/src/Tests/V2/TxInInfoInstances-test.ts similarity index 94% rename from src/Tests/TxInInfoInstances-test.ts rename to src/Tests/V2/TxInInfoInstances-test.ts index 12bc8c0..bcda740 100644 --- a/src/Tests/TxInInfoInstances-test.ts +++ b/src/Tests/V2/TxInInfoInstances-test.ts @@ -1,15 +1,15 @@ // Tests for the instances for `TxInInfo` -import * as LbAssocMap from "../Lib/AssocMap.js"; -import * as V1 from "../Lib/V1.js"; -import * as V2 from "../Lib/V2.js"; +import * as LbAssocMap from "../../Lib/AssocMap.js"; +import * as V1 from "../../Lib/V1.js"; +import * as V2 from "../../Lib/V2.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; -import * as TestTxOutRef from "./TxOutRefInstances-test.js"; +import * as TestTxOutRef from "../V1/TxOutRefInstances-test.js"; import * as TestTxOut from "./TxOutInstances-test.js"; export function fcTxInInfo(): fc.Arbitrary { diff --git a/src/Tests/TxOutInstances-test.ts b/src/Tests/V2/TxOutInstances-test.ts similarity index 91% rename from src/Tests/TxOutInstances-test.ts rename to src/Tests/V2/TxOutInstances-test.ts index 1867940..3bc31d6 100644 --- a/src/Tests/TxOutInstances-test.ts +++ b/src/Tests/V2/TxOutInstances-test.ts @@ -1,19 +1,19 @@ // Tests for the instances for `TxOut` -import * as LbAssocMap from "../Lib/AssocMap.js"; -import * as V1 from "../Lib/V1.js"; -import * as V2 from "../Lib/V2.js"; +import * as LbAssocMap from "../../Lib/AssocMap.js"; +import * as V1 from "../../Lib/V1.js"; +import * as V2 from "../../Lib/V2.js"; import * as Prelude from "prelude"; import { describe, it } from "node:test"; -import * as TestUtils from "./TestUtils.js"; +import * as TestUtils from "../TestUtils.js"; import fc from "fast-check"; -import * as TestAddress from "./AddressInstances-test.js"; -import * as TestValue from "./ValueInstances-test.js"; +import * as TestAddress from "../V1/AddressInstances-test.js"; +import * as TestValue from "../V1/ValueInstances-test.js"; import * as TestOutputDatum from "./OutputDatumInstances-test.js"; -import * as TestMaybe from "./MaybeInstances-test.js"; -import * as TestScriptHash from "./ScriptHashInstances-test.js"; +import * as TestMaybe from "../Prelude/MaybeInstances-test.js"; +import * as TestScriptHash from "../V1/ScriptHashInstances-test.js"; export function fcTxOut(): fc.Arbitrary { return fc.record({