📜  Solidity – 错误处理(1)

📅  最后修改于: 2023-12-03 15:35:01.886000             🧑  作者: Mango

Solidity – 错误处理

在编写 Solidity 智能合约时,虽然我们可以尽力避免错误,但在某些情况下,错误仍然可能会发生。 Solidity 提供了不同的机制来帮助程序员处理错误情况。

抛出异常

当遇到错误时,Solidity 中的函数可以选择抛出异常。异常可以通过 assert、require 或 revert 关键字(在 Solidity 0.5.0 之前使用 throw 关键字)来抛出。这些异常会终止当前函数的执行,并向调用者返回错误。

assert

assert 关键字用于检查假设的不可变条件。如果假设的条件不满足,则会抛出异常。这通常用于检查内部错误。下面是一个例子:

function giveMeMoney() public {
    address payable receiver = msg.sender;
    uint balance = address(this).balance;

    // 检查合约拥有的余额是否为 0。
    assert(balance > 0);

    // 向调用者发送合约拥有的所有余额。
    receiver.transfer(balance);
}

如果调用 giveMeMoney() 函数时,合约余额为 0,则会抛出异常并取消函数执行。

require

require 关键字用于检查函数的前置条件。如果前置条件不满足,则会抛出异常。这通常用于防止无效参数调用。下面是一个例子:

function buyToken(uint amount) public {
    address buyer = msg.sender;

    // 检查要购买的令牌数量是否为正数。
    require(amount > 0, "Token amount must be greater than 0.");

    // 向调用者发送等价于购买的令牌数量的以太币。
    uint price = amount * tokenPrice;
    require(buyer.balance >= price, "Insufficient balance.");
    buyer.transfer(price);
}

如果调用 buyToken() 函数时,amount 参数为 0,则会抛出一个具有指定错误信息的异常。

revert

revert 关键字用于在函数执行的任何时候返回错误信息。与 require 不同的是,revert 不会判断任何前置条件,它可以在任何时候使用。下面是一个例子:

function withdraw(uint amount) public {
    address payable owner = msg.sender;
    uint balance = balances[owner];

    // 检查要取回的金额是否小于等于账户余额。
    if (amount > balance) {
        revert("Not enough balance.");
    }

    // 取回余额。
    owner.transfer(amount);
    balances[owner] -= amount;
}

如果调用 withdraw() 函数时,amount 大于账户余额,则会抛出一个具有指定错误信息的异常。

处理异常

在进行错误处理时,有两种方式:在本地处理异常或让调用者处理异常。

本地处理异常

本地处理异常意味着在函数内部处理异常。这通常包括捕获异常并将其记录到日志中,然后继续执行函数。下面是一个例子:

function transfer(address recipient, uint amount) public {
    address sender = msg.sender;

    // 检查发送方余额是否足够。
    uint balance = balances[sender];
    require(balance >= amount, "Insufficient balance.");

    // 减少发送方余额。
    balances[sender] -= amount;

    // 增加接收方余额。
    balances[recipient] += amount;

    // 记录交易事件。
    emit Transfer(sender, recipient, amount);
}

如果调用 transfer() 函数时,发送方余额不足,则会抛出异常。在这种情况下,本地的异常处理可能会将异常记录到日志中,并跳过函数剩余的部分。

调用者处理异常

让调用者处理异常意味着函数只是抛出异常,而不进行任何处理。调用者需要捕获异常并处理它。下面是一个例子:

function transfer(address recipient, uint amount) public {
    address sender = msg.sender;

    // 检查发送方余额是否足够。
    uint balance = balances[sender];
    require(balance >= amount, "Insufficient balance.");

    // 减少发送方余额。
    balances[sender] -= amount;

    // 增加接收方余额。
    balances[recipient] += amount;

    // 记录交易事件。
    emit Transfer(sender, recipient, amount);
}

function buyToken(uint amount) public {
    address buyer = msg.sender;

    // 尝试购买令牌。
    try this.transfer(buyer, amount) {
        // 如果成功,则记录日志。
        emit TokenBought(buyer, amount);
    } catch {
        // 如果失败,则记录日志。
        emit TokenPurchaseFailed(buyer, amount);
    }
}

在上面的例子中,buyToken() 函数调用 transfer() 函数以购买令牌。如果 transfer() 函数抛出异常,则 catch 代码块会记录失败日志,否则 try 代码块会记录成功日志。

结论

在 Solidity 智能合约中,抛出和处理异常是处理错误情况的关键。使用 assert、require 或 revert 关键字可以在合约内部抛出异常。本地处理异常可以用于记录错误信息和从函数中跳出,而让调用者处理异常则可以用于捕获并处理异常。