Solidity语言学习笔记————36、 库

库(Libraries)

库与合约类似,但它的目的是在一个指定的地址,且仅部署一次,然后通过EVM的特性DELEGATECALL(Homestead之前是用CALLCODE)来复用代码。这意味着库函数调用时,它的代码是在调用合约的上下文中执行。使用this将会指向到调用合约,而且可以访问调用合约的storage。因为一个合约是一个独立的代码块,它仅可以访问调用合约明确提供的状态变量,否则除此之外,没有任何方法去知道这些状态变量。如果库函数不修改状态(即view 或pure 函数),则只能直接调用库函数(即不使用DELEGATECALL),因为库被假定为无状态(stateless)的。特别是,除非Solidity的类型系统被规避,否则不可能destroy 库。

使用库的合约,可以将库视为隐式的父合约(base contracts),当然它们不会显式的出现在继承关系中。但调用库函数的方式非常类似,如库L有函数f(),使用L.f()即可访问。此外,internal的库函数对所有合约可见,如果把库想像成一个父合约就能说得通了。当然调用内部函数使用的是internal的调用惯例,这意味着所有internal类型可以传进去,memory类型则通过引用传递,而不是拷贝的方式。为了在EVM中实现这一点,internal的库函数的代码和从其中调用的所有函数将被拉取(pull into)到调用合约中,然后执行一个普通的JUMP来代替DELEGATECALL

下面的例子展示了如何使用库(后续在using for章节有一个更适合的实现Set的例子)。

pragma solidity ^0.4.16;

library Set {
  // 我们定义了一个新的结构体数据类型,用于存放调用合约中的数据
  struct Data { mapping(uint => bool) flags; }
  // 注意第一个参数是 “存储引用”类型,这样仅仅是它的地址,
  // 而不是它的内容在调用中被传入 这是库函数的特点,
  // 若第一个参数用"self"调用时很笨的的,如果这个函数可以
  // 被对象的方法可见。
  function insert(Data storage self, uint value)
      public
      returns (bool)
  {
      if (self.flags[value])
          return false; // 已经在那里
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      public
      returns (bool)
  {
      if (!self.flags[value])
          return false; // 不在那里
      self.flags[value] = false;
      return true;
  }

  function contains(Data storage self, uint value)
      public
      view
      returns (bool)
  {
      return self.flags[value];
  }
}

contract C {
    Set.Data knownValues;

    function register(uint value) public {
        // 这个库函数没有特定的函数实例被调用,
        // 因为“instance”是当前的合约
        require(Set.insert(knownValues, value));
    }
    // 在这个合约里,如果我们要的话,
    // 也可以直接访问 knownValues.flags
}

当然,你完全可以不按上面的方式来使用库函数,可以不需要定义结构体,不需要使用storage类型的参数,还可以在任何位置有多个storage的引用类型的参数。

调用Set.containsSet.removeSet.insert都会编译为以DELEGATECALL的方式调用external的合约和库。如果使用库,需要注意的是一个实实在在的外部函数调用发生了。尽管msg.sendermsg.valuethis还会保持它们在此调用中的值(在Homestead之前,由于实际使用的是CALLCODEmsg.sendermsg.value会变化)。

下面的例子演示了如何使用memory类型和内部函数(inernal function),来实现一个自定义类型,但不会用到外部函数调用(external function)。

pragma solidity ^0.4.16;

library BigInt {
    struct bigint {
        uint[] limbs;
    }

    function fromUint(uint x) internal pure returns (bigint r) {
        r.limbs = new uint[](1);
        r.limbs[0] = x;
    }

    function add(bigint _a, bigint _b) internal pure returns (bigint r) {
        r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
        uint carry = 0;
        for (uint i = 0; i < r.limbs.length; ++i) {
            uint a = limb(_a, i);
            uint b = limb(_b, i);
            r.limbs[i] = a + b + carry;
            if (a + b < a || (a + b == uint(-1) && carry > 0))
                carry = 1;
            else
                carry = 0;
        }
        if (carry > 0) {
            // too bad, we have to add a limb
            uint[] memory newLimbs = new uint[](r.limbs.length + 1);
            for (i = 0; i < r.limbs.length; ++i)
                newLimbs[i] = r.limbs[i];
            newLimbs[i] = carry;
            r.limbs = newLimbs;
        }
    }

    function limb(bigint _a, uint _limb) internal pure returns (uint) {
        return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
    }

    function max(uint a, uint b) private pure returns (uint) {
        return a > b ? a : b;
    }
}

contract C {
    using BigInt for BigInt.bigint;

    function f() public pure {
        var x = BigInt.fromUint(7);
        var y = BigInt.fromUint(uint(-1));
        var z = x.add(y);
    }
}

因为编译器并不知道库最终部署的地址。这些地址须由linker填进最终的字节码中(使用命令行编译器来进行联接)。如果地址没有以参数的方式正确给到编译器,编译后的字节码将会仍包含一个这样格式的占们符_Set___(其中Set是库的名称)。可以通过手动将所有的40个符号替换为库的十六进制地址。

对比普通合约来说,库的限制:

  • 无状态变量。
  • 不能继承或被继承
  • 不能接收ether。

这些限制将来也可能被解除。

库的调用保护(Call Protection For Libraries)

正如在引言中提到的,如果库的代码是使用CALL 而不是DELEGATECALL 或CALLCODE来执行的,那么除非调用一个viewpure 函数,否则它将恢复。

EVM没有提供一种直接的方法来检测是否使用CALL来调用它,但是合约可以使用ADDRESS 操作码来查找它当前运行的“何处”。生成的代码将此地址与创建时使用的地址进行比较,以确定调用模式。

更具体地说,库的运行时代码总是从编译时为20字节的0的推送指令开始。当部署代码运行时,该常数由当前地址在内存中被替换,并且该修改的代码被存储在合约中。在运行时,这会导致部署时间地址是第一个被推到堆栈上的常量,调度器代码将当前地址与此常量对任何非视图和非纯函数进行比较。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值