rounded

Non avrei mai pensato che i contratti potessero essere scritti in questo modo? Questa è stata la mia più grande sorpresa di recente~

Recentemente ho scritto un tutorial sullo sviluppo di uno scambio decentralizzato https://github.com/WTFAcademy/WTF-Dapp, facendo riferimento all'implementazione del codice di Uniswap V3, e ho appreso molti concetti. Ho sviluppato in precedenza contratti NFT semplici, questa è la mia prima volta a provare a sviluppare un contratto Defi, credo che questi piccoli trucchi saranno molto utili per i principianti che vogliono imparare lo sviluppo di contratti.

I grandi esperti di sviluppo di contratti possono direttamente andare su https://github.com/WTFAcademy/WTF-Dapp per contribuire al codice e contribuire a Web3~

Ora vediamo questi piccoli trucchi, alcuni dei quali possono essere considerati vere e proprie abilità straordinarie.

L'indirizzo del contratto di distribuzione può essere reso prevedibile

Di solito, l'indirizzo che otteniamo dopo aver distribuito un contratto appare casuale, poiché è legato al « nonce », quindi l'indirizzo del contratto è difficile da prevedere. Tuttavia, in Uniswap, abbiamo questa esigenza: dobbiamo inferire l'indirizzo del contratto attraverso la coppia di trading e le informazioni correlate. Questo è utile in molte situazioni, come determinare i diritti di trading o ottenere l'indirizzo della piscina.

In Uniswap, la creazione del contratto avviene tramite codice come « pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); ». Aggiungendo « salt », si utilizza il metodo CREATE2 (https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md) per creare il contratto; il vantaggio di questo metodo è che l'indirizzo del contratto creato è prevedibile, e la logica di generazione dell'indirizzo è « nuovo indirizzo = hash("0xFF", indirizzo del creatore, salt, initcode) ».

Puoi vedere questa parte di contenuto nel capitolo corso di WTF-DApp qui: https://github.com/WTFAcademy/WTF-Dapp/blob/main/P103_Factory/readme.md per saperne di più.

Utilizzare bene le funzioni di callback

In Solidity, i contratti possono chiamarsi a vicenda. Un esempio è quando A chiama un metodo di B, e B richiama A nel metodo chiamato; questo può essere utile in certi scenari.

In Uniswap, quando chiami il metodo « swap » del contratto « UniswapV3Pool », esso richiamerà « swapCallback », passando i « Token » realmente necessari per la transazione calcolati, e il chiamante deve trasferire i Token necessari per la transazione al « UniswapV3Pool » nella callback, piuttosto che separare il metodo « swap » in due parti da far chiamare al chiamante; questo garantisce la sicurezza del metodo « swap » e assicura che l'intera logica venga eseguita completamente, senza la necessità di registrazioni di variabili complicate per garantire la sicurezza.

Il frammento di codice è il seguente:

Puoi imparare di più sui contenuti relativi alle transazioni nel corso qui: https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.

Usare eccezioni per trasmettere informazioni, utilizzare try catch per implementare la stima delle transazioni

Riferendoci al codice di Uniswap, abbiamo notato che nel suo contratto https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol, il metodo « swap » di « UniswapV3Pool » è stato racchiuso in un « try catch » per l'esecuzione:

Perché succede questo? Perché abbiamo bisogno di simulare il metodo « swap » per stimare i Token necessari per la transazione, ma poiché durante la stima non avviene effettivamente lo scambio dei Token, si verifica un errore. In Uniswap, viene generato un errore speciale nella funzione di callback della transazione, che viene poi catturato per estrarre le informazioni necessarie dal messaggio di errore.

Sembra piuttosto un Hack, ma è anche molto pratico. In questo modo, non è necessario modificare il metodo di swap per soddisfare le esigenze di stima delle transazioni, e la logica è più semplice. Nel nostro corso, abbiamo anche fatto riferimento a questa logica per implementare il contratto https://github.com/WTFAcademy/WTF-Dapp/blob/main/demo-contract/contracts/wtfswap/SwapRouter.sol.

Usare numeri grandi per risolvere i problemi di precisione

Nel codice di Uniswap, ci sono molte logiche di calcolo, come il calcolo dei Token scambiati in base al prezzo attuale e alla liquidità, e in questo processo dobbiamo evitare perdite di precisione durante le operazioni di divisione. In Uniswap, il processo di calcolo utilizza frequentemente l'operazione « << FixedPoint96.RESOLUTION », che rappresenta uno spostamento a sinistra di 96 bit, equivalente a moltiplicare per « 2^96 ». Dopo lo spostamento, si esegue l'operazione di divisione, garantendo così la precisione senza overflow nelle normali transazioni (di solito si utilizza « uint256 » per il calcolo, che è sufficiente).

Il codice è il seguente (calcola il numero di Token necessari per la transazione in base al prezzo e alla liquidità):

Possiamo vedere che, prima di tutto, in Uniswap i prezzi sono calcolati utilizzando la radice quadrata moltiplicata per « 2^96 » (corrispondente al « sqrtRatioAX96 » e « sqrtRatioBX96 » del codice sopra), poi la liquidità « liquidity » viene spostata a sinistra per calcolare « numerator1 ». Nei calcoli successivi, « 2^96 » verrà annullato nel processo di calcolo, ottenendo il risultato finale.

Certo, in teoria ci sarà comunque una perdita di precisione, ma in questo caso si tratta solo di una perdita dell'unità minima, che è accettabile.

Puoi approfondire ulteriori contenuti qui: https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.

Calcolare i guadagni usando il metodo Share

In Uniswap, dobbiamo registrare i guadagni delle commissioni degli LP (fornitori di liquidità). Ovviamente, non possiamo registrare le commissioni di ciascun LP ad ogni transazione, poiché ciò consumerebbe una grande quantità di Gas. Come possiamo gestirlo?

In Uniswap, possiamo vedere che in « Position » è definita la seguente struttura:

Include « feeGrowthInside0LastX128 » e « feeGrowthInside1LastX128 », che registrano le commissioni che ogni liquidità dovrebbe ricevere l'ultima volta che è stata prelevata la commissione per ciascuna posizione.

In parole semplici, devo solo registrare le commissioni totali e quanto ogni liquidità dovrebbe ricevere in commissioni, in modo che, quando LP prelevi le commissioni, possa calcolare quanto può prelevare in base alla liquidità in suo possesso. È come possedere azioni di una certa azienda: per prelevare i guadagni delle azioni, devi solo sapere quanto è stato guadagnato per azione nella storia dell'azienda e quali erano i guadagni dell'ultimo prelievo.

In precedenza, abbiamo anche introdotto in questo articolo (Progettazione ingegnosa del contratto, guarda come stETH distribuisce automaticamente i profitti giornalmente? Fai partecipare il tuo ETH allo staking per ottenere interessi stabili) il metodo di calcolo dei profitti di stETH, che segue una logica simile.

Non tutte le informazioni devono essere recuperate dalla blockchain

Lo stoccaggio sulla blockchain è relativamente costoso, quindi non tutte le informazioni devono essere caricate sulla blockchain o recuperate da essa. Ad esempio, molte delle interfacce chiamate dal sito frontend di Uniswap sono interfacce tradizionali di Web2.

L'elenco delle piscine di transazione e le informazioni sulle piscine possono essere memorizzati in normali database; alcune potrebbero dover essere sincronizzate periodicamente dalla blockchain, ma non è necessario chiamare in tempo reale le interfacce PRC fornite dalla blockchain o dai servizi di nodo per ottenere i dati pertinenti.

Certo, ora molti fornitori di PRC blockchain offrono alcune interfacce avanzate, che ti consentono di ottenere dati in modo più rapido e conveniente, questo è simile. Ad esempio, ZAN offre un'interfaccia simile per ottenere tutti gli NFT di un utente, queste informazioni possono chiaramente essere migliorate in termini di prestazioni e efficienza tramite caching; puoi visitare https://zan.top/service/advance-api per saperne di più.

Certo, le transazioni chiave vengono sicuramente eseguite sulla blockchain.

Impara a suddividere i contratti e a sfruttare contratti standard già esistenti come l'ERC721

Un progetto può contenere più contratti effettivamente distribuiti; anche se c'è solo un contratto effettivamente distribuito, il nostro codice può essere suddiviso in più contratti tramite l'ereditarietà per la manutenzione.

Ad esempio, in Uniswap, il contratto https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol eredita da molti contratti, il codice è il seguente:

Inoltre, quando guardi l'implementazione del contratto « ERC721Permit », noterai che utilizza direttamente il contratto « @openzeppelin/contracts/token/ERC721/ERC721.sol », il che facilita la gestione delle posizioni tramite NFT e consente anche di migliorare l'efficienza dello sviluppo del contratto utilizzando contratti standard già esistenti.

Nel nostro corso, puoi imparare a sviluppare un semplice contratto ERC721 per gestire le posizioni qui: https://github.com/WTFAcademy/WTF-Dapp/blob/main/P108_PositionManager/readme.md.

Riassunto

Leggere tanti articoli non è utile come mettersi in gioco e sviluppare, provare a implementare una versione semplificata di uno scambio decentralizzato ti permetterà di comprendere più a fondo l'implementazione del codice di Uniswap e di apprendere ulteriori concetti pratici che potresti incontrare nei progetti reali.

Il corso WTF-DApp è un corso open source completato dagli studenti della comunità degli sviluppatori di ZAN e della comunità degli sviluppatori della WTF Academy. Se sei anche interessato a Web3 e allo sviluppo di progetti Defi, puoi fare riferimento al nostro corso pratico https://github.com/WTFAcademy/WTF-Dapp, e completare passo dopo passo una versione semplificata di uno scambio, crediamo che ti sarà sicuramente utile~

Questo articolo è stato scritto da Fisher (X account @yudao1024) del team ZAN (X account @zan_team).