Неделя продаж завершена и канал возвращается к своему привычному ритму.
Недавно мое внимание привлек контракт Uniswap V3, где были использованы библиотеки для работы со структурами и и маппингами. Я не так часто встречал этот способ и решил рассказать о нем на канале.
Структуры в библиотеках
Посмотрите на код:
Solidity
library CounterLib {
struct Counter { uint i; }
function incremented(Counter storage self) internal returns (uint) {
return ++self.i;
}
}
contract CounterContract {
using CounterLib for CounterLib.Counter;
CounterLib.Counter public counter;
function increment() public returns (uint) {
return counter.incremented();
}
}
При том, что у библиотек не может быть собственного хранилища (storage), они могут работать и изменять память контракта, к которому подключены. В данном примере в бибилотеке мы создали структуру данных и функцию, которая инкрементирует значение. Обратите внимание на self, который в данном случае будет помогать обновлять память контракта.
Далее в самом контракте мы создаем переменную counter и подключаем библиотеку к структуре.
Порождение событий
Порождение событий также сопряжено с изменением памяти контракта и записи логов. Но библиотеки и тут можно использовать:
Solidity
library EventEmitterLib {
function emitEvent(string memory s) internal{
emit Emit(s);
}
event Emit(string s);
}
contract EventEmitterContract {
using EventEmitterLib for string;
function emitEvent(string memory s) public {
s.emitEvent();
}
}
Хоть сам even был определен в библиотеке, логи будут записаны в нашем контракте. Единственное что, при попытке сязать сайт и контракт на получение данного события, будет возникать ошибка, поэтому также рекомендуют дублировать event и в самом контракте.
Структура библиотеки в маппинге
Еще один не самый популярный способ:
Solidity
library Library {
struct data {
uint val;
bool isValue;
}
}
contract Array{
using Library for Library.data;
mapping(address => Library.data) clusterContract;
function addCluster(address id) public view returns(bool){
if(clusterContract[id].isValue) revert();
return true;
}
}
Тут мы создали мапиинг, в котором значение по ключу является структурой библиотеки. Не помню реальных примеров такого использования в протоколах, но такая реализация также возможна.
Использование библиотеки для маппинга
С простым примером для этого способа были некоторые трудности, поэтому покажу на примере контракта Uniswap.
Solidity
library TickBitmap {
...
function flipTick(
mapping(int16 => uint256) storage self,
int24 tick,
int24 tickSpacing
) internal {
unchecked {
require(tick % tickSpacing == 0); // ensure that the tick is spaced
(int16 wordPos, uint8 bitPos) = position(tick / tickSpacing);
uint256 mask = 1 << bitPos;
self[wordPos] ^= mask;
}
}
...
}
contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
using TickBitmap for mapping(int16 => uint256);
mapping(int16 => uint256) public override tickBitmap;
function _updatePosition(
...
) private returns (Position.Info storage position) {
...
if (flippedLower) {
tickBitmap.flipTick(tickLower, tickSpacing);
}
if (flippedUpper) {
tickBitmap.flipTick(tickUpper, tickSpacing);
}
...
}
}
Пришлось много вырезать для лучшего восприятия кода, но полную версию вы можете посмотреть по ссылке в начале поста.
Тут мы используем библиотеку для определенного вида маппинга с нужными размерностями ключа и значений. Далее в контракте в функции мы вызываем функцию flipTick по переменной маппинга tickBitmap и передаем в библиотеку сам маппинг и две аргумента.