在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”中获取,然后部署到区块链上。
网友评论