Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
433
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。! P0 C7 I6 h6 @/ \
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。8 i2 e8 p/ }( c9 V) v
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。1 K4 E1 z0 c" Y( x
::$ C8 }' e# \, O: E
pragma solidity ^0.4.11;
contract WithdrawalContract {' k/ n% T0 M. d( h5 Z) Z* Y- j
address public richest;
uint public mostSent;5 _+ }2 n6 C, ^: g
mapping (address => uint) pendingWithdrawals;3 F5 r' v) U3 u, X* H
function WithdrawalContract() public payable {- r) m5 ^$ z7 E& T6 F" ]2 M
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;/ T& k( h9 S, r" B
richest = msg.sender;% r+ \) z, d! Y, K' z+ \8 d
mostSent = msg.value;( y/ H4 u" O, u. h) R) b0 d
return true;3 r, x8 w% O9 N$ |5 m
} else {
return false;( A! Z% X- L, F* C1 b
}
}! K; S. W [$ x" w
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];8 q' s5 e4 R/ J L: Z
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击8 q) X8 i" T$ a
pendingWithdrawals[msg.sender] = 0;" }7 R7 ^$ C( O1 _1 ?
msg.sender.transfer(amount);# m+ N, f1 y, ]* z3 W! q E7 x
} f2 z/ L+ q: ~& p) W2 n1 q6 i* L
}! O( R; G" o+ E$ y
下面是一个相反的直接使用发送模式的例子:# Y- f+ y/ Q, j3 Q
::) P( w4 L) w. n4 z* N
pragma solidity ^0.4.11;7 I/ G% o- H( c) q3 ]
contract SendContract {. ]2 }/ `0 b0 X( X+ w
address public richest;" c, G% S. r6 F/ F1 ^5 n% r# U2 V9 _/ o
uint public mostSent;
function SendContract() public payable {7 w8 v8 J' k$ N- {4 u% Z' R4 x
richest = msg.sender;+ F% M7 p# R2 {3 V9 d, E" Q+ N
mostSent = msg.value;$ V4 m* c7 L) p2 M! ^+ r% H6 H0 ]7 `
}
function becomeRichest() public payable returns (bool) {$ }" G0 {* A2 ?/ s0 \+ R9 O
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);+ E7 @& l' ?- S8 g# Y
richest = msg.sender;7 H- V9 d- k2 c0 h! R
mostSent = msg.value;
return true;
} else {
return false;0 v- v, R% f4 H/ Z# P8 {
}; ^0 |) K5 {' \& T/ D! W6 U- |
}
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约) N2 }" y( I- Y: ?
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。' W+ y% p$ L9 L2 D
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting" ~6 e. d' i) m' x
9 G0 n; ^: L, `- v
限制访问5 s5 q. r: e- \
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。" R; j, \/ e8 M) `0 C- Q
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。' s5 s! T) O* B) H6 e; t
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。3 H; ~- {8 g8 {6 S( x
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。9 G) [! H" t8 i0 c% U8 W5 _
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。) i0 ?* @' s$ m3 w
::
pragma solidity ^0.4.22;, { I8 k" H/ {
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是" Z/ k/ ]1 F h( ]
// 创建这个合约的账户。
address public owner = msg.sender;/ B9 `% [# ^* E! @5 f
uint public creationTime = now;
// 修饰器可以用来更改" W, ], t+ r x
// 一个函数的函数体。6 E3 b3 U' Z- Y8 z
// 如果使用这个修饰器,$ K7 O/ k& M" V" |
// 它会预置一个检查,仅允许
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)
{
require(
msg.sender == _account,
"Sender not authorized."/ L8 q1 g* ?" s ]4 B' I# z
);
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的0 p3 G$ d+ F2 q$ V' R- N
// 函数体所替代。
_;+ y d7 G4 B; G6 X* j: r, B
}
// 使 `_newOwner` 成为这个合约的
// 新所有者。3 V8 Z) T* K" E6 O6 B! G3 p- O7 T
function changeOwner(address _newOwner)0 h$ h- K ~1 g( F
public
onlyBy(owner)1 I1 ^! h; P0 G8 E- m1 G6 I
{$ l9 | N$ n/ B( e: P
owner = _newOwner;
}7 M9 u: c" I& D% G
modifier onlyAfter(uint _time) {8 R Z5 P- q( B% h- ]9 n
require(
now >= _time,
"Function called too early."
);
_; a1 l! |, g/ c) m# B5 q# W! p
}5 [$ Y c- w; w9 O4 w' e3 X: F
// 抹掉所有者信息。9 A3 o3 W4 Q) A o0 I# n: b
// 仅允许在合约创建成功 6 周以后, W+ q8 E- r9 m* T) C; ~, _- x
// 的时间被调用。
function disown()# {) \" P2 t5 X Q- Q
public4 X) H/ k' N; R: Y: u3 Y3 l
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;2 v n( r5 D7 j3 Y( ?/ D# P
}& [ K" T! z: t0 L
// 这个修饰器要求对函数调用. ]& J+ Z' Z; t" L U
// 绑定一定的费用。9 A2 [* j4 k# Q1 O
// 如果调用方发送了过多的费用,/ Q: l. m/ h( u! y3 Y$ K9 T
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,2 }& @2 n) Z4 h2 _. i1 S. F5 [) P
// 因为很可能会跳过 `_;` 之后的代码。2 C( f0 ^& x* B+ d) P H6 O
modifier costs(uint _amount) {
require(
msg.value >= _amount,3 O! e" A2 `1 q7 \* [7 d
"Not enough Ether provided."
);
_;8 c& Z4 e& }; i6 o* P) ~$ F2 u
if (msg.value > _amount)9 b0 S3 w% U; z$ |: b' V( [% S( `
msg.sender.send(msg.value - _amount);2 {: O3 v6 S6 Q; O* ?
}
function forceOwnerChange(address _newOwner)4 r& E3 U. P7 z' r
public& [# F6 f1 h" {- c
payable
costs(200 ether), q7 u+ G# U2 |/ m+ @5 R7 {
{6 y7 C% c! Z6 y7 `+ }
owner = _newOwner;
// 这只是示例条件
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。
return;
// 退还多付的费用! o3 G& v) d( x* o, ?) x
}9 i$ x9 v( g+ |+ j. g
}- Y$ N) t# ]4 P: y1 e; v7 l2 p
一个更专用地限制函数调用的方法将在下一个例子中介绍。4 V+ X. B1 M1 K C4 E- z
… index:: state machine
! S/ j9 O# {3 q9 H' d
状态机) p4 H, G) p+ R/ J5 J
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。 x: ~0 N' }: ]" X' x
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。0 L% z6 G1 {/ l4 f6 L) ]( g
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。% p) Y' f: k& G- ?
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,: k! R- [. z$ Y% @' e/ q. R
它应该用在所有函数上。
… note::6 i/ \0 y5 q' |% \+ b, x% c
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以' a+ ^& Y% {9 m1 |; q" @% e+ h
首先被反映到账户中。
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。: H7 b; | @1 s0 |% X2 }# y! ?
… note::
|modifier| 可以被忽略。$ e; [" A5 H4 M' P
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:, {2 l4 Y7 Z8 b# X
由于 |modifier| 是通过简单的替换代码0 }" X# K% R1 H+ `6 B Z2 B
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。5 Q* a9 s; u+ {7 {9 l& E3 `, Q
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。0 T' a3 ~8 M0 B% |4 s( }
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::9 k2 r+ ?0 S, _. |
pragma solidity ^0.4.22;
contract StateMachine {" z* G% F3 H+ k$ Q
enum Stages {2 T3 b9 O1 c' r( U! c6 U# {
AcceptingBlindedBids,1 w& Q O$ R/ h% ]8 E: q0 ~5 ]/ c
RevealBids,
AnotherStage,; G* }& c" P, D/ {+ O& F
AreWeDoneYet,
Finished2 r2 S+ E$ c- p% G; h& w$ ?% ~
}
// 这是当前阶段。, Q, I6 w' M: o, W# T: \& o6 `
Stages public stage = Stages.AcceptingBlindedBids;7 F7 l& I! U( n4 ^$ A( v/ k) D7 s' d
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(9 I0 y0 i, t0 ^* k9 k, b/ P3 O
stage == _stage,2 b* I# M; c1 F* K1 k9 B
"Function cannot be called at this time."4 ]( G; X* b! ~$ a% T& [8 q
);
_;
}& ?2 d2 F) `- w; L
function nextStage() internal {( n8 W" y1 I! _3 N& I9 \* J
stage = Stages(uint(stage) + 1);! g) Z" E2 F' m7 E9 y7 o
}5 {4 P; a) r# g1 h3 g1 G* \& a- C
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,2 B. r+ Z+ ]5 I0 `
// 否则新阶段不会被带入账户。
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days): P' V! n1 C, L# H: w
nextStage();0 [9 H; C: [% P2 X! t1 {- H, G3 Y- {# t
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)+ s3 j8 c+ `/ |# N
nextStage();5 e! V* L& W' n7 X+ V
// 由交易触发的其他阶段转换8 [9 \ @$ } B8 N$ g7 | D8 W
_;
} c. m6 x* ]" W, F% K4 u R
// 这里的修饰器顺序非常重要!
function bid()
public f) a, u6 n2 J& c% k0 w0 a8 g
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)' L0 @% ]& G. I
{, U8 p; |. [2 D/ }
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)1 E S4 p( Y+ s/ C' F. B
}* ~' z' J! R' s% G0 S
function reveal()& G% z8 u) f( j2 q
public
timedTransitions
atStage(Stages.RevealBids)6 \+ D( [2 X/ o* s- u6 `
{
}$ W' ^9 ^% C- F) z7 a3 V$ v* O
// 这个修饰器在函数执行结束之后+ k$ Z! r" q9 a# A, B
// 使合约进入下一个阶段。
modifier transitionNext()& E0 }: `1 R" G# K+ }/ z
{
_;
nextStage();. x& p2 n$ l9 Z
}
function g()9 l# T3 s8 W$ e( }1 t
public( O; g: G' J2 y2 b& h5 K
timedTransitions
atStage(Stages.AnotherStage)% a5 M7 G6 ]& P% H$ E$ U& C! O
transitionNext6 C% c& O- \ J0 m% b% S
{* n% C9 B' O' l. `4 I+ u5 [
}
function h()
public. Q0 s- M! y/ ~
timedTransitions9 D5 y8 p: ~1 X; ]/ [% j' p
atStage(Stages.AreWeDoneYet)
transitionNext
{
}9 T4 V0 T `' l3 t
function i()# Y! Z M. o' g' W# S
public( b. j# n6 w' v* `) i
timedTransitions
atStage(Stages.Finished)$ E3 r" N! L5 p
{
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人