Як визначити адресу смарт-контракту до деплою: використання CREATE2 для криптобіржі

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

Як визначити адресу смарт-контракту до деплою: використання CREATE2 для криптобіржі
Опкод CREATE2 було додано до хард-форки Константинополь 28 лютого цього року. Як зазначено в EIP, цей опкод введено в основному для каналів станів (state channels). Однак ми використовували його для вирішення іншої проблеми.

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

У наступних розділах я аналізую варіанти розв'язання цього завдання без CREATE2 та розповідаю, чому ми відмовилися від них. Якщо вам цікавий лише кінцевий результат, ви можете знайти його у розділі «Підсумкове рішення».

Ethereum-адреса

Найпростіше рішення – генерувати нові ethereum-адреси для нових користувачів. Ці адреси і будуть гаманцями. Щоб перевести токени з гаманця в hotwallet, необхідно підписати транзакцію викликом функції передача () з приватним ключем гаманця з бекенда.

Цей підхід має такі переваги:

  • це просто
  • вартість перенесення токенів з гаманця на hotwallet дорівнює ціні виклику функції передача ()

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

Як визначити адресу смарт-контракту до деплою: використання CREATE2 для криптобіржі

Створювати окремий смарт-контракт для кожного користувача

Розгортання окремого смарт-контракту для кожного користувача дозволяє не зберігати приватні ключі від гаманців на сервері. Біржа викличе цей розумний контракт для передачі токенів у hotwallet.

Від цього рішення ми теж відмовилися, оскільки користувачеві не можна показати адресу його гаманця без розгортання смарт-контракту (це насправді можливо, але досить складно з іншими недоліками, які ми не будемо тут обговорювати). На біржі користувач може створити стільки облікових записів, скільки йому потрібно, і кожному потрібен власний гаманець. Це означає, що нам потрібно витрачати гроші на деплой контракту, навіть не будучи впевненими, що користувач буде використовувати цей обліковий запис.

Опкод CREATE2

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

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


, Де:

  • адреса - адреса смарт-контракту, який буде викликати CREATE2
  • сіль - Випадкове значення
  • init_code - Байт-код смарт-контракту для розгортання

Таким чином гарантується, що адреса, яку ми надаємо користувачеві, дійсно міститиме бажаний байт-код. Крім того цей смарт-контракт може бути розгорнутий, коли нам потрібно. Наприклад, коли користувач вирішить уперше використати свій гаманець.
Як визначити адресу смарт-контракту до деплою: використання CREATE2 для криптобіржі
Більше того, ви можете розраховувати адресу смарт-контракту щоразу замість того, щоб зберігати її, оскільки:

  • адреса у формулі є постійною, оскільки це адреса нашої фабрики гаманців
  • сіль - хеш user_id
  • init_code є постійним, тому що ми використовуємо один і той же гаманець

Більше покращень

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

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

Зверніть увагу, що це рішення аналогічне варіанту з ethereum-адресами, але без необхідності зберігати приватні ключі. Вартість переказу грошей з гаманця на hotwallet приблизно дорівнює вартості виклику функції передача ()оскільки ми не платимо за розгортання смарт-контракту.

Підсумкове рішення

Як визначити адресу смарт-контракту до деплою: використання CREATE2 для криптобіржі

Спочатку підготовлено:

  • функція для отримання солі по 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 з параметром _до, рівним адресою гаманця. На даний момент вже можна збільшити баланс користувача на біржі до розгортання гаманця.

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

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


Таким чином, викликається конструктор смарт-контракту гаманця, який передає всі свої токени на адресу hotwallet і потім самознищується.

Повний код можна знайти тут. Зверніть увагу, що це не наш продакшн-код, тому що ми вирішили оптимізувати гаманець байт-код і записали його в опкодах.

Автор Павло Кондратенков, спеціаліст у галузі Ethereum

Джерело: habr.com

Додати коментар або відгук