Это углубленная серия, посвященная головоломкам безопасности смарт-контрактов команды 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
: содержать фактическую логику выполнения, которую вы хотите. Это основная часть вашего кода, которая должна возвращать 0x0x42
и иметь менее 10 кодов операций.
На этом этапе, чтобы самостоятельно решить этот уровень, вы можете подробно прочитать о кодах операций и деконструкции смарт-контрактов (от автора этого уровня Ethernaut).
Чтобы получить дополнительные сведения, нажмите на…
Подробное пошаговое руководство
0. Включите консоль трюфеля с помощью Ropsten (или вашу предпочтительную настройку), чтобы иметь возможность напрямую развернуть байт-код в EVM. И откройте эту таблицу преобразование байт-кода‹ ›опкода для удобства.
Коды операций во время выполнения - Часть 1
Во-первых, давайте разберемся с runtime code
логикой. Уровень ограничивает вас всего 10 кодами операций. К счастью, чтобы вернуть простой 0x42
.
Возвращаемые значения обрабатываются кодом операции RETURN
, который принимает два аргумента:
p
: позиция, в которой ваше значение хранится в памяти, т.е. 0x0, 0x40, 0x50 (см. Рисунок). Давайте произвольно выберем слот 0x80.s
: размер ваших сохраненных данных. Напомним, что ваше значение составляет 32 байта (или 0x20 в шестнадцатеричном формате).
Напомним, что память Ethereum выглядит так: 0x0, 0x10, 0x20… в качестве официальных ссылок на позицию:
Но ... это означает, что прежде чем вы сможете вернуть значение, вы должны сначала сохранить его в памяти.
- Сначала сохраните значение
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, рекомендую следующие статьи:
Основы байт-кода и опкода Solidity
По мере того, как мы углубимся в написание смарт-контрактов, мы встретим такие термины, как« PUSH1 , SSTORE , CALLVALUE … medium.com »
Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик