Strona główna
» Wiedza
»
Przewodnik krok po kroku po audycie inteligentnego kontraktu projektu kryptograficznego
Przewodnik krok po kroku po audycie inteligentnego kontraktu projektu kryptograficznego
Audyt inteligentnych kontraktów to ustrukturyzowana próba wykrycia, w jaki sposób kontrakt może zawieść, zostać nadużyty lub być kontrolowany w sposób nieoczekiwany przez użytkowników. To nie to samo, co uruchomienie jednego skanera, odczytanie identyfikatora audytu czy potwierdzenie weryfikacji kodu źródłowego. Skorzystaj z poniższego schematu działania, aby przejrzeć wdrożony kontrakt EVM lub bazę kodu, zanim powierzysz im znaczące środki.
Ważne: Niniejszy dokument stanowi praktyczny przegląd, a nie gwarancję bezpieczeństwa projektu ani poradę inwestycyjną. Protokół produkcyjny o istotnej wartości powinien zostać poddany niezależnej ocenie przez doświadczonych specjalistów ds. bezpieczeństwa. Ilustracje interfejsu w tym przewodniku mają charakter poglądowy i nie należy ich traktować jako dowodu na temat konkretnego projektu ani wdrożenia.
Lista kontrolna audytu w skrócie
Krok
Główne pytanie
Przydatne dowody
1. Zakres
Czy przeglądam dokładnie tę umowę, do której dzwonią użytkownicy?
Adres, łańcuch, kod bajtowy, serwer proxy i implementacja
2. Punkty wejścia
Co może zrobić każdy dzwoniący?
Funkcje publiczne/zewnętrzne, zmiany stanu, graf wywołań
3. Automatyzacja
Jakie oczywiste wzorce zasługują na uwagę?
Wyjście kompilatora, wyniki Slither, selekcja detektora
Czy zachowanie jest takie samo w przypadku nieoczekiwanych danych wejściowych i sekwencji?
Testy jednostkowe, rozmyte, niezmienne i rozwidlenia
8. Raportowanie
Czy inna osoba może odtworzyć i ponownie przetestować wynik?
Znalezienie, wpływ, dowody, naprawa i status ponownego testowania
Krok 1: Potwierdź zakres i wdrożony artefakt
Widok weryfikacji kontraktu pokazujący sieć, adres, wersję kompilatora i zgodność kodu bajtowego, które należy zarejestrować przed analizą.
Zacznij od dokładnego łańcucha i adresu. Zapisz adres wdrożenia, skrót transakcji, numer bloku, wersję kompilatora, ustawienia optymalizatora, argumenty konstruktora oraz zatwierdzenie lub wydanie, które według zespołu jest wdrożone. Projekt może mieć kilka adresów dla tokena, routera, skarbca, serwera proxy, implementacji, wyroczni lub wdrożenia testowego. Przegląd błędnego adresu nie ma praktycznej wartości.
Sprawdź, czy zweryfikowane źródło eksploratora odtwarza wdrożony kod bajtowy. Weryfikacja jest przydatna, ponieważ pozwala na inspekcję źródła i interfejsu ABI, ale jest jedynie sprawdzeniem tożsamości: nie dowodzi bezpieczeństwa logiki biznesowej. Jeśli kontrakt można zaktualizować, zidentyfikuj zarówno serwer proxy, jak i jego obecną implementację. Odczytaj adres implementacji z udokumentowanego mechanizmu serwera proxy lub informacji eksploratora, a następnie potwierdź, że to właśnie ta implementacja ma zostać sprawdzona. Oficjalny przewodnik weryfikacji Foundry firmy Etherscan dokumentuje weryfikację nowych i istniejących kontraktów.
Zdefiniuj również granice. Uwzględnij zaimportowane biblioteki, odziedziczone kontrakty, biblioteki powiązane, wdrożone kontrakty pomocnicze, adaptery Oracle, tokeny otrzymane od użytkowników oraz uprzywilejowane komponenty poza łańcuchem. Zapisz, co wykracza poza zakres i dlaczego. Zapobiega to pomyleniu przeglądu zawężonego z przeglądem całego systemu.
Krok 2: Zbuduj punkt wejścia i mapę zasobów
Inwentarz funkcji oddzielający publiczne i zewnętrzne punkty wejścia zanim recenzent prześledzi zmiany ich stanu.
Wypisz wszystkie funkcje publiczne i zewnętrzne, w tym funkcje dziedziczone oraz procedury obsługi zapasowej i odbiorczej. Dla każdej z nich zanotuj, czy może:
przenoszenie rodzimej waluty lub tokenów;
wybijać, palić, pożyczać, likwidować lub zmieniać księgowość;
zmienić wyrocznię, opłatę, limit, rolę, stan pauzy lub implementację;
wykonać połączenie zewnętrzne, delegowane lub połączenie niskiego szczebla; lub
odczyt danych, na których opiera się inna funkcja zmieniająca stan.
Następnie zmapuj zasoby i granice zaufania. Prześledź wpłatę od użytkownika do magazynu, poprzez ustalanie cen i udziałów, aż do wypłaty. Zidentyfikuj każdy adres podany przez osobę dzwoniącą i każdy adres pobrany z magazynu. Zapytaj, które wartości są uznawane za uczciwe: wyrocznia, token, wiadomość pomostowa, strażnik, odbiorca wywołania zwrotnego czy administrator. Cele przeglądu o najwyższej wartości to funkcje łączące dane wejściowe kontrolowane przez użytkownika, stan uprzywilejowany, arytmetykę i wywołanie zewnętrzne.
Krok 3: Kompilacja czysta i uruchomienie analizy statycznej
Przeprowadzenie analizy statycznej może szybko wykryć potencjalne problemy, które nadal wymagają potwierdzenia w rzeczywistym kodzie i modelu zagrożeń.
Odtwórz kompilację projektu z podaną wersją Solidity, wersjami zależności, konfiguracją optymalizatora i założeniami łańcucha docelowego. Traktuj ostrzeżenia kompilatora jako elementy do sprawdzenia, a nie nieszkodliwy szum informacyjny. Zagadnienia bezpieczeństwa Solidity zalecają poważne traktowanie ostrzeżeń, utrzymywanie zrozumiałych kontraktów i sprawdzanie znanych problemów kompilatora. Zapoznaj się z oficjalną listą znanych błędów kompilatora Solidity, gdy wersja kompilatora lub wzorce kodu, których dotyczy problem, wskazują na ich znaczenie.
W przypadku projektu Hardhat, Foundry lub podobnego, uruchom Slither z katalogu głównego projektu. Oficjalna dokumentacja opisuje to narzędzie jako analizator statyczny Solidity i Vyper oraz podaje typowe polecenie:
slither .
Zapisz dane wyjściowe i sklasyfikuj każdy wynik według wpływu i poziomu pewności. Przyjrzyj się uważnie wynikom obejmującym dowolne wysyłanie tokenów, niezabezpieczone aktualizacje, reentrancy, niesprawdzone wartości zwracane, niebezpieczne wywołania delegatów, tx.origin, słabą losowość i nieprawidłowe interfejsy. Detektor może zgłosić fałszywie dodatni wynik, pominąć błąd ekonomiczny specyficzny dla projektu lub oznaczyć kod, który jest celowo ograniczony gdzie indziej. Analiza statyczna zawęża zakres wyszukiwania; nie zastępuje ręcznego rozumowania. Repozytorium i dokumentacja Slither zawierają również listę drukarek dla punktów wejścia, autoryzacji, wykresów wywołań i podsumowań umów, które ułatwiają organizację przeglądu.
Krok 4: Ręczne śledzenie połączeń zewnętrznych i ponownych wejść
Przed aktualizacją salda wyróżniono wywołanie zewnętrzne, ilustrujące pytanie porządkowe, które recenzent powinien przetestować w każdej ścieżce wypłaty.
Dla każdego wywołania zewnętrznego należy zatrzymać i prześledzić stan przed, w trakcie i po wywołaniu. Odbiorcą wywołania może być złośliwy kontrakt, token z haczykami, odbiornik wywołania zwrotnego lub inny protokół zmieniający współdzieloną zależność. Dokumentacja Solidity wyjaśnia, że interakcja z innym kontraktem może przekazać kontrolę temu kontraktowi i zaleca wzorzec Checks-Effects-Interactions: najpierw walidacja, potem aktualizacja stanu tego kontraktu i na końcu interakcja zewnętrzna.
Nie ograniczaj wyszukiwania do oczywistych transferów Ether. Sprawdź hooki w stylu ERC-777, wywołania zwrotne ERC-1155, wywołania zwrotne flash-loan, dowolne routery, wywołania Oracle i wywołania wykonywane za pośrednictwem odziedziczonych bibliotek. Przeanalizuj reentrancję międzyfunkcyjną i międzykontraktową: wywołanie zwrotne może wejść do innej funkcji, która odczytuje stan pośredni. Upewnij się, że każde wywołanie niskiego poziomu sprawdza swój wynik powodzenia i poprawnie obsługuje zwróconą wartość. Zapytaj, czy nieudany odbiorca może trwale zablokować wypłaty lub pętlę.
Zapisz konkretną sekwencję ataku dla każdego prawdopodobnego problemu. Na przykład: atakujący wpłaca, rozpoczyna wypłatę, otrzymuje wywołanie zwrotne, ponownie wprowadza drugą wypłatę i dopiero wtedy pozwala na zakończenie pierwszego wywołania. Jeśli sekwencja nie może zadziałać z powodu konkretnego niezmiennika lub zabezpieczenia, zapisz ten powód. Dzięki temu wniosek będzie weryfikowalny, a nie spekulatywny.
Krok 5: Przetestuj niezmienniki arytmetyczne i biznesowe
Lista kontrolna arytmetyki i logiki biznesowej podkreśla skrajne przypadki, które często są pomijane w standardowych testach ścieżki szczęścia.
Sprawdź znaczenie każdej jednostki i przeliczenia: wei względem etheru, dziesiętnych tokenów, punktów bazowych, akcji względem aktywów, wartości ze znakiem oraz jednostek czasu. Postępuj zgodnie z instrukcją zaokrąglania. Dzielenie, które zaokrągla na korzyść deponenta, pożyczkobiorcy, likwidatora lub odbiorcy opłaty, może spowodować utratę wartości po powtórzeniu. Sprawdź mnożenie przed dzieleniem, kwoty minimalne i maksymalne, limity opłat, ceny nieaktualne, zerową podaż, zerowe saldo oraz pierwszego deponenta lub ostatniego wypłacającego.
Solidity w wersji 0.8 i nowszych zazwyczaj wykrywa przepełnienie i niedopełnienie arytmetyczne, ale kod wewnątrz uncheckedbloku celowo zmienia to zachowanie. Sprawdzona arytmetyka może również spowodować cofnięcie protokołu lub jego bezużyteczność, jeśli limity nie zostały poprawnie zaprojektowane. Przetestuj oba scenariusze: kradzież lub nieprawidłowe rozliczenie oraz odmowę usługi spowodowaną wartością, której nigdy nie można przetworzyć.
Zapisuj niezmienniki prostym językiem, zanim przekształcisz je w testy. Przykłady to: „całkowita liczba udziałów odpowiada aktywom zgodnie z podaną regułą zaokrąglania”, „użytkownik nie może wypłacić więcej niż zarejestrowane roszczenie”, „całkowita podaż tokenów jest równa sumie sald, w przypadku których obowiązuje ten model” oraz „opłata nie może przekroczyć skonfigurowanego limitu”. Porównaj salda tokenów z rzeczywistymi saldami, ponieważ tokeny mogą być wysyłane bezpośrednio do kontraktu lub mogą zachowywać się inaczej niż zakładana implementacja ERC-20.
Krok 6: Sprawdź uprawnienia i możliwość aktualizacji
Przegląd uprawnień powinien łączyć każdą rolę z jej adresem, dozwoloną akcją, procesem transferu i ścieżką aktualizacji.
Zbuduj macierz uprawnień. Dla każdej funkcji administracyjnej określ wymaganą rolę, aktualnego posiadacza, mechanizm transferu, opóźnienie, kontrolę nad wieloma podpisami lub zarządzaniem oraz zachowanie w sytuacjach awaryjnych. Zwróć szczególną uwagę na tworzenie, wstrzymywanie, zmianę opłat, zmianę źródeł Oracle, odzyskiwanie funduszy, aktualizację kodu oraz zmianę zaufanych tokenów lub adresów routerów. Dokumentacja OpenZeppelin dotycząca kontroli dostępu odróżnia proste prawa własności od uprawnień opartych na rolach i opisuje minimalne uprawnienia jako przydatną praktykę bezpieczeństwa.
Oddziel sformułowanie „kod pozwala administratorowi to zrobić” od sformułowania „dowolny użytkownik może to zrobić”. Pierwsze może stanowić jawne ryzyko związane z zarządzaniem lub opieką; drugie to luka w zabezpieczeniach autoryzacji. Sprawdź, czy sprawdzanie ról obejmuje każdą wrażliwą ścieżkę, w tym wewnętrznych pomocników dostępnych z funkcji publicznych. Sprawdź, czy domyślny administrator może przyznać sobie lub innym dodatkowe uprawnienia oraz czy przeniesienie własności może zostać przypadkowo wysłane na nieużyteczny adres.
W przypadku serwerów proxy należy sprawdzić inicjator, autoryzację implementacji, opóźnienie aktualizacji, układ pamięci masowej oraz plan wycofania lub awaryjny. Wskazówki dotyczące kontraktów z możliwością aktualizacji w OpenZeppelin wyjaśniają, dlaczego konstruktorzy nie inicjują pamięci masowej proxy, dlaczego inicjatory muszą być chronione, dlaczego implementacja nie powinna pozostawać niezainicjowana i dlaczego zmiana kolejności lub typów pamięci masowej może uszkodzić aktualizację. Klucz administratora serwera proxy należy traktować jako część granicy bezpieczeństwa protokołu, a nie jako szczegół implementacji.
Krok 7: Przetestuj system za pomocą rozmycia, niezmienników i rozwidleń
Przechodzące kampanie stanowią przydatny dowód, natomiast ślad kontrprzykładu pokazuje dokładnie, która sekwencja wymaga zbadania.
Uruchom testy jednostkowe pod kątem oczekiwanego zachowania, a następnie dodaj testy negatywne dla nieautoryzowanych wywołań, wartości zerowych, wartości maksymalnych, wygasłych podpisów, nieaktualnych danych Oracle, nieudanych transferów i powtarzanych operacji. Zamiast testować tylko kilka ręcznie wybranych liczb, stosuj rozmyte dane wejściowe. Uwzględnij wielu aktorów i kontrakty złośliwych odbiorców tam, gdzie projekt zezwala na wywołania zwrotne.
Stosuj testy niezmienników dla właściwości, które muszą pozostać prawdziwe po wielu losowych wywołaniach. Dokumentacja testowania niezmienników w Foundry opisuje losowe sekwencje, rozmyte dane wejściowe, przebiegi, głębokość, kontrakty docelowe i nadawcy docelowi. Skonfiguruj procedury obsługi, aby wywołania były zrozumiałe; jeśli każdy rozmyty depozyt jest odwracany, ponieważ aktor testowy nie ma tokenów, przekazany niezmiennik może po prostu oznaczać, że żaden użyteczny stan się nie zmienił.
Jeśli to możliwe, użyj forka sieci docelowej, aby przetestować wdrożone adresy, bieżącą konfigurację, zachowanie tokena i routing proxy. Testy forka powinny być bezpieczne i tylko do odczytu, chyba że używasz odizolowanego forka lokalnego. Minimalizuj każdą nieudaną sekwencję i zachowaj kontrprzykład, adresy wywołujących, kontekst bloku, salda i istotne wartości pamięci masowej. Test, który przejdzie pomyślnie, jest dowodem na przetestowane ścieżki, a nie dowodem na wszystkie możliwe ścieżki.
Krok 8: Zapisz ustalenia, które można naprawić i ponownie przetestować
Przydatny raport wiąże stopień zaawansowania problemu i jego status z dowodami, konkretnym rozwiązaniem i warunkami ponownego badania.
Użyj jednego zapisu na problem. Praktyczne ustalenia powinny zawierać:
Tytuł i lokalizacja: umowa, funkcja, plik oraz odniesienie do wiersza lub kodu.
Wpływ: co może zostać skradzione, zamrożone, zawyżone, ominięte lub nieprawidłowe.
Warunek wstępny: wymagane uprawnienia, saldo, czas i konfiguracja.
Reprodukcja: krótka sekwencja transakcji, test, ślad lub dowód.
Zalecenie: konkretna zmiana kodu lub operacji, z pewnymi kompromisami.
Status: otwarty, ustalony, złagodzony, ryzyko akceptowane lub niemożliwy do odtworzenia.
Ponowny test: dokładny test lub obserwacja potwierdzająca rozwiązanie.
Powaga powinna odzwierciedlać realistyczny wpływ i podatność na wykorzystanie, a nie to, jak alarmujący jest dany wzorzec kodu. Wyjaśnij założenia. Wywołanie niskiego poziomu może być bezpieczne za pomocą silnego niezmiennika; pozornie zwyczajna zmiana parametru może być krytyczna, jeśli steruje wyrocznią lub aktualizacją. Po naprawie przejrzyj różnice, ponownie uruchom odpowiedni test, ponownie uruchom cały pakiet i sprawdź regresje. Jeśli wdrożony adres został już zaktualizowany lub zmieniony, ponownie przetestuj rzeczywistą implementację i konfigurację łańcucha.
Typowe błędy audytowe, których należy unikać
„Źródło jest zweryfikowane, więc jest bezpieczne”. Weryfikacja ustala zgodność między źródłem a kodem bajtowym; nie sprawdza poprawności projektu.
„Skaner niczego nie wykrył, więc nie ma błędów”. Narzędzia najlepiej sprawdzają się w przypadku znanych wzorców, natomiast wady ekonomiczne i błędy wynikające z umów krzyżowych często wymagają analizy przez człowieka.
„Projekt ma raport z audytu, więc bieżące wdrożenie jest uwzględnione”. Porównaj zatwierdzenia, zakres, adresy wdrożeń, poprawki i historię aktualizacji w raporcie.
„Testy rozmycia przeszły pomyślnie, więc niezmiennik jest poprawny”. Najpierw sprawdź, czy niezmiennik wyraża zamierzoną własność ekonomiczną i czy procedury obsługi osiągają sensowne stany.
„Kontrola administracyjna nie stanowi kwestii bezpieczeństwa”. Być może jest to celowe założenie o zaufaniu, ale użytkownicy powinni mieć możliwość sprawdzenia, kto może tworzyć, wstrzymywać, zmieniać parametry lub aktualizować.
Ostateczna autokontrola przed zaufaniem wynikowi
Powinieneś móc odpowiedzieć twierdząco na poniższe pytania:
Czy zapisałem dokładny łańcuch, adres, kod bajtowy, serwer proxy, implementację i ustawienia kompilacji?
Czy sporządziłem spis każdego punktu wejścia zmieniającego stan i zasobów, na które może on wpływać?
Czy skompilowałem wszystko czysto, sprawdziłem ostrzeżenia i dokonałem selekcji automatycznych ustaleń?
Czy śledziłem każde połączenie zewnętrzne, połączenie zwrotne, połączenie niskiego poziomu i ścieżkę awarii?
Czy testowałem zaokrąglanie, limity, wartości zerowe, nieaktualne dane i powtarzane działania?
Czy zmapowałem wszystkie uprzywilejowane role, klucze, opóźnienia, inicjatory i ścieżki aktualizacji?
Czy zachowałem znaczące niejasności i niezmienne kontrprzykłady?
Czy niezależny recenzent jest w stanie odtworzyć każde ustalenie i zweryfikować każdą poprawkę?
Jeśli którakolwiek odpowiedź brzmi „nie”, oznacz audyt jako niekompletny i wskaż brakujące dowody. Przejrzyste ograniczenie jest bardziej przydatne niż niejasne, „bezpieczne” wnioski. Bezpieczeństwo inteligentnych kontraktów to ciągły proces: każda aktualizacja, zmiana zależności, nowa integracja i zmiana uprawnień może utworzyć nową granicę przeglądu.