запись в любой слот (write to any slot) - demonoved/A-trial-run-in-solidity GitHub Wiki

// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.23;
// хранилище solidity похож на массив длины 2^256, каждый слот хранит 32 байта
// порядок обьявление и тип переменных состояния определяют, какие слоты он будет использовать
// используя ассемблер можно записать в любой слот
library StorageSlot{
    // обернем адресс в структуру, чтобы его можно было передавать в качестве указателя на хранилище
    struct AddressSlot {
        address value;
    }
    function getAddressSlot(bytes32 slot) internal pure returns(AddressSlot storage pointer){
        assembly {
            //получение указателя на AddressSlot, хранящегося в слоте
            pointer.slot := slot
        }
    }
}
contract TestSlot {
    bytes32 public constant TEST_SLOT = keccak256("TEST_SLOT");
    function write(address _addr) external {
        StorageSlot.AddressSlot storage data = StorageSlot.getAddressSlot(TEST_SLOT);
        data.value = _addr;
    }
    function get() external view returns(address){
        StorageSlot.AddressSlot storage data = StorageSlot.getAddressSlot(TEST_SLOT);
        return data.value;
    }
}
  1. Лицензия и версия компилятора:

    // SPDX-License-Identifier: MIT
    // compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
    pragma solidity ^0.8.23;
    

    Эти строки указывают, что данный смарт-контракт распространяется под MIT лицензией, и для его компиляции нужен компилятор Solidity версии не ниже 0.8.23 и меньше 0.9.0.

  2. Комментарий о хранилище в Solidity:

    Следующий комментарий описывает, как работает хранилище в Solidity:

    "хранилище solidity похож на массив длины 2^256, каждый слот хранит 32 байта"

  3. Библиотека StorageSlot:

    В коде объявляется библиотека StorageSlot, которая предоставляет утилиту для взаимодействия со слотами хранилища напрямую через ассемблер.

    library StorageSlot {
        struct AddressSlot {
            address value;
        }
        function getAddressSlot(bytes32 slot) internal pure returns(AddressSlot storage pointer) {
            assembly {
                pointer.slot := slot
            }
        }
    }
    

    Эта библиотека позволяет вам получать и устанавливать значения в произвольный слот хранилища. Функция getAddressSlot принимает ключ слота (bytes32) и возвращает ссылку на AddressSlot, который является указателем на слот хранилища.

  4. Контракт TestSlot:

    contract TestSlot {
        bytes32 public constant TEST_SLOT = keccak256("TEST_SLOT");
        ...
    }
    

    Здесь объявляется контракт TestSlot, который использует библиотеку StorageSlot для записи и чтения адресов в заданный слот хранилища. Хеш keccak256("TEST_SLOT") используется для генерации уникального идентификатора слота.

  5. Функции контракта:

    Функция write принимает адрес и записывает его в хранилище:

    function write(address _addr) external {
        StorageSlot.AddressSlot storage data = StorageSlot.getAddressSlot(TEST_SLOT);
        data.value = _addr;
    }
    

    А функция get возвращает значение из хранилища:

    function get() external view returns(address) {
        StorageSlot.AddressSlot storage data = StorageSlot.getAddressSlot(TEST_SLOT);
        return data.value;
    }
    

В итоге этот код показывает, как можно взаимодействовать с низкоуровневыми деталями хранилища смарт-контрактов, в частности, напрямую с слота хранилища по заданному ключу. Это можно использовать для эффективной организации хранения данных в контракте, особенно когда требуется гибкая или оптимизированная схема хранения.