Это углубленная серия, посвященная головоломкам безопасности смарт-контрактов команды Zeppelin. Мы изучаем ключевые концепции Solidity, чтобы решать головоломки на 100% самостоятельно.

Этот уровень требует некоторого программирования на ассемблере для развертывания крошечного контракта с EVM.

Что происходит при создании контракта

Напомним, что во время инициализации контракта происходит следующее:

1. Сначала пользователь или контракт отправляет транзакцию в сеть Ethereum. Эта транзакция содержит данные, но не содержит адреса получателя. Этот формат указывает для EVM, что это contract creation, а не обычная транзакция отправки / вызова.

2. Во-вторых, EVM компилирует код контракта на Solidity (высоком уровне, понятном человеку языке) в байт-код (низкоуровневый машиночитаемый язык). Этот байт-код напрямую преобразуется в коды операций, которые выполняются в одном стеке вызовов.

Важное примечание: байт-код contract creation содержит как 1) initialization code, так и 2) фактический runtime code контракта, соединенные в последовательном порядке.

3. Во время создания контракта EVM выполняет initialization code только до тех пор, пока не достигнет первой инструкции STOP или RETURN в стеке. На этом этапе выполняется функция контракта constructor (), и у контракта есть адрес.

3.1. После выполнения этого кода инициализации в стеке остается только runtime code. Затем эти коды операций копируются в память и возвращаются в EVM.

5. Наконец, EVM сохраняет этот возвращенный избыточный код в хранилище состояний вместе с новым адресом контракта. Это runtime code, который будет выполняться стеком во всех будущих вызовах нового контракта.

Проще говоря

Чтобы решить этот уровень, вам понадобятся 2 набора кодов операций:

  • Initialization opcodes: немедленно запускаться модулем EVM для создания вашего контракта и сохранения ваших будущих кодов операций времени выполнения, и
  • Runtime opcodes: содержать фактическую логику выполнения, которую вы хотите. Это основная часть вашего кода, которая должна возвращать 0x 0x42 и иметь менее 10 кодов операций.

На этом этапе, чтобы самостоятельно решить этот уровень, вы можете подробно прочитать о кодах операций и деконструкции смарт-контрактов (от автора этого уровня Ethernaut).

Чтобы получить дополнительные сведения, нажмите на…

Подробное пошаговое руководство

0. Включите консоль трюфеля с помощью Ropsten (или вашу предпочтительную настройку), чтобы иметь возможность напрямую развернуть байт-код в EVM. И откройте эту таблицу преобразование байт-кода‹ ›опкода для удобства.

Коды операций во время выполнения - Часть 1

Во-первых, давайте разберемся с runtime code логикой. Уровень ограничивает вас всего 10 кодами операций. К счастью, чтобы вернуть простой 0x42.

Возвращаемые значения обрабатываются кодом операции RETURN, который принимает два аргумента:

  • p: позиция, в которой ваше значение хранится в памяти, т.е. 0x0, 0x40, 0x50 (см. Рисунок). Давайте произвольно выберем слот 0x80.
  • s: размер ваших сохраненных данных. Напомним, что ваше значение составляет 32 байта (или 0x20 в шестнадцатеричном формате).

Напомним, что память Ethereum выглядит так: 0x0, 0x10, 0x20… в качестве официальных ссылок на позицию:

Но ... это означает, что прежде чем вы сможете вернуть значение, вы должны сначала сохранить его в памяти.

  1. Сначала сохраните значение 0x42 в памяти с помощью mstore(p, v), где p - позиция, а v - значение в шестнадцатеричном формате:
6042    // v: push1 0x42 (value is 0x42)
6080    // p: push1 0x80 (memory slot is 0x80)
52      // mstore

2. Затем вы можете return это значение 0x42:

6020    // s: push1 0x20 (value is 32 bytes in size)
6080    // p: push1 0x80 (value was stored in slot 0x80)
f3      // return

Эта результирующая последовательность кода операции должна быть 604260805260206080f3. Ваш код операции времени выполнения составляет ровно 10 кодов операции и длину 10 байтов.

Коды операций инициализации - Часть 2

Теперь давайте создадим контракт initialization opcodes. Эти коды операций должны скопировать ваш runtime opcodes в память, прежде чем возвращать их в EVM. Напомним, что EVM затем автоматически сохранит последовательность выполнения 604260805260206080f3 в блокчейне - вам не нужно будет обрабатывать эту последнюю часть.

Копирование кода из одного места в другое обрабатывается кодом операции codecopy, который принимает 3 аргумента:

  • t: конечная позиция кода в памяти. Давайте произвольно сохраним код в позиции 0x00.
  • f: текущая позиция runtime opcodes по отношению ко всему байт-коду. Помните, что f начинается после initialization opcodes конца. Какая проблема с курицей и яйцом! Это значение в настоящее время вам неизвестно.
  • s: размер кода в байтах. Напомним, что 604260805260206080f3 имеет длину 10 байт (или 0x0a в шестнадцатеричном формате).

3. Сначала скопируйте свой runtime opcodes в память. Добавьте заполнитель для f, поскольку он в настоящее время неизвестен:

600a    // s: push1 0x0a (10 bytes)
60??    // f: push1 0x?? (current position of runtime opcodes)
6000    // t: push1 0x00 (destination memory index 0)
39      // CODECOPY

4. Затем return вашу внутреннюю память runtime opcodes в EVM:

600a    // s: push1 0x0a (runtime opcode length)
6000    // p: push1 0x00 (access memory index 0)
f3      // return to EVM

5. Обратите внимание, что в целом ваш initialization opcodes занимает 12 байт или 0x0c пробелов. Это означает, что ваш runtime opcodes начнется с индекса 0x0c, где f теперь известен как 0x0c:

600a    // s: push1 0x0a (10 bytes)
600c    // f: push1 0x?? (current position of runtime opcodes)
6000    // t: push1 0x00 (destination memory index 0)
39      // CODECOPY

6. Окончательная последовательность такова:

0x600a600c600039600a6000f3604260805260206080f3

Где первые 12 байтов - это initialization opcodes, а последующие 10 байтов - это ваши runtime opcodes.

7. В консоли Truffle создайте свой контракт с помощью следующих команд:

> var account = "your address here";
> var bytecode = "0x600a600c600039600a6000f3604260805260206080f3";
> web3.eth.sendTransaction({ from: account, data: bytecode }, function(err,res){console.log(res)});

8. Найдите вновь созданный адрес контракта в возвращенном хеш-коде транзакции. Вы можете сделать это через Etherscan или через getTransactionReceipt (hash) .

9. В веб-консоли Ethernaut просто введите следующее, чтобы пройти уровень:

await contract.setSolver("contract address");

Чтобы узнать больше Solidity

Ознакомьтесь с Solidity Koans, чтобы узнать больше о Solidity посредством разработки через тестирование. В ближайшее время планируется открыть исходный код, поэтому все отзывы пользователей приветствуются!

Рекомендуемая литература

  • Для всех предыдущих уровней начни здесь.
  • Чтобы глубже погрузиться в чудесный мир сборки Solidity, рекомендую следующие статьи:




Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик