Solidity通用模式
温室小书生室d
发表于 2022-12-31 19:01:28
530
0
0
& c1 R% s2 f8 Q, ]( |
在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用,9 ^% L5 q+ _# N9 ^: o, N
但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 :ref:security_considerations 来获取更多信息。) B" X0 \8 b8 }- F
这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”,
其灵感来自 King of the Ether _。" d4 I9 W8 m! W
在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。, x9 X; r- H( r2 }6 ^, O
::0 m. l# H) h6 f* o
pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest;% U w5 t# u7 w) q- L$ N `9 Q& s
uint public mostSent;+ r$ K/ O$ l- l: e ~, T M
mapping (address => uint) pendingWithdrawals;$ n+ x4 |" S" g5 b7 I
function WithdrawalContract() public payable {
richest = msg.sender;8 b: h% F( @- P+ v* `
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;9 T- z& G) B3 b/ ~3 n- i/ y5 C
richest = msg.sender;# H3 D( L5 _" t& v( u% D# h3 ?
mostSent = msg.value;
return true;
} else {3 Z$ [8 o0 s3 m1 x) ^2 c. C- Y
return false;/ e- x: d3 f" N1 U5 p' o
}4 V8 v$ g& G! n7 k2 n
}
function withdraw() public {9 }& ]* ]2 e5 N4 x( \, T6 K' X
uint amount = pendingWithdrawals[msg.sender];
// 记住,在发送资金之前将待发金额清零
// 来防止重入(re-entrancy)攻击3 [$ y; L& t' U; b0 k9 i' @
pendingWithdrawals[msg.sender] = 0;$ P( R0 k$ A2 q5 \
msg.sender.transfer(amount);: ^3 {" v( D9 e" Z! z
}
}+ H/ w( ]) X2 a f$ l
下面是一个相反的直接使用发送模式的例子:, Q/ h" Q- R0 a+ F1 x
::: m' W/ B# _2 s7 R# d3 e
pragma solidity ^0.4.11;
contract SendContract { S/ Y/ m2 {( B3 c
address public richest;
uint public mostSent;& }3 d) ^- X' R% U9 o
function SendContract() public payable {, ?7 q: a# m& r* s
richest = msg.sender;
mostSent = msg.value;
}. u# o4 ^) B C. U' I$ v6 y. t. q
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// 这一行会导致问题(详见下文)
richest.transfer(msg.value);
richest = msg.sender;& C! v3 B) Z' d8 Z" }! n% o
mostSent = msg.value;' H8 k _' c# g& `3 C7 s1 H0 g# k
return true;
} else {
return false;
}) o2 L- h, _6 R3 ^) V
}' r [4 h e1 W
}0 @1 n$ }% A4 T0 z
注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest
(可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约
发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。) t$ [0 V' n8 E/ t/ @& R
如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。) ~* I6 a3 }2 ]. k, J, \6 u9 i
… index:: access;restricting
( r& f1 i# N' Y. S
限制访问# P7 U/ w, B/ O& X% I
限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。 H8 t: x$ o& H4 D' y
你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。
你可以限制 其他合约 读取你的合约状态。( s- j# x( \9 G$ j
这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。
此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。* ]3 U; {/ L; W
… index:: function;modifier0 S' J' Y' q/ J5 E6 c7 [
通过使用“函数 |modifier|”,可以使这些限制变得非常明确。3 U* B1 I% o0 u: j3 P0 F
::; _; x; w% }( A! _" ]+ k
pragma solidity ^0.4.22;; y! L0 N. N" |
contract AccessRestriction {
// 这些将在构造阶段被赋值
// 其中,`msg.sender` 是- y" X2 ~: f2 I+ r; o
// 创建这个合约的账户。: }- j, b- j) M/ Y
address public owner = msg.sender;
uint public creationTime = now;% N6 M4 v# F% A! e
// 修饰器可以用来更改2 R ^% A6 P+ O+ S2 s F+ j
// 一个函数的函数体。
// 如果使用这个修饰器,
// 它会预置一个检查,仅允许, V# ~8 O& y7 x/ X
// 来自特定地址的
// 函数调用。
modifier onlyBy(address _account)
{- v Z0 U+ n! q3 I3 s( O
require(: U5 P8 G0 f( E) C
msg.sender == _account,0 ]# P5 L& X; y
"Sender not authorized."0 N5 i$ ]6 T1 j5 Q" M: A0 J
);- B2 J% Q$ Y0 H8 ~# a6 f* e% V2 v. G* J
// 不要忘记写 `_;`!: {& o# L8 b( x3 ]; Q( y
// 它会被实际使用这个修饰器的' C, ~! \% t3 R/ q2 q+ J
// 函数体所替代。 l4 c! \& I' n% m
_;
}( b n% w# k5 D% C1 s$ _
// 使 `_newOwner` 成为这个合约的4 ?8 A: F. {- ]- o! g r
// 新所有者。% t; O5 \+ T: b
function changeOwner(address _newOwner)
public
onlyBy(owner)& j. F1 g4 h+ d- j
{
owner = _newOwner;# I3 L9 D. A+ U# t9 w8 ~2 N2 l
}
modifier onlyAfter(uint _time) {+ O( x4 T7 y, R# V( |
require(
now >= _time,( p# x! B' h/ _+ V, U
"Function called too early."
);! d, C, o& Q+ D$ m- v( U7 z$ l3 P
_;. O7 ~# Q0 G% ^- V
}7 }5 i! I6 c, a! `; n% ?; `
// 抹掉所有者信息。5 O" V* g# Q: f: ?8 A
// 仅允许在合约创建成功 6 周以后
// 的时间被调用。6 m1 F5 d2 r% o7 L' N
function disown()7 P: ^7 O# W7 \
public
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)4 X! q/ z8 g5 S, H" A
{
delete owner;
}7 {# Z L: T+ U
// 这个修饰器要求对函数调用# X' N0 f* f' s- V' V2 m& v/ r
// 绑定一定的费用。
// 如果调用方发送了过多的费用,) I( O; i5 ^. }- ?' A
// 他/她会得到退款,但需要先执行函数体。
// 这在 0.4.0 版本以前的 Solidity 中很危险,5 l0 M3 ?" J+ h+ Q6 r
// 因为很可能会跳过 `_;` 之后的代码。! b8 \: h: O5 g' y
modifier costs(uint _amount) {
require(2 U( _$ }+ F8 I
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);5 G# U/ z+ m S7 H* t
}
function forceOwnerChange(address _newOwner)
public
payable
costs(200 ether)2 ]5 f; Z& a" D+ x
{
owner = _newOwner;, N* I7 w- w+ }2 S( j
// 这只是示例条件" O$ a4 h( y9 H9 i8 _; g
if (uint(owner) & 0 == 1)
// 这无法在 0.4.0 版本之前的
// Solidity 上进行退还。% t# Z( o) b& l4 `
return;0 T7 m- m& ^" l8 W& \1 Y1 {" Q, c
// 退还多付的费用( `/ p0 r4 z2 ^" U6 L% `
}
}" _3 p4 b5 ?9 A* r( t
一个更专用地限制函数调用的方法将在下一个例子中介绍。( C% B9 K$ y }2 o4 p- s. O
… index:: state machine
4 ^/ U/ i. F. G4 @" v7 }
状态机
% e! Y X2 {& g: N6 k
合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。
一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。6 o+ J0 O4 O( G4 n |: B9 A
通过达到特定的 时间 点来达到某些阶段也是很常见的。1 Z7 j, u4 X; f* r
一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”,
然后转换到“公示出价”,最后结束于“确定拍卖结果”。9 P' ^0 I5 L7 \5 R$ r0 i
… index:: function;modifier* t/ R+ D- s9 M# `. y# p
函数 |modifier| 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。; u/ J& {: E5 v
示例
在下边的示例中, |modifier| atStage 确保了函数仅在特定的阶段才可以被调用。! h& n9 \4 M# O& s) b
根据时间来进行的自动阶段转换,是由 |modifier| timeTransitions 来处理的,
它应该用在所有函数上。
… note::
|modifier| 的顺序非常重要。
如果 atStage 和 timedTransitions 要一起使用,
请确保在 timedTransitions 之后声明 atStage,
以便新的状态可以
首先被反映到账户中。
最后, |modifier| transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。# G# h4 j6 i9 O$ E: v5 Q
… note::
|modifier| 可以被忽略。
以下特性仅在 0.4.0 版本之前的 Solidity 中有效:2 f, Y9 j9 D* M! v$ x
由于 |modifier| 是通过简单的替换代码
而不是使用函数调用来提供的,
如果函数本身使用了 return,那么 transitionNext |modifier|
的代码是可以被忽略的。
如果你希望这么做,
请确保你在这些函数中手工调用了 nextStage。
从 0.4.0 版本开始,即使函数明确地 return 了,9 a1 ^; m/ a# W$ Q
|modifier| 的代码也会执行。1 A+ T0 U& a- c
::
pragma solidity ^0.4.22;
contract StateMachine {
enum Stages {5 y* A" l0 I5 u/ H; \( b
AcceptingBlindedBids,
RevealBids,
AnotherStage,8 K, N7 z# q( g- j, P
AreWeDoneYet,* W" A( r" S4 K! t9 m3 l
Finished C: [* j0 b; X: H8 \5 J' ]1 M3 [' o
}
// 这是当前阶段。& s, m" U6 o, d, J4 {5 d, r& v
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {, d( c, T5 [3 d* o6 O2 N. \9 X
require(6 }' d! F4 i8 s4 ~: k; f
stage == _stage,8 F9 \% C( a* G% v, C5 S; e
"Function cannot be called at this time."
);
_;& r2 o3 q0 O$ J8 m K( c: q
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);& ^/ T% t% A5 T- R
}
// 执行基于时间的阶段转换。
// 请确保首先声明这个修饰器,
// 否则新阶段不会被带入账户。
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)8 ]) `: K$ V! P) u
nextStage();
if (stage == Stages.RevealBids &&& `# x6 x1 w7 K/ L [. Q
now >= creationTime + 12 days)
nextStage();
// 由交易触发的其他阶段转换. y+ w( y( `' a9 T+ V& k+ `
_;
}) T- [' U4 K% U8 ~" f8 j7 G- ?
// 这里的修饰器顺序非常重要!: W2 V9 X/ G: j1 r
function bid()- d# {% r: a( I+ y- T6 q
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注). w# h0 G3 }+ M( Y- G- i
}
function reveal(). u8 E6 a% Z6 l9 ], \+ B4 n8 v
public
timedTransitions
atStage(Stages.RevealBids)
{
} P: j2 C0 S: x* t' R5 D" L
// 这个修饰器在函数执行结束之后
// 使合约进入下一个阶段。
modifier transitionNext()7 }' ~" Z# q0 A7 |, G/ }
{+ Y3 b( Z- ^* X4 _" \) A, o
_;
nextStage();
}' v# o' `+ l- ~2 R
function g()4 i2 w9 `3 S- Q$ [; z% s
public+ S/ E6 k. V& R# `0 i% d
timedTransitions" d x+ a3 _6 q$ T" @/ g* o; J! U
atStage(Stages.AnotherStage)% ~( O8 K5 d( P& H9 J, |; \
transitionNext) q0 l0 }' E9 Y
{1 ]$ D, G9 M/ r8 q9 F* S+ J2 D. n
}
function h()
public
timedTransitions
atStage(Stages.AreWeDoneYet)+ k6 p$ i, p. F0 F
transitionNext
{
}
function i()
public
timedTransitions8 O& a8 o# v3 l' N% R5 X3 }
atStage(Stages.Finished)/ Z% e* ~8 \) P, a" ~5 {
{
}1 l& z/ T" J: i' j
}
原文:https://raw.githubusercontent.com/etherchina/solidity-doc-cn/develop/common-patterns.rst
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人