固态分析仪

静态代码分析是在不执行代码的情况下通过检查代码进行调试的过程。

The Solidity Analyzers plugin combines three analysis tools to perform static analysis on Solidity smart contracts. Each tool checks for security vulnerabilities and bad development practices, among other issues. The plugin can be activated from the Remix Plugin Manager.

Activating Solidity Analyzers from the Plugin Manager

Solidity Analyzers can also be loaded by clicking on the Solidity icon in the featured plugins section of Remix's home tab. This button loads the following plugins: Solidity Compiler, Solidity Unit Testing, and Static Analyzers.

固体分析仪 "使用这些工具:

备注

Slither can only be used on Remix Desktop.

如何使用

在运行分析之前,必须先编译合同

在面板顶部,选中要使用的工具。

Solidity Analyzers panel with tool selection checkboxes

错误和警告

By default, Solidity Analyzers will show both errors and warnings. The combined number of errors and warnings is shown in the badge on that tool's tab.

Error and warning badges on the analysis tool tabs

如果选中 "隐藏警告",警告将被隐藏,你只能看到错误。

NOTE: Remix Analysis does not flag errors — it only shows warnings, so if you check Hide warnings, nothing will be shown in the Remix Analysis tab.

Hide warnings checkbox in Solidity Analyzers

来自外部库的警告

By default, warnings from external libraries are not shown. If you check the box Show warnings for external libraries, the tools will also analyze the external libraries for warnings.

Slither

To run Slither with this plugin, you need to open your workspace on Remix Desktop.

索尔欣特

Solhint 内核程序无需将 Remix 连接到文件系统即可运行。

Remix analysis

Remix 分析有 4 个类别:安全"、"天然气与经济"、"ERC "和 "杂项"。

Here is the list of modules under each category along with example code which should be avoided or used very carefully during development:

类别:安全

  • 交易来源:使用'tx.origin'

tx.origin仅在非常特殊的情况下有用。如果您将其用于身份验证,通常应该将其替换为msg.sender,否则你调用的任何合约都可以代表你执行操作。

示例:

require(tx.origin == owner);
  • 检查影响:潜在的可重入错误

Checks-Effects-Interaction 模式的潜在违反可能导致重入漏洞。

示例:

// sending ether first
msg.sender.transfer(amount);

// updating state afterwards
balances[msg.sender] -= amount;
  • 内联汇编:使用内联汇编

仅在极少数情况下建议使用内联汇编。

示例:

assembly {
            // retrieve the size of the code, this needs assembly
            let size := extcodesize(_addr)
}
  • 区块时间戳:语义可能不清晰

now不表示当前时间。nowblock.timestamp的别名。block.timestamp在一定程度上可以受到矿工的影响,请小心。

示例:

// using now for date comparison
if(startDate > now)
    isStarted = true;

// using block.timestamp
uint c = block.timestamp;
  • 低级调用:语义可能不清晰

尽可能避免调用低级别的callcallcodedelegatecall。当send不成功时,不会抛出异常,请确保您相应地处理失败情况。如果想让转移的以太币在交易失败失败时回滚,请使用transfer。

示例:

x.call('something');
x.send(1 wei);
  • Blockhash usage: Semantics may be unclear

blockhash用于访问最近256个区块的哈希值。矿工通过“总结”当前挖掘的区块中的信息来计算区块哈希。通过巧妙地总结区块中的信息,矿工可以尝试影响当前区块中交易的结果。

示例:

bytes32 b = blockhash(100);
  • 自毁:小心调用合约

selfdestruct可能会意外地阻止调用合约的操作。特别是当该合约计划被其他合约使用(例如库合约、交互合约)时,需要特别小心。被调合约的自毁操作可能会使其调用方处于无法操作的状态。

示例:

selfdestruct(address(0x123abc..));

类别:gas与经济

  • Gas费用:函数的Gas消耗过高

If the gas requirement of a function is higher than the block gas limit, it cannot be executed. Please avoid loops in your functions or actions that modify large areas of storage.

示例:

for (uint8 proposal = 0; proposal < proposals.length; proposal++) {
    if (proposals[proposal].voteCount > winningVoteCount) {
        winningVoteCount = proposals[proposal].voteCount;
        winningProposal = proposal;
    }
}
  • 关于This的本地调用:通过“this”调用本地函数

永远不要使用this调用同一个合约中的函数,这样做只会比正常调用消耗更多的gas。

示例:

contract test {

    function callb() public {
        address x;
        this.b(x);
    }

    function b(address a) public returns (bool) {}
}
  • 动态数组的删除:适当使用 require/assert

在Solidity中,当对动态数组执行删除操作时,会生成代码来删除其中包含的每个元素。如果数组很大,此操作可能会超过区块的GAS限制并引发OOG异常。同样,嵌套的动态对象也可能产生相同的结果。

示例:

contract arr {
    uint[] users;
    function resetState() public{
        delete users;
    }
}
  • For循环遍历动态数组:根据动态数组的大小进行迭代

Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can stall the complete contract at a certain point. Additionally, using unbounded loops can incur a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.

示例:

contract forLoopArr {
    uint[] array;

    function shiftArrItem(uint index) public returns(uint[] memory) {
        for (uint i = index; i < array.length; i++) {
            array[i] = array[i+1];
        }
        return array;
    }
}
  • 循环中转移ETH:在for/while/do-while循环中转移ETH

Ether payout should not be done in a loop. Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. If required, make sure that the number of iterations is low and you trust each address involved.

示例:

contract etherTransferInLoop {
    address payable owner;

    function transferInForLoop(uint index) public  {
        for (uint i = index; i < 100; i++) {
            owner.transfer(i);
        }
    }

    function transferInWhileLoop(uint index) public  {
        uint i = index;
        while (i < 100) {
            owner.transfer(i);
            i++;
        }
    }
}

类别:ERC

  • ERC20:'decimals' 应该是 'uint8'类型

ERC20 contracts' decimals function should have uint8 as the return type.

示例:

contract EIP20 {

    uint public decimals = 12;
}

类别:其它

  • Constant/View/Pure 函数:可能的Constant/View/Pure函数

它警告那些可能应该是 constant/view/pure 但不是的方法。

示例:

function b(address a) public returns (bool) {
        return true;
}
  • 相似的变量名称:变量名称过于相似

它警告使用类似的变量名。

示例:

// Variables have very similar names voter and voters.
function giveRightToVote(address voter) public {
    require(voters[voter].weight == 0);
    voters[voter].weight = 1;
}
  • 没有返回:'returns'的函数没有返回

它警告定义返回类型但从不显式返回值的方法。

示例:

function noreturn(string memory _dna) public returns (bool) {
       dna = _dna;
   }
  • 守卫条件:适当使用“require”和“assert”

如果您永远不希望x在任何情况下为false(除了你代码中的错误),请使用assert(x)。如果由于无效的输入或错误的外部组件等原因,x可能为false,请使用require(x)。

示例:

assert(a.balance == 0);
  • 结果未使用:操作的结果没有被使用

二元运算产生的值在下文中未使用。 这通常是由混淆的assignment (=) 和 comparison (==) 引起的。

示例:

c == 5;
or
a + b;
  • 字符串长度:字节长度 != 字符串长度

字节和字符串长度不相同,因为字符串被采用UTF-8编码(根据ABI定义),因此一个字符不一定被编码为一个字节的数据。

示例:

function length(string memory a) public pure returns(uint) {
    bytes memory x = bytes(a);

    return x.length;
}
  • 从动态数组中删除元素:对数组使用 'delete' 会在数组中留下一个间隙

使用 delete 对数组进行删除操作,会在数组中留下一个间隙。这时数组的长度仍然保持不变。如果想要移除这个空白的位置,需要手动将后面的元素往前移,并更新数组的长度属性。

示例:

contract arr {
    uint[] array = [1,2,3];

    function removeAtIndex() public returns (uint[] memory) {
        delete array[1];
        return array;
    }
}
  • 数据截断:int/uint 值的除法会截断结果

整数值的除法再次产生整数值。 这意味着例如 10 / 100 = 0 而不是 0.1,因为结果又是一个整数。 这不适用于(仅)文字值的除法,因为它们产生有理常数。

示例:

function contribute() payable public {
    uint fee = msg.value * uint256(feePercentage / 100);
    fee = msg.value * (p2 / 100);
}

Remix-分析器

Remix-analyzer` 是在 Remix 分析工具下工作的库。

remix-analyzer is an NPM package. It can be used as a library in a solution supporting Node.js. Find more information about this type of usage in the remix-analyzer repository.