Хабрахабр

Как определить адрес смарт-контракта до деплоя: использование CREATE2 для криптобиржи

Тема блокчейна не перестает быть источником не только всяческого хайпа, но и весьма ценных с технологической точки зрения идей. Посему не обошла она стороной и жителей солнечного города. Присматриваются люди, изучают, пытаются переложить свою экспертизу в традиционном инфобезе на блокчейн-системы. Пока что точечно: одна из разработок «Ростелеком-Солар» умеет проверять безопасность софта на базе блокчейна. А попутно возникают некоторые мысли по решению прикладных задач блокчейн-сообщества. Одним из таких лайфхаков – как определить адрес смарт-контракта до деплоя с помощью CREATE2 – сегодня хочу с вами поделиться под катом.

Как указано в EIP, этот опкод был введен в основном для каналов состояний (state channels). image
Опкод CREATE2 был добавлен в хард-форке Константинополь 28 февраля этого года. Однако, мы использовали его для решения другой проблемы.

Каждому пользователю мы должны предоставить Ethereum-адрес, на который кто угодно сможет отправлять токены, тем самым пополняя свой аккаунт. На бирже есть пользователи с балансами. Когда токены приходят на кошельки, мы должны отправить их на единый кошелек (hotwallet). Давайте назовем эти адреса «кошельками».

Если вам интересен только конечный результат, вы можете найти его в разделе «Итоговое решение». В следующих разделах я анализирую варианты решения этой задачи без CREATE2 и рассказываю, почему мы отказались от них.

Ethereum-адреса

Самое простое решение — генерировать новые ethereum-адреса для новых пользователей. Эти адреса и будут кошельками. Чтобы перевести токены из кошелька в hotwallet, необходимо подписать транзакцию вызовом функции transfer() с приватным ключом кошелька из бэкенда.

Этот подход имеет следующие преимущества:

  • это просто
  • стоимость переноса токенов с кошелька на hotwallet равна цене вызова функции transfer()

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

image

Создавать отдельный смарт-контракт для каждого пользователя

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

На бирже пользователь может создать столько аккаунтов, сколько ему нужно, и каждому нужен собственный кошелек. От этого решения мы тоже отказались, поскольку пользователю нельзя показать адрес его кошелька без развертывания смарт-контракта (это на самом деле возможно, но довольно сложным образом с другими недостатками, которые мы не будем здесь обсуждать). Это означает, что нам нужно тратить деньги на деплой контракта, даже не будучи уверенными, что пользователь будет использовать эту учетную запись.

Опкод CREATE2

Чтобы устранить проблему предыдущего способа, мы решили использовать опкод CREATE2. CREATE2 позволяет заранее определить адрес, по которому будет развернут смарт-контракт. Адрес рассчитывается по следующей формуле:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]

, где:

  • address — адрес смарт-контракта, который будет вызывать CREATE2
  • salt — случайное значение
  • init_code — байт-код смарт-контракта для развертывания

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

  • address в формуле является постоянным, так как это адрес нашей фабрики кошельков
  • salt — хеш user_id
  • init_code является постоянным, так как мы используем один и тот же кошелек

Больше улучшений

Предыдущее решение все еще имеет один недостаток: вам нужно платить за развертывание умного контракта. Тем не менее, вы можете избавиться от этого. Для этого вы можете вызвать функцию transfer(), а затем selfdestruct() в конструкторе кошелька. И тогда газ за развертывание смарт-контракта будет возвращен.

Это связано с тем, что CREATE2 проверяет, что nonce целевого адреса равен нулю (ему присваивается значение «1» в начале конструктора). Вопреки распространенному заблуждению, вы можете развернуть смарт-контракт по одному и тому же адресу несколько раз с опкодом CREATE2. Таким образом, если вы снова вызовете CREATE2 с теми же аргументами, проверка на nonce пройдет. При этом функция selfdestruct() каждый раз сбрасывает nonce адреса.

Стоимость перевода денег с кошелька на hotwallet примерно равна стоимости вызова функции transfer(), поскольку мы не платим за развертывание смарт-контракта. Обратите внимание, что это решение аналогично варианту с ethereum-адресами, но без необходимости хранить приватные ключи.

Итоговое решение

image

Изначально подготовлено:

  • функция для получения соли по user_id
  • умный контракт, который будет вызывать опкод CREATE2 с соответствующей солью (т.е. фабрика кошельков)
  • байт-код кошелька, соответствующий контракту со следующим конструктором:

constructor () { address hotWallet = 0x…; address token = 0x…; token.transfer (hotWallet, token.balanceOf (address (this))); selfdestruct (address (0));
}

Для каждого нового пользователя мы показываем его / ее адрес кошелька путем расчета

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]

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

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

function deployWallet (соль uint256) { bytes memory walletBytecode =…; // invoke CREATE2 with wallet bytecode and salt
}

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

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

Автор Павел Кондратенков, специалист в области Ethereum

Теги
Показать больше

Похожие статьи

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть