Скорочення контрактів для боротьби з лімітом розміру контракту
Чому існує обмеження?
22 листопада 2016 року під час хард-форку Spurious Dragon (opens in a new tab) було запроваджено EIP-170 (opens in a new tab), який додав обмеження на розмір смарт-контракту у 24,576 КБ. Для вас як для розробника на Solidity це означає, що, коли ви додаєте все більше функціональності до свого контракту, у певний момент ви досягнете ліміту, і під час розгортання побачите таку помилку:
Попередження: розмір коду контракту перевищує 24576 байт (обмеження, запроваджене в Spurious Dragon). Цей контракт може бути неможливо розгорнути в Головній мережі. Подумайте про ввімкнення оптимізатора (з низьким значенням "runs"!), вимкнення рядків повернення або використання бібліотек.
Це обмеження було запроваджено для запобігання атакам типу «відмова в обслуговуванні» (DoS). Будь-який виклик контракту є відносно дешевим з погляду витрат газу. Однак, вплив виклику контракту для вузлів Ethereum непропорційно зростає залежно від розміру коду контракту, що викликається (зчитування коду з диска, попередня обробка коду, додавання даних до доказу Меркла). Щоразу, коли виникає ситуація, коли зловмиснику потрібно небагато ресурсів, щоб змусити інших виконати багато роботи, з’являється потенціал для DoS-атак.
Спочатку це було меншою проблемою, оскільки одним із природних обмежень розміру контракту є ліміт газу для блоку. Очевидно, що контракт має бути розгорнутий у межах транзакції, яка містить увесь байт-код контракту. Якщо ви включите в блок лише одну цю транзакцію, ви можете використати весь газ, але його кількість не є нескінченною. Після оновлення London ліміт газу блоку може коливатися від 15 до 30 мільйонів одиниць залежно від попиту в мережі.
Далі ми розглянемо деякі методи, впорядковані за їхнім потенційним впливом. Уявіть, що це процес схуднення. Найкраща стратегія для досягнення цільової ваги (у нашому випадку 24 КБ) — це спочатку зосередитися на методах, що мають найбільший вплив. У більшості випадків достатньо скоригувати свій раціон, але іноді потрібно щось більше. Тоді ви можете додати фізичні вправи (середній вплив) або навіть харчові добавки (незначний вплив).
Значний вплив
Розділяйте свої контракти
Це завжди має бути вашим першим підходом. Як можна розділити контракт на кілька менших? Зазвичай це змушує вас розробити хорошу архітектуру для своїх контрактів. Меншим контрактам завжди надається перевага з точки зору читабельності коду. Щоб розділити контракти, запитайте себе:
- Які функції логічно пов'язані? Кожен набір таких функцій краще винести в окремий контракт.
- Які функції не вимагають зчитування стану контракту або потребують лише певної його частини?
- Чи можете ви розділити сховище та функціональність?
Бібліотеки
Один простий спосіб винести код функціональності зі сховища — це використання бібліотеки (opens in a new tab). Не оголошуйте функції бібліотеки як internal, оскільки вони будуть безпосередньо додані до контракту (opens in a new tab) під час компіляції. Але якщо ви використовуєте функції public, то вони фактично будуть в окремому контракті-бібліотеці. Розгляньте можливість використання using for (opens in a new tab), щоб зробити використання бібліотек зручнішим.
Проксі-контракти
Більш просунутою стратегією є використання проксі-системи. Бібліотеки використовують DELEGATECALL «під капотом», який просто виконує функцію іншого контракту зі станом контракту, що його викликає. Перегляньте цю публікацію в блозі (opens in a new tab), щоб дізнатися більше про проксі-системи. Вони надають вам більше функціональності, наприклад, дозволяють оновлювати контракт, але водночас додають значної складності. Я б не радив додавати їх лише для зменшення розміру контракту, якщо тільки це не єдиний для вас варіант з якихось причин.
Середній вплив
Видаліть функції
Це має бути очевидним. Функції досить суттєво збільшують розмір контракту.
- Зовнішні (
external): часто ми додаємо багатоview-функцій для зручності. Це цілком нормально, поки ви не досягнете ліміту розміру. Тоді варто серйозно подумати про видалення всіх функцій, крім абсолютно необхідних. - Внутрішні (
internal): ви також можете видалитиinternal/privateфункції та просто вбудувати код, якщо функція викликається лише один раз.
Уникайте додаткових змінних
1function get(uint id) returns (address,address) {2 MyStruct memory myStruct = myStructs[id];3 return (myStruct.addr1, myStruct.addr2);4}1function get(uint id) returns (address,address) {2 return (myStructs[id].addr1, myStructs[id].addr2);3}Така проста зміна дає різницю в 0,28 КБ. Ймовірно, ви зможете знайти багато схожих ситуацій у своїх контрактах, і в сумі вони можуть дати значну економію.
Скорочуйте повідомлення про помилки
Довгі повідомлення revert і, зокрема, велика кількість різних повідомлень revert можуть роздувати контракт. Натомість використовуйте короткі коди помилок і розшифровуйте їх у своєму контракті. Довге повідомлення може стати набагато коротшим:
1require(msg.sender == owner, \"Only the owner of this contract can call this function\");1require(msg.sender == owner, \"OW1\");Використовуйте користувацькі помилки замість повідомлень про помилки
Користувацькі помилки були запроваджені в Solidity 0.8.4 (opens in a new tab). Це чудовий спосіб зменшити розмір ваших контрактів, оскільки вони кодуються в ABI як селектори (так само, як і функції).
1error Unauthorized();23if (msg.sender != owner) {4 revert Unauthorized();5}Розгляньте низьке значення runs в оптимізаторі
Ви також можете змінити налаштування оптимізатора. Значення за замовчуванням 200 означає, що він намагається оптимізувати байт-код так, ніби функція викликається 200 разів. Якщо ви зміните його на 1, ви фактично вкажете оптимізатору оптимізувати код для випадку, коли кожна функція запускається лише один раз. Функція, оптимізована для одноразового запуску, означає, що вона оптимізована для самого розгортання. Майте на увазі, що це збільшує вартість газу для виконання функцій, тому, можливо, ви не захочете цього робити.
Незначний вплив
Уникайте передачі структур у функції
Якщо ви використовуєте ABIEncoderV2 (opens in a new tab), може допомогти не передавати структури у функцію. Замість того, щоб передавати параметр як структуру, передавайте необхідні параметри безпосередньо. У цьому прикладі ми заощадили ще 0,1 КБ.
1function get(uint id) returns (address,address) {2 return _get(myStruct);3}45function _get(MyStruct memory myStruct) private view returns(address,address) {6 return (myStruct.addr1, myStruct.addr2);7}1function get(uint id) returns(address,address) {2 return _get(myStructs[id].addr1, myStructs[id].addr2);3}45function _get(address addr1, address addr2) private view returns(address,address) {6 return (addr1, addr2);7}Оголошуйте правильну видимість для функцій та змінних
- Функції або змінні, які викликаються лише ззовні? Оголошуйте їх як
externalзамістьpublic. - Функції або змінні, які викликаються лише зсередини контракту? Оголошуйте їх як
privateабоinternalзамістьpublic.
Видаліть модифікатори
Модифікатори, особливо при інтенсивному використанні, можуть мати значний вплив на розмір контракту. Розгляньте можливість їх видалення та використання замість них функцій.
1modifier checkStuff() {}23function doSomething() checkStuff {}1function checkStuff() private {}23function doSomething() { checkStuff(); }Ці поради мають допомогти вам значно зменшити розмір контракту. Ще раз, не можу не наголосити, завжди зосереджуйтеся на розділенні контрактів, якщо це можливо, для досягнення найбільшого ефекту.
Останні оновлення сторінки: 25 лютого 2026 р.