Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
412
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,4 L/ I7 v" `. W4 d" C! |# u* M
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。8 q9 {& u* @, t; A5 n8 f% A
::
pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest; s5 _, G$ ]1 H& `$ H
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() public payable {
richest = msg.sender;( Z& ?3 U$ W) n; {; K
mostSent = msg.value;! d& h+ ?8 V! C
}; K0 v* P8 r, o- ~* \
function becomeRichest() public payable returns (bool) {( x4 [' Q( z3 \
if (msg.value > mostSent) {- H3 t( Q! K) b, @" S8 z& R
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;4 k1 _9 X4 b9 t7 H9 O
mostSent = msg.value;1 C( ~8 Q: k" f' E' `& F7 |" a
return true;
} else {
return false;
}6 N. x; |1 R3 \ \
}1 N& O5 m/ [7 V6 ]( r. m
function withdraw() public {3 C9 y/ w# X& O. W; I2 B
uint amount = pendingWithdrawals[msg.sender];: K D/ F: V$ w& N% Z- g8 I6 ^* r
// 记住,在发送资金之前将待发金额清零9 ]% W# h8 J% X: k+ C
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;7 x# w1 k+ `5 R8 A
msg.sender.transfer(amount);
}
}# }4 c' X' w' M+ v3 h" [& j8 T
下面是一个相反的直接使用发送模式的例子:, i. o b/ l! i" Y3 |
::& l( S+ U/ c% E4 B
pragma solidity ^0.4.11;
contract SendContract {4 f% ^8 s( Y3 `
address public richest;
uint public mostSent;
function SendContract() public payable {
richest = msg.sender;$ ^( H0 r) F& g0 R4 r" x! W
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;3 F; H1 I3 k, {7 [# P4 b8 Y
return true;- R* p$ r% M) H. P* m: a
} else {
return false;
}
}
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。( [; ~; |: V* A
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。6 _3 _( t& ]! _" ]) ~
… index:: access;restricting
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。8 y+ `: W8 U! @) B) ~
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。% O9 ^5 _8 F8 r4 O! ?
… index:: function;modifier( v. h m3 C) I! B& f
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。; O( m% R- {" ]6 \( a/ f! V
::
pragma solidity ^0.4.22;
contract AccessRestriction {% M2 a( z; L( g
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是
// 创建这个合约的账户。0 g s: ?* {6 T: `. `% g
address public owner = msg.sender;
uint public creationTime = now;4 m8 |) i1 c+ m; U
// 修饰器可以用来更改" e' o( ?' z0 f3 [# y* c1 \
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)
{
require(
msg.sender == _account,
"Sender not authorized."
);9 a" [: r8 ]6 r9 x" x
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的
// 函数体所替代。- l8 t$ `! V3 q! D- X. v; N- G4 O
_;; G7 h. R# }. S3 Y" y0 C$ j# o
}
// 使 `_newOwner` 成为这个合约的0 [5 q7 q! i* n" N4 [7 D
// 新所有者。3 K' Y# C$ k" w
function changeOwner(address _newOwner)
public7 U! c* ?% w$ C
onlyBy(owner)/ J2 {$ @& A* H2 H5 ^* ~ p1 e
{ h' D& p: v7 w3 I0 j( l
owner = _newOwner;$ o2 ` Q) }* c
}
modifier onlyAfter(uint _time) {* {; ], s; G$ {# m$ r
require(
now >= _time,
"Function called too early."
);
_;+ V$ R( }6 w3 Q) x1 G
}
// 抹掉所有者信息。( ^% I7 ~- O; v: R
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。# M% [' ^% }7 s, L/ R, @
function disown()
public1 G! g$ b) X m+ i/ f ?
onlyBy(owner)
onlyAfter(creationTime + 6 weeks); y" V/ b, N. v0 F
{
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。 A8 N8 I+ T5 d2 o7 [
// 如果调用方发送了过多的费用,
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。5 [6 t- R6 Q- |4 z
modifier costs(uint _amount) {
require(# A8 Z: G3 V3 n3 e& E: A
msg.value >= _amount,
"Not enough Ether provided."# Q# E7 z) X) h/ y
);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)7 j8 S/ j6 v6 @" Q! y
public
payable
costs(200 ether): E+ w9 x/ S0 h+ {
{
owner = _newOwner;) L) j2 e0 v) O7 Z7 @
// 这只是示例条件7 H0 u& `' n; {" L" z
if (uint(owner) & 0 == 1)1 F& z' j- ?$ O" d+ u
// 这无法在 0.4.0 版本之前的$ o" r a' O" r6 k( A/ q
// Solidity 上进行退还。; L* P0 r7 o3 A: R* W3 p# m
return;
// 退还多付的费用
}6 s: Z5 ~2 n0 S8 [5 ^& a! j
}; H$ ]5 U' K' }; h% e* m
一个更专用地限制函数调用的方法将在下一个例子中介绍。" B* u2 }) @& Q2 q8 M1 ?# E
… index:: state machine1 A& n7 q, T: W7 x# I
) ]5 v2 ^+ D; }0 h+ Z7 M! K
状态机
# C' `" k9 p; X s% |. @' l- K5 N0 Q
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。/ k) v7 B4 Q( a" J5 t! q. P
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier U0 Y2 H# X) v6 t
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例, o9 r! O# J/ R4 }( Z- s- f5 R
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。 U- ^: g/ u/ u! s/ y; K4 F
… note::
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,8 I0 Q1 }, i* @7 Q; _3 A
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以
首先被反映到账户中。2 m# x/ v @. I: z+ y% A7 H
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。) {* ~3 W& P4 Z. ?1 t$ W
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:9 i' r* x6 c: c% B% H* t
由于 |modifier| 是通过简单的替换代码. I2 w; `/ g, N6 Z! `
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|6 y$ K* E n8 ^. O) u" B# }+ W
的代码是可以被忽略的。
如果你希望这么做,( h4 I8 ]5 ^, S$ h' Y
请确保你在这些函数中手工调用了 nextStage。8 y2 C2 V" I3 q0 L& { p" d, [
从 0.4.0 版本开始,即使函数明确地 return 了,& |- A; A6 Y; ~" v, L9 {) e
|modifier| 的代码也会执行。) f2 C7 D# x& h& E. g
::
pragma solidity ^0.4.22;" a# J. r* S1 j' ~- u K* [
contract StateMachine {6 `9 f* i: T( S( e1 u. ?0 j
enum Stages {2 k, y2 r9 H9 A% M" I
AcceptingBlindedBids,
RevealBids,
AnotherStage,8 C! X+ `- O% L4 ~# `5 d
AreWeDoneYet,
Finished6 g9 ?' Z' r$ M4 M! b D# q4 E
}
// 这是当前阶段。 E: {* E- n) w S8 n
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {* T1 n4 k+ I5 I' a# p! k- \
require(, t7 e* `( z& ^
stage == _stage,
"Function cannot be called at this time."
);% ` t e3 ]3 k- `% n$ P+ A
_;
}. `5 k3 R" N' l9 k
function nextStage() internal {$ e) a2 r( N8 q2 t- g4 q9 S
stage = Stages(uint(stage) + 1);; q" l& d& Q5 m0 r8 b% w
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,! @0 e& ^! d1 L* [& U
// 否则新阶段不会被带入账户。
modifier timedTransitions() {; ~9 F7 y, m4 b
if (stage == Stages.AcceptingBlindedBids &&& p$ `) G& x( N, e8 {* B& W$ M9 G
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)8 `0 c* w0 ~% e
nextStage();
// 由交易触发的其他阶段转换
_;
}
// 这里的修饰器顺序非常重要!
function bid()
public. }" F/ }/ p% O6 A
payable
timedTransitions5 p9 R2 N/ S) q% O
atStage(Stages.AcceptingBlindedBids)
{
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注): X+ n9 S; L( E7 `; {3 K- D6 }, i
}
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)3 i: I6 C% U1 y5 }6 n- J
{: w+ j J' n4 E; |$ h; L* j
}
// 这个修饰器在函数执行结束之后. j6 K- v) y2 a
// 使合约进入下一个阶段。' E, h: f/ n3 g y
modifier transitionNext()3 A0 C2 Y% p. Q4 M: n
{
_;
nextStage();/ e5 q2 X& x5 f1 u; l% {
}% Q- R# V) `' J0 I
function g()
public
timedTransitions
atStage(Stages.AnotherStage)0 @, y. A+ s# |4 n5 u% F5 I
transitionNext
{
}
function h()1 G% o$ a- S: _9 l3 B, P+ m: V
public
timedTransitions2 k% F, n/ @$ V
atStage(Stages.AreWeDoneYet)
transitionNext5 a' o1 {! y! N4 ?9 g% Y. y
{
}9 \% a" j& t. c! l/ e/ W5 a% h) H
function i(). ?7 I. K) @3 d/ U- V
public2 J. d5 g2 w& a+ F3 b2 O% T
timedTransitions( Y9 V4 K. I) ^5 k% k+ j
atStage(Stages.Finished)
{
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人