diff --git a/public/content/translations/pl/developers/tutorials/how-to-mint-an-nft/index.md b/public/content/translations/pl/developers/tutorials/how-to-mint-an-nft/index.md new file mode 100644 index 00000000000..f714254f59d --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-mint-an-nft/index.md @@ -0,0 +1,329 @@ +--- +title: "Jak wybić NFT (Część 2/3 serii samouczków NFT)" +description: "Ten samouczek opisuje, jak wybić NFT na blockchainie Ethereum, używając naszego inteligentnego kontraktu i Web3." +author: "Sumi Mudgil" +tags: [ "ERC-721", "alchemy", "solidity", "smart kontrakty" ] +skill: beginner +lang: pl +published: 2021-04-22 +--- + +[Beeple](https://www.nytimes.com/2021/03/11/arts/design/nft-auction-christies-beeple.html): 69 milionów USD +[3LAU](https://www.forbes.com/sites/abrambrown/2021/03/03/3lau-nft-nonfungible-tokens-justin-blau/?sh=5f72ef64643b): 11 milionów USD +[Grimes](https://www.theguardian.com/music/2021/mar/02/grimes-sells-digital-art-collection-non-fungible-tokens): 6 milionów USD + +Wszyscy oni wybili swoje NFT za pomocą potężnego API Alchemy. W tym samouczku nauczymy Cię, jak zrobić to samo w \<10 minut. + +„Wybicie NFT” to akt publikacji unikalnej instancji Twojego tokena ERC-721 na blockchainie. Używając naszego inteligentnego kontraktu z [części 1 tej serii samouczków NFT](/developers/tutorials/how-to-write-and-deploy-an-nft/), zaprezentujmy nasze umiejętności Web3 i wybijmy NFT. Na koniec tego samouczka będziesz w stanie wybić tyle NFT, ile tylko dusza (i portfel) zapragnie! + +Zaczynajmy! + +## Krok 1: Zainstaluj Web3 {#install-web3} + +Jeśli śledziłeś pierwszy samouczek dotyczący tworzenia inteligentnego kontraktu NFT, masz już doświadczenie w korzystaniu z Ethers.js. Web3 jest podobne do Ethers, ponieważ jest to biblioteka używana do ułatwienia tworzenia zapytań do blockchaina Ethereum. W tym samouczku będziemy używać [Alchemy Web3](https://docs.alchemyapi.io/alchemy/documentation/alchemy-web3), która jest ulepszoną biblioteką Web3, która oferuje automatyczne ponawianie prób i solidne wsparcie dla WebSocket. + +W głównym katalogu projektu uruchom: + +``` +npm install @alch/alchemy-web3 +``` + +## Krok 2: Stwórz plik `mint-nft.js` {#create-mintnftjs} + +Wewnątrz katalogu `scripts` utwórz plik `mint-nft.js` i dodaj następujące linie kodu: + +```js +require("dotenv").config() +const API_URL = process.env.API_URL +const { createAlchemyWeb3 } = require("@alch/alchemy-web3") +const web3 = createAlchemyWeb3(API_URL) +``` + +## Krok 3: Pobierz ABI swojego kontraktu {#contract-abi} + +ABI naszego kontraktu (Application Binary Interface) to interfejs do interakcji z naszym inteligentnym kontraktem. Możesz dowiedzieć się więcej o ABI kontraktów [tutaj](https://docs.alchemyapi.io/alchemy/guides/eth_getlogs#what-are-ab-is). Hardhat automatycznie generuje dla nas ABI i zapisuje je w pliku `MyNFT.json`. Aby z tego skorzystać, musimy przeanalizować zawartość, dodając następujące linie kodu do naszego pliku `mint-nft.js`: + +```js +const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json") +``` + +Jeśli chcesz zobaczyć ABI, możesz je wydrukować w swojej konsoli: + +```js +console.log(JSON.stringify(contract.abi)) +``` + +Aby uruchomić `mint-nft.js` i zobaczyć swoje ABI wydrukowane w konsoli, przejdź do terminala i uruchom: + +```js +node scripts/mint-nft.js +``` + +## Krok 4: Skonfiguruj metadane dla swojego NFT za pomocą IPFS {#config-meta} + +Jeśli pamiętasz z naszego samouczka w części 1, nasza funkcja inteligentnego kontraktu `mintNFT` przyjmuje parametr tokenURI, który powinien prowadzić do dokumentu JSON opisującego metadane NFT — co tak naprawdę ożywia NFT, pozwalając mu mieć konfigurowalne właściwości, takie jak nazwa, opis, obraz i inne atrybuty. + +> _Interplanetary File System (IPFS) to zdecentralizowany protokół i sieć peer-to-peer do przechowywania i udostępniania danych w rozproszonym systemie plików._ + +Użyjemy Pinaty, wygodnego API i zestawu narzędzi IPFS, do przechowywania naszych aktywów NFT i metadanych, aby upewnić się, że nasze NFT jest naprawdę zdecentralizowane. Jeśli nie masz konta Pinata, zarejestruj darmowe konto [tutaj](https://app.pinata.cloud) i wykonaj kroki w celu zweryfikowania swojego adresu e-mail. + +Po utworzeniu konta: + +- Przejdź na stronę "Files" i kliknij niebieski przycisk "Upload" w lewym górnym rogu strony. + +- Prześlij obraz do Pinaty — będzie to zasób obrazu dla Twojego NFT. Możesz nazwać zasób, jak tylko chcesz + +- Po przesłaniu zobaczysz informacje o pliku w tabeli na stronie "Files". Zobaczysz także kolumnę CID. Możesz skopiować CID, klikając przycisk kopiowania obok niego. Możesz zobaczyć swój plik pod adresem: `https://gateway.pinata.cloud/ipfs/`. Przykładowo, obraz, którego użyliśmy na IPFS, można znaleźć [tutaj](https://gateway.pinata.cloud/ipfs/QmZdd5KYdCFApWn7eTZJ1qgJu18urJrP9Yh1TZcZrZxxB5). + +Dla wzrokowców, powyższe kroki zostały podsumowane tutaj: + +![Jak przesłać swój obraz do Pinaty](./instructionsPinata.gif) + +Teraz będziemy chcieli przesłać jeszcze jeden dokument do Pinaty. Ale zanim to zrobimy, musimy go stworzyć! + +W katalogu głównym utwórz nowy plik o nazwie `nft-metadata.json` i dodaj następujący kod json: + +```json +{ + "attributes": [ + { + "trait_type": "Rasa", + "value": "Maltipoo" + }, + { + "trait_type": "Kolor oczu", + "value": "Mokka" + } + ], + "description": "Najbardziej uroczy i wrażliwy szczeniak na świecie.", + "image": "ipfs://QmWmvTJmJU3pozR9ZHFmQC2DNDwi2XJtf3QGyYiiagFSWb", + "name": "Ramses" +} +``` + +Możesz dowolnie zmieniać dane w pliku json. Możesz usuwać lub dodawać atrybuty w sekcji atrybutów. Co najważniejsze, upewnij się, że pole obrazu wskazuje na lokalizację Twojego obrazu IPFS — w przeciwnym razie Twoje NFT będzie zawierać zdjęcie (bardzo uroczego!) psa. + +Gdy skończysz edytować plik JSON, zapisz go i prześlij do Pinaty, postępując według tych samych kroków, co przy przesyłaniu obrazu. + +![Jak przesłać plik nft-metadata.json do Pinaty](./uploadPinata.gif) + +## Krok 5: Stwórz instancję swojego kontraktu {#instance-contract} + +Teraz, aby wejść w interakcję z naszym kontraktem, musimy stworzyć jego instancję w naszym kodzie. Aby to zrobić, będziemy potrzebować adresu naszego kontraktu, który możemy uzyskać z wdrożenia lub z [Blockscout](https://eth-sepolia.blockscout.com/), wyszukując adres, którego użyliśmy do wdrożenia kontraktu. + +![Wyświetl adres swojego kontraktu na Etherscan](./view-contract-etherscan.png) + +W powyższym przykładzie adres naszego kontraktu to 0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778. + +Następnie użyjemy metody kontraktowej Web3 [contract method](https://docs.web3js.org/api/web3-eth-contract/class/Contract), aby utworzyć nasz kontrakt przy użyciu ABI i adresu. W pliku `mint-nft.js` dodaj następujące elementy: + +```js +const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778" + +const nftContract = new web3.eth.Contract(contract.abi, contractAddress) +``` + +## Krok 6: Zaktualizuj plik `.env` {#update-env} + +Teraz, aby tworzyć i wysyłać transakcje do łańcucha Ethereum, użyjemy Twojego publicznego adresu konta Ethereum, aby uzyskać nonce konta (wyjaśnimy poniżej). + +Dodaj swój klucz publiczny do pliku `.env` — jeśli ukończyłeś część 1 samouczka, nasz plik `.env` powinien teraz wyglądać tak: + +```js +API_URL = "https://eth-sepolia.g.alchemy.com/v2/your-api-key" +PRIVATE_KEY = "your-private-account-address" +PUBLIC_KEY = "your-public-account-address" +``` + +## Krok 7: Stwórz swoją transakcję {#create-txn} + +Najpierw zdefiniujmy funkcję o nazwie `mintNFT(tokenData)` i utwórzmy naszą transakcję, wykonując następujące czynności: + +1. Pobierz swoje _PRIVATE_KEY_ i _PUBLIC_KEY_ z pliku `.env`. + +2. Następnie będziemy musieli ustalić nonce konta. Specyfikacja nonce służy do śledzenia liczby transakcji wysłanych z Twojego adresu – czego potrzebujemy do celów bezpieczeństwa i zapobiegania [atakom typu replay](https://docs.alchemyapi.io/resources/blockchain-glossary#account-nonce). Aby uzyskać liczbę transakcji wysłanych z Twojego adresu, używamy [getTransactionCount](https://docs.alchemyapi.io/documentation/alchemy-api-reference/json-rpc#eth_gettransactioncount). + +3. Na koniec skonfigurujemy naszą transakcję z następującymi informacjami: + +- `'from': PUBLIC_KEY` — Źródłem naszej transakcji jest nasz adres publiczny + +- `'to': contractAddress` — Kontrakt, z którym chcemy wejść w interakcję i do którego wysyłamy transakcję + +- `'nonce': nonce` — Nonce konta z liczbą transakcji wysłanych z naszego adresu + +- `'gas': estimatedGas` — Szacowany gaz potrzebny do sfinalizowania transakcji + +- `'data': nftContract.methods.mintNFT(PUBLIC_KEY, md).encodeABI()` — Obliczenia, które chcemy wykonać w tej transakcji — co w tym przypadku oznacza wybicie NFT + +Twój plik `mint-nft.js` powinien teraz wyglądać tak: + +```js + require('dotenv').config(); + const API_URL = process.env.API_URL; + const PUBLIC_KEY = process.env.PUBLIC_KEY; + const PRIVATE_KEY = process.env.PRIVATE_KEY; + + const { createAlchemyWeb3 } = require("@alch/alchemy-web3"); + const web3 = createAlchemyWeb3(API_URL); + + const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json"); + const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778"; + const nftContract = new web3.eth.Contract(contract.abi, contractAddress); + + async function mintNFT(tokenURI) { + const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, 'latest'); //pobierz najnowszy nonce + + //transakcja + const tx = { + 'from': PUBLIC_KEY, + 'to': contractAddress, + 'nonce': nonce, + 'gas': 500000, + 'data': nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI() + }; + }​ +``` + +## Krok 8: Podpisz transakcję {#sign-txn} + +Teraz, gdy stworzyliśmy naszą transakcję, musimy ją podpisać, aby ją wysłać. Tutaj użyjemy naszego klucza prywatnego. + +`web3.eth.sendSignedTransaction` da nam hasz transakcji, którego możemy użyć, aby upewnić się, że nasza transakcja została wydobyta i nie została odrzucona przez sieć. Zauważysz, że w sekcji podpisywania transakcji dodaliśmy sprawdzanie błędów, aby wiedzieć, czy nasza transakcja zakończyła się pomyślnie. + +```js +require("dotenv").config() +const API_URL = process.env.API_URL +const PUBLIC_KEY = process.env.PUBLIC_KEY +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const { createAlchemyWeb3 } = require("@alch/alchemy-web3") +const web3 = createAlchemyWeb3(API_URL) + +const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json") +const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778" +const nftContract = new web3.eth.Contract(contract.abi, contractAddress) + +async function mintNFT(tokenURI) { + const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest") //pobierz najnowszy nonce + + //transakcja + const tx = { + from: PUBLIC_KEY, + to: contractAddress, + nonce: nonce, + gas: 500000, + data: nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI(), + } + + const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY) + signPromise + .then((signedTx) => { + web3.eth.sendSignedTransaction( + signedTx.rawTransaction, + function (err, hash) { + if (!err) { + console.log( + "Hasz Twojej transakcji to: ", + hash, + "\nSprawdź Mempool Alchemy, aby zobaczyć status swojej transakcji!" + ) + } else { + console.log( + "Coś poszło nie tak podczas przesyłania transakcji:", + err + ) + } + } + ) + }) + .catch((err) => { + console.log(" Błąd Promise:", err) + }) +} +``` + +## Krok 9: Wywołaj `mintNFT` i uruchom node `mint-nft.js` {#call-mintnft-fn} + +Pamiętasz plik `metadata.json`, który przesłałeś do Pinaty? Pobierz jego hashcode z Pinaty i przekaż następujący parametr do funkcji `mintNFT`: `https://gateway.pinata.cloud/ipfs/` + +Oto jak uzyskać hashcode: + +![Jak uzyskać hashcode metadanych NFT na Pinacie](./metadataPinata.gif)_Jak uzyskać hashcode metadanych NFT na Pinacie_ + +> Sprawdź dwukrotnie, czy skopiowany hashcode prowadzi do Twojego **metadata.json**, ładując `https://gateway.pinata.cloud/ipfs/` w osobnym oknie. Strona powinna wyglądać podobnie do poniższego zrzutu ekranu: + +![Twoja strona powinna wyświetlać metadane json](./metadataJSON.png)_Twoja strona powinna wyświetlać metadane json_ + +W całości Twój kod powinien wyglądać mniej więcej tak: + +```js +require("dotenv").config() +const API_URL = process.env.API_URL +const PUBLIC_KEY = process.env.PUBLIC_KEY +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const { createAlchemyWeb3 } = require("@alch/alchemy-web3") +const web3 = createAlchemyWeb3(API_URL) + +const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json") +const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778" +const nftContract = new web3.eth.Contract(contract.abi, contractAddress) + +async function mintNFT(tokenURI) { + const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest") //pobierz najnowszy nonce + + //transakcja + const tx = { + from: PUBLIC_KEY, + to: contractAddress, + nonce: nonce, + gas: 500000, + data: nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI(), + } + + const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY) + signPromise + .then((signedTx) => { + web3.eth.sendSignedTransaction( + signedTx.rawTransaction, + function (err, hash) { + if (!err) { + console.log( + "Hasz Twojej transakcji to: ", + hash, + "\nSprawdź Mempool Alchemy, aby zobaczyć status swojej transakcji!" + ) + } else { + console.log( + "Coś poszło nie tak podczas przesyłania transakcji:", + err + ) + } + } + ) + }) + .catch((err) => { + console.log("Błąd Promise:", err) + }) +} + +mintNFT("ipfs://QmYueiuRNmL4MiA2GwtVMm6ZagknXnSpQnB3z2gWbz36hP") +``` + +Teraz uruchom `node scripts/mint-nft.js`, aby wdrożyć swoje NFT. Po kilku sekundach w terminalu powinna pojawić się odpowiedź podobna do tej: + + ``` + Hasz Twojej transakcji to: 0x301791fdf492001fcd9d5e5b12f3aa1bbbea9a88ed24993a8ab2cdae2d06e1e8 + + Sprawdź Mempool Alchemy, aby zobaczyć status swojej transakcji! + ``` + +Następnie odwiedź swój [mempool Alchemy](https://dashboard.alchemyapi.io/mempool), aby zobaczyć status swojej transakcji (czy jest w toku, wydobyta, czy została odrzucona przez sieć). Jeśli Twoja transakcja została odrzucona, warto również sprawdzić [Blockscout](https://eth-sepolia.blockscout.com/) i wyszukać hasz swojej transakcji. + +![Wyświetl hasz transakcji NFT na Etherscan](./view-nft-etherscan.png)_Wyświetl hasz transakcji NFT na Etherscan_ + +I to wszystko! Właśnie wdrożyłeś ORAZ wybiłeś NFT na blockchainie Ethereum + +Używając `mint-nft.js` możesz wybić tyle NFT, ile tylko dusza (i portfel) zapragnie! Pamiętaj tylko, aby przekazać nowy tokenURI opisujący metadane NFT (w przeciwnym razie skończysz, tworząc kilka identycznych z różnymi ID). + +Prawdopodobnie chciałbyś móc pochwalić się swoim NFT w swoim portfelu – więc koniecznie sprawdź [Część 3: Jak wyświetlić swoje NFT w portfelu](/developers/tutorials/how-to-view-nft-in-metamask/)! diff --git a/public/content/translations/pl/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md b/public/content/translations/pl/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md new file mode 100644 index 00000000000..d281347bff3 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md @@ -0,0 +1,108 @@ +--- +title: "Jak mockować inteligentne kontrakty Solidity na potrzeby testowania" +description: "Dlaczego podczas testowania powinieneś naśmiewać się ze swoich kontraktów" +author: Markus Waas +lang: pl +tags: + [ + "solidity", + "smart kontrakty", + "testowanie", + "tworzenie atrap" + ] +skill: intermediate +published: 2020-05-02 +source: soliditydeveloper.com +sourceUrl: https://soliditydeveloper.com/mocking-contracts +--- + +[Obiekty typu mock](https://wikipedia.org/wiki/Mock_object) to popularny wzorzec projektowy w programowaniu zorientowanym obiektowo. Pochodzi od starofrancuskiego słowa „mocquer”, oznaczającego „naśmiewać się”, które ewoluowało w „imitowanie czegoś prawdziwego”, co jest dokładnie tym, co robimy w programowaniu. Proszę, naśmiewajcie się ze swoich inteligentnych kontraktów tylko, jeśli chcecie, ale twórzcie ich atrapy (mocki), kiedy tylko możecie. To ułatwia życie. + +## Testowanie jednostkowe kontraktów z użyciem atrap (mocków) {#unit-testing-contracts-with-mocks} + +Mockowanie kontraktu w istocie oznacza stworzenie jego drugiej wersji, która zachowuje się bardzo podobnie do oryginału, ale w sposób, który deweloper może łatwo kontrolować. Często masz do czynienia ze złożonymi kontraktami, w których chcesz tylko [przetestować jednostkowo małe części kontraktu](/developers/docs/smart-contracts/testing/). Problem pojawia się, gdy testowanie tej małej części wymaga bardzo specyficznego stanu kontraktu, który jest trudny do osiągnięcia. + +Możesz za każdym razem pisać złożoną logikę konfiguracji testu, która doprowadza kontrakt do wymaganego stanu, albo napisać atrapę (mock). Mockowanie kontraktu jest łatwe dzięki dziedziczeniu. Po prostu utwórz drugi kontrakt-atrapę, który dziedziczy po oryginalnym. Teraz możesz nadpisywać funkcje do swojej atrapy (mocka). Zobaczmy to na przykładzie. + +## Przykład: Prywatny ERC20 {#example-private-erc20} + +Używamy przykładowego kontraktu ERC-20, który ma początkowy okres prywatny. Właściciel może zarządzać prywatnymi użytkownikami i tylko oni będą mogli na początku otrzymywać tokeny. Gdy upłynie określony czas, wszyscy będą mogli używać tokenów. Jeśli jesteś ciekaw, używamy hooka [`_beforeTokenTransfer`](https://docs.openzeppelin.com/contracts/5.x/extending-contracts#using-hooks) z nowych kontraktów OpenZeppelin v3. + +```solidity +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract PrivateERC20 is ERC20, Ownable { + mapping (address => bool) public isPrivateUser; + uint256 private publicAfterTime; + + constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public { + publicAfterTime = now + privateERC20timeInSec; + } + + function addUser(address user) external onlyOwner { + isPrivateUser[user] = true; + } + + function isPublic() public view returns (bool) { + return now >= publicAfterTime; + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override { + super._beforeTokenTransfer(from, to, amount); + + require(_validRecipient(to), "PrivateERC20: nieprawidłowy odbiorca"); + } + + function _validRecipient(address to) private view returns (bool) { + if (isPublic()) { + return true; + } + + return isPrivateUser[to]; + } +} +``` + +A teraz stwórzmy jego atrapę (mocka). + +```solidity +pragma solidity ^0.6.0; +import "../PrivateERC20.sol"; + +contract PrivateERC20Mock is PrivateERC20 { + bool isPublicConfig; + + constructor() public PrivateERC20(0) {} + + function setIsPublic(bool isPublic) external { + isPublicConfig = isPublic; + } + + function isPublic() public view returns (bool) { + return isPublicConfig; + } +} +``` + +Otrzymasz jeden z następujących komunikatów o błędach: + +- `PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.` +- `PrivateERC20.sol: TypeError: Trying to override non-virtual function.` `Czy zapomniałeś dodać „virtual”?` + +Ponieważ używamy nowej wersji Solidity 0.6, musimy dodać słowo kluczowe `virtual` dla funkcji, które można nadpisać, i `override` dla funkcji nadpisującej. Dodajmy je więc do obu funkcji `isPublic`. + +Teraz w swoich testach jednostkowych możesz zamiast tego użyć `PrivateERC20Mock`. Gdy chcesz przetestować zachowanie w czasie użytkowania prywatnego, użyj `setIsPublic(false)`, a do testowania w czasie użytkowania publicznego `setIsPublic(true)`. Oczywiście w naszym przykładzie moglibyśmy również użyć [funkcji pomocniczych do obsługi czasu](https://docs.openzeppelin.com/test-helpers/0.5/api#increase), aby odpowiednio zmienić czas. Ale idea mockowania powinna być już jasna i możesz sobie wyobrazić scenariusze, w których nie jest to tak proste, jak samo przesunięcie czasu. + +## Mockowanie wielu kontraktów {#mocking-many-contracts} + +Tworzenie kolejnego kontraktu dla każdej pojedynczej atrapy (mocka) może stać się kłopotliwe. Jeśli to ci przeszkadza, możesz zapoznać się z biblioteką [MockContract](https://github.com/gnosis/mock-contract). Pozwala ona na nadpisywanie i zmienianie zachowań kontraktów w locie. Działa ona jednak tylko w przypadku mockowania wywołań do innego kontraktu, więc nie zadziała w naszym przykładzie. + +## Mockowanie może być jeszcze potężniejsze {#mocking-can-be-even-more-powerful} + +Na tym nie kończą się możliwości mockowania. + +- Dodawanie funkcji: przydatne jest nie tylko nadpisywanie określonej funkcji, ale również dodawanie nowych. Dobrym przykładem dla tokenów jest posiadanie dodatkowej funkcji `mint`, aby umożliwić każdemu użytkownikowi otrzymywanie nowych tokenów za darmo. +- Użycie w sieciach testowych: wdrażając i testując swoje kontrakty w sieciach testowych razem ze swoją dappką, rozważ użycie wersji mockowanej. Unikaj nadpisywania funkcji, chyba że jest to absolutnie konieczne. W końcu chcesz przetestować prawdziwą logikę. Ale przydatne może być dodanie na przykład funkcji resetowania, która po prostu przywraca stan kontraktu do stanu początkowego, bez konieczności ponownego wdrożenia. Oczywiście nie chcesz mieć tego w kontrakcie w sieci głównej (Mainnet). diff --git a/public/content/translations/pl/developers/tutorials/how-to-use-echidna-to-test-smart-contracts/index.md b/public/content/translations/pl/developers/tutorials/how-to-use-echidna-to-test-smart-contracts/index.md new file mode 100644 index 00000000000..2fd213efa36 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-use-echidna-to-test-smart-contracts/index.md @@ -0,0 +1,705 @@ +--- +title: "Jak używać Echidny do testowania inteligentnych kontraktów" +description: "Jak używać Echidny do automatycznego testowania inteligentnych kontraktów" +author: "Trailofbits" +lang: pl +tags: + [ + "solidity", + "smart kontrakty", + "bezpieczeństwo", + "testowanie", + "fuzzing" + ] +skill: advanced +published: 2020-04-10 +source: Building secure contracts +sourceUrl: https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna +--- + +## Instalacja {#installation} + +Echidnę można zainstalować za pomocą platformy Docker lub używając wstępnie skompilowanego pliku binarnego. + +### Echidna przez platformę Docker {#echidna-through-docker} + +```bash +docker pull trailofbits/eth-security-toolbox +docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox +``` + +_Ostatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp do bieżącego katalogu. Możesz zmienić pliki z hosta i uruchomić narzędzia na plikach z dockera_ + +Wewnątrz Dockera uruchom: + +```bash +solc-select 0.5.11 +cd /home/training +``` + +### Plik binarny {#binary} + +[https://github.com/crytic/echidna/releases/tag/v1.4.0.0](https://github.com/crytic/echidna/releases/tag/v1.4.0.0) + +## Wprowadzenie do testowania opartego na właściwościach (fuzzing) {#introduction-to-property-based-fuzzing} + +Echidna to fuzzer oparty na właściwościach, co opisaliśmy w naszych poprzednich wpisach na blogu ([1](https://blog.trailofbits.com/2018/03/09/echidna-a-smart-fuzzer-for-ethereum/), [2](https://blog.trailofbits.com/2018/05/03/state-machine-testing-with-echidna/), [3](https://blog.trailofbits.com/2020/03/30/an-echidna-for-all-seasons/)). + +### Fuzzing {#fuzzing} + +[Fuzzing](https://wikipedia.org/wiki/Fuzzing) to dobrze znana technika w społeczności zajmującej się bezpieczeństwem. Polega ona na generowaniu danych wejściowych, które są mniej lub bardziej losowe, w celu znalezienia błędów w programie. Fuzzery dla tradycyjnego oprogramowania (takie jak [AFL](http://lcamtuf.coredump.cx/afl/) lub [LibFuzzer](https://llvm.org/docs/LibFuzzer.html)) są znane jako wydajne narzędzia do znajdowania błędów. + +Oprócz czysto losowego generowania danych wejściowych istnieje wiele technik i strategii generowania dobrych danych wejściowych, w tym: + +- Uzyskiwanie informacji zwrotnej z każdego wykonania i wykorzystywanie jej do kierowania generowaniem. Na przykład, jeśli nowo wygenerowane dane wejściowe prowadzą do odkrycia nowej ścieżki, sensowne może być wygenerowanie nowych danych wejściowych zbliżonych do nich. +- Generowanie danych wejściowych z poszanowaniem ograniczeń strukturalnych. Na przykład, jeśli dane wejściowe zawierają nagłówek z sumą kontrolną, sensowne będzie pozwolić fuzzerowi na wygenerowanie danych wejściowych weryfikujących sumę kontrolną. +- Używanie znanych danych wejściowych do generowania nowych danych wejściowych: jeśli masz dostęp do dużego zbioru prawidłowych danych wejściowych, fuzzer może generować nowe dane wejściowe na ich podstawie, zamiast rozpoczynać generowanie od zera. Są one zwykle nazywane _ziarnami_ (_seeds_). + +### Fuzzing oparty na właściwościach {#property-based-fuzzing} + +Echidna należy do specyficznej rodziny fuzzerów: fuzzingu opartego na właściwościach, mocno inspirowanego [QuickCheck](https://wikipedia.org/wiki/QuickCheck). W przeciwieństwie do klasycznego fuzzera, który próbuje znaleźć awarie, Echidna próbuje złamać niezmienniki zdefiniowane przez użytkownika. + +W inteligentnych kontraktach niezmienniki to funkcje Solidity, które mogą reprezentować dowolny nieprawidłowy lub nieważny stan, jaki kontrakt może osiągnąć, w tym: + +- Nieprawidłowa kontrola dostępu: atakujący stał się właścicielem kontraktu. +- Nieprawidłowa maszyna stanu: tokeny mogą być transferowane, gdy kontrakt jest wstrzymany. +- Nieprawidłowa arytmetyka: użytkownik może spowodować niedomiar swojego salda i uzyskać nieograniczoną liczbę darmowych tokenów. + +### Testowanie właściwości za pomocą Echidny {#testing-a-property-with-echidna} + +Zobaczymy, jak testować inteligentny kontrakt za pomocą Echidny. Celem jest następujący inteligentny kontrakt [`token.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/example/token.sol): + +```solidity +contract Token{ + mapping(address => uint) public balances; + function airdrop() public{ + balances[msg.sender] = 1000; + } + function consume() public{ + require(balances[msg.sender]>0); + balances[msg.sender] -= 1; + } + function backdoor() public{ + balances[msg.sender] += 1; + } +} +``` + +Przyjmiemy założenie, że ten token musi mieć następujące właściwości: + +- Każdy może mieć maksymalnie 1000 tokenów +- Token nie może być transferowany (nie jest to token ERC20) + +### Napisz właściwość {#write-a-property} + +Właściwości Echidny to funkcje Solidity. Właściwość musi: + +- Nie mieć argumentów +- Zwracać `true` w przypadku powodzenia +- Mieć nazwę zaczynającą się od `echidna` + +Echidna: + +- Automatycznie generuje dowolne transakcje w celu przetestowania właściwości. +- Zgłaszać wszelkie transakcje, które powodują, że właściwość zwraca `false` lub zgłasza błąd. +- Odrzucać efekty uboczne podczas wywoływania właściwości (tzn. jeśli właściwość zmienia zmienną stanu, jest to odrzucane po teście) + +Poniższa właściwość sprawdza, czy dzwoniący nie ma więcej niż 1000 tokenów: + +```solidity +function echidna_balance_under_1000() public view returns(bool){ + return balances[msg.sender] <= 1000; +} +``` + +Użyj dziedziczenia, aby oddzielić swój kontrakt od właściwości: + +```solidity +contract TestToken is Token{ + function echidna_balance_under_1000() public view returns(bool){ + return balances[msg.sender] <= 1000; + } + } +``` + +[`token.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/example/token.sol) implementuje właściwość i dziedziczy po tokenie. + +### Inicjowanie kontraktu {#initiate-a-contract} + +Echidna potrzebuje [konstruktora](/developers/docs/smart-contracts/anatomy/#constructor-functions) bez argumentów. Jeśli Twój kontrakt wymaga określonej inicjalizacji, musisz to zrobić w konstruktorze. + +W Echidnie istnieją pewne specyficzne adresy: + +- `0x00a329c0648769A73afAc7F9381E08FB43dBEA72`, który wywołuje konstruktor. +- `0x10000`, `0x20000` i `0x00a329C0648769a73afAC7F9381e08fb43DBEA70`, które losowo wywołują inne funkcje. + +W naszym obecnym przykładzie nie potrzebujemy żadnej szczególnej inicjalizacji, w rezultacie nasz konstruktor jest pusty. + +### Uruchom Echidnę {#run-echidna} + +Echidnę uruchamia się za pomocą: + +```bash +echidna-test contract.sol +``` + +Jeśli contract.sol zawiera wiele kontraktów, możesz określić cel: + +```bash +echidna-test contract.sol --contract MyContract +``` + +### Podsumowanie: Testowanie właściwości {#summary-testing-a-property} + +Poniżej podsumowano uruchomienie Echidny na naszym przykładzie: + +```solidity +contract TestToken is Token{ + constructor() public {} + function echidna_balance_under_1000() public view returns(bool){ + return balances[msg.sender] <= 1000; + } + } +``` + +```bash +echidna-test testtoken.sol --contract TestToken +... + +echidna_balance_under_1000: failed!💥 + Call sequence, shrinking (1205/5000): + airdrop() + backdoor() + +... +``` + +Echidna odkryła, że właściwość jest naruszana, jeśli wywołana zostanie funkcja `backdoor`. + +## Filtrowanie funkcji do wywołania podczas kampanii fuzzingu {#filtering-functions-to-call-during-a-fuzzing-campaign} + +Zobaczymy, jak filtrować funkcje, które mają być poddane fuzzingowi. +Celem jest następujący inteligentny kontrakt: + +```solidity +contract C { + bool state1 = false; + bool state2 = false; + bool state3 = false; + bool state4 = false; + + function f(uint x) public { + require(x == 12); + state1 = true; + } + + function g(uint x) public { + require(state1); + require(x == 8); + state2 = true; + } + + function h(uint x) public { + require(state2); + require(x == 42); + state3 = true; + } + + function i() public { + require(state3); + state4 = true; + } + + function reset1() public { + state1 = false; + state2 = false; + state3 = false; + return; + } + + function reset2() public { + state1 = false; + state2 = false; + state3 = false; + return; + } + + function echidna_state4() public returns (bool) { + return (!state4); + } +} +``` + +Ten mały przykład zmusza Echidnę do znalezienia określonej sekwencji transakcji w celu zmiany zmiennej stanu. +Jest to trudne dla fuzzera (zaleca się użycie narzędzia do wykonywania symbolicznego, takiego jak [Manticore](https://github.com/trailofbits/manticore)). +Możemy uruchomić Echidnę, aby to zweryfikować: + +```bash +echidna-test multi.sol +... +echidna_state4: passed! 🎉 +Seed: -3684648582249875403 +``` + +### Filtrowanie funkcji {#filtering-functions} + +Echidna ma problemy ze znalezieniem prawidłowej sekwencji do przetestowania tego kontraktu, ponieważ dwie funkcje resetowania (`reset1` i `reset2`) ustawią wszystkie zmienne stanu na `false`. +Możemy jednak użyć specjalnej funkcji Echidny, aby dodać funkcje resetowania do czarnej listy lub dodać do białej listy tylko funkcje `f`, `g`, +`h` i `i`. + +Aby dodać funkcje do czarnej listy, możemy użyć tego pliku konfiguracyjnego: + +```yaml +filterBlacklist: true +filterFunctions: ["reset1", "reset2"] +``` + +Innym podejściem do filtrowania funkcji jest utworzenie listy dozwolonych funkcji (biała lista). Aby to zrobić, możemy użyć tego pliku konfiguracyjnego: + +```yaml +filterBlacklist: false +filterFunctions: ["f", "g", "h", "i"] +``` + +- `filterBlacklist` jest domyślnie ustawiony na `true`. +- Filtrowanie odbywa się tylko po nazwie (bez parametrów). Jeśli masz `f()` i `f(uint256)`, filtr `"f"` będzie pasował do obu funkcji. + +### Uruchom Echidnę {#run-echidna-1} + +Aby uruchomić Echidnę z plikiem konfiguracyjnym `blacklist.yaml`: + +```bash +echidna-test multi.sol --config blacklist.yaml +... +echidna_state4: failed!💥 + Call sequence: + f(12) + g(8) + h(42) + i() +``` + +Echidna niemal natychmiast znajdzie sekwencję transakcji falsyfikujących właściwość. + +### Podsumowanie: Filtrowanie funkcji {#summary-filtering-functions} + +Echidna może dodawać funkcje do czarnej lub białej listy, które mają być wywoływane podczas kampanii fuzzingu, używając: + +```yaml +filterBlacklist: true +filterFunctions: ["f1", "f2", "f3"] +``` + +```bash +echidna-test contract.sol --config config.yaml +... +``` + +Echidna rozpoczyna kampanię fuzzingu, dodając `f1`, `f2` i `f3` do czarnej listy lub wywołując tylko te funkcje, w zależności od wartości logicznej `filterBlacklist`. + +## Jak testować asercję Solidity za pomocą Echidny {#how-to-test-soliditys-assert-with-echidna} + +W tym krótkim samouczku pokażemy, jak używać Echidny do testowania sprawdzania asercji w kontraktach. Załóżmy, że mamy taki kontrakt: + +```solidity +contract Incrementor { + uint private counter = 2**200; + + function inc(uint val) public returns (uint){ + uint tmp = counter; + counter += val; + // tmp <= counter + return (counter - tmp); + } +} +``` + +### Napisz asercję {#write-an-assertion} + +Chcemy się upewnić, że `tmp` jest mniejsze lub równe `counter` po zwróceniu ich różnicy. Moglibyśmy napisać właściwość Echidny, ale musielibyśmy gdzieś przechowywać wartość `tmp`. Zamiast tego moglibyśmy użyć takiej asercji: + +```solidity +contract Incrementor { + uint private counter = 2**200; + + function inc(uint val) public returns (uint){ + uint tmp = counter; + counter += val; + assert (tmp <= counter); + return (counter - tmp); + } +} +``` + +### Uruchom Echidnę {#run-echidna-2} + +Aby włączyć testowanie niepowodzenia asercji, utwórz [plik konfiguracyjny Echidna](https://github.com/crytic/echidna/wiki/Config) `config.yaml`: + +```yaml +checkAsserts: true +``` + +Gdy uruchomimy ten kontrakt w Echidnie, uzyskamy oczekiwane wyniki: + +```bash +echidna-test assert.sol --config config.yaml +Analyzing contract: assert.sol:Incrementor +assertion in inc: failed!💥 + Call sequence, shrinking (2596/5000): + inc(21711016731996786641919559689128982722488122124807605757398297001483711807488) + inc(7237005577332262213973186563042994240829374041602535252466099000494570602496) + inc(86844066927987146567678238756515930889952488499230423029593188005934847229952) + +Seed: 1806480648350826486 +``` + +Jak widać, Echidna zgłasza błąd asercji w funkcji `inc`. Możliwe jest dodanie więcej niż jednej asercji na funkcję, ale Echidna nie jest w stanie stwierdzić, która asercja zawiodła. + +### Kiedy i jak używać asercji {#when-and-how-use-assertions} + +Asercje mogą być używane jako alternatywa dla jawnych właściwości, zwłaszcza jeśli warunki do sprawdzenia są bezpośrednio związane z prawidłowym użyciem jakiejś operacji `f`. Dodanie asercji po jakimś kodzie wymusi, że sprawdzenie nastąpi natychmiast po jego wykonaniu: + +```solidity +function f(..) public { + // some complex code + ... + assert (condition); + ... +} + +``` + +W przeciwieństwie do tego, użycie jawnej właściwości echidna będzie losowo wykonywać transakcje i nie ma łatwego sposobu, aby wymusić, kiedy dokładnie zostanie ona sprawdzona. Nadal można zastosować to obejście: + +```solidity +function echidna_assert_after_f() public returns (bool) { + f(..); + return(condition); +} +``` + +Istnieją jednak pewne problemy: + +- Kończy się niepowodzeniem, jeśli `f` jest zadeklarowane jako `internal` lub `external`. +- Nie jest jasne, jakich argumentów należy użyć do wywołania `f`. +- Jeśli `f` zostanie wycofane (revert), właściwość zakończy się niepowodzeniem. + +Ogólnie rzecz biorąc, zalecamy stosowanie się do [zaleceń Johna Regehra](https://blog.regehr.org/archives/1091) dotyczących używania asercji: + +- Nie wymuszaj żadnych efektów ubocznych podczas sprawdzania asercji. Na przykład: `assert(ChangeStateAndReturn() == 1)` +- Nie należy sprawdzać oczywistych stwierdzeń. Na przykład `assert(var >= 0)`, gdzie `var` jest zadeklarowane jako `uint`. + +Na koniec, proszę, **nie używaj** `require` zamiast `assert`, ponieważ Echidna nie będzie w stanie tego wykryć (ale kontrakt i tak zostanie wycofany). + +### Podsumowanie: Sprawdzanie asercji {#summary-assertion-checking} + +Poniżej podsumowano uruchomienie Echidny na naszym przykładzie: + +```solidity +contract Incrementor { + uint private counter = 2**200; + + function inc(uint val) public returns (uint){ + uint tmp = counter; + counter += val; + assert (tmp <= counter); + return (counter - tmp); + } +} +``` + +```bash +echidna-test assert.sol --config config.yaml +Analyzing contract: assert.sol:Incrementor +assertion in inc: failed!💥 + Call sequence, shrinking (2596/5000): + inc(21711016731996786641919559689128982722488122124807605757398297001483711807488) + inc(7237005577332262213973186563042994240829374041602535252466099000494570602496) + inc(86844066927987146567678238756515930889952488499230423029593188005934847229952) + +Seed: 1806480648350826486 +``` + +Echidna odkryła, że asercja w `inc` może zakończyć się niepowodzeniem, jeśli ta funkcja zostanie wywołana wielokrotnie z dużymi argumentami. + +## Zbieranie i modyfikowanie korpusu Echidna {#collecting-and-modifying-an-echidna-corpus} + +Zobaczymy, jak zbierać i wykorzystywać korpus transakcji za pomocą Echidny. Celem jest następujący inteligentny kontrakt [`magic.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/example/magic.sol): + +```solidity +contract C { + bool value_found = false; + function magic(uint magic_1, uint magic_2, uint magic_3, uint magic_4) public { + require(magic_1 == 42); + require(magic_2 == 129); + require(magic_3 == magic_4+333); + value_found = true; + return; + } + + function echidna_magic_values() public returns (bool) { + return !value_found; + } + +} +``` + +Ten mały przykład zmusza Echidnę do znalezienia określonych wartości w celu zmiany zmiennej stanu. Jest to trudne dla fuzzera +(zaleca się użycie narzędzia do wykonywania symbolicznego, takiego jak [Manticore](https://github.com/trailofbits/manticore)). +Możemy uruchomić Echidnę, aby to zweryfikować: + +```bash +echidna-test magic.sol +... + +echidna_magic_values: passed! 🎉 + +Seed: 2221503356319272685 +``` + +Możemy jednak nadal używać Echidny do zbierania korpusu podczas prowadzenia tej kampanii fuzzingu. + +### Zbieranie korpusu {#collecting-a-corpus} + +Aby włączyć zbieranie korpusu, utwórz katalog korpusu: + +```bash +mkdir corpus-magic +``` + +Oraz [plik konfiguracyjny Echidna](https://github.com/crytic/echidna/wiki/Config) `config.yaml`: + +```yaml +coverage: true +corpusDir: "corpus-magic" +``` + +Teraz możemy uruchomić nasze narzędzie i sprawdzić zebrany korpus: + +```bash +echidna-test magic.sol --config config.yaml +``` + +Echidna nadal nie może znaleźć prawidłowych magicznych wartości, ale możemy spojrzeć na zebrany korpus. +Na przykład, jeden z tych plików to: + +```json +[ + { + "_gas'": "0xffffffff", + "_delay": ["0x13647", "0xccf6"], + "_src": "00a329c0648769a73afac7f9381e08fb43dbea70", + "_dst": "00a329c0648769a73afac7f9381e08fb43dbea72", + "_value": "0x0", + "_call": { + "tag": "SolCall", + "contents": [ + "magic", + [ + { + "contents": [ + 256, + "93723985220345906694500679277863898678726808528711107336895287282192244575836" + ], + "tag": "AbiUInt" + }, + { + "contents": [256, "334"], + "tag": "AbiUInt" + }, + { + "contents": [ + 256, + "68093943901352437066264791224433559271778087297543421781073458233697135179558" + ], + "tag": "AbiUInt" + }, + { + "tag": "AbiUInt", + "contents": [256, "332"] + } + ] + ] + }, + "_gasprice'": "0xa904461f1" + } +] +``` + +Oczywiście, te dane wejściowe nie spowodują niepowodzenia naszej właściwości. Jednak w następnym kroku zobaczymy, jak to zmodyfikować. + +### Zasilanie korpusu {#seeding-a-corpus} + +Echidna potrzebuje pomocy, aby poradzić sobie z funkcją `magic`. Skopiujemy i zmodyfikujemy dane wejściowe, aby użyć odpowiednich +parametrów: + +```bash +cp corpus/2712688662897926208.txt corpus/new.txt +``` + +Zmodyfikujemy plik `new.txt`, aby wywołać `magic(42,129,333,0)`. Teraz możemy ponownie uruchomić Echidnę: + +```bash +echidna-test magic.sol --config config.yaml +... +echidna_magic_values: failed!💥 + Call sequence: + magic(42,129,333,0) + + +Unique instructions: 142 +Unique codehashes: 1 +Seed: -7293830866560616537 +``` + +Tym razem natychmiast wykryła, że właściwość jest naruszona. + +## Znajdowanie transakcji o wysokim zużyciu gazu {#finding-transactions-with-high-gas-consumption} + +Zobaczymy, jak za pomocą Echidny znaleźć transakcje o wysokim zużyciu gazu. Celem jest następujący inteligentny kontrakt: + +```solidity +contract C { + uint state; + + function expensive(uint8 times) internal { + for(uint8 i=0; i < times; i++) + state = state + i; + } + + function f(uint x, uint y, uint8 times) public { + if (x == 42 && y == 123) + expensive(times); + else + state = 0; + } + + function echidna_test() public returns (bool) { + return true; + } + +} +``` + +Tutaj `expensive` może mieć duże zużycie gazu. + +Obecnie Echidna zawsze potrzebuje właściwości do testowania: tutaj `echidna_test` zawsze zwraca `true`. +Możemy uruchomić Echidnę, aby to zweryfikować: + +``` +echidna-test gas.sol +... +echidna_test: passed! 🎉 + +Seed: 2320549945714142710 +``` + +### Pomiar zużycia gazu {#measuring-gas-consumption} + +Aby włączyć pomiar zużycia gazu za pomocą Echidny, utwórz plik konfiguracyjny `config.yaml`: + +```yaml +estimateGas: true +``` + +W tym przykładzie zmniejszymy również rozmiar sekwencji transakcji, aby wyniki były łatwiejsze do zrozumienia: + +```yaml +seqLen: 2 +estimateGas: true +``` + +### Uruchom Echidnę {#run-echidna-3} + +Po utworzeniu pliku konfiguracyjnego możemy uruchomić Echidnę w następujący sposób: + +```bash +echidna-test gas.sol --config config.yaml +... +echidna_test: passed! 🎉 + +f used a maximum of 1333608 gas + Call sequence: + f(42,123,249) Gas price: 0x10d5733f0a Time delay: 0x495e5 Block delay: 0x88b2 + +Unique instructions: 157 +Unique codehashes: 1 +Seed: -325611019680165325 +``` + +- Pokazany gaz jest szacunkiem dostarczonym przez [HEVM](https://github.com/dapphub/dapptools/tree/master/src/hevm#hevm-). + +### Odsiewanie wywołań redukujących gaz {#filtering-out-gas-reducing-calls} + +Powyższy samouczek na temat **filtrowania funkcji do wywołania podczas kampanii fuzzingu** pokazuje, jak usunąć niektóre funkcje z testowania. +Może to mieć kluczowe znaczenie dla uzyskania dokładnego oszacowania gazu. +Rozważmy następujący przykład: + +```solidity +contract C { + address [] addrs; + function push(address a) public { + addrs.push(a); + } + function pop() public { + addrs.pop(); + } + function clear() public{ + addrs.length = 0; + } + function check() public{ + for(uint256 i = 0; i < addrs.length; i++) + for(uint256 j = i+1; j < addrs.length; j++) + if (addrs[i] == addrs[j]) + addrs[j] = address(0x0); + } + function echidna_test() public returns (bool) { + return true; + } +} +``` + +Jeśli Echidna może wywołać wszystkie funkcje, nie znajdzie łatwo transakcji o wysokim koszcie gazu: + +``` +echidna-test pushpop.sol --config config.yaml +... +pop used a maximum of 10746 gas +... +check used a maximum of 23730 gas +... +clear used a maximum of 35916 gas +... +push used a maximum of 40839 gas +``` + +Dzieje się tak, ponieważ koszt zależy od rozmiaru `addrs`, a losowe wywołania mają tendencję do pozostawiania tablicy prawie pustej. +Jednak umieszczenie na czarnej liście `pop` i `clear` daje nam znacznie lepsze wyniki: + +```yaml +filterBlacklist: true +filterFunctions: ["pop", "clear"] +``` + +``` +echidna-test pushpop.sol --config config.yaml +... +push used a maximum of 40839 gas +... +check used a maximum of 1484472 gas +``` + +### Podsumowanie: Znajdowanie transakcji o wysokim zużyciu gazu {#summary-finding-transactions-with-high-gas-consumption} + +Echidna może znaleźć transakcje o wysokim zużyciu gazu, używając opcji konfiguracji `estimateGas`: + +```yaml +estimateGas: true +``` + +```bash +echidna-test contract.sol --config config.yaml +... +``` + +Po zakończeniu kampanii fuzzingu Echidna zgłosi sekwencję z maksymalnym zużyciem gazu dla każdej funkcji. diff --git a/public/content/translations/pl/developers/tutorials/how-to-use-manticore-to-find-smart-contract-bugs/index.md b/public/content/translations/pl/developers/tutorials/how-to-use-manticore-to-find-smart-contract-bugs/index.md index 2ab68c87c69..6ade03e494c 100644 --- a/public/content/translations/pl/developers/tutorials/how-to-use-manticore-to-find-smart-contract-bugs/index.md +++ b/public/content/translations/pl/developers/tutorials/how-to-use-manticore-to-find-smart-contract-bugs/index.md @@ -1,17 +1,19 @@ --- -title: Jak korzystać z Manticore, aby znaleźć błędy w inteligentnych kontraktach -description: Jak używać Manticore do automatycznego wyszukiwania błędów w inteligentnych kontraktach +title: "Jak korzystać z Manticore, aby znaleźć błędy w inteligentnych kontraktach" +description: "Jak używać Manticore do automatycznego wyszukiwania błędów w inteligentnych kontraktach" author: Trailofbits lang: pl tags: - - "solidity" - - "inteligentne kontrakty" - - "ochrona" - - "testing" - - "weryfikacja formalna" + [ + "solidity", + "smart kontrakty", + "bezpieczeństwo", + "testowanie", + "weryfikacja formalna" + ] skill: advanced published: 2020-01-13 -source: Tworzenie bezpiecznych kontraktów +source: Building secure contracts sourceUrl: https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore --- @@ -19,14 +21,13 @@ Celem tego samouczka jest pokazanie, jak używać Manticore do automatycznego wy ## Instalacja {#installation} -Manticore wymaga >= Pythona 3.6. Można go zainstalować za pomocą pip lub dockera. +Manticore wymaga Pythona >= 3.6. Można go zainstalować za pomocą pip lub dockera. -### Manticore przez dockera {#manticore-through-docker} +### Manticore przez docker {#manticore-through-docker} ```bash docker pull trailofbits/eth-security-toolbox docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox - ``` _Ostatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp do bieżącego katalogu. Możesz zmienić pliki z hosta i uruchomić narzędzia na plikach z dockera_ @@ -34,7 +35,8 @@ _Ostatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp Wewnątrz dockera uruchom: ```bash -solc-select 0.5.11 cd /home/trufflecon/ +solc-select 0.5.11 +cd /home/trufflecon/ ``` ### Manticore przez pip {#manticore-through-pip} @@ -43,9 +45,9 @@ solc-select 0.5.11 cd /home/trufflecon/ pip3 install --user manticore ``` -zaleca się solc 0.5.11. +Zalecany jest solc w wersji 0.5.11. -### Uruchom skrypt {#running-a-script} +### Uruchamianie skryptu {#running-a-script} Aby uruchomić skrypt Pythona za pomocą Pythona 3: @@ -53,16 +55,16 @@ Aby uruchomić skrypt Pythona za pomocą Pythona 3: python3 script.py ``` -## Wprowadzenie do dynamicznego wykonania symbolicznego {#introduction-to-dynamic-symbolic-execution} +## Wprowadzenie do dynamicznej egzekucji symbolicznej {#introduction-to-dynamic-symbolic-execution} -### Dynamiczne wykonanie symboliczne w skrócie {#dynamic-symbolic-execution-in-a-nutshell} +### Dynamiczna egzekucja symboliczna w pigułce {#dynamic-symbolic-execution-in-a-nutshell} -Dynamiczne wykonanie symboliczne (DSE) to technika analizy programu, która bada przestrzeń stanów z wysokim stopniem świadomości semantycznej. Ta technika opiera się na odkryciu „ścieżek programu”, przedstawianych jako wzory matematyczne o nazwie `path predicates`. W ujęciu koncepcyjnym technika ta działa na ścieżce dwueatpowo: +Dynamiczna egzekucja symboliczna (DSE) to technika analizy programu, która bada przestrzeń stanów z wysokim stopniem świadomości semantycznej. Technika ta opiera się na odkrywaniu „ścieżek programu” reprezentowanych jako formuły matematyczne zwane `path predicates`. Koncepcyjnie, technika ta operuje na predykatach ścieżek w dwóch krokach: 1. Są one konstruowane przy użyciu ograniczeń na wejściu programu. 2. Są one używane do generowania danych wejściowych programu, które spowodują wykonanie powiązanych ścieżek. -Takie podejście nie daje fałszywych alarmów w tym sensie, że wszystkie zidentyfikowane stany programu mogą zostać wyzwolone podczas konkretnego wykonania. Na przykład, jeżeli w wyniku analizy stwierdza się przepełnienie liczby całkowitej, gwarantuje się, że jest ono odtwarzalne. +Podejście to nie generuje fałszywych alarmów, w tym sensie, że wszystkie zidentyfikowane stany programu mogą zostać wywołane podczas konkretnej egzekucji. Na przykład, jeśli analiza znajdzie przepełnienie liczby całkowitej, jest ono gwarantowanie odtwarzalne. ### Przykład predykatu ścieżki {#path-predicate-example} @@ -72,43 +74,43 @@ Aby zrozumieć, jak działa DSE, rozważmy następujący przykład: function f(uint a){ if (a == 65) { - // A bug is present + // Występuje błąd } } ``` -Jako że `f()` zawiera dwie ścieżki, DSE będzie konstruować dwa różne predykaty ścieżki: +Ponieważ `f()` zawiera dwie ścieżki, DSE skonstruuje dwa różne predykaty ścieżek: - Ścieżka 1: `a == 65` -- Ścieżka 2: `Not (== 65)` +- Ścieżka 2: `Not (a == 65)` -Każdy predykat ścieżki jest wzorem matematycznym, który można przypisać tak zwanemu [SMT solver](https://wikipedia.org/wiki/Satisfiability_modulo_theories), który spróbuje rozwiązać to równanie. W przypadku `Path 1` solver powie, że ścieżka może zostać zbadana za pomocą `a = 65`. Dla `Path 2` solver może dać `a` dowolną wartość inną niż 65, na przykład `a = 0`. +Każdy predykat ścieżki jest formułą matematyczną, którą można przekazać do tak zwanego [solvera SMT](https://wikipedia.org/wiki/Satisfiability_modulo_theories), który spróbuje rozwiązać równanie. Dla `Ścieżki 1` solver stwierdzi, że ścieżka może być zbadana przy `a = 65`. Dla `Ścieżki 2` solver może podać dla `a` dowolną wartość inną niż 65, na przykład `a = 0`. ### Weryfikacja właściwości {#verifying-properties} -Manticore pozwala na pełną kontrolę nad każdym wykonaniem każdej ścieżki. W rezultacie pozwala dodawać dowolne ograniczenia do prawie wszystkiego. Kontrola ta pozwala na tworzenie właściwości na kontrakcie. +Manticore pozwala na pełną kontrolę nad wykonaniem każdej ścieżki. W rezultacie pozwala na dodawanie dowolnych ograniczeń do prawie wszystkiego. Taka kontrola pozwala na tworzenie właściwości w kontrakcie. -Rozważ następujący przykład: +Rozważmy następujący przykład: ```solidity function unsafe_add(uint a, uint b) returns(uint c){ - c = a + b; // no overflow protection + c = a + b; // brak zabezpieczenia przed przepełnieniem return c; } ``` -Tutaj jest tylko jedna ścieżka do zbadania w funkcji: +W tej funkcji istnieje tylko jedna ścieżka do zbadania: - Ścieżka 1: `c = a + b` -Używając Manticore, możesz sprawdzić przepełnienie i dodać ograniczenia do predykatu ścieżki: +Używając Manticore, można sprawdzić przepełnienie i dodać ograniczenia do predykatu ścieżki: - `c = a + b AND (c < a OR c < b)` -Jeśli można znaleźć ocenę `a` i `b`, dla której powyższy predykat ścieżki jest wykonalny, oznacza to, że znaleziono przepełnienie. Na przykład solver może wygenerować wejście `a = 10 , b = MAXUINT256`. +Jeśli możliwe jest znalezienie takich wartości `a` i `b`, dla których powyższy predykat ścieżki jest spełnialny, oznacza to, że znaleziono przepełnienie. Na przykład solver może wygenerować dane wejściowe `a = 10, b = MAXUINT256`. -Jeśli rozważasz wersję stałą: +Rozważmy poprawioną wersję: ```solidity function safe_add(uint a, uint b) returns(uint c){ @@ -119,17 +121,17 @@ function safe_add(uint a, uint b) returns(uint c){ } ``` -Powiązany wzór z kontrolą przepełnienia to: +Powiązana formuła ze sprawdzaniem przepełnienia wyglądałaby następująco: - `c = a + b AND (c >= a) AND (c=>b) AND (c < a OR c < b)` -Ten wzór nie może być rozwiązany; innymi słowy jest to **dowód**, że w `safe_add` `c` zawsze będzie wzrastać. +Ta formuła nie może zostać rozwiązana; innymi słowy jest to **dowód** na to, że w `safe_add`, `c` zawsze będzie rosło. -DSE jest więc potężnym narzędziem, które może weryfikować dowolne ograniczenia Twojego kodu. +Dlatego DSE jest potężnym narzędziem, które może weryfikować dowolne ograniczenia w Twoim kodzie. -## Uruchamianie pod Manticore {#running-under-manticore} +## Uruchamianie w Manticore {#running-under-manticore} -Zobaczymy, jak zbadać inteligentny kontrakt z API Manticore. Celem jest następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): +W tej sekcji pokazano, jak eksplorować inteligentny kontrakt przy użyciu API Manticore. Celem jest następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): ```solidity pragma solidity >=0.4.24 <0.6.0; @@ -143,15 +145,15 @@ contract Simple { } ``` -### Uruchom samodzielną eksplorację {#run-a-standalone-exploration} +### Uruchomienie samodzielnej eksploracji {#run-a-standalone-exploration} -Możesz uruchomić Manticore bezpośrednio na inteligentnym kontrakcie za pomocą następującego polecenia (`projekt` może być plikiem Solidity lub katalogiem projektu): +Możesz uruchomić Manticore bezpośrednio na inteligentnym kontrakcie za pomocą następującego polecenia (`project` może być plikiem Solidity lub katalogiem projektu): ```bash $ manticore project ``` -Otrzymasz wyniki takich przypadków testowych (kolejność może się zmienić): +Otrzymasz dane wyjściowe przypadków testowych, takie jak te (kolejność może się zmienić): ``` ... @@ -166,39 +168,39 @@ Otrzymasz wyniki takich przypadków testowych (kolejność może się zmienić): ... ``` -Bez dodatkowych informacji Manticore będzie badać kontrakt za pomocą nowej transakcji symbolicznej, dopóki nie zbada nowych ścieżek w kontrakcie. Manticore nie uruchamia nowych transakcji po niepowodzeniu (np. po wycofaniu). +Bez dodatkowych informacji Manticore będzie badać kontrakt za pomocą nowych transakcji symbolicznych, dopóki nie zbada nowych ścieżek w kontrakcie. Manticore nie uruchamia nowych transakcji po nieudanej transakcji (np. po operacji `revert`). -Manticore umieści te informacje w katalogu `mcore_*`. W tym katalogu znajdziesz między innymi: +Manticore zapisze informacje w katalogu `mcore_*`. W tym katalogu znajdziesz między innymi: -- `global.summary`: ostrzeżenia o zakresie i kompilatorze -- `test_XXXXX.summary`: zakres, ostatnia instrukcja, salda konta na przypadek testowy -- `test_XXXXX.tx`: szczegółowa lista transakcji na przypadek testowy +- `global.summary`: pokrycie kodu i ostrzeżenia kompilatora +- `test_XXXXX.summary`: pokrycie kodu, ostatnia instrukcja, salda kont dla każdego przypadku testowego +- `test_XXXXX.tx`: szczegółowa lista transakcji dla każdego przypadku testowego -W tym miejscu Manticore znajduje 7 przypadków testowych, które odpowiadają (nazwa pliku może się zmienić): +Manticore znajduje tutaj 7 przypadków testowych, które odpowiadają (kolejność nazw plików może się zmienić): -| | Transakcja 0 | Transakcja 1 | Transakcja 2 | Wynik | -| :------------------: | :------------------: | :---------------: | ----------------- | :----: | -| **test_00000000.tx** | Tworzenie kontraktu | f(!=65) | f(!=65) | STOP | -| **test_00000001.tx** | Tworzenie kontraktu | funkcja zastępcza | | REVERT | -| **test_00000002.tx** | Tworzenie kontraktu | | | RETURN | -| **test_00000003.tx** | Tworzenie kontraktu | f(65) | | REVERT | -| **test_00000004.tx** | Tworzenie kontraktu | f(!=65) | | STOP | -| **test_00000005.tx** | Tworzenie kontraktu | f(!=65) | f(65) | REVERT | -| **test_00000006.tx** | Tworzenie kontraktów | f(!=65) | funkcja zastępcza | REVERT | +| | Transakcja 0 | Transakcja 1 | Transakcja 2 | Wynik | +| :-------------------------------------------------------: | :------------------: | :------------------------: | -------------------------- | :----: | +| **test_00000000.tx** | Utworzenie kontraktu | f(!=65) | f(!=65) | STOP | +| **test_00000001.tx** | Utworzenie kontraktu | funkcja zastępcza | | REVERT | +| **test_00000002.tx** | Utworzenie kontraktu | | | RETURN | +| **test_00000003.tx** | Utworzenie kontraktu | f(65) | | REVERT | +| **test_00000004.tx** | Utworzenie kontraktu | f(!=65) | | STOP | +| **test_00000005.tx** | Utworzenie kontraktu | f(!=65) | f(65) | REVERT | +| **test_00000006.tx** | Utworzenie kontraktu | f(!=65) | funkcja zastępcza | REVERT | -_Podsumowanie eksploracji f(!=65) oznacza f o dowolnej wartości innej niż 65._ +_Podsumowanie eksploracji: f(!=65) oznacza wywołanie f z dowolną wartością inną niż 65._ -Jak możesz zauważyć, Manticore generuje unikalny przypadek testowy dla każdej udanej lub odwróconej transakcji. +Jak można zauważyć, Manticore generuje unikalny przypadek testowy dla każdej pomyślnej lub anulowanej (`reverted`) transakcji. -Użyj flagi `--quick-mode`, jeśli chcesz szybko eksplorować kod (wyłącza wykrywanie błędów, obliczanie gazu, ...) +Użyj flagi `--quick-mode`, jeśli chcesz szybkiej eksploracji kodu (wyłącza ona wykrywacze błędów, obliczanie gazu, ...) -### Obsługa inteligentnego kontraktu poprzez API {#manipulate-a-smart-contract-through-the-api} +### Manipulowanie inteligentnym kontraktem poprzez API {#manipulate-a-smart-contract-through-the-api} -W tej sekcji opisano szczegóły, jak obsługiwać inteligentny kontrakt za pośrednictwem API Pythona Manticore. Możesz utworzyć nowy plik z rozszerzeniem python `*.py` i napisać potrzebny kod, dodając polecenia API (podstawy, które zostaną opisane poniżej) do tego pliku, a następnie uruchom go poleceniem ` $ python3 *.py`. Możesz również wykonać poniższe polecenia bezpośrednio w konsoli Pythona, aby uruchomić konsolę, użyj polecenia `$ python3`. +Ta sekcja opisuje szczegółowo, jak manipulować inteligentnym kontraktem za pośrednictwem API Manticore dla Pythona. Możesz utworzyć nowy plik z rozszerzeniem Pythona `*.py` i napisać niezbędny kod, dodając polecenia API (których podstawy zostaną opisane poniżej) do tego pliku, a następnie uruchomić go za pomocą polecenia `$ python3 *.py`. Możesz również wykonać poniższe polecenia bezpośrednio w konsoli Pythona. Aby uruchomić konsolę, użyj polecenia `$ python3`. ### Tworzenie kont {#creating-accounts} -Pierwszą rzeczą, którą powinieneś zrobić, jest uruchomienie nowego blockchaina za pomocą następujących poleceń: +Pierwszą rzeczą, którą należy zrobić, jest zainicjowanie nowego blockchaina za pomocą następujących poleceń: ```python from manticore.ethereum import ManticoreEVM @@ -206,7 +208,7 @@ from manticore.ethereum import ManticoreEVM m = ManticoreEVM() ``` -Konto bez kontraktu jest tworzone przy użyciu [m.create_account](https://manticore.readthedocs.io/en/latest/evm.html?highlight=create_account#manticore.ethereum.ManticoreEVM.create_account): +Konto niebędące kontraktem jest tworzone za pomocą [m.create_account](https://manticore.readthedocs.io/en/latest/evm.html?highlight=create_account#manticore.ethereum.ManticoreEVM.create_account): ```python user_account = m.create_account(balance=1000) @@ -225,24 +227,24 @@ contract Simple { } } ''' -# Initiate the contract +# Zainicjuj kontrakt contract_account = m.solidity_create_contract(source_code, owner=user_account) ``` #### Podsumowanie {#summary} -- Konta użytkowników i konitraktów można tworzyć za pomocą [m.create_account](https://manticore.readthedocs.io/en/latest/evm.html?highlight=create_account#manticore.ethereum.ManticoreEVM.create_account) i [m.solidity_create_contract](https://manticore.readthedocs.io/en/latest/evm.html?highlight=solidity_create#manticore.ethereum.ManticoreEVM.create_contract). +- Można tworzyć konta użytkowników i konta kontraktów za pomocą [m.create_account](https://manticore.readthedocs.io/en/latest/evm.html?highlight=create_account#manticore.ethereum.ManticoreEVM.create_account) i [m.solidity_create_contract](https://manticore.readthedocs.io/en/latest/evm.html?highlight=solidity_create#manticore.ethereum.ManticoreEVM.create_contract). ### Wykonywanie transakcji {#executing-transactions} -Manticore obsługuje dwa rodzaje transakcji: +Manticore obsługuje dwa typy transakcji: -- Transakcja surowa: wszystkie funkcje są analizowane -- Nazwana transakcja: analizowana jest tylko jedna funkcja +- Surowa transakcja: wszystkie funkcje są badane +- Nazwana transakcja: tylko jedna funkcja jest badana -#### Transakcja surowa {#raw-transaction} +#### Surowa transakcja {#raw-transaction} -Surowa transakcja jest wykonywana przy użyciu [m.transaction](https://manticore.readthedocs.io/en/latest/evm.html?highlight=transaction#manticore.ethereum.ManticoreEVM.transaction): +Surowa transakcja jest wykonywana za pomocą [m.transaction](https://manticore.readthedocs.io/en/latest/evm.html?highlight=transaction#manticore.ethereum.ManticoreEVM.transaction): ```python m.transaction(caller=user_account, @@ -267,26 +269,27 @@ m.transaction(caller=user_account, value=symbolic_value) ``` -Jeżeli dane są symboliczne, Manticore zbada wszystkie funkcje kontraktu podczas realizacji transakcji. Pomocne będzie zapoznanie się z wyjaśnieniem funkcji fallback w artykule wyjaśniającym, jak działa wybór funkcji: [Hands on the Ethernaut CTF](https://blog.trailofbits.com/2017/11/06/hands-on-the-ethernaut-ctf/). +Jeśli dane są symboliczne, Manticore zbada wszystkie funkcje kontraktu podczas wykonywania transakcji. Pomocne będzie zapoznanie się z wyjaśnieniem funkcji fallback w artykule [Hands on the Ethernaut CTF](https://blog.trailofbits.com/2017/11/06/hands-on-the-ethernaut-ctf/), aby zrozumieć, jak działa wybór funkcji. #### Nazwana transakcja {#named-transaction} -Funkcje mogą być wykonywane za pośrednictwem ich nazwy. Aby wykonać `f(uint var)` z wartością symboliczną, z user_account i 0 etherów, użyj: +Funkcje mogą być wykonywane za pomocą ich nazwy. +Aby wykonać `f(uint var)` z wartością symboliczną, z `user_account` i 0 etherów, użyj: ```python symbolic_var = m.make_symbolic_value() contract_account.f(symbolic_var, caller=user_account, value=0) ``` -Jeśli wartość `value` transakcji nie jest określona, jest to domyślnie 0. +Jeśli `value` transakcji nie jest określone, domyślnie wynosi 0. #### Podsumowanie {#summary-1} - Argumenty transakcji mogą być konkretne lub symboliczne -- Surowa transakcja analizuje wszystkie funkcje -- Funkcja może być wywołana przez jej nazwę +- Surowa transakcja zbada wszystkie funkcje +- Funkcje mogą być wywoływane po nazwie -### Obszar roboczy {#workspace} +### Przestrzeń robocza {#workspace} `m.workspace` to katalog używany jako katalog wyjściowy dla wszystkich wygenerowanych plików: @@ -294,11 +297,11 @@ Jeśli wartość `value` transakcji nie jest określona, jest to domyślnie 0. print("Results are in {}".format(m.workspace)) ``` -### Kończenie eksploracji {#terminate-the-exploration} +### Zakończenie eksploracji {#terminate-the-exploration} -Aby zatrzymać eksplorację, użyj [m.finalize()](https://manticore.readthedocs.io/en/latest/evm.html?highlight=finalize#manticore.ethereum.ManticoreEVM.finalize). Kolejne transakcje nie powinny być wysyłane po wywołaniu tej metody i wygenerowaniu przypadków testowych dla każdej zbadanej ścieżki. +Aby zatrzymać eksplorację, użyj [m.finalize()](https://manticore.readthedocs.io/en/latest/evm.html?highlight=finalize#manticore.ethereum.ManticoreEVM.finalize). Po wywołaniu tej metody nie należy wysyłać żadnych dalszych transakcji, a Manticore generuje przypadki testowe dla każdej zbadanej ścieżki. -### Podsumowanie: uruchamianie pod Manticore {#summary-running-under-manticore} +### Podsumowanie: Uruchamianie w Manticore {#summary-running-under-manticore} Łącząc wszystkie poprzednie kroki, otrzymujemy: @@ -317,14 +320,14 @@ symbolic_var = m.make_symbolic_value() contract_account.f(symbolic_var) print("Results are in {}".format(m.workspace)) -m.finalize() # stop the exploration +m.finalize() # zatrzymaj eksplorację ``` -Cały powyższy kod można znaleźć w [` example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) +Cały powyższy kod można znaleźć w [`example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) -## Pobieranie ścieżek zgłaszania {#getting-throwing-paths} +## Uzyskiwanie ścieżek powodujących błąd {#getting-throwing-paths} -Teraz wygenerujemy konkretne dane wejściowe dla ścieżek zgłaszających wyjątek w `f()`. Celem jest nadal następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): +Teraz wygenerujemy określone dane wejściowe dla ścieżek zgłaszających wyjątek w `f()`. Celem nadal jest następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): ```solidity pragma solidity >=0.4.24 <0.6.0; @@ -337,17 +340,17 @@ contract Simple { } ``` -### Używanie informacji o stanie {#using-state-information} +### Korzystanie z informacji o stanie {#using-state-information} -Każda wykonana ścieżka ma swój stan blockchain. Stan jest gotowy lub został zabity, co oznacza, że osiąga instrukcję THROW lub REVERT: +Każda wykonana ścieżka ma swój stan blockchaina. Stan jest gotowy albo zakończony (killed), co oznacza, że osiąga instrukcję THROW lub REVERT: - [m.ready_states](https://manticore.readthedocs.io/en/latest/states.html#accessing): lista stanów, które są gotowe (nie wykonały REVERT/INVALID) -- [m.killed_states](https://manticore.readthedocs.io/en/latest/states.html#accessings): lista stanów, które są gotowe (nie wykonały REVERT/INVALID) +- [m.killed_states](https://manticore.readthedocs.io/en/latest/states.html#accessings): lista stanów, które są zakończone (killed) - [m.all_states](https://manticore.readthedocs.io/en/latest/states.html#accessings): wszystkie stany ```python for state in m.all_states: - # do something with state + # zrób coś ze stanem ``` Możesz uzyskać dostęp do informacji o stanie. Na przykład: @@ -356,7 +359,7 @@ Możesz uzyskać dostęp do informacji o stanie. Na przykład: - `state.platform.transactions`: lista transakcji - `state.platform.transactions[-1].return_data`: dane zwrócone przez ostatnią transakcję -Dane zwrócone przez ostatnią transakcję są tablicą, która może zostać przekonwertowana na wartość z ABI.deserialize, na przykład: +Dane zwrócone przez ostatnią transakcję to tablica, którą można przekonwertować na wartość za pomocą ABI.deserialize, na przykład: ```python data = state.platform.transactions[0].return_data @@ -365,7 +368,7 @@ data = ABI.deserialize("uint", data) ### Jak wygenerować przypadek testowy {#how-to-generate-testcase} -Użyj [m.generate_testcase(stan, nazwa)](https://manticore.readthedocs.io/en/latest/evm.html?highlight=generate_testcase#manticore.ethereum.ManticoreEVM.generate_testcase) aby wygenerować testcase: +Użyj [m.generate_testcase(state, name)](https://manticore.readthedocs.io/en/latest/evm.html?highlight=generate_testcase#manticore.ethereum.ManticoreEVM.generate_testcase), aby wygenerować przypadek testowy: ```python m.generate_testcase(state, 'BugFound') @@ -373,13 +376,13 @@ m.generate_testcase(state, 'BugFound') ### Podsumowanie {#summary-2} -- Możesz iterować ponad stanem za pomocą stanów m.all_states +- Można iterować po stanach za pomocą `m.all_states` - `state.platform.get_balance(account.address)` zwraca saldo konta -- `State.platform.transactions` zwraca listę transakcji -- `transaction.return_data` to dane zwrócone -- `m.generate_testcase(stan, nazwa)` generuje dane wejściowe dla stanu +- `state.platform.transactions` zwraca listę transakcji +- `transaction.return_data` to zwrócone dane +- `m.generate_testcase(state, name)` generuje dane wejściowe dla stanu -### Podsumowanie: uzyskanie ścieżki zgłaszania {#summary-getting-throwing-path} +### Podsumowanie: uzyskiwanie ścieżki powodującej błąd {#summary-getting-throwing-path} ```python from manticore.ethereum import ManticoreEVM @@ -395,7 +398,8 @@ contract_account = m.solidity_create_contract(source_code, owner=user_account) symbolic_var = m.make_symbolic_value() contract_account.f(symbolic_var) -## Check if an execution ends with a REVERT or INVALID +## Sprawdź, czy wykonanie kończy się instrukcją REVERT lub INVALID + for state in m.terminated_states: last_tx = state.platform.transactions[-1] if last_tx.result in ['REVERT', 'INVALID']: @@ -403,13 +407,13 @@ for state in m.terminated_states: m.generate_testcase(state, 'ThrowFound') ``` -Cały powyższy kod można znaleźć w [` example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) +Cały powyższy kod można znaleźć w [`example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) -_Zauważ, że mogliśmy wygenerować znacznie prostszy skrypt, ponieważ wszystkie stany zwrócone przez terminated_state mają w wyniku REVERT lub INVALID: ten przykład miał jedynie zademonstrować, jak obsługiwać API._ +_Uwaga: mogliśmy wygenerować znacznie prostszy skrypt, ponieważ wszystkie stany zwrócone przez `terminated_states` mają w wyniku REVERT lub INVALID: ten przykład miał jedynie na celu zademonstrowanie, jak manipulować API._ ## Dodawanie ograniczeń {#adding-constraints} -Zobaczymy, jak ograniczyć eksplorację. Przyjmiemy założenie, że dokumentacja `f()` stwierdza, że ​​funkcja nigdy nie jest wywoływana z `a == 65`, więc każdy błąd z `a == 65` nie jest prawdziwy błąd. Celem jest nadal następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): +Zobaczymy, jak ograniczyć eksplorację. Przyjmiemy założenie, że dokumentacja funkcji `f()` stwierdza, że funkcja nigdy nie jest wywoływana z `a == 65`, więc każdy błąd przy `a == 65` nie jest prawdziwym błędem. Celem nadal jest następujący inteligentny kontrakt [`example.sol`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example.sol): ```solidity pragma solidity >=0.4.24 <0.6.0; @@ -424,22 +428,22 @@ contract Simple { ### Operatory {#operators} -Moduł [Operatorzy](https://github.com/trailofbits/manticore/blob/master/manticore/core/smtlib/operators.py) ułatwia manipulowanie ograniczeniami między innymi: +Moduł [Operators](https://github.com/trailofbits/manticore/blob/master/manticore/core/smtlib/operators.py) ułatwia manipulowanie ograniczeniami, między innymi udostępnia: - Operators.AND, - Operators.OR, -- Operators.UGT (bez znaku większe niż), -- Operators.UGE (bez znaku większe lub równe), -- Operators.ULT (bez znaku mniejsze niż), -- Operators.ULE (bez znaku mniejsze lub równe). +- Operators.UGT (większe niż bez znaku), +- Operators.UGE (większe lub równe niż bez znaku), +- Operators.ULT (mniejsze niż bez znaku), +- Operators.ULE (mniejsze lub równe niż bez znaku). -Aby zaimportować moduł, użyj następujących elementów: +Aby zaimportować moduł, użyj następującego polecenia: ```python from manticore.core.smtlib import Operators ``` -`Operators.CONCAT` jest używany do dołączania tablicy do wartości. Na przykład należy zmienić wartość return_data transakcji na wartość sprawdzaną względem innej wartości: +`Operators.CONCAT` służy do łączenia tablicy z wartością. Na przykład `return_data` transakcji musi zostać zmienione na wartość, aby można było je porównać z inną wartością: ```python last_return = Operators.CONCAT(256, *last_return) @@ -447,11 +451,12 @@ last_return = Operators.CONCAT(256, *last_return) ### Ograniczenia {#state-constraint} -Możesz używać ograniczeń globalnie lub dla określonego stanu. +Można używać ograniczeń globalnie lub dla określonego stanu. -#### Globalne ograniczenie {#state-constraint} +#### Ograniczenie globalne {#state-constraint} -Użyj `m.constrain (constraint)` aby dodać globalne ograniczenie. Na przykład możesz wywołać kontrakt z adresu symbolicznego i ograniczyć ten adres do określonych wartości: +Użyj `m.constrain(constraint)`, aby dodać ograniczenie globalne. +Na przykład, można wywołać kontrakt z adresu symbolicznego i ograniczyć ten adres do określonych wartości: ```python symbolic_address = m.make_symbolic_value() @@ -464,19 +469,21 @@ m.transaction(caller=user_account, #### Ograniczenie stanu {#state-constraint} -Użyj [state.constrain(constraint)](https://manticore.readthedocs.io/en/latest/states.html?highlight=StateBase#manticore.core.state.StateBase.constrain), aby dodać ograniczenie do określonego stanu Może być używany do ograniczania stanu po jego eksploracji, aby sprawdzić na nim jakąś właściwość. +Użyj [state.constrain(constraint)](https://manticore.readthedocs.io/en/latest/states.html?highlight=StateBase#manticore.core.state.StateBase.constrain), aby dodać ograniczenie do określonego stanu. +Można go użyć do ograniczenia stanu po jego eksploracji, aby sprawdzić na nim pewną właściwość. ### Sprawdzanie ograniczenia {#checking-constraint} -Użyj `solver.check(state.constraints)`, aby dowiedzieć się, czy ograniczenie jest nadal możliwe. Na przykład poniższe ograniczy symbolic_value do wartości różnej od 65 i sprawdzi, czy stan jest nadal możliwy: +Użyj `solver.check(state.constraints)`, aby dowiedzieć się, czy ograniczenie jest nadal możliwe do spełnienia. +Na przykład, poniższy kod ograniczy `symbolic_value` do wartości innej niż 65 i sprawdzi, czy stan jest nadal możliwy do spełnienia: ```python state.constrain(symbolic_var != 65) if solver.check(state.constraints): - # state is feasible + # stan jest możliwy do spełnienia ``` -### Podsumowanie: dodwanie ograniczeń {#summary-adding-constraints} +### Podsumowanie: dodawanie ograniczeń {#summary-adding-constraints} Dodając ograniczenie do poprzedniego kodu, otrzymujemy: @@ -499,11 +506,12 @@ contract_account.f(symbolic_var) no_bug_found = True -## Check if an execution ends with a REVERT or INVALID +## Sprawdź, czy wykonanie kończy się instrukcją REVERT lub INVALID + for state in m.terminated_states: last_tx = state.platform.transactions[-1] if last_tx.result in ['REVERT', 'INVALID']: - # we do not consider the path were a == 65 + # nie bierzemy pod uwagę ścieżki, w której a == 65 condition = symbolic_var != 65 if m.generate_testcase(state, name="BugFound", only_if=condition): print(f'Bug found, results are in {m.workspace}') @@ -513,4 +521,4 @@ if no_bug_found: print(f'No bug found') ``` -Cały powyższy kod można znaleźć w [` example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) +Cały powyższy kod można znaleźć w [`example_run.py`](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/manticore/examples/example_run.py) diff --git a/public/content/translations/pl/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/index.md b/public/content/translations/pl/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/index.md index 73ea471b570..f7eaa6eca38 100644 --- a/public/content/translations/pl/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/index.md +++ b/public/content/translations/pl/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/index.md @@ -1,17 +1,18 @@ --- -title: Slither – narzędzie do znajdowania błędów w inteligentnych kontraktach -description: Jak używać Slither do automatycznego wyszukiwania błędów w inteligentnych kontraktach +title: "Jak używać Slither do znajdowania błędów w inteligentnych kontraktach" +description: "Jak używać Slither do automatycznego wyszukiwania błędów w inteligentnych kontraktach" author: Trailofbits lang: pl tags: - - "solidity" - - "inteligentne kontrakty" - - "ochrona" - - "testing" - - "analiza statyczna" + [ + "solidity", + "smart kontrakty", + "bezpieczeństwo", + "testowanie" + ] skill: advanced published: 2020-06-09 -source: Tworzenie bezpiecznych kontraktów +source: Building secure contracts sourceUrl: https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither --- @@ -20,8 +21,8 @@ sourceUrl: https://github.com/crytic/building-secure-contracts/tree/master/progr Celem tego samouczka jest pokazanie, jak używać Slither do automatycznego wyszukiwania błędów w inteligentnych kontraktach. - [Instalacja](#installation) -- [Użycie wiersza poleceń](#command-line) -- [Wprowadzenie do analizy statycznej](#static-analysis): krótkie wprowadzenie do analizy statycznej +- [Użycie w wierszu poleceń](#command-line) +- [Wprowadzenie do analizy statycznej](#static-analysis): Krótkie wprowadzenie do analizy statycznej - [API](#api-basics): Opis API Pythona ## Instalacja {#installation} @@ -34,10 +35,11 @@ Slither przez pip: pip3 install --user slither-analyzer ``` -Slither przez dockera: +Slither przez Docker: ```bash -docker pull trailofbits/eth-security-toolbox docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox +docker pull trailofbits/eth-security-toolbox +docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox ``` _Ostatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp do bieżącego katalogu. Możesz zmienić pliki z hosta i uruchomić narzędzia na plikach z dockera_ @@ -45,10 +47,11 @@ _Ostatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp Wewnątrz dockera uruchom: ```bash -solc-select 0.5.11 cd /home/trufflecon/ +solc-select 0.5.11 +cd /home/trufflecon/ ``` -### Uruchom skrypt {#running-a-script} +### Uruchamianie skryptu {#running-a-script} Aby uruchomić skrypt Pythona za pomocą Pythona 3: @@ -58,23 +61,23 @@ python3 script.py ### Wiersz poleceń {#command-line} -**Skrypty wiersza poleceń a zdefiniowane przez użytkownika.** Slither jest wyposażony w zestaw predefiniowanych detektorów, które znajdują wiele częstych błędów. Wywołanie Slither z wiersza poleceń uruchomi wszystkie detektory, nie jest potrzebna szczegółowa wiedza na temat analizy statycznej: +**Wiersz poleceń a skrypty zdefiniowane przez użytkownika.** Slither jest wyposażony w zestaw predefiniowanych detektorów, które znajdują wiele częstych błędów. Wywołanie Slither z wiersza poleceń uruchomi wszystkie detektory; nie jest potrzebna szczegółowa wiedza na temat analizy statycznej: ```bash slither project_paths ``` -Oprócz detektorów, Slither ma możliwości przeglądania kodu poprzez swoje [drukarki](https://github.com/crytic/slither#printers) i [narzędzia](https://github.com/crytic/slither#tools). +Oprócz detektorów Slither ma możliwości przeglądu kodu za pomocą swoich [printerów](https://github.com/crytic/slither#printers) i [narzędzi](https://github.com/crytic/slither#tools). -Użyj [crytic.io](https://crytic.io), aby uzyskać dostęp do prywatnych detektorów i integracji GitHub. +Użyj [crytic.io](https://github.com/crytic), aby uzyskać dostęp do prywatnych detektorów i integracji z GitHub. ## Analiza statyczna {#static-analysis} -Możliwości i projekt struktury analizy statycznej Slither zostały opisane w postach na blogu ([1](https://blog.trailofbits.com/2018/10/19/slither-a-solidity-static-analysis-framework/), [2](https://blog.trailofbits.com/2019/05/27/slither-the-leading-static-analyzer-for-smart-contracts/)) oraz w [dokumencie akademickim](https://github.com/trailofbits/publications/blob/master/papers/wetseb19.pdf). +Możliwości i projekt frameworka do analizy statycznej Slither zostały opisane w postach na blogu ([1](https://blog.trailofbits.com/2018/10/19/slither-a-solidity-static-analysis-framework/), [2](https://blog.trailofbits.com/2019/05/27/slither-the-leading-static-analyzer-for-smart-contracts/)) i w [pracy naukowej](https://github.com/trailofbits/publications/blob/master/papers/wetseb19.pdf). -Istnieją różne postacie analizy statycznej Najprawdopodobniej zdajesz sobie sprawę, że kompilatory takie jak [clang](https://clang-analyzer.llvm.org/) i [gcc](https://lwn.net/Articles/806099/) zależą od tych technik badawczych, ale również stanowią one podstawę ([Infer](https://fbinfer.com/), [CodeClimate](https://codeclimate.com/), [FindBugs](http://findbugs.sourceforge.net/) i narzędzi opartych na formalnych metodach, takich jak [Frama-C](https://frama-c.com/) i [Polyspace](https://www.mathworks.com/products/polyspace.html). +Istnieją różne rodzaje analizy statycznej. Najprawdopodobniej zdajesz sobie sprawę, że kompilatory takie jak [clang](https://clang-analyzer.llvm.org/) i [gcc](https://lwn.net/Articles/806099/) opierają się na tych technikach badawczych, ale stanowią one również podstawę ([Infer](https://fbinfer.com/), [CodeClimate](https://codeclimate.com/), [FindBugs](http://findbugs.sourceforge.net/)) i narzędzi opartych na metodach formalnych, takich jak [Frama-C](https://frama-c.com/) i [Polyspace](https://www.mathworks.com/products/polyspace.html). -Nie dokonamy wyczerpującego przeglądu technik analizy statycznej. Zamiast tego skoncentrujemy się na tym, co jest potrzebne, aby zrozumieć, jak działa Slither tak, abyś mógł go skuteczniej używać, aby znaleźć błędy i zrozumieć kod. +Nie będziemy tutaj wyczerpująco omawiać technik analizy statycznej ani badań w tej dziedzinie. Zamiast tego skoncentrujemy się na tym, co jest potrzebne, aby zrozumieć, jak działa Slither tak, abyś mógł go skuteczniej używać, aby znaleźć błędy i zrozumieć kod. - [Reprezentacja kodu](#code-representation) - [Analiza kodu](#analysis) @@ -84,11 +87,11 @@ Nie dokonamy wyczerpującego przeglądu technik analizy statycznej. Zamiast tego W przeciwieństwie do analizy dynamicznej, która rozważa pojedynczą ścieżkę wykonania, analiza statyczna rozważa wszystkie ścieżki naraz. W tym celu opiera się na innej reprezentacji kodu. Dwa najczęściej spotykane to abstrakcyjne drzewo składni (AST) i graf przepływu sterowania (CFG). -### Abstrakcyjne drzewa składniowe (AST) {#abstract-syntax-trees-ast} +### Abstrakcyjne drzewa składni (AST) {#abstract-syntax-trees-ast} AST są używane za każdym razem, gdy kompilator analizuje kod. Jest to prawdopodobnie najbardziej podstawowa struktura, na podstawie której można przeprowadzić analizę statyczną. -Krótko mówiąc, AST jest ustrukturyzowanym drzewem, w którym zwyczajowo, każdy liść zawiera zmienną lub stałą, a węzły wewnętrzne są operandami lub operacjami przepływu sterowania. Rozważmy następujący kod: +Krótko mówiąc, AST to ustrukturyzowane drzewo, w którym zazwyczaj każdy liść zawiera zmienną lub stałą, a węzły wewnętrzne to operandy lub operacje przepływu sterowania. Rozważmy następujący kod: ```solidity function safeAdd(uint a, uint b) pure internal returns(uint){ @@ -99,15 +102,15 @@ function safeAdd(uint a, uint b) pure internal returns(uint){ } ``` -Odpowiedni AST jest pokazany w: +Odpowiadające mu drzewo AST pokazano poniżej: ![AST](./ast.png) Slither używa AST eksportowanego przez solc. -Choć prosty w budowie, AST jest strukturą zagnieżdżoną. Czasem jego przeanalizowanie nie jest proste. Na przykład, aby zidentyfikować operacje używane przez wyrażenie `a + b <= a`, musisz najpierw przeanalizować `<=`, a następnie `+`. Wspólnym podejściem jest stosowanie tak zwanego wzoru odwiedzającego, który rekursywnie przechodzi przez drzewo. Slither zawiera ogólnego odwiedzającego w [`ExpressionVisitor`](https://github.com/crytic/slither/blob/master/slither/visitors/expression/expression.py). +Choć prosty w budowie, AST jest strukturą zagnieżdżoną. Czasem jego przeanalizowanie nie jest proste. Na przykład, aby zidentyfikować operacje użyte w wyrażeniu `a + b <= a`, należy najpierw przeanalizować `<=` a następnie `+`. Powszechnym podejściem jest użycie tak zwanego wzorca wizytatora, który rekurencyjnie porusza się po drzewie. Slither zawiera ogólny wizytator w [`ExpressionVisitor`](https://github.com/crytic/slither/blob/master/slither/visitors/expression/expression.py). -Następujący kod używa `ExpressionVisitor` aby wykryć, czy wyrażenie zawiera dodatek: +Poniższy kod używa `ExpressionVisitor` do wykrycia, czy wyrażenie zawiera operację dodawania: ```python from slither.visitors.expression.expression import ExpressionVisitor @@ -128,52 +131,52 @@ print(f'The expression {expression} has a addition: {visitor.result()}') ### Graf przepływu sterowania (CFG) {#control-flow-graph-cfg} -Drugą najbardziej powszechną reprezentacją kodu jest graf przepływu sterowania. Jak sugeruje jego nazwa, jest to przedstawienie oparte na wykresie, które ujawnia wszystkie ścieżki wykonania. Każdy węzeł zawiera jedną lub wiele instrukcji. Krawędzie na wykresie reprezentują operacje przepływu sterowania (if/then/else, loop itp.). CFG naszego poprzedniego przykładu to: +Drugą najpowszechniejszą reprezentacją kodu jest graf przepływu sterowania (CFG). Jak sama nazwa wskazuje, jest to reprezentacja oparta na grafie, która ujawnia wszystkie ścieżki wykonania. Każdy węzeł zawiera jedną lub wiele instrukcji. Krawędzie w grafie reprezentują operacje przepływu sterowania (if/then/else, pętle itp.). CFG naszego poprzedniego przykładu to: ![CFG](./cfg.png) CFG jest reprezentacją, na której opiera się większość analiz. -Istnieje wiele innych reprezentacji kodów. Każda reprezentacja ma zalety i wady zgodnie z analizą, którą chcesz przeprowadzić. +Istnieje wiele innych reprezentacji kodu. Każda reprezentacja ma zalety i wady w zależności od analizy, którą chcesz przeprowadzić. ### Analiza {#analysis} -Najprostszym rodzajem analiz, które możesz wykonać za pomocą Slither, są analizy składni. +Najprostszym typem analiz, jakie można wykonać za pomocą Slither, są analizy składniowe. -### Analiza składni {#syntax-analysis} +### Analiza składniowa {#syntax-analysis} -Slither może nawigować przez różne elementy kodu i ich reprezentacje, aby znaleźć niespójności i wady za pomocą podejścia podobnego do dopasowania do wzorca. +Slither może poruszać się po różnych komponentach kodu i ich reprezentacjach, aby znaleźć niespójności i wady, stosując podejście podobne do dopasowywania wzorców. -Na przykład następujące detektory szukają problemów związanych z składnią: +Na przykład poniższe detektory szukają problemów związanych ze składnią: -- [Zastępowanie zmiennych stanu](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing): porusza się iteracyjnie po wszystkich zmiennych stanu i sprawdza, czy któryś zastępuje zmienną z dziedziczonego kontraktu ([state.py#L51-L62](https://github.com/crytic/slither/blob/0441338e055ab7151b30ca69258561a5a793f8ba/slither/detectors/shadowing/state.py#L51-L62)) +- [Przesłanianie zmiennej stanu](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing): iteruje po wszystkich zmiennych stanu i sprawdza, czy któraś z nich nie przesłania zmiennej z dziedziczonego kontraktu ([state.py#L51-L62](https://github.com/crytic/slither/blob/0441338e055ab7151b30ca69258561a5a793f8ba/slither/detectors/shadowing/state.py#L51-L62)) -- [Nieprawidłowy interfejs ERC20](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface): szukka nieprawidłowych sygnatur funkcji ERC20 ([incorrect_erc20_interface.py#L34-L55](https://github.com/crytic/slither/blob/0441338e055ab7151b30ca69258561a5a793f8ba/slither/detectors/erc/incorrect_erc20_interface.py#L34-L55)) +- [Nieprawidłowy interfejs ERC20](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface): wyszukuje nieprawidłowe sygnatury funkcji ERC20 ([incorrect_erc20_interface.py#L34-L55](https://github.com/crytic/slither/blob/0441338e055ab7151b30ca69258561a5a793f8ba/slither/detectors/erc/incorrect_erc20_interface.py#L34-L55)) ### Analiza semantyczna {#semantic-analysis} -W przeciwieństwie do analizy składni, analiza semantyczna sięga głębiej i analizuje „znaczenie” kodu. Rodzina ta obejmuje kilka szerokich rodzajów analiz. Prowadzą one do bardziej skutecznych i pożytecznych wyników, ale także są bardziej skomplikowane. +W przeciwieństwie do analizy składniowej, analiza semantyczna sięga głębiej i analizuje „znaczenie” kodu. Ta rodzina obejmuje kilka szerokich typów analiz. Prowadzą one do skuteczniejszych i bardziej użytecznych wyników, ale są również bardziej złożone w tworzeniu. -Analizy semantyczne są wykorzystywane do najbardziej zaawansowanego wykrywania podatności na zagrożenia. +Analizy semantyczne są wykorzystywane do wykrywania najbardziej zaawansowanych podatności. #### Analiza zależności danych {#fixed-point-computation} -Zmienna `variable_a` jest zależna od danych `variable_b`, jeśli istnieje ścieżka, dla której wartość `variable_a` jest zależna od `variable_b`. +Mówimy, że zmienna `variable_a` jest zależna od danych zmiennej `variable_b`, jeśli istnieje ścieżka wykonania, w której wartość `variable_b` wpływa na wartość `variable_a`. -W poniższym kodzie zmienna `_a` jest zależna od `variable_b`: +W poniższym kodzie `variable_a` jest zależna od `variable_b`: ```solidity // ... variable_a = variable_b + 1; ``` -Slither posiada wbudowane funkcje [zależności danych](https://github.com/crytic/slither/wiki/data-dependency) dzięki jego pośredniej reprezentacji (omówionej w dalszej części). +Slither ma wbudowane możliwości [analizy zależności danych](https://github.com/crytic/slither/wiki/data-dependency) dzięki swojej reprezentacji pośredniej (omówionej w dalszej części). -Przykład użycia zależności od danych można znaleźć w [niebezpiecznym ścisłym detektorze równości](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities). Tutaj Slither będzie szukał ścisłego porównania równości z niebezpieczną wartością ([wronct_strict_equality. y#L86-L87](https://github.com/crytic/slither/blob/6d86220a53603476f9567c3358524ea4db07fb25/slither/detectors/statements/incorrect_strict_equality.py#L86-L87)), i poinformuje użytkownika, że powinien użyć `>=` lub `<=` zamiast `==`, aby uniemożliwić atakującemu przechwycenie kontraktu. Spośród innych detektor uzna za niebezpieczną wartość zwrotną wywołania do `balanceOf(address)` ([invalid \_strict_equality. y#L63-L64](https://github.com/crytic/slither/blob/6d86220a53603476f9567c3358524ea4db07fb25/slither/detectors/statements/incorrect_strict_equality.py#L63-L64)) i użyje silnika zależności od danych, aby śledzić jego użycie. +Przykład wykorzystania zależności danych można znaleźć w detektorze [niebezpiecznych ścisłych równości](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities). W tym przypadku Slither będzie szukał porównania ścisłej równości z niebezpieczną wartością ([incorrect_strict_equality.py#L86-L87](https://github.com/crytic/slither/blob/6d86220a53603476f9567c3358524ea4db07fb25/slither/detectors/statements/incorrect_strict_equality.py#L86-L87)) i poinformuje użytkownika, że powinien użyć `>=` lub `<=` zamiast `==`, aby uniemożliwić atakującemu zastawienie pułapki na kontrakt. Między innymi detektor uzna za niebezpieczną wartość zwracaną przez wywołanie `balanceOf(address)` ([incorrect_strict_equality.py#L63-L64](https://github.com/crytic/slither/blob/6d86220a53603476f9567c3358524ea4db07fb25/slither/detectors/statements/incorrect_strict_equality.py#L63-L64)) i użyje silnika zależności danych do śledzenia jej użycia. -#### Obliczenia stałoprzecinkowe {#fixed-point-computation} +#### Obliczanie punktu stałego {#fixed-point-computation} -Jeśli Twoja analiza nawiguje przez CFG i porusza się wzdłuż krawędzi, prawdopodobnie zobaczysz już odwiedzone węzły. Na przykład, jeśli pętla jest przedstawiona w poniższy sposób: +Jeśli twoja analiza porusza się po CFG i podąża za krawędziami, prawdopodobnie zobaczysz już odwiedzone węzły. Na przykład, jeśli pętla jest przedstawiona jak poniżej: ```solidity for(uint i; i < zakres; ++){ @@ -181,21 +184,21 @@ for(uint i; i < zakres; ++){ } ``` -Twoja analiza będzie musiała wiedzieć, kiedy się zatrzymać. Tutaj są dwie główne strategie: (1) powtórzyć na każdym węźle skończoną liczbę razy, (2) obliczyć tak zwany punkt stały. Punkt stały zasadniczo oznacza, że analiza tego węzła nie dostarcza żadnych istotnych informacji. +Twoja analiza będzie musiała wiedzieć, kiedy się zatrzymać. Istnieją tu dwie główne strategie: (1) iteracja na każdym węźle skończoną liczbę razy, (2) obliczenie tak zwanego _punktu stałego_. Punkt stały w zasadzie oznacza, że analiza tego węzła nie dostarcza żadnych istotnych informacji. -Przykład użytego puntu stałego można znaleźć w detektorach wielobieżności: Slither eksploruje węzły i szuka wywołań zewnętrznych, zapisuje i odczytuje w pamięci. Po osiągnięciu punktu stałego ([reentrancy.py#L125-L131](https://github.com/crytic/slither/blob/master/slither/detectors/reentrancy/reentrancy.py#L125-L131)), zatrzymuje eksplorację i analizuje wyniki, aby sprawdzić, czy występuje wielobieżność, sprawdzając różne jej wzorce ([reentrancy_benign. y](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_benign.py), [reentrancy_read_before_write.py](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_read_before_write.py), [reentrancy_eth.py](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_eth.py)). +Przykład zastosowania punktu stałego można znaleźć w detektorach reentrancji: Slither bada węzły i szuka zewnętrznych wywołań, zapisów do pamięci i odczytów z niej. Gdy osiągnie punkt stały ([reentrancy.py#L125-L131](https://github.com/crytic/slither/blob/master/slither/detectors/reentrancy/reentrancy.py#L125-L131)), zatrzymuje eksplorację i analizuje wyniki, aby sprawdzić, czy występuje reentrancja, za pomocą różnych wzorców reentrancji ([reentrancy_benign.py](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_benign.py), [reentrancy_read_before_write.py](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_read_before_write.py), [reentrancy_eth.py](https://github.com/crytic/slither/blob/b275bcc824b1b932310cf03b6bfb1a1fef0ebae1/slither/detectors/reentrancy/reentrancy_eth.py)). -Analizy pisania z wykorzystaniem efektywnego obliczania punktów stałych wymagają dobrego zrozumienia sposobu, w jaki analiza propaguje jej informacje. +Pisanie analiz z wykorzystaniem wydajnych obliczeń punktu stałego wymaga dobrego zrozumienia, w jaki sposób analiza propaguje swoje informacje. ### Reprezentacja pośrednia {#intermediate-representation} -Pośrednia reprezentacja (IR) to język mający być bardziej dostosowany do analizy statycznej niż oryginalny. Slither tłumaczy Solidity na własną IR: [SlithIR](https://github.com/crytic/slither/wiki/SlithIR). +Reprezentacja pośrednia (IR) to język, który ma być bardziej podatny na analizę statyczną niż język oryginalny. Slither tłumaczy Solidity na własną reprezentację pośrednią (IR): [SlithIR](https://github.com/crytic/slither/wiki/SlithIR). -Zrozumienie SlithIR nie jest konieczne, jeśli chcesz tylko zapisać podstawowe kontrole. Jeśli jednak planuje się napisać zaawansowane analizy semantyczne, będzie to pomocne. Drukarki [SlithIR](https://github.com/crytic/slither/wiki/Printer-documentation#slithir) i [SSA](https://github.com/crytic/slither/wiki/Printer-documentation#slithir-ssa) pomogą Ci zrozumieć, jak kod jest przetłumaczony. +Zrozumienie SlithIR nie jest konieczne, jeśli chcesz pisać tylko podstawowe sprawdzenia. Będzie to jednak przydatne, jeśli planujesz pisać zaawansowane analizy semantyczne. Printery [SlithIR](https://github.com/crytic/slither/wiki/Printer-documentation#slithir) i [SSA](https://github.com/crytic/slither/wiki/Printer-documentation#slithir-ssa) pomogą ci zrozumieć, w jaki sposób tłumaczony jest kod. -## Podstawowe informacje o API {#api-basics} +## Podstawy API {#api-basics} -Slither ma interfejs API, który pozwala odkrywać podstawowe atrybuty kontraktu i jego funkcje. +Slither posiada API, które pozwala na eksplorację podstawowych atrybutów kontraktu i jego funkcji. Aby załadować bazę kodu: @@ -205,32 +208,32 @@ slither = Slither('/path/to/project') ``` -### Odkrywanie kontraktów i funkcji {#exploring-contracts-and-functions} +### Eksploracja kontraktów i funkcji {#exploring-contracts-and-functions} -Obiekt `Slither` zawiera: +Obiekt `Slither` posiada: -- `contracts (list(Contract)`: lista kontraktów -- `contracts_derived (list(Contract)`: lista kontraktów, które nie są dziedziczone przez inny kontrakt (podzbiór kontraktów) -- `get_contract_from_name (str)`: zwraca kontrakt z jego nazwy +- `contracts (list(Contract))`: lista kontraktów +- `contracts_derived (list(Contract))`: lista kontraktów, które nie są dziedziczone przez żaden inny kontrakt (podzbiór kontraktów) +- `get_contract_from_name (str)`: Zwraca kontrakt na podstawie jego nazwy -Obiekt `Contract` ma: +Obiekt `Contract` posiada: -- `name (str)`: nazwa kontraktu -- `functions (list(Function))`: lista funkcji -- `modifiers (list(Modifier))`: lista funkcji -- `all_functions_lated (list(Function/Modifier))`: lista wszystkich funkcji wewnętrznych osiągalnych przez kontrakt -- `inheritance (list(Contract))`: lista dziedziczonych kontraktów -- `get_function_from_signature (str)`: zwraca funkcję z jej podpisu -- `get_modifier_from_signature (str)`: zwraca modyfikator z jego podpisu -- `get_state_variable_from_name (str)`: zwraca zmienną stanową z jej nazwy +- `name (str)`: Nazwa kontraktu +- `functions (list(Function))`: Lista funkcji +- `modifiers (list(Modifier))`: Lista modyfikatorów +- `all_functions_called (list(Function/Modifier))`: Lista wszystkich funkcji wewnętrznych osiągalnych z poziomu kontraktu +- `inheritance (list(Contract))`: Lista dziedziczonych kontraktów +- `get_function_from_signature (str)`: Zwraca funkcję na podstawie jej sygnatury +- `get_modifier_from_signature (str)`: Zwraca modyfikator na podstawie jego sygnatury +- `get_state_variable_from_name (str)`: Zwraca zmienną stanu na podstawie jej nazwy -Obiekt `Function` lub `Modifier` ma: +Obiekt `Function` lub `Modifier` posiada: -- `name (str)`: nazwa funkcji -- `contract (contract)`: kontrakt, w którym zadeklarowana jest funkcja -- `nodes (list(Node))`: lista węzłów tworzących CFG funkcji/modyfikatora -- `entry_point (Node)`: punkt wejścia CFG -- `variables_read (list(Variable))`: lista odczytanych zmiennych -- `variables_written (list(Variable))`: lista zapisanych zmiennych -- `state_variables_read (list(StateVariable))`: lista odczytanych zmiennych stanu (podzbiór zmiennych`read) -- `state_variables_written (list(StateVariable))`: lista zapisanych zmiennych stanu (podzbiór zmiennych`written) +- `name (str)`: Nazwa funkcji +- `contract (Contract)`: kontrakt, w którym zadeklarowana jest funkcja +- `nodes (list(Node))`: Lista węzłów tworzących CFG funkcji/modyfikatora +- `entry_point (Node)`: Punkt wejściowy CFG +- `variables_read (list(Variable))`: Lista odczytanych zmiennych +- `variables_written (list(Variable))`: Lista zapisanych zmiennych +- `state_variables_read (list(StateVariable))`: Lista odczytanych zmiennych stanu (podzbiór `variables_read`) +- `state_variables_written (list(StateVariable))`: Lista zapisanych zmiennych stanu (podzbiór `variables_written`) diff --git a/public/content/translations/pl/developers/tutorials/how-to-use-tellor-as-your-oracle/index.md b/public/content/translations/pl/developers/tutorials/how-to-use-tellor-as-your-oracle/index.md new file mode 100644 index 00000000000..a9581082e43 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-use-tellor-as-your-oracle/index.md @@ -0,0 +1,81 @@ +--- +title: "Jak skonfigurować Tellor jako swoją wyrocznię" +description: "Przewodnik po rozpoczęciu integracji wyroczni Tellor z Twoim protokołem" +author: "Tellor" +lang: pl +tags: [ "solidity", "smart kontrakty", "wyrocznie" ] +skill: beginner +published: 2021-06-29 +source: Tellor Docs +sourceUrl: https://docs.tellor.io/tellor/ +--- + +Szybki quiz: Twój protokół jest już prawie gotowy, ale potrzebuje wyroczni, aby uzyskać dostęp do danych off-chain... Co zrobisz? + +## Wymagania wstępne (sugerowane) {#soft-prerequisites} + +Ten post ma na celu sprawienie, aby dostęp do źródła danych wyroczni był tak prosty i przejrzysty, jak to tylko możliwe. Niemniej jednak, aby skupić się na aspekcie wyroczni, zakładamy następujący poziom Twoich umiejętności programistycznych. + +Założenia: + +- umiesz poruszać się w terminalu +- masz zainstalowany npm +- wiesz, jak używać npm do zarządzania zależnościami + +Tellor to działająca wyrocznia o otwartym kodzie źródłowym, gotowa do wdrożenia. Ten przewodnik dla początkujących ma na celu pokazanie, z jaką łatwością można zacząć korzystać z Tellor, zapewniając Twojemu projektowi w pełni zdecentralizowaną i odporną na cenzurę wyrocznię. + +## Przegląd {#overview} + +Tellor to system wyroczni, w którym strony mogą zażądać wartości punktu danych off-chain (np. BTC/USD), a reporterzy konkurują o dodanie tej wartości do banku danych on-chain, dostępnego dla wszystkich smart kontraktów Ethereum. Dane wejściowe do tego banku danych są zabezpieczone przez sieć stakujących reporterów. Tellor wykorzystuje krypto-ekonomiczne mechanizmy motywacyjne, nagradzając reporterów za uczciwe przesyłanie danych i karząc złośliwych uczestników poprzez emisję tokena Tellor, Tributes (TRB), oraz mechanizm sporów. + +W tym samouczku omówimy: + +- Konfiguracja początkowego zestawu narzędzi, którego będziesz potrzebować, aby zacząć. +- Omówimy prosty przykład. +- Lista adresów sieci testowych, na których można obecnie testować Tellor. + +## UsingTellor {#usingtellor} + +Na początek zainstaluj podstawowe narzędzia niezbędne do używania Tellor jako swojej wyroczni. Użyj [tego pakietu](https://github.com/tellor-io/usingtellor), aby zainstalować kontrakty użytkownika Tellor: + +`npm install usingtellor` + +Po zainstalowaniu Twoje kontrakty będą mogły dziedziczyć funkcje z kontraktu „UsingTellor”. + +Świetnie! Teraz, gdy masz już gotowe narzędzia, przejdźmy przez proste ćwiczenie, w którym pobierzemy cenę bitcoina: + +### Przykład BTC/USD {#btcusd-example} + +Odziedzicz kontrakt UsingTellor, przekazując adres Tellor jako argument konstruktora: + +Spójrz na poniższy przykład: + +```solidity +import "usingtellor/contracts/UsingTellor.sol"; + +contract PriceContract is UsingTellor { + uint256 public btcPrice; + + //Ten kontrakt ma teraz dostęp do wszystkich funkcji w UsingTellor + +constructor(address payable _tellorAddress) UsingTellor(_tellorAddress) public {} + +function setBtcPrice() public { + bytes memory _b = abi.encode("SpotPrice",abi.encode("btc","usd")); + bytes32 _queryId = keccak256(_b); + + uint256 _timestamp; + bytes _value; + + (_value, _timestamp) = getDataBefore(_queryId, block.timestamp - 15 minutes); + + btcPrice = abi.decode(_value,(uint256)); + } +} +``` + +Pełną listę adresów kontraktów można znaleźć [tutaj](https://docs.tellor.io/tellor/the-basics/contracts-reference). + +Dla ułatwienia, repozytorium UsingTellor jest dostarczane z wersją kontraktu [Tellor Playground](https://github.com/tellor-io/TellorPlayground) w celu łatwiejszej integracji. [Tutaj](https://github.com/tellor-io/sampleUsingTellor#tellor-playground) znajdziesz listę przydatnych funkcji. + +Aby uzyskać bardziej niezawodną implementację wyroczni Tellor, zapoznaj się z pełną listą dostępnych funkcji [tutaj](https://github.com/tellor-io/usingtellor/blob/master/README.md). diff --git a/public/content/translations/pl/developers/tutorials/how-to-view-nft-in-metamask/index.md b/public/content/translations/pl/developers/tutorials/how-to-view-nft-in-metamask/index.md new file mode 100644 index 00000000000..81d67f46949 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-view-nft-in-metamask/index.md @@ -0,0 +1,33 @@ +--- +title: "Jak wyświetlić swoje NFT w portfelu (część 3/3 serii samouczków o NFT)" +description: "Ten samouczek opisuje, jak wyświetlić istniejące NFT na MetaMask!" +author: "Sumi Mudgil" +tags: [ "ERC-721", "Alchemy", "Solidity" ] +skill: beginner +lang: pl +published: 2021-04-22 +--- + +Ten samouczek jest częścią 3/3 w serii samouczków NFT, w której oglądamy nasze nowo wybite NFT. Możesz jednak użyć ogólnego samouczka dla dowolnego tokena ERC-721 za pomocą MetaMask, w tym w sieci głównej lub dowolnej sieci testowej. Jeśli chcesz dowiedzieć się, jak wybić własne NFT na Ethereum, zapoznaj się z [częścią 1 o tym, jak napisać i wdrożyć inteligentny kontrakt NFT](/developers/tutorials/how-to-write-and-deploy-an-nft)! + +Gratulacje! Dotarłeś do najkrótszej i najprostszej części naszej serii samouczków NFT — jak wyświetlić swoje świeżo wybite NFT w wirtualnym portfelu. W tym przykładzie będziemy używać MetaMask, ponieważ jest to to, czego używaliśmy w poprzednich dwóch częściach. + +Jako warunek wstępny, powinieneś mieć już zainstalowany MetaMask na telefonie komórkowym, a także powinno zawierać konto, na które wybiłeś swoje NFT — aplikację można pobrać za darmo na [iOS](https://apps.apple.com/us/app/metamask-blockchain-wallet/id1438144202) lub [Androida](https://play.google.com/store/apps/details?id=io.metamask&hl=en_US&gl=US). + +## Krok 1: Ustaw sieć na Sepolia {#set-network-to-sepolia} + +U góry aplikacji naciśnij przycisk „Portfel”, po czym zostaniesz poproszony o wybranie sieci. Ponieważ nasze NFT zostało wybite w sieci Sepolia, musisz wybrać Sepolia jako swoją sieć. + +![Jak ustawić sieć Sepolia jako swoją sieć na MetaMask Mobile](./goerliMetamask.gif) + +## Krok 2: Dodaj swój przedmiot kolekcjonerski do MetaMask {#add-nft-to-metamask} + +Gdy znajdziesz się w sieci Sepolia, wybierz zakładkę „Przedmioty kolekcjonerskie” po prawej stronie i dodaj adres inteligentnego kontraktu NFT oraz ID tokena ERC-721 Twojego NFT — które powinieneś być w stanie znaleźć na Etherscan na podstawie haszu transakcji z wdrożenia Twojego NFT w części II naszego samouczka. + +![Jak znaleźć hasz transakcji i ID tokena ERC-721](./findNFTEtherscan.png) + +Może być konieczne kilkukrotne odświeżenie strony, aby zobaczyć swoje NFT — ale na pewno tam będzie ! + +![Jak przesłać swoje NFT do MetaMask](./findNFTMetamask.gif) + +Gratulacje! Pomyślnie wybiłeś NFT i możesz je teraz zobaczyć! Nie możemy się doczekać, aby zobaczyć, jak podbijesz świat NFT! diff --git a/public/content/translations/pl/developers/tutorials/how-to-write-and-deploy-an-nft/index.md b/public/content/translations/pl/developers/tutorials/how-to-write-and-deploy-an-nft/index.md new file mode 100644 index 00000000000..db000fce47e --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/how-to-write-and-deploy-an-nft/index.md @@ -0,0 +1,380 @@ +--- +title: "Jak napisać i wdrożyć NFT (Część 1/3 serii samouczków NFT)" +description: "Ten samouczek jest częścią 1 serii o NFT, która poprowadzi Cię krok po kroku przez proces pisania i wdrażania inteligentnego kontraktu niewymienialnego tokena (token ERC-721) przy użyciu Ethereum i Inter Planetary File System (IPFS)." +author: "Sumi Mudgil" +tags: [ "ERC-721", "Alchemy", "Solidity", "smart kontrakty" ] +skill: beginner +lang: pl +published: 2021-04-22 +--- + +Ponieważ NFT przyciągają uwagę opinii publicznej do blockchaina, jest to doskonała okazja, aby samemu zrozumieć ten szum, publikując własny kontrakt NFT (token ERC-721) na blockchainie Ethereum! + +Alchemy jest niezwykle dumne z zasilania największych nazwisk w przestrzeni NFT, w tym Makersplace (które niedawno ustanowiło rekordową sprzedaż cyfrowego dzieła sztuki w Christie's za 69 milionów dolarów), Dapper Labs (twórców NBA Top Shot i Crypto Kitties), OpenSea (największy na świecie rynek NFT), Zora, Super Rare, NFTfi, Foundation, Enjin, Origin Protocol, Immutable i wiele innych. + +W tym samouczku przeprowadzimy Cię przez proces tworzenia i wdrażania inteligentnego kontraktu ERC-721 w sieci testowej Sepolia przy użyciu [MetaMask](https://metamask.io/), [Solidity](https://docs.soliditylang.org/en/v0.8.0/), [Hardhat](https://hardhat.org/), [Pinata](https://pinata.cloud/) i [Alchemy](https://alchemy.com/signup/eth) (nie martw się, jeśli jeszcze nie rozumiesz, co to wszystko oznacza — wyjaśnimy to!). + +W części 2 tego samouczka omówimy, jak możemy użyć naszego inteligentnego kontraktu do wybicia NFT, a w części 3 wyjaśnimy, jak wyświetlić swoje NFT w MetaMask. + +I oczywiście, jeśli masz w dowolnym momencie pytania, nie wahaj się skontaktować z nami na [Discordzie Alchemy](https://discord.gg/gWuC7zB) lub odwiedzić [dokumentację API NFT firmy Alchemy](https://docs.alchemy.com/alchemy/enhanced-apis/nft-api)! + +## Krok 1: Połącz się z siecią Ethereum {#connect-to-ethereum} + +Istnieje wiele sposobów na wysyłanie żądań do blockchaina Ethereum, ale dla ułatwienia użyjemy darmowego konta w [Alchemy](https://alchemy.com/signup/eth), platformie deweloperskiej blockchain i API, które pozwalają nam komunikować się z łańcuchem Ethereum bez konieczności uruchamiania własnych węzłów. + +W tym samouczku skorzystamy również z narzędzi deweloperskich Alchemy do monitorowania i analityki, aby zrozumieć, co dzieje się „pod maską” podczas wdrażania naszego inteligentnego kontraktu. Jeśli nie masz jeszcze konta Alchemy, możesz zarejestrować się za darmo [tutaj](https://alchemy.com/signup/eth). + +## Krok 2: Stwórz swoją aplikację (i klucz API) {#make-api-key} + +Po utworzeniu konta Alchemy możesz wygenerować klucz API, tworząc aplikację. Pozwoli nam to na wysyłanie żądań do sieci testowej Sepolia. Sprawdź [ten przewodnik](https://docs.alchemyapi.io/guides/choosing-a-network), jeśli chcesz dowiedzieć się więcej o sieciach testowych. + +1. Przejdź do strony „Utwórz aplikację” w panelu Alchemy, najeżdżając kursorem na „Aplikacje” na pasku nawigacyjnym i klikając „Utwórz aplikację”. + +![Stwórz swoją aplikację](./create-your-app.png) + +2. Nazwij swoją aplikację (my wybraliśmy „Mój pierwszy NFT!”), dodaj krótki opis, wybierz „Ethereum” jako łańcuch i „Sepolia” jako sieć. Od czasu The Merge pozostałe sieci testowe zostały wycofane. + +![Skonfiguruj i opublikuj swoją aplikację](./alchemy-explorer-sepolia.png) + +3. Kliknij „Utwórz aplikację” i to wszystko! Twoja aplikacja powinna pojawić się w poniższej tabeli. + +## Krok 3: Utwórz konto Ethereum (adres) {#create-eth-address} + +Potrzebujemy konta Ethereum do wysyłania i odbierania transakcji. W tym samouczku użyjemy MetaMask, wirtualnego portfela w przeglądarce, który służy do zarządzania adresem konta Ethereum. Jeśli chcesz dowiedzieć się więcej o tym, jak działają transakcje w Ethereum, sprawdź [tę stronę](/developers/docs/transactions/) od Ethereum Foundation. + +Możesz pobrać i utworzyć konto MetaMask za darmo [tutaj](https://metamask.io/download). Podczas tworzenia konta lub jeśli już je masz, pamiętaj o przełączeniu się na „Sieć testową Sepolia” w prawym górnym rogu (abyśmy nie operowali prawdziwymi pieniędzmi). + +![Ustaw Sepolię jako swoją sieć](./metamask-goerli.png) + +## Krok 4: Dodaj ether z Faucet {#step-4-add-ether-from-a-faucet} + +Aby wdrożyć nasz inteligentny kontrakt w sieci testowej, będziemy potrzebować trochę fałszywego ETH. Aby zdobyć ETH, możesz przejść do [Sepolia Faucet](https://sepoliafaucet.com/) hostowanego przez Alchemy, zalogować się i wpisać adres swojego konta, a następnie kliknąć „Wyślij mi ETH”. Wkrótce powinieneś zobaczyć ETH na swoim koncie MetaMask! + +## Krok 5: Sprawdź swoje saldo {#check-balance} + +Aby upewnić się, że nasze saldo jest prawidłowe, wykonajmy żądanie [eth_getBalance](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getbalance) za pomocą [narzędzia kompozytora Alchemy](https://composer.alchemyapi.io?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D). Zwróci to ilość ETH w naszym portfelu. Po wprowadzeniu adresu konta MetaMask i kliknięciu „Wyślij żądanie” powinieneś zobaczyć następującą odpowiedź: + + ``` + `{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}` + ``` + +> **Uwaga** Ten wynik jest w wei, a nie w ETH. Wei jest używany jako najmniejsza jednostka etheru. Przelicznik z wei na ETH to 1 eth = 1018 wei. Więc jeśli przekonwertujemy 0xde0b6b3a7640000 na liczbę dziesiętną, otrzymamy 1\*1018 wei, co równa się 1 ETH. + +Uff! Wszystkie nasze fałszywe pieniądze są na miejscu. + +## Krok 6: Zainicjuj nasz projekt {#initialize-project} + +Najpierw musimy utworzyć folder dla naszego projektu. Przejdź do wiersza poleceń i wpisz: + + ``` + mkdir my-nft + cd my-nft + ``` + +Teraz, gdy jesteśmy w folderze projektu, użyjemy `npm init`, aby zainicjować projekt. Jeśli nie masz jeszcze zainstalowanego npm, postępuj zgodnie z [tymi instrukcjami](https://docs.alchemyapi.io/alchemy/guides/alchemy-for-macs#1-install-nodejs-and-npm) (będziemy również potrzebować [Node.js](https://nodejs.org/en/download/), więc pobierz go również!). + + ``` + npm init + ``` + +Nie ma większego znaczenia, jak odpowiesz na pytania instalacyjne; dla odniesienia, oto jak my to zrobiliśmy: + +```json + package name: (my-nft) + version: (1.0.0) + description: Mój pierwszy NFT! + entry point: (index.js) + test command: + git repository: + keywords: + author: + license: (ISC) + About to write to /Users/thesuperb1/Desktop/my-nft/package.json: + + { + "name": "my-nft", + "version": "1.0.0", + "description": "Mój pierwszy NFT!", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" + } +``` + +Zatwierdź plik package.json i możemy zaczynać! + +## Krok 7: Zainstaluj [Hardhat](https://hardhat.org/getting-started/#overview) {#install-hardhat} + +Hardhat to środowisko programistyczne do kompilacji, wdrażania, testowania i debugowania oprogramowania Ethereum. Pomaga deweloperom w tworzeniu inteligentnych kontraktów i dapek lokalnie przed wdrożeniem ich na żywym łańcuchu. + +Wewnątrz naszego projektu my-nft uruchom: + + ``` + npm install --save-dev hardhat + ``` + +Sprawdź tę stronę, aby uzyskać więcej szczegółów na temat [instrukcji instalacji](https://hardhat.org/getting-started/#overview). + +## Krok 8: Utwórz projekt Hardhat {#create-hardhat-project} + +W folderze naszego projektu uruchom: + + ``` + npx hardhat + ``` + +Powinieneś wtedy zobaczyć wiadomość powitalną i opcję wyboru tego, co chcesz zrobić. Wybierz „utwórz pusty hardhat.config.js”: + + ``` + 888 888 888 888 888 + 888 888 888 888 888 + 888 888 888 888 888 + 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888 + 888 888 "88b 888P" d88" 888 888 "88b "88b 888 + 888 888 .d888888 888 888 888 888 888 .d888888 888 + 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b. + 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888 + 👷 Welcome to Hardhat v2.0.11 👷‍ + ? What do you want to do? … + Create a sample project + ❯ Create an empty hardhat.config.js + Quit + ``` + +Spowoduje to wygenerowanie dla nas pliku hardhat.config.js, w którym określimy całą konfigurację naszego projektu (w kroku 13). + +## Krok 9: Dodaj foldery projektu {#add-project-folders} + +Aby utrzymać porządek w naszym projekcie, utworzymy dwa nowe foldery. Przejdź do katalogu głównego projektu w wierszu poleceń i wpisz: + + ``` + mkdir contracts + mkdir scripts + ``` + +- contracts/ to miejsce, w którym będziemy przechowywać kod naszego inteligentnego kontraktu NFT + +- scripts/ to miejsce, w którym będziemy przechowywać skrypty do wdrażania i interakcji z naszym inteligentnym kontraktem + +## Krok 10: Napisz nasz kontrakt {#write-contract} + +Teraz, gdy nasze środowisko jest skonfigurowane, przejdźmy do bardziej ekscytujących rzeczy: _pisania kodu naszego inteligentnego kontraktu!_ + +Otwórz projekt my-nft w swoim ulubionym edytorze (my lubimy [VSCode](https://code.visualstudio.com/)). Inteligentne kontrakty są pisane w języku zwanym Solidity, którego użyjemy do napisania naszego inteligentnego kontraktu MyNFT.sol.‌ + +1. Przejdź do folderu `contracts` i utwórz nowy plik o nazwie MyNFT.sol + +2. Poniżej znajduje się kod naszego inteligentnego kontraktu NFT, który oparliśmy na implementacji ERC-721 z biblioteki [OpenZeppelin](https://docs.openzeppelin.com/contracts/3.x/erc721). Skopiuj i wklej poniższą zawartość do pliku MyNFT.sol. + + ```solidity + //Kontrakt oparty na [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721) + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + import "@openzeppelin/contracts/utils/Counters.sol"; + import "@openzeppelin/contracts/access/Ownable.sol"; + import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; + + contract MyNFT is ERC721URIStorage, Ownable { + using Counters for Counters.Counter; + Counters.Counter private _tokenIds; + + constructor() ERC721("MyNFT", "NFT") {} + + function mintNFT(address recipient, string memory tokenURI) + public onlyOwner + returns (uint256) + { + _tokenIds.increment(); + + uint256 newItemId = _tokenIds.current(); + _mint(recipient, newItemId); + _setTokenURI(newItemId, tokenURI); + + return newItemId; + } + } + ``` + +3. Ponieważ dziedziczymy klasy z biblioteki kontraktów OpenZeppelin, w wierszu poleceń uruchom `npm install @openzeppelin/contracts^4.0.0`, aby zainstalować bibliotekę w naszym folderze. + +Więc co dokładnie _robi_ ten kod? Przeanalizujmy go linijka po linijce. + +Na początku naszego inteligentnego kontraktu importujemy trzy klasy inteligentnych kontraktów [OpenZeppelin](https://openzeppelin.com/): + +- @openzeppelin/contracts/token/ERC721/ERC721.sol zawiera implementację standardu ERC-721, który odziedziczy nasz inteligentny kontrakt NFT. (Aby być prawidłowym NFT, twój inteligentny kontrakt musi implementować wszystkie metody standardu ERC-721). Aby dowiedzieć się więcej o odziedziczonych funkcjach ERC-721, sprawdź definicję interfejsu [tutaj](https://eips.ethereum.org/EIPS/eip-721). + +- @openzeppelin/contracts/utils/Counters.sol dostarcza liczniki, które mogą być zwiększane lub zmniejszane tylko o jeden. Nasz inteligentny kontrakt używa licznika do śledzenia całkowitej liczby wybitych NFT i ustawiania unikalnego ID dla naszego nowego NFT. (Każdy NFT wybity przy użyciu inteligentnego kontraktu musi mieć przypisane unikalne ID — tutaj nasze unikalne ID jest po prostu określane przez całkowitą liczbę istniejących NFT. Na przykład, pierwszy NFT, który wybijemy za pomocą naszego inteligentnego kontraktu, ma ID „1”, nasz drugi NFT ma ID „2” itd.). + +- @openzeppelin/contracts/access/Ownable.sol konfiguruje [kontrolę dostępu](https://docs.openzeppelin.com/contracts/3.x/access-control) w naszym inteligentnym kontrakcie, więc tylko właściciel inteligentnego kontraktu (Ty) może wybijać NFT. (Uwaga, włączenie kontroli dostępu jest całkowicie kwestią preferencji. Jeśli chcesz, aby każdy mógł wybić NFT za pomocą Twojego inteligentnego kontraktu, usuń słowo `Ownable` w linii 10 i `onlyOwner` w linii 17). + +Po naszych instrukcjach importu mamy nasz niestandardowy inteligentny kontrakt NFT, który jest zaskakująco krótki — zawiera tylko licznik, konstruktor i jedną funkcję! Jest to zasługa odziedziczonych kontraktów OpenZeppelin, które implementują większość metod potrzebnych do stworzenia NFT, takich jak `ownerOf`, która zwraca właściciela NFT, oraz `transferFrom`, która przenosi własność NFT z jednego konta na drugie. + +W naszym konstruktorze ERC-721 zauważysz, że przekazujemy 2 ciągi znaków, „MyNFT” i „NFT”. Pierwsza zmienna to nazwa inteligentnego kontraktu, a druga to jego symbol. Możesz nazwać każdą z tych zmiennych, jak tylko chcesz! + +Wreszcie, mamy naszą funkcję `mintNFT(address recipient, string memory tokenURI)`, która pozwala nam wybić NFT! Zauważysz, że ta funkcja przyjmuje dwie zmienne: + +- `address recipient` określa adres, który otrzyma Twój świeżo wybity NFT + +- `string memory tokenURI` to ciąg znaków, który powinien wskazywać na dokument JSON opisujący metadane NFT. Metadane NFT to to, co naprawdę ożywia go, pozwalając na konfigurowalne właściwości, takie jak nazwa, opis, obraz i inne atrybuty. W części 2 tego samouczka opiszemy, jak skonfigurować te metadane. + +`mintNFT` wywołuje niektóre metody z odziedziczonej biblioteki ERC-721 i ostatecznie zwraca liczbę reprezentującą ID świeżo wybitego NFT. + +## Krok 11: Połącz MetaMask i Alchemy z Twoim projektem {#connect-metamask-and-alchemy} + +Teraz, gdy utworzyliśmy portfel MetaMask, konto Alchemy i napisaliśmy nasz inteligentny kontrakt, nadszedł czas, aby połączyć te trzy elementy. + +Każda transakcja wysłana z Twojego wirtualnego portfela wymaga podpisu przy użyciu Twojego unikalnego klucza prywatnego. Aby udzielić naszemu programowi tego uprawnienia, możemy bezpiecznie przechowywać nasz klucz prywatny (i klucz API Alchemy) w pliku środowiskowym. + +Aby dowiedzieć się więcej o wysyłaniu transakcji, sprawdź [ten samouczek](/developers/tutorials/sending-transactions-using-web3-and-alchemy/) o wysyłaniu transakcji przy użyciu web3. + +Najpierw zainstaluj pakiet dotenv w katalogu swojego projektu: + + ``` + npm install dotenv --save + ``` + +Następnie utwórz plik `.env` w katalogu głównym naszego projektu i dodaj do niego swój klucz prywatny MetaMask oraz adres URL HTTP API Alchemy. + +- Postępuj zgodnie z [tymi instrukcjami](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key), aby wyeksportować swój klucz prywatny z MetaMask + +- Zobacz poniżej, jak uzyskać adres URL HTTP API Alchemy i skopiować go do schowka + +![Skopiuj swój adres URL API Alchemy](./copy-alchemy-api-url.gif) + +Twój plik `.env` powinien teraz wyglądać tak: + + ``` + API_URL="https://eth-sepolia.g.alchemy.com/v2/twój-klucz-api" + PRIVATE_KEY="twój-prywatny-klucz-metamask" + ``` + +Aby faktycznie połączyć je z naszym kodem, odwołamy się do tych zmiennych w naszym pliku hardhat.config.js w kroku 13. + + + +## Krok 12: Zainstaluj Ethers.js {#install-ethers} + +Ethers.js to biblioteka, która ułatwia interakcję i wysyłanie żądań do Ethereum, opakowując [standardowe metody JSON-RPC](/developers/docs/apis/json-rpc/) w bardziej przyjazne dla użytkownika metody. + +Hardhat bardzo ułatwia integrację [wtyczek](https://hardhat.org/plugins/) w celu uzyskania dodatkowych narzędzi i rozszerzonej funkcjonalności. Skorzystamy z [wtyczki Ethers](https://hardhat.org/docs/plugins/official-plugins#hardhat-ethers) do wdrażania kontraktów ([Ethers.js](https://github.com/ethers-io/ethers.js/) ma kilka bardzo przejrzystych metod wdrażania kontraktów). + +W katalogu projektu wpisz: + + ``` + npm install --save-dev @nomiclabs/hardhat-ethers ethers@^5.0.0 + ``` + +Będziemy również wymagać ethers w naszym pliku hardhat.config.js w następnym kroku. + +## Krok 13: Zaktualizuj hardhat.config.js {#update-hardhat-config} + +Do tej pory dodaliśmy kilka zależności i wtyczek, teraz musimy zaktualizować hardhat.config.js, aby nasz projekt o nich wszystkich wiedział. + +Zaktualizuj swój plik hardhat.config.js, aby wyglądał następująco: + +```js + /** + * @type import('hardhat/config').HardhatUserConfig + */ + require('dotenv').config(); + require("@nomiclabs/hardhat-ethers"); + const { API_URL, PRIVATE_KEY } = process.env; + module.exports = { + solidity: "0.8.1", + defaultNetwork: "sepolia", + networks: { + hardhat: {}, + sepolia: { + url: API_URL, + accounts: [`0x${PRIVATE_KEY}`] + } + }, + } +``` + +## Krok 14: Skompiluj nasz kontrakt {#compile-contract} + +Aby upewnić się, że wszystko do tej pory działa, skompilujmy nasz kontrakt. Zadanie kompilacji jest jednym z wbudowanych zadań Hardhat. + +Z wiersza poleceń uruchom: + + ``` + npx hardhat compile + ``` + +Możesz otrzymać ostrzeżenie o `SPDX license identifier not provided in source file`, ale nie musisz się tym martwić — miejmy nadzieję, że wszystko inne wygląda dobrze! Jeśli nie, zawsze możesz napisać wiadomość na [discordzie Alchemy](https://discord.gg/u72VCg3). + +## Krok 15: Napisz nasz skrypt wdrożeniowy {#write-deploy} + +Teraz, gdy nasz kontrakt jest napisany, a nasz plik konfiguracyjny jest gotowy, nadszedł czas, aby napisać nasz skrypt wdrażający kontrakt. + +Przejdź do folderu `scripts/` i utwórz nowy plik o nazwie `deploy.js`, dodając do niego następującą zawartość: + +```js +async function main() { + const MyNFT = await ethers.getContractFactory("MyNFT") + + // Rozpocznij wdrożenie, zwracając obietnicę, która rozwiązuje się do obiektu kontraktu + const myNFT = await MyNFT.deploy() + await myNFT.deployed() + console.log("Kontrakt wdrożony pod adresem:", myNFT.address) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) +``` + +Hardhat wykonuje niesamowitą robotę, wyjaśniając, co robi każda z tych linii kodu w swoim [samouczku dotyczącym kontraktów](https://hardhat.org/tutorial/testing-contracts.html#writing-tests), a my przyjęliśmy ich wyjaśnienia tutaj. + + ``` + const MyNFT = await ethers.getContractFactory("MyNFT"); + ``` + +`ContractFactory` w ethers.js to abstrakcja używana do wdrażania nowych inteligentnych kontraktów, więc `MyNFT` jest tutaj fabryką dla instancji naszego kontraktu NFT. Podczas korzystania z wtyczki hardhat-ethers, instancje `ContractFactory` i `Contract` są domyślnie połączone z pierwszym sygnatariuszem. + + ``` + const myNFT = await MyNFT.deploy(); + ``` + +Wywołanie `deploy()` na `ContractFactory` rozpocznie wdrożenie i zwróci `Promise`, który rozwiązuje się do obiektu `Contract`. Jest to obiekt, który ma metodę dla każdej z naszych funkcji inteligentnego kontraktu. + +## Krok 16: Wdróż nasz kontrakt {#deploy-contract} + +Jesteśmy wreszcie gotowi do wdrożenia naszego inteligentnego kontraktu! Wróć do katalogu głównego swojego projektu i w wierszu poleceń uruchom: + + ``` + npx hardhat --network sepolia run scripts/deploy.js + ``` + +Powinieneś wtedy zobaczyć coś takiego: + + ``` + Kontrakt wdrożony pod adresem: 0x4C5266cCc4b3F426965d2f51b6D910325a0E7650 + ``` + +Jeśli przejdziemy na [Etherscan Sepolia](https://sepolia.etherscan.io/) i wyszukamy adres naszego kontraktu, powinniśmy zobaczyć, że został on pomyślnie wdrożony. Jeśli nie widzisz go od razu, poczekaj chwilę, ponieważ może to zająć trochę czasu. Transakcja będzie wyglądać mniej więcej tak: + +![Zobacz adres swojej transakcji w Etherscan](./etherscan-sepoila-contract-creation.png) + +Adres `From` powinien pasować do adresu Twojego konta MetaMask, a adres `To` będzie oznaczony jako „Tworzenie Kontraktu”. Jeśli klikniemy w transakcję, zobaczymy adres naszego kontraktu w polu `To`: + +![Zobacz adres swojego kontraktu w Etherscan](./etherscan-sepolia-tx-details.png) + +Taaak! Właśnie wdrożyłeś swój inteligentny kontrakt NFT do łańcucha Ethereum (sieci testowej)! + +Aby zrozumieć, co dzieje się pod maską, przejdźmy do karty Explorer w naszym [panelu Alchemy](https://dashboard.alchemyapi.io/explorer). Jeśli masz wiele aplikacji Alchemy, pamiętaj, aby filtrować według aplikacji i wybrać „MyNFT”. + +![Zobacz wywołania wykonane „pod maską” za pomocą pulpitu nawigacyjnego Explorer firmy Alchemy](./alchemy-explorer-goerli.png) + +Tutaj zobaczysz kilka wywołań JSON-RPC, które Hardhat/Ethers wykonały dla nas „pod maską”, gdy wywołaliśmy funkcję `.deploy()`. Dwa ważne, o których należy tu wspomnieć, to [eth_sendRawTransaction](/developers/docs/apis/json-rpc/#eth_sendrawtransaction), czyli żądanie faktycznego zapisu naszego inteligentnego kontraktu na łańcuchu Sepolia, oraz [eth_getTransactionByHash](/developers/docs/apis/json-rpc/#eth_gettransactionbyhash), czyli żądanie odczytania informacji o naszej transakcji na podstawie jej hasza (typowy wzorzec podczas wysyłania transakcji). Aby dowiedzieć się więcej o wysyłaniu transakcji, zapoznaj się z tym samouczkiem na temat [wysyłania transakcji przy użyciu Web3](/developers/tutorials/sending-transactions-using-web3-and-alchemy/). + +To wszystko, jeśli chodzi o część 1 tego samouczka. W [części 2 będziemy faktycznie wchodzić w interakcję z naszym inteligentnym kontraktem, wybijając NFT](/developers/tutorials/how-to-mint-an-nft/), a w [części 3 pokażemy, jak wyświetlić swoje NFT w portfelu Ethereum](/developers/tutorials/how-to-view-nft-in-metamask/)! diff --git a/public/content/translations/pl/developers/tutorials/interact-with-other-contracts-from-solidity/index.md b/public/content/translations/pl/developers/tutorials/interact-with-other-contracts-from-solidity/index.md index a10463d95df..89d52ca84f3 100644 --- a/public/content/translations/pl/developers/tutorials/interact-with-other-contracts-from-solidity/index.md +++ b/public/content/translations/pl/developers/tutorials/interact-with-other-contracts-from-solidity/index.md @@ -1,14 +1,15 @@ --- -title: Interakcje z innymi kontraktami od Solidity -description: Jak wdrożyć inteligentny kontrakt z istniejącego kontraktu i pracować na nim +title: Interakcje z innymi kontraktami z poziomu Solidity +description: "Jak wdrożyć inteligentny kontrakt z istniejącego kontraktu i wejść z nim w interakcję" author: "jdourlens" tags: - - "inteligentne kontrakty" - - "solidity" - - "remix" - - "fabryki" - - "wdrożenie" - - "kompozycyjność" + [ + "smart kontrakty", + "solidity", + "remix", + "wdrażanie", + "kompozycyjność" + ] skill: advanced lang: pl published: 2020-04-05 @@ -17,9 +18,9 @@ sourceUrl: https://ethereumdev.io/interact-with-other-contracts-from-solidity/ address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE" --- -Z poprzednich samouczków dowiedzieliśmy się [jak wdrożyć swój pierwszy inteligentny kontrakt](/developers/tutorials/deploying-your-first-smart-contract/) i dodać do niego kilka funkcji, takich jak kontrola dostępu za pomocą modyfikatorów lub [obsługa błędów w Solidity](https://ethereumdev.io/handle-errors-in-solidity-with-require-and-revert/). Z tego samouczka dowiemy się, jak wdrożyć inteligentny kontrakt z istniejącego kontraktu i pracować na nim. +W poprzednich samouczkach wiele się nauczyliśmy, na przykład [jak wdrożyć swój pierwszy inteligentny kontrakt](/developers/tutorials/deploying-your-first-smart-contract/), jak dodać do niego pewne funkcje, takie jak [kontrola dostępu za pomocą modyfikatorów](https://ethereumdev.io/organize-your-code-and-control-access-to-your-smart-contract-with-modifiers/) czy [obsługa błędów w Solidity](https://ethereumdev.io/handle-errors-in-solidity-with-require-and-revert/). W tym samouczku dowiemy się, jak wdrożyć inteligentny kontrakt z istniejącego kontraktu i wejść z nim w interakcję. -Stworzymy kontrakt, który umożliwi każdemu posiadanie własnego inteligentnego kontraktu `Counter`, tworząc dla niego fabrykę o nazwie `CounterFactory`. Pierwszy jest kod naszego początkowego inteligentnego kontraktu `Counter`: +Stworzymy kontrakt, który umożliwi każdemu posiadanie własnego inteligentnego kontraktu `Counter`, tworząc dla niego fabrykę o nazwie `CounterFactory`. Na początek, oto kod naszego początkowego inteligentnego kontraktu `Counter`: ```solidity pragma solidity 0.5.17; @@ -32,12 +33,12 @@ contract Counter { modifier onlyOwner(address caller) { - require(caller == _owner, "You're not the owner of the contract"); + require(caller == _owner, "Nie jesteś właścicielem tego kontraktu"); _; } modifier onlyFactory() { - require(msg.sender == _factory, "You need to use the factory"); + require(msg.sender == _factory, "Musisz użyć fabryki"); _; } @@ -57,19 +58,19 @@ contract Counter { } ``` -Zwróć uwagę, że nieznacznie zmodyfikowaliśmy kod kontraktu, aby śledzić adres fabryki i adres właściciela umowy. Gdy wywołasz kod kontraktu z innego kontraktu, msg.sender odniesie się do adresu naszej fabryki kontraktowej. Jest to **bardzo ważny punkt do zrozumienia**, ponieważ używanie kontraktu do interakcji z innymi kontraktami jest powszechną praktyką. Dlatego w skomplikowanych przypadkach należy zadbać o to, kto jest nadawcą. +Zwróć uwagę, że nieznacznie zmodyfikowaliśmy kod kontraktu, aby śledzić adres fabryki i adres właściciela kontraktu. Gdy wywołujesz kod kontraktu z innego kontraktu, `msg.sender` będzie odnosić się do adresu naszej fabryki kontraktów. To **naprawdę ważna kwestia do zrozumienia**, ponieważ używanie kontraktu do interakcji z innymi kontraktami jest powszechną praktyką. Dlatego w złożonych przypadkach należy uważać, kto jest nadawcą. -W tym celu dodaliśmy również modyfikator `onlyFactory`, który zapewnia, że ​​funkcja zmiany stanu może być wywołana tylko przez fabrykę, która przekaże oryginalny obiekt wywołujący jako parametr. +W tym celu dodaliśmy również modyfikator `onlyFactory`, który zapewnia, że funkcja zmieniająca stan może być wywołana tylko przez fabrykę, która przekaże pierwotnego wywołującego jako parametr. -W naszej nowej `CounterFactory`, która będzie zarządzać wszystkimi innymi licznikami, dodamy mapowanie, które skojarzy właściciela z adresem jego kontraktu counter: +Wewnątrz naszej nowej fabryki `CounterFactory`, która będzie zarządzać wszystkimi innymi kontraktami `Counter`, dodamy mapowanie, które powiąże właściciela z adresem jego kontraktu `Counter`: ```solidity mapping(address => Counter) _counters; ``` -W Ethereum mapowanie jest równoważne obiektom w javascript, umożliwiają one mapowanie klucza typu A do wartości typu B. W tym przypadku mapujemy adres właściciela z instancją jego kontraktu counter. +W Ethereum mapowania są odpowiednikiem obiektów w JavaScript. Umożliwiają mapowanie klucza typu A na wartość typu B. W tym przypadku mapujemy adres właściciela z instancją jego kontraktu `Counter`. -Utworzenie nowego kontraktu Counter dla kogoś będzie wyglądać tak: +Instancjonowanie nowego kontraktu `Counter` dla kogoś będzie wyglądać następująco: ```solidity function createCounter() public { @@ -78,9 +79,9 @@ Utworzenie nowego kontraktu Counter dla kogoś będzie wyglądać tak: } ``` -Najpierw sprawdzamy, czy osoba jest już właścicielem Counter. Jeśli nie jest właścicielem counter, natychmiastowo przekazujemy jego adres do konstruktora `Counter` i przypisujemy nowo utworzoną instancję do mapowania. +Najpierw sprawdzamy, czy dana osoba jest już właścicielem kontraktu `Counter`. Jeśli nie posiada on kontraktu `Counter`, tworzymy jego nową instancję, przekazując jego adres do konstruktora `Counter` i przypisując nowo utworzoną instancję do mapowania. -Aby uzyskać liczbę konkretnego Counter, powinno to wyglądać tak: +Aby uzyskać stan licznika dla konkretnego kontraktu `Counter`, należy postąpić następująco: ```solidity function getCount(address account) public view returns (uint256) { @@ -93,9 +94,9 @@ function getMyCount() public view returns (uint256) { } ``` -Pierwsza funkcja sprawdza, czy kontrakt Counter istnieje dla danego adresu, a następnie wywołuje metodę `getCount` z instancji. Druga funkcja: `getMyCount` to tylko krótki koniec do przekazania msg.sender bezpośrednio do funkcji `getCount`. +Pierwsza funkcja sprawdza, czy kontrakt `Counter` istnieje dla danego adresu, a następnie wywołuje metodę `getCount` z instancji. Druga funkcja, `getMyCount`, to tylko skrót do przekazania `msg.sender` bezpośrednio do funkcji `getCount`. -Funkcja `increment` jest dość podobna, ale przekazuje oryginalnego nadawcę transakcji do kontraktu `Counter`: +Funkcja `increment` jest dość podobna, ale przekazuje pierwotnego nadawcę transakcji do kontraktu `Counter`: ```solidity function increment() public { @@ -104,9 +105,9 @@ function increment() public { } ``` -Zauważ, że jeśli zostaniesz wywołany wiele razy, nasz counter może paść ofiarą przepełnienia. Powinieneś użyć [biblioteki SafeMath](https://ethereumdev.io/using-safe-math-library-to-prevent-from-overflows/) w możliwie największym stopniu, aby chronić przed tym przypadkiem. +Należy pamiętać, że przy zbyt wielu wywołaniach w naszym liczniku może dojść do przepełnienia. Powinieneś używać [biblioteki SafeMath](https://ethereumdev.io/using-safe-math-library-to-prevent-from-overflows/) tak często, jak to możliwe, aby zabezpieczyć się przed taką ewentualnością. -Aby wdrożyć nasz kontrakt, musisz podać zarówno kod `CounterFactory`, jak i `Counter`. Podczas wdrażania na przykład w Remix musisz wybrać CounterFactory. +Aby wdrożyć nasz kontrakt, musisz podać zarówno kod `CounterFactory`, jak i `Counter`. Podczas wdrażania, na przykład w Remix, musisz wybrać `CounterFactory`. Oto pełny kod: @@ -121,12 +122,12 @@ contract Counter { modifier onlyOwner(address caller) { - require(caller == _owner, "You're not the owner of the contract"); + require(caller == _owner, "Nie jesteś właścicielem tego kontraktu"); _; } modifier onlyFactory() { - require(msg.sender == _factory, "You need to use the factory"); + require(msg.sender == _factory, "Musisz użyć fabryki"); _; } @@ -171,8 +172,8 @@ contract CounterFactory { } ``` -Po skompilowaniu wybierz w sekcji wdrażanie Remix fabrykę do wdrożenia: +Po skompilowaniu, w sekcji wdrażania w Remix, wybierz fabrykę do wdrożenia: ![Wybór fabryki do wdrożenia w Remix](./counterfactory-deploy.png) -Następnie możesz pobawić się swoją fabryką kontraktową i sprawdzić, jak zmienia się wartość. Jeśli chcesz wywołać inteligentny kontrakt z innego adresu, musisz zmienić adres w wyborze konta w Remix. +Następnie możesz pobawić się swoją fabryką kontraktów i sprawdzić, jak zmienia się wartość. Jeśli chcesz wywołać inteligentny kontrakt z innego adresu, musisz zmienić adres w polu wyboru konta w aplikacji Remix. diff --git a/public/content/translations/pl/developers/tutorials/ipfs-decentralized-ui/index.md b/public/content/translations/pl/developers/tutorials/ipfs-decentralized-ui/index.md new file mode 100644 index 00000000000..43432401932 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/ipfs-decentralized-ui/index.md @@ -0,0 +1,73 @@ +--- +title: "IPFS dla zdecentralizowanych interfejsów użytkownika" +description: "Ten samouczek uczy czytelnika, jak używać IPFS do przechowywania interfejsu użytkownika dla dapki. Chociaż dane i logika biznesowa aplikacji są zdecentralizowane, bez odpornego na cenzurę interfejsu użytkownika użytkownicy i tak mogą stracić do niej dostęp." +author: Ori Pomerantz +tags: [ "ipfs" ] +skill: beginner +lang: pl +published: 2024-06-29 +--- + +Udało Ci się stworzyć niesamowitą nową dapką. Udało Ci się nawet napisać dla niej [interfejs użytkownika](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/). Ale teraz obawiasz się, że ktoś spróbuje ją ocenzurować, wyłączając Twój interfejs użytkownika, który jest tylko jednym serwerem w chmurze. W tym samouczku dowiesz się, jak uniknąć cenzury, umieszczając swój interfejs użytkownika w **[międzyplanetarnym systemie plików (IPFS)](https://ipfs.tech/developers/)**, dzięki czemu każda zainteresowana osoba będzie mogła go przypiąć na serwerze w celu przyszłego dostępu. + +Możesz użyć usługi strony trzeciej, takiej jak [Fleek](https://resources.fleek.xyz/docs/), aby wykonać całą pracę. Ten samouczek jest dla osób, które chcą zrobić wystarczająco dużo, aby zrozumieć, co robią, nawet jeśli wymaga to więcej pracy. + +## Rozpoczęcie pracy lokalnie {#getting-started-locally} + +Istnieje wielu [zewnętrznych dostawców IPFS](https://docs.ipfs.tech/how-to/work-with-pinning-services/#use-a-third-party-pinning-service), ale najlepiej zacząć od uruchomienia IPFS lokalnie w celach testowych. + +1. Zainstaluj [interfejs użytkownika IPFS](https://docs.ipfs.tech/install/ipfs-desktop/#install-instructions). + +2. Utwórz katalog z Twoją stroną internetową. Jeśli używasz [Vite](https://vite.dev/), użyj tego polecenia: + + ```sh + pnpm vite build + ``` + +3. W IPFS Desktop kliknij **Importuj > Folder** i wybierz katalog utworzony w poprzednim kroku. + +4. Wybierz właśnie przesłany folder i kliknij **Zmień nazwę**. Nadaj mu bardziej znaczącą nazwę. + +5. Wybierz go ponownie i kliknij **Udostępnij link**. Skopiuj adres URL do schowka. Link będzie podobny do `https://ipfs.io/ipfs/QmaCuQ7yN6iyBjLmLGe8YiFuCwnePoKfVu6ue8vLBsLJQJ`. + +6. Kliknij **Status**. Rozwiń zakładkę **Zaawansowane**, aby zobaczyć adres bramki. Na przykład, w moim systemie adres to `http://127.0.0.1:8080`. + +7. Połącz ścieżkę z kroku z linkiem z adresem bramki, aby znaleźć swój adres. Na przykład dla powyższego przykładu adres URL to `http://127.0.0.1:8080/ipfs/QmaCuQ7yN6iyBjLmLGe8YiFuCwnePoKfVu6ue8vLBsLJQJ`. Otwórz ten adres URL w przeglądarce, aby zobaczyć swoją stronę. + +## Przesyłanie {#uploading} + +Teraz możesz używać IPFS do serwowania plików lokalnie, co nie jest zbyt ekscytujące. Następnym krokiem jest udostępnienie ich światu, gdy jesteś offline. + +Istnieje wiele dobrze znanych [usług przypinania](https://docs.ipfs.tech/concepts/persistence/#pinning-services). Wybierz jedną z nich. Niezależnie od tego, z której usługi korzystasz, musisz utworzyć konto i podać **identyfikator treści (CID)** w Twoim IPFS desktop. + +Osobiście uważam, że [4EVERLAND](https://docs.4everland.org/storage/4ever-pin/guides) jest najłatwiejszy w użyciu. Oto wskazówki: + +1. Przejdź do [pulpitu nawigacyjnego](https://dashboard.4everland.org/overview) i zaloguj się za pomocą swojego portfela. + +2. W lewym panelu bocznym kliknij **Przechowywanie > 4EVER Pin**. + +3. Kliknij **Prześlij > Wybrany CID**. Nazwij swoją zawartość i podaj CID z IPFS desktop. Obecnie CID to ciąg znaków, który zaczyna się od `Qm`, po którym następują 44 litery i cyfry reprezentujące [zakodowany w base-58](https://medium.com/bootdotdev/base64-vs-base58-encoding-c25553ff4524) hasz, taki jak `QmaCuQ7yN6iyBjLmLGe8YiFuCwnePoKfVu6ue8vLBsLJQJ`, ale [prawdopodobnie to się zmieni](https://docs.ipfs.tech/concepts/content-addressing/#version-1-v1). + +4. Początkowy status to **W kolejce**. Odświeżaj, aż zmieni się na **Przypięty**. + +5. Kliknij swój CID, aby uzyskać link. Moją aplikację możesz zobaczyć [tutaj](https://bafybeifqka2odrne5b6l5guthqvbxu4pujko2i6rx2zslvr3qxs6u5o7im/). + +6. Może być konieczne aktywowanie konta, aby było ono przypięte dłużej niż przez miesiąc. Aktywacja konta kosztuje około 1 USD. Jeśli to zamkniesz, wyloguj się i zaloguj ponownie, aby ponownie zostać poproszonym o aktywację. + +## Używanie z IPFS {#using-from-ipfs} + +W tym momencie masz link do scentralizowanej bramki, która serwuje Twoją zawartość IPFS. Krótko mówiąc, Twój interfejs użytkownika może być nieco bezpieczniejszy, ale wciąż nie jest odporny na cenzurę. Dla prawdziwej odporności na cenzurę użytkownicy muszą korzystać z IPFS [bezpośrednio z przeglądarki](https://docs.ipfs.tech/install/ipfs-companion/#prerequisites). + +Gdy już to zainstalujesz (i gdy IPFS desktop działa), możesz przejść do [/ipfs/``](https://any.site/ipfs/bafybeifqka2odrne5b6l5guthqvbxu4pujko2i6rx2zslvr3qxs6u5o7im) na dowolnej stronie i otrzymasz tę zawartość, serwowaną w sposób zdecentralizowany. + +## Wady {#drawbacks} + +Nie można niezawodnie usuwać plików IPFS, więc dopóki modyfikujesz swój interfejs użytkownika, prawdopodobnie najlepiej jest albo pozostawić go scentralizowanym, albo użyć [międzyplanetarnego systemu nazw (IPNS)](https://docs.ipfs.tech/concepts/ipns/#mutability-in-ipfs), systemu, który zapewnia zmienność na bazie IPFS. Oczywiście wszystko, co jest zmienne, może być cenzurowane, w przypadku IPNS poprzez wywieranie presji na osobę posiadającą klucz prywatny, któremu on odpowiada. + +Dodatkowo, niektóre pakiety mają problem z IPFS, więc jeśli Twoja strona internetowa jest bardzo skomplikowana, to może nie być to dobre rozwiązanie. I oczywiście nic, co opiera się na integracji z serwerem, nie może zostać zdecentralizowane tylko przez umieszczenie strony klienta w IPFS. + +## Wnioski {#conclusion} + +Tak jak Ethereum pozwala zdecentralizować bazę danych i aspekty logiki biznesowej Twojej dapki, tak IPFS pozwala zdecentralizować interfejs użytkownika. To pozwala wyeliminować kolejny wektor ataku przeciwko Twojej dapce. + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/kickstart-your-dapp-frontend-development-with-create-eth-app/index.md b/public/content/translations/pl/developers/tutorials/kickstart-your-dapp-frontend-development-with-create-eth-app/index.md new file mode 100644 index 00000000000..b67ef51a17d --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/kickstart-your-dapp-frontend-development-with-create-eth-app/index.md @@ -0,0 +1,111 @@ +--- +title: "Rozpocznij rozwój frontendu swojej dapki za pomocą create-eth-app" +description: "Przegląd sposobu użycia create-eth-app i jego funkcji" +author: "Markus Waas" +tags: + [ + "frontend", + "JavaScript", + "ethers.js", + "the graph", + "defi" + ] +skill: beginner +lang: pl +published: 2020-04-27 +source: soliditydeveloper.com +sourceUrl: https://soliditydeveloper.com/create-eth-app +--- + +Ostatnim razem przyjrzeliśmy się [ogólnemu obrazowi Solidity](https://soliditydeveloper.com/solidity-overview-2020) i wspomnieliśmy już o [create-eth-app](https://github.com/PaulRBerg/create-eth-app). Teraz dowiesz się, jak go używać, jakie funkcje są zintegrowane oraz poznasz dodatkowe pomysły na jego rozbudowę. Stworzona przez Paula Razvana Berga, założyciela [Sablier](http://sablier.com/), ta aplikacja przyspieszy rozwój Twojego frontendu i oferuje kilka opcjonalnych integracji do wyboru. + +## Instalacja {#installation} + +Instalacja wymaga Yarn w wersji 0.25 lub wyższej (`npm install yarn --global`). To tak proste, jak uruchomienie: + +```bash +yarn create eth-app my-eth-app +cd my-eth-app +yarn react-app:start +``` + +W tle wykorzystuje [create-react-app](https://github.com/facebook/create-react-app). Aby zobaczyć swoją aplikację, otwórz `http://localhost:3000/`. Gdy będziesz gotowy do wdrożenia na produkcję, utwórz zminimalizowany pakiet za pomocą yarn build. Jednym z łatwych sposobów na hostowanie tego jest [Netlify](https://www.netlify.com/). Możesz utworzyć repozytorium na GitHubie, dodać je do Netlify, skonfigurować polecenie kompilacji i gotowe! Twoja aplikacja będzie hostowana i dostępna dla wszystkich. A wszystko to bezpłatnie. + +## Funkcje {#features} + +### React i create-react-app {#react--create-react-app} + +Przede wszystkim serce aplikacji: React i wszystkie dodatkowe funkcje, które oferuje _create-react-app_. Używanie tylko tego jest świetną opcją, jeśli nie chcesz integrować Ethereum. [React](https://react.dev/) sam w sobie bardzo ułatwia tworzenie interaktywnych interfejsów użytkownika. Może nie jest tak przyjazny dla początkujących jak [Vue](https://vuejs.org/), ale nadal jest najczęściej używany, ma więcej funkcji, a co najważniejsze, oferuje tysiące dodatkowych bibliotek do wyboru. _create-react-app_ sprawia, że rozpoczęcie pracy z nim jest również bardzo łatwe i obejmuje: + +- Wsparcie dla składni React, JSX, ES6, TypeScript i Flow. +- Dodatki językowe wykraczające poza ES6, takie jak operator spread dla obiektów. +- Automatyczne dodawanie prefiksów w CSS, dzięki czemu nie potrzebujesz -webkit- ani innych prefiksów. +- Szybkie, interaktywne narzędzie do uruchamiania testów jednostkowych z wbudowanym wsparciem dla raportowania pokrycia kodu testami. +- Serwer deweloperski działający na żywo, który ostrzega o typowych błędach. +- Skrypt kompilacji do tworzenia pakietów JS, CSS i obrazów na produkcję, z haszami i sourcemapami. + +_create-eth-app_ w szczególności wykorzystuje nowe [efekty hooków](https://legacy.reactjs.org/docs/hooks-effect.html). Metoda pisania potężnych, a jednocześnie bardzo małych, tak zwanych komponentów funkcyjnych. Zobacz poniższą sekcję o Apollo, aby dowiedzieć się, jak są one używane w _create-eth-app_. + +### Yarn Workspaces {#yarn-workspaces} + +[Yarn Workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) pozwalają na posiadanie wielu pakietów, ale umożliwiają zarządzanie nimi wszystkimi z folderu głównego oraz instalowanie zależności dla wszystkich naraz za pomocą `yarn install`. Ma to szczególny sens w przypadku mniejszych dodatkowych pakietów, takich jak zarządzanie adresami/ABI inteligentnych kontraktów (informacje o tym, gdzie wdrożono które inteligentne kontrakty i jak się z nimi komunikować) lub integracja z The Graph, obie będące częścią `create-eth-app`. + +### ethers.js {#ethersjs} + +Chociaż [Web3](https://docs.web3js.org/) jest nadal najczęściej używane, [ethers.js](https://docs.ethers.io/) zyskuje w ostatnim roku na popularności jako alternatywa i to właśnie ono jest zintegrowane z _create-eth-app_. Możesz z nim pracować, zmienić go na Web3 lub rozważyć aktualizację do [ethers.js v5](https://docs.ethers.org/v5/), który jest już prawie po fazie beta. + +### The Graph {#the-graph} + +[GraphQL](https://graphql.org/) to alternatywny sposób obsługi danych w porównaniu z [Restful API](https://restfulapi.net/). Mają one kilka zalet w porównaniu z Restful API, szczególnie w przypadku zdecentralizowanych danych blockchain. Jeśli interesują Cię powody, które za tym stoją, zapoznaj się z [GraphQL Will Power the Decentralized Web](https://medium.com/graphprotocol/graphql-will-power-the-decentralized-web-d7443a69c69a). + +Zazwyczaj pobiera się dane bezpośrednio ze swojego inteligentnego kontraktu. Chcesz odczytać czas ostatniej transakcji? Wystarczy wywołać `MyContract.methods.latestTradeTime().call()`, co pobiera dane z węzła Ethereum do Twojej dapki. Ale co, jeśli potrzebujesz setek różnych punktów danych? Skutkowałoby to setkami zapytań o dane do węzła, z których każde wymagałoby [RTT](https://wikipedia.org/wiki/Round-trip_delay_time), co sprawiłoby, że Twoja dapka byłaby wolna i nieefektywna. Jednym z obejść może być funkcja pobierająca w Twoim kontrakcie, która zwraca wiele danych naraz. Nie zawsze jest to jednak idealne rozwiązanie. + +Możesz być również zainteresowany danymi historycznymi. Chcesz znać nie tylko czas ostatniej transakcji, ale także czasy wszystkich transakcji, które kiedykolwiek wykonałeś. Użyj pakietu podgrafu _create-eth-app_, przeczytaj [dokumentację](https://thegraph.com/docs/en/subgraphs/developing/creating/starting-your-subgraph) i dostosuj go do własnych kontraktów. Jeśli szukasz popularnych inteligentnych kontraktów, być może istnieje już dla nich podgraf. Sprawdź [eksplorator podgrafów](https://thegraph.com/explorer/). + +Gdy masz już podgraf, pozwala on na napisanie jednego prostego zapytania w Twojej dapce, które pobiera wszystkie potrzebne, ważne dane z blockchainu, w tym historyczne, wymagając tylko jednego pobrania. + +### Apollo {#apollo} + +Dzięki integracji z [Apollo Boost](https://www.apollographql.com/docs/react/get-started/) możesz łatwo zintegrować The Graph w swojej dapce React. Szczególnie przy użyciu [hooków React i Apollo](https://www.apollographql.com/blog/apollo-client-now-with-react-hooks), pobieranie danych jest tak proste, jak napisanie pojedynczego zapytania GraphQl w Twoim komponencie: + +```js +const { loading, error, data } = useQuery(myGraphQlQuery) + +React.useEffect(() => { + if (!loading && !error && data) { + console.log({ data }) + } +}, [loading, error, data]) +``` + +## Szablony {#templates} + +Dodatkowo możesz wybrać jeden z kilku różnych szablonów. Do tej pory można używać integracji z Aave, Compound, UniSwap lub sablier. Wszystkie one dodają ważne adresy inteligentnych kontraktów usług wraz z gotowymi integracjami podgrafów. Wystarczy dodać szablon do polecenia tworzenia, np. `yarn create eth-app my-eth-app --with-template aave`. + +### Aave {#aave} + +[Aave](https://aave.com/) to zdecentralizowany rynek pożyczek pieniężnych. Deponenci zapewniają płynność rynkowi, aby zarabiać pasywny dochód, podczas gdy pożyczkobiorcy mogą pożyczać, używając zabezpieczeń. Jedną z unikalnych cech Aave są [pożyczki błyskawiczne](https://aave.com/docs/developers/flash-loans), które pozwalają pożyczać pieniądze bez żadnego zabezpieczenia, o ile pożyczka zostanie zwrócona w ramach jednej transakcji. Może to być przydatne na przykład do uzyskania dodatkowej gotówki w handlu arbitrażowym. + +Tokeny, którymi się handluje i które przynoszą odsetki, nazywane są _aTokenami_. + +Gdy zdecydujesz się zintegrować Aave z _create-eth-app_, otrzymasz [integrację z podgrafem](https://docs.aave.com/developers/getting-started/using-graphql). Aave używa The Graph i już udostępnia kilka gotowych do użycia podgrafów na [Ropsten](https://thegraph.com/explorer/subgraph/aave/protocol-ropsten) i w [sieci głównej](https://thegraph.com/explorer/subgraph/aave/protocol) w formie [surowej](https://thegraph.com/explorer/subgraph/aave/protocol-raw) lub [sformatowanej](https://thegraph.com/explorer/subgraph/aave/protocol). + +![Mem o pożyczkach błyskawicznych Aave – „Taaak, gdybym mógł utrzymać moją pożyczkę błyskawiczną dłużej niż 1 transakcję, byłoby wspaniale”](./flashloan-meme.png) + +### Compound {#compound} + +[Compound](https://compound.finance/) jest podobny do Aave. Integracja obejmuje już nowy [podgraf Compound v2](https://medium.com/graphprotocol/https-medium-com-graphprotocol-compound-v2-subgraph-highlight-a5f38f094195). Tokeny przynoszące tutaj odsetki nazywają się, co zaskakujące, _cTokenami_. + +### Uniswap {#uniswap} + +[Uniswap](https://uniswap.exchange/) to zdecentralizowana giełda (DEX). Dostawcy płynności mogą zarabiać na opłatach, dostarczając wymagane tokeny lub ether dla obu stron transakcji. Jest powszechnie używany i dlatego ma jedną z najwyższych płynności dla bardzo szerokiej gamy tokenów. Możesz łatwo zintegrować go w swojej dapce, aby na przykład umożliwić użytkownikom wymianę ETH na DAI. + +Niestety, w momencie pisania tego tekstu integracja dotyczy tylko Uniswap v1, a nie [niedawno wydanej wersji v2](https://uniswap.org/blog/uniswap-v2/). + +### Sablier {#sablier} + +[Sablier](https://sablier.com/) umożliwia użytkownikom strumieniowanie płatności pieniężnych. Zamiast jednej wypłaty, otrzymujesz pieniądze na bieżąco bez dodatkowej administracji po wstępnej konfiguracji. Integracja obejmuje [własny podgraf](https://thegraph.com/explorer/subgraph/sablierhq/sablier). + +## Co dalej? {#whats-next} + +Jeśli masz pytania dotyczące _create-eth-app_, przejdź na [serwer społeczności Sablier](https://discord.gg/bsS8T47), gdzie możesz skontaktować się z autorami _create-eth-app_. Jako pierwsze kolejne kroki możesz zechcieć zintegrować framework UI, taki jak [Material UI](https://mui.com/material-ui/), napisać zapytania GraphQL dla danych, których faktycznie potrzebujesz i skonfigurować wdrożenie. diff --git a/public/content/translations/pl/developers/tutorials/learn-foundational-ethereum-topics-with-sql/index.md b/public/content/translations/pl/developers/tutorials/learn-foundational-ethereum-topics-with-sql/index.md new file mode 100644 index 00000000000..f8aa4aee4ed --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/learn-foundational-ethereum-topics-with-sql/index.md @@ -0,0 +1,269 @@ +--- +title: "Naucz się podstawowych zagadnień Ethereum za pomocą SQL" +description: "Ten samouczek pomaga czytelnikom zrozumieć podstawowe koncepcje Ethereum, w tym transakcje, bloki i gaz, poprzez odpytywanie danych w łańcuchu za pomocą języka zapytań strukturalnych (SQL)." +author: "Paul Apivat" +tags: [ "SQL", "Wykonywanie zapytań", "Transakcje" ] +skill: beginner +lang: pl +published: 2021-05-11 +source: paulapivat.com +sourceUrl: https://paulapivat.com/post/query_ethereum/ +--- + +Wiele samouczków Ethereum jest skierowanych do deweloperów, ale brakuje zasobów edukacyjnych dla analityków danych lub dla osób, które chcą zobaczyć dane w łańcuchu bez uruchamiania klienta lub węzła. + +Ten samouczek pomaga czytelnikom zrozumieć podstawowe koncepcje Ethereum, w tym transakcje, bloki i gaz, poprzez odpytywanie danych w łańcuchu za pomocą języka zapytań strukturalnych (SQL) za pośrednictwem interfejsu dostarczanego przez [Dune Analytics](https://dune.com/). + +Dane w łańcuchu mogą pomóc nam zrozumieć Ethereum, sieć i ekonomię mocy obliczeniowej oraz powinny służyć jako podstawa do zrozumienia wyzwań, przed którymi stoi dziś Ethereum (tj. rosnących cen gazu), a co ważniejsze, dyskusji na temat rozwiązań skalujących. + +### Transakcje {#transactions} + +Podróż użytkownika w Ethereum rozpoczyna się od zainicjowania konta kontrolowanego przez użytkownika lub podmiotu z saldem ETH. Istnieją dwa typy kont – kontrolowane przez użytkownika lub inteligentny kontrakt (zobacz [ethereum.org](/developers/docs/accounts/)). + +Każde konto można wyświetlić w eksploratorze bloków, takim jak [Etherscan](https://etherscan.io/) lub [Blockscout](https://eth.blockscout.com/). Eksploratory bloków to portal do danych Ethereum. Wyświetlają w czasie rzeczywistym dane o blokach, transakcjach, górnikach, kontach i innej aktywności w łańcuchu (zobacz [tutaj](/developers/docs/data-and-analytics/block-explorers/)). + +Jednak użytkownik może chcieć bezpośrednio odpytać dane, aby uzgodnić informacje dostarczane przez zewnętrzne eksploratory bloków. [Dune Analytics](https://dune.com/) zapewnia tę możliwość każdemu, kto ma pewną wiedzę na temat SQL. + +Dla porównania, konto inteligentnego kontraktu Fundacji Ethereum (EF) można zobaczyć na [Blockscout](https://eth.blockscout.com/address/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe). + +Należy zauważyć, że wszystkie konta, w tym EF, mają publiczny adres, który może być używany do wysyłania i odbierania transakcji. + +Saldo konta na Etherscan obejmuje transakcje zwykłe i transakcje wewnętrzne. Transakcje wewnętrzne, wbrew nazwie, nie są _faktycznymi_ transakcjami, które zmieniają stan łańcucha. Są to transfery wartości inicjowane przez wykonanie kontraktu ([źródło](https://ethereum.stackexchange.com/questions/3417/how-to-get-contract-internal-transactions)). Ponieważ transakcje wewnętrzne nie mają podpisu, **nie** są one uwzględniane w blockchainie i nie można ich odpytywać za pomocą Dune Analytics. + +Dlatego ten samouczek skupi się na zwykłych transakcjach. Można to zrobić za pomocą następującego zapytania: + +```sql +WITH temp_table AS ( +SELECT + hash, + block_number, + block_time, + "from", + "to", + value / 1e18 AS ether, + gas_used, + gas_price / 1e9 AS gas_price_gwei +FROM ethereum."transactions" +WHERE "to" = '\xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe' +ORDER BY block_time DESC +) +SELECT + hash, + block_number, + block_time, + "from", + "to", + ether, + (gas_used * gas_price_gwei) / 1e9 AS txn_fee +FROM temp_table +``` + +Spowoduje to uzyskanie tych samych informacji, co na stronie transakcji Etherscan. Dla porównania, oto dwa źródła: + +#### Etherscan {#etherscan} + +![](./etherscan_view.png) + +[Strona kontraktu EF na Blockscout.](https://eth.blockscout.com/address/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe) + +#### Dune Analytics {#dune-analytics} + +![](./dune_view.png) + +Pulpit nawigacyjny można znaleźć [tutaj](https://dune.com/paulapivat/Learn-Ethereum). Kliknij tabelę, aby zobaczyć zapytanie (zobacz również powyżej). + +### Analiza transakcji {#breaking_down_transactions} + +Przesłana transakcja zawiera kilka informacji, w tym ([źródło](/developers/docs/transactions/)): + +- **Odbiorca**: adres odbiorcy (w zapytaniu jako "to") +- **Podpis**: chociaż klucze prywatne nadawcy podpisują transakcję, to, co możemy odpytać za pomocą SQL, to publiczny adres nadawcy ("from"). +- **Wartość**: jest to ilość przesłanego ETH (zobacz kolumnę `ether`). +- **Dane**: Są to dowolne dane, które zostały zhaszowane (zobacz kolumnę `data`) +- **gasLimit** – maksymalna ilość jednostek gazu, które mogą zostać zużyte przez transakcję. Jednostki gazu reprezentują kroki obliczeniowe +- **maxPriorityFeePerGas** – maksymalna ilość gazu, która zostanie uwzględniona jako napiwek dla górnika +- **maxFeePerGas** – maksymalna kwota za gaz, jaką użytkownik jest skłonny zapłacić za transakcję (włączając baseFeePerGas i maxPriorityFeePerGas) + +Możemy zapytać o te konkretne informacje dotyczące transakcji na publiczny adres Fundacji Ethereum: + +```sql +SELECT + "to", + "from", + value / 1e18 AS ether, + data, + gas_limit, + gas_price / 1e9 AS gas_price_gwei, + gas_used, + ROUND(((gas_used / gas_limit) * 100),2) AS gas_used_pct +FROM ethereum."transactions" +WHERE "to" = '\xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe' +ORDER BY block_time DESC +``` + +### Bloki {#blocks} + +Każda transakcja zmieni stan Wirtualnej Maszyny Ethereum ([EVM](/developers/docs/evm/)) ([źródło](/developers/docs/transactions/)). Transakcje są rozgłaszane w sieci w celu weryfikacji i włączenia do bloku. Każda transakcja jest powiązana z numerem bloku. Aby zobaczyć dane, możemy odpytać konkretny numer bloku: 12396854 (najnowszy blok wśród transakcji Fundacji Ethereum w momencie pisania tego tekstu, 11.05.21). + +Co więcej, gdy wykonamy zapytanie dla dwóch kolejnych bloków, zobaczymy, że każdy blok zawiera hasz poprzedniego bloku (tj. hasz nadrzędny), co ilustruje sposób tworzenia blockchainu. + +Każdy blok zawiera odniesienie do swojego bloku nadrzędnego. Jest to pokazane poniżej między kolumnami `hash` i `parent_hash` ([źródło](/developers/docs/blocks/)): + +![parent_hash](./parent_hash.png) + +Oto [zapytanie](https://dune.com/queries/44856/88292) w Dune Analytics: + +```sql +SELECT + time, + number, + hash, + parent_hash, + nonce +FROM ethereum."blocks" +WHERE "number" = 12396854 OR "number" = 12396855 OR "number" = 12396856 +LIMIT 10 +``` + +Możemy zbadać blok, odpytując o czas, numer bloku, trudność, hasz, hasz nadrzędny i nonce. + +Jedyne, czego to zapytanie nie obejmuje, to _lista transakcji_, która wymaga osobnego zapytania poniżej, oraz _korzeń stanu_. Pełny lub archiwalny węzeł przechowuje wszystkie transakcje i przejścia stanów, pozwalając klientom na odpytywanie stanu łańcucha w dowolnym momencie. Ponieważ wymaga to dużej przestrzeni dyskowej, możemy oddzielić dane łańcucha od danych stanu: + +- Dane łańcucha (lista bloków, transakcji) +- Dane stanu (wynik przejścia stanu każdej transakcji) + +Korzeń stanu należy do tej drugiej kategorii i jest daną _niejawną_ (nie jest przechowywany w łańcuchu), podczas gdy dane łańcucha są jawne i przechowywane w samym łańcuchu ([źródło](https://ethereum.stackexchange.com/questions/359/where-is-the-state-data-stored)). + +W tym samouczku skupimy się na danych w łańcuchu, które _można_ odpytywać za pomocą SQL za pośrednictwem Dune Analytics. + +Jak wspomniano powyżej, każdy blok zawiera listę transakcji, możemy to odpytać, filtrując według konkretnego bloku. Spróbujemy z najnowszym blokiem, 12396854: + +```sql +SELECT * FROM ethereum."transactions" +WHERE block_number = 12396854 +ORDER BY block_time DESC` +``` + +Oto wynik SQL w Dune: + +![](./list_of_txn.png) + +Ten pojedynczy blok dodany do łańcucha zmienia stan Wirtualnej Maszyny Ethereum ([EVM](/developers/docs/evm/)). Jednocześnie weryfikowane są dziesiątki, a czasem setki transakcji. W tym konkretnym przypadku uwzględniono 222 transakcje. + +Aby zobaczyć, ile z nich faktycznie zakończyło się sukcesem, dodalibyśmy kolejny filtr do zliczania udanych transakcji: + +```sql +WITH temp_table AS ( + SELECT * FROM ethereum."transactions" + WHERE block_number = 12396854 AND success = true + ORDER BY block_time DESC +) +SELECT + COUNT(success) AS num_successful_txn +FROM temp_table +``` + +Dla bloku 12396854, z 222 wszystkich transakcji, 204 zostały pomyślnie zweryfikowane: + +![](./successful_txn.png) + +Żądania transakcji pojawiają się dziesiątki razy na sekundę, ale bloki są zatwierdzane mniej więcej raz na 15 sekund ([źródło](/developers/docs/blocks/)). + +Aby zobaczyć, że jeden blok jest produkowany mniej więcej co 15 sekund, możemy wziąć liczbę sekund w ciągu dnia (86400) i podzielić ją przez 15, aby uzyskać szacunkową średnią liczbę bloków dziennie (~ 5760). + +Wykres dla bloków Ethereum produkowanych dziennie (2016 – obecnie) to: + +![](./daily_blocks.png) + +Średnia liczba produkowanych dziennie bloków w tym okresie wynosi ~5874: + +![](./avg_daily_blocks.png) + +Zapytania są następujące: + +```sql +# zapytanie wizualizujące liczbę bloków produkowanych dziennie od 2016 roku + +SELECT + DATE_TRUNC('day', time) AS dt, + COUNT(*) AS block_count +FROM ethereum."blocks" +GROUP BY dt +OFFSET 1 + +# średnia liczba bloków produkowanych dziennie + +WITH temp_table AS ( +SELECT + DATE_TRUNC('day', time) AS dt, + COUNT(*) AS block_count +FROM ethereum."blocks" +GROUP BY dt +OFFSET 1 +) +SELECT + AVG(block_count) AS avg_block_count +FROM temp_table +``` + +Średnia liczba bloków produkowanych dziennie od 2016 roku jest nieco powyżej tej liczby i wynosi 5874. Alternatywnie, podzielenie 86400 sekund przez 5874 średnich bloków daje 14,7 sekundy, czyli około jednego bloku co 15 sekund. + +### Gaz {#gas} + +Bloki mają ograniczony rozmiar. Maksymalny rozmiar bloku jest dynamiczny i zmienia się w zależności od zapotrzebowania sieci między 12 500 000 a 25 000 000 jednostek. Limity są wymagane, aby zapobiec arbitralnie dużym rozmiarom bloków obciążającym pełne węzły pod względem przestrzeni dyskowej i wymagań dotyczących prędkości ([źródło](/developers/docs/blocks/)). + +Jednym ze sposobów konceptualizacji limitu gazu w bloku jest myślenie o nim jako o **podaży** dostępnej przestrzeni blokowej, w której można grupować transakcje. Limit gazu w bloku można odpytywać i wizualizować od 2016 roku do dnia dzisiejszego: + +![](./avg_gas_limit.png) + +```sql +SELECT + DATE_TRUNC('day', time) AS dt, + AVG(gas_limit) AS avg_block_gas_limit +FROM ethereum."blocks" +GROUP BY dt +OFFSET 1 +``` + +Następnie jest rzeczywisty gaz zużywany codziennie do opłacenia obliczeń wykonanych w łańcuchu Ethereum (tj. wysyłanie transakcji, wywoływanie inteligentnego kontraktu, mintowanie NFT). To jest **popyt** na dostępną przestrzeń blokową Ethereum: + +![](./daily_gas_used.png) + +```sql +SELECT + DATE_TRUNC('day', time) AS dt, + AVG(gas_used) AS avg_block_gas_used +FROM ethereum."blocks" +GROUP BY dt +OFFSET 1 +``` + +Możemy również zestawić te dwa wykresy razem, aby zobaczyć, jak układają się **popyt i podaż**: + +![gas_demand_supply](./gas_demand_supply.png) + +Dlatego możemy rozumieć ceny gazu jako funkcję popytu na przestrzeń blokową Ethereum, przy dostępnej podaży. + +Wreszcie, możemy chcieć odpytać o średnie dzienne ceny gazu dla łańcucha Ethereum, jednak spowoduje to szczególnie długi czas zapytania, więc przefiltrujemy nasze zapytanie do średniej ilości gazu płaconego za transakcję przez Fundację Ethereum. + +![](./ef_daily_gas.png) + +Możemy zobaczyć ceny gazu zapłacone za wszystkie transakcje dokonane na adres Fundacji Ethereum na przestrzeni lat. Oto zapytanie: + +```sql +SELECT + block_time, + gas_price / 1e9 AS gas_price_gwei, + value / 1e18 AS eth_sent +FROM ethereum."transactions" +WHERE "to" = '\xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe' +ORDER BY block_time DESC +``` + +### Podsumowanie {#summary} + +Dzięki temu samouczkowi rozumiemy podstawowe koncepcje Ethereum i sposób działania blockchainu Ethereum poprzez odpytywanie i poznawanie danych w łańcuchu. + +Pulpit nawigacyjny, który zawiera cały kod użyty w tym samouczku, można znaleźć [tutaj](https://dune.com/paulapivat/Learn-Ethereum). + +Aby dowiedzieć się więcej o wykorzystaniu danych do eksploracji web3, [znajdź mnie na Twitterze](https://twitter.com/paulapivat). diff --git a/public/content/translations/pl/developers/tutorials/logging-events-smart-contracts/index.md b/public/content/translations/pl/developers/tutorials/logging-events-smart-contracts/index.md index af0aba8bf15..8a88ffa4d66 100644 --- a/public/content/translations/pl/developers/tutorials/logging-events-smart-contracts/index.md +++ b/public/content/translations/pl/developers/tutorials/logging-events-smart-contracts/index.md @@ -1,12 +1,8 @@ --- -title: Rejestrowanie danych z inteligentnych kontraktów ze zdarzeniami -description: Wprowadzenie do zdarzeń kontraktów inteligentnych i sposobów używania ich rejestrowania danych +title: "Rejestrowanie danych z inteligentnych kontraktów za pomocą zdarzeń" +description: "Wprowadzenie do zdarzeń w inteligentnych kontraktach i sposobów ich wykorzystania do rejestrowania danych" author: "jdourlens" -tags: - - "inteligentne kontrakty" - - "remix" - - "solidity" - - "zdarzenia" +tags: [ "smart kontrakty", "remix", "solidity", "zdarzenia" ] skill: intermediate lang: pl published: 2020-04-03 @@ -15,9 +11,9 @@ sourceUrl: https://ethereumdev.io/logging-data-with-events/ address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE" --- -W Solidity [zdarzenia](/developers/docs/smart-contracts/anatomy/#events-and-logs) to wysyłane sygnały, które mogą uruchamiać inteligentne kontrakty. Aplikacje zdecentralizowane lub wszystko, co jest połączone z interfejsem API Ethereum JSON-RPC, może nasłuchiwać tych zdarzeń i odpowiednio działać. Zdarzenie można również zindeksować, aby później można było przeszukiwać historię zdarzeń. +W Solidity [zdarzenia](/developers/docs/smart-contracts/anatomy/#events-and-logs) to wysyłane sygnały, które inteligentne kontrakty mogą emitować. Dapki lub wszystko, co jest połączone z API JSON-RPC Ethereum, może nasłuchiwać tych zdarzeń i odpowiednio działać. Zdarzenie można również zindeksować, aby później można było przeszukiwać historię zdarzeń. -## Zdarzeniami {#events} +## Zdarzenia {#events} Najczęstszym zdarzeniem na blockchainie Ethereum w momencie pisania tego artykułu jest zdarzenie Transfer, które jest emitowane przez tokeny ERC20, gdy ktoś przenosi tokeny. @@ -25,24 +21,42 @@ Najczęstszym zdarzeniem na blockchainie Ethereum w momencie pisania tego artyku event Transfer(address indexed from, address indexed to, uint256 value); ``` -Sygnatura zdarzenia jest deklarowana w kodzie kontraktu i może być emitowana za pomocą słowa kluczowego emit. Na przykład dzienniki zdarzeń transferu, nadawca transferu (od), odbiorca transferu (_do_) i ilość przeniesionych tokenów (_wartość_). +Sygnatura zdarzenia jest deklarowana w kodzie kontraktu i może być emitowana za pomocą słowa kluczowego emit. Na przykład zdarzenie transferu rejestruje, kto wysłał transfer (_from_), do kogo (_to_) i ile tokenów zostało przeniesionych (_value_). -Jeśli wrócimy do naszego inteligentnego kontraktu Counter i zdecydujemy się rejestrować za każdym razem, gdy wartość się zmieni. Ponieważ kontrakt ten nie jest przeznaczony do wdrożenia, ale służy jako podstawa do zbudowania kolejnego kontraktu poprzez jego rozszerzenie: nazywa się to kontraktem abstrakcyjnym. W przypadku naszego przykładu counter wyglądałoby to tak: +Jeśli wrócimy do naszego inteligentnego kontraktu Counter i zdecydujemy się rejestrować za każdym razem, gdy wartość się zmieni. Ponieważ kontrakt ten nie jest przeznaczony do wdrożenia, ale służy jako podstawa do zbudowania kolejnego kontraktu poprzez jego rozszerzenie: nazywa się go kontraktem abstrakcyjnym. W przypadku naszego przykładu z licznikiem Counter wyglądałoby to tak: ```solidity -pragma solidity 0.5.17;contract Counter { event ValueChanged(uint oldValue, uint256 newValue); // Zmienna prywatna typu unsigned int przechowywania liczby zliczeń uint256 private count = 0; // Funkcja zwiększająca licznik function increment() public { count += 1; emit ValueChanged(count - 1, count); } +pragma solidity 0.5.17; - // Getter w celu uzyskania wartości zliczeń function getCount() public view returns (uint256) { return count; }} +contract Counter { + + event ValueChanged(uint oldValue, uint256 newValue); + + // Prywatna zmienna typu unsigned int do przechowywania liczby zliczeń + uint256 private count = 0; + + // Funkcja, która zwiększa nasz licznik + function increment() public { + count += 1; + emit ValueChanged(count - 1, count); + } + + // Getter do pobrania wartości licznika + function getCount() public view returns (uint256) { + return count; + } + +} ``` Zauważ, że: -- **Wiersz 5**: deklarujemy nasze wydarzenie i jego zawartość, starą wartość i nową wartość. +- **Wiersz 5**: deklarujemy nasze zdarzenie i to, co zawiera: starą i nową wartość. - **Wiersz 13**: Kiedy zwiększamy naszą zmienną count, emitujemy zdarzenie. -Jeżeli teraz wdrożymy umowę i wywołamy funkcję inkrementacji, zobaczymy, że Remix automatycznie ją wyświetli, jeśli zostanie kliknięta nowa transakcja w tablicy nazwanych rejestrów. +Jeżeli teraz wdrożymy kontrakt i wywołamy funkcję `increment`, zobaczymy, że Remix automatycznie to wyświetli, gdy klikniemy nową transakcję w tablicy o nazwie `logs`. -![Zrzut ekranu Remix](./remix-screenshot.png) +![Zrzut ekranu z Remix](./remix-screenshot.png) -Dzienniki są naprawdę przydatne do debugowania inteligentnych kontraktów, ale są również ważne, jeśli tworzysz aplikacje używane przez różne osoby i ułatwiają analizę w celu śledzenia i zrozumienia, w jaki sposób jest używany inteligentny kontrakt. Logi generowane przez transakcje są wyświetlane w popularnych eksploratorach bloków i można je również wykorzystać np. do tworzenia skryptów off-chain do nasłuchiwania określonych zdarzeń i podejmowania działań w momencie ich wystąpienia. +Logi są bardzo przydatne do debugowania inteligentnych kontraktów, ale są również ważne, jeśli tworzysz aplikacje używane przez różne osoby, ponieważ ułatwiają przeprowadzanie analiz w celu śledzenia i zrozumienia, w jaki sposób używane są Twoje inteligentne kontrakty. Logi generowane przez transakcje są wyświetlane w popularnych eksploratorach bloków i można je również wykorzystać, np. do tworzenia skryptów offchain do nasłuchiwania określonych zdarzeń i podejmowania działań w momencie ich wystąpienia. diff --git a/public/content/translations/pl/developers/tutorials/merkle-proofs-for-offline-data-integrity/index.md b/public/content/translations/pl/developers/tutorials/merkle-proofs-for-offline-data-integrity/index.md new file mode 100644 index 00000000000..22dd6f02b71 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/merkle-proofs-for-offline-data-integrity/index.md @@ -0,0 +1,246 @@ +--- +title: "Dowody Merklego dla integralności danych offline" +description: "Zapewnienie integralności danych w łańcuchu dla danych, które są przechowywane głównie poza łańcuchem" +author: Ori Pomerantz +tags: [ "przechowywanie" ] +skill: advanced +lang: pl +published: 2021-12-30 +--- + +## Wprowadzenie {#introduction} + +Idealnie byłoby przechowywać wszystko w pamięci masowej Ethereum, która jest przechowywana na tysiącach komputerów i ma niezwykle wysoką dostępność (dane nie mogą być cenzurowane) i integralność (danych nie można modyfikować w nieautoryzowany sposób), ale przechowywanie 32-bajtowego słowa kosztuje zazwyczaj 20 000 jednostek gazu. W chwili pisania tego tekstu koszt ten odpowiada 6,60 USD. Przy cenie 21 centów za bajt jest to zbyt drogie dla wielu zastosowań. + +Aby rozwiązać ten problem, ekosystem Ethereum opracował [wiele alternatywnych sposobów przechowywania danych w zdecentralizowany sposób](/developers/docs/storage/) Zazwyczaj wiążą się one z kompromisem między dostępnością a ceną. Jednak integralność jest zazwyczaj zapewniona. + +W tym artykule dowiesz się, **jak** zapewnić integralność danych bez przechowywania ich na blockchainie, używając [dowodów Merklego](https://computersciencewiki.org/index.php/Merkle_proof). + +## Jak to działa? {#how-does-it-work} + +Teoretycznie moglibyśmy po prostu przechowywać hasz danych w łańcuchu i wysyłać wszystkie dane w transakcjach, które ich wymagają. Jest to jednak nadal zbyt drogie. Bajt danych w transakcji kosztuje około 16 jednostek gazu, obecnie około pół centa, czyli około 5 USD za kilobajt. Przy cenie 5000 USD za megabajt jest to wciąż zbyt drogie dla wielu zastosowań, nawet bez dodatkowego kosztu haszowania danych. + +Rozwiązaniem jest wielokrotne haszowanie różnych podzbiorów danych, dzięki czemu dla danych, których nie trzeba wysyłać, można po prostu wysłać hasz. Robisz to za pomocą drzewa Merklego, struktury danych w formie drzewa, w której każdy węzeł jest haszem węzłów znajdujących się pod nim: + +![Drzewo Merklego](tree.png) + +Hasz główny jest jedyną częścią, która musi być przechowywana w łańcuchu. Aby udowodnić określoną wartość, należy podać wszystkie hasze, które należy z nią połączyć, aby uzyskać hasz główny. Na przykład, aby udowodnić `C`, podajesz `D`, `H(A-B)` i `H(E-H)`. + +![Dowód wartości C](proof-c.png) + +## Implementacja {#implementation} + +[Przykładowy kod jest dostępny tutaj](https://github.com/qbzzt/merkle-proofs-for-offline-data-integrity). + +### Kod off-chain {#offchain-code} + +W tym artykule używamy JavaScript do obliczeń off-chain. Większość aplikacji zdecentralizowanych ma swój komponent off-chain napisany w JavaScript. + +#### Tworzenie haszu głównego Merkle {#creating-the-merkle-root} + +Najpierw musimy dostarczyć hasz główny Merkle do łańcucha. + +```javascript +const ethers = require("ethers") +``` + +[Używamy funkcji haszującej z pakietu ethers](https://docs.ethers.io/v5/api/utils/hashing/#utils-keccak256). + +```javascript +// Surowe dane, których integralność musimy zweryfikować. Pierwsze dwa bajty +// to identyfikator użytkownika, a ostatnie dwa bajty to ilość tokenów, które +// użytkownik aktualnie posiada. +const dataArray = [ + 0x0bad0010, 0x60a70020, 0xbeef0030, 0xdead0040, 0xca110050, 0x0e660060, + 0xface0070, 0xbad00080, 0x060d0091, +] +``` + +Kodowanie każdego wpisu w pojedynczą 256-bitową liczbę całkowitą skutkuje mniej czytelnym kodem niż na przykład użycie formatu JSON. Oznacza to jednak znacznie mniej przetwarzania w celu pobrania danych w kontrakcie, a więc znacznie niższe koszty gazu. [Można odczytywać JSON w łańcuchu](https://github.com/chrisdotn/jsmnSol), ale jest to zły pomysł, jeśli można go uniknąć. + +```javascript +// Tablica wartości haszy jako BigInts +const hashArray = dataArray +``` + +W tym przypadku nasze dane to na początek wartości 256-bitowe, więc nie jest potrzebne żadne przetwarzanie. Jeśli użyjemy bardziej skomplikowanej struktury danych, takiej jak ciągi znaków, musimy najpierw zhaszować dane, aby uzyskać tablicę haszy. Należy zauważyć, że dzieje się tak również dlatego, że nie zależy nam na tym, aby użytkownicy znali informacje o innych użytkownikach. W przeciwnym razie musielibyśmy haszować, aby użytkownik 1 nie znał wartości dla użytkownika 0, użytkownik 2 nie znał wartości dla użytkownika 3 itd. + +```javascript +// Konwertuj między ciągiem znaków, którego oczekuje funkcja haszująca, a +// BigInt, którego używamy wszędzie indziej. +const hash = (x) => + BigInt(ethers.utils.keccak256("0x" + x.toString(16).padStart(64, 0))) +``` + +Funkcja haszująca ethers oczekuje ciągu znaków JavaScript z liczbą szesnastkową, taką jak `0x60A7`, i odpowiada innym ciągiem o tej samej strukturze. Jednak w pozostałej części kodu łatwiej jest użyć `BigInt`, więc konwertujemy do ciągu szesnastkowego i z powrotem. + +```javascript +// Symetryczny hasz pary, więc nie będziemy się przejmować, jeśli kolejność zostanie odwrócona. +const pairHash = (a, b) => hash(hash(a) ^ hash(b)) +``` + +Ta funkcja jest symetryczna (hasz a [xor](https://en.wikipedia.org/wiki/Exclusive_or) b). Oznacza to, że sprawdzając dowód Merklego, nie musimy się martwić o to, czy umieścić wartość z dowodu przed czy po obliczonej wartości. Sprawdzanie dowodu Merklego odbywa się w łańcuchu, więc im mniej musimy tam robić, tym lepiej. + +Ostrzeżenie: +Kryptografia jest trudniejsza, niż się wydaje. +Początkowa wersja tego artykułu zawierała funkcję haszującą `hash(a^b)`. +To był **zły** pomysł, ponieważ oznaczał, że jeśli znałeś prawidłowe wartości `a` i `b`, mogłeś użyć `b' = a^b^a'`, aby udowodnić dowolną pożądaną wartość `a'`. +Dzięki tej funkcji trzeba by obliczyć `b'` tak, aby `hash(a') ^ hash(b')` było równe znanej wartości (następnej gałęzi w drodze do haszu głównego), co jest o wiele trudniejsze. + +```javascript +// Wartość oznaczająca, że dana gałąź jest pusta, nie +// ma wartości +const empty = 0n +``` + +Gdy liczba wartości nie jest całkowitą potęgą dwójki, musimy obsłużyć puste gałęzie. Sposób, w jaki ten program to robi, polega na umieszczeniu zera jako symbolu zastępczego. + +![Drzewo Merklego z brakującymi gałęziami](merkle-empty-hash.png) + +```javascript +// Oblicz jeden poziom w górę drzewa tablicy haszy, biorąc hasz +// każdej pary w sekwencji +const oneLevelUp = (inputArray) => { + var result = [] + var inp = [...inputArray] // Aby uniknąć nadpisywania danych wejściowych // Dodaj pustą wartość, jeśli to konieczne (musimy, aby wszystkie liście były // sparowane) + + if (inp.length % 2 === 1) inp.push(empty) + + for (var i = 0; i < inp.length; i += 2) + result.push(pairHash(inp[i], inp[i + 1])) + + return result +} // oneLevelUp +``` + +Ta funkcja „wspina się” o jeden poziom w drzewie Merklego, haszując pary wartości na bieżącej warstwie. Należy zauważyć, że nie jest to najbardziej wydajna implementacja, mogliśmy uniknąć kopiowania danych wejściowych i po prostu dodać `hashEmpty` w odpowiednim momencie w pętli, ale ten kod jest zoptymalizowany pod kątem czytelności. + +```javascript +const getMerkleRoot = (inputArray) => { + var result + + result = [...inputArray] // Wspinaj się po drzewie, aż zostanie tylko jedna wartość, czyli // hasz główny. // // Jeśli warstwa ma nieparzystą liczbę wpisów, // kod w oneLevelUp dodaje pustą wartość, więc jeśli mamy, na przykład, // 10 liści, będziemy mieli 5 gałęzi w drugiej warstwie, 3 // gałęzie w trzeciej, 2 w czwartej, a hasz główny jest piąty + + while (result.length > 1) result = oneLevelUp(result) + + return result[0] +} +``` + +Aby uzyskać hasz główny, wspinaj się, aż zostanie tylko jedna wartość. + +#### Tworzenie dowodu Merklego {#creating-a-merkle-proof} + +Dowód Merklego to wartości, które należy zhaszować wraz z udowadnianą wartością, aby otrzymać hasz główny Merkle. Wartość do udowodnienia jest często dostępna z innych danych, więc wolę podawać ją osobno, a nie jako część kodu. + +```javascript +// Dowód Merklego składa się z wartości listy wpisów do +// zhaszowania. Ponieważ używamy symetrycznej funkcji haszującej, nie +// potrzebujemy lokalizacji elementu, aby zweryfikować dowód, a jedynie, aby go utworzyć +const getMerkleProof = (inputArray, n) => { +    var result = [], currentLayer = [...inputArray], currentN = n + +    // Aż dotrzemy na szczyt +    while (currentLayer.length > 1) { +        // Brak warstw o nieparzystej długości +        if (currentLayer.length % 2) +            currentLayer.push(empty) + +        result.push(currentN % 2 +               // Jeśli currentN jest nieparzyste, dodaj do dowodu wartość przed nim +            ? currentLayer[currentN-1] +               // Jeśli jest parzyste, dodaj wartość po nim +            : currentLayer[currentN+1]) + +``` + +Haszujemy `(v[0],v[1])`, `(v[2],v[3])`, itd. Tak więc dla wartości parzystych potrzebujemy następnej, a dla nieparzystych poprzedniej. + +```javascript +        // Przejdź do następnej warstwy w górę +        currentN = Math.floor(currentN/2) +        currentLayer = oneLevelUp(currentLayer) +    }   // while currentLayer.length > 1 + +    return result +}   // getMerkleProof +``` + +### Kod on-chain {#onchain-code} + +Na koniec mamy kod, który sprawdza dowód. Kod on-chain jest napisany w [Solidity](https://docs.soliditylang.org/en/v0.8.11/). Optymalizacja jest tutaj o wiele ważniejsza, ponieważ gaz jest stosunkowo drogi. + +```solidity +//SPDX-License-Identifier: Public Domain +pragma solidity ^0.8.0; + +import "hardhat/console.sol"; +``` + +Napisałem to przy użyciu [środowiska programistycznego Hardhat](https://hardhat.org/), które pozwala nam uzyskać [dane wyjściowe konsoli z Solidity](https://hardhat.org/docs/cookbook/debug-logs) podczas programowania. + +```solidity + +contract MerkleProof { +    uint merkleRoot; + +    function getRoot() public view returns (uint) { +      return merkleRoot; +    } + +    // Wyjątkowo niezabezpieczone, w kodzie produkcyjnym dostęp do +    // tej funkcji MUSI być ściśle ograniczony, prawdopodobnie do +    // właściciela +    function setRoot(uint _merkleRoot) external { +      merkleRoot = _merkleRoot; +    }   // setRoot +``` + +Funkcje set i get dla korzenia Merkle. Zezwalanie wszystkim na aktualizację korzenia Merkle jest _wyjątkowo złym pomysłem_ w systemie produkcyjnym. Robię to tutaj dla uproszczenia przykładowego kodu. **Nie rób tego w systemie, w którym integralność danych ma faktycznie znaczenie**. + +```solidity +    function hash(uint _a) internal pure returns(uint) { +      return uint(keccak256(abi.encode(_a))); +    } + +    function pairHash(uint _a, uint _b) internal pure returns(uint) { +      return hash(hash(_a) ^ hash(_b)); +    } +``` + +Ta funkcja generuje hasz pary. Jest to po prostu tłumaczenie kodu JavaScript dla `hash` i `pairHash` na język Solidity. + +**Uwaga:** To kolejny przypadek optymalizacji pod kątem czytelności. W oparciu o [definicję funkcji](https://www.tutorialspoint.com/solidity/solidity_cryptographic_functions.htm) możliwe może być przechowywanie danych jako wartości [`bytes32`](https://docs.soliditylang.org/en/v0.5.3/types.html#fixed-size-byte-arrays) i uniknięcie konwersji. + +```solidity +    // Weryfikacja dowodu Merkle +    function verifyProof(uint _value, uint[] calldata _proof) +        public view returns (bool) { +      uint temp = _value; +      uint i; + +      for(i=0; i<_proof.length; i++) { +        temp = pairHash(temp, _proof[i]); +      } + +      return temp == merkleRoot; +    } + +}  // MarkleProof +``` + +W notacji matematycznej weryfikacja dowodu Merkle wygląda następująco: `H(proof_n, H(proof_n-1, H(proof_n-2, ...` H(proof_1, H(proof_0, value))...)))`. Ten kod to implementuje. + +## Dowody Merkle i pakiety zbiorcze nie idą w parze {#merkle-proofs-and-rollups} + +Dowody Merkle nie działają dobrze z [pakietami zbiorczymi](/developers/docs/scaling/#rollups). Powodem jest to, że pakiety zbiorcze zapisują wszystkie dane transakcji na L1, ale przetwarzają je na L2. Koszt wysłania dowodu Merkle z transakcją wynosi średnio 638 jednostek gazu na warstwę (obecnie bajt w danych wywołania kosztuje 16 jednostek gazu, jeśli nie jest zerem, i 4, jeśli jest zerem). Jeśli mamy 1024 słów danych, dowód Merkle wymaga dziesięciu warstw, czyli łącznie 6380 jednostek gazu. + +Patrząc na przykład na [Optimism](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5m), koszt gazu za zapis na L1 wynosi około 100 gwei, a koszt gazu na L2 – 0,001 gwei (to normalna cena, może wzrosnąć w przypadku przeciążenia). Tak więc za koszt jednej jednostki gazu L1 możemy wydać sto tysięcy jednostek gazu na przetwarzanie na L2. Zakładając, że nie nadpisujemy przestrzeni do przechowywania, oznacza to, że możemy zapisać około pięciu słów w przestrzeni do przechowywania na L2 za cenę jednej jednostki gazu L1. Dla pojedynczego dowodu Merkle możemy zapisać całe 1024 słowa do przestrzeni do przechowywania (zakładając, że mogą być one obliczone na łańcuchu (on-chain), a nie podane w transakcji) i nadal pozostanie nam większość gazu. + +## Wnioski {#conclusion} + +W prawdziwym życiu możesz nigdy nie zaimplementować drzew Merkle samodzielnie. Istnieją dobrze znane i sprawdzone biblioteki, których można używać, i ogólnie rzecz biorąc, najlepiej nie wdrażać samodzielnie prymitywów kryptograficznych. Mam jednak nadzieję, że teraz lepiej rozumiesz dowody Merkle i możesz zdecydować, kiedy warto ich używać. + +Należy pamiętać, że chociaż dowody Merkle zachowują _integralność_, nie zachowują _dostępności_. Świadomość, że nikt inny nie może zabrać Twoich aktywów, jest niewielkim pocieszeniem, jeśli system przechowywania danych zdecyduje się zablokować dostęp, a Ty również nie możesz skonstruować drzewa Merkle, aby uzyskać do nich dostęp. Dlatego drzewa Merkle najlepiej stosować z jakimś rodzajem zdecentralizowanej przestrzeni do przechowywania, takiej jak IPFS. + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/monitoring-geth-with-influxdb-and-grafana/index.md b/public/content/translations/pl/developers/tutorials/monitoring-geth-with-influxdb-and-grafana/index.md new file mode 100644 index 00000000000..746854da417 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/monitoring-geth-with-influxdb-and-grafana/index.md @@ -0,0 +1,151 @@ +--- +title: "Monitorowanie Geth za pomocą InfluxDB i Grafana" +description: "Skonfiguruj monitorowanie węzła Geth za pomocą InfluxDB i Grafana, aby śledzić wydajność i identyfikować problemy." +author: "Mario Havel" +tags: [ "klienci", "węzły" ] +skill: intermediate +lang: pl +published: 2021-01-13 +--- + +Ten samouczek pomoże Ci skonfigurować monitorowanie węzła Geth, abyś mógł lepiej zrozumieć jego wydajność i zidentyfikować potencjalne problemy. + +## Wymagania wstępne {#prerequisites} + +- Powinieneś już mieć uruchomioną instancję Geth. +- Większość kroków i przykładów dotyczy środowiska Linux, pomocna będzie podstawowa znajomość terminala. +- Obejrzyj ten przegląd wideo zestawu metryk Geth: [Monitorowanie infrastruktury Ethereum autorstwa Pétera Szilágyiego](https://www.youtube.com/watch?v=cOBab8IJMYI). + +## Stos monitorowania {#monitoring-stack} + +Klient Ethereum zbiera wiele danych, które można odczytać w formie chronologicznej bazy danych. Aby ułatwić monitorowanie, możesz wprowadzić te dane do oprogramowania do wizualizacji danych. Dostępnych jest wiele opcji: + +- [Prometheus](https://prometheus.io/) (model pull) +- [InfluxDB](https://www.influxdata.com/get-influxdb/) (model push) +- [Telegraf](https://www.influxdata.com/get-influxdb/) +- [Grafana](https://www.grafana.com/) +- [Datadog](https://www.datadoghq.com/) +- [Chronograf](https://www.influxdata.com/time-series-platform/chronograf/) + +Istnieje również [Geth Prometheus Exporter](https://github.com/hunterlong/gethexporter), opcja wstępnie skonfigurowana z InfluxDB i Grafana. + +W tym samouczku skonfigurujemy Twojego klienta Geth tak, aby przesyłał dane do InfluxDB w celu utworzenia bazy danych, a Grafana posłuży do stworzenia wizualizacji graficznej tych danych. Ręczne wykonanie tych czynności pomoże Ci lepiej zrozumieć proces, modyfikować go i wdrażać w różnych środowiskach. + +## Konfiguracja InfluxDB {#setting-up-influxdb} + +Najpierw pobierzmy i zainstalujmy InfluxDB. Różne opcje pobierania można znaleźć na [stronie wydań Influxdata](https://portal.influxdata.com/downloads/). Wybierz tę, która pasuje do Twojego środowiska. +Możesz również zainstalować go z [repozytorium](https://repos.influxdata.com/). Na przykład w dystrybucji opartej na Debianie: + +``` +curl -tlsv1.3 --proto =https -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add +source /etc/lsb-release +echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list +sudo apt update +sudo apt install influxdb -y +sudo systemctl enable influxdb +sudo systemctl start influxdb +sudo apt install influxdb-client +``` + +Po pomyślnej instalacji InfluxDB upewnij się, że działa on w tle. Domyślnie jest on dostępny pod adresem `localhost:8086`. +Przed użyciem klienta `influx` musisz utworzyć nowego użytkownika z uprawnieniami administratora. Ten użytkownik będzie służył do zarządzania na wysokim poziomie, tworzenia baz danych i użytkowników. + +``` +curl -XPOST "http://localhost:8086/query" --data-urlencode "q=CREATE USER username WITH PASSWORD 'password' WITH ALL PRIVILEGES" +``` + +Teraz możesz użyć klienta influx, aby wejść do [powłoki InfluxDB](https://docs.influxdata.com/influxdb/v1.8/tools/shell/) z tym użytkownikiem. + +``` +influx -username 'username' -password 'password' +``` + +Komunikując się bezpośrednio z InfluxDB w jego powłoce, możesz utworzyć bazę danych i użytkownika dla metryk Geth. + +``` +create database geth +create user geth with password choosepassword +``` + +Zweryfikuj utworzone wpisy za pomocą: + +``` +show databases +show users +``` + +Wyjdź z powłoki InfluxDB. + +``` +exit +``` + +InfluxDB jest uruchomiony i skonfigurowany do przechowywania metryk z Geth. + +## Przygotowanie Geth {#preparing-geth} + +Po skonfigurowaniu bazy danych musimy włączyć zbieranie metryk w Geth. Zwróć uwagę na `METRICS AND STATS OPTIONS` w `geth --help`. Można tam znaleźć wiele opcji, w tym przypadku chcemy, aby Geth przesyłał dane do InfluxDB. +Podstawowa konfiguracja określa punkt końcowy, pod którym InfluxDB jest osiągalny, oraz uwierzytelnianie dla bazy danych. + +``` +geth --metrics --metrics.influxdb --metrics.influxdb.endpoint "http://0.0.0.0:8086" --metrics.influxdb.username "geth" --metrics.influxdb.password "chosenpassword" +``` + +Te flagi można dołączyć do polecenia uruchamiającego klienta lub zapisać w pliku konfiguracyjnym. + +Możesz zweryfikować, czy Geth pomyślnie przesyła dane, na przykład wyświetlając listę metryk w bazie danych. W powłoce InfluxDB: + +``` +use geth +show measurements +``` + +## Konfiguracja Grafany {#setting-up-grafana} + +Następnym krokiem jest instalacja Grafany, która zinterpretuje dane graficznie. Postępuj zgodnie z procesem instalacji dla swojego środowiska w dokumentacji Grafany. Upewnij się, że instalujesz wersję OSS, jeśli nie chcesz inaczej. +Przykładowe kroki instalacji dla dystrybucji Debian przy użyciu repozytorium: + +``` +curl -tlsv1.3 --proto =https -sL https://packages.grafana.com/gpg.key | sudo apt-key add - +echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list +sudo apt update +sudo apt install grafana +sudo systemctl enable grafana-server +sudo systemctl start grafana-server +``` + +Gdy Grafana jest uruchomiona, powinna być dostępna pod adresem `localhost:3000`. +Użyj preferowanej przeglądarki, aby uzyskać dostęp do tej ścieżki, a następnie zaloguj się przy użyciu domyślnych poświadczeń (użytkownik: `admin` i hasło: `admin`). Gdy pojawi się monit, zmień domyślne hasło i zapisz. + +![](./grafana1.png) + +Zostaniesz przekierowany na stronę główną Grafany. Najpierw skonfiguruj dane źródłowe. Kliknij ikonę konfiguracji na lewym pasku i wybierz „Źródła danych”. + +![](./grafana2.png) + +Nie ma jeszcze utworzonych żadnych źródeł danych, kliknij „Dodaj źródło danych”, aby zdefiniować jedno. + +![](./grafana3.png) + +W tej konfiguracji wybierz „InfluxDB” i kontynuuj. + +![](./grafana4.png) + +Konfiguracja źródła danych jest dość prosta, jeśli uruchamiasz narzędzia na tej samej maszynie. Musisz ustawić adres InfluxDB i szczegóły dostępu do bazy danych. Zapoznaj się z poniższym obrazkiem. + +![](./grafana5.png) + +Jeśli wszystko jest gotowe, a InfluxDB jest osiągalny, kliknij „Zapisz i przetestuj” i poczekaj na pojawienie się potwierdzenia. + +![](./grafana6.png) + +Grafana jest teraz skonfigurowana do odczytu danych z InfluxDB. Teraz musisz utworzyć pulpit nawigacyjny, który będzie go interpretował i wyświetlał. Właściwości pulpitów nawigacyjnych są zakodowane w plikach JSON, które mogą być tworzone przez każdego i łatwo importowane. Na lewym pasku kliknij „Utwórz i importuj”. + +![](./grafana7.png) + +W przypadku pulpitu nawigacyjnego do monitorowania Geth skopiuj identyfikator [tego pulpitu](https://grafana.com/grafana/dashboards/13877/) i wklej go na stronie „Importuj” w Grafanie. Po zapisaniu pulpitu nawigacyjnego powinien on wyglądać tak: + +![](./grafana8.png) + +Możesz modyfikować swoje pulpity nawigacyjne. Każdy panel można edytować, przesuwać, usuwać lub dodawać. Możesz zmieniać swoje konfiguracje. To zależy od Ciebie! Aby dowiedzieć się więcej o działaniu pulpitów nawigacyjnych, zapoznaj się z [dokumentacją Grafany](https://grafana.com/docs/grafana/latest/dashboards/). +Może Cię również zainteresować [Alerting](https://grafana.com/docs/grafana/latest/alerting/). Umożliwia to skonfigurowanie powiadomień o alertach, gdy metryki osiągną określone wartości. Obsługiwane są różne kanały komunikacji. diff --git a/public/content/translations/pl/developers/tutorials/nft-minter/index.md b/public/content/translations/pl/developers/tutorials/nft-minter/index.md new file mode 100644 index 00000000000..77ca2e9570b --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/nft-minter/index.md @@ -0,0 +1,876 @@ +--- +title: Samouczek mintowania NFT +description: "W tym samouczku zbudujesz minter NFT i dowiesz się, jak stworzyć pełną dapką, łącząc swój inteligentny kontrakt z frontendem React za pomocą MetaMask i narzędzi Web3." +author: "smudgil" +tags: + [ + "solidity", + "NFT", + "alchemy", + "smart kontrakty", + "frontend", + "Pinata" + ] +skill: intermediate +lang: pl +published: 2021-10-06 +--- + +Jednym z największych wyzwań dla deweloperów wywodzących się ze środowiska Web2 jest zorientowanie się, jak połączyć swój inteligentny kontrakt z projektem frontendu i wejść z nim w interakcję. + +Tworząc minter NFT — prosty interfejs użytkownika, w którym można wprowadzić link do zasobu cyfrowego, tytuł i opis — dowiesz się, jak: + +- Połączyć się z MetaMask za pośrednictwem projektu frontendowego +- Wywoływać metody inteligentnego kontraktu z poziomu frontendu +- Podpisywać transakcje za pomocą MetaMask + +W tym samouczku jako frameworku frontendowego będziemy używać [React](https://react.dev/). Ponieważ ten samouczek skupia się głównie na rozwoju Web3, nie będziemy poświęcać wiele czasu na analizowanie podstaw React. Zamiast tego skupimy się na wprowadzeniu funkcjonalności do naszego projektu. + +Warunkiem wstępnym jest posiadanie podstawowej wiedzy na temat React — jak działają komponenty, propsy, useState/useEffect i podstawowe wywoływanie funkcji. Jeśli te terminy są dla Ciebie nowe, możesz zapoznać się z tym [samouczkiem Wprowadzenie do React](https://react.dev/learn/tutorial-tic-tac-toe). Wzrokowcom gorąco polecamy tę doskonałą serię filmów [Pełny nowoczesny samouczek React](https://www.youtube.com/playlist?list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d) autorstwa Net Ninja. + +A jeśli jeszcze go nie masz, na pewno będziesz potrzebować konta Alchemy, aby ukończyć ten samouczek, a także aby cokolwiek budować na blockchainie. Zarejestruj się, aby otrzymać darmowe konto [tutaj](https://alchemy.com/). + +Bez zbędnych ceregieli, zaczynajmy! + +## Tworzenie NFT 101 {#making-nfts-101} + +Zanim w ogóle zaczniemy przyglądać się jakiemukolwiek kodowi, ważne jest, aby zrozumieć, jak działa tworzenie NFT. Składa się ono z dwóch kroków: + +### Opublikuj inteligentny kontrakt NFT na blockchainie Ethereum {#publish-nft} + +Największa różnica między dwoma standardami inteligentnych kontraktów NFT polega na tym, że ERC-1155 jest standardem wielotokenowym i zawiera funkcjonalność partii, podczas gdy ERC-721 jest standardem jednotokenowym i w związku z tym obsługuje tylko transfer jednego tokena na raz. + +### Wywołaj funkcję mintowania {#minting-function} + +Zazwyczaj ta funkcja mintowania wymaga podania dwóch zmiennych jako parametrów: po pierwsze `recipient`, który określa adres, na który trafi nowo wybite NFT, a po drugie `tokenURI` NFT, czyli ciąg znaków, który odwołuje się do dokumentu JSON opisującego metadane NFT. + +Metadane NFT to tak naprawdę to, co ożywia go, pozwalając na posiadanie takich właściwości jak nazwa, opis, obraz (lub inny zasób cyfrowy) i inne atrybuty. Oto [przykład tokenURI](https://gateway.pinata.cloud/ipfs/QmSvBcb4tjdFpajGJhbFAWeK3JAxCdNQLQtr6ZdiSi42V2), który zawiera metadane NFT. + +W tym samouczku skupimy się na części 2, wywołaniu istniejącej funkcji mintowania inteligentnego kontraktu NFT za pomocą naszego interfejsu użytkownika React. + +[Oto link](https://ropsten.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE) do inteligentnego kontraktu ERC-721 NFT, który będziemy wywoływać w tym samouczku. Jeśli chcesz dowiedzieć się, jak go stworzyliśmy, gorąco polecamy zapoznanie się z naszym innym samouczkiem, [„Jak stworzyć NFT”](https://www.alchemy.com/docs/how-to-create-an-nft). + +Super, teraz, gdy rozumiemy, jak działa tworzenie NFT, sklonujmy nasze pliki startowe! + +## Klonowanie plików startowych {#clone-the-starter-files} + +Najpierw przejdź do [repozytorium GitHub nft-minter-tutorial](https://github.com/alchemyplatform/nft-minter-tutorial), aby pobrać pliki startowe dla tego projektu. Sklonuj to repozytorium do swojego lokalnego środowiska. + +Po otwarciu sklonowanego repozytorium `nft-minter-tutorial` zauważysz, że zawiera ono dwa foldery: `minter-starter-files` i `nft-minter`. + +- `minter-starter-files` zawiera pliki startowe (zasadniczo interfejs użytkownika React) dla tego projektu. W tym samouczku **będziemy pracować w tym katalogu**, ponieważ dowiesz się, jak ożywić ten interfejs użytkownika, łącząc go z portfelem Ethereum i inteligentnym kontraktem NFT. +- `nft-minter` zawiera cały ukończony samouczek i jest dostępny jako **odniesienie**, **jeśli utkniesz**. + +Następnie otwórz swoją kopię `minter-starter-files` w edytorze kodu, a następnie przejdź do folderu `src`. + +Cały kod, który napiszemy, będzie znajdować się w folderze `src`. Będziemy edytować komponent `Minter.js` i pisać dodatkowe pliki javascript, aby nadać naszemu projektowi funkcjonalność Web3. + +## Krok 2: Sprawdź nasze pliki startowe {#step-2-check-out-our-starter-files} + +Zanim zaczniemy kodować, warto sprawdzić, co już zostało dla nas przygotowane w plikach startowych. + +### Uruchomienie projektu React {#get-your-react-project-running} + +Zacznijmy od uruchomienia projektu React w naszej przeglądarce. Piękno React polega na tym, że gdy nasz projekt jest już uruchomiony w przeglądarce, wszelkie zapisane przez nas zmiany będą na bieżąco aktualizowane w przeglądarce. + +Aby uruchomić projekt, przejdź do katalogu głównego folderu `minter-starter-files`, a następnie uruchom `npm install` w terminalu, aby zainstalować zależności projektu: + +```bash +cd minter-starter-files +npm install +``` + +Po zakończeniu instalacji uruchom `npm start` w terminalu: + +```bash +npm start +``` + +Powinno to otworzyć adres http://localhost:3000/ w przeglądarce, gdzie zobaczysz frontend naszego projektu. Powinien on składać się z 3 pól: miejsca na wprowadzenie linku do zasobu NFT, wprowadzenie nazwy NFT i podanie opisu. + +Jeśli spróbujesz kliknąć przyciski „Połącz portfel” lub „Mintuj NFT”, zauważysz, że nie działają – to dlatego, że wciąż musimy zaprogramować ich funkcjonalność! :\) + +### Komponent Minter.js {#minter-js} + +**UWAGA:** Upewnij się, że jesteś w folderze `minter-starter-files`, a nie w folderze `nft-minter`! + +Wróćmy do folderu `src` w naszym edytorze i otwórzmy plik `Minter.js`. Jest bardzo ważne, abyśmy zrozumieli wszystko w tym pliku, ponieważ jest to główny komponent React, nad którym będziemy pracować. + +Na górze tego pliku mamy zmienne stanu, które będziemy aktualizować po określonych zdarzeniach. + +```javascript +//Zmienne stanu +const [walletAddress, setWallet] = useState("") +const [status, setStatus] = useState("") +const [name, setName] = useState("") +const [description, setDescription] = useState("") +const [url, setURL] = useState("") +``` + +Nigdy nie słyszałeś o zmiennych stanu React lub hookach stanu? Sprawdź [tę](https://legacy.reactjs.org/docs/hooks-state.html) dokumentację. + +Oto, co reprezentuje każda ze zmiennych: + +- `walletAddress` - ciąg znaków, który przechowuje adres portfela użytkownika +- `status` - ciąg znaków, który zawiera wiadomość do wyświetlenia na dole interfejsu użytkownika +- `name` - ciąg znaków, który przechowuje nazwę NFT +- `description` - ciąg znaków, który przechowuje opis NFT +- `url` - ciąg znaków, który jest linkiem do zasobu cyfrowego NFT + +Po zmiennych stanu zobaczysz trzy niezimplementowane funkcje: `useEffect`, `connectWalletPressed` i `onMintPressed`. Zauważysz, że wszystkie te funkcje są `async`, ponieważ będziemy w nich wykonywać asynchroniczne wywołania API! Ich nazwy są tożsame z ich funkcjonalnością: + +```javascript +useEffect(async () => { + //TODO: zaimplementuj +}, []) + +const connectWalletPressed = async () => { + //TODO: zaimplementuj +} + +const onMintPressed = async () => { + //TODO: zaimplementuj +} +``` + +- [`useEffect`](https://legacy.reactjs.org/docs/hooks-effect.html) - to hook React, który jest wywoływany po wyrenderowaniu komponentu. Ponieważ ma przekazany pusty prop tablicy `[]` (patrz linia 3), zostanie wywołany tylko przy _pierwszym_ renderowaniu komponentu. Tutaj wywołamy nasz nasłuchiwacz portfela i inną funkcję portfela, aby zaktualizować nasz interfejs użytkownika, aby odzwierciedlić, czy portfel jest już podłączony. +- `connectWalletPressed` - ta funkcja zostanie wywołana, aby połączyć portfel MetaMask użytkownika z naszą dapką. +- `onMintPressed` - ta funkcja zostanie wywołana w celu mintowania NFT użytkownika. + +Pod koniec tego pliku mamy interfejs użytkownika naszego komponentu. Jeśli uważnie przeskanujesz ten kod, zauważysz, że aktualizujemy nasze zmienne stanu `url`, `name` i `description`, gdy zmienia się dane wejściowe w odpowiadających im polach tekstowych. + +Zobaczysz również, że `connectWalletPressed` i `onMintPressed` są wywoływane odpowiednio po kliknięciu przycisków o identyfikatorach `mintButton` i `walletButton`. + +```javascript +//interfejs użytkownika naszego komponentu +return ( +
+ + +

+

🧙‍♂️ Minter NFT od Alchemy

+

+ Wystarczy dodać link do zasobu, nazwę i opis, a następnie nacisnąć „Mintuj”. +

+
+

🖼 Link do zasobu:

+ setURL(event.target.value)} + /> +

🤔 Nazwa:

+ setName(event.target.value)} + /> +

✍️ Opis:

+ setDescription(event.target.value)} + /> +
+ +

{status}

+
+) +``` + +Na koniec zajmijmy się tym, gdzie ten komponent Minter jest dodawany. + +Jeśli przejdziesz do pliku `App.js`, który jest głównym komponentem w React, działającym jako kontener dla wszystkich innych komponentów, zobaczysz, że nasz komponent Minter jest wstawiony w linii 7. + +**W tym samouczku będziemy edytować tylko plik `Minter.js` i dodawać pliki w naszym folderze `src`.** + +Teraz, gdy rozumiemy, z czym pracujemy, skonfigurujmy nasz portfel Ethereum! + +## Skonfiguruj swój portfel Ethereum {#set-up-your-ethereum-wallet} + +Aby użytkownicy mogli wchodzić w interakcję z Twoim inteligentnym kontraktem, będą musieli połączyć swój portfel Ethereum z Twoją dapką. + +### Pobierz MetaMask {#download-metamask} + +W tym samouczku użyjemy MetaMask, wirtualnego portfela w przeglądarce, który służy do zarządzania adresem konta Ethereum. Jeśli chcesz dowiedzieć się więcej o tym, jak działają transakcje w Ethereum, sprawdź [tę stronę](/developers/docs/transactions/). + +Możesz pobrać i utworzyć konto MetaMask za darmo [tutaj](https://metamask.io/download). Podczas tworzenia konta, lub jeśli już je masz, upewnij się, że przełączyłeś się na „sieć testową Ropsten” w prawym górnym rogu (abyśmy nie mieli do czynienia z prawdziwymi pieniędzmi). + +### Dodaj ether z Faucet {#add-ether-from-faucet} + +Aby mintować nasze NFT (lub podpisywać jakiekolwiek transakcje na blockchainie Ethereum), będziemy potrzebować trochę fałszywego Eth. Aby uzyskać Eth, możesz przejść do [Ropsten faucet](https://faucet.ropsten.be/) i wprowadzić adres swojego konta Ropsten, a następnie kliknąć „Wyślij Ropsten Eth”. Wkrótce powinieneś zobaczyć Eth na swoim koncie MetaMask! + +### Sprawdź swoje saldo {#check-your-balance} + +Aby sprawdzić, czy nasze saldo jest na miejscu, wykonajmy żądanie [eth_getBalance](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getbalance) za pomocą [narzędzia kompozytora Alchemy](https://composer.alchemyapi.io/?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D). Zwróci to ilość Eth w naszym portfelu. Po wprowadzeniu adresu konta MetaMask i kliknięciu „Wyślij żądanie” powinieneś zobaczyć następującą odpowiedź: + +```text +{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"} +``` + +**UWAGA:** ten wynik jest w wei, a nie w eth. Wei jest używany jako najmniejsza jednostka etheru. Konwersja z wei na eth to: 1 eth = 10¹⁸ wei. Więc jeśli przekonwertujemy 0xde0b6b3a7640000 na system dziesiętny, otrzymamy 1\*10¹⁸, co równa się 1 eth. + +Uff! Nasze fałszywe pieniądze są na miejscu! + +## Połącz MetaMask ze swoim interfejsem użytkownika {#connect-metamask-to-your-UI} + +Teraz, gdy nasz portfel MetaMask jest skonfigurowany, połączmy z nim naszą dapką! + +Ponieważ chcemy stosować paradygmat [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller), stworzymy osobny plik, który będzie zawierał nasze funkcje do zarządzania logiką, danymi i zasadami naszej dapki, a następnie przekażemy te funkcje do naszego frontendu (naszego komponentu Minter.js). + +### Funkcja `connectWallet` {#connect-wallet-function} + +W tym celu utwórzmy nowy folder o nazwie `utils` w katalogu `src` i dodajmy do niego plik o nazwie `interact.js`, który będzie zawierał wszystkie nasze funkcje interakcji z portfelem i inteligentnym kontraktem. + +W naszym pliku `interact.js` napiszemy funkcję `connectWallet`, którą następnie zaimportujemy i wywołamy w naszym komponencie `Minter.js`. + +W pliku `interact.js` dodaj następujący kod: + +```javascript +export const connectWallet = async () => { + if (window.ethereum) { + try { + const addressArray = await window.ethereum.request({ + method: "eth_requestAccounts", + }) + const obj = { + status: "👆🏽 Wpisz wiadomość w polu tekstowym powyżej.", + address: addressArray[0], + } + return obj + } catch (err) { + return { + address: "", + status: "😥 " + err.message, + } + } + } else { + return { + address: "", + status: ( + +

+ {" "} + 🦊 + Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej + przeglądarce. + +

+
+ ), + } + } +} +``` + +Przeanalizujmy, co robi ten kod: + +Najpierw nasza funkcja sprawdza, czy `window.ethereum` jest włączone w Twojej przeglądarce. + +`window.ethereum` to globalny interfejs API wstrzykiwany przez MetaMask i innych dostawców portfeli, który pozwala stronom internetowym na żądanie dostępu do kont Ethereum użytkowników. Po zatwierdzeniu może odczytywać dane z blockchainów, z którymi użytkownik jest połączony, i sugerować, aby użytkownik podpisywał wiadomości i transakcje. Sprawdź [dokumentację MetaMask](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents), aby uzyskać więcej informacji! + +Jeśli `window.ethereum` _nie jest_ obecne, oznacza to, że MetaMask nie jest zainstalowany. Powoduje to zwrócenie obiektu JSON, w którym zwrócony `adres` jest pustym ciągiem, a obiekt `status` JSX informuje, że użytkownik musi zainstalować MetaMask. + +**Większość funkcji, które piszemy, będzie zwracać obiekty JSON, których możemy użyć do aktualizacji naszych zmiennych stanu i interfejsu użytkownika.** + +Teraz, jeśli `window.ethereum` _jest_ obecne, to wtedy robi się ciekawie. + +Używając pętli try/catch, spróbujemy połączyć się z MetaMask, wywołując [`window.ethereum.request({ method: "eth_requestAccounts" });`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts). Wywołanie tej funkcji otworzy MetaMask w przeglądarce, gdzie użytkownik zostanie poproszony o podłączenie swojego portfela do Twojej dapki. + +- Jeśli użytkownik zdecyduje się połączyć, `metoda: "eth_requestAccounts"` zwróci tablicę zawierającą wszystkie adresy kont użytkownika, które są połączone z dapką. W sumie nasza funkcja `connectWallet` zwróci obiekt JSON, który zawiera _pierwszy_ `adres` w tej tablicy (patrz linia 9) oraz wiadomość `status`, która prosi użytkownika o napisanie wiadomości do inteligentnego kontraktu. +- Jeśli użytkownik odrzuci połączenie, obiekt JSON będzie zawierał pusty ciąg dla zwróconego `adresu` oraz komunikat `status`, który odzwierciedla, że użytkownik odrzucił połączenie. + +### Dodaj funkcję connectWallet do komponentu interfejsu użytkownika Minter.js {#add-connect-wallet} + +Teraz, gdy napisaliśmy tę funkcję `connectWallet`, połączmy ją z naszym komponentem `Minter.js`. + +Najpierw będziemy musieli zaimportować naszą funkcję do naszego pliku `Minter.js`, dodając `import { connectWallet } from "./utils/interact.js";` na górze pliku `Minter.js`. Twoje pierwsze 11 linii `Minter.js` powinno teraz wyglądać tak: + +```javascript +import { useEffect, useState } from "react"; +import { connectWallet } from "./utils/interact.js"; + +const Minter = (props) => { + + //Zmienne stanu + const [walletAddress, setWallet] = useState(""); + const [status, setStatus] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [url, setURL] = useState(""); +``` + +Następnie w naszej funkcji `connectWalletPressed` wywołamy naszą zaimportowaną funkcję `connectWallet`, w ten sposób: + +```javascript +const connectWalletPressed = async () => { + const walletResponse = await connectWallet() + setStatus(walletResponse.status) + setWallet(walletResponse.address) +} +``` + +Zauważ, jak większość naszej funkcjonalności jest wyabstrahowana z naszego komponentu `Minter.js` z pliku `interact.js`? Robimy tak, aby zachować zgodność z paradygmatem M-V-C! + +W `connectWalletPressed` po prostu wykonujemy wywołanie await do naszej zaimportowanej funkcji `connectWallet`, a za pomocą jej odpowiedzi aktualizujemy nasze zmienne `status` i `walletAddress` za pomocą ich hooków stanu. + +Teraz zapiszmy oba pliki `Minter.js` i `interact.js` i przetestujmy nasz interfejs użytkownika. + +Otwórz przeglądarkę pod adresem localhost:3000 i naciśnij przycisk „Połącz portfel” w prawym górnym rogu strony. + +Jeśli masz zainstalowany MetaMask, powinieneś zostać poproszony o podłączenie swojego portfela do Twojej dapki. Zaakceptuj zaproszenie do połączenia. + +Powinieneś zobaczyć, że przycisk portfela teraz odzwierciedla, że Twój adres jest podłączony. + +Następnie spróbuj odświeżyć stronę... to jest dziwne. Nasz przycisk portfela prosi nas o podłączenie MetaMask, mimo że jest już podłączony... + +Ale nie martw się! Możemy to łatwo naprawić, implementując funkcję o nazwie `getCurrentWalletConnected`, która sprawdzi, czy adres jest już podłączony do naszej dapki i odpowiednio zaktualizuje nasz interfejs użytkownika! + +### Funkcja getCurrentWalletConnected {#get-current-wallet} + +W pliku `interact.js` dodaj następującą funkcję `getCurrentWalletConnected`: + +```javascript +export const getCurrentWalletConnected = async () => { + if (window.ethereum) { + try { + const addressArray = await window.ethereum.request({ + method: "eth_accounts", + }) + if (addressArray.length > 0) { + return { + address: addressArray[0], + status: "👆🏽 Wpisz wiadomość w polu tekstowym powyżej.", + } + } else { + return { + address: "", + status: "🦊 Połącz się z MetaMask za pomocą przycisku w prawym górnym rogu.", + } + } + } catch (err) { + return { + address: "", + status: "😥 " + err.message, + } + } + } else { + return { + address: "", + status: ( + +

+ {" "} + 🦊 + Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej + przeglądarce. + +

+
+ ), + } + } +} +``` + +Ten kod jest _bardzo_ podobny do funkcji `connectWallet`, którą napisaliśmy wcześniej. + +Główna różnica polega na tym, że zamiast wywoływać metodę `eth_requestAccounts`, która otwiera MetaMask, aby użytkownik mógł połączyć swój portfel, tutaj wywołujemy metodę `eth_accounts`, która po prostu zwraca tablicę zawierającą adresy MetaMask aktualnie połączone z naszą dapką. + +Aby zobaczyć tę funkcję w akcji, wywołajmy ją w funkcji `useEffect` naszego komponentu `Minter.js`. + +Tak jak zrobiliśmy to dla `connectWallet`, musimy zaimportować tę funkcję z naszego pliku `interact.js` do naszego pliku `Minter.js` w ten sposób: + +```javascript +import { useEffect, useState } from "react" +import { + connectWallet, + getCurrentWalletConnected, //importuj tutaj +} from "./utils/interact.js" +``` + +Teraz po prostu wywołujemy ją w naszej funkcji `useEffect`: + +```javascript +useEffect(async () => { + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) +}, []) +``` + +Zauważ, że używamy odpowiedzi z naszego wywołania `getCurrentWalletConnected`, aby zaktualizować nasze zmienne stanu `walletAddress` i `status`. + +Po dodaniu tego kodu spróbuj odświeżyć okno przeglądarki. Przycisk powinien informować, że jesteś połączony i pokazywać podgląd adresu podłączonego portfela - nawet po odświeżeniu! + +### Zaimplementuj addWalletListener {#implement-add-wallet-listener} + +Ostatnim krokiem w konfiguracji portfela naszej dapki jest zaimplementowanie nasłuchiwacza portfela, aby nasz interfejs użytkownika aktualizował się, gdy zmieni się stan naszego portfela, na przykład gdy użytkownik się rozłączy lub zmieni konto. + +W pliku `Minter.js` dodaj funkcję `addWalletListener`, która wygląda następująco: + +```javascript +function addWalletListener() { + if (window.ethereum) { + window.ethereum.on("accountsChanged", (accounts) => { + if (accounts.length > 0) { + setWallet(accounts[0]) + setStatus("👆🏽 Wpisz wiadomość w polu tekstowym powyżej.") + } else { + setWallet("") + setStatus("🦊 Połącz się z MetaMask za pomocą przycisku w prawym górnym rogu.") + } + }) + } else { + setStatus( +

+ {" "} + 🦊 + Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej przeglądarce. + +

+ ) + } +} +``` + +Przeanalizujmy szybko, co się tutaj dzieje: + +- Najpierw nasza funkcja sprawdza, czy `window.ethereum` jest włączone (tj. MetaMask jest zainstalowany). + - Jeśli nie jest, po prostu ustawiamy naszą zmienną stanu `status` na ciąg JSX, który prosi użytkownika o zainstalowanie MetaMask. + - Jeśli jest włączone, ustawiamy nasłuchiwacz `window.ethereum.on("accountsChanged")` w linii 3, który nasłuchuje zmian stanu w portfelu MetaMask, co obejmuje sytuacje, gdy użytkownik podłącza dodatkowe konto do dapki, zmienia konta lub odłącza konto. Jeśli co najmniej jedno konto jest połączone, zmienna stanu `walletAddress` jest aktualizowana jako pierwsze konto w tablicy `konta` zwróconej przez nasłuchiwacz. W przeciwnym razie `walletAddress` jest ustawiany jako pusty ciąg. + +Na koniec musimy wywołać go w naszej funkcji `useEffect`: + +```javascript +useEffect(async () => { + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) + + addWalletListener() +}, []) +``` + +I voila! Ukończyliśmy programowanie całej funkcjonalności naszego portfela! Teraz, gdy nasz portfel jest skonfigurowany, dowiedzmy się, jak mintować nasze NFT! + +## Metadane NFT 101 {#nft-metadata-101} + +Pamiętasz więc metadane NFT, o których właśnie mówiliśmy w kroku 0 tego samouczka – ożywiają one NFT, pozwalając mu mieć właściwości, takie jak zasób cyfrowy, nazwa, opis i inne atrybuty. + +Będziemy musieli skonfigurować te metadane jako obiekt JSON i przechować go, abyśmy mogli przekazać go jako parametr `tokenURI` podczas wywoływania funkcji `mintNFT` naszego inteligentnego kontraktu. + +Tekst w polach „Link do zasobu”, „Nazwa”, „Opis” będzie składał się z różnych właściwości metadanych naszego NFT. Sformatujemy te metadane jako obiekt JSON, ale istnieje kilka opcji, gdzie możemy przechowywać ten obiekt JSON: + +- Moglibyśmy przechowywać go na blockchainie Ethereum; jednak byłoby to bardzo kosztowne. +- Moglibyśmy przechowywać go na scentralizowanym serwerze, takim jak AWS lub Firebase. Ale to zniweczyłoby nasz etos decentralizacji. +- Moglibyśmy użyć IPFS, zdecentralizowanego protokołu i sieci peer-to-peer do przechowywania i udostępniania danych w rozproszonym systemie plików. Ponieważ ten protokół jest zdecentralizowany i bezpłatny, jest to nasza najlepsza opcja! + +Aby przechowywać nasze metadane w IPFS, użyjemy [Pinata](https://pinata.cloud/), wygodnego API i zestawu narzędzi IPFS. W następnym kroku wyjaśnimy dokładnie, jak to zrobić! + +## Użyj Pinata, aby przypiąć swoje metadane do IPFS {#use-pinata-to-pin-your-metadata-to-IPFS} + +Jeśli nie masz konta [Pinata](https://pinata.cloud/), zarejestruj się, aby uzyskać bezpłatne konto [tutaj](https://app.pinata.cloud/auth/signup) i wykonaj kroki, aby zweryfikować swój adres e-mail i konto. + +### Utwórz swój klucz API Pinata {#create-pinata-api-key} + +Przejdź na stronę [https://pinata.cloud/keys](https://pinata.cloud/keys), a następnie wybierz przycisk „New Key” u góry, ustaw widżet Admin jako włączony i nazwij swój klucz. + +Następnie pojawi się wyskakujące okienko z informacjami o Twoim API. Upewnij się, że umieściłeś to w bezpiecznym miejscu. + +Teraz, gdy nasz klucz jest skonfigurowany, dodajmy go do naszego projektu, abyśmy mogli go użyć. + +### Utwórz plik .env {#create-a-env} + +Możemy bezpiecznie przechowywać nasz klucz Pinata i sekret w pliku środowiskowym. Zainstalujmy pakiet [dotenv](https://www.npmjs.com/package/dotenv) w katalogu projektu. + +Otwórz nową kartę w terminalu (oddzielną od tej, na której działa host lokalny) i upewnij się, że jesteś w folderze `minter-starter-files`, a następnie uruchom następujące polecenie w terminalu: + +```text +npm install dotenv --save +``` + +Następnie utwórz plik `.env` w katalogu głównym `minter-starter-files`, wpisując w wierszu poleceń: + +```javascript +vim.env +``` + +To otworzy twój plik `.env` w vim (edytorze tekstu). Aby go zapisać, naciśnij kolejno „esc” + „:” + „q” na klawiaturze. + +Następnie w VSCode przejdź do pliku `.env` i dodaj do niego swój klucz API Pinata i sekret API, w ten sposób: + +```text +REACT_APP_PINATA_KEY = +REACT_APP_PINATA_SECRET = +``` + +Zapisz plik, a następnie jesteś gotowy do rozpoczęcia pisania funkcji przesyłania metadanych JSON do IPFS! + +### Zaimplementuj pinJSONToIPFS {#pin-json-to-ipfs} + +Na szczęście dla nas Pinata ma [API specjalnie do przesyłania danych JSON do IPFS](https://docs.pinata.cloud/api-reference/endpoint/ipfs/pin-json-to-ipfs#pin-json) i wygodny przykład JavaScript z axios, którego możemy użyć, z niewielkimi modyfikacjami. + +W folderze `utils` utwórzmy kolejny plik o nazwie `pinata.js`, a następnie zaimportujmy nasz sekret i klucz Pinata z pliku .env w ten sposób: + +```javascript +require("dotenv").config() +const key = process.env.REACT_APP_PINATA_KEY +const secret = process.env.REACT_APP_PINATA_SECRET +``` + +Następnie wklej dodatkowy kod z poniższego przykładu do pliku `pinata.js`. Nie martw się, wyjaśnimy, co wszystko oznacza! + +```javascript +require("dotenv").config() +const key = process.env.REACT_APP_PINATA_KEY +const secret = process.env.REACT_APP_PINATA_SECRET + +const axios = require("axios") + +export const pinJSONToIPFS = async (JSONBody) => { + const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS` + //wykonywanie żądania POST axios do Pinata ⬇️ + return axios + .post(url, JSONBody, { + headers: { + pinata_api_key: key, + pinata_secret_api_key: secret, + }, + }) + .then(function (response) { + return { + success: true, + pinataUrl: + "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash, + } + }) + .catch(function (error) { + console.log(error) + return { + success: false, + message: error.message, + } + }) +} +``` + +Więc co dokładnie robi ten kod? + +Po pierwsze, importuje [axios](https://www.npmjs.com/package/axios), klienta HTTP opartego na obietnicach dla przeglądarki i node.js, którego użyjemy do złożenia żądania do Pinata. + +Następnie mamy naszą asynchroniczną funkcję `pinJSONToIPFS`, która przyjmuje `JSONBody` jako dane wejściowe oraz klucz API i sekret Pinata w nagłówku, wszystko po to, aby wykonać żądanie POST do ich API `pinJSONToIPFS`. + +- Jeśli to żądanie POST zakończy się pomyślnie, nasza funkcja zwraca obiekt JSON z wartością logiczną `success` ustawioną na true i `pinataUrl`, pod którym zostały przypięte nasze metadane. Użyjemy tego zwróconego `pinataUrl` jako wejścia `tokenURI` do funkcji mintowania naszego inteligentnego kontraktu. +- Jeśli to żądanie post nie powiedzie się, nasza funkcja zwróci obiekt JSON z wartością logiczną `success` ustawioną na false i ciągiem `message`, który przekaże nasz błąd. + +Podobnie jak w przypadku typów zwrotnych funkcji `connectWallet`, zwracamy obiekty JSON, abyśmy mogli użyć ich parametrów do aktualizacji naszych zmiennych stanu i interfejsu użytkownika. + +## Wczytaj swój inteligentny kontrakt {#load-your-smart-contract} + +Teraz, gdy mamy sposób na przesyłanie metadanych NFT do IPFS za pomocą naszej funkcji `pinJSONToIPFS`, będziemy potrzebować sposobu na załadowanie instancji naszego inteligentnego kontraktu, abyśmy mogli wywołać jego funkcję `mintNFT`. + +Jak już wspomnieliśmy, w tym samouczku będziemy używać [tego istniejącego inteligentnego kontraktu NFT](https://ropsten.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE); jeśli jednak chcesz dowiedzieć się, jak go stworzyliśmy, lub stworzyć własny, gorąco polecamy zapoznanie się z naszym innym samouczkiem, [„Jak stworzyć NFT”](https://www.alchemy.com/docs/how-to-create-an-nft). + +### ABI kontraktu {#contract-abi} + +Jeśli dokładnie przeanalizowałeś nasze pliki, zauważysz, że w naszym katalogu `src` znajduje się plik `contract-abi.json`. ABI jest niezbędne do określenia, którą funkcję kontrakt wywoła, a także do zapewnienia, że funkcja zwróci dane w oczekiwanym formacie. + +Będziemy również potrzebować klucza API Alchemy i API Alchemy Web3, aby połączyć się z blockchainem Ethereum i załadować nasz inteligentny kontrakt. + +### Utwórz klucz API Alchemy {#create-alchemy-api} + +Jeśli nie masz jeszcze konta Alchemy, [zarejestruj się za darmo tutaj.](https://alchemy.com/?a=eth-org-nft-minter) + +Po utworzeniu konta Alchemy możesz wygenerować klucz API, tworząc aplikację. Pozwoli nam to na składanie żądań do sieci testowej Ropsten. + +Przejdź na stronę „Create App” w swoim panelu Alchemy, najeżdżając kursorem na „Apps” w pasku nawigacyjnym i klikając „Create App”. + +Nazwij swoją aplikację — my wybraliśmy „My First NFT!”, podaj krótki opis, wybierz „Staging” dla środowiska używanego do księgowania aplikacji i wybierz „Ropsten” dla swojej sieci. + +Kliknij „Utwórz aplikację” i to wszystko! Twoja aplikacja powinna pojawić się w poniższej tabeli. + +Wspaniale, teraz, gdy stworzyliśmy nasz adres URL HTTP API Alchemy, skopiuj go do schowka... + +…a następnie dodajmy go do naszego pliku `.env`. W sumie twój plik .env powinien wyglądać tak: + +```text +REACT_APP_PINATA_KEY = +REACT_APP_PINATA_SECRET = +REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/ +``` + +Teraz, gdy mamy już ABI naszego kontraktu i klucz API Alchemy, jesteśmy gotowi do załadowania naszego inteligentnego kontraktu za pomocą [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3). + +### Skonfiguruj punkt końcowy Alchemy Web3 i kontrakt {#setup-alchemy-endpoint} + +Po pierwsze, jeśli jeszcze go nie masz, musisz zainstalować [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3), przechodząc do katalogu domowego: `nft-minter-tutorial` w terminalu: + +```text +cd .. +npm install @alch/alchemy-web3 +``` + +Następnie wróćmy do naszego pliku `interact.js`. Na górze pliku dodaj następujący kod, aby zaimportować swój klucz Alchemy z pliku .env i skonfigurować swój punkt końcowy Alchemy Web3: + +```javascript +require("dotenv").config() +const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY +const { createAlchemyWeb3 } = require("@alch/alchemy-web3") +const web3 = createAlchemyWeb3(alchemyKey) +``` + +[Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3) to nakładka na [Web3.js](https://docs.web3js.org/), zapewniająca ulepszone metody API i inne kluczowe korzyści, które ułatwiają życie dewelopera web3. Został zaprojektowany tak, aby wymagał minimalnej konfiguracji, dzięki czemu możesz od razu zacząć go używać w swojej aplikacji! + +Następnie dodajmy do naszego pliku ABI kontraktu i adres kontraktu. + +```javascript +require("dotenv").config() +const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY +const { createAlchemyWeb3 } = require("@alch/alchemy-web3") +const web3 = createAlchemyWeb3(alchemyKey) + +const contractABI = require("../contract-abi.json") +const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE" +``` + +Gdy mamy już obie te rzeczy, jesteśmy gotowi do rozpoczęcia kodowania naszej funkcji mintowania! + +## Zaimplementuj funkcję mintNFT {#implement-the-mintnft-function} + +W pliku `interact.js` zdefiniujmy naszą funkcję, `mintNFT`, która, jak sama nazwa wskazuje, będzie mintować nasze NFT. + +Ponieważ będziemy wykonywać liczne wywołania asynchroniczne (do Pinata, aby przypiąć nasze metadane do IPFS, Alchemy Web3, aby załadować nasz inteligentny kontrakt, i MetaMask, aby podpisać nasze transakcje), nasza funkcja również będzie asynchroniczna. + +Trzy dane wejściowe do naszej funkcji to `url` naszego zasobu cyfrowego, `name` i `description`. Dodaj następującą sygnaturę funkcji pod funkcją `connectWallet`: + +```javascript +export const mintNFT = async (url, name, description) => {} +``` + +### Obsługa błędów wejściowych {#input-error-handling} + +Naturalnie, sensowne jest posiadanie jakiejś formy obsługi błędów wejściowych na początku funkcji, abyśmy opuścili tę funkcję, jeśli nasze parametry wejściowe nie są poprawne. Wewnątrz naszej funkcji dodajmy następujący kod: + +```javascript +export const mintNFT = async (url, name, description) => { + //obsługa błędów + if (url.trim() == "" || name.trim() == "" || description.trim() == "") { + return { + success: false, + status: "❗Upewnij się, że wszystkie pola są wypełnione przed mintowaniem.", + } + } +} +``` + +Zasadniczo, jeśli którykolwiek z parametrów wejściowych jest pustym ciągiem, zwracamy obiekt JSON, w którym wartość logiczna `success` jest fałszywa, a ciąg `status` informuje, że wszystkie pola w naszym interfejsie użytkownika muszą być wypełnione. + +### Przesyłanie metadanych do IPFS {#upload-metadata-to-ipfs} + +Gdy już wiemy, że nasze metadane są poprawnie sformatowane, następnym krokiem jest opakowanie ich w obiekt JSON i przesłanie go do IPFS za pomocą funkcji `pinJSONToIPFS`, którą napisaliśmy! + +W tym celu musimy najpierw zaimportować funkcję `pinJSONToIPFS` do naszego pliku `interact.js`. Na samym początku `interact.js` dodajmy: + +```javascript +import { pinJSONToIPFS } from "./pinata.js" +``` + +Przypomnijmy, że `pinJSONToIPFS` przyjmuje treść JSON. Więc zanim do niego zadzwonimy, będziemy musieli sformatować nasze parametry `url`, `name` i `description` w obiekt JSON. + +Zaktualizujmy nasz kod, aby utworzyć obiekt JSON o nazwie `metadata`, a następnie wykonajmy wywołanie `pinJSONToIPFS` z tym parametrem `metadata`: + +```javascript +export const mintNFT = async (url, name, description) => { + //obsługa błędów + if (url.trim() == "" || name.trim() == "" || description.trim() == "") { + return { + success: false, + status: "❗Upewnij się, że wszystkie pola są wypełnione przed mintowaniem.", + } + } + + //tworzenie metadanych + const metadata = new Object() + metadata.name = name + metadata.image = url + metadata.description = description + + //wywołanie pinata + const pinataResponse = await pinJSONToIPFS(metadata) + if (!pinataResponse.success) { + return { + success: false, + status: "😢 Coś poszło nie tak podczas przesyłania Twojego tokenURI.", + } + } + const tokenURI = pinataResponse.pinataUrl +} +``` + +Zauważ, że przechowujemy odpowiedź naszego wywołania do `pinJSONToIPFS(metadata)` w obiekcie `pinataResponse`. Następnie analizujemy ten obiekt pod kątem ewentualnych błędów. + +Jeśli wystąpi błąd, zwracamy obiekt JSON, w którym wartość logiczna `success` jest fałszywa, a nasz ciąg `status` informuje, że nasze wywołanie nie powiodło się. W przeciwnym razie wyodrębniamy `pinataURL` z `pinataResponse` i przechowujemy go jako naszą zmienną `tokenURI`. + +Teraz nadszedł czas, aby załadować nasz inteligentny kontrakt za pomocą Alchemy Web3 API, które zainicjowaliśmy na początku naszego pliku. Dodaj następującą linię kodu na dole funkcji `mintNFT`, aby ustawić kontrakt w globalnej zmiennej `window.contract`: + +```javascript +window.contract = await new web3.eth.Contract(contractABI, contractAddress) +``` + +Ostatnią rzeczą do dodania w naszej funkcji `mintNFT` jest nasza transakcja Ethereum: + +```javascript +//ustaw swoją transakcję Ethereum +const transactionParameters = { + to: contractAddress, // Wymagane, z wyjątkiem publikacji kontraktów. + from: window.ethereum.selectedAddress, // musi pasować do aktywnego adresu użytkownika. + data: window.contract.methods + .mintNFT(window.ethereum.selectedAddress, tokenURI) + .encodeABI(), //wykonaj wywołanie do inteligentnego kontraktu NFT +} + +//podpisz transakcję przez MetaMask +try { + const txHash = await window.ethereum.request({ + method: "eth_sendTransaction", + params: [transactionParameters], + }) + return { + success: true, + status: + "✅ Sprawdź swoją transakcję na Etherscan: https://ropsten.etherscan.io/tx/" + + txHash, + } +} catch (error) { + return { + success: false, + status: "😥 Coś poszło nie tak: " + error.message, + } +} +``` + +Jeśli jesteś już zaznajomiony z transakcjami Ethereum, zauważysz, że struktura jest dość podobna do tego, co widziałeś. + +- Po pierwsze, ustawiamy nasze parametry transakcji. + - `to` określa adres odbiorcy (nasz inteligentny kontrakt) + - `from` określa sygnatariusza transakcji (połączony adres użytkownika z MetaMask: `window.ethereum.selectedAddress`) + - `data` zawiera wywołanie metody `mintNFT` naszego inteligentnego kontraktu, która jako dane wejściowe otrzymuje nasz `tokenURI` i adres portfela użytkownika, `window.ethereum.selectedAddress` +- Następnie wykonujemy wywołanie await, `window.ethereum.request,`, w którym prosimy MetaMask o podpisanie transakcji. Zauważ, że w tym żądaniu określamy naszą metodę eth (eth_SentTransaction) i przekazujemy nasze `transactionParameters`. W tym momencie w przeglądarce otworzy się MetaMask i poprosi użytkownika o podpisanie lub odrzucenie transakcji. + - Jeśli transakcja zakończy się powodzeniem, funkcja zwróci obiekt JSON, w którym wartość logiczna `success` jest ustawiona na true, a ciąg `status` prosi użytkownika o sprawdzenie Etherscan w celu uzyskania dalszych informacji o transakcji. + - Jeśli transakcja się nie powiedzie, funkcja zwróci obiekt JSON, w którym wartość logiczna `success` jest ustawiona na false, a ciąg `status` przekazuje komunikat o błędzie. + +W sumie nasza funkcja `mintNFT` powinna wyglądać tak: + +```javascript +export const mintNFT = async (url, name, description) => { + //obsługa błędów + if (url.trim() == "" || name.trim() == "" || description.trim() == "") { + return { + success: false, + status: "❗Upewnij się, że wszystkie pola są wypełnione przed mintowaniem.", + } + } + + //tworzenie metadanych + const metadata = new Object() + metadata.name = name + metadata.image = url + metadata.description = description + + //żądanie przypięcia pinata + const pinataResponse = await pinJSONToIPFS(metadata) + if (!pinataResponse.success) { + return { + success: false, + status: "😢 Coś poszło nie tak podczas przesyłania Twojego tokenURI.", + } + } + const tokenURI = pinataResponse.pinataUrl + + //wczytaj inteligentny kontrakt + window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract(); + + //ustaw swoją transakcję Ethereum + const transactionParameters = { + to: contractAddress, // Wymagane, z wyjątkiem publikacji kontraktów. + from: window.ethereum.selectedAddress, // musi pasować do aktywnego adresu użytkownika. + data: window.contract.methods + .mintNFT(window.ethereum.selectedAddress, tokenURI) + .encodeABI(), //wykonaj wywołanie do inteligentnego kontraktu NFT + } + + //podpisz transakcję przez MetaMask + try { + const txHash = await window.ethereum.request({ + method: "eth_sendTransaction", + params: [transactionParameters], + }) + return { + success: true, + status: + "✅ Sprawdź swoją transakcję na Etherscan: https://ropsten.etherscan.io/tx/" + + txHash, + } + } catch (error) { + return { + success: false, + status: "😥 Coś poszło nie tak: " + error.message, + } + } +} +``` + +To jest jedna gigantyczna funkcja! Teraz musimy tylko podłączyć naszą funkcję `mintNFT` do naszego komponentu `Minter.js`... + +## Podłącz mintNFT do naszego frontendu Minter.js {#connect-our-frontend} + +Otwórz plik `Minter.js` i zaktualizuj linię `import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js";` na górze, aby wyglądała tak: + +```javascript +import { + connectWallet, + getCurrentWalletConnected, + mintNFT, +} from "./utils/interact.js" +``` + +Na koniec zaimplementuj funkcję `onMintPressed`, aby wykonać wywołanie await do zaimportowanej funkcji `mintNFT` i zaktualizować zmienną stanu `status`, aby odzwierciedlić, czy nasza transakcja powiodła się, czy nie: + +```javascript +const onMintPressed = async () => { + const { status } = await mintNFT(url, name, description) + setStatus(status) +} +``` + +## Wdróż swoje NFT na działającej stronie internetowej {#deploy-your-NFT} + +Gotowy na udostępnienie swojego projektu na żywo, aby użytkownicy mogli z nim wchodzić w interakcję? Sprawdź [ten samouczek](https://docs.alchemy.com/alchemy/tutorials/nft-minter/how-do-i-deploy-nfts-online) dotyczący wdrażania Twojego Minera na działającej stronie internetowej. + +Jeszcze jeden krok... + +## Szturmem zdobądź świat blockchaina {#take-the-blockchain-world-by-storm} + +Żartuję, dotarłeś do końca samouczka! + +Podsumowując, budując minter NFT, z powodzeniem nauczyłeś się, jak: + +- Połączyć się z MetaMask za pośrednictwem projektu frontendowego +- Wywoływać metody inteligentnego kontraktu z poziomu frontendu +- Podpisywać transakcje za pomocą MetaMask + +Prawdopodobnie chcesz mieć możliwość pochwalenia się NFT wygenerowanymi za pośrednictwem Twojej dapki w swoim portfelu — więc koniecznie sprawdź nasz krótki samouczek [„Jak wyświetlić swoje NFT w portfelu”](https://www.alchemy.com/docs/how-to-view-your-nft-in-your-mobile-wallet)! + +I, jak zawsze, jeśli masz jakieś pytania, jesteśmy tutaj, aby pomóc na [Discordzie Alchemy](https://discord.gg/gWuC7zB). Nie możemy się doczekać, aby zobaczyć, jak zastosujesz koncepcje z tego samouczka w swoich przyszłych projektach! diff --git a/public/content/translations/pl/developers/tutorials/optimism-std-bridge-annotated-code/index.md b/public/content/translations/pl/developers/tutorials/optimism-std-bridge-annotated-code/index.md new file mode 100644 index 00000000000..fe4f1a8254e --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/optimism-std-bridge-annotated-code/index.md @@ -0,0 +1,1356 @@ +--- +title: "Przegląd standardowego kontraktu mostu Optimism" +description: "Jak działa standardowy most dla Optimism? Dlaczego działa w ten sposób?" +author: Ori Pomerantz +tags: [ "solidity", "most", "warstwa 2" ] +skill: intermediate +published: 2022-03-30 +lang: pl +--- + +[Optimism](https://www.optimism.io/) to [rollup optymistyczny](/developers/docs/scaling/optimistic-rollups/). +Rollupy optymistyczne mogą przetwarzać transakcje po znacznie niższej cenie niż sieć główna Ethereum (znana również jako warstwa 1 lub L1), ponieważ transakcje są przetwarzane tylko przez kilka węzłów, a nie przez każdy węzeł w sieci. +Jednocześnie wszystkie dane są zapisywane w L1, dzięki czemu wszystko można udowodnić i zrekonstruować z zachowaniem wszystkich gwarancji integralności i dostępności sieci głównej. + +Aby używać aktywów L1 na Optimism (lub dowolnej innej L2), aktywa te muszą być [przeniesione przez most](/bridges/#prerequisites). +Jednym ze sposobów na osiągnięcie tego jest zablokowanie przez użytkowników aktywów (najczęściej są to tokeny ETH i [ERC-20](/developers/docs/standards/tokens/erc-20/)) na L1 i otrzymanie równoważnych aktywów do wykorzystania na L2. +Ostatecznie ten, kto je zdobędzie, może chcieć przenieść je z powrotem na L1 za pomocą mostu. +W takim przypadku aktywa są spalane na L2, a następnie uwalniane z powrotem do użytkownika na L1. + +W ten sposób działa [standardowy most Optimism](https://docs.optimism.io/app-developers/bridging/standard-bridge). +W tym artykule przeanalizujemy kod źródłowy tego mostu, aby zobaczyć, jak on działa i zbadać go jako przykład dobrze napisanego kodu Solidity. + +## Przepływy sterowania {#control-flows} + +Most ma dwa główne przepływy: + +- Depozyt (z L1 do L2) +- Wypłata (z L2 do L1) + +### Przepływ depozytu {#deposit-flow} + +#### Warstwa 1 {#deposit-flow-layer-1} + +1. W przypadku deponowania ERC-20 deponent udziela mostowi zgody na wydanie zdeponowanej kwoty +2. Deponent wywołuje most L1 (`depositERC20`, `depositERC20To`, `depositETH` lub `depositETHTo`) +3. Most L1 przejmuje w posiadanie przeniesione aktywa + - ETH: Aktywa są przekazywane przez deponenta w ramach wywołania + - ERC-20: Aktywa są przenoszone przez most na siebie, korzystając ze zgody udzielonej przez deponenta +4. Most L1 używa mechanizmu wiadomości między domenami do wywołania funkcji `finalizeDeposit` na moście L2 + +#### Warstwa 2 {#deposit-flow-layer-2} + +5. Most L2 weryfikuje, czy wywołanie funkcji `finalizeDeposit` jest prawidłowe: + - Pochodzi z kontraktu wiadomości między domenami + - Pochodził pierwotnie z mostu na L1 +6. Most L2 sprawdza, czy kontrakt tokenu ERC-20 na L2 jest prawidłowy: + - Kontrakt L2 informuje, że jego odpowiednik L1 jest taki sam jak ten, z którego pochodzą tokeny na L1 + - Kontrakt L2 informuje, że obsługuje prawidłowy interfejs ([używając ERC-165](https://eips.ethereum.org/EIPS/eip-165)). +7. Jeśli kontrakt L2 jest prawidłowy, wywołaj go, aby wyemitować odpowiednią liczbę tokenów na odpowiedni adres. Jeśli nie, rozpocznij proces wypłaty, aby umożliwić użytkownikowi odebranie tokenów na L1. + +### Przepływ wypłat {#withdrawal-flow} + +#### Warstwa 2 {#withdrawal-flow-layer-2} + +1. Wypłacający wywołuje most L2 (`withdraw` lub `withdrawTo`) +2. Most L2 spala odpowiednią liczbę tokenów należących do `msg.sender` +3. Most L2 używa mechanizmu wiadomości między domenami do wywołania funkcji `finalizeETHWithdrawal` lub `finalizeERC20Withdrawal` na moście L1 + +#### Warstwa 1 {#withdrawal-flow-layer-1} + +4. Most L1 weryfikuje, czy wywołanie funkcji `finalizeETHWithdrawal` lub `finalizeERC20Withdrawal` jest prawidłowe: + - Pochodzi z mechanizmu wiadomości między domenami + - Pochodził pierwotnie z mostu na L2 +5. Most L1 przekazuje odpowiednie aktywa (ETH lub ERC-20) na odpowiedni adres + +## Kod warstwy 1 {#layer-1-code} + +To jest kod, który działa na L1, w sieci głównej Ethereum. + +### IL1ERC20Bridge {#IL1ERC20Bridge} + +[Ten interfejs jest zdefiniowany tutaj](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/IL1ERC20Bridge.sol). +Zawiera on funkcje i definicje wymagane do przenoszenia tokenów ERC-20 za pomocą mostu. + +```solidity +// SPDX-License-Identifier: MIT +``` + +[Większość kodu Optimism jest udostępniana na licencji MIT](https://help.optimism.io/hc/en-us/articles/4411908707995-What-software-license-does-Optimism-use-). + +```solidity +pragma solidity >0.5.0 <0.9.0; +``` + +W chwili pisania tego tekstu najnowsza wersja Solidity to 0.8.12. +Dopóki wersja 0.9.0 nie zostanie wydana, nie wiemy, czy ten kod jest z nią kompatybilny, czy nie. + +```solidity +/** + * @title IL1ERC20Bridge + */ +interface IL1ERC20Bridge { + /********** + * Zdarzenia * + **********/ + + event ERC20DepositInitiated( +``` + +W terminologii mostów Optimism _depozyt_ oznacza transfer z L1 do L2, a _wypłata_ oznacza transfer z L2 do L1. + +```solidity + address indexed _l1Token, + address indexed _l2Token, +``` + +W większości przypadków adres ERC-20 na L1 nie jest taki sam jak adres równoważnego ERC-20 na L2. +[Listę adresów tokenów można zobaczyć tutaj](https://static.optimism.io/optimism.tokenlist.json). +Adres z `chainId` 1 znajduje się na L1 (sieć główna), a adres z `chainId` 10 na L2 (Optimism). +Pozostałe dwie wartości `chainId` dotyczą sieci testowej Kovan (42) i sieci testowej Optimistic Kovan (69). + +```solidity + address indexed _from, + address _to, + uint256 _amount, + bytes _data + ); +``` + +Możliwe jest dodawanie notatek do transferów, w którym to przypadku są one dodawane do zdarzeń, które je raportują. + +```solidity + event ERC20WithdrawalFinalized( + address indexed _l1Token, + address indexed _l2Token, + address indexed _from, + address _to, + uint256 _amount, + bytes _data + ); +``` + +Ten sam kontrakt mostu obsługuje transfery w obu kierunkach. +W przypadku mostu L1 oznacza to inicjalizację depozytów i finalizację wypłat. + +```solidity + + /******************** + * Funkcje publiczne * + ********************/ + + /** + * @dev pobierz adres odpowiedniego kontraktu mostu L2. + * @return Adres odpowiedniego kontraktu mostu L2. + */ + function l2TokenBridge() external returns (address); +``` + +Ta funkcja nie jest tak naprawdę potrzebna, ponieważ na L2 jest to kontrakt wdrożony wstępnie, więc zawsze znajduje się pod adresem `0x4200000000000000000000000000000000000010`. +Jest tu dla symetrii z mostem L2, ponieważ adres mostu L1 _nie jest_ trywialny do poznania. + +```solidity + /** + * @dev zdeponuj kwotę ERC20 na saldo wywołującego na L2. + * @param _l1Token Adres ERC20 L1, który deponujemy + * @param _l2Token Adres odpowiedniego ERC20 L2 + * @param _amount Kwota ERC20 do zdeponowania + * @param _l2Gas Limit gazu wymagany do ukończenia depozytu na L2. + * @param _data Opcjonalne dane do przesłania do L2. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function depositERC20( + address _l1Token, + address _l2Token, + uint256 _amount, + uint32 _l2Gas, + bytes calldata _data + ) external; +``` + +Parametr `_l2Gas` to ilość gazu L2, którą transakcja może zużyć. +[Do pewnego (wysokiego) limitu jest to darmowe](https://community.optimism.io/docs/developers/bridge/messaging/#for-l1-%E2%87%92-l2-transactions-2), więc o ile kontrakt ERC-20 nie robi czegoś naprawdę dziwnego podczas emisji, nie powinno to stanowić problemu. +Ta funkcja obsługuje typowy scenariusz, w którym użytkownik przenosi aktywa za pomocą mostu na ten sam adres na innym blockchainie. + +```solidity + /** + * @dev zdeponuj kwotę ERC20 na saldo odbiorcy na L2. + * @param _l1Token Adres ERC20 L1, który deponujemy + * @param _l2Token Adres odpowiedniego ERC20 L2 + * @param _to Adres L2, na który ma być zaksięgowana wypłata. + * @param _amount Kwota ERC20 do zdeponowania. + * @param _l2Gas Limit gazu wymagany do ukończenia depozytu na L2. + * @param _data Opcjonalne dane do przesłania do L2. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function depositERC20To( + address _l1Token, + address _l2Token, + address _to, + uint256 _amount, + uint32 _l2Gas, + bytes calldata _data + ) external; +``` + +Ta funkcja jest niemal identyczna jak `depositERC20`, ale pozwala na wysłanie ERC-20 na inny adres. + +```solidity + /************************* + * Funkcje międzyłańcuchowe * + *************************/ + + /** + * @dev Zakończ wypłatę z L2 do L1 i zasil saldo odbiorcy tokenem + * L1 ERC20. + * To wywołanie nie powiedzie się, jeśli zainicjowana wypłata z L2 nie została sfinalizowana. + * + * @param _l1Token Adres tokenu L1, dla którego finalizowana jest wypłata. + * @param _l2Token Adres tokenu L2, na którym zainicjowano wypłatę. + * @param _from Adres L2 inicjujący transfer. + * @param _to Adres L1, na który ma zostać zaksięgowana wypłata. + * @param _amount Kwota ERC20 do zdeponowania. + * @param _data Dane dostarczone przez nadawcę na L2. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function finalizeERC20Withdrawal( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external; +} +``` + +Wypłaty (i inne wiadomości z L2 do L1) w Optimism to proces dwuetapowy: + +1. Transakcja inicjująca na L2. +2. Transakcja finalizująca lub odbierająca na L1. + Ta transakcja musi nastąpić po zakończeniu [okresu kwestionowania błędu](https://community.optimism.io/docs/how-optimism-works/#fault-proofs) dla transakcji L2. + +### IL1StandardBridge {#il1standardbridge} + +[Ten interfejs jest zdefiniowany tutaj](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/IL1StandardBridge.sol). +Ten plik zawiera definicje zdarzeń i funkcji dla ETH. +Definicje te są bardzo podobne do tych zdefiniowanych powyżej w `IL1ERC20Bridge` dla ERC-20. + +Interfejs mostu jest podzielony na dwa pliki, ponieważ niektóre tokeny ERC-20 wymagają niestandardowego przetwarzania i nie mogą być obsługiwane przez standardowy most. +W ten sposób niestandardowy most, który obsługuje taki token, może zaimplementować `IL1ERC20Bridge` i nie musi również przenosić ETH. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity >0.5.0 <0.9.0; + +import "./IL1ERC20Bridge.sol"; + +/** + * @title IL1StandardBridge + */ +interface IL1StandardBridge is IL1ERC20Bridge { + /********** + * Zdarzenia * + **********/ + event ETHDepositInitiated( + address indexed _from, + address indexed _to, + uint256 _amount, + bytes _data + ); +``` + +To zdarzenie jest prawie identyczne z wersją ERC-20 (`ERC20DepositInitiated`), z wyjątkiem braku adresów tokenów L1 i L2. +To samo dotyczy innych zdarzeń i funkcji. + +```solidity + event ETHWithdrawalFinalized( + . + . + . + ); + + /******************** + * Funkcje publiczne * + ********************/ + + /** + * @dev Zdeponuj kwotę ETH na saldo wywołującego na L2. + . + . + . + */ + function depositETH(uint32 _l2Gas, bytes calldata _data) external payable; + + /** + * @dev Zdeponuj kwotę ETH na saldo odbiorcy na L2. + . + . + . + */ + function depositETHTo( + address _to, + uint32 _l2Gas, + bytes calldata _data + ) external payable; + + /************************* + * Funkcje międzyłańcuchowe * + *************************/ + + /** + * @dev Zakończ wypłatę z L2 do L1 i zasil saldo odbiorcy tokenem + * L1 ETH. Ponieważ tylko xDomainMessenger może wywołać tę funkcję, nigdy nie zostanie ona wywołana + * przed sfinalizowaniem wypłaty. + . + . + . + */ + function finalizeETHWithdrawal( + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external; +} +``` + +### CrossDomainEnabled {#crossdomainenabled} + +[Ten kontrakt](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/bridge/CrossDomainEnabled.sol) jest dziedziczony przez oba mosty ([L1](#the-l1-bridge-contract) i [L2](#the-l2-bridge-contract)) do wysyłania wiadomości do drugiej warstwy. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity >0.5.0 <0.9.0; + +/* Importy interfejsów */ +import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol"; +``` + +[Ten interfejs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/bridge/ICrossDomainMessenger.sol) informuje kontrakt, jak wysyłać wiadomości do drugiej warstwy, używając komunikatora między domenami. +Ten komunikator między domenami to zupełnie inny system i zasługuje na osobny artykuł, który mam nadzieję napisać w przyszłości. + +```solidity +/** + * @title CrossDomainEnabled + * @dev Kontrakt pomocniczy dla kontraktów wykonujących komunikację między domenami + * + * Użyty kompilator: zdefiniowany przez kontrakt dziedziczący + */ +contract CrossDomainEnabled { + /************* + * Zmienne * + *************/ + + // Kontrakt komunikatora używany do wysyłania i odbierania wiadomości z innej domeny. + address public messenger; + + /*************** + * Konstruktor * + ***************/ + + /** + * @param _messenger Adres CrossDomainMessenger w bieżącej warstwie. + */ + constructor(address _messenger) { + messenger = _messenger; + } +``` + +Jedyny parametr, który kontrakt musi znać, to adres komunikatora między domenami w tej warstwie. +Ten parametr jest ustawiany raz, w konstruktorze i nigdy się nie zmienia. + +```solidity + + /********************** + * Modyfikatory funkcji * + **********************/ + + /** + * Wymusza, aby zmodyfikowana funkcja była wywoływana tylko przez określone konto między domenami. + * @param _sourceDomainAccount Jedyne konto w domenie źródłowej, które jest + * uwierzytelnione do wywołania tej funkcji. + */ + modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) { +``` + +Komunikacja między domenami jest dostępna dla każdego kontraktu na blockchainie, na którym jest uruchomiona (zarówno w sieci głównej Ethereum, jak i Optimism). +Ale potrzebujemy, aby most po każdej stronie ufał _tylko_ określonym wiadomościom, jeśli pochodzą one z mostu po drugiej stronie. + +```solidity + require( + msg.sender == address(getCrossDomainMessenger()), + "OVM_XCHAIN: kontrakt komunikatora nieuwierzytelniony" + ); +``` + +Tylko wiadomości z odpowiedniego komunikatora między domenami (`messenger`, jak widać poniżej) mogą być zaufane. + +```solidity + + require( + getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount, + "OVM_XCHAIN: zły nadawca wiadomości między domenami" + ); +``` + +Sposób, w jaki komunikator między domenami dostarcza adres, który wysłał wiadomość z drugiej warstwy, to [funkcja `.xDomainMessageSender()`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol#L122-L128). +Dopóki jest wywoływana w transakcji zainicjowanej przez wiadomość, może dostarczyć tych informacji. + +Musimy upewnić się, że otrzymana wiadomość pochodzi z drugiego mostu. + +```solidity + + _; + } + + /********************** + * Funkcje wewnętrzne * + **********************/ + + /** + * Pobiera komunikator, zazwyczaj z pamięci masowej. Ta funkcja jest udostępniana na wypadek, gdyby kontrakt potomny + * musiał ją nadpisać. + * @return Adres kontraktu komunikatora między domenami, który powinien być używany. + */ + function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) { + return ICrossDomainMessenger(messenger); + } +``` + +Ta funkcja zwraca komunikator między domenami. +Używamy funkcji, a nie zmiennej `messenger`, aby umożliwić kontraktom dziedziczącym po tym kontrakcie użycie algorytmu do określenia, którego komunikatora między domenami użyć. + +```solidity + + /** + * Wysyła wiadomość do konta w innej domenie + * @param _crossDomainTarget Docelowy odbiorca w domenie docelowej + * @param _message Dane do wysłania do celu (zazwyczaj calldata do funkcji z + * `onlyFromCrossDomainAccount()`) + * @param _gasLimit Limit gazu na odbiór wiadomości w domenie docelowej. + */ + function sendCrossDomainMessage( + address _crossDomainTarget, + uint32 _gasLimit, + bytes memory _message +``` + +Na koniec funkcja, która wysyła wiadomość do drugiej warstwy. + +```solidity + ) internal { + // slither-disable-next-line reentrancy-events, reentrancy-benign +``` + +[Slither](https://github.com/crytic/slither) to statyczny analizator, który Optimism uruchamia na każdym kontrakcie w poszukiwaniu luk w zabezpieczeniach i innych potencjalnych problemów. +W tym przypadku poniższa linia uruchamia dwie luki w zabezpieczeniach: + +1. [Zdarzenia ponownego wejścia](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) +2. [Łagodne ponowne wejście](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) + +```solidity + getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit); + } +} +``` + +W tym przypadku nie martwimy się o ponowne wejście, ponieważ wiemy, że `getCrossDomainMessenger()` zwraca zaufany adres, nawet jeśli Slither nie ma sposobu, aby to wiedzieć. + +### Kontrakt mostu L1 {#the-l1-bridge-contract} + +[Kod źródłowy tego kontraktu znajduje się tutaj](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; +``` + +Interfejsy mogą być częścią innych kontraktów, więc muszą obsługiwać szeroki zakres wersji Solidity. +Ale sam most jest naszym kontraktem i możemy być rygorystyczni co do wersji Solidity, której używa. + +```solidity +/* Importy interfejsów */ +import { IL1StandardBridge } from "./IL1StandardBridge.sol"; +import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol"; +``` + +[IL1ERC20Bridge](#IL1ERC20Bridge) i [IL1StandardBridge](#IL1StandardBridge) zostały wyjaśnione powyżej. + +```solidity +import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol"; +``` + +[Ten interfejs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/IL2ERC20Bridge.sol) pozwala nam tworzyć wiadomości do kontrolowania standardowego mostu na L2. + +```solidity +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +``` + +[Ten interfejs](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol) pozwala nam kontrolować kontrakty ERC-20. +[Więcej na ten temat możesz przeczytać tutaj](/developers/tutorials/erc20-annotated-code/#the-interface). + +```solidity +/* Importy bibliotek */ +import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol"; +``` + +[Jak wyjaśniono powyżej](#crossdomainenabled), ten kontrakt jest używany do komunikacji międzywarstwowej. + +```solidity +import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol"; +``` + +[`Lib_PredeployAddresses`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/constants/Lib_PredeployAddresses.sol) zawiera adresy kontraktów L2, które zawsze mają ten sam adres. Obejmuje to standardowy most na L2. + +```solidity +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +``` + +[Narzędzia adresowe OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol). Jest używany do rozróżniania adresów kontraktów od adresów należących do kont zewnętrznych (EOA). + +Należy pamiętać, że nie jest to idealne rozwiązanie, ponieważ nie ma sposobu, aby odróżnić wywołania bezpośrednie od wywołań z konstruktora kontraktu, ale przynajmniej pozwala nam to zidentyfikować i zapobiec niektórym typowym błędom użytkowników. + +```solidity +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +``` + +[Standard ERC-20](https://eips.ethereum.org/EIPS/eip-20) obsługuje dwa sposoby raportowania niepowodzenia przez kontrakt: + +1. Przywróć +2. Zwróć `false` + +Obsługa obu przypadków skomplikowałaby nasz kod, dlatego zamiast tego używamy [`SafeERC20` OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol), co zapewnia, że [wszystkie niepowodzenia skutkują przywróceniem](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol#L96). + +```solidity +/** + * @title L1StandardBridge + * @dev Most L1 ETH i ERC20 to kontrakt, który przechowuje zdeponowane środki L1 i standardowe + * tokeny, które są w użyciu na L2. Synchronizuje on odpowiedni most L2, informując go o depozytach + * i nasłuchując na nowo sfinalizowane wypłaty. + * + */ +contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled { + using SafeERC20 for IERC20; +``` + +Ta linia określa, że za każdym razem, gdy używamy interfejsu `IERC20`, używamy opakowania `SafeERC20`. + +```solidity + + /******************************** + * Odniesienia do kontraktów zewnętrznych * + ********************************/ + + address public l2TokenBridge; +``` + +Adres [L2StandardBridge](#the-l2-bridge-contract). + +```solidity + + // Mapuje token L1 do tokenu L2 do salda zdeponowanego tokenu L1 + mapping(address => mapping(address => uint256)) public deposits; +``` + +Podwójne [mapowanie](https://www.tutorialspoint.com/solidity/solidity_mappings.htm) takie jak to jest sposobem na zdefiniowanie [dwuwymiarowej tablicy rzadkiej](https://en.wikipedia.org/wiki/Sparse_matrix). +Wartości w tej strukturze danych są identyfikowane jako `deposit[L1 token addr][L2 token addr]`. +Wartość domyślna to zero. +Tylko komórki, które są ustawione na inną wartość, są zapisywane w pamięci. + +```solidity + + /*************** + * Konstruktor * + ***************/ + + // Ten kontrakt znajduje się za proxy, więc parametry konstruktora nie będą używane. + constructor() CrossDomainEnabled(address(0)) {} +``` + +Aby móc uaktualnić ten kontrakt bez konieczności kopiowania wszystkich zmiennych w pamięci. +Aby to zrobić, używamy [`Proxy`](https://docs.openzeppelin.com/contracts/3.x/api/proxy), kontraktu, który używa [`delegatecall`](https://solidity-by-example.org/delegatecall/) do przekazywania wywołań do osobnego kontraktu, którego adres jest przechowywany przez kontrakt proxy (podczas aktualizacji informujesz proxy o zmianie tego adresu). +Gdy używasz `delegatecall`, pamięć pozostaje pamięcią kontraktu _wywołującego_, więc wartości wszystkich zmiennych stanu kontraktu pozostają nienaruszone. + +Jednym z efektów tego wzorca jest to, że pamięć kontraktu, który jest _wywoływany_ przez `delegatecall`, nie jest używana, a zatem wartości konstruktora przekazane do niego nie mają znaczenia. +To jest powód, dla którego możemy podać bezsensowną wartość do konstruktora `CrossDomainEnabled`. +Jest to również powód, dla którego poniższa inicjalizacja jest oddzielona od konstruktora. + +```solidity + /****************** + * Inicjalizacja * + ******************/ + + /** + * @param _l1messenger Adres komunikatora L1 używany do komunikacji międzyłańcuchowej. + * @param _l2TokenBridge Adres standardowego mostu L2. + */ + // slither-disable-next-line external-function +``` + +Ten [test Slither](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) identyfikuje funkcje, które nie są wywoływane z kodu kontraktu i dlatego mogą być zadeklarowane jako `external` zamiast `public`. +Koszt gazu funkcji `external` może być niższy, ponieważ mogą one być dostarczane z parametrami w calldata. +Funkcje zadeklarowane jako `public` muszą być dostępne z wnętrza kontraktu. +Kontrakty nie mogą modyfikować własnych calldata, więc parametry muszą znajdować się w pamięci. +Gdy taka funkcja jest wywoływana zewnętrznie, konieczne jest skopiowanie calldata do pamięci, co kosztuje gaz. +W tym przypadku funkcja jest wywoływana tylko raz, więc nieefektywność nie ma dla nas znaczenia. + +```solidity + function initialize(address _l1messenger, address _l2TokenBridge) public { + require(messenger == address(0), "Kontrakt został już zainicjowany."); +``` + +Funkcja `initialize` powinna być wywoływana tylko raz. +Jeśli zmieni się adres komunikatora między domenami L1 lub mostu tokenów L2, tworzymy nowe proxy i nowy most, który je wywołuje. +Jest to mało prawdopodobne, chyba że cały system zostanie uaktualniony, co jest bardzo rzadkim zjawiskiem. + +Należy pamiętać, że ta funkcja nie ma żadnego mechanizmu ograniczającego, _kto_ może ją wywołać. +Oznacza to, że teoretycznie napastnik może poczekać, aż wdrożymy proxy i pierwszą wersję mostu, a następnie [wykonać front-running](https://solidity-by-example.org/hacks/front-running/), aby dostać się do funkcji `initialize` przed legalnym użytkownikiem. Ale istnieją dwie metody, aby temu zapobiec: + +1. Jeśli kontrakty są wdrażane nie bezpośrednio przez EOA, ale [w transakcji, w której inny kontrakt je tworzy](https://medium.com/upstate-interactive/creating-a-contract-with-a-smart-contract-bdb67c5c8595), cały proces może być atomowy i zakończyć się przed wykonaniem jakiejkolwiek innej transakcji. +2. Jeśli legalne wywołanie `initialize` nie powiedzie się, zawsze można zignorować nowo utworzone proxy i most i utworzyć nowe. + +```solidity + messenger = _l1messenger; + l2TokenBridge = _l2TokenBridge; + } +``` + +To są dwa parametry, które most musi znać. + +```solidity + + /************** + * Deponowanie * + **************/ + + /** @dev Modyfikator wymagający, aby nadawca był EOA. To sprawdzenie mogłoby zostać ominięte przez złośliwy + * kontrakt poprzez initcode, ale zapobiega to błędowi użytkownika, którego chcemy uniknąć. + */ + modifier onlyEOA() { + // Używane do zatrzymywania depozytów z kontraktów (unikanie przypadkowej utraty tokenów) + require(!Address.isContract(msg.sender), "Konto nie jest EOA"); + _; + } +``` + +To jest powód, dla którego potrzebowaliśmy narzędzi `Address` od OpenZeppelin. + +```solidity + /** + * @dev Tę funkcję można wywołać bez danych + * w celu zdeponowania kwoty ETH na saldzie wywołującego na L2. + * Ponieważ funkcja odbioru nie przyjmuje danych, konserwatywna + * domyślna kwota jest przekazywana do L2. + */ + receive() external payable onlyEOA { + _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes("")); + } +``` + +Ta funkcja istnieje w celach testowych. +Zauważ, że nie pojawia się w definicjach interfejsu - nie jest przeznaczona do normalnego użytku. + +```solidity + /** + * @inheritdoc IL1StandardBridge + */ + function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA { + _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data); + } + + /** + * @inheritdoc IL1StandardBridge + */ + function depositETHTo( + address _to, + uint32 _l2Gas, + bytes calldata _data + ) external payable { + _initiateETHDeposit(msg.sender, _to, _l2Gas, _data); + } +``` + +Te dwie funkcje są opakowaniami wokół `_initiateETHDeposit`, funkcji obsługującej faktyczny depozyt ETH. + +```solidity + /** + * @dev Wykonuje logikę depozytów, przechowując ETH i informując bramę L2 ETH o + * depozycie. + * @param _from Konto, z którego ma być pobrany depozyt na L1. + * @param _to Konto, na które ma być przekazany depozyt na L2. + * @param _l2Gas Limit gazu wymagany do ukończenia depozytu na L2. + * @param _data Opcjonalne dane do przesłania do L2. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function _initiateETHDeposit( + address _from, + address _to, + uint32 _l2Gas, + bytes memory _data + ) internal { + // Skonstruuj calldata dla wywołania finalizeDeposit + bytes memory message = abi.encodeWithSelector( +``` + +Sposób działania wiadomości między domenami polega na tym, że kontrakt docelowy jest wywoływany z wiadomością jako jego calldata. +Kontrakty Solidity zawsze interpretują swoje calldata zgodnie z +[specyfikacjami ABI](https://docs.soliditylang.org/en/v0.8.12/abi-spec.html). +Funkcja Solidity [`abi.encodeWithSelector`](https://docs.soliditylang.org/en/v0.8.12/units-and-global-variables.html#abi-encoding-and-decoding-functions) tworzy te calldata. + +```solidity + IL2ERC20Bridge.finalizeDeposit.selector, + address(0), + Lib_PredeployAddresses.OVM_ETH, + _from, + _to, + msg.value, + _data + ); +``` + +Wiadomość tutaj polega na wywołaniu [funkcji `finalizeDeposit`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol#L141-L148) z tymi parametrami: + +| Parametr | Wartość | Znaczenie | +| ------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| \_l1Token | address(0) | Specjalna wartość oznaczająca ETH (który nie jest tokenem ERC-20) na L1 | +| \_l2Token | Lib_PredeployAddresses.OVM_ETH | Kontrakt L2, który zarządza ETH na Optimism, `0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000` (ten kontrakt jest przeznaczony wyłącznie do użytku wewnętrznego Optimism) | +| \_from | \_from | Adres na L1, który wysyła ETH | +| \_to | \_to | Adres na L2, który odbiera ETH | +| kwota | msg.value | Ilość wysłanych wei (które zostały już wysłane na most) | +| \_data | \_data | Dodatkowe dane do dołączenia do depozytu | + +```solidity + // Wyślij calldata do L2 + // slither-disable-next-line reentrancy-events + sendCrossDomainMessage(l2TokenBridge, _l2Gas, message); +``` + +Wyślij wiadomość za pośrednictwem komunikatora między domenami. + +```solidity + // slither-disable-next-line reentrancy-events + emit ETHDepositInitiated(_from, _to, msg.value, _data); + } +``` + +Wyemituj zdarzenie, aby poinformować o tym transferze każdą aplikację zdecentralizowaną, która go nasłuchuje. + +```solidity + /** + * @inheritdoc IL1ERC20Bridge + */ + function depositERC20( + . + . + . + ) external virtual onlyEOA { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data); + } + + /** + * @inheritdoc IL1ERC20Bridge + */ + function depositERC20To( + . + . + . + ) external virtual { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data); + } +``` + +Te dwie funkcje są opakowaniami wokół `_initiateERC20Deposit`, funkcji obsługującej faktyczny depozyt ERC-20. + +```solidity + /** + * @dev Wykonuje logikę depozytów, informując kontrakt tokenu zdeponowanego L2 + * o depozycie i wywołując obsługę w celu zablokowania środków L1. (np. transferFrom) + * + * @param _l1Token Adres ERC20 L1, który deponujemy + * @param _l2Token Adres odpowiedniego ERC20 L2 + * @param _from Konto, z którego ma być pobrany depozyt na L1 + * @param _to Konto, na które ma być przekazany depozyt na L2 + * @param _amount Kwota ERC20 do zdeponowania. + * @param _l2Gas Limit gazu wymagany do ukończenia depozytu na L2. + * @param _data Opcjonalne dane do przesłania do L2. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function _initiateERC20Deposit( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + uint32 _l2Gas, + bytes calldata _data + ) internal { +``` + +Ta funkcja jest podobna do `_initiateETHDeposit` powyżej, z kilkoma ważnymi różnicami. +Pierwsza różnica polega na tym, że ta funkcja otrzymuje adresy tokenów i kwotę do przeniesienia jako parametry. +W przypadku ETH wywołanie mostu już obejmuje transfer aktywów na konto mostu (`msg.value`). + +```solidity + // Gdy depozyt jest inicjowany na L1, most L1 przekazuje środki do siebie na przyszłe + // wypłaty. safeTransferFrom sprawdza również, czy kontrakt ma kod, więc to się nie powiedzie, jeśli + // _from jest EOA lub address(0). + // slither-disable-next-line reentrancy-events, reentrancy-benign + IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount); +``` + +Transfery tokenów ERC-20 przebiegają inaczej niż transfery ETH: + +1. Użytkownik (`_from`) daje mostowi upoważnienie do transferu odpowiednich tokenów. +2. Użytkownik wywołuje most z adresem kontraktu tokenu, kwotą itp. +3. Most transferuje tokeny (do siebie) w ramach procesu depozytowego. + +Pierwszy krok może nastąpić w osobnej transakcji niż dwa ostatnie. +Jednak front-running nie stanowi problemu, ponieważ dwie funkcje, które wywołują `_initiateERC20Deposit` (`depositERC20` i `depositERC20To`) wywołują tę funkcję tylko z `msg.sender` jako parametrem `_from`. + +```solidity + // Skonstruuj calldata dla _l2Token.finalizeDeposit(_to, _amount) + bytes memory message = abi.encodeWithSelector( + IL2ERC20Bridge.finalizeDeposit.selector, + _l1Token, + _l2Token, + _from, + _to, + _amount, + _data + ); + + // Wyślij calldata do L2 + // slither-disable-next-line reentrancy-events, reentrancy-benign + sendCrossDomainMessage(l2TokenBridge, _l2Gas, message); + + // slither-disable-next-line reentrancy-benign + deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount; +``` + +Dodaj zdeponowaną kwotę tokenów do struktury danych `deposits`. +Na L2 może istnieć wiele adresów odpowiadających temu samemu tokenowi ERC-20 L1, więc nie wystarczy użyć salda mostu tokenu ERC-20 L1, aby śledzić depozyty. + +```solidity + + // slither-disable-next-line reentrancy-events + emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data); + } + + /************************* + * Funkcje międzyłańcuchowe * + *************************/ + + /** + * @inheritdoc IL1StandardBridge + */ + function finalizeETHWithdrawal( + address _from, + address _to, + uint256 _amount, + bytes calldata _data +``` + +Most L2 wysyła wiadomość do komunikatora między domenami L2, co powoduje, że komunikator między domenami L1 wywołuje tę funkcję (oczywiście, gdy [transakcja finalizująca wiadomość](https://community.optimism.io/docs/developers/bridge/messaging/#fees-for-l2-%E2%87%92-l1-transactions) zostanie przesłana na L1). + +```solidity + ) external onlyFromCrossDomainAccount(l2TokenBridge) { +``` + +Upewnij się, że jest to _legalna_ wiadomość, pochodząca z komunikatora między domenami i pochodząca z mostu tokenów L2. +Ta funkcja służy do wypłacania ETH z mostu, więc musimy upewnić się, że jest wywoływana tylko przez upoważnionego wywołującego. + +```solidity + // slither-disable-next-line reentrancy-events + (bool success, ) = _to.call{ value: _amount }(new bytes(0)); +``` + +Sposobem na transfer ETH jest wywołanie odbiorcy z kwotą wei w `msg.value`. + +```solidity + require(success, "TransferHelper::safeTransferETH: Transfer ETH nie powiódł się"); + + // slither-disable-next-line reentrancy-events + emit ETHWithdrawalFinalized(_from, _to, _amount, _data); +``` + +Wyemituj zdarzenie dotyczące wypłaty. + +```solidity + } + + /** + * @inheritdoc IL1ERC20Bridge + */ + function finalizeERC20Withdrawal( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external onlyFromCrossDomainAccount(l2TokenBridge) { +``` + +Ta funkcja jest podobna do `finalizeETHWithdrawal` powyżej, z niezbędnymi zmianami dla tokenów ERC-20. + +```solidity + deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount; +``` + +Zaktualizuj strukturę danych `deposits`. + +```solidity + + // Gdy wypłata jest finalizowana na L1, most L1 przekazuje środki do wypłacającego + // slither-disable-next-line reentrancy-events + IERC20(_l1Token).safeTransfer(_to, _amount); + + // slither-disable-next-line reentrancy-events + emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data); + } + + + /***************************** + * Tymczasowe – Migracja ETH * + *****************************/ + + /** + * @dev Dodaje saldo ETH do konta. Ma to na celu umożliwienie migracji ETH + * ze starej bramy do nowej. + * UWAGA: Jest to pozostawione tylko na jedną aktualizację, abyśmy mogli otrzymać zmigrowane ETH ze + * starego kontraktu + */ + function donateETH() external payable {} +} +``` + +Istniała wcześniejsza implementacja mostu. +Kiedy przeszliśmy z tamtej implementacji na tę, musieliśmy przenieść wszystkie aktywa. +Tokeny ERC-20 można po prostu przenieść. +Jednak aby przetransferować ETH do kontraktu, potrzebna jest zgoda tego kontraktu, którą zapewnia nam `donateETH`. + +## Tokeny ERC-20 na L2 {#erc-20-tokens-on-l2} + +Aby token ERC-20 pasował do standardowego mostu, musi on zezwalać standardowemu mostowi, i _tylko_ standardowemu mostowi, na emisję tokenów. +Jest to konieczne, ponieważ mosty muszą zapewnić, że liczba tokenów w obiegu na Optimism jest równa liczbie tokenów zablokowanych w kontrakcie mostu L1. +Jeśli na L2 będzie zbyt wiele tokenów, niektórzy użytkownicy nie będą mogli przenieść swoich aktywów z powrotem na L1. +Zamiast zaufanego mostu, w zasadzie odtworzylibyśmy [bankowość rezerw cząstkowych](https://www.investopedia.com/terms/f/fractionalreservebanking.asp). +Jeśli na L1 jest zbyt wiele tokenów, niektóre z nich pozostaną na zawsze zablokowane w kontrakcie mostu, ponieważ nie ma sposobu, aby je uwolnić bez spalenia tokenów L2. + +### IL2StandardERC20 {#il2standarderc20} + +Każdy token ERC-20 na L2, który używa standardowego mostu, musi dostarczać [ten interfejs](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/standards/IL2StandardERC20.sol), który zawiera funkcje i zdarzenia potrzebne standardowemu mostowi. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +``` + +[Standardowy interfejs ERC-20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol) nie zawiera funkcji `mint` i `burn`. +Metody te nie są wymagane przez [standard ERC-20](https://eips.ethereum.org/EIPS/eip-20), który nie określa mechanizmów tworzenia i niszczenia tokenów. + +```solidity +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +``` + +[Interfejs ERC-165](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/introspection/IERC165.sol) służy do określania, jakie funkcje dostarcza kontrakt. +[Standard można przeczytać tutaj](https://eips.ethereum.org/EIPS/eip-165). + +```solidity +interface IL2StandardERC20 is IERC20, IERC165 { + function l1Token() external returns (address); +``` + +Ta funkcja dostarcza adres tokenu L1, który jest przenoszony na ten kontrakt za pomocą mostu. +Zauważ, że nie mamy podobnej funkcji w przeciwnym kierunku. +Musimy być w stanie przenosić dowolny token L1 za pomocą mostu, niezależnie od tego, czy wsparcie L2 było planowane podczas jego implementacji, czy nie. + +```solidity + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; + + event Mint(address indexed _account, uint256 _amount); + event Burn(address indexed _account, uint256 _amount); +} +``` + +Funkcje i zdarzenia do emisji (tworzenia) i spalania (niszczenia) tokenów. +Most powinien być jedynym podmiotem, który może uruchamiać te funkcje, aby zapewnić prawidłową liczbę tokenów (równą liczbie tokenów zablokowanych na L1). + +### L2StandardERC20 {#L2StandardERC20} + +[To jest nasza implementacja interfejsu `IL2StandardERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/standards/L2StandardERC20.sol). +O ile nie potrzebujesz jakiejś niestandardowej logiki, powinieneś użyć tej. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +``` + +[Kontrakt ERC-20 OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol). +Optimism nie wierzy w wynajdywanie koła na nowo, zwłaszcza gdy koło jest dobrze audytowane i musi być na tyle godne zaufania, aby przechowywać aktywa. + +```solidity +import "./IL2StandardERC20.sol"; + +contract L2StandardERC20 is IL2StandardERC20, ERC20 { + address public l1Token; + address public l2Bridge; +``` + +Są to dwa dodatkowe parametry konfiguracyjne, których wymagamy, a których ERC-20 normalnie nie wymaga. + +```solidity + + /** + * @param _l2Bridge Adres standardowego mostu L2. + * @param _l1Token Adres odpowiadającego mu tokenu L1. + * @param _name Nazwa ERC20. + * @param _symbol Symbol ERC20. + */ + constructor( + address _l2Bridge, + address _l1Token, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) { + l1Token = _l1Token; + l2Bridge = _l2Bridge; + } +``` + +Najpierw wywołaj konstruktor dla kontraktu, z którego dziedziczymy (`ERC20(_name, _symbol)`), a następnie ustaw nasze własne zmienne. + +```solidity + + modifier onlyL2Bridge() { + require(msg.sender == l2Bridge, "Tylko Most L2 może emitować i spalać"); + _; + } + + + // slither-disable-next-line external-function + function supportsInterface(bytes4 _interfaceId) public pure returns (bool) { + bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165 + bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^ + IL2StandardERC20.mint.selector ^ + IL2StandardERC20.burn.selector; + return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface; + } +``` + +Tak działa [ERC-165](https://eips.ethereum.org/EIPS/eip-165). +Każdy interfejs to liczba obsługiwanych funkcji i jest identyfikowany jako [alternatywa wykluczająca](https://en.wikipedia.org/wiki/Exclusive_or) [selektorów funkcji ABI](https://docs.soliditylang.org/en/v0.8.12/abi-spec.html#function-selector) tych funkcji. + +Most L2 używa ERC-165 jako testu poprawności, aby upewnić się, że kontrakt ERC-20, do którego wysyła aktywa, jest `IL2StandardERC20`. + +**Uwaga:** Nic nie stoi na przeszkodzie, aby nieuczciwy kontrakt dostarczał fałszywych odpowiedzi do `supportsInterface`, więc jest to mechanizm sprawdzania poprawności, _nie_ mechanizm bezpieczeństwa. + +```solidity + // slither-disable-next-line external-function + function mint(address _to, uint256 _amount) public virtual onlyL2Bridge { + _mint(_to, _amount); + + emit Mint(_to, _amount); + } + + // slither-disable-next-line external-function + function burn(address _from, uint256 _amount) public virtual onlyL2Bridge { + _burn(_from, _amount); + + emit Burn(_from, _amount); + } +} +``` + +Tylko most L2 może emitować i spalać aktywa. + +`_mint` i `_burn` są w rzeczywistości zdefiniowane w [kontrakcie ERC-20 OpenZeppelin](/developers/tutorials/erc20-annotated-code/#the-_mint-and-_burn-functions-_mint-and-_burn). +Ten kontrakt po prostu nie udostępnia ich na zewnątrz, ponieważ warunki emisji i spalania tokenów są tak zróżnicowane, jak liczba sposobów wykorzystania ERC-20. + +## Kod mostu L2 {#l2-bridge-code} + +To jest kod, który uruchamia most na Optimism. +[Źródło tego kontraktu znajduje się tutaj](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +/* Importy interfejsów */ +import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol"; +import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol"; +import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol"; +``` + +Interfejs [IL2ERC20Bridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/IL2ERC20Bridge.sol) jest bardzo podobny do [odpowiednika L1](#IL1ERC20Bridge), który widzieliśmy powyżej. +Istnieją dwie znaczące różnice: + +1. Na L1 inicjujesz depozyty i finalizujesz wypłaty. + Tutaj inicjujesz wypłaty i finalizujesz depozyty. +2. Na L1 konieczne jest rozróżnienie między tokenami ETH i ERC-20. + Na L2 możemy używać tych samych funkcji dla obu, ponieważ wewnętrznie salda ETH na Optimism są obsługiwane jako token ERC-20 z adresem [0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000](https://explorer.optimism.io/address/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000). + +```solidity +/* Importy bibliotek */ +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol"; +import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol"; + +/* Importy kontraktów */ +import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol"; + +/** + * @title L2StandardBridge + * @dev Most standardowy L2 to kontrakt, który współpracuje z mostem standardowym L1, aby + * umożliwić przejścia ETH i ERC20 między L1 i L2. + * Ten kontrakt działa jako emitent nowych tokenów, gdy dowiaduje się o depozytach na moście standardowym + * L1. + * Ten kontrakt działa również jako spalacz tokenów przeznaczonych do wypłaty, informując most L1 + * o uwolnieniu środków L1. + */ +contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled { + /******************************** + * Odniesienia do kontraktów zewnętrznych * + ********************************/ + + address public l1TokenBridge; +``` + +Śledź adres mostu L1. +Zauważ, że w przeciwieństwie do odpowiednika L1, tutaj _potrzebujemy_ tej zmiennej. +Adres mostu L1 nie jest znany z góry. + +```solidity + + /*************** + * Konstruktor * + ***************/ + + /** + * @param _l2CrossDomainMessenger Komunikator między domenami używany przez ten kontrakt. + * @param _l1TokenBridge Adres mostu L1 wdrożonego w głównej sieci. + */ + constructor(address _l2CrossDomainMessenger, address _l1TokenBridge) + CrossDomainEnabled(_l2CrossDomainMessenger) + { + l1TokenBridge = _l1TokenBridge; + } + + /*************** + * Wypłacanie * + ***************/ + + /** + * @inheritdoc IL2ERC20Bridge + */ + function withdraw( + address _l2Token, + uint256 _amount, + uint32 _l1Gas, + bytes calldata _data + ) external virtual { + _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data); + } + + /** + * @inheritdoc IL2ERC20Bridge + */ + function withdrawTo( + address _l2Token, + address _to, + uint256 _amount, + uint32 _l1Gas, + bytes calldata _data + ) external virtual { + _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data); + } +``` + +Te dwie funkcje inicjują wypłaty. +Zauważ, że nie ma potrzeby określania adresu tokenu L1. +Oczekuje się, że tokeny L2 podadzą nam adres swojego odpowiednika L1. + +```solidity + + /** + * @dev Wykonuje logikę wypłat, spalając token i informując + * bramę tokenów L1 o wypłacie. + * @param _l2Token Adres tokenu L2, na którym inicjowana jest wypłata. + * @param _from Konto, z którego ma być pobrana wypłata na L2. + * @param _to Konto, na które ma być przekazana wypłata na L1. + * @param _amount Ilość tokenu do wypłaty. + * @param _l1Gas Nieużywane, ale zawarte w celu zapewnienia potencjalnej zgodności w przyszłości. + * @param _data Opcjonalne dane do przesłania do L1. Dane te są udostępniane + * wyłącznie dla wygody zewnętrznych kontraktów. Oprócz egzekwowania maksymalnej + * długości, kontrakty te nie dają żadnych gwarancji co do ich zawartości. + */ + function _initiateWithdrawal( + address _l2Token, + address _from, + address _to, + uint256 _amount, + uint32 _l1Gas, + bytes calldata _data + ) internal { + // Gdy inicjowana jest wypłata, spalamy środki wypłacającego, aby zapobiec późniejszemu wykorzystaniu na L2 + // slither-disable-next-line reentrancy-events + IL2StandardERC20(_l2Token).burn(msg.sender, _amount); +``` + +Zauważ, że _nie_ polegamy na parametrze `_from`, ale na `msg.sender`, który jest znacznie trudniejszy do sfałszowania (o ile mi wiadomo, niemożliwy). + +```solidity + + // Skonstruuj calldata dla l1TokenBridge.finalizeERC20Withdrawal(_to, _amount) + // slither-disable-next-line reentrancy-events + address l1Token = IL2StandardERC20(_l2Token).l1Token(); + bytes memory message; + + if (_l2Token == Lib_PredeployAddresses.OVM_ETH) { +``` + +Na L1 konieczne jest rozróżnienie między ETH i ERC-20. + +```solidity + message = abi.encodeWithSelector( + IL1StandardBridge.finalizeETHWithdrawal.selector, + _from, + _to, + _amount, + _data + ); + } else { + message = abi.encodeWithSelector( + IL1ERC20Bridge.finalizeERC20Withdrawal.selector, + l1Token, + _l2Token, + _from, + _to, + _amount, + _data + ); + } + + // Wyślij wiadomość do mostu L1 + // slither-disable-next-line reentrancy-events + sendCrossDomainMessage(l1TokenBridge, _l1Gas, message); + + // slither-disable-next-line reentrancy-events + emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data); + } + + /************************************ + * Funkcja międzyłańcuchowa: Deponowanie * + ************************************/ + + /** + * @inheritdoc IL2ERC20Bridge + */ + function finalizeDeposit( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data +``` + +Ta funkcja jest wywoływana przez `L1StandardBridge`. + +```solidity + ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) { +``` + +Upewnij się, że źródło wiadomości jest legalne. +Jest to ważne, ponieważ ta funkcja wywołuje `_mint` i może być użyta do wydawania tokenów, które nie są pokryte tokenami, które most posiada na L1. + +```solidity + // Sprawdź, czy token docelowy jest zgodny i + // zweryfikuj, czy zdeponowany token na L1 odpowiada reprezentacji zdeponowanego tokenu L2 tutaj + if ( + // slither-disable-next-line reentrancy-events + ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) && + _l1Token == IL2StandardERC20(_l2Token).l1Token() +``` + +Testy poprawności: + +1. Obsługiwany jest prawidłowy interfejs +2. Adres L1 kontraktu ERC-20 na L2 odpowiada źródłu tokenów na L1 + +```solidity + ) { + // Po sfinalizowaniu depozytu zasilamy konto na L2 taką samą kwotą + // tokenów. + // slither-disable-next-line reentrancy-events + IL2StandardERC20(_l2Token).mint(_to, _amount); + // slither-disable-next-line reentrancy-events + emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data); +``` + +Jeśli testy poprawności przejdą pomyślnie, sfinalizuj depozyt: + +1. Wyemituj tokeny +2. Wyemituj odpowiednie zdarzenie + +```solidity + } else { + // Albo token L2, do którego dokonywany jest depozyt, nie zgadza się co do prawidłowego adresu + // swojego tokenu L1, albo nie obsługuje prawidłowego interfejsu. + // Powinno to nastąpić tylko wtedy, gdy istnieje złośliwy token L2 lub jeśli użytkownik w jakiś sposób + // podał nieprawidłowy adres tokenu L2 do zdeponowania. + // W obu przypadkach zatrzymujemy tutaj proces i konstruujemy wiadomość + // wypłaty, aby użytkownicy mogli w niektórych przypadkach odzyskać swoje środki. + // Nie ma sposobu, aby całkowicie zapobiec złośliwym kontraktom tokenów, ale ogranicza to + // błędy użytkowników i łagodzi niektóre formy złośliwego zachowania kontraktów. +``` + +Jeśli użytkownik popełnił wykrywalny błąd, używając niewłaściwego adresu tokenu L2, chcemy anulować depozyt i zwrócić tokeny na L1. +Jedynym sposobem, w jaki możemy to zrobić z L2, jest wysłanie wiadomości, która będzie musiała poczekać na okres kwestionowania błędu, ale jest to znacznie lepsze dla użytkownika niż trwała utrata tokenów. + +```solidity + bytes memory message = abi.encodeWithSelector( + IL1ERC20Bridge.finalizeERC20Withdrawal.selector, + _l1Token, + _l2Token, + _to, // zamieniono tutaj _to i _from, aby zwrócić depozyt do nadawcy + _from, + _amount, + _data + ); + + // Wyślij wiadomość do mostu L1 + // slither-disable-next-line reentrancy-events + sendCrossDomainMessage(l1TokenBridge, 0, message); + // slither-disable-next-line reentrancy-events + emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data); + } + } +} +``` + +## Wnioski {#conclusion} + +Standardowy most jest najbardziej elastycznym mechanizmem transferu aktywów. +Jednakże, ponieważ jest tak ogólny, nie zawsze jest to najłatwiejszy mechanizm do użycia. +Szczególnie w przypadku wypłat, większość użytkowników woli korzystać z [mostów stron trzecich](https://optimism.io/apps#bridge), które nie czekają na okres kwestionowania i nie wymagają dowodu Merkle, aby sfinalizować wypłatę. + +Te mosty zazwyczaj działają poprzez posiadanie aktywów na L1, które dostarczają natychmiast za niewielką opłatą (często niższą niż koszt gazu za standardową wypłatę z mostu). +Gdy most (lub osoby go prowadzące) przewiduje brak aktywów L1, przekazuje wystarczającą ilość aktywów z L2. Ponieważ są to bardzo duże wypłaty, koszt wypłaty jest amortyzowany na dużą kwotę i stanowi znacznie mniejszy procent. + +Mam nadzieję, że ten artykuł pomógł ci lepiej zrozumieć, jak działa warstwa 2 i jak pisać kod Solidity, który jest jasny i bezpieczny. + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/reverse-engineering-a-contract/index.md b/public/content/translations/pl/developers/tutorials/reverse-engineering-a-contract/index.md new file mode 100644 index 00000000000..c195807d286 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/reverse-engineering-a-contract/index.md @@ -0,0 +1,743 @@ +--- +title: "Inżynieria odwrotna kontraktu" +description: "Jak zrozumieć kontrakt, gdy nie masz kodu źródłowego" +author: Ori Pomerantz +lang: pl +tags: [ "evm", "kody operacyjne" ] +skill: advanced +published: 2021-12-30 +--- + +## Wprowadzenie {#introduction} + +_W blockchainie nie ma tajemnic_, wszystko, co się dzieje, jest spójne, weryfikowalne i publicznie dostępne. [kontrakty powinny mieć opublikowany i zweryfikowany kod źródłowy na Etherscan](https://etherscan.io/address/0xb8901acb165ed027e32754e0ffe830802919727f#code). [Nie zawsze tak jest](https://etherscan.io/address/0x2510c039cc3b061d79e564b38836da87e31b342f#code). W tym artykule dowiesz się, jak przeprowadzać inżynierię wsteczną kontraktów, przyglądając się kontraktowi bez kodu źródłowego, [`0x2510c039cc3b061d79e564b38836da87e31b342f`](https://etherscan.io/address/0x2510c039cc3b061d79e564b38836da87e31b342f). + +Istnieją odwrotne kompilatory, ale nie zawsze dają one [użyteczne wyniki](https://etherscan.io/bytecode-decompiler?a=0x2510c039cc3b061d79e564b38836da87e31b342f). W tym artykule dowiesz się, jak ręcznie przeprowadzić inżynierię wsteczną i zrozumieć kontrakt z [kodów operacyjnych](https://github.com/wolflo/evm-opcodes), a także jak interpretować wyniki dekompilatora. + +Aby zrozumieć ten artykuł, powinieneś już znać podstawy EVM i być przynajmniej w pewnym stopniu zaznajomiony z asemblerem EVM. [Możesz przeczytać o tych tematach tutaj](https://medium.com/mycrypto/the-ethereum-virtual-machine-how-does-it-work-9abac2b7c9e). + +## Przygotuj kod wykonywalny {#prepare-the-executable-code} + +Kody operacyjne można uzyskać, przechodząc do Etherscan dla kontraktu, klikając kartę **Kontrakt**, a następnie **Przełącz na widok kodów operacyjnych**. Otrzymasz widok, w którym w każdej linii znajduje się jeden kod operacyjny. + +![Widok kodów operacyjnych z Etherscan](opcode-view.png) + +Aby jednak zrozumieć skoki, musisz wiedzieć, gdzie w kodzie znajduje się każdy kod operacyjny. Aby to zrobić, jednym ze sposobów jest otwarcie Arkusza Google i wklejenie kodów operacyjnych w kolumnie C. [Możesz pominąć następujące kroki, tworząc kopię tego już przygotowanego arkusza kalkulacyjnego](https://docs.google.com/spreadsheets/d/1tKmTJiNjUwHbW64wCKOSJxHjmh0bAUapt6btUYE7kDA/edit?usp=sharing). + +Następnym krokiem jest uzyskanie prawidłowych lokalizacji kodu, abyśmy mogli zrozumieć skoki. Rozmiar kodu operacyjnego umieścimy w kolumnie B, a lokalizację (w systemie szesnastkowym) w kolumnie A. Wpisz tę funkcję w komórce `B1`, a następnie skopiuj i wklej ją do reszty kolumny B, aż do końca kodu. Po wykonaniu tej czynności możesz ukryć kolumnę B. + +``` +=1+IF(REGEXMATCH(C1,"PUSH"),REGEXEXTRACT(C1,"PUSH(\d+)"),0) +``` + +Najpierw ta funkcja dodaje jeden bajt dla samego kodu operacyjnego, a następnie szuka `PUSH`. Kody operacyjne typu „push” są specjalne, ponieważ potrzebują dodatkowych bajtów dla przesyłanej wartości. Jeśli kod operacyjny to `PUSH`, wyodrębniamy liczbę bajtów i dodajemy ją. + +W `A1` umieść pierwsze przesunięcie, zero. Następnie w `A2` umieść tę funkcję i ponownie skopiuj i wklej ją do reszty kolumny A: + +``` +=dec2hex(hex2dec(A1)+B1) +``` + +Potrzebujemy tej funkcji, aby dać nam wartość szesnastkową, ponieważ wartości, które są wypychane przed skokami (`JUMP` i `JUMPI`), są nam podawane w systemie szesnastkowym. + +## Punkt wejścia (0x00) {#the-entry-point-0x00} + +Kontrakty są zawsze wykonywane od pierwszego bajtu. To jest początkowa część kodu: + +| Przesunięcie | Kod operacyjny | Stos (po kodzie operacyjnym) | +| -----------: | -------------- | ----------------------------------------------- | +| 0 | PUSH1 0x80 | 0x80 | +| 2 | PUSH1 0x40 | 0x40, 0x80 | +| 4 | MSTORE | Pusty | +| 5 | PUSH1 0x04 | 0x04 | +| 7 | CALLDATASIZE | CALLDATASIZE 0x04 | +| 8 | LT | CALLDATASIZE\<4 | +| 9 | PUSH2 0x005e | 0x5E CALLDATASIZE\<4 | +| C | JUMPI | Pusty | + +Ten kod robi dwie rzeczy: + +1. Zapisz 0x80 jako 32-bajtową wartość w lokalizacjach pamięci 0x40-0x5F (0x80 jest przechowywane w 0x5F, a 0x40-0x5E to same zera). +2. Odczytaj rozmiar calldata. Zwykle dane wywołania dla kontraktu Ethereum są zgodne z [ABI (binarny interfejs aplikacji)](https://docs.soliditylang.org/en/v0.8.10/abi-spec.html), który wymaga co najmniej czterech bajtów dla selektora funkcji. Jeśli rozmiar danych wywołania jest mniejszy niż cztery, przejdź do 0x5E. + +![Schemat blokowy dla tej części](flowchart-entry.png) + +### Procedura obsługi w 0x5E (dla danych wywołania innych niż ABI) {#the-handler-at-0x5e-for-non-abi-call-data} + +| Przesunięcie | Kod operacyjny | +| -----------: | -------------- | +| 5E | JUMPDEST | +| 5F | CALLDATASIZE | +| 60 | PUSH2 0x007c | +| 63 | JUMPI | + +Ten fragment zaczyna się od `JUMPDEST`. Programy EVM (Wirtualna Maszyna Ethereum) zgłaszają wyjątek, jeśli przeskoczysz do kodu operacyjnego, który nie jest `JUMPDEST`. Następnie sprawdza CALLDATASIZE i jeśli jest „prawdziwe” (to znaczy nie jest zerem), przeskakuje do 0x7C. Do tego przejdziemy poniżej. + +| Przesunięcie | Kod operacyjny | Stos (po kodzie operacyjnym) | +| -----------: | -------------- | -------------------------------------------------------------------------------------------------- | +| 64 | CALLVALUE | [Wei](/glossary/#wei) dostarczone przez wywołanie. Nazywane `msg.value` w Solidity | +| 65 | PUSH1 0x06 | 6 CALLVALUE | +| 67 | PUSH1 0x00 | 0 6 CALLVALUE | +| 69 | DUP3 | CALLVALUE 0 6 CALLVALUE | +| 6A | DUP3 | 6 CALLVALUE 0 6 CALLVALUE | +| 6B | SLOAD | Storage[6] CALLVALUE 0 6 CALLVALUE | + +Więc kiedy nie ma danych wywołania, odczytujemy wartość Storage[6]. Nie wiemy jeszcze, jaka to wartość, ale możemy poszukać transakcji, które kontrakt otrzymał bez danych wywołania. Transakcje, które po prostu przesyłają ETH bez żadnych danych wywołania (a zatem bez metody), mają w Etherscan metodę `Transfer`. W rzeczywistości [pierwsza transakcja, jaką otrzymał kontrakt](https://etherscan.io/tx/0xeec75287a583c36bcc7ca87685ab41603494516a0f5986d18de96c8e630762e7) jest transferem. + +Jeśli spojrzymy na tę transakcję i klikniemy **Kliknij, aby zobaczyć więcej**, zobaczymy, że dane wywołania, zwane danymi wejściowymi, są rzeczywiście puste (`0x`). Zauważ również, że wartość wynosi 1,559 ETH, co będzie istotne później. + +![Dane wywołania są puste](calldata-empty.png) + +Następnie kliknij kartę **Stan** i rozwiń kontrakt, którego inżynierię odwrotną przeprowadzamy (0x2510...). Można zauważyć, że `Storage[6]` zmieniło się podczas transakcji, a jeśli zmienisz Hex na **Liczba**, zobaczysz, że stało się to 1,559,000,000,000,000,000, wartość przeniesiona w wei (dodałem przecinki dla przejrzystości), co odpowiada następnej wartości kontraktu. + +![Zmiana w Storage[6]](storage6.png) + +Jeśli spojrzymy na zmiany stanu spowodowane przez [inne transakcje `Transfer` z tego samego okresu](https://etherscan.io/tx/0xf708d306de39c422472f43cb975d97b66fd5d6a6863db627067167cbf93d84d1#statechange), zobaczymy, że `Storage[6]` przez pewien czas śledziło wartość kontraktu. Na razie nazwijmy to `Value*`. Gwiazdka (`*`) przypomina nam, że jeszcze nie _wiemy_, co robi ta zmienna, ale nie może ona służyć tylko do śledzenia wartości kontraktu, ponieważ nie ma potrzeby używania pamięci masowej (storage), która jest bardzo droga, skoro można uzyskać saldo konta za pomocą `ADDRESS BALANCE`. Pierwszy kod operacyjny umieszcza na stosie własny adres kontraktu. Drugi odczytuje adres na szczycie stosu i zastępuje go saldem tego adresu. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------- | +| 6C | PUSH2 0x0075 | 0x75 Value\* CALLVALUE 0 6 CALLVALUE | +| 6F | SWAP2 | CALLVALUE Value\* 0x75 0 6 CALLVALUE | +| 70 | SWAP1 | Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 71 | PUSH2 0x01a7 | 0x01A7 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 74 | JUMP | | + +Będziemy kontynuować śledzenie tego kodu w miejscu docelowym skoku. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------- | +| 1A7 | JUMPDEST | Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1A8 | PUSH1 0x00 | 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1AA | DUP3 | CALLVALUE 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1AB | NOT | 2^256-CALLVALUE-1 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | + +`NOT` jest operacją bitową, więc odwraca wartość każdego bitu w wartości wywołania. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------------------------------------------------------------------ | +| 1AC | DUP3 | Value\* 2^256-CALLVALUE-1 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1AD | GT | Value\*>2^256-CALLVALUE-1 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1AE | ISZERO | Value\*\<=2^256-CALLVALUE-1 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1AF | PUSH2 0x01df | 0x01DF Value\*\<=2^256-CALLVALUE-1 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1B2 | JUMPI | | + +Skaczemy, jeśli `Value*` jest mniejsze niż 2^256-CALLVALUE-1 lub równe tej wartości. Wygląda to na logikę zapobiegającą przepełnieniu. I rzeczywiście, widzimy, że po kilku bezsensownych operacjach (na przykład zapis do pamięci, który zaraz zostanie usunięty) przy przesunięciu 0x01DE kontrakt powraca, jeśli wykryte zostanie przepełnienie, co jest normalnym zachowaniem. + +Należy zauważyć, że takie przepełnienie jest niezwykle mało prawdopodobne, ponieważ wymagałoby, aby wartość wywołania plus `Value*` była porównywalna z 2^256 wei, czyli około 10^59 ETH. [Całkowita podaż ETH w momencie pisania tego tekstu wynosi mniej niż dwieście milionów](https://etherscan.io/stat/supply). + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------- | +| 1DF | JUMPDEST | 0x00 Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1E0 | POP | Value\* CALLVALUE 0x75 0 6 CALLVALUE | +| 1E1 | ADD | Value\*+CALLVALUE 0x75 0 6 CALLVALUE | +| 1E2 | SWAP1 | 0x75 Value\*+CALLVALUE 0 6 CALLVALUE | +| 1E3 | JUMP | | + +Jeśli dotarliśmy tutaj, pobierz `Value* + CALLVALUE` i przejdź do przesunięcia 0x75. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------- | +| 75 | JUMPDEST | Value\*+CALLVALUE 0 6 CALLVALUE | +| 76 | SWAP1 | 0 Value\*+CALLVALUE 6 CALLVALUE | +| 77 | SWAP2 | 6 Value\*+CALLVALUE 0 CALLVALUE | +| 78 | SSTORE | 0 CALLVALUE | + +Jeśli dotrzemy tutaj (co wymaga, aby dane wywołania były puste), dodajemy do `Value*` wartość wywołania. Jest to zgodne z tym, co według nas robią transakcje `Transfer`. + +| Przesunięcie | Kod operacyjny | +| -----------: | -------------- | +| 79 | POP | +| 7A | POP | +| 7B | STOP | + +Na koniec wyczyść stos (co nie jest konieczne) i zasygnalizuj pomyślne zakończenie transakcji. + +Podsumowując, oto schemat blokowy dla początkowego kodu. + +![Schemat blokowy punktu wejścia](flowchart-entry.png) + +## Procedura obsługi w 0x7C {#the-handler-at-0x7c} + +Celowo nie umieściłem w nagłówku informacji o tym, co robi ta procedura obsługi. Celem nie jest nauczenie Cię, jak działa ten konkretny kontrakt, ale jak przeprowadzać inżynierię wsteczną kontraktów. Dowiesz się, co robi, w ten sam sposób, co ja – śledząc kod. + +Docieramy tu z kilku miejsc: + +- Jeśli istnieją dane wywołania o długości 1, 2 lub 3 bajtów (z przesunięcia 0x63) +- Jeśli sygnatura metody jest nieznana (z przesunięć 0x42 i 0x5D) + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------------------------------------ | +| 7C | JUMPDEST | | +| 7D | PUSH1 0x00 | 0x00 | +| 7F | PUSH2 0x009d | 0x9D 0x00 | +| 82 | PUSH1 0x03 | 0x03 0x9D 0x00 | +| 84 | SLOAD | Storage[3] 0x9D 0x00 | + +To jest kolejna komórka pamięci, której nie mogłem znaleźć w żadnej transakcji, więc trudniej jest dowiedzieć się, co ona oznacza. Poniższy kod wyjaśni to. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| 85 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xff....ff Storage[3] 0x9D 0x00 | +| 9A | AND | Storage[3]-as-address 0x9D 0x00 | + +Te kody operacyjne obcinają wartość, którą odczytujemy z Storage[3] do 160 bitów, czyli długości adresu Ethereum. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------------------------------- | +| 9B | SWAP1 | 0x9D Storage[3]-as-address 0x00 | +| 9C | JUMP | Storage[3]-as-address 0x00 | + +Ten skok jest zbędny, ponieważ przechodzimy do następnego kodu operacyjnego. Ten kod nie jest tak wydajny pod względem zużycia gazu, jak mógłby być. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| 9D | JUMPDEST | Storage[3]-as-address 0x00 | +| 9E | SWAP1 | 0x00 Storage[3]-as-address | +| 9F | POP | Storage[3]-as-address | +| A0 | PUSH1 0x40 | 0x40 Storage[3]-as-address | +| A2 | MLOAD | Mem[0x40] Storage[3]-as-address | + +Na samym początku kodu ustawiliśmy Mem[0x40] na 0x80. Jeśli spojrzymy na 0x40 później, zobaczymy, że go nie zmieniamy – więc możemy założyć, że jest to 0x80. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------------------------------------------------- | +| A3 | CALLDATASIZE | CALLDATASIZE 0x80 Storage[3]-as-address | +| A4 | PUSH1 0x00 | 0x00 CALLDATASIZE 0x80 Storage[3]-as-address | +| A6 | DUP3 | 0x80 0x00 CALLDATASIZE 0x80 Storage[3]-as-address | +| A7 | CALLDATACOPY | 0x80 Storage[3]-as-address | + +Skopiuj wszystkie dane wywołania do pamięci, zaczynając od 0x80. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| A8 | PUSH1 0x00 | 0x00 0x80 Storage[3]-as-address | +| AA | DUP1 | 0x00 0x00 0x80 Storage[3]-as-address | +| AB | CALLDATASIZE | CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address | +| AC | DUP4 | 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address | +| AD | DUP6 | Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address | +| AE | GAS | GAS Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address | +| AF | DELEGATE_CALL | | + +Teraz wszystko jest znacznie jaśniejsze. Ten kontrakt może działać jako [proxy](https://blog.openzeppelin.com/proxy-patterns/), wywołując adres w Storage[3] w celu wykonania prawdziwej pracy. `DELEGATE_CALL` wywołuje oddzielny kontrakt, ale pozostaje w tej samej pamięci masowej (storage). Oznacza to, że delegowany kontrakt, dla którego jesteśmy proxy, ma dostęp do tej samej przestrzeni pamięci masowej (storage). Parametry wywołania to: + +- _Gaz_: Cały pozostały gaz +- _Wywołany adres_: Storage[3]-as-address +- _Dane wywołania_: Bajty CALLDATASIZE zaczynające się od 0x80, gdzie umieściliśmy oryginalne dane wywołania +- _Dane zwrotne_: Brak (0x00 - 0x00) Dane zwrotne uzyskamy w inny sposób (patrz niżej) + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| B0 | RETURNDATASIZE | RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B1 | DUP1 | RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B2 | PUSH1 0x00 | 0x00 RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B4 | DUP5 | 0x80 0x00 RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B5 | RETURNDATACOPY | RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | + +Tutaj kopiujemy wszystkie dane zwrotne do bufora pamięci, zaczynając od 0x80. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| B6 | DUP2 | (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B7 | DUP1 | (((call success/failure))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B8 | ISZERO | (((did the call fail))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| B9 | PUSH2 0x00c0 | 0xC0 (((did the call fail))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| BC | JUMPI | (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| BD | DUP2 | RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| BE | DUP5 | 0x80 RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| BF | RETURN | | + +Więc po wywołaniu kopiujemy dane zwrotne do bufora 0x80 - 0x80+RETURNDATASIZE, a jeśli wywołanie się powiedzie, to `RETURN` z dokładnie tym buforem. + +### DELEGATECALL Nie powiodło się {#delegatecall-failed} + +Jeśli dotrzemy tutaj, do 0xC0, oznacza to, że wywołany kontrakt został wycofany. Ponieważ jesteśmy tylko proxy dla tego kontraktu, chcemy zwrócić te same dane, a także cofnąć operację. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| C0 | JUMPDEST | (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| C1 | DUP2 | RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| C2 | DUP5 | 0x80 RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address | +| C3 | REVERT | | + +Więc `REVERT` z tym samym buforem, którego użyliśmy wcześniej dla `RETURN`: 0x80 - 0x80+RETURNDATASIZE + +![Schemat blokowy wywołania do proxy](flowchart-proxy.png) + +## Wywołania ABI {#abi-calls} + +Jeśli rozmiar danych wywołania wynosi cztery bajty lub więcej, może to być prawidłowe wywołanie ABI. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| D | PUSH1 0x00 | 0x00 | +| F | CALLDATALOAD | (((Pierwsze słowo (256 bitów) danych wywołania))) | +| 10 | PUSH1 0xe0 | 0xE0 (((Pierwsze słowo (256 bitów) danych wywołania))) | +| 12 | SHR | (((pierwsze 32 bity (4 bajty) danych wywołania))) | + +Etherscan informuje nas, że `1C` to nieznany kod operacyjny, ponieważ [został dodany po tym, jak Etherscan napisał tę funkcję](https://eips.ethereum.org/EIPS/eip-145), a oni go nie zaktualizowali. [Aktualna tabela kodów operacyjnych](https://github.com/wolflo/evm-opcodes) pokazuje, że jest to przesunięcie w prawo + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 13 | DUP1 | (((pierwsze 32 bity (4 bajty) danych wywołania))) (((pierwsze 32 bity (4 bajty) danych wywołania))) | +| 14 | PUSH4 0x3cd8045e | 0x3CD8045E (((pierwsze 32 bity (4 bajty) danych wywołania))) (((pierwsze 32 bity (4 bajty) danych wywołania))) | +| 19 | GT | 0x3CD8045E>pierwsze-32-bity-danych-wywołania (((pierwsze 32 bity (4 bajty) danych wywołania))) | +| 1A | PUSH2 0x0043 | 0x43 0x3CD8045E>pierwsze-32-bity-danych-wywołania (((pierwsze 32 bity (4 bajty) danych wywołania))) | +| 1D | JUMPI | (((pierwsze 32 bity (4 bajty) danych wywołania))) | + +Podzielenie testów dopasowania sygnatury metody na dwie części w ten sposób oszczędza średnio połowę testów. Kod, który bezpośrednio następuje po tym, oraz kod w 0x43, mają ten sam wzorzec: `DUP1` pierwszych 32 bitów danych wywołania, `PUSH4 (((sygnatura metody>`, uruchom `EQ`, aby sprawdzić równość, a następnie `JUMPI`, jeśli sygnatura metody pasuje. Oto sygnatury metod, ich adresy oraz, jeśli jest znana, [odpowiadająca im definicja metody](https://www.4byte.directory/): + +| Metoda | Sygnatura metody | Przesunięcie do skoku | +| --------------------------------------------------------------------------------------------------------- | ---------------- | --------------------- | +| [splitter()](https://www.4byte.directory/signatures/?bytes4_signature=0x3cd8045e) | 0x3cd8045e | 0x0103 | +| ??? | 0x81e580d3 | 0x0138 | +| [currentWindow()](https://www.4byte.directory/signatures/?bytes4_signature=0xba0bafb4) | 0xba0bafb4 | 0x0158 | +| ??? | 0x1f135823 | 0x00C4 | +| [merkleRoot()](https://www.4byte.directory/signatures/?bytes4_signature=0x2eb4a7ab) | 0x2eb4a7ab | 0x00ED | + +Jeśli nie zostanie znalezione dopasowanie, kod przeskakuje do [procedury obsługi proxy w 0x7C](#the-handler-at-0x7c), w nadziei, że kontrakt, dla którego jesteśmy proxy, ma dopasowanie. + +![Schemat blokowy wywołań ABI](flowchart-abi.png) + +## splitter() {#splitter} + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------- | +| 103 | JUMPDEST | | +| 104 | CALLVALUE | CALLVALUE | +| 105 | DUP1 | CALLVALUE CALLVALUE | +| 106 | ISZERO | CALLVALUE==0 CALLVALUE | +| 107 | PUSH2 0x010f | 0x010F CALLVALUE==0 CALLVALUE | +| 10A | JUMPI | CALLVALUE | +| 10B | PUSH1 0x00 | 0x00 CALLVALUE | +| 10D | DUP1 | 0x00 0x00 CALLVALUE | +| 10E | REVERT | | + +Pierwszą rzeczą, jaką robi ta funkcja, jest sprawdzenie, czy wywołanie nie wysłało żadnego ETH. Ta funkcja nie jest [`płatna`](https://solidity-by-example.org/payable/). Jeśli ktoś wysłał nam ETH, to musi być pomyłka i chcemy `REVERT`, aby uniknąć posiadania tego ETH tam, gdzie nie mogą go odzyskać. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 10F | JUMPDEST | | +| 110 | POP | | +| 111 | PUSH1 0x03 | 0x03 | +| 113 | SLOAD | (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) | +| 114 | PUSH1 0x40 | 0x40 (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) | +| 116 | MLOAD | 0x80 (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) | +| 117 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xFF...FF 0x80 (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) | +| 12C | SWAP1 | 0x80 0xFF...FF (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) | +| 12D | SWAP2 | (((Storage[3] inaczej kontrakt, dla którego jesteśmy proxy))) 0xFF...FF 0x80 | +| 12E | AND | ProxyAddr 0x80 | +| 12F | DUP2 | 0x80 ProxyAddr 0x80 | +| 130 | MSTORE | 0x80 | + +A 0x80 zawiera teraz adres proxy + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | --------- | +| 131 | PUSH1 0x20 | 0x20 0x80 | +| 133 | ADD | 0xA0 | +| 134 | PUSH2 0x00e4 | 0xE4 0xA0 | +| 137 | JUMP | 0xA0 | + +### Kod E4 {#the-e4-code} + +Po raz pierwszy widzimy te linie, ale są one współdzielone z innymi metodami (patrz niżej). Więc nazwiemy wartość na stosie X i po prostu zapamiętamy, że w `splitter()` wartość tego X to 0xA0. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------- | +| E4 | JUMPDEST | X | +| E5 | PUSH1 0x40 | 0x40 X | +| E7 | MLOAD | 0x80 X | +| E8 | DUP1 | 0x80 0x80 X | +| E9 | SWAP2 | X 0x80 0x80 | +| EA | SUB | X-0x80 0x80 | +| EB | SWAP1 | 0x80 X-0x80 | +| EC | RETURN | | + +Tak więc ten kod otrzymuje wskaźnik pamięci na stosie (X) i powoduje, że kontrakt `RETURN` z buforem, który wynosi 0x80 - X. + +W przypadku `splitter()` zwraca to adres, dla którego jesteśmy proxy. `RETURN` zwraca bufor w 0x80-0x9F, czyli tam, gdzie zapisaliśmy te dane (przesunięcie 0x130 powyżej). + +## currentWindow() {#currentwindow} + +Kod w przesunięciach 0x158-0x163 jest identyczny z tym, który widzieliśmy w 0x103-0x10E w `splitter()` (inny niż miejsce docelowe `JUMPI`), więc wiemy, że `currentWindow()` również nie jest `płatne`. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------------------------------------ | +| 164 | JUMPDEST | | +| 165 | POP | | +| 166 | PUSH2 0x00da | 0xDA | +| 169 | PUSH1 0x01 | 0x01 0xDA | +| 16B | SLOAD | Storage[1] 0xDA | +| 16C | DUP2 | 0xDA Storage[1] 0xDA | +| 16D | JUMP | Storage[1] 0xDA | + +### Kod DA {#the-da-code} + +Ten kod jest również współdzielony z innymi metodami. Więc nazwiemy wartość na stosie Y i po prostu zapamiętamy, że w `currentWindow()` wartość tego Y to Storage[1]. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ---------------- | +| DA | JUMPDEST | Y 0xDA | +| DB | PUSH1 0x40 | 0x40 Y 0xDA | +| DD | MLOAD | 0x80 Y 0xDA | +| DE | SWAP1 | Y 0x80 0xDA | +| DF | DUP2 | 0x80 Y 0x80 0xDA | +| E0 | MSTORE | 0x80 0xDA | + +Zapisz Y do 0x80-0x9F. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | -------------- | +| E1 | PUSH1 0x20 | 0x20 0x80 0xDA | +| E3 | ADD | 0xA0 0xDA | + +A reszta jest już wyjaśniona [powyżej](#the-e4-code). Tak więc skoki do 0xDA zapisują szczyt stosu (Y) do 0x80-0x9F i zwracają tę wartość. W przypadku `currentWindow()` zwraca Storage[1]. + +## merkleRoot() {#merkleroot} + +Kod w przesunięciach 0xED-0xF8 jest identyczny z tym, który widzieliśmy w 0x103-0x10E w `splitter()` (inny niż miejsce docelowe `JUMPI`), więc wiemy, że `merkleRoot()` również nie jest `płatny`. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------------------------------------ | +| F9 | JUMPDEST | | +| FA | POP | | +| FB | PUSH2 0x00da | 0xDA | +| FE | PUSH1 0x00 | 0x00 0xDA | +| 100 | SLOAD | Storage[0] 0xDA | +| 101 | DUP2 | 0xDA Storage[0] 0xDA | +| 102 | JUMP | Storage[0] 0xDA | + +Co dzieje się po skoku, [już ustaliliśmy](#the-da-code). Więc `merkleRoot()` zwraca Storage[0]. + +## 0x81e580d3 {#0x81e580d3} + +Kod w przesunięciach 0x138-0x143 jest identyczny z tym, który widzieliśmy w 0x103-0x10E w `splitter()` (inny niż miejsce docelowe `JUMPI`), więc wiemy, że ta funkcja również nie jest `płatna`. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ------------------------------------------------------------------------------- | +| 144 | JUMPDEST | | +| 145 | POP | | +| 146 | PUSH2 0x00da | 0xDA | +| 149 | PUSH2 0x0153 | 0x0153 0xDA | +| 14C | CALLDATASIZE | CALLDATASIZE 0x0153 0xDA | +| 14D | PUSH1 0x04 | 0x04 CALLDATASIZE 0x0153 0xDA | +| 14F | PUSH2 0x018f | 0x018F 0x04 CALLDATASIZE 0x0153 0xDA | +| 152 | JUMP | 0x04 CALLDATASIZE 0x0153 0xDA | +| 18F | JUMPDEST | 0x04 CALLDATASIZE 0x0153 0xDA | +| 190 | PUSH1 0x00 | 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 192 | PUSH1 0x20 | 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 194 | DUP3 | 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 195 | DUP5 | CALLDATASIZE 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 196 | SUB | CALLDATASIZE-4 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 197 | SLT | CALLDATASIZE-4\<32 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 198 | ISZERO | CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 199 | PUSH2 0x01a0 | 0x01A0 CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 19C | JUMPI | 0x00 0x04 CALLDATASIZE 0x0153 0xDA | + +Wygląda na to, że ta funkcja przyjmuje co najmniej 32 bajty (jedno słowo) danych wywołania. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | -------------------------------------------- | +| 19D | DUP1 | 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 19E | DUP2 | 0x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 19F | REVERT | | + +Jeśli nie otrzyma danych wywołania, transakcja zostanie wycofana bez żadnych danych zwrotnych. + +Zobaczmy, co się stanie, jeśli funkcja _otrzyma_ potrzebne dane wywołania. + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | ----------------------------------------------------------- | +| 1A0 | JUMPDEST | 0x00 0x04 CALLDATASIZE 0x0153 0xDA | +| 1A1 | POP | 0x04 CALLDATASIZE 0x0153 0xDA | +| 1A2 | CALLDATALOAD | calldataload(4) CALLDATASIZE 0x0153 0xDA | + +`calldataload(4)` to pierwsze słowo danych wywołania _po_ sygnaturze metody + +| Przesunięcie | Kod operacyjny | Stos | +| -----------: | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1A3 | SWAP2 | 0x0153 CALLDATASIZE calldataload(4) 0xDA | +| 1A4 | SWAP1 | CALLDATASIZE 0x0153 calldataload(4) 0xDA | +| 1A5 | POP | 0x0153 calldataload(4) 0xDA | +| 1A6 | JUMP | calldataload(4) 0xDA | +| 153 | JUMPDEST | calldataload(4) 0xDA | +| 154 | PUSH2 0x016e | 0x016E calldataload(4) 0xDA | +| 157 | JUMP | calldataload(4) 0xDA | +| 16E | JUMPDEST | calldataload(4) 0xDA | +| 16F | PUSH1 0x04 | 0x04 calldataload(4) 0xDA | +| 171 | DUP2 | calldataload(4) 0x04 calldataload(4) 0xDA | +| 172 | DUP2 | 0x04 calldataload(4) 0x04 calldataload(4) 0xDA | +| 173 | SLOAD | Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA | +| 174 | DUP2 | calldataload(4) Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA | +| 175 | LT | calldataload(4)\)`, a drugą `isClaimed()`, więc wygląda na to, że jest to kontrakt airdropu. Zamiast przechodzić przez resztę kod po kodzie, możemy [wypróbować dekompilator](https://etherscan.io/bytecode-decompiler?a=0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761), który daje użyteczne wyniki dla trzech funkcji z tego kontraktu. Inżynieria odwrotna pozostałych jest pozostawiona jako ćwiczenie dla czytelnika. + +### scaleAmountByPercentage {#scaleamountbypercentage} + +Oto, co dekompilator daje nam dla tej funkcji: + +```python +def unknown8ffb5c97(uint256 _param1, uint256 _param2) payable: + require calldata.size - 4 >=′ 64 + if _param1 and _param2 > -1 / _param1: + revert with 0, 17 + return (_param1 * _param2 / 100 * 10^6) +``` + +Pierwsze `require` sprawdza, czy dane wywołania mają, oprócz czterech bajtów sygnatury funkcji, co najmniej 64 bajty, wystarczające dla dwóch parametrów. Jeśli nie, to oczywiście coś jest nie tak. + +Instrukcja `if` wydaje się sprawdzać, czy `_param1` nie jest zerem i czy `_param1 * _param2` nie jest ujemne. Prawdopodobnie ma to na celu zapobieżenie przypadkom zawijania. + +Na koniec funkcja zwraca przeskalowaną wartość. + +### claim {#claim} + +Kod, który tworzy dekompilator, jest złożony i nie cały jest dla nas istotny. Pominę niektóre z nich, aby skupić się na liniach, które moim zdaniem dostarczają przydatnych informacji + +```python +def unknown2e7ba6ef(uint256 _param1, uint256 _param2, uint256 _param3, array _param4) payable: + ... + require _param2 == addr(_param2) + ... + if currentWindow <= _param1: + revert with 0, 'nie można odebrać nagrody za przyszłe okno' +``` + +Widzimy tutaj dwie ważne rzeczy: + +- `_param2`, chociaż jest zadeklarowany jako `uint256`, jest w rzeczywistości adresem +- `_param1` to okno, za które odbierana jest nagroda, które musi być `currentWindow` lub wcześniejsze. + +```python + ... + if stor5[_claimWindow][addr(_claimFor)]: + revert with 0, 'Konto już odebrało nagrodę za dane okno' +``` + +Więc teraz wiemy, że Storage[5] to tablica okien i adresów oraz tego, czy adres odebrał nagrodę za to okno. + +```python + idx = 0 + s = 0 + while idx < _param4.length: + ... + if s + sha3(mem[(32 * _param4.length) + 328 len mem[(32 * _param4.length) + 296]]) > mem[(32 * idx) + 296]: + mem[mem[64] + 32] = mem[(32 * idx) + 296] + ... + s = sha3(mem[_62 + 32 len mem[_62]]) + continue + ... + s = sha3(mem[_66 + 32 len mem[_66]]) + continue + if unknown2eb4a7ab != s: + revert with 0, 'Nieprawidłowy dowód' +``` + +Wiemy, że `unknown2eb4a7ab` to w rzeczywistości funkcja `merkleRoot()`, więc ten kod wygląda, jakby weryfikował [dowód Merkle'a](https://medium.com/crypto-0-nite/merkle-proofs-explained-6dd429623dc5). Oznacza to, że `_param4` jest dowodem Merkle'a. + +```python + call addr(_param2) with: + value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei + gas 30000 wei +``` + +W ten sposób kontrakt przekazuje własne ETH na inny adres (kontrakt lub konto zewnętrzne). Wywołuje go z wartością, która jest kwotą do przeniesienia. Wygląda więc na to, że jest to airdrop ETH. + +```python + if not return_data.size: + if not ext_call.success: + require ext_code.size(stor2) + call stor2.deposit() with: + value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei +``` + +Dwie dolne linie mówią nam, że Storage[2] to również kontrakt, który wywołujemy. Jeśli [spojrzymy na transakcję konstruktora](https://etherscan.io/tx/0xa1ea0549fb349eb7d3aff90e1d6ce7469fdfdcd59a2fd9b8d1f5e420c0d05b58#statechange), zobaczymy, że ten kontrakt to [0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2), kontrakt Wrapped Ether, [którego kod źródłowy został przesłany do Etherscan](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code). + +Wygląda więc na to, że kontrakty próbują wysłać ETH do `_param2`. Jeśli może to zrobić, świetnie. Jeśli nie, próbuje wysłać [WETH](https://weth.tkn.eth.limo/). Jeśli `_param2` to konto zewnętrzne (EOA), zawsze może ono otrzymywać ETH, ale kontrakty mogą odmawiać przyjmowania ETH. Jednak WETH jest ERC-20 i kontrakty nie mogą odmówić jego przyjęcia. + +```python + ... + log 0xdbd5389f: addr(_param2), unknown81e580d3[_param1] * _param3 / 100 * 10^6, bool(ext_call.success) +``` + +Na końcu funkcji widzimy, że generowany jest wpis w dzienniku. [Spójrz na wygenerowane wpisy w dzienniku](https://etherscan.io/address/0x2510c039cc3b061d79e564b38836da87e31b342f#events) i przefiltruj według tematu, który zaczyna się od `0xdbd5...`. Jeśli [klikniemy jedną z transakcji, która wygenerowała taki wpis](https://etherscan.io/tx/0xe7d3b7e00f645af17dfbbd010478ef4af235896c65b6548def1fe95b3b7d2274), zobaczymy, że rzeczywiście wygląda to na odebranie nagrody – konto wysłało wiadomość do kontraktu, którego inżynierię odwrotną przeprowadzamy, a w zamian otrzymało ETH. + +![Transakcja odebrania nagrody](claim-tx.png) + +### 1e7df9d3 {#1e7df9d3} + +Ta funkcja jest bardzo podobna do [`claim`](#claim) powyżej. Sprawdza również dowód Merkle'a, próbuje przenieść ETH do pierwszego i tworzy ten sam typ wpisu w dzienniku. + +```python +def unknown1e7df9d3(uint256 _param1, uint256 _param2, array _param3) payable: + ... + idx = 0 + s = 0 + while idx < _param3.length: + if idx >= mem[96]: + revert with 0, 50 + _55 = mem[(32 * idx) + 128] + if s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]]) > mem[(32 * idx) + 128]: + ... + s = sha3(mem[_58 + 32 len mem[_58]]) + continue + mem[mem[64] + 32] = s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]]) + ... + if unknown2eb4a7ab != s: + revert with 0, 'Nieprawidłowy dowód' + ... + call addr(_param1) with: + value s wei + gas 30000 wei + if not return_data.size: + if not ext_call.success: + require ext_code.size(stor2) + call stor2.deposit() with: + value s wei + gas gas_remaining wei + ... + log 0xdbd5389f: addr(_param1), s, bool(ext_call.success) +``` + +Główna różnica polega na tym, że nie ma tam pierwszego parametru, czyli okna do wypłaty. Zamiast tego istnieje pętla obejmująca wszystkie okna, za które można odebrać nagrodę. + +```python + idx = 0 + s = 0 + while idx < currentWindow: + ... + if stor5[mem[0]]: + if idx == -1: + revert with 0, 17 + idx = idx + 1 + s = s + continue + ... + stor5[idx][addr(_param1)] = 1 + if idx >= unknown81e580d3.length: + revert with 0, 50 + mem[0] = 4 + if unknown81e580d3[idx] and _param2 > -1 / unknown81e580d3[idx]: + revert with 0, 17 + if s > !(unknown81e580d3[idx] * _param2 / 100 * 10^6): + revert with 0, 17 + if idx == -1: + revert with 0, 17 + idx = idx + 1 + s = s + (unknown81e580d3[idx] * _param2 / 100 * 10^6) + continue +``` + +Wygląda więc na wariant `claim`, który odbiera nagrody za wszystkie okna. + +## Wnioski {#conclusion} + +Teraz powinieneś już wiedzieć, jak rozumieć kontrakty, których kod źródłowy nie jest dostępny, używając albo kodów operacyjnych, albo (gdy to działa) dekompilatora. Jak widać po długości tego artykułu, inżynieria odwrotna kontraktu nie jest trywialna, ale w systemie, w którym bezpieczeństwo jest kluczowe, ważną umiejętnością jest możliwość weryfikacji, czy kontrakty działają zgodnie z obietnicą. + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/run-node-raspberry-pi/index.md b/public/content/translations/pl/developers/tutorials/run-node-raspberry-pi/index.md index eb71d4366aa..c10f41f9ae7 100644 --- a/public/content/translations/pl/developers/tutorials/run-node-raspberry-pi/index.md +++ b/public/content/translations/pl/developers/tutorials/run-node-raspberry-pi/index.md @@ -1,260 +1,190 @@ --- -title: Jak zmienić Raspberry Pi 4 w węzeł, po prostu flashując kartę MicroSD -description: Flash Raspberry Pi 4, podłącz kabel Ethernet, podłącz dysk SSD i włącz urządzenie, aby zmienić Raspberry Pi 4 w pełny węzeł Ethereum 1.0 lub Ethereum 2.0 (łańcuch śledzący / walidator) +title: "Uruchom węzeł Ethereum na Raspberry Pi 4" +description: "Sflashuj swoje Raspberry Pi 4, podłącz kabel ethernet, podłącz dysk SSD i włącz urządzenie, aby zmienić Raspberry Pi 4 w pełny węzeł Ethereum + walidator" author: "EthereumOnArm" tags: - - "klienty" - - "eth2" - - "węzły" + [ + "klienci", + "warstwa wykonawcza", + "warstwa konsensusu", + "węzły" + ] lang: pl skill: intermediate -published: 2020-05-07 -source: r/ethereum -sourceUrl: https://www.reddit.com/r/ethereum/comments/gf3nhg/ethereum_on_arm_raspberry_pi_4_images_release/ +published: 2022-06-10 +source: Ethereum on ARM +sourceUrl: https://ethereum-on-arm-documentation.readthedocs.io/en/latest/ --- -**TL;DR**: Flashuj Raspberry Pi 4, podłącz kabel Ethernet, podłącz dysk SSD i włącz urządzenie, aby zmienić Raspberry Pi 4 w pełne Ethereum 1.0 węzeł lub węzeł Ethereum 2.0 (łańcuch śledzący/ walidator) +**Ethereum on Arm to niestandardowy obraz Linuksa, który może zamienić Raspberry Pi w węzeł Ethereum.** -[Dowiedz się więcej o Ethereum 2.0 (Eth2)](/roadmap/) +Aby użyć Ethereum on Arm do przekształcenia Raspberry Pi w węzeł Ethereum, zalecany jest następujący sprzęt: -Najpierw trochę tła. Jak wiesz, napotkaliśmy pewne problemy z pamięcią [[1]](/developers/tutorials/run-node-raspberry-pi/#references) związane z obrazem Raspberry Pi 4 ponieważ Raspbian OS jest nadal 32-bitowy [[2]](/developers/tutorials/run-node-raspberry-pi/#references) (przynajmniej w przestrzeni użytkownika). Chociaż wolimy pozostać przy oficjalnym systemie operacyjnym, doszliśmy do wniosku, że aby rozwiązać te problemy, musimy przeprowadzić migrację do natywnego 64-bitowego systemu operacyjnego - -Poza tym klienty Eth 2.0 nie obsługują 32-bitowych plików binarnych, więc użycie Raspbian wykluczyłoby Raspberry Pi 4 z uruchamiania węzła Eth 2.0 (oraz możliwość stakingu). - -Po kilku testach wydajemy teraz 2 różne obrazy oparte na 64-bitowym Ubuntu 20.04 [[3]](/developers/tutorials/run-node-raspberry-pi/#references): Edycje Eth 1.0 i Eth 2.0. - -Zasadniczo oba są tym samym obrazem i zawierają te same cechy obrazów opartych na Raspbian. Ale są one domyślnie skonfigurowane do uruchamiania oprogramowania Eth 1.0 lub Eth 2.0. - -**Obrazy wykonują wszystkie niezbędne kroki**, od konfiguracji środowiska i formatowania dysku SSD po instalację i uruchomienie oprogramowania Ethereum, a także uruchomienie synchronizacji łańcucha bloków. - -## Główne funkcje {#main-features} - -- Na podstawie Ubuntu 20.04 64bit -- Automatyczna partycja i formatowanie dysku USB -- Dodaje pamięć wymiany (moduł jądra ZRAM + plik wymiany) na podstawie pracy Armbiana [[7]](/developers/tutorials/run-node-raspberry-pi/#references) -- Zmienia nazwę hosta na coś w rodzaju „ethnode-e2a3e6fe” w oparciu o hash MAC -- Uruchamia oprogramowanie jako usługę systemową i rozpoczyna synchronizację Blockchain -- Zawiera repozytorium APT do instalacji i aktualizacji oprogramowania Ethereum -- Zawiera panel monitorowania oparty na Grafana / Prometheus - -## Dołączone oprogramowanie {#software-included} - -Oba obrazy zawierają te same pakiety, jedyną różnicą między nimi jest to, że Eth 1.0 domyślnie uruchamia Geth, a Eth 2.0 domyślnie uruchamia łańcuch śledzący Prysm. - -### Klienty Ethereum 1.0 {#execution-clients} - -- Geth [[8]](/developers/tutorials/run-node-raspberry-pi/#references): 1.9.13 (oficjalny plik binarny) -- Parity [[9]](/developers/tutorials/run-node-raspberry-pi/#references): 2.7.2 (kompilacja krzyżowa) -- Nethermind [[10]](/developers/tutorials/run-node-raspberry-pi/#references): 1.8.28 (kompilacja krzyżowa) -- Hyperledger Besu [[11]](/developers/tutorials/run-node-raspberry-pi/#references): 1.4.4 (skompilowane) - -### Klienty Ethereum 2.0 {#consensus-clients} - -- Prysm [[12]](/developers/tutorials/run-node-raspberry-pi/#references): 1.0.0-alpha6 (oficjalny plik binarny) -- Lighthouse [[13]](/developers/tutorials/run-node-raspberry-pi/#references): 0.1.1 (kompilacja) - -### Framework Ethereum {#ethereum-framework} - -- Swarm [[14]](/developers/tutorials/run-node-raspberry-pi/#references): 0.5.7 (oficjalny plik binarny) -- Raiden Network [[15]](/developers/tutorials/run-node-raspberry-pi/#references): 0.200.0~rc1 (oficjalny plik binarny) -- IPFS [[16]](/developers/tutorials/run-node-raspberry-pi/#references): 0.5.0 (oficjalny plik binarny) -- Statusd [[17]](/developers/tutorials/run-node-raspberry-pi/#references): 0.52.3 (skompilowany) -- Vipnode [[18]](/developers/tutorials/run-node-raspberry-pi/#references): 2.3.3 (oficjalny plik binarny) - -## Instrukcja instalacji i użytkowania {#installation-guide-and-usage} - -### Zalecany sprzęt i konfiguracja {#recommended-hardware-and-setup} - -- Raspberry 4 (model B) - 4GB -- Karta MicroSD (16 GB klasy 10 minimum) -- SSD USB 3.0 (zob. sekcja pamięci) +- Płytka Raspberry 4 (model B 8GB), Odroid M1 lub Rock 5B (8GB/16GB RAM) +- Karta MicroSD (minimum 16 GB Klasy 10) +- Dysk SSD o pojemności co najmniej 2 TB z USB 3.0 lub dysk SSD z obudową USB na SATA. - Zasilacz - Kabel Ethernet -- 30303 Przekierowanie portów (Eth 1.0) i 13000 przekierowanie portów (Eth 2.0) [[4]](/developers/tutorials/run-node-raspberry-pi/#references) -- Obudowa z radiatorem i wentylatorem (opcjonalna, ale zdecydowanie zalecana) +- Przekierowanie portów (zobacz klientów, aby uzyskać więcej informacji) +- Obudowa z radiatorem i wentylatorem - Klawiatura USB, monitor i kabel HDMI (micro-HDMI) (opcjonalnie) -## Pamięć {#storage} +## Dlaczego warto uruchomić Ethereum na ARM? {#why-run-ethereum-on-arm} -Będziesz potrzebować dysku SSD, aby uruchomić klientów Ethereum (bez dysku SSD nie ma absolutnie żadnej szansy na zsynchronizowanie łańcucha bloków Ethereum). Istnieją dwie opcje: +Płyty ARM to bardzo przystępne cenowo, elastyczne i małe komputery. Są dobrym wyborem do uruchamiania węzłów Ethereum, ponieważ można je tanio kupić, skonfigurować tak, aby wszystkie ich zasoby skupiały się tylko na węźle, co czyni je wydajnymi, zużywają niewielkie ilości energii i są fizycznie małe, dzięki czemu mogą dyskretnie zmieścić się w każdym domu. Bardzo łatwo jest również uruchomić węzły, ponieważ kartę MicroSD Raspberry Pi można po prostu sflashować gotowym obrazem, bez konieczności pobierania lub budowania oprogramowania. -- Użyj przenośnego dysku SSD USB, takiego jak przenośny dysk SSD Samsung T5. -- Użyj etui na zewnętrzny dysk twardy USB 3.0 z dyskiem SSD. W naszym przypadku zastosowaliśmy obudowę dysku twardego Inateck 2.5 FE2011. Upewnij się, że kupujesz obudowę z chipem zgodnym z UAS, w szczególności jedną z nich: JMicron (JMS567 lub JMS578) lub ASMedia (ASM1153E). +## Jak to działa? {#how-does-it-work} -W obu przypadkach unikaj uzyskiwania dysków SSD niskiej jakości, ponieważ jest to kluczowy element węzła i może drastycznie wpłynąć na wydajność (i czasy synchronizacji). +Karta pamięci Raspberry Pi jest flashowana gotowym obrazem. Ten obraz zawiera wszystko, co jest potrzebne do uruchomienia węzła Ethereum. Po sflashowaniu karty, wszystko co użytkownik musi zrobić, to włączyć Raspberry Pi. Wszystkie procesy wymagane do uruchomienia węzła są uruchamiane automatycznie. Działa to, ponieważ karta pamięci zawiera system operacyjny (OS) oparty na systemie Linux, na którym automatycznie uruchamiane są procesy na poziomie systemu, które przekształcają urządzenie w węzeł Ethereum. -Pamiętaj, że musisz podłączyć dysk do portu USB 3.0 (niebieski) +Ethereum nie może być uruchomione przy użyciu popularnego systemu operacyjnego Linux dla Raspberry Pi "Raspbian", ponieważ Raspbian wciąż używa 32-bitowej architektury, co prowadzi użytkowników Ethereum do problemów z pamięcią, a klienci konsensusu nie obsługują 32-bitowych plików binarnych. Aby to przezwyciężyć, zespół Ethereum on Arm przeniósł się na natywny 64-bitowy system operacyjny o nazwie "Armbian". -## Pobieranie i instalacja obrazu {#image-download-and-installation} +**Obrazy zajmują się wszystkimi niezbędnymi krokami**, od konfiguracji środowiska i formatowania dysku SSD po instalację i uruchomienie oprogramowania Ethereum, a także uruchomienie synchronizacji blockchain. -### 1. Pobierz obrazy Eth 1.0 lub Eth 2.0 {#1-download-execution-or-consensus-images} +## Uwaga na temat klientów wykonawczych i konsensusu {#note-on-execution-and-consensus-clients} -Pobierz obraz Eth 1.0 +Obraz Ethereum on Arm zawiera preinstalowanych klientów wykonawczych i konsensusu jako usługi. Węzeł Ethereum wymaga, aby obaj klienci byli zsynchronizowani i działali. Wymagane jest jedynie pobranie i sflashowanie obrazu, a następnie uruchomienie usług. Obraz jest wstępnie załadowany następującymi klientami wykonawczymi: -sha256 7fa9370d13857dd6abcc8fde637c7a9a7e3a66b307d5c28b0c0d29a09c73c55cPobierz obraz Eth2 +- Geth +- Nethermind +- Besu -sha256 74c0c15b708720e5ae5cac324f1afded6316537fb17166109326755232cd316e +oraz następującymi klientami konsensusu: -### 2. Wgraj obraz {#2-flash-the-image} +- Lighthouse +- Nimbus +- Prysm +- Teku -Włóż kartę microSD do komputera stacjonarnego / laptopa i pobierz plik (na przykład Eth 1.0): +Należy wybrać po jednym z każdego do uruchomienia - wszyscy klienci wykonawczy są kompatybilni ze wszystkimi klientami konsensusu. Jeśli nie wybierzesz jawnie klienta, węzeł powróci do swoich wartości domyślnych - Geth i Lighthouse - i uruchomi je automatycznie po włączeniu zasilania płyty. Musisz otworzyć port 30303 na swoim routerze, aby Geth mógł znaleźć i połączyć się z peerami. -```bash -wget https://ethraspbian.com/downloads/ubuntu-20.04-preinstalled-server-arm64+raspi-eth1.img.zip -``` +## Pobieranie obrazu {#downloading-the-image} -Uwaga: jeśli nie czujesz się dobrze z wierszem poleceń lub jeśli używasz systemu Windows, możesz użyć [Etcher](https://etcher.io) +Obraz Ethereum dla Raspberry Pi 4 to obraz typu "plug and play", który automatycznie instaluje i konfiguruje zarówno klientów wykonawczych, jak i konsensusu, konfigurując ich do komunikacji między sobą i połączenia z siecią Ethereum. Wszystko, co użytkownik musi zrobić, to uruchomić ich procesy za pomocą prostego polecenia. -Otwórz terminal i sprawdź nazwę swojego urządzenia MicroSD, które działa: +Pobierz obraz Raspberry Pi z [Ethereum na ARM](https://ethereumonarm-my.sharepoint.com/:u:/p/dlosada/Ec_VmUvr80VFjf3RYSU-NzkBmj2JOteDECj8Bibde929Gw?download=1) i zweryfikuj hasz SHA256: -```bash -sudo fdisk -l +```sh +# Z katalogu zawierającego pobrany obraz +shasum -a 256 ethonarm_22.04.00.img.zip +# Wynikowy hasz powinien być: fb497e8f8a7388b62d6e1efbc406b9558bee7ef46ec7e53083630029c117444f ``` -Powinieneś zobaczyć urządzenie o nazwie mmcblk0 lub sdd. Rozpakuj i wgraj obraz: +Zwróć uwagę, że obrazy dla płyt Rock 5B i Odroid M1 są dostępne na stronie [pobierania](https://ethereum-on-arm-documentation.readthedocs.io/en/latest/quick-guide/download-and-install.html) Ethereum-on-Arm. -```bash -unzip ubuntu-20.04-preinstalled-server-arm64+raspi-eth1.img.zip -sudo dd bs=1M if=ubuntu-20.04-preinstalled-server-arm64+raspi-eth1.img of=/dev/mmcblk0 && sync -``` +## Flashowanie MicroSD {#flashing-the-microsd} -### 3. Włóż microSD do Raspberry Pi 4. Podłącz kabel Ethernet i podłącz dysk USB SSD (upewnij się, że używasz niebieskiego portu). {#3-insert-the-microsd-into-the-raspberry-pi-4-connect-an-ethernet-cable-and-attach-the-usb-ssd-disk-make-sure-you-are-using-a-blue-port} +Karta MicroSD, która będzie używana w Raspberry Pi, powinna najpierw zostać włożona do komputera stacjonarnego lub laptopa, aby można ją było sflashować. Następnie poniższe polecenia terminala sflashują pobrany obraz na kartę SD: -### 4. Włącz urządzenie {#4-power-on-the-device} - -System operacyjny Ubuntu uruchomi się za mniej niż minutę, ale **trzeba poczekać około 10 minut**, aby skrypt mógł wykonać niezbędne zadania, aby zmienić urządzenie w węzeł Ethereum i zrestartuj Raspberry. - -W zależności od obrazu uruchomisz: - -- Eth 1.0: Geth jako domyślny klient synchronizujący łańcuch bloków -- Eth2: Prysm jako domyślny klient synchronizujący łańcuch śledzący (sieć testowa Topaz) - -### 5. Zaloguj się {#5-log-in} - -Możesz zalogować się przez SSH lub za pomocą konsoli (jeśli masz podłączony monitor i klawiaturę) +```shell +# sprawdź nazwę karty MicroSD +sudo fdisk -l -```bash -User: ethereum -Password: ethereum +>> sdxxx ``` -Zostaniesz poproszony o zmianę hasła przy pierwszym logowaniu, więc będziesz musiał zalogować się dwukrotnie. - -### 6. Otwórz port 30303 dla Getha i 13000, jeśli używasz łańcucha śledzącego Prysm. Jeśli nie wiesz, jak to zrobić, wygoogluj „przekierowanie portów”, a następnie model routera. {#6-open-30303-port-for-geth-and-13000-if-you-are-running-prysm-beacon-chain-if-you-dont-know-how-to-do-this-google-port-forwarding-followed-by-your-router-model} - -### 7. Uzyskaj dane wyjściowe konsoli {#7-get-console-output} +Bardzo ważne jest, aby podać poprawną nazwę, ponieważ następne polecenie zawiera `dd`, które całkowicie usuwa istniejącą zawartość karty przed zapisaniem na niej obrazu. Aby kontynuować, przejdź do katalogu zawierającego spakowany obraz: -Możesz zobaczyć, co dzieje się w tle, wpisując: - -```bash -sudo tail -f /var/log/syslog +```shell +# rozpakuj i sflashuj obraz +unzip ethonarm_22.04.00.img.zip +sudo dd bs=1M if=ethonarm_22.04.00.img of=/dev/ conv=fdatasync status=progress ``` -**Gratulacje. Korzystasz teraz z pełnego węzła Ethereum na swoim Raspberry Pi 4.** - -## Synchronizowanie łańcucha bloków {#syncing-the-blockchain} +Karta jest teraz sflashowana, więc można ją włożyć do Raspberry Pi. -Teraz musisz poczekać na synchronizację łańcucha bloków. W przypadku Eth 1.0 zajmie to kilka dni w zależności od kilku czynników, ale możesz spodziewać się do około 5-7 dni. +## Uruchom węzeł {#start-the-node} -Jeśli korzystasz z tesnetu Eth2 Topaz, możesz spodziewać się 1-2 dni czasu synchronizacji łańcucha śledzącego. Pamiętaj, że będziesz musiał ustawić walidator później, aby rozpocząć proces stakingu. [Jak uruchomić walidator Eth 2.0](/developers/tutorials/run-node-raspberry-pi/#validator) +Po włożeniu karty SD do Raspberry Pi, podłącz kabel Ethernet i dysk SSD, a następnie włącz zasilanie. System operacyjny uruchomi się i automatycznie rozpocznie wykonywanie wstępnie skonfigurowanych zadań, które przekształcą Raspberry Pi w węzeł Ethereum, w tym instalację i budowanie oprogramowania klienta. Prawdopodobnie zajmie to 10-15 minut. -## Pulpity kontrolne {#monitoring-dashboards} +Gdy wszystko zostanie zainstalowane i skonfigurowane, zaloguj się do urządzenia za pomocą połączenia ssh lub bezpośrednio za pomocą terminala, jeśli do płyty podłączony jest monitor i klawiatura. Użyj konta `ethereum` do zalogowania się, ponieważ ma ono uprawnienia wymagane do uruchomienia węzła. -W tej pierwszej wersji dołączyliśmy 3 panele monitorowania oparte na Prometheus [[5]](/developers/tutorials/run-node-raspberry-pi/#references) /Grafana [[6]](/developers/tutorials/run-node-raspberry-pi/#references) w celu monitorowania danych węzła i klientów (Geth i Besu). Możesz uzyskać dostęp przez przeglądarkę internetową: - -```bash -URL: http://your_raspberrypi_IP:3000 -User: admin -Password: ethereum +```shell +Użytkownik: ethereum +Hasło: ethereum ``` -## Przełączanie klientów {#switching-clients} - -Wszyscy klienci działają jako usługa systemowa. Jest to ważne, ponieważ jeśli pojawi się problem, system automatycznie odrodzi proces. - -Łańcuch śledzący Geth i Prysm jest uruchamiany domyślnie (w zależności od tego, co synchronizujesz, Eth 1.0 lub Eth2), więc jeśli chcesz przełączyć się na innych klientów (na przykład z Geth na Nethermind), musisz najpierw zatrzymać i wyłączyć Geth, oraz włącz i uruchom drugiego klienta: +Domyślny klient wykonawczy, Geth, uruchomi się automatycznie. Możesz to potwierdzić, sprawdzając logi za pomocą następującego polecenia terminala: -```bash -sudo systemctl stop geth && sudo systemctl disable geth +```sh +sudo journalctl -u geth -f ``` -Polecenia włączania i uruchamiania każdego klienta Eth 1.0: +Klient konsensusu musi być uruchomiony jawnie. Aby to zrobić, najpierw otwórz port 9000 na routerze, aby Lighthouse mógł znaleźć i połączyć się z peerami. Następnie włącz i uruchom usługę lighthouse: -```bash -sudo systemctl enable besu && sudo systemctl start besu -sudo systemctl enable nethermind && sudo systemctl start nethermind -sudo systemctl enable parity && sudo systemctl start parity +```sh +sudo systemctl enable lighthouse-beacon +sudo systemctl start lighthouse-beacon ``` -Eth2: +Sprawdź klienta za pomocą logów: -```bash -sudo systemctl stop prysm-beacon && sudo systemctl disable prysm-beacon -sudo systemctl start lighthouse && sudo systemctl enable lighthouse +```sh +sudo journalctl -u lighthouse-beacon ``` -## Zmiana parametrów {#changing-parameters} +Zwróć uwagę, że klient konsensusu zsynchronizuje się w kilka minut, ponieważ używa synchronizacji z punktu kontrolnego. Synchronizacja klienta wykonawczego potrwa dłużej – potencjalnie kilka godzin i nie rozpocznie się, dopóki klient konsensusu nie zakończy synchronizacji (dzieje się tak, ponieważ klient wykonawczy potrzebuje celu do synchronizacji, który zapewnia zsynchronizowany klient konsensusu). -Pliki konfiguracyjne klientów znajdują się w katalogu /etc/ethereum/. Możesz edytować te pliki i ponownie uruchomić usługę systemd, aby zmiany zaczęły obowiązywać. Jedynym wyjątkiem jest Nethermind, który dodatkowo posiada plik konfiguracyjny sieci głównej, który znajduje się tutaj: +Gdy usługi Geth i Lighthouse działają i są zsynchronizowane, Twoje Raspberry Pi jest teraz węzłem Ethereum! Najczęściej interakcja z siecią Ethereum odbywa się za pomocą konsoli Javascript Geth, którą można podłączyć do klienta Geth na porcie 8545. Możliwe jest również przesyłanie poleceń sformatowanych jako obiekty JSON za pomocą narzędzia do zapytań, takiego jak Curl. Zobacz więcej w [dokumentacji Geth](https://geth.ethereum.org/). -```bash -/etc/nethermind/configs/mainnet.cfg -``` +Geth jest wstępnie skonfigurowany do raportowania metryk na pulpicie nawigacyjnym Grafana, który można wyświetlić w przeglądarce. Bardziej zaawansowani użytkownicy mogą chcieć użyć tej funkcji do monitorowania kondycji swojego węzła, przechodząc do `ipaddress:3000` i podając `user: admin` i `passwd: ethereum`. + +## Walidatorzy {#validators} -Dane klientów Blockchain są przechowywane na koncie domowym Ethereum w następujący sposób (zwróć uwagę na kropkę przed nazwą katalogu): +Walidator można również opcjonalnie dodać do klienta konsensusu. Oprogramowanie walidatora pozwala węzłowi aktywnie uczestniczyć w konsensusie i zapewnia sieci bezpieczeństwo kryptoekonomiczne. Za tę pracę otrzymujesz nagrodę w ETH. Aby uruchomić walidatora, musisz najpierw posiadać 32 ETH, które muszą zostać zdeponowane w kontrakcie depozytowym. Wpłaty można dokonać, postępując zgodnie z przewodnikiem krok po kroku na [Launchpadzie](https://launchpad.ethereum.org/). Zrób to na komputerze stacjonarnym/laptopie, ale nie generuj kluczy — można to zrobić bezpośrednio na Raspberry Pi. -### Eth 1.0 {#execution-layer} +Otwórz terminal na Raspberry Pi i uruchom następujące polecenie, aby wygenerować klucze depozytowe: -```bash -/home/ethereum/.geth -/home/ethereum/.parity -/home/ethereum/.besu -/home/ethereum/.nethermind +``` +sudo apt-get update +sudo apt-get install staking-deposit-cli +cd && deposit new-mnemonic --num_validators 1 ``` -### Eth2 {#consensus-layer} +(Lub pobierz [staking-deposit-cli](https://github.com/ethereum/staking-deposit-cli), aby uruchomić go na maszynie odizolowanej od sieci i uruchom polecenie `deposit new-mnemnonic`) -```bash -/home/ethereum/.eth2 -/home/ethereum/.eth2validators -/home/ethereum/.lighthouse -``` +Przechowuj frazę mnemoniczną w bezpiecznym miejscu! Powyższe polecenie wygenerowało dwa pliki w keystorze węzła: klucze walidatora i plik danych depozytowych. Dane depozytowe muszą zostać przesłane do Launchpada, więc muszą zostać skopiowane z Raspberry Pi na komputer stacjonarny/laptop. Można to zrobić za pomocą połączenia ssh lub dowolnej innej metody kopiuj/wklej. -## Nethermind i Hyperledger Besu {#nethermind-and-hyperledger-besu} +Gdy plik danych depozytowych jest dostępny na komputerze, na którym uruchomiony jest Launchpad, można go przeciągnąć i upuścić na znak `+` na ekranie Launchpada. Postępuj zgodnie z instrukcjami na ekranie, aby wysłać transakcję do kontraktu depozytowego. -Te 2 wspaniałe klienty Eth 1.0 stały się świetną alternatywą dla Geth and Parity. Im większa różnorodność w sieci, tym lepiej, więc możesz spróbować i przyczynić się do poprawy stanu sieci. +Wracając do Raspberry Pi, można uruchomić walidatora. Wymaga to zaimportowania kluczy walidatora, ustawienia adresu do zbierania nagród, a następnie uruchomienia wstępnie skonfigurowanego procesu walidatora. Poniższy przykład dotyczy Lighthouse – instrukcje dla innych klientów konsensusu są dostępne w [dokumentacji Ethereum on Arm](https://ethereum-on-arm-documentation.readthedocs.io/en/latest/): -Oba wymagają dalszych testów, więc wypróbuj je i zgłoś swoją opinię. +```shell +# zaimportuj klucze walidatora +lighthouse account validator import --directory=/home/ethereum/validator_keys -## Jak uruchomić walidator Eth 2.0 (staking) {#validator} +# ustaw adres nagrody +sudo sed -i 's/' /etc/ethereum/lighthouse-validator.conf + +# uruchom walidatora +sudo systemctl start lighthouse-validator +``` -Gdy łańcuch śledzący sieci testowej Topaz zostanie zsynchronizowany, można uruchomić walidator na tym samym urządzeniu. Będziesz musiał postępować według [tych etapów uczestnictwa](https://prylabs.net/participate). +Gratulacje, masz teraz pełny węzeł Ethereum i walidatora działającego na Raspberry Pi! -Po raz pierwszy, musisz utworzyć ręcznie konto, uruchamiając plik binarny „validator” i skonfigurować hasło. Po zakończeniu tego kroku możesz dodać hasło do `/etc/ethereum/prysm-validator.conf` i uruchomić walidator jako usługę systemową. +## Więcej szczegółów {#more-details} -## Opinie są mile widziane! {#feedback-appreciated} +Ta strona zawiera przegląd sposobu konfiguracji węzła i walidatora Geth-Lighthouse przy użyciu Raspberry Pi. Bardziej szczegółowe instrukcje są dostępne na [stronie internetowej Ethereum-on-Arm](https://ethereum-on-arm-documentation.readthedocs.io/en/latest/index.html). -Włożyliśmy dużo pracy, próbując skonfigurować Raspberry Pi 4 jako pełny węzeł Ethereum, ponieważ wiemy, że ogromna baza użytkowników tego urządzenia może mieć bardzo pozytywny wpływ na sieć. +## Twoja opinia jest mile widziana {#feedback-appreciated} -Proszę wziąć pod uwagę, że jest to pierwszy obraz oparty na Ubuntu 20.04, więc może być kilka błędów. Jeśli tak, otwórz zgłoszenie na [GitHub](https://github.com/diglos/ethereumonarm) lub skontaktuj się z nami na [Twitter](https://twitter.com/EthereumOnARM). +Wiemy, że Raspberry Pi ma ogromną bazę użytkowników, która może mieć bardzo pozytywny wpływ na kondycję sieci Ethereum. +Zapoznaj się ze szczegółami tego samouczka, spróbuj uruchomić go na sieciach testowych, sprawdź GitHub Ethereum on Arm, przekaż opinie, zgłaszaj problemy i pull requesty oraz pomóż w rozwoju technologii i dokumentacji! -## Odniesienia {#references} +## Materiały źródłowe {#references} -1. [geth repeatedly crashes with SIGSEGV](https://github.com/ethereum/go-ethereum/issues/20190) -2. [https://github.com/diglos/ethereumonarm](https://github.com/diglos/ethereumonarm) -3. https://ubuntu.com/download/raspberry-pi -4. https://wikipedia.org/wiki/Port_forwarding -5. https://prometheus.io -6. https://grafana.com -7. https://forum.armbian.com/topic/5565-zram-vs-swap/ -8. https://geth.ethereum.org -9. https://github.com/openethereum/openethereum -10. https://nethermind.io -11. https://www.hyperledger.org/projects/besu -12. https://github.com/prysmaticlabs/prysm -13. https://lighthouse.sigmaprime.io -14. https://ethersphere.github.io/swarm-home -15. https://raiden.network -16. https://ipfs.io -17. https://status.im -18. https://vipnode.org +1. https://ubuntu.com/download/raspberry-pi +2. https://wikipedia.org/wiki/Port_forwarding +3. https://prometheus.io +4. https://grafana.com +5. https://forum.armbian.com/topic/5565-zram-vs-swap/ +6. https://geth.ethereum.org +7. https://nethermind.io +8. https://www.hyperledger.org/projects/besu +9. https://github.com/prysmaticlabs/prysm +10. https://lighthouse.sigmaprime.io +11. https://ethersphere.github.io/swarm-home +12. https://raiden.network +13. https://ipfs.io +14. https://status.im +15. https://vipnode.org diff --git a/public/content/translations/pl/developers/tutorials/scam-token-tricks/index.md b/public/content/translations/pl/developers/tutorials/scam-token-tricks/index.md new file mode 100644 index 00000000000..fa0cade193a --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/scam-token-tricks/index.md @@ -0,0 +1,470 @@ +--- +title: "Kilka sztuczek używanych przez scamerskie tokeny i jak je wykryć" +description: "W tym samouczku przeanalizujemy scamerski token, aby zobaczyć niektóre sztuczki stosowane przez oszustów, jak je wdrażają i jak możemy je wykrywać." +author: Ori Pomerantz +tags: + [ + "scam", + "solidity", + "erc-20", + "JavaScript", + "typescript" + ] +skill: intermediate +published: 2023-09-15 +lang: pl +--- + +W tym samouczku przeanalizujemy [scamerski token](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code), aby zobaczyć niektóre sztuczki stosowane przez oszustów i jak je wdrażają. Pod koniec samouczka będziesz mieć bardziej kompleksowy pogląd na kontrakty tokenów ERC-20, ich możliwości i dlaczego sceptycyzm jest konieczny. Następnie przyjrzymy się zdarzeniom emitowanym przez ten scamerski token i zobaczymy, jak możemy automatycznie zidentyfikować, że nie jest on wiarygodny. + +## Scamerskie tokeny – czym są, dlaczego ludzie je tworzą i jak ich unikać {#scam-tokens} + +Jednym z najczęstszych zastosowań Ethereum jest tworzenie przez grupę wymienialnych tokenów, w pewnym sensie własnej waluty. Jednak wszędzie tam, gdzie istnieją uzasadnione przypadki użycia, które przynoszą wartość, są też przestępcy, którzy próbują ukraść tę wartość dla siebie. + +Możesz przeczytać więcej na ten temat [w innym miejscu na ethereum.org](/guides/how-to-id-scam-tokens/) z perspektywy użytkownika. Ten samouczek skupia się na analizie scamerskiego tokena, aby zobaczyć, jak to się robi i jak można go wykryć. + +### Skąd mam wiedzieć, że wARB to oszustwo? {#warb-scam} + +Token, który analizujemy to [wARB](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code), który udaje, że jest odpowiednikiem legalnego [tokena ARB](https://etherscan.io/token/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1). + +Najprostszym sposobem, aby dowiedzieć się, który token jest legalny, jest spojrzenie na organizację, która go stworzyła, [Arbitrum](https://arbitrum.foundation/). Legalne adresy są określone [w ich dokumentacji](https://docs.arbitrum.foundation/deployment-addresses#token). + +### Dlaczego kod źródłowy jest dostępny? {#why-source} + +Zwykle spodziewalibyśmy się, że ludzie, którzy próbują oszukiwać innych, będą skryci, i rzeczywiście, wiele scamerskich tokenów nie ma dostępnego swojego kodu (na przykład, [ten](https://optimistic.etherscan.io/token/0x15992f382d8c46d667b10dc8456dc36651af1452#code) i [ten](https://optimistic.etherscan.io/token/0x026b623eb4aada7de37ef25256854f9235207178#code)). + +Jednakże, legalne tokeny zwykle publikują swój kod źródłowy, więc aby wyglądać na legalne, autorzy scamerskich tokenów czasami robią to samo. [wARB](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code) jest jednym z tych tokenów z dostępnym kodem źródłowym, co ułatwia jego zrozumienie. + +Podczas gdy wdrażający kontrakty mogą wybrać, czy opublikować kod źródłowy, czy nie, to _nie mogą_ opublikować niewłaściwego kodu źródłowego. Eksplorator bloków niezależnie kompiluje dostarczony kod źródłowy, i jeśli nie uzyska dokładnie tego samego kodu bajtowego, odrzuca ten kod źródłowy. [Możesz przeczytać więcej na ten temat na stronie Etherscan](https://etherscan.io/verifyContract). + +## Porównanie z legalnymi tokenami ERC-20 {#compare-legit-erc20} + +Porównamy ten token z legalnymi tokenami ERC-20. Jeśli nie znasz sposobu, w jaki zazwyczaj pisane są legalne tokeny ERC-20, [zobacz ten samouczek](/developers/tutorials/erc20-annotated-code/). + +### Stałe dla uprzywilejowanych adresów {#constants-for-privileged-addresses} + +Kontrakty czasami potrzebują uprzywilejowanych adresów. Kontrakty, które są zaprojektowane do długotrwałego użytku, pozwalają pewnym uprzywilejowanym adresom na zmianę tych adresów, na przykład, aby umożliwić korzystanie z nowego kontraktu multisig. Istnieje kilka sposobów na zrobienie tego. + +Kontrakt tokena [`HOP`](https://etherscan.io/address/0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc#code) używa wzorca [`Ownable`](https://docs.openzeppelin.com/contracts/2.x/access-control#ownership-and-ownable). Uprzywilejowany adres jest przechowywany w pamięci masowej, w polu o nazwie `_owner` (zobacz trzeci plik, `Ownable.sol`). + +```solidity +abstract contract Ownable is Context { + address private _owner; + . + . + . +} +``` + +Kontrakt tokena [`ARB`](https://etherscan.io/address/0xad0c361ef902a7d9851ca7dcc85535da2d3c6fc7#code) nie ma bezpośrednio uprzywilejowanego adresu. Jednakże nie potrzebuje go. Znajduje się za [`proxy`](https://docs.openzeppelin.com/contracts/5.x/api/proxy) pod [adresem `0xb50721bcf8d664c30412cfbc6cf7a15145234ad1`](https://etherscan.io/address/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1#code). Ten kontrakt ma uprzywilejowany adres (zobacz czwarty plik, `ERC1967Upgrade.sol`), który może być używany do aktualizacji. + +```solidity + /** + * @dev Przechowuje nowy adres w slocie administratora EIP1967. + */ + function _setAdmin(address newAdmin) private { + require(newAdmin != address(0), "ERC1967: new admin is the zero address"); + StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; + } +``` + +W przeciwieństwie do tego, kontrakt `wARB` ma na stałe zakodowanego `contract_owner`. + +```solidity +contract WrappedArbitrum is Context, IERC20 { + . + . + . + address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1; + address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33; + . + . + . +} +``` + +[Ten właściciel kontraktu](https://etherscan.io/address/0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33) nie jest kontraktem, który mógłby być kontrolowany przez różne konta w różnym czasie, ale [kontem zarządzanym zewnętrznie](/developers/docs/accounts/#externally-owned-accounts-and-key-pairs). Oznacza to, że jest prawdopodobnie przeznaczony do krótkotrwałego użytku przez jedną osobę, a nie jako długoterminowe rozwiązanie do kontrolowania ERC-20, które pozostanie cenne. + +I rzeczywiście, jeśli spojrzymy na Etherscan, zobaczymy, że oszust używał tego kontraktu tylko przez 12 godzin ([pierwsza transakcja](https://etherscan.io/tx/0xf49136198c3f925fcb401870a669d43cecb537bde36eb8b41df77f06d5f6fbc2) do [ostatniej transakcji](https://etherscan.io/tx/0xdfd6e717157354e64bbd5d6adf16761e5a5b3f914b1948d3545d39633244d47b)) w dniu 19 maja 2023 r. + +### Fałszywa funkcja `_transfer` {#the-fake-transfer-function} + +Standardem jest, że faktyczne transfery odbywają się przy użyciu [wewnętrznej funkcji `_transfer`](/developers/tutorials/erc20-annotated-code/#the-_transfer-function-_transfer). + +W `wARB` ta funkcja wygląda na prawie legalną: + +```solidity + function _transfer(address sender, address recipient, uint256 amount) internal virtual{ + require(sender != address(0), "ERC20: transfer z adresu zerowego"); + require(recipient != address(0), "ERC20: transfer na adres zerowy"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: kwota transferu przekracza saldo"); + _balances[recipient] = _balances[recipient].add(amount); + if (sender == contract_owner){ + sender = deployer; + } + emit Transfer(sender, recipient, amount); + } +``` + +Podejrzana część to: + +```solidity + if (sender == contract_owner){ + sender = deployer; + } + emit Transfer(sender, recipient, amount); +``` + +Jeśli właściciel kontraktu wysyła tokeny, dlaczego zdarzenie `Transfer` pokazuje, że pochodzą one od `deployer`? + +Jest jednak ważniejsza kwestia. Kto wywołuje tę funkcję `_transfer`? Nie można jej wywołać z zewnątrz, jest oznaczona jako `internal`. A kod, który mamy, nie zawiera żadnych wywołań `_transfer`. Oczywiście jest to tylko przynęta. + +```solidity + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _f_(_msgSender(), recipient, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _f_(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: kwota transferu przekracza przydział")); + return true; + } +``` + +Gdy przyjrzymy się funkcjom wywoływanym w celu transferu tokenów, `transfer` i `transferFrom`, widzimy, że wywołują one zupełnie inną funkcję, `_f_`. + +### Prawdziwa funkcja `_f_` {#the-real-f-function} + +```solidity + function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual { + require(sender != address(0), "ERC20: transfer z adresu zerowego"); + require(recipient != address(0), "ERC20: transfer na adres zerowy"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: kwota transferu przekracza saldo"); + _balances[recipient] = _balances[recipient].add(amount); + if (sender == contract_owner){ + + sender = deployer; + } + emit Transfer(sender, recipient, amount); + } +``` + +W tej funkcji są dwa potencjalne sygnały ostrzegawcze. + +- Użycie [modyfikatora funkcji](https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm) `_mod_`. Jednak gdy spojrzymy w kod źródłowy, zobaczymy, że `_mod_` jest w rzeczywistości nieszkodliwy. + + ```solidity + modifier _mod_(address sender, address recipient, uint256 amount){ + _; + } + ``` + +- Ten sam problem, który widzieliśmy w `_transfer`, czyli gdy `contract_owner` wysyła tokeny, wydaje się, że pochodzą one z `deployer`. + +### Fałszywa funkcja zdarzeń `dropNewTokens` {#the-fake-events-function-dropNewTokens} + +Teraz dochodzimy do czegoś, co wygląda jak prawdziwe oszustwo. Edytowałem funkcję dla większej czytelności, ale jest ona funkcjonalnie równoważna. + +```solidity +function dropNewTokens(address uPool, + address[] memory eReceiver, + uint256[] memory eAmounts) public auth() +``` + +Ta funkcja ma modyfikator `auth()`, co oznacza, że może być wywołana tylko przez właściciela kontraktu. + +```solidity +modifier auth() { + require(msg.sender == contract_owner, "Brak pozwolenia na interakcję"); + _; +} +``` + +To ograniczenie ma sens, ponieważ nie chcielibyśmy, aby przypadkowe konta dystrybuowały tokeny. Jednak reszta funkcji jest podejrzana. + +```solidity +{ + for (uint256 i = 0; i < eReceiver.length; i++) { + emit Transfer(uPool, eReceiver[i], eAmounts[i]); + } +} +``` + +Funkcja transferu z konta puli do tablicy odbiorców tablicy kwot ma sens. Istnieje wiele przypadków użycia, w których będziesz chciał dystrybuować tokeny z jednego źródła do wielu miejsc docelowych, takich jak listy płac, airdropy itp. Jest to tańsze (w gazie) do wykonania w jednej transakcji zamiast emitowania wielu transakcji lub nawet wielokrotnego wywoływania ERC-20 z innego kontraktu w ramach tej samej transakcji. + +Jednak `dropNewTokens` tego nie robi. Emituje [zdarzenia `Transfer`](https://eips.ethereum.org/EIPS/eip-20#transfer-1), ale tak naprawdę nie transferuje żadnych tokenów. Nie ma żadnego uzasadnionego powodu, aby wprowadzać w błąd aplikacje offchain, informując je o transferze, który tak naprawdę nie miał miejsca. + +### Funkcja `Approve` do spalania {#the-burning-approve-function} + +Kontrakty ERC-20 powinny mieć [funkcję `approve`](/developers/tutorials/erc20-annotated-code/#approve) dla przydziałów i rzeczywiście, nasz scamerski token ma taką funkcję, a nawet jest ona poprawna. Jednakże, ponieważ Solidity wywodzi się z C, wielkość liter ma znaczenie. "Approve" i "approve" to różne ciągi znaków. + +Ponadto funkcjonalność nie jest związana z `approve`. + +```solidity + function Approve( + address[] memory holders) +``` + +Ta funkcja jest wywoływana z tablicą adresów posiadaczy tokena. + +```solidity + public approver() { +``` + +Modyfikator `approver()` zapewnia, że tylko `contract_owner` może wywołać tę funkcję (zobacz poniżej). + +```solidity + for (uint256 i = 0; i < holders.length; i++) { + uint256 amount = _balances[holders[i]]; + _beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount); + _balances[holders[i]] = _balances[holders[i]].sub(amount, + "ERC20: spalana kwota przekracza saldo"); + _balances[0x0000000000000000000000000000000000000001] = + _balances[0x0000000000000000000000000000000000000001].add(amount); + } + } + +``` + +Dla każdego adresu posiadacza funkcja przenosi całe saldo posiadacza na adres `0x00...01`, skutecznie je spalając (rzeczywiste `spalenie` w standardzie zmienia również całkowitą podaż i transferuje tokeny do `0x00...00`). Oznacza to, że `contract_owner` może usunąć aktywa dowolnego użytkownika. Nie wydaje się to funkcją, którą chciałbyś mieć w tokenie zarządzania. + +### Problemy z jakością kodu {#code-quality-issues} + +Te problemy z jakością kodu nie _udowadniają_, że ten kod jest oszustwem, ale sprawiają, że wydaje się on podejrzany. Zorganizowane firmy, takie jak Arbitrum, zwykle nie wydają tak złego kodu. + +#### Funkcja `mount` {#the-mount-function} + +Chociaż nie jest to określone w [standardzie](https://eips.ethereum.org/EIPS/eip-20), ogólnie rzecz biorąc, funkcja, która tworzy nowe tokeny, nazywa się [`mint`](https://ethereum.org/el/developers/tutorials/erc20-annotated-code/#the-_mint-and-_burn-functions-_mint-and-_burn). + +Jeśli spojrzymy na konstruktor `wARB`, zobaczymy, że funkcja mintowania została z jakiegoś powodu przemianowana na `mount` i jest wywoływana pięć razy z jedną piątą początkowej podaży, zamiast raz dla całej kwoty dla wydajności. + +```solidity + constructor () public { + + _name = "Wrapped Arbitrum"; + _symbol = "wARB"; + _decimals = 18; + uint256 initialSupply = 1000000000000; + + mount(deployer, initialSupply*(10**18)/5); + mount(deployer, initialSupply*(10**18)/5); + mount(deployer, initialSupply*(10**18)/5); + mount(deployer, initialSupply*(10**18)/5); + mount(deployer, initialSupply*(10**18)/5); + } +``` + +Sama funkcja `mount` jest również podejrzana. + +```solidity + function mount(address account, uint256 amount) public { + require(msg.sender == contract_owner, "ERC20: mintowanie na adres zerowy"); +``` + +Patrząc na `require`, widzimy, że tylko właściciel kontraktu może mintować. To jest legalne. Ale komunikat o błędzie powinien brzmieć _tylko właściciel może mintować_ lub coś w tym rodzaju. Zamiast tego jest to nieistotne _ERC20: mintowanie na adres zerowy_. Prawidłowy test mintowania na adres zerowy to `require(account != address(0), "")`, czego kontrakt nigdy nie sprawdza. + +```solidity + _totalSupply = _totalSupply.add(amount); + _balances[contract_owner] = _balances[contract_owner].add(amount); + emit Transfer(address(0), account, amount); + } +``` + +Istnieją jeszcze dwa podejrzane fakty, bezpośrednio związane z mintowaniem: + +- Istnieje parametr `account`, który jest przypuszczalnie kontem, które powinno otrzymać z-mintowaną kwotę. Ale saldo, które wzrasta, należy w rzeczywistości do `contract_owner`. + +- Podczas gdy zwiększone saldo należy do `contract_owner`, emitowane zdarzenie pokazuje transfer na `account`. + +### Po co zarówno `auth`, jak i `approver`? Po co `mod`, który nic nie robi? {#why-both-autho-and-approver-why-the-mod-that-does-nothing} + +Ten kontrakt zawiera trzy modyfikatory: `_mod_`, `auth` i `approver`. + +```solidity + modifier _mod_(address sender, address recipient, uint256 amount){ + _; + } +``` + +`_mod_` przyjmuje trzy parametry i nic z nimi nie robi. Po co on jest? + +```solidity + modifier auth() { + require(msg.sender == contract_owner, "Brak pozwolenia na interakcję"); + _; + } + + modifier approver() { + require(msg.sender == contract_owner, "Brak pozwolenia na interakcję"); + _; + } +``` + +`auth` i `approver` mają więcej sensu, ponieważ sprawdzają, czy kontrakt został wywołany przez `contract_owner`. Spodziewalibyśmy się, że pewne uprzywilejowane działania, takie jak mintowanie, będą ograniczone do tego konta. Jednak jaki jest sens posiadania dwóch oddzielnych funkcji, które robią _dokładnie to samo_? + +## Co możemy wykryć automatycznie? {#what-can-we-detect-automatically} + +Możemy zobaczyć, że `wARB` jest scamerskim tokenem, patrząc na Etherscan. Jest to jednak scentralizowane rozwiązanie. W teorii Etherscan mógłby zostać obalony lub zhakowany. Lepiej jest być w stanie samodzielnie ocenić, czy token jest legalny, czy nie. + +Istnieją pewne sztuczki, których możemy użyć, aby zidentyfikować, że token ERC-20 jest podejrzany (albo jest to oszustwo, albo jest bardzo źle napisany), patrząc na emitowane przez niego zdarzenia. + +## Podejrzane zdarzenia `Approval` {#suspicious-approval-events} + +[Zdarzenia `Approval`](https://eips.ethereum.org/EIPS/eip-20#approval) powinny mieć miejsce tylko na bezpośrednie żądanie (w przeciwieństwie do [zdarzeń `Transfer`](https://eips.ethereum.org/EIPS/eip-20#transfer-1), które mogą mieć miejsce w wyniku przydziału). [Zobacz dokumentację Solidity](https://docs.soliditylang.org/en/v0.8.20/security-considerations.html#tx-origin), aby uzyskać szczegółowe wyjaśnienie tego problemu i dlaczego żądania muszą być bezpośrednie, a nie za pośrednictwem kontraktu. + +Oznacza to, że zdarzenia `Approval` zatwierdzające wydatki z [konta zarządzanego zewnętrznie](/developers/docs/accounts/#types-of-account) muszą pochodzić z transakcji, które pochodzą z tego konta i których miejscem docelowym jest kontrakt ERC-20. Każdy inny rodzaj zatwierdzenia z konta zarządzanego zewnętrznie jest podejrzany. + +Oto [program, który identyfikuje tego rodzaju zdarzenia](https://github.com/qbzzt/20230915-scam-token-detection), używając [viem](https://viem.sh/) i [TypeScript](https://www.typescriptlang.org/docs/), wariantu JavaScript z bezpieczeństwem typów. Aby go uruchomić: + +1. Skopiuj `.env.example` do `.env`. +2. Edytuj `.env`, aby podać adres URL do węzła sieci głównej Ethereum. +3. Uruchom `pnpm install`, aby zainstalować niezbędne pakiety. +4. Uruchom `pnpm susApproval`, aby wyszukać podejrzane zatwierdzenia. + +Oto wyjaśnienie linijka po linijce: + +```typescript +import { + Address, + TransactionReceipt, + createPublicClient, + http, + parseAbiItem, +} from "viem" +import { mainnet } from "viem/chains" +``` + +Importuj definicje typów, funkcje i definicję łańcucha z `viem`. + +```typescript +import { config } from "dotenv" +config() +``` + +Odczytaj `.env`, aby uzyskać adres URL. + +```typescript +const client = createPublicClient({ + chain: mainnet, + transport: http(process.env.URL), +}) +``` + +Utwórz klienta Viem. Musimy tylko odczytywać z blockchainu, więc ten klient nie potrzebuje klucza prywatnego. + +```typescript +const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82" +const fromBlock = 16859812n +const toBlock = 16873372n +``` + +Adres podejrzanego kontraktu ERC-20 i bloki, w których będziemy szukać zdarzeń. Dostawcy węzłów zazwyczaj ograniczają naszą zdolność do odczytywania zdarzeń, ponieważ przepustowość może być kosztowna. Na szczęście `wARB` nie był używany przez osiemnaście godzin, więc możemy szukać wszystkich zdarzeń (w sumie było ich tylko 13). + +```typescript +const approvalEvents = await client.getLogs({ + address: testedAddress, + fromBlock, + toBlock, + event: parseAbiItem( + "event Approval(address indexed _owner, address indexed _spender, uint256 _value)" + ), +}) +``` + +W ten sposób prosi się Viem o informacje o zdarzeniach. Gdy podamy mu dokładną sygnaturę zdarzenia, w tym nazwy pól, parser przetwarza dla nas zdarzenie. + +```typescript +const isContract = async (addr: Address): boolean => + await client.getBytecode({ address: addr }) +``` + +Nasz algorytm ma zastosowanie tylko do kont zarządzanych zewnętrznie. Jeśli `client.getBytecode` zwróci jakikolwiek kod bajtowy, oznacza to, że jest to kontrakt i powinniśmy go po prostu pominąć. + +Jeśli nie używałeś wcześniej TypeScript, definicja funkcji może wyglądać nieco dziwnie. Nie tylko mówimy mu, że pierwszy (i jedyny) parametr nazywa się `addr`, ale także, że jest typu `Address`. Podobnie, część `: boolean` mówi TypeScript, że wartość zwracana przez funkcję jest typu boolean. + +```typescript +const getEventTxn = async (ev: Event): TransactionReceipt => + await client.getTransactionReceipt({ hash: ev.transactionHash }) +``` + +Ta funkcja pobiera potwierdzenie transakcji ze zdarzenia. Potrzebujemy potwierdzenia, aby upewnić się, że znamy miejsce docelowe transakcji. + +```typescript +const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => { +``` + +To jest najważniejsza funkcja, ta, która faktycznie decyduje, czy zdarzenie jest podejrzane, czy nie. Typ zwrotny, `(Event | null)`, informuje TypeScript, że ta funkcja może zwrócić `Event` lub `null`. Zwracamy `null`, jeśli zdarzenie nie jest podejrzane. + +```typescript +const owner = ev.args._owner +``` + +Viem ma nazwy pól, więc przetworzył dla nas zdarzenie. `_owner` jest właścicielem tokenów do wydania. + +```typescript +// Zatwierdzenia przez kontrakty nie są podejrzane +if (await isContract(owner)) return null +``` + +Jeśli właścicielem jest kontrakt, załóż, że to zatwierdzenie nie jest podejrzane. Aby sprawdzić, czy zatwierdzenie kontraktu jest podejrzane, czy nie, musimy prześledzić pełne wykonanie transakcji, aby zobaczyć, czy kiedykolwiek dotarła ona do kontraktu właściciela i czy ten kontrakt wywołał bezpośrednio kontrakt ERC-20. Jest to znacznie bardziej zasobochłonne, niż chcielibyśmy. + +```typescript +const txn = await getEventTxn(ev) +``` + +Jeśli zatwierdzenie pochodzi z konta zarządzanego zewnętrznie, pobierz transakcję, która je spowodowała. + +```typescript +// Zatwierdzenie jest podejrzane, jeśli pochodzi od właściciela EOA, który nie jest `from` transakcji +if (owner.toLowerCase() != txn.from.toLowerCase()) return ev +``` + +Nie możemy po prostu sprawdzić równości ciągów znaków, ponieważ adresy są szesnastkowe, więc zawierają litery. Czasami, na przykład w `txn.from`, wszystkie te litery są małe. W innych przypadkach, takich jak `ev.args._owner`, adres ma [mieszaną wielkość liter do identyfikacji błędów](https://eips.ethereum.org/EIPS/eip-55). + +Ale jeśli transakcja nie pochodzi od właściciela, a ten właściciel jest zarządzany zewnętrznie, to mamy podejrzaną transakcję. + +```typescript +// Jest to również podejrzane, jeśli miejscem docelowym transakcji nie jest badany przez nas +// kontrakt ERC-20 +if (txn.to.toLowerCase() != testedAddress) return ev +``` + +Podobnie, jeśli adres `to` transakcji, czyli pierwszy wywołany kontrakt, nie jest badanym kontraktem ERC-20, jest to podejrzane. + +```typescript + // Jeśli nie ma powodu do podejrzeń, zwróć null. + return null +} +``` + +Jeśli żaden z warunków nie jest prawdziwy, zdarzenie `Approval` nie jest podejrzane. + +```typescript +const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev)) +const testResults = (await Promise.all(testPromises)).filter((x) => x != null) + +console.log(testResults) +``` + +[Funkcja `async`](https://www.w3schools.com/js/js_async.asp) zwraca obiekt `Promise`. Przy użyciu popularnej składni `await x()` czekamy na spełnienie `Promise` przed kontynuowaniem przetwarzania. Jest to proste do zaprogramowania i śledzenia, ale jest również nieefektywne. Podczas gdy czekamy na spełnienie `Promise` dla określonego zdarzenia, możemy już pracować nad następnym zdarzeniem. + +Tutaj używamy [`map`](https://www.w3schools.com/jsref/jsref_map.asp), aby utworzyć tablicę obiektów `Promise`. Następnie używamy [`Promise.all`](https://www.javascripttutorial.net/es6/javascript-promise-all/), aby poczekać na rozwiązanie wszystkich tych obietnic. Następnie [`filtrujemy`](https://www.w3schools.com/jsref/jsref_filter.asp) te wyniki, aby usunąć niepodejrzane zdarzenia. + +### Podejrzane zdarzenia `Transfer` {#suspicious-transfer-events} + +Innym możliwym sposobem identyfikacji scamerskich tokenów jest sprawdzenie, czy mają jakieś podejrzane transfery. Na przykład transfery z kont, które nie mają tak wielu tokenów. Możesz zobaczyć, [jak zaimplementować ten test](https://github.com/qbzzt/20230915-scam-token-detection/blob/main/susTransfer.ts), ale `wARB` nie ma tego problemu. + +## Wnioski {#conclusion} + +Automatyczne wykrywanie oszustw ERC-20 cierpi z powodu [fałszywie negatywnych wyników](https://en.wikipedia.org/wiki/False_positives_and_false_negatives#False_negative_error), ponieważ oszustwo może wykorzystywać całkowicie normalny kontrakt tokena ERC-20, który po prostu nie reprezentuje niczego prawdziwego. Dlatego zawsze powinieneś próbować _uzyskać adres tokena z zaufanego źródła_. + +Automatyczne wykrywanie może pomóc w niektórych przypadkach, takich jak elementy DeFi, gdzie jest wiele tokenów i muszą być one obsługiwane automatycznie. Ale jak zawsze [caveat emptor](https://www.investopedia.com/terms/c/caveatemptor.asp), przeprowadzaj własne badania i zachęcaj swoich użytkowników do tego samego. + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/secret-state/index.md b/public/content/translations/pl/developers/tutorials/secret-state/index.md new file mode 100644 index 00000000000..8519a6d5990 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/secret-state/index.md @@ -0,0 +1,741 @@ +--- +title: "Użycie technologii zerowej wiedzy dla tajnego stanu" +description: "Gry onchain są ograniczone, ponieważ nie mogą przechowywać żadnych ukrytych informacji. Po przeczytaniu tego samouczka czytelnik będzie w stanie połączyć dowody zerowej wiedzy i komponenty serwera w celu tworzenia weryfikowalnych gier z tajnym stanem, komponentem offchain. Technika ta zostanie zademonstrowana poprzez stworzenie gry w sapera." +author: Ori Pomerantz +tags: + [ + "serwer", + "offchain", + "scentralizowane", + "zerowej-wiedzy", + "zokrates", + "mud" + ] +skill: advanced +lang: pl +published: 2025-03-15 +--- + +_W blockchainie nie ma tajemnic_. Wszystko, co jest publikowane na blockchainie, jest otwarte dla każdego do odczytu. Jest to konieczne, ponieważ blockchain opiera się na tym, że każdy może go zweryfikować. Jednak gry często opierają się na tajnym stanie. Na przykład gra w [sapera](https://en.wikipedia.org/wiki/Minesweeper_\(video_game\)) nie ma absolutnie żadnego sensu, jeśli można po prostu wejść do eksploratora bloków i zobaczyć mapę. + +Najprostszym rozwiązaniem jest użycie [komponentu serwera](/developers/tutorials/server-components/) do przechowywania tajnego stanu. Jednak powodem, dla którego używamy blockchain, jest zapobieganie oszustwom ze strony dewelopera gry. Musimy zapewnić uczciwość komponentu serwera. Serwer może dostarczyć hasz stanu i użyć [dowodów zerowej wiedzy](/zero-knowledge-proofs/#why-zero-knowledge-proofs-are-important), aby udowodnić, że stan użyty do obliczenia wyniku ruchu jest prawidłowy. + +Po przeczytaniu tego artykułu dowiesz się, jak stworzyć tego rodzaju serwer przechowujący tajny stan, klienta do pokazywania stanu oraz komponent onchain do komunikacji między nimi. Głównymi narzędziami, których użyjemy, będą: + +| Narzędzie | Cel | Zweryfikowano w wersji | +| --------------------------------------------- | ------------------------------------------------------ | --------------------------------------: | +| [Zokrates](https://zokrates.github.io/) | Dowody zerowej wiedzy i ich weryfikacja | 1.1.9 | +| [Typescript](https://www.typescriptlang.org/) | Język programowania zarówno dla serwera, jak i klienta | 5.4.2 | +| [Node](https://nodejs.org/en) | Uruchamianie serwera | 20.18.2 | +| [Viem](https://viem.sh/) | Komunikacja z blockchainem | 2.9.20 | +| [MUD](https://mud.dev/) | Zarządzanie danymi onchain | 2.0.12 | +| [React](https://react.dev/) | Interfejs użytkownika klienta | 18.2.0 | +| [Vite](https://vitejs.dev/) | Serwowanie kodu klienta | 4.2.1 | + +## Przykład sapera {#minesweeper} + +[Saper](https://en.wikipedia.org/wiki/Minesweeper_\(video_game\)) to gra, która zawiera tajną mapę z polem minowym. Gracz wybiera kopanie w określonym miejscu. Jeśli w tym miejscu jest mina, gra się kończy. W przeciwnym razie gracz otrzymuje liczbę min na ośmiu polach otaczających to miejsce. + +Ta aplikacja jest napisana przy użyciu [MUD](https://mud.dev/), frameworka, który pozwala nam przechowywać dane onchain za pomocą [bazy danych klucz-wartość](https://aws.amazon.com/nosql/key-value/) i automatycznie synchronizować te dane z komponentami offchain. Oprócz synchronizacji MUD ułatwia zapewnienie kontroli dostępu, a innym użytkownikom pozwala na [rozszerzanie](https://mud.dev/guides/extending-a-world) naszej aplikacji bez potrzeby uzyskiwania zgody. + +### Uruchamianie przykładu sapera {#running-minesweeper-example} + +Aby uruchomić przykład sapera: + +1. Upewnij się, że masz [zainstalowane wymagania wstępne](https://mud.dev/quickstart#prerequisites): [Node](https://mud.dev/quickstart#prerequisites), [Foundry](https://book.getfoundry.sh/getting-started/installation), [`git`](https://git-scm.com/downloads), [`pnpm`](https://git-scm.com/downloads) i [`mprocs`](https://github.com/pvolok/mprocs). + +2. Sklonuj repozytorium. + + ```sh copy + git clone https://github.com/qbzzt/20240901-secret-state.git + ``` + +3. Zainstaluj pakiety. + + ```sh copy + cd 20240901-secret-state/ + pnpm install + npm install -g mprocs + ``` + + Jeśli Foundry zostało zainstalowane jako część `pnpm install`, musisz ponownie uruchomić powłokę wiersza poleceń. + +4. Skompiluj kontrakty + + ```sh copy + cd packages/contracts + forge build + cd ../.. + ``` + +5. Uruchom program (w tym blockchain [anvil](https://book.getfoundry.sh/anvil/)) i poczekaj. + + ```sh copy + mprocs + ``` + + Zauważ, że uruchamianie trwa długo. Aby zobaczyć postęp, najpierw użyj strzałki w dół, aby przewinąć do zakładki _kontrakty_, aby zobaczyć wdrażane kontrakty MUD. Kiedy otrzymasz komunikat _Waiting for file changes…_, kontrakty są wdrożone, a dalszy postęp będzie widoczny w zakładce _server_. Tam poczekaj, aż otrzymasz komunikat _Verifier address: 0x...._. + + Jeśli ten krok się powiedzie, zobaczysz ekran `mprocs` z różnymi procesami po lewej stronie i wyjściem konsoli dla aktualnie wybranego procesu po prawej. + + ![Ekran mprocs](./mprocs.png) + + Jeśli wystąpi problem z `mprocs`, możesz uruchomić cztery procesy ręcznie, każdy w osobnym oknie wiersza poleceń: + + - **Anvil** + + ```sh + cd packages/contracts + anvil --base-fee 0 --block-time 2 + ``` + + - **Kontrakty** + + ```sh + cd packages/contracts + pnpm mud dev-contracts --rpc http://127.0.0.1:8545 + ``` + + - **Serwer** + + ```sh + cd packages/server + pnpm start + ``` + + - **Klient** + + ```sh + cd packages/client + pnpm run dev + ``` + +6. Teraz możesz przejść do [klienta](http://localhost:3000), kliknąć **New Game** i zacząć grać. + +### Tabele {#tables} + +Potrzebujemy [kilku tabel](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/mud.config.ts) onchain. + +- `Configuration`: Ta tabela jest singletonem, nie ma klucza i ma jeden rekord. Służy do przechowywania informacji o konfiguracji gry: + - `height`: Wysokość pola minowego + - `width`: Szerokość pola minowego + - `numberOfBombs`: Liczba bomb na każdym polu minowym + +- `VerifierAddress`: Ta tabela jest również singletonem. Służy do przechowywania jednej części konfiguracji, adresu kontraktu weryfikatora (`verifier`). Mogliśmy umieścić te informacje w tabeli `Configuration`, ale są one ustawiane przez inny komponent, serwer, więc łatwiej jest umieścić je w osobnej tabeli. + +- `PlayerGame`: Kluczem jest adres gracza. Dane to: + + - `gameId`: 32-bajtowa wartość, która jest haszem mapy, na której gra gracz (identyfikator gry). + - `win`: wartość logiczna określająca, czy gracz wygrał grę. + - `lose`: wartość logiczna określająca, czy gracz przegrał grę. + - `digNumber`: liczba udanych odkryć w grze. + +- `GamePlayer`: Ta tabela przechowuje odwrotne mapowanie, z `gameId` na adres gracza. + +- `Map`: Kluczem jest krotka trzech wartości: + + - `gameId`: 32-bajtowa wartość, która jest haszem mapy, na której gra gracz (identyfikator gry). + - współrzędna `x` + - współrzędna `y` + + Wartością jest pojedyncza liczba. To 255, jeśli wykryto bombę. W przeciwnym razie jest to liczba bomb wokół tego miejsca plus jeden. Nie możemy po prostu użyć liczby bomb, ponieważ domyślnie cała pamięć w EVM i wszystkie wartości wierszy w MUD są zerowe. Musimy rozróżnić między „gracz jeszcze tutaj nie kopał” a „gracz tu kopał i odkrył, że wokół nie ma bomb”. + +Dodatkowo komunikacja między klientem a serwerem odbywa się za pośrednictwem komponentu onchain. Jest to również zaimplementowane przy użyciu tabel. + +- `PendingGame`: Nieobsłużone żądania rozpoczęcia nowej gry. +- `PendingDig`: Nieobsłużone żądania kopania w określonym miejscu w określonej grze. Jest to [tabela offchain](https://mud.dev/store/tables#types-of-tables), co oznacza, że nie jest zapisywana w pamięci EVM, jest czytelna tylko offchain za pomocą zdarzeń. + +### Przepływy wykonania i danych {#execution-data-flows} + +Te przepływy koordynują wykonanie między klientem, komponentem onchain i serwerem. + +#### Inicjalizacja {#initialization-flow} + +Po uruchomieniu `mprocs` następują następujące kroki: + +1. [`mprocs`](https://github.com/pvolok/mprocs) uruchamia cztery komponenty: + + - [Anvil](https://book.getfoundry.sh/anvil/), który uruchamia lokalny blockchain + - [Contracts](https://github.com/qbzzt/20240901-secret-state/tree/main/packages/contracts), który kompiluje (w razie potrzeby) i wdraża kontrakty dla MUD + - [Client](https://github.com/qbzzt/20240901-secret-state/tree/main/packages/client), który uruchamia [Vite](https://vitejs.dev/), aby serwować interfejs użytkownika i kod klienta do przeglądarek internetowych. + - [Server](https://github.com/qbzzt/20240901-secret-state/tree/main/packages/server), który wykonuje działania serwera + +2. Pakiet `contracts` wdraża kontrakty MUD, a następnie uruchamia [skrypt `PostDeploy.s.sol`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/script/PostDeploy.s.sol). Ten skrypt ustawia konfigurację. Kod z GitHub określa [pole minowe o wymiarach 10x5 z ośmioma minami](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/script/PostDeploy.s.sol#L23). + +3. [Serwer](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts) rozpoczyna od [skonfigurowania MUD](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L6). Między innymi aktywuje to synchronizację danych, dzięki czemu kopia odpowiednich tabel istnieje w pamięci serwera. + +4. Serwer subskrybuje funkcję, która ma zostać wykonana, [gdy tabela `Configuration` ulegnie zmianie](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L23). [Ta funkcja](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L24-L168) jest wywoływana po wykonaniu `PostDeploy.s.sol` i zmodyfikowaniu tabeli. + +5. Gdy funkcja inicjalizacyjna serwera ma konfigurację, [wywołuje `zkFunctions`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L34-L35), aby zainicjować [część serwera opartą na technologii zerowej wiedzy](#using-zokrates-from-typescript). Nie może się to zdarzyć, dopóki nie uzyskamy konfiguracji, ponieważ funkcje zerowej wiedzy muszą mieć szerokość i wysokość pola minowego jako stałe. + +6. Po zainicjowaniu części serwera opartej na technologii zerowej wiedzy następnym krokiem jest [wdrożenie kontraktu weryfikacji zerowej wiedzy na blockchain](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L42-L53) i ustawienie adresu weryfikowanego w MUD. + +7. Na koniec subskrybujemy aktualizacje, aby zobaczyć, kiedy gracz zażąda [rozpoczęcia nowej gry](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L55-L71) lub [kopania w istniejącej grze](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L73-L108). + +#### Nowa gra {#new-game-flow} + +Oto co się dzieje, gdy gracz zażąda nowej gry. + +1. Jeśli dla tego gracza nie ma żadnej gry w toku lub jest jedna, ale z gameId równym zero, klient wyświetla [przycisk nowej gry](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L175). Gdy użytkownik naciśnie ten przycisk, [React uruchamia funkcję `newGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L96). + +2. [`newGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/mud/createSystemCalls.ts#L43-L46) to wywołanie `System`. W MUD wszystkie wywołania są kierowane przez kontrakt `World` i w większości przypadków wywołujesz `__`. W tym przypadku wywołanie jest skierowane do `app__newGame`, które MUD następnie kieruje do [`newGame` w `GameSystem`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/GameSystem.sol#L16-L22). + +3. Funkcja onchain sprawdza, czy gracz nie ma gry w toku, a jeśli nie, [dodaje żądanie do tabeli `PendingGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/GameSystem.sol#L21). + +4. Serwer wykrywa zmianę w `PendingGame` i [uruchamia subskrybowaną funkcję](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L55-L71). Ta funkcja wywołuje [`newGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L110-L114), która z kolei wywołuje [`createGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L116-L144). + +5. Pierwszą rzeczą, jaką robi `createGame`, jest [stworzenie losowej mapy z odpowiednią liczbą min](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L120-L135). Następnie wywołuje [`makeMapBorders`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L147-L166), aby utworzyć mapę z pustymi obramowaniami, co jest konieczne dla Zokrates. Na koniec `createGame` wywołuje [`calculateMapHash`](#calculateMapHash), aby uzyskać hasz mapy, który jest używany jako ID gry. + +6. Funkcja `newGame` dodaje nową grę do `gamesInProgress`. + +7. Ostatnią rzeczą, jaką robi serwer, jest wywołanie [`app__newGameResponse`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L38-L43), które jest onchain. Ta funkcja znajduje się w innym `System`, [`ServerSystem`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol), aby umożliwić kontrolę dostępu. Kontrola dostępu jest zdefiniowana w [pliku konfiguracyjnym MUD](https://mud.dev/config), [`mud.config.ts`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/mud.config.ts#L67-L72). + + Lista dostępu pozwala tylko jednemu adresowi na wywołanie `System`. Ogranicza to dostęp do funkcji serwera do jednego adresu, więc nikt nie może podszyć się pod serwer. + +8. Komponent onchain aktualizuje odpowiednie tabele: + + - Utwórz grę w `PlayerGame`. + - Ustaw odwrotne mapowanie w `GamePlayer`. + - Usuń żądanie z `PendingGame`. + +9. Serwer identyfikuje zmianę w `PendingGame`, ale nic nie robi, ponieważ [`wantsGame`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L58-L60) jest fałszywe. + +10. Na kliencie [`gameRecord`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L143-L148) jest ustawiony na wpis `PlayerGame` dla adresu gracza. Gdy `PlayerGame` się zmienia, `gameRecord` również się zmienia. + +11. Jeśli w `gameRecord` jest wartość, a gra nie została wygrana ani przegrana, klient [wyświetla mapę](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L175-L190). + +#### Kopanie {#dig-flow} + +1. Gracz [klika przycisk komórki mapy](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L188), co wywołuje [funkcję `dig`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/mud/createSystemCalls.ts#L33-L36). Ta funkcja wywołuje [`dig` onchain](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/GameSystem.sol#L24-L32). + +2. Komponent onchain [wykonuje szereg testów poprawności](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/GameSystem.sol#L25-L30), a jeśli się powiedzie, dodaje żądanie kopania do [`PendingDig`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/GameSystem.sol#L31). + +3. Serwer [wykrywa zmianę w `PendingDig`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L73). [Jeśli jest prawidłowe](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L75-L84), [wywołuje kod zerowej wiedzy](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L86-L95) (wyjaśniony poniżej), aby wygenerować zarówno wynik, jak i dowód jego poprawności. + +4. [Serwer](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L97-L107) wywołuje [`digResponse`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L45-L64) onchain. + +5. `digResponse` robi dwie rzeczy. Po pierwsze, sprawdza [dowód zerowej wiedzy](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L47-L61). Następnie, jeśli dowód jest poprawny, wywołuje [`processDigResult`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L67-L86), aby faktycznie przetworzyć wynik. + +6. `processDigResult` sprawdza, czy gra została [przegrana](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L76-L78) lub [wygrana](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L83-L86) i [aktualizuje `Map`, mapę onchain](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol#L80). + +7. Klient automatycznie pobiera aktualizacje i [aktualizuje mapę wyświetlaną graczowi](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/client/src/App.tsx#L175-L190), a w stosownych przypadkach informuje gracza, czy jest to wygrana, czy przegrana. + +## Korzystanie z Zokrates {#using-zokrates} + +W przepływach wyjaśnionych powyżej pominęliśmy części dotyczące zerowej wiedzy, traktując je jak czarną skrzynkę. Teraz otwórzmy ją i zobaczmy, jak ten kod jest napisany. + +### Haszowanie mapy {#hashing-map} + +Możemy użyć [tego kodu JavaScript](https://github.com/ZK-Plus/ICBC24_Tutorial_Compute-Offchain-Verify-onchain/tree/solutions/exercise), aby zaimplementować [Poseidon](https://www.poseidon-hash.info), funkcję haszującą Zokrates, której używamy. Jednakże, chociaż byłoby to szybsze, byłoby to również bardziej skomplikowane niż tylko użycie funkcji haszującej Zokrates do tego celu. To jest samouczek, więc kod jest zoptymalizowany pod kątem prostoty, a nie wydajności. Dlatego potrzebujemy dwóch różnych programów Zokrates, jednego do samego obliczenia haszu mapy (`hash`) i drugiego do faktycznego stworzenia dowodu zerowej wiedzy wyniku kopania w danym miejscu na mapie (`dig`). + +### Funkcja haszująca {#hash-function} + +Jest to funkcja, która oblicza hasz mapy. Przejdziemy przez ten kod linia po linii. + +``` +import "hashes/poseidon/poseidon.zok" as poseidon; +import "utils/pack/bool/pack128.zok" as pack128; +``` + +Te dwie linie importują dwie funkcje z [biblioteki standardowej Zokrates](https://zokrates.github.io/toolbox/stdlib.html). [Pierwsza funkcja](https://github.com/Zokrates/ZoKrates/blob/latest/zokrates_stdlib/stdlib/hashes/poseidon/poseidon.zok) to [hasz Poseidon](https://www.poseidon-hash.info/). Przyjmuje tablicę elementów [`field`](https://zokrates.github.io/language/types.html#field) i zwraca `field`. + +Element pola w Zokrates ma zwykle mniej niż 256 bitów, ale niewiele. Aby uprościć kod, ograniczamy mapę do 512 bitów i haszujemy tablicę czterech pól, a w każdym polu używamy tylko 128 bitów. [Funkcja `pack128`](https://github.com/Zokrates/ZoKrates/blob/latest/zokrates_stdlib/stdlib/utils/pack/bool/pack128.zok) zmienia w tym celu tablicę 128 bitów na `field`. + +``` + def hashMap(bool[${width+2}][${height+2}] map) -> field { +``` + +Ta linia rozpoczyna definicję funkcji. `hashMap` otrzymuje jeden parametr o nazwie `map`, dwuwymiarową tablicę `bool`(ean). Rozmiar mapy to `width+2` na `height+2` z powodów, które są [wyjaśnione poniżej](#why-map-border). + +Możemy użyć `${width+2}` i `${height+2}`, ponieważ programy Zokrates są przechowywane w tej aplikacji jako [ciągi szablonów](https://www.w3schools.com/js/js_string_templates.asp). Kod między `${` a `}` jest oceniany przez JavaScript, dzięki czemu program może być używany dla różnych rozmiarów mapy. Parametr mapy ma wokół siebie obramowanie o szerokości jednego miejsca bez żadnych bomb, co jest powodem, dla którego musimy dodać dwa do szerokości i wysokości. + +Zwracana wartość to `field`, który zawiera hasz. + +``` + bool[512] mut map1d = [false; 512]; +``` + +Mapa jest dwuwymiarowa. Jednak funkcja `pack128` nie działa z tablicami dwuwymiarowymi. Dlatego najpierw spłaszczamy mapę do 512-bajtowej tablicy, używając `map1d`. Domyślnie zmienne Zokrates są stałymi, ale musimy przypisać wartości do tej tablicy w pętli, więc definiujemy ją jako [`mut`](https://zokrates.github.io/language/variables.html#mutability). + +Musimy zainicjować tablicę, ponieważ Zokrates nie ma `undefined`. Wyrażenie `[false; 512]` oznacza [tablicę 512 wartości `false`](https://zokrates.github.io/language/types.html#declaration-and-initialization). + +``` + u32 mut counter = 0; +``` + +Potrzebujemy również licznika, aby odróżnić bity, które już wypełniliśmy w `map1d`, od tych, których jeszcze nie wypełniliśmy. + +``` + for u32 x in 0..${width+2} { +``` + +W ten sposób deklaruje się [pętlę `for`](https://zokrates.github.io/language/control_flow.html#for-loops) w Zokrates. Pętla `for` w Zokrates musi mieć stałe granice, ponieważ chociaż wydaje się być pętlą, kompilator faktycznie ją „rozwija”. Wyrażenie `${width+2}` jest stałą czasu kompilacji, ponieważ `width` jest ustawiane przez kod TypeScript przed wywołaniem kompilatora. + +``` + for u32 y in 0..${height+2} { + map1d[counter] = map[x][y]; + counter = counter+1; + } + } +``` + +Dla każdego miejsca na mapie umieść tę wartość w tablicy `map1d` i zwiększ licznik. + +``` + field[4] hashMe = [ + pack128(map1d[0..128]), + pack128(map1d[128..256]), + pack128(map1d[256..384]), + pack128(map1d[384..512]) + ]; +``` + +Użyj `pack128`, aby utworzyć tablicę czterech wartości `field` z `map1d`. W Zokrates `array[a..b]` oznacza wycinek tablicy, który zaczyna się od `a` i kończy na `b-1`. + +``` + return poseidon(hashMe); +} +``` + +Użyj `poseidon`, aby przekonwertować tę tablicę na hasz. + +### Program haszujący {#hash-program} + +Serwer musi bezpośrednio wywołać `hashMap`, aby utworzyć identyfikatory gry. Jednakże Zokrates może wywołać tylko funkcję `main` w programie, aby rozpocząć, więc tworzymy program z funkcją `main`, która wywołuje funkcję haszującą. + +``` +${hashFragment} + +def main(bool[${width+2}][${height+2}] map) -> field { + return hashMap(map); +} +``` + +### Program do kopania {#dig-program} + +To jest serce części aplikacji opartej na zerowej wiedzy, gdzie tworzymy dowody używane do weryfikacji wyników kopania. + +``` +${hashFragment} + +// The number of mines in location (x,y) +def map2mineCount(bool[${width+2}][${height+2}] map, u32 x, u32 y) -> u8 { + return if map[x+1][y+1] { 1 } else { 0 }; +} +``` + +#### Dlaczego obramowanie mapy {#why-map-border} + +Dowody zerowej wiedzy używają [obwodów arytmetycznych](https://medium.com/web3studio/simple-explanations-of-arithmetic-circuits-and-zero-knowledge-proofs-806e59a79785), które nie mają łatwego odpowiednika instrukcji `if`. Zamiast tego używają odpowiednika [operatora warunkowego](https://en.wikipedia.org/wiki/Ternary_conditional_operator). Jeśli `a` może być zerem lub jedynką, możesz obliczyć `if a { b } else { c }` jako `ab+(1-a)c`. + +Z tego powodu instrukcja `if` w Zokrates zawsze ocenia obie gałęzie. Na przykład, jeśli masz taki kod: + +``` +bool[5] arr = [false; 5]; +u32 index=10; +return if index>4 { 0 } else { arr[index] } +``` + +Spowoduje to błąd, ponieważ musi obliczyć `arr[10]`, mimo że ta wartość zostanie później pomnożona przez zero. + +To jest powód, dla którego potrzebujemy obramowania o szerokości jednego miejsca wokół całej mapy. Musimy obliczyć całkowitą liczbę min wokół danego miejsca, a to oznacza, że musimy widzieć miejsce o jeden wiersz powyżej i poniżej, na lewo i na prawo od miejsca, w którym kopiemy. Oznacza to, że te miejsca muszą istnieć w tablicy mapy, która jest dostarczana do Zokrates. + +``` +def main(private bool[${width+2}][${height+2}] map, u32 x, u32 y) -> (field, u8) { +``` + +Domyślnie dowody Zokrates zawierają swoje dane wejściowe. Nie ma sensu wiedzieć, że wokół danego miejsca jest pięć min, jeśli faktycznie nie wiesz, które to miejsce (i nie możesz po prostu dopasować go do swojego żądania, ponieważ wtedy dowodzący mógłby użyć innych wartości i ci o tym nie powiedzieć). Jednak musimy zachować mapę w tajemnicy, jednocześnie dostarczając ją do Zokrates. Rozwiązaniem jest użycie parametru `private`, który _nie_ jest ujawniany przez dowód. + +Otwiera to kolejne pole do nadużyć. Dowodzący mógłby użyć poprawnych współrzędnych, ale stworzyć mapę z dowolną liczbą min wokół miejsca, a być może i w samym miejscu. Aby zapobiec temu nadużyciu, sprawiamy, że dowód zerowej wiedzy zawiera hasz mapy, który jest identyfikatorem gry. + +``` + return (hashMap(map), +``` + +Zwracana wartość jest tu krotką, która zawiera zarówno hasz mapy, jak i wynik kopania. + +``` + if map2mineCount(map, x, y) > 0 { 0xFF } else { +``` + +Używamy 255 jako specjalnej wartości w przypadku, gdy samo miejsce zawiera bombę. + +``` + map2mineCount(map, x-1, y-1) + map2mineCount(map, x, y-1) + map2mineCount(map, x+1, y-1) + + map2mineCount(map, x-1, y) + map2mineCount(map, x+1, y) + + map2mineCount(map, x-1, y+1) + map2mineCount(map, x, y+1) + map2mineCount(map, x+1, y+1) + } + ); +} +``` + +Jeśli gracz nie trafił na minę, dodaj liczby min z obszaru wokół miejsca i zwróć tę wartość. + +### Używanie Zokrates z TypeScript {#using-zokrates-from-typescript} + +Zokrates ma interfejs wiersza poleceń, ale w tym programie używamy go w [kodzie TypeScript](https://zokrates.github.io/toolbox/zokrates_js.html). + +Biblioteka zawierająca definicje Zokrates nazywa się [`zero-knowledge.ts`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts). + +```typescript +import { initialize as zokratesInitialize } from "zokrates-js" +``` + +Importuj [powiązania JavaScript Zokrates](https://zokrates.github.io/toolbox/zokrates_js.html). Potrzebujemy tylko funkcji [`initialize`](https://zokrates.github.io/toolbox/zokrates_js.html#initialize), ponieważ zwraca ona obietnicę, która rozwiązuje się do wszystkich definicji Zokrates. + +```typescript +export const zkFunctions = async (width: number, height: number) : Promise => { +``` + +Podobnie jak sam Zokrates, my również eksportujemy tylko jedną funkcję, która jest również [asynchroniczna](https://www.w3schools.com/js/js_async.asp). Kiedy w końcu zwróci wartość, dostarczy kilka funkcji, jak zobaczymy poniżej. + +```typescript +const zokrates = await zokratesInitialize() +``` + +Zainicjuj Zokrates, pobierz wszystko, czego potrzebujemy z biblioteki. + +```typescript +const hashFragment = ` + import "utils/pack/bool/pack128.zok" as pack128; + import "hashes/poseidon/poseidon.zok" as poseidon; + . + . + . + } + ` + +const hashProgram = ` + ${hashFragment} + . + . + . + ` + +const digProgram = ` + ${hashFragment} + . + . + . + ` +``` + +Następnie mamy funkcję haszującą i dwa programy Zokrates, które widzieliśmy powyżej. + +```typescript +const digCompiled = zokrates.compile(digProgram) +const hashCompiled = zokrates.compile(hashProgram) +``` + +Tutaj kompilujemy te programy. + +```typescript +// Create the keys for zero knowledge verification. +// On a production system you'd want to use a setup ceremony. +// (https://zokrates.github.io/toolbox/trusted_setup.html#initializing-a-phase-2-ceremony). +const keySetupResults = zokrates.setup(digCompiled.program, "") +const verifierKey = keySetupResults.vk +const proverKey = keySetupResults.pk +``` + +W systemie produkcyjnym moglibyśmy użyć bardziej skomplikowanej [ceremonii konfiguracji](https://zokrates.github.io/toolbox/trusted_setup.html#initializing-a-phase-2-ceremony), ale to jest wystarczające do demonstracji. Nie jest problemem, że użytkownicy mogą znać klucz dowodzącego - nadal nie mogą go użyć do udowodnienia rzeczy, chyba że są one prawdziwe. Ponieważ określamy entropię (drugi parametr, `""`), wyniki zawsze będą takie same. + +**Uwaga:** Kompilacja programów Zokrates i tworzenie kluczy to powolne procesy. Nie ma potrzeby ich powtarzać za każdym razem, tylko gdy zmienia się rozmiar mapy. W systemie produkcyjnym zrobisz to raz, a następnie zapiszesz wynik. Jedynym powodem, dla którego tego tu nie robię, jest prostota. + +#### `calculateMapHash` {#calculateMapHash} + +```typescript +const calculateMapHash = function (hashMe: boolean[][]): string { + return ( + "0x" + + BigInt(zokrates.computeWitness(hashCompiled, [hashMe]).output.slice(1, -1)) + .toString(16) + .padStart(64, "0") + ) +} +``` + +Funkcja [`computeWitness`](https://zokrates.github.io/toolbox/zokrates_js.html#computewitnessartifacts-args-options) faktycznie uruchamia program Zokrates. Zwraca strukturę z dwoma polami: `output`, które jest wynikiem programu jako ciąg JSON, oraz `witness`, które jest informacją potrzebną do stworzenia dowodu zerowej wiedzy wyniku. Tutaj potrzebujemy tylko wyniku. + +Wynik to ciąg znaków w postaci `"31337"`, liczba dziesiętna w cudzysłowach. Ale wynik, którego potrzebujemy dla `viem`, to liczba szesnastkowa w postaci `0x60A7`. Używamy więc `.slice(1,-1)`, aby usunąć cudzysłowy, a następnie `BigInt`, aby uruchomić pozostały ciąg, który jest liczbą dziesiętną, do [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). `.toString(16)` konwertuje ten `BigInt` na ciąg szesnastkowy, a `"0x"+` dodaje znacznik dla liczb szesnastkowych. + +```typescript +// Dig and return a zero knowledge proof of the result +// (server-side code) +``` + +Dowód zerowej wiedzy zawiera publiczne dane wejściowe (`x` i `y`) oraz wyniki (hasz mapy i liczba bomb). + +```typescript + const zkDig = function(map: boolean[][], x: number, y: number) : any { + if (x<0 || x>=width || y<0 || y>=height) + throw new Error("Trying to dig outside the map") +``` + +Sprawdzanie, czy indeks jest poza zakresem, jest problematyczne w Zokrates, więc robimy to tutaj. + +```typescript +const runResults = zokrates.computeWitness(digCompiled, [map, `${x}`, `${y}`]) +``` + +Wykonaj program do kopania. + +```typescript + const proof = zokrates.generateProof( + digCompiled.program, + runResults.witness, + proverKey) + + return proof + } +``` + +Użyj [`generateProof`](https://zokrates.github.io/toolbox/zokrates_js.html#generateproofprogram-witness-provingkey-entropy) i zwróć dowód. + +```typescript +const solidityVerifier = ` + // Map size: ${width} x ${height} + \n${zokrates.exportSolidityVerifier(verifierKey)} + ` +``` + +Weryfikator Solidity, inteligentny kontrakt, który możemy wdrożyć na blockchainie i używać do weryfikacji dowodów generowanych przez `digCompiled.program`. + +```typescript + return { + zkDig, + calculateMapHash, + solidityVerifier, + } +} +``` + +Na koniec zwróć wszystko, czego może potrzebować inny kod. + +## Testy bezpieczeństwa {#security-tests} + +Testy bezpieczeństwa są ważne, ponieważ błąd funkcjonalności w końcu się ujawni. Ale jeśli aplikacja jest niebezpieczna, prawdopodobnie pozostanie to ukryte przez długi czas, zanim zostanie ujawnione przez kogoś, kto oszukuje i ucieka z zasobami należącymi do innych. + +### Uprawnienia {#permissions} + +W tej grze jest jeden uprzywilejowany podmiot, serwer. Jest to jedyny użytkownik, który może wywoływać funkcje w [`ServerSystem`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol). Możemy użyć [`cast`](https://book.getfoundry.sh/cast/), aby zweryfikować, czy wywołania funkcji z uprawnieniami są dozwolone tylko z konta serwera. + +[Klucz prywatny serwera znajduje się w `setupNetwork.ts`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/mud/setupNetwork.ts#L52). + +1. Na komputerze, który uruchamia `anvil` (blockchain), ustaw te zmienne środowiskowe. + + ```sh copy + WORLD_ADDRESS=0x8d8b6b8414e1e3dcfd4168561b9be6bd3bf6ec4b + UNAUTHORIZED_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a + AUTHORIZED_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + ``` + +2. Użyj `cast`, aby spróbować ustawić adres weryfikatora jako nieautoryzowany adres. + + ```sh copy + cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $UNAUTHORIZED_KEY + ``` + + Nie tylko `cast` zgłasza błąd, ale możesz otworzyć **MUD Dev Tools** w grze w przeglądarce, kliknąć **Tables** i wybrać **app\_\_VerifierAddress**. Zobacz, że adres nie jest zerowy. + +3. Ustaw adres weryfikatora jako adres serwera. + + ```sh copy + cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $AUTHORIZED_KEY + ``` + + Adres w **app\_\_VerifiedAddress** powinien być teraz zerowy. + +Wszystkie funkcje MUD w tym samym `System` przechodzą przez tę samą kontrolę dostępu, więc uważam ten test za wystarczający. Jeśli nie, możesz sprawdzić inne funkcje w [`ServerSystem`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/contracts/src/systems/ServerSystem.sol). + +### Nadużycia w technologii zerowej wiedzy {#zero-knowledge-abuses} + +Matematyka do weryfikacji Zokrates wykracza poza zakres tego samouczka (i moich umiejętności). Możemy jednak przeprowadzić różne testy na kodzie zerowej wiedzy, aby zweryfikować, że jeśli nie jest on wykonany poprawnie, to zawodzi. Wszystkie te testy będą wymagały zmiany [`zero-knowledge.ts`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts) i ponownego uruchomienia całej aplikacji. Nie wystarczy ponowne uruchomienie procesu serwera, ponieważ stawia to aplikację w niemożliwym stanie (gracz ma grę w toku, ale gra nie jest już dostępna dla serwera). + +#### Zła odpowiedź {#wrong-answer} + +Najprostszą możliwością jest podanie błędnej odpowiedzi w dowodzie zerowej wiedzy. Aby to zrobić, wchodzimy do `zkDig` i [modyfikujemy linię 91](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L91): + +```ts +proof.inputs[3] = "0x" + "1".padStart(64, "0") +``` + +Oznacza to, że zawsze będziemy twierdzić, że jest jedna bomba, niezależnie od poprawnej odpowiedzi. Spróbuj zagrać w tę wersję, a zobaczysz w zakładce **server** na ekranie `pnpm dev` ten błąd: + +``` + cause: { + code: 3, + message: 'execution reverted: revert: Zero knowledge verification fail', + data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000 +000000000000000000000000000000000000000000000000205a65726f206b6e6f776c6564676520766572696669636174696f6 +e206661696c' + }, +``` + +Więc tego rodzaju oszustwo kończy się niepowodzeniem. + +#### Zły dowód {#wrong-proof} + +Co się stanie, jeśli podamy poprawne informacje, ale będziemy mieli nieprawidłowe dane dowodu? Teraz zamień linię 91 na: + +```ts +proof.proof = { + a: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")], + b: [ + ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")], + ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")], + ], + c: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")], +} +``` + +Nadal zawodzi, ale teraz zawodzi bez podania przyczyny, ponieważ dzieje się to podczas wywołania weryfikatora. + +### Jak użytkownik może zweryfikować kod zerowej wiedzy? {#user-verify-zero-trust} + +Inteligentne kontrakty są stosunkowo łatwe do zweryfikowania. Zazwyczaj deweloper publikuje kod źródłowy w eksploratorze bloków, a eksplorator bloków weryfikuje, czy kod źródłowy kompiluje się do kodu w [transakcji wdrożenia kontraktu](/developers/docs/smart-contracts/deploying/). W przypadku `System`ów MUD jest to [nieco bardziej skomplikowane](https://mud.dev/cli/verify), ale niewiele. + +Jest to trudniejsze w przypadku zerowej wiedzy. Weryfikator zawiera pewne stałe i wykonuje na nich obliczenia. To nie mówi ci, co jest udowadniane. + +```solidity + function verifyingKey() pure internal returns (VerifyingKey memory vk) { + vk.alpha = Pairing.G1Point(uint256(0x0f43f4fe7b5c2326fed4ac6ed2f4003ab9ab4ea6f667c2bdd77afb068617ee16), uint256(0x25a77832283f9726935219b5f4678842cda465631e72dbb24708a97ba5d0ce6f)); + vk.beta = Pairing.G2Point([uint256(0x2cebd0fbd21aca01910581537b21ae4fed46bc0e524c055059aa164ba0a6b62b), uint256(0x18fd4a7bc386cf03a95af7163d5359165acc4e7961cb46519e6d9ee4a1e2b7e9)], [uint256(0x11449dee0199ef6d8eebfe43b548e875c69e7ce37705ee9a00c81fe52f11a009), uint256(0x066d0c83b32800d3f335bb9e8ed5e2924cf00e77e6ec28178592eac9898e1a00)]); +``` + +Rozwiązaniem, przynajmniej do czasu, gdy eksploratory bloków dodadzą weryfikację Zokrates do swoich interfejsów użytkownika, jest udostępnienie przez deweloperów aplikacji programów Zokrates, a przynajmniej niektórzy użytkownicy będą je kompilować samodzielnie z odpowiednim kluczem weryfikacyjnym. + +Aby to zrobić: + +1. [Zainstaluj Zokrates](https://zokrates.github.io/gettingstarted.html). + +2. Utwórz plik `dig.zok` z programem Zokrates. Poniższy kod zakłada, że zachowałeś oryginalny rozmiar mapy, 10x5. + + ```zokrates + import "utils/pack/bool/pack128.zok" as pack128; + import "hashes/poseidon/poseidon.zok" as poseidon; + + def hashMap(bool[12][7] map) -> field { + bool[512] mut map1d = [false; 512]; + u32 mut counter = 0; + + for u32 x in 0..12 { + for u32 y in 0..7 { + map1d[counter] = map[x][y]; + counter = counter+1; + } + } + + field[4] hashMe = [ + pack128(map1d[0..128]), + pack128(map1d[128..256]), + pack128(map1d[256..384]), + pack128(map1d[384..512]) + ]; + + return poseidon(hashMe); + } + + + // The number of mines in location (x,y) + def map2mineCount(bool[12][7] map, u32 x, u32 y) -> u8 { + return if map[x+1][y+1] { 1 } else { 0 }; + } + + def main(private bool[12][7] map, u32 x, u32 y) -> (field, u8) { + return (hashMap(map) , + if map2mineCount(map, x, y) > 0 { 0xFF } else { + map2mineCount(map, x-1, y-1) + map2mineCount(map, x, y-1) + map2mineCount(map, x+1, y-1) + + map2mineCount(map, x-1, y) + map2mineCount(map, x+1, y) + + map2mineCount(map, x-1, y+1) + map2mineCount(map, x, y+1) + map2mineCount(map, x+1, y+1) + } + ); + } + ``` + +3. Skompiluj kod Zokrates i utwórz klucz weryfikacyjny. Klucz weryfikacyjny musi być utworzony z tą samą entropią, która została użyta w oryginalnym serwerze, [w tym przypadku pustym ciągiem znaków](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L67). + + ```sh copy + zokrates compile --input dig.zok + zokrates setup -e "" + ``` + +4. Utwórz weryfikator Solidity samodzielnie i zweryfikuj, czy jest on funkcjonalnie identyczny z tym na blockchainie (serwer dodaje komentarz, ale to nie jest ważne). + + ```sh copy + zokrates export-verifier + diff verifier.sol ~/20240901-secret-state/packages/contracts/src/verifier.sol + ``` + +## Decyzje projektowe {#design} + +W każdej wystarczająco złożonej aplikacji istnieją konkurencyjne cele projektowe, które wymagają kompromisów. Przyjrzyjmy się niektórym kompromisom i dlaczego obecne rozwiązanie jest lepsze od innych opcji. + +### Dlaczego zerowa wiedza {#why-zero-knowledge} + +Do gry w sapera tak naprawdę nie potrzebujesz zerowej wiedzy. Serwer zawsze może przechowywać mapę, a następnie po prostu ujawnić ją całą, gdy gra się skończy. Następnie, na koniec gry, inteligentny kontrakt może obliczyć hasz mapy, zweryfikować, czy się zgadza, a jeśli nie, ukarać serwer lub całkowicie zignorować grę. + +Nie użyłem tego prostszego rozwiązania, ponieważ działa ono tylko w przypadku krótkich gier z dobrze zdefiniowanym stanem końcowym. Gdy gra jest potencjalnie nieskończona (jak w przypadku [autonomicznych światów](https://0xparc.org/blog/autonomous-worlds)), potrzebujesz rozwiązania, które udowadnia stan _bez_ jego ujawniania. + +Jako samouczek ten artykuł potrzebował krótkiej gry, która jest łatwa do zrozumienia, ale ta technika jest najbardziej przydatna w dłuższych grach. + +### Dlaczego Zokrates? {#why-zokrates} + +[Zokrates](https://zokrates.github.io/) nie jest jedyną dostępną biblioteką zerowej wiedzy, ale jest podobny do normalnego, [imperatywnego](https://en.wikipedia.org/wiki/Imperative_programming) języka programowania i obsługuje zmienne logiczne. + +Dla Twojej aplikacji, z innymi wymaganiami, możesz preferować użycie [Circum](https://docs.circom.io/getting-started/installation/) lub [Cairo](https://www.cairo-lang.org/tutorials/getting-started-with-cairo/). + +### Kiedy kompilować Zokrates {#when-compile-zokrates} + +W tym programie kompilujemy programy Zokrates [przy każdym uruchomieniu serwera](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L60-L61). Jest to oczywiste marnotrawstwo zasobów, ale to jest samouczek, zoptymalizowany pod kątem prostoty. + +Gdybym pisał aplikację na poziomie produkcyjnym, sprawdziłbym, czy mam plik ze skompilowanymi programami Zokrates dla tego rozmiaru pola minowego, a jeśli tak, użyłbym go. To samo dotyczy wdrażania kontraktu weryfikatora onchain. + +### Tworzenie kluczy weryfikatora i dowodzącego {#key-creation} + +[Tworzenie kluczy](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L63-L69) to kolejne czyste obliczenie, które nie musi być wykonywane więcej niż raz dla danego rozmiaru pola minowego. Ponownie, robi się to tylko raz dla uproszczenia. + +Dodatkowo moglibyśmy użyć [ceremonii konfiguracji](https://zokrates.github.io/toolbox/trusted_setup.html#initializing-a-phase-2-ceremony). Zaletą ceremonii konfiguracji jest to, że potrzebujesz albo entropii, albo jakiegoś pośredniego wyniku od każdego uczestnika, aby oszukać dowód zerowej wiedzy. Jeśli przynajmniej jeden uczestnik ceremonii jest uczciwy i usunie te informacje, dowody zerowej wiedzy są bezpieczne przed niektórymi atakami. Jednak _nie ma mechanizmu_, aby zweryfikować, czy informacje zostały usunięte zewsząd. Jeśli dowody zerowej wiedzy są krytycznie ważne, chcesz uczestniczyć w ceremonii konfiguracji. + +Tutaj polegamy na [perpetual powers of tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau), w którym brały udział dziesiątki uczestników. Jest to prawdopodobnie wystarczająco bezpieczne i znacznie prostsze. Nie dodajemy również entropii podczas tworzenia kluczy, co ułatwia użytkownikom [weryfikację konfiguracji zerowej wiedzy](#user-verify-zero-trust). + +### Gdzie weryfikować {#where-verification} + +Możemy weryfikować dowody zerowej wiedzy albo onchain (co kosztuje gaz), albo w kliencie (używając [`verify`](https://zokrates.github.io/toolbox/zokrates_js.html#verifyverificationkey-proof)). Wybrałem to pierwsze, ponieważ pozwala to [zweryfikować weryfikator](#user-verify-zero-trust) raz, a następnie ufać, że się nie zmieni, dopóki adres kontraktu dla niego pozostanie taki sam. Gdyby weryfikacja była przeprowadzana na kliencie, musiałbyś weryfikować kod, który otrzymujesz za każdym razem, gdy pobierasz klienta. + +Ponadto, chociaż ta gra jest dla jednego gracza, wiele gier na blockchainie jest wieloosobowych. Weryfikacja onchain oznacza, że weryfikujesz dowód zerowej wiedzy tylko raz. Robienie tego w kliencie wymagałoby, aby każdy klient weryfikował niezależnie. + +### Spłaszczyć mapę w TypeScript czy Zokrates? {#where-flatten} + +Ogólnie rzecz biorąc, gdy przetwarzanie może być wykonane w TypeScript lub Zokrates, lepiej jest to zrobić w TypeScript, który jest znacznie szybszy i nie wymaga dowodów zerowej wiedzy. To jest powód, dla którego, na przykład, nie dostarczamy Zokratesowi haszu i nie każemy mu weryfikować, czy jest on poprawny. Haszowanie musi być wykonane wewnątrz Zokrates, ale dopasowanie między zwróconym haszem a haszem onchain może nastąpić poza nim. + +Jednak nadal [spłaszczamy mapę w Zokrates](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L15-L20), podczas gdy mogliśmy to zrobić w TypeScript. Powodem jest to, że inne opcje są, moim zdaniem, gorsze. + +- Dostarczyć jednowymiarową tablicę wartości logicznych do kodu Zokrates i użyć wyrażenia takiego jak `x*(height+2) + +y`, aby uzyskać mapę dwuwymiarową. To sprawiłoby, że [kod](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/zero-knowledge.ts#L44-L47) byłby nieco bardziej skomplikowany, więc zdecydowałem, że wzrost wydajności nie jest tego wart w przypadku samouczka. + +- Wysłać do Zokrates zarówno tablicę jednowymiarową, jak i dwuwymiarową. Jednak to rozwiązanie nic nam nie daje. Kod Zokrates musiałby zweryfikować, czy dostarczona mu tablica jednowymiarowa jest rzeczywiście poprawną reprezentacją tablicy dwuwymiarowej. Więc nie byłoby żadnego wzrostu wydajności. + +- Spłaszczyć tablicę dwuwymiarową w Zokrates. To jest najprostsza opcja, więc ją wybrałem. + +### Gdzie przechowywać mapy {#where-store-maps} + +W tej aplikacji [`gamesInProgress`](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L20) to po prostu zmienna w pamięci. Oznacza to, że jeśli serwer ulegnie awarii i będzie musiał zostać ponownie uruchomiony, wszystkie przechowywane informacje zostaną utracone. Gracze nie tylko nie mogą kontynuować gry, ale nawet nie mogą rozpocząć nowej, ponieważ komponent onchain uważa, że nadal mają grę w toku. + +Jest to oczywiście zły projekt dla systemu produkcyjnego, w którym przechowywałbyś te informacje w bazie danych. Jedynym powodem, dla którego użyłem tutaj zmiennej, jest to, że jest to samouczek, a prostota jest głównym czynnikiem. + +## Wnioski: W jakich warunkach jest to odpowiednia technika? {#conclusion} + +Więc teraz wiesz, jak napisać grę z serwerem, który przechowuje tajny stan, który nie należy do onchain. Ale w jakich przypadkach powinieneś to robić? Istnieją dwa główne czynniki. + +- _Długo trwająca gra_: [Jak wspomniano powyżej](#why-zero-knowledge), w krótkiej grze możesz po prostu opublikować stan, gdy gra się skończy i wszystko zostanie wtedy zweryfikowane. Ale to nie jest opcja, gdy gra trwa długo lub w nieskończoność, a stan musi pozostać tajny. + +- _Akceptowalna pewna centralizacja_: Dowody zerowej wiedzy mogą weryfikować integralność, czyli to, że podmiot nie fałszuje wyników. Nie mogą jednak zapewnić, że podmiot będzie nadal dostępny i będzie odpowiadał na wiadomości. W sytuacjach, w których dostępność również musi być zdecentralizowana, dowody zerowej wiedzy nie są wystarczającym rozwiązaniem i potrzebne jest [obliczanie wielostronne](https://en.wikipedia.org/wiki/Secure_multi-party_computation). + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). + +### Podziękowania {#acknowledgements} + +- Alvaro Alonso przeczytał szkic tego artykułu i wyjaśnił niektóre z moich nieporozumień dotyczących Zokrates. + +Wszelkie pozostałe błędy są moją odpowiedzialnością. diff --git a/public/content/translations/pl/developers/tutorials/secure-development-workflow/index.md b/public/content/translations/pl/developers/tutorials/secure-development-workflow/index.md new file mode 100644 index 00000000000..501e3b77b13 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/secure-development-workflow/index.md @@ -0,0 +1,52 @@ +--- +title: "Lista kontrolna bezpieczeństwa inteligentnych kontraktów" +description: "Sugerowany przepływ pracy do pisania bezpiecznych inteligentnych kontraktów" +author: "Trailofbits" +tags: [ "smart kontrakty", "bezpieczeństwo", "solidity" ] +skill: intermediate +lang: pl +published: 2020-09-07 +source: Building secure contracts +sourceUrl: https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/workflow.md +--- + +## Lista kontrolna tworzenia inteligentnych kontraktów {#smart-contract-development-checklist} + +Oto ogólny proces, który zalecamy stosować podczas pisania inteligentnych kontraktów. + +Sprawdź, czy nie występują znane problemy z bezpieczeństwem: + +- Sprawdź swoje kontrakty za pomocą [Slither](https://github.com/crytic/slither). Ma ponad 40 wbudowanych detektorów dla popularnych luk w zabezpieczeniach. Uruchamiaj go przy każdym zatwierdzeniu nowego kodu i upewnij się, że generuje czysty raport (lub użyj trybu triage, aby wyciszyć niektóre problemy). +- Sprawdź swoje kontrakty za pomocą [Crytic](https://crytic.io/). Sprawdza 50 problemów, których Slither nie wykrywa. Crytic może również pomóc Twojemu zespołowi w bieżącej kontroli, łatwo ujawniając problemy z bezpieczeństwem w żądaniach Pull Request na GitHub. + +Rozważ specjalne cechy swojego kontraktu: + +- Czy Twoje kontrakty można aktualizować? Sprawdź swój kod pod kątem błędów w możliwości aktualizacji za pomocą [`slither-check-upgradeability`](https://github.com/crytic/slither/wiki/Upgradeability-Checks) lub [Crytic](https://blog.trailofbits.com/2020/06/12/upgradeable-contracts-made-safer-with-crytic/). Udokumentowaliśmy 17 sposobów, w jakie aktualizacje mogą pójść nie tak. +- Czy Twoje kontrakty mają być zgodne z ERC? Sprawdź je za pomocą [`slither-check-erc`](https://github.com/crytic/slither/wiki/ERC-Conformance). To narzędzie natychmiast identyfikuje odchylenia od sześciu popularnych specyfikacji. +- Czy integrujesz się z tokenami stron trzecich? Zapoznaj się z naszą [listą kontrolną integracji tokenów](/developers/tutorials/token-integration-checklist/) zanim zaczniesz polegać na zewnętrznych kontraktach. + +Sprawdź wizualnie krytyczne funkcje bezpieczeństwa swojego kodu: + +- Przejrzyj narzędzie Slithera [inheritance-graph](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph). Unikaj niezamierzonego przesłaniania (shadowing) i problemów z linearyzacją C3. +- Przejrzyj narzędzie Slithera [function-summary](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary). Raportuje widoczność funkcji i kontrolę dostępu. +- Przejrzyj narzędzie Slithera [vars-and-auth](https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization). Raportuje kontrolę dostępu do zmiennych stanu. + +Udokumentuj krytyczne właściwości bezpieczeństwa i użyj automatycznych generatorów testów, aby je ocenić: + +- Naucz się [dokumentować właściwości bezpieczeństwa swojego kodu](/developers/tutorials/guide-to-smart-contract-security-tools/). Na początku jest to trudne, ale jest to najważniejsza czynność dla osiągnięcia dobrego wyniku. Jest to również warunek wstępny do użycia którejkolwiek z zaawansowanych technik w tym samouczku. +- Zdefiniuj właściwości bezpieczeństwa w Solidity, do użytku z [Echidna](https://github.com/crytic/echidna) i [Manticore](https://manticore.readthedocs.io/en/latest/verifier.html). Skup się na swojej maszynie stanu, kontroli dostępu, operacjach arytmetycznych, interakcjach zewnętrznych i zgodności ze standardami. +- Zdefiniuj właściwości bezpieczeństwa za pomocą [API Pythona Slithera](/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/). Skup się na dziedziczeniu, zależnościach zmiennych, kontroli dostępu i innych problemach strukturalnych. +- Uruchamiaj testy właściwości przy każdym zatwierdzeniu (commit) za pomocą [Crytic](https://crytic.io). Crytic może wykorzystywać i oceniać testy właściwości bezpieczeństwa, dzięki czemu każdy w Twoim zespole może łatwo zobaczyć na GitHubie, że zostały one zaliczone. Niezaliczone testy mogą blokować zatwierdzenia (commit). + +Na koniec pamiętaj o problemach, których zautomatyzowane narzędzia nie mogą łatwo znaleźć: + +- Brak prywatności: wszyscy inni mogą zobaczyć Twoje transakcje, gdy są w kolejce w puli +- Wykonywanie transakcji z wyprzedzeniem (front-running) +- Operacje kryptograficzne +- Ryzykowne interakcje z zewnętrznymi komponentami DeFi + +## Poproś o pomoc {#ask-for-help} + +[Godziny konsultacji Ethereum](https://calendly.com/dan-trailofbits/office-hours) odbywają się w każdy wtorek po południu. Te godzinne, indywidualne sesje są okazją, aby zadać nam wszelkie pytania dotyczące bezpieczeństwa, rozwiązać problemy z użyciem naszych narzędzi i uzyskać opinie ekspertów na temat Twojego obecnego podejścia. Pomożemy Ci przejść przez ten przewodnik. + +Dołącz do naszego Slacka: [Empire Hacking](https://join.slack.com/t/empirehacking/shared_invite/zt-h97bbrj8-1jwuiU33nnzg67JcvIciUw). Jesteśmy zawsze dostępni na kanałach #crytic i #ethereum, jeśli masz jakieś pytania. diff --git a/public/content/translations/pl/developers/tutorials/send-token-ethersjs/index.md b/public/content/translations/pl/developers/tutorials/send-token-ethersjs/index.md new file mode 100644 index 00000000000..7b29f0f07d6 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/send-token-ethersjs/index.md @@ -0,0 +1,210 @@ +--- +title: "Wysyłanie tokenów przy użyciu ethers.js" +description: "Przewodnik dla początkujących dotyczący wysyłania tokenów przy użyciu ethers.js." +author: Kim YongJun +tags: [ "ETHERS.JS", "ERC-20", "TOKENY" ] +skill: beginner +lang: pl +published: 2021-04-06 +--- + +## Wyślij token przy użyciu ethers.js(5.0) {#send-token} + +### W tym samouczku dowiesz się, jak {#you-learn-about} + +- Importować ethers.js +- Przesłać token +- Ustawić cenę gazu zgodnie z natężeniem ruchu w sieci + +### Na początek {#to-get-started} + +Na początek musimy najpierw zaimportować bibliotekę ethers.js do naszego javascript +Dołącz ethers.js(5.0) + +### Instalacja {#install-ethersjs} + +```shell +/home/ricmoo> npm install --save ethers +``` + +ES6 w przeglądarce + +```html + +``` + +ES3(UMD) w przeglądarce + +```html + +``` + +### Parametry {#param} + +1. **`contract_address`**: Adres kontraktu tokena (adres kontraktu jest potrzebny, gdy token, który chcesz przesłać, nie jest etherem) +2. **`send_token_amount`**: Kwota, którą chcesz wysłać do odbiorcy +3. **`to_address`**: Adres odbiorcy +4. **`send_account`**: Adres nadawcy +5. **`private_key`**: Klucz prywatny nadawcy do podpisania transakcji i faktycznego przesłania tokenów + +## Uwaga {#notice} + +Funkcja `signTransaction(tx)` została usunięta, ponieważ `sendTransaction()` wykonuje tę czynność wewnętrznie. + +## Procedury wysyłania {#procedure} + +### 1. Połącz z siecią (sieć testowa) {#connect-to-network} + +#### Ustaw dostawcę (Infura) {#set-provider} + +Połącz z siecią testową Ropsten + +```javascript +window.ethersProvider = new ethers.providers.InfuraProvider("ropsten") +``` + +### 2. Utwórz portfel {#create-wallet} + +```javascript +let wallet = new ethers.Wallet(private_key) +``` + +### 3. Połącz portfel z siecią {#connect-wallet-to-net} + +```javascript +let walletSigner = wallet.connect(window.ethersProvider) +``` + +### 4. Pobierz aktualną cenę gazu {#get-gas} + +```javascript +window.ethersProvider.getGasPrice() // cena gazu +``` + +### 5. Zdefiniuj transakcję {#define-transaction} + +Zmienne zdefiniowane poniżej są zależne od `send_token()` + +### Parametry transakcji {#transaction-params} + +1. **`send_account`**: adres nadawcy tokena +2. **`to_address`**: adres odbiorcy tokena +3. **`send_token_amount`**: ilość tokenów do wysłania +4. **`gas_limit`**: limit gazu +5. **`gas_price`**: cena gazu + +[Zobacz poniżej, jak używać](#how-to-use) + +```javascript +const tx = { + from: send_account, + to: to_address, + value: ethers.utils.parseEther(send_token_amount), + nonce: window.ethersProvider.getTransactionCount(send_account, "latest"), + gasLimit: ethers.utils.hexlify(gas_limit), // 100000 + gasPrice: gas_price, +} +``` + +### 6. Przesłanie {#transfer} + +```javascript +walletSigner.sendTransaction(tx).then((transaction) => { + console.dir(transaction) + alert("Wysyłanie zakończone!") +}) +``` + +## Jak tego używać {#how-to-use} + +```javascript +let private_key = + "41559d28e936dc92104ff30691519693fc753ffbee6251a611b9aa1878f12a4d" +let send_token_amount = "1" +let to_address = "0x4c10D2734Fb76D3236E522509181CC3Ba8DE0e80" +let send_address = "0xda27a282B5B6c5229699891CfA6b900A716539E6" +let gas_limit = "0x100000" +let wallet = new ethers.Wallet(private_key) +let walletSigner = wallet.connect(window.ethersProvider) +let contract_address = "" +window.ethersProvider = new ethers.providers.InfuraProvider("ropsten") + +send_token( + contract_address, + send_token_amount, + to_address, + send_address, + private_key +) +``` + +### Sukces! {#success} + +![obraz transakcji zakończonej pomyślnie](./successful-transaction.png) + +## send_token() {#send-token-method} + +```javascript +function send_token( + contract_address, + send_token_amount, + to_address, + send_account, + private_key +) { + let wallet = new ethers.Wallet(private_key) + let walletSigner = wallet.connect(window.ethersProvider) + + window.ethersProvider.getGasPrice().then((currentGasPrice) => { + let gas_price = ethers.utils.hexlify(parseInt(currentGasPrice)) + console.log(`gas_price: ${gas_price}`) + + if (contract_address) { + // ogólne wysyłanie tokenów + let contract = new ethers.Contract( + contract_address, + send_abi, + walletSigner + ) + + // Ile tokenów? + let numberOfTokens = ethers.utils.parseUnits(send_token_amount, 18) + console.log(`numberOfTokens: ${numberOfTokens}`) + + // Wyślij tokeny + contract.transfer(to_address, numberOfTokens).then((transferResult) => { + console.dir(transferResult) + alert("wysłano token") + }) + } // wysyłanie etheru + else { + const tx = { + from: send_account, + to: to_address, + value: ethers.utils.parseEther(send_token_amount), + nonce: window.ethersProvider.getTransactionCount( + send_account, + "latest" + ), + gasLimit: ethers.utils.hexlify(gas_limit), // 100000 + gasPrice: gas_price, + } + console.dir(tx) + try { + walletSigner.sendTransaction(tx).then((transaction) => { + console.dir(transaction) + alert("Wysyłanie zakończone!") + }) + } catch (error) { + alert("wysyłanie nie powiodło się!!") + } + } + }) +} +```