Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
486
0
0
# p, Q- C1 ~# F
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。, C5 e4 ]! P$ Q }7 d
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。 A: z: d' Q' N. }0 b i
::) G" |- W( w) G9 i6 g9 s! P
pragma solidity ^0.4.11;# D7 z* K& D$ j6 L3 ]
contract WithdrawalContract {
address public richest;
uint public mostSent; [8 h/ K! x! y
mapping (address => uint) pendingWithdrawals;9 A5 f7 T1 e* N0 S/ V `7 z3 Q
function WithdrawalContract() public payable {
richest = msg.sender;* T x! Z" r; t8 l
mostSent = msg.value;
}, D+ G0 C3 p) @+ n
function becomeRichest() public payable returns (bool) {% j2 E" P7 {( j8 k3 P3 B/ M0 P
if (msg.value > mostSent) {; z$ y/ z* t c& {4 K5 N
pendingWithdrawals[richest] += msg.value;! c% T5 {0 H0 h$ j; M% ?
richest = msg.sender;
mostSent = msg.value;
return true;! ]% h$ x, ?" x, ]: p
} else {
return false;- I7 |+ R9 d! d2 n- ~+ B8 e c
}7 V5 r: }2 B$ t
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];) V5 F: _1 d% U, S
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;( e' @# l2 r2 D+ U
msg.sender.transfer(amount);
}
}8 p& a& u6 e; B" C# P
下面是一个相反的直接使用发送模式的例子:
::8 o0 m0 o. S3 t: a' O+ X4 _0 c3 _2 F
pragma solidity ^0.4.11;
contract SendContract {4 W/ t1 o( H" Z& u: ?* Y- N3 v
address public richest;
uint public mostSent;
function SendContract() public payable {
richest = msg.sender;
mostSent = msg.value;7 V2 [: Y* h8 f [& p8 v
}
function becomeRichest() public payable returns (bool) {' N# B% q/ B$ W% v/ [/ z
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)2 \' z1 M2 |8 U) [7 \- Q) J1 I0 t& h
richest.transfer(msg.value);4 J5 h0 u2 V' f
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;( m: a) `: d! v( }6 \' S! D& ]& C/ j# D
}$ ~. Q, l4 D' v% r( e0 X) y
}
}4 e. Y/ K* W( K. a3 z; `. Y
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest2 ~6 a. m' U0 ]! G8 ^# K1 V
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting
; i6 n. e) E: Z) j) `
限制访问- X H0 z) _$ _! P7 |( o" @
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。7 J" i/ {, I* D. K
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。 c& D) F, @# J8 C
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。+ J; Z% I! f- T, V
… index:: function;modifier1 |. J y- W+ ^* Z2 x" Q
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。
::
pragma solidity ^0.4.22;
contract AccessRestriction {6 M- c+ E; s, w5 G8 w
// 这些将在构造阶段被赋值: ]2 e, ?8 m8 M+ w$ p
// 其中,`msg.sender` 是
// 创建这个合约的账户。
address public owner = msg.sender;
uint public creationTime = now;2 u: @: q3 n- Q# K4 f
// 修饰器可以用来更改2 j0 ~- L+ ?4 E3 z3 q( L, d3 p# F7 W
// 一个函数的函数体。7 K/ i9 ^& e; Z# }
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许' E" U5 j6 C- u1 O- r7 P9 m
// 来自特定地址的1 o6 b+ a8 {) X: A: g
// 函数调用。
modifier onlyBy(address _account)
{: x) k; B9 k3 _& w4 n4 N% G
require($ A$ D0 k3 S3 C* u
msg.sender == _account,
"Sender not authorized.", L. ?. q$ o2 s( a$ F# Z
);
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的) ~ H4 m4 R( A5 g
// 函数体所替代。
_;, v8 u1 ^: O$ ~- ?% Z/ |" S. ]
}
// 使 `_newOwner` 成为这个合约的' e; A* Y8 ]! H$ E2 g5 K2 O
// 新所有者。
function changeOwner(address _newOwner)
public
onlyBy(owner)1 ? R9 h; n! J1 l- n8 g
{9 L/ Q, O0 n% {( d- g+ x
owner = _newOwner;
}/ i9 L* C; v' R! x8 M
modifier onlyAfter(uint _time) {
require(
now >= _time,
"Function called too early."
);
_;+ B O5 G- G/ i) a+ p0 i" m
}
// 抹掉所有者信息。+ G0 c9 [6 Z- d! l3 p
// 仅允许在合约创建成功 6 周以后8 |* p8 H. m& L- N2 T" g
// 的时间被调用。$ o+ _' f$ R: ~' [* X3 ?
function disown()$ x/ \$ D5 h; q% ^: i
public( x. Q( x5 ~$ V" A* a; v
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{- {; `; B9 r: c9 |
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。
// 如果调用方发送了过多的费用,- x; w# n8 E( I& n, F
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,6 d" ? T; C! |/ M) W+ S
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {
require(
msg.value >= _amount,$ W3 e) n G3 T
"Not enough Ether provided."' ~, S' s, `+ {$ |# n0 y
);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);$ I; l4 a7 K f0 N; T% {* D4 d! c% {, I
}, H8 x; {4 J5 v! y% }
function forceOwnerChange(address _newOwner)/ P6 _! f- Y- q% V& ^
public: P% K) X5 g) y( A8 o. l
payable
costs(200 ether)8 g+ n, W" ]2 a! v) E$ _
{
owner = _newOwner;2 Q/ n4 ~2 R: K: ~4 U- C
// 这只是示例条件1 G. Y G& J7 u
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的: _6 X7 b ?1 A
// Solidity 上进行退还。" N/ Z; c# X# e1 e
return;$ N2 ~) I7 k8 |
// 退还多付的费用- Q" F$ S- x1 L! n
}" @" k% R0 Q5 u2 y/ v P1 s( g
}6 v0 O9 Y0 t4 D, [) V' l A( e. p
一个更专用地限制函数调用的方法将在下一个例子中介绍。
… index:: state machine
状态机+ J$ J; [- e0 W% J! ^
# U# `- o* a) O# I; l8 h' p
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。5 C s3 N' O Z
通过达到特定的 时间 点来达到某些阶段也是很常见的。' v* G4 u3 `9 M2 t# b
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,/ w ?7 Q$ g5 N2 K
然后转换到“公示出价”,最后结束于“确定拍卖结果”。 }: A8 Q: e( ]% H0 x$ D
… index:: function;modifier+ h9 ?* w3 Y* X
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。* a3 l/ q! U$ Z0 |. N; e
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,% s* d# p* ^2 y3 B1 y! L% ~& W( M
它应该用在所有函数上。9 @7 p" K0 w2 I7 [
… note::! J; E. A, u d
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,9 A% s& A7 f, a& K. G4 B
请确保在 timedTransitions 之后声明 atStage,) v. x5 C6 j/ f7 n, _* z
以便新的状态可以
首先被反映到账户中。- G- k1 O& x1 [& _
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:! E+ Z0 J" W3 j7 m( N
由于 |modifier| 是通过简单的替换代码
而不是使用函数调用来提供的,& j6 b U: e/ t q: W
如果函数本身使用了 return,那么 transitionNext |modifier|- C1 X1 y" r7 |5 Y+ \$ O8 X
的代码是可以被忽略的。
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::
pragma solidity ^0.4.22;( c( e+ D9 q8 z) o' \, d# z) K
contract StateMachine {
enum Stages {4 w; i t; I3 O$ a- E* n" Y% b
AcceptingBlindedBids,3 J2 N8 q8 P5 B2 y) P
RevealBids,, r8 N; G; |' z
AnotherStage,9 i! A) _. J p e" |% Z' w
AreWeDoneYet,
Finished
}
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(
stage == _stage,
"Function cannot be called at this time."3 i H! B( }) O# C' [3 s0 N- J- Y
);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);9 D$ R# L; |7 N1 m
}; `2 W/ s- @' e9 E! |4 c" ~; d: C/ P
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。8 Z% a2 o& r% D G& n
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)2 @) Q* C9 {3 b. }5 D
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();" H; c- q$ i' U
// 由交易触发的其他阶段转换
_;
}
// 这里的修饰器顺序非常重要!% ]' `- ~" m7 a
function bid()5 p1 ?" u- I$ a% }; D
public
payable
timedTransitions8 F5 _7 O" C% J( a3 w$ l
atStage(Stages.AcceptingBlindedBids)8 G5 t$ X* W1 z( B6 N8 e) J4 I
{) x/ k0 a$ N4 u+ H+ |
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
}# e; z! h1 u! X2 }
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)
{% ?+ N, O5 _: H9 K+ i7 L- D2 g
}! c) W6 v" g# f$ h* n- V* \0 H. k
// 这个修饰器在函数执行结束之后2 b( \0 _% O3 C, T( v+ u P7 ]
// 使合约进入下一个阶段。
modifier transitionNext()7 D1 Z$ U& t( X" l" M& X4 u: T
{" L {' Y" p- d5 {1 k% y
_;
nextStage();
}" n+ ~! c/ D1 v/ z: V
function g()) M d& S+ r: S
public; s; p/ [! w* L
timedTransitions
atStage(Stages.AnotherStage)" o: x. `$ K& O6 P0 W% i: m
transitionNext
{6 Y. j% k3 L6 Y- o2 e% k C6 m
}5 a- T! j2 [2 ]* x
function h()5 B. l) R: }" S u
public
timedTransitions' Q8 ~. k O9 ~" ^/ I
atStage(Stages.AreWeDoneYet)0 M$ j2 {5 H k/ Q9 _
transitionNext
{
}
function i()+ f7 A: b! Q2 @
public4 w' b% x! o/ ?3 B" K
timedTransitions
atStage(Stages.Finished)
{
}# b- l \. T9 w
}3 ^* n! y8 f+ @3 I
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人