原文链接

    让我们回顾一下本教程所做的工作:

    2.1中,我们了解了在Windows/Linux/Mac系统下如何通过指令安装Reach,对大多数开发者而言不存在技术门槛。
    2.2中,我们了解了Reach仅需几行代码和三个关键API调用就能完成设置。
    2.3中,我们了解了Reach如何让开发人员关注去中心化应用程序的业务逻辑,并略去区块链交互和协议设计的细枝末节。
    2.4中,我们了解了Reach处理代币与网络交易如同共享数据一样简单。
    2.5中,我们向您介绍Reach自动合法验证引擎,它确保我们的程序不存在类型缺陷和安全漏洞。
    2.6中,我们了解了Reach如何处理参与者迟迟不响应的情况以及如何防止资金被合约锁定。
    2.7中,我们了解了Reach如何用循环来处理平局的情况,以及Reach前端如何灵活地适应后端的变更。
    2.8中,我们了解了如何将Reach程序从Reach测试环境中分离出来,并在真实网络上发布可交互应用程序。
    2.9中,我们了解了如何将Reach程序部署为去中心化的Web应用程序。
    即使已经做了这么多工作,这仅仅只是关于Reach功能的一个简单介绍。

    上述9个小节完成的最终版本其实很简单,让我们看看程序的最终版本。

    首先,让我们看看Reach程序:

    tut-8/index.rsh

    1. 'reach 0.1';
    2. const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
    3. const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
    4. const winner = (handA, handB) =>
    5. ((handA + (4 - handB)) % 3);
    6. assert(winner(ROCK, PAPER) == B_WINS);
    7. assert(winner(PAPER, ROCK) == A_WINS);
    8. assert(winner(ROCK, ROCK) == DRAW);
    9. forall(UInt, handA =>
    10. forall(UInt, handB =>
    11. assert(isOutcome(winner(handA, handB)))));
    12. forall(UInt, (hand) =>
    13. assert(winner(hand, hand) == DRAW));
    14. const Player =
    15. { ...hasRandom,
    16. getHand: Fun([], UInt),
    17. seeOutcome: Fun([UInt], Null),
    18. informTimeout: Fun([], Null) };
    19. const Alice =
    20. { ...Player,
    21. wager: UInt };
    22. const Bob =
    23. { ...Player,
    24. acceptWager: Fun([UInt], Null) };
    25. const DEADLINE = 10;
    26. export const main =
    27. Reach.App(
    28. {},
    29. [Participant('Alice', Alice), Participant('Bob', Bob)],
    30. (A, B) => {
    31. const informTimeout = () => {
    32. each([A, B], () => {
    33. interact.informTimeout(); }); };
    34. A.only(() => {
    35. const wager = declassify(interact.wager); });
    36. A.publish(wager)
    37. .pay(wager);
    38. commit();
    39. B.only(() => {
    40. interact.acceptWager(wager); });
    41. B.pay(wager)
    42. .timeout(DEADLINE, () => closeTo(A, informTimeout));
    43. var outcome = DRAW;
    44. invariant(balance() == 2 * wager && isOutcome(outcome) );
    45. while ( outcome == DRAW ) {
    46. commit();
    47. A.only(() => {
    48. const _handA = interact.getHand();
    49. const [_commitA, _saltA] = makeCommitment(interact, _handA);
    50. const commitA = declassify(_commitA); });
    51. A.publish(commitA)
    52. .timeout(DEADLINE, () => closeTo(B, informTimeout));
    53. commit();
    54. unknowable(B, A(_handA, _saltA));
    55. B.only(() => {
    56. const handB = declassify(interact.getHand()); });
    57. B.publish(handB)
    58. .timeout(DEADLINE, () => closeTo(A, informTimeout));
    59. commit();
    60. A.only(() => {
    61. const [saltA, handA] = declassify([_saltA, _handA]); });
    62. A.publish(saltA, handA)
    63. .timeout(DEADLINE, () => closeTo(B, informTimeout));
    64. checkCommitment(commitA, saltA, handA);
    65. outcome = winner(handA, handB);
    66. continue; }
    67. assert(outcome == A_WINS || outcome == B_WINS);
    68. transfer(2 * wager).to(outcome == A_WINS ? A : B);
    69. commit();
    70. each([A, B], () => {
    71. interact.seeOutcome(outcome); });
    72. exit(); });

    接下来是JavaScript命令行前端:
    tut-8/index.mjs

    import { loadStdlib } from '@reach-sh/stdlib';
    import * as backend from './build/index.main.mjs';
    import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
    
    (async () => {
      const stdlib = await loadStdlib();
    
      const isAlice = await ask(
        `Are you Alice?`,
        yesno
      );
      const who = isAlice ? 'Alice' : 'Bob';
    
      console.log(`Starting Rock, Paper, Scissors! as ${who}`);
    
      let acc = null;
      const createAcc = await ask(
        `Would you like to create an account? (only possible on devnet)`,
        yesno
      );
      if (createAcc) {
        acc = await stdlib.newTestAccount(stdlib.parseCurrency(1000));
      } else {
        const secret = await ask(
          `What is your account secret?`,
          (x => x)
        );
        acc = await stdlib.newAccountFromSecret(secret);
      }
    
      let ctc = null;
      const deployCtc = await ask(
        `Do you want to deploy the contract? (y/n)`,
        yesno
      );
      if (deployCtc) {
        ctc = acc.deploy(backend);
        const info = await ctc.getInfo();
        console.log(`The contract is deployed as = ${JSON.stringify(info)}`);
      } else {
        const info = await ask(
          `Please paste the contract information:`,
          JSON.parse
        );
        ctc = acc.attach(backend, info);
      }
    
      const fmt = (x) => stdlib.formatCurrency(x, 4);
      const getBalance = async () => fmt(await stdlib.balanceOf(acc));
    
      const before = await getBalance();
      console.log(`Your balance is ${before}`);
    
      const interact = { ...stdlib.hasRandom };
    
      interact.informTimeout = () => {
        console.log(`There was a timeout.`);
        process.exit(1);
      };
    
      if (isAlice) {
        const amt = await ask(
          `How much do you want to wager?`,
          stdlib.parseCurrency
        );
        interact.wager = amt;
      } else {
        interact.acceptWager = async (amt) => {
          const accepted = await ask(
            `Do you accept the wager of ${fmt(amt)}?`,
            yesno
          );
          if (accepted) {
            return;
          } else {
            process.exit(0);
          }
        };
      }
    
      const HAND = ['Rock', 'Paper', 'Scissors'];
      const HANDS = {
        'Rock': 0, 'R': 0, 'r': 0,
        'Paper': 1, 'P': 1, 'p': 1,
        'Scissors': 2, 'S': 2, 's': 2,
      };
      interact.getHand = async () => {
        const hand = await ask(`What hand will you play?`, (x) => {
          const hand = HANDS[x];
          if ( hand == null ) {
            throw Error(`Not a valid hand ${hand}`);
          }
          return hand;
        });
        console.log(`You played ${HAND[hand]}`);
        return hand;
      };
    
      const OUTCOME = ['Bob wins', 'Draw', 'Alice wins'];
      interact.seeOutcome = async (outcome) => {
        console.log(`The outcome is: ${OUTCOME[outcome]}`);
      };
    
      const part = isAlice ? backend.Alice : backend.Bob;
      await part(ctc, interact);
    
      const after = await getBalance();
      console.log(`Your balance is now ${after}`);
    
      done();
    })();
    

    最后是Web前端:
    tut-9/index.js

    import React from 'react';
    import AppViews from './views/AppViews';
    import DeployerViews from './views/DeployerViews';
    import AttacherViews from './views/AttacherViews';
    import {renderDOM, renderView} from './views/render';
    import './index.css';
    import * as backend from './build/index.main.mjs';
    import * as reach from '@reach-sh/stdlib/ETH';
    
    const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
    const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
    const {standardUnit} = reach;
    const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
    
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {view: 'ConnectAccount', ...defaults};
      }
      async componentDidMount() {
        const acc = await reach.getDefaultAccount();
        const balAtomic = await reach.balanceOf(acc);
        const bal = reach.formatCurrency(balAtomic, 4);
        this.setState({acc, bal});
        try {
          const faucet = await reach.getFaucet();
          this.setState({view: 'FundAccount', faucet});
        } catch (e) {
          this.setState({view: 'DeployerOrAttacher'});
        }
      }
      async fundAccount(fundAmount) {
        await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
        this.setState({view: 'DeployerOrAttacher'});
      }
      async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
      selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
      selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
      render() { return renderView(this, AppViews); }
    }
    
    class Player extends React.Component {
      random() { return reach.hasRandom.random(); }
      async getHand() { // Fun([], UInt)
        const hand = await new Promise(resolveHandP => {
          this.setState({view: 'GetHand', playable: true, resolveHandP});
        });
        this.setState({view: 'WaitingForResults', hand});
        return handToInt[hand];
      }
      seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
      informTimeout() { this.setState({view: 'Timeout'}); }
      playHand(hand) { this.state.resolveHandP(hand); }
    }
    
    class Deployer extends Player {
      constructor(props) {
        super(props);
        this.state = {view: 'SetWager'};
      }
      setWager(wager) { this.setState({view: 'Deploy', wager}); }
      async deploy() {
        const ctc = this.props.acc.deploy(backend);
        this.setState({view: 'Deploying', ctc});
        this.wager = reach.parseCurrency(this.state.wager); // UInt
        backend.Alice(ctc, this);
        const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
        this.setState({view: 'WaitingForAttacher', ctcInfoStr});
      }
      render() { return renderView(this, DeployerViews); }
    }
    
    class Attacher extends Player {
      constructor(props) {
        super(props);
        this.state = {view: 'Attach'};
      }
      attach(ctcInfoStr) {
        const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
        this.setState({view: 'Attaching'});
        backend.Bob(ctc, this);
      }
      async acceptWager(wagerAtomic) { // Fun([UInt], Null)
        const wager = reach.formatCurrency(wagerAtomic, 4);
        return await new Promise(resolveAcceptedP => {
          this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
        });
      }
      termsAccepted() {
        this.state.resolveAcceptedP();
        this.setState({view: 'WaitingForTurn'});
      }
      render() { return renderView(this, AttacherViews); }
    }
    
    renderDOM(<App />);
    

    我们写了88行Reach代码,并且有两个不同的前端,其中命令行版本由111行JavaScript代码构成,共计199行。而Web版本由96行JavaScript代码构成,共计184行。

    在这背后,Reach生成了341行Solidity代码(可以查看文件:tut-8/build/index.main.sol),1621行TEAL代码(可以查看文件:tut-8/build/index.main.mjs#L584),以及1586行JavaScript代码(可以查看文件:tut-8/build/index.main.mjs)。如果我们没有使用Reach,那么我们就必须自己编写这3553行代码,并确保它们在应用程序的每次更改时都保持同步更新。

    现在您已经从头到尾看到了完整的Reach应用,是时候开始开发自己的应用程序了!

    你可能想从Workshop着手,这是一个自学课程,通过不同的具体项目来实践和学习Reach。
    或者,也可以在指南中学习Reach项目中用到的一些概念和背景知识。
    或者,也许深入到参考资料中去研究Reach的特征。

    不管你下一步打算做什么,我们都希望你能加入我们的Discord社区。一旦你加入Discord社区,发信息@team说“我完成了教程!”我们将会赋予你精通教程的角色,这样你就可以帮助其他人学习本教程!

    感谢您和我们一起度过学习本课程的美好时光!