Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
525
0
0
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。) S( x& b$ l7 H+ @1 f
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,4 J9 |( p+ V k0 a
其灵感来自 King of the Ether _。# P/ \$ y0 A! N) L0 O5 n! i
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。& F4 }6 B8 X9 B; f& `
::
pragma solidity ^0.4.11;% {. L( o- o' `1 \9 ^
contract WithdrawalContract {
address public richest;$ A# e0 |, A8 Q
uint public mostSent;
mapping (address => uint) pendingWithdrawals;$ ]. z. ?0 u3 R+ Y0 d# b X
function WithdrawalContract() public payable {$ A: Y2 @% f6 V4 v+ }: C) b
richest = msg.sender;
mostSent = msg.value;9 b5 r B( e" V3 @1 ^
}: ~( i+ X4 U3 k# B! p y" n- }
function becomeRichest() public payable returns (bool) {8 H8 ?+ H* w7 j" e$ `: x0 n" C
if (msg.value > mostSent) {& l$ v, C7 L$ q& a& v1 @) v2 W
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;7 P4 g, ^) T6 H; w2 E) C/ t; D
return true;
} else {
return false;
}
}
function withdraw() public {! w. u7 p+ |" r
uint amount = pendingWithdrawals[msg.sender];: T$ q1 {' k. P/ H5 f& {
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击9 `- {& ^: b/ w% a
pendingWithdrawals[msg.sender] = 0;( Z; f) u) }, x1 C
msg.sender.transfer(amount);4 p. o! W# q. [6 l
}
}
下面是一个相反的直接使用发送模式的例子:" }# I6 h' }4 ~* A- f
::
pragma solidity ^0.4.11;: V, }0 U, \3 C' _. I/ [% t! V3 R
contract SendContract {5 F+ y: W" i6 q4 V: D7 D+ p
address public richest;6 Y/ d# x1 O4 \
uint public mostSent;% ]2 X L4 L9 t# A6 l
function SendContract() public payable {$ B. t/ d3 u3 p
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {8 \: U4 [* A* J8 b
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;' d! f/ ~8 P" C8 V
}
}
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest9 w- {6 X- Z2 M
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。; [/ X. F4 O, @" l; R
… index:: access;restricting
限制访问
) u9 [% l, N% b) ^3 `( N
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。7 P, k+ Z& T# v# \# x6 X+ }
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。3 @" S S4 g, h0 o
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。1 |* @3 F1 ~3 ]; w7 U" [8 c
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。* h8 ?, ]) G+ V: E$ _$ t# T" Z
::6 \$ I2 t+ n# ?; k9 E9 T( d
pragma solidity ^0.4.22;
contract AccessRestriction {) s ^8 `9 p. T
// 这些将在构造阶段被赋值% G: u1 t/ V U( w$ f" U
// 其中,`msg.sender` 是
// 创建这个合约的账户。/ ?2 Q% G; \% @- X- \9 F: y* M* F
address public owner = msg.sender;. h" E4 N0 Q. A7 l1 U: F
uint public creationTime = now;
// 修饰器可以用来更改2 K3 z4 n1 o5 ?. N
// 一个函数的函数体。) k% ^9 T P' W* K
// 如果使用这个修饰器,: b. S& a; K5 p! x
// 它会预置一个检查,仅允许2 m7 u* J( j6 u$ N' v/ m& d
// 来自特定地址的
// 函数调用。6 q. U$ R1 U4 R3 ]3 Y% ^
modifier onlyBy(address _account)
{
require(% P$ E: f( ~4 q
msg.sender == _account,
"Sender not authorized."
);
// 不要忘记写 `_;`!
// 它会被实际使用这个修饰器的
// 函数体所替代。
_;
}
// 使 `_newOwner` 成为这个合约的, D3 n( u3 Z. d% @7 f; P
// 新所有者。
function changeOwner(address _newOwner)+ w' \( h' M% u3 Y- l5 q. b/ L
public
onlyBy(owner)6 s E+ k8 _1 c9 T! y
{8 m" {) Z+ y6 ?% H' f a
owner = _newOwner;7 _( t: j* Q. R( I4 A( u
}2 }- Q5 A' A* O& Q+ m
modifier onlyAfter(uint _time) {
require(* S2 b8 x L- A9 ]! z6 |) `
now >= _time,( m$ p; v$ w+ |. c0 k
"Function called too early."
);
_;
}. O1 {& q) i* o9 V
// 抹掉所有者信息。/ V" B$ x& f4 H1 I
// 仅允许在合约创建成功 6 周以后0 s; [0 L; U- a0 A. z8 D) F" d+ Z
// 的时间被调用。
function disown()
public! b A- ^! s1 [
onlyBy(owner)* N" l8 C7 r+ W* a' |
onlyAfter(creationTime + 6 weeks)1 L: @, O; D# R
{
delete owner;: ^# a8 J5 W& S( p
}* q: e: X5 o: I# T1 Q/ [ D
// 这个修饰器要求对函数调用
// 绑定一定的费用。: d" {1 Q: H9 {) v
// 如果调用方发送了过多的费用,; o, p9 x# y- {) s+ L% ~
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,$ f8 h% D' B( h
// 因为很可能会跳过 `_;` 之后的代码。4 m* L5 Y1 f+ v+ h9 H* W; f$ l/ O
modifier costs(uint _amount) {
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;# @( n, X& N$ b; y
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);0 l- b1 _1 @. F g( e1 ~
}6 q9 c5 i5 O p* L6 z
function forceOwnerChange(address _newOwner)' ~8 Q; @2 Z; H3 U# w4 D
public; z) J! |9 [+ t' {
payable8 ]8 |8 t$ e: T2 X
costs(200 ether)
{! ^! N C; U5 t# U2 K) u6 ~
owner = _newOwner;
// 这只是示例条件5 {( J$ {. I- Q# ]
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的5 \7 v' g3 {: D" Q' [( ^8 G7 k
// Solidity 上进行退还。
return;
// 退还多付的费用5 h9 X9 w$ A; ^
}2 b/ _+ [5 m0 p8 i; P6 e
}
一个更专用地限制函数调用的方法将在下一个例子中介绍。* S( E3 h( q- r9 ~- H
… index:: state machine5 B% ], p8 q6 [1 m3 I
1 J" c. C& z7 v' i
状态机" z2 q% g1 r4 r/ h
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。; \: T( U9 }* m1 l. U
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。6 Z6 {: s G, l5 D. v3 s
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier0 j& m9 z" q1 {
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。0 v9 `$ T3 W; D
示例0 ^1 k3 o6 c. P2 _' c
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。, T) R+ a) X4 M9 g9 V( ]
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。9 B2 S( K" L8 |/ v
… note::( v1 E8 X5 b4 j/ S" b
|modifier| 的顺序非常重要。6 ?& D6 y1 g& n( F
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,& l7 u9 @ \9 u2 L0 W; _5 N
以便新的状态可以
首先被反映到账户中。0 y2 Q r X9 B. e$ ?/ o/ l+ G
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::
|modifier| 可以被忽略。* {5 u* d1 g6 Y' L1 M" o/ {2 L8 c
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|* B, t# D' i: e: @& P$ h s
的代码是可以被忽略的。6 d/ b3 P4 }- r* ]- A+ U, S
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。3 v# @4 U* ~" j, F
从 0.4.0 版本开始,即使函数明确地 return 了,
|modifier| 的代码也会执行。
::0 U$ q, `$ S' ?; o
pragma solidity ^0.4.22;+ j* t: q5 N# V v2 k- P& K
contract StateMachine {. \) c3 a8 g+ F( W5 b- }
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,$ F) A6 k/ r3 C) c* P4 s# O: E1 f
AreWeDoneYet,) K) B1 W9 n4 T0 N6 T
Finished
}! C& d7 O* S& I. x7 }
// 这是当前阶段。7 t# U* X% v% B2 V3 u
Stages public stage = Stages.AcceptingBlindedBids;- B# i3 R g+ S9 [
uint public creationTime = now;
modifier atStage(Stages _stage) {! k: g7 ?/ A/ R* Q y
require(7 K! I0 R+ c* s# I; a* e
stage == _stage,
"Function cannot be called at this time."
);
_;
}* \ D y' m7 C- N
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}& P( A4 c2 ~0 y4 |2 W$ }7 j1 L
// 执行基于时间的阶段转换。- j' d! d6 g) O! E* w
// 请确保首先声明这个修饰器,0 d5 H, p3 e, i, m
// 否则新阶段不会被带入账户。( v% p% z! C5 W2 F0 W+ Q4 l
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&% Y( I( D( U5 g9 y( S5 z
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)" l2 s W' T5 d% @4 ^$ q
nextStage(); u3 u9 V2 x& K* a* }. x
// 由交易触发的其他阶段转换6 d$ r; w$ B( R1 y, ~
_;
}
// 这里的修饰器顺序非常重要!5 P0 B( c2 `" w$ E& t3 M
function bid(); @' d( F8 G' T8 K# y! m
public" v' ^/ o" }8 m; W
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{& C2 y9 ^0 ^& K- i [0 ^+ K& p
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)1 W5 i' _ `2 w8 y$ I, {
}
function reveal()
public
timedTransitions; [# Q& p7 u% E4 O( _7 Y
atStage(Stages.RevealBids)1 R* ~. F7 S7 E8 B3 U* G8 @
{% z9 W9 Y9 E3 g2 o& w/ [+ a
}! ~$ c6 \( V" [: F9 d5 H) A
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。
modifier transitionNext()) X: }- Q; |* e5 x
{+ W2 r- Z# S4 l) |5 t$ W
_;
nextStage();
}) e9 m7 F* X" \
function g()* Z: q% Q7 d# \. l4 k/ V8 z
public' |9 d! ?: c2 b: B- r# Q% U
timedTransitions8 `! n J' N) ^
atStage(Stages.AnotherStage)
transitionNext
{
}# `! C9 Z- U5 i5 Y/ R, h, p0 T
function h()
public* |# {9 f' }7 S$ f1 p( {/ Y5 ]2 Y
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext
{, [$ G7 Y6 J: S$ @# p
}6 Q! Z' q9 @/ n9 V" w: ~. e
function i()" ~% Q ^8 u5 W; H9 W
public
timedTransitions
atStage(Stages.Finished)/ P' z4 k- e I, |0 ~+ J" x t
{8 V3 _$ z& g& H4 |8 M
}
}& Y) s# l3 O, O" m
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人