Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
523
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,% o3 f. p2 j- c# {0 c" H5 {
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。: |. M9 J( a0 ]! O/ k
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。& z) }' C* _, b2 Q7 e
::- P& r( g0 I8 H# C+ `; s* L: b$ M
pragma solidity ^0.4.11;
contract WithdrawalContract {1 }2 m- L& V, v8 Z1 @
address public richest;! W: {" c; W6 j8 g3 e% ^- `7 V" M0 d
uint public mostSent; o4 Q: P4 ?$ Y, a
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() public payable {
richest = msg.sender;
mostSent = msg.value;3 G9 C6 a) ], |! p% J7 L
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {2 M8 [$ b! h9 |, B e
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;. f) ^( T- L/ @& L
mostSent = msg.value;
return true;
} else {
return false;/ p4 l9 e; `/ o- F
}) O6 w( z- F0 m( }# V
}! n5 c* c; {- |' e
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零' X4 X- a3 k5 P! N
// 来防止重入(re-entrancy)攻击3 n( s6 M! P0 J# R1 P% T3 F5 V
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}! g" ^: ~# ]3 k1 G
下面是一个相反的直接使用发送模式的例子:% N! Y. k4 H. r: |* A: l8 S' x
::* `1 S- ]" c2 N' y6 b8 P
pragma solidity ^0.4.11;; r; ?; J7 p4 b% s9 Z' l1 z
contract SendContract {
address public richest;
uint public mostSent;6 L' `* @5 V8 j' y
function SendContract() public payable {& W# M3 ~) A. G, G! t
richest = msg.sender;
mostSent = msg.value;
}" x. N0 i0 E4 g8 r+ N! @/ b. U
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);# S8 ]" }% \9 `& r& r
richest = msg.sender;
mostSent = msg.value;* c5 }: ^& X& Y4 E p8 a
return true;7 x1 s3 X1 W/ e& E5 ]0 s
} else {
return false;9 {, [3 o6 b3 g) R' T
}: o* k/ G+ e4 R8 G( t" A* y7 }5 Q# ]/ f
}
}2 W: f3 _3 r5 }' E1 g6 y& a
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest+ O" l2 L& ]) i
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting9 n& [: a+ b6 O0 K
5 l3 V4 f' |) F* p" j6 v+ V# @
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。 d8 m9 |' i# K! p7 n) o+ J9 T
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。6 r/ D! t- l2 M6 i7 `
… index:: function;modifier k$ O: _' E9 s, Y; E
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。1 W: @! O* H" X; j) G
::
pragma solidity ^0.4.22;
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是; h* @! r+ u" S8 X
// 创建这个合约的账户。" e1 v$ ]! s- _1 T! Q) ^
address public owner = msg.sender;
uint public creationTime = now;4 h+ d7 X2 k# ]
// 修饰器可以用来更改% g9 f3 u' H9 T( R. n" }
// 一个函数的函数体。; }- Z J* D z+ z
// 如果使用这个修饰器,# R5 ^8 @$ d1 k6 O9 h, x
// 它会预置一个检查,仅允许1 q7 j8 Z a8 g# o2 q# Z/ Z: b
// 来自特定地址的
// 函数调用。: O, u) Q; p" D9 K g% P
modifier onlyBy(address _account)5 L) Q# y w$ @) j: o0 j5 H
{
require(
msg.sender == _account,
"Sender not authorized."
);$ K! E8 t, t" C4 ?: T& W1 d
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的! W; D# L0 [) Z! u
// 函数体所替代。
_;1 t5 K0 m2 z8 I8 E" n! @9 G% l
}
// 使 `_newOwner` 成为这个合约的) M5 D+ _/ q6 R. t) x
// 新所有者。
function changeOwner(address _newOwner)
public
onlyBy(owner)
{5 R; b/ y/ l1 z; R, k0 w E
owner = _newOwner;
} K# y" `3 i9 l9 W0 I* K0 M
modifier onlyAfter(uint _time) {. A: C+ a2 h- J, A4 r
require(
now >= _time,( t/ U" P( |# h& g& w3 V' h
"Function called too early."
);
_;
}
// 抹掉所有者信息。
// 仅允许在合约创建成功 6 周以后. S, r4 x3 V! S
// 的时间被调用。
function disown()& T; o! K1 P% r- f9 T% W
public( c- a; M0 ?: H8 J+ B6 u
onlyBy(owner) V! v3 F3 ]: K: ]. p% i* d; Z
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。2 X7 Y, A+ S d+ N: T
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {& W0 N' D+ Y1 _: f- j4 F1 e
require(6 B }/ {2 C0 N& T
msg.value >= _amount,2 X3 @: Q9 o ]4 Q
"Not enough Ether provided.") c, F# ^( l7 ~$ D( t/ R
);
_;1 w* L! \ u5 ]1 u, M4 T8 m$ C! w
if (msg.value > _amount)* P7 s9 @5 D4 X" p
msg.sender.send(msg.value - _amount);
}" J6 J j% V2 n5 e# O5 N
function forceOwnerChange(address _newOwner); E. u* P8 @* ]9 G2 t
public6 j7 y$ O& j( h2 s$ Y
payable
costs(200 ether)
{3 e/ E4 l* F) W7 F
owner = _newOwner;
// 这只是示例条件
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。
return;% ]4 F3 `: R6 Y9 [5 I" b
// 退还多付的费用
}1 I, z& r+ l3 b% o4 T4 X$ l
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。/ u3 s) P+ G7 e/ M% y% ~1 [1 l
… index:: state machine
, m: P8 c9 C/ a5 S. c
状态机
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。' p( l/ X+ m' `! V7 ^- j
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。: y2 X! ]7 _5 n% ?3 u7 ] `. |' P2 a
… index:: function;modifier, T: h/ @8 @& n8 E
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。+ W* X" t& d' K* ?
示例* O* E- G5 ~5 W0 z: G$ Y
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。 i# _. w4 K4 k& y; x e6 |# }
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。1 @2 N# k0 V4 i' h
… note::
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,3 q* A! P$ w1 H5 G2 F/ y
以便新的状态可以
首先被反映到账户中。$ \5 }# j7 Q5 R+ h. f
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码 I; x3 P/ ^! o+ P& H$ E( J
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。
从 0.4.0 版本开始,即使函数明确地 return 了,. J9 O, Q9 I5 q+ U( {' T$ n& l5 i
|modifier| 的代码也会执行。
::9 j P4 B8 j* s# F; {
pragma solidity ^0.4.22;
contract StateMachine {* }9 _$ y5 n/ L- d. L. f4 d* t* b7 V
enum Stages {
AcceptingBlindedBids,
RevealBids,! Y" m8 L6 M+ y; E# w
AnotherStage,1 ]2 O- [6 l$ t
AreWeDoneYet,
Finished
}
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;; [1 @3 M9 J9 @/ T: X$ e" P
uint public creationTime = now;, ]% @, k* Y6 G& F; W: v
modifier atStage(Stages _stage) {% d' v7 H7 s0 c% V0 K
require(
stage == _stage,
"Function cannot be called at this time."
);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);- p5 ?9 I: m$ Y
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。' Z5 ]9 k& F/ G( ]. [4 B8 o
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&4 H* u" r `9 P! o$ P J }9 j
now >= creationTime + 12 days)" G/ t. L. g" v( Z9 P
nextStage();+ b5 o- T) Q$ H/ e8 w2 d( r( D+ l
// 由交易触发的其他阶段转换
_;
}* C+ ] r( T/ N2 P
// 这里的修饰器顺序非常重要!
function bid(), U3 K/ Z. d& {8 Y
public
payable
timedTransitions+ k- X. Z$ m5 U# U7 b& h$ ~
atStage(Stages.AcceptingBlindedBids)
{& ~5 y2 \9 q, R+ R& X" q5 D& E
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注): }# V/ N4 \ G
}
function reveal()& E W3 Y8 X- k. E
public
timedTransitions
atStage(Stages.RevealBids)
{
}* ?) {% t! T7 d; @8 T8 a
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。% P- n0 j& d; ]
modifier transitionNext()* z, r" `7 g$ [5 z6 O" ~% ]
{( n2 l. R. D, A3 z. Z4 I( a9 P
_;
nextStage();$ o) j8 z* t* c: G6 g8 b
}' f" ]0 |" O# p: u/ i
function g()
public
timedTransitions
atStage(Stages.AnotherStage)
transitionNext6 v' z% N9 I: ?: ?3 Z
{
}" u4 N3 L% X- k% u- R5 V0 e
function h()5 t; W3 X! g" }% O- t8 t* b+ W
public, M% _; U; s! `7 ]$ W
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext; w, P6 t' [" u! x$ F
{% Z% |$ w F/ o. s
}; [7 h( Y' |1 C z: Z, k/ c
function i()
public
timedTransitions8 U& f( ?1 q% J/ [+ w
atStage(Stages.Finished)
{& @$ I9 R( n" V. }: f
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人