Привет, Хабр! Сегодня мой рассказ пойдёт о техническом опыте проведения ICO на платформе Ethereum перед и во время ICO. Я поучаствовал пока в небольшом количестве проектов, но уже успел собрать некоторый технический бэкграунд. Могу поспорить, технический roadmap по проведению ICO аудитории Хабра будет интересен. Прошу под кат всех заинтересованных.
Подготовка экономической модели crowdsale
Всё начинается с заказчика, который хочет провести crowdsale. Хотя задача «выпустите нам N токенов, и пусть ваш контракт продаст их по курсу X к эфиру» кажется вполне себе понятной и простой, часто конкретные параметры подаются в режиме «мы сейчас не уверены, но перед ICO вам скажем». Очень часто заказчик знает только то, что он готов дать за свои токены, но совершенно не задумывается, каким образом он будет их раздавать, и считает, что количество токенов, даты, размеры дисконтов при продаже — эдакие свободные параметры, можно свободно крутить их в какой-нибудь админке и задеплоить контракт в ночь перед ICO. На самом деле это не так, и в реальной жизни надо учитывать риск даже банальной опечатки. Помните, что вы имеете дело со смарт-контрактом, который невозможно поправить, единожды задеплоив. И у вас есть отличная возможность (перепутав, к примеру, даты) выкатить контракт, который в какой-то момент залочит все средства пользователей — и вы никогда их не достанете.
Например, вы пишете что-то такое в каждой функции:
require(currentTime > icoStartTime);
Тем самым запрещая любые операции с контрактом вне заданных при деплое временных рамок. В этом случае опечатка при деплое может стоить довольно дорого, особенно если она останавливает контракт не в самом начале, а где-нибудь в середине. Разумеется, можно задеплоить новый контракт, если вы предусмотрели контракт-контроллер, но вам придётся исправлять собственные косяки, сделанные по невнимательности, на глазах у инвесторов. А те, наблюдая за вашей работой, в которой вы банально перепутали даты, сделают свои выводы.
Во многих моделях есть тонкие моменты касательно бонусов и реферальных программ. Особенно хороши реферальные программы, когда при предъявлении определённого секрета от одного пользователя другой пользователь получает бонус к своим токенам. Такие штуки хорошо работают в обычных системах, в которых порядок транзакций определяется на одном сервере и существует база данных всех пользователей. При реализации в смарт-контракте такая программа:
- может содержать часто невидимые при первом рассмотрении логические дыры, например позволяет создать цепочку инвесторов, которые по очереди зарабатывают бонусы сами на себе, циклически «продавая» сами себя;
- требует программировать дополнительные вычисления, не сильно сложные, но всё-таки более объёмные, чем просто присвоение адресу N токенов. А это дополнительные расходы газа, и чем хитрее алгоритм заказчика, тем дороже будет стоить транзакция, отправляемая в ваш контракт.
Любые дополнительные вычисления — это потенциально уязвимое место, арифметические переполнения в смарт-контрактах — это реальность.
Поэтому проработка экономической модели — самый важный начальный шаг. Заканчивается он тем, что на бумаге фиксируется конкретное задание на разработку. Причём старайтесь получить все константы сразу — и используйте их сразу при разработке. 90 % кода, который вам придётся писать, — это тесты контрактов, и их количество сильно зависит от количества периодов, дисконтов и т. п. Если за вечер до публикации контракта на GitHub к вам приходит заказчик и говорит, что решил проводить ICO год и каждый день снижать дисконт на 0,1 % вместо предыдущего варианта с тремя периодами, — вам придётся переписывать все тесты.
Как по мне, лучше всего попросить заказчика для начала подать задание в виде user-story про инвестора и бенефициара. Гипотетический инвестор первого сентября переводит на адрес контракта сумму N эфира и получает M токенов. Второго сентября бенефициар отправляет со своего личного адреса инструкцию изменить курс токенов и за один ETH выдавать X токенов. Третьего сентября второй инвестор отправляет N эфира и получает другую сумму токенов. Третий инвестор отправляет N эфира, но токены кончились, поэтому он получает столько, сколько осталось, а сдача в эфире ему возвращается. В каком-то подобном виде выжмите из заказчика истории типа этой, чтобы точно понять, что вы имеете в виду одно и то же.
Заранее закладывайте время на первый этап. Можно просидеть с клиентом пять-шесть часов, повторить подобные посиделки ещё несколько раз и так и не прийти к консенсусу. Если вы хотите сделать всё правильно и качественно — возможно, стоит после первой бесплодной встречи вежливо попросить клиента, чтобы он сначала хорошенько, за несколько дней и своей командой обдумал и расписал все аспекты — и лишь потом тратить деньги и время на разработку.
Ну ладно, давайте заканчивать уже первый раздел, опытным разрабам он знаком как «без хорошего тз результат хз».
Если вы даже в общих чертах не представляете себе запуск смарт-контракта в работу
Проматывайте этот небольшой раздел, если уже деплоили хотя бы один контракт: это очередная попытка кратко объяснить суть запуска контракта в работу.
Деплой проходит следующим образом. Вы из своего клиента Ethereum отправляете очень большую транзакцию, в которой находится код смарт-контракта. Вы говорите сети Ethereum: «Помести мой смарт-контракт по определённому адресу. С помощью этого адреса пользователи будут обращаться к контракту». Вы словно выкладываете свою версию сайта в интернет, и она по некому адресу начинает работать. Перезаписать другой контракт на место задеплоенного нельзя, никакой код не исправляется, возможно лишь помещение новой версии контракта по другому адресу. Контракт как обычный участник сети имеет свой собственный баланс эфира и умеет принимать и отправлять эфир, эдакий «автоматизированный кошелёк». Если вы просто пошлёте в него деньги (разумеется, если в контракте такой сценарий предусмотрен), то баланс у него увеличится. Если вы отправите 10 ETH и в соответствующем методе в контракте не сломаетесь, получите +10 ETH на баланс контракта. В случае crowdsale контракта внутри смарт-контракта функция, принимающая ETH, умножает полученную сумму на текущий курс токена и присваивает приславшему адресу соответствующее количество токенов. Фактически он просто записывает в блокчейн информацию, что этому адресу нужно начислить столько-то токенов.
К примеру, количество токенов, которое выдаётся на ICO, — всего лишь данные программы. Если точнее, балансы токенов — это значения в ассоциативном массиве, где ключами являются адреса Ethereum. Трансфер токенов от одного участнику другому можно рассматривать как вызов метода transfer
у смарт-контракта, помещённого по заданному адресу, который отнимет часть токенов у одного адреса и добавит другому. Эти изменения в памяти программы запишет в следующий блок майнер, который исполнит контракт и пришедшую в него транзакцию от пользователя.
Одна из особенностей программирования в этой области — публичность данных. Не забывайте, что любые данные, которые вы передаёте в любую функцию смарт-контракта, открыты. К примеру, нельзя простым образом играть в «камень, ножницы, бумага», тупо отправляя свой выбор в контракт. Если так делать, то можно будет подсмотреть предыдущую транзакцию и отправить свою уже с учётом увиденной. Поэтому алгоритмы программирования всяких тотализаторов, аукционов и т. п. всегда многоходовые. Сначала все стороны должны представить хеши своих решений — и лишь после этого опубликовать сами решения, от которых теперь уже не отказаться.
Ещё одна проблема — порядок транзакций. Дело в том, что майнер способен посмотреть, какая пришла транзакция, и быстро сгенерировать свою с большей комиссией, таким образом поставив её в блоке выше исходной. Так что на эти моменты стоит обращать внимание при проектировании вашего децентрализованного сервиса и процедуры crowdsale.
Технические аспекты разработки смарт-контрактов в Ethereum
Код контракта, выложенного в блокчейн Ethereum, доступен всем, но с оговорками. Фактически после того как транзакцию «create_contract» замайнят, в блокчейне будет доступен только байт-код контракта, а чтобы он был красиво представлен, например как здесь, нужно пройти верификацию контракта. А это, в общем, довольно муторная процедура. Просто пулять десятками контрактов в день без специальных инструментов у вас не получится. Ну а кидать деньги в неверифицированный контракт, чей код нельзя прочитать, инвесторы вряд ли захотят — так что необходимость верификации очевидна.
Публикация исходника смарт-контракта означает, что любой может увидеть весь интерфейс контракта и убедиться в том, что его не обманут, не отнимут токены, не начислят лишнего бенефициарам и т. д. Давайте не забывать, для чего созданы децентрализованные системы исполнения смарт-контрактов — это как раз публичное размещение программного кода таким образом, чтобы его исполнение было неоспоримым. Поэтому если у вас в контракте есть «интересная» функция, при помощи которой вы можете довыпустить произвольное количество токенов или перевести любое количество токенов с одного адреса на другой, то у всех инвесторов это вызовет неудобные для вас вопросы. Ни одну операцию с токенами не скрыть, потому что каждая транзакция и каждое действие, производимое ей в контракте, фиксируются в блокчейне эфира.
Я бы привёл несколько эдаких features; они вызывают у читающих код контракта инвесторов закономерные вопросы, которые могут сильно подорвать доверие к вашему проекту.
Feature 1 — довыпуск токенов после ICO. Если вы в white paper не обосновали довыпуск токенов после ICO, то функция, создающая N токенов для любого адреса, работающая после периода ICO, — это возможность бенефициаров в любой момент сделать себе 100500 токенов и, слив их на бирже, свалить подальше. Доверия к вашему crowdsale это не добавит.
Feature 2 — вывод средств без мультиподписи. Должен ли проект складывать весь эфир и другие валюты на кошельки, вывод с которых возможен с единственного компа? И дело тут даже не в порядочности бенефициара или в безопасности Ethereum. Даже папа римский, собирая средства на новую церковь, может воспользоваться уязвимым ноутбуком. А я напомню, что в случае ICO удачная атака на комп с секретными ключами к адресу, на который собираются средства, иногда приносит атакующему миллионы. Так что добро пожаловать в XXI век: с развитием крипты PC становится очень заманчивой целью для хакеров. И то, что бенефициары об этом не подумали и не снабдили свои адреса мультиподписью, также может вызвать смешанные чувства у инвесторов.
Кстати, чтобы вы были в курсе: вывод BTC с мультисиг-адреса Биткойна и вывод ETH c мультисиг-контракта в Эфире — вещи технически сильно разные. В Биткойне, чтобы вывести BTC с мультисиг-адреса, надо сформировать транзакцию с несколькими подписями заранее, прогулявшись с флешкой по всем подписывающим компам. А в Ethereum надо сначала поместить в сети мультисиг-контракт и, после того как на нем появится ETH, послать в него несколько транзакций с нужного количества компов. То есть в Эфире подписи шлются отдельными транзакциями, и ETH отправляется на указанный заранее адрес в тот момент, когда приходит ровно нужное количество валидных подписей, например две из трёх.
Обязательно заранее (за несколько дней) позаботьтесь о мультисиг-адресах. Их создание из различных клиентов типа Ethereum Wallet для Ethereum и Electrum для Bitcoin — задача простая, но надо позаботиться не о самом адресе — создать мультисиг действительно несложно, а о том, чтобы хотя бы на одном из мультисиговых компов (а это может быть зоопарк из новенького макбука owner’a, древнего ноута-мастодонта весом 7,5 кило и случайно накопанного нового ноутбука пятилетней давности в IT-отделе) была полная актуальная нода Ethereum, которая бы успевала догонять и процессить новые блоки при запуске быстро, за считанные минуты, — иначе вы ни в одном из своих мультисиг-клиентов не увидите актуальных цифр.
Feature 3 — отсутствие на GitHub кода контракта, выложенного за разумный срок до ICO. Если по активности на GitHub видно, что вы закоммитили сильно изменённый контракт в последнюю ночь перед ICO, то есть серьёзные основания полагать, что его готовили «на коленке». А это означает, что, скорее всего, вы не проводили аудит или подзапрогали что-то уже после аудита, потенциально добавив новые дыры. В идеальном мире вы публикуете код контракта за месяц до ICO — тогда без проблем можно успеть провести аудит кода, объявить bug bounty (оно как специально создано для ICO, ведь у owner’a есть секретное оружие — токены, которые он может очень щедро раздавать).
Верифицированный код, опубликованный в блокчейне, должен появиться за несколько дней до ICO. Вообще, если вы шлёте кучу эфира на какой-то адрес, хорошо бы всё-таки глянуть этот адрес в etherscan и убедиться, что это тот самый crowdsale контракт. Я бы вообще посоветовал всем, разработчик вы или нет, глядеть-таки в код контракта, даже если не сильно понятно. В хорошем контракте есть нормальные комментарии, доступные для понимания, а сам язык вполне себе ясен практически для любого разработчика.
Feature 4 — всякий интересный дополнительный функционал типа довыпуска токенов, их принудительной передачи по команде owner’a и т. п. К примеру, возможность досрочного вывода средств, если функция, отдающая эфир бенефициарам, не содержит никаких условий о времени вывода и владельцы контракта могут вызвать её в любой момент, чтобы забрать эфир. Обычно на период crowdsale блокируются как вывод эфира, так и любые операции с токенами.
В общем, принцип формирования таких feature вы поняли. Именно это и имеют в виду, когда говорят о новой цифровой экономике. Тут не разработчики нужны, а скорее финансисты, которые понимают, какой функционал может обмануть инвесторов, а какой полезен. В общем, разработчики — программируйте честно, а понимающие финтеховцы — давайте уже потихоньку садитесь за Solidity.
Раз уж мы обсуждаем проблемы разработки, то добавлю, что после запуска нескольких контрактов у нас появилась новая паранойя относительно timestamp’ов, которые задаются в контрактах вручную. Дело в том, что вероятность ошибиться в ts довольно высока, сколько бы вы ни говорили об аккуратности в задании констант. Бывает и так, что даты вам пришлёт owner проекта, а вы просто воткнёте их в контракт, потому что поздняя ночь и вообще перепроверять неохота. Мы даже слепили JS helper, который ищет timestamp’ы в скопированном коде и выводит их в виде отсортированного списка дат, так удобней проверять. Цена ошибки здесь неприлично высока. Если где-нибудь в третьем скидочном периоде crowdsale контракт вдруг застрянет или закроет crowdsale — будет очень неприятно.
Ещё немного о технических особенностях разработки. Для работы правильного эфирного кошеля с полным блокчейном вам понадобится нормальный комп с SSD. Сейчас поясню — весь блокчейн вам нужен не потому, что вы считаете, что в прошлом кто-то что-то изменил и украл весь ваш эфир, а чтобы при проведении crowdsale у вас была полностью синхронизированная с сетью нода, которая быстро и актуально получает новые блоки. Ведь вы хотите видеть актуальное состояние ваших сборов на проект и среагировать, если что-то идёт не так. А без SSD комп может «догонять» блокчейн очень-очень долго: речь идёт о сутках. С технической точки зрения это происходит потому, что для валидации смарт-контрактов клиентскому софту надо часто обращаться к произвольным блокам цепочки. А такое псевдорандомное чтение — худший паттерн для традиционных HDD, так как надо всё время переставлять считывающую головку. А вот для SSD такое чтение — конёк. Да ещё плюс играет на руку то, что блоки в блокчейне не меняются — а это означает небольшое количество перезаписей, что, по идее, SSD любят. Так что разница между HDD и SSD тут весьма серьёзная. Мне кажется, что и дисковому кешу тоже непросто с паттерном работы клиента Ethereum, так как прочитанный блок нечасто требуется снова и кеш здесь не особо эффективен. Это уже относится к обоим типам дисков.
Плюс когда мы говорили, что код контракта нельзя менять, мы, конечно же, были не совсем искренни. Вообще-то такая возможность закладывается. Для этого делают основной контракт-контроллер, эдакий прокси, хранящий в себе адреса контрактов, которые, возможно, придётся заменять. У него есть методы (доступные, разумеется, только owner’ам), позволяющие поменять внутренний адрес контракта, и, так как пользователи продолжают отправлять транзакции в контроллер, они отправляются уже в новый контракт вместо старого. Вообще любая более-менее сложная система смарт-контрактов не обходится без контрактов контроллеров. Об этом мы подробнее расскажем в других статьях. С точки зрения инвестора история с прокси-контрактами, с одной стороны, вроде хорошая, ведь если найдут ошибку, то разработчики смогут её исправить, заменив контракт, а с другой — подозрительная: те же разработчики могут в любой момент «исправить» контракт без возможности досрочного вывода средств на «более дружественную версию». Ну и до кучи, как водится, любое усложнение общей схемы, особенно в области, где один контракт вызывает другой (а здесь кроется масса интересных уязвимостей), приводит к большему количеству мест с потенциальными уязвимостями.
Если вы заказчик, ждёте от разработчиков разработанный смарт-контракт и втихаря злитесь на них, так как они отдают вам три файла суммарно в 100 значимых строк длиной и говорят «мы всё» — то вы правильно злитесь. На самом деле кода действительно немного, но вам вообще-то не в него надо смотреть, а в тесты. Именно их количество и качество говорят о проработке сценария crowdsale. В разработке смарт-контрактов тесты — это не опциональная, а обязательная часть разработки. При такой стоимости ошибки test-driven development — вообще единственный способ что-то нормальное написать. Именно тесты помогут вам поймать самые глупые и обидные баги, связанные с границами периодов, сменой дисконтов, особенно когда owner решает неожиданно поменять несколько констант.
Разработка web-морды
Есть два вида crowdsale — в чистом эфире и «все остальные — костыльные». Если бы заказчики ограничились сборами в одном эфире, разработчики были бы счастливы, а проблем с безопасностью стало бы на 80—90 % меньше (имхо). Ведь в случае токенов (где клиенту нужно всего-то видеть баланс да переводить токены туда-сюда) клиент сети (кошелёк) сам по себе — вполне функциональный личный кабинет с куда большей степенью защиты, чем то, что вы напрограммируете сами. История транзакций + backup вам достанутся прямо из коробки. Отдельный очень важный плюс — то, что в этом случае исходник страницы crowdsale может выглядеть вот так:
<html><h1>0x2bd1f12269c1ff80042c8d354bba4c1ca52e2061</h1></html>
Если наш контракт автоматически начисляет токены на адрес, с которого был прислан эфир, нам не нужен никакой личный кабинет, никакая персональная информация; нужно просто сказать человеку, куда отправить ETH. Ну или личный кабинет имеет косметическую роль, умеет обращаться к публичным нодам и красиво показывать баланс. Конечно, придётся защищать адрес от фишинговых атак, но это, в общем-то, и всё. Дыр в статическом html — абсолютный минимум, уложить такой сайт DDoS’ом очень проблематично, и наиболее вероятный вектор атаки — где-нибудь у клиента в сети (подмена сертификатов + фишинг) или социалка (типа напихать в чаты проекта кучу фишинговых адресов). Так что если вы за параноидальный минимализм, то сбор в чистом эфире — ваш вариант. Заодно можно и с контрактом не сильно заморачиваться, берите готовый, правьте даты-дисконты — и вперёд.
Если же owner хочет собирать в нескольких криптовалютах (а, посмотрев статистику сборов, он наверняка захочет, ибо есть регионы, в которых никто и не слыхал про Ethereum) — то придётся потрудиться, причём по сравнению с прошлым вариантом потрудиться гораздо серьёзней. Давайте объясню на примере BTC.
Из публичных данных необходимо будет показать пользователю адрес, куда переводить BTC. Теперь вам уже потребуется ЛК для указания каждым BTC-пользователем ETH-адреса, на который он желает получить свои токены. Токены у нас в Ethereum и могут начисляться только на ETH-адрес, так что надо, чтобы юзер его где-нибудь нарыл, сам создал себе кошелёк или сгенерировал прямо у нас в ЛК. То есть вы можете сами сгенерировать им пары ключей, только надо сделать так, чтобы они потом смогли бы загнать выданные им ключи в клиенты Ethereum, в MyEtherWallet какой-нибудь. Если хотите делать это честно, не подглядывая за секретными ключами, то придётся делать это в чистом JavaScript таким образом, чтобы ключи генерировались строго на клиентской стороне и вы могли бы доказать, что ваш сервер никогда не видит секретный ключ клиента. Реальный пользователь, конечно, скорее всего, не будет задаваться вопросом, где генерируются ключи. Но сами посудите, хочется ли вам отвечать за секретные ключи пользователей, если есть способ просто не хранить их у себя?
Зная, куда положить токены, можно перейти и к тому, сколько положить. Теперь пользователь должен указать BTC-адрес, с которого он инвестирует. Его хорошо бы проверить, в отличие от ETH-адреса для зачисления токенов, который необязательно должен быть представлен в блокчейне (это может быть новая пара ключей, ни разу не засвеченная). BTC-адрес стоит проверить при заведении, чтобы нельзя было завалить сервер бесплодными ожиданиями прихода BTC с мусорных адресов. Хотя это добавочный функционал, а значит, потенциальное место для атаки, так что можно и не заморачиваться: в конце концов, верно указать данные — в интересах пользователя.
В принципе, после этого онлайн-история с личным кабинетом заканчивается. В случае остальных криптовалют всё похоже на BTC: юзер должен ввести адрес, с которого собирается инвестировать, а мы его сохраняем, чтобы потом идентифицировать.
Затем уже в offline по крону какому-нибудь наш скрипт пылесосит блокчейн BTC (LTC, DASH и т. п.) и ищет в нём транзакции со знакомых адресов на наш, указанный в ЛК адрес. Если такая транзакция есть — вычисляется количество токенов, и скрипт дергает ноду Ethereum (заботливо подготовленную либо публичную), отправляя в token-contract транзакцию, вызывающую метод mint(address addr, uint amount)
, который добавляет новую запись про ETH-адрес addr
и его баланс токенов amount
в хранилище смарт-контракта. В этом cron-скрипте нам, в общем-то, всё равно, что парсить; если у вас есть лог платежей через VISA или MasterCard, можно парсить его и минтить токены по соответствующему курсу.
Общие вопросы безопасности crowdsale
При разработке обеих частей, смарт-контрактов и web-морды, надо понимать, насколько «вкусен» для атакующих сайт crowdsale. Вообще традиционные уязвимости применимо к ICO становятся действительно критическими. После успешной атаки на традиционные сервисы у атакующего остаётся ещё проблема вывода денег — ему надо нанимать гоблинов для обналичивания средств с ворованных сайтов, перепродавать слитую информацию, контактировать с другими преступными сетями. В случае крипты утёкшие на подставной адрес биткойны или эфир уже не вернуть — поэтому атакующему достаточно всего лишь подменить одну строчку другой. А учитывая объёмы, собираемые ICO, атакующим ещё и очень выгодно осаждать именно такие ресурсы: игра точно стоит свеч. Так что, если у вас вкусное ICO, можно ожидать всего: от троянов, которые подменяют адреса прямо в буфере при copy-pastе, до полномасштабных DDoS-атак на сайт crowdsale.
Именно поэтому я так давил на проведение crowdsale строго на Ethereum, стараясь минимизировать поверхность атаки: меньше запросов в БД — меньше SQL-инъекций, минимум вывода user-supplied данных — минимум потенциальных XSS. Так что лучший кабинет инвестора — Ethereum Wallet.
DDoS сайта crowdsale — тоже модный тренд. Видимо, когда инвесторы находятся в режиме «shut up and take my money», некоторые из них всё-таки засылают эфир на фишинговые адреса, как, например, на вот этот, наловивший 133 ETH (~40к$):
https://etherscan.io/address/0x5d3f32f4b2e99fb79d2f6a1cbf3aa7390f8fc751
Поэтому чем проще будет ваш суровый и немногословный crowdsale сайт — тем лучше. Возможно, стоит поместить сайт на каком-нибудь Cloudflare, т. е. на сервисе, который специально заточен на защиту от DDoS и атак на сайты. Но если накрутите дырявых украшательств сайта, то не спасёт и Cloudflare: перезаписи одной строчки адреса на сайте достаточно, чтобы множество людей потеряли деньги, и это будут не банковские транзакции, которые можно откатить.
Разумно также иметь fallback-сценарии для основных проблем — сайт лежит / контракт сломался. Надо заранее понять, что вы будете делать, если произойдёт худшее, проговорить все последовательности действий в случае DDoS, нашествия фишеров в чатах, взлома контракта. Если баг позволяет выводить из контракта средства последовательно множеством транзакций, то скорость, с которой ваша команда соберёт мультиподписи с команды (в 04:30 ночи), может стоить проекту кучу эфира. И к таким вариантам должны быть готовы не только разрабы и девопсы, но и SMM — им нужны готовые официальные тексты на каждый плохой кейс и возможность оперативно банить плохишей в чатах. В качестве хорошей новости отмечу, что если у вас всё хорошо с собранным эфиром (вы используете надежный мультисиг-кошель), а проблемы в crowdsale-контракте, или вы набажили в датах-скидках, или кто-то стырил себе миллиард токенов, то балансы токенов потом можно-таки восстановить, просмотрев транзакции, все необходимые данные там есть.
Пока писал статью — подоспел и пример, который показывает, насколько рискованным может быть заход в ICO и «надёжный мультисиг-кошель»: это баг в мультисиг-кошельках Parity (Security Alert от 2017-11-08). Один из смарт-контрактов, используемый другими контрактами как библиотека, был «убит» (вызовом метода selfdestruct()
) по ошибке, сломав все использующие его контракты-кошельки. Пока я ещё не знаю, чем кончится дело, но всего залочилось около 150M долларов. Как это ни грустно, но если вы проводите ICO, то должны учитывать и риск, что всё станет плохо и без вашего участия. Ребята из Parity — такие же программисты, как и остальные, и совершают ошибки, как любой из нас, и я совершенно не уверен, что у других нет похожих проблем.
Обычно аудит безопасности всего crowdsale-комбайна не занимает большую часть мыслей owner’а, а зря. Задачи по аудиту безопасности web-морды и самого контракта чуть ли не важнее, чем сама разработка контракта и морды, которые довольно скоро станут стандартными либо переедут в конструкторы ICO. Так что на это стоит запланировать время и деньги заранее, и хорошо бы было считать нормальной практикой аудит подобного софта как минимум несколькими внешними командами. Цена ошибки крайне высока, а для того чтобы сделать аудит именно вашего проекта привлекательным и недорогим, у вас есть токены, серьёзные ребята выставляют серьёзные ценники, и токены тут могут сильно подсобить.
Деплой и мониторинг процесса crowdsale
В процессе ICO мониторинг процесса необходим, и лучше, чтобы автоматизированных средств там было побольше. Так как география у ICO широкая, проблемы могут начаться в любое время суток. Плюс ICO может идти месяц, два, год. Месяц никто не будет сидеть перед экраном монитора, смотреть логи, так что надо хоть как-то автоматизировать процесс. Пусть даже это cron-скрипт, который всё время ходит на сайт и проверяет, есть ли на нём адрес crowdsale:
wget -O- https://myico.com | fgrep ‘<h1>0xTRUE_CROWDSALE_ADDRESS</h1>’
Делайте на чём угодно, но лежащий сайт ICO — это точно сигнал всем приготовиться спасать эфир и токены. Ну и конечно, смотрите на транзакции (ставьте свою ноду, парсите etherscan) — фиксируйте вывод эфира из контракта. Входящие транзакции — окей, выходящие транзакции (в период ICO) — это, скорее всего, успешная атака.
Выводы
Надеюсь, что мне удалось немного развеять туман вокруг расплодившихся ICO и рассказать что-то интересное. Пишите хороший код, пилите крутые децентрализованные проекты, развивайте новые алгоритмы и криптопротоколы, пишите много документации и статей. Для освоения Солнечной системы нам точно понадобятся децентрализованные решения, пора уже начинать ими заниматься вплотную.
И следите за обновлениями на Хабре, скоро мой коллега опубликует вторую часть, где некоторые аспекты разработки будут рассмотрены более подробно.
Комментарии (33)
amaksr
10.11.2017 17:55Тревожит этот момент
Перезаписать другой контракт на место задеплоенного нельзя, никакой код не исправляется, возможно лишь помещение новой версии контракта по другому адресу.
Т.е. если в коде баг, то можно потерять собранные эфиры. Проблема в том, что код это всегда баги.BoogerWooger Автор
10.11.2017 18:02именно так, существует состояние когда ничего сделать нельзя. Но в принципе для крипты эта проблема повсеместная, потеряв секретный ключ от адреса точно так же необратимо можно лишиться любого криптоактива
amaksr
10.11.2017 18:20От потери ключа легко подстраховаться. От багов в коде подстраховаться невозможно. Отрицать возможность багов даже в коротком коде на несколько страниц — это отрыв от реальности, любой разработчик подтвердит. А тем более если код этот написан в спешке.
BoogerWooger Автор
10.11.2017 18:29я не отрицаю баги ни в каком коде, любого объёма. Просто сообщил, что когда кода меньше и он проще — потенциальных багов в нём меньше.
hlogeon
10.11.2017 19:10А про Proxy контракты Вы ничего не слышали?)
BoogerWooger Автор
10.11.2017 19:14Я вроде про них написал: «С точки зрения инвестора история с прокси-контрактами, с одной стороны, вроде хорошая, ведь если найдут ошибку, то разработчики смогут её исправить, заменив контракт, а с другой — подозрительная: те же разработчики могут в любой момент «исправить» контракт без возможности досрочного вывода средств на «более дружественную версию»». Я их правда назвал контроллерами, и в принципе «контроллер» мне больше нравится, ибо подразумевает возможность управления
hlogeon
10.11.2017 19:11Помните, что вы имеете дело со смарт-контрактом, который невозможно поправить, единожды задеплоив. И у вас есть отличная возможность (перепутав, к примеру, даты) выкатить контракт, который в какой-то момент залочит все средства пользователей — и вы никогда их не достанете.
Ага, а геттеры и сеттеры с модификатором onlyOwner это вид черной магии.BoogerWooger Автор
10.11.2017 19:17они позволяют заменить код контракта, не изменив его адрес?
hlogeon
10.11.2017 19:20Они позволяют установить нужные значения в свойства контракта не изменив его адрес. Если тебе говорят, что не знают, по какой цене будут продавать токены, то делаешь свойство tokenPrice и метод setTokenPrice с модификатором onlyOwner. И не надо ничего передеплоивать при изменении мнения заказчика относительно цены.
BoogerWooger Автор
10.11.2017 19:37код методов-то остаётся неизменным. Внутреннее состояние контракта ясен пень меняется, иначе нафига они — смартконтракты эти? А вот код контракта заменить нельзя.
Только на тыкву, с помошью selfdestruct()
hlogeon
10.11.2017 19:18Довольно забавно еще читать про «точку зрения инвесторов». Это я о верифицированных контрактах, контрактах-контроллерах и тому подобном. Якобы, инвесторам это важно. Может быть, у вас какая-то другая практика, но по моему опыту, крупные инвесторы, которые дают 80% средств ICO вообще не понимают, что такое смарт-контракт и уж тем более не разбираются ни в каком коде. Многим даже приходится объяснять, как купить крипту. Код смотрят программисты. Ни больше ни меньше и рассчитывать что кому-то кроме них он будет интересен — такое себе. Это конечно не отменяет необходимость верификации, но причины совершенно другие, нежели траст со стороны инвесторов
BoogerWooger Автор
10.11.2017 19:26Я конечно имею в виду умных инвесторов, которые изучают проект, прежде чем в него инвестировать, а не тех, кто сейчас «даёт 80% средств ICO». То, что сейчас не смотрят код контрактов, скоро изменится. Люди вообще довольно быстро начинают догонять, когда их кидают на деньги.
therealal
11.11.2017 14:11У инвесторов свои каналы получения информации. Если они не могут прочитать solidity-код (даже если кто-то может — тратить свое время на это будет крайне неэффективно), это не значит, что они не узнают, что ICO «X» looks scammy.
hlogeon
11.11.2017 15:37Looks scammy вытекает вовсе не из кода, вот о чем я говорю. Вообще, сейчас вполне нормальная практика иметь ЛК инвестора, в котором ты даже не можешь посмотреть адрес СК. 90% ICO которые я вижу сейчас поступают так. Для интересующихся всегда есть github. А скамануть можно и с безупречным смарт-контрактом и целым отрядом Escrow-агентов. Скам\не скам это не об этом
therealal
11.11.2017 16:23Вообще, сейчас вполне нормальная практика иметь ЛК инвестора, в котором ты даже не можешь посмотреть адрес СК. 90% ICO которые я вижу сейчас поступают так.
90% по сборам или штучно? Понимаете, к чему я веду?
А скамануть можно и с безупречным смарт-контрактом и целым отрядом Escrow-агентов.
Это аргумент из серии «кому надо — все равно сломают». Конечно. Но при прочих равных лучше иметь прозрачные, криптографически гарантированные условия.hlogeon
11.11.2017 19:03Что-то Parity это не помогло, понимаете к чему я?) Хотя их контракты посмотрел не один десяток программистов
BoogerWooger Автор
13.11.2017 01:29«Нормальная практика» она потому, что компаниям похрен на качество, и они ждут только бабло, а разработчики и рады покорее отстреляться и пойти к следующему клиенту. Это нифига не нормальная практика, и она обречена, как только на рынок зайдёт достаточное количество скиллованных компаний и разрабов.
hlogeon
13.11.2017 13:43Пруф, или не было)
что компаниям похрен на качество, и они ждут только бабло, а разработчики и рады покорее отстреляться и пойти к следующему клиенту.
Более того, я не уверен, что большинство ICOшек аутсоряст создание СК и личных кабинетов. По моей личной практике и опыту общения с другими компаниями, большинство все таки стараются делать это in-house. А в таком ключе Ваши слова выше вообще слабо соотносятся с реальностью.BoogerWooger Автор
13.11.2017 15:42Хм, по нашему опыту, больше чем половина заказчиков точно хотели бы отдать весь процесс ICO на аутсорс. Разработка самого проекта — это да, предпочитают своих растить, а вот ICO — это отдельная песня, ее обычно хотят отдельной историей провести.
Про «пруф или не было» — если вы про «нормальную практику», то когда меня знакомый инвестор просит посмотреть что-как с проектом, то, если я не могу увидеть верифицированный код контракта, то просто говорю, что там по раздолбайски отнеслись к деплою — и я бы этим чувакам серьезные инвестиции бы не доверил. Он уже делает свои выводы.
JacobL
10.11.2017 20:18Спасибо за отличную статью.
А как происходит выпуск токенов, которыми контракт торгует?BoogerWooger Автор
10.11.2017 20:49выпуск токена это просто запись, внесённая в storage контракта, фактически делается
tokenBalances['0xADDRESS_FROM_WHICH_ETH_CAME'] += nTokens;
а трансфер с одного адреса на другой:
tokenBalances['0xSOURCE_ADDR'] -= nTokens;
tokenBalances['0xDEST_ADDR'] += nTokens;
############
и эти изменения в storage контракта сохраняются в блокчейне (майнер исполняет контракт, и если всё ок — фиксирует изменения в storagе в свежесмайненном блоке)
JacobL
11.11.2017 09:39То есть на основе данного storage у нас получается новая криптовалюта?
Насколько в этом случае реально договориться с какой-нибудь биржей, чтобы организовать торги этими токенами?
Это я к вопросу о безопасности продаж. Переложить частично на биржу.hlogeon
11.11.2017 19:28Скажем так: не просто, но более, чем реально. Большинство токенов так и делают — договариваются с биржами о торгах после ICO.
to0n1
12.11.2017 04:14Есть «интерфейс» для токенов(ERC20), который приводит их к общему виду. Соответсвенно биржам намного меньше работы по внедрению новых токенов
BoogerWooger Автор
13.11.2017 01:23Существует стандарт ERC20 — он фактически говорит «у вашего контракта токена должны быть методы balanceOf(...), totalSupply(), transfer(...) и т.п read-more. После этого бирже (которая фактически проводит транзакции за вас) достаточно знать лишь адрес контракта токена (например SONM токен — адрес 0x983f6d60db79ea8ca4eb9968c6aff8cfa04b3c63б) и теперь биржа может просто посылать транзакции с вызовами, например „transfer“ чтобы торговать определенным токеном. Т.е. надо просто заставить биржу добавить себе в список еще одни ETH адрес и название токена, под которым он будет листиться на бирже, а операции с любым токеном ERC20 стандартны
remoteadmiral
13.11.2017 01:05Было бы интересно почитать каким образом происходит сборка проектов, сдача релизов, тестирование(кем и как), согласование ТЗ на практике. Имеется ли опыт сдачи проектов с частично закрытым кодом но в котором разумеется детально описаны условия тестирования и приемки? Примеры подобных проектов?
BoogerWooger Автор
13.11.2017 01:15Я не очень вопрос понял — если вы про ICO — то это в общем-то «одноразовый» проект. Смарт контракт не должен работать после ICO, сайт crowdsale тоже. Код контракта открыт полностью, «собирать» его не нужно. Сайт — это как принято у вас в компании. Или вы про сами блокчейн-проекты и методы работы команд с ними?
hlogeon
13.11.2017 13:49Можете посмотреть в сторону truffle framework, там есть инструменты для тестирования и деплоя. «Сборка», как правило ограничивается заменой импортов содержимым тех самых файлов, например, можно использовать npm-пакет `sol-merger`. Но такая «сборка» нужна только в целях верификации контрактов, потому что верификация etherscan не умеет работать с импортами.
svr_91
Мне кажется, в случае биткоина нужно не запрашивать у пользователя адрес, с которого он будет посылать, а генерировать новый адрес для принятия платежа. Так как у пользователя может быть множество адресов на одном аккаунте и он сам может не знать, с какого адреса кошелек проведет транзакцию
BoogerWooger Автор
Да, можно и так, это тоже вариант — я вот поленился расписать абзац про адреса. Есть даже еще более правильный с точки зрения безопасности вариант. Дело в том, что и Ethereum и Bitcoin используют один и тот же ассиметричный алгоритм — и если у инвестора есть BTC адрес, то секретный ключ от него «подходит» к «парному» ему ETH адресу. Проблема только в том, что адреса ETH и BTC — это не чистые публичные ключи, а хеши от них, причем хеш функции разные — SHA-256 и Keccak-256 вроде. Поэтому, если юзер даст нам свой публичный ключ от BTC, мы можем без проблем создать ему адрес ETH, который управляется тем же секретным ключом, что и BTC. Но этот способ почему-то не используют, видимо инвесторам это сложно проделывать, проще выдать готовые адреса.
grumbler66rus
Наверное, сложно объяснить инвестору, что от него требуется. :)
BoogerWooger Автор
Да, надо писать инструкцию по каждому кошельку, как достать свой public key, куда его вкопипастить и почему это безопасно. А учитывая, что пар ключей у юзера много, ошибок не избежать. Так что этот вариант походу не катит, хотя с точки зрения дизайна он хороший — нет необходимости хранить лишние данные. Человек сделал транзакцию на BTC адрес (значит у него точно есть ключик к адресу, с котрого он шлет BTC), и мы ему гарантированно безопасно начисляем токены на derived ETH адрес, никакой другой инфы не нужно