В статье мы рассмотрим технические аспекты подготовки и проведения ICO. Это продолжение статьи моего коллеги «Технические особенности проведения ICO. Начало», и здесь мы поговорим подробнее о некоторых технических вопросах, связанных со смарт-контрактами сети Ethereum.
Разработка техпроекта
Прежде чем разрабатывать смарт-контракт, хорошо бы сделать технический проект. Это принципиальная схема вашей системы контрактов, которая, отвечая на ключевые технические вопросы, показывает реализуемость экономики проекта и вашего сценария ICO.
Что обычно включается в техпроект? Это необязательно должен быть толстенный формальный документ, достаточно четырех-пяти страниц с ключевыми моментами и пары диаграмм. И на этих страницах для начала рассмотрите механику владения. Самый простой вариант: реализуется один владелец токена, ICO — всего подряд. Обычно так и делают. Более сложный вариант — когда идет в ход мультиподпись. Можно взять за основу надежный контракт кошелька, извлечь оттуда мультиподпись и применять ее не только для кошелька и хранения эфира, но и для управления самим ICO и токеном. Тут ваша фантазия не ограничена, вы можете математически легко реализовать самые разные сценарии.
Хорошо, когда ICO декомпозировано, т. е. представляет собой не один контракт, который считает курс, выдает токены и принимает эфир, а систему смарт-контрактов, когда каждый контракт занимается своим делом. То есть вы принимаете эфир на этапе ICO, который в зависимости от своей логики понимает, сколько токенов вам нужно выдать. Он дает соответствующую команду контракту токена, владельцу токена, и в то же время он не держит эфир в себе, а переводит его в специальное хранилище, которое также является контрактом.
Я не вижу никакого криминала в том, чтобы хранить эфир в контракте. Наоборот, это даже более прозрачно, поскольку понятно, что делает контракт. Можно легко видеть, что эфир там хранится до окончания ICO, а если не собрали денег, то он гарантированно все вернет инвесторам, и даже владельцы не могут этому помешать.
С другой стороны, такое усложнение не бывает бесплатно. Можно сказать, что это порождает дополнительные проблемы на этапе деплоя. И да, эти соединения и вызовы — лишняя возможность внести уязвимость. Очень внимательно следите за правами.
Есть стандарт на токен, он называется ERC20, он определяет несколько методов, которые нужно реализовать в контракте. Если ваш токен реализует этот стандарт, то он может выводиться на многие биржи. Кроме того, ERC20 поддерживают и другие кошельки помимо Ethereum Wallet. Таким образом, просто реализовав нужные функции, вы получаете уже готовую инфраструктуру для работы именно с вашим токеном. Более того, есть библиотеки, которые закладывают базу для токена и сразу реализуют ERC20. Реализовывать стандарт необязательно, если у вас какой-то сугубо внутренний ресурсный токен и если не предполагается, что пользователи будут им обмениваться между собой. Если они приходят с этим токеном в парк развлечений и покупают себе билет на аттракционы, то, наверное, ему не нужен листинг на бирже.
Кроме того, в контракт можно заложить состояние паузы в случае проблем или странной активности. Например, когда эфир льется, а токены не выдаются. Тогда надо приостановить ICO, разобраться и принять решение. Стоит заложить это состояние в техпроекте. Также вы можете предусмотреть эмиссию токена в техпроекте. Важно подумать об этом до ICO, поскольку нужно понимать, как это будет совместимо с токеном, сможете ли вы делать потом дополнительные эмиссии.
Обязательно продумайте такую схему — это граф состояний вашего ICO. Она представляет собой конечный автомат, т. е. состояния, в которых может находиться ICO. Это и есть граф, он описывает состояния и говорит, при каких условиях происходит переход из одного в другое. Ваше ICO не должно просто так менять состояния с собирания денег на «Успешно собрали. Давайте скорее их выводить». Вы должны понимать, каковы переходы между состояниями в ICO. Это должны понимать и инвесторы, потому что они видят, что все по-честному. И это должны понимать и четко запрограммировать разработчики. Для этого в Solidity есть все средства, есть так называемые перечисления, с помощью которых описывается состояние. Кроме того, я рекомендую сделать переходы между состояниями, контролируемыми кодом, так, чтобы недопустимых переходов не было.
Любой функции, которая доступна снаружи контракта, вы можете задать состояние с помощью так называемых модификаторов Solidity. Так, инвестировать можно только в состоянии ICO, но не паузы. И наоборот, выводить деньги владельцы могут только при успешно завершенном ICO.
Разработка смарт-контракта
Когда техпроект готов, заказчик счастлив, а разработчик понимает, что нужно делать, мы переходим к разработке. В Ethereum есть виртуальная машина (Ethereum Virtual Machine, EVM), она выполняет эти самые программы контрактов, а контракт — это так называемый байт-код, который исполняется виртуальной машиной. В связи с этим появляется возможность разрабатывать контракты на нескольких языках. Язык Solidity создан специально под задачу разработки смарт-контрактов с нуля, он имеет C-подобный синтаксис. Кроме него, есть другие языки: есть похожие на JavaScript, есть язык, похожий на Python.
Мое интуитивное мнение состоит в том, что лучше выбрать Solidity даже по причинам нейрофизиологического характера. Когда разработчик пользуется языком, который похож на Python, он тем не менее пишет смарт-контракт Ethereum. Но при этом у него в голове начинают всплывать семантики и паттерны, типичные для Python, и они неочевидно работают при разработке под Blockchain.
Говорить о Solidity можно долго, поэтому нужны ориентиры. Во-первых, сильно облегчает разработку фреймворк Truffle. Он помогает проводить тестирование контракта, деплоить его в основной, тестовый, локальный блокчейны. И даже позволяет это делать инкрементально, т. е. дополнять свою систему контрактов новыми контрактами. При этом фреймворк запомнит все старые и не будет их заново перевыкладывать.
Почему я призываю переиспользовать библиотеки? Сообщество довольно молодое, мало кода, но все принципы, полученные в разработке софта за последние 30—40 лет, остаются в силе и в случае Blockchain. И они нам говорят, что если есть код, который достаточно хорошо проверен, который предполагает сам по себе переиспользование в широком круге проектов, то стоит взять его, а не изобретать велосипед.
Прежде всего отмечу библиотеку OpenZeppelin/zeppelin-solidity. Она находится на грани де-факто стандарта при разработке смарт-контрактов. Там не так много контрактов и библиотек, они относительно невелики. Но они покрывают частые случаи, предоставляют вам из коробки многие вещи: владение контрактом, какие-то токены, токены, которые можно создавать, сжигать, несколько типичных ICO, кое-что связанное с безопасностью, например защиту от reentrancy-атаки.
Еще одна библиотека — SafeMath. Рекомендую использовать ее, даже если вам не хочется подключать Zeppelin-solidity. Она маленькая, буквально несколько однострочников, ее можно скопировать прямо себе в контракт. Она страхует вас от переполнений. Это очень актуальная и серьезная проблема. Например, когда вы имеете дело с беззнаковыми числами в Solidity и по недосмотру вычитаете из тройки пятерку, то контракт продолжает работать, но как результат вам показывается огромное число. Это случилось из-за переполнения. Если вы используете библиотеку SafeMath — происходит ошибка, т. е. транзакция завершается с ошибкой, а состояние не изменяется.
Существует много рекомендаций. Я упоминал о конечном автомате, желательно его реализовать: прописать состояния и модификатор. Кроме того, переходы из одного состояния в другое можно реализовать по времени. При этом к каждой функции добавляется модификатор, который говорит: «Я буду смотреть на время, и если сейчас уже начало ICO, то я изменю состояние на ICO».
Есть шаблоны, типичные паттерны, которые начинают формироваться в мире Solidity, в мире смарт-контрактов. Например, вы слышали, что есть много шаблонов в разработке софта, тем более каких-то корпоративных систем. Здесь к таким можно отнести, например, известный шаблон withdrawal pattern, когда пользователь снимает причитающиеся ему средства сам, а не когда вы пытаетесь послать их ему. Например, при неудаче ICO, когда требуется вернуть деньги инвесторам, если вы попробуете сделать это сами — то, скорее всего, у вас это по ряду причин не получится. Также это чревато проблемами с безопасностью. Если оформить возврат как снятие средств пользователями с соответствующими проверками, станет гораздо проще.
Я упоминал о режиме паузы. Нечасто используется в ICO, которые мы в последнее время видим, но это очень полезный механизм. Понятное дело, он не страхует от 100 % проблемных ситуаций (даже несмотря на паузу, можно украсть эфир), но существенно снижает возможность атаки.
О замене контрактов должен сказать, что необязательно идти до конца и делать совершенно динамический контракт, код которого может быть произвольно заменен с помощью, например, вызова delegatecall
и т. д. Достаточно, чтобы эта частичка, этап ICO, которая содержит в себе нетривиальную накрученную логику, могла быть заменена, если обнаруживается проблема. Это возможно за счет того, что токены и деньги у вас отдельно, деньги остаются без изменений. В принципе, можно заменить эту частичку и связать ее с двумя уже имеющимися частями совершенно прозрачно. При этом есть особенности. Если ICO производит инициализацию, например токена, то примите меры, чтобы при замене инициализация не стартовала второй раз. У вашего ICO в результате появится новый адрес, это новый контракт. Возможность замены будет видна в коде контрактов, и нужно понимать, что это некий компромисс. С одной стороны, хочется, чтобы все было абсолютно неизменяемо (это повышает доверие к вам), с другой стороны, неплохо бы подстраховаться. Как компромисс вы можете прописать в коде условия, которые совершенно неизменяемы. Например, прописать в хранилище эфира, что минимальный порог сбора средств неизменяем в принципе. Тогда инвесторы будут знать, что не случится так, что собрали 100 тысяч, а хотели собрать 100 миллионов, но все равно взяли что есть.
По возможности следует проверять на разумность параметры, которые методы вашего контракта получают на вход. Допустим, если у вас токен поддерживает эмиссию и в ее параметрах задается число токенов, которое нужно сгенерировать, то вы можете это число проверить, например посмотрев, не больше ли оно, чем сейчас токенов вообще есть. Это может вас подстраховать, когда есть опечатка во вводимом вручную параметре. Или, например, если эмиссию инициирует внешняя инфраструктура, когда в ней происходит сбой — она вам может заэмитировать миллиарды миллиардов токенов.
Еще три ссылки на GitHub:
Здесь набор репозиториев с кое-какими решениями. Это экспериментальные вещи, пробуйте их крайне осторожно, и обычно там так и написано: «Это альфа-версия, не храните на ней больше 20 долларов», но понятно, что все хорошие вещи рождаются из экспериментов. Может быть, лучше взять за основу что-то готовое, чем писать все самому с нуля.
Какую IDE взять для solidity-разработки? Я привык активно работать в командной строке, Truffle я запускаю из командной строки, и на самом деле там требуется всего три команды: test
, compile
, migrate
. Что касается среды разработки, я выбрал PyCharm, есть бесплатный плагин для Solidity, и это неплохая вещь. Среда разработки очень помогает вам, и PyCharm/Idea — хорошая IDE, я бы даже сказал, что раза в два лучше конкурентов. Она позволит вам подсветить переменную в контракте, все ее использование, и это очень помогает, когда мы делаем аудит, когда вы буквально за несколько часов разбираетесь в незнакомом коде с помощью подсказок среды разработки.
Немного о стоимости выполнения смарт-контрактов. Стоить будут только вычисления, которые изменяют Blockchain, т. е. участвуют в транзакции. Есть функции с модификаторами view
или pure
, они не модифицируют состояние, они могут использоваться без посылки данных в Blockchain. Если бы мы разработали функцию, которая вызывается сугубо локально, то она бы не стоила вообще ничего.
Любой код, который выполняется действительно в Blockchain и модифицирует состояние, сохраняет или пересылает данные, стоит газа. Легко поставить мысленный эксперимент на эту тему, могу ли я написать код, который будет, допустим, очень долго работать и много чего-то сохранять. Если при этом участникам сети Ethereum приходится его повторять, то ваш код будет работать на миллионах компьютеров по всему миру. Значит, разработчики Ethereum по-любому озаботились, чтобы вы за это заплатили, и довольно дорого по текущим расценкам. Другое дело, что у разных вычислений и строчек разная стоимость. Цена простой арифметики — 3—5 единиц газа, а модификация состояния обойдется в 20 тысяч единиц газа. Почему? Потому что блокчейн изменился, стал больше на 32 байта из-за вас, эти байты легли на миллионы компьютеров по всему миру.
Тестирование смарт-контракта
Тестирование — обязательный шаг, об этом нам говорит 50-летняя история программного обеспечения. И странно не видеть тестов в репозиториях проектов, которые приходят на ICO. Может быть, они проделаны, написаны, но просто не выложены. Но в любом случае если вы делаете тесты, почему бы их не выложить? Так вы показываете инвесторам, что здесь все хорошо протестировано. Это не гарантия, что нет ошибок, но помогает избежать многих проблем.
Тесты пишутся непосредственно на Solidity или на JavaScript. В любом случае с помощью Truffle вы можете запустить все тесты разом и получить сводный отчет. В самом низу у вас должно быть сказано: все тесты прошли, неудач нет. Вы показываете и заказчикам ICO, и пользователям, и инвесторам, что вы протестировали самые важные аспекты вашего ICO: токен, сам по себе crowdsale, хранение денег, даже бонусы. Бонусы — немаловажная вещь. Инвесторам очень важно знать, что они получат свои бонусы.
Есть юнит-тесты — они проверяют небольшие изолированные фрагменты кода, и такие тесты удобно писать на Solidity.
function testValidationOfDecrease() {
Bonuses b = new Bonuses();
b.add(1000000000, 50);
b.add(1000000010, 60);
b.validate(false);
assertInvalid(b, true);
b = new Bonuses();
b.add(1000, 60);
b.add(1000000000, 50);
b.add(1000000010, 60);
b.add(1000000020, 0);
b.validate(false);
assertInvalid(b, true);
}
Код именно Solidity, он просто создает контракт, посылает в него данные и проверяет, что контракт работает, как и ожидалось. Есть тесты, написанные на JavaScript. Обычно здесь проверяются более сложные сценарии, когда во взаимодействии участвуют несколько контрактов. Допустим, мы создаем набор контрактов, о которых упоминалось раньше. Тогда участвует несколько контрактов, также может быть несколько пользователей. Мы даже именуем их — «инвестор такой-то», «инвестор такой-то». Есть еще владельцы, есть пользователи вообще без прав. И здесь можно запрограммировать комплексное взаимодействие и проверить, что в ходе взаимодействий токены выдаются правильно. Или проверить, что при достижении hard cap инвестор получит свою сдачу. Эти тесты называются интеграционными.
Любые обращения к Blockchain из JavaScript асинхронные — это добавляет лишнего кода, всякие promise и т. д., поэтому я предлагаю использовать Babel, своего рода компилятор JavaScript, который всю асинхронность спрячет за конструкцией await
, async
.
it("test max cap", async function() {
const role = getRoles();
const [crowdsale, token, funds] = await instantiate();
// +5%
await crowdsale.setTime(1511913601, {from: role.owner1});
await crowdsale.sendTransaction({from: role.investor1, value: web3.toWei(20, 'finney')});
await assertBalances(crowdsale, token, funds, web3.toWei(20, 'finney'));
const investor3initial = await web3.eth.getBalance(role.investor3);
await crowdsale.sendTransaction({from: role.investor3, value: web3.toWei(1000, 'finney'), gasPrice: 0});
const investor3spent = investor3initial.sub(await web3.eth.getBalance(role.investor3));
assertBigNumberEqual(investor3spent, web3.toWei(378, 'finney'), 'change has to be sent');
assert.equal(await crowdsale.m_state(), 4);
await assertBalances(crowdsale, token, funds, web3.toWei(398, 'finney'));
assertBigNumberEqual(await token.balanceOf(role.investor1), STQ(2100));
assertBigNumberEqual(await token.balanceOf(role.investor3), STQ(39690));
await checkNoTransfers(crowdsale, token, funds);
await checkNotInvesting(crowdsale, token, funds);
await checkNotWithdrawing(crowdsale, token, funds);
await checkNotSendingEther(crowdsale, token, funds);
await crowdsale.distributeBonuses(10, {from: role.nobody});
assertBigNumberEqual(await token.balanceOf(role.investor1), STQ(2600));
assertBigNumberEqual(await token.balanceOf(role.investor3), STQ(49140));
await checkNotInvesting(crowdsale, token, funds);
await checkNotWithdrawing(crowdsale, token, funds);
});
Не забываем тестировать исключительные ситуации. Например, мы проверили, что владельцы после завершения успешного ICO выводят деньги. Мы также должны протестировать, что эти товарищи — инвестор 2, инвестор 3, аноним с улицы — не могут вывести деньги, т. е. при подаче запроса на вывод денег получится исключительная ситуация, контракт не примет запрос.
Резюмируем: тестировать стоит, есть все средства для этого. Кстати, программист не должен быть тестировщиком. Почему? Потому что создатель плохо видит свои ошибки. У него в голове уже сложилась какая-то модель, и он ее перенес в код. Когда программист пытается проверить свой код, его, модель неминуемо выскакивает у него в голове, и
Помимо тестирования также нужен аудит. Аудитом занимаются специально обученные люди. В ходе аудита могут быть разработаны дополнительные тесты, но аудит — это совсем другая ситуация и совсем другие навыки.
Если мы говорим об аудите, то прежде всего нужно понять, какие бывают уязвимости и сколько вы за них заплатите аудиторам. Есть несколько способов оценки. Например, хорошая методология OWASP, она вводит для уязвимости два измерения: важность и вероятность. Влияние уязвимостей на проект — произведение этих двух величин. Это может кому-то напомнить матрицу оценки рисков.
На практике в отчетах об аудите смарт-контракта часто приходится видеть, что притягивается за уши степень влияния уязвимости, ее важность. Не знаю, почему это происходит. Тем не менее критическими называются вовсе не критические уязвимости. Поэтому мы предлагаем другую методологию, которая связывает влияние уязвимости на реальную жизнь с вашим проектом. В случае смарт-контрактов есть довольно понятные результаты эксплуатации уязвимости: можно вывести эфир или токены, что очень плохо. В каких-то условиях контракт зависнет или его состояние испортится настолько, что придется заменять контракт. Тут у аудиторов и у заказчика аудита может быть минимальное количество споров. Пара ссылок на отчеты по аудиту:
- https://blog.zeppelin.solutions/fuel-token-audit-30cc02f257f5
- https://github.com/mixbytes/audits_public/blob/master/solidity/KickICO/README.md
Отчет — это документ, который говорит о том, какие уязвимости найдены и (в идеале) что делать дальше. Кто-то в западных кругах пишет большие отчеты: вам рассказывают, что нужно назвать эту функцию по-другому, а лучше перенести этот код куда-то в другое место. Но я просто не вижу смысла так делать, если заказчик не будет заниматься серийной разработкой смарт-контрактов, если ему нужно провести ICO один раз и продолжать добывать свой песок в карьере, на который он собирал эфир.
Проблемы безопасности смарт-контрактов
Перейдем к проблемам, на которые нужно обратить внимание. Это тема для отдельной статьи о безопасности смарт-контрактов. Отмечу несколько вещей. Для начала очень внимательно просмотрите функции и убедитесь, что они закрыты правами как надо, с самыми жесткими ограничениями. Например, функция, ничего не меняющая в блокчейне, может быть объявлена как view
. Функция, которая вообще не читает блокчейн, — как pure
. Стоит наложить эти ограничения. Более того, у административных функций должен проверяться владелец. Это кажется очевидным, но вспомните Parity.
Повторюсь еще раз: все данные в блокчейне открыты всем, несмотря на модификаторы и права доступа к состоянию. Там может быть public
, может не быть, но на самом деле все можно прочитать из контракта. Не используйте для авторизации такой параметр транзакции, как tx.origin
: это открывает уязвимость. Выбирайте msg.sender
.
Из нетривиального. Наверное, многие слышали про так называемую reentrancy attack, через которую взломали The DAO. Проблема в том, что, когда ваш метод контракта еще работает, он может делать внешний вызов. Это касается только внешних вызовов, которые, в свою очередь, по цепочке могут вернуться и обратиться в этот же самый метод или в другой метод контракта. И это открывает уязвимости: ваше состояние контракта может быть еще не консистентно, какие-то инварианты в ходе работы метода могут быть временно нарушены и еще не восстановлены.
Для борьбы с этим есть ряд методов. Самый хардкорный метод — модификатор nonReentrant
из библиотечного контракта ReentrancyGuard
— запрещает reentrancy для контрактов. Почему бы его не использовать? Он стоит некоторое количество газа, он пишет состояние, но в ряде случаев безопасность более важна, чем лишний расход газа.
Вспомним для примера атаку front running. Что, если вы зашифровали что-то и положили это в Blockchain? Это невозможно расшифровать, потому что асимметричную криптографию никто не отменял. Но как только вы пытаетесь легитимно расшифровать это, послав транзакцию с паролем, то происходит вот что. Во-первых, пароль получается одноразовый, потому что он прилетел в блокчейн, его все видят. Во-вторых, когда транзакция ушла от вас, но еще не стала частью основной ветки и не зафиксировала за вами токены, в этот момент кто-то может увидеть ваш пароль и подсунуть свою транзакцию, которая окажется раньше вашей, хотя по времени она произошла позже.
Не так давно появилась информация еще об одной атаке — short address attack. Ей были подвержены некоторые биржи. И библиотеки, которые посылают транзакции в Ethereum. Основная API-библиотека Ethereum web3 не подвержена, но был подвержен софт бирж. Атака заключалась в том, что пользователь использовал адрес, укороченный на один байт. Когда параметры слеплялись в транзакцию, следующие за этим адресом параметры сдвигались на один байт. И когда происходило обращение из виртуальной машины Ethereum к этим параметрам, на самый конец calldata EVM неявно дописывала ноль. Число сдвигалось на один байт, а такой сдвиг — это умножение на 256. То есть пользователь снимал 1 единицу эфира, когда уже пройдены все проверки безопасности на стороне биржи, а на самом деле ему перечислялось 256 единиц.
Кто-то говорит: «Неправильно запрограммировали биржу». Тут есть проблема и в Ethereum виртуальной машине, поскольку, на мой взгляд, при обращении к байтам за пределами существующих параметров транзакции на самом деле должна происходить не неявная подстановка нулей, а ошибка, исключительная ситуация. Так, как это случается в управлении адресным пространством в обычных приложениях на компьютере.
Я уже упоминал о переполнении. И еще раз скажу, что есть короткие типы данных, допустим, числа, занимающие 1 байт. В беззнаковом случае это от 0 до 255 включительно. Важно избежать их переполнения. Порой оно возникает неявно, вы можете его не заметить. Например, если это параметр цикла, которому вы подставляете тип с помощью ключевого слова var
. То есть тип там будет неявно выбран как однобайтовый, если вы инициализируете нулем параметр; значит, никогда не дойдет до 256; значит, вы потенциально имеете бесконечный цикл и зависший контракт. Он не будет зависать, он упадет, но если цикл стоит где-то в ключевом месте, то дальше падения ваш контракт никуда не пойдет.
При различной арифметике могут быть неявные типы выражений. Например, если вы возводите что-то в степень, два байтовых числа, и присваиваете результат большому числу, то без приведения типов у вас результат будет байтовый и опять же возникнет переполнение.
Bug bounty
Bug bounty — если переводить дословно, это выдача вознаграждений за баги. Тут применима та же методология оценки, что и оценка аудита, т. е. оценка уязвимостей. Я бы выделил два вида принципиально разных программ bug bounty. Первый — так называемая ручная bug bounty. Хорошая ссылка, как это дело оформляется: Aventus Token Sale Bug Bounty. По сути, это своего рода оферта, которая предлагает: «Давайте поищем уязвимости в таком-то коде», оговариваются многие условия, например кто первый нашел — тот и молодец. И вообще сходите, посмотрите, там все классно описано, сколько денег полагается за какую уязвимость. Тут понятна механика. Вам пишут специалисты по безопасности, хакеры, что нашли то-то и то-то. Вы проверяете, и если да, то им выплачивается вознаграждение.
Есть второй вид: автоматическая bug bounty. Она привлекательна и для хакеров, и для заказчиков. Здесь удобно то, что нет переговоров, нет споров на тему того, какая это уязвимость, сколько за нее денег. В ручном варианте начинаются сомнения, что, дескать, ее никогда не заэксплуатируют в таком виде. Здесь этого нет. Здесь может быть два сценария. Первый удобен для кошельков, хранилищ эфира, когда просто деплоится хранилище, на него заливается эфир, далее говорится: «Ломайте, ребята». Тут единственное, что можно добавить, — комментарий «Это bug bounty, все можно и нужно ломать и забирать эфир». Я не уверен, но, казалось бы, этим самым вы снимаете потенциальные вопросы о несанкционированном доступе к информации и уголовную статью с хакера, который займется взломом в ваших интересах. Второй сценарий автоматической bug bounty — нарушение инварианта. У контрактов обычно бывают явные или неявные инварианты. Например, у токенов есть число, которое говорит, сколько же всего токенов. И это число должно равняться сумме балансов держателей токенов. Или, например, хранилище эфира, которое работает в ходе ICO, оно может вести сумму, сколько же эфира собрано, и баланс контракта хранилища должен быть больше этой суммы либо равен ей. Если это не так, то происходит нарушение инварианта.
Как организуется такая bug bounty? Есть контракт, который выдает заинтересованным лицам новую копию контракта с инвариантом, и они могут своими действиями как-то играться с копией и пытаться нарушить инвариант. Когда он нарушен, им автоматически прилетают деньги, их выплачивает родительский контракт. Удобно это тем, что вся эта история — автоматическая, и хакер может за пару часов получить вознаграждение за труды, а заказчик bug bounty увидит, как взломали его контракт, он будет точно это знать, потому что было 100 эфира, а стало 0. Он увидит это, ведь в Blockchain все публично. И обеим сторонам не нужно тратить время на споры.
Деплой контракта
Разработали, протестировали, провели аудит и bug bounty — теперь время деплоить, т. е. выкладывать контракты в блокчейн. Деплой автоматизируется Truffle в соответствии с прописанными вами контрактами и связями между ними, это одна команда в консоли — migrate
. Здесь есть моменты, которые требуют внимания.
Начнем с лимита газа — это предел, больше которого не может потребить ни одна транзакция, поскольку транзакция должна находиться в каком-то блоке, и лимит не так уж велик. Он меняется динамически, это происходит отнюдь не резко, и сейчас предел таков, что в контракте вы не можете в одной транзакции записать матрицу 20 на 20 больших чисел. Просто не хватит газа. Разворачивание контрактов, т. е. закидывание кусков кода в блокчейн, тоже стоит довольно много газа.
Кроме того, если ваш контракт именно создает другие контракты, а не просто использует их, то он содержит в себе их байт-код, и его цена приплюсовывается к потреблению газа транзакцией. Мы сталкивались с тем, что система контрактов накладывает свои проблемы на этап деплоя, были случаи, когда это не влезало в блок, и вообще деплой системы стоил порядка 1 эфира. Это значит, что на один деплой вынь да положь 300 долларов. Это, конечно, решается — контракты деплоятся по отдельности и затем связываются. Но, понятное дело, это лишняя работа, лишние проблемы, лишние уязвимости. Поскольку деплой дорогой, стоит задеплоить сначала в Testnet — это тестовый блокчейн Ethereum, убедиться, что там все правильно. Там эфир не стоит почти ничего, его довольно легко получить, там можно делать ошибки, и лучше для начала откатать весь процесс в нем.
Мы говорили об управляющих нодах, о владельцах контрактов, токенов. Ноды заранее следует подготовить, иметь на них полностью синхронизированный блокчейн и какое-то количество эфира для управляющих сигналов, т. е. для вызова транзакций.
Существует два принципиально разных сценария деплоя. В одном случае разработчики приезжают к заказчику, показывают: «Вот, все хорошо, тесты проходят. Давайте деплоиться». Берут ноду, деплоятся у него. Есть другой вариант: разработчики проводят деплой у себя, в своем окружении, и передают права и владение заказчику. В первом случае заранее позаботьтесь о том, чтобы на ноде, с которой будет деплой, было достаточное количество эфира и синхронизированный блокчейн, потому что при деплое на несинхронизированном блокчейне Truffle выдаст ошибку. Во втором случае, когда деплой происходит у разработчиков, есть свои плюсы для обеих сторон. Разработчики используют свое окружение, т. е. у них стоит и Truffle правильный, и geth, и еще много чего, и делают все это быстро и комфортно. Для заказчика профит в том, что у разработчиков нет физического доступа к нодам в сети заказчика. С точки зрения информационной безопасности физический доступ — один из худших сценариев. В свою очередь, передача прав и владения математически строго гарантируется, и заказчику легко проверить, что все сделано правильно.
В ходе деплоя в любом случае нужно подумать, как будет передаваться информация между нодами, участвующими в деплое. Например, адреса контрактов или адреса мультиподписей. Адреса все большие, их даже не стоит пытаться переписывать или запоминать. Нужно придумать механизм передачи. Но тут ничего страшного нет, они публичные. Сколько угодно его публикуй. Есть закрытый ключ. По открытому ключу его никто в ближайшее время получить не сможет.
По итогу деплоя следует проверить владельцев всех контрактов, что все правильно, и связь контрактов, что они связаны в нужном порядке, что ICO не будет пытаться кидать токены в фонд эфира, например.
Заказчику на своей стороне нужно научиться этой простой процедуре — добавлять задеплоенный контракт себе в кошелек. При добавлении указывается его адрес, имя для запоминания и ABI контракта, который можно получить после верификации контракта, например на Etherscan. В него не надо особо вникать. Важно его скопировать, вставить, добавить контракт.
По-любому нужно уметь выполнять эту процедуру. Я точно не уверен, то ли кошелек сохраняет в себя информацию о контракте, то ли он теряет ее после закрытия. Но в любом случае нужно понимать, что это добавление в кошелек контракта, оно никак не влияет на блокчейн, это происходит сугубо у вас, локально.
Сделав это, вы сможете удобно читать контракт. В интерфейсе есть возможность прямо посмотреть поля контракта, в том числе сложные, например массивы, маппинги. Кроме того, можно писать в контракт, что тоже нужно уметь, потому что это посылка управляющих сигналов, это отсылка транзакций, например в административные функции.
Предположим, вы хотите поменять владельца контракта. Все довольно просто. Указываете, с кого и на кого. Указываете, с какого аккаунта вы хотите заплатить эфир за операцию. С вас спросят пароль от этого аккаунта, и затем транзакция уйдет в сеть.
Если у вас используется подмена ICO-контракта, то следует сразу не забыть проработать сценарий подмены, кто что делает, чтобы, допустим, у владельцев, которым вы уже отдали права, не было вопросов, чтобы они у себя в офисе быстро открыли, поменяли. И чтобы никто судорожно не пытался понять, что делать, и не наделал ошибок.
Верификация контракта
Теперь давайте обсудим верификацию: что это такое, зачем она нужна. Мы говорили, что в блокчейне все публично. Байт-код контракта также публичен. Однако я упоминал о том, что его выполняют виртуальные машины Ethereum, а вы не виртуальная машина Ethereum, и, скажу я вам, эта машина довольно низкоуровневая, она стековая, более низкоуровневая, чем набор инструкций современных процессоров Intel и AMD. То есть по сравнению с набором инструкций AMD64 она исполняет код куда более запутанный, просто потому, что там довольно примитивные операции. Это более похоже на RISC-процессоры, так называемый reduced instruction set. Очень трудно понять по байт-коду контракта, что он делает. Мне трудно разобраться, что делает контракт, без исходного кода, инвестору — тем более. Это не значит, что можно в контракты писать какие-то backdoor, потому что их найдут. Не через пять минут, а через пять дней, десять дней.
Вернемся к исходной проблеме. Нужно дать понять инвестору, что же делает контракт. Для этого имеется механизм верификации: для любого контракта любой человек может предложить его исходный код. Этот механизм есть на Etherscan. Он берет исходный код, пытается его скомпилировать, получить ровно то же самое, что лежит в блокчейне. И если это так, то можно считать, что это именно тот самый исходный код контракта. Ваш код может храниться в нескольких файлах. Нужно их скомпоновать, запихнуть туда в виде одного куска кода. Кроме того, требуется передать аргументы конструктора в упакованном виде, как они были посланы. То есть совсем не user friendly вещь. Но умеючи это можно сделать.
В итоге, когда код проверифицирован в Etherscan, он будет виден на вкладке исходников контракта. Предвосхищаю вопрос: «Как же так, получается, этот самый Etherscan — единственное доверенное лицо?» Могу сказать, что нет. Каждый точно так же может локально взять код контракта, скомпилировать его, получить бинарь и сравнить его с тем, что лежит в блокчейне, и это не очень сложно. Таким образом, верификацию для контракта лучше сделать. Инвесторы увидят код и убедятся, что в блокчейне происходит именно то, что вы обещали в white paper.
Заключение
Итак, прорезюмирую. Перед работой над контрактами стоит продумать их общую структуру и связь. В процессе разработки контрактов нужно помнить не только о типичных приемах и проблемах программирования, но и о специфике блокчейна. Обязательно стоит провести доскональное автоматизированное тестирование и аудит контрактов, а также проверифицировать их код. Закончите все это не позднее чем за 3 дня до запуска. Удачного ICO!
Автор: therealal