共同模式
退出合同
在效果后发送资金的推荐方法是使用提款模式。虽然最直观的发送 Ether 的方法,作为一种效果,是直接的 transfer
呼叫,不建议这样做,因为这会带来潜在的安全风险。您可以在 安全注意事项 页。
以下是一个在实践中的退出模式的例子,合同的目标是将最多的钱发送给合同,以成为“最富有的”,灵感来自 King of the Ether .
在下面的合同中,如果你不再是最富有的,你将得到现在最富有的人的资金。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract WithdrawalContract {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
/// The amount of Ether sent was not higher than
/// the currently highest amount.
error NotEnoughEther();
constructor() payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable {
if (msg.value <= mostSent) revert NotEnoughEther();
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
这与更直观的发送模式相反:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract SendContract {
address payable public richest;
uint public mostSent;
/// The amount of Ether sent was not higher than
/// the currently highest amount.
error NotEnoughEther();
constructor() payable {
richest = payable(msg.sender);
mostSent = msg.value;
}
function becomeRichest() public payable {
if (msg.value <= mostSent) revert NotEnoughEther();
// This line can cause problems (explained below).
richest.transfer(msg.value);
richest = payable(msg.sender);
mostSent = msg.value;
}
}
注意,在本例中,攻击者可能通过导致 richest
具有接收或回退功能但失败的合同的地址(例如使用 revert()
或者仅仅是消耗了超过2300美元的天然气补贴。这样,无论何时 transfer
被要求将资金交付给“中毒”合同,它将失败,因此 becomeRichest
将失败,合同将永远被卡住。
相反,如果使用第一个示例中的“撤消”模式,攻击者只能导致他或她自己的撤消失败,而不能导致合同的其余工作失败。
限制访问
限制访问是合同的常见模式。请注意,您永远不能限制任何人或计算机读取您的事务或合同状态的内容。您可以通过使用加密使其变得更难,但是如果您的合同应该读取数据,那么其他人也一样。
您可以通过以下方式限制对合同状态的读取访问: 其他合同 .这实际上是默认值,除非声明状态变量 public
.
此外,您可以限制谁可以修改您的合同状态或调用您的合同的函数,这就是本节的内容。
使用 函数修饰符 使这些限制具有高度可读性。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract AccessRestriction {
// These will be assigned at the construction
// phase, where `msg.sender` is the account
// creating this contract.
address public owner = msg.sender;
uint public creationTime = block.timestamp;
// Now follows a list of errors that
// this contract can generate together
// with a textual explanation in special
// comments.
/// Sender not authorized for this
/// operation.
error Unauthorized();
/// Function called too early.
error TooEarly();
/// Not enough Ether sent with function call.
error NotEnoughEther();
// Modifiers can be used to change
// the body of a function.
// If this modifier is used, it will
// prepend a check that only passes
// if the function is called from
// a certain address.
modifier onlyBy(address _account)
{
if (msg.sender != _account)
revert Unauthorized();
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
_;
}
/// Make `_newOwner` the new owner of this
/// contract.
function changeOwner(address _newOwner)
public
onlyBy(owner)
{
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
if (block.timestamp < _time)
revert TooEarly();
_;
}
/// Erase ownership information.
/// May only be called 6 weeks after
/// the contract has been created.
function disown()
public
onlyBy(owner)
onlyAfter(creationTime + 6 weeks)
{
delete owner;
}
// This modifier requires a certain
// fee being associated with a function call.
// If the caller sent too much, he or she is
// refunded, but only after the function body.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
if (msg.value < _amount)
revert NotEnoughEther();
_;
if (msg.value > _amount)
payable(msg.sender).transfer(msg.value - _amount);
}
function forceOwnerChange(address _newOwner)
public
payable
costs(200 ether)
{
owner = _newOwner;
// just some example condition
if (uint160(owner) & 0 == 1)
// This did not refund for Solidity
// before version 0.4.0.
return;
// refund overpaid fees
}
}
下一个例子将讨论一种更专门的方法,即限制对函数调用的访问。
状态机
合同通常充当一个状态机,这意味着它们具有 阶段 它们的行为不同,或者可以调用不同的函数。函数调用通常结束一个阶段并将契约转换到下一个阶段(尤其是当契约模型 相互作用 )。某些阶段在某个时间点自动到达也是很常见的。 time .
例如,一个盲目的拍卖合同从“接受盲目出价”阶段开始,然后过渡到“公开出价”,最后是“确定拍卖结果”。
在这种情况下,可以使用函数修饰符来建模状态,并防止不正确地使用合同。
例子
在下面的示例中,修饰符 atStage
确保只能在特定阶段调用函数。
自动定时转换由修改器处理 timedTransitions
,它应该用于所有功能。
注解
修改命令事项 .如果atstage与timedttransitions结合在一起,请确保在timedttransitions之后提到它,以便考虑到新的阶段。
最后,修饰符 transitionNext
可用于在功能完成时自动进入下一阶段。
注解
可以跳过修饰符 .这只适用于0.4.0版本之前的solidity:由于修饰符仅通过替换代码而不是使用函数调用来应用,因此如果函数本身使用return,则可以跳过transitionNext修饰符中的代码。如果要这样做,请确保从这些函数中手动调用nextstage。从0.4.0版开始,即使函数显式返回,修饰符代码也将运行。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
/// Function cannot be called at this time.
error FunctionInvalidAtThisStage();
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = block.timestamp;
modifier atStage(Stages _stage) {
if (stage != _stage)
revert FunctionInvalidAtThisStage();
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// Perform timed transitions. Be sure to mention
// this modifier first, otherwise the guards
// will not take the new stage into account.
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
block.timestamp >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
block.timestamp >= creationTime + 12 days)
nextStage();
// The other stages transition by transaction
_;
}
// Order of the modifiers matters here!
function bid()
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// We will not implement that here
}
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)
{
}
// This modifier goes to the next stage
// after the function is done.
modifier transitionNext()
{
_;
nextStage();
}
function g()
public
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}
function h()
public
timedTransitions
atStage(Stages.AreWeDoneYet)
transitionNext
{
}
function i()
public
timedTransitions
atStage(Stages.Finished)
{
}
}