石头剪刀布的教程至此完整啦! 这篇文章提供了完整地总结与更多学习资料的整理。欢迎留言感兴趣的话题,点播后续的文章内容!
[Reach教程翻译] Reach是安全简单的Dapp开发语言 让用户可以像开发传统App一样开发DApp 目前使用Reach开发的智能合约可以部署在以太坊、Conflux、Algorand Reach官网 Reach官方文挡
让我们回顾一下本教程所做的工作:
在 2.1 安装与初始化 中,我们展示了在不同系统下如何通过令命行安装 Reach,这个过程对多数开发者而言都非常容易。
在 2.2 创建DApp脚手架 中,我们展示了一个简单的 Reach 框架,可以发现不管要部署在哪个共识网络,这个框架都不用改动。
在 2.3 石头剪刀布 中,我们可以发现使用 Reach 的开发只要关注DApp的业务逻辑就好了,不用在乎与不同区块链交互和协议设计的麻烦而琐碎的问题。
在 2.4 打赌与下注 中,我们展示了使用 Reach 处理代币与交易就像简单的发送数据一样。
在 2.5 信任与约定 中,我们介绍了Reach自动验证引擎,他能替我们的程序发现并避免很大一部份的安全性漏洞。
在 2.6 超时与不参与 中,我们介绍了该如何使用 Reach 应对参与者迟迟不响应的行为,从而防止资金锁定在合约中。
在 2.7 平手则继续猜拳直至分出胜负**中,我们展示了如何通过While循环让程序可以不断的交互。
在 2.8 玩家交互、自主运行游戏 中,我们展示了除了使用 Reach 的测试环境外,我们如何将可交互的 Reach 程序部署到真实共识网络上。
在 2.9 通过React实现游戏网页交互 中,我们展示了如何将 Reach 程序部署成一个完整的 DApp!
即使已经做了这么多工作,这仅仅只是关于 Reach 功能的一个简单介绍。
上述 9 个小节完成的最终版本其实很简单,让我们看看程序的最终版本。
首先,让我们看看 Reach 程序:
'reach 0.1';
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
const winner = (handAlice, handBob) =>
((handAlice + (4 - handBob)) % 3);
assert(winner(ROCK, PAPER) == B_WINS);
assert(winner(PAPER, ROCK) == A_WINS);
assert(winner(ROCK, ROCK) == DRAW);
forall(UInt, handAlice =>
forall(UInt, handBob =>
assert(isOutcome(winner(handAlice, handBob)))));
forall(UInt, (hand) =>
assert(winner(hand, hand) == DRAW));
const Player = {
...hasRandom,
getHand: Fun([], UInt),
seeOutcome: Fun([UInt], Null),
informTimeout: Fun([], Null),
};
export const main = Reach.App(() => {
const Alice = Participant('Alice', {
...Player,
wager: UInt, // atomic units of currency
deadline: UInt, // time delta (blocks/rounds)
});
const Bob = Participant('Bob', {
...Player,
acceptWager: Fun([UInt], Null),
});
deploy();
const informTimeout = () => {
each([Alice, Bob], () => {
interact.informTimeout();
});
};
Alice.only(() => {
const wager = declassify(interact.wager);
const deadline = declassify(interact.deadline);
});
Alice.publish(wager, deadline)
.pay(wager);
commit();
Bob.only(() => {
interact.acceptWager(wager);
});
Bob.pay(wager)
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
var outcome = DRAW;
invariant( balance() == 2 * wager && isOutcome(outcome) );
while ( outcome == DRAW ) {
commit();
Alice.only(() => {
const _handAlice = interact.getHand();
const [_commitAlice, _saltAlice] = makeCommitment(interact, _handAlice);
const commitAlice = declassify(_commitAlice);
});
Alice.publish(commitAlice)
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
commit();
unknowable(Bob, Alice(_handAlice, _saltAlice));
Bob.only(() => {
const handBob = declassify(interact.getHand());
});
Bob.publish(handBob)
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
commit();
Alice.only(() => {
const saltAlice = declassify(_saltAlice);
const handAlice = declassify(_handAlice);
});
Alice.publish(saltAlice, handAlice)
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
checkCommitment(commitAlice, saltAlice, handAlice);
outcome = winner(handAlice, handBob);
continue;
}
assert(outcome == A_WINS || outcome == B_WINS);
transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob);
commit();
each([Alice, Bob], () => {
interact.seeOutcome(outcome);
});
});
还有以 Javascript 编写的命令行版本的前端
import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
import { ask, yesno, done } from '@reach-sh/stdlib/ask.mjs';
const stdlib = loadStdlib(process.env);
(async () => {
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;
if (isAlice) {
ctc = acc.contract(backend);
ctc.getInfo().then((info) => {
console.log(`The contract is deployed as = ${JSON.stringify(info)}`); });
} else {
const info = await ask(
`Please paste the contract information:`,
JSON.parse
);
ctc = acc.contract(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;
interact.deadline = { ETH: 100, ALGO: 100, CFX: 1000 }[stdlib.connector];
} else {
interact.acceptWager = async (amt) => {
const accepted = await ask(
`Do you accept the wager of ${fmt(amt)}?`,
yesno
);
if (!accepted) {
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 ? ctc.p.Alice : ctc.p.Bob;
await part(interact);
const after = await getBalance();
console.log(`Your balance is now ${after}`);
done();
})();
最后是 Web 前端:
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 {loadStdlib} from '@reach-sh/stdlib';
const reach = loadStdlib(process.env);
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});
if (await reach.canFundFromFaucet()) {
this.setState({view: 'FundAccount'});
} else {
this.setState({view: 'DeployerOrAttacher'});
}
}
async fundAccount(fundAmount) {
await reach.fundFromFaucet(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
this.deadline = {ETH: 10, ALGO: 100, CFX: 1000}[reach.connector]; // 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 />);
我们总共写了约 100 行的 Reach 代码,并且有两个不同的前端,分别是命令行版本和网页版本的,他们各自也都大约只有 100 行。
在这背后,Reach 生成了几百行的 Solidity 代码(可以查看文件:rps-8-interact/build/index.main.sol),近 2000 行的 TEAL 代码(可以查看文件:rps-8-interact/build/index.main.appApproval.teal),以及超过 1000 行的 JavaScript 代码(可以查看文件:rps-8-interact/build/index.main.mjs)。如果我们不是使用 Reach,那么我们就必须自己编写这几千行代码,并确保它们在每次改动时彼此同步、相容,那将是一个很复杂的任务!
现在我们已经从头到尾的带着你写出一个完整的 Reach 应用程序了,该是你开发自己的应用程序的时候啦!
无论如何,我们非常希望你能加入我们的 Discord 社区。一旦你加入 Discord 社区,发信息@team, I just completed the tutorial!
我们就会赋予你tutorial veteran
的角色,这样你就可以帮助其他人学习本教程!
感谢您和我们一起度过学习这个教程的美好时光!
文挡内还有很多值得阅读的部份, 比如: 工作坊内有更多的应用案例,并且不断持续更新。 指南部份有关于各种不同主题与概念的详细讨论。 参考资料中则可以找到全部语法与概念仔细的说明。
我们也会持续与大家分享,也欢迎各位读者加入微信群与更多开发者一起讨论、学习。 有想要了解的主题也欢迎留言点播!!!
欢迎关注Reach微信公众号 并在公众号目录 -> 加入群聊 选择加入官方开发者微信群与官方Discord群 与更多Reach开发者一起学习交流!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!