Аналіз смарт-контрактів
07.08.2024

Розуміння поширених помилок смарт-контрактів ERC-20 в солідності

Смарт-контракти в екосистемі Ethereum часто містять складну логіку для забезпечення безпеки та функціональності децентралізованих додатків. Проте помилки можуть виникати з різних причин, що призводить до невдалих транзакцій і потенційних вразливостей. У цьому блозі розглянемо деякі поширені помилки, що зустрічаються в транзакціях з токенами ERC-20, їх причини та рішення.

1. Pancake: K

Помилка виникає, якщо порушується інваріант «продукт резервів має залишатися постійним». Це умова гарантує, що після виконання свопу новий продукт резервів (включаючи комісії) не буде меншим за старий продукт. Порушення цієї умови може статися, якщо:

  1. Токени, що вводяться в пул (amount0In або amount1In), не відповідають очікуваним значенням відповідно до інваріантної формули. Сталася помилка при розрахунку балансів після свопу.
  2. Хтось спробував маніпулювати пулом, що призвело до зміни балансів токенів, обійшовши стандартний процес свопу.
  3. Якщо ця умова не дотримується, виникає помилка «Pancake: K», яка сигналізує про порушення математичного інваріанту.

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


require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'Pancake: K');

Рішення:
Перевірте, чи є в пулі достатня ліквідність для виконання обміну та чи не перевищують значення резервів (_reserve0, _reserve1). Постійно моніторте резерви пулу та вживайте заходів для їх поповнення за потреби.

2. TransferHelper: TRANSFER_FROM_FAILED

Ця помилка вказує на невдалий переказ токенів за допомогою методу safeTransferFrom, який є поширеним шаблоном для безпечного переказу токенів ERC-20.


function safeTransferFrom(address token, address from, address to, uint value) 
  internal {
    // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
    (bool success, bytes memory data) = 
token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 
'TransferHelper: TRANSFER_FROM_FAILED');
  }

Причини:

  1. Адреса `from` не надала або не надала достатньо дозволів для адреси `msg.sender` для переказу токенів (allowance, approve).
  2. Адреса `from` не має достатньої кількості токенів для виконання переказу. Це також може бути через те, що адміністратори токенів мають можливість змінювати баланс токенів, і на момент зняття користувач, який зробив інвестицію, може не мати достатньо балансу для виконання зняття.
  3. Токен-контракт може не реалізовувати функцію transferFrom або реалізовувати її неправильно.
  4. Токен-контракт може містити додаткові перевірки або обмеження у функції transferFrom, що може призвести до скасування транзакції.
  5. Якщо не вистачає газу для виконання транзакції, транзакція може зазнати невдачі.

Рішення:

  1. Переконайтеся, що адреса `from` надала достатній дозвіл для `msg.sender`. Це можна перевірити, викликавши функцію allowance токен-контракту.
  2. Переконайтеся, що адреса `from` має достатню кількість токенів для виконання переказу.
  3. Перевірте, чи правильна адреса токен-контракту і чи відповідає контракт стандарту ERC-20.
  4. Перевірте реалізацію функції transferFrom у токен-контракті. Переконайтеся, що вона реалізована правильно і не має додаткових обмежень, які можуть спричинити помилку.
  5. Спробуйте збільшити ліміт газу під час виклику транзакції, щоб переконатися, що проблема не в нестачі газу.

3. INSUFFICIENT_LIQUIDITY

Ця помилка виникає при спробі вивести більше ліквідності, ніж є в резерві пулу ліквідності.


(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'Pancake:
INSUFFICIENT_LIQUIDITY');

Причини:

  1. Пул ліквідності не містить достатньої кількості одного або обох токенів для виконання запитаного обміну.
  2. Запитувана кількість токенів для виведення (amount0Out або amount1Out) перевищує доступну кількість токенів у пулі.
  3. Несумісність між резервами в контракті та фактичним станом пулу. Резерви, що зберігаються в контракті, можуть не відповідати фактичному балансу токенів у пулі через помилки або маніпуляції.

Рішення:

  1. Перевірте резерви пулу і переконайтеся, що вони достатні для виконання запитаного обміну. Це можна зробити за допомогою функції getReserves.
  2. Переконайтеся, що параметри amount0Out і amount1Out правильні і не перевищують доступну кількість токенів у пулі.
  3. Переконайтеся, що резерви в контракті відповідають фактичному балансу токенів. Для цього можна додати перевірку і оновлення резервів перед виконанням обміну.

INSUFFICIENT_LIQUIDITY

4. APPROVE_FAILED

Помилка `APPROVE_FAILED` виникає під час виконання функції `safeApprove`. Ця функція призначена для встановлення кількості токенів, яку власник дозволяє витратити розпоряднику від його імені.


function safeApprove(address token, address to, uint value) internal {
  (bool success, bytes memory data) = 
token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
  require(success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper: APPROVE_FAILED');
}

Причини:

  1. Токен-контракт може не мати функції `approve` або вона може бути реалізована неправильно.
  2. Можуть бути проблеми зі станом токен -контракту, такі як недостатній баланс або дозвіл.
  3. Функція `approve` може скасовуватися з міркувань безпеки, реалізованих у токен-контракті.

Рішення:

  1. Переконайтеся, що токен-контракт відповідає стандарту ERC-20 і включає правильно реалізовану функцію `approve`.
  2. Перевірте стан токен-контракту та переконайтеся, що на рахунку достатньо токенів для затвердження.
  3. Перевірте правильність адреси та суми, переданих у функцію `safeApprove`.
  4. Додатково налагодьте помилки, перевіряючи конкретне повідомлення про помилку або причину скасування у токен-контракті.

5. Fail with error 'ds-math-sub-underflow'

Помилка `ds-math-sub-underflow` виникає, коли операція віднімання призводить до переповнення, тобто результат віднімання менший за нуль.


    function sub(uint x, uint y) internal pure returns (uint z) {
      require((z = x - y) <= x, 'ds-math-sub-underflow');
    } 

Причина:
Ця помилка виникає, коли операція віднімання `x - y` призводить до негативного числа, що не дозволено для беззнакових цілих чисел у Solidity.

Рішення:

  1. Переконайтеся, що значення `y` завжди менше або дорівнює `x` перед виконанням віднімання.
  2. Реалізуйте перевірки у вашому коді для обробки випадків, коли `y` може бути більше за `x`, і вживайте відповідних заходів, таких як скасування транзакції або коригування логіки.

6. Помилка 'ERC20: transfer amount exceeds allowance'

Помилка `ERC20: transfer amount exceeds allowance` виникає, коли спроба переказати токени від імені іншого користувача перевищує дозволену суму, яку власник токенів встановив для витратника.

Причина:
Цю помилку викликає функція `transferFrom` контракту токенів ERC-20, коли сума переказу перевищує дозволений ліміт, встановлений власником токенів.

Рішення:

  1. Переконайтеся, що власник токенів встановив достатній дозвіл для витратника за допомогою функції `approve`.
  2. Перевірте поточний дозвіл перед спробою виконання операції `transferFrom`.
  3. Якщо необхідно, попросіть власника токенів збільшити дозвіл, викликавши функцію `approve` з більшою сумою.

7. TRANSFER_FAILED

Ця помилка виникає, коли переказ токенів з однієї адреси на іншу не вдається. Функція `_safeTransfer` гарантує успіх операції переказу та те, що повернені дані, якщо такі є, декодуються в `true`.


    `function _safeTransfer(address token, address to, uint value) private { (bool 
    success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to,
    value)); require(success && (data.length == 0 || abi.decode(data, (bool))),
    'Pancake: TRANSFER_FAILED'); }`

Причина:

  1. Функція `token.call` не виконується успішно (тобто `success` дорівнює `false`).
  2. Виклик повертає дані, які не декодуються в `true`, що вказує на збій у функції `transfer` токен-контракту.

Рішення:

  1. Переконайтесь у відповідності ERC-20: перевірте, що токен-контракт відповідає стандарту ERC-20, що включає правильну реалізацію функції `transfer`.
  2. Правильні параметри: переконайтесь, що адреса `to` є дійсною, а `value` знаходиться в межах дозволених значень, враховуючи баланс відправника та логіку контракту.
  3. Додаткові умови: перевірте, чи не вимагає токен-контракт додаткових умов, таких як попереднє схвалення суми витрат (`approve` і `allowance` функції).

INSUFFICIENT_LIQUIDITY

8. INSUFFICIENT_OUTPUT_AMOUNT

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


    `require(amounts[amounts.length - 1] >= amountOutMin,
    'PancakeRouter: INSUFFICIENT_OUTPUT_AMOUNT');`

Причина:

  1. Ринкова волатильність, яка впливає на ціни токенів між моментом ініціювання транзакції та її виконанням.
  2. Налаштування високого ковзання, які дозволяють значні відхилення в очікуваних виходах.
  3. Недостатня ліквідність у торговому пулі, що викликає більші цінові впливи.

Рішення:

  1. Переконайтесь у відповідності ERC-20: перевірте, що токен-контракт відповідає стандарту ERC-20, що включає правильну реалізацію функції `transfer`.
  2. Правильні параметри: переконайтесь, що адреса `to` є дійсною, а `value` знаходиться в межах дозволених значень, враховуючи баланс відправника та логіку контракту.
  3. Додаткові умови: перевірте, чи не вимагає токен-контракт додаткових умов, таких як попереднє схвалення суми витрат (`approve` і `allowance` функції).

9. INSUFFICIENT_INPUT_AMOUNT

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


    uint amount0In = ...; // Input amount of token0
    uint amount1In = ...; // Input amount of token1
    require(amount0In > 0 || amount1In > 0, 'Pancake: INSUFFICIENT_INPUT_AMOUNT');

Причина:

  1. Неправильні параметри вводу для функції обміну.
  2. Незадовільний баланс у акаунті користувача.
  3. Помилки в логіці обчислення вхідних сум.

Рішення:

  1. Перевірте вхідні суми: переконайтесь, що вхідні суми правильно задані перед викликом функції обміну. Це включає в себе правильну перевірку вводу користувача та налаштування параметрів.
  2. Перевірте баланси користувача: переконайтесь, що користувач має достатній баланс токенів для обміну. Це можна перевірити, викликавши функцію `balanceOf` відповідних токен-контрактів.

10. Fail

Причина:

  1. Транзакція не може бути виконана через недостатню кількість газу для завершення всіх операцій.
  2. Неправильна логіка або умови в смарт-контракті можуть призвести до збою виконання (наприклад, виклик функції require або assert, який зазнає невдачі).
  3. Спроба виконати транзакцію з токенами або криптовалютою, коли баланс акаунта недостатній.
  4. У випадку роботи з токенами стандарту ERC-20 та ERC-721 транзакція може зазнати невдачі через недостатні дозволи.
  5. Виклик смарт-контракту може бути скасований через неспроможність виконати умови всередині нього (наприклад, використання функції revert).

Рішення:

  1. Збільште ліміт газу при відправці транзакції.
  2. Перевірте умови та логіку в смарт-контракті.
  3. Переконайтесь, що акаунт має достатньо коштів для завершення транзакції.
  4. Викличте функцію approve з достатньою сумою перед викликом transferFrom.
  5. Перевірте умови, що викликають скасування транзакції.
  6. Перевірте ваш баланс на комісії.

Висновок

Розуміння та усунення цих поширених помилок у смарт-контрактах ERC-20 вимагає чіткого розуміння програмування Solidity, стандарту ERC-20 та внутрішньої роботи децентралізованих бірж. Докладний перегляд причин та впровадження запропонованих рішень допоможе розробникам створювати більш надійні смарт-контракти, забезпечуючи безперебійний досвід для користувачів у децентралізованій екосистемі.

All posts

Connect to a wallet

Metamask