Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
387
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,& F# c3 t5 r+ ~8 ? n
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,2 ?. { G! y! Q- ?- f8 Q% _6 L- L
其灵感来自 King of the Ether _。4 J6 r9 [0 q% J9 q4 n# T
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。" T9 S* d: S9 n# a6 I6 o+ r/ X: D
::
pragma solidity ^0.4.11;% O. A9 z0 H& C! ]
contract WithdrawalContract {# c9 i) N9 O9 E2 S6 `( Y
address public richest;9 w6 j' O1 B* z+ z' t% T `" Z6 x
uint public mostSent;2 F7 v7 n/ `, |% ^6 [0 u# q
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() public payable {9 z, y' X' B+ o
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;& {2 q1 j* \1 L3 q8 ]
richest = msg.sender;
mostSent = msg.value;
return true;; k4 f& M6 D4 m( j' J. A
} else {) A* Z: B& n, W& a& z
return false;; b% ?8 ?' ~- h3 b) A
}
}
function withdraw() public {0 A0 N- l9 v8 l; F
uint amount = pendingWithdrawals[msg.sender];2 y0 a3 |2 \; a8 i
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击& I3 y- y- E: J' c/ Y
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);) i; _& y& T2 w" T" }+ O/ [# [
}3 M9 r% p1 ]3 q/ [6 Q9 A+ Y, A; |5 G
}
下面是一个相反的直接使用发送模式的例子:& f+ Z' g+ E1 e
::
pragma solidity ^0.4.11;. |1 ?0 h0 F8 S# Z% G
contract SendContract {" r/ O% W6 t/ }1 }- Y9 }# C q
address public richest;) R" L) u- C1 z1 Y' b6 h: @. r+ n; n
uint public mostSent;+ x, A$ I1 S* F: I
function SendContract() public payable {
richest = msg.sender;
mostSent = msg.value;) k& {6 _8 U4 `! X
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {+ Y4 q! {. T& N6 J! _- e
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);7 b. D/ @4 X7 J6 N
richest = msg.sender;4 _* h8 r! V0 ]' _5 v; b- l
mostSent = msg.value;
return true;# h7 Q5 `4 g4 r/ d( l
} else {
return false;$ d' o$ I7 a# p: s0 h+ |- u
}$ N, P" x0 O1 M. Y/ k6 Z( t% l
}
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约9 y V+ V$ A/ E! N# M* L5 g
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。/ V" S8 |8 j5 m
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。
… index:: access;restricting
6 A. g# p/ C. V% |5 N1 d
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。6 z' B; Z" a# X% O8 Y8 Y
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。; {, N' T0 |4 }/ i1 k
::( l9 V1 h$ V. D* X
pragma solidity ^0.4.22;$ c- W& {! U0 v# Y! t! C, D
contract AccessRestriction {7 e; _" L, L+ e
// 这些将在构造阶段被赋值7 ~) @4 j; V) ?/ W( V
// 其中,`msg.sender` 是( R8 C2 m1 U5 S. d' ?& I/ `7 ^
// 创建这个合约的账户。" D4 O7 z) _8 `2 [
address public owner = msg.sender;
uint public creationTime = now; t/ u4 W! t/ K& D* m9 K
// 修饰器可以用来更改
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许
// 来自特定地址的, D7 h/ e3 T& z9 _
// 函数调用。& e( g1 N( K! h8 u: g+ b+ a
modifier onlyBy(address _account)1 R" D T0 Z' {: d! L1 b
{6 E3 {+ {* Z+ E( N6 C
require(
msg.sender == _account,
"Sender not authorized."8 _6 p, q- ?! @7 ?0 {4 i2 B2 T1 o
);, m, t8 E+ d# i N
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的 P; |: @4 P5 W: m0 G; s3 O
// 函数体所替代。" U5 d- I7 G2 q0 T1 \& I
_;7 Z: d- V( b0 M% W
}9 B3 M8 Z4 J: @7 ^' g
// 使 `_newOwner` 成为这个合约的
// 新所有者。" o7 ]7 h: f) r: H7 @
function changeOwner(address _newOwner)
public; Z2 P/ \6 z: j. e% o
onlyBy(owner)5 E) J& {6 V) p" R+ M7 u
{
owner = _newOwner;
}- j) l% P0 J9 g. Q A' |/ @
modifier onlyAfter(uint _time) {& x$ v1 _. K6 A) {1 f
require(8 f4 s z" v& x8 f
now >= _time,! C% ]9 N# y0 ^) x1 n: _; a
"Function called too early."
);
_;! k# p5 Z& C4 g
}) x& U9 ?, u5 g& n% K2 _2 o o
// 抹掉所有者信息。
// 仅允许在合约创建成功 6 周以后8 v1 u) G2 B( T0 j) B
// 的时间被调用。
function disown(); ~ A1 ?% j- k, _. u1 K6 f" W) q
public Q8 a9 K- g8 Y* D, e
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。
// 如果调用方发送了过多的费用,. G! [" i9 c6 ]6 e2 N2 c( [8 F% z
// 他/她会得到退款,但需要先执行函数体。! l } e9 G2 ?
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {
require(
msg.value >= _amount,9 x" c+ ~& D3 J
"Not enough Ether provided."
);' s" h+ X6 w* p6 v8 A4 H3 v
_;# A' T' R* [4 W1 ]
if (msg.value > _amount); e- w* \3 Z8 d3 E$ c/ M
msg.sender.send(msg.value - _amount);" C4 y7 K) B6 P
}5 i; E) k. M# \( o+ @
function forceOwnerChange(address _newOwner)
public$ g* _6 m0 T; j: X
payable9 R* p, j- h2 E. `
costs(200 ether)* V/ @( U* ~& w
{
owner = _newOwner;- Y2 p% K! L9 I! Y
// 这只是示例条件' X6 c& \, x8 r
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。
return;
// 退还多付的费用
}
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。
… index:: state machine
状态机
5 v% p! U0 a- d8 G# @- s% ^
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,* Y# l2 A3 ?" e6 u3 p/ W: ]
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。* Q: e/ I9 |4 p3 U
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,/ o8 k. h& h( m
它应该用在所有函数上。
… note::; u& O( a1 W3 ~1 @
|modifier| 的顺序非常重要。! H! k. H O7 L; K9 n+ N
如果 atStage 和 timedTransitions 要一起使用,; `# w4 h# S; c8 K# \! f
请确保在 timedTransitions 之后声明 atStage,- b. A4 p* T- C! Z% Q' ?
以便新的状态可以
首先被反映到账户中。/ s1 ?+ p, R" ^
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::
|modifier| 可以被忽略。. s& z7 {+ J e: d
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:8 M& G: b5 ?: n V1 S
由于 |modifier| 是通过简单的替换代码
而不是使用函数调用来提供的,+ S" Y: _( [$ {4 I
如果函数本身使用了 return,那么 transitionNext |modifier|3 f# {% T1 k# u% x0 L& v
的代码是可以被忽略的。
如果你希望这么做,2 ~9 ~/ G8 O& f8 Y
请确保你在这些函数中手工调用了 nextStage。% l6 h* E3 A$ x' g
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::
pragma solidity ^0.4.22;
contract StateMachine {
enum Stages {$ V5 U$ _! N" Q; Y! M0 ~1 Q/ c* \
AcceptingBlindedBids,
RevealBids,
AnotherStage,! F/ X9 s( w; I8 W
AreWeDoneYet, E8 W& W; C' O3 e% k! c) g, g
Finished- o2 ?: C' o& S( X$ @! l
}
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;7 ~ A5 ]3 I3 n' O- a6 f1 W9 k2 U
uint public creationTime = now;& x% ^6 ]. u! D2 d5 O C3 O* i& q0 p$ N
modifier atStage(Stages _stage) {8 \: Z$ O2 b- |% @. G
require(
stage == _stage,
"Function cannot be called at this time."
);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);: z1 E" {- K5 v1 }
}
// 执行基于时间的阶段转换。, H% K! \$ g" B! ]* M
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。- r, E, H. ]4 C
modifier timedTransitions() {7 b' G6 O" m0 X2 C! M
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)8 A% H$ H7 k" F
nextStage();6 G. m7 P9 y4 J( e! {
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)8 i. K. X' E |5 {4 u& [5 \" g
nextStage();
// 由交易触发的其他阶段转换
_;
}
// 这里的修饰器顺序非常重要!
function bid()# F( ]6 p2 M( D
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{8 X) X% p; t- \% l
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
}
function reveal()
public
timedTransitions/ i& L1 @# s J3 R/ ?/ X
atStage(Stages.RevealBids)
{
}' }6 M6 [$ x- _. I6 J- B
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。: s' C$ }" ^& J' k- I
modifier transitionNext()* ?! [' l# `( r b: T
{$ q( i3 C1 J4 k6 s, O0 n: B: G2 b* n, x
_;
nextStage();% H5 ^# k% O) c; [3 M
}
function g()
public
timedTransitions1 V3 C8 J$ E5 ^0 s- b& F( t
atStage(Stages.AnotherStage) [- f3 d$ Z$ b
transitionNext
{
}, O0 Z% l& p2 J. W3 y
function h()" {0 v: {! |3 \! j# ?* A
public7 F. A6 M! o: ^9 |7 M8 u( _
timedTransitions$ P/ T, W# J+ u: m5 \5 X
atStage(Stages.AreWeDoneYet)8 Z' n" _8 ^8 ~ z, y3 ~* A
transitionNext
{# Q, o9 _, Q5 J
}# Q) o4 a8 Y3 I: o- {
function i()( a# t8 r! t& J3 Q
public4 ~9 J- U6 L* \! d) k
timedTransitions
atStage(Stages.Finished)
{
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人