solidity 上的坑
Solidity 是一种类 JavaScript 语法的运行与以太坊 EVM 上的智能合约语言,目前(v0.7.1)限制还是很大,很多业务逻辑受限于 Solidity 的蹩脚设计无法实现。
从 0.4.x 一路用到 0.7.x,也算是使用了一年多了,记忆比较深刻,比较难定位到问题的两个坑,在这里跟大家分享一下。
uint 越界
一般来说使用 OpenZeppelin 的 SafeMath
库可以避免大多数情况下的越界情况,但是在一个简单的循环中,我们可能考虑不到,比如说这种情况:
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;
}
}