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

Понимание распространенных ошибок смарт-контрактов ERC-20 в Solidity

Умные контракты в экосистеме 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 предоставил достаточный allowance для 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. Может быть проблема с состоянием контракта токена, например, недостаточный баланс или allowance.
  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: сумма перевода превышает разрешение'

Ошибка `ERC20: сумма перевода превышает разрешение` возникает, когда производится попытка перевести токены от имени другого пользователя, но сумма перевода превышает лимит, который владелец токенов установил для тратителя.

Причина:
Эта ошибка возникает в функции `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. Ошибка

Причина:

  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