Рекомендации для DAO 2.0

После успешной реализации хард-форка Ethereum самое время критически оценить сложившуюся ситуацию.

По моему прогнозу около 40 % инвесторов TheDAO выведут свои деньги, тихо сожалея и обещая себе никогда не вкладываться в подобные проекты.

Из оставшихся 60 % некоторая весомая часть скажет что-то вроде «Это было забавно! Попробуем еще раз!»

Рекомендации для DAO 2.0

Хотелось бы, чтобы следующий опыт этих 60 % был более безопасным, и авторы контрактов DAO могут принять несколько простых мер, чтобы сделать ситуацию более благоприятной для своих клиентов/акционеров/держателей токенов.

В частности, я хотел бы обсудить простоту архитектуры, проблемы безопасности и вопросы законодательства/регулирования.

Проблемы безопасности: вызовы, состояние, токены и потеря значимости

Существует четыре основных уязвимости с точки зрения безопасности, с которыми постоянно сталкиваются умные контракты DAO: рекурсивные вызовы, неправильное управление состоянием, неправильное управление токенами и проблемы с переполнением/потерей значимости.

Давайте рассмотрим следующий безобидный код DAO, который имеет все четыре уязвимости.

// Не копируйте этот код. В него намеренно заложены проблемы.
bool withdrawingMutex;
mapping (address => uint) tokens;
uint globalTokens;
function withdraw(uint amount) returns(bool) {
   if(withdrawingMutex) { return false; }
   withdrawingMutex = true;
   if (amount > tokens[msg.sender]) { return false; }
   if (!msg.sender.call.value(amount)()) { return false; }
   tokens[msg.sender] -= amount;
   globalTokens -= amount;
   withdrawingMutex = false;
}
function sendTokens(address to, uint amount) returns(bool) {
   if (tokens[msg.sender] >= amount) {
      tokens[to] += amount;
      tokens[msg.sender] -= amount;
      return true;
   }
   return false;
}

Вообще говоря, в этот код включены некоторые полезные ограничения: вывод средств регулируется исключениями с помощью флагов, так что невозможно рекурсивно вызывать функцию выведения.

Автор кода постарался проявить осторожность в отношении потери значимости для токенов (хотя здесь потеря значимости рассматривается как использование токенов в количестве, превышающем доступное, а не как риск технического уровня).

Но несмотря на все это данные функции позволяют полностью вывести все средства из контракта любому держателю токенов.

Вот схема возможной атаки:

  1. Пусть на балансе есть 10 токенов, каждый из которых подлежит возмещению 1 эфиром.
  2. Предположим, что контракт включает 100 000 эфиров.
  3. Атакующий вызывает contract.withdraw(10).
  4. Вызывается функция по умолчанию кошелька атакующего.
  5. Она вызывает contract.sendTokens(otherAddr, 10).
  6. contract.sendTokens уменьшает tokens[msg.sender] до 0.
  7. contract.withdraw(10) уменьшает tokens[msg.sender] до -10 или 2^256-10
  8. Теперь возможен вызов contract.withdraw(999990).

Вариант данного стека вызовов использовался для масштабирования атаки на TheDAO.
Эта атака может быть расширена, чтобы также нарушить присвоение globalToken.

Простая перестановка и беззнаковые целые

Простейший способ исправить этот код — это поменять местами несколько строк в функции выведения средств:

tokens[msg.sender] -= amount;
globalTokens -= amount;
if (!msg.sender.call.value(amount)()) { return false; }

Это позволит избавиться от ошибки потери значимости токенов, но не исключает возможности потери значимости в другом месте. Для отображения можно использовать изменение типа на int. По какой-то неизвестной мне причине это не является общепринятым в документации Solidity.

Тип int обладает полезным свойством, благодаря которому проверки сравнения значений работают в случае потери значимости. Даже если не поменять местами эти две строки, int ограничит направление атаки значением, которое хранится первоначальным владельцем.

Также приведенный выше код позволяет нарушить синхронизацию общего баланса токенов: если вызов кошелька получателя не удается, возможно, в связи с недостатком доступного газа в стеке функции, то владелец теряет токены.

Для решения этой проблемы необходимое количество должно быть добавлено впоследствии. Такой уровень контроля нередко имеется в качественных контрактах, но я не встречал контракта, который бы выполнял проверку на переполнение при возмещении. Если контракт не вызывается где-либо еще в Ethereum, нет необходимости беспокоиться о переполнении. Но если это так, переполнение возможно.

Что такое состояние контракта?

Функция withdraw возвращает false, а не throw. Это означает, что для withdrawingMutex может быть установлено значение true, тест может не пройти и контракт будет приостановлен; последующее выведение средств окажется невозможным.

Возможно, вариант throw сработал бы здесь лучше, однако если контракт входит в группу из нескольких контрактов, даже throw может быть недостаточно для распутывания всех изменений состояния. Простого единого решения проблем с состоянием не существует: в каждом конкретном случае инженеры должны отследить цепочки вызовов и убедиться, что ничего не пропущено.

Проблемы с состоянием токенов

Экземпляры функции ERC20 отслеживают tokenSupply в глобальной переменной uint, а затем возвращают uint при вызове tokenSupply. Это имеет смысл с точки зрения контроля газа и регулирования сложности: перебор всех держателей токенов может быть трудоемким. Фактически, учитывая реализацию отображения в Solidity, может оказаться невозможным перебрать все элементы и проверить, хранятся ли балансы токенов только в отображении — в Solidity невозможен перебор отображений по ключу.

Многие авторы смотрят на мир слишком оптимистично. Но каждая организация DAO должна иметь средства (даже при высоком расходе газа) ручной проверки передачи токенов и план действий в чрезвычайной ситуации.

Также эти значения токенов должны иметь тип int: потеря значимости очень опасна.

Переполнение

Контракт не выполняет проверку на переполнение в sendTokens: несмотря на то что типичным является объявление отображений токенов как uint и, следовательно, uint256, имеются некоторые направления атаки на токены, которые могут обеспечивать геометрический рост, используя уязвимости токенов.

Либо система токенов может использовать более компактный тип, например uint32, что делает атаку с переполнением более вероятной.

Правовые соображения

История с TheDAO далеко не закончилась: еще не начались судебные процессы! Нас ждут годы увлекательных разбирательств, пока все не утрясется. Я хотел бы рассмотреть под несколькими углами одну сферу, которая меня беспокоит, а именно ответственное хранение (депозитарную деятельность).

Отказ от ответственности: я не адвокат, и это не юридическая консультация.

Вопросы ответственного хранения

Умные контракты могут быть составлены таким образом, что они не имеют ответственного хранителя или, иначе говоря, единственным хранителем является сам код. При этом они приобретают полезное свойство, благодаря которому ни одна сторона не может быть принуждена, юридически или иными средствами, к исполнению каких-либо действий в отношении контракта.

Принуждение к совершению действий — это не единственная проблема; также следует учитывать юридическую ответственность за принятые и непринятые меры.

В своем посте с рекомендациями по контрактам с возмещением я настоятельно советовал, чтобы такие контракты не имели ответственных хранителей, а протокол возмещения был закодирован в самом контракте.

К сожалению, распорядители условных депозитов, осуществляющие надзор над TheDAO, имеют возможность блокировать или утверждать выведение средств. Такая ситуация несет в себе большой риск. Например, я могу предположить, что они не являются лицензированными распорядителями условных депозитов во всех юрисдикционных территориях, где проживают держатели токенов.

Можно было бы продолжить, но ограничимся этим; так или иначе, с этими ответственными хранителями сопряжено много проблем.

Опытные юристы, такие как Патрик Мурк и другие, также отметили, что разработчики Ethereum рискуют, что их будут рассматривать как имеющих ответственного хранителя, когда они управляют перемещением средств между счетами.

Я призываю создателей DAO полностью отказаться от ответственных хранителей, обеспечив кодирование необходимых действий в программном обеспечении.

Возможность обновления и ответственное хранение (депозитарная деятельность)

Однако в этом случае возникает вопрос — что происходит в случае проблем? Программное обеспечение требует обновления. Ошибки необходимо исправлять. Я часто рекомендую интегрировать в контракты функции «приостановки» и «обновления». Передовые практические методы, подобные этому, поддерживаются dappsys и другими качественными предложениями по контрактам.

Если сторона или группа сторон имеет возможность обновлять контракт, то она может нести ответственность за хранение. С другой стороны, если никто не может обновлять контракт, вероятно, что через какое-то время он станет бесполезным.

Здесь нет простых решений. Я настоятельно рекомендую создателям DAO провести со своими сообществами всеобъемлющее обсуждение для определения предпочтительных подходов и оценить самые неблагоприятные сценарии, включая контракты с несколькими сторонами, мультиюрисдикционные судебные разбирательства, преступный тайный сговор и спорные атаки. К сожалению, мы пока не знаем в точности, как лучше всего решать такие проблемы, но некоторых грубых ошибок можно избежать просто путем обсуждения.

Самомнение и простота

Одним из самых сомнительных архитектурных решений в TheDAO стало добавление концепции дочерних DAO. Насколько я понимаю, такую возможность ввели по двум причинам:

  1. Считалось, что хорошо было бы иметь возможность вывести эфир, но сохранить права на доход от инвестиций, уже сделанных TheDAO до этого вывода.
  2. Считалось, что хорошо было бы иметь готовую структуру, с помощью которой участники могли бы «отделиться», но продолжать использовать систему.

Если бы я был единоличным правителем TheDAO, то не включил бы в спецификацию дочерние DAO. Они значительно расширили почву для атак и усложнили определение результатов. Не менее 60 % выявленных теоретико-игровых атак были основаны на дочерних структурах, крупнейшая уязвимость явилась следствием возможности отделения, и остаются проблемы с токенами, которые еще до конца не изучены.

Я полагаю, что в значительной мере проблемы явились следствием желания быть «крутыми» и выглядеть «интеллектуально». Однако можно пожертвовать некоторой долей «крутизны», выбрав один из следующих вариантов: либо вывод средств из TheDAO снижает процент долевого участия, либо любой вывод средств ликвидирует счет и означает отказ от будущих доходов. В первом случае остаются непростые вопросы относительно того, как оценивать удерживаемые инвестиции (предложения).

С моей точки зрения предпочтительным является второй подход. Если вы выходите и не желаете продавать свои токены, выберите ликвидацию — это обеспечит арбитражную основу для определения ценности токенов и будет поощрять участников оставаться в организации. Если вы хотите выйти, и рынок токенов благоприятен — все прекрасно. Рынки токенов получат повышенную ликвидность, и стоимость будет оставаться привязанной к эфиру, оставшемуся в контракте, с учетом некоторой оценки рынком возврата по оплаченным предложениям.

Если пользователи захотят основать новую DAO, ничто не помешает им вывести средства и опубликовать свой собственный контракт; вложенные DAO не нужны.

Я убедительно прошу создателей DAO сохранять максимально простые принципы. Именно к ним нужно стремиться.

Как обычно, я и моя команда предлагаем свои услуги по созданию, аудиту и определению спецификаций умных контрактов — пишите мне на адрес [email protected]

Peter Vessenes

Категория: 
Безопасность
Ваша оценка: Нет
0
Голосов еще нет
1105 / 0
Аватар пользователя admin
Публикацию добавил: admin
Дата публикации: чт, 07/28/2016 - 10:33

Что еще почитать:

Комментарии:

Аватар пользователя Анонимус

Анонимус

Даже интересно, сколько людей снова поведется на эту скамятину

вс, 07/31/2016 - 15:30

Аватар пользователя РоманЛанской

РоманЛанской

Как кто-то писал на ЛГБ "В Эфире вам чтобы сделать бутерброд, надо как минимум уметь растить пшеницу и пасти корову. А потом перерабатывать их продукцию, чтобы получить хлеб и масло"

ср, 09/14/2016 - 02:28

Добавить комментарий