以太坊开发演练从智能合约开始
人民干脆面
发表于 2022-12-2 03:37:35
160
0
0
一些人在测试网络中部署智能合约,另一些人告诉你要读黄皮书,而另一些人则建议你使用truffle套件,因为它很有帮助。但其实你并不知道该做什么,也不知道这些东西放在一起是怎么用的。' t4 ]4 g* _, B2 O/ X
如果这是你读到的第一篇关于以太坊或区块链生态系统的文章,你会爱上以太坊的!虽然“专家们”在twitter上互掐,有很多不安全的标准和协议,未经测试有bug的开发工具……并不是所有的问题都已经解决,但每个人都在朝着不同的方向前进探索,任重而道远。机构、银行和政府的未来将由这些疯狂的开发者决定!这真是太棒了。
" w0 G$ X5 j9 L# T5 W4 K
不管怎样,别担心,我会努力把本系列教程中所有的信息点串连起来,带你走进智能合约和dapps的开发世界,向你展示各部分是如何配合工作的。
虽然我不会对每一个细节进行阐释,但我会给你一些阅读材料的链接,这些材料能帮助你更好地理解某些概念,这要靠你自己研究并了解所有的细节。这个系列的目的就是以最简单的方式,帮助你更好地理解各部分是如何协同工作的,就像一个朋友向你解释说明那样。
我甚至不知道以太坊是什么
以太坊的官方网站告诉我们:
以太坊是一个运行着智能合约的分布式平台:应用程序完全按照程序运行,不存在故障、审查、欺诈或第三方干预的可能性。
h% D( l# I0 a
换句话说,以太坊给予了我们使用区块链技术来验证我们运行的代码的执行情况的能力。
如果你不知道区块链、以太坊、比特币、加密货币这些词的含义,我建议你听听TimFerriss采访NickSzabo和NavalRavikant的精彩播客:沉默的加密货币大师——NickSzabo。" J- v( a5 J5 q
智能合约是什么?/ Z& y1 L/ u/ C
在以太坊,智能合约是可以处理资金的脚本。就是这么简单。
+ Q+ ?! z7 k$ R
这些合约由我们称为“矿工”的参与方强制执行和证明。矿工其实是多台电脑,它们把一项交易(执行智能合约,支付加密货币等)添加到一个公开分类账上,我们称之为区块。多个区块构成一个区块链。
我们给这些矿工支付一种叫作“Gas”的东西,它是运行一份合约的成本。当你发布一份智能合约,或者执行一份智能合约,或者把钱转到另一个账户,你就要支付一些被转换成Gas的以太币。% c3 ?! z" J/ X$ B
4 Z6 \1 s2 d2 w* w% O6 q; @
在深入智能合约的开发之前- k0 _& V; G" R: o& T0 C! `
在本教程中,我会假设你已经在软件开发方面有了一定的基础,并且具备了Javascript和Node.JS的基本知识。否则,你很快会看不懂。Solidity是我们用来开发的编程语言,它与Javascript的语法非常接近,而且由于以太坊周围很多的开发工具都是基于Javascrip和Node.JS.的,所以如果你对两者都已经很熟悉的话,那你就更容易理解。这不是针对初学者的教程,如果你不理解一些概念,我会为给你一些替代教程的链接,剩下的部分靠你自己研究吃透。9 v7 t* D& i+ \8 u9 b; J* V4 V
智能合约开发. t# k) ^7 @2 a/ N
* X3 \' |5 ^4 L0 [5 Z
现在,我们开始有趣的部分,正如我所说的,Solidity与Javascript很接近,但它们并不相同。关于Solidity,我很遗憾地告诉你,作为前端开发人员,你不能在一段代码上强加JQuery并希望它能起作用,你不能忽略安全性,不能“另辟蹊径”。我们会在讨论安全性、开发模式和防止代码故障的时候知晓原因。
现在,关于我们的第一个例子,我正在考虑一个由电影《时间规划局》启发的脚本。电影中,人们生活在一个反乌托邦式的未来,改用时间作为货币流通。他们可以通过掰手腕的方式赢取对手的时间(他们的“手臂”上存储着时间,输方的时间将会传送给赢家),我们也可以这么做!用智能合约以角力(Wrestling)的方式赚钱。1 N: _. B5 O+ e6 ^+ r( t( X Q, {1 H
别担心,我会在Github上为你提供接下来我们会看到的所有脚本。
9 s! O3 F$ _3 j7 N
另外,如果这是你第一次处理智能合约,如果你现在不了解所有的事情那也没关系,你需要练习、阅读文档和做一些研究来熟悉Solidity。
2 ^3 Y+ d4 O0 b9 V
代码
5 X& b& x# ]8 ]3 ^
现在话题转到写代码上,首先,solidity脚本的基础是下面这段代码,pragma指令会告诉编译器我们正在使用的Solidity版本,以及我们的合约名称,是一种与Javascrip上的类(class)相似的结构。这就是“Wrestling(角力)”。
+ \0 _- y- ], o( b$ w' E, a5 {% q
pragmasolidity^0.4.18;3 B" X/ T( R+ z# F2 g
* _- K7 H0 I5 p9 @) R8 C6 o& u
contractWrestling{
//ourcodewillgohere
" ]6 l0 H* @+ @- n
}/ M0 y$ e6 f. o E
$ m7 B8 m2 b2 x. |
我们需要两个参与者,所以我们要添加两个保存他们账户地址的变量(他们钱包的公钥)。8 w% J! @; k& @
addresspublicwrestler1;
% c* w+ V# Q* b
addresspublicwrestler2;
在我们的小游戏中,每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱是另一个人的两倍(总计),那他就赢了。
/ b! f" g* q3 }
boolpublicwrestler1Played;
9 l! J1 J( I' s, L
boolpublicwrestler2Played;
uintprivatewrestler1Deposit;; X$ o+ v6 }# ~7 F
# R- x$ _+ _6 z# d" q& Q
uintprivatewrestler2Deposit;
boolpublicgameFinished;; E& M+ c) V) b: V
: u3 G% q* {1 E
addresspublictheWinner;7 V+ ]- p6 `: Y, D
, @, a K5 o2 c& v6 m& C( O: z; D8 J
uintgains;$ ?; _3 @. e0 f' [" a
: E# Y" f u$ Y3 z p1 ]' ^
关于公钥/私钥的一件重要的事项是,即使一个变量是私有的,但并不意味着其他人不能读取它的内容,它只是意味着它只能在合约中被访问,但实际上,由于整个区块链存储在许多计算机上,所以存储在变量中的信息总是可以被其他人看到,这是你应该记住的第一个安全考虑。; m$ ]( B3 O4 q. z% T c( f. W
0 Z9 d4 A! ^4 N0 u: q5 S
另一方面,编译器会自动为公共变量创建getter函数。为了使其他的合约和用户能够更改公共变量的值,你需要创建一个setter函数。& |* Z( N0 D7 L% h' s: w
1 m6 x% J" C7 ?( ~+ `# [0 Z
现在我们将为游戏的每一步添加三个事件。开始,参与者注册;游戏期间,登记每一轮赛果;最后,其中一位参与者获胜。事件是简单的日志,可以在分布式应用程序(也称为dapps)的用户界面中调用JavaScript回调函数。在开发过程中,事件甚至可以用于调试的目的,因为它不同于JavaScript在Solidity上的console.log()函数。8 Y$ m5 p5 c; B. `# J5 o
eventWrestlingStartsEvent(addresswrestler1,addresswrestler2);" ]; R7 f. T G1 x- {
eventEndOfRoundEvent(uintwrestler1Deposit,uintwrestler2Deposit);& Y0 L+ |1 x. n9 ~. B
; j2 {! i: q/ t
eventEndOfWrestlingEvent(addresswinner,uintgains);
现在我们将添加构造函数,在Solidity中,它与我们的合约具有相同的名称,并且在创建合约时只调用一次。, b* s5 j2 x% L! Z8 E- X; L/ Z
- U" o" f. W4 V# P9 F
在这里,第一位参与者将是创造合约的人。“msg.sender”是调用该函数的人的地址。( E# @2 ?: k% r7 l! w6 T! M
functionWrestling()public{
3 b+ l! l# u' x/ F1 k
wrestler1=msg.sender;
) T) q _& J, c; l) H- I
}
! d& c. f0 x) k2 [; J
接下来,我们让另一个参与者使用以下函数进行注册:8 z- K3 [+ K. M+ A& C3 I
. [! p6 I. X0 ?& ~
functionregisterAsAnOpponent()public{
+ a7 ], ?& o& I& O
require(wrestler2==address(0));
wrestler2=msg.sender;" A1 E8 `. H) q) ^' X6 `# R
\! E6 m, I; U0 ?
WrestlingStartsEvent(wrestler1,wrestler2);
0 r4 x' j: U# x! `; v1 X3 P8 G9 P
}$ {' \! N" w6 ?0 p8 m% D* `
require函数是Solidity中一个特殊的错误处理函数,如果条件不满足,它会回滚更改。在我们的示例中,如果变量参与者2等于0x0地址(地址等于0),我们可以继续;如果参与者2的地址与0x0地址不同,这就意味着某个玩家已经注册为对手,所以我们会拒绝新的注册。
再次强调,“msg.sender”是调用该函数的帐户地址,并且当我们触发一个事件,就标志着角力的开始。
现在,每一个参与者都会调用一个函数,“wrestle()”,并投入资金。如果双方已经玩过这场游戏,我们就能知道其中一方是否获胜(我们的规则是其中一方投入的资金必须是另一方的双倍)。关键字“payable”意味着函数可以接收资金,如果它不是集合,函数则不会接受以太币。“msg.value”是发送到合约中的以太币的数量。
functionwrestle()publicpayable{
+ o. L- T6 B7 O, p0 o: q
require(!gameFinished&&(msg.sender==wrestler1||msg.sender==wrestler2));7 U& a9 \0 @7 a4 N& }, E6 {3 ]
' e- O, a( r B9 Z+ O
if(msg.sender==wrestler1){5 A: C9 B6 X3 L$ a
" t( G8 b( O5 [0 k
require(wrestler1Played==false);3 `; L K4 X+ q! i/ y9 Y
$ v' K/ M% c) P1 Z. P- C
wrestler1Played=true;; W" Q8 f' v) e9 P& d6 g
# v u6 d! Q* K9 H3 A; m
wrestler1Deposit=wrestler1Deposit+msg.value;
}else{# Z: n! o7 U1 `* `8 W7 n3 ^
# t8 W( Z6 _8 _6 Z3 R$ T. |
require(wrestler2Played==false);
+ `; N G9 A- \; h
wrestler2Played=true; j6 N5 y( s3 |# i2 B' d, L
- d* U8 f+ E4 ?. D
wrestler2Deposit=wrestler2Deposit+msg.value;
}# y/ }' C2 X& a7 R: n+ `4 @/ S
if(wrestler1Played&&wrestler2Played){4 ~+ a( N- x9 z G" r7 b* t- R" w
+ W! U K- l5 A3 ?# F+ x
if(wrestler1Deposit>=wrestler2Deposit*2){' P- N5 G& B3 T* ?
endOfGame(wrestler1);! L% M* ^; o/ }+ C
6 `; c$ A# f$ P6 G
}elseif(wrestler2Deposit>=wrestler1Deposit*2){+ A* Z4 e4 P* H- b) l+ j7 }1 k
endOfGame(wrestler2);- d& D2 S9 Q3 i5 Y) E! J- f: q- L
}else{
endOfRound();
& Q% p1 F- J+ n; N5 t, E( R* Z
}7 x- d/ ^) e/ F6 K" m& @
}
- s; `# p3 V7 x8 H, i0 y5 W
}
% o; B! |6 R" s/ ~ Z) J1 C. U
然后,我们添加endOfGame()和endOfRound()函数。关键字“internal”与“private”是一样的,唯一的区别是internal函数可以由其他合约继承(因为Solidity与其他面向对象语言相似),而private函数不能继承。; f2 h& |1 s1 Y1 J. `
functionendOfRound()internal{
wrestler1Played=false;
, m2 s8 `7 k7 Q7 ~9 ^
wrestler2Played=false;* X% Q" H0 n1 _" t3 }
EndOfRoundEvent(wrestler1Deposit,wrestler2Deposit);4 O( A+ w7 f3 } @- i; r
" O( A+ b; ?) W; y$ `' _( w
}
functionendOfGame(addresswinner)internal{
0 A) K* G0 ~- Q7 T
gameFinished=true;
5 V5 n9 ` m9 d# d
theWinner=winner;0 T6 |4 d. K# x" B. ?, f
gains=wrestler1Deposit+wrestler2Deposit;
EndOfWrestlingEvent(winner,gains);
}5 t4 R# R1 u9 C
请注意,我们不是直接把钱交给赢家,在此情况下这并不重要,因为赢家会把该合约所有的钱提取出来;而在其他情况下,当多个用户要把合约中的以太币提取出来,使用withdraw模式会更安全,可以避免重入。5 N, d- t$ J6 T+ n" f9 I/ q
简单地说,如果多个用户都可以从合约中提取资金,那么任谁都能一次性多次调用withdraw函数并多次得到报酬。所以我们需要以这样一种方式来编写我们的取款功能:在他继续得到报酬之前,他应得的数额会作废。$ w q$ ]; O# ^* _- B
它看起来像这样:9 u9 Z9 ]& S8 i3 j3 }+ B
' W; N0 Z. Z5 ?' g$ X% y$ }- W
functionwithdraw()public{; ?; l) B7 x3 ?8 k: F
, {8 H8 k v5 ^( B
require(gameFinished&&theWinner==msg.sender);
uintamount=gains;
gains=0;
3 \- h; I* |$ o5 J- g+ ]- K
msg.sender.transfer(amount);
}
就是这样。你可以在以下链接中找到整个代码段:) p6 Q# y. Q* [9 F! n& M
- b" U1 Q7 f; k V+ Z3 y' k7 N2 E
devzl/ethereum-walkthrough-1
% o, A. i0 W0 q1 [# @: Z) p* k
使用IDE& v& y) r w1 X. s
* F3 f/ h9 I, s
现在复制代码段,在浏览器的新标签页中打开RemixIDE:: u% ?3 M( A0 M" r& P. E: V) A0 c
你可以直接在浏览器上使用Remix,它有很多有意思的功能,这些功能你在别的地方几乎找不到。" q8 F+ C$ n, F$ Z, U: Z
7 J* T5 S& L! \& ^- T( V8 h5 s
点击页面左上方的“+”按钮,创建一个名为“Wrestling.sol”的新文件,然后粘贴你在上面的github中找到的代码。
+ S8 r7 x$ X: d7 F: a
最后会有一些语法高亮显示。Github暂不支持Solidity.sol扩展。$ ^" H# y, @8 p% A# U \+ Q
- e# L3 |$ N! T+ ]/ F1 c/ D$ R
在页面的右侧,你可以找到很多有意思的选项卡,比如Analysis,它会显示错误和建议。这个工具留给你好好探索。我还是想向你演示一下,即使在接下来的教程中我们会使用其他的工具。但正如我之前所说的,本系列教程的目标是连接这些信息点并向你展示它们是如何在一起协同工作的,你可以自由决定是否在浏览器上使用IDE。如果你愿意的话,邀请你读一下Remix的文档。
2 r) M9 I0 x6 r/ Q8 o+ }
或者,你可以阅读这些文档来熟悉Solidity。6 Z. C3 u& t! t8 a
在接下来的教程中,我们将了解如何将智能合约部署到两种测试网络中,探索truffle,ganache和geth以及它们是如何一起协同工作的。
成为第一个吐槽的人