文章阅读页通栏

solidity与library的之间关系与应用

来源: 区块链研究实验室 作者:链三丰
solidity与Libraries(库) solidity是在以太坊网络和任何支持evm(以太坊虚拟机)的区块链上实现智能合约的高级编程语言之一。它的javascript语法和C语言风格......
solidity与Libraries(库)

solidity是在以太坊网络和任何支持evm(以太坊虚拟机)的区块链上实现智能合约的高级编程语言之一。它的javascript语法和C语言风格的数据类型使它成为生态系统中最受欢迎和支持的语言。

由于区块链的不变性,开发没有漏洞的智能合约非常重要。因此,对于以太坊智能合约的开发,开发人员应该始终尝试重用和依赖已经被许多开发人员审查、测试和使用过的代码,例如使用solidity库。

在solidity中,库类似于合约,它们实现操作逻辑,并且可以部署一次以供不受限制数量的其他智能合约使用,而无需一次又一次地部署相同的逻辑。这有利于节省部署所需的gas费用。然而,如果多个合约依赖于同一段代码的话,那么如果该段代码存在漏洞,则可能会产生不良后果。因此,这是非常关键的调用,您应该非常谨慎地选择在智能合约中使用的库。库的好处不仅限于可重用性,它们还可以在一定程度上提高代码的可维护性、可读性甚至可升级性。

在solidity中使用库

库就像合约,我们使用library关键字来创建它们。但是库没有存储空间,不能保存任何以太坊,也不能继承或被继承。还有两种不同类型的库,具有外部功能的库称为链接库,它们应该首先部署,并且它们的地址应该通过部署过程添加到合约的bytecode中。只有内部函数的库称为嵌入式库。它们与合约一起部署,因此它们的bytecode成为合约bytecode的一部分,也可以通过jump命令访问。

下面是一个简单的例子的safemath库,它有一个函数来添加两个uint256类型的数字,并防止可能的溢出错误。

pragma solidity >= 0.4.25 < 0.6.0;

library SafeMath {

  function add(uint256 a, uint256 b) public pure returns (uint256){
    uint256 c = a + b;
    require(c >= a, "SafeMath: addition overflow");
    return c;
  }

}

要使用该库,我们首先使用指令将uint256类型附加到该库

using SafeMath for uint256

然后,uint256类型的任何变量都可以使用一个特殊的delegatecall调用函数add(),其中发送方和值不会更改,只是从父作用域传播到子作用域,而且存储仍然引用调用协定。

uint256 currentBalance;

function pay(uint256 amount) public {
  require(amount > 0, "amount is not bigger than zero");
  uint256 currentBalance  = 10;
  currentBalance  = currentBalance.add(amount);
}

如前所述,库是没有存储空间,但是它们可以修改链接智能合约的存储空间,方法是将关键字存储空间添加到函数的第一个参数中,并使用struct将状态变量封装在库中,如safemath库的修改实现所示。我们将在地址数组库的实现中使用它。

pragma solidity >= 0.4.25 < 0.6.0;

library SafeMath {

  struct Balance { uint256 _balance; }

  function add(Num storage self, uint256 amount) public {
    uint256 newBalance = self._ balance + amount;
    require(newBalance >= self._ balance, "addition overflow");
    self._ balance = newBalance;
  }

}

为什么需要一个地址数组?

address是Solidity中的一种特殊数据类型,具有20个字节的值,并提供诸如transfer和balance之类的内置函数。 由于每个帐户和智能合约都通过唯一的地址表示,因此几乎每个智能合约项目都需要使用地址。

简单地迭代一个完整的映射是不可能的,因此在许多情况下,开发人员必须将地址保存在数组中,特别是当涉及到在事务数据中不能给出地址的合约之间的链上交互时。最近我们在一个项目中工作,我们需要存储和管理一个数组中链上投资者的地址。在没有找到任何可用的公共库之后,我们实现了自己的库来管理地址数组。

示例实现

/*
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

pragma solidity >=0.4.25 <0.6.0;

library AddrArrayLib {
    using AddrArrayLib for Addresses;

    struct Addresses {
      address[]  _items;
    }

    /**
     * @notice push an address to the array
     * @dev if the address already exists, it will not be added again
     * @param self Storage array containing address type variables
     * @param element the element to add in the array
     */
    function pushAddress(Addresses storage self, address element) internal {
      if (!exists(self, element)) {
        self._items.push(element);
      }
    }

    /**
     * @notice remove an address from the array
     * @dev finds the element, swaps it with the last element, and then deletes it;
     *      returns a boolean whether the element was found and deleted
     * @param self Storage array containing address type variables
     * @param element the element to remove from the array
     */
    function removeAddress(Addresses storage self, address element) internal returns (bool) {
        for (uint i = 0; i < self.size(); i++) {
            if (self._items[i] == element) {
                self._items[i] = self._items[self.size() - 1];
                self._items.pop();
                return true;
            }
        }
        return false;
    }

    /**
     * @notice get the address at a specific index from array
     * @dev revert if the index is out of bounds
     * @param self Storage array containing address type variables
     * @param index the index in the array
     */
    function getAddressAtIndex(Addresses storage self, uint256 index) internal view returns (address) {
        require(index < size(self), "the index is out of bounds");
        return self._items[index];
    }

    /**
     * @notice get the size of the array
     * @param self Storage array containing address type variables
     */
    function size(Addresses storage self) internal view returns (uint256) {
      return self._items.length;
    }

    /**
     * @notice check if an element exist in the array
     * @param self Storage array containing address type variables
     * @param element the element to check if it exists in the array
     */
    function exists(Addresses storage self, address element) internal view returns (bool) {
        for (uint i = 0; i < self.size(); i++) {
            if (self._items[i] == element) {
                return true;
            }
        }
        return false;
    }

    /**
     * @notice get the array
     * @param self Storage array containing address type variables
     */
    function getAllAddresses(Addresses storage self) internal view returns(address[] memory) {
        return self._items;
    }

}

在上面的库代码中,我们有addresses结构,它包含address类型的数组和管理数组通常需要的函数,例如:

· pushAddress:仅在地址不存在时将其添加到列表中。不允许使用重复地址,因为这会使数组更大,操作更复杂并增加使用gas。因此,我们假定对合同代码中的此类地址进行简单的特殊处理会更有效。

· removeAddress:从列表中删除地址,如果地址已删除,则返回true。如果该地址不存在,则该函数将返回false。该方法将找到该元素,将其与最后一个元素交换,然后将其删除。
· exists:如果数组中存在地址,返回true。
· size:返回数组的大小。
· getAddressAtIndex:获取给定索引处的地址。

在所有函数中,我们使用关键字作为存储传递addresses参数,因此evm从智能合约的存储中通过引用传递它,而不是在内存中创建它的副本。

现在我们将创建一个使用这个库来管理用户地址的示例合约,用户可以创建一个代币。

pragma solidity >= 0.4.25 < 0.6.0;

import '../node_modules/openzeppelin solidity/contracts/ownership/Ownable.sol';
import '../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol';
import './AddrArrayLib.sol';

contract TokenContract is Ownable {
  using AddrArrayLib for AddrArrayLib.Addresses;
  using SafeMath for uint256;

  // List of trusted addresses which can mint tokens
  AddrArrayLib.Addresses trustedMinters;

  uint256 public totalSupply;
  mapping (address => uint256) balances;

  constructor () public {
    totalSupply = 100;
    balances[msg.sender] = 100;
  }

  function addMinter(address minter) public onlyOwner() {
    trustedMinters.pushAddress(minter);
  }

  function removeMinter(address minter) public onlyOwner() {
    trustedMinters.removeAddress(minter);
  }

  function mintToken(address to, uint256 amount) external {
    require(trustedMinters.exists(msg.sender), 'The sender address is not registered as a Minter');
    totalSupply = totalSupply.add(amount);
    balances[to] = balances[to]. add(amount);
  }

}

在这个合约中,我们使用了两个嵌入式库,第一个是基于openzeppelin实现的嵌入式safemath库,它提供了添加的逻辑,但不会直接更改合约的状态,第二个是我们之前定义并通过其.sol文件导入的addrarraylib库。

我们创建结构类型为AddrArrayLib.Addresses的私有变量,该私有变量表示合约的状态变量并存储地址。之后,我们有一个构造函数和三个合约函数:

· addMinter函数接收新的Minter的地址,并调用trustMinters.pushAddress函数,该函数从存储中传入结构实例。

· removeMinter函数。

· mintToken它通过调用trustMinters.exists(msg.sender)来检查msg.sender是否存在于受信任的造币者数组中,并在成功验证之后创建该令牌。

包含库,TokenContract和测试说明的存储库可在此链接下的Github上找到。https://github.com/51nodes/address-library-sample

总结与未来工作

在本文中,我们简要地解释了库,为什么使用它们以及如何使用它们。此外,我们还介绍了一个用于管理数组中地址的库的实现,我们认为这个库是智能合约经常需要的构建块。

今后的工作重点是测试、改进现有功能和实现新功能。此外,我们还将分析gas成本,并将该库的链接版本与嵌入式版本进行比较。最后经过许多有经验的开发人员的审查,这个库将被部署在以太坊区块链上,并可用于新的项目。
关键词: solidity  library  
0/300