Содержание

Библиотеки в Solidity

Библиотеки в Solidity подобны контрактам, но они деплоятся один раз на конкретный адрес, и их код повторно используется с помощью DELEGATECALL (CALLCODE до Homestead). Это означает, что, если функции библиотеки вызваны, то их код выполняется в контексте вызывающего контракта, то есть this указывает на вызывающий контракт, и доступно хранилище из вызывающего контракта. Поскольку библиотека является изолированной частью исходного кода, она может получить доступ только к глобальным переменным вызывающего контракта, если они явно предоставлены (в противном случае, у библиотеки не было бы возможности называть их).

Библиотеки могут рассматриваться как неявные базовые контракты для контрактов, которые их используют. Они не будут явно видны в иерархии наследования, но вызовы функций библиотеки выглядят так же, как вызовы функций явных базовых контрактов (L.f(), если L — имя библиотеки). Кроме того, internal-функции библиотек видны всем контрактам, как если бы библиотека была базовым контрактом. Конечно, вызовы internal-функций используют соглашение об internal вызовах. Это означает, что все internal-типы могут быть переданы. Memory-типы будет переданы по ссылке, а не скопированы. Чтобы реализовать это в EVM, код internal-функций библиотеки и все функции, вызываемые оттуда, будут втянуты в вызывающий контракт, и вызов JUMP будет использован вместо DELEGATECALL.

Следующий пример иллюстрирует, как использовать библиотеки.

pragma solidity ^0.4.16;

library Set {
  // Объявление новой структуры, которая будет использоваться
  // для хранения данных в вызывающем контракте.
  struct Data { mapping(uint => bool) flags; }

  // Первый параметр указан "storage", таким образом, передается
  // только адрес его хранилища, а не содержимое, как часть вызова.
  // Это особое свойство функций библиотеки.
  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 {
        // Функции библиотеки могут вызываться без
        // конкретного экземпляра библиотеки, поскольку
        // "экземпляр" будет текущим контрактом.
        require(Set.insert(knownValues, value));
    }
    // В этом контракте также можно напрямую получить доступ к knownValues.flags.
}

Конечно, не обязательно следовать этому пути, чтобы использовать библиотеки: они могут также использоваться без объявления структуры. Также, функции работают без storage-параметров или  у них может быть несколько storage-параметров.

Вызовы Set.contains, Set.insert и Set.remove скомпилированы как вызовы (DELEGATECALL) к внешнему контракту/библиотеке. При использовании библиотек нужно следить, чтобы выполнялся фактический вызов external-функции. msg.sender, msg.value и this сохранят свои значения в этом вызове.

Следующий пример демонстрирует, как использовать memory-типы и internal-функции в библиотеках для реализации пользовательских типов без накладных вызовов внешних функций:

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) {
            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);
    }
}

Поскольку компилятор не может знать, где будет развернута библиотека, эти адреса должны быть заполнены в финальный байт-код линкером. Если адреса не будете переданы в качестве аргументов компилятору, то скомпилированный шестнадцатеричный код будет содержать заполнители формы __Set______ (где Set — это имя библиотеки). Адрес может быть заполнен вручную путем замены всех 40 символов шестнадцатеричной кодировкой адреса контракта библиотеки.

Ограничения для библиотек по сравнению с контрактами:

  • Нет глобальных переменных
  • Не могут наследовать и не могут наследоваться
  • Не могут получать эфир
Материал был полезен? Поделитесь в соц. сетях:
Логотип echain.ru

Добавить комментарий

Ваш e-mail не будет опубликован.