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

PLT-8891: Add marlowe-object contract flow example #136

Merged
merged 22 commits into from
Jan 10, 2024

Conversation

hrajchert
Copy link
Collaborator

@hrajchert hrajchert commented Dec 16, 2023

This PR creates a node.js interactive app that allows you to create and interact with a marlowe-object contract.

A full list of changes can be found in the scriv entry.

To test this PR you can:

$ npm run build
$ cd examples/nodejs
# Follow the folder readme to see the shape of this file
$ vim .config.json 
$ node dist/marlowe-object-flow.js
# And follow the interactive menu.

resolves #125

Summary by CodeRabbit

  • New Features

    • Implemented a script for creating and interacting with large contracts using Merkleization.
    • Added a utility for converting ISO8601 time to POSIX time.
    • Introduced experimental modules for determining applicable actions in a Marlowe contract.
    • Added a flexible configuration file reading option in Node.js examples.
    • New functionality to split an address into two equal parts.
  • Enhancements

    • Improved contract validation with the addition of new interfaces and types.
    • Extended transaction interfaces to better represent Marlowe transactions.
  • Bug Fixes

    • Fixed control flow in wallet example by removing unnecessary await.
    • Enhanced error handling in the main function of wallet example.
  • Documentation & Style

    • Updated comments and formatting in various files for better readability.
    • Added TypeScript language server configuration and debug settings for JavaScript.
  • Refactor

    • Streamlined import statements for better module interaction.
    • Modified function parameters to increase clarity and consistency.
    • Adjusted pagination interface for improved functionality.
  • Chores

    • Updated .gitignore to better exclude config files.

Copy link
Contributor

coderabbitai bot commented Dec 16, 2023

Walkthrough

Recent updates introduce a Node.js example showcasing the creation and interaction with Marlowe contracts using the marlowe-object package. This includes Merkleization of large contracts, delayed payments, and contract validation. Error handling is improved in scripts, and a new minBigint function is added for bigint comparisons. The getNextTimeout function is refined, and TypeScript configurations are updated. Changes to package imports and interfaces enhance functionality related to Merkleized inputs and contract transactions, supporting experimental features and contract lifecycle management.

Changes

File Path Change Summary
examples/nodejs/src/marlowe-object-flow.ts Added a script for creating and managing Marlowe contracts with marlowe-object package.
examples/nodejs/src/wallet-flow.ts Removed await from main() call; added error handling.
packages/language/core/v1/src/contract.ts Added minBigint function; modified getNextTimeout.
packages/language/core/v1/src/index.ts Exported getNextTimeout.
packages/runtime/lifecycle/src/api.ts Modified imports; added restClient parameter.
packages/runtime/lifecycle/src/generic/runtime.ts Added restClient parameter to mkRuntimeLifecycle function.
.vscode/settings.json, examples/nodejs/.gitignore Updated TypeScript settings; modified .gitignore rule.
examples/nodejs/src/config.ts Enhanced readConfig function with optional path parameter.
examples/nodejs/src/experimental-features/... Introduced experimental modules for contract actions and metadata handling.
packages/adapter/src/bigint.ts Added utility functions for bigint and time conversions.
packages/language/core/v1/src/inputs.ts Updated interfaces and types related to Merkleized inputs.
packages/language/core/v1/src/semantics.ts Various changes including addition of IChoice and IDeposit and modifications to functions.
packages/language/core/v1/src/transaction.ts Extended Transaction interface; updated comments.
packages/marlowe-object/src/guards.ts, index.ts Added ContractBundle interface and exports.
packages/marlowe-object/src/object.ts Added the ContractBundle interface and its corresponding ContractBundleGuard type guard.
packages/runtime/client/rest/src/index.ts Changed imports; updated function parameters for contract sources.
packages/runtime/client/rest/src/pagination.ts Modified Page interface to make current property optional.
packages/runtime/lifecycle/src/generic/contracts.ts Implemented getInputHistory function for transaction history retrieval.

Assessment against linked issues

Objective Addressed Explanation
#125: Add marlowe-object contract flow example (PLT-8891)

Poem

In the code where logic weaves,
🐇 A rabbit hops through marlowe-object leaves.
With each commit, the garden grows,
Bigints and contracts, in tidy rows.

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.

@hrajchert hrajchert added this to the [#0] Release 0.3.0-beta milestone Dec 16, 2023
@hrajchert hrajchert self-assigned this Dec 16, 2023
@hrajchert hrajchert marked this pull request as draft December 16, 2023 00:37
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: 4

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between b3378c5 and b05e8eb.
Files ignored due to filter (5)
  • examples/nodejs/package-lock.json
  • examples/nodejs/package.json
  • examples/nodejs/src/tsconfig.json
  • examples/nodejs/tsconfig.json
  • tsconfig.json
Files selected for processing (6)
  • examples/nodejs/src/marlowe-object-flow.ts (1 hunks)
  • examples/nodejs/src/wallet-flow.ts (1 hunks)
  • packages/language/core/v1/src/contract.ts (1 hunks)
  • packages/language/core/v1/src/index.ts (1 hunks)
  • packages/runtime/lifecycle/src/api.ts (5 hunks)
  • packages/runtime/lifecycle/src/generic/runtime.ts (1 hunks)
Additional comments: 5
packages/language/core/v1/src/contract.ts (1)
  • 334-365: The implementation of minBigint and getNextTimeout functions appears to be correct. The getNextTimeout function uses pattern matching to handle different contract types and seems to handle the when contract case appropriately by checking if minTime is greater than whenContract.timeout. If it is, the function recursively calls itself with the timeout_continuation of the when contract; otherwise, it returns the timeout of the when contract. This logic is consistent with the expected behavior of finding the next timeout after a given time.
packages/language/core/v1/src/index.ts (1)
  • 43-43: The addition of getNextTimeout to the exports in index.ts aligns with the PR's objective to expose this function as part of the public API.
packages/runtime/lifecycle/src/api.ts (2)
  • 13-13: The import statement has been correctly updated to include both RestClient and RestDI.

  • 26-26: The RuntimeLifecycle type has been correctly updated to include the new restClient property.

packages/runtime/lifecycle/src/generic/runtime.ts (1)
  • 16-16: The addition of the restClient property to the RuntimeLifecycle return type is consistent with the PR's objectives. Ensure that the new property is properly integrated and utilized in the codebase where RuntimeLifecycle objects are used.

examples/nodejs/src/wallet-flow.ts Outdated Show resolved Hide resolved
examples/nodejs/src/marlowe-object-flow.ts Outdated Show resolved Hide resolved
Comment on lines 13 to 19
): RuntimeLifecycle {
return {
wallet: wallet,
restClient,
contracts: mkContractLifecycle(wallet, deprecatedRestAPI, restClient),
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 NOTE
Codebase verification is a beta feature.

The verification process has identified several issues with the current invocations of the mkRuntimeLifecycle function:

  • In packages/runtime/lifecycle/test/provisionning.ts, both the higher-order function on line 75 and the direct invocation on line 99 need to be updated to include the restClient parameter.
  • In examples/nodejs/src/marlowe-object-flow.ts and examples/nodejs/src/escrow-flow.ts, the calls to mkRuntimeLifecycle are incorrect as they pass an object instead of individual parameters. These calls need to be updated to match the new function signature that includes the restClient parameter.
Analysis chain

The mkRuntimeLifecycle function now requires an additional restClient parameter. Verify that all invocations of this function across the codebase have been updated to include this new argument.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for `mkRuntimeLifecycle` function calls without the `restClient` parameter.
ast-grep --lang typescript --pattern $'mkRuntimeLifecycle($_, $_, $_)'
# Search for `mkRuntimeLifecycle` function calls with the `restClient` parameter to ensure they are updated.
ast-grep --lang typescript --pattern $'mkRuntimeLifecycle($_, $_, $_, $_)'

Length of output: 147



Script:

#!/bin/bash
# Search for any calls to `mkRuntimeLifecycle` to ensure all possible invocations are caught.
ast-grep --lang typescript --pattern $'mkRuntimeLifecycle($$$)'

Length of output: 797



Script:

#!/bin/bash
# Check the context around the higher-order function in `provisionning.ts` to understand how it's used with `mkRuntimeLifecycle`.
rg --context 5 "mkRuntimeLifecycle" "packages/runtime/lifecycle/test/provisionning.ts"

Length of output: 818

@hrajchert hrajchert force-pushed the plt-8891-add-merkleized-contract-flow-example branch 3 times, most recently from 6872a25 to 921cd84 Compare December 22, 2023 12:40
@hrajchert hrajchert force-pushed the plt-8891-add-merkleized-contract-flow-example branch 3 times, most recently from 936fbf1 to de3da3f Compare January 6, 2024 01:05
@hrajchert hrajchert marked this pull request as ready for review January 8, 2024 20:33
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: 6

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 4160a76 and 5882547.
Files ignored due to filter (8)
  • changelog.d/scriv.ini
  • examples/nodejs/package-lock.json
  • examples/nodejs/package.json
  • examples/nodejs/src/tsconfig.json
  • examples/nodejs/tsconfig.json
  • packages/adapter/package.json
  • tsconfig-base.json
  • tsconfig.json
Files selected for processing (24)
  • .vscode/settings.json (1 hunks)
  • changelog.d/20240108_165452_hrajchert_plt_8891_add_merkleized_contract_flow_example.md (1 hunks)
  • examples/nodejs/.gitignore (1 hunks)
  • examples/nodejs/src/config.ts (1 hunks)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts (1 hunks)
  • examples/nodejs/src/experimental-features/metadata.ts (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (1 hunks)
  • examples/nodejs/src/wallet-flow.ts (1 hunks)
  • jsdelivr-npm-importmap.js (1 hunks)
  • packages/adapter/src/bigint.ts (1 hunks)
  • packages/adapter/src/time.ts (1 hunks)
  • packages/language/core/v1/src/contract.ts (2 hunks)
  • packages/language/core/v1/src/index.ts (1 hunks)
  • packages/language/core/v1/src/inputs.ts (3 hunks)
  • packages/language/core/v1/src/semantics.ts (11 hunks)
  • packages/language/core/v1/src/transaction.ts (2 hunks)
  • packages/marlowe-object/src/guards.ts (1 hunks)
  • packages/marlowe-object/src/index.ts (1 hunks)
  • packages/marlowe-object/src/object.ts (1 hunks)
  • packages/runtime/client/rest/src/index.ts (3 hunks)
  • packages/runtime/client/rest/src/pagination.ts (2 hunks)
  • packages/runtime/lifecycle/src/api.ts (6 hunks)
  • packages/runtime/lifecycle/src/generic/contracts.ts (4 hunks)
  • packages/runtime/lifecycle/src/generic/runtime.ts (1 hunks)
Files skipped from review due to trivial changes (3)
  • .vscode/settings.json
  • examples/nodejs/.gitignore
  • packages/marlowe-object/src/guards.ts
Additional comments: 52
packages/adapter/src/bigint.ts (1)
  • 5-6: The implementation of min and max functions for bigint types looks good and follows best practices for utility functions.
examples/nodejs/src/experimental-features/metadata.ts (1)
  • 3-8: The splitAddress function is implemented correctly and follows good practices in terms of simplicity and readability.
packages/runtime/lifecycle/src/generic/runtime.ts (1)
  • 16-16: The addition of the restClient parameter to the RuntimeLifecycle object is correctly implemented.
packages/adapter/src/time.ts (1)
  • 16-17: The iso8601ToPosixTime function is correctly implemented and follows best practices for time conversion functions.
examples/nodejs/src/config.ts (1)
  • 22-23: The readConfig function is correctly updated to accept an optional path parameter, enhancing its flexibility.
packages/runtime/client/rest/src/pagination.ts (1)
  • 20-20: Making the current property optional in the Page interface is a reasonable change that can accommodate pagination responses without a current page.
packages/marlowe-object/src/index.ts (1)
  • 53-53: The export of the ContractBundle is correctly added to the list of exported entities.
examples/nodejs/src/wallet-flow.ts (1)
  • 52-54: The addition of error handling to the main function with a catch block is a good practice to ensure any unhandled errors are logged.
changelog.d/20240108_165452_hrajchert_plt_8891_add_merkleized_contract_flow_example.md (1)
  • 4-35: The changelog entries are well-documented and provide a clear overview of the features and changes introduced in the PR.
packages/language/core/v1/src/index.ts (1)
  • 66-66: The export of the getNextTimeout function is correctly added to the list of exported entities.
packages/marlowe-object/src/object.ts (1)
  • 175-186: The ContractBundle interface and its corresponding ContractBundleGuard are correctly defined and follow best practices for type safety.
packages/language/core/v1/src/inputs.ts (1)
  • 119-160: The new interfaces and types for Merkleized inputs are correctly added, and the MerkleizedInput type is appropriately updated to be a union of these new types.
jsdelivr-npm-importmap.js (1)
  • 7-8: The addition of the @marlowe.io/adapter/bigint entry to the import map is consistent with the existing pattern and versioning. Looks good.
packages/language/core/v1/src/transaction.ts (2)
  • 42-42: The updated comment for PaymentGuard adds clarity on the composition of a marlowe transaction with multiple inputs within a time interval. This is a helpful clarification.

  • 59-72: The addition of the SingleInputTx interface is well-documented and follows TypeScript best practices for interface declarations. The comments provide useful insights into the use of the interface.

packages/language/core/v1/src/contract.ts (2)
  • 16-16: The import of Big from @marlowe.io/adapter/bigint is correctly implemented and necessary for the getNextTimeout function that follows.

  • 335-363: The getNextTimeout function is a well-documented and cleanly implemented addition that uses pattern matching to handle different contract types and determine the next timeout. It correctly utilizes the Big.min function from the @marlowe.io/adapter/bigint module to compare timeouts.

packages/runtime/lifecycle/src/generic/contracts.ts (2)
  • 3-3: The import of Option from "fp-ts/lib/Option.js" is added to handle optional values in a functional programming style. This is a standard practice in FP and should be fine as long as it's used consistently throughout the codebase.

  • 22-22: The import of BlockHeader from "@marlowe.io/runtime-core" is added, presumably for use in the new getInputHistory function to sort transactions based on block headers.

packages/runtime/lifecycle/src/api.ts (4)
  • 13-13: The import statement now includes RestClient, which is used in the RuntimeLifecycle type. This change aligns with the addition of restClient as a dependency for various lifecycle functions.

  • 27-27: The RuntimeLifecycle type has been modified to include restClient: RestClient. This change is consistent with the PR's objective to integrate the RestClient into the lifecycle management.

  • 23-23: The import of SingleInputTx from "@marlowe.io/language-core-v1/transaction.js" is added, which is likely used in the newly added getInputHistory method in the ContractsAPI interface.

  • 273-273: The getInputHistory method is added to the ContractsAPI interface, which aligns with the implementation provided in the contracts.ts file. This method is part of the new functionality to retrieve the transaction history for a contract.

examples/nodejs/src/experimental-features/applicable-inputs.ts (5)
  • 48-73: The AppliedActionResult interface is well-documented and clearly defines the expected results of applying an action. Ensure that the documentation remains up-to-date if any changes are made to the interface in the future.

  • 76-79: The CanNotify interface defines a type and a policyId along with an applyAction method. It's important to ensure that the applyAction method is implemented correctly in all classes that implement this interface.

  • 122-195: The getApplicableActions function is complex and performs several asynchronous operations. It's crucial to ensure that error handling is robust, especially since the function interacts with external services via restClient. Additionally, the function should be tested thoroughly to handle various edge cases and potential failures in external service communication.

Verification successful

Given the absence of a try-catch block within the getApplicableActions function, as indicated by the ast-grep script output, it appears that the function may not have robust error handling implemented in the traditional sense. However, this does not necessarily mean that error handling is not present in another form. To provide a more comprehensive verification, we should investigate other error handling mechanisms that may be in place.


The script contained an error due to the misuse of the --exec flag with ast-grep, which does not support this flag. To proceed, we will directly use rg to search for patterns of error handling within the file containing the getApplicableActions function.


The script has successfully identified several instances where throw new Error is used within the getApplicableActions function in the applicable-inputs.ts file. This indicates that the function does implement some form of error handling by throwing exceptions when certain error conditions are met.

However, the absence of catch blocks or .catch() methods suggests that while the function can throw errors, it may not be handling them internally. Instead, it could be relying on the caller to handle these exceptions.

Based on the script output, it seems that the function is designed to propagate errors up the call stack rather than handling them internally. This is a valid error handling strategy, especially in asynchronous functions that are expected to be used with await, where the caller can use try-catch blocks to handle the errors.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that error handling is robust in getApplicableActions function.
ast-grep --lang typescript --pattern $'async function getApplicableActions($_, $_, $_) {
  try {
    $$$
  } catch ($_) {
    $$$
  }
}'

Length of output: 137



Script:

#!/bin/bash
# Check for promise rejection handling with .catch() or async/await with try-catch in the getApplicableActions function.
ast-grep --lang typescript --pattern $'async function getApplicableActions($_, $_, $_) {
  $$$
}' --exec 'rg "catch\(|throw new Error\("'

Length of output: 332



Script:

#!/bin/bash
# Find the file containing getApplicableActions and check for error handling patterns.
file_path=$(fd applicable-inputs.ts)
rg "catch\(|throw new Error\(" $file_path

Length of output: 540

* 198-224: The `mkPartyFilter` function creates a filter based on the wallet's addresses and tokens. It's important to ensure that the filter logic is correct and that it handles all possible cases for parties and policy IDs. Additionally, consider the performance implications if the tokens array is large.
  • 322-346: The mergeBounds function is used to merge overlapping bounds. It's important to ensure that the sorting and merging logic is correct and that it handles all edge cases, such as adjacent bounds that should be merged.
Verification successful

The mergeBounds function exists in the file examples/nodejs/src/experimental-features/applicable-inputs.ts. To verify the logic of the function, we need to examine its implementation details. Since the function is present, we can proceed to check the sorting and merging logic within the function to ensure it handles overlapping and adjacent bounds correctly.


The mergeBounds function in examples/nodejs/src/experimental-features/applicable-inputs.ts sorts the bounds and merges them if they overlap or are adjacent. The logic checks if the from value of the current bound is less than or equal to the to value of the previous bound to decide if a merge is necessary. If so, it updates the to value to the maximum of the two to values. If not, it pushes the current bound to the mergedBounds array and starts a new current bound. The function appears to handle both overlapping and adjacent bounds as intended.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that mergeBounds function correctly merges overlapping and adjacent bounds.
ast-grep --lang typescript --pattern $'function mergeBounds($_) {
  $$$
}'

Length of output: 75



Script:

#!/bin/bash
# Search for the definition of the mergeBounds function to verify its logic.
ast-grep --lang typescript --pattern $'function mergeBounds($$$) {
  $$$
}'

Length of output: 76



Script:

#!/bin/bash
# Use ripgrep to search for the mergeBounds function in the codebase.
rg 'function mergeBounds'

Length of output: 139



Script:

#!/bin/bash
# Extract the implementation details of the mergeBounds function to verify its logic.
rg -A 20 'function mergeBounds' examples/nodejs/src/experimental-features/applicable-inputs.ts

Length of output: 689

packages/runtime/client/rest/src/index.ts (2)
  • 18-18: The import statement has been updated to use ContractBundle instead of Label and Bundle. This change aligns with the PR's objective to work with marlowe-object contracts.

  • 90-93: The createContractSources function now takes a ContractBundle object as a parameter. Ensure that all calls to this function have been updated to pass the correct object structure.

examples/nodejs/src/marlowe-object-flow.ts (14)
  • 38-38: The previous comment regarding the placement of the main() function call is still valid. It should be moved to the bottom of the file or wrapped in a guard to prevent it from running on module import.

  • 41-64: The parseCli function correctly parses command-line arguments and handles the --help option. It is well-structured and follows best practices.

  • 74-91: The waitIndicator function is implemented correctly and provides clear feedback to the user. It uses a non-blocking loop to write dots to the console, which is a good practice for CLI tools.

  • 97-119: The validators for bech32 addresses and positive bigints are implemented correctly and provide clear error messages. They follow best practices for input validation.

  • 126-134: The dateInFutureValidator function correctly checks if the provided date is in the future. It is a good practice to validate user input, especially when dealing with dates.

  • 142-200: The createContractMenu function is well-structured and uses the validators correctly. It also provides clear feedback to the user about the contract creation process.

  • 207-236: The loadContractMenu function correctly handles the loading of an existing contract. It validates the contract's tags and provides appropriate feedback if the contract is invalid.

  • 241-315: The contractMenu function is well-implemented, with clear logic for handling different contract actions. It uses the getApplicableActions and mkApplicableActionsFilter functions to determine the actions available to the user.

  • 318-351: The mainLoop function is correctly implemented and handles the main menu of the CLI tool. It provides a clear structure for the different actions a user can take.

  • 359-380: The DelayPaymentScheme interface and related types are well-defined and provide a clear structure for the delay payment contract parameters.

  • 383-418: The mkDelayPayment function correctly constructs a contract bundle for delay payment contracts. It uses the datetoTimeout function to convert dates to timeouts, which is appropriate for the contract logic.

  • 493-519: The getState function correctly determines the contract's state based on the transaction history and current time. It handles different scenarios and provides appropriate error handling.

  • 605-643: The validateExistingContract function correctly checks if the contract ID corresponds to a delay payment contract. It uses deep equality to compare the initial contract with the expected contract, which is a robust method for validation.

  • 645-666: The main function is well-structured and sets up the necessary configurations for the CLI tool. It initializes the wallet and runtime lifecycle correctly and starts the main loop.

packages/language/core/v1/src/semantics.ts (8)
  • 62-62: The import statement for IChoice and IDeposit from inputs.js has been added. This change is likely related to the new functionality introduced in the PR for handling different types of inputs.

  • 90-90: The import of the Big module from @marlowe.io/adapter/bigint is new. This change is consistent with the replacement of minBigint and maxBigint functions with Big.min and Big.max methods later in the file.

  • 95-95: The export of SingleInputTx is new. Ensure that this export is being used elsewhere in the codebase and that it is necessary to be public.

Verification successful

The export of SingleInputTx is being used in multiple files across the codebase, including lifecycle API and contract files, as well as in an example Node.js application. This confirms that the export is necessary and is being utilized as intended.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usage of SingleInputTx across the codebase.
rg --type ts "SingleInputTx"

Length of output: 932

* 433-433: The use of `Big.min` to determine the `paidAmount` is a change from the previous `minBigint` function. This is a correctness improvement if the `Big` library handles `bigint` operations more accurately or efficiently.
  • 535-543: The addition of the ContractQuiescentReduceResult type is new. This type seems to be part of the new functionality for handling contract reduction results. Ensure that this new type is integrated correctly throughout the codebase.

  • 790-790: The convertReduceWarning function is exported, which is a new addition. Verify that this function is used elsewhere in the codebase and that its export is necessary.

Verification successful

The convertReduceWarning function is used within the semantics.ts file and is also referenced in the applicable-inputs.ts file within the Node.js examples. This confirms that the function's export is justified as it is utilized in other parts of the codebase.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usage of convertReduceWarning across the codebase.
rg --type ts "convertReduceWarning"

Length of output: 602

* 841-841: The `applyAllInputs` function is exported, which is new. As with other exports, ensure that this function is used elsewhere and that its export is necessary.
Verification successful

The applyAllInputs function is used both internally within semantics.ts and externally in the examples/nodejs/src/experimental-features/applicable-inputs.ts file. This confirms that the function's export is necessary for its usage outside of its definition module.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usage of applyAllInputs across the codebase.
rg --type ts "applyAllInputs"

Length of output: 1331

* 929-929: The use of `Big.max` to determine the `newFrom` time is a change from the previous `maxBigint` function. This is a correctness improvement if the `Big` library handles `bigint` operations more accurately or efficiently.

Comment on lines 54 to 99
const getInputHistory =
({ restClient }: ContractsDI) =>
async (contractId: ContractId): Promise<SingleInputTx[]> => {
const transactionHeaders = await restClient.getTransactionsForContract(
contractId
);
const transactions = await Promise.all(
transactionHeaders.transactions.map((txHeader) =>
restClient.getContractTransactionById(
contractId,
txHeader.transactionId
)
)
);
const sortOptionalBlock = (
a: Option<BlockHeader>,
b: Option<BlockHeader>
) => {
if (a._tag === "None" || b._tag === "None") {
// TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
// status and with the appropiate values for each state.
throw new Error("A confirmed transaction should have a valid block");
} else {
if (a.value.blockNo < b.value.blockNo) {
return -1;
} else if (a.value.blockNo > b.value.blockNo) {
return 1;
} else {
return 0;
}
}
};
return transactions
.filter((tx) => tx.status === "confirmed")
.sort((a, b) => sortOptionalBlock(a.block, b.block))
.map((tx) => {
const interval = {
from: iso8601ToPosixTime(tx.invalidBefore),
to: iso8601ToPosixTime(tx.invalidHereafter),
};
if (tx.inputs.length === 0) {
return [{ interval }];
} else {
return tx.inputs.map((input) => ({ input, interval }));
}
})
.flat();
Copy link
Contributor

Choose a reason for hiding this comment

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

The getInputHistory function retrieves and sorts transaction history for a given contract. It uses the restClient to fetch transactions and then sorts them based on block numbers. The function throws an error if a confirmed transaction does not have a valid block, which is a good practice for ensuring data integrity. However, the error message could be more descriptive, and the function could potentially handle the 'None' case more gracefully, perhaps by logging a warning and skipping the transaction rather than throwing an error, depending on the expected behavior in this scenario.

- throw new Error("A confirmed transaction should have a valid block");
+ // Consider handling the 'None' case without throwing an error, depending on the expected behavior.
+ // For example, log a warning and continue processing other transactions.

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
const getInputHistory =
({ restClient }: ContractsDI) =>
async (contractId: ContractId): Promise<SingleInputTx[]> => {
const transactionHeaders = await restClient.getTransactionsForContract(
contractId
);
const transactions = await Promise.all(
transactionHeaders.transactions.map((txHeader) =>
restClient.getContractTransactionById(
contractId,
txHeader.transactionId
)
)
);
const sortOptionalBlock = (
a: Option<BlockHeader>,
b: Option<BlockHeader>
) => {
if (a._tag === "None" || b._tag === "None") {
// TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
// status and with the appropiate values for each state.
throw new Error("A confirmed transaction should have a valid block");
} else {
if (a.value.blockNo < b.value.blockNo) {
return -1;
} else if (a.value.blockNo > b.value.blockNo) {
return 1;
} else {
return 0;
}
}
};
return transactions
.filter((tx) => tx.status === "confirmed")
.sort((a, b) => sortOptionalBlock(a.block, b.block))
.map((tx) => {
const interval = {
from: iso8601ToPosixTime(tx.invalidBefore),
to: iso8601ToPosixTime(tx.invalidHereafter),
};
if (tx.inputs.length === 0) {
return [{ interval }];
} else {
return tx.inputs.map((input) => ({ input, interval }));
}
})
.flat();
const getInputHistory =
({ restClient }: ContractsDI) =>
async (contractId: ContractId): Promise<SingleInputTx[]> => {
const transactionHeaders = await restClient.getTransactionsForContract(
contractId
);
const transactions = await Promise.all(
transactionHeaders.transactions.map((txHeader) =>
restClient.getContractTransactionById(
contractId,
txHeader.transactionId
)
)
);
const sortOptionalBlock = (
a: Option<BlockHeader>,
b: Option<BlockHeader>
) => {
if (a._tag === "None" || b._tag === "None") {
// TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
// status and with the appropiate values for each state.
// Consider handling the 'None' case without throwing an error, depending on the expected behavior.
// For example, log a warning and continue processing other transactions.
} else {
if (a.value.blockNo < b.value.blockNo) {
return -1;
} else if (a.value.blockNo > b.value.blockNo) {
return 1;
} else {
return 0;
}
}
};
return transactions
.filter((tx) => tx.status === "confirmed")
.sort((a, b) => sortOptionalBlock(a.block, b.block))
.map((tx) => {
const interval = {
from: iso8601ToPosixTime(tx.invalidBefore),
to: iso8601ToPosixTime(tx.invalidHereafter),
};
if (tx.inputs.length === 0) {
return [{ interval }];
} else {
return tx.inputs.map((input) => ({ input, interval }));
}
})
.flat();

Comment on lines 567 to 594
async function createContract(
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
const contractSources = await lifecycle.restClient.createContractSources(
contractBundle
);
const walletAddress = await lifecycle.wallet.getChangeAddress();
const unsignedTx = await lifecycle.restClient.buildCreateContractTx({
sourceId: contractSources.contractSourceId,
tags,
changeAddress: walletAddress,
stakeAddress: rewardAddress,
minimumLovelaceUTxODeposit: 3_000_000,
version: "v1",
});
const signedCborHex = await lifecycle.wallet.signTx(unsignedTx.tx.cborHex);
await lifecycle.restClient.submitContract(
unsignedTx.contractId,
transactionWitnessSetTextEnvelope(signedCborHex)
);
const txId = contractIdToTxId(unsignedTx.contractId);
return [unsignedTx.contractId, txId];
//----------------
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The createContract function is implemented with a TODO comment indicating a pending task (PLT-9089). Ensure that this task is tracked and prioritized appropriately.

Comment on lines 721 to 732
function inputToInputContent (input: Input): InputContent {
if (input === "input_notify") {
return "input_notify";
}
if ("that_deposits" in input) {
input
return input as IDeposit
}
if ("input_that_chooses_num" in input) {
input
return input as IChoice;
}
return "input_notify"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The inputToInputContent function has been added to convert Input to InputContent. This function appears to be incomplete as it contains lines with only the variable input (lines 726 and 730). These lines should either be removed or completed if they were intended to perform some operation.

-    input
-    input

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
function inputToInputContent (input: Input): InputContent {
if (input === "input_notify") {
return "input_notify";
}
if ("that_deposits" in input) {
input
return input as IDeposit
}
if ("input_that_chooses_num" in input) {
input
return input as IChoice;
}
return "input_notify"
}
function inputToInputContent (input: Input): InputContent {
if (input === "input_notify") {
return "input_notify";
}
if ("that_deposits" in input) {
return input as IDeposit
}
if ("input_that_chooses_num" in input) {
return input as IChoice;
}
return "input_notify"
}

Comment on lines 387 to 515
// For IDeposit and IChoice is the InputContent + the merkle object
return {
...merkleizedHashAndContinuation,
...content,
};
}
} else {
return content;
}
}

if (isDepositAction(cse.case)) {
const deposit = cse.case;
return accumulatorFromDeposit(env, state, {
type: "Deposit",
deposit,
policyId,
applyAction() {
const input = decorateInput({
input_from_party: deposit.party,
that_deposits: evalValue(env, state, deposit.deposits),
of_token: deposit.of_token,
into_account: deposit.into_account,
});
// TODO: Re-check if this env should be the same as the initial env or a new one.
const appliedInput = applyAllInputs(env, state, currentContract, [
input,
]);

// TODO: Improve error handling
if (typeof appliedInput === "string") throw new Error(appliedInput);
return {
inputs: [input],
environment: env,
reducedState: appliedInput.state,
reducedContract: appliedInput.continuation,
warnings: [...previousWarnings, ...appliedInput.warnings],
payments: [...previousPayments, ...appliedInput.payments],
};
},
});
} else if (isChoice(cse.case)) {
const choice = cse.case;

return accumulatorFromChoice({
type: "Choice",
choice,
policyId,
applyAction(chosenNum: ChosenNum) {
if (!inBounds(chosenNum, choice.choose_between)) {
throw new Error("Chosen number is not in bounds");
}
const input = decorateInput({
for_choice_id: choice.for_choice,
input_that_chooses_num: chosenNum,
});
// TODO: Re-check if this env should be the same as the initial env or a new one.
const appliedInput = applyAllInputs(env, state, currentContract, [
input,
]);
// TODO: Improve error handling
if (typeof appliedInput === "string") throw new Error(appliedInput);
return {
inputs: [input],
environment: env,
reducedState: appliedInput.state,
reducedContract: appliedInput.continuation,
warnings: [...previousWarnings, ...appliedInput.warnings],
payments: [...previousPayments, ...appliedInput.payments],
};
},
});
} else {
const notify = cse.case;
if (!evalObservation(env, state, notify.notify_if)) {
return mergeApplicableActionAccumulator.empty;
}

return accumulatorFromNotify({
type: "Notify",
policyId,
applyAction() {
const input = decorateInput("input_notify");
// TODO: Re-check if this env should be the same as the initial env or a new one.
const appliedInput = applyAllInputs(env, state, currentContract, [
input,
]);
// TODO: Improve error handling
if (typeof appliedInput === "string") throw new Error(appliedInput);
return {
inputs: [input],
environment: env,
reducedState: appliedInput.state,
reducedContract: appliedInput.continuation,
warnings: [...previousWarnings, ...appliedInput.warnings],
payments: [...previousPayments, ...appliedInput.payments],
};
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The script output now provides the complete content of the getApplicableActionFromCase function. From the code, we can observe the following:

  1. The function handles different case types: Deposit, Choice, and Notify.
  2. For each case type, there is a corresponding applyAction function that processes the input and applies it to the contract state.
  3. Error handling is present with checks like if (typeof appliedInput === "string") throw new Error(appliedInput); and if (!inBounds(chosenNum, choice.choose_between)) { throw new Error("Chosen number is not in bounds"); }.
  4. There are TODO comments indicating areas for potential improvement, particularly in error handling and environment checks.

Based on the code provided, the function appears to handle all specified case types and includes basic error handling. However, the TODO comments suggest that error handling could be further improved, and the environment handling re-checked.

Given this information, the review comment is partially verified. The function does handle all case types and includes error handling, but there are indications that error handling could be enhanced.

Analysis chain

The getApplicableActionFromCase function is another complex function that involves asynchronous calls and conditional logic based on the type of case. It's crucial to ensure that the function handles all case types correctly and that the error handling is robust, especially since it interacts with external services via restClient.




The getApplicableActionFromCase function exists in the examples/nodejs/src/experimental-features/applicable-inputs.ts file. To further analyze the handling of different case types and the robustness of error handling within the function, we need to inspect the contents of the function.


The provided script output shows the beginning of the getApplicableActionFromCase function and some of its internal logic. However, the output is truncated and does not include the entire function body, which is necessary to fully assess the handling of different case types and the error management within the function.

To complete the verification, we need to see the rest of the function body. Let's execute another script to retrieve the remaining part of the function.


The script output provides additional content from the getApplicableActionFromCase function, but it is still not the complete function. The output is cut off, and we do not have the full context to assess the handling of all case types and the error management within the function.

To ensure a thorough verification, we need to retrieve the rest of the function. Let's execute another script to get the remaining part of the function.


The script failed due to a regex parse error caused by an unclosed character class. The square brackets [ are special characters in regex and need to be escaped to be used as literal characters in the search pattern.

Let's correct the script by escaping the square brackets and attempt to retrieve the rest of the getApplicableActionFromCase function for complete analysis.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that getApplicableActionFromCase function handles all case types and has robust error handling.
ast-grep --lang typescript --pattern $'async function getApplicableActionFromCase($_, $_, $_, $_, $_, $_, $_, $_) {
  $$$
}'

Length of output: 125



Script:

#!/bin/bash
# Search for the function getApplicableActionFromCase without specifying the parameter count
ast-grep --lang typescript --pattern $'async function getApplicableActionFromCase($$$) {
  $$$
}'

Length of output: 98



Script:

#!/bin/bash
# Search for the function getApplicableActionFromCase within TypeScript files
rg --type ts 'async function getApplicableActionFromCase'

Length of output: 166



Script:

#!/bin/bash
# Extract the content of getApplicableActionFromCase function to analyze case handling and error management
rg --type ts --context 30 'async function getApplicableActionFromCase' examples/nodejs/src/experimental-features/applicable-inputs.ts

Length of output: 1958



Script:

#!/bin/bash
# Retrieve the remaining part of the getApplicableActionFromCase function for further analysis
rg --type ts --after-context 40 'cseContinuation = cse.then;' examples/nodejs/src/experimental-features/applicable-inputs.ts

Length of output: 1428



Script:

#!/bin/bash
# Retrieve the rest of the getApplicableActionFromCase function for complete analysis
rg --type ts --after-context 60 'applyAllInputs(env, state, currentContract, [' examples/nodejs/src/experimental-features/applicable-inputs.ts

Length of output: 295



Script:

#!/bin/bash
# Correct the regex and retrieve the rest of the getApplicableActionFromCase function for complete analysis
rg --type ts --after-context 60 'applyAllInputs\(env, state, currentContract, \[' examples/nodejs/src/experimental-features/applicable-inputs.ts

Length of output: 2773

@bjornkihlberg bjornkihlberg force-pushed the plt-8891-add-merkleized-contract-flow-example branch from 5882547 to 0a7e961 Compare January 9, 2024 12:29
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 c7a77c6 and 0a7e961.
Files ignored due to filter (8)
  • changelog.d/scriv.ini
  • examples/nodejs/package-lock.json
  • examples/nodejs/package.json
  • examples/nodejs/src/tsconfig.json
  • examples/nodejs/tsconfig.json
  • packages/adapter/package.json
  • tsconfig-base.json
  • tsconfig.json
Files selected for processing (24)
  • .vscode/settings.json (1 hunks)
  • changelog.d/20240108_165452_hrajchert_plt_8891_add_merkleized_contract_flow_example.md (1 hunks)
  • examples/nodejs/.gitignore (1 hunks)
  • examples/nodejs/src/config.ts (1 hunks)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts (1 hunks)
  • examples/nodejs/src/experimental-features/metadata.ts (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (1 hunks)
  • examples/nodejs/src/wallet-flow.ts (1 hunks)
  • jsdelivr-npm-importmap.js (1 hunks)
  • packages/adapter/src/bigint.ts (1 hunks)
  • packages/adapter/src/time.ts (1 hunks)
  • packages/language/core/v1/src/contract.ts (2 hunks)
  • packages/language/core/v1/src/index.ts (1 hunks)
  • packages/language/core/v1/src/inputs.ts (3 hunks)
  • packages/language/core/v1/src/semantics.ts (11 hunks)
  • packages/language/core/v1/src/transaction.ts (2 hunks)
  • packages/marlowe-object/src/guards.ts (1 hunks)
  • packages/marlowe-object/src/index.ts (1 hunks)
  • packages/marlowe-object/src/object.ts (1 hunks)
  • packages/runtime/client/rest/src/index.ts (3 hunks)
  • packages/runtime/client/rest/src/pagination.ts (2 hunks)
  • packages/runtime/lifecycle/src/api.ts (3 hunks)
  • packages/runtime/lifecycle/src/generic/contracts.ts (4 hunks)
  • packages/runtime/lifecycle/src/generic/runtime.ts (1 hunks)
Files skipped from review as they are similar to previous changes (23)
  • changelog.d/20240108_165452_hrajchert_plt_8891_add_merkleized_contract_flow_example.md
  • examples/nodejs/.gitignore
  • examples/nodejs/src/config.ts
  • examples/nodejs/src/experimental-features/applicable-inputs.ts
  • examples/nodejs/src/experimental-features/metadata.ts
  • examples/nodejs/src/marlowe-object-flow.ts
  • examples/nodejs/src/wallet-flow.ts
  • jsdelivr-npm-importmap.js
  • packages/adapter/src/bigint.ts
  • packages/adapter/src/time.ts
  • packages/language/core/v1/src/contract.ts
  • packages/language/core/v1/src/index.ts
  • packages/language/core/v1/src/inputs.ts
  • packages/language/core/v1/src/semantics.ts
  • packages/language/core/v1/src/transaction.ts
  • packages/marlowe-object/src/guards.ts
  • packages/marlowe-object/src/index.ts
  • packages/marlowe-object/src/object.ts
  • packages/runtime/client/rest/src/index.ts
  • packages/runtime/client/rest/src/pagination.ts
  • packages/runtime/lifecycle/src/api.ts
  • packages/runtime/lifecycle/src/generic/contracts.ts
  • packages/runtime/lifecycle/src/generic/runtime.ts
Additional comments: 1
.vscode/settings.json (1)
  • 3-17: The addition of the debug configuration for JavaScript with specific outFiles settings looks correct. This will help in debugging the TypeScript files that are compiled to JavaScript and placed in the dist directories of various packages. Ensure that all the paths specified match the actual output directories of the compiled JavaScript files.

Copy link
Contributor

@bjornkihlberg bjornkihlberg left a comment

Choose a reason for hiding this comment

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

  • Some code could be cleaned up.
  • Some code might not be formatted.
  • The CI doesn't build.


### General

- Feat: Added debugging configuration for VSCode. Now if you are developing with VSCode you can open the folder as a workspace and the [Javascript Debug Terminal](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal) will have the appropiate source maps. ([PR#136](https://github.com/input-output-hk/marlowe-ts-sdk/pull/136)).
Copy link
Contributor

Choose a reason for hiding this comment

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

Should debugging configuration really be included in the changelog?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

basically so you know this is now a thing (if not you wouldn't even have realized it)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I wasn't able to make it work, if I can have 5 min of your time to do it @hrajchert ... once we have pushed our 2 PR for example...

examples/nodejs/package.json Show resolved Hide resolved
packages/language/core/v1/src/inputs.ts Outdated Show resolved Hide resolved
examples/nodejs/src/marlowe-object-flow.ts Outdated Show resolved Hide resolved
@hrajchert hrajchert force-pushed the plt-8891-add-merkleized-contract-flow-example branch from 0a7e961 to 0185f4e Compare January 9, 2024 16:14
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: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 5b7506f and fd9f7d9.
Files selected for processing (2)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts
Additional comments: 12
examples/nodejs/src/marlowe-object-flow.ts (12)
  • 46-68: The parseCli function is well-implemented with clear help instructions and proper argument parsing.

  • 93-130: The validators for bech32 addresses, positive bigints, and future dates are correctly implemented and provide user-friendly error messages.

  • 138-195: The createContractMenu function provides a clear interactive process for creating a contract, with proper validation and user feedback.

  • 203-231: The loadContractMenu function correctly implements the loading and validation of an existing contract, ensuring it matches the expected tags and contract source.

  • 237-311: The contractMenu function is well-structured, providing an interactive menu for contract interaction with clear options for checking the state and applying actions.

  • 314-348: The mainLoop function provides a clear main menu loop with options to create or load contracts and exit the application, with proper error handling for closed prompts.

  • 352-377: The DelayPaymentScheme interface and related types are well-defined, providing a clear structure for the delayed payment contract.

  • 379-415: The mkDelayPayment function correctly constructs a contract bundle from the given scheme, with clear labeling and structure.

  • 463-515: The printState and getState functions are well-implemented, providing clear logic to print and determine the state of the delay payment contract.

  • 519-561: The mkDelayPaymentTags and extractSchemeFromTags functions are correctly implemented to handle the creation and extraction of tags for the delay payment contract.

  • 594-636: The validateExistingContract function is well-implemented, ensuring that a given contract is an instance of the delay payment contract and that it shares the same sources between different runtimes.

  • 638-658: The main function is well-structured, setting up the environment and starting the main loop of the application with clear configuration and wallet setup.

import { deepEqual } from "@marlowe.io/adapter/deep-equal";

// When this script is called, start with main.
main();
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the previous comment about moving the main() function call, it seems the call is still at the top of the file. This should be addressed to prevent unexpected behavior when the module is imported elsewhere.

- main();
+ if (require.main === module) {
+   main();
+ }

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
main();
if (require.main === module) {
main();
}

Comment on lines +563 to +590
async function createContract(
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
const contractSources =
await lifecycle.restClient.createContractSources(contractBundle);
const walletAddress = await lifecycle.wallet.getChangeAddress();
const unsignedTx = await lifecycle.restClient.buildCreateContractTx({
sourceId: contractSources.contractSourceId,
tags,
changeAddress: walletAddress,
stakeAddress: rewardAddress,
minimumLovelaceUTxODeposit: 3_000_000,
version: "v1",
});
const signedCborHex = await lifecycle.wallet.signTx(unsignedTx.tx.cborHex);
await lifecycle.restClient.submitContract(
unsignedTx.contractId,
transactionWitnessSetTextEnvelope(signedCborHex)
);
const txId = contractIdToTxId(unsignedTx.contractId);
return [unsignedTx.contractId, txId];
//----------------
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The createContract function appears to be correctly implemented for creating the contract on the blockchain. However, there is a TODO comment indicating a pending task that should be addressed.

// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)

Please ensure that the task referenced by PLT-9089 is tracked and prioritized appropriately.


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
async function createContract(
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
const contractSources =
await lifecycle.restClient.createContractSources(contractBundle);
const walletAddress = await lifecycle.wallet.getChangeAddress();
const unsignedTx = await lifecycle.restClient.buildCreateContractTx({
sourceId: contractSources.contractSourceId,
tags,
changeAddress: walletAddress,
stakeAddress: rewardAddress,
minimumLovelaceUTxODeposit: 3_000_000,
version: "v1",
});
const signedCborHex = await lifecycle.wallet.signTx(unsignedTx.tx.cborHex);
await lifecycle.restClient.submitContract(
unsignedTx.contractId,
transactionWitnessSetTextEnvelope(signedCborHex)
);
const txId = contractIdToTxId(unsignedTx.contractId);
return [unsignedTx.contractId, txId];
//----------------
}
async function createContract(
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
const contractSources =
await lifecycle.restClient.createContractSources(contractBundle);
const walletAddress = await lifecycle.wallet.getChangeAddress();
const unsignedTx = await lifecycle.restClient.buildCreateContractTx({
sourceId: contractSources.contractSourceId,
tags,
changeAddress: walletAddress,
stakeAddress: rewardAddress,
minimumLovelaceUTxODeposit: 3_000_000,
version: "v1",
});
const signedCborHex = await lifecycle.wallet.signTx(unsignedTx.tx.cborHex);
await lifecycle.restClient.submitContract(
unsignedTx.contractId,
transactionWitnessSetTextEnvelope(signedCborHex)
);
const txId = contractIdToTxId(unsignedTx.contractId);
return [unsignedTx.contractId, txId];
//----------------
}

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 fd9f7d9 and c2480fa.
Files selected for processing (3)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts (1 hunks)
  • packages/language/core/v1/src/index.ts (2 hunks)
  • packages/language/core/v1/src/semantics.ts (13 hunks)
Files skipped from review as they are similar to previous changes (3)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts
  • packages/language/core/v1/src/index.ts
  • packages/language/core/v1/src/semantics.ts

@@ -19,8 +19,8 @@ const configGuard = t.type({

export type Config = t.TypeOf<typeof configGuard>;

Copy link
Collaborator

Choose a reason for hiding this comment

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

the documentation in the README is the following :

{
  // We use blockfrost for the lucid wallet, you can create a projectId here
  // https://blockfrost.io/
  "blockfrostProjectId": "YOUR_PROJECT_ID",
  "blockfrostUrl": "https://cardano-preprod.blockfrost.io/api/v0",
  "blockfrostNetwork": "Preprod",
  // You can create this seed phrase from any wallet. Do not reuse a real wallet phrase
  // for a test example.
  "seedPhrase": "alpha beta delta...",
  "runtimeURL": "<url to a runtime instance>"
}

and the associated guard is :

const configGuard = t.type({
  blockfrostProjectId: t.string,
  blockfrostUrl: t.string,
  network: lucidNetworkGuard,
  seedPhrase: t.string,
  runtimeURL: t.string,
});

the documentation is saying blockfrostNetwork instead of network, there is a mismatch

@@ -1,3 +1,18 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"debug.javascript.terminalOptions": {
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

I have added this issue : #162 , because I'm not able to attach the debugger ti VSCode since we moved to packages, even with this PR, it doesn't work in my environment.

### General

- Feat: Added debugging configuration for VSCode. Now if you are developing with VSCode you can open the folder as a workspace and the [Javascript Debug Terminal](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_javascript-debug-terminal) will have the appropiate source maps. ([PR#136](https://github.com/input-output-hk/marlowe-ts-sdk/pull/136)).
- Feat: Started an experimental getApplicableActions that should replace the current getApplicableInputs. ([PR#136](https://github.com/input-output-hk/marlowe-ts-sdk/pull/136))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we officially using a convention for the change log ? like Feat, Breaking Change etc ? If yes it should be documented I guess, do we have some documentation around ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we discussed this together with you and @bjornkihlberg.

But yes, we should/could document the structure

* What is the new state after applying an action and reducing until quiescent
*/
reducedState: MarloweState;
/**
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

what about this instead ?

export interface AppliedActionResult {
  environment: Environment;
  inputs: Input[];
  outputs : {
     warnings: TransactionWarning[];
     state: MarloweState;
     contract: Contract;
     payments: Payment[];
  }
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

could be... I do want to start a discussion regarding this type (also why is on a separate experimental folder for the moment).

I also don't like to have the applyAction method as part of the applicableActions


interface CanNotify {
type: "Notify";
policyId: PolicyId;
Copy link
Collaborator

Choose a reason for hiding this comment

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

what policyId are we talking about here ? I don't understand

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The policyId of a created contract. I need this for filtering my actions. I need to hide this away somehow

const timeInterval = { from: now, to: nextTimeout - 1n };
const env = environment ?? { timeInterval };
if (typeof contractDetails.state === "undefined") {
// TODO: Check, I believe this happens when a contract is in a closed state, but it would be nice
Copy link
Collaborator

Choose a reason for hiding this comment

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

When currentContract and state are undefined

} /** Only available when the contract is Unsigned/Submitted and Active */ & {
  currentContract?: Contract;
  state?: MarloweState;
} /** Only available when Active */ & {
  utxo?: TxOutRef;
} /** Only available when the contract is confirmed */ & {
  block?: BlockHeader;
} /** Only available When the contract is Unsigned/Submitted */ & {
  txBody?: TextEnvelope;
};

? [
{
type: "Advance",
policyId: contractDetails.roleTokenMintingPolicyId,
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

@hrajchert , I don't understand why contractDetails.roleTokenMintingPolicyId is not optional. can't we use a contract without role tokens ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't make that a required field, it was defined required so I use it. If the contract doesn't have role tokens this part of the code should not even matter

environment?: Environment
): Promise<ApplicableAction[]> {
const contractDetails = await restClient.getContractById(contractId);
const currentContract = contractDetails.currentContract
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

what does it means when it is closed ? currentContract == undefined but we want it to be the initial one ? why ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I need to revisit this as part of PLT-9054. But you can use this as an example of the confusing API of get contract details discussion

const nextTimeout = getNextTimeout(currentContract, now) ?? oneDayFrom(now);
const timeInterval = { from: now, to: nextTimeout - 1n };
const env = environment ?? { timeInterval };
if (typeof contractDetails.state === "undefined") {
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

this check should be the first line of the function to avoid having variable above in an unpredictable state.
N.B : If we type the states of the contracts, these pieces of code could be removed

function getTokenMap() {
if (tokenMap.size === 0 && tokens.length > 0) {
tokens.forEach((token) => {
// Role tokens only have 1 element
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

Not always I believe, Roles Tokens could have a quantity > 1
e.g : for the raffle we have duplicated the role tokens in case of the winners lost their tokens...

Should we filter out the Ada ? Same issue as there : #153

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

probably, I'll do it as part of PLT-9054

restClient: RestClient,
env: Environment,
currentContract: Contract,
state: MarloweState,
Copy link
Collaborator

Choose a reason for hiding this comment

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

state -> currentState , there is an hidden concept [Contract,State] imo

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, I tried to name that concept in the past with no luck

state: MarloweState,
previousPayments: Payment[],
previousWarnings: TransactionWarning[],
cse: Case,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I bugged on cse... could it be something more aCase ?
and cseContinuation -> aCaseContinuation

@@ -0,0 +1,8 @@
import { Address } from "@marlowe.io/language-core-v1";
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand the link between metadata and splitting an address in 2

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

const tag = "DELAY_PYMNT-1";
const tags = {} as Tags;

tags[`${tag}-from-0`] = splitAddress(schema.payFrom)[0];
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

if your intent is to store only metadata more than tagging you can simply create a tag like that :

{ "myDappId"  :{ schema : schema}}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This can (and will) be improved of course. There is one discussion I want to have about TemplateParameters/Scheme to add a DSL to define it and to be able to share it inside the metadata of contract creation.

I'm abusing tags here really, Ideally I should have a single tag DELAY_PAYMENT: v1, and then have all the scheme encoded efficiently inside the metadata.

The tag should only help me easily find the type of contracts that I want.

Encoding the scheme in the metadata is a simple way to share the contract sources between unconnected runtime instances. The approach you suggest doesn't cope with the max size of the value. Note that I even have to split the address in half to cope with this limitation.

@@ -17,7 +17,7 @@ export type ItemRange = t.TypeOf<typeof ItemRangeGuard>;
export const itemRanged = (s: string) => unsafeEither(ItemRangeGuard.decode(s));

export interface Page {
current: ItemRange;
current?: ItemRange;
Copy link
Collaborator

@nhenin nhenin Jan 10, 2024

Choose a reason for hiding this comment

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

in which case we don't have current ? when there is only 1 page ? the semantic is a bit strange, we could consider than there is always a currentPage and handle that case....

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

when there arent any transactions for example.

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.

A couple of minor changes...

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: 5

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between c2480fa and cc1b883.
Files selected for processing (3)
  • examples/nodejs/Readme.md (1 hunks)
  • examples/nodejs/src/experimental-features/applicable-inputs.ts (1 hunks)
  • examples/nodejs/src/marlowe-object-flow.ts (1 hunks)
Files skipped from review due to trivial changes (1)
  • examples/nodejs/Readme.md
Additional comments: 3
examples/nodejs/src/experimental-features/applicable-inputs.ts (3)
  • 193-220: The mkPartyFilter function has been updated with a caching mechanism for tokens. Ensure that this optimization does not introduce any unintended side effects and that it is tested thoroughly.

  • 120-190: The getApplicableActions function has been updated. Review the logic to ensure that it correctly handles the various states of a contract and returns the appropriate actions. Pay special attention to error handling and the use of reduceContractUntilQuiescent.

  • 384-511: The getApplicableActionFromCase function has been updated. Ensure that the logic for handling different case types (Deposit, Choice, Notify) is correct and that the error handling is robust.

Comment on lines +42 to +43
// When this script is called, start with main.
main();
Copy link
Contributor

Choose a reason for hiding this comment

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

The previous comment about moving the main() function call to prevent unexpected behavior when the module is imported elsewhere has not been addressed. This should be fixed to ensure that main() is only called when the script is run directly.

- main();
+ if (require.main === module) {
+   main();
+ }

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
// When this script is called, start with main.
main();
// When this script is called, start with main.
if (require.main === module) {
main();
}

Comment on lines +563 to +570
async function createContract(
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
Copy link
Contributor

Choose a reason for hiding this comment

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

The createContract function has a TODO comment about modifying runtimeLifecycle.contracts.createContract to support a bundle. Ensure that the task referenced by PLT-9089 is tracked and prioritized appropriately.

Comment on lines +1 to +663
lifecycle: RuntimeLifecycle,
schema: DelayPaymentScheme,
rewardAddress?: StakeAddressBech32
): Promise<[ContractId, TxId]> {
const contractBundle = mkDelayPayment(schema);
const tags = mkDelayPaymentTags(schema);
// TODO: PLT-9089: Modify runtimeLifecycle.contracts.createContract to support bundle (calling createContractSources)
const contractSources =
await lifecycle.restClient.createContractSources(contractBundle);
const walletAddress = await lifecycle.wallet.getChangeAddress();
const unsignedTx = await lifecycle.restClient.buildCreateContractTx({
sourceId: contractSources.contractSourceId,
tags,
changeAddress: walletAddress,
stakeAddress: rewardAddress,
minimumLovelaceUTxODeposit: 3_000_000,
version: "v1",
});
const signedCborHex = await lifecycle.wallet.signTx(unsignedTx.tx.cborHex);
await lifecycle.restClient.submitContract(
unsignedTx.contractId,
transactionWitnessSetTextEnvelope(signedCborHex)
);
const txId = contractIdToTxId(unsignedTx.contractId);
return [unsignedTx.contractId, txId];
//----------------
}

type ValidationResults = "InvalidTags" | "InvalidContract" | DelayPaymentScheme;

/**
* This function checks if the contract with the given id is an instance of the delay payment contract
* @param lifecycle
* @param contractId
* @returns
*/
async function validateExistingContract(
lifecycle: RuntimeLifecycle,
contractId: ContractId
): Promise<ValidationResults> {
// First we try to fetch the contract details and the required tags
const contractDetails =
await lifecycle.restClient.getContractById(contractId);

const scheme = extractSchemeFromTags(contractDetails.tags);

if (!scheme) {
return "InvalidTags";
}

// If the contract seems to be an instance of the contract we want (meanin, we were able
// to retrieve the contract scheme) we check that the actual initial contract has the same
// sources.
// This has 2 purposes:
// 1. Make sure we are interacting with the expected contract
// 2. Share the same sources between different Runtimes.
// When a contract source is uploaded to the runtime, it merkleizes the source code,
// but it doesn't share those sources with other runtime instances. One option would be
// to download the sources from the initial runtime and share those with another runtime.
// Or this option which doesn't require runtime to runtime communication, and just requires
// the dapp to be able to recreate the same sources.
const contractBundle = mkDelayPayment(scheme);
const { contractSourceId } =
await lifecycle.restClient.createContractSources(contractBundle);
const initialContract = await lifecycle.restClient.getContractSourceById({
contractSourceId,
});

if (!deepEqual(initialContract, contractDetails.initialContract)) {
return "InvalidContract";
}
return scheme;
}

async function main() {
const args = parseCli();
const config = await readConfig(args["--config"]);
const lucid = await Lucid.new(
new Blockfrost(config.blockfrostUrl, config.blockfrostProjectId),
config.network
);
lucid.selectWalletFromSeed(config.seedPhrase);
const rewardAddressStr = await lucid.wallet.rewardAddress();
const rewardAddress = rewardAddressStr
? stakeAddressBech32(rewardAddressStr)
: undefined;
const runtimeURL = config.runtimeURL;

const wallet = mkLucidWallet(lucid);

const lifecycle = mkRuntimeLifecycle({
runtimeURL,
wallet,
});
try {
await mainLoop(lifecycle, rewardAddress);
} catch (e) {
console.log(`Error : ${JSON.stringify(e, null, 4)}`);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Overall, the script is well-structured and follows the interactive CLI pattern using inquirer.js. The utility functions are modular and maintainable. Ensure that the TODO comment in the createContract function is addressed and that the main() function is only called when the script is run directly.

Comment on lines +3 to +4
TODO: After PR-8891 and as part of PLT-9054 move these to the runtime-lifecycle package
and create a github discussion to modify the backend.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please ensure that the TODO task mentioned here is tracked in the project's issue tracking system to avoid it being overlooked.

Comment on lines +231 to +232
function isDepositAction(action: Action): action is Deposit {
return "party" in action;
Copy link
Contributor

Choose a reason for hiding this comment

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

The isDepositAction function may incorrectly identify an action as a Deposit based solely on the presence of a party property. Consider refining the type guard to accurately distinguish Deposit actions.

@hrajchert hrajchert merged commit a3b673f into main Jan 10, 2024
2 checks passed
@hrajchert hrajchert deleted the plt-8891-add-merkleized-contract-flow-example branch January 10, 2024 16:00
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.

PLT-8891: Add marlowe-object contract flow example
3 participants