1. 程式人生 > >區塊鏈Solidity中library的使用和部署

區塊鏈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”中獲取,然後部署到區塊鏈上。