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

В данном случае мы говорим о Vyper: языке программирования Pythonic, ориентированном на смарт-контракты, предназначенном для взаимодействия с виртуальной машиной Ethereum (EVM). Обстоятельства, связанные с этим эксплойтом, очаровали меня, поэтому я хотел глубоко погрузиться в то, что вообще позволило этому эксплойту произойти.

По мере развертывания эксплойта заголовки каждый день сообщали о новых цифрах. Кажется, что ситуацию наконец удалось сдержать, но не раньше, чем было украдено более 70 миллионов долларов США. На сегодняшний день также были взломаны несколько пулов проектов DeFi, в том числе pETH/ETH PEGD: 11 миллионов долларов; msETH/ETH компании Metronome: 3,4 миллиона долларов; alETH/ETH Alchemix: 22,6 миллиона долларов; и Curve DAO: около 24,7 млн ​​долларов, согласно оценке LlamaRisk после эксплойта.

Эксплойт известен как неисправность повторного входа, которая стала возможной в некоторых версиях языка программирования Vyper, в частности v0.2.15, v0.2.16 и v0.3.0. Поэтому все проекты, использующие именно эти версии Vyper, являются вектором атаки.

Что такое реентерабельность?

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

Функция называется реентерабельной, если ее можно прервать в середине выполнения, а затем безопасно вызвать снова («повторно войти») до того, как ее предыдущие вызовы завершат выполнение. Реентерабельные функции используются в таких приложениях, как обработка аппаратных прерываний, рекурсия и т. д.

Чтобы функция была реентерабельной, она должна удовлетворять следующим условиям:

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