On this page:
4.3.1 Problem Analysis
4.3.2 Data Definition
4.3.3 Communication Construction
4.3.4 Assertion Insertion
4.3.5 Interaction Introduction
4.3.6 Deployment Decisions
4.3.7 Discussion and Next Steps
0.1.2

4.3 Workshop: Trust Fund

In this workshop, we’ll look at yet another strategy for transferring funds, but in this version, we’ll think about it as establishing a "trust fund": a funder will establish an account for the receiver, which they must wait a certain amount of time to access, and if they do not, then it reverts to the funder, and if the funder does not claim it, then it is dormant and any third party can remove the funds. You could think of this as a variant of the relay account, with a mandatory waiting period and two fallbacks on non-participation.

This workshop assumes that you have recently completed Workshop: Relay Account.

We assume that you’ll go through this workshop in a directory named ~/reach/workshop-trust-fund:
  $ mkdir -p ~/reach/workshop-trust-fund

And that you have a copy of Reach installed in ~/reach so you can write
  $ ../reach version

And it will run Reach.

You should start off by initializing your Reach program:
  $ ../reach init

4.3.1 Problem Analysis

For this workshop, we’ll provide some constraints on your solution and problem analysis, since we’d like you to explore writing a Reach program with a specific design.

The overall purpose of this application is so that:
  • The Funder must decide an amount of funds to provide, as well as all of the other parameters of the application.

  • The Funder will know the identity of the Receiver at the beginning.

  • Whomever ever ultimately receives the funds transfers it to themselves.

With this in mind, let’s answer the questions:
  • What are the participants of the application?

  • What information do they know at the start of the program?

  • What information are they going to discover and use in the program?

  • What funds change ownership during the application and how?




Stop!
Write down the problem analysis of this program as a comment.



Let’s see how your answers compare to our answers:

4.3.2 Data Definition

The next step of designing our program is representing this information in our program and deciding the participant interact interface for each participant. In this application, we’ll be using a new concept of Reach: the time delta. The trust fund has a "maturity", as well as the lengths of time before which the fund is forsook or abandoned. In the fiat world, these would likely be expressed as real time durations, like months and years. However, on most consensus networks there is an abstraction of time into something like a "block height", which represents the number of rounds of consensus which have reached their conclusion. There is a loose relationship of these notions to real-time, but most networks do not guarantee any particular connection. (Indeed, such a connection between the abstract world of consensus networks and the "real" world is typically provided by an oracle.) Reach abstracts the details of particular consensus networks away into the concept of a time delta, which is represented by an integer in Reach programs, and used in positions that reference time.

With that knowledge in hand,




Stop!
Write down the data definitions for this program as definitions.



Let’s compare notes again. Here’s what we wrote in our program:

 1    'reach 0.1';
 2    
 3    const common = {
 4      funded: Fun([], Null),
 5      ready : Fun([], Null),
 6      recvd : Fun([UInt256], Null) };
 7    
 8    export const main =
 9      Reach.App(
10        { deployMode: 'firstMsg' },
11        [ [   'Funder', {
12    ...common,
13    getParams: Fun([], Object({
14      receiverAddr: Address,
15      payment:      UInt256,
16      maturity:     UInt256,
17      refund:       UInt256,
18      dormant:      UInt256 })) }],
19    [ 'Receiver', common],
20    ['Bystander', common] ],
21        (Funder, Receiver, Bystander) => {
..    // ...

We’ve represented most values as UInt256 fields, and created a "common" interface that has a series of signals for the different phases of the application: one for when the account is funded, one for when the particular participant is ready to extract the funds, and finally one for when they have successfuly recvd (received) them.

4.3.3 Communication Construction

Now, we can write down the structure of communication and action in our application. Try this on your own based on your experience with Workshop: Trust Fund.




Stop!
Write down the communication pattern for this program as comments.



Here’s what we wrote:

// 1. The Funder publishes the parameters of the fund and makes the initial deposit.
// 2. The consensus remembers who the Receiver is.
// 3. Everyone waits for the fund to mature.
// 4. The Receiver may extract the funds with a deadline of `refund`.
// 5. The Funder may extract the funds with a deadline of `dormant`.
// 6. The Bystander may extract the funds with no deadline.

The next step is to convert this pattern into actual program code using publish, pay, and commit. However, this program gives us the opportunity to look at a few more features of Reach.

First, how do we implement step three, where each party waits for the fund to mature? Reach has a primitive named wait which causes this to happen. This may only occur in a step, which is the same context where publish may occur. This primitive, however, doesn’t just cause the participants to wait, instead it guarantees that the entire computation waits. In other words, this means that the contract will ensure that the later steps do not occur until after the waiting time.

Second, how do we implement steps four and five, where there is a deadline for an action to take place? Reach publication steps take an option called .timeout that specifies an alternative computation to occur if the first does not take place before the deadline. The syntax looks like: publish().timeout(deadline, () => alternative), which uses the arrow expression syntax for specifying the alternative computation.

Finally, we hope you notice that steps four, five, and six are extremely similar. Consider trying to write a function that is used three times to implement all of them!




Stop!
Write down the communication pattern for this program as code.



The body of your application should look something like:

// 1. The Funder publishes the parameters of the fund and makes the initial deposit.
Funder.publish(receiverAddr, payment, maturity, refund, dormant )
  .pay(payment);

// 2. The consensus remembers who the Receiver is.
Receiver.set(receiverAddr);
commit();

// 3. Everyone waits for the fund to mature.
wait(maturity);

// 4. The Receiver may extract the funds with a deadline of `refund`.
Receiver.publish()
  .timeout(refund,
    () => {
     // 5. The Funder may extract the funds with a deadline of `dormant`.
      Funder.publish()
        .timeout(dormant,
          () => {
            // 6. The Bystander may extract the funds with no deadline.
            Bystander.publish();
            transfer(payment).to(Bystander);
            commit();
            exit(); });
       transfer(payment).to(Funder);
       commit();
       exit(); });
transfer(payment).to(Receiver);
commit();
exit();

If you’d like to see how you might contain the repetition into a function, keep reading!

4.3.4 Assertion Insertion

As usual, we should consider what assertions we can add to our program, but this program doesn’t have any interesting properties to prove, so we’ll move on. Or rather, all of its interesting properties are the ones automatically included in all Reach programs, like that the funds are used linearly and nothing is left over in the account at the end, or that the protocol steps must be received before the corresponding deadlines.

4.3.5 Interaction Introduction

Next, we need to insert the appropriate calls to interact. In this case, our program is very simple and we expect you’ll do a great job without further discussion. However, if you want to simplify things, you might like to use each to signal to all the parties that the account is funded, rather than duplicating the interaction code over and over.




Stop!
Insert interact calls to the frontend into the program.



Let’s look at our whole program now:

 1    'reach 0.1';
 2    
 3    const common = {
 4      funded: Fun([], Null),
 5      ready : Fun([], Null),
 6      recvd : Fun([UInt256], Null) };
 7    
 8    export const main =
 9      Reach.App(
10        { deployMode: 'firstMsg' },
11        [ [   'Funder', {
12          ...common,
13          getParams: Fun([], Object({
14            receiverAddr: Address,
15            payment:      UInt256,
16            maturity:     UInt256,
17            refund:       UInt256,
18            dormant:      UInt256 })) }],
19          [ 'Receiver', common],
20          ['Bystander', common] ],
21        (Funder, Receiver, Bystander) => {
22          Funder.only(() => {
23            const { receiverAddr,
24                    payment, maturity, refund, dormant }
25                  = declassify(interact.getParams()); });
26          Funder.publish(
27            receiverAddr,
28            payment, maturity, refund, dormant )
29            .pay(payment);
30          Receiver.set(receiverAddr);
31          commit();
32    
33          each([Funder, Receiver, Bystander], () => {
34            interact.funded(); });
35          wait(maturity);
36    
37          const giveChance = (Who, then) => {
38            Who.only(() => interact.ready());
39    
40            if ( then ) {
41              Who.publish()
42                .timeout(then.deadline, () => then.after()); }
43            else {
44              Who.publish(); }
45    
46            transfer(payment).to(Who);
47            commit();
48            Who.only(() => interact.recvd(payment));
49            exit(); };
50    
51          giveChance(
52            Receiver,
53            { deadline: refund,
54              after: () =>
55              giveChance(
56                Funder,
57                { deadline: dormant,
58                  after: () =>
59                  giveChance(Bystander, false) }) }); } );

This program demonstrates some of the remarkable features of Reach: we were able to abstract away a pattern of communication into a function and use it repeatedly and in different ways. Behind the scenes, when Reach compiles this program into a contract, it will derive a four step protocol with implicit state to check that the appropriate participant takes their action only when allowed.

4.3.6 Deployment Decisions

Next, it is time to test our program. As usual, we’ll present a completely automated test deployment, rather than an interactive one. This means that we’ll have to have our participants purposefully "miss" their deadlines so we can see that the timeouts and deadlines work correctly. We’ll implement it by abstracting away the test into a function of two parameters: booleans that decide whether the Receiver and Funder (respectively) should miss their deadline. We’ll implement this miss by using the standard library function stdlib.wait which takes a time delta encoded as a number. This function is like wait, except it is local only to a single participant and has no bearing on the rules of the application. It’s just a convenience mechanism for allowing time to pass on the consensus network.

We highly recommend that you try to implement a test setup like this yourself; when you’re done, scroll down to see our solution.




Stop!
Decide how you will deploy and use this application.



Here’s the JavaScript frontend we wrote:

  1    import * as stdlib_loader from '@reach-sh/stdlib/loader.mjs';
  2    import * as backend from './build/index.main.mjs';
  3    
  4    const runDemo = async (delayReceiver, delayFunder) => {
  5      const stdlib = await stdlib_loader.loadStdlib();
  6      const connector = stdlib_loader.getConnector();
  7      const toCurrency =
  8            connector == 'ETH' ? (amt) => stdlib.toWeiBigNumber(amt, 'ether') :
  9            (amt) => stdlib.bigNumberify(amt);
 10      const fromCurrency =
 11            connector == 'ETH' ? (amt) => stdlib.fromWei(amt) :
 12            (amt) => amt.toString(); // ?
 13      const getBalance = async (who) => fromCurrency(await stdlib.balanceOf(who));
 14    
 15      const MATURITY = 10;
 16      const REFUND = 10;
 17      const DORMANT = 10;
 18      const fDelay = delayFunder ? MATURITY + REFUND + DORMANT + 1 : 0;
 19      const rDelay = delayReceiver ? MATURITY + REFUND + 1 : 0;
 20      console.log(`Begin demo with funder delay(${fDelay}) and receiver delay(${rDelay}).`);
 21    
 22      const common = (who, delay = 0) => ({
 23        funded: async () => {
 24          console.log(`${who} sees that the account is funded.`);
 25          if ( delay != 0 ) {
 26            console.log(`${who} begins to wait...`);
 27            await stdlib.wait(delay); } },
 28        ready : async () => console.log(`${who} is ready to receive the funds.`),
 29        recvd : async () => console.log(`${who} received the funds.`) });
 30    
 31      const startingBalance = toCurrency('100');
 32    
 33      const funder = await stdlib.newTestAccount(startingBalance);
 34      const receiver = await stdlib.newTestAccount(startingBalance);
 35      const bystander = await stdlib.newTestAccount(startingBalance);
 36    
 37      const ctcFunder = await funder.deploy(backend);
 38      const ctcReceiver = await receiver.attach(backend, ctcFunder);
 39      const ctcBystander = await bystander.attach(backend, ctcFunder);
 40    
 41      await Promise.all([
 42        backend.Funder(stdlib, ctcFunder, {
 43          ...common('Funder', fDelay),
 44          getParams: () => ({
 45            receiverAddr: receiver.networkAccount.address,
 46            payment: toCurrency('10'),
 47            maturity: MATURITY,
 48            refund: REFUND,
 49            dormant: DORMANT, }) }),
 50        backend.Receiver(stdlib, ctcReceiver, common('Receiver', rDelay)),
 51        backend.Bystander(stdlib, ctcBystander, common('Bystander')) ]);
 52      for (const [who, acc] of [['Funder', funder], ['Receiver', receiver], ['Bystander', bystander]]) {
 53        console.log(`${who} has a balance of ${await getBalance(acc)}`); }
 54      console.log(`\n`); };
 55    
 56    (async () => {
 57      await runDemo(false, false);
 58      await runDemo(true, false);
 59      await runDemo(true, true);
 60    })();

The most interesting part of this program is on lines 25 through 27 when we optionally cause a delay in the participant after they receive the signal that the account is funded.

Let’s see what it looks like when we run this program:

$ ../reach run

Begin demo with funder delay(0) and receiver delay(0).

Receiver sees that the account is funded.

Bystander sees that the account is funded.

Funder sees that the account is funded.

Receiver is ready to receive the funds.

Receiver received the funds.

Funder has a balance of 89.99926093

Receiver has a balance of 109.999956574

Bystander has a balance of 100.0

 

Begin demo with funder delay(0) and receiver delay(21).

Receiver sees that the account is funded.

Receiver begins to wait...

Bystander sees that the account is funded.

Funder sees that the account is funded.

Funder is ready to receive the funds.

Receiver is ready to receive the funds.

Funder received the funds.

Funder has a balance of 99.999217452

Receiver has a balance of 99.99995488

Bystander has a balance of 100.0

 

Begin demo with funder delay(31) and receiver delay(21).

Receiver sees that the account is funded.

Receiver begins to wait...

Bystander sees that the account is funded.

Funder sees that the account is funded.

Funder begins to wait...

Receiver is ready to receive the funds.

Bystander is ready to receive the funds.

Funder is ready to receive the funds.

Bystander received the funds.

Funder has a balance of 89.99921581

Receiver has a balance of 100.0

Bystander has a balance of 109.999956956

4.3.7 Discussion and Next Steps

Great job! You could use this application today and start put your child’s college funds away for safe keeping! Although, perhaps you should wait until you read the workshop about interest-bearing accounts like this.

If you found this workshop rewarding, please let us know on the Discord community!

If you’d like to make this application a little more interesting, maybe you’d like to have a secret password just like the hash lock as well, so the Funder can separate the revealing of information to Receiver. Similarly, you could make it like a relay account and have the Receiver generated by the Funder and allow the Receiver to specify a third-party (fourth-party) to receive the actual funds.

We recommend that you take a pause from workshops like this and revisit the Rock, Paper, Scissors! application in the fairness workshop. Why don’t you check it out?