• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Учебное пособие по Solidity: все об ABI

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
1663385898115.png

ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro плюс немного отсебятины для нашего развития
$600 на SSD для Jolah Milovski ---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов


В этой статье мы расскажем, что такое ABI в Solidity, как он помогает описывать смарт-контракты и как он транслируется в байт-код EVM. Мы также рассмотрим несколько встроенных в Solidity методов кодирования и декодирования в соответствии со спецификацией ABI.


Смарт-контракты — очень расслабленные маленькие существа. Они неподвижно сидят в сети под солнцем блокчейна, ожидая вызова. Сами по себе они бессмысленны и малопригодны. Но нельзя сбрасывать со счетов их «интеллигентность»! На самом деле то, что заставило их заслужить звание « умных контрактов», — это связанный с ними байт. Когда смарт-контракт развертывается в сети, его байт-код связывается с его адресом и сохраняется в блокчейне. Чтобы быть более точным, байт-код смарт-контракта хранится в состоянии покоя в поле «код» адреса смарт-контракта.
Давайте приступим к практике, чтобы понять и увидеть байт-код, стоящий за UniswapV3Factory смарт-контракта в основной сети Ethereum! Следующий фрагмент кода Javascript и web3.js позволит вам сделать это.


Код:
const Web3 = require("web3");const provider = "YOUR_INFURA_OR_QUICKNODE_HTTP_ENDPOINT";
const web3 = new Web3(provider);const UniswapV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984";web3.eth.getCode(UniswapV3Factory).then(console.log)
;> 0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620...

А еще можно зайти на etherscan.io и в разделе CONTRACT ниже кода solidity будет ABI-код / прим. пер. /

Думайте о байт-коде смарт-контракта как о его «мозге» . Он описывает логику контракта в виде машинного кода. Этот машинный код получается путем компиляции высокоуровневого языка программирования смарт-контрактов, такого как Solidity, в исполняемый машинный язык: байт-код EVM.
Приведенный выше байт-код EVM представляет собой не что иное, как последовательность кодов операций EVM, записанных в шестнадцатеричном формате.
Это оставляет нам первую проблему: исполняемый код — это машинный код. Он не читается человеком, а только машиночитаем. И только специальная машина (= виртуальная машина Ethereum) может его понять и знает, как его выполнить. Но как насчет людей? Для людей байт-код контракта не предоставляет никакого контекста (например, двоичный исполняемый файл файла C++).
Вторая проблема , которая у нас есть, заключается в том, что смарт-контракты не очень полезны, если вы не начнете взаимодействовать с ними, чтобы они могли запускать и выполнять свою логику. Но что делает смарт-контракты исполняемыми в блокчейне, так это их фактический байт-код. Здесь мы приходим к противоречивой логике, когда две вышеуказанные проблемы подпитывают друг друга. Решение этой проблемы лежит в ABI, описанном в документации Solidity как:
«ABI — это стандартный способ взаимодействия с контрактами в экосистеме Ethereum. Как извне блокчейна, так и для взаимодействия контракт-контракт».
« в ABI означает « Интерфейс . Это то, что обеспечивает связь с байт-кодом EVM контракта путем перевода данных входных данных в «машиночитаемый» формат для EVM.
Думайте об ABI как о более простом и понятном способе описания смарт-контракта для человека. ABI смарт-контракта будет описывать его публичный интерфейс и способы взаимодействия с ним.
Он определяет, какие функции вы можете вызывать, и гарантирует, что функция вернет данные в ожидаемом вами формате.

ABI = спецификация для кодирования + декодирования

Ранее мы видели, что ABI — это то, что создает связь между клиентом (непосредственно из EOA или интерфейса) и байт-кодом смарт-контракта (логика контракта в кодах операций EVM).
Однако ABI является не только связующим звеном между этими двумя уровнями (человеческим и EVM). Что наиболее важно, ABI определяет четкие спецификации того, как кодировать и декодировать данные и вызовы .
Таким образом, в Ethereum и любой цепочке на основе EVM ABI в основном представляет собой способ кодирования вызовов контрактов для EVM (чтобы EVM понимала, какие инструкции выполнять).
То же самое происходит в обратном направлении. ABI указывает, как читать и декодировать данные из транзакций, поскольку все данные, указанные в транзакциях, закодированы как необработанные шестнадцатеричные числа.
Следовательно, ABI — это метод кодирования и декодирования данных в/из машинного кода.
Мы увидим это в отдельном разделе о том, как разные типы данных кодируются и декодируются ABI.

Понимание JSON ABI контракта

Для таких агентов, как интерфейсы Dapp, взаимодействующие вне блокчейна, ABI контракта представлен в формате JSON.
Для интерфейса Dapp ABI смарт-контракта Solidity представлен в виде массива объектов.
Каждому объекту может соответствовать:
  • Метод (функция) в методе контракта, который является общедоступным (= может быть вызван кем угодно, кроме случаев, когда к нему присоединены ограничительные модификаторы).
  • определение события
  • функция fallback() или receive().
При составлении контракта Solidity можно найти ABI внутри сгенерированного .json артефакт. Этот файл JSON будет содержать поле с именем “abi”.
Вот простой пример из UniswapV3Factory:

Код:
{
"abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        },
        {
          "indexed": true,
          "internalType": "int24",
          "name": "tickSpacing",
          "type": "int24"
        }
      ],
      "name": "FeeAmountEnabled",
      "type": "event"
    },
    // events definition of `OwnerChanged`, `PoolCreated`
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "tokenA",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "tokenB",
          "type": "address"
        },
        {
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        }
      ],
      "name": "createPool",
      "outputs": [
        {
          "internalType": "address",
          "name": "pool",
          "type": "address"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint24",
          "name": "fee",
          "type": "uint24"
        },
        {
          "internalType": "int24",
          "name": "tickSpacing",
          "type": "int24"
        }
      ],
      "name": "enableFeeAmount",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    // function definitions of:
    //     - `feeAmountTickSpacing(...)`
    //     - `getPool(...)`
    //     - `owner()`
    //     - `parameters(...)`
    //     - `setOwner(...)`
  ]
}


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

JSON ABI спецификация функции

  • type: function, constructor, event, receive(для функции получения эфира ), или fallback(для резервной функции по умолчанию )
  • name : имя функции.
  • inputs : параметры функции в виде массива с именем и типом каждого параметра.
  • outputs : массив возвращаемых значений функцией
  • stateMutability: view, pure, payable или же nonpayable(= изменчивость функции по умолчанию). Является ли функция доступной только для чтения или может записывать в состояние контракта.
Примечание : для constructor а также fallback функции, поля имени и вывода в JSON ABI пусты.
NB2: для fallback функция, входных данных нет так как резервная функция не может принимать аргументы.
функциональные входы (детали)
в "inputs"Поле содержит массив объектов, каждый из которых определяет параметр функции.
  • type : элементарный тип параметра ( например: uint256, address, так далее.).
  • outputs : это массив выходных объектов, похожих на входы.
Если параметр является кортежем ( например, определяемым пользователем как struct), “input"поле будет содержать поле с именем “components”. Если другой кортеж , он будет определен как “type”: “tuple”)
“StateMutability” Поле для функции может иметь одно из четырех следующих значений, в зависимости от того, как функция написана в Solidity:
  • pure: функция не читает и не записывает состояние блокчейна.
  • view: функция может только читать из состояния блокчейна, но не может писать в него.
  • payable: функция может читать и записывать в состояние блокчейна + принимать эфиры ✅ 💸 ( или нативную валюту из цепочки EVM) во время вызова.
  • nonpayable(по умолчанию): функция может читать и записывать состояние блокчейна, но не может принимать эфиры ❌ 💸 ( или нативную валюту из цепочки EVM) во время вызова.
Изменение состояни функции происходит 4 способами:

1. JSON ABI спецификация событий

  • тип: всегда event.
  • имя: название события.
  • что принимает на вход: массив параметров, которые можно передать событию.
  • анонимно: true если событие было объявлено как anonymous в коде Solidity в противном случае false.
входы событий (детали)
Подобно функциям, «входное» поле события в JSON ABI содержит массив объектов, представляющих параметры события. Каждый объект состоит из следующих свойств:
  • name : имя параметра.
  • type : элементарный тип параметра ( например: uint256, address, так далее.).
  • indexed : true - если поле является частью тем журнала, или false - если это один из сегментов данных журнала.

2. Solidity ABI — кодирование данных

Ранее мы обсуждали и видели, что ABI определяет, как типы данных Solidity должны быть закодированы, чтобы их можно было передать + интерпретировать EVM.
Большинство статических типов в Solidity, таких как address, uint256 или bytes32 кодируются как 32-байтовые слова.
Заполнение байтами зависит от базовых типов Solidity. Например, addressдополняются нулями слева, а значения байтов фиксированного размера меньше 32 байтов (например, bytes4, bytes8, bytes20, и т. д.) дополняются нулями с правой стороны.
Самое интересное приходит с неэлементарными типами, такими как string или массивы. Это означает, что такие типы кодируются определенным образом, указанным в Solidity ABI.
Ниже мы покажем два примера с string и массив фиксированного размера.

abi.encode(…)

Встроенная функция Solidity abi.encode позволяет кодировать любые типы Solidity в необработанные байты, которые могут быть интерпретированы непосредственно EVM.
Обратите внимание, что этой функции может быть присвоено несколько аргументов.
Начнем с первого примера:
string
abi.encode("solidity")
Приведенная выше функция вернет следующее необработанное значение байтов.
0X0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004536F6C6964697479900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F _
0x0000000000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000004
536f6c69646974790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Строки всегда кодируются в следующем формате:
  • 1-е (32 байта) слово = смещение → указывает, с какого байта индекса начинается строка. Здесь 0x20 (в шестнадцатеричном формате) = 32 (в десятичном формате). Если вы считаете 32 с начала (= индекс 32), вы достигнете начальной точки, где начинается фактическая закодированная строка.
  • 2-е (32 байта) слово = длина строки → в случае строки это указывает, сколько символов (включая пробелы) включено в строку. Так что просто « string.length “
  • 3-е (32 байта) слово = фактическая строка в кодировке utf8 → каждый отдельный байт соответствует шестнадцатеричной записи буквы/символа, закодированной в utf8. Если вы ищете каждый отдельный байт из 536f6c6964697479 внутри таблицы utf8 вы сможете декодировать строку. Например, 53 соответствует верхнему регистру S, 6f соответствует нижнему регистру o, 6c соответствует нижнему регистру l, так далее…

Нестандартная кодировка = abi.encodePacked(…)

Solidity предлагает нестандартный режим кодирования данных с помощью встроенной функции abi.encodePacked(...). Это позволяет кодировать данные в необработанных байтах, не следуя соглашениям, указанным ABI.
Следующие правила ABI удаляются при выполнении упакованного кодирования в Solidity:
  • динамические типы ( например, строки, массивы и т. д.) кодируются как есть, без смещения или длины.
  • статические типы короче 32 байт ( например: uint8, bytes4 и т. д.) не дополняются нулями.
Давайте воспользуемся нашим предыдущим примером кодирования строки "Solidity"и сравним разницу между стандартной кодировкой через abi.encode(...) и нестандартное/упакованное кодирование через abi.encodePacked(...).
Код:
abi.encode("solidity") 
> 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004536F6C6964697479900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FACK000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000н . 

abi.encodePacked("Solidity")
> 0x536f6c6964697479

Как вы можете видеть сверху, abi.encodePacked(...)отбрасывает смещение и длину строки, чтобы просто вернуть представление utf8 строки в базе 16 (= шестнадцатеричное),
показать разницу между этими выводами:

Код:
abi.encode(0xaaaaaaaaa, 0xbbbbbbbb, 0xcccccccc) 
abi.encodePacked(0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc)
См. раздел «ABI > Нестандартный упакованный режим » в документации Solidity для получения более подробной информации об упакованном кодировании и его последствиях.

3.Solidity ABI — кодирование контрактных вызовов

Функции abi.encodeWithSignature(...)а также abi.encodeWithSelector(...)может использоваться в Solidity для подготовки полезных данных в необработанных байтах для вызовов внешних контрактов. Затем такие полезные нагрузки можно передать в качестве параметров низкоуровневым вызовам .call(...), .delegatecall(...) а также .staticcall(...) функциям.
Эти две функции будут кодировать переданные аргументы, начиная со второго параметра.

abi.encodeWithSelector(...)

Код:
abi.encodeWithSelector(...)

abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)

С помощью этой функции вы должны предоставить bytes4 селектор функции, которую вы хотите вызвать. Селектор состоит из первых четырех байтов хэша Keccak256 сигнатуры функции.
Есть несколько способов получить селектор функции:
  • если вы имеете в виду переменную типа контракта, используйте синтаксис someContract.someFunction.selector.
  • вы можете ссылаться на селектор функций interface напрямую.
Вы можете сгенерировать хеш bytes4 из строки подписи:

Код:
bytes4 selector = bytes4(keccak256("transfer(address,address,uint256)"));

Пример из Uniswap

Код Solidity Uniswap представляет собой действительно хороший пример того, как эти встроенные abi функции используются в Solidity. Взгляните на UniswapV2Pair

_safeTransfer(…) использование внутренней функции abi.encodeWithSelector для выполнения общего низкоуровневого вызова любого контракта токена ERC20, позволяя протоколу передавать, а затем перемещать средства между пулом Uniswap и контрактами токена ERC20.

Код:
function _safeTransfer(
    address token,
    address to,
    uint value
) private {       
    
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
    
    require(
        success && (data.length == 0 || abi.decode(data, (bool))),
        'UniswapV2: TRANSFER_FAILED');   
}

Аналогично приведенному выше примеру, bytes4 селектор универсального токена ERC20 функция transfer(...) в UniswapV2Pair генерируется, как мы описали:

Код:
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));

4. abi.encodeWithSignature(…)

Код:
abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)

Эквивалентно

Код:
abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)

Сопоставление Solidity с типами ABI​

Большинство элементарных типов, доступных в Solidity, являются частью спецификаций ABI. Это включает address, bytes32, и так далее.
Однако некоторые конкретные типы Solidity не имеют прямого соответствия в ABI. Поэтому они преобразуются в типы, определенные в ABI.
1*f4jYe6T6VSXPYruhjssL2w.png

Короче говоря, переменная типа address payable или же contract будет кодироваться/декодироваться под капотом ABI в качестве стандарта address.
То же самое касается struct. ABI будет кодировать struct как кортеж элементарных типов.
Раньше перечисления определялись в ABI по наименьшему uint достаточно большой, чтобы вместить все значения, определенные в перечислении.
Например, enum с 255 значениями или менее были бы сопоставлены с uint8 ABI, в то время как enum с 256 значениями или более были бы сопоставлены с uint16.
Это изменилось с версии Solidity 0.8.0, в качестве enum больше не может иметь более 256
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх