Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
526
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。2 i ] k9 J7 u0 ^$ j4 R
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。- W; n) N& {' [* x% d/ h
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。0 K( e4 f: Q& a
::) @# V7 F: G Q: \1 E8 X! [% X
pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest;
uint public mostSent;. r9 O& n, p4 J5 M& j, H
mapping (address => uint) pendingWithdrawals;7 d+ j# S+ c9 Z8 b8 S5 K' t
function WithdrawalContract() public payable {0 T/ T4 Q) h. c1 ~0 w
richest = msg.sender;
mostSent = msg.value;
}; {9 N' Z( s9 H H" U+ q R
function becomeRichest() public payable returns (bool) {2 ^2 Q ?& d. p! ` t: s
if (msg.value > mostSent) {2 F4 G& h1 w3 Y7 `4 h7 w
pendingWithdrawals[richest] += msg.value;& i" c4 y' z2 c: }
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;% S, o' `# H- j6 X0 {4 Q
}
}. [1 I e4 K! }
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;9 j! F+ y# p9 Q3 O
msg.sender.transfer(amount);
}
}. ~! k# \/ Z4 l6 H: _; |1 S" V
下面是一个相反的直接使用发送模式的例子:7 k* q% J; Y+ G: m& G1 F
::, [" I/ `' l0 o& P$ D
pragma solidity ^0.4.11;; J1 H' U* S+ n/ @; y1 F
contract SendContract {
address public richest;
uint public mostSent;
function SendContract() public payable {8 h4 Y8 U) `6 i0 M3 B
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {, g7 H1 D, L* q6 c- I# X4 O2 k
if (msg.value > mostSent) {3 M% A8 ?/ ?0 c: o! D A3 u
// 这一行会导致问题(详见下文)1 @( m y% l9 ^4 l' I5 h
richest.transfer(msg.value);
richest = msg.sender;# h- k/ e9 u( J+ o5 L$ I; H# @
mostSent = msg.value;# B0 |4 Y1 k ~& V h' b, ~8 R: n+ m
return true;1 F" s6 b9 S9 ?% B3 l3 f
} else {4 K9 l8 O0 G* ^1 |
return false;
}
}$ k4 U% ]& M! L# t7 z3 ^
}8 t0 Z: n( x" a0 |% Y8 }' s
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约3 Y4 ?! J) `4 q% N. p" J8 Y+ E; F& w
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。* h( B% T8 U7 O7 M. v
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting9 z+ g1 e& |- j6 ?* Q. u
) Z% S/ k3 ^; @* i4 ~6 p
限制访问1 p3 T3 h2 V Y
/ B" `3 n, s' T9 r% [1 q, l
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。2 v; q; M1 U% q0 i( N
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。) k9 A: t3 b- Z* s3 ~
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。0 X6 }4 l2 |; }9 _
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。) R4 }, Y: ^( q' F
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。
::7 W# e. H* X2 u( G1 }5 }* y
pragma solidity ^0.4.22;
contract AccessRestriction {* Z& v$ r1 c- n4 I/ j) U
// 这些将在构造阶段被赋值1 _( Z+ j3 W" M( ~- D
// 其中,`msg.sender` 是
// 创建这个合约的账户。
address public owner = msg.sender;
uint public creationTime = now;
// 修饰器可以用来更改
// 一个函数的函数体。
// 如果使用这个修饰器,, x3 `, Q8 `0 t. g4 R/ u. F
// 它会预置一个检查,仅允许; O9 a5 U; b1 O! [* A( }; Y/ W% B2 V
// 来自特定地址的: K! `$ @( o4 P' k9 _
// 函数调用。1 r. E6 g7 D% Z7 T, [: j+ B
modifier onlyBy(address _account)
{5 G; _* R- W0 T- d8 A& Z
require(8 z: S2 N! _ l9 C. N/ }
msg.sender == _account,
"Sender not authorized."
); b4 y) f$ s$ W; ~- ~
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的
// 函数体所替代。" L7 e5 |# _- W, x. y
_;; J2 g/ Q+ {5 V5 S! Q$ z, w& t
}9 S7 E6 p* t6 v5 } Y5 m
// 使 `_newOwner` 成为这个合约的
// 新所有者。
function changeOwner(address _newOwner)- o& ^; t' ? U% p( t7 z
public9 e" T5 T% C9 f3 P
onlyBy(owner)
{1 R/ J& N7 m$ |. t7 u+ M/ Y# P
owner = _newOwner;6 C/ {5 B8 t6 U! l
}
modifier onlyAfter(uint _time) {
require(
now >= _time,
"Function called too early."/ \5 f6 l6 `5 s. o) O. C
);5 J1 B% T6 d: F/ }) O
_;% @& |4 t9 x$ R# M8 U- Z1 X; Y
}
// 抹掉所有者信息。* u9 E+ c( D+ ?; @8 I9 x
// 仅允许在合约创建成功 6 周以后. n2 M% _! N+ `( v- h
// 的时间被调用。
function disown()5 D+ K9 {9 _% } Z
public- z, s ~0 X9 \! @2 m* X
onlyBy(owner)9 r3 u3 g4 x8 o5 m3 i: b& _
onlyAfter(creationTime + 6 weeks); g+ c. Z3 R, O' i% F# @) j; w$ W e
{2 R3 a8 J) ]) M Q; M! O
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。0 A- R7 B6 q+ }; ]# A/ E
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,3 |; I" T5 q% ]+ ~+ o" u
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {
require(
msg.value >= _amount,% q' Z) N+ m2 q$ B% ` _) s
"Not enough Ether provided."
);5 l9 T, t% N! X* z# z2 J |* p
_;
if (msg.value > _amount)4 ]% H. X+ O1 t0 t- b) R: Y
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)$ n4 D) ]7 `, h& M6 A! M1 k0 t
public& r: r+ h% Z) `7 Y2 E! }' @* e
payable
costs(200 ether)
{1 Y- x `- [. u$ h" G
owner = _newOwner;
// 这只是示例条件
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的% N- s; c k- Q: f& A, {
// Solidity 上进行退还。
return;
// 退还多付的费用# ~$ ?; S. u( Z- H9 S' c
}
}3 p' x3 z# z- d! n9 Z( s
一个更专用地限制函数调用的方法将在下一个例子中介绍。- M" ?+ H# G9 u \% g K# N. N
… index:: state machine
, W& Q; b+ d7 O' `) b
状态机0 I3 j. ]+ ~1 {3 e: F( h. g
3 \: N% c: t7 H% d7 r8 M4 }/ U
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。8 a- z+ I9 k+ u! [
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。, r* e! {) G; z
通过达到特定的 时间 点来达到某些阶段也是很常见的。5 P N6 x5 Y" ~- e: t& Q6 @
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。. P, R* [) G) c% Y( J# ?/ k
… index:: function;modifier* s5 a" v- C0 u7 y& f2 I5 W
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。5 u2 H2 V5 M1 k" i' ]: T
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。! I. K: f5 D( b( T
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。5 [6 @/ q* C4 H2 @
… note::* E8 m [* f. t
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,, u6 S4 l. z4 N, j" i1 u- ~; l( x
请确保在 timedTransitions 之后声明 atStage,( _$ s Z6 F4 D" m5 g
以便新的状态可以6 ]& H+ r& z% d6 h
首先被反映到账户中。
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。+ x3 f. K* {! H3 o- \' e4 I& N* }1 |
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码' O! l: R; C; Z/ P% B8 J
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。
如果你希望这么做," Y! g6 b! ~7 E4 c. o c
请确保你在这些函数中手工调用了 nextStage。5 } p/ W/ w) ]! h
从 0.4.0 版本开始,即使函数明确地 return 了,6 p: g8 o; |6 j" j3 b" y' ^
|modifier| 的代码也会执行。( P9 s `! u6 m
::& c/ y9 w# g( Z! G4 b& w" B H
pragma solidity ^0.4.22;) d, D p4 L5 a1 }) e5 u) V
contract StateMachine {* s6 x, x0 D3 n* ?2 j) I6 T$ Q
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// 这是当前阶段。 q4 o" F; |* h* y6 Q& |* G
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(
stage == _stage,8 c E; L, P. m2 d* I1 u, Y+ Z
"Function cannot be called at this time."" n$ Y- ?" f7 g: ?
);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)4 A' b( D6 T3 g1 Y2 n2 ~# Y/ `* C# {
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();( i) B5 h+ _3 `, g8 c% g% X
// 由交易触发的其他阶段转换# V7 |! B- \ q. k4 ~2 _
_;* H, x O; i% ?% f" U
}; o# n, c) U8 n. n7 _
// 这里的修饰器顺序非常重要!/ E9 L. T9 w: S/ X& C
function bid()
public& L1 r( X. L& g
payable# C7 T# K9 X/ J6 i- W$ l6 X. A5 O
timedTransitions# _& R/ B) k4 ?$ x; i( h
atStage(Stages.AcceptingBlindedBids), F# n- Y7 m. V' _9 I2 h+ o( m
{$ _* A: n" ~9 P2 U8 F( D
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)/ }$ N' ^ w. p ]
}
function reveal()" v! c; i$ P' k( _2 k
public
timedTransitions
atStage(Stages.RevealBids)
{
}
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。
modifier transitionNext()- \! O$ Q: C, F
{4 d3 _* A- u' G8 Y9 Z
_;0 M1 @0 p' k1 F% P+ f
nextStage();/ R- R( m# K9 a7 `7 \) d3 h( f$ J
}- f/ R( N+ \$ k
function g()9 u' V5 ]0 \* f
public: ~9 u7 I6 b$ S! p
timedTransitions+ M$ ~( ~5 ^2 p' ~
atStage(Stages.AnotherStage)4 k4 M0 |* |- F
transitionNext
{2 z* q E# j2 N' W5 T1 ~
}
function h()
public' g, Z! Z8 b g4 x
timedTransitions
atStage(Stages.AreWeDoneYet): D; ~% B; c4 [
transitionNext2 [- f' N5 _* N- } y
{) v; e) M; f0 Q1 l
}9 C3 m0 X$ W( ?- N. X
function i()
public% c0 \; q2 J* G. W* N
timedTransitions
atStage(Stages.Finished)
{
} M2 o! p) A0 G# T( z( M
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人