以太坊智能合約編寫part6
Mapping與Structs
在Solidty中,Mapping是一種特別的key-value pair的資料類型。Mapping中的每筆record的key(也可以稱為identifier)是使用者的帳號。這個Key(使用者帳號)可以對映到任何一種的資料型態。例如,假設以太坊的用戶會有一個他們自己資產清單。這個資產清單我們可以設置成一個簡單的string value,我們可以建立一個稱為favoriteLists的對照表(如下呈現)。
mapping(address => string) public favoriteLists;
上面的代碼會創建一個稱為favoriteLists的對mapping。這個mapping會將單一個string value對映到以太坊中的每個地址。建立mapping後,我們可以在這個mapping中建立get/set這兩個函數,允許每個使用者設定與讀取這個mapping中的資料。
function setMyFavoriteList(string myFavoriteList) public {
favoriteTokens[msg.sender] = myFavoriteToken;
} //setMyFavotiteToken
function getMyFavoriteList() public view returns (string) {
return myFavoriteLists[mmsg.sender];
} //getMyFavoriteToken
mapping可能非常有用,但是如果我們想將帳號mapping到比簡單的原始資料類型更複雜的資料應該怎麼做呢? 如果我們想要一種將資產清單(owner、asset name和tracks)與每個以太坊中的地址關聯起來應該怎麼做呢? 這就是structs可以上場的地方。
Structs是通過將多個原始資料類型組裝在一起的自定義複雜的資料類型。 依我們的資產清單的資訊來看。 每個資產資料都包含三種原始資料類型 — — 一個string vlaue的owner、一個string value的asset name和一個表示資產數量的無符號整數值。 在 Solidity 中,我們可以為assetList定義一個structs,將這三種資料類型組合成一個單一的複雜資料類型。 然後可以在mapping中使用這種複雜的資料類型,使我們能夠將三種原始資料類型mapping到每個帳號。
第一步
首先,我們將定義一個名為assetList 的new struct,它由我們要觀看的三個屬性組成。 將以下代碼添加到智能合約的頂部。
現在我們有了一個呈現所有三個資產屬性的struct,我們不再需要三個單獨的變量來追踪每個屬性。 所以刪除 owner、assetName 和 tracks 變量。 我們將定義新 assetList struct(currentAsset)的單一個insatnce來代替這三個變量來呈現當前的資產。 此外,我們將建立一個new mapping(userAsset),它將用戶地址對映到 assetList的記錄。 這個mapping將用於追蹤每個用戶自己的投資幣種清單(如下圖)。
第二步:更新assetEvent的定義
接下來,我們要更新 assetEvent 事件的定義。 當我們完成我們的智能合約時,我們將會有兩種不同類型的資產更新。 第一種將改變當前選擇的資產。 第二種資料類型將允許用戶設置他們自己的資產清單資訊。 在這個範例中,這兩種不同的更新類型都會觸發相同的事件,因此讓我們向我們的事件加上一個description string property,我們可以使用它來指示更新事件的類型。 修改 assetEvent 的定義,使其與下面的代碼匹配。
第三步: 更新舊的Reference
在上一步中,我們刪除了owner、asset name和tracks的變量。 我們用名為 currentAsset 的new assetList struct的單一個instance替換了這些變量。 現在我們需要檢查我們的智能合約並更新對這些現已刪除的變量的所有reference以使用我們的new struct instance。 讓我們從constructor函數開始。 更新我們的constructor函數代碼以匹配以下內容。
下一個函數需要更新 getasset函數。 為了使我們的代碼更容易理解,我們將把這個方法重命名為 getCurrentAsset。 更新此函數以使用 currentAsset變量。
最後一個需要更新的函數是 setasset 函數。 為了使我們的代碼更容易理解,我們將把這個方法重命名為 setCurrentAsset。 此功能將需要兩次更新。 第一次更新將與對先前函數執行的更新相同 — 更新代碼以使用 currentAsset 結構變量。 第二個更新是將一些description text包含到從該方法引發的 assetEvent 中。 更新我們的 setCurrentAsset 函數以匹配以下內容。
第四步:取得與設定使用者的資產清單
最後,我們需要向我們的智能合約加入兩個新函數。 第一個,getUsersFavoriteList,將使用 msg.sender 獲取當前用戶的資產訊息。 第二個要添加的函數,setUsersFavoriteList,將使用msg.sender 來設置當前用戶最的資產訊息。 將以下兩種方法添加入到我們的智能合約中。
第五步:測試
接著我們編譯與部署這個智能合約並進行測試。
修改前端程式
在這個前端頁面我們將用三個不同的部分修改頁面上當前的“資產”命名部分 — 一個用於事件狀態訊息,第二個用於顯示當前資產資訊,第三個部分用於顯示當前用戶的個人資產的訊息。我們還新增第二個按鈕,這是讓用戶可以自行新增他們的資產清單。首先修改 index.html 頁面的頂部 div 部分(如下圖)。
更新函數呼叫
將getAsset函數修改成getCurrentAsset(如下圖)。
載入頁面時顯示使用者自己的資產清單
我們也需要頁面顯示使用者的資產清單,新增以下這一段新的代碼。
更新事件監聽器
現在讓我們對 assetEvent 偵聽器進行一些編輯。 我們現在要在頁面的#status 區域中顯示來自此事件的任何資訊。 我們還想確保顯示新加入的事件描述。
更新錯誤事件監聽器
如下所示更新 errorEvent 監聽器,以便它在頁面的#status 區域顯示訊息。
更新Button Handler
我們的#button 點擊事件處理程序仍然呼叫 setasset 函數,但我們已將此函數重命名為 setCurrentAsset。我們需要改成如下代碼:
增加用戶的個人資產按鈕
最後我們要新增一個可以讓用戶自行增加資產的按鈕,新增以下代碼。
之後我們就可以在前端頁面測試所有的相關功能。
Mapping與Arrays
雖然mapping可能是一種非常有用的工具,但它們確實有一個相當大的局限性; 由於它們內部實現的性質,它無法進行迭代。 Solidity 認為mapping的大小是無限的,並且不允許任何代碼進行loop或迭代。 例如,假設我們想要查找將特定資產,並且計算最多用戶所擁有的資產總數。 單獨使用mapping是不可能的。 作為一種解決方法,建立一個傳統array來支援mapping並在兩者之間複製資料是很常見的。 雖然這肯定不是最有效的方法,但這允許在array中進行迭代,並能夠通過使用mapping返回特定於特定帳號的資料。
繼承功能
在以太坊中,智能合約具有從其他合約繼承(inheritance)的能力。 這一個意思是我們的智能合約可以“繼承”另一個合約的所有功能。 這對於我們希望跨多個應用程序重複使用一般性邏輯的場景是有用的 。而這一般性邏輯可以被其他智能合約”繼承”到該合約中。
在這個實作中,我們將建立一個一般性邏輯的智能合約來支援我們的資產智能合約。 我們將在這個一般性合約中放置支援 onlyOwner modifier的功能,因為這是我們希望在多個應用程序中使用到的邏輯。
建立一般性智能合約
在原來的asset1.sol檔案中的最上方新增一個智能合約稱為Utility並且加入以下代碼。
Utility智能合約包含支援 onlyOwner modifier所需的所有邏輯 — local “owner”變量、當某人不是owner時會觸發在代碼中定義的“errorEvent”事件,以及 onlyOwner modifier本身。 基本上繼承這個Utility合約的其他智能合約等同與擁有這個Utility內的所有功能。
修改我們的資產智能合約
在 Utility 智能合約中, 我們已經有了owner 變量、errorEvent definition和 onlyOwner modifier definition。這些在原來的資產合約中是重複的,所以我們需要在資產合約中刪除它們。
定義繼承(Inheritance)
現在我們有兩個獨立的智能合約,但我們沒有將它們捆綁在一起。 我們需要做的最後一件事是告訴 資產合約它是從 Utility 合約衍生的。 在 Solidity 中,這是通過使用“is”關鍵字在合約定義行上完成的(如下圖)。