让我们回顾一下本教程所做的工作:
在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程序:
'reach 0.1';
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
const winner = (handA, handB) =>
((handA + (4 - handB)) % 3);
assert(winner(ROCK, PAPER) == B_WINS);
assert(winner(PAPER, ROCK) == A_WINS);
assert(winner(ROCK, ROCK) == DRAW);
forall(UInt, handA =>
forall(UInt, handB =>
assert(isOutcome(winner(handA, handB)))));
forall(UInt, (hand) =>
assert(winner(hand, hand) == DRAW));
const Player =
{ ...hasRandom,
getHand: Fun([], UInt),
seeOutcome: Fun([UInt], Null),
informTimeout: Fun([], Null) };
const Alice =
{ ...Player,
wager: UInt };
const Bob =
{ ...Player,
acceptWager: Fun([UInt], Null) };
const DEADLINE = 10;
export const main =
Reach.App(
{},
[Participant('Alice', Alice), Participant('Bob', Bob)],
(A, B) => {
const informTimeout = () => {
each([A, B], () => {
interact.informTimeout(); }); };
A.only(() => {
const wager = declassify(interact.wager); });
A.publish(wager)
.pay(wager);
commit();
B.only(() => {
interact.acceptWager(wager); });
B.pay(wager)
.timeout(DEADLINE, () => closeTo(A, informTimeout));
var outcome = DRAW;
invariant(balance() == 2 * wager && isOutcome(outcome) );
while ( outcome == DRAW ) {
commit();
A.only(() => {
const _handA = interact.getHand();
const [_commitA, _saltA] = makeCommitment(interact, _handA);
const commitA = declassify(_commitA); });
A.publish(commitA)
.timeout(DEADLINE, () => closeTo(B, informTimeout));
commit();
unknowable(B, A(_handA, _saltA));
B.only(() => {
const handB = declassify(interact.getHand()); });
B.publish(handB)
.timeout(DEADLINE, () => closeTo(A, informTimeout));
commit();
A.only(() => {
const [saltA, handA] = declassify([_saltA, _handA]); });
A.publish(saltA, handA)
.timeout(DEADLINE, () => closeTo(B, informTimeout));
checkCommitment(commitA, saltA, handA);
outcome = winner(handA, handB);
continue; }
assert(outcome == A_WINS || outcome == B_WINS);
transfer(2 * wager).to(outcome == A_WINS ? A : B);
commit();
each([A, B], () => {
interact.seeOutcome(outcome); });
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说“我完成了教程!”我们将会赋予你精通教程的角色,这样你就可以帮助其他人学习本教程!
感谢您和我们一起度过学习本课程的美好时光!