using for

using for 用法

Solidity 提供了 using for,用于为任何类型添加成员函数。

using for 的使用方法如下:

usingFor.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

library L {
    // 下面需要将 isEven 附加到 int 类型
    // 将调用 int a = 3; a.isEven() 时,a 会做为第一个参数 self 传入
    function isEven(int self) internal pure returns (bool) {
        return self % 2 == 0;
    }

    // 下面需要将 isEven 附加到 int 类型
    // 将调用 int a = 3; a.isEven() 时,self 值为 a 自身
    function isOdd(int self) internal pure returns (bool) {
        return self % 2 != 0;
    }

// 将库 L 中的所有非 private 函数附加到 int 类型
using L for int;
// 或者,只指定 L.isEven 附加到 int 类型
using {L.isEven, L.isOdd} for int;

contract C {
    function test() public pure returns (bool, bool) {
        int a = 3;
        return (a.isEven(), a.isOdd());
    }
}

使用 using L for Type 来将库 L 中的所有 private 函数附加到 Type 类型。

使用 using {fn1, fn2, ...} for Type 来显式将多个函数附加到 Type 类型。

由于我们要将 isEven 附加到 int 类型,因此我们把它的第一个参数声明为对应的类型:int self。当我们调用 a.isEven() 时,a 会作为第一个参数传给 isEven

上面的 using for 是在文件层级声明的,将在当前文件中生效。

如果是自定义类型,可以使用 using for UserType global 为自定义类型附加成员函数,全局生效:

usingForGlobal.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

// 自定义类型 Int
type Int is int256;

function isEvenForInt(Int a) pure returns (bool) {
    return Int.unwrap(a) % 2 == 0;
}

// 将 isEvenForInt 附加到 Int 类型,全局生效
// 指定全局生效时,只能是自定义的类型
using {isEvenForInt} for Int global;

contract C {
    function test() public pure returns (bool) {
        Int a = Int.wrap(3);
        return a.isEvenForInt();
    }
}

除了在文件层级使用 using for,也可以合约层级使用,此时仅当前合约生效:

usingForContract.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

function sayHi(string memory self) pure returns (string memory) {
    return string.concat("Hi, I am a string, my value is: ", self);
}

contract C {
    using {sayHi} for string;

    function test() public pure returns (string memory) {
        string memory str = "USING FOR STRING";
        return str.sayHi();
    }
}

改进 SmallSet

与上一节中的集合实现不同。在这一节,我们实现一个 SmallSet。该 SmallSet 使用 type SmallSet is bytes32 声明。

bytes32 是一个32位长的字节数组,每一个元素长8位,用于保存 uint8 元素,最多存 32 个元素。每个元素取值为 1-2550 表示该位置无元素:

smallSet.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;

/// @dev 数组实现的集合操作
library SmallSetOp {
    /// @notice 判断集合内是否存在存在某元素
    /// @param self 集合结构体
    /// @param v 要查找的元素
    /// @return bool true 表示存在,false 表示不存在
    function has(SmallSet self, uint8 v) internal pure returns (bool) {
        if (v == 0) return false;
        for (uint256 i = 0; i < 32; i++)
            if (uint8(SmallSet.unwrap(self)[i]) == v) return true;
        return false;
    }

    /// @notice 在原集合的基础上,插入新元素,并返回插入后的新集合
    /// @param self 原集合
    /// @param v 要插入的元素
    /// @return 返回原集合插入元素后的集合
    function insert(SmallSet self, uint8 v) internal pure returns (SmallSet) {
        if (has(self, v)) return self;
        bytes32 b = SmallSet.unwrap(self);
        for (uint256 i = 0; i < 32; i++)
            if (b[i] == 0x00)
                return SmallSet.wrap(b | (bytes32(bytes1(v)) >> (8 * i)));
        return self;
    }

    /// @notice 从集合中移除元素,并返回移除目标元素后的新集合
    /// @param self 集合
    /// @param v 要移除的元素
    /// @return SmallSet 返回移除目标元素后的新集合
    function remove(SmallSet self, uint8 v) internal pure returns (SmallSet) {
        bytes32 b = SmallSet.unwrap(self);
        bytes1 target = bytes1(v);

        for (uint256 i = 0; i < 32; i++)
            if (b[i] == target)
                return SmallSet.wrap(b & (~(bytes32(hex"ff") >> (8 * i))));
        return self;
    }

    /// @notice 并集操作
    /// @param self 并集集合1
    /// @param unionSet 并集集合2
    /// @return 返回集合1与集合2并集后的结果集合
    function union(SmallSet self, SmallSet unionSet) internal pure returns (SmallSet) {
        bytes32 b = SmallSet.unwrap(unionSet);
        for (uint256 i = 0; i < 32; i++) self = insert(self, uint8(b[i]));
        return self;
    }

    /// @notice 交集操作
    /// @param self 集合1
    /// @param diffSet 集合2:集合1中要移除的元素的集合
    /// @return 返回集合1中移除集合2中的元素之后的新集合
    function diff(SmallSet self,SmallSet diffSet) internal pure returns (SmallSet) {
        bytes32 b = SmallSet.unwrap(diffSet);
        for (uint256 i = 0; i < 32; i++) self = remove(self, uint8(b[i]));
        return self;
    }
}

未使用 using for 时,SmallSet 的使用如下:

TestSmallSet.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

// 导入 SetOp 库
import {SmallSetOp, SmallSet} from "./SmallSet.sol";

contract TestSet {
    SmallSet public set1;
    SmallSet public set2;

    function test() public returns (SmallSet, SmallSet) {
        set1 = SmallSetOp.insert(set1, 2);
        set1 = SmallSetOp.insert(set1, 4);
        set1 = SmallSetOp.insert(set1, 6);
        set1 = SmallSetOp.insert(set1, 8);
        // set1: 2, 4, 6, 8

        set2 = SmallSetOp.insert(set2, 1);
        set2 = SmallSetOp.insert(set2, 3);
        set2 = SmallSetOp.insert(set2, 5);
        set2 = SmallSetOp.insert(set2, 7);
        // set2: 1, 3, 5, 7

        set1 = SmallSetOp.remove(set1, 2);
        set2 = SmallSetOp.remove(set2, 5);
        // set1: 4, 6, 8
        // set2: 1, 3, 7

        set1 = SmallSetOp.union(set1, set2);
        // set1: 4, 6, 8, 1, 3, 7
        // set2: 1, 3, 7

        set2 = SmallSetOp.diff(set2, set1);
        // set1: 4, 6, 8, 1, 3, 7
        // set2:

        return (set1, set2);
    }
}

现在我们使用 using forSmallSetOp 中的函数作为成员函数添加到类型 SmallSet 中去:

SmallSetUsingFor.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;

// 将 SmallSetOp 库中的函数作为 SmallSet 的成员函数
using SmallSetOp for SmallSet global;

/// @dev 数组实现的集合操作
library SmallSetOp {
   ...
}

现在,不需要再导入 SmallSetOp 了,只需要导入使用 SmallSet 即可。并且直接通过调用 SmallSet 的成员函数来操纵集合:

TestSmallSetUsingFor.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

// 导入 SetOp 库
import {SmallSet} from "./SmallSetUsingFor.sol";

contract TestSet {
    SmallSet public set1;
    SmallSet public set2;

    function test() public returns (SmallSet, SmallSet) {
        set1 = set1.insert(2);
        set1 = set1.insert(4);
        set1 = set1.insert(6);
        set1 = set1.insert(8);
        // set1: 2, 4, 6, 8


        set2 = set2.insert(1);
        set2 = set2.insert(3);
        set2 = set2.insert(5);
        set2 = set2.insert(7);
        // set2: 1, 3, 5, 7

        set1 = set1.remove(2);
        set2 = set2.remove(5);
        // set1: 4, 6, 8
        // set2: 1, 3, 7

        set1 = set1.union(set2);
        // set1: 4, 6, 8, 1, 3, 7
        // set2: 1, 3, 7

        set2 = set2.diff(set1);
        // set1: 4, 6, 8, 1, 3, 7
        // set2:

        return (set1, set2);
    }
}

操作符重载

using for 还支持操作符重载,使用语法为:

using { 加法函数 as +, 减法函数 as -, ... } for 自定义值类型 global;

操作符重载只支持自定义的值类型。这就是为什么我们使用底层类型为 bytes32(值类型)的自定义类型 SmallSet

我们的想法是,将加法 set1 + set2 重载为求并集, 将减法 set1 - set2 重载为求差集:

SmallSetOperatorOverload.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;

// 将 SetOp 库中的函数附加到 Set 结构体
using SmallSetOp for SmallSet global;

/// @dev 数组实现的集合操作
library SmallSetOp {
    ...
}

// 用于重载为 SmallSet 加法操作(操作符+)
// 重载操作符的函数必须是文件层级声明的 pure 自由函数
function unionAdd(SmallSet self, SmallSet unionSet) pure returns (SmallSet) {
    return SmallSetOp.union(self, unionSet);
}

// 用于重载为 SmallSet 减法操作(操作符-)
// 重载操作符的函数必须是文件层级声明的 pure 自由函数
function diffSub(SmallSet self, SmallSet diffSet) pure returns (SmallSet) {
    return SmallSetOp.diff(self, diffSet);
}

// 操作符重载
using {unionAdd as +, diffSub as -} for SmallSet global;

由于重载操作符函数必须是自由函数,因此我们额外定义了 unionAdd diffSub 用于重载加、减法操作符。

现在 union diff 操作可以这样写:

TestSmallSetOperatorOverload.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

// 导入 SetOp 库
import {SmallSet} from "./SmallSetOperatorOverload.sol";

contract TestSet {
    SmallSet public set1;
    SmallSet public set2;

    function test() public returns (SmallSet, SmallSet) {
        ...

        set1 = set1 + set2; // => set1 = set1.union(set2);
        // set1: 4, 6, 8, 1, 3, 7
        // set2: 1, 3, 7

        set2 = set2 - set1; // => set2 = set2.diff(set1);
        // set1: 4, 6, 8, 1, 3, 7
        // set2:

        return (set1, set2);
    }
}