Skip to content

Commit

Permalink
account Deposit Integration (runtime V1.0.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
nhenin committed May 6, 2024
1 parent 0837b00 commit a90f886
Show file tree
Hide file tree
Showing 27 changed files with 366 additions and 634 deletions.
5 changes: 5 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ Prototypes have been also built on top of this sdk:
To report a bug or request a new feature, please look through existing [Github Issues](https://github.com/input-output-hk/marlowe-ts-sdk/issues) before opening a new one.

To help in the development of this SDK, please refer to [this document](./doc/howToDevelop.md).





10 changes: 5 additions & 5 deletions examples/nodejs/src/marlowe-object-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,14 @@ async function contractMenu(

const choices: Array<{
name: string;
value: CanDeposit | CanAdvance | { actionType: "check-state" } | { actionType: "return" };
value: CanDeposit | CanAdvance | { type: "check-state" } | { type: "return" };
}> = [
{
name: "Re-check contract state",
value: { actionType: "check-state" },
value: { type: "check-state" },
},
...applicableActions.myActions.map((action) => {
switch (action.actionType) {
switch (action.type) {
case "Advance":
return {
name: "Close contract",
Expand All @@ -269,15 +269,15 @@ async function contractMenu(
}),
{
name: "Return to main menu",
value: { actionType: "return" },
value: { type: "return" },
},
];

const selectedAction = await select({
message: "Contract menu",
choices,
});
switch (selectedAction.actionType) {
switch (selectedAction.type) {
case "check-state":
return contractMenu(wallet, contractInstance, scheme, sourceMap);
case "return":
Expand Down
2 changes: 0 additions & 2 deletions jsdelivr-npm-importmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.4.0-beta-rc1/dist/bundled/esm/version.js",
"@marlowe.io/language-examples":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/language-examples.js",
"@marlowe.io/language-examples/atomic-swap":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/atomic-swap.js",
"@marlowe.io/language-specification-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.4.0-beta-rc1/dist/bundled/esm/language-specification-client.js",
"@marlowe.io/token-metadata-client":
Expand Down
3 changes: 0 additions & 3 deletions packages/adapter/src/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ export const waitForPredicatePromise = async (
// Predicate is already true, no need to wait
return;
}
// Use a promise to wait for the specified interval
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Wait for the specified interval
await sleep(seconds);

Expand Down
2 changes: 1 addition & 1 deletion packages/language/core/v1/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export {
MerkleizedNotify,
} from "./inputs.js";

export { role, Party, Address, Role, RoleName } from "./participants.js";
export { role, Party, Address, Role, RoleName, partiesToStrings, partyToString } from "./participants.js";

export { Payee, PayeeAccount, PayeeParty, AccountId } from "./payee.js";

Expand Down
12 changes: 8 additions & 4 deletions packages/language/core/v1/src/semantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,11 @@ export function emptyState(minTime: POSIXTime): MarloweState {
* @returns The resulting state and contract, along with the accumulated warnings and payments or a {@link TransactionError}.
* @category Evaluation
*/
export function playTrace(initialTime: POSIXTime, contract: Contract, transactions: Transaction[]): TransactionOutput {
export function playTrace(
initialState: MarloweState,
contract: Contract,
transactions: Transaction[]
): TransactionOutput {
function go(prev: TransactionOutput, txs: Transaction[]): TransactionOutput {
if (txs.length === 0) return prev;
if (!G.TransactionSuccess.is(prev)) return prev;
Expand All @@ -855,19 +859,19 @@ export function playTrace(initialTime: POSIXTime, contract: Contract, transactio
warnings: [],
payments: [],
contract,
state: emptyState(initialTime),
state: initialState,
},
transactions
);
}

export function playSingleInputTxTrace(initialTime: POSIXTime, contract: Contract, transactions: SingleInputTx[]) {
export function playSingleInputTxTrace(initialState: MarloweState, contract: Contract, transactions: SingleInputTx[]) {
const txs = transactions.map((tx) => {
const tx_inputs = typeof tx.input === "undefined" ? [] : [tx.input];
return {
tx_interval: tx.interval,
tx_inputs,
};
});
return playTrace(initialTime, contract, txs);
return playTrace(initialState, contract, txs);
}
124 changes: 31 additions & 93 deletions packages/language/examples/src/atomicSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* are timeboxed. Sellers are known at the contract creation (fixed Address) and Buyers are unknown
* (This showcases a feature of marlowe that is called Open Roles.).
* There are 3 main stages :
* - The Offer : The Sellers deposit their tokens.
* - The Offer : The Sellers create the contract and deposit their tokens at the same time via the initial account deposits feature.
* - The Ask : The Buyers deposit their tokens
* - The Swap Confirmation : an extra Notify input is added after the swap to avoid double-satisfaction attack (see link attached).
* (Any third participant could perform this action)
Expand Down Expand Up @@ -54,19 +54,18 @@
* @packageDocumentation
*/

import { POSIXTime } from "@marlowe.io/adapter/time";
import {
Address,
Contract,
IChoice,
IDeposit,
INotify,
Input,
MarloweState,
Role,
Timeout,
TokenValue,
close,
datetoTimeout,
} from "@marlowe.io/language-core-v1";
import * as G from "@marlowe.io/language-core-v1/guards";
import { SingleInputTx } from "@marlowe.io/language-core-v1/semantics";
Expand Down Expand Up @@ -96,22 +95,7 @@ export type Scheme = {
/* #region State */
export type State = ActiveState | Closed;

export type ActiveState = WaitingSellerOffer | NoSellerOfferInTime | WaitingForAnswer | WaitingForSwapConfirmation;

export type WaitingSellerOffer = {
type: "WaitingSellerOffer";
};

export const waitingSellerOffer: WaitingSellerOffer = {
type: "WaitingSellerOffer",
};

export type NoSellerOfferInTime = {
type: "NoSellerOfferInTime";
};
export const noSellerOfferInTime: NoSellerOfferInTime = {
type: "NoSellerOfferInTime",
};
export type ActiveState = WaitingForAnswer | WaitingForSwapConfirmation;

export type WaitingForAnswer = {
type: "WaitingForAnswer";
Expand Down Expand Up @@ -154,10 +138,6 @@ export type Closed = {
* Action List available for the contract lifecycle.
*/
export type ApplicableAction =
/* When Contract Created (timed out > NoOfferProvisionnedOnTime) */
| ProvisionOffer // > OfferProvisionned
/* When NoOfferProvisionnedOnTime (timed out > no timeout (need to be reduced to be closed))*/
| RetrieveMinimumLovelaceAdded // > closed
/* When OfferProvisionned (timed out > NotConfirmedOnTime) */
| Retract // > closed
| Swap // > Swapped
Expand All @@ -166,17 +146,6 @@ export type ApplicableAction =

export type ActionParticipant = "buyer" | "seller" | "anybody";

export type RetrieveMinimumLovelaceAdded = {
type: "RetrieveMinimumLovelaceAdded";
owner: ActionParticipant;
};

export type ProvisionOffer = {
type: "ProvisionOffer";
owner: ActionParticipant;
input: IDeposit;
};

export type Swap = {
type: "Swap";
owner: ActionParticipant;
Expand All @@ -198,16 +167,8 @@ export type Retract = {
/* #endregion */

/* #region Close Reason */
export type CloseReason =
| NoOfferProvisionnedOnTime
| SellerRetracted
| NotAnsweredOnTime
| Swapped
| SwappedButNotNotifiedOnTime;
export type CloseReason = SellerRetracted | NotAnsweredOnTime | Swapped | SwappedButNotNotifiedOnTime;

export type NoOfferProvisionnedOnTime = {
type: "NoOfferProvisionnedOnTime";
};
export type SellerRetracted = { type: "SellerRetracted" };
export type NotAnsweredOnTime = { type: "NotAnsweredOnTime" };
export type SwappedButNotNotifiedOnTime = {
Expand Down Expand Up @@ -243,26 +204,6 @@ export class UnexpectedClosedSwapContractState extends Error {

export const getApplicableActions = (scheme: Scheme, state: ActiveState): ApplicableAction[] => {
switch (state.type) {
case "WaitingSellerOffer":
return [
{
type: "ProvisionOffer",
owner: "seller",
input: {
input_from_party: scheme.offer.seller,
that_deposits: scheme.offer.asset.amount,
of_token: scheme.offer.asset.token,
into_account: scheme.offer.seller,
},
},
];
case "NoSellerOfferInTime":
return [
{
type: "RetrieveMinimumLovelaceAdded",
owner: "anybody",
},
];
case "WaitingForAnswer":
return [
{
Expand Down Expand Up @@ -306,37 +247,32 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
switch (inputHistory.length) {
// Offer Provision Deadline has passed and there is one reduced applied to close the contract
case 0:
return {
type: "Closed",
reason: { type: "NoOfferProvisionnedOnTime" },
};
case 1:
return {
type: "Closed",
reason: { type: "NotAnsweredOnTime" },
};
case 2: {
case 1: {
const isRetracted =
G.IChoice.is(inputHistory[1].input) && inputHistory[1].input.for_choice_id.choice_name == "retract";
G.IChoice.is(inputHistory[0].input) && inputHistory[0].input.for_choice_id.choice_name == "retract";
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
if (isRetracted && nbDeposits === 1) {
if (isRetracted) {
return {
type: "Closed",
reason: { type: "SellerRetracted" },
};
}
if (nbDeposits === 2) {
if (nbDeposits === 1) {
return {
type: "Closed",
reason: { type: "SwappedButNotNotifiedOnTime" },
};
}
break;
}
case 3: {
case 2: {
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
const nbNotify = inputHistory.filter((singleInputTx) => G.INotify.is(singleInputTx.input)).length;
if (nbDeposits === 2 && nbNotify === 1) {
if (nbDeposits === 1 && nbNotify === 1) {
return {
type: "Closed",
reason: { type: "Swapped" },
Expand All @@ -355,15 +291,13 @@ export const getActiveState = (
): ActiveState => {
switch (inputHistory.length) {
case 0:
return now < scheme.offer.deadline ? { type: "WaitingSellerOffer" } : { type: "NoSellerOfferInTime" };
case 1:
if (now < scheme.ask.deadline) {
return { type: "WaitingForAnswer" };
}
break;
case 2: {
case 1: {
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
if (nbDeposits === 2 && now < scheme.swapConfirmation.deadline) {
if (nbDeposits === 1 && now < scheme.swapConfirmation.deadline) {
return { type: "WaitingForSwapConfirmation" };
}
break;
Expand All @@ -373,22 +307,26 @@ export const getActiveState = (
throw new UnexpectedActiveSwapContractState(scheme, inputHistory, state);
};

export function mkContract(scheme: Scheme): Contract {
const mkOffer = (ask: Contract): Contract => {
const depositOffer = {
party: scheme.offer.seller,
deposits: scheme.offer.asset.amount,
of_token: scheme.offer.asset.token,
into_account: scheme.offer.seller,
};

return {
when: [{ case: depositOffer, then: ask }],
timeout: scheme.offer.deadline,
timeout_continuation: close,
};
/**
* Generate the initial state of the contract from a given scheme and start date.
* @param startDate : the start date of the contract creation
* @param scheme : the scheme of the contract
* @returns
*/
export const mkInitialMarloweState = (startDate: POSIXTime, scheme: Scheme): MarloweState => {
return {
accounts: [[[scheme.offer.seller, scheme.offer.asset.token], scheme.offer.asset.amount]],
boundValues: [],
choices: [],
minTime: startDate,
};
};

/**
* Generate the contract from the scheme.
* N.B : The offer asset is provisioned at the contract creation via initial account deposits.
*/
export function mkContract(scheme: Scheme): Contract {
const mkAsk = (confirmSwap: Contract): Contract => {
const depositAsk = {
party: scheme.ask.buyer,
Expand Down Expand Up @@ -450,5 +388,5 @@ export function mkContract(scheme: Scheme): Contract {
};
};

return mkOffer(mkAsk(mkSwapConfirmation()));
return mkAsk(mkSwapConfirmation());
}

0 comments on commit a90f886

Please sign in to comment.