Analise smart-contract
07.08.2024

Zrozumienie typowych błędów inteligentnych kontraktów ERC-20 w Solidity

Inteligentne kontrakty w ekosystemie Ethereum często zawierają skomplikowaną logikę, aby zapewnić bezpieczeństwo i funkcjonalność zdecentralizowanych aplikacji. Jednakże błędy mogą wystąpić z różnych powodów, prowadząc do nieudanych transakcji i potencjalnych luk w zabezpieczeniach. W tym wpisie na blogu przyjrzymy się niektórym powszechnym błędom napotykanym podczas transakcji tokenów ERC-20, ich przyczynom oraz rozwiązaniom.

1. Pancake: K

Błąd występuje, jeśli naruszony jest warunek „iloczyn rezerw musi być stały”. Ten warunek gwarantuje, że po wykonaniu wymiany nowy iloczyn rezerw (w tym prowizje) nie będzie mniejszy niż stary iloczyn. Naruszenie tego warunku może nastąpić, jeśli:

  1. Tokeny wprowadzone do puli (amount0In lub amount1In) nie odpowiadają oczekiwanym wartościom zgodnie z formułą invariantną. Wystąpił błąd w obliczeniach sald po wymianie.
  2. Ktoś próbował manipulować pulą, co spowodowało zmianę sald tokenów, omijając standardowy proces wymiany.
  3. Jeśli warunek ten nie jest spełniony, wywoływany jest błąd „Pancake: K”, sygnalizujący naruszenie matematycznej invariantności.

Naruszenie invariantności oznacza, że jeden z podstawowych warunków zapewniających poprawne działanie systemu przestał być spełniony. W kontekście zdecentralizowanych giełd, takich jak PancakeSwap, invariantność jest zwykle związana z równaniem matematycznym, które musi pozostać prawdziwe, aby utrzymać równowagę między rezerwami tokenów w puli płynności.


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

Rozwiązanie:
Sprawdź, czy pula ma wystarczającą płynność do przeprowadzenia wymiany oraz czy wartości nie przekraczają rezerw (_reserve0, _reserve1). Monitoruj rezerwy puli i podejmuj kroki w celu ich uzupełnienia, jeśli to konieczne.

2. TransferHelper: TRANSFER_FROM_FAILED

Ten błąd oznacza nieudane przekazanie tokenów za pomocą metody safeTransferFrom, która jest powszechnym wzorcem bezpiecznego przekazywania tokenów 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');
  }

Przyczyna:

  1. Adres „from” nie udzielił lub nie udzielił wystarczających uprawnień adresowi „msg.sender” do przekazywania tokenów (allowance, approve).
  2. Adres „from” nie ma wystarczającej liczby tokenów, aby przeprowadzić przekazanie. Może to być również spowodowane tym, że administratorzy tokenów mają możliwość modyfikowania salda tokenów, a w momencie wypłaty użytkownik, który dokonał inwestycji, może nie mieć wystarczającego salda do wykonania wypłaty.
  3. Kontrakt tokenu może nie implementować funkcji transferFrom lub implementować ją niepoprawnie.
  4. Kontrakt tokenu może zawierać dodatkowe kontrole lub ograniczenia w funkcji transferFrom, które mogą spowodować anulowanie transakcji.
  5. Jeśli nie ma wystarczającej ilości gazu do wykonania transakcji, transakcja może nie powieść się.

Rozwiązanie:

  1. Upewnij się, że adres „from” udzielił wystarczającego allowance dla „msg.sender”. Można to zrobić, wywołując funkcję allowance kontraktu tokenu.
  2. Upewnij się, że adres „from” ma wystarczającą liczbę tokenów do wykonania przekazania.
  3. Sprawdź, czy adres kontraktu tokenu jest poprawny i czy kontrakt jest zgodny z ERC-20.
  4. Sprawdź implementację funkcji transferFrom w kontrakcie tokenu. Upewnij się, że jest poprawnie zaimplementowana i nie zawiera dodatkowych ograniczeń, które mogą powodować błąd.
  5. Spróbuj zwiększyć limit gazu podczas wywoływania transakcji, aby upewnić się, że problem nie jest spowodowany brakiem gazu.

3. INSUFFICIENT_LIQUIDITY

Ten błąd występuje, gdy próbujesz wypłacić więcej płynności niż jest dostępne w rezerwach puli płynności.


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

Przyczyna:

  1. Pula płynności nie zawiera wystarczającej ilości jednego lub obu tokenów, aby zrealizować żądaną wymianę.
  2. Żądana ilość tokenów do wypłaty (amount0Out lub amount1Out) przekracza dostępną liczbę tokenów w puli.
  3. Niedopasowanie między rezerwami w kontrakcie a rzeczywistym stanem puli. Rezerwy przechowywane w kontrakcie mogą nie odpowiadać rzeczywistemu stanowi salda tokenów w puli z powodu błędów lub manipulacji.

Rozwiązanie:

  1. Sprawdź rezerwy puli i upewnij się, że są wystarczające do zrealizowania żądanej wymiany. Można to zrobić, korzystając z funkcji getReserves.
  2. Upewnij się, że parametry amount0Out i amount1Out są poprawne i nie przekraczają dostępnej liczby tokenów w puli.
  3. Upewnij się, że rezerwy w kontrakcie odpowiadają rzeczywistemu saldu tokenów. Aby to zrobić, można dodać sprawdzanie i aktualizowanie rezerw przed wykonaniem wymiany.

INSUFFICIENT_LIQUIDITY

4. APPROVE_FAILED

Błąd `APPROVE_FAILED` występuje podczas wykonania funkcji `safeApprove`. Funkcja ta jest zaprojektowana w celu ustawienia ilości tokenów, które właściciel zezwala wydatkowującemu na użycie w jego imieniu.


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');
}

Przyczyna:

  1. Kontrakt tokenu może nie mieć funkcji `approve` lub może być ona niepoprawnie zaimplementowana.
  2. Mogą wystąpić problemy ze stanem kontraktu tokenu, takie jak niew ystarczające saldo lub allowance.
  3. Funkcja `approve` może zostać wycofana z powodów bezpieczeństwa zaimplementowanych w kontrakcie tokenu.

Rozwiązanie:

  1. Upewnij się, że kontrakt tokenu jest zgodny z standardem ERC-20 i zawiera poprawnie zaimplementowaną funkcję `approve`.
  2. Sprawdź stan kontraktu tokenu i upewnij się, że konto ma wystarczającą liczbę tokenów do zatwierdzenia.
  3. Sprawdź, czy adres i kwota przekazane do funkcji `safeApprove` są poprawne.
  4. Przeprowadź dalsze debugowanie, sprawdzając konkretny komunikat błędu lub powód wycofania w kontrakcie tokenu.

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

Błąd `ds-math-sub-underflow` jest rzucany, gdy operacja odejmowania powoduje niedobór, tzn. gdy wynik odejmowania jest mniejszy niż zero.


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

Przyczyna:
Ten błąd występuje, ponieważ operacja odejmowania `x - y` prowadzi do liczby ujemnej, co jest niedozwolone dla liczb bez znaku w Solidity.

Rozwiązanie:

  1. Upewnij się, że wartość `y` jest zawsze mniejsza lub równa `x` przed wykonaniem odejmowania.
  2. Wprowadź kontrole w swoim kodzie, aby obsłużyć przypadki, w których `y` może być większe niż `x`, i podejmij odpowiednie działania, takie jak wycofanie transakcji lub dostosowanie logiki.

6. Błąd: 'ERC20: transfer amount exceeds allowance'

Błąd `ERC20: transfer amount exceeds allowance` występuje, gdy próba przetransferowania tokenów w imieniu innego użytkownika przekracza dozwoloną kwotę, którą właściciel tokenów ustawił dla wydatkującego.

Przyczyna:
Błąd ten jest zgłaszany przez funkcję `transferFrom` kontraktu tokenu ERC-20, gdy kwota do przetransferowania przekracza dozwolony limit ustalony przez właściciela tokenów.

Rozwiązanie:

  1. Upewnij się, że właściciel tokenów ustawił odpowiedni limit dla wydatkującego za pomocą funkcji `approve`.
  2. Sprawdź aktualny limit przed próbą operacji `transferFrom`.
  3. Jeśli to konieczne, poproś właściciela tokenów o zwiększenie limitu, wywołując funkcję `approve` z wyższą wartością.

7. TRANSFER_FAILED

Błąd ten występuje, gdy transfer tokenów z jednego adresu na inny nie powiedzie się. Funkcja `_safeTransfer` zapewnia, że operacja transferu zakończyła się sukcesem, a zwrócone dane, jeśli występują, dekodują się do `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'); }`

Przyczyna:

  1. Funkcja `token.call` nie wykonuje się pomyślnie (tj. `success` jest `false`).
  2. Wywołanie zwraca dane, które nie dekodują się do `true`, co wskazuje na niepowodzenie funkcji `transfer` kontraktu tokenu.

Rozwiązanie:

  1. Upewnij się, że kontrakt tokenu jest zgodny ze standardem ERC-20, który obejmuje poprawną implementację funkcji `transfer`.
  2. Poprawne parametry: Upewnij się, że adres `to` jest ważny, a `value` mieści się w dozwolonych limitach, biorąc pod uwagę saldo nadawcy i logikę kontraktu.
  3. Dodatkowe warunki: Sprawdź, czy kontrakt tokenu wymaga dodatkowych warunków, takich jak wstępne zatwierdzenie kwoty wydatków (funkcje `approve` i `allowance`).

INSUFFICIENT_LIQUIDITY

8. INSUFFICIENT_OUTPUT_AMOUNT

Błąd ten występuje w kontekście zdecentralizowanej giełdy, gdy kwota wyjściowa wymiany tokenów jest mniejsza niż minimalna kwota określona przez użytkownika. Jest to zabezpieczenie mające na celu zapewnienie, że użytkownicy nie otrzymają mniejszej liczby tokenów niż oczekiwano z powodu slippage lub zmian cen podczas transakcji.


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

Przyczyna:

  1. Zmienność rynku wpływająca na ceny tokenów między czasem inicjowania transakcji a jej realizacją.
  2. Wysokie ustawienia slippage, które pozwalają na znaczące odchylenia w oczekiwanych wynikach.
  3. Niewystarczająca płynność w puli handlowej, powodująca większy wpływ cenowy.

Rozwiązanie:

  1. Upewnij się, że kontrakt tokenu jest zgodny ze standardem ERC-20, który obejmuje poprawną implementację funkcji `transfer`.
  2. Poprawne parametry: Upewnij się, że adres `to` jest ważny, a `value` mieści się w dozwolonych limitach, biorąc pod uwagę saldo nadawcy i logikę kontraktu.
  3. Dodatkowe warunki: Sprawdź, czy kontrakt tokenu wymaga dodatkowych warunków, takich jak wstępne zatwierdzenie kwoty wydatków (funkcje `approve` i `allowance`).

9. INSUFFICIENT_INPUT_AMOUNT

Błąd ten występuje, gdy żadna z kwot wejściowych dla wymiany tokenów nie jest większa niż zero. Zapewnia to, że przynajmniej jedna z kwot wejściowych (`amount0In` lub `amount1In`) jest dodatnia, aby kontynuować wymianę.


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

Przyczyna:

  1. Niepoprawne parametry wejściowe dla funkcji wymiany.
  2. Niewystarczające środki na koncie użytkownika.
  3. Błędy w logice obliczania kwot wejściowych.

Rozwiązanie:

  1. Walidacja kwot wejściowych: Upewnij się, że kwoty wejściowe są poprawnie ustawione przed wywołaniem funkcji wymiany. Wymaga to odpowiedniej walidacji danych wejściowych użytkownika i ustawiania parametrów.
  2. Sprawdź salda użytkownika: Upewnij się, że użytkownik ma wystarczającą ilość tokenów przeznaczonych na wymianę. Można to zrobić, wywołując funkcję `balanceOf` odpowiednich kontraktów tokenów.

10. Fail

Przyczyna:

  1. Transakcja nie może zostać wykonana, ponieważ brakuje jej wystarczającej ilości gazu do zakończenia wszystkich operacji.
  2. Niepoprawna logika lub warunki w kontrakcie inteligentnym mogą spowodować niepowodzenie wykonania (na przykład, wywołanie funkcji require lub assert, które zawiodło).
  3. Próba wykonania transakcji tokenów lub kryptowalut, gdy saldo konta jest niewystarczające.
  4. W przypadku pracy z tokenami zgodnymi ze standardem ERC-20 i ERC-721, transakcja może zakończyć się niepowodzeniem z powodu niewystarczających uprawnień.
  5. Wywołanie kontraktu inteligentnego może zostać wycofane z powodu niespełnienia warunków wewnętrznych (np. użycie funkcji revert).

Rozwiązanie:

  1. Zwiększ limit gazu podczas wysyłania transakcji.
  2. Sprawdź warunki i logikę wewnętrzną kontraktu inteligentnego.
  3. Upewnij się, że konto ma wystarczające środki do realizacji transak cji.
  4. Wywołaj funkcję approve z odpowiednią wartością przed wywołaniem transferFrom.
  5. Sprawdź warunki, które powodują wycofanie transakcji.
  6. Sprawdź swoje saldo pod kątem prowizji.

Podsumowanie

Zrozumienie i rozwiązywanie tych powszechnych błędów w kontraktach inteligentnych ERC-20 wymaga solidnej znajomości programowania w Solidity, standardu ERC-20 oraz wewnętrznej pracy zdecentralizowanych giełd. Dokładne zapoznanie się z przyczynami i wdrożenie sugerowanych rozwiązań pozwoli deweloperom tworzyć bardziej solidne i niezawodne kontrakty inteligentne, zapewniając płynne doświadczenie użytkowników w zdecentralizowanym ekosystemie.

All posts

Connect to a wallet

Metamask