Дивовижні прийоми розробки контрактів: досвід, отриманий з коду Uniswap
Нещодавно, під час написання посібника з розробки децентралізованих бірж, я звернувся до коду реалізації відомого DEX і дізнався багато нового. Як розробник, який раніше створював прості NFT-контракти, я вперше намагаюся розробити Defi-контракти, і впевнений, що ці маленькі поради будуть дуже корисні для новачків, які хочуть навчитися розробці контрактів.
Давайте разом подивимося на ці цікаві трюки, деякі з яких навіть можна назвати хитрощами.
Прогнозовані адреси розгортання контрактів
Зазвичай, розгортаючи контракт, ви отримуєте, здавалося б, випадкову адресу, оскільки вона пов'язана з nonce, тому адресу контракту важко передбачити. Але в певних ситуаціях нам потрібно зробити висновок про адресу контракту за допомогою торгових пар та відповідної інформації. Це дуже корисно в багатьох випадках, наприклад, для визначення прав на торгівлю або отримання адреси ліквідного пулу.
Можна створити контракт за допомогою методу CREATE2, додавши параметр salt, таким чином адреса створеного контракту є передбачуваною. Логіка генерації адреси: нова адреса = hash("0xFF", адреса творця, salt, initcode).
Вміле використання функцій зворотного виклику
У Solidity контракти можуть викликати один одного. Існує ситуація, коли A викликає метод B, а B у викликаному методі зворотно викликає A, що є корисним у певних ситуаціях.
Наприклад, коли ви викликаєте метод swap певного DEX-контракту для торгівлі, він викликає swapCallback, передаючи обчислений токен, необхідний для цієї торгівлі. Викликаюча сторона повинна в callback передати токен, необхідний для торгівлі, до DEX-контракту, а не розділяти метод swap на дві частини, щоб викликати їх окремо. Це забезпечує безпеку методу swap і гарантує, що вся логіка буде виконана в повному обсязі без необхідності складного ведення записів змінних для забезпечення безпеки.
Використовуйте винятки для передачі інформації, реалізуйте оцінку угоди за допомогою try catch
У деяких кодах DEX ми виявили, що метод swap виконують, обгортаючи його в блок try catch. Чому так? Тому що нам потрібно змоделювати метод swap, щоб оцінити необхідні токени для обміну, але під час оцінки фактичного обміну токенів не відбувається, тому виникає помилка. Він викидає спеціальну помилку в функції зворотного виклику транзакції, а потім ловить цю помилку, розбираючи потрібну інформацію з повідомлення про помилку.
Це виглядає трохи хитро, але дуже практично. Таким чином, не потрібно переробляти метод свопу для оцінки потреб у торгівлі, логіка також простіша.
Використання великих чисел для вирішення проблеми точності
У коді DEX є багато обчислювальної логіки, наприклад, обчислення токенів для обміну на основі поточної ціни та ліквідності. У цьому процесі ми повинні уникати втрати точності під час операцій ділення. У деяких реалізаціях обчислювальний процес часто використовує операцію "<< FixedPoint96.RESOLUTION", яка означає зсув вліво на 96 біт, що відповідає множенню на 2^96. Після зсуву виконуються операції ділення, що дозволяє забезпечити точність у звичайних угодах без переповнення (, зазвичай обчислюючи за допомогою uint256, що достатньо для ).
Обчислення прибутку за допомогою Share
У DEX нам потрібно зафіксувати дохід від зборів LP( для постачальника ліквідності ). Очевидно, що не можна фіксувати збори для кожного LP під час кожної транзакції, це споживає велику кількість Gas. Як з цим впоратися?
Можна визначити структуру, що містить feeGrowthInside0LastX128 і feeGrowthInside1LastX128 у позиції, які фіксують комісію, що має отримати кожна ліквідність під час останнього вилучення комісії з кожної позиції.
Простими словами, потрібно лише зафіксувати загальні комісії та комісії, які повинні бути розподілені між кожною ліквідністю. Коли LP знімає комісії, можна розрахувати доступні для зняття комісії на основі утримуваної ліквідності. Це схоже на володіння акціями певної компанії: при знятті прибутку з акцій потрібно лише знати історичний прибуток на акцію компанії та прибуток, який ви отримали під час останнього зняття.
Не вся інформація повинна отримуватись з блокчейну
Зберігання даних в ланцюгу є відносно дорогим, тому не вся інформація повинна бути розміщена в ланцюзі або отримана з нього. Наприклад, багато інтерфейсів, які використовуються передніми сайтами DEX, є традиційними веб-інтерфейсами Web2.
Список торгових пулів, інформація про торгові пули тощо можуть зберігатися в звичайній базі даних, деякі з них можуть потребувати періодичної синхронізації з ланцюгом, але не потрібно в реальному часі викликати RPC-інтерфейс, наданий ланцюгом або вузлом, щоб отримати відповідні дані.
Звичайно, ключові угоди обов'язково здійснюються в мережі.
Навчіться розділяти контракти, використовуючи наявні стандартні контракти
Проект може містити кілька фактичних розгорнених контрактів. Навіть якщо фактично розгорнуто лише один контракт, ми також можемо розділити контракт на кілька контрактів за допомогою успадкування для його обслуговування.
Крім того, можна безпосередньо використовувати стандартні контракти, такі як @openzeppelin/contracts/token/ERC721/ERC721.sol. Таким чином, з одного боку, можна управляти позиціями за допомогою NFT, а з іншого боку, можна підвищити ефективність розробки, використовуючи вже існуючі стандартні контракти.
Підсумок
Практичний досвід розробки спрощеної децентралізованої біржі дозволить вам глибше зрозуміти реалізацію коду DEX, а також отримати більше знань про практичні проекти. Незалежно від того, чи цікавлять вас проекти Web3 чи DeFi, практичний досвід буде вам дуже корисний.
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
7 основних порад з розробки контрактів: вивчайте найкращі практики DeFi з коду DEX
Дивовижні прийоми розробки контрактів: досвід, отриманий з коду Uniswap
Нещодавно, під час написання посібника з розробки децентралізованих бірж, я звернувся до коду реалізації відомого DEX і дізнався багато нового. Як розробник, який раніше створював прості NFT-контракти, я вперше намагаюся розробити Defi-контракти, і впевнений, що ці маленькі поради будуть дуже корисні для новачків, які хочуть навчитися розробці контрактів.
Давайте разом подивимося на ці цікаві трюки, деякі з яких навіть можна назвати хитрощами.
Прогнозовані адреси розгортання контрактів
Зазвичай, розгортаючи контракт, ви отримуєте, здавалося б, випадкову адресу, оскільки вона пов'язана з nonce, тому адресу контракту важко передбачити. Але в певних ситуаціях нам потрібно зробити висновок про адресу контракту за допомогою торгових пар та відповідної інформації. Це дуже корисно в багатьох випадках, наприклад, для визначення прав на торгівлю або отримання адреси ліквідного пулу.
Можна створити контракт за допомогою методу CREATE2, додавши параметр salt, таким чином адреса створеного контракту є передбачуваною. Логіка генерації адреси: нова адреса = hash("0xFF", адреса творця, salt, initcode).
Вміле використання функцій зворотного виклику
У Solidity контракти можуть викликати один одного. Існує ситуація, коли A викликає метод B, а B у викликаному методі зворотно викликає A, що є корисним у певних ситуаціях.
Наприклад, коли ви викликаєте метод swap певного DEX-контракту для торгівлі, він викликає swapCallback, передаючи обчислений токен, необхідний для цієї торгівлі. Викликаюча сторона повинна в callback передати токен, необхідний для торгівлі, до DEX-контракту, а не розділяти метод swap на дві частини, щоб викликати їх окремо. Це забезпечує безпеку методу swap і гарантує, що вся логіка буде виконана в повному обсязі без необхідності складного ведення записів змінних для забезпечення безпеки.
Використовуйте винятки для передачі інформації, реалізуйте оцінку угоди за допомогою try catch
У деяких кодах DEX ми виявили, що метод swap виконують, обгортаючи його в блок try catch. Чому так? Тому що нам потрібно змоделювати метод swap, щоб оцінити необхідні токени для обміну, але під час оцінки фактичного обміну токенів не відбувається, тому виникає помилка. Він викидає спеціальну помилку в функції зворотного виклику транзакції, а потім ловить цю помилку, розбираючи потрібну інформацію з повідомлення про помилку.
Це виглядає трохи хитро, але дуже практично. Таким чином, не потрібно переробляти метод свопу для оцінки потреб у торгівлі, логіка також простіша.
Використання великих чисел для вирішення проблеми точності
У коді DEX є багато обчислювальної логіки, наприклад, обчислення токенів для обміну на основі поточної ціни та ліквідності. У цьому процесі ми повинні уникати втрати точності під час операцій ділення. У деяких реалізаціях обчислювальний процес часто використовує операцію "<< FixedPoint96.RESOLUTION", яка означає зсув вліво на 96 біт, що відповідає множенню на 2^96. Після зсуву виконуються операції ділення, що дозволяє забезпечити точність у звичайних угодах без переповнення (, зазвичай обчислюючи за допомогою uint256, що достатньо для ).
Обчислення прибутку за допомогою Share
У DEX нам потрібно зафіксувати дохід від зборів LP( для постачальника ліквідності ). Очевидно, що не можна фіксувати збори для кожного LP під час кожної транзакції, це споживає велику кількість Gas. Як з цим впоратися?
Можна визначити структуру, що містить feeGrowthInside0LastX128 і feeGrowthInside1LastX128 у позиції, які фіксують комісію, що має отримати кожна ліквідність під час останнього вилучення комісії з кожної позиції.
Простими словами, потрібно лише зафіксувати загальні комісії та комісії, які повинні бути розподілені між кожною ліквідністю. Коли LP знімає комісії, можна розрахувати доступні для зняття комісії на основі утримуваної ліквідності. Це схоже на володіння акціями певної компанії: при знятті прибутку з акцій потрібно лише знати історичний прибуток на акцію компанії та прибуток, який ви отримали під час останнього зняття.
Не вся інформація повинна отримуватись з блокчейну
Зберігання даних в ланцюгу є відносно дорогим, тому не вся інформація повинна бути розміщена в ланцюзі або отримана з нього. Наприклад, багато інтерфейсів, які використовуються передніми сайтами DEX, є традиційними веб-інтерфейсами Web2.
Список торгових пулів, інформація про торгові пули тощо можуть зберігатися в звичайній базі даних, деякі з них можуть потребувати періодичної синхронізації з ланцюгом, але не потрібно в реальному часі викликати RPC-інтерфейс, наданий ланцюгом або вузлом, щоб отримати відповідні дані.
Звичайно, ключові угоди обов'язково здійснюються в мережі.
Навчіться розділяти контракти, використовуючи наявні стандартні контракти
Проект може містити кілька фактичних розгорнених контрактів. Навіть якщо фактично розгорнуто лише один контракт, ми також можемо розділити контракт на кілька контрактів за допомогою успадкування для його обслуговування.
Крім того, можна безпосередньо використовувати стандартні контракти, такі як @openzeppelin/contracts/token/ERC721/ERC721.sol. Таким чином, з одного боку, можна управляти позиціями за допомогою NFT, а з іншого боку, можна підвищити ефективність розробки, використовуючи вже існуючі стандартні контракти.
Підсумок
Практичний досвід розробки спрощеної децентралізованої біржі дозволить вам глибше зрозуміти реалізацію коду DEX, а також отримати більше знань про практичні проекти. Незалежно від того, чи цікавлять вас проекти Web3 чи DeFi, практичний досвід буде вам дуже корисний.