共同模式

退出合同

在效果后发送资金的推荐方法是使用提款模式。虽然最直观的发送 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)
    {
    }
}