Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
185
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。5 ]% z: p% q1 x/ V L$ u- @- ?' W
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。6 ?2 F# T; m; j3 R
::
pragma solidity ^0.4.11;
contract WithdrawalContract {! p! m% k) G3 Z
address public richest;& \* h8 H4 z2 C. N
uint public mostSent;
mapping (address => uint) pendingWithdrawals;% i* N, o9 S$ W1 y7 ~
function WithdrawalContract() public payable {
richest = msg.sender;
mostSent = msg.value;+ |7 D0 T$ l+ R4 ~) `
}$ `& ]1 D7 n- L' {, l, Y2 C6 x
function becomeRichest() public payable returns (bool) {" j- z3 K7 A) s$ _0 p/ G
if (msg.value > mostSent) {, ]) j# I7 n# ~1 S, B
pendingWithdrawals[richest] += msg.value;% n7 @( Q" T1 S5 L9 d
richest = msg.sender;, P+ {5 p1 L% M, t0 Z
mostSent = msg.value;4 ]1 z2 n& O9 x: F+ _3 e
return true;
} else {
return false;
}1 w$ X: f0 ^3 j
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
下面是一个相反的直接使用发送模式的例子:) g' f( d4 O7 Y3 |
::( `+ A' D/ a) ?) o% g( \( Y) W
pragma solidity ^0.4.11;# N+ ]. p3 l$ }$ E$ t D" H7 g
contract SendContract {) e! q/ G9 ]2 }0 k
address public richest;
uint public mostSent;8 g- h& a: K2 m& [- z3 @( h/ \
function SendContract() public payable {. Y9 h; z: L5 N+ R, c! F
richest = msg.sender;' Y* j; R( s4 g; {, `! b. q; c3 l+ v
mostSent = msg.value;1 @3 ^; T, n1 U
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {2 H: Y# O0 V9 n2 ~; g3 m
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);# g/ x- m, F: ^; w0 J3 c' W
richest = msg.sender;
mostSent = msg.value;& ?6 Y" z1 v: _7 X2 N
return true;
} else {
return false;" K# V4 F" q: b) b6 s0 F
}) x C5 V( m8 @% U- m
}/ L6 Y' U* j6 N+ p$ P" k; W7 e5 [2 \1 j
}1 ^( d3 C# L( @. d% a
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。# h; V! B4 w ]
… index:: access;restricting0 y6 R) E- m z8 S
0 D4 v% J9 D8 \9 t
限制访问% }( b9 i4 U: Q9 r2 ^& U
0 Z) p+ `1 w8 _
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。2 i1 I) \+ K! h. N# j
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。9 p/ a# I8 s" n3 M3 _
你可以限制 其他合约 读取你的合约状态。. b. S/ t( H* D' a
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。/ y& e( I F# h5 _
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。3 S) D/ S2 ~9 }* `0 @; Y
::2 E" f& v& f6 j6 l f" c# r
pragma solidity ^0.4.22;
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是$ X% b7 x# f( z4 s& p/ V0 g$ T' @
// 创建这个合约的账户。( W% {: X+ D X: X' m
address public owner = msg.sender;( B! t) O* s' o6 W3 G
uint public creationTime = now;
// 修饰器可以用来更改2 s% B9 ]5 ?& F7 l. ?
// 一个函数的函数体。" q; N: [5 A( C1 P% y
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)# Z8 k4 Z2 u3 u
{
require(
msg.sender == _account,' O2 m u' P5 [- e
"Sender not authorized."/ Y$ U1 C' h7 e, F! @$ L
);' Q+ e8 U0 N4 }, ^0 B
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的) i" w8 [3 T8 p8 v4 W
// 函数体所替代。
_;
}
// 使 `_newOwner` 成为这个合约的% A! {! C% m8 @" j& q' d
// 新所有者。
function changeOwner(address _newOwner) `1 g7 C3 j& }! B" _9 x3 Z) m
public
onlyBy(owner)
{0 V/ @1 |. }) c1 S
owner = _newOwner;
}
modifier onlyAfter(uint _time) {8 r7 |; y1 f$ p5 |) d
require(3 U$ [/ {4 Z: h. Q
now >= _time,9 g# O7 g# c6 }2 N5 c* t
"Function called too early."% m A h0 o$ O& I
);
_;
}
// 抹掉所有者信息。- M: f6 |8 D- p- y4 o7 p' ]& d
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。- [1 o: ~3 n6 M8 W
function disown()7 x8 j; z% f' H {: E8 X+ M/ u
public
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)2 A8 K' J8 z5 c( S0 T
{- C4 Z4 T% V N2 r' b
delete owner;( u* E9 e9 q# M |
}6 p9 g4 D$ h6 e& f0 U: h& h
// 这个修饰器要求对函数调用
// 绑定一定的费用。, d1 z+ I# G, ?3 D5 y
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。0 ]; ~9 q- s. A: y9 }' ~! ?
// 这在 0.4.0 版本以前的 Solidity 中很危险,) L9 m3 k- A T' l1 c
// 因为很可能会跳过 `_;` 之后的代码。' l1 `4 z- |" O7 y
modifier costs(uint _amount) {" f6 T' p2 E% c/ D. ]- t% K
require(
msg.value >= _amount, z0 Q5 V ~& V* s/ @
"Not enough Ether provided."' C2 B5 z* q; ?0 O4 f
);& ^4 j6 Q% M; l# d' [, _" z
_;
if (msg.value > _amount)2 o7 t4 H( |6 y
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)9 `+ `* X: v3 X: A% U& H
public% M7 J+ e- w/ M7 u# @# P) Y
payable
costs(200 ether)0 P' A \- x; Y8 ?1 W
{
owner = _newOwner;6 p$ ]* B, p7 y5 a+ |
// 这只是示例条件6 q$ {) q) X- _5 P3 c
if (uint(owner) & 0 == 1)! t. w5 B; g2 u$ [7 h! I
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。) I! ^! T6 a5 v" E( z' W6 f
return;7 _. n- Z; Y. A" q7 H
// 退还多付的费用* K+ D2 r/ B0 `4 s; }
}
}0 {" b* A( m1 J4 g" \
一个更专用地限制函数调用的方法将在下一个例子中介绍。
… index:: state machine
状态机8 i1 M5 E7 N# C% E
7 x& m+ ~+ W, q4 h' `/ J
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,: T5 F' L% w4 x! r f2 m
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier; E" | \' T, p+ z2 f! N+ f0 @: f
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。* `! s* G' @9 ?4 I
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。! h7 {7 Y0 ]5 t7 |
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。
… note::8 {2 ~+ y1 P& {9 s
|modifier| 的顺序非常重要。6 Z) _, |& G D
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以 o: }, }' g; { h* d
首先被反映到账户中。/ U0 R9 ~/ m3 M8 k7 E( C
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::$ W( M% z8 ~4 R5 G f
|modifier| 可以被忽略。( L( O2 y( S* s L3 D
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码7 |+ t* C: S8 w* U! K% A6 M) N
而不是使用函数调用来提供的," N- T# ~) M9 ? s6 \
如果函数本身使用了 return,那么 transitionNext |modifier|' _& p( W: m9 K& f" C$ t2 q3 O
的代码是可以被忽略的。
如果你希望这么做,; S! h; ?2 o2 w
请确保你在这些函数中手工调用了 nextStage。
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::
pragma solidity ^0.4.22;
contract StateMachine {, @5 r6 r/ P3 ^1 w0 N$ `- b% J# P1 [
enum Stages {
AcceptingBlindedBids,- `$ k& t( P5 W# ?) W* w9 b( j6 d% ]
RevealBids,# t1 K. t. [9 y: J4 e) g8 } s- [
AnotherStage,6 G4 d) m0 ?; N: S P
AreWeDoneYet,3 ~4 ]" r; S0 Z) t9 j4 D! E( D9 U
Finished
}. w7 X' K6 D L- I' e7 L# Z
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;. u6 J- z. a3 z% U: h. h4 V9 v
modifier atStage(Stages _stage) {
require(
stage == _stage,6 w1 b& T) g& P0 [5 Z, ?/ i* o+ n
"Function cannot be called at this time.": Y2 O. K6 F" F' C! Q# k
);
_;$ ]: [* k7 J- T3 c6 Y, L
}
function nextStage() internal {) U' b1 F4 ~7 {! {
stage = Stages(uint(stage) + 1);
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,* Z" B- b* ?% _1 H" y& q' {! P
// 否则新阶段不会被带入账户。
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&( h6 _& K5 Z5 y
now >= creationTime + 10 days)1 S6 d, K" {+ V+ e) C
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)6 j2 ~! o* o4 d2 d1 m6 `
nextStage();7 F1 S) K' J. n3 ?5 m& C) I
// 由交易触发的其他阶段转换* n$ i# c) e9 Z2 O2 `, O3 ?/ w! q
_;4 x$ ^8 N* b* f" c5 j2 S
}
// 这里的修饰器顺序非常重要!
function bid()( c: r0 @, j1 r; Z; e- G# p
public
payable9 v0 V S5 B9 Z% A" B) M
timedTransitions
atStage(Stages.AcceptingBlindedBids)% ?, D( W6 j/ X9 y% ?; \+ D
{
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
}. D9 j$ v3 O# y9 P2 R- C$ i# j+ B9 l
function reveal()" G& j4 P1 k/ k0 |0 w
public8 h% y1 ^* v' }% ]2 K- [( h5 `
timedTransitions
atStage(Stages.RevealBids)' b6 Z) a8 ]! ^# x l+ M5 _+ R
{
}
// 这个修饰器在函数执行结束之后# z$ Q* k% S1 {; W2 B8 P# X/ |! A: E
// 使合约进入下一个阶段。2 w) G# s0 i0 F7 z5 R: p
modifier transitionNext()
{# v! }6 F' H( ]/ r
_;3 J3 L k, E( h: S, f4 f
nextStage();
}* \" j/ U# g( T6 {
function g()
public
timedTransitions3 N- u& s0 J2 E
atStage(Stages.AnotherStage)! ~9 j, \8 T9 b7 K# C# ]/ A: `
transitionNext* S9 ?1 n5 a% U# s3 D% ~. e9 K
{4 ^; H# }1 X' o0 `# F u% A6 J2 T6 Q
}( {( G- S% S, R$ L/ o/ H
function h()- e. w- ]' ~$ W$ V( H: v
public
timedTransitions
atStage(Stages.AreWeDoneYet), `: ^0 v" f* K" U: g% L: Q: b5 g7 O
transitionNext
{6 I. b) u% z* V7 M+ p9 B* B
}
function i()0 x$ \% f+ r8 I
public H+ U0 Y3 P; i( h" g" G
timedTransitions; N+ \& c4 g; p8 e% _0 o, z( W+ L
atStage(Stages.Finished)
{% A5 A& }/ Y; Z1 @! r2 h
}1 w5 G2 Y9 P) z5 h
}8 Y3 ~6 y+ u' g& N6 g% G
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人