Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
413
0
0
+ Z$ G0 Y! [( ~: {9 `: R6 p
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,& ^( j: G) h4 w# X ^" _
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。2 L+ j8 U5 O# O! v9 `( B
::- k6 u2 [' d7 ?+ d4 V( ~: `9 Y
pragma solidity ^0.4.11;
contract WithdrawalContract {! J* D4 U8 y' y, L3 `8 a
address public richest;
uint public mostSent;1 P: n5 }: H) y7 H
mapping (address => uint) pendingWithdrawals;( `" G1 B( R) Z! H. X1 `
function WithdrawalContract() public payable {( e( f/ {) @0 {" S N( {7 p6 C
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {; b7 k N0 m+ {2 t
if (msg.value > mostSent) {8 u& i% M% U+ V7 j! c5 r
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;1 M5 W( O" _1 {+ Z/ V+ `8 |
} else {
return false;/ r e; J0 r! l2 B5 ~0 ]
}
}
function withdraw() public {, }% {7 o6 r2 {; j' N
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);& `1 k; a6 m- c
}( f. j! N0 O: w/ @+ A
}
下面是一个相反的直接使用发送模式的例子:
::
pragma solidity ^0.4.11;
contract SendContract {: `9 j3 Z! b; \% `7 d# X4 ^
address public richest;
uint public mostSent;
function SendContract() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {+ ?# c( p; s0 u1 w
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)6 t& l; W% s1 O7 p
richest.transfer(msg.value);+ u5 R+ R0 O( o" [ [
richest = msg.sender;8 {, b) k' U& G" W( Z& N
mostSent = msg.value;
return true;; C4 z1 E& ^" I
} else {8 P% C3 f# x( z* D, t" @8 G, L
return false;( `" T" c# x, \' y" I% i4 {5 K
}
}4 Z6 Q* A+ m" D2 Y* m! V; r
}
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest( P7 _/ B" i2 i
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约5 @) S' Y }; X
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。/ c3 `2 E: e7 I0 S* a7 X! y
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。7 j, v! O1 E6 {; m6 Q1 c; Y* L
… index:: access;restricting
5 q0 p$ U6 R( K7 d
限制访问
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。6 v. @% w' r6 W" E6 x, r1 W$ L
你可以限制 其他合约 读取你的合约状态。$ m; }9 U# ~) W
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。 P; u/ y$ p3 ^$ }# C" W% {
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。
… index:: function;modifier
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。9 w" M! u, Y8 P2 Y; Y/ s" \7 e
::
pragma solidity ^0.4.22;8 }' ~, r" h. V$ U: m) M; Q
contract AccessRestriction {* n Y+ T0 l2 m- h* l- v6 M
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是
// 创建这个合约的账户。
address public owner = msg.sender;
uint public creationTime = now;* r4 v% ~' Y( Z- Z1 r2 P
// 修饰器可以用来更改9 T2 J& A2 o8 G0 a5 |8 W
// 一个函数的函数体。1 P: M1 X- r& @* w
// 如果使用这个修饰器,! j, z5 b, G3 b
// 它会预置一个检查,仅允许
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)' A; c7 ~- I3 G2 H0 U" K8 k# S) Q
{
require(3 ]! g+ f$ {+ Z s7 s; Y; u$ I
msg.sender == _account,. {6 O* J7 k% w6 b: l# M
"Sender not authorized."
);
// 不要忘记写 `_;`!1 c/ B$ G: }3 D9 |9 o
// 它会被实际使用这个修饰器的
// 函数体所替代。, M% }' `2 L" Y* l: {3 X/ F; o* I
_;9 H3 N1 g9 P$ E- l ^$ {
}; L/ `- D4 f4 _- k( x0 G
// 使 `_newOwner` 成为这个合约的
// 新所有者。
function changeOwner(address _newOwner)( I8 D" J/ O1 G) c: a b1 n
public4 ?9 W, J" D5 q: m. Z
onlyBy(owner)' k) n: ?+ C7 u5 \, C* R. X$ {
{
owner = _newOwner;" n6 t. c' U. b! E
}
modifier onlyAfter(uint _time) {1 Y; x6 L$ |; U& Y/ y e
require(
now >= _time,
"Function called too early.", y0 N: I0 }3 j0 ?0 a x9 C, v' ^
);+ ?" N$ d3 q) ~9 |" l
_;
}
// 抹掉所有者信息。$ X! z' m! ^# }% R
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。
function disown()
public
onlyBy(owner)* m7 A4 [3 r' Y8 y3 R8 X) ?
onlyAfter(creationTime + 6 weeks)* E7 E: F R8 R+ y
{
delete owner;6 ?) X2 r6 r! z: H2 j9 w% P
}
// 这个修饰器要求对函数调用
// 绑定一定的费用。
// 如果调用方发送了过多的费用,8 r+ u$ G+ y+ a# Z X- E
// 他/她会得到退款,但需要先执行函数体。- D6 ^4 z+ M) G' n1 f
// 这在 0.4.0 版本以前的 Solidity 中很危险,
// 因为很可能会跳过 `_;` 之后的代码。
modifier costs(uint _amount) {
require(& V, Q% r! f( ?
msg.value >= _amount,
"Not enough Ether provided."
);" u3 k. i0 u3 x% R; n" i5 X {
_;. V$ x/ x A Z& e9 {+ j
if (msg.value > _amount); B* x5 f: R6 o
msg.sender.send(msg.value - _amount);
}! g( X# V" o; b2 w, o) ^# e5 h& D5 g
function forceOwnerChange(address _newOwner)
public( V* U* y4 I' T: g5 h
payable& @5 M- K0 m8 q. Q, f1 c
costs(200 ether)
{
owner = _newOwner;) @* Y/ W: X e! h1 Y
// 这只是示例条件( d% }( y) K' l$ c+ y* T& z$ t a$ U
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。1 R# U& w8 Y; M" z3 _: C
return;* C' Q6 e+ k# k
// 退还多付的费用& }3 L- P0 b- Y. T
}
}9 }' w4 z# r7 t" P
一个更专用地限制函数调用的方法将在下一个例子中介绍。 s D- e7 {3 G& v. N
… index:: state machine
1 ]: [4 n* b4 _8 t% e
状态机/ ~6 a: g+ w2 h) m) t, u. M3 m
$ x; s4 J! A8 t
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。; j. B, M! A) m8 L0 S- y
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。
通过达到特定的 时间 点来达到某些阶段也是很常见的。+ A3 }- [! f, K3 t; _& ~: M
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。
… index:: function;modifier- q( C: p( {- @5 Q7 M" E7 y3 `) p% O
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。- Y2 h& q" }( v7 h
… note::; n. e/ K. X. o
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,, t; d, l( ^! D9 q' {. ^6 N1 |
以便新的状态可以
首先被反映到账户中。
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:
由于 |modifier| 是通过简单的替换代码# x9 L0 F: N9 f s! y7 @- q# M( a
而不是使用函数调用来提供的,3 ]- Y& Z0 \8 Y. c6 G1 s) }
如果函数本身使用了 return,那么 transitionNext |modifier|$ d4 n% i0 V. e- O" i' x, v* D6 f
的代码是可以被忽略的。4 N$ o; u+ k b3 z7 o$ w1 J8 X
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。/ q! {6 h1 r% D1 Z# M( p$ b
从 0.4.0 版本开始,即使函数明确地 return 了,3 e. z* Y# ?+ c" @. V o- c2 r9 L
|modifier| 的代码也会执行。5 |# `0 i% m7 V3 N7 b
::
pragma solidity ^0.4.22;
contract StateMachine {
enum Stages {1 Q7 G5 \4 Z% D9 Q& g& I' i0 A
AcceptingBlindedBids,/ l8 q. C- x3 |; y( \
RevealBids,% [( X! z' J& e7 ?6 i* \
AnotherStage,; @: W( S3 P9 n j
AreWeDoneYet,
Finished
}
// 这是当前阶段。
Stages public stage = Stages.AcceptingBlindedBids;9 _% N$ n) j1 S$ ^+ p
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(
stage == _stage,* J2 Z* ]) ~6 N* g6 M4 O+ ^
"Function cannot be called at this time." f; v# z. M# H4 a3 m! A h3 p
);& |; f/ y1 c6 i" L" G; S
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。
modifier timedTransitions() {5 r' ~. ^# X$ s
if (stage == Stages.AcceptingBlindedBids &&- ^ k4 z, V w, F- u/ m4 u" J
now >= creationTime + 10 days)4 {8 {* I/ k# O% P4 `6 j
nextStage();3 L, L. W: I+ E' \$ Q: A, R' U# T
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)8 t& A9 f. G' s4 ]
nextStage();
// 由交易触发的其他阶段转换
_;( ^0 x9 u9 w" G$ u
}# M' P: W5 a& p' _7 [3 R# S
// 这里的修饰器顺序非常重要!( F1 U N4 L4 { \; ]; V
function bid()
public
payable/ U, l5 y! y; C9 O
timedTransitions# ?2 N3 B* R% c4 n0 r" P9 \: T
atStage(Stages.AcceptingBlindedBids)+ w9 d m& x# H5 f, k
{6 K/ V8 R5 x& | f& y1 O @
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)3 a' z& @; q; ]* |7 M3 k4 B
}
function reveal()
public& x2 o- q( ~* \# }1 i+ b+ s
timedTransitions6 V% Z7 B2 W N a
atStage(Stages.RevealBids)
{% D |$ i9 z. ^3 x, V! G
}" }4 l7 B0 p. O/ N F- H- ?0 O" o
// 这个修饰器在函数执行结束之后0 F* r3 X1 n3 y) k: w3 y
// 使合约进入下一个阶段。0 M* N& S8 P( c( A/ w# s
modifier transitionNext()
{
_;
nextStage();4 K) T* D: ?" F/ j& ?( p
}& ~* y: @% a$ r1 g% S
function g()
public; g5 f, R' ^: p7 k
timedTransitions2 l1 Y4 y* c3 B/ K% {* [9 v
atStage(Stages.AnotherStage)
transitionNext
{
}, ~0 [& G6 Y: H6 E1 o. q
function h()# g2 j$ O. a* `# i' X- D
public
timedTransitions; N4 z) F$ ?# w/ @8 ?
atStage(Stages.AreWeDoneYet)
transitionNext
{% c* Y* L) l& e& }) D: U
}
function i()1 w- O$ u) I+ |5 _
public
timedTransitions& Y" D, G1 F6 B* }7 ~
atStage(Stages.Finished)& o1 Z! {3 e. |( x" M
{
}
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人