Смарт-контракты Ethereum, если верить официальному сайту, выполняются «в точности так, как запрограммированы, без какой-либо возможности простоя, цензуры, мошенничества или вмешательства третьей стороны». Сегодня я попробую разобраться, так ли всё радужно на самом деле, рассмотрев некоторые проблемы, с которыми пользователи смарт-контрактов сталкиваются на практике.
В конце статьи я резюмирую свои размышления краткой инструкцией по написанию безопасных смарт-контрактов.
Ремарки
- Речь в статье пойдёт только о смарт-контрактах Ethereum. Сообщество молчаливо отождествило «smart contracts» с «Ethereum smart contracts». Меж тем первое — скорее концепция, а второе — реализация, и вопрос о том, насколько эта реализация отвечает концепции, можно обсуждать (как и в принципе саму концепцию смарт-контрактов и другие возможные имплементации). Это тема сложная, недооценённая и интересная, но это не тема данной статьи, поэтому интересующихся отошлю к работам Nick Szabo. Соответственно, везде далее, где я говорю «смарт-контракты», я на самом деле имею в виду «смарт-контракты Ethereum».
- Речь в статье пойдёт только о языке смарт-контрактов Solidity как о самом популярном и для пользователя Ethereum по сути единственном на данный момент.
Проблемы безопасности смарт-контрактов
Речь пойдёт о проблемах, с которыми в итоге сталкиваются разработчики смарт-контрактов, а не о безопасности самой платформы (хотя немного и о ней). Условно разделим эти проблемы на следующие типы:
- проблемы в коде смарт-контракта;
- проблемы в процессе разработки;
- проблемы в языке;
- проблемы в концепции.
1. Проблемы в коде смарт-контракта
Под проблемами в коде смарт-контракта здесь я подразумеваю такие проблемы, которые решаются редактированием .sol-файла. Это, в частности:
- Известные уязвимые конструкции (например, re-entrancy).
Пример из жизни: re-entrancy, или, шире, нарушение паттерна Checks-Effects-Interactions — даже широко известные (и ранее проэксплуатированные) уязвимости продолжают встречаться в новых контрактах. - Авторские уязвимые конструкции (в частности, ошибки в логике кода).
Пример из жизни: MultiSig-кошелёк, который на самом деле не MultiSig. Для того чтобы подписать транзакцию и перевести средства, необходимо количество подписей владельцев кошелька, равноеrequired
. При этом для того чтобы поменятьrequired
(например, на единицу, чтобы дальше самому подписывать транзакции), достаточно подписи только одного из владельцев:
- Неудачная архитектура.
Пример из жизни: попытка имплементировать в рамках одного контракта и токен, и кошелёк, и хранилище ключей. Контракт получается чрезмерно усложнённым, код сложно поддерживать, аудировать, модифицировать. В итоге страдает и безопасность. - Низкое качество кода.
Пример из жизни: контракты с разными отступами, произвольными переходами на новую строку и пробелами. Код трудно читать, а значит, снова страдает безопасность. При том, что для Solidity уже есть линтеры (Solium, Solhit).
Проблемы в коде непосредственно приводят к атакам и потере средств. Хорошая новость в том, что проблемы в коде можно выявить в процессе аудита и устранить. При этом важно понимать, откуда они берутся, чтобы избежать их в дальнейшем. Поэтому переходим к следующему пункту.
2. Проблемы в процессе разработки
Проблемы в коде обусловлены в первую очередь неправильно выстроенным процессом разработки. Казалось бы, разработка ПО — дело давно изученное, с устоявшимися best practices. Тем не менее, молодость области смарт-контрактов, непропорционально большие деньги и хайп приводят к тому, что люди по тем или иным причинам пренебрегают стандартными процедурами, что часто приводит к серьёзным проблемам. Из самого типичного стоит упомянуть:
- ТЗ: большинство смарт-контрактов Ethereum пишется без технического задания. К чему это может привести, объяснять излишне.
- Сроки: в среднем на разработку выделяется очень мало времени, порядка месяца. Экстремальный пример из моей практики: заказчик попросил разработчика написать смарт-контракт за 3 дня до ICO.
- Уровень разработчика: в область приходит много людей, в том числе вообще без бэкграунда в программировании.
- Понимание экосистемы: а даже если с бэкграундом, то зачастую разработчики недостаточно глубоко погружены в тему и не понимают специфику смарт-контрактов.
- Цена разработки: и тем не менее, тех, кто пишет смарт-контракты, мало; ещё меньше тех, кто пишет их хорошо. Отсюда неоправданно высокая цена разработки.
3. Проблемы в языке
Добавляет дёгтя сам язык Solidity. Изначально он был создан скорее для того, чтобы его могли быстро освоить большое количество людей, чем для того, чтобы на нём было удобно писать безопасные смарт-контракты. Вот лишь некоторые его особенности, которые мешают безопасности:
- Множественное наследование,
using for
,call
/delegate call
— всё это запускает код не из текущего исходника, а значит, снижает читаемость и, как следствие, безопасность кода. - Checks-Effects-Interactions pattern — если конструкции, нарушающие CEI, небезопасны, то почему не запретить их (и многие другие) просто на уровне языка?
- Вызывая другой контракт, можно внезапно попасть в его fallback-функцию с неожиданными последствиями.
Понятно, что проект Ethereum развивается, и невозможно всё учесть заранее. Но в итоге разработчики вынуждены помнить о многих особенностях и пользоваться костылями, чтобы сделать свой смарт-контракт безопасным.
4. Проблемы в концепции
Однако самая серьёзная проблема ещё глубже — в том, что многие недостаточно хорошо понимают, для чего смарт-контракты нужны, что они могут, чего не могут и как работают. В худшем случае это приводит к тому, что смарт-контракт:
Не смарт:
- Плохо написан — делает то, что написано, но не то, что задумано.
- Сильно завязан на backend и/или frontend — а этот код уже не смарт-контракт, он выполняется обычным образом, и его выполнение полностью контролирует администратор сервера.
Не контракт:
- Не фиксирует обязательства сторон — например, ICO продаёт свои токены за ETH, но токены по сути являются фантиками, т.к. не налагают на того, кто их выпустил, никаких ограничений или обязательств.
- Допускает односторонние изменения — и получается, что одна из сторон может менять контракт уже после его подписания.
Пример из жизни: владелец контракта может произвольно менять капитализацию, курс и продолжительность продажи токена:
Не нужен:
- Смарт-контракт добавили не потому, что он был технически необходим, а в попытках придумать, что бы сделать на смарт-контрактах, и оседлать волну хайпа;
- Пытались придумать, как к существующему продукту прикрутить смарт-контракты.
Смарт-контракт как угроза безопасности блокчейн-стартапа
Что же в итоге? Хотели модно, безопасно, блокчейн, а получаем дорогой неподдерживаемый код, который угрожает безопасности и вообще не нужен. Хотели, чтобы наши смарт-контракты выполнялись «в точности так, как запрограммированы, без какой-либо возможности простоя, цензуры, мошенничества или вмешательства третьей стороны», и в итоге они действительно выполняются так, как написаны — только написаны они с уязвимостью. И нам остаётся лишь помахать ручкой своим средствам. Ну или сделать хардфорк (тем самым вообще-то дискредитировав саму исходную идею), если из-за уязвимости потеряли деньги в том числе создатели Ethereum.
Как писать безопасные смарт-контракты
На самом деле, конечно же, всё не так плохо. Я просто сгущаю краски в попытках обратить внимание на некоторые важные, на мой взгляд, моменты. В целом же область развивается, люди учатся, в том числе и безопасности.
Если же читатель решит-таки написать смарт-контракт, я бы посоветовал:
- понять, зачем нужен (и нужен ли) смарт-контракт;
- получить от заказчика или самостоятельно написать ТЗ;
- рассчитать время;
- использовать инструменты разработки и тестирования (Truffle, Remix, SmartCheck, линтер Solhint или Solium);
- написать тесты;
- провести несколько независимых аудитов и bug bounty;
- следить за безопасностью остальных составляющих проекта (сайт, Twitter и т.п.).
Следуя этим простым рекомендациям, можно избежать большинства описанных выше проблем (от хардфорка, впрочем, это всё равно не спасёт).
Статья была создана совместно с Евгением Марченко (eMarchenko).