Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
332
0
0
- X0 F" R! D+ Y
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,$ _0 Y9 O! Y2 @ b; `) _& { A
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。2 Z6 L$ N+ {7 R. L! o+ W
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,7 [7 z4 q4 P" |& J, s6 \
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。
::
pragma solidity ^0.4.11;& i' a0 d! L9 |0 L! |8 v
contract WithdrawalContract {
address public richest;
uint public mostSent;+ b7 {# R& z- p& b; l6 g+ Y8 \2 P, t
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() public payable {3 G5 m5 k* O$ _8 }5 H: |# w; M P- H
richest = msg.sender;
mostSent = msg.value;/ ^. k/ n" T4 O( L2 w7 n
}( w M: E3 T0 s- X
function becomeRichest() public payable returns (bool) {" B6 r1 Z5 I' c1 X% f# _( X
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;. u- V! d' t$ s, b
mostSent = msg.value;$ x. d: ]0 r6 C2 N. V8 Y( r* m
return true;
} else {: M. n% N- n2 W
return false;! F* W6 ]; x; m% }4 d
}0 |6 z6 e' x& ]
}6 V l& T+ M4 z
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];; t' s/ d( x+ z6 f* N. U A2 g& H
// 记住,在发送资金之前将待发金额清零# f# m& f+ M5 t) r7 n$ c
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;8 ~3 T' J8 Q. g% t0 h. }8 I9 R1 K
msg.sender.transfer(amount);4 X c" H/ ^$ O2 r7 ~5 Y% I' d; ^, D7 D. @
}
}! d' ]# u# `( `; U0 p+ j1 |- T
下面是一个相反的直接使用发送模式的例子:
::
pragma solidity ^0.4.11;; l9 {/ W" z0 }, v! Z: N
contract SendContract {
address public richest;
uint public mostSent;5 a; M1 e$ @) q5 B4 v# r
function SendContract() public payable {$ V3 \" k" |% I E. L% f, x
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) { V: i6 D3 n) ]$ r0 S7 Z0 Y! h
if (msg.value > mostSent) {/ \: j8 Q0 x% h+ Q
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);( v+ B! W+ A) c6 `8 I: d
richest = msg.sender;7 S6 \ Q4 }& [! D5 k: h
mostSent = msg.value;
return true;& ~7 M; j3 J1 y; H( g
} else {+ h5 H- ~! z- o! Q) I
return false;4 ^( p! J+ r" n$ t7 o) Q/ P7 y: u) a! R
}
}) G6 }4 F0 g% O+ g' N, l
}* E$ n6 L# J! X' n5 u+ Z: }
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest$ q7 E% o1 b4 o5 l/ a# h
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。; j# ?8 ]9 v; X, j2 o# d7 R! U
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。; K9 v* u. |* [7 C! ^6 z
… index:: access;restricting
( Q; `/ i0 i; n, F8 F; ^# P
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。6 A4 q# D' J, k2 g* h+ G# W* D
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。+ N7 g- y: n6 m" y$ r$ o+ W
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。1 I7 {2 j# _( @+ v. P6 @: R7 K
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。; S9 C5 R) Q! _& Q6 J5 O# G8 e
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。
::
pragma solidity ^0.4.22;
contract AccessRestriction {* \* L( q) T, N. P y# V( t
// 这些将在构造阶段被赋值" X) P+ z7 J# i
// 其中,`msg.sender` 是) Y# `9 ]. q4 R1 c% L
// 创建这个合约的账户。% y( n8 O6 u8 q
address public owner = msg.sender;6 Z7 O) M1 n( Q$ a! [+ n' X' N
uint public creationTime = now;
// 修饰器可以用来更改
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)6 ]2 h; F E G
{
require(
msg.sender == _account,0 I9 v# n2 U6 q9 _
"Sender not authorized."
);0 z, V. U: m' h& T8 F; g* I
// 不要忘记写 `_;`!' d' A7 k7 h+ P% H. G4 N
// 它会被实际使用这个修饰器的
// 函数体所替代。
_;
}
// 使 `_newOwner` 成为这个合约的) J. }/ p. W s5 S$ l
// 新所有者。
function changeOwner(address _newOwner)
public ]& k1 c1 \$ K% w: m# q9 ~
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(
now >= _time,( c6 Y* B) e/ m3 |- R/ D: N) c6 ~- |+ G
"Function called too early."
);
_;* @8 W. T0 N; U6 x3 P8 I" V2 ]3 F$ q
}
// 抹掉所有者信息。* D! s. A3 p& A# T/ j3 H
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。- q/ M% K$ q* Y' k9 {* f2 z
function disown()
public0 g4 b* r/ V, @7 W; L$ _* s1 V
onlyBy(owner)
onlyAfter(creationTime + 6 weeks), V/ C4 Y6 d3 f( l, _
{
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。
// 如果调用方发送了过多的费用,, `9 G! p. S5 k
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {! V2 j e6 F" w X6 a
require( J$ ?3 B$ L4 U1 h
msg.value >= _amount,
"Not enough Ether provided."
);, E3 m# l) m. ~# g% \
_;' ]; W6 u! Q7 ~5 D7 q+ L
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
}% m3 Q( g* m' `5 T Z/ u8 Y
function forceOwnerChange(address _newOwner)* }2 V0 |9 w. _( {! M
public- H$ i6 U& n- I: a
payable
costs(200 ether)) H" Z' @$ e8 [+ d7 a
{# t. _: |7 M8 u5 g' X& V5 P
owner = _newOwner;
// 这只是示例条件
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的7 ^, O3 O9 |, b" g1 S$ r
// Solidity 上进行退还。% K" ^" \1 _7 C* E
return;
// 退还多付的费用
}3 O& v; L) `! j# D# H H4 z( B0 O
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。9 D+ i9 ^2 g8 r7 Y0 a0 W! Y
… index:: state machine
状态机7 {- z7 W5 X% c! E
9 n: y p* F; Y
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,1 x( R& r6 q8 N& V9 t
然后转换到“公示出价”,最后结束于“确定拍卖结果”。, ?# m \$ r5 |& H% o0 t
… index:: function;modifier
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,8 u4 N& W) D! p' m9 V% ~6 v! [* t
它应该用在所有函数上。7 D0 [+ E/ u5 P c
… note::/ P! e- o: n7 X
|modifier| 的顺序非常重要。& ~/ G; T" Q. ~+ P2 [; z) q$ ]4 z
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以
首先被反映到账户中。
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。; ]; D" O4 l4 c& D" }
… note::, |" q+ y- u: v5 T, ?' h
|modifier| 可以被忽略。: h/ r3 C) Q" K9 ]0 W2 z4 i
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码' d0 D% i( J- E: k% j% R1 k8 X8 C
而不是使用函数调用来提供的," C* u4 H6 o% c
如果函数本身使用了 return,那么 transitionNext |modifier|( f3 `8 @' d: P2 V
的代码是可以被忽略的。! R3 {, P0 w5 h( P; t+ n
如果你希望这么做,& _+ g+ s' j2 i, ^
请确保你在这些函数中手工调用了 nextStage。
从 0.4.0 版本开始,即使函数明确地 return 了," K0 ]! k% }3 [
|modifier| 的代码也会执行。
::
pragma solidity ^0.4.22;( U! v4 m+ d; d& c h% ?& Q2 L
contract StateMachine {
enum Stages {
AcceptingBlindedBids,% j0 m- P% b& P% ]4 X9 f' [% O
RevealBids,
AnotherStage,4 }% U0 j; Q' b7 k, `
AreWeDoneYet,3 m; D5 J1 W T) l. u
Finished
}
// 这是当前阶段。( \, j6 H# q1 d
Stages public stage = Stages.AcceptingBlindedBids;& s7 r& V7 o4 ^2 G
uint public creationTime = now;* V- ^ H# W- r) r* G. w
modifier atStage(Stages _stage) {
require(( R0 T* T( x1 `+ _7 ]$ b7 a" E
stage == _stage,5 Y5 x$ c3 m9 v4 E# Y) n
"Function cannot be called at this time."- |$ h; |8 h- D' _- C' u
);
_;
}
function nextStage() internal {5 m9 S, U8 Q9 ]5 D7 q% x/ f
stage = Stages(uint(stage) + 1);2 z+ @8 U, Y5 Y& I' S8 x
}5 V1 B: R7 S; n6 {( ~/ E
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,* ?; _! |% R& G6 { T7 o$ `
// 否则新阶段不会被带入账户。5 \# b6 D/ M* R- S$ H
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage(); l/ z& O7 S$ E: h* p. b0 q. Z
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
// 由交易触发的其他阶段转换, V1 U Q- X- K! D s6 {+ G: d/ B
_;
}
// 这里的修饰器顺序非常重要!7 |: Q1 Q& ]1 x( y1 a
function bid()
public! @% m8 O( ~7 u# I( Z; P
payable
timedTransitions4 K! C1 p5 z/ f6 T6 N9 q0 ^
atStage(Stages.AcceptingBlindedBids)1 o. \; f3 P4 P9 k- t
{, U% k* c4 g5 J: [
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
}
function reveal()$ u( ?2 k$ w% @1 a, R! @
public
timedTransitions
atStage(Stages.RevealBids)& @/ e9 g$ G0 g7 a
{
}
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。! t1 o& k5 o( V5 U2 ]
modifier transitionNext()
{, q& r& e$ p% e3 ?9 E/ O
_;- Y6 l$ U9 l* A; N) Q
nextStage();
}
function g()
public1 i) p" S2 A5 ?
timedTransitions
atStage(Stages.AnotherStage)
transitionNext! l; c1 Y' c c+ ^, C
{# G& w& p9 u1 u$ M
}
function h()( y% [, {; M+ ~1 p( |- y
public
timedTransitions. A( B5 b& T3 W
atStage(Stages.AreWeDoneYet)
transitionNext
{, t, n7 L9 b0 w
}
function i()% A3 Y' r; A0 R" D
public
timedTransitions4 Q; ]7 y% k: s, H6 Z6 v) E
atStage(Stages.Finished)) n3 _: [& m, h6 _' q
{
}
}6 t' {- T5 u' s$ J$ X
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人