From a9fef809779d2cee8fc4abf496c1ed057367ba08 Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Fri, 5 Sep 2025 16:23:47 -0600 Subject: [PATCH 1/2] feat: upgrade modules --- packages/evolution/src/core/AddressDetails.ts | 4 +- packages/evolution/src/core/AddressEras.ts | 4 +- .../evolution/src/core/AddressStructure.ts | 8 +- packages/evolution/src/core/Anchor.ts | 2 +- packages/evolution/src/core/AssetName.ts | 13 +- .../evolution/src/core/AuxiliaryDataHash.ts | 6 +- packages/evolution/src/core/BaseAddress.ts | 5 +- packages/evolution/src/core/Bip32PublicKey.ts | 6 +- packages/evolution/src/core/BlockBodyHash.ts | 6 +- .../evolution/src/core/BlockHeaderHash.ts | 6 +- packages/evolution/src/core/Bytes.ts | 2 +- packages/evolution/src/core/Bytes32.ts | 44 +----- packages/evolution/src/core/Bytes4.ts | 44 +----- packages/evolution/src/core/Bytes64.ts | 35 +---- packages/evolution/src/core/Coin.ts | 2 +- packages/evolution/src/core/CostModel.ts | 2 +- packages/evolution/src/core/DRep.ts | 14 +- packages/evolution/src/core/Data.ts | 2 +- packages/evolution/src/core/DatumOption.ts | 24 ++- .../evolution/src/core/Ed25519Signature.ts | 26 ++-- .../evolution/src/core/EnterpriseAddress.ts | 5 +- packages/evolution/src/core/EpochNo.ts | 8 +- packages/evolution/src/core/Function.ts | 128 +++++++++------- .../evolution/src/core/GovernanceAction.ts | 18 ++- packages/evolution/src/core/Hash28.ts | 42 +----- packages/evolution/src/core/Header.ts | 3 +- packages/evolution/src/core/HeaderBody.ts | 6 +- packages/evolution/src/core/IPv4.ts | 15 +- packages/evolution/src/core/KESVkey.ts | 6 +- packages/evolution/src/core/KeyHash.ts | 6 +- packages/evolution/src/core/Metadata.ts | 2 +- packages/evolution/src/core/MultiAsset.ts | 4 +- .../evolution/src/core/NonnegativeInterval.ts | 4 +- packages/evolution/src/core/Numeric.ts | 16 +- .../evolution/src/core/OperationalCert.ts | 8 +- packages/evolution/src/core/PointerAddress.ts | 5 +- packages/evolution/src/core/PolicyId.ts | 6 +- packages/evolution/src/core/PoolKeyHash.ts | 6 +- packages/evolution/src/core/PositiveCoin.ts | 2 +- packages/evolution/src/core/PrivateKey.ts | 20 +-- packages/evolution/src/core/RewardAccount.ts | 5 +- packages/evolution/src/core/ScriptDataHash.ts | 6 +- packages/evolution/src/core/ScriptHash.ts | 6 +- packages/evolution/src/core/ScriptRef.ts | 6 +- packages/evolution/src/core/SingleHostAddr.ts | 6 +- packages/evolution/src/core/SingleHostName.ts | 7 +- .../evolution/src/core/TransactionBody.ts | 50 ++----- .../evolution/src/core/TransactionHash.ts | 35 +---- .../evolution/src/core/TransactionOutput.ts | 38 ++--- .../src/core/TransactionWitnessSet.ts | 2 +- packages/evolution/src/core/UnitInterval.ts | 7 +- packages/evolution/src/core/VKey.ts | 6 +- .../evolution/src/core/VotingProcedures.ts | 19 +-- packages/evolution/src/core/VrfCert.ts | 6 +- packages/evolution/src/core/VrfKeyHash.ts | 6 +- packages/evolution/src/core/VrfVkey.ts | 6 +- packages/evolution/src/sdk/Address.ts | 141 ++++++++++++------ packages/evolution/src/sdk/Credential.ts | 95 ++++++------ packages/evolution/src/sdk/RewardAddress.ts | 20 +-- packages/evolution/src/sdk/Script.ts | 72 ++++----- 60 files changed, 486 insertions(+), 618 deletions(-) diff --git a/packages/evolution/src/core/AddressDetails.ts b/packages/evolution/src/core/AddressDetails.ts index 28bb2bc8..f9c4bb0c 100644 --- a/packages/evolution/src/core/AddressDetails.ts +++ b/packages/evolution/src/core/AddressDetails.ts @@ -31,7 +31,7 @@ export class AddressDetails extends Schema.Class("AddressDetails hex: Bytes.HexSchema }) {} -export const FromBech32 = Schema.transformOrFail(Schema.String, AddressDetails, { +export const FromBech32 = Schema.transformOrFail(Schema.String, Schema.typeSchema(AddressDetails), { strict: true, encode: (_, __, ___, toA) => ParseResult.succeed(toA.bech32), decode: (_, __, ___, fromA) => @@ -48,7 +48,7 @@ export const FromBech32 = Schema.transformOrFail(Schema.String, AddressDetails, }) }) -export const FromHex = Schema.transformOrFail(Bytes.HexSchema, AddressDetails, { +export const FromHex = Schema.transformOrFail(Bytes.HexSchema, Schema.typeSchema(AddressDetails), { strict: true, encode: (_, __, ___, toA) => ParseResult.succeed(toA.hex), decode: (_, __, ___, fromA) => diff --git a/packages/evolution/src/core/AddressEras.ts b/packages/evolution/src/core/AddressEras.ts index 81138839..f8ecf6b1 100644 --- a/packages/evolution/src/core/AddressEras.ts +++ b/packages/evolution/src/core/AddressEras.ts @@ -86,7 +86,7 @@ export type AddressEras = typeof AddressEras.Type * @since 2.0.0 * @category schema */ -export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, AddressEras, { +export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, Schema.typeSchema(AddressEras), { strict: true, encode: (_, __, ___, toA) => { switch (toA._tag) { @@ -156,7 +156,7 @@ export const FromHex = Schema.compose(Bytes.FromHex, FromBytes) * @since 2.0.0 * @category schema */ -export const FromBech32 = Schema.transformOrFail(Schema.String, AddressEras, { +export const FromBech32 = Schema.transformOrFail(Schema.String, Schema.typeSchema(AddressEras), { strict: true, encode: (_, __, ast, toA) => Eff.gen(function* () { diff --git a/packages/evolution/src/core/AddressStructure.ts b/packages/evolution/src/core/AddressStructure.ts index 62f0ed81..be2fa1ca 100644 --- a/packages/evolution/src/core/AddressStructure.ts +++ b/packages/evolution/src/core/AddressStructure.ts @@ -47,7 +47,7 @@ export class AddressStructure extends Schema.Class("AddressStr */ export const FromBytes = Schema.transformOrFail( Schema.Union(Bytes57.BytesSchema, Bytes29.BytesSchema), - AddressStructure, + Schema.typeSchema(AddressStructure), { strict: true, encode: (_, __, ___, toA) => @@ -93,7 +93,7 @@ export const FromBytes = Schema.transformOrFail( ? new KeyHash.KeyHash({ hash: fromA.slice(29, 57) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(29, 57) }) - return yield* ParseResult.decode(AddressStructure)({ + return AddressStructure.make({ networkId, paymentCredential, stakingCredential @@ -105,7 +105,7 @@ export const FromBytes = Schema.transformOrFail( ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) - return yield* ParseResult.decode(AddressStructure)({ + return AddressStructure.make({ networkId, paymentCredential, stakingCredential: undefined @@ -135,7 +135,7 @@ export const FromHex = Schema.compose(Bytes.FromHex, FromBytes).annotations({ * @since 1.0.0 * @category Transformations */ -export const FromBech32 = Schema.transformOrFail(Schema.String, AddressStructure, { +export const FromBech32 = Schema.transformOrFail(Schema.String, Schema.typeSchema(AddressStructure), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { diff --git a/packages/evolution/src/core/Anchor.ts b/packages/evolution/src/core/Anchor.ts index 419ea4fe..13292593 100644 --- a/packages/evolution/src/core/Anchor.ts +++ b/packages/evolution/src/core/Anchor.ts @@ -28,7 +28,7 @@ export class AnchorError extends Data.TaggedError("AnchorError")<{ */ export class Anchor extends Schema.Class("Anchor")({ anchorUrl: Url.Url, - anchorDataHash: Bytes32.BytesSchema + anchorDataHash: Bytes32.BytesFromHex }) {} export const CDDLSchema = Schema.Tuple( diff --git a/packages/evolution/src/core/AssetName.ts b/packages/evolution/src/core/AssetName.ts index 413ad09b..12c5d2b9 100644 --- a/packages/evolution/src/core/AssetName.ts +++ b/packages/evolution/src/core/AssetName.ts @@ -22,15 +22,8 @@ export class AssetNameError extends Data.TaggedError("AssetNameError")<{ * @category model */ export class AssetName extends Schema.TaggedClass()("AssetName", { - bytes: Bytes32.VariableBytes -}) { - toJSON(): string { - return toHex(this) - } - toString(): string { - return toHex(this) - } -} + bytes: Bytes32.VariableBytesFromHex +}) {} /** * Schema for encoding/decoding AssetName as bytes. @@ -38,7 +31,7 @@ export class AssetName extends Schema.TaggedClass()("AssetName", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.VariableBytes, AssetName, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.VariableBytesFromHex), Schema.typeSchema(AssetName), { strict: true, decode: (bytes) => new AssetName({ bytes }, { disableValidation: true }), encode: (assetName) => assetName.bytes diff --git a/packages/evolution/src/core/AuxiliaryDataHash.ts b/packages/evolution/src/core/AuxiliaryDataHash.ts index 7efd1fa8..52226d36 100644 --- a/packages/evolution/src/core/AuxiliaryDataHash.ts +++ b/packages/evolution/src/core/AuxiliaryDataHash.ts @@ -31,10 +31,10 @@ export class AuxiliaryDataHashError extends Data.TaggedError("AuxiliaryDataHashE * @category model */ export class AuxiliaryDataHash extends Schema.TaggedClass()("AuxiliaryDataHash", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} -export const FromBytes = Schema.transform(Bytes32.BytesSchema, AuxiliaryDataHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(AuxiliaryDataHash), { strict: true, decode: (bytes) => new AuxiliaryDataHash({ bytes }, { disableValidation: true }), encode: (a) => a.bytes @@ -43,7 +43,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, AuxiliaryDataHash }) export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> AuxiliaryDataHash ).annotations({ identifier: "AuxiliaryDataHash.FromHex" diff --git a/packages/evolution/src/core/BaseAddress.ts b/packages/evolution/src/core/BaseAddress.ts index f0c16acb..19aace7b 100644 --- a/packages/evolution/src/core/BaseAddress.ts +++ b/packages/evolution/src/core/BaseAddress.ts @@ -33,7 +33,7 @@ export class BaseAddress extends Schema.TaggedClass("BaseAddress")( } } -export const FromBytes = Schema.transformOrFail(Bytes57.BytesSchema, BaseAddress, { +export const FromBytes = Schema.transformOrFail(Bytes57.BytesSchema, Schema.typeSchema(BaseAddress), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { @@ -72,8 +72,7 @@ export const FromBytes = Schema.transformOrFail(Bytes57.BytesSchema, BaseAddress : new ScriptHash.ScriptHash({ hash: fromA.slice(29, 57) }) - return yield* ParseResult.decode(BaseAddress)({ - _tag: "BaseAddress", + return BaseAddress.make({ networkId, paymentCredential, stakeCredential diff --git a/packages/evolution/src/core/Bip32PublicKey.ts b/packages/evolution/src/core/Bip32PublicKey.ts index 7d2e611b..f40debc8 100644 --- a/packages/evolution/src/core/Bip32PublicKey.ts +++ b/packages/evolution/src/core/Bip32PublicKey.ts @@ -29,7 +29,7 @@ export class Bip32PublicKeyError extends Data.TaggedError("Bip32PublicKeyError") * @category schemas */ export class Bip32PublicKey extends Schema.TaggedClass()("Bip32PublicKey", { - bytes: Bytes64.BytesSchema + bytes: Bytes64.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -46,7 +46,7 @@ export class Bip32PublicKey extends Schema.TaggedClass()("Bip32P * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes64.BytesSchema, Bip32PublicKey, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes64.BytesFromHex), Schema.typeSchema(Bip32PublicKey), { strict: true, decode: (bytes) => new Bip32PublicKey({ bytes }, { disableValidation: true }), encode: (bip32PublicKey) => bip32PublicKey.bytes @@ -61,7 +61,7 @@ export const FromBytes = Schema.transform(Bytes64.BytesSchema, Bip32PublicKey, { * @category schemas */ export const FromHex = Schema.compose( - Bytes64.FromHex, // string -> Bytes64 + Bytes64.BytesFromHex, // string -> Bytes64 FromBytes // Bytes64 -> Bip32PublicKey ).annotations({ identifier: "Bip32PublicKey.FromHex" diff --git a/packages/evolution/src/core/BlockBodyHash.ts b/packages/evolution/src/core/BlockBodyHash.ts index f80db41b..2410f26a 100644 --- a/packages/evolution/src/core/BlockBodyHash.ts +++ b/packages/evolution/src/core/BlockBodyHash.ts @@ -23,7 +23,7 @@ export class BlockBodyHashError extends Data.TaggedError("BlockBodyHashError")<{ * @category model */ export class BlockBodyHash extends Schema.TaggedClass()("BlockBodyHash", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} /** @@ -32,7 +32,7 @@ export class BlockBodyHash extends Schema.TaggedClass()("BlockBod * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesSchema, BlockBodyHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(BlockBodyHash), { strict: true, decode: (bytes) => new BlockBodyHash({ bytes }, { disableValidation: true }), encode: (bbh) => bbh.bytes @@ -47,7 +47,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, BlockBodyHash, { * @category schemas */ export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> BlockBodyHash ).annotations({ identifier: "BlockBodyHash.FromHex" diff --git a/packages/evolution/src/core/BlockHeaderHash.ts b/packages/evolution/src/core/BlockHeaderHash.ts index 5c2d70ff..a69d30a8 100644 --- a/packages/evolution/src/core/BlockHeaderHash.ts +++ b/packages/evolution/src/core/BlockHeaderHash.ts @@ -23,7 +23,7 @@ export class BlockHeaderHashError extends Data.TaggedError("BlockHeaderHashError * @category model */ export class BlockHeaderHash extends Schema.TaggedClass()("BlockHeaderHash", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} /** @@ -32,7 +32,7 @@ export class BlockHeaderHash extends Schema.TaggedClass()("Bloc * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesSchema, BlockHeaderHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(BlockHeaderHash), { strict: true, decode: (bytes) => new BlockHeaderHash({ bytes }, { disableValidation: true }), encode: (bhh) => bhh.bytes @@ -47,7 +47,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, BlockHeaderHash, * @category schemas */ export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> BlockHeaderHash ).annotations({ identifier: "BlockHeaderHash.FromHex" diff --git a/packages/evolution/src/core/Bytes.ts b/packages/evolution/src/core/Bytes.ts index 3cbb6c72..8031da4b 100644 --- a/packages/evolution/src/core/Bytes.ts +++ b/packages/evolution/src/core/Bytes.ts @@ -296,7 +296,7 @@ export const bytesLengthEquals = */ export const bytesLengthBetween = (minBytes: number, maxBytes: number) => - >(baseSchema: S) => + >(baseSchema: S) => baseSchema.pipe( Schema.filter((bytes: Uint8Array) => bytes.length >= minBytes && bytes.length <= maxBytes, { message: () => `Must be between ${minBytes} and ${maxBytes} bytes` diff --git a/packages/evolution/src/core/Bytes32.ts b/packages/evolution/src/core/Bytes32.ts index 8fea3e4d..84f85866 100644 --- a/packages/evolution/src/core/Bytes32.ts +++ b/packages/evolution/src/core/Bytes32.ts @@ -27,39 +27,10 @@ export class Bytes32Error extends Data.TaggedError("Bytes32Error")<{ */ export const BYTES_LENGTH = 32 -export const BytesSchema = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) +export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) -export const HexSchema = Bytes.HexSchema.pipe(Bytes.hexLengthEquals(BYTES_LENGTH)) +export const VariableBytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) -/** - * Schema transformation for fixed-length bytes - * - * @since 2.0.0 - * @category schemas - */ -export const FromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.Bytes${BYTES_LENGTH}FromHex`, - stringSchema: HexSchema, - uint8ArraySchema: BytesSchema, - decode: Bytes.fromHexUnsafe, - encode: Bytes.toHexUnsafe -}) - -export const VariableBytes = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) - -/** - * Schema transformation for variable-length bytes (0..BYTES_LENGTH). - * - * @since 2.0.0 - * @category schemas - */ -export const VariableBytesFromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.VariableBytes${BYTES_LENGTH}FromHex`, - stringSchema: Bytes.HexLenientSchema.pipe(Bytes.hexLengthBetween(0, BYTES_LENGTH)), - uint8ArraySchema: VariableBytes, - decode: Bytes.fromHexLenient, - encode: Bytes.toHexLenientUnsafe -}) export const equals = Bytes.equals @@ -73,7 +44,7 @@ export const equals = Bytes.equals * @since 2.0.0 * @category decoding */ -export const fromHex = Function.makeDecodeSync(FromHex, Bytes32Error, "Bytes32.fromHex") +export const fromHex = Function.makeDecodeSync(BytesFromHex, Bytes32Error, "Bytes32.fromHex") /** * Encode fixed-length bytes to hex. @@ -81,7 +52,7 @@ export const fromHex = Function.makeDecodeSync(FromHex, Bytes32Error, "Bytes32.f * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(FromHex, Bytes32Error, "Bytes32.toHex32") +export const toHex = Function.makeEncodeSync(BytesFromHex, Bytes32Error, "Bytes32.toHex32") /** * Decode variable-length hex (0..BYTES_LENGTH) into bytes. @@ -98,7 +69,6 @@ export const fromVariableHex = Function.makeDecodeSync(VariableBytesFromHex, Byt * @category encoding */ export const toVariableHex = Function.makeEncodeSync(VariableBytesFromHex, Bytes32Error, "Bytes32.toVariableHex32") - // ============================================================================= // Either (safe) API // ============================================================================= @@ -109,13 +79,13 @@ export namespace Either { * @since 2.0.0 * @category decoding */ - export const fromHex = Function.makeDecodeEither(FromHex, Bytes32Error) + export const fromHex = Function.makeDecodeEither(BytesFromHex, Bytes32Error) /** * Safely encode fixed-length bytes to hex. * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(FromHex, Bytes32Error) + export const toHex = Function.makeEncodeEither(BytesFromHex, Bytes32Error) /** * Safely decode variable-length hex (0..BYTES_LENGTH) into bytes. * @since 2.0.0 @@ -127,5 +97,5 @@ export namespace Either { * @since 2.0.0 * @category encoding */ - export const toVariableHex = Function.makeDecodeEither(VariableBytesFromHex, Bytes32Error) + export const toVariableHex = Function.makeEncodeEither(VariableBytesFromHex, Bytes32Error) } diff --git a/packages/evolution/src/core/Bytes4.ts b/packages/evolution/src/core/Bytes4.ts index e8ec4f6b..e4680bc0 100644 --- a/packages/evolution/src/core/Bytes4.ts +++ b/packages/evolution/src/core/Bytes4.ts @@ -27,39 +27,9 @@ export class Bytes4Error extends Data.TaggedError("Bytes4Error")<{ */ export const BYTES_LENGTH = 4 -export const BytesSchema = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) +export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) -export const HexSchema = Bytes.HexSchema.pipe(Bytes.hexLengthEquals(BYTES_LENGTH)) - -/** - * Schema transformation for fixed-length bytes - * - * @since 2.0.0 - * @category schemas - */ -export const FromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.Bytes${BYTES_LENGTH}FromHex`, - stringSchema: HexSchema, - uint8ArraySchema: BytesSchema, - decode: Bytes.fromHexUnsafe, - encode: Bytes.toHexUnsafe -}) - -export const VariableBytes = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) - -/** - * Schema transformation for variable-length bytes (0..BYTES_LENGTH). - * - * @since 2.0.0 - * @category schemas - */ -export const VariableBytesFromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.VariableBytes${BYTES_LENGTH}FromHex`, - stringSchema: Bytes.HexLenientSchema.pipe(Bytes.hexLengthBetween(0, BYTES_LENGTH)), - uint8ArraySchema: VariableBytes, - decode: Bytes.fromHexLenient, - encode: Bytes.toHexLenientUnsafe -}) +export const VariableBytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) export const equals = Bytes.equals @@ -73,7 +43,7 @@ export const equals = Bytes.equals * @since 2.0.0 * @category decoding */ -export const fromHex = Function.makeDecodeSync(FromHex, Bytes4Error, "Bytes4.fromHex") +export const fromHex = Function.makeDecodeSync(BytesFromHex, Bytes4Error, "Bytes4.fromHex") /** * Encode fixed-length bytes to hex. @@ -81,7 +51,7 @@ export const fromHex = Function.makeDecodeSync(FromHex, Bytes4Error, "Bytes4.fro * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(FromHex, Bytes4Error, "Bytes4.toHex4") +export const toHex = Function.makeEncodeSync(BytesFromHex, Bytes4Error, "Bytes4.toHex4") /** * Decode variable-length hex (0..BYTES_LENGTH) into bytes. @@ -109,13 +79,13 @@ export namespace Either { * @since 2.0.0 * @category decoding */ - export const fromHex = Function.makeDecodeEither(FromHex, Bytes4Error) + export const fromHex = Function.makeDecodeEither(BytesFromHex, Bytes4Error) /** * Safely encode fixed-length bytes to hex. * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(FromHex, Bytes4Error) + // export const toHex = Function.makeEncodeEither(FromHex, Bytes4Error) /** * Safely decode variable-length hex (0..BYTES_LENGTH) into bytes. * @since 2.0.0 @@ -127,5 +97,5 @@ export namespace Either { * @since 2.0.0 * @category encoding */ - export const toVariableHex = Function.makeDecodeEither(VariableBytesFromHex, Bytes4Error) + export const toVariableHex = Function.makeEncodeEither(VariableBytesFromHex, Bytes4Error) } diff --git a/packages/evolution/src/core/Bytes64.ts b/packages/evolution/src/core/Bytes64.ts index 606e2f8d..dc535b8f 100644 --- a/packages/evolution/src/core/Bytes64.ts +++ b/packages/evolution/src/core/Bytes64.ts @@ -16,33 +16,10 @@ export class Bytes64Error extends Data.TaggedError("Bytes64Error")<{ */ export const BYTES_LENGTH = 64 -export const BytesSchema = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) +export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) -export const HexSchema = Bytes.HexSchema.pipe(Bytes.hexLengthEquals(BYTES_LENGTH)) -export const FromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.Bytes${BYTES_LENGTH}FromHex`, - stringSchema: HexSchema, - uint8ArraySchema: BytesSchema, - decode: Bytes.fromHexUnsafe, - encode: Bytes.toHexUnsafe -}) - -export const VariableBytes = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) - -/** - * Schema transformation for variable-length bytes (0..BYTES_LENGTH). - * - * @since 2.0.0 - * @category schemas - */ -export const VariableBytesFromHex = Bytes.makeBytesTransformation({ - id: `Bytes${BYTES_LENGTH}.VariableBytes${BYTES_LENGTH}FromHex`, - stringSchema: Bytes.HexLenientSchema.pipe(Bytes.hexLengthBetween(0, BYTES_LENGTH)), - uint8ArraySchema: VariableBytes, - decode: Bytes.fromHexLenient, - encode: Bytes.toHexLenientUnsafe -}) +export const VariableBytesFromHex = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) // ============================================================================= // Public (throwing) API @@ -54,7 +31,7 @@ export const VariableBytesFromHex = Bytes.makeBytesTransformation({ * @since 2.0.0 * @category decoding */ -export const fromHex = Function.makeDecodeSync(FromHex, Bytes64Error, "Bytes64.fromHex") +export const fromHex = Function.makeDecodeSync(BytesFromHex, Bytes64Error, "Bytes64.fromHex") /** * Encode fixed-length bytes to hex. @@ -62,7 +39,7 @@ export const fromHex = Function.makeDecodeSync(FromHex, Bytes64Error, "Bytes64.f * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(FromHex, Bytes64Error, "Bytes64.toHex64") +export const toHex = Function.makeEncodeSync(BytesFromHex, Bytes64Error, "Bytes64.toHex") /** * Decode variable-length hex (0..BYTES_LENGTH) into bytes. @@ -92,13 +69,13 @@ export namespace Either { * @since 2.0.0 * @category decoding */ - export const fromHex = Function.makeDecodeEither(FromHex, Bytes64Error) + export const fromHex = Function.makeDecodeEither(BytesFromHex, Bytes64Error) /** * Safely encode fixed-length bytes to hex. * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(FromHex, Bytes64Error) + export const toHex = Function.makeEncodeEither(BytesFromHex, Bytes64Error) /** * Safely decode variable-length hex (0..BYTES_LENGTH) into bytes. * @since 2.0.0 diff --git a/packages/evolution/src/core/Coin.ts b/packages/evolution/src/core/Coin.ts index 1c662de7..4e112107 100644 --- a/packages/evolution/src/core/Coin.ts +++ b/packages/evolution/src/core/Coin.ts @@ -26,7 +26,7 @@ export const MAX_COIN_VALUE = 18446744073709551615n * @since 2.0.0 * @category schemas */ -export const Coin = Schema.BigIntFromSelf.pipe( +export const Coin = Schema.BigInt.pipe( Schema.filter((value) => value >= 0n && value <= MAX_COIN_VALUE) ).annotations({ message: (issue) => `Coin must be between 0 and ${MAX_COIN_VALUE}, but got ${issue.actual}`, diff --git a/packages/evolution/src/core/CostModel.ts b/packages/evolution/src/core/CostModel.ts index d25fa52b..233e3dfb 100644 --- a/packages/evolution/src/core/CostModel.ts +++ b/packages/evolution/src/core/CostModel.ts @@ -20,7 +20,7 @@ export class CostModelError extends Data.TaggedError("CostModelError")<{ * ``` */ export class CostModel extends Schema.Class("CostModel")({ - costs: Schema.Array(Schema.BigIntFromSelf) + costs: Schema.Array(Schema.BigInt) }) {} /** diff --git a/packages/evolution/src/core/DRep.ts b/packages/evolution/src/core/DRep.ts index dff4c4ff..14c3ca01 100644 --- a/packages/evolution/src/core/DRep.ts +++ b/packages/evolution/src/core/DRep.ts @@ -83,26 +83,20 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(DRe switch (tag) { case 0n: { const keyHash = yield* ParseResult.decode(KeyHash.FromBytes)(rest[0] as Uint8Array) - return yield* ParseResult.decode(DRep)({ - _tag: "KeyHashDRep", + return DRep.members[0].make({ keyHash }) } case 1n: { const scriptHash = yield* ParseResult.decode(ScriptHash.FromBytes)(rest[0] as Uint8Array) - return yield* ParseResult.decode(DRep)({ - _tag: "ScriptHashDRep", + return DRep.members[1].make({ scriptHash }) } case 2n: - return yield* ParseResult.decode(DRep)({ - _tag: "AlwaysAbstainDRep" - }) + return DRep.members[2].make({}) case 3n: - return yield* ParseResult.decode(DRep)({ - _tag: "AlwaysNoConfidenceDRep" - }) + return DRep.members[3].make({}) default: return yield* ParseResult.fail( new ParseResult.Type(Schema.typeSchema(DRep).ast, fromA, `Invalid DRep tag: ${tag}`) diff --git a/packages/evolution/src/core/Data.ts b/packages/evolution/src/core/Data.ts index f938413a..05b647f3 100644 --- a/packages/evolution/src/core/Data.ts +++ b/packages/evolution/src/core/Data.ts @@ -255,7 +255,7 @@ export const isBytes = Schema.is(ByteArray) * @since 2.0.0 * @category constructors */ -export const constr = (index: bigint, fields: Array): Constr => Schema.decodeSync(Constr)({ index, fields }) +export const constr = (index: bigint, fields: Array): Constr => Constr.make({ index, fields }) // new Constr({ // index: Numeric.Uint64Make(index), diff --git a/packages/evolution/src/core/DatumOption.ts b/packages/evolution/src/core/DatumOption.ts index 595c57ec..195ec81d 100644 --- a/packages/evolution/src/core/DatumOption.ts +++ b/packages/evolution/src/core/DatumOption.ts @@ -25,22 +25,18 @@ export class DatumOptionError extends Data.TaggedError("DatumOptionError")<{ * @category schemas */ export class DatumHash extends Schema.TaggedClass()("DatumHash", { - hash: Bytes32.BytesSchema -}) { - toString(): string { - return `DatumHash { hash: ${this.hash} }` - } + hash: Bytes32.BytesFromHex +}) {} - [Symbol.for("nodejs.util.inspect.custom")](): string { - return this.toString() +export const DatumHashFromBytes = Schema.transform( + Schema.typeSchema(Bytes32.BytesFromHex), + Schema.typeSchema(DatumHash), + { + strict: true, + decode: (bytes) => new DatumHash({ hash: bytes }, { disableValidation: true }), + encode: (dh) => dh.hash } -} - -export const DatumHashFromBytes = Schema.transform(Bytes32.BytesSchema, DatumHash, { - strict: true, - decode: (bytes) => new DatumHash({ hash: bytes }, { disableValidation: true }), - encode: (dh) => dh.hash -}).annotations({ +).annotations({ identifier: "DatumOption.DatumHashFromBytes" }) diff --git a/packages/evolution/src/core/Ed25519Signature.ts b/packages/evolution/src/core/Ed25519Signature.ts index f846decf..cfab04e7 100644 --- a/packages/evolution/src/core/Ed25519Signature.ts +++ b/packages/evolution/src/core/Ed25519Signature.ts @@ -13,7 +13,7 @@ import * as Function from "./Function.js" * @category model */ export class Ed25519Signature extends Schema.Class("Ed25519Signature")({ - bytes: Bytes64.BytesSchema + bytes: Bytes64.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -34,15 +34,19 @@ export class Ed25519Signature extends Schema.Class("Ed25519Sig * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes64.BytesSchema, Ed25519Signature, { - strict: true, - decode: (bytes) => - make( - { bytes }, - { disableValidation: true } // Disable validation since we already check length in Bytes64 - ), - encode: (signature) => new Uint8Array(signature.bytes) -}).annotations({ +export const FromBytes = Schema.transform( + Schema.typeSchema(Bytes64.BytesFromHex), + Schema.typeSchema(Ed25519Signature), + { + strict: true, + decode: (bytes) => + make( + { bytes }, + { disableValidation: true } // Disable validation since we already check length in Bytes64 + ), + encode: (signature) => new Uint8Array(signature.bytes) + } +).annotations({ identifier: "Ed25519Signature.FromBytes" }) @@ -53,7 +57,7 @@ export const FromBytes = Schema.transform(Bytes64.BytesSchema, Ed25519Signature, * @category schemas */ export const FromHex = Schema.compose( - Bytes64.FromHex, // string -> Bytes64 + Bytes64.BytesFromHex, // string -> Bytes64 FromBytes ).annotations({ identifier: "Ed25519Signature.FromHex" diff --git a/packages/evolution/src/core/EnterpriseAddress.ts b/packages/evolution/src/core/EnterpriseAddress.ts index fae047b4..74aefdfd 100644 --- a/packages/evolution/src/core/EnterpriseAddress.ts +++ b/packages/evolution/src/core/EnterpriseAddress.ts @@ -32,7 +32,7 @@ export class EnterpriseAddress extends Schema.TaggedClass("En } } -export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, EnterpriseAddress, { +export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, Schema.typeSchema(EnterpriseAddress), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { @@ -64,8 +64,7 @@ export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, EnterpriseA : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) - return yield* ParseResult.decode(EnterpriseAddress)({ - _tag: "EnterpriseAddress", + return EnterpriseAddress.make({ networkId, paymentCredential }) diff --git a/packages/evolution/src/core/EpochNo.ts b/packages/evolution/src/core/EpochNo.ts index 71c2a5ee..af3e1b23 100644 --- a/packages/evolution/src/core/EpochNo.ts +++ b/packages/evolution/src/core/EpochNo.ts @@ -1,4 +1,4 @@ -import { Data, ParseResult, Schema } from "effect" +import { Data, Schema } from "effect" import * as CBOR from "./CBOR.js" import * as Numeric from "./Numeric.js" @@ -87,10 +87,10 @@ export const generator = Numeric.Uint64Arbitrary */ export const CDDLSchema = CBOR.Integer -export const FromCDDL = Schema.transformOrFail(CDDLSchema, EpochNoSchema, { +export const FromCDDL = Schema.transform(CDDLSchema, Schema.typeSchema(EpochNoSchema), { strict: true, - encode: (epoch) => ParseResult.succeed(epoch as unknown as bigint), - decode: (n) => ParseResult.decode(EpochNoSchema)(n as unknown as bigint) + encode: (epoch) => epoch, + decode: (n) => EpochNoSchema.make(n) }).annotations({ identifier: "EpochNo.CDDL" }) export const arbitrary = Numeric.Uint64Arbitrary diff --git a/packages/evolution/src/core/Function.ts b/packages/evolution/src/core/Function.ts index f1c4b856..00ba90e5 100644 --- a/packages/evolution/src/core/Function.ts +++ b/packages/evolution/src/core/Function.ts @@ -3,44 +3,59 @@ import { Either, Schema } from "effect" import * as Bytes from "./Bytes.js" import * as CBOR from "./CBOR.js" +type ErrorCtor = new (props: { message?: string; cause?: unknown }) => E + /** * Creates a named function with proper stack traces using dynamic object property */ -export const makeDecodeSync = ( +export const makeDecodeSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string ): ((input: A) => T) => { // Pre-create decoder once to avoid per-call allocation/closure overhead - const decode = Schema.decodeSync(schemaTransformer) + const decode = Schema.decodeEither(schemaTransformer) + // Create a named function using object property syntax const fn = { - [functionName]: (input: A): T => { - try { - return decode(input) - } catch (e) { - // Keep error creation cheap; avoid stringifying potentially large inputs - throw new ErrorClass({ message: `Failed to decode in ${functionName}`, cause: (e as Error).message }) + [functionName](input: A): T { + const result = decode(input) + if (result._tag === "Left") { + const error = new ErrorClass({ + message: `Failed to decode in ${functionName}`, + cause: result.left + }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, fn[functionName]) + } + throw error } + return result.right } } return fn[functionName] } -export const makeEncodeSync = ( +export const makeEncodeSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string ): ((input: T) => A) => { // Pre-create encoder once to avoid per-call allocation/closure overhead - const encode = Schema.encodeSync(schemaTransformer) + const encode = Schema.encodeEither(schemaTransformer) const fn = { [functionName]: (input: T): A => { - try { - return encode(input) - } catch (e) { - // Keep error creation cheap; avoid stringifying potentially large inputs - throw new ErrorClass({ message: `Failed to encode in ${functionName}`, cause: (e as Error).message }) + const result = encode(input) + if (result._tag === "Left") { + const error = new ErrorClass({ + message: `Failed to encode in ${functionName}`, + cause: result.left + }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, fn[functionName]) + } + throw error } + return result.right } } return fn[functionName] @@ -53,9 +68,9 @@ export const makeEncodeSync = ( * @since 2.0.0 * @category constructors */ -export const makeCBOREncodeSync = ( +export const makeCBOREncodeSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string, defaultOptions?: CBOR.CodecOptions ): ((input: A, options?: CBOR.CodecOptions) => Uint8Array) => { @@ -67,29 +82,27 @@ export const makeCBOREncodeSync = ( const cborValue = encode(input) return CBOR.internalEncodeSync(cborValue, options || defaultOptions) } catch (e) { - // Keep error creation cheap; avoid stringifying potentially large inputs - throw new ErrorClass({ message: `Failed to encode in ${functionName}`, cause: (e as Error).message }) + const error = new ErrorClass({ message: `Failed to encode in ${functionName}`, cause: e }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, wrapped) + } + throw error } } } - return fn[functionName] + const wrapped = fn[functionName] + return wrapped } export const makeDecodeEither = - ( - schema: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError - ) => + (schema: Schema.Schema, ErrorClass: ErrorCtor) => (input: A) => Schema.decodeEither(schema)(input).pipe( Either.mapLeft((e) => new ErrorClass({ message: "Failed to decode input", cause: e })) ) export const makeEncodeEither = - ( - schema: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError - ) => + (schema: Schema.Schema, ErrorClass: ErrorCtor) => (input: T) => Schema.encodeEither(schema)(input).pipe( Either.mapLeft((e) => new ErrorClass({ message: "Failed to encode input", cause: e })) @@ -102,9 +115,9 @@ export const makeEncodeEither = * @since 2.0.0 * @category constructors */ -export const makeCBORDecodeSync = ( +export const makeCBORDecodeSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string, defaultOptions?: CBOR.CodecOptions ): ((bytes: Uint8Array, options?: CBOR.CodecOptions) => A) => { @@ -116,20 +129,25 @@ export const makeCBORDecodeSync = ( const cborValue = CBOR.internalDecodeSync(bytes, options || defaultOptions) return decode(cborValue as T) } catch (e) { - throw new ErrorClass({ message: `Failed to decode in ${functionName}`, cause: (e as Error).message }) + const error = new ErrorClass({ message: `Failed to decode in ${functionName}`, cause: e }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, wrapped) + } + throw error } } } - return fn[functionName] + const wrapped = fn[functionName] + return wrapped } /** * Creates a synchronous function that encodes a value to CBOR hex string. * Uses a schema to encode T -> A then serializes to hex. */ -export const makeCBOREncodeHexSync = ( +export const makeCBOREncodeHexSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string, defaultOptions?: CBOR.CodecOptions ): ((input: A, options?: CBOR.CodecOptions) => string) => { @@ -141,19 +159,24 @@ export const makeCBOREncodeHexSync = ( const bytes = CBOR.internalEncodeSync(cborValue, options || defaultOptions) return Bytes.toHexUnsafe(bytes) } catch (e) { - throw new ErrorClass({ message: `Failed to encode hex in ${functionName}`, cause: (e as Error).message }) + const error = new ErrorClass({ message: `Failed to encode hex in ${functionName}`, cause: e }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, wrapped) + } + throw error } } } - return fn[functionName] + const wrapped = fn[functionName] + return wrapped } /** * Creates a synchronous function that decodes a CBOR hex string into a value using a schema. */ -export const makeCBORDecodeHexSync = ( +export const makeCBORDecodeHexSync = ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => Error, + ErrorClass: ErrorCtor, functionName: string, defaultOptions?: CBOR.CodecOptions ): ((hex: string, options?: CBOR.CodecOptions) => A) => { @@ -165,20 +188,25 @@ export const makeCBORDecodeHexSync = ( const cborValue = CBOR.internalDecodeSync(bytes, options || defaultOptions) return decode(cborValue as T) } catch (e) { - throw new ErrorClass({ message: `Failed to decode hex in ${functionName}`, cause: (e as Error).message }) + const error = new ErrorClass({ message: `Failed to decode hex in ${functionName}`, cause: e }) + if (Error.captureStackTrace) { + Error.captureStackTrace(error, wrapped) + } + throw error } } } - return fn[functionName] + const wrapped = fn[functionName] + return wrapped } /** * Creates a function that decodes CBOR bytes into a value using a schema, returning Either. */ export const makeCBORDecodeEither = - ( + ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError, + ErrorClass: ErrorCtor, defaultOptions?: CBOR.CodecOptions ) => (bytes: Uint8Array, options?: CBOR.CodecOptions) => @@ -191,9 +219,9 @@ export const makeCBORDecodeEither = * Creates a function that decodes CBOR hex string into a value using a schema, returning Either. */ export const makeCBORDecodeHexEither = - ( + ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError, + ErrorClass: ErrorCtor, defaultOptions?: CBOR.CodecOptions ) => (hex: string, options?: CBOR.CodecOptions) => @@ -207,9 +235,9 @@ export const makeCBORDecodeHexEither = * Creates a function that encodes a value to CBOR bytes using a schema, returning Either. */ export const makeCBOREncodeEither = - ( + ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError, + ErrorClass: ErrorCtor, defaultOptions?: CBOR.CodecOptions ) => (input: A, options?: CBOR.CodecOptions) => @@ -222,9 +250,9 @@ export const makeCBOREncodeEither = * Creates a function that encodes a value to CBOR hex string using a schema, returning Either. */ export const makeCBOREncodeHexEither = - ( + ( schemaTransformer: Schema.Schema, - ErrorClass: new (props: { message?: string; cause?: unknown }) => TError, + ErrorClass: ErrorCtor, defaultOptions?: CBOR.CodecOptions ) => (input: A, options?: CBOR.CodecOptions) => diff --git a/packages/evolution/src/core/GovernanceAction.ts b/packages/evolution/src/core/GovernanceAction.ts index 945335af..f93259bd 100644 --- a/packages/evolution/src/core/GovernanceAction.ts +++ b/packages/evolution/src/core/GovernanceAction.ts @@ -72,22 +72,28 @@ export const GovActionIdCDDL = Schema.Tuple( * @since 2.0.0 * @category schemas */ -export const GovActionIdFromCDDL = Schema.transformOrFail(GovActionIdCDDL, GovActionId, { +export const GovActionIdFromCDDL = Schema.transformOrFail(GovActionIdCDDL, Schema.typeSchema(GovActionId), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { // Convert domain types to CBOR types - const transactionIdBytes = yield* ParseResult.encode(TransactionHash.FromBytes)(toA.transactionId) + const transactionIdBytes = toA.transactionId.hash const indexNumber = yield* ParseResult.encode(TransactionIndex.TransactionIndex)(toA.govActionIndex) return [transactionIdBytes, BigInt(indexNumber)] as const }), decode: (fromA) => Eff.gen(function* () { - const [transactionIdBytes, govActionIndexNumber] = fromA + const [transactionIdBytes, govActionIndex] = fromA // Convert CBOR types to domain types - const transactionId = yield* ParseResult.decode(TransactionHash.FromBytes)(transactionIdBytes) - const govActionIndex = yield* ParseResult.decode(TransactionIndex.TransactionIndex)(govActionIndexNumber) - return new GovActionId({ transactionId, govActionIndex }) + const govActionId = yield* ParseResult.decode(Schema.typeSchema(GovActionId))({ + _tag: "GovActionId", + transactionId: yield* ParseResult.decode(Schema.typeSchema(TransactionHash.TransactionHash))({ + _tag: "TransactionHash", + hash: transactionIdBytes + }), + govActionIndex: yield* ParseResult.decode(Schema.typeSchema(TransactionIndex.TransactionIndex))(govActionIndex) + }) + return govActionId }) }) diff --git a/packages/evolution/src/core/Hash28.ts b/packages/evolution/src/core/Hash28.ts index 57ac4a9f..7197151c 100644 --- a/packages/evolution/src/core/Hash28.ts +++ b/packages/evolution/src/core/Hash28.ts @@ -27,39 +27,9 @@ export class Hash28Error extends Data.TaggedError("Hash28Error")<{ */ export const BYTES_LENGTH = 28 -export const BytesSchema = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) +export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) -export const HexSchema = Bytes.HexSchema.pipe(Bytes.hexLengthEquals(BYTES_LENGTH)) - -/** - * Schema transformation for fixed-length bytes - * - * @since 2.0.0 - * @category schemas - */ -export const FromHex = Bytes.makeBytesTransformation({ - id: `Hash${BYTES_LENGTH}.Hash${BYTES_LENGTH}FromHex`, - stringSchema: HexSchema, - uint8ArraySchema: BytesSchema, - decode: Bytes.fromHexUnsafe, - encode: Bytes.toHexUnsafe -}) - -export const VariableBytes = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) - -/** - * Schema transformation for variable-length bytes (0..BYTES_LENGTH). - * - * @since 2.0.0 - * @category schemas - */ -export const VariableBytesFromHex = Bytes.makeBytesTransformation({ - id: `Hash${BYTES_LENGTH}.VariableHash${BYTES_LENGTH}FromHex`, - stringSchema: Bytes.HexLenientSchema.pipe(Bytes.hexLengthBetween(0, BYTES_LENGTH)), - uint8ArraySchema: VariableBytes, - decode: Bytes.fromHexLenient, - encode: Bytes.toHexLenientUnsafe -}) +export const VariableBytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) export const equals = Bytes.equals @@ -73,7 +43,7 @@ export const equals = Bytes.equals * @since 2.0.0 * @category decoding */ -export const fromHex = Function.makeDecodeSync(FromHex, Hash28Error, "Hash28.fromHex") +export const fromHex = Function.makeDecodeSync(BytesFromHex, Hash28Error, "Hash28.fromHex") /** * Encode fixed-length bytes to hex. @@ -81,7 +51,7 @@ export const fromHex = Function.makeDecodeSync(FromHex, Hash28Error, "Hash28.fro * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(FromHex, Hash28Error, "Hash28.toHex") +export const toHex = Function.makeEncodeSync(BytesFromHex, Hash28Error, "Hash28.toHex") /** * Decode variable-length hex (0..BYTES_LENGTH) into bytes. @@ -109,13 +79,13 @@ export namespace Either { * @since 2.0.0 * @category decoding */ - export const fromHex = Function.makeDecodeEither(FromHex, Hash28Error) + export const fromHex = Function.makeDecodeEither(BytesFromHex, Hash28Error) /** * Safely encode fixed-length bytes to hex. * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(FromHex, Hash28Error) + export const toHex = Function.makeEncodeEither(BytesFromHex, Hash28Error) /** * Safely decode variable-length hex (0..BYTES_LENGTH) into bytes. * @since 2.0.0 diff --git a/packages/evolution/src/core/Header.ts b/packages/evolution/src/core/Header.ts index 9ca4fcde..e9bb2638 100644 --- a/packages/evolution/src/core/Header.ts +++ b/packages/evolution/src/core/Header.ts @@ -79,8 +79,7 @@ export const FromCDDL = Schema.transformOrFail( Effect.gen(function* () { const headerBody = yield* ParseResult.decode(HeaderBody.FromCDDL)(headerBodyCddl) const bodySignature = yield* ParseResult.decode(KesSignature.FromBytes)(bodySignatureBytes) - return yield* ParseResult.decode(Header)({ - _tag: "Header", + return new Header({ headerBody, bodySignature }) diff --git a/packages/evolution/src/core/HeaderBody.ts b/packages/evolution/src/core/HeaderBody.ts index 35d84b43..f6dd73c0 100644 --- a/packages/evolution/src/core/HeaderBody.ts +++ b/packages/evolution/src/core/HeaderBody.ts @@ -202,12 +202,12 @@ export const FromCDDL = Schema.transformOrFail( [protocolMajor, protocolMinor] ]) => Eff.gen(function* () { - const blockNumber = yield* ParseResult.decode(Numeric.Uint64Schema)(rawBlockNumber) - const slot = yield* ParseResult.decode(Numeric.Uint64Schema)(rawSlotNumber) + const blockNumber = yield* ParseResult.decode(Schema.typeSchema(Numeric.Uint64Schema))(rawBlockNumber) + const slot = yield* ParseResult.decode(Schema.typeSchema(Numeric.Uint64Schema))(rawSlotNumber) const prevHash = prevHashBytes ? yield* ParseResult.decode(BlockHeaderHash.FromBytes)(prevHashBytes) : null const issuerVkey = yield* ParseResult.decode(VKey.FromBytes)(issuerVkeyBytes) const vrfVkey = yield* ParseResult.decode(VrfVkey.FromBytes)(vrfVkeyBytes) - const blockBodySize = yield* ParseResult.decode(Numeric.Uint64Schema)(rawBlockBodySize) + const blockBodySize = yield* ParseResult.decode(Schema.typeSchema(Numeric.Uint64Schema))(rawBlockBodySize) const blockBodyHash = yield* ParseResult.decode(BlockBodyHash.FromBytes)(blockBodyHashBytes) const vrfResult = yield* ParseResult.decode(VrfCert.FromCDDL)([vrfOutputBytes, vrfProofBytes]) const operationalCert = yield* ParseResult.decode(OperationalCert.FromCDDL)([ diff --git a/packages/evolution/src/core/IPv4.ts b/packages/evolution/src/core/IPv4.ts index 699f2752..00c115f5 100644 --- a/packages/evolution/src/core/IPv4.ts +++ b/packages/evolution/src/core/IPv4.ts @@ -22,18 +22,11 @@ export class IPv4Error extends Data.TaggedError("IPv4Error")<{ * @category schemas */ export class IPv4 extends Schema.TaggedClass()("IPv4", { - bytes: Bytes4.BytesSchema -}) { - toJSON(): string { - return toHex(this) - } - toString(): string { - return toHex(this) - } -} + bytes: Bytes4.BytesFromHex +}) {} // Transform between raw bytes (Uint8Array length 4) and IPv4 -export const FromBytes = Schema.transform(Bytes4.BytesSchema, IPv4, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes4.BytesFromHex), Schema.typeSchema(IPv4), { strict: true, decode: (bytes) => new IPv4({ bytes }, { disableValidation: true }), encode: (ipv4) => ipv4.bytes @@ -42,7 +35,7 @@ export const FromBytes = Schema.transform(Bytes4.BytesSchema, IPv4, { }) export const FromHex = Schema.compose( - Bytes4.FromHex, // string -> Uint8Array(4) + Bytes4.BytesFromHex, // string -> Uint8Array(4) FromBytes // bytes -> IPv4 ).annotations({ identifier: "IPv4.FromHex" diff --git a/packages/evolution/src/core/KESVkey.ts b/packages/evolution/src/core/KESVkey.ts index 2ebde197..40b700b1 100644 --- a/packages/evolution/src/core/KESVkey.ts +++ b/packages/evolution/src/core/KESVkey.ts @@ -23,7 +23,7 @@ export class KESVkeyError extends Data.TaggedError("KESVkeyError")<{ * @category model */ export class KESVkey extends Schema.TaggedClass()("KESVkey", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} /** @@ -32,7 +32,7 @@ export class KESVkey extends Schema.TaggedClass()("KESVkey", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesSchema, KESVkey, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(KESVkey), { strict: true, decode: (bytes) => new KESVkey({ bytes }, { disableValidation: true }), encode: (k) => k.bytes @@ -47,7 +47,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, KESVkey, { * @category schemas */ export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> KESVkey ).annotations({ identifier: "KESVkey.FromHex" diff --git a/packages/evolution/src/core/KeyHash.ts b/packages/evolution/src/core/KeyHash.ts index 26370c4d..0acc3695 100644 --- a/packages/evolution/src/core/KeyHash.ts +++ b/packages/evolution/src/core/KeyHash.ts @@ -30,7 +30,7 @@ export class KeyHashError extends Data.TaggedError("KeyHashError")<{ * @category model */ export class KeyHash extends Schema.TaggedClass()("KeyHash", { - hash: Hash28.BytesSchema + hash: Hash28.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -47,7 +47,7 @@ export class KeyHash extends Schema.TaggedClass()("KeyHash", { * @since 2.0.0 * @category transformer */ -export const FromBytes = Schema.transform(Hash28.BytesSchema, KeyHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex), Schema.typeSchema(KeyHash), { strict: true, decode: (bytes) => new KeyHash({ hash: bytes }, { disableValidation: true }), // Disable validation since we already check length in Hash28 encode: (keyHash) => keyHash.hash @@ -62,7 +62,7 @@ export const FromBytes = Schema.transform(Hash28.BytesSchema, KeyHash, { * @category transformer */ export const FromHex = Schema.compose( - Bytes.FromHex, // string -> Uint8Array + Hash28.BytesFromHex, FromBytes ).annotations({ identifier: "KeyHash.FromHex" diff --git a/packages/evolution/src/core/Metadata.ts b/packages/evolution/src/core/Metadata.ts index db4c7d81..585b8b43 100644 --- a/packages/evolution/src/core/Metadata.ts +++ b/packages/evolution/src/core/Metadata.ts @@ -213,7 +213,7 @@ export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, MetadataError, * @category constructors */ export const fromEntries = (entries: Array<[MetadataLabel, TransactionMetadatum.TransactionMetadatum]>): Metadata => - Schema.decodeSync(Metadata)(new Map(entries)) + (new Map(entries)) /** * Create an empty Metadata map. diff --git a/packages/evolution/src/core/MultiAsset.ts b/packages/evolution/src/core/MultiAsset.ts index 9cdf2065..da60fa4e 100644 --- a/packages/evolution/src/core/MultiAsset.ts +++ b/packages/evolution/src/core/MultiAsset.ts @@ -55,8 +55,8 @@ export type AssetMap = typeof AssetMap.Type * @category schemas */ export const MultiAsset = Schema.MapFromSelf({ - key: PolicyId.PolicyId, - value: AssetMap + key: Schema.typeSchema(PolicyId.PolicyId), + value: Schema.typeSchema(AssetMap) }) .pipe(Schema.filter((map) => map.size > 0)) .pipe(Schema.brand("MultiAsset")) diff --git a/packages/evolution/src/core/NonnegativeInterval.ts b/packages/evolution/src/core/NonnegativeInterval.ts index 816d7d93..17fccec4 100644 --- a/packages/evolution/src/core/NonnegativeInterval.ts +++ b/packages/evolution/src/core/NonnegativeInterval.ts @@ -41,7 +41,7 @@ export const CDDLSchema = CBOR.tag(30, Schema.Tuple(CBOR.Integer, CBOR.Integer)) /** * Transform between tag(30) tuple and NonnegativeInterval model. */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, NonnegativeInterval, { +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(NonnegativeInterval), { strict: true, encode: (_, __, ___, interval) => Effect.succeed({ @@ -59,7 +59,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, NonnegativeInterval, const [numerator, denominator] = yield* ParseResult.decodeUnknown(Schema.Tuple(CBOR.Integer, CBOR.Integer))( taggedValue.value ) - return yield* ParseResult.decode(NonnegativeInterval)({ numerator, denominator }) + return NonnegativeInterval.make({ numerator, denominator }) }) }).annotations({ identifier: "NonnegativeInterval.CDDL" }) diff --git a/packages/evolution/src/core/Numeric.ts b/packages/evolution/src/core/Numeric.ts index d2709653..aa52ca74 100644 --- a/packages/evolution/src/core/Numeric.ts +++ b/packages/evolution/src/core/Numeric.ts @@ -20,7 +20,7 @@ export const UINT8_MAX = 255n * @since 2.0.0 * @category schemas */ -export const Uint8Schema = Schema.BigIntFromSelf.pipe( +export const Uint8Schema = Schema.BigInt.pipe( Schema.filter((value) => value >= UINT8_MIN && value <= UINT8_MAX), Schema.annotations({ identifier: "Uint8", @@ -59,7 +59,7 @@ export const Uint8Generator = FastCheck.bigInt({ export const UINT16_MIN = 0n export const UINT16_MAX = 65535n -export const Uint16Schema = Schema.BigIntFromSelf.pipe( +export const Uint16Schema = Schema.BigInt.pipe( Schema.filter((value) => value >= UINT16_MIN && value <= UINT16_MAX), Schema.annotations({ identifier: "Uint16", @@ -86,7 +86,7 @@ export const Uint16Arbitrary = FastCheck.bigInt({ export const UINT32_MIN = 0n export const UINT32_MAX = 4294967295n -export const Uint32Schema = Schema.BigIntFromSelf.pipe( +export const Uint32Schema = Schema.BigInt.pipe( Schema.filter((value) => value >= UINT32_MIN && value <= UINT32_MAX), Schema.annotations({ identifier: "Uint32", @@ -112,7 +112,7 @@ export const Uint32Arbitrary = FastCheck.bigInt({ export const UINT64_MIN = 0n export const UINT64_MAX = 18446744073709551615n -export const Uint64Schema = Schema.BigIntFromSelf.pipe( +export const Uint64Schema = Schema.BigInt.pipe( Schema.filter((bigint) => bigint >= UINT64_MIN && bigint <= UINT64_MAX), Schema.annotations({ identifier: "Uint64", @@ -138,7 +138,7 @@ export const Uint64Arbitrary = FastCheck.bigInt({ export const INT8_MIN = -128n export const INT8_MAX = 127n -export const Int8 = Schema.BigIntFromSelf.pipe( +export const Int8 = Schema.BigInt.pipe( Schema.filter((value) => value >= INT8_MIN && value <= INT8_MAX), Schema.annotations({ identifier: "Int8", @@ -165,7 +165,7 @@ export const Int8Generator = FastCheck.bigInt({ export const INT16_MIN = -32768n export const INT16_MAX = 32767n -export const Int16 = Schema.BigIntFromSelf.pipe( +export const Int16 = Schema.BigInt.pipe( Schema.filter((value) => value >= INT16_MIN && value <= INT16_MAX), Schema.annotations({ identifier: "Int16", @@ -192,7 +192,7 @@ export const Int16Generator = FastCheck.bigInt({ export const INT32_MIN = -2147483648n export const INT32_MAX = 2147483647n -export const Int32 = Schema.BigIntFromSelf.pipe( +export const Int32 = Schema.BigInt.pipe( Schema.filter((value) => value >= INT32_MIN && value <= INT32_MAX), Schema.annotations({ identifier: "Int32", @@ -219,7 +219,7 @@ export const Int32Generator = FastCheck.bigInt({ export const INT64_MIN = -9223372036854775808n export const INT64_MAX = 9223372036854775807n -export const Int64 = Schema.BigIntFromSelf.pipe( +export const Int64 = Schema.BigInt.pipe( Schema.filter((bigint) => bigint >= INT64_MIN && bigint <= INT64_MAX), Schema.annotations({ identifier: "Int64", diff --git a/packages/evolution/src/core/OperationalCert.ts b/packages/evolution/src/core/OperationalCert.ts index 45deb29b..61ca9a78 100644 --- a/packages/evolution/src/core/OperationalCert.ts +++ b/packages/evolution/src/core/OperationalCert.ts @@ -72,13 +72,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Ope Eff.gen(function* () { const hotVkey = yield* ParseResult.decode(KESVkey.FromBytes)(hotVkeyBytes) const sigma = yield* ParseResult.decode(Ed25519Signature.FromBytes)(sigmaBytes) - return yield* ParseResult.decode(OperationalCert)({ - _tag: "OperationalCert", - hotVkey, - sequenceNumber, - kesPeriod, - sigma - }) + return new OperationalCert({ hotVkey, sequenceNumber, kesPeriod, sigma }) }) }) diff --git a/packages/evolution/src/core/PointerAddress.ts b/packages/evolution/src/core/PointerAddress.ts index 7080faf7..3c019dcd 100644 --- a/packages/evolution/src/core/PointerAddress.ts +++ b/packages/evolution/src/core/PointerAddress.ts @@ -113,7 +113,7 @@ export const decodeVariableLength: ( } }) -export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, PointerAddress, { +export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, Schema.typeSchema(PointerAddress), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { @@ -180,8 +180,7 @@ export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, Point return yield* ParseResult.fail(new ParseResult.Type(ast, fromA, "PointerAddress: unexpected trailing bytes")) } - return yield* ParseResult.decode(PointerAddress)({ - _tag: "PointerAddress", + return PointerAddress.make({ networkId, paymentCredential, pointer: new Pointer.Pointer({ slot, txIndex, certIndex }, { disableValidation: true }) diff --git a/packages/evolution/src/core/PolicyId.ts b/packages/evolution/src/core/PolicyId.ts index 57952191..99279382 100644 --- a/packages/evolution/src/core/PolicyId.ts +++ b/packages/evolution/src/core/PolicyId.ts @@ -26,7 +26,7 @@ export class PolicyIdError extends Data.TaggedError("PolicyIdError")<{ * @category model */ export class PolicyId extends Schema.TaggedClass()("PolicyId", { - hash: Hash28.BytesSchema + hash: Hash28.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -43,7 +43,7 @@ export class PolicyId extends Schema.TaggedClass()("PolicyId", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Hash28.BytesSchema, PolicyId, { +export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex), Schema.typeSchema(PolicyId), { strict: true, decode: (hash) => new PolicyId({ hash }, { disableValidation: true }), encode: (policyId) => policyId.hash @@ -55,7 +55,7 @@ export const FromBytes = Schema.transform(Hash28.BytesSchema, PolicyId, { * @since 2.0.0 * @category schemas */ -export const FromHex = Schema.compose(Hash28.FromHex, FromBytes).annotations({ +export const FromHex = Schema.compose(Hash28.BytesFromHex, FromBytes).annotations({ identifier: "PolicyId.FromHex" }) diff --git a/packages/evolution/src/core/PoolKeyHash.ts b/packages/evolution/src/core/PoolKeyHash.ts index 69840126..96fe415f 100644 --- a/packages/evolution/src/core/PoolKeyHash.ts +++ b/packages/evolution/src/core/PoolKeyHash.ts @@ -22,7 +22,7 @@ export class PoolKeyHashError extends Data.TaggedError("PoolKeyHashError")<{ * @category model */ export class PoolKeyHash extends Schema.TaggedClass()("PoolKeyHash", { - hash: Hash28.BytesSchema + hash: Hash28.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -39,7 +39,7 @@ export class PoolKeyHash extends Schema.TaggedClass()("PoolKeyHash" * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Hash28.BytesSchema, PoolKeyHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex), Schema.typeSchema(PoolKeyHash), { strict: true, decode: (hash) => new PoolKeyHash({ hash }, { disableValidation: true }), encode: (poolKeyHash) => poolKeyHash.hash @@ -51,7 +51,7 @@ export const FromBytes = Schema.transform(Hash28.BytesSchema, PoolKeyHash, { * @since 2.0.0 * @category schemas */ -export const FromHex = Schema.compose(Hash28.FromHex, FromBytes).annotations({ +export const FromHex = Schema.compose(Hash28.BytesFromHex, FromBytes).annotations({ identifier: "PoolKeyHash.FromHex" }) diff --git a/packages/evolution/src/core/PositiveCoin.ts b/packages/evolution/src/core/PositiveCoin.ts index 88435378..8aa281dc 100644 --- a/packages/evolution/src/core/PositiveCoin.ts +++ b/packages/evolution/src/core/PositiveCoin.ts @@ -36,7 +36,7 @@ export const MAX_POSITIVE_COIN_VALUE = Coin.MAX_COIN_VALUE * @since 2.0.0 * @category schemas */ -export const PositiveCoinSchema = Schema.BigIntFromSelf.pipe( +export const PositiveCoinSchema = Schema.BigInt.pipe( Schema.filter((value) => value >= MIN_POSITIVE_COIN_VALUE && value <= MAX_POSITIVE_COIN_VALUE) ).annotations({ message: (issue) => diff --git a/packages/evolution/src/core/PrivateKey.ts b/packages/evolution/src/core/PrivateKey.ts index 9e3cbde5..4b601c59 100644 --- a/packages/evolution/src/core/PrivateKey.ts +++ b/packages/evolution/src/core/PrivateKey.ts @@ -32,22 +32,10 @@ export class PrivateKeyError extends Data.TaggedError("PrivateKeyError")<{ * @category schemas */ export class PrivateKey extends Schema.TaggedClass()("PrivateKey", { - key: Schema.Union(Bytes64.BytesSchema, Bytes32.BytesSchema) -}) { - toJSON(): string { - return toHex(this) - } - - toString(): string { - return toHex(this) - } - - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `PrivateKey(${toHex(this)})` - } -} + key: Schema.Union(Bytes64.BytesFromHex, Bytes32.BytesFromHex) +}) {} -export const FromBytes = Schema.transform(Schema.Union(Bytes64.BytesSchema, Bytes32.BytesSchema), PrivateKey, { +export const FromBytes = Schema.transform(Schema.typeSchema(Schema.Union(Bytes64.BytesFromHex, Bytes32.BytesFromHex)), Schema.typeSchema(PrivateKey), { strict: true, decode: (bytes) => new PrivateKey({ key: bytes }), encode: (privateKey) => privateKey.key @@ -62,7 +50,7 @@ export const FromHex = Schema.compose( identifier: "PrivateKey.FromHex" }) -export const FromBech32 = Schema.transformOrFail(Schema.String, PrivateKey, { +export const FromBech32 = Schema.transformOrFail(Schema.String, Schema.typeSchema(PrivateKey), { strict: true, encode: (_, __, ___, toA) => E.gen(function* () { diff --git a/packages/evolution/src/core/RewardAccount.ts b/packages/evolution/src/core/RewardAccount.ts index 20b455b0..e0df24de 100644 --- a/packages/evolution/src/core/RewardAccount.ts +++ b/packages/evolution/src/core/RewardAccount.ts @@ -32,7 +32,7 @@ export class RewardAccount extends Schema.TaggedClass("RewardAcco } } -export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, RewardAccount, { +export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, Schema.typeSchema(RewardAccount), { strict: true, encode: (_, __, ___, toA) => Eff.gen(function* () { @@ -60,8 +60,7 @@ export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, RewardAccou : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) - return yield* ParseResult.decode(RewardAccount)({ - _tag: "RewardAccount", + return RewardAccount.make({ networkId, stakeCredential }) diff --git a/packages/evolution/src/core/ScriptDataHash.ts b/packages/evolution/src/core/ScriptDataHash.ts index bd5cc675..6765e688 100644 --- a/packages/evolution/src/core/ScriptDataHash.ts +++ b/packages/evolution/src/core/ScriptDataHash.ts @@ -30,7 +30,7 @@ export class ScriptDataHashError extends Data.TaggedError("ScriptDataHashError") * @category model */ export class ScriptDataHash extends Schema.TaggedClass()("ScriptDataHash", { - hash: Bytes32.BytesSchema + hash: Bytes32.BytesFromHex }) {} /** @@ -39,7 +39,7 @@ export class ScriptDataHash extends Schema.TaggedClass()("Script * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesSchema, ScriptDataHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(ScriptDataHash), { strict: true, decode: (bytes) => new ScriptDataHash({ hash: bytes }, { disableValidation: true }), encode: (s) => s.hash @@ -54,7 +54,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, ScriptDataHash, { * @category schemas */ export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> ScriptDataHash ).annotations({ identifier: "ScriptDataHash.FromHex" diff --git a/packages/evolution/src/core/ScriptHash.ts b/packages/evolution/src/core/ScriptHash.ts index 75f69f5a..6aa673a6 100644 --- a/packages/evolution/src/core/ScriptHash.ts +++ b/packages/evolution/src/core/ScriptHash.ts @@ -31,7 +31,7 @@ export class ScriptHashError extends Data.TaggedError("ScriptHashError")<{ * @category schemas */ export class ScriptHash extends Schema.TaggedClass()("ScriptHash", { - hash: Hash28.BytesSchema + hash: Hash28.BytesFromHex }) { toJSON(): string { return toHex(this) @@ -48,7 +48,7 @@ export class ScriptHash extends Schema.TaggedClass()("ScriptHash", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Hash28.BytesSchema, ScriptHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex), Schema.typeSchema(ScriptHash), { strict: true, decode: (bytes) => new ScriptHash({ hash: bytes }, { disableValidation: true }), // Disable validation since we already check length in Hash28 encode: (scriptHash) => scriptHash.hash @@ -63,7 +63,7 @@ export const FromBytes = Schema.transform(Hash28.BytesSchema, ScriptHash, { * @category schemas */ export const FromHex = Schema.compose( - Bytes.FromHex, // string -> Uint8Array + Hash28.BytesFromHex, FromBytes ).annotations({ identifier: "ScriptHash.FromHex" diff --git a/packages/evolution/src/core/ScriptRef.ts b/packages/evolution/src/core/ScriptRef.ts index 3540d6c7..9d3e1742 100644 --- a/packages/evolution/src/core/ScriptRef.ts +++ b/packages/evolution/src/core/ScriptRef.ts @@ -30,7 +30,7 @@ export class ScriptRefError extends Data.TaggedError("ScriptRefError")<{ * @category schemas */ export class ScriptRef extends Schema.TaggedClass()("ScriptRef", { - bytes: Schema.Uint8ArrayFromSelf + bytes: Schema.Uint8ArrayFromHex.pipe(Schema.filter((b) => b.length > 0)) }) { toJSON(): string { return toHex(this) @@ -47,7 +47,7 @@ export class ScriptRef extends Schema.TaggedClass()("ScriptRef", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Schema.Uint8ArrayFromSelf, ScriptRef, { +export const FromBytes = Schema.transform(Schema.Uint8ArrayFromSelf, Schema.typeSchema(ScriptRef), { strict: true, decode: (bytes) => new ScriptRef({ bytes }, { disableValidation: true }), encode: (s) => s.bytes @@ -82,7 +82,7 @@ export const CDDLSchema = CBOR.tag(24, Schema.Uint8ArrayFromSelf) * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, ScriptRef, { +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(ScriptRef), { strict: true, encode: (_, __, ___, toA) => Effect.succeed({ diff --git a/packages/evolution/src/core/SingleHostAddr.ts b/packages/evolution/src/core/SingleHostAddr.ts index 7ca6216c..bab0f0c3 100644 --- a/packages/evolution/src/core/SingleHostAddr.ts +++ b/packages/evolution/src/core/SingleHostAddr.ts @@ -126,7 +126,7 @@ export const FromCDDL = Schema.transformOrFail( Eff.gen(function* () { const port = Option.isSome(toA.port) ? BigInt(toA.port.value) : null - const ipv4 = Option.isSome(toA.ipv4) ? yield* ParseResult.encode(IPv4.FromBytes)(toA.ipv4.value) : null + const ipv4 = Option.isSome(toA.ipv4) ? toA.ipv4.value.bytes : null const ipv6 = Option.isSome(toA.ipv6) ? yield* ParseResult.encode(IPv6.FromBytes)(toA.ipv6.value) : null @@ -139,14 +139,14 @@ export const FromCDDL = Schema.transformOrFail( const ipv4 = ipv4Value === null || ipv4Value === undefined ? Option.none() - : Option.some(yield* ParseResult.decode(IPv4.FromBytes)(ipv4Value)) + : Option.some(yield* ParseResult.decode(Schema.typeSchema(IPv4.IPv4))({ _tag: "IPv4", bytes: ipv4Value })) const ipv6 = ipv6Value === null || ipv6Value === undefined ? Option.none() : Option.some(yield* ParseResult.decode(IPv6.FromBytes)(ipv6Value)) - return yield* Eff.succeed(new SingleHostAddr({ port, ipv4, ipv6 })) + return new SingleHostAddr({ port, ipv4, ipv6 }, { disableValidation: true }) }) } ).annotations({ diff --git a/packages/evolution/src/core/SingleHostName.ts b/packages/evolution/src/core/SingleHostName.ts index 5103ec96..0e652c0d 100644 --- a/packages/evolution/src/core/SingleHostName.ts +++ b/packages/evolution/src/core/SingleHostName.ts @@ -147,14 +147,11 @@ export const FromCDDL = Schema.transformOrFail( }), decode: ([, portValue, dnsNameValue]) => Effect.gen(function* () { - const port = - portValue === null || portValue === undefined - ? Option.none() - : Option.some(yield* ParseResult.decode(Port.PortSchema)(portValue)) + const port = portValue === null || portValue === undefined ? Option.none() : Option.some(Port.make(portValue)) const dnsName = yield* ParseResult.decode(DnsName.DnsName)(dnsNameValue) - return yield* Effect.succeed(new SingleHostName({ port, dnsName })) + return new SingleHostName({ port, dnsName }) }) } ) diff --git a/packages/evolution/src/core/TransactionBody.ts b/packages/evolution/src/core/TransactionBody.ts index 237fc8b5..71aa8d31 100644 --- a/packages/evolution/src/core/TransactionBody.ts +++ b/packages/evolution/src/core/TransactionBody.ts @@ -74,41 +74,7 @@ export class TransactionBody extends Schema.TaggedClass()("Tran proposalProcedures: Schema.optional(ProposalProcedures.ProposalProcedures), // 20 currentTreasuryValue: Schema.optional(Coin.Coin), // 21 donation: Schema.optional(PositiveCoin.PositiveCoinSchema) // 22 -}) { - toString(): string { - const fields: Array = [] - - // Required fields - fields.push(`inputs: [${this.inputs.join(", ")}]`) - fields.push(`outputs: [${this.outputs.join(", ")}]`) - fields.push(`fee: ${this.fee}`) - - // Optional fields (only show if present) - if (this.ttl !== undefined) fields.push(`ttl: ${this.ttl}`) - if (this.certificates !== undefined) fields.push(`certificates: [${this.certificates.join(", ")}]`) - if (this.withdrawals !== undefined) fields.push(`withdrawals: ${this.withdrawals}`) - if (this.auxiliaryDataHash !== undefined) fields.push(`auxiliaryDataHash: ${this.auxiliaryDataHash}`) - if (this.validityIntervalStart !== undefined) fields.push(`validityIntervalStart: ${this.validityIntervalStart}`) - if (this.mint !== undefined) fields.push(`mint: ${this.mint}`) - if (this.scriptDataHash !== undefined) fields.push(`scriptDataHash: ${this.scriptDataHash}`) - if (this.collateralInputs !== undefined) fields.push(`collateralInputs: [${this.collateralInputs.join(", ")}]`) - if (this.requiredSigners !== undefined) fields.push(`requiredSigners: [${this.requiredSigners.join(", ")}]`) - if (this.networkId !== undefined) fields.push(`networkId: ${this.networkId}`) - if (this.collateralReturn !== undefined) fields.push(`collateralReturn: ${this.collateralReturn}`) - if (this.totalCollateral !== undefined) fields.push(`totalCollateral: ${this.totalCollateral}`) - if (this.referenceInputs !== undefined) fields.push(`referenceInputs: [${this.referenceInputs.join(", ")}]`) - if (this.votingProcedures !== undefined) fields.push(`votingProcedures: ${this.votingProcedures}`) - if (this.proposalProcedures !== undefined) fields.push(`proposalProcedures: ${this.proposalProcedures}`) - if (this.currentTreasuryValue !== undefined) fields.push(`currentTreasuryValue: ${this.currentTreasuryValue}`) - if (this.donation !== undefined) fields.push(`donation: ${this.donation}`) - - return `TransactionBody { ${fields.join(", ")} }` - } - - [Symbol.for("nodejs.util.inspect.custom")](): string { - return this.toString() - } -} +}) {} export const make = (...args: ConstructorParameters) => new TransactionBody(...args) @@ -138,8 +104,8 @@ const encodeProposalProcedure = ParseResult.encodeEither(ProposalProcedure.FromC const decodeProposalProcedure = ParseResult.decodeEither(ProposalProcedure.FromCDDL) const encodeRewardAccountBytes = ParseResult.encodeEither(RewardAccount.FromBytes) const decodeRewardAccountBytes = ParseResult.decodeEither(RewardAccount.FromBytes) -const decodeAuxiliaryDataHash = ParseResult.decodeEither(AuxiliaryDataHash.BytesSchema) -const decodeScriptDataHash = ParseResult.decodeEither(ScriptDataHash.FromBytes) +const decodeAuxiliaryDataHash = ParseResult.decodeEither(Schema.typeSchema(AuxiliaryDataHash.AuxiliaryDataHash)) +const decodeScriptDataHash = ParseResult.decodeEither(Schema.typeSchema(ScriptDataHash.ScriptDataHash)) const decodeKeyHash = ParseResult.decodeEither(KeyHash.FromBytes) const decodeInputs = ParseResult.decodeUnknownEither(CBOR.tag(258, Schema.Array(TransactionInput.FromCDDL))) @@ -312,13 +278,15 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra const auxiliaryDataHashBytes = fromA.get(7n) as Uint8Array | undefined const auxiliaryDataHash = auxiliaryDataHashBytes - ? yield* decodeAuxiliaryDataHash(auxiliaryDataHashBytes) + ? yield* decodeAuxiliaryDataHash({ _tag: "AuxiliaryDataHash", bytes: auxiliaryDataHashBytes }) : undefined const validityIntervalStart = fromA.get(8n) as bigint | undefined const mintData = fromA.get(9n) as typeof Mint.CDDLSchema.Type | undefined const mint = mintData ? yield* decodeMint(mintData) : undefined const scriptDataHashBytes = fromA.get(11n) as Uint8Array | undefined - const scriptDataHash = scriptDataHashBytes ? yield* decodeScriptDataHash(scriptDataHashBytes) : undefined + const scriptDataHash = scriptDataHashBytes + ? yield* decodeScriptDataHash({ _tag: "ScriptDataHash", hash: scriptDataHashBytes }) + : undefined const collateralInputsTag = fromA.get(13n) as | { @@ -536,7 +504,7 @@ export const arbitrary: FastCheck.Arbitrary = maxLength: 5, selector: (i) => `${Bytes.toHexUnsafe(i.transactionId.hash)}:${i.index.toString()}` }), - outputs: FastCheck.array(TransactionOutput.arbitrary(), { minLength: 1, maxLength: 5 }), + outputs: FastCheck.array(TransactionOutput.arbitrary, { minLength: 1, maxLength: 5 }), fee: Coin.arbitrary, networkId: FastCheck.option(FastCheck.integer({ min: 0, max: 1 }).map(NetworkId.make), { nil: undefined }), // Optional extra (added first for iterative hardening) @@ -583,7 +551,7 @@ export const arbitrary: FastCheck.Arbitrary = { nil: undefined } ), // Eleventh optional extra: collateral_return (transaction_output) - collateralReturn: FastCheck.option(TransactionOutput.arbitrary(), { nil: undefined }), + collateralReturn: FastCheck.option(TransactionOutput.arbitrary, { nil: undefined }), // Twelfth optional extra: total_collateral (coin) totalCollateral: FastCheck.option(Coin.arbitrary, { nil: undefined }), // Thirteenth optional extra: voting_procedures diff --git a/packages/evolution/src/core/TransactionHash.ts b/packages/evolution/src/core/TransactionHash.ts index 8572bd71..6f78fd38 100644 --- a/packages/evolution/src/core/TransactionHash.ts +++ b/packages/evolution/src/core/TransactionHash.ts @@ -22,12 +22,8 @@ export class TransactionHashError extends Data.TaggedError("TransactionHashError * @category schemas */ export class TransactionHash extends Schema.TaggedClass()("TransactionHash", { - hash: Bytes32.BytesSchema + hash: Bytes32.BytesFromHex }) { - toJSON(): string { - return toHex(this) - } - toString(): string { return `TransactionHash { hash: ${this.hash} }` } @@ -43,7 +39,7 @@ export class TransactionHash extends Schema.TaggedClass()("Tran * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesSchema, TransactionHash, { +export const FromBytes = Schema.transform(Bytes32.BytesFromHex, Schema.typeSchema(TransactionHash), { strict: true, decode: (bytes) => new TransactionHash({ hash: bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 encode: (txHash) => txHash.hash @@ -51,19 +47,6 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, TransactionHash, identifier: "TransactionHash.FromBytes" }) -/** - * Schema for transforming between hex string and TransactionHash. - * - * @since 2.0.0 - * @category schemas - */ -export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 - FromBytes // Bytes32 -> TransactionHash -).annotations({ - identifier: "TransactionHash.FromHex" -}) - /** * Smart constructor for TransactionHash that validates and applies branding. * @@ -111,14 +94,6 @@ export const arbitrary = FastCheck.uint8Array({ */ export const fromBytes = Function.makeDecodeSync(FromBytes, TransactionHashError, "TransactionHash.fromBytes") -/** - * Parse TransactionHash from hex string. - * - * @since 2.0.0 - * @category parsing - */ -export const fromHex = Function.makeDecodeSync(FromHex, TransactionHashError, "TransactionHash.fromHex") - /** * Encode TransactionHash to bytes. * @@ -133,7 +108,7 @@ export const toBytes = Function.makeEncodeSync(FromBytes, TransactionHashError, * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(FromHex, TransactionHashError, "TransactionHash.toHex") +export const toHex = Function.makeEncodeSync(TransactionHash, TransactionHashError, "TransactionHash.toHex") // ============================================================================ // Effect Namespace @@ -160,7 +135,7 @@ export namespace Either { * @since 2.0.0 * @category parsing */ - export const fromHex = Function.makeDecodeEither(FromHex, TransactionHashError) + export const fromHex = Function.makeDecodeEither(TransactionHash, TransactionHashError) /** * Encode TransactionHash to bytes with Effect error handling. @@ -176,5 +151,5 @@ export namespace Either { * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(FromHex, TransactionHashError) + export const toHex = Function.makeEncodeEither(TransactionHash, TransactionHashError) } diff --git a/packages/evolution/src/core/TransactionOutput.ts b/packages/evolution/src/core/TransactionOutput.ts index 3ba5e6bf..270afe3e 100644 --- a/packages/evolution/src/core/TransactionOutput.ts +++ b/packages/evolution/src/core/TransactionOutput.ts @@ -46,7 +46,8 @@ const decScriptRef = ParseResult.decodeUnknownEither(ScriptRef.FromCDDL) export class ShelleyTransactionOutput extends Schema.TaggedClass()( "ShelleyTransactionOutput", { - address: Schema.Union(BaseAddress.BaseAddress, EnterpriseAddress.EnterpriseAddress), + address: AddressEras.FromBech32, + // Schema.Union(BaseAddress.BaseAddress, EnterpriseAddress.EnterpriseAddress), amount: Value.Value, datumHash: Schema.optional(DatumOption.DatumHash) } @@ -77,7 +78,7 @@ export class ShelleyTransactionOutput extends Schema.TaggedClass()( "BabbageTransactionOutput", { - address: Schema.Union(BaseAddress.BaseAddress, EnterpriseAddress.EnterpriseAddress), + address: AddressEras.FromBech32, amount: Value.Value, // 1 datumOption: Schema.optional(DatumOption.DatumOptionSchema), // 2 scriptRef: Schema.optional(ScriptRef.ScriptRef) // 3 @@ -322,23 +323,22 @@ export const makeBabbage = (...args: ConstructorParameters => - FastCheck.oneof( - // Shelley TransactionOutput - FastCheck.record({ - address: FastCheck.oneof(BaseAddress.arbitrary, EnterpriseAddress.arbitrary), - amount: Value.arbitrary, - datumHash: FastCheck.option(DatumOption.datumHashArbitrary, { nil: undefined }) - }).map((props) => new ShelleyTransactionOutput(props)), - - // Babbage TransactionOutput - FastCheck.record({ - address: FastCheck.oneof(BaseAddress.arbitrary, EnterpriseAddress.arbitrary), - amount: Value.arbitrary, - datumOption: FastCheck.option(DatumOption.arbitrary, { nil: undefined }), - scriptRef: FastCheck.option(ScriptRef.arbitrary, { nil: undefined }) - }).map((props) => new BabbageTransactionOutput(props)) - ) +export const arbitrary = FastCheck.oneof( + // Shelley TransactionOutput + FastCheck.record({ + address: FastCheck.oneof(BaseAddress.arbitrary, EnterpriseAddress.arbitrary), + amount: Value.arbitrary, + datumHash: FastCheck.option(DatumOption.datumHashArbitrary, { nil: undefined }) + }).map((props) => new ShelleyTransactionOutput(props)), + + // Babbage TransactionOutput + FastCheck.record({ + address: FastCheck.oneof(BaseAddress.arbitrary, EnterpriseAddress.arbitrary), + amount: Value.arbitrary, + datumOption: FastCheck.option(DatumOption.arbitrary, { nil: undefined }), + scriptRef: FastCheck.option(ScriptRef.arbitrary, { nil: undefined }) + }).map((props) => new BabbageTransactionOutput(props)) +) /** * Convert TransactionOutput to CBOR bytes (unsafe). diff --git a/packages/evolution/src/core/TransactionWitnessSet.ts b/packages/evolution/src/core/TransactionWitnessSet.ts index a82a3e6f..a08ed663 100644 --- a/packages/evolution/src/core/TransactionWitnessSet.ts +++ b/packages/evolution/src/core/TransactionWitnessSet.ts @@ -311,7 +311,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra witnessSet.plutusV3Scripts = plutusV3Scripts } - return yield* ParseResult.decode(TransactionWitnessSet)(witnessSet) + return yield* ParseResult.decode(Schema.typeSchema(TransactionWitnessSet))(witnessSet) }) }) diff --git a/packages/evolution/src/core/UnitInterval.ts b/packages/evolution/src/core/UnitInterval.ts index b736f014..5e4fe0a3 100644 --- a/packages/evolution/src/core/UnitInterval.ts +++ b/packages/evolution/src/core/UnitInterval.ts @@ -86,7 +86,7 @@ export const CDDLSchema = CBOR.tag(30, Schema.Tuple(CBOR.Integer, CBOR.Integer)) * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, UnitInterval, { +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(UnitInterval), { strict: true, encode: (_, __, ___, unitInterval) => Effect.succeed({ @@ -113,10 +113,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, UnitInterval, { const [numerator, denominator] = tupleValue // Create and validate UnitInterval using the validated schema - return yield* ParseResult.decode(UnitInterval)({ - numerator, - denominator - }) + return UnitInterval.make({ numerator, denominator }) }) }).annotations({ identifier: "UnitInterval.CDDL" diff --git a/packages/evolution/src/core/VKey.ts b/packages/evolution/src/core/VKey.ts index 2a3c8a6d..291fd461 100644 --- a/packages/evolution/src/core/VKey.ts +++ b/packages/evolution/src/core/VKey.ts @@ -26,10 +26,10 @@ export class VKeyError extends Data.TaggedError("VKeyError")<{ * @category schemas */ export class VKey extends Schema.TaggedClass()("VKey", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} -export const FromBytes = Schema.transform(Bytes32.BytesSchema, VKey, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(VKey), { strict: true, decode: (bytes) => new VKey({ bytes }), encode: (vkey) => vkey.bytes @@ -38,7 +38,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, VKey, { }) export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> VKey ).annotations({ identifier: "VKey.FromHex" diff --git a/packages/evolution/src/core/VotingProcedures.ts b/packages/evolution/src/core/VotingProcedures.ts index 0c192a77..cba9d104 100644 --- a/packages/evolution/src/core/VotingProcedures.ts +++ b/packages/evolution/src/core/VotingProcedures.ts @@ -99,20 +99,16 @@ export const VoterFromCDDL = Schema.transformOrFail(VoterCDDL, Schema.typeSchema switch (voter._tag) { case "ConstitutionalCommitteeVoter": { if (voter.credential._tag === "KeyHash") { - const keyHashBytes = yield* ParseResult.encode(KeyHash.FromBytes)(voter.credential) - return [0n, keyHashBytes] as const + return [0n, voter.credential.hash] as const } else { - const scriptHashBytes = yield* ParseResult.encode(ScriptHash.FromBytes)(voter.credential) - return [1n, scriptHashBytes] as const + return [1n, voter.credential.hash] as const } } case "DRepVoter": { if (voter.drep._tag === "KeyHashDRep") { - const keyHashBytes = yield* ParseResult.encode(KeyHash.FromBytes)(voter.drep.keyHash) - return [2n, keyHashBytes] as const + return [2n, voter.drep.keyHash.hash] as const } else if (voter.drep._tag === "ScriptHashDRep") { - const scriptHashBytes = yield* ParseResult.encode(ScriptHash.FromBytes)(voter.drep.scriptHash) - return [3n, scriptHashBytes] as const + return [3n, voter.drep.scriptHash.hash] as const } else { return yield* ParseResult.fail( new ParseResult.Type(VoterCDDL.ast, voter, "Always* DRep variants are not valid Voter identifiers") @@ -120,8 +116,7 @@ export const VoterFromCDDL = Schema.transformOrFail(VoterCDDL, Schema.typeSchema } } case "StakePoolVoter": { - const poolKeyHashBytes = yield* ParseResult.encode(PoolKeyHash.FromBytes)(voter.poolKeyHash) - return [4n, poolKeyHashBytes] as const + return [4n, voter.poolKeyHash.hash] as const } } }), @@ -668,12 +663,12 @@ export const arbitrary = FastCheck.array( FastCheck.tuple( // Create GovActionId instances using proper branded types FastCheck.tuple( - FastCheck.hexaString({ minLength: 64, maxLength: 64 }), + FastCheck.uint8Array({ minLength: 32, maxLength: 32 }), FastCheck.bigInt({ min: 0n, max: 65535n }) ).map( ([transactionId, govActionIndex]) => new GovernanceAction.GovActionId({ - transactionId: TransactionHash.fromHex(transactionId), + transactionId: TransactionHash.make({ hash: transactionId }), govActionIndex: TransactionIndex.make(govActionIndex) }) ), diff --git a/packages/evolution/src/core/VrfCert.ts b/packages/evolution/src/core/VrfCert.ts index 43d14622..a85b0da2 100644 --- a/packages/evolution/src/core/VrfCert.ts +++ b/packages/evolution/src/core/VrfCert.ts @@ -25,7 +25,7 @@ export class VrfCertError extends Data.TaggedError("VrfCertError")<{ * @category schemas */ export class VRFOutput extends Schema.TaggedClass()("VrfOutput", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} /** @@ -35,7 +35,7 @@ export class VRFOutput extends Schema.TaggedClass()("VrfOutput", { * @since 2.0.0 * @category schemas */ -export const VRFOutputFromBytes = Schema.transform(Bytes32.BytesSchema, VRFOutput, { +export const VRFOutputFromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(VRFOutput), { strict: true, decode: (bytes) => new VRFOutput({ bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 encode: (vrfOutput) => vrfOutput.bytes @@ -51,7 +51,7 @@ export const VRFOutputFromBytes = Schema.transform(Bytes32.BytesSchema, VRFOutpu * @category schemas */ export const VRFOutputHexSchema = Schema.compose( - Bytes32.FromHex, // string -> hex string + Bytes32.BytesFromHex, // string -> hex string VRFOutputFromBytes // hex string -> VRFOutput ).annotations({ identifier: "VrfOutput.Hex" diff --git a/packages/evolution/src/core/VrfKeyHash.ts b/packages/evolution/src/core/VrfKeyHash.ts index 95a05943..1b81dfd6 100644 --- a/packages/evolution/src/core/VrfKeyHash.ts +++ b/packages/evolution/src/core/VrfKeyHash.ts @@ -22,7 +22,7 @@ export class VrfKeyHashError extends Data.TaggedError("VrfKeyHashError")<{ * @category schemas */ export class VrfKeyHash extends Schema.TaggedClass()("VrfKeyHash", { - hash: Bytes32.BytesSchema + hash: Bytes32.BytesFromHex }) { toJSON(): string { return Bytes32.toHex(this.hash) @@ -33,7 +33,7 @@ export class VrfKeyHash extends Schema.TaggedClass()("VrfKeyHash", { } } -export const FromBytes = Schema.transform(Bytes32.BytesSchema, VrfKeyHash, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(VrfKeyHash), { strict: true, decode: (bytes) => new VrfKeyHash({ hash: bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 encode: (vrfKeyHash) => vrfKeyHash.hash @@ -42,7 +42,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, VrfKeyHash, { }) export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> hex string + Bytes32.BytesFromHex, // string -> hex string FromBytes // hex string -> VrfKeyHash ).annotations({ identifier: "VrfKeyHash.FromHex" diff --git a/packages/evolution/src/core/VrfVkey.ts b/packages/evolution/src/core/VrfVkey.ts index 6d6dd976..9d866595 100644 --- a/packages/evolution/src/core/VrfVkey.ts +++ b/packages/evolution/src/core/VrfVkey.ts @@ -24,10 +24,10 @@ export class VrfVkeyError extends Data.TaggedError("VrfVkeyError")<{ * @category schemas */ export class VrfVkey extends Schema.TaggedClass()("VrfVkey", { - bytes: Bytes32.BytesSchema + bytes: Bytes32.BytesFromHex }) {} -export const FromBytes = Schema.transform(Bytes32.BytesSchema, VrfVkey, { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(VrfVkey), { strict: true, decode: (bytes) => new VrfVkey({ bytes }), encode: (vrfVkey) => vrfVkey.bytes @@ -36,7 +36,7 @@ export const FromBytes = Schema.transform(Bytes32.BytesSchema, VrfVkey, { }) export const FromHex = Schema.compose( - Bytes32.FromHex, // string -> Bytes32 + Bytes32.BytesFromHex, // string -> Bytes32 FromBytes // Bytes32 -> VrfVkey ).annotations({ identifier: "VrfVkey.FromHex" diff --git a/packages/evolution/src/sdk/Address.ts b/packages/evolution/src/sdk/Address.ts index 3961b7e0..35a6ef06 100644 --- a/packages/evolution/src/sdk/Address.ts +++ b/packages/evolution/src/sdk/Address.ts @@ -1,52 +1,99 @@ +import { Data, ParseResult, pipe, Schema } from "effect" + import * as CoreAddressStructure from "../core/AddressStructure.js" -import * as Credential from "./Credential.js" + +export class AddressError extends Data.TaggedError("AddressError")<{ + message?: string + cause?: unknown +}> {} // bech32 export type Address = string -export const toCoreAddress = (address: Address): CoreAddressStructure.AddressStructure => - CoreAddressStructure.fromBech32(address) - -export const fromCoreAddress = (address: CoreAddressStructure.AddressStructure): Address => - CoreAddressStructure.toBech32(address) - -export const withCredentials = ({ - network, - paymentCredential, - stakingCredential -}: { - network: "Mainnet" | "Testnet" - paymentCredential: Credential.Credential - stakingCredential?: Credential.Credential -}) => { - const coreAddress = new CoreAddressStructure.AddressStructure({ - networkId: network === "Mainnet" ? 1 : 0, // Fix: 1 for Mainnet, 0 for Testnet - paymentCredential: Credential.toCoreCredential(paymentCredential), - stakingCredential: stakingCredential ? Credential.toCoreCredential(stakingCredential) : undefined - }) - return CoreAddressStructure.toBech32(coreAddress) -} - -/** - * Create an enterprise address (payment only) from a payment credential. - * An enterprise address has only a payment credential and no staking credential. - * - * @example - * ```typescript - * const credential = { type: "Key", hash: "abcd1234..." } - * const address = fromPaymentCredential(credential, "Mainnet") - * // Returns: "addr1w9rlm65wjfkctdlyxl5cw87h9... - * ``` - */ -export const fromPaymentCredential = ( - paymentCredential: Credential.Credential, - network: Credential.Network = "Mainnet" -): Address => { - const coreAddress = new CoreAddressStructure.AddressStructure({ - networkId: network === "Mainnet" ? 1 : 0, - paymentCredential: Credential.toCoreCredential(paymentCredential), - // No staking credential for enterprise addresses - stakingCredential: undefined - }) - return CoreAddressStructure.toBech32(coreAddress) -} +export const toAddressStructure = Schema.decodeSync(CoreAddressStructure.FromBech32) +export const fromAddressStructure = Schema.encodeSync(CoreAddressStructure.FromBech32) + +export const fromStruct = Schema.decodeSync(CoreAddressStructure.AddressStructure) +export const toStruct = Schema.encodeSync(CoreAddressStructure.AddressStructure) + + +// export const FromAddressStructure = Schema.transformOrFail( +// Schema.typeSchema(CoreAddressStructure.AddressStructure), +// Schema.String, +// { +// strict: true, +// encode: (address) => ParseResult.decodeEither(CoreAddressStructure.FromBech32)(address), +// decode: (value) => ParseResult.encodeEither(CoreAddressStructure.FromBech32)(value) +// } +// ) + +// export const toCoreAddress = Function.makeEncodeSync(FromAddressStructure, AddressError, "toCoreAddress") + +// export const fromCoreAddress = Function.makeDecodeSync(FromAddressStructure, AddressError, "fromCoreAddress") + +// export const fromCredentials = ({ +// network = "Mainnet", +// paymentCredential, +// stakingCredential +// }: { +// network?: "Mainnet" | "Testnet" +// paymentCredential: Credential.Credential +// stakingCredential: Credential.Credential +// }): Either.Either => { +// const payment = CoreCredential.Either.fromCBORHex(paymentCredential.hash) +// if (Either.isLeft(payment)) { +// return Either.left(new AddressError({ message: "Invalid payment credential", cause: payment.left })) +// } +// const staking = CoreCredential.Either.fromCBORHex(stakingCredential.hash) +// if (Either.isLeft(staking)) { +// return Either.left(new AddressError({ message: "Invalid staking credential", cause: staking.left })) +// } + +// const coreAddress = new CoreAddressStructure.AddressStructure({ +// networkId: network === "Mainnet" ? 1 : 0, +// paymentCredential: payment.right, +// stakingCredential: staking.right +// }) +// return Either.right(CoreAddressStructure.toBech32(coreAddress)) +// } + +// export const withCredentials = ({ +// network, +// paymentCredential, +// stakingCredential +// }: { +// network: "Mainnet" | "Testnet" +// paymentCredential: Credential.Credential +// stakingCredential?: Credential.Credential +// }) => { +// const coreAddress = new CoreAddressStructure.AddressStructure({ +// networkId: network === "Mainnet" ? 1 : 0, // Fix: 1 for Mainnet, 0 for Testnet +// paymentCredential: Credential.toCoreCredential(paymentCredential), +// stakingCredential: stakingCredential ? Credential.toCoreCredential(stakingCredential) : undefined +// }) +// return CoreAddressStructure.toBech32(coreAddress) +// } + +// /** +// * Create an enterprise address (payment only) from a payment credential. +// * An enterprise address has only a payment credential and no staking credential. +// * +// * @example +// * ```typescript +// * const credential = { type: "Key", hash: "abcd1234..." } +// * const address = fromPaymentCredential(credential, "Mainnet") +// * // Returns: "addr1w9rlm65wjfkctdlyxl5cw87h9... +// * ``` +// */ +// export const fromPaymentCredential = ( +// paymentCredential: Credential.Credential, +// network: Credential.Network = "Mainnet" +// ): Address => { +// const coreAddress = new CoreAddressStructure.AddressStructure({ +// networkId: network === "Mainnet" ? 1 : 0, +// paymentCredential: Credential.toCoreCredential(paymentCredential), +// // No staking credential for enterprise addresses +// stakingCredential: undefined +// }) +// return CoreAddressStructure.toBech32(coreAddress) +// } diff --git a/packages/evolution/src/sdk/Credential.ts b/packages/evolution/src/sdk/Credential.ts index 3f3ca1a3..2d9713b8 100644 --- a/packages/evolution/src/sdk/Credential.ts +++ b/packages/evolution/src/sdk/Credential.ts @@ -1,42 +1,53 @@ -import { fromHex } from "../core/Bytes.js" -import * as CoreCredential from "../core/Credential.js" - -export type ScriptHash = { - type: "Script" - hash: string // hex string -} - -export type KeyHash = { - type: "Key" - hash: string // hex string -} - -export type Credential = ScriptHash | KeyHash - -export type Network = "Mainnet" | "Testnet" - -export const toCoreCredential = (credential: Credential): CoreCredential.Credential => { - if (credential.type === "Key") { - return CoreCredential.Credential.members[0].make({ - hash: fromHex(credential.hash) - }) - } else { - return CoreCredential.Credential.members[1].make({ - hash: fromHex(credential.hash) - }) - } -} - -export const fromCoreCredential = (credential: CoreCredential.Credential): Credential => { - if (credential._tag === "KeyHash") { - return { - type: "Key", - hash: credential.toString() - } - } else { - return { - type: "Script", - hash: credential.toString() - } - } -} +import { Schema } from "effect" + +import * as _Credential from "../core/Credential.js" +import type * as _KeyHash from "../core/KeyHash.js" +import type * as _ScriptHash from "../core/ScriptHash.js" + +export type ScriptHash = typeof _ScriptHash.ScriptHash.Encoded +export type KeyHash = typeof _KeyHash.KeyHash.Encoded + +export type Credential = typeof _Credential.Credential.Encoded + +export const toCoreCredential = Schema.decodeSync(_Credential.Credential) +export const fromCoreCredential = Schema.encodeSync(_Credential.Credential) + +// export type ScriptHash = { +// type: "Script" +// hash: string // hex string +// } + +// export type KeyHash = { +// type: "Key" +// hash: string // hex string +// } + +// export type Credential = ScriptHash | KeyHash + +// export type Network = "Mainnet" | "Testnet" + +// export const toCoreCredential = (credential: Credential): CoreCredential.Credential => { +// if (credential.type === "Key") { +// return CoreCredential.Credential.members[0].make({ +// hash: fromHex(credential.hash) +// }) +// } else { +// return CoreCredential.Credential.members[1].make({ +// hash: fromHex(credential.hash) +// }) +// } +// } + +// export const fromCoreCredential = (credential: CoreCredential.Credential): Credential => { +// if (credential._tag === "KeyHash") { +// return { +// type: "Key", +// hash: credential.toString() +// } +// } else { +// return { +// type: "Script", +// hash: credential.toString() +// } +// } +// } diff --git a/packages/evolution/src/sdk/RewardAddress.ts b/packages/evolution/src/sdk/RewardAddress.ts index 848ee116..be5d5bfa 100644 --- a/packages/evolution/src/sdk/RewardAddress.ts +++ b/packages/evolution/src/sdk/RewardAddress.ts @@ -45,13 +45,13 @@ export const fromCoreRewardAccount = (account: CoreRewardAccount.RewardAccount): * // Returns: "stake_test1ur9rlm65wjfkctdlyxl5cw87h9..." * ``` */ -export const fromStakeCredential = ( - stakeCredential: Credential.Credential, - network: Credential.Network = "Mainnet" -): RewardAddress => { - const coreRewardAccount = new CoreRewardAccount.RewardAccount({ - networkId: network === "Mainnet" ? 1 : 0, - stakeCredential: Credential.toCoreCredential(stakeCredential) - }) - return CoreAddressEras.toBech32(coreRewardAccount) -} +// export const fromStakeCredential = ( +// stakeCredential: Credential.Credential, +// network: Credential.Network = "Mainnet" +// ): RewardAddress => { +// const coreRewardAccount = new CoreRewardAccount.RewardAccount({ +// networkId: network === "Mainnet" ? 1 : 0, +// stakeCredential: Credential.toCoreCredential(stakeCredential) +// }) +// return CoreAddressEras.toBech32(coreRewardAccount) +// } diff --git a/packages/evolution/src/sdk/Script.ts b/packages/evolution/src/sdk/Script.ts index 9ee7745f..c9b409e6 100644 --- a/packages/evolution/src/sdk/Script.ts +++ b/packages/evolution/src/sdk/Script.ts @@ -34,36 +34,36 @@ export type SpendingValidator = Script export type MintingPolicy = Script export type PolicyId = string // hex string -/** - * Convert user-facing Script to strict Script type for hash computation. - */ -const toCoreScript = (script: Script): CoreScript.Script => { - const strict = CoreScript.fromCBORHex(script.script) - - switch (script.type) { - case "Native": { - // Native scripts are the union members without a `_tag` property - if ("_tag" in strict) { - throw new Error("Script type mismatch: expected Native") - } - return strict - } - case "PlutusV1": { - if ("_tag" in strict && strict._tag === "PlutusV1") return strict - break - } - case "PlutusV2": { - if ("_tag" in strict && strict._tag === "PlutusV2") return strict - break - } - case "PlutusV3": { - if ("_tag" in strict && strict._tag === "PlutusV3") return strict - break - } - } - - throw new Error(`Script type mismatch: expected ${script.type}`) -} +// /** +// * Convert user-facing Script to strict Script type for hash computation. +// */ +// const toCoreScript = (script: Script): CoreScript.Script => { +// const strict = CoreScript.fromCBORHex(script.script) + +// switch (script.type) { +// case "Native": { +// // Native scripts are the union members without a `_tag` property +// if ("_tag" in strict) { +// throw new Error("Script type mismatch: expected Native") +// } +// return strict +// } +// case "PlutusV1": { +// if ("_tag" in strict && strict._tag === "PlutusV1") return strict +// break +// } +// case "PlutusV2": { +// if ("_tag" in strict && strict._tag === "PlutusV2") return strict +// break +// } +// case "PlutusV3": { +// if ("_tag" in strict && strict._tag === "PlutusV3") return strict +// break +// } +// } + +// throw new Error(`Script type mismatch: expected ${script.type}`) +// } /** * Compute the hash of a script. @@ -74,11 +74,11 @@ const toCoreScript = (script: Script): CoreScript.Script => { * - PlutusV2 scripts: tag 2 * - PlutusV3 scripts: tag 3 */ -export const toScriptHash = (script: Script): Credential.ScriptHash => - pipe(toCoreScript(script), CoreScriptHash.fromScript, CoreScriptHash.toHex, (hash) => ({ - type: "Script", - hash - })) +// export const toScriptHash = (script: Script): Credential.ScriptHash => +// pipe(toCoreScript(script), CoreScriptHash.fromScript, CoreScriptHash.toHex, (hash) => ({ +// type: "Script", +// hash +// })) // Constructor Functions export const makeNativeScript = (cbor: string): Native => ({ @@ -111,7 +111,7 @@ export const scriptEquals = (a: Script, b: Script): boolean => a.type === b.type * Compute the policy ID for a minting policy script. * The policy ID is identical to the script hash. */ -export const mintingPolicyToId = (script: Script): Credential.ScriptHash => toScriptHash(script) +// export const mintingPolicyToId = (script: Script): Credential.ScriptHash => toScriptHash(script) export const applyDoubleCborEncoding = (script: string): string => { // Convert hex string to bytes, then encode as CBOR bytes, then back to hex From 899f026f302afeb128cebfbe464037b9a524567b Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Mon, 8 Sep 2025 20:47:59 -0600 Subject: [PATCH 2/2] feat: upgrade modules --- .../evolution/src/core/AddressStructure.ts | 10 +- packages/evolution/src/core/AssetName.ts | 14 +- packages/evolution/src/core/AuxiliaryData.ts | 10 +- .../evolution/src/core/AuxiliaryDataHash.ts | 14 +- packages/evolution/src/core/BaseAddress.ts | 8 +- .../evolution/src/core/BootstrapWitness.ts | 9 +- packages/evolution/src/core/Bytes32.ts | 1 - packages/evolution/src/core/Bytes64.ts | 1 - packages/evolution/src/core/CBOR.ts | 2 +- packages/evolution/src/core/Certificate.ts | 32 +- packages/evolution/src/core/Coin.ts | 4 +- packages/evolution/src/core/Credential.ts | 14 +- packages/evolution/src/core/Data.ts | 118 +- .../evolution/src/core/EnterpriseAddress.ts | 4 +- .../evolution/src/core/GovernanceAction.ts | 47 +- packages/evolution/src/core/KeyHash.ts | 5 +- packages/evolution/src/core/Metadata.ts | 2 +- packages/evolution/src/core/Mint.ts | 8 +- packages/evolution/src/core/MultiAsset.ts | 31 +- packages/evolution/src/core/NativeScripts.ts | 780 +++++----- .../evolution/src/core/NativeScriptsOLD.ts | 511 +++++++ packages/evolution/src/core/NonZeroInt64.ts | 122 +- packages/evolution/src/core/PlutusV1.ts | 4 +- packages/evolution/src/core/PlutusV2.ts | 4 +- packages/evolution/src/core/PlutusV3.ts | 4 +- packages/evolution/src/core/PointerAddress.ts | 6 +- packages/evolution/src/core/PrivateKey.ts | 14 +- .../evolution/src/core/ProposalProcedure.ts | 2 +- packages/evolution/src/core/RewardAccount.ts | 65 +- packages/evolution/src/core/Script.ts | 120 +- packages/evolution/src/core/ScriptHash.ts | 52 +- packages/evolution/src/core/ScriptRef.ts | 6 +- packages/evolution/src/core/SingleHostAddr.ts | 2 +- packages/evolution/src/core/StakeReference.ts | 2 +- packages/evolution/src/core/TSchema.ts | 92 +- packages/evolution/src/core/Transaction.ts | 125 +- .../evolution/src/core/TransactionBody.ts | 33 +- .../evolution/src/core/TransactionHash.ts | 23 +- .../src/core/TransactionWitnessSet.ts | 156 +- packages/evolution/src/core/Value.ts | 2 +- .../evolution/src/core/VotingProcedures.ts | 6 +- packages/evolution/src/core/VrfCert.ts | 14 +- packages/evolution/src/core/Withdrawals.ts | 4 +- packages/evolution/src/sdk/Address.ts | 94 +- packages/evolution/src/sdk/Assets.ts | 2 +- packages/evolution/src/sdk/Credential.ts | 46 +- packages/evolution/src/sdk/RewardAddress.ts | 54 +- packages/evolution/src/sdk/Script.ts | 5 - .../evolution/test/AuxiliaryData.CML.test.ts | 73 - packages/evolution/test/Data.golden.test.ts | 11 + packages/evolution/test/Data.test.ts | 1281 +++++++++-------- .../evolution/test/NativeScripts.CML.test.ts | 33 +- .../evolution/test/Transaction.CML.test.ts | 31 + .../test/TransactionOutput.CML.test.ts | 2 +- .../test/TransactionWitnessSet.CML.test.ts | 6 +- 55 files changed, 2381 insertions(+), 1740 deletions(-) create mode 100644 packages/evolution/src/core/NativeScriptsOLD.ts create mode 100644 packages/evolution/test/Transaction.CML.test.ts diff --git a/packages/evolution/src/core/AddressStructure.ts b/packages/evolution/src/core/AddressStructure.ts index be2fa1ca..5663e0f0 100644 --- a/packages/evolution/src/core/AddressStructure.ts +++ b/packages/evolution/src/core/AddressStructure.ts @@ -25,8 +25,8 @@ export class AddressStructureError extends Data.TaggedError("AddressStructureErr */ export class AddressStructure extends Schema.Class("AddressStructure")({ networkId: NetworkId.NetworkId, - paymentCredential: Credential.Credential, - stakingCredential: Schema.optional(Credential.Credential) + paymentCredential: Credential.CredentialSchema, + stakingCredential: Schema.optional(Credential.CredentialSchema) }) { toString(): string { const staking = this.stakingCredential ? `, stakingCredential: ${this.stakingCredential}` : "" @@ -84,12 +84,12 @@ export const FromBytes = Schema.transformOrFail( if (fromA.length === 57) { // BaseAddress (with staking credential) const isPaymentKey = (addressTypeBits & 0b0001) === 0 - const paymentCredential: Credential.Credential = isPaymentKey + const paymentCredential: Credential.CredentialSchema = isPaymentKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) const isStakeKey = (addressTypeBits & 0b0010) === 0 - const stakingCredential: Credential.Credential = isStakeKey + const stakingCredential: Credential.CredentialSchema = isStakeKey ? new KeyHash.KeyHash({ hash: fromA.slice(29, 57) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(29, 57) }) @@ -101,7 +101,7 @@ export const FromBytes = Schema.transformOrFail( } else if (fromA.length === 29) { // EnterpriseAddress (no staking credential) const isPaymentKey = (addressTypeBits & 0b0001) === 0 - const paymentCredential: Credential.Credential = isPaymentKey + const paymentCredential: Credential.CredentialSchema = isPaymentKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) diff --git a/packages/evolution/src/core/AssetName.ts b/packages/evolution/src/core/AssetName.ts index 12c5d2b9..e3d0221d 100644 --- a/packages/evolution/src/core/AssetName.ts +++ b/packages/evolution/src/core/AssetName.ts @@ -31,11 +31,15 @@ export class AssetName extends Schema.TaggedClass()("AssetName", { * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.VariableBytesFromHex), Schema.typeSchema(AssetName), { - strict: true, - decode: (bytes) => new AssetName({ bytes }, { disableValidation: true }), - encode: (assetName) => assetName.bytes -}).annotations({ +export const FromBytes = Schema.transform( + Schema.typeSchema(Bytes32.VariableBytesFromHex), + Schema.typeSchema(AssetName), + { + strict: true, + decode: (bytes) => new AssetName({ bytes }, { disableValidation: true }), + encode: (assetName) => assetName.bytes + } +).annotations({ identifier: "AssetName.FromBytes" }) diff --git a/packages/evolution/src/core/AuxiliaryData.ts b/packages/evolution/src/core/AuxiliaryData.ts index 2cc40a3a..f0fec751 100644 --- a/packages/evolution/src/core/AuxiliaryData.ts +++ b/packages/evolution/src/core/AuxiliaryData.ts @@ -43,7 +43,7 @@ export class ConwayAuxiliaryData extends Schema.TaggedClass "ConwayAuxiliaryData", { metadata: Schema.optional(Metadata.Metadata), - nativeScripts: Schema.optional(Schema.Array(NativeScripts.Native)), + nativeScripts: Schema.optional(Schema.Array(NativeScripts.NativeScript)), plutusV1Scripts: Schema.optional(Schema.Array(PlutusV1.PlutusV1)), plutusV2Scripts: Schema.optional(Schema.Array(PlutusV2.PlutusV2)), plutusV3Scripts: Schema.optional(Schema.Array(PlutusV3.PlutusV3)) @@ -65,7 +65,7 @@ export class ShelleyMAAuxiliaryData extends Schema.TaggedClass | undefined + let nativeScripts: Array | undefined if (arr.length >= 1 && arr[0] !== undefined) { const m = yield* ParseResult.decodeEither(Metadata.FromCDDL)(arr[0]) @@ -325,7 +325,7 @@ export const empty = (): AuxiliaryData => new ConwayAuxiliaryData({}) */ export const conway = (input: { metadata?: Metadata.Metadata - nativeScripts?: Array + nativeScripts?: Array plutusV1Scripts?: Array plutusV2Scripts?: Array plutusV3Scripts?: Array @@ -339,7 +339,7 @@ export const conway = (input: { */ export const shelleyMA = (input: { metadata?: Metadata.Metadata - nativeScripts?: Array + nativeScripts?: Array }): AuxiliaryData => new ShelleyMAAuxiliaryData({ ...input }) /** diff --git a/packages/evolution/src/core/AuxiliaryDataHash.ts b/packages/evolution/src/core/AuxiliaryDataHash.ts index 52226d36..8325b6b7 100644 --- a/packages/evolution/src/core/AuxiliaryDataHash.ts +++ b/packages/evolution/src/core/AuxiliaryDataHash.ts @@ -34,11 +34,15 @@ export class AuxiliaryDataHash extends Schema.TaggedClass()(" bytes: Bytes32.BytesFromHex }) {} -export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(AuxiliaryDataHash), { - strict: true, - decode: (bytes) => new AuxiliaryDataHash({ bytes }, { disableValidation: true }), - encode: (a) => a.bytes -}).annotations({ +export const FromBytes = Schema.transform( + Schema.typeSchema(Bytes32.BytesFromHex), + Schema.typeSchema(AuxiliaryDataHash), + { + strict: true, + decode: (bytes) => new AuxiliaryDataHash({ bytes }, { disableValidation: true }), + encode: (a) => a.bytes + } +).annotations({ identifier: "AuxiliaryDataHash.FromBytes" }) diff --git a/packages/evolution/src/core/BaseAddress.ts b/packages/evolution/src/core/BaseAddress.ts index 19aace7b..5a0c1055 100644 --- a/packages/evolution/src/core/BaseAddress.ts +++ b/packages/evolution/src/core/BaseAddress.ts @@ -21,8 +21,8 @@ export class BaseAddressError extends Data.TaggedError("BaseAddressError")<{ */ export class BaseAddress extends Schema.TaggedClass("BaseAddress")("BaseAddress", { networkId: NetworkId.NetworkId, - paymentCredential: Credential.Credential, - stakeCredential: Credential.Credential + paymentCredential: Credential.CredentialSchema, + stakeCredential: Credential.CredentialSchema }) { toString(): string { return `BaseAddress { networkId: ${this.networkId}, paymentCredential: ${this.paymentCredential}, stakeCredential: ${this.stakeCredential} }` @@ -57,7 +57,7 @@ export const FromBytes = Schema.transformOrFail(Bytes57.BytesSchema, Schema.type const addressType = header >> 4 // Script payment, Script stake const isPaymentKey = (addressType & 0b0001) === 0 - const paymentCredential: Credential.Credential = isPaymentKey + const paymentCredential: Credential.CredentialSchema = isPaymentKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) @@ -65,7 +65,7 @@ export const FromBytes = Schema.transformOrFail(Bytes57.BytesSchema, Schema.type hash: fromA.slice(1, 29) }) const isStakeKey = (addressType & 0b0010) === 0 - const stakeCredential: Credential.Credential = isStakeKey + const stakeCredential: Credential.CredentialSchema = isStakeKey ? new KeyHash.KeyHash({ hash: fromA.slice(29, 57) }) diff --git a/packages/evolution/src/core/BootstrapWitness.ts b/packages/evolution/src/core/BootstrapWitness.ts index 960ba53a..ad87cdaf 100644 --- a/packages/evolution/src/core/BootstrapWitness.ts +++ b/packages/evolution/src/core/BootstrapWitness.ts @@ -1,5 +1,6 @@ import { Effect as Eff, FastCheck, ParseResult, Schema } from "effect" +import * as Bytes32 from "./Bytes32.js" import * as CBOR from "./CBOR.js" import * as Ed25519Signature from "./Ed25519Signature.js" import * as Function from "./Function.js" @@ -21,12 +22,8 @@ import * as VKey from "./VKey.js" export class BootstrapWitness extends Schema.Class("BootstrapWitness")({ publicKey: VKey.VKey, signature: Ed25519Signature.Ed25519Signature, - chainCode: Schema.Uint8ArrayFromSelf.pipe( - Schema.filter((bytes) => bytes.length === 32, { - message: () => "Chain code must be exactly 32 bytes" - }) - ), - attributes: Schema.Uint8ArrayFromSelf + chainCode: Bytes32.BytesFromHex, + attributes: Schema.Uint8ArrayFromHex }) {} // Tuple schema as per CDDL diff --git a/packages/evolution/src/core/Bytes32.ts b/packages/evolution/src/core/Bytes32.ts index 84f85866..8570ca0d 100644 --- a/packages/evolution/src/core/Bytes32.ts +++ b/packages/evolution/src/core/Bytes32.ts @@ -31,7 +31,6 @@ export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEqual export const VariableBytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) - export const equals = Bytes.equals // ============================================================================= diff --git a/packages/evolution/src/core/Bytes64.ts b/packages/evolution/src/core/Bytes64.ts index dc535b8f..051c5ac6 100644 --- a/packages/evolution/src/core/Bytes64.ts +++ b/packages/evolution/src/core/Bytes64.ts @@ -18,7 +18,6 @@ export const BYTES_LENGTH = 64 export const BytesFromHex = Schema.Uint8ArrayFromHex.pipe(Bytes.bytesLengthEquals(BYTES_LENGTH)) - export const VariableBytesFromHex = Schema.Uint8ArrayFromSelf.pipe(Bytes.bytesLengthBetween(0, BYTES_LENGTH)) // ============================================================================= diff --git a/packages/evolution/src/core/CBOR.ts b/packages/evolution/src/core/CBOR.ts index 2667f588..31d084a4 100644 --- a/packages/evolution/src/core/CBOR.ts +++ b/packages/evolution/src/core/CBOR.ts @@ -392,7 +392,7 @@ export const CBORSchema: Schema.Schema = Schema.Union( Tag, Simple, Float -) +).annotations({ identifier: "CBOR", description: "CBOR value schema" }) /** * Schema for encoding/decoding CBOR bytes using internal functions diff --git a/packages/evolution/src/core/Certificate.ts b/packages/evolution/src/core/Certificate.ts index 060f8ec1..b992c5ea 100644 --- a/packages/evolution/src/core/Certificate.ts +++ b/packages/evolution/src/core/Certificate.ts @@ -26,18 +26,18 @@ export class CertificateError extends Data.TaggedError("CertificateError")<{ }> {} export class StakeRegistration extends Schema.TaggedClass("StakeRegistration")("StakeRegistration", { - stakeCredential: Credential.Credential + stakeCredential: Credential.CredentialSchema }) {} export class StakeDeregistration extends Schema.TaggedClass("StakeDeregistration")( "StakeDeregistration", { - stakeCredential: Credential.Credential + stakeCredential: Credential.CredentialSchema } ) {} export class StakeDelegation extends Schema.TaggedClass("StakeDelegation")("StakeDelegation", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, poolKeyHash: PoolKeyHash.PoolKeyHash }) {} @@ -51,37 +51,37 @@ export class PoolRetirement extends Schema.TaggedClass("PoolReti }) {} export class RegCert extends Schema.TaggedClass("RegCert")("RegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, coin: Coin.Coin }) {} export class UnregCert extends Schema.TaggedClass("UnregCert")("UnregCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, coin: Coin.Coin }) {} export class VoteDelegCert extends Schema.TaggedClass("VoteDelegCert")("VoteDelegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, drep: DRep.DRep }) {} export class StakeVoteDelegCert extends Schema.TaggedClass("StakeVoteDelegCert")( "StakeVoteDelegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, poolKeyHash: PoolKeyHash.PoolKeyHash, drep: DRep.DRep } ) {} export class StakeRegDelegCert extends Schema.TaggedClass("StakeRegDelegCert")("StakeRegDelegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, poolKeyHash: PoolKeyHash.PoolKeyHash, coin: Coin.Coin }) {} export class VoteRegDelegCert extends Schema.TaggedClass("VoteRegDelegCert")("VoteRegDelegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, drep: DRep.DRep, coin: Coin.Coin }) {} @@ -89,7 +89,7 @@ export class VoteRegDelegCert extends Schema.TaggedClass("Vote export class StakeVoteRegDelegCert extends Schema.TaggedClass("StakeVoteRegDelegCert")( "StakeVoteRegDelegCert", { - stakeCredential: Credential.Credential, + stakeCredential: Credential.CredentialSchema, poolKeyHash: PoolKeyHash.PoolKeyHash, drep: DRep.DRep, coin: Coin.Coin @@ -99,32 +99,32 @@ export class StakeVoteRegDelegCert extends Schema.TaggedClass("AuthCommitteeHotCert")( "AuthCommitteeHotCert", { - committeeColdCredential: Credential.Credential, - committeeHotCredential: Credential.Credential + committeeColdCredential: Credential.CredentialSchema, + committeeHotCredential: Credential.CredentialSchema } ) {} export class ResignCommitteeColdCert extends Schema.TaggedClass("ResignCommitteeColdCert")( "ResignCommitteeColdCert", { - committeeColdCredential: Credential.Credential, + committeeColdCredential: Credential.CredentialSchema, anchor: Schema.NullishOr(Anchor.Anchor) } ) {} export class RegDrepCert extends Schema.TaggedClass("RegDrepCert")("RegDrepCert", { - drepCredential: Credential.Credential, + drepCredential: Credential.CredentialSchema, coin: Coin.Coin, anchor: Schema.NullishOr(Anchor.Anchor) }) {} export class UnregDrepCert extends Schema.TaggedClass("UnregDrepCert")("UnregDrepCert", { - drepCredential: Credential.Credential, + drepCredential: Credential.CredentialSchema, coin: Coin.Coin }) {} export class UpdateDrepCert extends Schema.TaggedClass("UpdateDrepCert")("UpdateDrepCert", { - drepCredential: Credential.Credential, + drepCredential: Credential.CredentialSchema, anchor: Schema.NullishOr(Anchor.Anchor) }) {} diff --git a/packages/evolution/src/core/Coin.ts b/packages/evolution/src/core/Coin.ts index 4e112107..a34cda84 100644 --- a/packages/evolution/src/core/Coin.ts +++ b/packages/evolution/src/core/Coin.ts @@ -26,9 +26,7 @@ export const MAX_COIN_VALUE = 18446744073709551615n * @since 2.0.0 * @category schemas */ -export const Coin = Schema.BigInt.pipe( - Schema.filter((value) => value >= 0n && value <= MAX_COIN_VALUE) -).annotations({ +export const Coin = Schema.BigInt.pipe(Schema.filter((value) => value >= 0n && value <= MAX_COIN_VALUE)).annotations({ message: (issue) => `Coin must be between 0 and ${MAX_COIN_VALUE}, but got ${issue.actual}`, identifier: "Coin" }) diff --git a/packages/evolution/src/core/Credential.ts b/packages/evolution/src/core/Credential.ts index a7fd26f9..a04aaabb 100644 --- a/packages/evolution/src/core/Credential.ts +++ b/packages/evolution/src/core/Credential.ts @@ -26,7 +26,7 @@ export class CredentialError extends Data.TaggedError("CredentialError")<{ * @since 2.0.0 * @category schemas */ -export const Credential = Schema.Union(KeyHash.KeyHash, ScriptHash.ScriptHash) +export const CredentialSchema = Schema.Union(KeyHash.KeyHash, ScriptHash.ScriptHash) /** * Type representing a credential that can be either a key hash or script hash @@ -35,7 +35,11 @@ export const Credential = Schema.Union(KeyHash.KeyHash, ScriptHash.ScriptHash) * @since 2.0.0 * @category model */ -export type Credential = typeof Credential.Type +export type CredentialSchema = typeof CredentialSchema.Type +export type Credential = typeof CredentialSchema.Encoded + +export const makeKeyHash = (hash: Uint8Array): CredentialSchema => KeyHash.make({ hash }) +export const makeScriptHash = (hash: Uint8Array): CredentialSchema => ScriptHash.make({ hash }) /** * Check if the given value is a valid Credential @@ -43,7 +47,7 @@ export type Credential = typeof Credential.Type * @since 2.0.0 * @category predicates */ -export const is = Schema.is(Credential) +export const is = Schema.is(CredentialSchema) export const CDDLSchema = Schema.Tuple( Schema.Literal(0n, 1n), @@ -57,7 +61,7 @@ export const CDDLSchema = Schema.Tuple( * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Credential), { +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(CredentialSchema), { strict: true, encode: (toI) => Eff.gen(function* () { @@ -103,7 +107,7 @@ export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTION * @since 2.0.0 * @category equality */ -export const equals = (a: Credential, b: Credential): boolean => { +export const equals = (a: CredentialSchema, b: CredentialSchema): boolean => { return a._tag === b._tag && Bytes.equals(a.hash, b.hash) } diff --git a/packages/evolution/src/core/Data.ts b/packages/evolution/src/core/Data.ts index 05b647f3..bf879c76 100644 --- a/packages/evolution/src/core/Data.ts +++ b/packages/evolution/src/core/Data.ts @@ -16,8 +16,38 @@ export class DataError extends EffectData.TaggedError("DataError")<{ cause?: unknown }> {} +// Working recursive schema example with BigInt transformation +type TestRecursiveEncoded = ReadonlyArray | string + +type TestRecursiveType = ReadonlyArray | bigint + +const TestRecursive: Schema.Schema = Schema.Union( + Schema.Array(Schema.suspend((): Schema.Schema => TestRecursive)), + Schema.BigInt +) + +/** + * PlutusData encoded type definition (wire format) + * Used for serialization/deserialization from JSON/CBOR + * + * @since 2.0.0 + * @category model + */ +export type DataEncoded = + // Constr (encoded with string index) + | { readonly index: string; readonly fields: ReadonlyArray } + // Map (encoded as array of [key, value] pairs) + | ReadonlyArray + // List + | ReadonlyArray + // Int (encoded as string) + | string + // ByteArray (encoded as hex string) + | string + /** - * PlutusData type definition based on Conway CDDL specification + * PlutusData type definition (runtime type) + * Based on Conway CDDL specification * * ``` * CDDL: plutus_data = @@ -47,7 +77,18 @@ export class DataError extends EffectData.TaggedError("DataError")<{ * @since 2.0.0 * @category model */ -export type Data = Constr | Map | List | Int | ByteArray +export type Data = + // Constr (runtime with bigint index) + | Constr + // { readonly index: bigint; readonly fields: ReadonlyArray } | + // Map (using standard Map since Schema.Map produces Map) + | globalThis.Map + // List + | ReadonlyArray + // Int (runtime as bigint) + | bigint + // ByteArray (runtime as Uint8Array) + | Uint8Array /** * Constr type for constructor alternatives based on Conway CDDL specification @@ -104,7 +145,7 @@ export class Constr extends Schema.Class("Constr")({ title: "Constructor Index", description: "The index of the constructor, must be a non-negative integer" }), - fields: Schema.Array(Schema.suspend((): Schema.Schema => DataSchema)).annotations({ + fields: Schema.Array(Schema.suspend((): Schema.Schema => DataSchema)).annotations({ identifier: "Data.Constr.Fields", title: "Fields of Constr", description: "A list of PlutusData fields for the constructor" @@ -118,13 +159,13 @@ export class Constr extends Schema.Class("Constr")({ * * @since 2.0.0 */ -export const MapSchema = Schema.MapFromSelf({ - key: Schema.suspend((): Schema.Schema => DataSchema).annotations({ +export const MapSchema = Schema.Map({ + key: Schema.suspend((): Schema.Schema => DataSchema).annotations({ identifier: "Data.Map.Key", title: "Map Key", description: "The key of the PlutusMap, must be a PlutusData type" }), - value: Schema.suspend((): Schema.Schema => DataSchema).annotations({ + value: Schema.suspend((): Schema.Schema => DataSchema).annotations({ identifier: "Data.Map.Value", title: "Map Value", description: "The value of the PlutusMap, must be a PlutusData type" @@ -133,16 +174,14 @@ export const MapSchema = Schema.MapFromSelf({ identifier: "Data.Map", title: "PlutusMap", description: "A map of PlutusData key-value pairs" -}) - -/** +}) /** * Schema for PlutusList data type * * @category schemas * * @since 2.0.0 */ -export const ListSchema = Schema.Array(Schema.suspend((): Schema.Schema => DataSchema)).annotations({ +export const ListSchema = Schema.Array(Schema.suspend((): Schema.Schema => DataSchema)).annotations({ identifier: "Data.List" }) @@ -167,7 +206,7 @@ export const ListSchema = Schema.Array(Schema.suspend((): Schema.Schema => * * @since 2.0.0 */ -export const IntSchema = Schema.BigIntFromSelf.annotations({ +export const IntSchema = Schema.BigInt.annotations({ identifier: "Data.Int" }) export type Int = typeof IntSchema.Type @@ -179,26 +218,39 @@ export type Int = typeof IntSchema.Type * * @since 2.0.0 */ -export const ByteArray = Bytes.HexLenientSchema.annotations({ +export const ByteArray = Schema.Uint8ArrayFromHex.annotations({ identifier: "Data.ByteArray" }) export type ByteArray = typeof ByteArray.Type /** - * Combined schema for PlutusData type + * Combined schema for PlutusData type with proper recursion * * @category schemas * * @since 2.0.0 */ -export const DataSchema: Schema.Schema = Schema.Union( - Schema.typeSchema(Constr), - Schema.typeSchema(MapSchema), +export const DataSchema: Schema.Schema = Schema.Union( + // Map: ReadonlyArray<[DataEncoded, DataEncoded]> <-> ReadonlyMap + MapSchema, + + // List: ReadonlyArray <-> ReadonlyArray ListSchema, - Schema.typeSchema(IntSchema), - ByteArray + + // Int: string <-> bigint + IntSchema, + + // ByteArray: hex string <-> Uint8Array + ByteArray, + + // Constr: { index: string, fields: DataEncoded[] } <-> { index: bigint, fields: Data[] } + Constr + // Schema.Struct({ + // fields: Schema.Array(Schema.suspend((): Schema.Schema => DataSchema)), + // index: Schema.BigInt + // }) ).annotations({ - identifier: "Data" + identifier: "DataSchema" }) /** @@ -268,8 +320,7 @@ export const constr = (index: bigint, fields: Array): Constr => Constr.mak * @since 2.0.0 * @category constructors */ -export const map = (entries: Array<[key: Data, value: Data]>) => - Schema.decodeSync(MapSchema)(new globalThis.Map(entries)) +export const map = (entries: Array<[key: Data, value: Data]>): Map => new globalThis.Map(entries) /** * Creates a Plutus list from items @@ -277,7 +328,7 @@ export const map = (entries: Array<[key: Data, value: Data]>) => * @since 2.0.0 * @category constructors */ -export const list = (list: Array): List => Schema.decodeSync(ListSchema)(list) +export const list = (list: Array): List => list /** * Creates Plutus big integer @@ -285,7 +336,7 @@ export const list = (list: Array): List => Schema.decodeSync(ListSchema)(l * @since 2.0.0 * @category constructors */ -export const int = (integer: bigint): Int => Schema.decodeSync(IntSchema)(integer) +export const int = (integer: bigint): Int => Schema.decodeSync(Schema.typeSchema(IntSchema))(integer) /** * Creates Plutus bounded bytes from hex string @@ -327,7 +378,7 @@ export const matchData = ( Map: (entries: ReadonlyArray<[Data, Data]>) => T List: (items: ReadonlyArray) => T Int: (value: bigint) => T - Bytes: (bytes: string) => T + Bytes: (bytes: Uint8Array) => T Constr: (constr: Constr) => T } ): T => { @@ -529,7 +580,7 @@ export const plutusDataToCBORValue = (data: Data): CBOR.CBOR => { return value }, Bytes: (bytes): CBOR.CBOR => { - return Bytes.fromHexLenient(bytes) + return bytes }, Constr: (constr): CBOR.CBOR => { // PlutusData Constr -> CBOR tags based on index @@ -573,11 +624,7 @@ export const cborValueToPlutusData = (cborValue: CBOR.CBOR): Data => { // Handle Uint8Array (bytes) if (CBOR.isByteArray(cborValue)) { - // Handle empty bytes case - if (cborValue.length === 0) { - return "" - } - return Bytes.toHexLenient(cborValue) // Convert Uint8Array to hex string + return cborValue } // Handle tagged values @@ -694,6 +741,15 @@ export const equals = (a: Data, b: Data): boolean => { if (typeof a === "string" || typeof b === "string") return a === b + // Uint8Array (ByteArray) + if (a instanceof Uint8Array && b instanceof Uint8Array) { + if (a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true + } + // Arrays (Lists) if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false @@ -759,7 +815,7 @@ export const CDDLSchema = CBOR.CBORSchema * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, DataSchema, { +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(DataSchema), { strict: true, encode: (_, __, ___, data) => Effect.succeed(plutusDataToCBORValue(data)), decode: (_, __, ___, cborValue) => diff --git a/packages/evolution/src/core/EnterpriseAddress.ts b/packages/evolution/src/core/EnterpriseAddress.ts index 74aefdfd..2bf8e0f5 100644 --- a/packages/evolution/src/core/EnterpriseAddress.ts +++ b/packages/evolution/src/core/EnterpriseAddress.ts @@ -21,7 +21,7 @@ export class EnterpriseAddressError extends Data.TaggedError("EnterpriseAddressE */ export class EnterpriseAddress extends Schema.TaggedClass("EnterpriseAddress")("EnterpriseAddress", { networkId: NetworkId.NetworkId, - paymentCredential: Credential.Credential + paymentCredential: Credential.CredentialSchema }) { toString(): string { return `EnterpriseAddress { networkId: ${this.networkId}, paymentCredential: ${this.paymentCredential} }` @@ -57,7 +57,7 @@ export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, Schema.type // Script payment const isPaymentKey = (addressType & 0b0001) === 0 - const paymentCredential: Credential.Credential = isPaymentKey + const paymentCredential: Credential.CredentialSchema = isPaymentKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) diff --git a/packages/evolution/src/core/GovernanceAction.ts b/packages/evolution/src/core/GovernanceAction.ts index f93259bd..095459d3 100644 --- a/packages/evolution/src/core/GovernanceAction.ts +++ b/packages/evolution/src/core/GovernanceAction.ts @@ -5,7 +5,7 @@ import * as CBOR from "./CBOR.js" import * as Coin from "./Coin.js" import * as CommiteeColdCredential from "./CommitteeColdCredential.js" import * as Constituion from "./Constitution.js" -import type { Credential as CredentialT } from "./Credential.js" +import type { CredentialSchema as CredentialT } from "./Credential.js" import * as Credential from "./Credential.js" import * as EpochNo from "./EpochNo.js" import * as Function from "./Function.js" @@ -85,13 +85,13 @@ export const GovActionIdFromCDDL = Schema.transformOrFail(GovActionIdCDDL, Schem Eff.gen(function* () { const [transactionIdBytes, govActionIndex] = fromA // Convert CBOR types to domain types - const govActionId = yield* ParseResult.decode(Schema.typeSchema(GovActionId))({ - _tag: "GovActionId", - transactionId: yield* ParseResult.decode(Schema.typeSchema(TransactionHash.TransactionHash))({ - _tag: "TransactionHash", - hash: transactionIdBytes - }), - govActionIndex: yield* ParseResult.decode(Schema.typeSchema(TransactionIndex.TransactionIndex))(govActionIndex) + const transactionId = new TransactionHash.TransactionHash({ hash: transactionIdBytes }) + const govActionIndexParsed = yield* ParseResult.decode(Schema.typeSchema(TransactionIndex.TransactionIndex))( + govActionIndex + ) + const govActionId = new GovActionId({ + transactionId, + govActionIndex: govActionIndexParsed }) return govActionId }) @@ -250,8 +250,8 @@ export const HardForkInitiationActionFromCDDL = Schema.transformOrFail( export class TreasuryWithdrawalsAction extends Schema.TaggedClass()( "TreasuryWithdrawalsAction", { - withdrawals: Schema.MapFromSelf({ - key: RewardAccount.RewardAccount, + withdrawals: Schema.Map({ + key: RewardAccount.FromBech32, value: Coin.Coin }), policyHash: Schema.NullOr(ScriptHash.ScriptHash) // policy_hash / nil @@ -387,9 +387,9 @@ export const NoConfidenceActionFromCDDL = Schema.transformOrFail( */ export class UpdateCommitteeAction extends Schema.TaggedClass()("UpdateCommitteeAction", { govActionId: Schema.NullOr(GovActionId), // gov_action_id / nil - membersToRemove: Schema.Array(CommiteeColdCredential.CommitteeColdCredential.Credential), // set - membersToAdd: Schema.MapFromSelf({ - key: CommiteeColdCredential.CommitteeColdCredential.Credential, // committee_cold_credential + membersToRemove: Schema.Array(CommiteeColdCredential.CommitteeColdCredential.CredentialSchema), // set + membersToAdd: Schema.Map({ + key: CommiteeColdCredential.CommitteeColdCredential.CredentialSchema, // committee_cold_credential value: EpochNo.EpochNoSchema // epoch_no }), threshold: UnitInterval.UnitInterval @@ -468,7 +468,7 @@ export const UpdateCommitteeActionFromCDDL = Schema.transformOrFail( const govActionId = govActionIdCDDL ? yield* ParseResult.decode(GovActionIdFromCDDL)(govActionIdCDDL) : null const threshold = yield* ParseResult.decode(UnitInterval.FromCDDL)(thresholdCDDL) // Decode set into an array of credentials (accept tag 258 or plain array) - const membersToRemove: Array = [] + const membersToRemove: Array = [] const removeArr = CBOR.isTag(membersToRemoveCDDL) ? membersToRemoveCDDL.tag === 258 ? (membersToRemoveCDDL.value as ReadonlyArray) @@ -481,7 +481,7 @@ export const UpdateCommitteeActionFromCDDL = Schema.transformOrFail( membersToRemove.push(coldCred) } const membersToAdd = new Map< - typeof CommiteeColdCredential.CommitteeColdCredential.Credential.Type, + typeof CommiteeColdCredential.CommitteeColdCredential.CredentialSchema.Type, EpochNo.EpochNo >() for (const [coldCredCDDL, epochNoCDDL] of membersToAddCDDL) { @@ -852,8 +852,8 @@ export const makeNoConfidence = (govActionId: GovActionId | null): NoConfidenceA */ export const makeUpdateCommittee = ( govActionId: GovActionId | null, - membersToRemove: ReadonlyArray, - membersToAdd: Map, + membersToRemove: ReadonlyArray, + membersToAdd: Map, threshold: UnitInterval.UnitInterval ): UpdateCommitteeAction => new UpdateCommitteeAction({ @@ -936,7 +936,7 @@ export const noConfidenceArbitrary: FastCheck.Arbitrary = Fa nil: null }).map((govActionId) => new NoConfidenceAction({ govActionId })) -const uniqueCredArray: FastCheck.Arbitrary> = FastCheck.uniqueArray( +const uniqueCredArray: FastCheck.Arbitrary> = FastCheck.uniqueArray( Credential.arbitrary, { maxLength: 5, @@ -944,14 +944,14 @@ const uniqueCredArray: FastCheck.Arbitrary> } ) -const membersToAddMapArbitrary: FastCheck.Arbitrary> = +const membersToAddMapArbitrary: FastCheck.Arbitrary> = uniqueCredArray.chain((colds) => FastCheck.array(EpochNo.arbitrary, { minLength: colds.length, maxLength: colds.length }).map((epochsRaw) => { const epochs = epochsRaw.map((e) => EpochNo.make(e)) - const m = new Map() + const m = new Map() for (let i = 0; i < colds.length; i++) m.set(colds[i], epochs[i]) return m }) @@ -1032,8 +1032,11 @@ export const match = ( NoConfidenceAction: (govActionId: GovActionId | null) => R UpdateCommitteeAction: ( govActionId: GovActionId | null, - membersToRemove: ReadonlyArray, - membersToAdd: ReadonlyMap, + membersToRemove: ReadonlyArray, + membersToAdd: ReadonlyMap< + typeof CommiteeColdCredential.CommitteeColdCredential.CredentialSchema.Type, + EpochNo.EpochNo + >, threshold: UnitInterval.UnitInterval ) => R NewConstitutionAction: (govActionId: GovActionId | null, constitution: Constituion.Constitution) => R diff --git a/packages/evolution/src/core/KeyHash.ts b/packages/evolution/src/core/KeyHash.ts index 0acc3695..f0046831 100644 --- a/packages/evolution/src/core/KeyHash.ts +++ b/packages/evolution/src/core/KeyHash.ts @@ -61,10 +61,7 @@ export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex) * @since 2.0.0 * @category transformer */ -export const FromHex = Schema.compose( - Hash28.BytesFromHex, - FromBytes -).annotations({ +export const FromHex = Schema.compose(Hash28.BytesFromHex, FromBytes).annotations({ identifier: "KeyHash.FromHex" }) diff --git a/packages/evolution/src/core/Metadata.ts b/packages/evolution/src/core/Metadata.ts index 585b8b43..e02ee38c 100644 --- a/packages/evolution/src/core/Metadata.ts +++ b/packages/evolution/src/core/Metadata.ts @@ -213,7 +213,7 @@ export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, MetadataError, * @category constructors */ export const fromEntries = (entries: Array<[MetadataLabel, TransactionMetadatum.TransactionMetadatum]>): Metadata => - (new Map(entries)) + new Map(entries) /** * Create an empty Metadata map. diff --git a/packages/evolution/src/core/Mint.ts b/packages/evolution/src/core/Mint.ts index 7073564f..9b650d19 100644 --- a/packages/evolution/src/core/Mint.ts +++ b/packages/evolution/src/core/Mint.ts @@ -28,7 +28,7 @@ export class MintError extends Data.TaggedError("MintError")<{ * @since 2.0.0 * @category schemas */ -export const AssetMap = Schema.MapFromSelf({ +export const AssetMap = Schema.Map({ key: AssetName.AssetName, value: NonZeroInt64.NonZeroInt64 }).annotations({ @@ -50,7 +50,7 @@ export type AssetMap = typeof AssetMap.Type * @since 2.0.0 * @category schemas */ -export const Mint = Schema.MapFromSelf({ +export const Mint = Schema.Map({ key: PolicyId.PolicyId, value: AssetMap }) @@ -304,7 +304,7 @@ export const FromCDDL = Schema.transformOrFail(Schema.encodedSchema(CDDLSchema), const assetMap = new Map() for (const [assetNameBytes, amount] of assetMapCddl.entries()) { const assetName = yield* ParseResult.decode(AssetName.FromBytes)(assetNameBytes) - const nonZeroAmount = yield* ParseResult.decode(NonZeroInt64.NonZeroInt64)(amount) + const nonZeroAmount = yield* ParseResult.decode(Schema.typeSchema(NonZeroInt64.NonZeroInt64))(amount) assetMap.set(assetName, nonZeroAmount) } @@ -371,7 +371,7 @@ export const arbitrary: FastCheck.Arbitrary = FastCheck.oneof( maxLength: 5, selector: (a) => Bytes.toHexUnsafe(a.bytes) }).chain((names) => - FastCheck.array(NonZeroInt64.arbitrary.map(NonZeroInt64.make), { + FastCheck.array(NonZeroInt64.arbitrary, { minLength: names.length, maxLength: names.length }).map((amounts) => names.map((n, i) => [n, amounts[i]] as const)) diff --git a/packages/evolution/src/core/MultiAsset.ts b/packages/evolution/src/core/MultiAsset.ts index da60fa4e..bc1e5deb 100644 --- a/packages/evolution/src/core/MultiAsset.ts +++ b/packages/evolution/src/core/MultiAsset.ts @@ -25,7 +25,7 @@ export class MultiAssetError extends Data.TaggedError("MultiAssetError")<{ * @since 2.0.0 * @category schemas */ -export const AssetMap = Schema.MapFromSelf({ +export const AssetMap = Schema.Map({ key: AssetName.AssetName, value: PositiveCoin.PositiveCoinSchema }) @@ -54,9 +54,9 @@ export type AssetMap = typeof AssetMap.Type * @since 2.0.0 * @category schemas */ -export const MultiAsset = Schema.MapFromSelf({ - key: Schema.typeSchema(PolicyId.PolicyId), - value: Schema.typeSchema(AssetMap) +export const MultiAsset = Schema.Map({ + key: PolicyId.PolicyId, + value: AssetMap }) .pipe(Schema.filter((map) => map.size > 0)) .pipe(Schema.brand("MultiAsset")) @@ -76,14 +76,7 @@ export const MultiAsset = Schema.MapFromSelf({ * @category model */ export interface MultiAsset extends Schema.Schema.Type {} - -/** - * Smart constructor for MultiAsset that validates and applies branding. - * - * @since 2.0.0 - * @category constructors - */ -export const make = Schema.decodeSync(MultiAsset) +// export type MultiAsset = typeof MultiAsset.Type /** * Create an empty Map for building MultiAssets (note: empty maps will fail validation). @@ -106,7 +99,9 @@ export const singleton = ( amount: PositiveCoin.PositiveCoin ): MultiAsset => { const assetMap = new Map([[assetName, amount]]) - return make(new Map([[policyId, assetMap]])) + + const multiAsset = new Map([[policyId, assetMap]]) + return multiAsset as MultiAsset } /** @@ -132,12 +127,12 @@ export const addAsset = ( const result = new Map(multiAsset) result.set(policyId, updatedAssetMap) - return make(result) + return result as MultiAsset } else { const newAssetMap = new Map([[assetName, amount]]) const result = new Map(multiAsset) result.set(policyId, newAssetMap) - return make(result) + return result as MultiAsset } } @@ -264,7 +259,7 @@ export const arbitrary: FastCheck.Arbitrary = FastCheck.uniqueArray( result.set(policy, assetMap as Map) } - return make(result) + return result as MultiAsset } ) }) @@ -327,7 +322,7 @@ export const FromCDDL = Schema.transformOrFail( result.set(policyId, assetMap) } - return yield* ParseResult.decode(MultiAsset)(result) + return result as MultiAsset }) } ) @@ -475,7 +470,7 @@ export const subtract = (a: MultiAsset, b: MultiAsset): MultiAsset => { }) } - return make(result) + return result as MultiAsset } // ============================================================================ diff --git a/packages/evolution/src/core/NativeScripts.ts b/packages/evolution/src/core/NativeScripts.ts index b49f8c00..aaa091a5 100644 --- a/packages/evolution/src/core/NativeScripts.ts +++ b/packages/evolution/src/core/NativeScripts.ts @@ -1,9 +1,9 @@ -import { Data, Either as E, FastCheck, ParseResult, Schema } from "effect" -import type { ParseIssue } from "effect/ParseResult" +import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" import * as Bytes from "./Bytes.js" import * as CBOR from "./CBOR.js" import * as Function from "./Function.js" +import * as Hash28 from "./Hash28.js" /** * Error class for Native script related operations. @@ -11,501 +11,591 @@ import * as Function from "./Function.js" * @since 2.0.0 * @category errors */ -export class NativeError extends Data.TaggedError("NativeError")<{ +export class NativeScriptError extends Data.TaggedError("NativeScriptError")<{ message?: string cause?: unknown }> {} +// ============================================================================ +// Type Definitions +// ============================================================================ + /** - * Type representing a native script following cardano-cli JSON syntax. + * Native script encoded type definition (wire format) * * @since 2.0.0 * @category model */ -export type Native = - | { - type: "sig" - keyHash: string - } - | { - type: "before" - slot: number - } - | { - type: "after" - slot: number - } - | { - type: "all" - scripts: ReadonlyArray - } - | { - type: "any" - scripts: ReadonlyArray - } - | { - type: "atLeast" - required: number - scripts: ReadonlyArray - } +export type NativeScriptEncoded = + | { readonly _tag: "ScriptPubKey"; readonly keyHash: string } + | { readonly _tag: "InvalidBefore"; readonly slot: string } + | { readonly _tag: "InvalidHereafter"; readonly slot: string } + | { readonly _tag: "ScriptAll"; readonly scripts: ReadonlyArray } + | { readonly _tag: "ScriptAny"; readonly scripts: ReadonlyArray } + | { readonly _tag: "ScriptNOfK"; readonly required: string; readonly scripts: ReadonlyArray } /** - * Represents a cardano-cli JSON script syntax + * Native script type definition (runtime representation) * - * Native type follows the standard described in the - * link https://github.com/IntersectMBO/cardano-node/blob/1.26.1-with-cardano-cli/doc/reference/simple-scripts.md#json-script-syntax JSON script syntax documentation. + * @since 2.0.0 + * @category model + */ +export type NativeScriptVariants = + | { readonly _tag: "ScriptPubKey"; readonly keyHash: Uint8Array } + | { readonly _tag: "InvalidBefore"; readonly slot: bigint } + | { readonly _tag: "InvalidHereafter"; readonly slot: bigint } + | { readonly _tag: "ScriptAll"; readonly scripts: ReadonlyArray } + | { readonly _tag: "ScriptAny"; readonly scripts: ReadonlyArray } + | { readonly _tag: "ScriptNOfK"; readonly required: bigint; readonly scripts: ReadonlyArray } + +// ============================================================================ +// Schema Definition +// ============================================================================ + +/** + * Internal Union schema for the actual native script variants * * @since 2.0.0 * @category schemas */ -export const NativeSchema: Schema.Schema = Schema.Union( +export const NativeScriptVariants: Schema.Schema = Schema.Union( Schema.Struct({ - type: Schema.Literal("sig"), - keyHash: Schema.String + _tag: Schema.Literal("ScriptPubKey"), + keyHash: Hash28.BytesFromHex }), Schema.Struct({ - type: Schema.Literal("before"), - slot: Schema.Number + _tag: Schema.Literal("InvalidBefore"), + slot: Schema.BigInt }), Schema.Struct({ - type: Schema.Literal("after"), - slot: Schema.Number + _tag: Schema.Literal("InvalidHereafter"), + slot: Schema.BigInt }), Schema.Struct({ - type: Schema.Literal("all"), - scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + _tag: Schema.Literal("ScriptAll"), + scripts: Schema.Array( + Schema.suspend((): Schema.Schema => NativeScriptVariants) + ) }), Schema.Struct({ - type: Schema.Literal("any"), - scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + _tag: Schema.Literal("ScriptAny"), + scripts: Schema.Array( + Schema.suspend((): Schema.Schema => NativeScriptVariants) + ) }), Schema.Struct({ - type: Schema.Literal("atLeast"), - required: Schema.Number, - scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + _tag: Schema.Literal("ScriptNOfK"), + required: Schema.BigInt, + scripts: Schema.Array( + Schema.suspend((): Schema.Schema => NativeScriptVariants) + ) }) -).annotations({ - identifier: "Native", - title: "Native Script", - description: "A native script following cardano-cli JSON syntax" -}) +) -export const Native = NativeSchema +/** + * TaggedClass schema for native scripts containing the Union + * + * @since 2.0.0 + * @category schemas + */ +export class NativeScript extends Schema.TaggedClass("NativeScript")( + "NativeScript", + { + script: NativeScriptVariants + }, + { + identifier: "NativeScript", + title: "Native Script", + description: "A native script following Cardano specifications" + } +) {} + +// ============================================================================ +// Smart Constructors +// ============================================================================ /** - * Smart constructor for Native that validates and applies branding. + * Create a signature script for a specific key hash * * @since 2.0.0 * @category constructors */ -export const make = (native: Native): Native => native +export const makeScriptPubKey = (keyHash: Uint8Array) => + new NativeScript({ + script: { + _tag: "ScriptPubKey", + keyHash + } + }) /** - * CDDL schemas for native scripts. - * - * These schemas define the CBOR encoding format for native scripts according to the CDDL specification: - * - * - script_pubkey = (0, addr_keyhash) - * - script_all = (1, [* native_script]) - * - script_any = (2, [* native_script]) - * - script_n_of_k = (3, n : int64, [* native_script]) - * - invalid_before = (4, slot_no) - * - invalid_hereafter = (5, slot_no) - * - slot_no = uint .size 8 + * Create a time-based script that is invalid before a slot * * @since 2.0.0 - * @category schemas + * @category constructors */ - -const ScriptPubKeyCDDL = Schema.Tuple(Schema.Literal(0n), Schema.Uint8ArrayFromSelf) - -const ScriptAllCDDL = Schema.Tuple( - Schema.Literal(1n), - Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) -) - -const ScriptAnyCDDL = Schema.Tuple( - Schema.Literal(2n), - Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) -) - -const ScriptNOfKCDDL = Schema.Tuple( - Schema.Literal(3n), - CBOR.Integer, - Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) -) - -const InvalidBeforeCDDL = Schema.Tuple(Schema.Literal(4n), CBOR.Integer) - -const InvalidHereafterCDDL = Schema.Tuple(Schema.Literal(5n), CBOR.Integer) +export const makeInvalidBefore = (slot: bigint) => + new NativeScript({ + script: { + _tag: "InvalidBefore", + slot + } + }) /** - * CDDL representation of a native script as a union of tuple types. - * - * This type represents the low-level CBOR structure of native scripts, - * where each variant is encoded as a tagged tuple. + * Create a time-based script that is invalid after a slot * * @since 2.0.0 - * @category model + * @category constructors */ -export type NativeCDDL = - | readonly [0n, Uint8Array] - | readonly [1n, ReadonlyArray] - | readonly [2n, ReadonlyArray] - | readonly [3n, bigint, ReadonlyArray] - | readonly [4n, bigint] - | readonly [5n, bigint] - -export const CDDLSchema = Schema.Union( - ScriptPubKeyCDDL, - ScriptAllCDDL, - ScriptAnyCDDL, - ScriptNOfKCDDL, - InvalidBeforeCDDL, - InvalidHereafterCDDL -) +export const makeInvalidHereafter = (slot: bigint) => + new NativeScript({ + script: { + _tag: "InvalidHereafter", + slot + } + }) /** - * Schema for NativeCDDL union type. + * Create a script that requires all nested scripts * * @since 2.0.0 - * @category schemas + * @category constructors */ -export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Native), { - strict: true, - encode: (native) => internalEncodeCDDL(native), - decode: (cborTuple) => internalDecodeCDDL(cborTuple) -}) +export const makeScriptAll = (scripts: ReadonlyArray) => + new NativeScript({ + script: { + _tag: "ScriptAll", + scripts + } + }) /** - * Convert a Native to its CDDL representation. + * Create a script that requires any one nested script * * @since 2.0.0 - * @category encoding + * @category constructors */ -export const internalEncodeCDDL = (native: Native): E.Either => - E.gen(function* () { - switch (native.type) { - case "sig": { - // Convert hex string keyHash to bytes for CBOR encoding - const keyHashBytes = yield* ParseResult.decodeEither(Bytes.FromHex)(native.keyHash) - return [0n, keyHashBytes] as const - } - case "all": { - const scriptResults: Array = [] - for (const script of native.scripts) { - const encoded = yield* internalEncodeCDDL(script) - scriptResults.push(encoded) - } - return [1n, scriptResults] as const - } - case "any": { - const scriptResults: Array = [] - for (const script of native.scripts) { - const encoded = yield* internalEncodeCDDL(script) - scriptResults.push(encoded) - } - return [2n, scriptResults] as const - } - case "atLeast": { - const scriptResults: Array = [] - for (const script of native.scripts) { - const encoded = yield* internalEncodeCDDL(script) - scriptResults.push(encoded) - } - return [3n, BigInt(native.required), scriptResults] as const - } - case "before": { - return [4n, BigInt(native.slot)] as const - } - case "after": { - return [5n, BigInt(native.slot)] as const - } +export const makeScriptAny = (scripts: ReadonlyArray) => + new NativeScript({ + script: { + _tag: "ScriptAny", + scripts } }) /** - * Convert a CDDL representation back to a Native. + * Create a script that requires at least N nested scripts * - * This function recursively decodes nested CBOR scripts and constructs - * the appropriate Native instances. + * @since 2.0.0 + * @category constructors + */ +export const makeScriptNOfK = (required: bigint, scripts: ReadonlyArray) => + new NativeScript({ + script: { + _tag: "ScriptNOfK", + required, + scripts + } + }) + +// ============================================================================ +// JSON Conversion Utilities +// ============================================================================ + +/** + * Convert a NativeScript to JSON representation matching cardano-cli format * * @since 2.0.0 - * @category decoding + * @category conversion */ -export const internalDecodeCDDL = (cborTuple: NativeCDDL): E.Either => - E.gen(function* () { - switch (cborTuple[0]) { - case 0n: { - // sig: [0, keyHash_bytes] - convert bytes back to hex string - const [, keyHashBytes] = cborTuple - const keyHash = yield* ParseResult.encodeEither(Bytes.FromHex)(keyHashBytes) - return { - type: "sig" as const, - keyHash - } +export const toJSON = (script: NativeScriptVariants): any => { + switch (script._tag) { + case "ScriptPubKey": + return { + type: "sig" as const, + keyHash: Bytes.toHex(script.keyHash) } - case 1n: { - // all: [1, [native_script, ...]] - const [, scriptCBORs] = cborTuple - const scripts: Array = [] - for (const scriptCBOR of scriptCBORs) { - const script = yield* internalDecodeCDDL(scriptCBOR) - scripts.push(script) - } - return { - type: "all" as const, - scripts - } + case "InvalidBefore": + return { + type: "after" as const, + slot: Number(script.slot) } - case 2n: { - // any: [2, [native_script, ...]] - const [, scriptCBORs] = cborTuple - const scripts: Array = [] - for (const scriptCBOR of scriptCBORs) { - const script = yield* internalDecodeCDDL(scriptCBOR) - scripts.push(script) - } - return { - type: "any" as const, - scripts - } + case "InvalidHereafter": + return { + type: "before" as const, + slot: Number(script.slot) } - case 3n: { - // atLeast: [3, required, [native_script, ...]] - const [, required, scriptCBORs] = cborTuple - const scripts: Array = [] - for (const scriptCBOR of scriptCBORs) { - const script = yield* internalDecodeCDDL(scriptCBOR) - scripts.push(script) - } - return { - type: "atLeast" as const, - required: Number(required), - scripts - } + case "ScriptAll": + return { + type: "all" as const, + scripts: script.scripts.map(toJSON) } - case 4n: { - // before: [4, slot] - const [, slot] = cborTuple - return { - type: "before" as const, - slot: Number(slot) - } + case "ScriptAny": + return { + type: "any" as const, + scripts: script.scripts.map(toJSON) } - case 5n: { - // after: [5, slot] - const [, slot] = cborTuple - return { - type: "after" as const, - slot: Number(slot) - } + case "ScriptNOfK": + return { + type: "atLeast" as const, + required: Number(script.required), + scripts: script.scripts.map(toJSON) } - default: - // This should never happen with proper CBOR validation - return yield* E.left(new ParseResult.Type(Schema.Literal(0, 1, 2, 3, 4, 5).ast, cborTuple[0])) - } - }) + } +} + +// ============================================================================ +// CDDL Types and Schemas +// ============================================================================ /** - * FastCheck arbitrary for Native scripts. - * Generates valid native scripts with bounded depth and sizes. + * CDDL representation following Cardano specification + * + * native_script = + * [ script_pubkey // 0 + * // script_all // 1 + * // script_any // 2 + * // script_n_of_k // 3 + * // invalid_before // 4 + * // invalid_hereafter // 5 + * ] * - * Depth limit prevents exponential blow-up. At depth 0, only base cases are generated. + * @since 2.0.0 + * @category model */ -const nativeArbitrary = (depth: number): FastCheck.Arbitrary => { - const baseSig = FastCheck.record({ - type: FastCheck.constant("sig" as const), - // 28-byte keyhash (56 hex chars) - keyHash: FastCheck.hexaString({ minLength: 56, maxLength: 56 }) - }) - - const baseBefore = FastCheck.record({ - type: FastCheck.constant("before" as const), - slot: FastCheck.integer({ min: 0, max: 10_000_000 }) - }) +export type NativeScriptCDDL = + | readonly [0n, Uint8Array] // script_pubkey + | readonly [1n, ReadonlyArray] // script_all + | readonly [2n, ReadonlyArray] // script_any + | readonly [3n, bigint, ReadonlyArray] // script_n_of_k + | readonly [4n, bigint] // invalid_before + | readonly [5n, bigint] // invalid_hereafter + +// Individual CDDL schemas +const ScriptPubKeyCDDL = Schema.Tuple(Schema.Literal(0n), Schema.Uint8ArrayFromSelf) +const ScriptAllCDDL = Schema.Tuple( + Schema.Literal(1n), + Schema.Array(Schema.suspend((): Schema.Schema => CDDLSchema)) +) +const ScriptAnyCDDL = Schema.Tuple( + Schema.Literal(2n), + Schema.Array(Schema.suspend((): Schema.Schema => CDDLSchema)) +) +const ScriptNOfKCDDL = Schema.Tuple( + Schema.Literal(3n), + Schema.BigIntFromSelf, + Schema.Array(Schema.suspend((): Schema.Schema => CDDLSchema)) +) +const InvalidBeforeCDDL = Schema.Tuple(Schema.Literal(4n), Schema.BigIntFromSelf) +const InvalidHereafterCDDL = Schema.Tuple(Schema.Literal(5n), Schema.BigIntFromSelf) - const baseAfter = FastCheck.record({ - type: FastCheck.constant("after" as const), - slot: FastCheck.integer({ min: 0, max: 10_000_000 }) - }) +export const CDDLSchema: Schema.Schema = Schema.Union( + ScriptPubKeyCDDL, + ScriptAllCDDL, + ScriptAnyCDDL, + ScriptNOfKCDDL, + InvalidBeforeCDDL, + InvalidHereafterCDDL +).annotations({ + identifier: "NativeScriptCDDL" +}) - if (depth <= 0) { - return FastCheck.oneof(baseSig, baseBefore, baseAfter) +/** + * Transform between NativeScript and CDDL representation + * + * @since 2.0.0 + * @category schemas + */ +export const FromCDDL: Schema.Schema = Schema.transformOrFail( + CDDLSchema, + Schema.typeSchema(NativeScript), + { + strict: true, + encode: (nativeScript: NativeScript): Eff.Effect => + Eff.gen(function* () { + const script = nativeScript.script + switch (script._tag) { + case "ScriptPubKey": + return [0n, script.keyHash] as const + case "ScriptAll": { + const encodedScripts: Array = [] + for (const nestedScript of script.scripts) { + const nestedNativeScript = new NativeScript({ script: nestedScript }) + const encoded = yield* ParseResult.encode(FromCDDL)(nestedNativeScript) + encodedScripts.push(encoded) + } + return [1n, encodedScripts] as const + } + case "ScriptAny": { + const encodedScripts: Array = [] + for (const nestedScript of script.scripts) { + const nestedNativeScript = new NativeScript({ script: nestedScript }) + const encoded = yield* ParseResult.encode(FromCDDL)(nestedNativeScript) + encodedScripts.push(encoded) + } + return [2n, encodedScripts] as const + } + case "ScriptNOfK": { + const encodedScripts: Array = [] + for (const nestedScript of script.scripts) { + const nestedNativeScript = new NativeScript({ script: nestedScript }) + const encoded = yield* ParseResult.encode(FromCDDL)(nestedNativeScript) + encodedScripts.push(encoded) + } + return [3n, script.required, encodedScripts] as const + } + case "InvalidBefore": + return [4n, script.slot] as const + case "InvalidHereafter": + return [5n, script.slot] as const + } + }), + decode: (cddl: NativeScriptCDDL): Eff.Effect => + Eff.gen(function* () { + switch (cddl[0]) { + case 0n: { + const [, keyHash] = cddl as readonly [0n, Uint8Array] + return makeScriptPubKey(keyHash) + } + case 1n: { + const [, scripts] = cddl as readonly [1n, ReadonlyArray] + const decodedScripts: Array = [] + for (const scriptCddl of scripts) { + const decoded = yield* ParseResult.decode(FromCDDL)(scriptCddl) + decodedScripts.push(decoded.script) + } + return makeScriptAll(decodedScripts) + } + case 2n: { + const [, scripts] = cddl as readonly [2n, ReadonlyArray] + const decodedScripts: Array = [] + for (const scriptCddl of scripts) { + const decoded = yield* ParseResult.decode(FromCDDL)(scriptCddl) + decodedScripts.push(decoded.script) + } + return makeScriptAny(decodedScripts) + } + case 3n: { + const [, required, scripts] = cddl as readonly [3n, bigint, ReadonlyArray] + const decodedScripts: Array = [] + for (const scriptCddl of scripts) { + const decoded = yield* ParseResult.decode(FromCDDL)(scriptCddl) + decodedScripts.push(decoded.script) + } + return makeScriptNOfK(required, decodedScripts) + } + case 4n: { + const [, slot] = cddl as readonly [4n, bigint] + return makeInvalidBefore(slot) + } + case 5n: { + const [, slot] = cddl as readonly [5n, bigint] + return makeInvalidHereafter(slot) + } + default: + return yield* ParseResult.fail( + new ParseResult.Type(Schema.typeSchema(NativeScript).ast, cddl, `Unknown native script tag: ${cddl[0]}`) + ) + } + }) } +).annotations({ + identifier: "NativeScript.FromCDDL" +}) - const sub = nativeArbitrary(depth - 1) - const scriptsArray = FastCheck.array(sub, { minLength: 0, maxLength: 3 }) - - const all = scriptsArray.map((scripts) => ({ type: "all" as const, scripts })) - const any = scriptsArray.map((scripts) => ({ type: "any" as const, scripts })) +// ============================================================================ +// CBOR Transformations +// ============================================================================ - const atLeast = FastCheck.array(sub, { minLength: 0, maxLength: 4 }).chain((scripts) => - FastCheck.integer({ min: 0, max: scripts.length }).map((required) => ({ - type: "atLeast" as const, - required, - scripts - })) +export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose( + CBOR.FromBytes(options), // Uint8Array → CBOR + FromCDDL // CBOR → NativeScript ) - // Weight base cases a bit higher for performance and balance - return FastCheck.oneof( - { arbitrary: baseSig, weight: 3 }, - { arbitrary: baseBefore, weight: 2 }, - { arbitrary: baseAfter, weight: 2 }, - { arbitrary: all, weight: 1 }, - { arbitrary: any, weight: 1 }, - { arbitrary: atLeast, weight: 1 } +export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose( + Bytes.FromHex, // string → Uint8Array + FromCBORBytes(options) // Uint8Array → NativeScript ) -} -export const arbitrary: FastCheck.Arbitrary = nativeArbitrary(2) +// ============================================================================ +// Validation and Predicates +// ============================================================================ /** - * Deep structural equality for Native scripts. - * Compares shape, values and recurses into nested scripts. + * Check if the given value is a valid NativeScript + * + * @since 2.0.0 + * @category predicates */ -export const equals = (a: Native, b: Native): boolean => { - if (a.type !== b.type) return false - switch (a.type) { - case "sig": - return a.keyHash === (b as any).keyHash - case "before": - return a.slot === (b as any).slot - case "after": - return a.slot === (b as any).slot - case "all": - case "any": { - const as = a.scripts - const bs = (b as any).scripts as ReadonlyArray - if (as.length !== bs.length) return false - for (let i = 0; i < as.length; i++) if (!equals(as[i], bs[i])) return false - return true - } - case "atLeast": { - const bs = b as any - if (a.required !== bs.required) return false - const as = a.scripts - const bscripts = bs.scripts as ReadonlyArray - if (as.length !== bscripts.length) return false - for (let i = 0; i < as.length; i++) if (!equals(as[i], bscripts[i])) return false - return true - } - } -} +export const is = Schema.is(NativeScriptVariants) /** - * CBOR bytes transformation schema for Native. - * Transforms between CBOR bytes and Native using CBOR encoding. + * Check if two NativeScript instances are equal * * @since 2.0.0 - * @category schemas + * @category equality */ -export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => - Schema.compose( - CBOR.FromBytes(options), // Uint8Array → CBOR - FromCDDL // CBOR → Native - ).annotations({ - identifier: "Native.FromCBORBytes", - title: "Native from CBOR Bytes", - description: "Transforms CBOR bytes to Native" - }) +export const equals = (a: NativeScript, b: NativeScript): boolean => { + // Use CBOR encoding for deep equality comparison + try { + const aBytes = toCBORBytes(a) + const bBytes = toCBORBytes(b) + return Bytes.equals(aBytes, bBytes) + } catch { + return false + } +} + +// ============================================================================ +// FastCheck Arbitraries +// ============================================================================ /** - * CBOR hex transformation schema for Native. - * Transforms between CBOR hex string and Native using CBOR encoding. + * FastCheck arbitrary for generating random NativeScript instances * * @since 2.0.0 - * @category schemas + * @category testing */ -export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => - Schema.compose( - Bytes.FromHex, // string → Uint8Array - FromCBORBytes(options) // Uint8Array → Native - ).annotations({ - identifier: "Native.FromCBORHex", - title: "Native from CBOR Hex", - description: "Transforms CBOR hex string to Native" - }) +export const arbitrary: FastCheck.Arbitrary = FastCheck.letrec((tie) => ({ + nativeScript: FastCheck.oneof( + // ScriptPubKey + FastCheck.uint8Array({ minLength: 28, maxLength: 28 }).map((keyHash) => makeScriptPubKey(keyHash)), + // InvalidBefore + FastCheck.bigInt({ min: 0n, max: 2n ** 64n - 1n }).map((slot) => makeInvalidBefore(slot)), + // InvalidHereafter + FastCheck.bigInt({ min: 0n, max: 2n ** 64n - 1n }).map((slot) => makeInvalidHereafter(slot)), + // ScriptAll (limit depth to prevent infinite recursion) + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 3 }).map((scripts) => + makeScriptAll(scripts as ReadonlyArray) + ), + // ScriptAny (limit depth to prevent infinite recursion) + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 3 }).map((scripts) => + makeScriptAny(scripts as ReadonlyArray) + ), + // ScriptNOfK (limit depth to prevent infinite recursion) + FastCheck.tuple( + FastCheck.bigInt({ min: 0n, max: 10n }), + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 3 }) + ).map(([required, scripts]) => makeScriptNOfK(required, scripts as ReadonlyArray)) + ), + // IMPORTANT: this generates NativeScriptVariants (plain variant objects), not NativeScript wrappers + nativeScriptVariant: FastCheck.oneof( + // ScriptPubKey + FastCheck.uint8Array({ minLength: 28, maxLength: 28 }).map((keyHash) => ({ + _tag: "ScriptPubKey" as const, + keyHash + })), + // InvalidBefore + FastCheck.bigInt({ min: 0n, max: 2n ** 64n - 1n }).map((slot) => ({ + _tag: "InvalidBefore" as const, + slot + })), + // InvalidHereafter + FastCheck.bigInt({ min: 0n, max: 2n ** 64n - 1n }).map((slot) => ({ + _tag: "InvalidHereafter" as const, + slot + })), + // ScriptAll (limit depth to prevent infinite recursion) + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 2 }).map((scripts) => ({ + _tag: "ScriptAll" as const, + scripts: scripts as ReadonlyArray + })), + // ScriptAny (limit depth to prevent infinite recursion) + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 2 }).map((scripts) => ({ + _tag: "ScriptAny" as const, + scripts: scripts as ReadonlyArray + })), + // ScriptNOfK (limit depth to prevent infinite recursion) + FastCheck.tuple( + FastCheck.bigInt({ min: 0n, max: 10n }), + FastCheck.array(tie("nativeScriptVariant"), { maxLength: 2 }) + ).map(([required, scripts]) => ({ + _tag: "ScriptNOfK" as const, + required, + scripts: scripts as ReadonlyArray + })) + ) +})).nativeScript -/** - * Root Functions - * ============================================================================ - */ +// ============================================================================ +// Root Functions +// ============================================================================ /** - * Parse Native from CBOR bytes. + * Parse a NativeScript from CBOR bytes * * @since 2.0.0 * @category parsing */ -export const fromCBORBytes = Function.makeCBORDecodeSync(FromCDDL, NativeError, "NativeScripts.fromCBORBytes") +export const fromCBORBytes = Function.makeCBORDecodeSync(FromCDDL, NativeScriptError, "NativeScript.fromCBORBytes") /** - * Parse Native from CBOR hex string. + * Parse a NativeScript from CBOR hex string * * @since 2.0.0 * @category parsing */ -export const fromCBORHex = (hex: string, options?: CBOR.CodecOptions): Native => - E.getOrThrow(Either.fromCBORHex(hex, options)) +export const fromCBORHex = Function.makeCBORDecodeHexSync(FromCDDL, NativeScriptError, "NativeScript.fromCBORHex") /** - * Encode Native to CBOR bytes. + * Convert a NativeScript to CBOR bytes * * @since 2.0.0 * @category encoding */ -export const toCBORBytes = Function.makeCBOREncodeSync(FromCDDL, NativeError, "Native.toCBORBytes") +export const toCBORBytes = Function.makeCBOREncodeSync(FromCDDL, NativeScriptError, "NativeScript.toCBORBytes") /** - * Encode Native to CBOR hex string. + * Convert a NativeScript to CBOR hex string * * @since 2.0.0 * @category encoding */ -export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, NativeError, "Native.toCBORHex") +export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, NativeScriptError, "NativeScript.toCBORHex") // ============================================================================ // Effect Namespace // ============================================================================ /** - * Effect-based error handling variants for functions that can fail. + * Effect-based error handling variants for functions that can fail * * @since 2.0.0 * @category effect */ export namespace Either { /** - * Parse Native from CBOR bytes with Effect error handling. + * Parse a NativeScript from CBOR bytes with Effect error handling * * @since 2.0.0 * @category parsing */ - export const fromCBORBytes = Function.makeCBORDecodeEither(FromCDDL, NativeError) + export const fromCBORBytes = Function.makeCBORDecodeEither(FromCDDL, NativeScriptError) /** - * Parse Native from CBOR hex string with Effect error handling. + * Parse a NativeScript from CBOR hex string with Effect error handling * * @since 2.0.0 * @category parsing */ - export const fromCBORHex = Function.makeCBORDecodeHexEither(FromCDDL, NativeError) + export const fromCBORHex = Function.makeCBORDecodeHexEither(FromCDDL, NativeScriptError) /** - * Encode Native to CBOR bytes with Effect error handling. + * Convert a NativeScript to CBOR bytes with Effect error handling * * @since 2.0.0 * @category encoding */ - export const toCBORBytes = Function.makeCBOREncodeEither(FromCDDL, NativeError) + export const toCBORBytes = Function.makeCBOREncodeEither(FromCDDL, NativeScriptError) /** - * Encode Native to CBOR hex string with Effect error handling. + * Convert a NativeScript to CBOR hex string with Effect error handling * * @since 2.0.0 * @category encoding */ - export const toCBORHex = Function.makeCBOREncodeHexEither(FromCDDL, NativeError) + export const toCBORHex = Function.makeCBOREncodeHexEither(FromCDDL, NativeScriptError) } diff --git a/packages/evolution/src/core/NativeScriptsOLD.ts b/packages/evolution/src/core/NativeScriptsOLD.ts new file mode 100644 index 00000000..b49f8c00 --- /dev/null +++ b/packages/evolution/src/core/NativeScriptsOLD.ts @@ -0,0 +1,511 @@ +import { Data, Either as E, FastCheck, ParseResult, Schema } from "effect" +import type { ParseIssue } from "effect/ParseResult" + +import * as Bytes from "./Bytes.js" +import * as CBOR from "./CBOR.js" +import * as Function from "./Function.js" + +/** + * Error class for Native script related operations. + * + * @since 2.0.0 + * @category errors + */ +export class NativeError extends Data.TaggedError("NativeError")<{ + message?: string + cause?: unknown +}> {} + +/** + * Type representing a native script following cardano-cli JSON syntax. + * + * @since 2.0.0 + * @category model + */ +export type Native = + | { + type: "sig" + keyHash: string + } + | { + type: "before" + slot: number + } + | { + type: "after" + slot: number + } + | { + type: "all" + scripts: ReadonlyArray + } + | { + type: "any" + scripts: ReadonlyArray + } + | { + type: "atLeast" + required: number + scripts: ReadonlyArray + } + +/** + * Represents a cardano-cli JSON script syntax + * + * Native type follows the standard described in the + * link https://github.com/IntersectMBO/cardano-node/blob/1.26.1-with-cardano-cli/doc/reference/simple-scripts.md#json-script-syntax JSON script syntax documentation. + * + * @since 2.0.0 + * @category schemas + */ +export const NativeSchema: Schema.Schema = Schema.Union( + Schema.Struct({ + type: Schema.Literal("sig"), + keyHash: Schema.String + }), + Schema.Struct({ + type: Schema.Literal("before"), + slot: Schema.Number + }), + Schema.Struct({ + type: Schema.Literal("after"), + slot: Schema.Number + }), + Schema.Struct({ + type: Schema.Literal("all"), + scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + }), + Schema.Struct({ + type: Schema.Literal("any"), + scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + }), + Schema.Struct({ + type: Schema.Literal("atLeast"), + required: Schema.Number, + scripts: Schema.Array(Schema.suspend((): Schema.Schema => NativeSchema)) + }) +).annotations({ + identifier: "Native", + title: "Native Script", + description: "A native script following cardano-cli JSON syntax" +}) + +export const Native = NativeSchema + +/** + * Smart constructor for Native that validates and applies branding. + * + * @since 2.0.0 + * @category constructors + */ +export const make = (native: Native): Native => native + +/** + * CDDL schemas for native scripts. + * + * These schemas define the CBOR encoding format for native scripts according to the CDDL specification: + * + * - script_pubkey = (0, addr_keyhash) + * - script_all = (1, [* native_script]) + * - script_any = (2, [* native_script]) + * - script_n_of_k = (3, n : int64, [* native_script]) + * - invalid_before = (4, slot_no) + * - invalid_hereafter = (5, slot_no) + * - slot_no = uint .size 8 + * + * @since 2.0.0 + * @category schemas + */ + +const ScriptPubKeyCDDL = Schema.Tuple(Schema.Literal(0n), Schema.Uint8ArrayFromSelf) + +const ScriptAllCDDL = Schema.Tuple( + Schema.Literal(1n), + Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) +) + +const ScriptAnyCDDL = Schema.Tuple( + Schema.Literal(2n), + Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) +) + +const ScriptNOfKCDDL = Schema.Tuple( + Schema.Literal(3n), + CBOR.Integer, + Schema.Array(Schema.suspend((): Schema.Schema => Schema.encodedSchema(FromCDDL))) +) + +const InvalidBeforeCDDL = Schema.Tuple(Schema.Literal(4n), CBOR.Integer) + +const InvalidHereafterCDDL = Schema.Tuple(Schema.Literal(5n), CBOR.Integer) + +/** + * CDDL representation of a native script as a union of tuple types. + * + * This type represents the low-level CBOR structure of native scripts, + * where each variant is encoded as a tagged tuple. + * + * @since 2.0.0 + * @category model + */ +export type NativeCDDL = + | readonly [0n, Uint8Array] + | readonly [1n, ReadonlyArray] + | readonly [2n, ReadonlyArray] + | readonly [3n, bigint, ReadonlyArray] + | readonly [4n, bigint] + | readonly [5n, bigint] + +export const CDDLSchema = Schema.Union( + ScriptPubKeyCDDL, + ScriptAllCDDL, + ScriptAnyCDDL, + ScriptNOfKCDDL, + InvalidBeforeCDDL, + InvalidHereafterCDDL +) + +/** + * Schema for NativeCDDL union type. + * + * @since 2.0.0 + * @category schemas + */ +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Native), { + strict: true, + encode: (native) => internalEncodeCDDL(native), + decode: (cborTuple) => internalDecodeCDDL(cborTuple) +}) + +/** + * Convert a Native to its CDDL representation. + * + * @since 2.0.0 + * @category encoding + */ +export const internalEncodeCDDL = (native: Native): E.Either => + E.gen(function* () { + switch (native.type) { + case "sig": { + // Convert hex string keyHash to bytes for CBOR encoding + const keyHashBytes = yield* ParseResult.decodeEither(Bytes.FromHex)(native.keyHash) + return [0n, keyHashBytes] as const + } + case "all": { + const scriptResults: Array = [] + for (const script of native.scripts) { + const encoded = yield* internalEncodeCDDL(script) + scriptResults.push(encoded) + } + return [1n, scriptResults] as const + } + case "any": { + const scriptResults: Array = [] + for (const script of native.scripts) { + const encoded = yield* internalEncodeCDDL(script) + scriptResults.push(encoded) + } + return [2n, scriptResults] as const + } + case "atLeast": { + const scriptResults: Array = [] + for (const script of native.scripts) { + const encoded = yield* internalEncodeCDDL(script) + scriptResults.push(encoded) + } + return [3n, BigInt(native.required), scriptResults] as const + } + case "before": { + return [4n, BigInt(native.slot)] as const + } + case "after": { + return [5n, BigInt(native.slot)] as const + } + } + }) + +/** + * Convert a CDDL representation back to a Native. + * + * This function recursively decodes nested CBOR scripts and constructs + * the appropriate Native instances. + * + * @since 2.0.0 + * @category decoding + */ +export const internalDecodeCDDL = (cborTuple: NativeCDDL): E.Either => + E.gen(function* () { + switch (cborTuple[0]) { + case 0n: { + // sig: [0, keyHash_bytes] - convert bytes back to hex string + const [, keyHashBytes] = cborTuple + const keyHash = yield* ParseResult.encodeEither(Bytes.FromHex)(keyHashBytes) + return { + type: "sig" as const, + keyHash + } + } + case 1n: { + // all: [1, [native_script, ...]] + const [, scriptCBORs] = cborTuple + const scripts: Array = [] + for (const scriptCBOR of scriptCBORs) { + const script = yield* internalDecodeCDDL(scriptCBOR) + scripts.push(script) + } + return { + type: "all" as const, + scripts + } + } + case 2n: { + // any: [2, [native_script, ...]] + const [, scriptCBORs] = cborTuple + const scripts: Array = [] + for (const scriptCBOR of scriptCBORs) { + const script = yield* internalDecodeCDDL(scriptCBOR) + scripts.push(script) + } + return { + type: "any" as const, + scripts + } + } + case 3n: { + // atLeast: [3, required, [native_script, ...]] + const [, required, scriptCBORs] = cborTuple + const scripts: Array = [] + for (const scriptCBOR of scriptCBORs) { + const script = yield* internalDecodeCDDL(scriptCBOR) + scripts.push(script) + } + return { + type: "atLeast" as const, + required: Number(required), + scripts + } + } + case 4n: { + // before: [4, slot] + const [, slot] = cborTuple + return { + type: "before" as const, + slot: Number(slot) + } + } + case 5n: { + // after: [5, slot] + const [, slot] = cborTuple + return { + type: "after" as const, + slot: Number(slot) + } + } + default: + // This should never happen with proper CBOR validation + return yield* E.left(new ParseResult.Type(Schema.Literal(0, 1, 2, 3, 4, 5).ast, cborTuple[0])) + } + }) + +/** + * FastCheck arbitrary for Native scripts. + * Generates valid native scripts with bounded depth and sizes. + * + * Depth limit prevents exponential blow-up. At depth 0, only base cases are generated. + */ +const nativeArbitrary = (depth: number): FastCheck.Arbitrary => { + const baseSig = FastCheck.record({ + type: FastCheck.constant("sig" as const), + // 28-byte keyhash (56 hex chars) + keyHash: FastCheck.hexaString({ minLength: 56, maxLength: 56 }) + }) + + const baseBefore = FastCheck.record({ + type: FastCheck.constant("before" as const), + slot: FastCheck.integer({ min: 0, max: 10_000_000 }) + }) + + const baseAfter = FastCheck.record({ + type: FastCheck.constant("after" as const), + slot: FastCheck.integer({ min: 0, max: 10_000_000 }) + }) + + if (depth <= 0) { + return FastCheck.oneof(baseSig, baseBefore, baseAfter) + } + + const sub = nativeArbitrary(depth - 1) + const scriptsArray = FastCheck.array(sub, { minLength: 0, maxLength: 3 }) + + const all = scriptsArray.map((scripts) => ({ type: "all" as const, scripts })) + const any = scriptsArray.map((scripts) => ({ type: "any" as const, scripts })) + + const atLeast = FastCheck.array(sub, { minLength: 0, maxLength: 4 }).chain((scripts) => + FastCheck.integer({ min: 0, max: scripts.length }).map((required) => ({ + type: "atLeast" as const, + required, + scripts + })) + ) + + // Weight base cases a bit higher for performance and balance + return FastCheck.oneof( + { arbitrary: baseSig, weight: 3 }, + { arbitrary: baseBefore, weight: 2 }, + { arbitrary: baseAfter, weight: 2 }, + { arbitrary: all, weight: 1 }, + { arbitrary: any, weight: 1 }, + { arbitrary: atLeast, weight: 1 } + ) +} + +export const arbitrary: FastCheck.Arbitrary = nativeArbitrary(2) + +/** + * Deep structural equality for Native scripts. + * Compares shape, values and recurses into nested scripts. + */ +export const equals = (a: Native, b: Native): boolean => { + if (a.type !== b.type) return false + switch (a.type) { + case "sig": + return a.keyHash === (b as any).keyHash + case "before": + return a.slot === (b as any).slot + case "after": + return a.slot === (b as any).slot + case "all": + case "any": { + const as = a.scripts + const bs = (b as any).scripts as ReadonlyArray + if (as.length !== bs.length) return false + for (let i = 0; i < as.length; i++) if (!equals(as[i], bs[i])) return false + return true + } + case "atLeast": { + const bs = b as any + if (a.required !== bs.required) return false + const as = a.scripts + const bscripts = bs.scripts as ReadonlyArray + if (as.length !== bscripts.length) return false + for (let i = 0; i < as.length; i++) if (!equals(as[i], bscripts[i])) return false + return true + } + } +} + +/** + * CBOR bytes transformation schema for Native. + * Transforms between CBOR bytes and Native using CBOR encoding. + * + * @since 2.0.0 + * @category schemas + */ +export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose( + CBOR.FromBytes(options), // Uint8Array → CBOR + FromCDDL // CBOR → Native + ).annotations({ + identifier: "Native.FromCBORBytes", + title: "Native from CBOR Bytes", + description: "Transforms CBOR bytes to Native" + }) + +/** + * CBOR hex transformation schema for Native. + * Transforms between CBOR hex string and Native using CBOR encoding. + * + * @since 2.0.0 + * @category schemas + */ +export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose( + Bytes.FromHex, // string → Uint8Array + FromCBORBytes(options) // Uint8Array → Native + ).annotations({ + identifier: "Native.FromCBORHex", + title: "Native from CBOR Hex", + description: "Transforms CBOR hex string to Native" + }) + +/** + * Root Functions + * ============================================================================ + */ + +/** + * Parse Native from CBOR bytes. + * + * @since 2.0.0 + * @category parsing + */ +export const fromCBORBytes = Function.makeCBORDecodeSync(FromCDDL, NativeError, "NativeScripts.fromCBORBytes") + +/** + * Parse Native from CBOR hex string. + * + * @since 2.0.0 + * @category parsing + */ +export const fromCBORHex = (hex: string, options?: CBOR.CodecOptions): Native => + E.getOrThrow(Either.fromCBORHex(hex, options)) + +/** + * Encode Native to CBOR bytes. + * + * @since 2.0.0 + * @category encoding + */ +export const toCBORBytes = Function.makeCBOREncodeSync(FromCDDL, NativeError, "Native.toCBORBytes") + +/** + * Encode Native to CBOR hex string. + * + * @since 2.0.0 + * @category encoding + */ +export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, NativeError, "Native.toCBORHex") + +// ============================================================================ +// Effect Namespace +// ============================================================================ + +/** + * Effect-based error handling variants for functions that can fail. + * + * @since 2.0.0 + * @category effect + */ +export namespace Either { + /** + * Parse Native from CBOR bytes with Effect error handling. + * + * @since 2.0.0 + * @category parsing + */ + export const fromCBORBytes = Function.makeCBORDecodeEither(FromCDDL, NativeError) + + /** + * Parse Native from CBOR hex string with Effect error handling. + * + * @since 2.0.0 + * @category parsing + */ + export const fromCBORHex = Function.makeCBORDecodeHexEither(FromCDDL, NativeError) + + /** + * Encode Native to CBOR bytes with Effect error handling. + * + * @since 2.0.0 + * @category encoding + */ + export const toCBORBytes = Function.makeCBOREncodeEither(FromCDDL, NativeError) + + /** + * Encode Native to CBOR hex string with Effect error handling. + * + * @since 2.0.0 + * @category encoding + */ + export const toCBORHex = Function.makeCBOREncodeHexEither(FromCDDL, NativeError) +} diff --git a/packages/evolution/src/core/NonZeroInt64.ts b/packages/evolution/src/core/NonZeroInt64.ts index 25e4d8de..13dc4e45 100644 --- a/packages/evolution/src/core/NonZeroInt64.ts +++ b/packages/evolution/src/core/NonZeroInt64.ts @@ -1,4 +1,4 @@ -import { Data, Either as E, FastCheck, Schema } from "effect" +import { Data, FastCheck, Schema } from "effect" /** * Constants for NonZeroInt64 validation. @@ -31,7 +31,7 @@ export class NonZeroInt64Error extends Data.TaggedError("NonZeroInt64Error")<{ * @since 2.0.0 * @category schemas */ -export const NegInt64Schema = Schema.BigIntFromSelf.pipe( +export const NegInt64Schema = Schema.BigInt.pipe( Schema.filter((value: bigint) => value >= NEG_INT64_MIN && value <= NEG_INT64_MAX) ).annotations({ message: (issue: any) => `NegInt64 must be between ${NEG_INT64_MIN} and ${NEG_INT64_MAX}, but got ${issue.actual}`, @@ -44,7 +44,7 @@ export const NegInt64Schema = Schema.BigIntFromSelf.pipe( * @since 2.0.0 * @category schemas */ -export const PosInt64Schema = Schema.BigIntFromSelf.pipe( +export const PosInt64Schema = Schema.BigInt.pipe( Schema.filter((value: bigint) => value >= POS_INT64_MIN && value <= POS_INT64_MAX) ).annotations({ message: (issue: any) => `PosInt64 must be between ${POS_INT64_MIN} and ${POS_INT64_MAX}, but got ${issue.actual}`, @@ -58,13 +58,11 @@ export const PosInt64Schema = Schema.BigIntFromSelf.pipe( * @since 2.0.0 * @category schemas */ -export const NonZeroInt64 = Schema.Union(NegInt64Schema, PosInt64Schema) - .pipe(Schema.brand("NonZeroInt64")) - .annotations({ - identifier: "NonZeroInt64", - title: "Non-Zero 64-bit Integer", - description: "A non-zero signed 64-bit integer (-9223372036854775808 to -1 or 1 to 9223372036854775807)" - }) +export const NonZeroInt64 = Schema.Union(NegInt64Schema, PosInt64Schema).annotations({ + identifier: "NonZeroInt64", + title: "Non-Zero 64-bit Integer", + description: "A non-zero signed 64-bit integer (-9223372036854775808 to -1 or 1 to 9223372036854775807)" +}) /** * Type alias for NonZeroInt64 representing non-zero signed 64-bit integers. @@ -113,16 +111,7 @@ export const isNegative = (value: NonZeroInt64): boolean => value < 0n * @since 2.0.0 * @category transformation */ -export const abs = (value: NonZeroInt64): NonZeroInt64 => { - try { - return Schema.decodeSync(NonZeroInt64)(value < 0n ? -value : value) - } catch (cause) { - throw new NonZeroInt64Error({ - message: "Failed to get absolute value of NonZeroInt64", - cause - }) - } -} +export const abs = (value: NonZeroInt64): NonZeroInt64 => (value < 0n ? (-value as NonZeroInt64) : value) /** * Negate a NonZeroInt64. @@ -130,16 +119,7 @@ export const abs = (value: NonZeroInt64): NonZeroInt64 => { * @since 2.0.0 * @category transformation */ -export const negate = (value: NonZeroInt64): NonZeroInt64 => { - try { - return Schema.decodeSync(NonZeroInt64)(-value) - } catch (cause) { - throw new NonZeroInt64Error({ - message: "Failed to negate NonZeroInt64", - cause - }) - } -} +export const negate = (value: NonZeroInt64): NonZeroInt64 => -value as NonZeroInt64 /** * Compare two NonZeroInt64 values. @@ -171,85 +151,3 @@ export const arbitrary = FastCheck.oneof( FastCheck.bigInt({ min: NEG_INT64_MIN, max: NEG_INT64_MAX }), FastCheck.bigInt({ min: POS_INT64_MIN, max: POS_INT64_MAX }) ) - -// ============================================================================ -// Root Functions -// ============================================================================ - -/** - * Parse NonZeroInt64 from bigint. - * - * @since 2.0.0 - * @category parsing - */ -export const fromBigInt = (value: bigint): NonZeroInt64 => { - try { - return Schema.decodeSync(NonZeroInt64)(value) - } catch (cause) { - throw new NonZeroInt64Error({ - message: "Failed to parse NonZeroInt64 from bigint", - cause - }) - } -} - -/** - * Encode NonZeroInt64 to bigint. - * - * @since 2.0.0 - * @category encoding - */ -export const toBigInt = (value: NonZeroInt64): bigint => { - try { - return Schema.encodeSync(NonZeroInt64)(value) - } catch (cause) { - throw new NonZeroInt64Error({ - message: "Failed to encode NonZeroInt64 to bigint", - cause - }) - } -} - -// ============================================================================ -// Either Namespace -// ============================================================================ - -/** - * Either-based error handling variants for functions that can fail. - * - * @since 2.0.0 - * @category either - */ -export namespace Either { - /** - * Parse NonZeroInt64 from bigint with Either error handling. - * - * @since 2.0.0 - * @category parsing - */ - export const fromBigInt = (value: bigint): E.Either => - E.mapLeft( - Schema.decodeEither(NonZeroInt64)(value), - (cause) => - new NonZeroInt64Error({ - message: "Failed to parse NonZeroInt64 from bigint", - cause - }) - ) - - /** - * Encode NonZeroInt64 to bigint with Either error handling. - * - * @since 2.0.0 - * @category encoding - */ - export const toBigInt = (value: NonZeroInt64): E.Either => - E.mapLeft( - Schema.encodeEither(NonZeroInt64)(value), - (cause) => - new NonZeroInt64Error({ - message: "Failed to encode NonZeroInt64 to bigint", - cause - }) - ) -} diff --git a/packages/evolution/src/core/PlutusV1.ts b/packages/evolution/src/core/PlutusV1.ts index 2acb7eda..6de9265d 100644 --- a/packages/evolution/src/core/PlutusV1.ts +++ b/packages/evolution/src/core/PlutusV1.ts @@ -20,7 +20,7 @@ export class PlutusV1Error extends Data.TaggedError("PlutusV1Error")<{ * @category model */ export class PlutusV1 extends Schema.TaggedClass("PlutusV1")("PlutusV1", { - bytes: Schema.Uint8ArrayFromSelf + bytes: Schema.Uint8ArrayFromHex }) {} /** @@ -37,7 +37,7 @@ export const CDDLSchema = CBOR.ByteArray * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transform(CDDLSchema, PlutusV1, { +export const FromCDDL = Schema.transform(CDDLSchema, Schema.typeSchema(PlutusV1), { strict: true, encode: (toI) => toI.bytes, decode: (fromA) => new PlutusV1({ bytes: fromA }) diff --git a/packages/evolution/src/core/PlutusV2.ts b/packages/evolution/src/core/PlutusV2.ts index 2d56ba6f..abb1c852 100644 --- a/packages/evolution/src/core/PlutusV2.ts +++ b/packages/evolution/src/core/PlutusV2.ts @@ -20,7 +20,7 @@ export class PlutusV2Error extends Data.TaggedError("PlutusV2Error")<{ * @category model */ export class PlutusV2 extends Schema.TaggedClass("PlutusV2")("PlutusV2", { - bytes: Schema.Uint8ArrayFromSelf + bytes: Schema.Uint8ArrayFromHex }) {} /** @@ -37,7 +37,7 @@ export const CDDLSchema = CBOR.ByteArray * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transform(CDDLSchema, PlutusV2, { +export const FromCDDL = Schema.transform(CDDLSchema, Schema.typeSchema(PlutusV2), { strict: true, encode: (toI) => toI.bytes, decode: (fromA) => new PlutusV2({ bytes: fromA }) diff --git a/packages/evolution/src/core/PlutusV3.ts b/packages/evolution/src/core/PlutusV3.ts index 5ced800d..0bcec29d 100644 --- a/packages/evolution/src/core/PlutusV3.ts +++ b/packages/evolution/src/core/PlutusV3.ts @@ -20,7 +20,7 @@ export class PlutusV3Error extends Data.TaggedError("PlutusV3Error")<{ * @category model */ export class PlutusV3 extends Schema.TaggedClass("PlutusV3")("PlutusV3", { - bytes: Schema.Uint8ArrayFromSelf + bytes: Schema.Uint8ArrayFromHex }) {} /** @@ -37,7 +37,7 @@ export const CDDLSchema = CBOR.ByteArray * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transform(CDDLSchema, PlutusV3, { +export const FromCDDL = Schema.transform(CDDLSchema, Schema.typeSchema(PlutusV3), { strict: true, encode: (toI) => toI.bytes, decode: (fromA) => new PlutusV3({ bytes: fromA }) diff --git a/packages/evolution/src/core/PointerAddress.ts b/packages/evolution/src/core/PointerAddress.ts index 3c019dcd..3c22b17f 100644 --- a/packages/evolution/src/core/PointerAddress.ts +++ b/packages/evolution/src/core/PointerAddress.ts @@ -28,7 +28,7 @@ export class PointerAddressError extends Data.TaggedError("PointerAddressError") */ export class PointerAddress extends Schema.TaggedClass("PointerAddress")("PointerAddress", { networkId: NetworkId.NetworkId, - paymentCredential: Credential.Credential, + paymentCredential: Credential.CredentialSchema, pointer: Pointer.Pointer }) { toString(): string { @@ -163,7 +163,7 @@ export const FromBytes = Schema.transformOrFail(Schema.Uint8ArrayFromSelf, Schem // payment credential kind const isPaymentKey = (addressType & 0b0001) === 0 - const paymentCredential: Credential.Credential = isPaymentKey + const paymentCredential: Credential.CredentialSchema = isPaymentKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) : new ScriptHash.ScriptHash({ hash: fromA.slice(1, 29) }) @@ -207,7 +207,7 @@ export const FromHex = Schema.compose( */ export const make = (props: { networkId: NetworkId.NetworkId - paymentCredential: Credential.Credential + paymentCredential: Credential.CredentialSchema pointer: Pointer.Pointer }): PointerAddress => new PointerAddress(props) diff --git a/packages/evolution/src/core/PrivateKey.ts b/packages/evolution/src/core/PrivateKey.ts index 4b601c59..85c24f6c 100644 --- a/packages/evolution/src/core/PrivateKey.ts +++ b/packages/evolution/src/core/PrivateKey.ts @@ -35,11 +35,15 @@ export class PrivateKey extends Schema.TaggedClass()("PrivateKey", { key: Schema.Union(Bytes64.BytesFromHex, Bytes32.BytesFromHex) }) {} -export const FromBytes = Schema.transform(Schema.typeSchema(Schema.Union(Bytes64.BytesFromHex, Bytes32.BytesFromHex)), Schema.typeSchema(PrivateKey), { - strict: true, - decode: (bytes) => new PrivateKey({ key: bytes }), - encode: (privateKey) => privateKey.key -}).annotations({ +export const FromBytes = Schema.transform( + Schema.typeSchema(Schema.Union(Bytes64.BytesFromHex, Bytes32.BytesFromHex)), + Schema.typeSchema(PrivateKey), + { + strict: true, + decode: (bytes) => new PrivateKey({ key: bytes }), + encode: (privateKey) => privateKey.key + } +).annotations({ identifier: "PrivateKey.FromBytes" }) diff --git a/packages/evolution/src/core/ProposalProcedure.ts b/packages/evolution/src/core/ProposalProcedure.ts index d9161aea..51212fc6 100644 --- a/packages/evolution/src/core/ProposalProcedure.ts +++ b/packages/evolution/src/core/ProposalProcedure.ts @@ -38,7 +38,7 @@ export class ProposalProcedureError extends Data.TaggedError("ProposalProcedureE */ export class ProposalProcedure extends Schema.Class("ProposalProcedure")({ deposit: Coin.Coin, - rewardAccount: RewardAccount.RewardAccount, + rewardAccount: RewardAccount.FromBech32, governanceAction: GovernanceAction.GovernanceAction, anchor: Schema.NullOr(Anchor.Anchor) }) {} diff --git a/packages/evolution/src/core/RewardAccount.ts b/packages/evolution/src/core/RewardAccount.ts index e0df24de..1b6011b4 100644 --- a/packages/evolution/src/core/RewardAccount.ts +++ b/packages/evolution/src/core/RewardAccount.ts @@ -1,3 +1,4 @@ +import { bech32 } from "@scure/base" import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" import * as Bytes from "./Bytes.js" @@ -21,7 +22,7 @@ export class RewardAccountError extends Data.TaggedError("RewardAccountError")<{ */ export class RewardAccount extends Schema.TaggedClass("RewardAccount")("RewardAccount", { networkId: NetworkId.NetworkId, - stakeCredential: Credential.Credential + stakeCredential: Credential.CredentialSchema }) { toString(): string { return `RewardAccount { networkId: ${this.networkId}, stakeCredential: ${this.stakeCredential} }` @@ -53,7 +54,7 @@ export const FromBytes = Schema.transformOrFail(Bytes29.BytesSchema, Schema.type const addressType = header >> 4 const isStakeKey = (addressType & 0b0001) === 0 - const stakeCredential: Credential.Credential = isStakeKey + const stakeCredential: Credential.CredentialSchema = isStakeKey ? new KeyHash.KeyHash({ hash: fromA.slice(1, 29) }) @@ -78,6 +79,32 @@ export const FromHex = Schema.compose( description: "Transforms raw hex string to RewardAccount" }) +export const FromBech32 = Schema.transformOrFail(Schema.String, Schema.typeSchema(RewardAccount), { + strict: true, + encode: (_, __, ___, toA) => + Eff.gen(function* () { + const prefix = toA.networkId === 0 ? "stake_test" : "stake" + const bytes = yield* ParseResult.encode(FromBytes)(toA) + const words = bech32.toWords(bytes) + return bech32.encode(prefix, words, false) + }), + decode: (fromA, _, ast) => + Eff.gen(function* () { + const result = yield* Eff.try({ + try: () => { + const decoded = bech32.decode(fromA as any, false) + const bytes = bech32.fromWords(decoded.words) + return new Uint8Array(bytes) + }, + catch: () => new ParseResult.Type(ast, fromA, `Failed to decode Bech32: ${fromA}`) + }) + return yield* ParseResult.decode(FromBytes)(result) + }) +}).annotations({ + identifier: "RewardAccount.FromBech32", + description: "Transforms Bech32 string to RewardAccount" +}) + /** * Smart constructor for creating RewardAccount instances * @@ -86,7 +113,7 @@ export const FromHex = Schema.compose( */ export const make = (props: { networkId: NetworkId.NetworkId - stakeCredential: Credential.Credential + stakeCredential: Credential.CredentialSchema }): RewardAccount => new RewardAccount(props) /** @@ -133,6 +160,14 @@ export const fromBytes = Function.makeDecodeSync(FromBytes, RewardAccountError, */ export const fromHex = Function.makeDecodeSync(FromHex, RewardAccountError, "RewardAccount.fromHex") +/** + * Parse a RewardAccount from Bech32 string. + * + * @since 2.0.0 + * @category parsing + */ +export const fromBech32 = Function.makeDecodeSync(FromBech32, RewardAccountError, "RewardAccount.fromBech32") + // ============================================================================ // Encoding Functions // ============================================================================ @@ -153,6 +188,14 @@ export const toBytes = Function.makeEncodeSync(FromBytes, RewardAccountError, "R */ export const toHex = Function.makeEncodeSync(FromHex, RewardAccountError, "RewardAccount.toHex") +/** + * Convert a RewardAccount to Bech32 string. + * + * @since 2.0.0 + * @category encoding + */ +export const toBech32 = Function.makeEncodeSync(FromBech32, RewardAccountError, "RewardAccount.toBech32") + // ============================================================================ // Either Namespace - Either-based Error Handling // ============================================================================ @@ -180,6 +223,14 @@ export namespace Either { */ export const fromHex = Function.makeDecodeEither(FromHex, RewardAccountError) + /** + * Parse a RewardAccount from Bech32 string. + * + * @since 2.0.0 + * @category parsing + */ + export const fromBech32 = Function.makeDecodeEither(FromBech32, RewardAccountError) + /** * Convert a RewardAccount to bytes. * @@ -195,4 +246,12 @@ export namespace Either { * @category encoding */ export const toHex = Function.makeEncodeEither(FromHex, RewardAccountError) + + /** + * Convert a RewardAccount to Bech32 string. + * + * @since 2.0.0 + * @category encoding + */ + export const toBech32 = Function.makeEncodeEither(FromBech32, RewardAccountError) } diff --git a/packages/evolution/src/core/Script.ts b/packages/evolution/src/core/Script.ts index 2c95cd28..7252d3c7 100644 --- a/packages/evolution/src/core/Script.ts +++ b/packages/evolution/src/core/Script.ts @@ -34,7 +34,7 @@ export class ScriptError extends Data.TaggedError("ScriptError")<{ * @category model */ export const Script = Schema.Union( - NativeScripts.Native, + NativeScripts.NativeScript, PlutusV1.PlutusV1, PlutusV2.PlutusV2, PlutusV3.PlutusV3 @@ -69,55 +69,51 @@ export type ScriptCDDL = typeof ScriptCDDL.Type * @since 2.0.0 * @category schemas */ -export const FromCDDL = Schema.transformOrFail(ScriptCDDL, Script, { +export const FromCDDL = Schema.transformOrFail(ScriptCDDL, Schema.typeSchema(Script), { strict: true, - encode: (value, _, ast) => { - // Handle native scripts (no _tag property, has type property) - if ("type" in value) { - return NativeScripts.internalEncodeCDDL(value).pipe( - Eff.map((nativeCDDL) => [0n, nativeCDDL] as const), - Eff.mapError((cause) => new ParseResult.Type(ast, value, `Failed to encode native script: ${cause}`)) - ) - } - - // Handle Plutus scripts (with _tag property) - if ("_tag" in value) { - const plutusScript = value as PlutusV1.PlutusV1 | PlutusV2.PlutusV2 | PlutusV3.PlutusV3 - switch (plutusScript._tag) { + encode: (script) => + Eff.gen(function* () { + switch (script._tag) { + // Plutus script cases case "PlutusV1": - return Eff.succeed([1n, plutusScript.bytes] as const) + return [1n, script.bytes] as const + case "PlutusV2": - return Eff.succeed([2n, plutusScript.bytes] as const) + return [2n, script.bytes] as const + case "PlutusV3": - return Eff.succeed([3n, plutusScript.bytes] as const) + return [3n, script.bytes] as const + + // Native script case (TaggedClass) + case "NativeScript": { + const nativeCDDL = yield* ParseResult.encode(NativeScripts.FromCDDL)(script) + return [0n, nativeCDDL] as const + } + default: - return Eff.fail(new ParseResult.Type(ast, value, `Unknown Plutus script type: ${(plutusScript as any)._tag}`)) + return yield* Eff.fail( + new ParseResult.Type(Schema.typeSchema(Script).ast, script, `Unknown script type: ${(script as any)._tag}`) + ) } - } - - return Eff.fail(new ParseResult.Type(ast, value, "Invalid script structure")) - }, - decode: (tuple, _, ast) => { - const [tag, bytes] = tuple - switch (tag) { - case 0n: - // Native script - return NativeScripts.internalDecodeCDDL(bytes).pipe( - Eff.mapError((cause) => new ParseResult.Type(ast, tuple, `Failed to decode native script: ${cause}`)) - ) - case 1n: - // PlutusV1 - return Eff.succeed(new PlutusV1.PlutusV1({ bytes })) - case 2n: - // PlutusV2 - return Eff.succeed(new PlutusV2.PlutusV2({ bytes })) - case 3n: - // PlutusV3 - return Eff.succeed(new PlutusV3.PlutusV3({ bytes })) - default: - return Eff.fail(new ParseResult.Type(ast, tuple, `Unknown script tag: ${tag}`)) - } - } + }), + decode: (tuple) => + Eff.gen(function* () { + const [tag, data] = tuple + switch (tag) { + case 0n: + // Native script + return yield* ParseResult.decode(NativeScripts.FromCDDL)(data) + case 1n: + // PlutusV1 + return new PlutusV1.PlutusV1({ bytes: data }) + case 2n: + // PlutusV2 + return new PlutusV2.PlutusV2({ bytes: data }) + case 3n: + // PlutusV3 + return new PlutusV3.PlutusV3({ bytes: data }) + } + }) }).annotations({ identifier: "Script.FromCDDL", title: "Script from CDDL", @@ -131,32 +127,20 @@ export const FromCDDL = Schema.transformOrFail(ScriptCDDL, Script, { * @category equality */ export const equals = (a: Script, b: Script): boolean => { - // Handle native scripts (no _tag property, has type property) - if ("type" in a && "type" in b) { - // Compare native scripts by type and basic properties - if (a.type !== b.type) return false - // For now, assume objects with same type and structure are equal - // TODO: Implement proper structural comparison for native scripts - return true - } + if (a._tag !== b._tag) return false - // Handle Plutus scripts (with _tag property) - if ("_tag" in a && "_tag" in b) { - if (a._tag !== b._tag) return false - - switch (a._tag) { - case "PlutusV1": - return PlutusV1.equals(a, b as PlutusV1.PlutusV1) - case "PlutusV2": - return PlutusV2.equals(a, b as PlutusV2.PlutusV2) - case "PlutusV3": - return PlutusV3.equals(a, b as PlutusV3.PlutusV3) - default: - return a === b - } + switch (a._tag) { + case "NativeScript": + return NativeScripts.equals(a, b as NativeScripts.NativeScript) + case "PlutusV1": + return PlutusV1.equals(a, b as PlutusV1.PlutusV1) + case "PlutusV2": + return PlutusV2.equals(a, b as PlutusV2.PlutusV2) + case "PlutusV3": + return PlutusV3.equals(a, b as PlutusV3.PlutusV3) + default: + return false } - - return false } /** diff --git a/packages/evolution/src/core/ScriptHash.ts b/packages/evolution/src/core/ScriptHash.ts index 6aa673a6..69f8811b 100644 --- a/packages/evolution/src/core/ScriptHash.ts +++ b/packages/evolution/src/core/ScriptHash.ts @@ -62,10 +62,7 @@ export const FromBytes = Schema.transform(Schema.typeSchema(Hash28.BytesFromHex) * @since 2.0.0 * @category schemas */ -export const FromHex = Schema.compose( - Hash28.BytesFromHex, - FromBytes -).annotations({ +export const FromHex = Schema.compose(Hash28.BytesFromHex, FromBytes).annotations({ identifier: "ScriptHash.FromHex" }) @@ -155,29 +152,34 @@ export const toHex = (scriptHash: ScriptHash): string => Bytes.toHex(scriptHash. * @category computation */ export const fromScript = (script: Script.Script): ScriptHash => { - // Native scripts use CBOR of native_script as body with 0x00 tag - if (!("_tag" in script)) { - const body = NativeScripts.toCBORBytes(script) - const prefixed = new Uint8Array(1 + body.length) - prefixed[0] = 0x00 - prefixed.set(body, 1) - const hashBytes = blake2b(prefixed, { dkLen: 28 }) - return make({ hash: new Uint8Array(hashBytes) }, { disableValidation: true }) - } - - // Plutus scripts use raw bytes with language-specific tag let tag: number let body: Uint8Array - if (script._tag === "PlutusV1") { - tag = 0x01 - body = script.bytes - } else if (script._tag === "PlutusV2") { - tag = 0x02 - body = script.bytes - } else { - // Remaining case is PlutusV3 - tag = 0x03 - body = script.bytes + + switch (script._tag) { + // Plutus script cases + case "PlutusV1": + tag = 0x01 + body = script.bytes + break + + case "PlutusV2": + tag = 0x02 + body = script.bytes + break + + case "PlutusV3": + tag = 0x03 + body = script.bytes + break + + // Native script case (TaggedClass) + case "NativeScript": + tag = 0x00 + body = NativeScripts.toCBORBytes(script) + break + + default: + throw new Error(`Unknown script type: ${(script as any)._tag}`) } const prefixed = new Uint8Array(1 + body.length) diff --git a/packages/evolution/src/core/ScriptRef.ts b/packages/evolution/src/core/ScriptRef.ts index 9d3e1742..8143054b 100644 --- a/packages/evolution/src/core/ScriptRef.ts +++ b/packages/evolution/src/core/ScriptRef.ts @@ -1,4 +1,4 @@ -import { Data, Effect, Either as E, FastCheck, ParseResult, Schema } from "effect" +import { Data, Effect, FastCheck, Schema } from "effect" import * as Bytes from "./Bytes.js" import * as CBOR from "./CBOR.js" @@ -149,10 +149,8 @@ export const arbitrary = FastCheck.uint8Array({ }).chain(() => // Generate a valid Script first, then CBOR-encode it and wrap in tag(24) bytes Script.arbitrary.map((script) => { - // Encode Script -> CDDL tuple - const cddl = E.getOrThrow(ParseResult.encodeEither(Script.FromCDDL)(script)) // Encode CDDL (CBOR value) -> bytes using canonical options compatible with CML - const bytes = E.getOrThrow(ParseResult.encodeEither(CBOR.FromBytes(CBOR.CML_DEFAULT_OPTIONS))(cddl)) + const bytes = Script.toCBOR(script) return new ScriptRef({ bytes }, { disableValidation: true }) }) ) diff --git a/packages/evolution/src/core/SingleHostAddr.ts b/packages/evolution/src/core/SingleHostAddr.ts index bab0f0c3..7997723f 100644 --- a/packages/evolution/src/core/SingleHostAddr.ts +++ b/packages/evolution/src/core/SingleHostAddr.ts @@ -139,7 +139,7 @@ export const FromCDDL = Schema.transformOrFail( const ipv4 = ipv4Value === null || ipv4Value === undefined ? Option.none() - : Option.some(yield* ParseResult.decode(Schema.typeSchema(IPv4.IPv4))({ _tag: "IPv4", bytes: ipv4Value })) + : Option.some(yield* ParseResult.decode(IPv4.FromBytes)(ipv4Value)) const ipv6 = ipv6Value === null || ipv6Value === undefined diff --git a/packages/evolution/src/core/StakeReference.ts b/packages/evolution/src/core/StakeReference.ts index 0621974a..b8531912 100644 --- a/packages/evolution/src/core/StakeReference.ts +++ b/packages/evolution/src/core/StakeReference.ts @@ -9,7 +9,7 @@ import * as Pointer from "./Pointer.js" * @since 2.0.0 * @category schemas */ -export const StakeReference = Schema.UndefinedOr(Schema.Union(Credential.Credential, Pointer.Pointer)) +export const StakeReference = Schema.UndefinedOr(Schema.Union(Credential.CredentialSchema, Pointer.Pointer)) /** * Type representing a reference to staking information diff --git a/packages/evolution/src/core/TSchema.ts b/packages/evolution/src/core/TSchema.ts index 96c1aa0a..7f53b52c 100644 --- a/packages/evolution/src/core/TSchema.ts +++ b/packages/evolution/src/core/TSchema.ts @@ -4,6 +4,12 @@ import type { NonEmptyReadonlyArray } from "effect/Array" import * as Data from "./Data.js" +export interface ByteArray + extends Schema.transform< + Schema.SchemaClass, Uint8Array, never>, + typeof Schema.String + > {} + /** * Schema transformations between TypeScript types and Plutus Data * @@ -13,20 +19,67 @@ import * as Data from "./Data.js" */ /** - * ByteArray schema (hex string) directly re-exported from Data layer. + * ByteArray schema that transforms hex string to Data.ByteArray for PlutusData. + * This enables withSchema compatibility by transforming from hex string to Uint8Array. * * @since 2.0.0 * @category schemas */ -export const ByteArray = Data.ByteArray +export const ByteArray: ByteArray = Schema.transform(Schema.typeSchema(Data.ByteArray), Schema.String, { + strict: true, + encode: (hex: string) => { + // Convert hex string to Uint8Array + if (hex.length % 2 !== 0) { + throw new Error("Invalid hex string length") + } + const bytes = new Uint8Array(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.substr(i, 2), 16) + } + return bytes + }, + decode: (bytes: Uint8Array) => { + // Convert Uint8Array to hex string + return globalThis.Array.from(bytes, (byte: number) => byte.toString(16).padStart(2, "0")).join("") + } +}) /** - * Integer schema (bigint) directly re-exported from Data layer. + * HexString schema that transforms hex string to ByteArray for PlutusData. + * This transforms from hex string to Uint8Array (runtime Data type) and back. * * @since 2.0.0 * @category schemas */ -export const Integer = Data.IntSchema +export const HexString = Schema.transform(Schema.Uint8ArrayFromSelf, Schema.String, { + strict: true, + encode: (hex: string) => { + // Convert hex string to Uint8Array + if (hex.length % 2 !== 0) { + throw new Error("Invalid hex string length") + } + const bytes = new Uint8Array(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = parseInt(hex.substr(i, 2), 16) + } + return bytes + }, + decode: (bytes: Uint8Array) => { + // Convert Uint8Array to hex string + return globalThis.Array.from(bytes, (byte: number) => byte.toString(16).padStart(2, "0")).join("") + } +}) + +export interface Integer extends Schema.SchemaClass {} + +/** + * Integer schema that represents Data.Int for PlutusData. + * This enables withSchema compatibility by using the Data type schema directly. + * + * @since 2.0.0 + * @category schemas + */ +export const Integer: Integer = Schema.typeSchema(Data.IntSchema) interface Literal> extends Schema.transform, Schema.Literal<[...Literals]>> {} @@ -59,19 +112,14 @@ export const OneLiteral = self }) -interface Array extends Schema.transform> {} +interface Array extends Schema.Array$ {} /** - * Creates a schema for arrays with Plutus list type annotation + * Creates a schema for arrays - just passes through to Schema.Array directly * * @since 1.0.0 */ -export const Array = (items: S): Array => - Schema.transform(Data.ListSchema, Schema.Array(items), { - strict: false, - encode: (value) => Data.list([...value] as globalThis.Array), - decode: (value) => [...value] - }) +export const Array = (items: S): Array => Schema.Array(items) interface Map extends Schema.transform< @@ -312,26 +360,16 @@ export const Union = >(...membe }) } -interface Tuple - extends Schema.transform> {} +interface Tuple extends Schema.Tuple {} /** - * Creates a schema for tuple types using Plutus Data List transformation - * Tuples are represented as a constructor with index 0 and fields as an array + * Creates a schema for tuple types - just passes through to Schema.Tuple directly * * @since 2.0.0 */ export const Tuple = (element: [...Elements]): Tuple => - Schema.transform( - Data.ListSchema, - Schema.Tuple(...element).annotations({ - identifier: "Tuple" - }), - { - strict: false, - encode: (value) => Data.list([...value] as globalThis.Array), - decode: (value) => [...value] as any - } - ) + Schema.Tuple(...element).annotations({ + identifier: "Tuple" + }) as Tuple export const compose = Schema.compose diff --git a/packages/evolution/src/core/Transaction.ts b/packages/evolution/src/core/Transaction.ts index b2c1639f..8c645274 100644 --- a/packages/evolution/src/core/Transaction.ts +++ b/packages/evolution/src/core/Transaction.ts @@ -1,6 +1,8 @@ -import { Schema } from "effect" +import { Data, Effect as Eff, FastCheck, ParseResult, Schema } from "effect" import * as AuxiliaryData from "./AuxiliaryData.js" +import * as CBOR from "./CBOR.js" +import * as Function from "./Function.js" import * as TransactionBody from "./TransactionBody.js" import * as TransactionWitnessSet from "./TransactionWitnessSet.js" @@ -19,3 +21,124 @@ export class Transaction extends Schema.TaggedClass()("Transaction" isValid: Schema.Boolean, auxiliaryData: Schema.NullOr(AuxiliaryData.AuxiliaryData) }) {} + +export const make = (...args: ConstructorParameters) => new Transaction(...args) + +/** + * Error class for Transaction related operations. + * + * @since 2.0.0 + * @category errors + */ +export class TransactionError extends Data.TaggedError("TransactionError")<{ + message?: string + cause?: unknown +}> {} + +/** + * Conway CDDL schema for Transaction tuple structure. + * + * CDDL: transaction = [transaction_body, transaction_witness_set, bool, auxiliary_data / nil] + */ +export const CDDLSchema = Schema.Tuple( + TransactionBody.CDDLSchema.annotations({ identifier: "TransactionBodyCDDL", description: "Transaction body" }), + TransactionWitnessSet.CDDLSchema.annotations({ + identifier: "TransactionWitnessSetCDDL", + description: "Transaction witness set" + }), + Schema.Boolean, + // Auxiliary data is a CBOR value; CBOR schema already includes null in its domain + CBOR.CBORSchema.annotations({ identifier: "AuxiliaryDataCDDL", description: "Auxiliary data as raw CBOR" }) +).annotations({ identifier: "TransactionCDDL", description: "Transaction tuple structure" }) + +/** + * Transform between CDDL tuple and Transaction class. + */ +export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Transaction), { + strict: true, + encode: (tx) => + Eff.gen(function* () { + const bodyReadonly = yield* ParseResult.encode(TransactionBody.FromCDDL)(tx.body) + const witnessReadonly = yield* ParseResult.encode(TransactionWitnessSet.FromCDDL)(tx.witnessSet) + // Ensure mutable Map instances for tuple A-type compatibility + const body = new Map(bodyReadonly.entries()) + const witnessSet = new Map(witnessReadonly.entries()) + const isValid = tx.isValid + const auxiliaryData = + tx.auxiliaryData === null ? null : yield* ParseResult.encode(AuxiliaryData.FromCDDL)(tx.auxiliaryData) + return [body, witnessSet, isValid, auxiliaryData] as const + }), + decode: (tuple) => + Eff.gen(function* () { + const [bodyCDDL, witnessSetCDDL, isValid, aux] = tuple + const body = yield* ParseResult.decode(TransactionBody.FromCDDL)(bodyCDDL) + const witnessSet = yield* ParseResult.decode(TransactionWitnessSet.FromCDDL)(witnessSetCDDL) + const auxiliaryData = aux === null ? null : yield* ParseResult.decodeUnknownEither(AuxiliaryData.FromCDDL)(aux) + return new Transaction({ body, witnessSet, isValid, auxiliaryData }, { disableValidation: true }) + }) +}) + +/** + * CBOR bytes transformation schema for Transaction. + */ +export const FromCBORBytes = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose(CBOR.FromBytes(options), FromCDDL).annotations({ + identifier: "Transaction.FromCBORBytes", + description: "Decode Transaction from CBOR bytes per Conway CDDL" + }) + +/** + * CBOR hex transformation schema for Transaction. + */ +export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DEFAULT_OPTIONS) => + Schema.compose(CBOR.FromHex(options), FromCDDL).annotations({ + identifier: "Transaction.FromCBORHex", + description: "Decode Transaction from CBOR hex per Conway CDDL" + }) + +// ============================================================================ +// Parsing / Encoding Functions +// ============================================================================ + +export const fromCBORBytes = Function.makeCBORDecodeSync(FromCDDL, TransactionError, "Transaction.fromCBORBytes") +export const fromCBORHex = Function.makeCBORDecodeHexSync(FromCDDL, TransactionError, "Transaction.fromCBORHex") +export const toCBORBytes = Function.makeCBOREncodeSync(FromCDDL, TransactionError, "Transaction.toCBORBytes") +export const toCBORHex = Function.makeCBOREncodeHexSync(FromCDDL, TransactionError, "Transaction.toCBORHex") + +// Either variants +export namespace Either { + export const fromCBORBytes = Function.makeCBORDecodeEither(FromCDDL, TransactionError, CBOR.CML_DEFAULT_OPTIONS) + export const fromCBORHex = Function.makeCBORDecodeHexEither(FromCDDL, TransactionError, CBOR.CML_DEFAULT_OPTIONS) + export const toCBORBytes = Function.makeCBOREncodeEither(FromCDDL, TransactionError, CBOR.CML_DEFAULT_OPTIONS) + export const toCBORHex = Function.makeCBOREncodeHexEither(FromCDDL, TransactionError, CBOR.CML_DEFAULT_OPTIONS) +} + +// ============================================================================ +// Equality +// ============================================================================ + +export const equals = (a: Transaction, b: Transaction): boolean => { + if (a === b) return true + try { + const aBytes = toCBORBytes(a) + const bBytes = toCBORBytes(b) + if (aBytes.length !== bBytes.length) return false + for (let i = 0; i < aBytes.length; i++) { + if (aBytes[i] !== bBytes[i]) return false + } + return true + } catch { + return false + } +} + +// ============================================================================ +// Arbitrary (FastCheck) +// ============================================================================ + +export const arbitrary: FastCheck.Arbitrary = FastCheck.record({ + body: TransactionBody.arbitrary, + witnessSet: TransactionWitnessSet.arbitrary, + isValid: FastCheck.boolean(), + auxiliaryData: FastCheck.option(AuxiliaryData.arbitrary, { nil: null }).map((a) => (a === undefined ? null : a)) +}).map((r) => new Transaction(r)) diff --git a/packages/evolution/src/core/TransactionBody.ts b/packages/evolution/src/core/TransactionBody.ts index 71aa8d31..293cc942 100644 --- a/packages/evolution/src/core/TransactionBody.ts +++ b/packages/evolution/src/core/TransactionBody.ts @@ -57,11 +57,11 @@ export class TransactionBody extends Schema.TaggedClass()("Tran inputs: Schema.Array(TransactionInput.TransactionInput), // 0 outputs: Schema.Array(TransactionOutput.TransactionOutput), // 1 fee: Coin.Coin, // 2 - ttl: Schema.optional(Schema.BigIntFromSelf), // 3 - slot_no + ttl: Schema.optional(Schema.BigInt), // 3 - slot_no certificates: Schema.optional(Schema.NonEmptyArray(Certificate.Certificate)), // 4 withdrawals: Schema.optional(Withdrawals.Withdrawals), // 5 auxiliaryDataHash: Schema.optional(AuxiliaryDataHash.AuxiliaryDataHash), // 7 - validityIntervalStart: Schema.optional(Schema.BigIntFromSelf), // 8 - slot_no + validityIntervalStart: Schema.optional(Schema.BigInt), // 8 - slot_no mint: Schema.optional(Mint.Mint), // 9 scriptDataHash: Schema.optional(ScriptDataHash.ScriptDataHash), // 11 collateralInputs: Schema.optional(Schema.NonEmptyArray(TransactionInput.TransactionInput)), // 13 @@ -104,8 +104,8 @@ const encodeProposalProcedure = ParseResult.encodeEither(ProposalProcedure.FromC const decodeProposalProcedure = ParseResult.decodeEither(ProposalProcedure.FromCDDL) const encodeRewardAccountBytes = ParseResult.encodeEither(RewardAccount.FromBytes) const decodeRewardAccountBytes = ParseResult.decodeEither(RewardAccount.FromBytes) -const decodeAuxiliaryDataHash = ParseResult.decodeEither(Schema.typeSchema(AuxiliaryDataHash.AuxiliaryDataHash)) -const decodeScriptDataHash = ParseResult.decodeEither(Schema.typeSchema(ScriptDataHash.ScriptDataHash)) +const decodeAuxiliaryDataHash = ParseResult.decodeEither(AuxiliaryDataHash.FromBytes) +const decodeScriptDataHash = ParseResult.decodeEither(ScriptDataHash.FromBytes) const decodeKeyHash = ParseResult.decodeEither(KeyHash.FromBytes) const decodeInputs = ParseResult.decodeUnknownEither(CBOR.tag(258, Schema.Array(TransactionInput.FromCDDL))) @@ -188,10 +188,12 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra } if (toA.requiredSigners) { - record.set( - 14n, - toA.requiredSigners.map((signer) => signer.hash) - ) + const len = toA.requiredSigners.length + const arr = new Array(len) + for (let i = 0; i < len; i++) { + arr[i] = toA.requiredSigners[i].hash + } + record.set(14n, CBOR.Tag.make({ tag: 258, value: arr }, { disableValidation: true })) } if (toA.networkId !== undefined) record.set(15n, BigInt(toA.networkId)) @@ -278,15 +280,13 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra const auxiliaryDataHashBytes = fromA.get(7n) as Uint8Array | undefined const auxiliaryDataHash = auxiliaryDataHashBytes - ? yield* decodeAuxiliaryDataHash({ _tag: "AuxiliaryDataHash", bytes: auxiliaryDataHashBytes }) + ? yield* decodeAuxiliaryDataHash(auxiliaryDataHashBytes) : undefined const validityIntervalStart = fromA.get(8n) as bigint | undefined const mintData = fromA.get(9n) as typeof Mint.CDDLSchema.Type | undefined const mint = mintData ? yield* decodeMint(mintData) : undefined const scriptDataHashBytes = fromA.get(11n) as Uint8Array | undefined - const scriptDataHash = scriptDataHashBytes - ? yield* decodeScriptDataHash({ _tag: "ScriptDataHash", hash: scriptDataHashBytes }) - : undefined + const scriptDataHash = scriptDataHashBytes ? yield* decodeScriptDataHash(scriptDataHashBytes) : undefined const collateralInputsTag = fromA.get(13n) as | { @@ -306,7 +306,14 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra collateralInputs = arr as NonEmptyArray } - const requiredSignersArray = fromA.get(14n) as ReadonlyArray | undefined + const requiredSignersTag = fromA.get(14n) as + | { + _tag: "Tag" + tag: 258 + value: ReadonlyArray + } + | undefined + const requiredSignersArray = requiredSignersTag ? requiredSignersTag.value : undefined let requiredSigners: NonEmptyArray | undefined if (requiredSignersArray) { const len = requiredSignersArray.length diff --git a/packages/evolution/src/core/TransactionHash.ts b/packages/evolution/src/core/TransactionHash.ts index 6f78fd38..35ad0248 100644 --- a/packages/evolution/src/core/TransactionHash.ts +++ b/packages/evolution/src/core/TransactionHash.ts @@ -39,7 +39,7 @@ export class TransactionHash extends Schema.TaggedClass()("Tran * @since 2.0.0 * @category schemas */ -export const FromBytes = Schema.transform(Bytes32.BytesFromHex, Schema.typeSchema(TransactionHash), { +export const FromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(TransactionHash), { strict: true, decode: (bytes) => new TransactionHash({ hash: bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 encode: (txHash) => txHash.hash @@ -47,6 +47,13 @@ export const FromBytes = Schema.transform(Bytes32.BytesFromHex, Schema.typeSchem identifier: "TransactionHash.FromBytes" }) +export const FromHex = Schema.compose( + Bytes32.BytesFromHex, // string -> Bytes32 + FromBytes // Bytes32 -> TransactionHash +).annotations({ + identifier: "TransactionHash.FromHex" +}) + /** * Smart constructor for TransactionHash that validates and applies branding. * @@ -94,6 +101,14 @@ export const arbitrary = FastCheck.uint8Array({ */ export const fromBytes = Function.makeDecodeSync(FromBytes, TransactionHashError, "TransactionHash.fromBytes") +/** + * Parse TransactionHash from hex string. + * + * @since 2.0.0 + * @category parsing + */ +export const fromHex = Function.makeDecodeSync(FromHex, TransactionHashError, "TransactionHash.fromHex") + /** * Encode TransactionHash to bytes. * @@ -108,7 +123,7 @@ export const toBytes = Function.makeEncodeSync(FromBytes, TransactionHashError, * @since 2.0.0 * @category encoding */ -export const toHex = Function.makeEncodeSync(TransactionHash, TransactionHashError, "TransactionHash.toHex") +export const toHex = Function.makeEncodeSync(FromHex, TransactionHashError, "TransactionHash.toHex") // ============================================================================ // Effect Namespace @@ -135,7 +150,7 @@ export namespace Either { * @since 2.0.0 * @category parsing */ - export const fromHex = Function.makeDecodeEither(TransactionHash, TransactionHashError) + export const fromHex = Function.makeDecodeEither(FromHex, TransactionHashError) /** * Encode TransactionHash to bytes with Effect error handling. @@ -151,5 +166,5 @@ export namespace Either { * @since 2.0.0 * @category encoding */ - export const toHex = Function.makeEncodeEither(TransactionHash, TransactionHashError) + export const toHex = Function.makeEncodeEither(FromHex, TransactionHashError) } diff --git a/packages/evolution/src/core/TransactionWitnessSet.ts b/packages/evolution/src/core/TransactionWitnessSet.ts index a08ed663..3e296f6f 100644 --- a/packages/evolution/src/core/TransactionWitnessSet.ts +++ b/packages/evolution/src/core/TransactionWitnessSet.ts @@ -94,25 +94,16 @@ export type PlutusScript = typeof PlutusScript.Type */ export class TransactionWitnessSet extends Schema.Class("TransactionWitnessSet")({ vkeyWitnesses: Schema.optional(Schema.Array(VKeyWitness)), - nativeScripts: Schema.optional(Schema.Array(NativeScripts.Native)), + nativeScripts: Schema.optional(Schema.Array(NativeScripts.NativeScript)), bootstrapWitnesses: Schema.optional(Schema.Array(Bootstrap.BootstrapWitness)), plutusV1Scripts: Schema.optional(Schema.Array(PlutusV1.PlutusV1)), plutusData: Schema.optional(Schema.Array(PlutusData.DataSchema)), - redeemers: Schema.optional(Schema.Array(Schema.typeSchema(Redeemer.Redeemer))), + redeemers: Schema.optional(Schema.Array(Redeemer.Redeemer)), plutusV2Scripts: Schema.optional(Schema.Array(PlutusV2.PlutusV2)), plutusV3Scripts: Schema.optional(Schema.Array(PlutusV3.PlutusV3)) }) {} -/** - * CDDL schema for VKeyWitness. - * - * @since 2.0.0 - * @category schemas - */ -const VKeyWitnessCDDL = Schema.Tuple( - CBOR.ByteArray, // vkey as bytes - CBOR.ByteArray // signature as bytes -) +// Note: Individual tuple encodings are handled inline during encode/decode. /** * CDDL schema for BootstrapWitness. @@ -123,22 +114,25 @@ const VKeyWitnessCDDL = Schema.Tuple( // BootstrapWitness CDDL schema provided by ./BootstrapWitness.ts /** - * CDDL schema for TransactionWitnessSet as struct/record structure. - * Supports both tagged (CBOR tag 258) and untagged arrays for nonempty_set. - * Uses number keys to leverage CBOR Record encoding with proper integer key handling. + * CDDL schema for TransactionWitnessSet encoded as a CBOR map with integer keys. + * Keys and values follow Conway-era CDDL: + * 0: nonempty_set + * 1: nonempty_set + * 2: nonempty_set + * 3: nonempty_set + * 4: nonempty_set + * 5: redeemers (array of [tag, index, data, ex_units]) + * 6: nonempty_set + * 7: nonempty_set + * + * nonempty_set = #6.258([+ a0]) / [+ a0] * * @since 2.0.0 * @category schemas */ -export const CDDLSchema = Schema.Struct({ - 0: Schema.optional(CBOR.tag(258, Schema.Array(VKeyWitnessCDDL))), // 0: tagged nonempty_set - 1: Schema.optional(CBOR.tag(258, Schema.Array(NativeScripts.CDDLSchema))), // 1: tagged nonempty_set - 2: Schema.optional(CBOR.tag(258, Schema.Array(Bootstrap.CDDLSchema))), // 2: tagged nonempty_set - 3: Schema.optional(CBOR.tag(258, Schema.Array(PlutusV1.CDDLSchema))), // 3: tagged nonempty_set - 4: Schema.optional(CBOR.tag(258, Schema.Array(PlutusData.CDDLSchema))), // 4: tagged nonempty_set - 5: Schema.optional(Schema.Array(Redeemer.CDDLSchema)), // 5: redeemers - 6: Schema.optional(CBOR.tag(258, Schema.Array(PlutusV2.CDDLSchema))), // 6: tagged nonempty_set - 7: Schema.optional(CBOR.tag(258, Schema.Array(PlutusV3.CDDLSchema))) // 7: tagged nonempty_set +export const CDDLSchema = Schema.MapFromSelf({ + key: CBOR.Integer, + value: CBOR.CBORSchema }) /** @@ -151,7 +145,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra strict: true, encode: (toA) => Eff.gen(function* () { - const record: { [key: number]: any } = {} + const record = new Map() // 0: vkeywitnesses if (toA.vkeyWitnesses && toA.vkeyWitnesses.length > 0) { @@ -165,7 +159,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra ) ) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[0] = CBOR.Tag.make({ tag: 258, value: vkeyWitnesses }) + record.set(0n, CBOR.Tag.make({ tag: 258, value: vkeyWitnesses })) } // 1: native_scripts @@ -174,7 +168,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra toA.nativeScripts.map((script) => ParseResult.encode(NativeScripts.FromCDDL)(script)) ) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[1] = CBOR.Tag.make({ tag: 258, value: nativeScripts }) + record.set(1n, CBOR.Tag.make({ tag: 258, value: nativeScripts })) } // 2: bootstrap_witnesses @@ -183,14 +177,14 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra toA.bootstrapWitnesses.map((witness) => ParseResult.encode(Bootstrap.FromCDDL)(witness)) ) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[2] = CBOR.Tag.make({ tag: 258, value: bootstrapWitnesses }) + record.set(2n, CBOR.Tag.make({ tag: 258, value: bootstrapWitnesses })) } // 3: plutus_v1_scripts if (toA.plutusV1Scripts && toA.plutusV1Scripts.length > 0) { const plutusV1Scripts = toA.plutusV1Scripts.map((script) => script.bytes) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[3] = CBOR.Tag.make({ tag: 258, value: plutusV1Scripts }) + record.set(3n, CBOR.Tag.make({ tag: 258, value: plutusV1Scripts })) } // 4: plutus_data @@ -199,7 +193,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra toA.plutusData.map((data) => ParseResult.encode(PlutusData.FromCDDL)(data)) ) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[4] = CBOR.Tag.make({ tag: 258, value: plutusDataCBOR }) + record.set(4n, CBOR.Tag.make({ tag: 258, value: plutusDataCBOR })) } // 5: redeemers @@ -213,21 +207,21 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra }) ) ) - record[5] = redeemers + record.set(5n, redeemers) } // 6: plutus_v2_scripts if (toA.plutusV2Scripts && toA.plutusV2Scripts.length > 0) { const plutusV2Scripts = toA.plutusV2Scripts.map((script) => script.bytes) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[6] = CBOR.Tag.make({ tag: 258, value: plutusV2Scripts }) + record.set(6n, CBOR.Tag.make({ tag: 258, value: plutusV2Scripts })) } // 7: plutus_v3_scripts if (toA.plutusV3Scripts && toA.plutusV3Scripts.length > 0) { const plutusV3Scripts = toA.plutusV3Scripts.map((script) => script.bytes) // Use CBOR tag 258 for nonempty_set as per CDDL spec - record[7] = CBOR.Tag.make({ tag: 258, value: plutusV3Scripts }) + record.set(7n, CBOR.Tag.make({ tag: 258, value: plutusV3Scripts })) } return record @@ -236,7 +230,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra Eff.gen(function* () { const witnessSet: { vkeyWitnesses?: Array - nativeScripts?: Array + nativeScripts?: Array bootstrapWitnesses?: Array plutusV1Scripts?: Array plutusData?: Array @@ -246,10 +240,26 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra } = {} // Parse each field from the record + // Helper to accept nonempty_set in both forms: + // - Tagged: #6.258([+ a0]) + // - Untagged: [+ a0] + const asNonEmptyArray = (value: unknown): ReadonlyArray | undefined => { + if (value === undefined) return undefined + if (CBOR.isTag(value)) { + const tag = value as { _tag: "Tag"; tag: number; value: unknown } + if (tag.tag !== 258) return undefined + if (Array.isArray(tag.value)) return tag.value as ReadonlyArray + return undefined + } + if (Array.isArray(value)) return value as ReadonlyArray + return undefined + } + // 0: vkeywitnesses - if (fromA[0] !== undefined) { + const vkeysArr = asNonEmptyArray(fromA.get(0n)) + if (vkeysArr !== undefined) { const vkeyWitnesses: Array = [] - for (const [vkeyBytes, signatureBytes] of fromA[0].value) { + for (const [vkeyBytes, signatureBytes] of vkeysArr) { const vkey = yield* ParseResult.decode(VKey.FromBytes)(vkeyBytes) const signature = yield* ParseResult.decode(Ed25519Signature.FromBytes)(signatureBytes) vkeyWitnesses.push(new VKeyWitness({ vkey, signature })) @@ -258,17 +268,19 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra } // 1: native_scripts - if (fromA[1] !== undefined) { + const nativeArr = asNonEmptyArray(fromA.get(1n)) + if (nativeArr !== undefined) { const nativeScripts = yield* Eff.all( - fromA[1].value.map((scriptCBOR) => ParseResult.decode(NativeScripts.FromCDDL)(scriptCBOR)) + nativeArr.map((scriptCBOR) => ParseResult.decode(NativeScripts.FromCDDL)(scriptCBOR)) ) witnessSet.nativeScripts = nativeScripts } // 2: bootstrap_witnesses - if (fromA[2] !== undefined) { + const bootstrapArr = asNonEmptyArray(fromA.get(2n)) + if (bootstrapArr !== undefined) { const bootstrapWitnesses: Array = [] - for (const tuple of fromA[2].value) { + for (const tuple of bootstrapArr) { const bw = yield* ParseResult.decode(Bootstrap.FromCDDL)(tuple) bootstrapWitnesses.push(bw) } @@ -276,42 +288,78 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Tra } // 3: plutus_v1_scripts - if (fromA[3] !== undefined) { - const plutusV1Scripts = fromA[3].value.map((script) => new PlutusV1.PlutusV1({ bytes: script })) + const p1Arr = asNonEmptyArray(fromA.get(3n)) + if (p1Arr !== undefined) { + const plutusV1Scripts = p1Arr.map((script) => new PlutusV1.PlutusV1({ bytes: script })) witnessSet.plutusV1Scripts = plutusV1Scripts } // 4: plutus_data - if (fromA[4] !== undefined) { + const pdataArr = asNonEmptyArray(fromA.get(4n)) + if (pdataArr !== undefined) { + // Some real-world CBOR producers may include the simple value `undefined` inside this set. + // Filter out such entries before decoding to strict PlutusData. + const isDefined = (x: T | undefined): x is T => x !== undefined + const sanitized = pdataArr.filter(isDefined) const plutusData = yield* Eff.all( - fromA[4].value.map((dataCBOR) => ParseResult.decode(PlutusData.FromCDDL)(dataCBOR)) + sanitized.map((dataCBOR) => ParseResult.decode(PlutusData.FromCDDL)(dataCBOR)) ) witnessSet.plutusData = plutusData } - // 5: redeemers - if (fromA[5] !== undefined) { + // 5: redeemers (note: some producers may wrap this in nonempty_set tag 258 even though CDDL doesn't require it) + const asRedeemersArray = ( + value: unknown + ): + | ReadonlyArray + | undefined => { + if (value === undefined) return undefined + if (CBOR.isTag(value)) { + const tag = value as { _tag: "Tag"; tag: number; value: unknown } + if (tag.tag !== 258) return undefined + if (Array.isArray(tag.value)) + return tag.value as ReadonlyArray< + readonly [bigint, bigint, typeof PlutusData.CDDLSchema.Type, readonly [bigint, bigint]] + > + return undefined + } + if (Array.isArray(value)) + return value as ReadonlyArray< + readonly [bigint, bigint, typeof PlutusData.CDDLSchema.Type, readonly [bigint, bigint]] + > + return undefined + } + + const redeemersArray = asRedeemersArray(fromA.get(5n)) + if (redeemersArray !== undefined) { const redeemers: Array = [] - for (const [tag, index, dataCBOR, exUnits] of fromA[5]) { - const data = yield* ParseResult.decode(PlutusData.FromCDDL)(dataCBOR) + for (const [tag, index, dataCBOR, exUnits] of redeemersArray) { + // Guard against unexpected CBOR simple `undefined` in datum position by substituting empty bytes + const safeDataCBOR = ( + dataCBOR === undefined ? new Uint8Array(0) : dataCBOR + ) as typeof PlutusData.CDDLSchema.Type + const data = yield* ParseResult.decode(PlutusData.FromCDDL)(safeDataCBOR) redeemers.push(new Redeemer.Redeemer({ tag: Redeemer.integerToTag(tag), index, data, exUnits })) } witnessSet.redeemers = redeemers } // 6: plutus_v2_scripts - if (fromA[6] !== undefined) { - const plutusV2Scripts = fromA[6].value.map((bytes) => new PlutusV2.PlutusV2({ bytes })) + const p2Arr = asNonEmptyArray(fromA.get(6n)) + if (p2Arr !== undefined) { + const plutusV2Scripts = p2Arr.map((bytes) => new PlutusV2.PlutusV2({ bytes })) witnessSet.plutusV2Scripts = plutusV2Scripts } // 7: plutus_v3_scripts - if (fromA[7] !== undefined) { - const plutusV3Scripts = fromA[7].value.map((bytes) => new PlutusV3.PlutusV3({ bytes })) + const p3Arr = asNonEmptyArray(fromA.get(7n)) + if (p3Arr !== undefined) { + const plutusV3Scripts = p3Arr.map((bytes) => new PlutusV3.PlutusV3({ bytes })) witnessSet.plutusV3Scripts = plutusV3Scripts } - return yield* ParseResult.decode(Schema.typeSchema(TransactionWitnessSet))(witnessSet) + // Build the class instance directly to allow fully empty witness sets + return new TransactionWitnessSet(witnessSet, { disableValidation: true }) }) }) @@ -495,7 +543,7 @@ export const fromVKeyWitnesses = (witnesses: Array): TransactionWit * @since 2.0.0 * @category constructors */ -export const fromNativeScripts = (scripts: Array): TransactionWitnessSet => +export const fromNativeScripts = (scripts: Array): TransactionWitnessSet => TransactionWitnessSet.make({ nativeScripts: scripts }) // ============================================================================ diff --git a/packages/evolution/src/core/Value.ts b/packages/evolution/src/core/Value.ts index 281678e5..8e611dc8 100644 --- a/packages/evolution/src/core/Value.ts +++ b/packages/evolution/src/core/Value.ts @@ -328,7 +328,7 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Val return new WithAssets({ coin: Coin.make(coinAmount), - assets: MultiAsset.make(result) + assets: result as MultiAsset.MultiAsset }) } }) diff --git a/packages/evolution/src/core/VotingProcedures.ts b/packages/evolution/src/core/VotingProcedures.ts index cba9d104..309f7c27 100644 --- a/packages/evolution/src/core/VotingProcedures.ts +++ b/packages/evolution/src/core/VotingProcedures.ts @@ -43,7 +43,7 @@ export class VotingProceduresError extends Data.TaggedError("VotingProceduresErr export class ConstitutionalCommitteeVoter extends Schema.TaggedClass()( "ConstitutionalCommitteeVoter", { - credential: Credential.Credential + credential: Credential.CredentialSchema } ) {} @@ -411,7 +411,7 @@ export const makeProcedure = (vote: Vote, anchor?: Anchor.Anchor | null): Voting * @since 2.0.0 * @category constructors */ -export const makeCommitteeVoter = (credential: Credential.Credential): Voter => +export const makeCommitteeVoter = (credential: Credential.CredentialSchema): Voter => new ConstitutionalCommitteeVoter({ credential }) /** @@ -522,7 +522,7 @@ export const isAbstainVote = (vote: Vote): vote is Schema.Schema.Type(patterns: { - ConstitutionalCommitteeVoter: (credential: Credential.Credential) => R + ConstitutionalCommitteeVoter: (credential: Credential.CredentialSchema) => R DRepVoter: (drep: DRep.DRep) => R StakePoolVoter: (poolKeyHash: PoolKeyHash.PoolKeyHash) => R }) => diff --git a/packages/evolution/src/core/VrfCert.ts b/packages/evolution/src/core/VrfCert.ts index a85b0da2..32f756e0 100644 --- a/packages/evolution/src/core/VrfCert.ts +++ b/packages/evolution/src/core/VrfCert.ts @@ -35,11 +35,15 @@ export class VRFOutput extends Schema.TaggedClass()("VrfOutput", { * @since 2.0.0 * @category schemas */ -export const VRFOutputFromBytes = Schema.transform(Schema.typeSchema(Bytes32.BytesFromHex), Schema.typeSchema(VRFOutput), { - strict: true, - decode: (bytes) => new VRFOutput({ bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 - encode: (vrfOutput) => vrfOutput.bytes -}).annotations({ +export const VRFOutputFromBytes = Schema.transform( + Schema.typeSchema(Bytes32.BytesFromHex), + Schema.typeSchema(VRFOutput), + { + strict: true, + decode: (bytes) => new VRFOutput({ bytes }, { disableValidation: true }), // Disable validation since we already check length in Bytes32 + encode: (vrfOutput) => vrfOutput.bytes + } +).annotations({ identifier: "VrfOutput.Bytes" }) diff --git a/packages/evolution/src/core/Withdrawals.ts b/packages/evolution/src/core/Withdrawals.ts index 7c179442..60f50faa 100644 --- a/packages/evolution/src/core/Withdrawals.ts +++ b/packages/evolution/src/core/Withdrawals.ts @@ -28,8 +28,8 @@ export class WithdrawalsError extends Data.TaggedError("WithdrawalsError")<{ * @category model */ export class Withdrawals extends Schema.TaggedClass()("Withdrawals", { - withdrawals: Schema.MapFromSelf({ - key: RewardAccount.RewardAccount, + withdrawals: Schema.Map({ + key: RewardAccount.FromBech32, value: Coin.Coin }) }) {} diff --git a/packages/evolution/src/sdk/Address.ts b/packages/evolution/src/sdk/Address.ts index 35a6ef06..4afc73d9 100644 --- a/packages/evolution/src/sdk/Address.ts +++ b/packages/evolution/src/sdk/Address.ts @@ -1,99 +1,15 @@ -import { Data, ParseResult, pipe, Schema } from "effect" +import { Function, Schema } from "effect" import * as CoreAddressStructure from "../core/AddressStructure.js" -export class AddressError extends Data.TaggedError("AddressError")<{ - message?: string - cause?: unknown -}> {} - // bech32 export type Address = string export const toAddressStructure = Schema.decodeSync(CoreAddressStructure.FromBech32) export const fromAddressStructure = Schema.encodeSync(CoreAddressStructure.FromBech32) -export const fromStruct = Schema.decodeSync(CoreAddressStructure.AddressStructure) -export const toStruct = Schema.encodeSync(CoreAddressStructure.AddressStructure) - - -// export const FromAddressStructure = Schema.transformOrFail( -// Schema.typeSchema(CoreAddressStructure.AddressStructure), -// Schema.String, -// { -// strict: true, -// encode: (address) => ParseResult.decodeEither(CoreAddressStructure.FromBech32)(address), -// decode: (value) => ParseResult.encodeEither(CoreAddressStructure.FromBech32)(value) -// } -// ) - -// export const toCoreAddress = Function.makeEncodeSync(FromAddressStructure, AddressError, "toCoreAddress") - -// export const fromCoreAddress = Function.makeDecodeSync(FromAddressStructure, AddressError, "fromCoreAddress") - -// export const fromCredentials = ({ -// network = "Mainnet", -// paymentCredential, -// stakingCredential -// }: { -// network?: "Mainnet" | "Testnet" -// paymentCredential: Credential.Credential -// stakingCredential: Credential.Credential -// }): Either.Either => { -// const payment = CoreCredential.Either.fromCBORHex(paymentCredential.hash) -// if (Either.isLeft(payment)) { -// return Either.left(new AddressError({ message: "Invalid payment credential", cause: payment.left })) -// } -// const staking = CoreCredential.Either.fromCBORHex(stakingCredential.hash) -// if (Either.isLeft(staking)) { -// return Either.left(new AddressError({ message: "Invalid staking credential", cause: staking.left })) -// } - -// const coreAddress = new CoreAddressStructure.AddressStructure({ -// networkId: network === "Mainnet" ? 1 : 0, -// paymentCredential: payment.right, -// stakingCredential: staking.right -// }) -// return Either.right(CoreAddressStructure.toBech32(coreAddress)) -// } - -// export const withCredentials = ({ -// network, -// paymentCredential, -// stakingCredential -// }: { -// network: "Mainnet" | "Testnet" -// paymentCredential: Credential.Credential -// stakingCredential?: Credential.Credential -// }) => { -// const coreAddress = new CoreAddressStructure.AddressStructure({ -// networkId: network === "Mainnet" ? 1 : 0, // Fix: 1 for Mainnet, 0 for Testnet -// paymentCredential: Credential.toCoreCredential(paymentCredential), -// stakingCredential: stakingCredential ? Credential.toCoreCredential(stakingCredential) : undefined -// }) -// return CoreAddressStructure.toBech32(coreAddress) -// } +export const fromJsonToAddressStructure = Schema.decodeSync(CoreAddressStructure.AddressStructure) +export const fromAddressStructureToJson = Schema.encodeSync(CoreAddressStructure.AddressStructure) -// /** -// * Create an enterprise address (payment only) from a payment credential. -// * An enterprise address has only a payment credential and no staking credential. -// * -// * @example -// * ```typescript -// * const credential = { type: "Key", hash: "abcd1234..." } -// * const address = fromPaymentCredential(credential, "Mainnet") -// * // Returns: "addr1w9rlm65wjfkctdlyxl5cw87h9... -// * ``` -// */ -// export const fromPaymentCredential = ( -// paymentCredential: Credential.Credential, -// network: Credential.Network = "Mainnet" -// ): Address => { -// const coreAddress = new CoreAddressStructure.AddressStructure({ -// networkId: network === "Mainnet" ? 1 : 0, -// paymentCredential: Credential.toCoreCredential(paymentCredential), -// // No staking credential for enterprise addresses -// stakingCredential: undefined -// }) -// return CoreAddressStructure.toBech32(coreAddress) -// } +export const addressToJson = Function.flow(toAddressStructure, fromAddressStructureToJson) +export const jsonToAddress = Function.flow(fromJsonToAddressStructure, fromAddressStructure) diff --git a/packages/evolution/src/sdk/Assets.ts b/packages/evolution/src/sdk/Assets.ts index 662c153e..d71cdee3 100644 --- a/packages/evolution/src/sdk/Assets.ts +++ b/packages/evolution/src/sdk/Assets.ts @@ -254,7 +254,7 @@ export const assetsToValue = (assets: Assets): CoreValue.Value => { } // Create the MultiAsset using the make function - const multiAsset = MultiAsset.make(multiAssetMap) + const multiAsset = multiAssetMap as MultiAsset.MultiAsset return CoreValue.withAssets(coin, multiAsset) } diff --git a/packages/evolution/src/sdk/Credential.ts b/packages/evolution/src/sdk/Credential.ts index 2d9713b8..d703cc0b 100644 --- a/packages/evolution/src/sdk/Credential.ts +++ b/packages/evolution/src/sdk/Credential.ts @@ -7,47 +7,7 @@ import type * as _ScriptHash from "../core/ScriptHash.js" export type ScriptHash = typeof _ScriptHash.ScriptHash.Encoded export type KeyHash = typeof _KeyHash.KeyHash.Encoded -export type Credential = typeof _Credential.Credential.Encoded +export type Credential = typeof _Credential.CredentialSchema.Encoded -export const toCoreCredential = Schema.decodeSync(_Credential.Credential) -export const fromCoreCredential = Schema.encodeSync(_Credential.Credential) - -// export type ScriptHash = { -// type: "Script" -// hash: string // hex string -// } - -// export type KeyHash = { -// type: "Key" -// hash: string // hex string -// } - -// export type Credential = ScriptHash | KeyHash - -// export type Network = "Mainnet" | "Testnet" - -// export const toCoreCredential = (credential: Credential): CoreCredential.Credential => { -// if (credential.type === "Key") { -// return CoreCredential.Credential.members[0].make({ -// hash: fromHex(credential.hash) -// }) -// } else { -// return CoreCredential.Credential.members[1].make({ -// hash: fromHex(credential.hash) -// }) -// } -// } - -// export const fromCoreCredential = (credential: CoreCredential.Credential): Credential => { -// if (credential._tag === "KeyHash") { -// return { -// type: "Key", -// hash: credential.toString() -// } -// } else { -// return { -// type: "Script", -// hash: credential.toString() -// } -// } -// } +export const fromCredentialToJson = Schema.encodeSync(_Credential.CredentialSchema) +export const jsonToCredential = Schema.decodeSync(_Credential.CredentialSchema) diff --git a/packages/evolution/src/sdk/RewardAddress.ts b/packages/evolution/src/sdk/RewardAddress.ts index be5d5bfa..571f671f 100644 --- a/packages/evolution/src/sdk/RewardAddress.ts +++ b/packages/evolution/src/sdk/RewardAddress.ts @@ -1,6 +1,6 @@ -import * as CoreAddressEras from "../core/AddressEras.js" +import { Function, Schema } from "effect" + import * as CoreRewardAccount from "../core/RewardAccount.js" -import * as Credential from "./Credential.js" /** * Reward address in bech32 format. @@ -9,49 +9,11 @@ import * as Credential from "./Credential.js" */ export type RewardAddress = string -/** - * Convert bech32 reward address to core RewardAccount structure - */ -export const toCoreRewardAccount = (address: RewardAddress): CoreRewardAccount.RewardAccount => { - const addressEras = CoreAddressEras.fromBech32(address) - if (addressEras._tag !== "RewardAccount") { - throw new Error(`Invalid reward address: expected RewardAccount, got ${addressEras._tag}`) - } - return addressEras -} +export const toRewardAccount = Schema.decodeSync(CoreRewardAccount.FromBech32) +export const fromRewardAccount = Schema.encodeSync(CoreRewardAccount.FromBech32) -/** - * Convert core RewardAccount structure to bech32 reward address - */ -export const fromCoreRewardAccount = (account: CoreRewardAccount.RewardAccount): RewardAddress => - CoreAddressEras.toBech32(account) +export const fromJsonToRewardAccount = Schema.decodeSync(CoreRewardAccount.RewardAccount) +export const fromRewardAccountToJson = Schema.encodeSync(CoreRewardAccount.RewardAccount) -/** - * Create a reward address from a stake credential. - * A reward address is used for staking rewards and has only a stake credential. - * - * @param stakeCredential - The stake credential (key hash or script hash) - * @param network - Target network (defaults to "Mainnet") - * @returns Bech32 reward address string - * - * @example - * ```typescript - * const credential = { type: "Key", hash: "abcd1234..." } - * const rewardAddr = fromStakeCredential(credential, "Mainnet") - * // Returns: "stake1u9rlm65wjfkctdlyxl5cw87h9..." - * - * const scriptCredential = { type: "Script", hash: "def5678..." } - * const testnetAddr = fromStakeCredential(scriptCredential, "Testnet") - * // Returns: "stake_test1ur9rlm65wjfkctdlyxl5cw87h9..." - * ``` - */ -// export const fromStakeCredential = ( -// stakeCredential: Credential.Credential, -// network: Credential.Network = "Mainnet" -// ): RewardAddress => { -// const coreRewardAccount = new CoreRewardAccount.RewardAccount({ -// networkId: network === "Mainnet" ? 1 : 0, -// stakeCredential: Credential.toCoreCredential(stakeCredential) -// }) -// return CoreAddressEras.toBech32(coreRewardAccount) -// } +export const rewardAddressToJson = Function.flow(toRewardAccount, fromRewardAccountToJson) +export const jsonToRewardAddress = Function.flow(fromJsonToRewardAccount, fromRewardAccount) diff --git a/packages/evolution/src/sdk/Script.ts b/packages/evolution/src/sdk/Script.ts index c9b409e6..45bf6e48 100644 --- a/packages/evolution/src/sdk/Script.ts +++ b/packages/evolution/src/sdk/Script.ts @@ -1,10 +1,5 @@ -import { pipe } from "effect" - import { fromHex } from "../core/Bytes.js" import * as CBOR from "../core/CBOR.js" -import * as CoreScript from "../core/Script.js" -import * as CoreScriptHash from "../core/ScriptHash.js" -import type * as Credential from "./Credential.js" export type Native = { type: "Native" diff --git a/packages/evolution/test/AuxiliaryData.CML.test.ts b/packages/evolution/test/AuxiliaryData.CML.test.ts index dab180f8..f3ba479b 100644 --- a/packages/evolution/test/AuxiliaryData.CML.test.ts +++ b/packages/evolution/test/AuxiliaryData.CML.test.ts @@ -26,79 +26,6 @@ describe("AuxiliaryData CML Compatibility", () => { expect(evolutionCbor).toBe(cmlCbor) }) - it("validates auxiliary data with native scripts - Conway format compatibility", () => { - // Create a simple native script (sig script) - use 28 bytes for Ed25519KeyHash - const keyHashBytes = new Uint8Array(28).fill(0x42) // 28 bytes of 0x42 - - const evolutionNativeScript = { - type: "sig" as const, - keyHash: Array.from(keyHashBytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - } - - const cmlNativeScript = CML.NativeScript.new_script_pubkey(CML.Ed25519KeyHash.from_raw_bytes(keyHashBytes)) - - // Create auxiliary data with native scripts - const evolutionAuxData = AuxiliaryData.conway({ - nativeScripts: [evolutionNativeScript] - }) - - const cmlNativeScriptList = CML.NativeScriptList.new() - cmlNativeScriptList.add(cmlNativeScript) - - const cmlAuxData = CML.ConwayFormatAuxData.new() - cmlAuxData.set_native_scripts(cmlNativeScriptList) - - // Compare CBOR outputs - these should be identical in Conway format - const evolutionCbor = AuxiliaryData.toCBORHex(evolutionAuxData) - const cmlCbor = cmlAuxData.to_cbor_hex() - - expect(evolutionCbor).toBe(cmlCbor) - }) - - it("validates auxiliary data with multiple native scripts - Conway format compatibility", () => { - // Create multiple native scripts with 28-byte hashes - const keyHash1 = new Uint8Array(28).fill(0x42) - const keyHash2 = new Uint8Array(28).fill(0x24) - - const evolutionScript1 = { - type: "sig" as const, - keyHash: Array.from(keyHash1) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - } - - const evolutionScript2 = { - type: "sig" as const, - keyHash: Array.from(keyHash2) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - } - - const cmlScript1 = CML.NativeScript.new_script_pubkey(CML.Ed25519KeyHash.from_raw_bytes(keyHash1)) - - const cmlScript2 = CML.NativeScript.new_script_pubkey(CML.Ed25519KeyHash.from_raw_bytes(keyHash2)) - - // Create auxiliary data with native scripts - const evolutionAuxData = AuxiliaryData.conway({ - nativeScripts: [evolutionScript1, evolutionScript2] - }) - - const cmlNativeScriptList = CML.NativeScriptList.new() - cmlNativeScriptList.add(cmlScript1) - cmlNativeScriptList.add(cmlScript2) - - const cmlAuxData = CML.ConwayFormatAuxData.new() - cmlAuxData.set_native_scripts(cmlNativeScriptList) - - // Compare CBOR outputs - these should be identical in Conway format - const evolutionCbor = AuxiliaryData.toCBORHex(evolutionAuxData) - const cmlCbor = cmlAuxData.to_cbor_hex() - - expect(evolutionCbor).toBe(cmlCbor) - }) - it("property: Evolution SDK CBOR matches CML CBOR for any generated AuxiliaryData", () => { FastCheck.assert( FastCheck.property(AuxiliaryData.arbitrary, (evolutionAuxData) => { diff --git a/packages/evolution/test/Data.golden.test.ts b/packages/evolution/test/Data.golden.test.ts index 6b419a63..8ae895b8 100644 --- a/packages/evolution/test/Data.golden.test.ts +++ b/packages/evolution/test/Data.golden.test.ts @@ -639,6 +639,17 @@ const normalizeDecodedData = (data: unknown): unknown => { } } + if (data instanceof Uint8Array) { + // Convert Uint8Array to hex string for comparison + const hexString = Array.from(data) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") + return { + _tag: "ByteArray", + bytearray: hexString + } + } + if (data === null || data === undefined) { return data } diff --git a/packages/evolution/test/Data.test.ts b/packages/evolution/test/Data.test.ts index 7f7a50c1..72fe7e29 100644 --- a/packages/evolution/test/Data.test.ts +++ b/packages/evolution/test/Data.test.ts @@ -8,7 +8,8 @@ describe("Data Module Tests", () => { const validHexCases = ["deadbeef", "cafe0123", "abcdef0123456789", "00", "ff"] it.each(validHexCases)("should create valid Bytes: %s", (input) => { - expect(Data.isBytes(input)).toBe(true) + const bytes = Data.bytearray(input) + expect(Data.isBytes(bytes)).toBe(true) }) const invalidHexCases = ["not-hex", "xyz", "123g", "deadbeef ", " deadbeef", "0x123456"] @@ -16,764 +17,768 @@ describe("Data Module Tests", () => { it.each(invalidHexCases)("should fail schema validation on invalid hex string: %s", (input) => { expect(Data.isBytes(input)).toBe(false) }) + }) - it("should validate PlutusBytes with schema", () => { - const bytes = "deadbeef" - expect(Data.isBytes(bytes)).toBe(true) - }) + it("should validate PlutusBytes with schema", () => { + const bytes = Data.bytearray("deadbeef") + expect(Data.isBytes(bytes)).toBe(true) }) + }) - describe("Plutus Int", () => { - const integerCases = [ - 0n, - 1n, - -1n, - 42n, - -42n, - 2n ** 63n - 1n, - -(2n ** 63n), - 2n ** 64n, - -(2n ** 64n), - 123456789123456789n, - -123456789123456789n - ] + describe("Plutus Int", () => { + const integerCases = [ + 0n, + 1n, + -1n, + 42n, + -42n, + 2n ** 63n - 1n, + -(2n ** 63n), + 2n ** 64n, + -(2n ** 64n), + 123456789123456789n, + -123456789123456789n + ] - it.each(integerCases)("should create valid Plutus Int: %s", (input) => { - expect(Data.isInt(input)).toBe(true) - }) + it.each(integerCases)("should create valid Plutus Int: %s", (input) => { + expect(Data.isInt(input)).toBe(true) + }) - it("should fail validation with non-bigint value", () => { - const invalidInt = "not-a-bigint" - expect(Data.isInt(invalidInt)).toBe(false) - }) + it("should fail validation with non-bigint value", () => { + const invalidInt = "not-a-bigint" + expect(Data.isInt(invalidInt)).toBe(false) + }) - it("should validate PlutusBigInt with schema", () => { - const integer = 42n - expect(Data.isInt(integer)).toBe(true) - }) + it("should validate PlutusBigInt with schema", () => { + const integer = 42n + expect(Data.isInt(integer)).toBe(true) }) + }) - describe("Plutus List", () => { - it("should create a valid empty list", () => { - const list: Data.List = [] - expect(list).toEqual([]) - expect(Data.isList(list)).toBe(true) - }) + describe("Plutus List", () => { + it("should create a valid empty list", () => { + const list: Data.List = [] + expect(list).toEqual([]) + expect(Data.isList(list)).toBe(true) + }) - it("should create a valid list with elements", () => { - const list = [42n, "deadbeef"] - expect(list).toHaveLength(2) - expect(Data.isList(list)).toBe(true) - }) + it("should create a valid list with elements", () => { + const list = [42n, Data.bytearray("deadbeef")] + expect(list).toHaveLength(2) + expect(Data.isList(list)).toBe(true) + }) - it("should validate Plutus List with schema", () => { - const list = [42n, "cafe"] - expect(Data.isList(list)).toBe(true) - }) + it("should validate Plutus List with schema", () => { + const list = [42n, Data.bytearray("cafe")] + expect(Data.isList(list)).toBe(true) }) + }) - describe("Plutus Map", () => { - it("should create a valid empty map", () => { - const map = Data.map([]) - expect((map as Data.Map).size).toBe(0) - expect(Data.isMap(map)).toBe(true) - }) + describe("Plutus Map", () => { + it("should create a valid empty map", () => { + const map = Data.map([]) + expect((map as Data.Map).size).toBe(0) + expect(Data.isMap(map)).toBe(true) + }) - it("should create a valid map with entries", () => { - const map = Data.map([ - ["cafe", 42n], - [99n, "deadbeef"] - ]) - expect((map as Data.Map).size).toBe(2) - expect(Data.isMap(map)).toBe(true) - }) + it("should create a valid map with entries", () => { + const map = Data.map([ + [Data.bytearray("cafe"), 42n], + [99n, Data.bytearray("deadbeef")] + ]) + expect((map as Data.Map).size).toBe(2) + expect(Data.isMap(map)).toBe(true) + }) - it("should validate Plutus Map with schema", () => { - const map = Data.map([[1n, 2n]]) - expect(Data.isMap(map)).toBe(true) - }) + it("should validate Plutus Map with schema", () => { + const map = Data.map([[1n, 2n]]) + expect(Data.isMap(map)).toBe(true) }) + }) - describe("Constr", () => { - it("should create a valid constructor with no fields", () => { - const constr = Data.constr(0n, []) - expect(constr.index).toBe(0n) - expect(constr.fields).toEqual([]) - expect(Data.isConstr(constr)).toBe(true) - }) + describe("Constr", () => { + it("should create a valid constructor with no fields", () => { + const constr = Data.constr(0n, []) + expect(constr.index).toBe(0n) + expect(constr.fields).toEqual([]) + expect(Data.isConstr(constr)).toBe(true) + }) - it("should create a valid constructor with fields", () => { - const constr = Data.constr(2n, [42n, "deadbeef", [99n]]) - expect(constr.index).toBe(2n) - expect(constr.fields).toHaveLength(3) - expect(Data.isConstr(constr)).toBe(true) - }) + it("should create a valid constructor with fields", () => { + const constr = Data.constr(2n, [42n, Data.bytearray("deadbeef"), [99n]]) + expect(constr.index).toBe(2n) + expect(constr.fields).toHaveLength(3) + expect(Data.isConstr(constr)).toBe(true) + }) - it("should validate Constr with schema", () => { - const constr = Data.constr(5n, [42n, "cafe"]) - expect(Data.isConstr(constr)).toBe(true) - }) + it("should validate Constr with schema", () => { + const constr = Data.constr(5n, [42n, Data.bytearray("cafe")]) + expect(Data.isConstr(constr)).toBe(true) + }) - it("should handle large constructor indices", () => { - const largeIndex = 2n ** 32n + 1n // Well beyond the direct tag range - const constr = Data.constr(largeIndex, [1n]) - expect(constr.index).toBe(largeIndex) - expect(Data.isConstr(constr)).toBe(true) - }) + it("should handle large constructor indices", () => { + const largeIndex = 2n ** 32n + 1n // Well beyond the direct tag range + const constr = Data.constr(largeIndex, [1n]) + expect(constr.index).toBe(largeIndex) + expect(Data.isConstr(constr)).toBe(true) }) }) +}) - describe("CBOR Encoding/Decoding", () => { - const testCases = [ - { - name: "empty constructor", - value: Data.constr(0n, []), - expectedHex: "d87980" - }, - { - name: "constructor with fields", - value: Data.constr(1n, [42n, "cafe"]), - expectedHex: "d87a9f182a42cafeff" - }, - { - name: "large constructor index", - value: Data.constr(999999n, [42n]), - expectedHex: "d8669f1a000f423f9f182affff" - }, - { - name: "empty list", - value: [], - expectedHex: "80" - }, - { - name: "list with mixed elements", - value: [1n, "deadbeef", []], - expectedHex: "9f0144deadbeef80ff" - }, - { - name: "empty map", - value: Data.map([]), - expectedHex: "a0" - }, - { - name: "map with entries", - value: Data.map([[1n, "cafe"]]), - expectedHex: "bf0142cafeff" - }, - { - name: "small int", - value: 42n, - expectedHex: "182a" - }, - { - name: "negative int", - value: -42n, - expectedHex: "3829" - }, - { - name: "large positive int with value", - value: 11375342928504387279n, - expectedHex: "1b9ddd561fd3c176cf" - }, - { - name: "bytes", - value: "deadbeef", - expectedHex: "44deadbeef" - }, - { - name: "empty bytes", - value: "", - expectedHex: "40" - } - ] - - describe("Standard Encoding/Decoding", () => { - describe("Hex Encoding/Decoding", () => { - it.each(testCases)("should round-trip $name via hex encoding", ({ expectedHex, value }) => { - const encoded = Data.toCBORHex(value) - const decoded = Data.fromCBORHex(encoded) - expect(decoded).toEqual(value) - expect(encoded).toBe(expectedHex) - }) +describe("CBOR Encoding/Decoding", () => { + const testCases = [ + { + name: "empty constructor", + value: Data.constr(0n, []), + expectedHex: "d87980" + }, + { + name: "constructor with fields", + value: Data.constr(1n, [42n, Data.bytearray("cafe")]), + expectedHex: "d87a9f182a42cafeff" + }, + { + name: "large constructor index", + value: Data.constr(999999n, [42n]), + expectedHex: "d8669f1a000f423f9f182affff" + }, + { + name: "empty list", + value: [], + expectedHex: "80" + }, + { + name: "list with mixed elements", + value: [1n, Data.bytearray("deadbeef"), []], + expectedHex: "9f0144deadbeef80ff" + }, + { + name: "empty map", + value: Data.map([]), + expectedHex: "a0" + }, + { + name: "map with entries", + value: Data.map([[1n, Data.bytearray("cafe")]]), + expectedHex: "bf0142cafeff" + }, + { + name: "small int", + value: 42n, + expectedHex: "182a" + }, + { + name: "negative int", + value: -42n, + expectedHex: "3829" + }, + { + name: "large positive int with value", + value: 11375342928504387279n, + expectedHex: "1b9ddd561fd3c176cf" + }, + { + name: "bytes", + value: Data.bytearray("deadbeef"), + expectedHex: "44deadbeef" + }, + { + name: "empty bytes", + value: Data.bytearray(""), + expectedHex: "40" + } + ] + + describe("Standard Encoding/Decoding", () => { + describe("Hex Encoding/Decoding", () => { + it.each(testCases)("should round-trip $name via hex encoding", ({ expectedHex, value }) => { + const encoded = Data.toCBORHex(value) + const decoded = Data.fromCBORHex(encoded) + expect(decoded).toEqual(value) + expect(encoded).toBe(expectedHex) }) + }) - describe("Bytes Encoding/Decoding", () => { - it.each(testCases)("should round-trip $name via bytes encoding", ({ value }) => { - const encoded = Data.toCBORBytes(value) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(value) - }) + describe("Bytes Encoding/Decoding", () => { + it.each(testCases)("should round-trip $name via bytes encoding", ({ value }) => { + const encoded = Data.toCBORBytes(value) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(value) }) }) }) +}) - describe("Utility Functions", () => { - describe("matchConstr", () => { - it("should match specific constructor indices", () => { - const constr = Data.constr(1n, [42n]) - const result = Data.matchConstr(constr, { - 1: (fields) => `Found index 1 with ${fields.length} fields`, - 2: (fields) => `Found index 2 with ${fields.length} fields`, - _: (index, fields) => `Default case: index ${index} with ${fields.length} fields` - }) - expect(result).toBe("Found index 1 with 1 fields") +describe("Utility Functions", () => { + describe("matchConstr", () => { + it("should match specific constructor indices", () => { + const constr = Data.constr(1n, [42n]) + const result = Data.matchConstr(constr, { + 1: (fields) => `Found index 1 with ${fields.length} fields`, + 2: (fields) => `Found index 2 with ${fields.length} fields`, + _: (index, fields) => `Default case: index ${index} with ${fields.length} fields` }) + expect(result).toBe("Found index 1 with 1 fields") + }) - it("should use default case for non-matched indices", () => { - const constr = Data.constr(99n, [42n]) - const result = Data.matchConstr(constr, { - 1: (fields) => `Found index 1 with ${fields.length} fields`, - 2: (fields) => `Found index 2 with ${fields.length} fields`, - _: (index, fields) => `Default case: index ${index} with ${fields.length} fields` - }) - expect(result).toBe("Default case: index 99 with 1 fields") + it("should use default case for non-matched indices", () => { + const constr = Data.constr(99n, [42n]) + const result = Data.matchConstr(constr, { + 1: (fields) => `Found index 1 with ${fields.length} fields`, + 2: (fields) => `Found index 2 with ${fields.length} fields`, + _: (index, fields) => `Default case: index ${index} with ${fields.length} fields` }) + expect(result).toBe("Default case: index 99 with 1 fields") }) + }) - describe("matchPlutusData", () => { - it("should match Plutus Map type", () => { - const map = Data.map([]) - const result = Data.matchData(map, { - Map: (entries) => `Map with ${entries.length} entries`, - List: (items) => `List with ${items.length} items`, - Int: (value) => `BigInt: ${value}`, - Bytes: (bytes) => `Bytes: ${bytes}`, - Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` - }) - expect(result).toBe("Map with 0 entries") - }) + describe("matchPlutusData", () => { + it("should match Plutus Map type", () => { + const map = Data.map([]) + const result = Data.matchData(map, { + Map: (entries) => `Map with ${entries.length} entries`, + List: (items) => `List with ${items.length} items`, + Int: (value) => `BigInt: ${value}`, + Bytes: (bytes) => `Bytes: ${bytes}`, + Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` + }) + expect(result).toBe("Map with 0 entries") + }) - it("should match Plutus List type", () => { - const list = [1n] - const result = Data.matchData(list, { - Map: (entries) => `Map with ${entries.length} entries`, - List: (items) => `List with ${items.length} items`, - Int: (value) => `BigInt: ${value}`, - Bytes: (bytes) => `Bytes: ${bytes}`, - Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` - }) - expect(result).toBe("List with 1 items") + it("should match Plutus List type", () => { + const list = [1n] + const result = Data.matchData(list, { + Map: (entries) => `Map with ${entries.length} entries`, + List: (items) => `List with ${items.length} items`, + Int: (value) => `BigInt: ${value}`, + Bytes: (bytes) => `Bytes: ${bytes}`, + Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` }) + expect(result).toBe("List with 1 items") + }) - it("should match Plutus Int type", () => { - const int = 42n - const result = Data.matchData(int, { - Map: (entries) => `Map with ${entries.length} entries`, - List: (items) => `List with ${items.length} items`, - Int: (value) => `BigInt: ${value}`, - Bytes: (bytes) => `Bytes: ${bytes}`, - Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` - }) - expect(result).toBe("BigInt: 42") + it("should match Plutus Int type", () => { + const int = 42n + const result = Data.matchData(int, { + Map: (entries) => `Map with ${entries.length} entries`, + List: (items) => `List with ${items.length} items`, + Int: (value) => `BigInt: ${value}`, + Bytes: (bytes) => `Bytes: ${bytes}`, + Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` }) + expect(result).toBe("BigInt: 42") + }) - it("should match Plutus Bytes type", () => { - const bytes = "cafe" - const result = Data.matchData(bytes, { - Map: (entries) => `Map with ${entries.length} entries`, - List: (items) => `List with ${items.length} items`, - Int: (value) => `BigInt: ${value}`, - Bytes: (bytes) => `Bytes: ${bytes}`, - Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` - }) - expect(result).toBe("Bytes: cafe") + it("should match Plutus Bytes type", () => { + const bytes = Data.bytearray("cafe") + const result = Data.matchData(bytes, { + Map: (entries) => `Map with ${entries.length} entries`, + List: (items) => `List with ${items.length} items`, + Int: (value) => `BigInt: ${value}`, + Bytes: (bytes) => `Bytes: ${Array.from(bytes).join(",")}`, + Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` }) + expect(result).toBe("Bytes: 202,254") + }) - it("should match Constr type", () => { - const constr = Data.constr(3n, []) - const result = Data.matchData(constr, { - Map: (entries) => `Map with ${entries.length} entries`, - List: (items) => `List with ${items.length} items`, - Int: (value) => `BigInt: ${value}`, - Bytes: (bytes) => `Bytes: ${bytes}`, - Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` - }) - expect(result).toBe("Constructor 3 with 0 fields") + it("should match Constr type", () => { + const constr = Data.constr(3n, []) + const result = Data.matchData(constr, { + Map: (entries) => `Map with ${entries.length} entries`, + List: (items) => `List with ${items.length} items`, + Int: (value) => `BigInt: ${value}`, + Bytes: (bytes) => `Bytes: ${bytes}`, + Constr: (constr) => `Constructor ${constr.index} with ${constr.fields.length} fields` }) + expect(result).toBe("Constructor 3 with 0 fields") }) }) +}) - describe("Complex Structures", () => { - it("should handle nested structures", () => { - // Create a complex nested structure - const complex = Data.constr(0n, [ - [1n, 2n, "cafe"], - Data.map([ - [42n, ["deadbeef"]], - ["deadbeef", Data.constr(1n, [-999n])] - ]), - Data.constr(7n, [[], Data.map([])]) - ]) +describe("Complex Structures", () => { + it("should handle nested structures", () => { + // Create a complex nested structure + const complex = Data.constr(0n, [ + [1n, 2n, Data.bytearray("cafe")], + Data.map([ + [42n, [Data.bytearray("deadbeef")]], + [Data.bytearray("deadbeef"), Data.constr(1n, [-999n])] + ]), + Data.constr(7n, [[], Data.map([])]) + ]) - const encoded = Data.toCBORHex(complex) - const decoded = Data.fromCBORHex(encoded) - expect(decoded).toEqual(complex) - }) + const encoded = Data.toCBORHex(complex) + const decoded = Data.fromCBORHex(encoded) + expect(decoded).toEqual(complex) }) +}) - describe("Integer Boundary Testing", () => { - const boundaryTestCases = [ - { name: "Zero", value: 0n }, - { name: "Small positive (23)", value: 23n }, - { name: "Small positive (24)", value: 24n }, - { name: "Small positive (255)", value: 255n }, - { name: "Small positive (256)", value: 256n }, - { name: "Medium positive (65535)", value: 65535n }, - { name: "Medium positive (65536)", value: 65536n }, - { name: "Large positive (4294967295)", value: 4294967295n }, - { name: "Large positive (4294967296)", value: 4294967296n }, - { name: "64-bit boundary (2^63 - 1)", value: 9223372036854775807n }, - { name: "64-bit boundary (2^63)", value: 9223372036854775808n }, - { name: "64-bit max (2^64 - 1)", value: 18446744073709551615n }, - { name: "Bignum threshold (2^64)", value: 18446744073709551616n }, - { name: "Large bignum (2^64 + 1)", value: 18446744073709551617n }, - { - name: "Very large bignum (2^128)", - value: 340282366920938463463374607431768211456n - }, - { name: "Small negative (-1)", value: -1n }, - { name: "Small negative (-24)", value: -24n }, - { name: "Small negative (-25)", value: -25n }, - { name: "Medium negative (-256)", value: -256n }, - { name: "Medium negative (-257)", value: -257n }, - { name: "Large negative (-4294967296)", value: -4294967296n }, +describe("Integer Boundary Testing", () => { + const boundaryTestCases = [ + { name: "Zero", value: 0n }, + { name: "Small positive (23)", value: 23n }, + { name: "Small positive (24)", value: 24n }, + { name: "Small positive (255)", value: 255n }, + { name: "Small positive (256)", value: 256n }, + { name: "Medium positive (65535)", value: 65535n }, + { name: "Medium positive (65536)", value: 65536n }, + { name: "Large positive (4294967295)", value: 4294967295n }, + { name: "Large positive (4294967296)", value: 4294967296n }, + { name: "64-bit boundary (2^63 - 1)", value: 9223372036854775807n }, + { name: "64-bit boundary (2^63)", value: 9223372036854775808n }, + { name: "64-bit max (2^64 - 1)", value: 18446744073709551615n }, + { name: "Bignum threshold (2^64)", value: 18446744073709551616n }, + { name: "Large bignum (2^64 + 1)", value: 18446744073709551617n }, + { + name: "Very large bignum (2^128)", + value: 340282366920938463463374607431768211456n + }, + { name: "Small negative (-1)", value: -1n }, + { name: "Small negative (-24)", value: -24n }, + { name: "Small negative (-25)", value: -25n }, + { name: "Medium negative (-256)", value: -256n }, + { name: "Medium negative (-257)", value: -257n }, + { name: "Large negative (-4294967296)", value: -4294967296n }, + { + name: "64-bit negative boundary (-2^63)", + value: -9223372036854775808n + }, + { + name: "64-bit negative max (-2^64 + 1)", + value: -18446744073709551615n + }, + { + name: "Negative bignum threshold (-2^64)", + value: -18446744073709551616n + }, + { + name: "Large negative bignum (-2^64 - 1)", + value: -18446744073709551617n + }, + { + name: "Very large negative bignum (-2^128)", + value: -340282366920938463463374607431768211456n + } + ] + + it.each(boundaryTestCases)("should handle $name correctly", ({ value }) => { + const plutusData = value + const encoded = Data.toCBORBytes(plutusData) + const decoded = Data.fromCBORBytes(encoded) + + expect(decoded).toEqual(plutusData) + expect(decoded as Data.Int).toBe(value) + }) +}) + +describe("CBOR Encoding Format Validation", () => { + it("should use correct CBOR major types for different integer ranges", () => { + const testCases = [ + { value: 23n, expectedMajorType: 0, description: "direct encoding" }, + { value: 255n, expectedMajorType: 0, description: "1-byte unsigned" }, + { value: 65535n, expectedMajorType: 0, description: "2-byte unsigned" }, { - name: "64-bit negative boundary (-2^63)", - value: -9223372036854775808n + value: 4294967295n, + expectedMajorType: 0, + description: "4-byte unsigned" }, { - name: "64-bit negative max (-2^64 + 1)", - value: -18446744073709551615n + value: 18446744073709551615n, + expectedMajorType: 0, + description: "8-byte unsigned" }, { - name: "Negative bignum threshold (-2^64)", - value: -18446744073709551616n + value: 18446744073709551616n, + expectedMajorType: 6, + description: "positive bignum tag" }, + { value: -1n, expectedMajorType: 1, description: "direct negative" }, + { value: -24n, expectedMajorType: 1, description: "1-byte negative" }, + { value: -256n, expectedMajorType: 1, description: "2-byte negative" }, { - name: "Large negative bignum (-2^64 - 1)", - value: -18446744073709551617n + value: -18446744073709551615n, + expectedMajorType: 1, + description: "8-byte negative" }, { - name: "Very large negative bignum (-2^128)", - value: -340282366920938463463374607431768211456n + value: -18446744073709551617n, + expectedMajorType: 6, + description: "negative bignum tag" } ] - it.each(boundaryTestCases)("should handle $name correctly", ({ value }) => { + testCases.forEach(({ expectedMajorType, value }) => { const plutusData = value const encoded = Data.toCBORBytes(plutusData) - const decoded = Data.fromCBORBytes(encoded) + const firstByte = encoded[0] + const majorType = firstByte >> 5 - expect(decoded).toEqual(plutusData) - expect(decoded as Data.Int).toBe(value) + expect(majorType).toBe(expectedMajorType) + + // Additional validation for bignum tags + if (expectedMajorType === 6) { + const additionalInfo = firstByte & 0x1f + if (value > 0n) { + expect(additionalInfo).toBe(2) // Tag 2 for positive bignum + } else { + expect(additionalInfo).toBe(3) // Tag 3 for negative bignum + } + } }) }) - describe("CBOR Encoding Format Validation", () => { - it("should use correct CBOR major types for different integer ranges", () => { - const testCases = [ - { value: 23n, expectedMajorType: 0, description: "direct encoding" }, - { value: 255n, expectedMajorType: 0, description: "1-byte unsigned" }, - { value: 65535n, expectedMajorType: 0, description: "2-byte unsigned" }, - { - value: 4294967295n, - expectedMajorType: 0, - description: "4-byte unsigned" - }, - { - value: 18446744073709551615n, - expectedMajorType: 0, - description: "8-byte unsigned" - }, - { - value: 18446744073709551616n, - expectedMajorType: 6, - description: "positive bignum tag" - }, - { value: -1n, expectedMajorType: 1, description: "direct negative" }, - { value: -24n, expectedMajorType: 1, description: "1-byte negative" }, - { value: -256n, expectedMajorType: 1, description: "2-byte negative" }, - { - value: -18446744073709551615n, - expectedMajorType: 1, - description: "8-byte negative" - }, - { - value: -18446744073709551617n, - expectedMajorType: 6, - description: "negative bignum tag" - } - ] + it("should handle empty collections correctly", () => { + const emptyList: Data.List = [] + const emptyMap = Data.map([]) + const emptyConstr = Data.constr(0n, []) - testCases.forEach(({ expectedMajorType, value }) => { - const plutusData = value - const encoded = Data.toCBORBytes(plutusData) - const firstByte = encoded[0] - const majorType = firstByte >> 5 - - expect(majorType).toBe(expectedMajorType) - - // Additional validation for bignum tags - if (expectedMajorType === 6) { - const additionalInfo = firstByte & 0x1f - if (value > 0n) { - expect(additionalInfo).toBe(2) // Tag 2 for positive bignum - } else { - expect(additionalInfo).toBe(3) // Tag 3 for negative bignum - } - } - }) + // Test that empty collections encode/decode correctly + ;[emptyList, emptyMap, emptyConstr].forEach((data) => { + const encoded = Data.toCBORBytes(data) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(data) }) + }) +}) - it("should handle empty collections correctly", () => { - const emptyList: Data.List = [] - const emptyMap = Data.map([]) - const emptyConstr = Data.constr(0n, []) +describe("Large Data Structure Edge Cases", () => { + it("should handle large lists", () => { + // Create a list with many elements + const largeList = Array.from({ length: 1000 }, (_, i) => BigInt(i)) - // Test that empty collections encode/decode correctly - ;[emptyList, emptyMap, emptyConstr].forEach((data) => { - const encoded = Data.toCBORBytes(data) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(data) - }) - }) + const encoded = Data.toCBORBytes(largeList) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(largeList) + expect(decoded as Data.List).toHaveLength(1000) }) - describe("Large Data Structure Edge Cases", () => { - it("should handle large lists", () => { - // Create a list with many elements - const largeList = Array.from({ length: 1000 }, (_, i) => BigInt(i)) + it("should handle large maps", () => { + // Create a map with many entries + const entries = Array.from( + { length: 100 }, + (_, i) => [BigInt(i), Data.bytearray(`${i.toString(16).padStart(4, "0")}`)] as [Data.Int, Data.ByteArray] + ) + const largeMap = Data.map(entries) + + const encoded = Data.toCBORBytes(largeMap) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(largeMap) + expect((decoded as Data.Map).size).toBe(100) + }) - const encoded = Data.toCBORBytes(largeList) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(largeList) - expect(decoded as Data.List).toHaveLength(1000) - }) + it("should handle constructors with many fields", () => { + // Create a constructor with many fields + const manyFields = Array.from({ length: 50 }, (_, i) => BigInt(i)) + const constr = Data.constr(42n, manyFields) - it("should handle large maps", () => { - // Create a map with many entries - const entries = Array.from( - { length: 100 }, - (_, i) => [BigInt(i), `${i.toString(16).padStart(4, "0")}`] as [Data.Int, Data.ByteArray] - ) - const largeMap = Data.map(entries) + const encoded = Data.toCBORBytes(constr) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(constr) + expect((decoded as Data.Constr).fields).toHaveLength(50) + }) +}) - const encoded = Data.toCBORBytes(largeMap) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(largeMap) - expect((decoded as Data.Map).size).toBe(100) - }) +describe("Error Handling and Edge Cases", () => { + describe("Invalid CBOR Input", () => { + const invalidInputs = [ + { name: "non-hex string", input: "not-valid-cbor", method: "cborHex" }, + { name: "partial hex string", input: "deadbee", method: "cborHex" }, + { name: "truncated CBOR", input: "d8", method: "cborHex" }, + { name: "invalid CBOR tag", input: "d8ff", method: "cborHex" }, + { name: "incomplete tagged value", input: "d87900", method: "cborHex" } + ] - it("should handle constructors with many fields", () => { - // Create a constructor with many fields - const manyFields = Array.from({ length: 50 }, (_, i) => BigInt(i)) - const constr = Data.constr(42n, manyFields) + it.each(invalidInputs)("should handle $name gracefully", ({ input, method }) => { + expect(() => { + if (method === "cborHex") { + Data.fromCBORHex(input) + } + }).toThrow() + }) + }) - const encoded = Data.toCBORBytes(constr) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(constr) - expect((decoded as Data.Constr).fields).toHaveLength(50) + describe("Malformed PlutusData Objects", () => { + it("should handle objects with wrong _tag values", () => { + const malformedObjects = [ + "test", + 10, + [1, 2n, 3], + new Map([["key", "value"]]), + { index: -1n, fields: [] }, + "invalidhex!" + ] as const + + malformedObjects.forEach((obj) => { + expect(Data.isInt(obj)).toBe(false) + expect(Data.isList(obj)).toBe(false) + expect(Data.isMap(obj)).toBe(false) + expect(Data.isConstr(obj)).toBe(false) + expect(Data.isBytes(obj)).toBe(false) + }) }) }) - describe("Error Handling and Edge Cases", () => { - describe("Invalid CBOR Input", () => { - const invalidInputs = [ - { name: "non-hex string", input: "not-valid-cbor", method: "cborHex" }, - { name: "partial hex string", input: "deadbee", method: "cborHex" }, - { name: "truncated CBOR", input: "d8", method: "cborHex" }, - { name: "invalid CBOR tag", input: "d8ff", method: "cborHex" }, - { name: "incomplete tagged value", input: "d87900", method: "cborHex" } + describe("Boundary Value Testing", () => { + it("should handle bytearray edge cases", () => { + const edgeCases = [ + { name: "empty string", value: "" }, + { name: "single zero byte", value: "00" }, + { name: "single max byte", value: "ff" }, + { name: "lowercase hex", value: "deadbeef" }, + { name: "uppercase hex", value: "DEADBEEF" } ] - it.each(invalidInputs)("should handle $name gracefully", ({ input, method }) => { + edgeCases.forEach(({ value }) => { expect(() => { - if (method === "cborHex") { - Data.fromCBORHex(input) + const bytes = Data.bytearray(value) + const encoded = Data.toCBORBytes(bytes) + const decoded = Data.fromCBORBytes(encoded) + // Note: hex strings are normalized to lowercase during processing + if (Data.isBytes(decoded)) { + // Convert Uint8Array back to hex string for comparison + const hexString = Array.from(decoded) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") + expect(hexString).toBe(value.toLowerCase()) } - }).toThrow() - }) - }) - - describe("Malformed PlutusData Objects", () => { - it("should handle objects with wrong _tag values", () => { - const malformedObjects = [ - "test", - 10, - [1, 2n, 3], - new Map([["key", "value"]]), - { index: -1n, fields: [] }, - "invalidhex!" - ] as const - - malformedObjects.forEach((obj) => { - expect(Data.isInt(obj)).toBe(false) - expect(Data.isList(obj)).toBe(false) - expect(Data.isMap(obj)).toBe(false) - expect(Data.isConstr(obj)).toBe(false) - expect(Data.isBytes(obj)).toBe(false) - }) + }).not.toThrow() }) }) - describe("Boundary Value Testing", () => { - it("should handle bytearray edge cases", () => { - const edgeCases = [ - { name: "empty string", value: "" }, - { name: "single zero byte", value: "00" }, - { name: "single max byte", value: "ff" }, - { name: "lowercase hex", value: "deadbeef" }, - { name: "uppercase hex", value: "DEADBEEF" } - ] - - edgeCases.forEach(({ value }) => { - expect(() => { - const bytes = Data.bytearray(value) - const encoded = Data.toCBORBytes(bytes) - const decoded = Data.fromCBORBytes(encoded) - // Note: hex strings are normalized to lowercase during processing - if (Data.isBytes(decoded)) { - expect(decoded).toBe(value.toLowerCase()) - } - }).not.toThrow() - }) - }) + it("should handle invalid bytearray inputs", () => { + const invalidCases = [ + "deadbee", // Odd length + "deadbeeg", // Invalid hex character + "0x123456", // Hex prefix not allowed + " deadbeef", // Leading whitespace + "deadbeef " // Trailing whitespace + ] - it("should handle invalid bytearray inputs", () => { - const invalidCases = [ - "deadbee", // Odd length - "deadbeeg", // Invalid hex character - "0x123456", // Hex prefix not allowed - " deadbeef", // Leading whitespace - "deadbeef " // Trailing whitespace - ] - - invalidCases.forEach((invalidHex) => { - expect(Data.isBytes(invalidHex)).toBe(false) - }) + invalidCases.forEach((invalidHex) => { + expect(Data.isBytes(invalidHex)).toBe(false) }) }) }) +}) - describe("Type Safety and Validation", () => { - describe("Type Guards", () => { - it("should correctly identify PlutusData types", () => { - const testCases = [ - { data: 42n, expectedType: "PlutusBigInt" }, - { data: "deadbeef", expectedType: "PlutusBytes" }, - { data: [], expectedType: "PlutusList" }, - { data: Data.map([]), expectedType: "PlutusMap" }, - { data: Data.constr(0n, []), expectedType: "Constr" } - ] - - testCases.forEach(({ data, expectedType }) => { - switch (expectedType) { - case "PlutusBigInt": - expect(Data.isInt(data)).toBe(true) - expect(Data.isBytes(data)).toBe(false) - expect(Data.isList(data)).toBe(false) - expect(Data.isMap(data)).toBe(false) - expect(Data.isConstr(data)).toBe(false) - break - case "PlutusBytes": - expect(Data.isInt(data)).toBe(false) - expect(Data.isBytes(data)).toBe(true) - expect(Data.isList(data)).toBe(false) - expect(Data.isMap(data)).toBe(false) - expect(Data.isConstr(data)).toBe(false) - break - case "PlutusList": - expect(Data.isInt(data)).toBe(false) - expect(Data.isBytes(data)).toBe(false) - expect(Data.isList(data)).toBe(true) - expect(Data.isMap(data)).toBe(false) - expect(Data.isConstr(data)).toBe(false) - break - case "PlutusMap": - expect(Data.isInt(data)).toBe(false) - expect(Data.isBytes(data)).toBe(false) - expect(Data.isList(data)).toBe(false) - expect(Data.isMap(data)).toBe(true) - expect(Data.isConstr(data)).toBe(false) - break - case "Constr": - expect(Data.isInt(data)).toBe(false) - expect(Data.isBytes(data)).toBe(false) - expect(Data.isList(data)).toBe(false) - expect(Data.isMap(data)).toBe(false) - expect(Data.isConstr(data)).toBe(true) - break - } - }) +describe("Type Safety and Validation", () => { + describe("Type Guards", () => { + it("should correctly identify PlutusData types", () => { + const testCases = [ + { data: 42n, expectedType: "PlutusBigInt" }, + { data: Data.bytearray("deadbeef"), expectedType: "PlutusBytes" }, + { data: [], expectedType: "PlutusList" }, + { data: Data.map([]), expectedType: "PlutusMap" }, + { data: Data.constr(0n, []), expectedType: "Constr" } + ] + + testCases.forEach(({ data, expectedType }) => { + switch (expectedType) { + case "PlutusBigInt": + expect(Data.isInt(data)).toBe(true) + expect(Data.isBytes(data)).toBe(false) + expect(Data.isList(data)).toBe(false) + expect(Data.isMap(data)).toBe(false) + expect(Data.isConstr(data)).toBe(false) + break + case "PlutusBytes": + expect(Data.isInt(data)).toBe(false) + expect(Data.isBytes(data)).toBe(true) + expect(Data.isList(data)).toBe(false) + expect(Data.isMap(data)).toBe(false) + expect(Data.isConstr(data)).toBe(false) + break + case "PlutusList": + expect(Data.isInt(data)).toBe(false) + expect(Data.isBytes(data)).toBe(false) + expect(Data.isList(data)).toBe(true) + expect(Data.isMap(data)).toBe(false) + expect(Data.isConstr(data)).toBe(false) + break + case "PlutusMap": + expect(Data.isInt(data)).toBe(false) + expect(Data.isBytes(data)).toBe(false) + expect(Data.isList(data)).toBe(false) + expect(Data.isMap(data)).toBe(true) + expect(Data.isConstr(data)).toBe(false) + break + case "Constr": + expect(Data.isInt(data)).toBe(false) + expect(Data.isBytes(data)).toBe(false) + expect(Data.isList(data)).toBe(false) + expect(Data.isMap(data)).toBe(false) + expect(Data.isConstr(data)).toBe(true) + break + } }) }) + }) - describe("Schema Validation", () => { - it("should validate all PlutusData types with their schemas", () => { - expect(Data.isInt(42n)).toBe(true) - expect(Data.isBytes("deadbeef")).toBe(true) - expect(Data.isList([1n])).toBe(true) - expect(Data.isMap(Data.map([[1n, 2n]]))).toBe(true) - expect(Data.isConstr(Data.constr(0n, [42n]))).toBe(true) - }) + describe("Schema Validation", () => { + it("should validate all PlutusData types with their schemas", () => { + expect(Data.isInt(42n)).toBe(true) + expect(Data.isBytes(Data.bytearray("deadbeef"))).toBe(true) + expect(Data.isList([1n])).toBe(true) + expect(Data.isMap(Data.map([[1n, 2n]]))).toBe(true) + expect(Data.isConstr(Data.constr(0n, [42n]))).toBe(true) }) }) +}) - describe("Edge Cases and Robustness", () => { - describe("Deeply Nested Structures", () => { - it("should handle moderate nesting levels", () => { - // Create a moderately nested structure (20 levels) - let nested: any = 42n - for (let i = 0; i < 20; i++) { - nested = [nested] - } +describe("Edge Cases and Robustness", () => { + describe("Deeply Nested Structures", () => { + it("should handle moderate nesting levels", () => { + // Create a moderately nested structure (20 levels) + let nested: any = 42n + for (let i = 0; i < 20; i++) { + nested = [nested] + } - expect(() => { - const encoded = Data.toCBORBytes(nested) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(nested) - }).not.toThrow() - }) + expect(() => { + const encoded = Data.toCBORBytes(nested) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(nested) + }).not.toThrow() + }) - it("should handle mixed nested structures", () => { - // Create a complex mixed structure with valid hex strings - const complex = Data.constr(0n, [ - [Data.map([[1n, "cafe"]]), Data.constr(1n, [-999n])], - Data.map([ - [ - "deadbeef", // Valid hex string - [1n, 2n, 3n] - ], - [42n, Data.constr(2n, [])] - ]) + it("should handle mixed nested structures", () => { + // Create a complex mixed structure with valid hex strings + const complex = Data.constr(0n, [ + [Data.map([[1n, Data.bytearray("cafe")]]), Data.constr(1n, [-999n])], + Data.map([ + [ + Data.bytearray("deadbeef"), // Valid hex string + [1n, 2n, 3n] + ], + [42n, Data.constr(2n, [])] ]) + ]) - const encoded = Data.toCBORBytes(complex) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(complex) - }) + const encoded = Data.toCBORBytes(complex) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(complex) }) + }) - describe("Data Structure Consistency", () => { - it("should maintain referential integrity", () => { - // Verify that separate instances with same data are equal - const data1 = Data.constr(42n, [123n, "cafe"]) - const data2 = Data.constr(42n, [123n, "cafe"]) + describe("Data Structure Consistency", () => { + it("should maintain referential integrity", () => { + // Verify that separate instances with same data are equal + const data1 = Data.constr(42n, [123n, Data.bytearray("cafe")]) + const data2 = Data.constr(42n, [123n, Data.bytearray("cafe")]) - expect(data1).toEqual(data2) + expect(data1).toEqual(data2) - const encoded1 = Data.toCBORBytes(data1) - const encoded2 = Data.toCBORBytes(data2) + const encoded1 = Data.toCBORBytes(data1) + const encoded2 = Data.toCBORBytes(data2) - expect(encoded1).toEqual(encoded2) - }) + expect(encoded1).toEqual(encoded2) + }) - it("should detect data differences correctly", () => { - const similar = [ - { - data1: Data.constr(42n, [123n]), - data2: Data.constr(43n, [123n]) // Different index - }, - { - data1: Data.constr(42n, [123n]), - data2: Data.constr(42n, [124n]) // Different field - }, - { - data1: "deadbeef", - data2: "deadbeee" // Different hex - }, - { - data1: 42n, - data2: -42n // Different sign - } - ] + it("should detect data differences correctly", () => { + const similar = [ + { + data1: Data.constr(42n, [123n]), + data2: Data.constr(43n, [123n]) // Different index + }, + { + data1: Data.constr(42n, [123n]), + data2: Data.constr(42n, [124n]) // Different field + }, + { + data1: Data.bytearray("deadbeef"), + data2: Data.bytearray("deadbeee") // Different hex + }, + { + data1: 42n, + data2: -42n // Different sign + } + ] - similar.forEach(({ data1, data2 }) => { - expect(data1).not.toEqual(data2) + similar.forEach(({ data1, data2 }) => { + expect(data1).not.toEqual(data2) - const encoded1 = Data.toCBORBytes(data1) - const encoded2 = Data.toCBORBytes(data2) + const encoded1 = Data.toCBORBytes(data1) + const encoded2 = Data.toCBORBytes(data2) - expect(encoded1).not.toEqual(encoded2) - }) + expect(encoded1).not.toEqual(encoded2) }) }) }) +}) - describe("Constructor Index Edge Cases", () => { - it("should handle direct tag constructors (0-6)", () => { - for (let i = 0; i <= 6; i++) { - const constr = Data.constr(BigInt(i), [42n]) - const encoded = Data.toCBORBytes(constr) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(constr) +describe("Constructor Index Edge Cases", () => { + it("should handle direct tag constructors (0-6)", () => { + for (let i = 0; i <= 6; i++) { + const constr = Data.constr(BigInt(i), [42n]) + const encoded = Data.toCBORBytes(constr) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(constr) - // Verify CBOR encoding uses direct tags (121-127) with 1-byte encoding - const firstByte = encoded[0] - expect(firstByte).toBe(0xd8) // Tag encoding prefix for 1-byte tags - const tagByte = encoded[1] - expect(tagByte).toBe(121 + i) - } - }) + // Verify CBOR encoding uses direct tags (121-127) with 1-byte encoding + const firstByte = encoded[0] + expect(firstByte).toBe(0xd8) // Tag encoding prefix for 1-byte tags + const tagByte = encoded[1] + expect(tagByte).toBe(121 + i) + } + }) - it("should handle general constructor tag (index >= 7)", () => { - const testIndices = [7n, 100n, 999999n, 2n ** 32n, 2n ** 64n - 1n] + it("should handle general constructor tag (index >= 7)", () => { + const testIndices = [7n, 100n, 999999n, 2n ** 32n, 2n ** 64n - 1n] - testIndices.forEach((index) => { - const constr = Data.constr(index, [42n]) - const encoded = Data.toCBORBytes(constr) - const decoded = Data.fromCBORBytes(encoded) - expect(decoded).toEqual(constr) - expect((decoded as Data.Constr).index).toBe(index) + testIndices.forEach((index) => { + const constr = Data.constr(index, [42n]) + const encoded = Data.toCBORBytes(constr) + const decoded = Data.fromCBORBytes(encoded) + expect(decoded).toEqual(constr) + expect((decoded as Data.Constr).index).toBe(index) - const firstByte = encoded[0] + const firstByte = encoded[0] - if (index >= 7n && index <= 127n) { - // CML compatibility format: tags 1280+ for indices 7-127 - expect(firstByte).toBe(0xd9) // Tag encoding prefix for 2-byte tags - const tagBytes = (encoded[1] << 8) | encoded[2] - expect(tagBytes).toBe(1280 + Number(index - 7n)) - } else { - // General constructor tag (102) for indices >= 128 - expect(firstByte).toBe(0xd8) // Tag encoding prefix for 1-byte tags - expect(encoded[1]).toBe(102) // General constructor tag - } - }) + if (index >= 7n && index <= 127n) { + // CML compatibility format: tags 1280+ for indices 7-127 + expect(firstByte).toBe(0xd9) // Tag encoding prefix for 2-byte tags + const tagBytes = (encoded[1] << 8) | encoded[2] + expect(tagBytes).toBe(1280 + Number(index - 7n)) + } else { + // General constructor tag (102) for indices >= 128 + expect(firstByte).toBe(0xd8) // Tag encoding prefix for 1-byte tags + expect(encoded[1]).toBe(102) // General constructor tag + } }) }) +}) - it("should handle canonical format roundtrip", () => { - const firstCanonical = - "d90503828181436ad232a34277931bd5a95fcb2d914b711b431e8fb0e0c31fe71b70cec8a3bf37064e1b785c7997367a91151b6a63f4cd860738d9" - const decoded = Data.fromCBORHex(firstCanonical) - - const encoded = Data.toCBORHex(decoded, { mode: "canonical" }) - expect(encoded).toBe(firstCanonical) - expect(Data.fromCBORHex(encoded)).toEqual(decoded) - - const secondCanonical = - "d9050883a4410742b6491b81df71d083fb9aac1b5f7385f841d1edec4a70f3d8f8803183c150334688472079a8ce4b0f7f249c0a9f4f829d2e4f1bc0556aa7b700508606a14365b7f71b4b62f0e2fbb95d6d" - const secondDecoded = Data.fromCBORHex(secondCanonical) - const secondEncoded = Data.toCBORHex(secondDecoded, { mode: "canonical" }) - expect(secondEncoded).toBe(secondCanonical) - expect(Data.fromCBORHex(secondEncoded)).toEqual(secondDecoded) - }) - - it("should handle unsorted map and return sorted canonical format", () => { - const unsorted = Data.constr(15n, [ - Data.map([ - [9358323691080620716n, 6877988357227539948n], - ["70f3d8f8803183c15033", "88472079a8ce"], - ["0f7f249c0a9f4f829d2e4f", 13859100696864903302n], - ["07", "b649"] - ]), - 6n, - Data.map([["65b7f7", 5432168958238743917n]]) - ]) +it("should handle canonical format roundtrip", () => { + const firstCanonical = + "d90503828181436ad232a34277931bd5a95fcb2d914b711b431e8fb0e0c31fe71b70cec8a3bf37064e1b785c7997367a91151b6a63f4cd860738d9" + const decoded = Data.fromCBORHex(firstCanonical) + + const encoded = Data.toCBORHex(decoded, { mode: "canonical" }) + expect(encoded).toBe(firstCanonical) + expect(Data.fromCBORHex(encoded)).toEqual(decoded) + + const secondCanonical = + "d9050883a4410742b6491b81df71d083fb9aac1b5f7385f841d1edec4a70f3d8f8803183c150334688472079a8ce4b0f7f249c0a9f4f829d2e4f1bc0556aa7b700508606a14365b7f71b4b62f0e2fbb95d6d" + const secondDecoded = Data.fromCBORHex(secondCanonical) + const secondEncoded = Data.toCBORHex(secondDecoded, { mode: "canonical" }) + expect(secondEncoded).toBe(secondCanonical) + expect(Data.fromCBORHex(secondEncoded)).toEqual(secondDecoded) +}) - const encoded = Data.toCBORHex(unsorted, { mode: "canonical" }) - const expectedCBORHex = - "d9050883a4410742b6491b81df71d083fb9aac1b5f7385f841d1edec4a70f3d8f8803183c150334688472079a8ce4b0f7f249c0a9f4f829d2e4f1bc0556aa7b700508606a14365b7f71b4b62f0e2fbb95d6d" - expect(encoded).toBe(expectedCBORHex) - }) +it("should handle unsorted map and return sorted canonical format", () => { + const unsorted = Data.constr(15n, [ + Data.map([ + [9358323691080620716n, 6877988357227539948n], + [Data.bytearray("70f3d8f8803183c15033"), Data.bytearray("88472079a8ce")], + [Data.bytearray("0f7f249c0a9f4f829d2e4f"), 13859100696864903302n], + [Data.bytearray("07"), Data.bytearray("b649")] + ]), + 6n, + Data.map([[Data.bytearray("65b7f7"), 5432168958238743917n]]) + ]) + + const encoded = Data.toCBORHex(unsorted, { mode: "canonical" }) + const expectedCBORHex = + "d9050883a4410742b6491b81df71d083fb9aac1b5f7385f841d1edec4a70f3d8f8803183c150334688472079a8ce4b0f7f249c0a9f4f829d2e4f1bc0556aa7b700508606a14365b7f71b4b62f0e2fbb95d6d" + expect(encoded).toBe(expectedCBORHex) }) diff --git a/packages/evolution/test/NativeScripts.CML.test.ts b/packages/evolution/test/NativeScripts.CML.test.ts index d33b11f3..2997928f 100644 --- a/packages/evolution/test/NativeScripts.CML.test.ts +++ b/packages/evolution/test/NativeScripts.CML.test.ts @@ -1,26 +1,27 @@ import * as CML from "@dcspark/cardano-multiplatform-lib-nodejs" +import { FastCheck } from "effect" import { describe, expect, it } from "vitest" import * as NativeScripts from "../src/core/NativeScripts.js" -describe("NativeScripts CML Compatibility", () => { - it("validates native script sig compatibility", () => { - // Create a simple native script (sig script) - use 28 bytes for Ed25519KeyHash - const keyHashBytes = new Uint8Array(28).fill(0x42) // 28 bytes of 0x42 +describe("NativeScripts CML Compatibility (property)", () => { + it("Evolution NativeScript CBOR is parseable by CML and roundtrips CBOR", () => { + FastCheck.assert( + FastCheck.property(NativeScripts.arbitrary, (ns) => { + // Evolution -> CBOR hex + const evoHex = NativeScripts.toCBORHex(ns) - const evolutionNativeScript = { - type: "sig" as const, - keyHash: Array.from(keyHashBytes) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - } + // CML parse and re-encode + const cmlNative = CML.NativeScript.from_cbor_hex(evoHex) + const cmlHex = cmlNative.to_cbor_hex() - const cmlNativeScript = CML.NativeScript.new_script_pubkey(CML.Ed25519KeyHash.from_raw_bytes(keyHashBytes)) + // Exact CBOR parity + expect(cmlHex).toBe(evoHex) - // Compare CBOR outputs - const evolutionCbor = NativeScripts.toCBORHex(evolutionNativeScript) - const cmlCbor = cmlNativeScript.to_cbor_hex() - - expect(evolutionCbor).toBe(cmlCbor) + // Evolution decoder accepts CML's CBOR and equals original by CBOR encoding + const evoRoundTrip = NativeScripts.fromCBORHex(cmlHex) + expect(NativeScripts.equals(evoRoundTrip, ns)).toBe(true) + }) + ) }) }) diff --git a/packages/evolution/test/Transaction.CML.test.ts b/packages/evolution/test/Transaction.CML.test.ts new file mode 100644 index 00000000..e61c0759 --- /dev/null +++ b/packages/evolution/test/Transaction.CML.test.ts @@ -0,0 +1,31 @@ +import * as CML from "@dcspark/cardano-multiplatform-lib-nodejs" +import { FastCheck } from "effect" +import { describe, expect, it } from "vitest" + +import * as Transaction from "../src/core/Transaction.js" + +/** + * CML compatibility test for full Transaction CBOR serialization. + * + * Validates Evolution SDK Transaction CBOR matches CML's encoding and + * roundtrips through both libraries. + */ +describe("Transaction CML Compatibility", () => { + it("property: Evolution Transaction CBOR equals CML and roundtrips", () => { + FastCheck.assert( + FastCheck.property(Transaction.arbitrary, (evoTx) => { + // Evolution -> CBOR hex + const evoHex = Transaction.toCBORHex(evoTx) + // CML parses it + const cmlTx = CML.Transaction.from_cbor_hex(evoHex) + // CML -> CBOR hex + const cmlHex = cmlTx.to_cbor_hex() + // Equality on hex + expect(cmlHex).toBe(evoHex) + // Roundtrip back into Evolution and compare (let failures throw) + const evoBack = Transaction.fromCBORHex(cmlHex) + expect(Transaction.equals(evoBack, evoTx)).toBe(true) + }) + ) + }) +}) diff --git a/packages/evolution/test/TransactionOutput.CML.test.ts b/packages/evolution/test/TransactionOutput.CML.test.ts index 49c8a5ef..3d3695b8 100644 --- a/packages/evolution/test/TransactionOutput.CML.test.ts +++ b/packages/evolution/test/TransactionOutput.CML.test.ts @@ -7,7 +7,7 @@ import * as TransactionOutput from "../src/core/TransactionOutput.js" describe("TransactionOutput CML Compatibility", () => { it("property: Evolution SDK CBOR matches CML CBOR for any generated TransactionOutput", () => { FastCheck.assert( - FastCheck.property(TransactionOutput.arbitrary(), (evOut) => { + FastCheck.property(TransactionOutput.arbitrary, (evOut) => { const evCbor = TransactionOutput.toCBORHex(evOut) const cmlOut = CML.TransactionOutput.from_cbor_hex(evCbor) const cmlCbor = cmlOut.to_cbor_hex() diff --git a/packages/evolution/test/TransactionWitnessSet.CML.test.ts b/packages/evolution/test/TransactionWitnessSet.CML.test.ts index adbde9b8..0019218a 100644 --- a/packages/evolution/test/TransactionWitnessSet.CML.test.ts +++ b/packages/evolution/test/TransactionWitnessSet.CML.test.ts @@ -212,13 +212,9 @@ describe("TransactionWitnessSet CML Compatibility", () => { it("validates encoding native scripts", () => { // Create test data for native script (simple pubkey script) const publicKeyHashBytes = new Uint8Array(28).fill(25) - const publicKeyHashHex = Array.from(publicKeyHashBytes, (byte) => byte.toString(16).padStart(2, "0")).join("") // Create Evolution SDK witness set with native script - const evolutionNativeScript = NativeScripts.make({ - type: "sig", - keyHash: publicKeyHashHex - }) + const evolutionNativeScript = NativeScripts.makeScriptPubKey(publicKeyHashBytes) const evolutionWitnessSet = new TransactionWitnessSet.TransactionWitnessSet({ nativeScripts: [evolutionNativeScript]