區塊鏈Solidity中library的使用和部署
在Solidity中什麼是library?
大家可能聽說過DRY原則(don’t repeat yourself)。在大型程式中,程式碼的重用是十分重要的,並且程式碼重用可以提高整體程式碼的可維護性和可讀性。在solidity具體程式設計的時候,DRY原則可能不像其他語言一樣顯而易見。
Solidity提供了Library的概念來實現程式碼重用,它可以被多個不同的智慧合約呼叫。大家可以把library想象成在面嚮物件語言中的static類中的static函式。 Library在部署到區塊鏈上時是很類似普通的智慧合約的。這也允許大家可以使用別人部署的Library,但要十分小心的是使用非自己建立的、部署的library需要承擔一定的安全風險。
現在我們看看如何構建一個Library並使用它!
建立和使用Library
我們要建立一個使用Library的合約來維護一個由人名到年齡的mapping結構。我們使用library關鍵字來建立一個library,這和建立contract十分類似。但不像contract,在library中我們不能定義任何storage型別的變數。因為library只是意味著程式碼的重用而不是進行state的狀態管理。
// Code for StringToUintMap.sol pragma solidity ^0.4.15; library StringToUintMap { struct Data { mapping (string => uint8) map; } function insert( Data storage self, string key, uint8 value) public returns (bool updated) { require(value > 0); updated = self.map[key] != 0; self.map[key] = value; } function get(Data storage self, string key) public returns (uint8) { return self.map[key]; } }
在以上的library程式碼中,我們定義了一個名為Data的struct,它包含了一個由string到uint8的mapping結構,進行插入和獲取的函式insert和get。我們傳入了storage型別的Data,這樣EVM就不需要在記憶體中再進行建立一個copy,而是直接從storage中傳入一個引用。
讓我們使用這個library建立一個合約來管理這個mapping結構吧!
// Code for PersonsAge.sol pragma solidity ^0.4.15; import { StringToUintMap } from "../libraries/StringToUintMap.sol"; contract PersonsAge { StringToUintMap.Data private _stringToUintMapData; event PersonAdded(string name, uint8 age); event GetPersonAgeResponse(string name, uint8 age); function addPersonAge(string name, uint8 age) public { StringToUintMap.insert(_stringToUintMapData, name, age); PersonAdded(name, age); } function getPersonAge(string name) public returns (uint8) { uint8 age = StringToUintMap.get(_stringToUintMapData, name); GetPersonAgeResponse(name, age); return age; } }
首先我們使用import來匯入我們想使用的library。
import { StringToUintMap } from "../libraries/StringToUintMap.sol";
在合約中,我們建立了一個private的變數StringToUintMap.Data,其型別是一個struct。我們定義了兩個合約函式。第一個是addPersonAge。它的輸入時name和age,然後呼叫StringToUintMap.insert將struct從storage中傳入,最後觸發PersonAdded事件。 函式getPersonAge呼叫了StringToUintMap.get來從mapping中獲取age,然後觸發GetPersonAgeResponse事件。
因此,我們可以看到建立library程式碼並使用它非常簡單。但是問題是,當我們部署合約時,我們需要首先部署library的程式碼,然後在部署合約之前指向已部署的庫地址。這個過程稱為連結。
部署Library和合約
我們使用web3.py庫與區塊鏈進行互動,區塊鏈使用testrpc模擬。
import json
import web3
import random
import time
import os
from web3 import Web3, HTTPProvider
from solc import compile_source
from web3.contract import ConciseContract
# Solidity source code
source_file_name = 'contract.sol'
with open(source_file_name, 'r') as f:
source = f.read()
compiled_sol = compile_source(source) # Compiled source code
contract_id1, contract_interface1 = compiled_sol.popitem()
contract_id2, contract_interface2 = compiled_sol.popitem()
#contract_id, contract_interface = compiled_sol.popitem()
# web3.py instance
w3 = Web3(HTTPProvider('http://localhost:8545'))
# set pre-funded account as sender
w3.eth.defaultAccount = w3.eth.accounts[0]
# Instantiate and deploy contract
Lib = w3.eth.contract(abi=contract_interface1['abi'], bytecode=contract_interface1['bin'])
# Submit the transaction that deploys the contract
tx_hash = Lib.constructor().transact()
# Wait for the transaction to be mined, and get the transaction receipt
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
libraryaddr = tx_receipt.contractAddress
os.system("solc -o ~/ --bin --overwrite --libraries StringToUintMap:"+libraryaddr+" contract.sol")
binfilename = "PersonsAge.bin"
with open(binfilename) as f:
binstr = f.read()
Greeter = w3.eth.contract(abi=contract_interface2['abi'], bytecode=binstr)
# Create the contract instance with the newly-deployed address
tx_hash = Greeter.constructor().transact()
# Wait for the transaction to be mined, and get the transaction receipt
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
greeter = w3.eth.contract(
address=tx_receipt.contractAddress,
abi=contract_interface2['abi'],
)
greeter.functions.addPersonAge("Alice",20).transact({'gasPrice':1,'gas':3000000})
我們將library的code和contract程式碼一起寫入contract.sol檔案。首先Lib被部署到區塊鏈上,然後通過tx_receipt中的contractAddress獲得了部署library的地址。之後呼叫
os.system("solc -o ~/ --bin --overwrite --libraries IncMTree:"+libraryaddr+" contract.sol")
來編譯和連結library庫到contract.sol中的PersonsAge合約。 之後就可以將編譯好的合約的bytecode從“PersonsAge.bin”中獲取,然後部署到區塊鏈上。