Web3 DApp 最佳編程實踐指南

大家好,我是 ConardLi

這一篇來自 Mirror.xyz 文章,作者:guoyu.eth (28 歲從字節退休的工程師)。本文知識密度非常高。工程師入門 Web3,這一篇足以。本文非常長,但值得每一個想要了解 Web3 的工程師,細讀幾遍。

原文鏈接:https://guoyu.mirror.xyz/RD-xkpoxasAU7x5MIJmiCX4gll3Cs0pAd5iM258S1Ek

以下是正文。

自宣佈進入創業間隔年以來,CodeforDAO(GitHub) 與 Checks Finance(@checksfinance) 兩個項目進入了密集而緊張的迭代週期,在合約編寫,單元測試,工作流自動化,前端與客戶端方面都遇到了較多問題,對此,我總結出了一些經驗。當前這兩個項目還有大量細節等待優化,尚未正式 landing,我認爲將開發過程中的經驗和總結與大家進行分享,能幫助更多工程師轉向 Web3,也有助於項目的長遠發展。

這篇文章將會涉及到開發一個 DApp 所涵蓋的幾乎所有方面內容,因此,它會非常冗長繁瑣,如果你對某一方面特別感興趣,我建議你可以通過下邊這個目錄直接跳去感興趣的章節閱讀。另外,這篇文章並不是 Step by Step 的代碼教學範例,因此,跳躍章節閱讀並不會影響體驗。

本文中提到的所有項目均列在我的 GitHub Star 清單中,可以在這裏統一查閱。

https://github.com/stars/guo-yu/lists/dapp-best-practice-stack

\1. 認識 DApp 技術棧

\2. 智能合約編碼

\3. 開發工作流與單元測試

\4. 前端與客戶端開發

\5. 開發、測試與生產環境調試

\6. 服務端編碼與集成

\7. 合約部署方案 L1s & L2

\8. 去中心化儲存方案

\9. 附錄

「1. 認識 DApp 技術棧」

與傳統的 App(包括 Web AppMobile App)最大的不同點在於,DApp 的大量功能依賴直接與智能合約(以下簡稱合約)進行交互。我們無法直接使用前端代碼調用合約,因此,在開發 DApp 之前,我們必須理解這一技術棧中存在哪些技術細節以及它們分別扮演何種角色。

現在,我們知道編寫一個 DApp 大概需要哪些領域的知識,如果你已經決定邁向下一代互聯網並打算闖蕩一番,我會在接下來的內容中仔細介紹這些角色分別需要理解哪些編程語言,框架和庫。

讓我們先進入最重要的部分,智能合約。大量程序員望而卻步的重要門檻是,他們認爲智能合約需要學習一門新的編程語言,Solidity,這毫無疑問,我非常推薦入門 Web3 的程序員—— 無論你是從哪一個軟件開發領域轉型而來 —— 從 Solidity 入手學習 DApp 開發。

在智能合約的編碼方面,我們目前有許多工具,但認識和理解 Solidity 非常有必要,大量的已經存在的,和流行的合約都使用它進行編碼,因此,學習 Solidity 不但有助於幫助你理解區塊鏈開發的基本知識和概念,還能讓你在許多優秀的開發者已有的卓越工程上快速起步。

就編程語言而言,在目前的 EVM 兼容鏈上,你可以使用 SolidityVyper 進行開發,在其他 L1s 區塊鏈上,例如 Solana,你可以使用 Rust 來進行合約的開發;在 Layer2 方案 StarkNet 中,你可以使用 Cairo 來進行開發;在 Arweave 儲存網絡中,也存在着類似 3em 這樣的運行環境支持你使用 JavaScript 來編寫合約。

在這些百花齊放的方案中,實際上存在着兩種不同的合約運行環境,EVM 或非 EVM 方案,前者的代碼都會被編譯成 EVM bytecode,而後者則會採用各種各樣的 runtime,各顯神通。

這篇文章不會在合約編程語言上討論太多,我認爲,我們目前正處於合約 runtime 的戰國時代,沒有人能斷言哪種合約編程語言的地位會成爲 Web 世界的 JavaScript。但對於智能合約編碼來說,我們必須要了解和熟悉 Solidity,這是毫無疑問的。

關於 Solidity,我推薦你從 Solidity by Example 教程開始學習。

https://solidity-by-example.org/

這一教程沒有繁瑣的語法介紹,而根據範例幫助讀者掌握基本知識,因此,完成這一教程大約只需要不到一個工作日。Solidity 並不是一個特別複雜的語言,在使用它時,我們可以逐步理解每一項語句的語義,我推薦你設置好編碼環境後按照網站上的範例來進行實踐。當你已經掌握所有範例的寫法之後,可以打開 Solidity 語言官方文檔(中文)對照編碼中的錯誤來進行鍼對性的學習。

https://docs.soliditylang.org/

理解並掌握智能合約後,我們可以進入 DApp 的編碼,這是許多互聯網行業從業者的強項,我不會在此贅述關於前端編碼的經驗,如上所述,我們可以使用流行的前端框架,例如 React 或者 Vue 來進行 DApp 的編碼。毫無疑問,你會需要一些前端的技術棧知識,主要是 JavaScript 與 CSS。

在此,我想向大家推薦一些優秀的前端庫,使用這些代碼庫來進行合約交互,會使我們的開發效率事半功倍。

React 爲例,我們可以使用 wagmi 來幫助我們更好的操作合約,它集成了大量基礎但夠用的 hooks,並提供了與外部 Provider/Signer 交互的快捷函數。與此同時,wgami 沒有過多的外部依賴,它的核心依賴只有 ethers.js。

https://github.com/tmm/wagmi

如果你不是一個框架愛好者,想要從零開始構建應用程序,不可避免的,你需要使用 ethers.js 或者 web3.js 來進行基本操作。從我自己的使用經驗來看,我更推薦 ethers.js。

https://docs.ethers.io/

https://web3js.readthedocs.io/

一般來說,我們並不需要其他的庫爲我們提供專門的 Provider/Signer 支持,如果你打算支持更多複雜的 Provider,或者同時支持多網絡 Provider/Signer 的讀寫功能,類似 Apeboard 爲它的用戶提供跨區塊鏈的數據展現,可以參考 react-web3 或者 w3modal 兩個流行的模塊,這些模塊提供了一些好用的功能,但他們的設計不夠解耦,有時會帶來不必要的 bug,對此,我保持謹慎推薦。

https://github.com/NoahZinsmeister/web3-react

進一步,如果你想不想支持外部 Provider/Signer,而爲自己的用戶構建一個 Web 錢包,你可以使用 ethers.js 從零開始構建。

如果你想爲用戶提供一個 onboard 體驗更好(但更不去中心化)的託管錢包系統,讓他們可以從普通的賬戶密碼或者社交網絡賬戶來登錄你的 DApp,可以選擇採用 Web3Auth 或 MagicLink 的方案。託管錢包系統是一個非常大的話題,感興趣的讀者可以參考上述兩個解決方案自行研究。

https://web3auth.io/

https://magic.link/

服務端方面,你可以使用任何你喜歡的編程語言,運行環境和軟件架構,沒有什麼特殊的限制,只要保證你選擇的技術棧能和本地節點或者(通常是)Relay Network 進行交互即可。

一般來說,我會選擇 Node 運行環境。說些題外話,由於大部分合約使用 NPM 來進行包管理,並使用 hardhat 來做編譯和測試工作流,使用 JavaScript 已經成爲智能合約編碼中必不可少的一個環節。既然如此,在服務端同時使用 JavaScript 語言有助於我們複用代碼,留出更多的時間享受人生。

編寫服務端並不意味着我們需要做完所有事,通常,我們使用 DApp 的服務端代碼來儲存沒必要儲存在合約中的「鏈下狀態」。在合約中儲存數據是十分昂貴的選擇(至少目前看來)這種昂貴不僅涉及到我們部署合約中產生的費用,還涉及到每一次修改狀態的函數請求帶來的,用戶需要付出的 gas 成本。所以,大部分時候,我們會使用自己的服務端來儲存這些「鏈下狀態」

使用一個健壯的 FaaS 對許多工程師來說是簡單而且實用的選擇,我推薦 Firebase,如果你想體驗深度集成區塊鏈的 FaaS,也可以參考上述提及的 Moralis。

https://firebase.google.com/

我選擇 Firebase 的主要原因是他們提供成本低廉,服務完善和穩定的健壯 API,同時,他們針對開發者開發了功能齊全的本地模擬測試套件,這會節省我們相當多的時間。

https://firebase.google.com/docs/emulator-suite

FaaS 在市面上有太多可選的方案,你可以依賴一個全功能 FaaS,也可以將自己爲數不多的「鏈下狀態」儲存在 headless CMS 當中,例如 Vercel 或者 Netlify。

https://vercel.com/

https://www.netlify.com/

或者,如果你希望自己搭建 FaaS 服務器,以獲得更完善的控制與更低的成本,我向你推薦一些 Firebase 的開源替代品,例如 Supabase。

https://github.com/supabase/supabase

「2. 智能合約編碼」

在這一章節,我們會從 Solidity 語言入手,理解編寫一個智能合約與傳統的應用軟件或界面有何不同,你可以使用上一章節提到的其他智能合約編程語言,但本章節將使用 Solidity(以下簡稱 Sol) 作爲範例闡述智能合約編碼中應當注意的問題。

在此,我不會逐行逐句解釋 Sol 語言的語義細節,因此,閱讀這一章要求你有起碼的 Sol 語言知識。我建議,在此之前,請參考並讀完所有的 Solidity Examples:

https://solidity-by-example.org/

「2.1 合約特徵」

事務性:我們可以將區塊鏈看成是一個事務性數據庫,這意味着,要麼我們在合約中編寫的函數全部被執行,狀態依次被修改,要麼,所有的狀態都會回滾到當初未曾被修改的樣子。這意味着,我們在對智能合約進行編碼的過程中,要十分注意函數 API 的設計,在具體的函數中,不應當對參數進行重載。同時,也意味着我們在進行錯誤處理時要十分小心。

錯誤處理:我們可以選擇兩種常用的錯誤處理方式,require(condition, ERR_MESSAGE) 或者 revert customError(),前者傳入一個字符串代表錯誤,後者可以自定義錯誤類型。兩種方式並無本質上的不同,並且都會導致 tx 失敗。對於前端而言,我們都需要自定義錯誤類型來捕獲這兩種錯誤。

運行成本:合約的狀態儲存會消耗 Gas 費用(區塊鏈的激勵機制,作爲付給運行節點的計算與儲存費用)爲此,在設計儲存對象時,如何善用聲明的內存是需要被考慮的問題之一。簡單的法則是,不要爲不需要的狀態聲明過多的內存空間,如果你需要優化一個合約的運行成本,可以考慮參考許多合約使用內聯彙編來優化內存佔用。

爲此,合約中的複雜數據結構必須聲明儲存空間位置,例如 storage, memory, calldata,每種位置所產生的費用會有很大不同。合約的函數也會有對應的函數類型聲明,view 函數 與 pure 函數在外部調用時不需要承擔 gas 費用,但改變狀態的函數都需要消耗 gas。

注意:由於合約運行和儲存成本高,許多對外部白名單進行管理的最佳實踐是使用 MerkleProof,你可以在這裏找到它的合約實現和 JavaScript 實現。

不可變:合約一旦部署,就無法動態替換或進行升級,這意味着,你需要在部署前考慮是否要依賴可升級架構(Proxy 部署方案)這些方案所依賴的合約和抽象合約,都需要遵循同一種初始化範式,才能保證合約的可升級性。

權限和可見性:合約不同於服務端代碼,它對網絡中的所有人是透明的,這裏的透明不僅指的是合約的字節碼,還包括它的公開和私有狀態。這意味着,你不應當在合約中儲存任何敏感數據,也不應當依賴區塊當中的任何狀態(比如區塊高度和時間戳)作爲核心業務邏輯的判斷基準。

爲此,發佈一個未經權限控制的合約是十分危險的,任何外部賬戶都可以輕易地對某個合約進行修改,並通過發送消耗合約指令將合約中的資產轉走。所以,除了特定的治理合約不受權限管制以外,我推薦任何合約都必須至少依賴 Ownable 來進行基本的權限配置,同時,複雜合約可以使用 AccessControl 來進行管理。

安全性:如上所述,合約的安全性是非常重要和嚴謹的問題,在將合約發佈到生產環境網絡之前,確保你已閱讀 ConsenSys 編寫的合約代碼安全最佳實踐指南並遵守其中所有的約定,同時,確保合約有足夠的測試用例並且較高的測試覆蓋度。請不要帶有僥倖心理發佈未經任何測試的合約代碼,並主觀地希望它能夠正常工作。

「2.2 合約依賴與調用」

依賴引入:合約可以通過 import 引入依賴的外部合約,抽象合約,Interface 或者庫。通常,我們使用 npm 管理合約的外部依賴,管理合約的依賴也有其他辦法(例如 git submodule)這會在工作流章節中詳細敘述。

調用:合約可以調用其他合約,只需知道地址和 ABI,我們就可以在合約內部調用其他合約,需要注意的是,調用合約也是事務性操作,因此,你不需要通過手動管理異步操作的方式來等待返回結果。在合約內部調用其他合約需要消耗額外的 Gas 費用。調用合約可能由於 ABI 錯誤或者不支持某個函數方法而導致失敗,但 Gas 費用並不會返還,我們需要確保在調用其他第三方合約前理解對方合約的接口(包括參數類型,順序,返回結構)

如果你試圖調試本地合約調用某個生產環境的線上合約,可以使用 fork 的方式將某個高度的區塊鏈下載到本地運行,這會在工作流章節中詳細敘述。

ABI:也叫應用程序二進制接口(Application Binary Interface)ABI 是我們理解如何操作一個合約的具體方法的描述,通常在 Interface 文件中被定義(如果合約命名爲 Membership.sol,那麼它的 Interface 文件通常叫做 IMembership.sol)

注意:通過這種方式定義可以讓任意合約通過引用 interface 的方式來調用你的合約,但如果你不在 Interface 中文件定義它,編譯器也能幫助你編譯出 ABI。

我們可以依賴完整的 ABI 來調用合約(對外部調用者來說,ABI 通常被編譯成一個 JSON 文件),也可以使用它其中的一部分來調用,只要它滿足真實合約所聲明的函數(包括參數,參數類型,返回值,返回值類型都一致)後者通常被成爲 human-readable ABI,例如:

calldatas[0] = abi.encodeWithSignature('execTransfer(uint256,address,address[],uint256[])',   memberId,   memberWallet,   payroll.tokens.addresses,   payroll.tokens.amounts);

「合約事件」:由於合約的函數調用是事務性的,並且無法爲外部調用者(指代 DApp 或錢包用戶)提供返回值,合約引入了事件的概念。

事件通過向日志系統中寫入特定數據的方式來實現函數修改的記錄。我們可以通過監聽和查詢的方式列出一個合約註冊的所有事件,實現對函數異步結果的查詢和前端 UI 狀態變更。合約事件以某個單一合約爲 key 來進行索引,同時,在聲明事件時,我們可以指定不多於三個 index key 來確保 DApp 前端對這些索引 key 的查詢效率,例如:

event ModuleProposalCreated(address indexed module,bytes32 indexed id,address indexed sender,uint256 timestamp);

如果你期望的查詢是非常複雜的,包括一系列相關聯的合約事件,更好的方法是採用 Relay 提供的 graph/webhook 來進行查詢。

創建合約:我們可以通過合約創建其他合約,這意味着,合約可以成爲其他合約的工廠合約或者代理合約。我們也可以通過外部調用者(錢包賬戶)向 0x00 地址發送合約創建操作來新建網絡上的合約,這是我們進行測試和依賴工作流創建合約的方法。

創建合約需要消耗大量 Gas 費用,通常,我們會使用特定工具在創建合約前預估並計算費用,這會在工作流章節中詳述。

「2.3 合約編程語言特徵」

Sol 需要依賴相應工作流被編譯成字節碼發佈到對應環境的網絡中才能被運行,因此,它不像 JavaScript 那樣的動態類型語言有隨處可見的 runtime,編譯器在檢查時會幫助我們發現大部分問題,因此,你需要一個 IDE,例如 VSCode 之類的 IDE 或編輯器才能進入合約開發。

Sol 與大部分編程語言類似,支持基本多種數據類型(但不支持浮點數)、複雜數據結構(例如 map,array 和 struct)、合約支持繼承和多重繼承(is)、原型方法重寫(override)等。合約有特殊的構造函數,合約聲明的函數支持修飾器語法。特殊地,合約中可以通過 payable 聲明或顯示轉換來實現對原生 Gas token (ETH) 的資金操作。

Sol 雖然是圖靈完備的語言,但其中複雜結構的操作會帶來相應的 gas 消耗,因此,在設計合約中的狀態變量時,應當足夠清晰和簡單。

「2.4 閱讀優秀的合約代碼」

合約編程雖然不復雜,但大量的運行時限制和非冗餘的設計,導致我們在進行合約編碼時,不得不參考許多優秀的合約代碼,才能保證我們的合約代碼質量。

對於許多其他領域的程序員來說,這一步更是非常必要的。我推薦大家在合約編碼的過程中,反覆參考優秀合約項目的設計思路和編碼思維。在這裏,我爲大家推薦一些我認爲不錯的智能合約開源項目。

首先,OpenZeppelin 合約是進入 Web3 領域必須反覆的閱讀的聖經之一,自 2017 年以來,他們實現了大量的 EIP(以太坊改進提案),併成爲了智能合約編碼的實際標準。雖然,OZ 的合約在 Gas 費用和效率上存在一些問題,但他們在安全性、代碼完成度、可維護性、註釋和測試方面都做的很好,是值得信賴的合約基礎庫。最近,OZ 也發佈了他們在 StarkNet 上的 Cairo 語言版本合約。

https://github.com/OpenZeppelin/openzeppelin-contracts

Solmate 也提供了一系列對應的 EIP 實現,同時,他們更注重合約的運行效率,優化了執行中的 gas 費用,並且每個合約依賴更少,閱讀起來更加簡單。

https://github.com/Rari-Capital/solmate

ERC721A 是知名 NFT 項目 Azuki 發佈的 ERC721 改善版本,通過特定的位操作,他們實現了內存佔用的優化,帶來了批量 mint 低 Gas 費用的優勢。如果你的項目涉及到大量 NFT 的鑄造,可以參考它的合約代碼來進行實現。

https://github.com/chiru-labs/ERC721A

Compond 是 DeFi 借貸領域的老牌項目,代碼質量經過實踐的檢驗,如果你的項目設計到 DeFi 相關的需求,請務必閱讀他們的合約代碼。

https://github.com/compound-finance/compound-protocol

Uniswap 是世界上最大的 DEX,他們的合約實現的非常優秀,無論你是否有 DeFi 方面的需求,我都建議你完整閱讀他們的合約代碼。

https://github.com/Uniswap/v3-core

Lens 是 AAVE 推出的以 NFT 爲核心的新型社交合約開發套件(或者他們稱之爲社交合約協議)如果你的項目設計到 SocialFi,可以參考他們的代碼實現。

https://github.com/aave/lens-protocol

其次,我想給大家推薦的是 Zora v3 版本合約與 Gonsis safe,前者是著名的 NFT 交易市場退出的交易合約,後者是著名的多簽名錢包合約實現。這些都是我們在使用智能合約能夠完成的產品當中非常重要的組成部分:

https://github.com/ourzora/v3

最後,如果你對 DAO 和鏈上治理感興趣,我推薦你閱讀我編寫的 CodeforDAO 的合約,在這個項目中,實現了傳統的治理模式,多籤積極治理與模塊化合約。

https://github.com/CodeforDAO/contracts

「3. 開發工作流與單元測試」

當我們掌握編寫智能合約的編程語言後,便可以開始進行工程編碼,在這一章節中,我會介紹流行的 DApp 合約開發工作流和編寫單元測試的方法。

智能合於自 2017 年發展至今,已存在相當多的項目支撐合約開發中的工作流,如今,大部分項目使用 Hardhat 來支持本地開發工作流。

https://hardhat.org/

Hardhat 提供了一種簡單的方式創建本地 EVM 兼容區塊鏈開發的環境,並且支持直觀的 debug 方式,此外,還有豐富的插件社區,幫助開發者完成一系列特定的需求。

依賴 Hardhat,我們可以在本地創建 block 快速確認的開發環境,使用公開私鑰的調試錢包作爲測試用戶,編譯合約併發布到本地測試網絡,編寫並在內存網絡中快速運行單元測試,如果你是 DApp 開發的入門新手,使用 Hardhat 是作爲工作流最簡單和直接的方案。

Hardhat 還支持配置不同的區塊鏈網絡並通過工作流部署合約到生產環境,或者將某個高度的區塊鏈 fork 到本地創建集成測試環境。它提供豐富齊全的文檔,可以在他們的官網進行參考。

「3.1 整合 Hardhat 工作流」

有兩種方法可以簡單將 Hardhat 整合到你的合約項目,最簡單的方法是採用 npx hardhat 嚮導,它會幫助你在本地建立一個特定的腳手架項目並安裝對應依賴。

如果你打算在已經初始化的項目中引入 hardhat 工作流,手動的方式是新建一個配置文件 hardhat.config.js 並安裝對應依賴

npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers

hardhat 工作流非常簡單,我們不需要花太多時間理解它,對於前端工程師而言,它就像是智能合約領域中的 Webpack(但沒有 Webpack 的配置那麼複雜)它預設了一系列的 task(任務)幫助你將 Sol 文件編譯爲字節碼並輸出對應的 ABI 到本地,當然,它也幫助你整合單元測試環境並運行測試。

「Hardhat 任務:」

對於一般的合約開發而言,常用的任務是:

npx hardhat node

運行一個本地區塊鏈節點並將 JSON-RPC endpoint 暴露給客戶端。

npx hardhat test

運行合約的單元測試。

npx hardhat deploy

發佈合約的字節碼到某個區塊鏈網絡,網絡地址和 deployer 賬戶,這些我們可以在 hardhat.config.js 文件中配置。

「整合插件:」

hardhat 工作流還支持插件機制,插件能夠將特定邏輯作爲 hook function (鉤子函數)插入到對應的 task(任務)當中,所以,當我們執行某個任務時,需要確認它是否依賴了某個插件,否則它可能有與預期不同的行爲。

引入插件的方式是,首先使用 npm 安裝這個插件,再於 hardhat.config.js 配置文件頭部引入即可。

「HRE 運行環境變量:」

當我們在 JS 文件中引入 hardhat 時,HRE 會被插入運行環境。一些插件可能會拓展 HRE,將他們的實用函數方法插入到 HRE 中,類似地,我們也可以使用這種方法構建特定的 hardhat 插件。但一般來說,我們不需要這麼做。

HRE 會在我們執行 npx hardhat run 任務時被自動插入到全局變量中去,我們可以通過這種方法編寫某些簡單的合約發佈腳本或合約交互腳本。

3.2 單元測試

編寫合約的第二步是編寫合約的單元測試。當我們運行 npx hardhat test 任務時,hardhat 會自動尋找 ./test 文件夾下的單元測試並運行它們。這個默認的地址可以在 hardhat.config.js 配置文件中使用 path.tests 修改:

// Rewrite the `./test` folder to `./tests`paths: {tests: './tests',},

運行測試所需要安裝的依賴可以在這個指南上找到:

https://hardhat.org/guides/waffle-testing.html

與傳統的單元測試一樣,使用 Mocha 作爲單元測試框架。對於合約特定的變量類型,我們使用 Waffle 和 chai 作爲斷言庫。

一般來說,我們在 hardhat.config.js 配置文件頭部引入測試輔助插件 @nomiclabs/hardhat-waffle 可以幫助我們解決大部分問題,而不需要額外手動安裝 mocha, waffle 和 chai 並進行配置,與前一小節所提到的 HRE 相關,它們會被自動插入 HRE 運行環境。

關於合約事件,合約方法調用,BigNumber 等完整的斷言庫範例可以在這個文檔中找到:

https://ethereum-waffle.readthedocs.io/en/latest/matchers.html

注意:合約的單元測試中可以使用 contractInstance.connect(signer) 來隨意改變調用合約的外部賬戶。

「3.3 改善測試效率」

編寫單元測試首先需要我們在測試鉤子中編寫發佈合約的代碼,這意味着,我們需要在每次 beforeEach 鉤子中重新發布我們的合約並使其從零狀態開始運行。

即使 hardhat 支持在內存中運行區塊鏈並整合了單元測試流程,但這樣反覆的發佈合約也會極大拖慢測試速度。

因此,就單元測試的最佳實踐,我向大家推薦 hardhat-deploy 插件。

hardhat-deploy 插件支持使用 evm_snapshot 快速地跳轉到某個高度的區塊鏈狀態,因此,我們可以使用它在單元測試中維護測試前、中、後以及各種特定高度狀態,極大地加快測試速度。

https://github.com/wighawag/hardhat-deploy

注意,引入 hardhat-deploy 插件,需要修改對應的 @nomiclabs/hardhat-ethers 插件來源,這可能會導致在未來的 npm install 中帶來版本衝突,如果你遇到了版本衝突,可以使用 npm install --force 跳過版本依賴檢查,強制安裝兩者。

"devDependencies"{"@nomiclabs/hardhat-ethers""npm:hardhat-deploy-ethers","hardhat-deploy""^0.11.2",    ...}

簡單來說,在單元測試中,我們可以使用:

await deployments.fixture(['SomeContractName']);

來確保在測試執行前跳回某個狀態。如果你需要更加複雜和自定義的 fixture(而非直接跳回某個合約發佈後的乾淨狀態)可以使用 deployments.createFixture 來創建自定義 fixture,具體的範例代碼和指南可以在這裏尋找到:

https://github.com/CodeforDAO/contracts/blob/main/utils/helpers.js#L42

值得注意的是,使用 hardhat-deploy 插件會同時改變我們發佈合約的代碼邏輯(正如其名)它支持在 ./deploy 文件夾下編寫每個合約的發佈腳本。事實上,默認的 deployments.fixture 正會退回這些發佈腳本所敘述的合約狀態。

不同於 hardhat 默認的發佈腳本(使用 npx hardhat run)我們可以使用 hardhat-deploy 插件提供的功能在發佈腳本中做更多工作,例如使用 execute 函數立即修改發佈後的合約狀態,這會在對權限敏感的合約當中非常有用:

// 來自 code祕密花園
const { deploy, execute } = deployments;
const shareGovernor = await deploy('ShareGovernor'{
    contract: 'TreasuryGovernor',
    from: deployer,
    args: [name + '-ShareGovernor', share.address, treasury.address, settings.share.governor],
    log: true,
  });

  // Setup governor roles
  // Both membership and share governance have PROPOSER_ROLE by default
  await execute(
    'Treasury',
    { from: deployer },
    'grantRole',
    PROPOSER_ROLE,
    membershipGovernor.address
  );

除此之外,hardhat-deploy 插件還提供了非常多的 HRE 實用函數,例如 getNamedAccounts 能幫助我們命名本地測試賬戶,而非使用數組下標訪問它們。你可以參考該插件的 GitHub 主頁瞭解這些實用功能。

「3.4 測試覆蓋率與 Gas 報告」

當合約的單元測試編碼到一定程度之後,我們會希望這些合約被髮布到某個測試或生產環境(例如測試網絡或 ETH 主網)時是否是健壯和低成本的,在此時,我向你推薦兩個 hardhat 插件 hardhat-gas-reporter 與 solidity-coverage

hardhat-gas-reporter 插件幫助你瞭解運行單元測試中部署和執行合約方法消耗的 gas 費用,如果在本地環境變量中提供 COINMARKETCAP_API_KEY,它會自動將這些成本折算爲美元或其他法幣計價。

https://github.com/cgewecke/hardhat-gas-reporter

solidity-coverage 插件提供單元測試覆蓋率報告,這有助於開發團隊理解合約是否得到了應有的測試。

https://github.com/sc-forks/solidity-coverage

「3.5 其他實用插件」

如前所述,hardhat 並不需要以來太多的插件就可以正常工作,滿足大部分合約開發團隊的需求,我在這裏推薦兩個其他的使用插件,他們是 @nomiclabs/hardhat-etherscan 和 @tenderly/hardhat-tenderly

這些插件都是可選的,並依賴第三方服務的 API Key,各位讀者可以根據自己的情況選擇是否使用他們。

hardhat-etherscan 插件將 etherscan 網站的源碼 verify 功能整合到發佈工作流中,能夠將所發佈合約的源碼和 ABI 都展示在合約地址頁面。

https://github.com/NomicFoundation/hardhat

Gihardhat-tenderly 插件整合了 Tenderly 工作流,後者是一個新興的 CI / 監控 平臺,能夠幫助我們監控線上合約的狀態並提供 debug 建議。

https://tenderly.co/

「3.6 更快的工作流方案:Foundry」

儘管在過去的三年中 hardhat 逐漸壟斷了 EVM 兼容鏈中的智能合約開發工作流市場,但最近也有很多強有力的競爭者出現,Foundry 就是其中之一:

Foundry 由 Rust 語言編寫,並在很多方面極大地提升了合約單元測試的運行效率:

Foundry 由它的命令行工具 Forge 與 cast 組成,前者幫助我們安裝第三方依賴組件(使用 git submodule 方式)運行測試,發佈合約,後者幫助我們與合約進行 RPC 通信交互。

同時,它也改變了我們編寫測試的方法,讓開發者可以直接編寫 Sol 而不再依賴 JavaScript 就可以編寫合約的單元測試,測試文件與合約源碼在同一個文件夾中管理,通常以 ContractName.t.Sol 特殊後綴結尾。

它也提供了一系列的測試套件工具幫助我們編寫基於 Sol 的單元測試,包括可繼承的 Test 合約,和一個特殊的,與 vm 通信的合約 Cheatcodes 幫助我們改變外部調用者地址,進行錯誤斷言等等功能。

如果你對 Foundry 感興趣,我推薦你閱讀他們的文檔,它的學習曲線並不陡峭,但考慮到使用它會改變絕大部分智能合約項目的開發流程,我推薦你在本地分支中支持它並同時兼容 hardhat 工作流。

https://book.getfoundry.sh/

「4. 前端與客戶端開發」

由於第三方錢包軟件的盛行,Web3 大部分產品(DApp)的用戶節目實際上都由前端網頁所構成,這與移動互聯網的開發流程相悖,但很像早期 Web2.0 歷史發展進程的一部分。

許多 DApp 並不提供 Mobile App 版本,部分原因是由於構建一個跨平臺錢包方案過於複雜,以及大部分 Web3 領域內的用戶都在使用諸如 MetaMask 這類瀏覽器插件錢包,而它的移動端 App 體驗並不好用。

我們當然可以使用託管錢包服務來進行開發,讓更多不熟悉 Web3 的用戶使用郵箱或者密碼登錄,但這會帶來一系列的安全問題與風險,況且,就算我們能夠使用簡單易用的錢包降低用戶准入門檻,在許多國家,用戶仍需要複雜的 kyc 才能獲取到某些 token。

所以,我建議你也採用 Web 前端作爲第一個 DApp 的界面方案。

在本章節,我們所涉及到的大部分內容都是基於這樣的假設,因此,這部分內容需要你熟悉 JavaScript,React.js/Vue.js 和它們相關的工作流。

「4.1 前端框架的選擇」

使用何種視圖框架並不會影響你的 DApp 體驗,但是,這會影響到開發效率。

在本文的第一部分「認識 DApp 技術棧」我們介紹了 ethers.js 和 web3.js,這兩者都是構建 Web 前端的基礎類庫。如上所述,我建議使用 ethers.js 入門進行開發。

事實上,相較於 Vue 而言,React 的生態系統中目前擁有較多的活躍 Web3 開發者和相關依賴庫,如果你沒有特殊的偏好,可以嘗試先採用 React 作爲框架來進行開發。

另外,大部分對於 ethers.js/web3.js 的包裝項目,諸如 web3-react,wagmi 等都依賴你理解 React hooks 的概念,所以在你着手進行編碼之前,需要查閱它的文檔。

「4.2 搭建腳手架項目」

我們可以使用任何藍圖工具創建對應的視圖框架的腳手架項目,但是,我們也可以參考現有的 Web App 項目來進行開發。

在編寫前端代碼之前,我推薦你參考流行的腳手架項目 scaffold-eth

scaffold-eth 是一個完整的合約腳手架,它的 packages/react-app 文件夾是它的 Web 前端代碼,對於熟練合約開發的工程師而言,這個腳手架的組織方式有些不太理想,但對於剛入門 Web3,打算搭建第一個測試項目的朋友來說,這是個很棒的入門工具。

https://github.com/scaffold-eth/scaffold-eth

當我們決定了使用何種視圖框架來開發前端 Web App 之後,就可以着手使用它對應的腳手架 cli 來創建項目了。對於沒有任何偏好的讀者,我推薦你使用 React + Next.js 來初始化新項目,Next.js 使用 React 作爲基礎視圖框架,並提供了豐富的工作流,簡單的路由系統,好用的 SSR 與 FaaS 支持,當然,它也是一個非常好用 site builder 工具。

https://nextjs.org/

「4.3 前端與合約的交互流程」

當我們在 DApp 的業務邏輯編碼進行到一定程度後,需要與合約 ABI 進行讀寫,或者,我們需要連接用戶的錢包,爲其鑄造一個 NFT,這裏就涉及到前端與合約的交互。

在本文的第一章節「認識 DApp 技術棧」中,我們提到與區塊鏈網絡進行交互最終會使用 Provider/Signer(前端) + Relay Network(區塊鏈端)因此,這個流程最終會使用 ethers.js 或 web3.js 發送對應的 XHR 請求到 Relay Network 的 API Endpoint。但是,具體而言,開發者和用戶如何理解這一與衆不同的流程呢?

一般來說,我們可以將這個交互流程簡述爲:

  1. 用戶打開網頁,默認進入只讀模式。我們只能通過 Provider + Relay Network 訪問到我們所設定的默認網絡的合約的 view 方法返回值。

  2. 用戶點擊「連接錢包」時,我們和對應的 connector 進行通信,獲取連接狀態。如果用戶授權連接,我們可以通過 connector 獲取到用戶錢包的地址(0x)在此之後,用戶錢包的 Provider 只讀模式將只支持用戶錢包中選定的網絡,如果網絡不符合我們的期望,我們可以通過 connector 的特定通信來請求用戶修改它。

  3. 用戶點擊某個表單,希望與合約的寫操作函數進行交互。此時, ethers.js 或 web3.js 會將交易信息打包發給 connector,後者將引導用戶進行簽名和確認交易的操作。如果交易成功提交到區塊鏈網絡(經由我們配置的 Relay Network)我們需要監聽該交易的狀態和合約事件,進行下一步前端狀態更新。

  4. 用戶進行錢包登錄操作或其他簽名操作時,我們可以不與合約通信僅在本地請求 connector 來發行使用用戶錢包私鑰簽名後的加密數據。

在進行以上所述的流程之前,我們需要回顧 「認識 DApp 技術棧」中的內容,並且準備好 Relay Network 的 access key,無論操作是讀或者寫,我們都需要準備這些 access key 才能爲用戶提供高質量和穩定的請求訪問。

我們可以通過提供多個 Provider 實體的方式來訪問多個區塊鏈網絡的合約 ABI,但一般而言,我們只能依賴 connector 中用戶選定的網絡來進行寫操作。

對於合約多個 ABI 的寫操作可以合併請求,這樣可以減少用戶在進行操作時的 Gas 費用,如果你有這個需求,可以參考 Multicall.js 的實現:

https://github.com/makerdao/multicall.js

「4.4 前端依賴」

大量的重複工作都建立在優秀的開源項目基礎上,在 DApp 編寫過程中,我推薦你使用一些優秀的前端庫來減少工作量,並實現更好的代碼交付質量。

wagmi 是我推薦的核心依賴之一,它提供了豐富的 React hooks 來完成 DApp Web 前端與合約交互的所有流程。它的實現簡單,測試健全,而且沒有多餘的冗餘依賴庫。

https://github.com/tmm/wagmi

談到 React hooks,我也推薦 useDApp,相比於 wagmi,它更加複雜,但默認支持 multicall.js

https://github.com/TrueFiEng/useDApp

如果你的網站將要集成錢包登錄的功能,那你則需要考慮引入 Siwe(Sign-In with Ethereum)它實現了 EIP-4361 中的錢包登錄流程。

https://github.com/spruceid/siwe

如果你的 Next.js DApp 計劃提供多語言版本和檢測,我推薦你使用 i18next 與 react-i18next 與 i18next-browser-languagedetector 這些依賴與 DApp 的核心交互邏輯沒有關係,因此不再贅述。

在 UI 庫方面,我推薦基於 Google Materials UI 設計系統的 MUI 與 NextUI:

https://mui.com/zh

https://nextui.org/

「4.5 客戶端開發」

客戶端開發的方案比較多樣,流行的方案是 React Native(跨平臺)Flutter(跨平臺)Swift(iOS)和 Java (Android) 這些方案都有一些流行的依賴庫可以借鑑。

考慮到 React Native(跨平臺)的實現,它的依賴庫與 React 應當並無差異,可以使用上述針對 React 的方案。

對於 Flutter(跨平臺) 而言,我推薦你參考官方指南

https://ethereum.org/en/developers/docs/programming-languages/dart

其中,我們可以使用 web3dart 來與區塊鏈 Relay 進行通信。

https://pub.dev/packages/web3dart

對於 Swift(iOS)而言,我們可以使用 Argent labs 團隊提供的 web3.swift 方案。

https://github.com/argentlabs/web3.swift

對於 Java (Android) 而言,流行的方案之一是 Web3j。

https://github.com/web3j/web3j

「5. 開發、測試與生產環境調試」

與其他軟件一樣,DApp 在正式上線過程前也會經過幾個環節的調試與測試過程。與其他軟件不同的時,我們通常無法簡單地在本地搭建所有測試環境。

「5.1 開發環境調試」

在「開發工作流與單元測試」章節中,我們提到使用 hardhat node 能夠快速在本地運行一個自動 mining 的區塊鏈調試網絡。那麼,我們如何將每次修改的合約 ABI 同步給前端項目呢?

默認地,hardhat 會將編譯後的 ABI 文件和合約字節碼放在 ./artifacts 文件夾,但不包括合約地址,文件組織方式對前端項目也並不友好。

藉助 hardhat-deploy 插件,我們可以簡單地使用 --export-all 導出所有被髮布的合約 ABI(包含地址信息)爲一個完整的 json 文件,例如:

npx hardhat node --export-all ../website/contracts/localhost.json

這個 json 文件的結構看起來是這樣:

{
  "31337"[
    {
      "name""localhost",
      "chainId""31337",
      "contracts"{
         "Membership"{ address: "...", abi: [...] }
         ...
    }
  ]
}

可以看出,它是一個由 Chain ID(31337 是 hardhat Chain ID)索引的合約 ABI 清單,根據這個清單,我們可以很方便的構建出對應的合約實例並與本地合約進行交互。

注意:前端項目與本地合約進行調試時,請特別注意 Provider/Signer 當前連接的網絡。另外,默認地,hardhat 網絡的區塊確認是即時的,如果你需要模仿公開網絡的行爲,可以在這裏尋找到修改它的配置。

「5.2 測試與生產環境調試」

當我們的合約準備發佈到公開測試網絡,例如 Rinkeby, Kovan, Ropsten 或者 Goerli 時,我們只需要在 hardhat deploy 中指定對應的 network 選項即可:

npx hardhat deploy --network rinkeby

需要注意的是,我們需要確保對應的 deployer 錢包賬戶中有足夠的 Gas Token 餘額,對於上述網絡來說,即是 ETH 餘額。

合約一旦發佈到公開網絡,它的狀態就不受到我們的控制,任何用戶都可以根據我們提供的 ABI 修改某個合約的狀態,因此,如果你需要穩定的,某種狀態的測試合約充當不同版本的測試環境,確保你在發佈之前使用了特殊的權限管理或是地址硬編碼。

如果你不想如此麻煩地管理測試網絡,我推薦你使用 hardhat node --fork URI 功能在本地計算機或者服務器集羣中 fork 主網狀態充當測試環境。你可以在這裏找到它的詳細指南。

公開測試環境中的第三方合約狀態是未可知的,因此,我們需要再三確認調用的地址是否正確。其次,大部分流行的協議或者 DEX 在幾大公開測試網絡都提供了測試合約,包括 OpenSea 在內,部分流行的 DApp 前端也提供了測試網絡的版本,以幫助開發者在發佈到線上網絡前發現問題:

https://testnets.opensea.io/

此外,你需要一些測試 ETH 才能確保公開測試網絡中合約與邏輯正常工作,同時,你的用戶也會需要它們。這裏是一些可以獲得測試 ETH 的網站和服務:

https://faucet.paradigm.xyz/

「6. 服務端編碼與集成」

服務端一直是 DApp 被認爲沒那麼「去中心化」的原因之一。就我所知,世界上絕大多數 DApp 都有服務端 API 提供支持,只有少數類似 Uniswap 這樣的產品,僅依賴前端與合約進行通信。

但絕大多數 DApp 的 Web UI 實現了去中心化。因此,我們需要區分服務端 API 在 DApp 開發中所屬的地位,不能將核心邏輯放在私有服務器中依賴,或者一味使用服務端儲存的機器人錢包私鑰來操作區塊鏈。

我們需要編寫服務端 API 的原因之一是,鏈上狀態儲存的成本過高,以及反覆地簽名與交互對用戶來說體驗不佳。另外一些原因是,一些不重要的,可以被丟棄的數據並不需要放在合約中儲存。

DApp 的服務端 API 並不要求特殊配置。因此,你可以使用任何你喜歡的編程語言運行環境來編寫它。一般來說,我們使用 Node,因爲這樣可以複用一部分 Provider/Signer 的前端業務邏輯。

「6.1 開發環境」

如果你的 DApp 並不複雜,不需要儲存太多狀態,我推薦你使用上述章節提到的 Next.js 方案,它可以直接被 push 到 Vercel,後者將能夠自動地將你 Next.js 項目中的 API 部署到對應的服務環境。

如果你的 DApp 依賴較多的數據庫和服務,我推薦你使用 FaaS,我常用的一個 FaaS 服務是 Firebase,你可以使用 Firebase 快速連接實時數據庫,整合 Twitter 或 GitHub 的第三方登錄,它還提供非常好用的本地模擬器工具套件,以及,它能夠非常好地支持跨平臺。

https://firebase.google.com/docs/web/setup?hl=zh-cn

服務端編碼沒有太多與 DApp 開發相關的內容,但其中有兩個我們需要提及的部分:

接下來我們會簡單介紹這兩部分內容。

「6.2 關於錢包登錄」

許多剛入門 Web3 的開發者會認爲,錢包登錄只需要使用前端腳本連接錢包即可,但這種邏輯很容易被 hack,因爲任何客戶端狀態都能夠被低成本修改。

Siwe(Sign-In with Ethereum)將 EIP-4361 草案引入了以太坊改進協議,目的是標準化開發者使用錢包登錄授權 Off-chain 產品的邏輯。它的流程與 JWT 的發行相似。

Siwe 支持絕大多數編程語言和它們的運行環境(JavaScript, Rust, Python, Golang, Ruby 等)因此你可以直接使用官方提供的依賴庫來完成大部分流程。如果你使用 Next.js 也可以採用 NextAuth 來快速整合它。

簡單來說,Siwe 通過對服務端發行的隨機 nonce 和其他標準輸入進行簽名,再通過服務端驗證簽名內容的方式來確認提交方的地址。

因此,我們需要提供兩個 API 來實現 Siwe,分別是 /nonce 和 /vertify 你可以在這裏找到它們的代碼範例:

https://github.com/spruceid/siwe-quickstart

「6.3 服務端與區塊鏈的通信」

在某些情況下我們需要在服務端進行與區塊鏈 Relay Network 的通信,如果你使用 Node 作爲運行環境,它的邏輯代碼和前端是一致的。

如果服務端只需要使用到 Provider 與 Relay Network 通信(只讀模式),比如,我們通過整合區塊高度,某個合約狀態和訂閱事件,來組建我們自己的數據緩存服務並提供 API,那我們只需要按照前端的方式與 Relay Network 建立通信即可。在這種模式下,我們可以把 access key 存放在生產環境的環境變量中,我推薦你使用 dotenv 去處理它們。

https://github.com/motdotla/dotenv

如果你使用 Next.js 它會自動讀取 dotenv 的環境變量文件,因此你可以在 process.env 中快速使用到它們。這裏是 Next.js 的相關文檔。如果你使用其他服務端框架或 FaaS,你可以自己維護這些環境變量文件。

如果服務端需要使用到 Provider 和 Signer 與 Relay Network 進行通信(讀寫模式)我們就需要好好考慮如何存放機器人錢包的密鑰的問題。

注意:在做這些事情之前,我們應該提前思考爲何需要在服務端使用機器人錢包對某些合約進行寫操作,以及這樣的設計帶來的合約權限問題與安全風險。

我認爲,將私鑰放在環境變量中不是一個好辦法,我們無法控制第三方模塊是否會將環境變量中的內容計入日誌或者遠程統計。因此,我們可以採用專業的私鑰管理服務來管理,例如使用 Google Secret Manager 或者 AWS Secrets Manager 來進行管理,而前者可以與 Firebase 很好地進行整合。

https://cloud.google.com/secret-manager

通過 Provider 和 Signer 與 Relay Network 進行通信只需要傳遞錢包私鑰給對應的 Provider/Signer 實例即可完成操作,與本地進行單元測試的機器人錢包一樣,它不會有額外的確認過程,因此,我們需要確認錢包中有足夠的 ETH 或其他 Gas token 餘額,否則該交易會失敗。

一般來說,如果我們的服務端接口中有對應的合約寫操作,我們不會等待交易完成再返回數據,因此,我們需要返回對應的 tx.hash 方便前端界面處理後續邏輯。

「6.4 實用的 SDK」

一些 SDK 和對應服務可以幫助我們更方便地在服務端與合約進行通信,例如 ThirdWeb。

https://thirdweb.com/

Thirdweb 提供了一個 SaaS 合約開發平臺,你可以通過它的前端 App 發佈預設功能的合約,例如 NFT Drop 或者 NFT 交易平臺,也可以使用它提供的第三方 access key 與已發佈的合約進行通信(而無需依賴合約的 ABI):

// 來自 code祕密花園
import { ThirdwebSDK } from "@thirdweb-dev/sdk";

// The RPC url determines which blockchain you want to connect toconst rpcUrl = "https://polygon-rpc.com/";// instantiate the SDK as read only on a given blockchainconst sdk = new ThirdwebSDK(rpcUrl);

// access your deployed contractsconst nftDrop = sdk.getNFTDrop("0x...");const marketplace = sdk.getMarketplace("0x...");

// Read from your contractsconst claimedNFTs = await nftDrop.getAllClaimed();const listings = await marketplace.getActiveListings();

https://github.com/thirdweb-dev/typescript-sdk

「7. 合約部署方案 L1s & L2」

在 DApp 開發中,與傳統產品最大的不同點之一是我們需要決定將產品核心邏輯的智能合約發佈到那個網絡(或者哪些網絡)這意味着,DApp 需要有「跨平臺跨網絡」的支持能力。

對於傳統互聯網開發人員來說,我們很容易理解「跨平臺」,它是指我們需要爲 App 界面提供 PC Web/Mobile Web,iOS/Android App 的各種版本。「跨網絡」在 DApp 的開發中指的是,我們需要讓 DApp 前端 / 客戶端支持多個區塊鏈網絡。在那之前,我們需要決定哪些區塊鏈網絡是我們首選的發佈環境。

自 2017 年發展至今,目前市場中有大量的區塊鏈網絡供我們使用。按照它們的共識證明種類,可以被分爲 PoS 和 PoW;按照它們的角色定位,可以分爲 L1 與 L2;按照它們對 EVM 兼容的類型,可以分爲 EVM 兼容鏈和非 EVM 兼容鏈。

一般來說,我們可以選擇發行到這些流行的區塊鏈網絡:

  1. Ethereum (ETH) 主網:Gas 費用昂貴,但其中儲存了大量資產,如果你的項目與 NFT 相關,許多人會選擇發佈到主網。

  2. Polygon (Matic):類 ETH 的 PoS 側鏈,EVM 兼容,在許多國家都有一定的用戶基礎,有限的 TPS 支持與可接受的成本,開發者友好。

  3. BNB Chain (BNB): 幣安的區塊鏈網絡,EVM 兼容,開發者友好。

  4. Solana (SOL): 高性能區塊鏈網絡,支持多種編程語言編寫合約,EVM 兼容(使用 Neon)

  5. AVAX C-chain (AVAX):AVAX 的應用鏈,EVM 兼容,提供快速區塊確認,相當程度的 TPS,可以自己搭建 C-chain 作爲應用鏈 sub-chain(例如 DFK Crystalvale)

  6. Cosmos(ATOM): 連接應用鏈的區塊鏈網絡,非 EVM 兼容(Evmos 除外)提供快速的 IBC 跨鏈橋支持,可以自定義應用鏈的 Gas token,適合 GameFi 與需要定製 TPS 的大型應用。

  7. Near (NEAR):提供完善的開發者套件和網頁錢包一整套方案,因此用戶入門難度最低,非 EVM 兼容(使用 Rust 編寫合約)。

  8. StarkNet (ETH Layer2): 使用 zkRollup 技術支持的 L2 網絡,非 EVM 兼容,由 Starkware 提供技術支持(它同時支持了 IMX 與衍生品 DEX DyDx)支持與 L1 的合約進行通信,支持使用 warp 工具將 Sol 代碼轉換爲 Cario 語言的合約代碼。

  9. zkSync2.0 (ETH Layer2): 使用 zkRollup 技術支持的 L2 網絡,同時支持了 DEX ZigZag。支持與 L1 的合約進行通信。

  10. scroll (ETH Layer2): 使用 zkRollup(zkEVM) 技術支持的 L2 網絡。

  11. xDai(Gnosis Chain): 它支持了著名的到場證明合約 POAP。

  12. Harmony(ONE):高性能區塊鏈,它支持了 DFK 的第一個版本。

  13. Dfinity (ICP):一個完整的 DApp 生態系統。

我們可以選擇發佈到某個區塊鏈網絡,或者發佈到所有支持 EVM 兼容的網絡中。不過,不同的區塊鏈網絡中的合約無法直接通信,資產也無法隨意互換(可以採用跨鏈橋合約進行鎖定和重新鑄造)目前,大部分 DApp 只會選擇某一個區塊鏈網絡進行發佈。

如果你的項目涉及到 NFT,我會推薦發佈到 ETH 主網或儲存了相當數量資產的網絡,如果你的項目涉及 GameFi,可以考慮 TPS 高的區塊鏈網絡。如果你考慮 TPS 又同時注重資產安全,可以考慮使用 Layer2 網絡。

我們正處於一個區塊鏈網絡的戰國時代,因此,選擇部署的網絡不存在絕對的最佳實踐,可以參考個人的需求進行選擇。

「8. 去中心化儲存方案」

在 DApp 開發中,我們通常會將資產的元數據、DApp UI 界面等儲存在去中心化儲存網絡當中,以防止單點故障導致的資產損失和不可用。

簡單來說,資產的元數據通常指的是 NFT 中合約儲存的 tokenURI() 返回的內容,它可能是一個 JSON 編碼的字符串,將這些字符串儲存在合約當中需要耗費相當大的成本,因此,最佳實踐是將他們部署到去中心化儲存網絡中,再保存儲存對象的索引 ID(例如 IPFS CID)

流行的去中心化儲存方案有:

由於 IPFS 等方案需要多個節點保持(Pin)儲存對象的狀態,因此,上述服務都有針對開發者的高級包裝儲存服務(類似 AWS S3)我向大家推薦:

如果你考慮爲 NFT 元數據尋找去中心化儲存方案,需要確保 OpenSea 支持它們,否則你的 NFT 與合約將無法正常在 OpenSea 頁面中顯示。OpenSea 目前支持 IPFS 與 Arweave 的儲存協議。

另外,如果你在編寫合約時預先硬編碼寫入 NFT 和其他元數據,可以考慮使用 IPFS CAR(Content-Addressed Archive) 來預先計算他們的 CID hash,這有助於保存一些 OpenSea 要求的非標準數據,例如 NFT 合約描述和 Banner 背景圖地址。

https://car.ipfs.io/

「9. 附錄」

本文中提到的所有項目均列在我的 GitHub Star 清單中,可以在這裏統一查閱:

https://github.com/stars/guo-yu/lists/dapp-best-practice-stack

此外,附錄中列出了許多我認爲有助於幫我們理解 DApp 與智能合約開發的指南,如果你感興趣,可以參閱這些指南(這個列表也會不定時更新):

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YH-GDD_45xumXsdJj_G_cQ