Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix how the ContractId and TxId branded types are printed in the documentation #185

Merged
merged 2 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.d/20240222_130945_hrajchert_fix_branded_output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### @marlowe.io/runtime-core

- Fix: Branding of ContractId and TxId ([PR-185](https://github.com/input-output-hk/marlowe-ts-sdk/pull/185))


20 changes: 12 additions & 8 deletions packages/adapter/src/io-ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,21 +150,25 @@ export interface BrandP<A, B extends A, I>
// This helper is a workaround to that issue, making sure that if the original codec
// has the same Output than the Actual type, the new codec outputs the branded type
// So a PositiveInt codec will have the type t.Type<PositiveInt, PositiveInt, unknown>
export function preservedBrand<A, I, B extends A>(
codec: t.Type<A, A, I>,
predicate: Refinement<A, B>,
name: string
): BrandP<A, B, I> {
export function preservedBrand<
C extends t.Any,
N extends string,
B extends { readonly [K in N]: symbol },
>(
codec: C,
predicate: Refinement<t.TypeOf<C>, t.Branded<t.TypeOf<C>, B>>,
name: N
): BrandP<t.TypeOf<C>, B, t.InputOf<C>> {
return new t.Type(
name,
(u): u is t.Branded<A, B> => codec.is(u) && predicate(u),
(u): u is t.Branded<t.TypeOf<C>, B> => codec.is(u) && predicate(u),
(u, c) =>
pipe(
codec.validate(u, c),
Either.chain((a) =>
predicate(a)
? t.success(a as t.Branded<A, B>)
: t.failure<t.Branded<A, B>>(
? t.success(a as t.Branded<t.TypeOf<C>, B>)
: t.failure<t.Branded<t.TypeOf<C>, B>>(
a,
c,
`Value does not satisfy the ${name} constraint`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
BlockHeader,
ContractIdGuard,
TextEnvelope,
TxIdGuard,
} from "@marlowe.io/runtime-core";
import { TxStatus } from "./status.js";
import { assertGuardEqual, proxy } from "@marlowe.io/adapter/io-ts";
Expand Down Expand Up @@ -97,7 +98,7 @@ export const TransactionDetailsGuard = assertGuardEqual(
proxy<TransactionDetails>(),
t.type({
contractId: ContractIdGuard,
transactionId: TxId,
transactionId: TxIdGuard,
continuations: optionFromNullable(G.BuiltinByteString),
tags: TagsGuard,
metadata: MetadataGuard,
Expand All @@ -108,7 +109,7 @@ export const TransactionDetailsGuard = assertGuardEqual(
outputUtxo: optionFromNullable(TxOutRef),
outputContract: optionFromNullable(G.Contract),
outputState: optionFromNullable(G.MarloweState),
consumingTx: optionFromNullable(TxId),
consumingTx: optionFromNullable(TxIdGuard),
invalidBefore: ISO8601,
invalidHereafter: ISO8601,
txBody: optionFromNullable(TextEnvelopeGuard),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
unTxOutRef,
ContractId,
ContractIdGuard,
TxIdGuard,
} from "@marlowe.io/runtime-core";
import { TxHeader, TxHeaderGuard } from "../header.js";
import { assertGuardEqual, proxy } from "@marlowe.io/adapter/io-ts";
Expand Down Expand Up @@ -125,7 +126,7 @@ export const GetTransactionsForContractResponseGuard = assertGuardEqual(
export type TransactionTextEnvelope = t.TypeOf<typeof TransactionTextEnvelope>;
export const TransactionTextEnvelope = t.type({
contractId: ContractIdGuard,
transactionId: TxId,
transactionId: TxIdGuard,
tx: TextEnvelopeGuard,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
HexTransactionWitnessSet,
HexTransactionWitnessSetGuard,
TxId,
TxIdGuard,
transactionWitnessSetTextEnvelope,
} from "@marlowe.io/runtime-core";

Expand Down Expand Up @@ -45,7 +46,7 @@ export const GetContractTransactionByIdRequestGuard = assertGuardEqual(
proxy<GetContractTransactionByIdRequest>(),
t.type({
contractId: ContractIdGuard,
txId: TxId,
txId: TxIdGuard,
})
);

Expand Down Expand Up @@ -80,7 +81,7 @@ export const SubmitContractTransactionRequestGuard = assertGuardEqual(
proxy<SubmitContractTransactionRequest>(),
t.type({
contractId: ContractIdGuard,
transactionId: TxId,
transactionId: TxIdGuard,
hexTransactionWitnessSet: HexTransactionWitnessSetGuard,
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TxOutRef,
TxId,
ContractId,
TxIdGuard,
} from "@marlowe.io/runtime-core";
import { TxStatus } from "./status.js";
import { BuiltinByteString } from "@marlowe.io/language-core-v1";
Expand Down Expand Up @@ -46,7 +47,7 @@ export interface TxHeader {
*/
export const TxHeaderGuard = t.type({
contractId: ContractIdGuard,
transactionId: TxId,
transactionId: TxIdGuard,
continuations: optionFromNullable(G.BuiltinByteString),
tags: TagsGuard,
metadata: MetadataGuard,
Expand Down
12 changes: 9 additions & 3 deletions packages/runtime/core/src/contract/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as t from "io-ts/lib/index.js";
import { split } from "fp-ts/lib/string.js";
import { pipe } from "fp-ts/lib/function.js";
import { head } from "fp-ts/lib/ReadonlyNonEmptyArray.js";
import { TxId } from "../tx/id.js";
import { TxId, txId } from "../tx/id.js";
import { unsafeEither } from "@marlowe.io/adapter/fp-ts";
import { preservedBrand } from "@marlowe.io/adapter/io-ts";

Expand All @@ -16,11 +16,17 @@ export const ContractIdGuard = preservedBrand(
"ContractId"
);

export type ContractId = t.TypeOf<typeof ContractIdGuard>;
/**
* Marlowe contract identifier.
*
* @remarks The underlying data structure is a normal string, but in the type
* level it is **Branded** with a unique symbol so that it is not confused with other strings
*/
export type ContractId = t.Branded<string, ContractIdBrand>;

export const contractId = (s: string) =>
unsafeEither(ContractIdGuard.decode(s));

export const contractIdToTxId: (contractId: ContractId) => TxId = (
contractId
) => pipe(contractId, split("#"), head);
) => pipe(contractId, split("#"), head, txId);
6 changes: 3 additions & 3 deletions packages/runtime/core/src/payout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fromNewtype } from "io-ts-types";
import { split } from "fp-ts/lib/string.js";
import { pipe } from "fp-ts/lib/function.js";
import { head } from "fp-ts/lib/ReadonlyNonEmptyArray.js";
import { TxId } from "../tx/id.js";
import { txId, TxId } from "../tx/id.js";
import { ContractIdGuard } from "../contract/id.js";
import { AssetId, Assets } from "../asset/index.js";

Expand All @@ -15,7 +15,7 @@ export const unPayoutId = iso<PayoutId>().unwrap;
export const payoutId = iso<PayoutId>().wrap;

export const payoutIdToTxId: (payoutId: PayoutId) => TxId = (payoutId) =>
pipe(payoutId, unPayoutId, split("#"), head);
pipe(payoutId, unPayoutId, split("#"), head, txId);

export type WithdrawalId = Newtype<
{ readonly WithdrawalId: unique symbol },
Expand All @@ -27,7 +27,7 @@ export const withdrawalId = iso<WithdrawalId>().wrap;

export const withdrawalIdToTxId: (withdrawalId: WithdrawalId) => TxId = (
withdrawalId
) => pipe(withdrawalId, unWithdrawalId);
) => pipe(withdrawalId, unWithdrawalId, txId);

// DISCUSSION: PayoutAvailable or AvailablePayout?
export type PayoutAvailable = t.TypeOf<typeof PayoutAvailable>;
Expand Down
24 changes: 20 additions & 4 deletions packages/runtime/core/src/tx/id.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { unsafeEither } from "@marlowe.io/adapter/fp-ts";
import * as t from "io-ts/lib/index.js";

// TODO: Try to make newtype as this gets replaced to string
// in the docs.
export type TxId = t.TypeOf<typeof TxId>;
export const TxId = t.string; // to refine
export interface TxIdBrand {
readonly TxId: unique symbol;
}

/**
* Cardano transaction identifier.
*
* @remarks The underlying data structure is a normal string, but in the type
* level it is **Branded** with a unique symbol so that it is not confused with other strings
*/
export type TxId = t.Branded<string, TxIdBrand>;

export const TxIdGuard = t.brand(
t.string,
(s): s is t.Branded<string, TxIdBrand> => true,
"TxId"
);
Comment on lines +16 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TxIdGuard is defined using t.brand to validate strings as TxId types. The predicate function always returns true, which means it does not perform any runtime validation. Consider implementing a more meaningful validation logic to ensure the input string conforms to the expected format of a transaction ID.

- (s): s is t.Branded<string, TxIdBrand> => true,
+ (s): s is t.Branded<string, TxIdBrand> => /^[0-9a-fA-F]{64}$/.test(s),

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const TxIdGuard = t.brand(
t.string,
(s): s is t.Branded<string, TxIdBrand> => true,
"TxId"
);
export const TxIdGuard = t.brand(
t.string,
(s): s is t.Branded<string, TxIdBrand> => /^[0-9a-fA-F]{64}$/.test(s),
"TxId"
);


export const txId = (s: string) => unsafeEither(TxIdGuard.decode(s));
3 changes: 3 additions & 0 deletions typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"fp-ts": {
"Option": "https://gcanti.github.io/fp-ts/modules/Option.ts.html"
},
"io-ts": {
"Branded": "https://github.com/gcanti/io-ts/blob/master/index.md#branded-types--refinements"
},
"lucid-cardano": {
"Lucid": "https://deno.land/x/[email protected]/mod.ts?s=Lucid"
},
Expand Down
Loading