solidity 上的坑

Solidity 是一种类 JavaScript 语法的运行与以太坊 EVM 上的智能合约语言,目前(v0.7.1)限制还是很大,很多业务逻辑受限于 Solidity 的蹩脚设计无法实现。

从 0.4.x 一路用到 0.7.x,也算是使用了一年多了,记忆比较深刻,比较难定位到问题的两个坑,在这里跟大家分享一下。

uint 越界

一般来说使用 OpenZeppelinSafeMath 库可以避免大多数情况下的越界情况,但是在一个简单的循环中,我们可能考虑不到,比如说这种情况:

uint[10] memory aUintArray;
for (uint i = aUintArray.length - 1; i >= 0; i--) {
   do something
}

在这个情境下你是否发现了什么问题?这个循环到 0 的时候,会越界,0--,这样智能合约就会出错了。

delete 用法

delete 是个比较常用的用法,而且使用 delete 还会返还部分 Gas 费用,在 mapping 类型的数据中使用 delete 是十分正常的,比如 mapping(address => uint) _addressToID 这种,delete _addressToID(msg.sender) 会将 msg.sender 这个 key 的 value 置为目标数据类型的默认值(布尔值:false,数字:0),这也是我们期望的。

那么在数组中,delete 就显得比较奇怪了,比如说:

uint[] uintArray;

function deleteIndex(uint i) public {
   uint len = uintArray.length;
   assert(i >= 0 && i < len); // 编号 ①
   delete uintArray(i);
   assert(uintArray.length == len-1); // 编号 ②
}

你认为 编号②true 还是 false ?实际上是 false ,solidity 在 delete 删除数组元素时只会将该元素位置置为数组类型的默认值,并不会改变数组长度。
文档地址:delete

delete a[x] deletes the item at index x of the array and leaves all other elements and the length of the array untouched. This especially means that it leaves a gap in the array. If you plan to remove items, a mapping is probably a better choice.

删除数组元素

如上面一个部分所述,删除数组元素时只会将该元素位置置为数组类型的默认值,并不会改变数组长度,那么我们应当如何正确的删除数组内的元素呢?

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

contract Test {
    uint[] public arr;
  
    function add(uint item) public {
        arr.push(item);
    }
  
    function remove(uint item) public {
        bool flag;
        for (uint i = 0; i < arr.length; i++) {
            if (arr[i] == item) {
                // 将要删除的元素与最后一个元素互换
                arr[i]=arr[arr.length-1];
                // 删除最后一个元素
                arr.pop();
                flag = true;
            }
        }
        if (!flag) revert("item not found");
    }
  
    function len() public view returns(uint) {
        return arr.length;
    }

}

Comments