2.10 Onward and Further

Let’s review what we’ve done through this tutorial:

Despite having done so much, this is really just a brief introduction to what is possible with Reach.

How difficult was all this? Let’s look at the final versions of our programs.

First, let’s look at the Reach program:

 1    'reach 0.1';
 2    
 3    const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
 4    const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
 5    
 6    const winner = (handA, handB) =>
 7      ((handA + (4 - handB)) % 3);
 8    
 9    assert(winner(ROCK, PAPER) == B_WINS);
10    assert(winner(PAPER, ROCK) == A_WINS);
11    assert(winner(ROCK, ROCK) == DRAW);
12    
13    forall(UInt, handA =>
14      forall(UInt, handB =>
15        assert(isOutcome(winner(handA, handB)))));
16    
17    forall(UInt, (hand) =>
18      assert(winner(hand, hand) == DRAW));
19    
20    const Player = {
21      ...hasRandom,
22      getHand: Fun([], UInt),
23      seeOutcome: Fun([UInt], Null),
24      informTimeout: Fun([], Null),
25    };
26    
27    export const main = Reach.App(() => {
28      const Alice = Participant('Alice', {
29        ...Player,
30        wager: UInt, // atomic units of currency
31        deadline: UInt, // time delta (blocks/rounds)
32      });
33      const Bob   = Participant('Bob', {
34        ...Player,
35        acceptWager: Fun([UInt], Null),
36      });
37      deploy();
38    
39      const informTimeout = () => {
40        each([Alice, Bob], () => {
41          interact.informTimeout();
42        });
43      };
44    
45      Alice.only(() => {
46        const wager = declassify(interact.wager);
47        const deadline = declassify(interact.deadline);
48      });
49      Alice.publish(wager, deadline)
50        .pay(wager);
51      commit();
52    
53      Bob.only(() => {
54        interact.acceptWager(wager);
55      });
56      Bob.pay(wager)
57        .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
58    
59      var outcome = DRAW;
60      invariant( balance() == 2 * wager && isOutcome(outcome) );
61      while ( outcome == DRAW ) {
62        commit();
63    
64        Alice.only(() => {
65          const _handAlice = interact.getHand();
66          const [_commitAlice, _saltAlice] = makeCommitment(interact, _handAlice);
67          const commitAlice = declassify(_commitAlice);
68        });
69        Alice.publish(commitAlice)
70          .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
71        commit();
72    
73        unknowable(Bob, Alice(_handAlice, _saltAlice));
74        Bob.only(() => {
75          const handBob = declassify(interact.getHand());
76        });
77        Bob.publish(handBob)
78          .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
79        commit();
80    
81        Alice.only(() => {
82          const saltAlice = declassify(_saltAlice);
83          const handAlice = declassify(_handAlice);
84        });
85        Alice.publish(saltAlice, handAlice)
86          .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
87        checkCommitment(commitAlice, saltAlice, handAlice);
88    
89        outcome = winner(handAlice, handBob);
90        continue;
91      }
92    
93      assert(outcome == A_WINS || outcome == B_WINS);
94      transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob);
95      commit();
96    
97      each([Alice, Bob], () => {
98        interact.seeOutcome(outcome);
99      });
100    });

Next, the JavaScript command-line frontend:

 1    import { loadStdlib } from '@reach-sh/stdlib';
 2    import * as backend from './build/index.main.mjs';
 3    import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
 4    const stdlib = loadStdlib(process.env);
 5    
 6    (async () => {
 7      const isAlice = await ask(
 8        `Are you Alice?`,
 9        yesno
10      );
11      const who = isAlice ? 'Alice' : 'Bob';
12    
13      console.log(`Starting Rock, Paper, Scissors! as ${who}`);
14    
15      let acc = null;
16      const createAcc = await ask(
17        `Would you like to create an account? (only possible on devnet)`,
18        yesno
19      );
20      if (createAcc) {
21        acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
22      } else {
23        const secret = await ask(
24          `What is your account secret?`,
25          (x => x)
26        );
27        acc = await stdlib.newAccountFromSecret(secret);
28      }
29    
30      let ctc = null;
31      const deployCtc = await ask(
32        `Do you want to deploy the contract? (y/n)`,
33        yesno
34      );
35      if (deployCtc) {
36        ctc = acc.deploy(backend);
37        ctc.getInfo().then((info) => {
38          console.log(`The contract is deployed as = ${JSON.stringify(info)}`); });
39      } else {
40        const info = await ask(
41          `Please paste the contract information:`,
42          JSON.parse
43        );
44        ctc = acc.attach(backend, info);
45      }
46    
47      const fmt = (x) => stdlib.formatCurrency(x, 4);
48      const getBalance = async () => fmt(await stdlib.balanceOf(acc));
49    
50      const before = await getBalance();
51      console.log(`Your balance is ${before}`);
52    
53      const interact = { ...stdlib.hasRandom };
54    
55      interact.informTimeout = () => {
56        console.log(`There was a timeout.`);
57        process.exit(1);
58      };
59    
60      if (isAlice) {
61        const amt = await ask(
62          `How much do you want to wager?`,
63          stdlib.parseCurrency
64        );
65        interact.wager = amt;
66        interact.deadline = { ETH: 100, ALGO: 100, CFX: 1000 }[stdlib.connector];
67      } else {
68        interact.acceptWager = async (amt) => {
69          const accepted = await ask(
70            `Do you accept the wager of ${fmt(amt)}?`,
71            yesno
72          );
73          if (!accepted) {
74            process.exit(0);
75          }
76        };
77      }
78    
79      const HAND = ['Rock', 'Paper', 'Scissors'];
80      const HANDS = {
81        'Rock': 0, 'R': 0, 'r': 0,
82        'Paper': 1, 'P': 1, 'p': 1,
83        'Scissors': 2, 'S': 2, 's': 2,
84      };
85    
86      interact.getHand = async () => {
87        const hand = await ask(`What hand will you play?`, (x) => {
88          const hand = HANDS[x];
89          if ( hand == null ) {
90            throw Error(`Not a valid hand ${hand}`);
91          }
92          return hand;
93        });
94        console.log(`You played ${HAND[hand]}`);
95        return hand;
96      };
97    
98      const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
99      interact.seeOutcome = async (outcome) => {
100        console.log(`The outcome is: ${OUTCOME[outcome]}`);
101      };
102    
103      const part = isAlice ? backend.Alice : backend.Bob;
104      await part(ctc, interact);
105    
106      const after = await getBalance();
107      console.log(`Your balance is now ${after}`);
108    
109      done();
110    })();

And finally, the Web frontend:

 1    import React from 'react';
 2    import AppViews from './views/AppViews';
 3    import DeployerViews from './views/DeployerViews';
 4    import AttacherViews from './views/AttacherViews';
 5    import {renderDOM, renderView} from './views/render';
 6    import './index.css';
 7    import * as backend from './build/index.main.mjs';
 8    import {loadStdlib} from '@reach-sh/stdlib';
 9    const reach = loadStdlib(process.env);
10    
11    const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
12    const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
13    const {standardUnit} = reach;
14    const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
15    
16    class App extends React.Component {
17      constructor(props) {
18        super(props);
19        this.state = {view: 'ConnectAccount', ...defaults};
20      }
21      async componentDidMount() {
22        const acc = await reach.getDefaultAccount();
23        const balAtomic = await reach.balanceOf(acc);
24        const bal = reach.formatCurrency(balAtomic, 4);
25        this.setState({acc, bal});
26        if (await reach.canFundFromFaucet()) {
27          this.setState({view: 'FundAccount'});
28        } else {
29          this.setState({view: 'DeployerOrAttacher'});
30        }
31      }
32      async fundAccount(fundAmount) {
33        await reach.fundFromFaucet(this.state.acc, reach.parseCurrency(fundAmount));
34        this.setState({view: 'DeployerOrAttacher'});
35      }
36      async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
37      selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
38      selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
39      render() { return renderView(this, AppViews); }
40    }
41    
42    class Player extends React.Component {
43      random() { return reach.hasRandom.random(); }
44      async getHand() { // Fun([], UInt)
45        const hand = await new Promise(resolveHandP => {
46          this.setState({view: 'GetHand', playable: true, resolveHandP});
47        });
48        this.setState({view: 'WaitingForResults', hand});
49        return handToInt[hand];
50      }
51      seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
52      informTimeout() { this.setState({view: 'Timeout'}); }
53      playHand(hand) { this.state.resolveHandP(hand); }
54    }
55    
56    class Deployer extends Player {
57      constructor(props) {
58        super(props);
59        this.state = {view: 'SetWager'};
60      }
61      setWager(wager) { this.setState({view: 'Deploy', wager}); }
62      async deploy() {
63        const ctc = this.props.acc.deploy(backend);
64        this.setState({view: 'Deploying', ctc});
65        this.wager = reach.parseCurrency(this.state.wager); // UInt
66        this.deadline = {ETH: 10, ALGO: 100, CFX: 1000}[reach.connector]; // UInt
67        backend.Alice(ctc, this);
68        const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
69        this.setState({view: 'WaitingForAttacher', ctcInfoStr});
70      }
71      render() { return renderView(this, DeployerViews); }
72    }
73    class Attacher extends Player {
74      constructor(props) {
75        super(props);
76        this.state = {view: 'Attach'};
77      }
78      attach(ctcInfoStr) {
79        const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
80        this.setState({view: 'Attaching'});
81        backend.Bob(ctc, this);
82      }
83      async acceptWager(wagerAtomic) { // Fun([UInt], Null)
84        const wager = reach.formatCurrency(wagerAtomic, 4);
85        return await new Promise(resolveAcceptedP => {
86          this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
87        });
88      }
89      termsAccepted() {
90        this.state.resolveAcceptedP();
91        this.setState({view: 'WaitingForTurn'});
92      }
93      render() { return renderView(this, AttacherViews); }
94    }
95    
96    renderDOM(<App />);

We wrote about a hundred lines of Reach and two different frontends. Our command-line version is about a hundred lines of JavaScript, while our Web version is about the same length, but has a lot of presentation code as well.

Behind the scenes, Reach generated hundreds of lines of Solidity (which you can look at here: tut-8/build/index.main.sol), almost two thousand lines of TEAL (which you can look at here: tut-8/build/index.main.appApproval.teal), as well as over a thousand lines of JavaScript (which you can look at here: tut-8/build/index.main.mjs). If we weren’t using Reach, then we’d have to write all this code ourselves and ensure that they are consistent and updated at every change to the application.

Now that you’ve seen an entire Reach application from beginning to end, it’s time for you to start working on your own applications!

No matter what you decide to read or work on next, we hope you’ll join us on the Discord community. Once you join, message @team, I just completed the tutorial! and we’ll give you the tutorial veteran role, so you can more easily help others work through it!

Thanks for spending your afternoon with us!