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

Experimental feature: Add @marlowe.io/marlowe-template package #184

Merged
merged 23 commits into from
Feb 20, 2024

Conversation

hrajchert
Copy link
Collaborator

@hrajchert hrajchert commented Feb 13, 2024

When you create a Marlowe contract using the ts-sdk, it is common to have a template function that receives some Parameters/Schema/Blueprint and generates either a Contract or ContractBundleMap. When you want another participant to know if a given contract is an instance of your template, the other participant must have the same template function applied with the same parameters.

Sharing the template function is easy and generally solved by using the same web DAPP. The template parameters, on the other hand, need to be shared differently.

It is easy to see how this PR simplifies the Parameter sharing In the marlowe-object-flow.ts example changes.
Before, we manually defined the type DelayPaymentScheme and two functions (extractSchemeFromTags and mkDelayPaymentTags) to decode and encode the Scheme as Tags. Now, we manually define the MarloweTemplate but we infer the DelayPaymentParameters type and the decoding and encoding function as Metadata are provided by the package.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Enhanced the Marlowe Blueprint for defining typed blueprints and improved data validation with new types and codecs.
    • Updated node.js example with improved contract creation logic and payment scheme using the new Metadata type.
    • Improved Bech32 address validation in runtime core functionalities.
  • Documentation
    • Added initial documentation for the Marlowe Blueprint.
  • Tests
    • Introduced Jest unit test configurations and test cases for blueprint functionalities, including basic types validation, delayed payment logic, and codecs.
  • Chores
    • Updated VS Code settings for blueprint package development and testing.

@hrajchert hrajchert marked this pull request as draft February 13, 2024 13:18
Copy link
Contributor

coderabbitai bot commented Feb 13, 2024

Walkthrough

This update introduces significant enhancements to the development environment and the core functionality of a TypeScript-based project. It focuses on improving debugging configurations, refining the handling and validation of data types, and advancing the project's architecture through the introduction of a new blueprint package. Additionally, the update modernizes the contract creation logic and payment scheme in a specific example, showcasing the practical application of the newly introduced concepts and tools.

Changes

Files Change Summary
.vscode/settings.json Added paths for blueprint dist and test-dist JS files to outFiles.
examples/nodejs/src/marlowe-object-flow.ts Updated contract logic, introduced Metadata type, and refined payment scheme with new identifiers.
jest.unit.config.js Added Jest unit test config for blueprint.
jsdelivr-npm-importmap.js Added entry for @marlowe.io/blueprint.
packages/adapter/src/bigint.ts Introduced BigIntOrNumber type and guard.
packages/blueprint/... (multiple files) Introduced the blueprint package with core functionality, codecs, and tests.
packages/runtime/core/src/... (address, metadata) Updated address validation and refined metadata types.

🐇✨
In a world of code, where bugs often strode,
Came a blueprint, so bold, its story to be told.
With types so refined, and contracts designed,
A rabbit hopped along, improvements it aligned.
"To the future," it said, "with leaps, not dread,
For our code is now stronger, and far ahead."
🌟🐾

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 771f2ef and d22ed40.
Files ignored due to path filters (8)
  • package-lock.json is excluded by: !**/*.json
  • package.json is excluded by: !**/*.json
  • packages/blueprint/package.json is excluded by: !**/*.json
  • packages/blueprint/src/tsconfig.json is excluded by: !**/*.json
  • packages/blueprint/test/tsconfig.json is excluded by: !**/*.json
  • packages/blueprint/typedoc.json is excluded by: !**/*.json
  • tsconfig.json is excluded by: !**/*.json
  • tsconfig.test.json is excluded by: !**/*.json
Files selected for processing (15)
  • .vscode/settings.json (2 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (11 hunks)
  • jest.unit.config.js (1 hunks)
  • jsdelivr-npm-importmap.js (1 hunks)
  • packages/adapter/src/bigint.ts (1 hunks)
  • packages/blueprint/Readme.md (1 hunks)
  • packages/blueprint/src/codecs.ts (1 hunks)
  • packages/blueprint/src/index.ts (1 hunks)
  • packages/blueprint/src/typed.ts (1 hunks)
  • packages/blueprint/test/blueprint-basic-types.spec.ts (1 hunks)
  • packages/blueprint/test/blueprint-delayed-payment.spec.ts (1 hunks)
  • packages/blueprint/test/codecs.spec.ts (1 hunks)
  • packages/blueprint/test/jest.unit.config.mjs (1 hunks)
  • packages/runtime/core/src/address.ts (1 hunks)
  • packages/runtime/core/src/metadata.ts (1 hunks)
Files skipped from review due to trivial changes (1)
  • packages/blueprint/src/index.ts
Additional comments: 24
packages/blueprint/test/jest.unit.config.mjs (1)
  • 1-14: Configuration in jest.unit.config.mjs is correctly set up for Jest testing of the blueprint package.
jest.unit.config.js (1)
  • 5-5: Addition of the blueprint package to Jest projects array in jest.unit.config.js is correctly done.
packages/adapter/src/bigint.ts (1)
  • 4-24: Implementation of BigIntOrNumber type and BigIntOrNumberGuard in bigint.ts is correctly done using io-ts for runtime validation.
packages/blueprint/src/codecs.ts (1)
  • 1-33: Custom codecs for Date and String types in codecs.ts are correctly implemented using io-ts, ensuring type safety and data integrity.
.vscode/settings.json (1)
  • 4-10: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [7-17]

Addition of blueprint package paths to the outFiles list in .vscode/settings.json correctly enhances debugging configuration.

packages/runtime/core/src/address.ts (1)
  • 6-23: Update to use a lighter library for Bech32 address validation in address.ts potentially enhances performance and maintainability.
packages/blueprint/test/codecs.spec.ts (1)
  • 1-39: Tests in codecs.spec.ts comprehensively validate the custom codecs for Date and String types, ensuring their correctness and reliability.
packages/blueprint/test/blueprint-basic-types.spec.ts (1)
  • 1-94: Tests in blueprint-basic-types.spec.ts effectively validate the functionality of a blueprint with basic types, demonstrating its correctness and utility.
packages/runtime/core/src/metadata.ts (1)
  • 1-93: Refinements and updates to metadata types in metadata.ts correctly enhance type safety and enforce constraints on metadata values.
packages/blueprint/test/blueprint-delayed-payment.spec.ts (1)
  • 1-166: Tests in blueprint-delayed-payment.spec.ts comprehensively validate the functionality of the delayed payment blueprint, ensuring its correctness and reliability.
jsdelivr-npm-importmap.js (1)
  • 25-26: Addition of @marlowe.io/blueprint to the import map in jsdelivr-npm-importmap.js correctly enhances its accessibility and usability in web projects.
packages/blueprint/src/typed.ts (1)
  • 1-229: Implementation in typed.ts correctly establishes a comprehensive system for creating and working with typed blueprints, enhancing type safety and data integrity.
examples/nodejs/src/marlowe-object-flow.ts (12)
  • 18-21: The import of Metadata is introduced but not used within the provided code. Verify if it's necessary or remove it to avoid unused imports.
  • 43-43: The import of BlueprintOf, mkBlueprint from @marlowe.io/blueprint is correctly added for the new functionality. Ensure that the @marlowe.io/blueprint package is included in the project dependencies.
  • 145-150: The use of addressBech32 for validating and formatting the payee address is correct. Ensure that the bech32Validator function properly uses the C.Address.from_bech32 method from lucid-cardano for validation.
  • 184-185: Replacing payFrom and payTo with payer and payee in the DelayPaymentScheme aligns with the updated contract logic. Ensure all references to these fields are updated accordingly.
  • 190-195: Using delayPaymentBlueprint.encode(scheme) for encoding contract details is correct. Ensure that the delayPaymentBlueprint is properly defined and that its encode method correctly handles the scheme object structure.
  • 218-218: The validation result InvalidBlueprint is correctly handled. Ensure that the delayPaymentBlueprint.decode method properly identifies invalid blueprints.
  • 231-232: Updating console outputs to reflect the new payer and payee terminology is correct. Ensure consistency across all user-facing messages.
  • 383-417: The definition of delayPaymentBlueprint using mkBlueprint is correct and aligns with the new blueprint functionality. Ensure that all parameter types (address, value, date) are correctly handled by the blueprint and the Marlowe runtime.
  • 422-422: The type alias DelayPaymentScheme for the blueprint parameters is correctly defined. Ensure that it is used consistently throughout the code.
  • 459-462: The updated contract creation logic using the new payer and payee fields is correct. Ensure that the lovelace token type and the into_account structure are correctly handled by the Marlowe runtime.
  • 526-526: The updated console output using the new payer field is correct. Ensure consistency in messaging and that the scheme.amount is correctly formatted for display.
  • 606-609: Handling the case where delayPaymentBlueprint.decode returns null (indicating an invalid blueprint) is correct. Ensure that the decode function is robust and accurately identifies invalid metadata.

@@ -0,0 +1,3 @@
# Marlowe Blueprint

TODO.
Copy link
Contributor

Choose a reason for hiding this comment

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

The Readme.md file contains a "TODO" placeholder, indicating incomplete documentation.

Would you like assistance in completing the documentation?

@hrajchert hrajchert marked this pull request as ready for review February 14, 2024 18:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between d22ed40 and a49629e.
Files ignored due to path filters (3)
  • package-lock.json is excluded by: !**/*.json
  • packages/blueprint/src/tsconfig.json is excluded by: !**/*.json
  • packages/blueprint/typedoc.json is excluded by: !**/*.json
Files selected for processing (16)
  • changelog.d/20240214_142506_hrajchert_blueprints.md (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (11 hunks)
  • packages/adapter/src/bigint.ts (1 hunks)
  • packages/blueprint/src/blueprint-param.ts (1 hunks)
  • packages/blueprint/src/blueprint.ts (1 hunks)
  • packages/blueprint/src/codecs.ts (1 hunks)
  • packages/blueprint/src/index.ts (1 hunks)
  • packages/blueprint/test/blueprint-basic-types.spec.ts (1 hunks)
  • packages/blueprint/test/codecs.spec.ts (1 hunks)
  • packages/runtime/client/rest/src/contract/details.ts (2 hunks)
  • packages/runtime/client/rest/src/contract/endpoints/collection.ts (4 hunks)
  • packages/runtime/client/rest/src/contract/header.ts (2 hunks)
  • packages/runtime/client/rest/src/contract/transaction/details.ts (3 hunks)
  • packages/runtime/client/rest/src/contract/transaction/endpoints/collection.ts (3 hunks)
  • packages/runtime/client/rest/src/contract/transaction/header.ts (2 hunks)
  • packages/runtime/core/src/metadata.ts (1 hunks)
Files skipped from review as they are similar to previous changes (4)
  • examples/nodejs/src/marlowe-object-flow.ts
  • packages/adapter/src/bigint.ts
  • packages/blueprint/test/blueprint-basic-types.spec.ts
  • packages/runtime/core/src/metadata.ts
Additional comments: 19
changelog.d/20240214_142506_hrajchert_blueprints.md (1)
  • 1-12: LGTM!
packages/blueprint/src/codecs.ts (1)
  • 1-52: LGTM!
packages/runtime/client/rest/src/contract/transaction/header.ts (2)
  • 9-9: LGTM!
  • 52-52: LGTM!
packages/runtime/client/rest/src/contract/header.ts (2)
  • 11-11: LGTM!
  • 72-72: LGTM!
packages/blueprint/test/codecs.spec.ts (1)
  • 1-52: LGTM!
packages/blueprint/src/index.ts (1)
  • 1-73: LGTM!
packages/runtime/client/rest/src/contract/details.ts (2)
  • 6-6: LGTM!
  • 16-16: LGTM!
packages/runtime/client/rest/src/contract/transaction/details.ts (2)
  • 15-15: LGTM!
  • 103-103: LGTM!
packages/blueprint/src/blueprint-param.ts (1)
  • 1-161: LGTM!
packages/runtime/client/rest/src/contract/transaction/endpoints/collection.ts (3)
  • 20-20: LGTM!
  • 159-159: LGTM!
  • 208-208: LGTM!
packages/blueprint/src/blueprint.ts (1)
  • 1-226: LGTM!
packages/runtime/client/rest/src/contract/endpoints/collection.ts (2)
  • 220-220: Replace Metadata with MetadataGuard in BuildCreateContractTxRequestOptionsGuard to ensure type safety.
  • 549-549: Replace Metadata with MetadataGuard in PostContractsRequest to ensure type safety.

@@ -19,6 +19,7 @@ import {
Tag,
Tags,
TagsGuard,
MetadataGuard,
Copy link
Contributor

Choose a reason for hiding this comment

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

Duplicate import of Metadata is unnecessary since MetadataGuard is now used for type validation.

-  Metadata,

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
MetadataGuard,
MetadataGuard,

@hrajchert hrajchert assigned hrajchert and unassigned paluh and nhenin Feb 14, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between a49629e and 783f96d.
Files selected for processing (10)
  • packages/adapter/src/io-ts.ts (2 hunks)
  • packages/blueprint/src/blueprint.ts (1 hunks)
  • packages/blueprint/src/codecs.ts (1 hunks)
  • packages/blueprint/src/index.ts (1 hunks)
  • packages/runtime/client/rest/src/pagination.ts (1 hunks)
  • packages/runtime/client/rest/src/runtime/version.ts (1 hunks)
  • packages/runtime/core/src/address.ts (1 hunks)
  • packages/runtime/core/src/contract/id.ts (1 hunks)
  • packages/runtime/core/src/metadata.ts (1 hunks)
  • packages/runtime/core/src/policyId.ts (1 hunks)
Files skipped from review as they are similar to previous changes (5)
  • packages/blueprint/src/blueprint.ts
  • packages/blueprint/src/codecs.ts
  • packages/blueprint/src/index.ts
  • packages/runtime/core/src/address.ts
  • packages/runtime/core/src/metadata.ts
Additional comments: 5
packages/runtime/core/src/policyId.ts (1)
  • 9-9: The predicate function in PolicyIdGuard always returns true. Confirm if this is a placeholder for future validation logic or if more specific validation is intended but not implemented.
packages/runtime/client/rest/src/runtime/version.ts (1)
  • 9-9: The predicate function in RuntimeVersionGuard always returns true. Verify if this is intentional or if it's a placeholder for more specific future validation.
packages/runtime/core/src/contract/id.ts (1)
  • 13-13: The predicate function in ContractIdGuard always returns true. Confirm if this is by design or a placeholder for more specific validation logic.
packages/runtime/client/rest/src/pagination.ts (1)
  • 13-13: The predicate function in ItemRangeGuard always returns true. Verify if this is intentional or if it's a placeholder for more specific future validation.
packages/adapter/src/io-ts.ts (1)
  • 139-176: The implementation of preservedBrand aligns with the PR's objectives and addresses the limitations of t.brand as described. Ensure thorough testing to validate its behavior across various use cases.

nhenin
nhenin previously requested changes Feb 20, 2024
Copy link
Collaborator

@nhenin nhenin left a comment

Choose a reason for hiding this comment

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

All Good, just the renaming we discussed to be done...

@hrajchert hrajchert changed the title Experimental feature: Add @marlowe.io/blueprints package Experimental feature: Add @marlowe.io/marlowe-template package Feb 20, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 783f96d and ee4bd09.
Files ignored due to path filters (8)
  • package-lock.json is excluded by: !**/*.json
  • package.json is excluded by: !**/*.json
  • packages/marlowe-template/package.json is excluded by: !**/*.json
  • packages/marlowe-template/src/tsconfig.json is excluded by: !**/*.json
  • packages/marlowe-template/test/tsconfig.json is excluded by: !**/*.json
  • packages/marlowe-template/typedoc.json is excluded by: !**/*.json
  • tsconfig.json is excluded by: !**/*.json
  • tsconfig.test.json is excluded by: !**/*.json
Files selected for processing (13)
  • .vscode/settings.json (2 hunks)
  • changelog.d/20240214_142506_hrajchert_marlowe_template.md (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (13 hunks)
  • jest.unit.config.js (1 hunks)
  • jsdelivr-npm-importmap.js (1 hunks)
  • packages/marlowe-template/src/codecs.ts (1 hunks)
  • packages/marlowe-template/src/index.ts (1 hunks)
  • packages/marlowe-template/src/template-param.ts (1 hunks)
  • packages/marlowe-template/src/template.ts (1 hunks)
  • packages/marlowe-template/test/basic-types-template.spec.ts (1 hunks)
  • packages/marlowe-template/test/codecs.spec.ts (1 hunks)
  • packages/marlowe-template/test/delayed-payment-template.spec.ts (1 hunks)
  • packages/marlowe-template/test/jest.unit.config.mjs (1 hunks)
Files skipped from review as they are similar to previous changes (4)
  • .vscode/settings.json
  • examples/nodejs/src/marlowe-object-flow.ts
  • jest.unit.config.js
  • jsdelivr-npm-importmap.js
Additional comments: 15
packages/marlowe-template/test/jest.unit.config.mjs (1)
  • 1-14: The Jest configuration for the marlowe-template package is correctly set up for a TypeScript project using ESM modules. It includes appropriate settings for the test environment, handling of ESM extensions, module name mapping, and transformation of TypeScript files.
changelog.d/20240214_142506_hrajchert_marlowe_template.md (1)
  • 3-12: The changelog entries clearly document the introduction of the new @marlowe.io/marlowe-template package and updates to the @marlowe.io/runtime-core package. The inclusion of PR links provides valuable context for these changes.
packages/marlowe-template/src/codecs.ts (3)
  • 10-21: The DateFromEpochMS codec correctly handles the conversion between BigIntOrNumber and Date, ensuring proper type checking and conversion logic.
  • 23-37: The StringSplitCodec efficiently handles string splitting and joining, considering the ledger's constraints on string lengths. It ensures strings are correctly split into chunks of 64 characters or less and reassembled.
  • 39-52: The TokenCodec correctly manages the conversion between a tuple representation and the Token type, leveraging the TokenFromTuple intermediate codec for accurate type validation and conversion.
packages/marlowe-template/test/codecs.spec.ts (3)
  • 7-19: The tests for DateFromEpochMS codec correctly cover scenarios for decoding numbers as dates, failing to decode strings, and encoding dates as numbers. These tests ensure the codec's functionality is as expected.
  • 22-42: The tests for StringSplitCodec cover decoding small strings, merging strings, encoding small strings, splitting long strings, and failing to decode numbers. These tests comprehensively validate the codec's string handling capabilities.
  • 45-55: The tests for TokenCodec ensure that it correctly decodes and encodes valid tokens, providing comprehensive coverage for the codec's functionality in handling token representations.
packages/marlowe-template/src/index.ts (1)
  • 57-75: The exports related to the MarloweTemplate, including the class, creation function, parameter types, and utility types, are correctly listed and relevant to the package's functionality. This modular organization facilitates easy usage and extension of the package.
packages/marlowe-template/test/basic-types-template.spec.ts (1)
  • 5-96: The unit tests for the template with basic types comprehensively cover scenarios for correct and incorrect values, encoding and decoding of valid values, and handling of long strings. These tests ensure the template's functionality is as expected and robustly handles various parameter types.
packages/marlowe-template/src/template-param.ts (1)
  • 10-166: The interfaces for various template parameters (StringParam, ValueParam, AddressParam, DateParam, TokenParam) and utility types/functions (TypeOfParam, TemplateKeys, TemplateType, templateParamsCodec, templateParamsObjectGuard) are correctly defined and implemented. They provide efficient and type-safe ways to work with Marlowe templates, adhering to best practices in TypeScript.
packages/marlowe-template/test/delayed-payment-template.spec.ts (1)
  • 6-168: The unit tests for the delayed payment template comprehensively cover scenarios for correct and incorrect values, encoding and decoding of valid values, and handling of invalid addresses. These tests ensure the template's functionality is as expected and robustly handles various parameter types and edge cases.
packages/marlowe-template/src/template.ts (3)
  • 1-9: The imports are well-organized and clearly separated by their source. However, ensure that all imported entities are used within the file to avoid unnecessary dependencies.
  • 15-25: The DecodingTemplateError class is a specialized error type for decoding issues. It's well-implemented, providing clear error messaging and extending the native Error class properly. Including the template and error details in the constructor enhances error traceability.
  • 172-226: The utility types and functions, such as Expand, TemplateParametersOf, and mkMarloweTemplate, are well-implemented, enhancing the usability and readability of the template system. The mkMarloweTemplate function, in particular, simplifies the creation of MarloweTemplate instances with type inference for template parameters. These additions follow best practices for type safety and code clarity.

Comment on lines +42 to +169
}
if (metadatum === null) {
return t.failure(val, ctx, "Metadata entry 9041 is null");
}

if ("v" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a version field"
);
}
if ("params" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a params field"
);
}

const version = metadatum["v" as any];

if (
!BigIntOrNumberGuard.is(version) ||
BigIntOrNumberGuard.encode(version) !== 1n
) {
return t.failure(
val,
ctx,
"Metadata entry 9041 has an invalid version"
);
}

const paramList = paramListCodec.decode(metadatum["params" as any]);
if (paramList._tag === "Left") {
return t.failure(
paramList.left[0].value,
paramList.left[0].context,
"Invalid params"
);
}
const result = {} as any;
templateParams.forEach((param, ix) => {
result[param.name] = paramList.right[ix];
});
return t.success(result as ObjectParams);
},
(values) => {
// FIXME: Try to type
let valuesAsList: any[] = [];

templateParams.forEach((param, ix) => {
const value = (values as any)[param.name];
if (typeof value === "undefined") {
throw new Error(`The value for ${param.name} is missing`);
}
valuesAsList.push(value);
});
return {
"9041": {
v: 1,
params: paramListCodec.encode(valuesAsList),
},
};
}
)
);
}

/**
* Type guard for the ObjectParams type.
* @param value - an unknown value to check if it is a valid ObjectParams.
* @returns true if the value is a valid ObjectParams, false otherwise.
*/
is(value: unknown): value is ObjectParams {
return this.templateCodec.is(value);
}
/**
* Decodes a Metadata into an ObjectParams.
* @param value - a Metadata to decode.
* @returns the decoded ObjectParams.
* @throws {@link DecodingTemplateError} - if the value is not a valid Metadata.
*/
fromMetadata(value: Metadata): ObjectParams {
const decoded = this.templateCodec.decode(value);
if (decoded._tag === "Right") {
return decoded.right;
} else {
throw new DecodingTemplateError(this, decoded.left);
}
}
/**
* Encodes the given value into a Metadata object.
*
* @param value The value to be encoded.
* @returns The encoded Metadata object.
*/
toMetadata(value: ObjectParams) {
return this.templateCodec.encode(value);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The MarloweTemplate class is the core of this file, encapsulating the logic for serializing and deserializing template parameters to and from Metadata. The implementation is robust, with several key points to note:

  • The use of io-ts codecs and guards for type safety and validation is commendable.
  • The constructor's logic for creating codecs based on template parameters is complex but well-structured. However, the magic number 9041 used as a key in the metadata object could benefit from being defined as a constant with a descriptive name for better readability and maintainability.
  • The error handling in fromMetadata method is precise, throwing a DecodingTemplateError with relevant information when decoding fails.
  • The FIXME comment on line 118 suggests an area for improvement. Attempting to type the valuesAsList more specifically than any[] would enhance type safety.

Consider defining the magic number 9041 as a constant and addressing the FIXME note by exploring ways to type valuesAsList more accurately.

+ const METADATA_KEY = 9041; // Descriptive name for the metadata key
- const metadatum = val[9041] ?? (val["9041"] as unknown);
+ const metadatum = val[METADATA_KEY] ?? (val[String(METADATA_KEY)] as unknown);

Address the FIXME on line 118 by exploring more specific typing for valuesAsList.


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 class MarloweTemplate<ObjectParams extends object> {
private templateCodec: t.Type<ObjectParams, Metadata, unknown>;
name: string;
description?: string;
/**
* Manual constructor for the MarloweTemplate class, you should use {@link mkMarloweTemplate} instead.
* @typeParam ObjectParams - The inferred type of the `options.params` as an object.
* @param options - The {@link MkTemplateOptions | options} to create a new MarloweTemplate.
*/
constructor(options: MkTemplateOptions<readonly TemplateParam<any>[]>) {
const templateParams = options.params;
this.name = options.name;
this.description = options.description;
const paramListCodec = templateParamsCodec(templateParams);
const paramObjectCodec = templateParamsObjectGuard(templateParams);
this.templateCodec = MetadataGuard.pipe(
new t.Type<ObjectParams, Metadata, Metadata>(
options.name,
paramObjectCodec.is,
(val, ctx) => {
const metadatum = val[9041] ?? (val["9041"] as unknown);
if (typeof metadatum === "undefined") {
return t.failure(val, ctx, "Missing metadata entry 9041");
}
if (typeof metadatum !== "object") {
return t.failure(val, ctx, "Metadata entry 9041 is not an object");
}
if (metadatum === null) {
return t.failure(val, ctx, "Metadata entry 9041 is null");
}
if ("v" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a version field"
);
}
if ("params" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a params field"
);
}
const version = metadatum["v" as any];
if (
!BigIntOrNumberGuard.is(version) ||
BigIntOrNumberGuard.encode(version) !== 1n
) {
return t.failure(
val,
ctx,
"Metadata entry 9041 has an invalid version"
);
}
const paramList = paramListCodec.decode(metadatum["params" as any]);
if (paramList._tag === "Left") {
return t.failure(
paramList.left[0].value,
paramList.left[0].context,
"Invalid params"
);
}
const result = {} as any;
templateParams.forEach((param, ix) => {
result[param.name] = paramList.right[ix];
});
return t.success(result as ObjectParams);
},
(values) => {
// FIXME: Try to type
let valuesAsList: any[] = [];
templateParams.forEach((param, ix) => {
const value = (values as any)[param.name];
if (typeof value === "undefined") {
throw new Error(`The value for ${param.name} is missing`);
}
valuesAsList.push(value);
});
return {
"9041": {
v: 1,
params: paramListCodec.encode(valuesAsList),
},
};
}
)
);
}
/**
* Type guard for the ObjectParams type.
* @param value - an unknown value to check if it is a valid ObjectParams.
* @returns true if the value is a valid ObjectParams, false otherwise.
*/
is(value: unknown): value is ObjectParams {
return this.templateCodec.is(value);
}
/**
* Decodes a Metadata into an ObjectParams.
* @param value - a Metadata to decode.
* @returns the decoded ObjectParams.
* @throws {@link DecodingTemplateError} - if the value is not a valid Metadata.
*/
fromMetadata(value: Metadata): ObjectParams {
const decoded = this.templateCodec.decode(value);
if (decoded._tag === "Right") {
return decoded.right;
} else {
throw new DecodingTemplateError(this, decoded.left);
}
}
/**
* Encodes the given value into a Metadata object.
*
* @param value The value to be encoded.
* @returns The encoded Metadata object.
*/
toMetadata(value: ObjectParams) {
return this.templateCodec.encode(value);
}
export class MarloweTemplate<ObjectParams extends object> {
private templateCodec: t.Type<ObjectParams, Metadata, unknown>;
name: string;
description?: string;
/**
* Manual constructor for the MarloweTemplate class, you should use {@link mkMarloweTemplate} instead.
* @typeParam ObjectParams - The inferred type of the `options.params` as an object.
* @param options - The {@link MkTemplateOptions | options} to create a new MarloweTemplate.
*/
constructor(options: MkTemplateOptions<readonly TemplateParam<any>[]>) {
const templateParams = options.params;
this.name = options.name;
this.description = options.description;
const paramListCodec = templateParamsCodec(templateParams);
const paramObjectCodec = templateParamsObjectGuard(templateParams);
+ const METADATA_KEY = 9041; // Descriptive name for the metadata key
this.templateCodec = MetadataGuard.pipe(
new t.Type<ObjectParams, Metadata, Metadata>(
options.name,
paramObjectCodec.is,
(val, ctx) => {
- const metadatum = val[9041] ?? (val["9041"] as unknown);
+ const metadatum = val[METADATA_KEY] ?? (val[String(METADATA_KEY)] as unknown);
if (typeof metadatum === "undefined") {
return t.failure(val, ctx, "Missing metadata entry 9041");
}
if (typeof metadatum !== "object") {
return t.failure(val, ctx, "Metadata entry 9041 is not an object");
}
if (metadatum === null) {
return t.failure(val, ctx, "Metadata entry 9041 is null");
}
if ("v" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a version field"
);
}
if ("params" in metadatum === false) {
return t.failure(
val,
ctx,
"Metadata entry 9041 doesn't have a params field"
);
}
const version = metadatum["v" as any];
if (
!BigIntOrNumberGuard.is(version) ||
BigIntOrNumberGuard.encode(version) !== 1n
) {
return t.failure(
val,
ctx,
"Metadata entry 9041 has an invalid version"
);
}
const paramList = paramListCodec.decode(metadatum["params" as any]);
if (paramList._tag === "Left") {
return t.failure(
paramList.left[0].value,
paramList.left[0].context,
"Invalid params"
);
}
const result = {} as any;
templateParams.forEach((param, ix) => {
result[param.name] = paramList.right[ix];
});
return t.success(result as ObjectParams);
},
(values) => {
// FIXME: Try to type
let valuesAsList: any[] = [];
templateParams.forEach((param, ix) => {
const value = (values as any)[param.name];
if (typeof value === "undefined") {
throw new Error(`The value for ${param.name} is missing`);
}
valuesAsList.push(value);
});
return {
"9041": {
v: 1,
params: paramListCodec.encode(valuesAsList),
},
};
}
)
);
}
/**
* Type guard for the ObjectParams type.
* @param value - an unknown value to check if it is a valid ObjectParams.
* @returns true if the value is a valid ObjectParams, false otherwise.
*/
is(value: unknown): value is ObjectParams {
return this.templateCodec.is(value);
}
/**
* Decodes a Metadata into an ObjectParams.
* @param value - a Metadata to decode.
* @returns the decoded ObjectParams.
* @throws {@link DecodingTemplateError} - if the value is not a valid Metadata.
*/
fromMetadata(value: Metadata): ObjectParams {
const decoded = this.templateCodec.decode(value);
if (decoded._tag === "Right") {
return decoded.right;
} else {
throw new DecodingTemplateError(this, decoded.left);
}
}
/**
* Encodes the given value into a Metadata object.
*
* @param value The value to be encoded.
* @returns The encoded Metadata object.
*/
toMetadata(value: ObjectParams) {
return this.templateCodec.encode(value);
}

Comment on lines +382 to 421
const delayPaymentTemplate = mkMarloweTemplate({
name: "Delayed payment",
description:
"In a delay payment, a `payer` transfer an `amount` of ADA to the `payee` which can be redeemed after a `releaseDeadline`. While the payment is held by the contract, it can be staked to the payer, to generate pasive income while the payee has the guarantees that the money will be released.",
params: [
{
name: "payer",
description: "Who is making the payment",
type: "address",
},
{
name: "payee",
description: "Who is receiving the payment",
type: "address",
},
{
name: "amount",
description: "The amount of lovelaces to be paid",
type: "value",
},
{
name: "depositDeadline",
description:
"The deadline for the payment to be made. If the payment is not made by this date, the contract can be closed",
type: "date",
},
{
name: "releaseDeadline",
description:
"A date after the payment can be released to the receiver. NOTE: An empty transaction must be done to close the contract",
type: "date",
},
] as const,
});

/**
* These are the parameters of the contract
*/
interface DelayPaymentScheme {
/**
* Who is making the delayed payment
*/
payFrom: Address;
/**
* Who is receiving the payment
*/
payTo: Address;
/**
* The amount of lovelaces to be paid
*/
amount: bigint;
/**
* The deadline for the payment to be made. If the payment is not made by this date, the contract can be closed
*/
depositDeadline: Date;
/**
* A date after the payment can be released to the receiver.
* NOTE: An empty transaction must be done to close the contract
*/
releaseDeadline: Date;
}
type DelayPaymentParameters = TemplateParametersOf<typeof delayPaymentTemplate>;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Before we manually defined the DelayPaymentScheme, now we manually define the delayPaymentTemplate and the Scheme is inferred (and renamed as DelayPaymentParameters)

Comment on lines -565 to -607
const mkDelayPaymentTags = (schema: DelayPaymentScheme) => {
const tag = "DELAY_PYMNT-1";
const tags = {} as Tags;

tags[`${tag}-from-0`] = splitAddress(schema.payFrom)[0];
tags[`${tag}-from-1`] = splitAddress(schema.payFrom)[1];
tags[`${tag}-to-0`] = splitAddress(schema.payTo)[0];
tags[`${tag}-to-1`] = splitAddress(schema.payTo)[1];
tags[`${tag}-amount`] = schema.amount;
tags[`${tag}-deposit`] = schema.depositDeadline;
tags[`${tag}-release`] = schema.releaseDeadline;
return tags;
};

const extractSchemeFromTags = (
tags: unknown
): DelayPaymentScheme | undefined => {
const tagsGuard = t.type({
"DELAY_PYMNT-1-from-0": t.string,
"DELAY_PYMNT-1-from-1": t.string,
"DELAY_PYMNT-1-to-0": t.string,
"DELAY_PYMNT-1-to-1": t.string,
"DELAY_PYMNT-1-amount": t.bigint,
"DELAY_PYMNT-1-deposit": t.string,
"DELAY_PYMNT-1-release": t.string,
});

if (!tagsGuard.is(tags)) {
return;
}

return {
payFrom: {
address: `${tags["DELAY_PYMNT-1-from-0"]}${tags["DELAY_PYMNT-1-from-1"]}`,
},
payTo: {
address: `${tags["DELAY_PYMNT-1-to-0"]}${tags["DELAY_PYMNT-1-to-1"]}`,
},
amount: tags["DELAY_PYMNT-1-amount"],
depositDeadline: new Date(tags["DELAY_PYMNT-1-deposit"]),
releaseDeadline: new Date(tags["DELAY_PYMNT-1-release"]),
};
};
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

these functions that manually encoded and decoded the parameters as tags are now provided by the toMetadata and fromMetadata functions of the template parameters.

@hrajchert hrajchert merged commit a0d24f3 into main Feb 20, 2024
4 checks passed
@hrajchert hrajchert deleted the experiment/blueprints branch February 20, 2024 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants