Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
402
0
0
/ R0 C$ F# ~$ v; `& ], q5 r
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。" i+ [4 l4 h+ l% l0 u8 x6 ~
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。
::/ t+ u: t$ K! [' `% t
pragma solidity ^0.4.11;
contract WithdrawalContract {3 n$ V3 g9 b' f4 [! f+ G8 l+ G+ `
address public richest;' @2 Q i0 _( w+ k& z
uint public mostSent;) S. e. k9 a5 I5 t* c# ^' I
mapping (address => uint) pendingWithdrawals;4 r& x3 h, B, N' C
function WithdrawalContract() public payable {3 z( Y% a' ]9 U
richest = msg.sender;3 X4 ]' c5 P6 {: o. k0 O
mostSent = msg.value;6 x* y& } ~, ]+ e1 B" ]
}
function becomeRichest() public payable returns (bool) {( V0 | a3 k7 Q' s$ v! q
if (msg.value > mostSent) {) q) p( [) T. N- K8 h- c1 J- \
pendingWithdrawals[richest] += msg.value;# F ^1 K9 a& L. Y1 y' f0 A! v) W/ ^
richest = msg.sender;4 K7 a5 q" C% G" `) v' G
mostSent = msg.value;3 A% j8 n5 b1 c3 V8 Z5 q& c
return true;' U8 E( S7 B9 x6 y# K- s& Y% D
} else {6 R S J. Q" |. F
return false;( S7 R* ?7 d* C; {
}
}6 Y4 N9 q" O: B% [3 g: n
function withdraw() public {- G6 O- p G+ Y" q u2 ?9 T) ]4 n
uint amount = pendingWithdrawals[msg.sender];7 n, b) t8 x1 i" z; h* |
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击. ^9 H0 C. p! h7 N& l/ e
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);6 n! G/ E4 T" M8 I7 W: ]* C/ \
}+ |' {) \# l" P' m7 k* l5 Z
}. o+ u F/ V4 ?# K; h3 K( e! F
下面是一个相反的直接使用发送模式的例子:
::
pragma solidity ^0.4.11;0 D0 Q1 c8 `* G5 k9 a& o$ G5 \
contract SendContract { n6 [2 n. m* K. e0 {
address public richest;/ U; p/ D" I! ^7 ~0 ?0 H& T5 ]* N& D% R! f
uint public mostSent;) p% N2 i. J! i: F) O5 |
function SendContract() public payable {
richest = msg.sender;
mostSent = msg.value; I: O8 ^$ `% P6 i: S; V
}; L' A. x5 Y- O$ m% W+ T; D7 y# Q
function becomeRichest() public payable returns (bool) {9 e {2 a3 c' T* \" N, u
if (msg.value > mostSent) {" O, @4 v: j* g5 @! ?
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);
richest = msg.sender;9 i l# z$ x: M; W: o1 `3 K
mostSent = msg.value;8 \+ J" C G% j1 B
return true; Q7 B7 ~! C& Q9 G f7 ~- N
} else {4 a: h. _5 x! h3 Z
return false;
}
}$ Q7 Q- a0 d& |6 n# z
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest& M0 _5 Q4 |* r" I8 Z, R' A! n: j! z
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。$ U6 U3 @: M; d3 U' P
… index:: access;restricting
0 g6 B! b- z+ ?
限制访问/ b9 w4 R9 x' K' l! L9 ?
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。% X1 o# h/ k9 V4 P
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。: e. j( _1 `: M9 Q9 h- Z N
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。$ e: N; A, q; u+ Z
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。( U$ e4 w9 G( L4 C3 p2 } Y' U
::
pragma solidity ^0.4.22;$ [1 P7 z2 J. V& x
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是4 i$ E" W( X3 b8 X. f6 u
// 创建这个合约的账户。
address public owner = msg.sender;* m; P5 P% l, U; P/ b
uint public creationTime = now;& o. s; \ L, P/ s4 Z/ p
// 修饰器可以用来更改8 ]5 \/ u9 M& W* [; n
// 一个函数的函数体。
// 如果使用这个修饰器,( y( r3 d, M6 s4 z& g6 Q0 Z
// 它会预置一个检查,仅允许( ^4 P) X" I6 K0 R5 \- _& n
// 来自特定地址的
// 函数调用。' S- i; r, T& n- @
modifier onlyBy(address _account)
{
require($ b! T. y. K+ D7 i N
msg.sender == _account,6 S5 K2 z8 t' t1 O0 Y
"Sender not authorized."
);
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的
// 函数体所替代。1 {; J. J( d: {2 K8 f; S) h1 k
_;0 g9 a% u. z/ k! o2 l4 C4 O& U( g
}
// 使 `_newOwner` 成为这个合约的. |' ^- N ^9 u
// 新所有者。
function changeOwner(address _newOwner)& z4 f( k3 ~) G, k/ r* g
public
onlyBy(owner)
{
owner = _newOwner;9 _" W1 w* v i. @
} _6 P4 Y3 j& q4 s$ }& e# @
modifier onlyAfter(uint _time) {
require(
now >= _time,
"Function called too early."
);
_;- m: h; L2 R( T" z7 R
}
// 抹掉所有者信息。' R/ p9 b T6 i$ d% i5 G3 U. p2 o
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。8 ^( [+ e: `( ]5 e% g# V
function disown()
public% b7 a* p8 K/ S% A
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;9 H2 R- n( Z) n c
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。 _5 d/ M9 Q B' Q$ m. k
// 这在 0.4.0 版本以前的 Solidity 中很危险, z, v) J7 \1 O, k/ I' X% L4 K, L9 ]
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)" u1 q2 G& H& n$ n/ Y
msg.sender.send(msg.value - _amount);$ u& I9 F- T$ ?6 Q- l$ ^" M
}& p6 c3 k0 L) q, c% @$ J
function forceOwnerChange(address _newOwner)8 M& z3 I4 {) j$ ~
public* i) V9 e! f% l
payable H# y3 o W5 J! m1 b. X
costs(200 ether)" H7 g6 R- u/ r% p
{
owner = _newOwner;
// 这只是示例条件9 V" u& t1 h; }' Y
if (uint(owner) & 0 == 1)0 {. T/ ]' y- _, S0 m4 m
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。
return;1 U$ U- C$ j* I8 B
// 退还多付的费用
}; U Q& V# \4 q- l* m( b
}7 _$ m! \3 U8 V7 x- ~, T+ Z4 E
一个更专用地限制函数调用的方法将在下一个例子中介绍。
… index:: state machine
9 j( H% `- I( M
状态机 Q6 H) `% _+ X# x
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。: B0 ^/ D5 M% D/ z
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”," u0 t3 ?8 |1 b; C4 @
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier$ ~6 @2 v) {! a/ F
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,6 N: n& ^/ p4 N5 p7 P
它应该用在所有函数上。
… note::, _1 L) R3 \4 w" c& t( E- l
|modifier| 的顺序非常重要。3 n+ X( ?; b6 H" |5 L
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以
首先被反映到账户中。9 g: W% b0 N2 D
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。" o6 g+ g* ]2 f) K2 N
… note::
|modifier| 可以被忽略。% d/ h/ \+ j- k( I0 ]4 }
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:) ?' z6 _, s. \# W. C4 I
由于 |modifier| 是通过简单的替换代码# }* h8 z% l. a) a4 b
而不是使用函数调用来提供的,, X# a* y5 f( D" O g
如果函数本身使用了 return,那么 transitionNext |modifier|' B( U7 R$ ~5 j1 z
的代码是可以被忽略的。8 L9 C" X5 C1 ^7 Q
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。2 l( B) R! f# ?/ H
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::) ]: k9 c% e! K
pragma solidity ^0.4.22;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,0 v2 l# J2 `! m( f3 i3 q
RevealBids,
AnotherStage,
AreWeDoneYet,3 e- |3 _; J" A$ L C# }
Finished
}
// 这是当前阶段。, x, n& ~ G6 U) e
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;! T! {5 ^* H2 d6 h. U8 [
modifier atStage(Stages _stage) {
require(
stage == _stage,
"Function cannot be called at this time."
);
_;
}% j) B- {( [% f$ w4 v! |) {
function nextStage() internal {
stage = Stages(uint(stage) + 1);* v8 p6 N; H3 T9 i+ M8 u/ h. G& e9 B
}( u. l; }; y, x& [) P* R; n
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)$ B- ~% S) Y1 T/ S
nextStage();8 f$ W6 a. W1 T/ |; Q
if (stage == Stages.RevealBids &&0 x: O9 s* Z9 ^% D& ~
now >= creationTime + 12 days)
nextStage();
// 由交易触发的其他阶段转换' U( Z; |! p' A6 \" f( Z1 @8 x
_;: E9 e9 |6 j7 y3 e
}
// 这里的修饰器顺序非常重要!2 g9 \! ~7 I1 i: B% K% J
function bid()
public
payable1 h2 U# h3 J) b1 L$ c
timedTransitions( G4 h% f/ M5 U* V% h& K( ^, V! x
atStage(Stages.AcceptingBlindedBids)1 u2 o/ o% l6 C2 V2 z
{( [* E4 W6 E& h1 [ U- u9 Y$ J
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)% Z5 ?) J- c @9 j
}
function reveal()8 {! p2 u# Y2 e1 y! A
public' ?2 k0 y% }0 g6 k4 ~, x# [/ t9 c2 l
timedTransitions' T, u1 z5 F5 p- q8 r; L
atStage(Stages.RevealBids)
{/ C& q+ V& ]# ~( w, I8 h- t
}
// 这个修饰器在函数执行结束之后: M; v2 J2 g3 v' m2 H( C$ U
// 使合约进入下一个阶段。: ?; p" q* @; l5 |# a8 N
modifier transitionNext()# W4 B" h+ e3 q) t6 g8 ]
{8 p1 f4 m8 z( Q' a% a
_;; ~. A/ H; W: G* |
nextStage();% E: S( u1 A- C9 u+ A J
}
function g()
public; S( `/ x' U- p A
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}( V- T( t' o9 }" c8 G J
function h()$ T/ N1 {! K6 P/ ~9 b
public
timedTransitions
atStage(Stages.AreWeDoneYet). o9 @( G$ ~1 p
transitionNext% R. s& {. h: p0 j
{- n1 g4 M2 m! F4 i5 v! w$ k
}, K( G* X$ d# S' G' q. _
function i(), X( ^& S4 m! ?
public
timedTransitions
atStage(Stages.Finished) A, f3 c) s" Z- O
{* A1 S5 r( ]5 v! l6 Q9 F; F' w
}
}2 h8 [& n9 ?: ]* m; l& }
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人