深入了解 Solidity 错误第二篇, 了解编译器错误。
从上一篇我们开启了"深入Solidity 错误"系列, 继续更新。
现在,我们将详细介绍 Solidity 中的第一大类错误:与 Solidity 编译器有关的错误,称为 "编译时错误 "或 "编译器错误"。
Solidity 编译器 solc
在将 Solidity 代码编译成字节码时,会产生不同类型的错误。各种不同的错误原因都会导致编译器中止运行。
编译时错误(Compiler Errors),顾名思义,是指 Solidity 合约被 solc
编译器编译时发生的错误。
如果你的集成开发环境使用了某些插件(如 Solidity Visual Developer for VS Code),编译时错误会出现在集成了插件的开发环境(Remix、VS Code...)中,或者运行编译命令时出现在终端中。
当使用solc
编译Solidity智能合约时,solc
编译器会将.sol
文件及其内容作为输入,生成输出:智能合约的EVM字节码。但在编译时,可能会错误。我们将这种类型的错误称为编译时错误。
Solidity 编译时错误可分为两大类:
这些错误与通过命令行使用 solc
编译器有关。
JSONError
:JSON 输入不符合所需的格式,例如输入不是 JSON 对象、不支持语言等。
IOError
:IO 和导入处理错误,如无法解决的 URL 或所提供源代码的哈希值不匹配等。
当 Solidity 代码被编译成可执行字节码时,就会出现这类编译时错误。
这包括无效语法、类型转换和不符合 Solidity 语法的声明等方面的错误。
以下是不同类型的 Solidity 编译器错误摘要
编译器错误 | 描述 |
---|---|
Warning |
关于合约中可能发生的潜在错误或安全的警告 |
ParserError |
Solidity代码不符合Solidity语言规则 |
DocstringParsingError |
无法解析 Natspec 注释或 Natspec 块 |
SyntaxError |
无效的Solidity语法,例如在错误的位置使用内置关键字 |
DeclarationError |
无法解析为变量、函数或内置标志符 |
TypeError |
将值赋给变量或将变量赋给函数或返回参数时,类型无效 |
UnimplementedFeatureError |
当使用Solidity编译器尚未支持但计划在将来支持的语法时出现 |
Warning
(警告)有时你会使用 solc
编译合约,编译成功完成会生成合约字节码 + ABI。
不过,CLI 的输出会标出一些警告错误。这些警告通常以橙色显示,如下图所示。
让我们用一个常见的例子来说明 solc 编译器何时会生成警告:变量遮蔽。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract MyToken {
string private _name;
string private _symbol;
constructor(string memory _name, string memory _symbol) {
// ...
}
}
<p align="center"> 由 solc 编译器生成的警告会在终端或 Remix 中显示为橙色。</p>
在这段代码,参数变量被传递给了 MyToken
合约的 constructor
,与定义的状态变量有相同的名称 (称为 变量遮蔽(variable shadowing) 。虽然 solc
编译器由于变量声明的范围(在构造函数内)会尝试解决这些赋值问题,但它仍然会感到困惑,并警告开发者可能发生了错误。是因为,由于状态变量与构造函数参数之间的命名冲突,可能会出现错误的赋值。
Solc 编译器器出现 warning
依旧允许你编译合约,但可能导致合约中的安全问题。
有多种情况会发生 warning
, 包括:
return
语句。view
或pure
)。.call
、.staticcall
或 .delegatecall
的返回值。无法访问的代码通常是由于合约代码中的一条(或几条)语句永远不会被运行。它们就像死代码
。这是因为合约中的逻辑流要么提前return
了, 要么在这条(这些)语句之前停止(revert
或其他情况)。下面有一些示例。
关于函数的状态可变性,如果可以建议将其向下锁定为 view
或 pure
。尤其是内部函数。“向下锁定”可以确保这些函数不会对状态产生不必要的副作用,而只是负责读取合约状态或执行纯计算。
下面是一些warnings
示例:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^ 0.8 .0;
contract Warnings {
// all the lines below will return a warning
// ---
// Warning: This declaration shadows a builtin symbol.
string gasleft;
uint tx;
uint selfdestruct;
// function keccak256() public pure;
function warningExample1() pure internal {
// Warning: Statement has no effect.
5;
}
// Warning: Unnamed return variable can remain unassigned.
// Add an explicit return with value to all non-reverting code paths or
name the variable.
function warningExample2(uint _value) public pure returns(bool) {
if (_value < 10) {
revert();
// Warning: Unreachable code.
_value = 12;
}
}
uint256 a;
// Warning: Function state mutability can be restricted to pure
function warningExample3() public {
// Warning: Unused local variable.
uint256 a = 32;
}
}
另一种需要牢记的重要warnings
类型是 "未使用的低级调用的返回值(return value of low-level calls not used)"。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^ 0.8 .0;
contract DeployedContract {
uint public result = 0;
function add(uint256 input) public {
result = result + input;
}
}
contract Proxy {
address deployed_contract = 0x1212121212121212121212121212121212121212;
function lowLevelCall(uint256 lucky_number) public {
bytes memory _calldata = abi.encode(bytes4(keccak256("add(uint256)")),
lucky_number);
deployed_contract.call(_calldata);
}
}
建议始终检查低级调用(如 .call
、.staticcall
和 .delegatecall
)的第一个 bool
类型的返回值。以确保外部调用是成功。
上面的函数 lowLevelCall(uint256)
可以修改为如下代码:
function LowLevelCall(uint256 lucky_number) public {
bytes memory _calldata = abi.encode(bytes4(keccak256("add(uint256)")), lucky_number);
(bool lowLevelCallSucceeded, ) = deployed_contract.call(_calldata);
}
注意:我给这个 bool
参数起了一个很长的名字,目的是明确描述这个 bool
代表什么,以及低级调用函数的第一个返回值是什么。
编译器 warnings
并不会阻止 solc 编译器生成合约字节码。但是,强烈建议重构你的 Solidity 代码,以处理和修复编译器警告。
<p align=center>来源:https://ethereum.org/en/developers/docs/smart-contracts/security/</p>
Parser Error
(解析器错误)当 Solidity 源代码不符合 Solidity 语言规则时,就会出现Parser Error
(解析器错误)。
例如:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!