Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
371
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。
::
pragma solidity ^0.4.11;1 K. {/ j7 y/ R' [8 Q8 l
contract WithdrawalContract {
address public richest;
uint public mostSent;- x4 O% F/ n( o2 E3 m$ J0 A
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() public payable {5 G# a* ~4 ]" I* p. v
richest = msg.sender; m: M) S: f& ?
mostSent = msg.value; W# e$ }$ N: B4 s$ K8 z
}1 m. D M" Z* o7 A% ]) P2 P
function becomeRichest() public payable returns (bool) {- i4 e4 h4 U) ]+ [- d z- s# @2 y% `
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;. ^" n+ b+ c9 b( F
return true;
} else {
return false;9 T$ f/ L9 V- w& x5 b+ _! ]- F, J
}$ Z1 u2 g" F4 [1 u2 C! P* ?
}4 o' {8 t; L" i: w6 U6 C0 m
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];( ^* r9 J+ ~5 Y+ J( _
// 记住,在发送资金之前将待发金额清零# a+ }! u' w( L/ _, [6 a& d
// 来防止重入(re-entrancy)攻击 [$ t* x0 w2 D3 K4 L$ q4 k' O
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);- s W$ I6 \' o5 u2 w: H
}5 l; ~3 _+ r$ ~
}% f+ I4 h3 H. p, j! P
下面是一个相反的直接使用发送模式的例子:/ A% l' q T# `" p- E# z S
::
pragma solidity ^0.4.11;
contract SendContract {
address public richest;1 u/ t% ? @# X9 S) l) N
uint public mostSent;
function SendContract() public payable {
richest = msg.sender;: q9 V! R# c+ G" ?+ P) T' d
mostSent = msg.value;; n& ]' o, j% q
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {1 h' L$ c; `6 {8 ~1 T, c; m2 Z. H
// 这一行会导致问题(详见下文)7 B9 A' w# U0 o# u+ \+ G+ {
richest.transfer(msg.value);6 K8 f! _/ F% h4 Z7 ]) n
richest = msg.sender;
mostSent = msg.value;2 o" `' G7 h* q2 T: ^, Z$ |
return true;" Y9 R5 H8 t4 t2 R" `& y! b0 d
} else {
return false;
}
}% r) j4 s& ?" f8 Z" V8 h4 w- k/ Z
}8 j9 _1 O4 c) C# C: N
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。! t% _1 Y& a7 N9 D; ?
… index:: access;restricting: d% \% x$ K; x. ^& \; Y
6 m6 w. s; S w! n3 ?
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。/ E0 w9 c& @' h
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。6 \9 P- N" A# b4 J
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。4 E' Z+ K2 H0 ?9 I9 o
::
pragma solidity ^0.4.22;5 S k' g4 d$ v. E9 i
contract AccessRestriction {& { [7 J. t+ D2 p, u" T K- G5 w
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是' }0 T: u0 `$ z* c+ _ F
// 创建这个合约的账户。; W) v. u5 `9 E7 W
address public owner = msg.sender;
uint public creationTime = now;: c5 B+ M" C1 d6 ~- z# X& v( L
// 修饰器可以用来更改9 S9 w! n' o6 r7 @% E f
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许# ^# [; e( d6 b0 q
// 来自特定地址的/ o$ v) k% \: L
// 函数调用。
modifier onlyBy(address _account)
{/ W( K% {5 h1 x7 g# ]
require() {; l% L2 Y3 _, Z
msg.sender == _account,1 M$ z% t; O7 q \- u5 j' |6 B
"Sender not authorized."
);. q. C9 l7 X5 C+ h
// 不要忘记写 `_;`!: m5 M* L W8 w3 \3 U
// 它会被实际使用这个修饰器的% {3 }+ V# l& i* p
// 函数体所替代。
_;
}6 ]5 A7 @. y/ U7 K* J; ]* ?
// 使 `_newOwner` 成为这个合约的' S, Y: q. A# P* E3 W* B$ d
// 新所有者。
function changeOwner(address _newOwner): X0 w8 e6 @7 t. y
public+ i2 M* g1 r$ I$ X) H; b9 `
onlyBy(owner)
{& o' ^$ J4 E$ B
owner = _newOwner;
}
modifier onlyAfter(uint _time) {0 Y7 i- T- z+ j7 Q2 T$ o% r3 G
require(0 @ i0 a1 w) ^2 I% `
now >= _time,0 H9 A4 x0 `& D/ b2 H0 N3 f
"Function called too early."7 u! d$ Y2 @, X% E
); r- R! Y W7 v0 x R
_;+ c" X0 Y8 p, s* j/ }' {0 L- ?
}9 F, ?% ]7 I, ] O( S# U
// 抹掉所有者信息。
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。
function disown()! b0 |9 u1 {% {% t( R' ~0 G- }
public1 ]2 O: D9 s. o, h
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。! g3 Z0 I t$ n' u3 `* E6 x) _
// 如果调用方发送了过多的费用," D) _2 k% U4 s. m3 R/ }6 G' W% H
// 他/她会得到退款,但需要先执行函数体。5 C, \4 e9 _2 ^* \0 M
// 这在 0.4.0 版本以前的 Solidity 中很危险,. p% Q; }6 H* Y7 y' d
// 因为很可能会跳过 `_;` 之后的代码。 W2 f. W5 F" D1 j ^4 @
modifier costs(uint _amount) {
require(( i% u' j/ }# O
msg.value >= _amount,
"Not enough Ether provided."
);
_;. g$ W0 l. t- t" Y8 W2 \; O6 N5 |
if (msg.value > _amount)4 o8 e! a7 |# N. e4 u" x( K
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)
public
payable
costs(200 ether)% t' w' K3 K! z1 j6 S
{3 Q/ ` ~1 ]9 v6 a! X( b
owner = _newOwner;5 m/ p) z+ t/ r& d1 N# h( a+ a
// 这只是示例条件; ]- h1 U; C- d
if (uint(owner) & 0 == 1)' Z1 H- g) v; J7 Q
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。% X( q) D; ]9 U" V* ?# U
return;
// 退还多付的费用4 a/ b# Q, g0 L0 _
}4 J) ~$ ]* g/ Y5 e0 p t
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。: R; @+ D# l, w& I* ^" E- A
… index:: state machine
* V* J6 Q5 I% k' N) b
状态机7 G w/ N8 v: |% Y
- X5 B' r; l! s/ f1 r" J! J4 T. n$ ]
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。1 E% x) V: ~: _7 Y3 G7 J
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。6 O4 H' L( J. `
… index:: function;modifier# O0 H) C" ^3 F
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。/ K! u9 O0 i4 g& ]! n7 h5 n
示例, ?% H: D) p1 c+ Y. G7 [
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。4 x7 g& h, w9 _& @
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。+ V1 y" M1 a# X
… note::
|modifier| 的顺序非常重要。7 {" I7 |- ~/ _9 X! O& X2 N9 u
如果 atStage 和 timedTransitions 要一起使用,4 T, K5 c( P) q% G
请确保在 timedTransitions 之后声明 atStage,/ H: _5 _4 v. y& p& [* I4 B/ W
以便新的状态可以
首先被反映到账户中。' V4 h" W1 A$ Y8 M! {, m: y3 T
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。: x! k+ U% S' _9 U
… note::% ]2 `& d) `& Z# A
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码7 K5 c2 l5 {2 ^1 b1 z* ^
而不是使用函数调用来提供的,, a- t# v% i- A$ ]8 e5 q
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。, \! ]% x! S) A
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。! i0 Z( Q% Y# {- E2 ]1 l& B
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。( ~7 K' a8 L# x: w8 B
::/ c( G0 Q [+ d; r
pragma solidity ^0.4.22;
contract StateMachine {+ R2 i. {% e/ b3 R6 |" B/ S8 `
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// 这是当前阶段。" D! n0 |! w6 I3 R( I) Z/ b: }2 }
Stages public stage = Stages.AcceptingBlindedBids;5 A6 V: K& F- U' {+ o
uint public creationTime = now;
modifier atStage(Stages _stage) {8 ? Q0 {1 ?. c+ c- N
require(. V6 T$ O4 _1 ?
stage == _stage,2 I) u; H5 c( d/ c6 ~5 ] x: K
"Function cannot be called at this time."0 x1 n% l4 T( l
);6 p9 G q, f1 q+ R
_;' ^# @1 X$ {& z
}4 L, i# M0 x3 z7 i; L) i9 t2 m
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// 执行基于时间的阶段转换。" x4 z$ S, Q7 m2 |
// 请确保首先声明这个修饰器,1 m; y* j: x% H* ^$ b" d
// 否则新阶段不会被带入账户。. m G; S6 x5 T) O L4 v- w
modifier timedTransitions() {% J: Q6 o0 e" B
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();/ W+ W+ ^+ X: N, Z$ A, Q# Q! L
if (stage == Stages.RevealBids &&: Z4 z3 ^6 A5 j3 z7 Q. p9 q, p+ k
now >= creationTime + 12 days)
nextStage();) x* d9 f) i/ {" M# N5 W
// 由交易触发的其他阶段转换* k5 a, I+ W" S1 D' l; x
_;
}
// 这里的修饰器顺序非常重要!
function bid()+ H! d( ~2 L9 v5 w1 u
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{6 j' `: x& i: a) M9 U+ J5 G9 _
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
}& d* r f' `& x$ |) n1 v; Y8 x' r' j# C# C
function reveal()
public
timedTransitions' I3 y$ z+ |7 V( K' b* ]
atStage(Stages.RevealBids)
{
}
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。8 U3 j6 V: \5 a1 Y3 }* h
modifier transitionNext()
{
_;- ^: `' L, n4 }- m" m* c
nextStage();% G' q0 i% s7 p. }& v
}
function g()
public
timedTransitions
atStage(Stages.AnotherStage)6 s3 x$ s4 w1 b4 }* D* `- h) A4 v
transitionNext
{& q, X& d9 Y* W& R, l
}* h C; R+ t* A1 \
function h()
public* L; r8 v5 l; M- j; c' w
timedTransitions3 g% b, r; s0 @" d
atStage(Stages.AreWeDoneYet)
transitionNext' U8 n6 W E* t
{! Q0 r$ k9 o0 |" P) w; a/ e5 I; W
}8 ~( w+ B: U/ j
function i()
public7 `% v6 _! }1 Y' v7 e% o
timedTransitions, H; Q+ {: w; ]) X5 y' S% S" y0 A; g
atStage(Stages.Finished)7 @; a2 ?4 h) m4 F5 U) g
{
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人