Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
532
0
0
5 g7 o# r1 O1 ^. q& I
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,1 _- D' j4 ?! r* V' `" M
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。
::; \% f q. W$ B$ U# [
pragma solidity ^0.4.11;
contract WithdrawalContract {- Z6 A5 u+ ?' f4 z' [! `" u* o
address public richest;
uint public mostSent;5 N+ _. m$ H; C; w
mapping (address => uint) pendingWithdrawals;5 J7 n: n! V' q
function WithdrawalContract() public payable {
richest = msg.sender;
mostSent = msg.value;! t& k. d B; s5 Q
}
function becomeRichest() public payable returns (bool) {3 i: g; P/ M9 e% k
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;; W( T! y! @8 l1 `" b" D
richest = msg.sender;3 `( I. b) a% e: I& o
mostSent = msg.value;
return true;
} else {
return false;! R- @# \ i: _! u, N* q( a1 c4 F$ G
}; r/ R) w6 J5 X/ ^+ ~8 ~- C, L
}
function withdraw() public {: }1 t( Y. {$ F7 x1 O9 n
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零2 r' A/ \4 Q+ q* o: U
// 来防止重入(re-entrancy)攻击5 H$ v$ K6 t+ ^& l) g
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}; S% a k# o/ `7 c f- P2 m
}
下面是一个相反的直接使用发送模式的例子:
::+ M8 A. F' f, R4 x$ H: q
pragma solidity ^0.4.11;4 y2 R: E& E$ O/ O5 E
contract SendContract {6 _8 n: n5 w( ^4 T+ E8 n
address public richest;
uint public mostSent;; |# i* m2 t( H: m
function SendContract() public payable {/ i& s1 }( x9 `2 n
richest = msg.sender;
mostSent = msg.value;
}& A0 }# V" z# g9 z9 O9 w
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {" T- \/ s' R3 y3 K
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);# G5 _1 m( O( x1 C) m. }- l7 c
richest = msg.sender;+ J4 T ]+ \, e4 j0 C* C
mostSent = msg.value;# [: H0 S, m! \5 V$ m/ i
return true;
} else {7 J6 n8 }* e7 y a: G0 C; R
return false;
}; X2 [. }9 b6 I1 l! G& a
}; W! I9 L4 g. `0 L( [
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest4 J3 f1 Z% S2 T1 s
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约0 v; k( z* T: M) S( l
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting
# u9 G7 u# N2 z4 f
限制访问- J! A: ~' @+ D+ J; a
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。% l! Q( v- p, O" f8 F* d
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。% x( |8 `8 w' R& F, l1 j' ^
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。" z7 A4 H3 G+ g6 A) _5 p9 u, P! F. A
::
pragma solidity ^0.4.22;
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是* A9 J2 X( a$ d0 j* t: l
// 创建这个合约的账户。
address public owner = msg.sender;
uint public creationTime = now;
// 修饰器可以用来更改5 Q* l( x) S& V0 ^6 Z
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许3 H/ i, [# f. l. O
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)- O+ B5 q, c) I7 W; Z% A
{
require(6 T* ? L: z! G
msg.sender == _account,8 t7 G9 C# I: E
"Sender not authorized."
);6 G% l4 H* Z% e! n- I: ]
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的
// 函数体所替代。
_;
}
// 使 `_newOwner` 成为这个合约的
// 新所有者。9 Y2 q; n& f- d2 p8 i
function changeOwner(address _newOwner)
public
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(3 j) J* B" [* ?/ G
now >= _time,
"Function called too early."
);
_;
}! d/ V) T- x5 q+ @; d1 x
// 抹掉所有者信息。' T; b/ h) O9 Y
// 仅允许在合约创建成功 6 周以后$ |: T; I; G; V Q# v, K
// 的时间被调用。
function disown()3 Z3 ~! f( }1 N3 {( u5 |/ Y
public
onlyBy(owner)1 a) U" \2 q$ D- ]1 Q) Q+ W8 ], @3 a
onlyAfter(creationTime + 6 weeks)
{6 S: Z! S; O: ^
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。! }: _+ k" V& n3 w! a0 ]+ ^
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。9 q! V/ }" L5 r3 }+ T1 H. N% j1 d
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {" D Q/ q, ?7 W5 r6 ^0 m
require(
msg.value >= _amount,2 i7 |/ @0 W( Z3 K
"Not enough Ether provided."
);
_;' P& d2 a/ T% H$ I$ B+ g
if (msg.value > _amount)3 S+ o2 _4 H/ e! {* U8 l; M
msg.sender.send(msg.value - _amount);- A1 Q" x- S7 A6 |) v" Q
}
function forceOwnerChange(address _newOwner). \& d7 g5 a9 q& m- R0 n9 B
public
payable( R- J1 O4 K# s1 h' d$ E
costs(200 ether)
{ [' G) _' i! ?# P' B; [
owner = _newOwner;
// 这只是示例条件
if (uint(owner) & 0 == 1)1 t5 t6 B; Q& H
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。# w7 I% k ~0 O5 T9 m% K
return;$ V1 O- Q) k; v) F3 \- E
// 退还多付的费用, h6 e _% Z( }: R
}% u, J6 u3 _, J# G; @0 M' q( L
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。8 n; U% L2 ` u. R; s- b# w8 K
… index:: state machine
状态机
8 `- H. K6 p, ]" Y
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。6 R- ]3 X+ S* m# S; k+ E
… index:: function;modifier
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例# [. e! ?: e) Q& v: _% F5 K
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。. k9 r, y# }" ?
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,+ k: `" U2 L+ y6 k4 a, B
它应该用在所有函数上。
… note::
|modifier| 的顺序非常重要。2 G; z; i" W% x+ }" ?. p: ]0 Z
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以) J" a% ^: S8 u" ?# Y; ?4 M
首先被反映到账户中。& G: K: e% j! I
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::$ h) S7 d) N4 ^
|modifier| 可以被忽略。! ]2 N( Y# w- _7 }' t; P+ B* O, R
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:: ~8 K7 V J& \3 F! S( y# M4 s
由于 |modifier| 是通过简单的替换代码! J8 Z* o) i1 g" Q& x6 A5 v3 r
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。8 U6 Q; f6 W+ |& s
如果你希望这么做,1 ?: T5 i- S2 r& G8 ^) r
请确保你在这些函数中手工调用了 nextStage。) r0 Y( J% F) I; _6 \; S
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。0 H3 p" s" _6 ~" Z5 L
::
pragma solidity ^0.4.22;
contract StateMachine {8 I# P- m& S6 R2 l) g6 v" k( H
enum Stages {' u7 d* j* c3 r$ a2 e
AcceptingBlindedBids,4 Y2 |8 C2 E8 O8 y( S" x9 _
RevealBids,# J1 k2 n v5 K: i
AnotherStage,
AreWeDoneYet,6 U5 Q! D" n r% R: Z% _# a
Finished. u" [) }+ t. h! z
}0 F1 X' F' Z2 W& \* C$ n" J& h
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;: {4 N( C; X+ I' |- V
modifier atStage(Stages _stage) {
require(
stage == _stage,: H$ r- @( A3 h: z# t X; d0 m
"Function cannot be called at this time."! E- a4 ^+ {! k0 C
);
_;
}
function nextStage() internal {7 C" u5 H8 a# r& u& m
stage = Stages(uint(stage) + 1); S; Q9 m3 I+ h6 _% V$ p! @( E
}
// 执行基于时间的阶段转换。1 S7 a( f, B% I' v
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。7 l$ O" w: d6 x( ]& z/ ^/ k. g
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&/ U# u+ v& Y, A; ]5 i8 S3 R" S
now >= creationTime + 12 days)
nextStage();
// 由交易触发的其他阶段转换& T1 P* R |+ {/ T3 W$ R7 t# V* q
_;
}
// 这里的修饰器顺序非常重要!
function bid()& w) N1 g, i* |+ k X
public( \; r [5 @5 F4 N% [
payable. y0 w y: p7 i7 V' X# V0 n
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)! e. W. ?" q# O! t6 X* F& E
}
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)
{
}
// 这个修饰器在函数执行结束之后 }' t- g9 s; P* n3 p
// 使合约进入下一个阶段。8 R2 [# v- G( T0 _1 Y6 T- E
modifier transitionNext()- P) p, _$ I' S" F3 m
{4 q1 a) l& {0 c9 w
_;
nextStage();
}! U4 d% W9 k1 n
function g()
public( U8 P" b1 p6 `# d0 ]
timedTransitions
atStage(Stages.AnotherStage)
transitionNext |8 [/ l) z6 o$ c
{: d$ o* ]/ O9 Z0 M, p
}
function h()
public. _9 I) w- I+ ~3 L, m9 \9 R
timedTransitions& e) G& [9 N# f4 B0 R
atStage(Stages.AreWeDoneYet)3 }5 @4 B' k$ K
transitionNext
{3 G d, |9 I, n1 F6 Y3 V5 m
}
function i()
public6 }0 A$ g. ?7 _5 i8 e) d
timedTransitions
atStage(Stages.Finished) w; @$ e# ~2 j* S! W$ G. ]
{5 c+ ?- @% O, x2 U3 C
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人