1. 程式人生 > 實用技巧 >第四章:以太坊智慧合約 - 合約

第四章:以太坊智慧合約 - 合約


以太坊智慧合約 - 合約

合約概述

Solidity 中合約和麵向物件語言中的類差不多。合約包含狀態變數(狀態變數的資料儲存在鏈上的區塊中)及函式。 呼叫另一個合約例項的函式時,會執行一個 EVM 函式呼叫 , 這個操作會切換執行時的上下文 ,這時上一個合約的狀態變數就無法訪問了 。

建立合約

合約可以通過發起交易(通過 Web3 發起)或是 Solidity 建立。 在以太坊上動態地建立合約可以使用 JavaScript API Web3.js 的 web3.eth.Contract 來建立合約。

使用 new 建立合約
合約內可以通過 new 關鍵字來建立一個新合約。

pragma solidity ^0.4.0;
contract D {
	uint x;
	function D(uint a) public payable {
		x = a;
	}
}

contract C {
	D d = new D(4);
	function createD(uint arg) public {
		D newD = new D(arg);
	}
	function createAndEndowD(uint arg,uint amount) public payable {
		D newD = (new D).value(amount)(arg);
	}
}

可以在建立的合約中傳送 Ether,但不能限制 gas。 如果建立發生 out-of-stack 或無足夠的餘額, 則會丟擲一個異常。

pragma solidity ^0.4.24;

contract OwnedToken {
    TokenCreator creator;
    address owner;
    bytes32 name;
    
    constructor(bytes32 _name) public {
        owner = msg.sender;
        creator = TokenCreator(msg.sender);
        name = _name;
    }
    function changeName(bytes32 newName) public {
        if (msg.sender == address(creator))
            name = newName;
    }
    function getCreator() returns (address) {
        return creator;
    }
}

contract TokenCreator {
    function createToken(bytes32 name) public returns (OwnedToken tokenAddress) {
        return new OwnedToken(name);        
    }
    function changeName(OwnedToken tokenAddress, bytes32 name) public{
        tokenAddress.changeName(name);
    }
    function isTokenTransferOK(address currentOwner, address newOwner) public view returns (bool ok) {
        address tokenAddress = msg.sender;
        return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
    }
}

Solidity 有兩種函式呼叫方式,一種 是內部呼叫,不會建立 EVM 呼叫(也叫作訊息呼叫),另一種是外部呼叫,會建立 EVM 呼叫。

public - (任意訪問,作為合約介面)可以通過內部呼叫或通過訊息呼叫。對公共狀態變數而言,會有的自動訪問限制符的函式生成。
private - (僅當前合約內)私有函式和狀態變數僅僅在定義該合約中可見, 在派生的合約中不可見。
internal - (僅當前合約及所繼承的合約)這些函式和狀態變數只能內部訪問(即在當前合約或由它派生的合約),而不使用(關鍵字)this 。
external - (僅外部訪問,也是合約介面)它們可以從其他合約呼叫, 也可以通過事務呼叫。外部函式f不能被內部呼叫(在內部也只能用外部訪問方式訪問,即 f()不執行,但this.f()執行)。

在下面的例子中, D 可以呼叫 c.getData()來訪問 data 的值,但不能呼叫 f。 合約 E 繼承自 C,所以它可以訪問 compute 函式。

pragma solidity ^0.4.24;

contract C {
    uint private data;
    
    function f(uint a) private returns (uint) {
        return a + 1;
    }
    function setData(uint a) {
        data = a;
    }
    function getData() public returns (uint) {
        return data;
    }
    
    function compute(uint a,uint b) internal returns (uint) {
        return a + b;
    }
}
contract D {
    function readData() {
        C c = new C ();
        // uint local = c.f(7); // private不可訪問
        c.setData(3);
        uint local = c.getData();
        // local = c.compute(3,5); // 內部函式呼叫
    }
}
// E繼承C
contract E is C{
    function g() {
        C c = new C();
        uint val = compute(3,5);
    }
}

訪問函式(Getter Function)

編譯器會自動為所有的 public 的狀態變數生成訪問函式。

pragma solidity ^0.4.0;
contract C {
	uint public data = 42;
}
contract Caller {
	C c = new C();
	function f() public {
		uint local = c.data();
	}
}

訪問函式有外部可見性。 如果是內部訪問的,則可以直接訪問變數(不用this)。但如果使用外部方式來訪問 (通過 this. ),則必須通過函式的方式來呼叫 。

pragma solidity ^0.4.24;

contract Complex {
    struct Data {
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
    }
    mapping (uint => mapping(bool => Data[])) public data;
}

編譯器會生成以下函式:

function data(uint arg1,bool arg2) public returns (uint a,bytes3 b) {
	a = data[arg1][arg2].a;
	b = data[arg1][arg2].b;
}

函式修改器(Function Modifier)

熟悉Python 的讀者會發現函式修改器的作用和 Python 的裝飾器很相似。

pragma solidity ^0.4.24;

contract owned {
    function owned() public{
        owner = msg.sender;
    }
    address owner;
    // 修改器
    // 定義了一個函式修改器,可被繼承 
    // 修改時, 函式體被插入到 ”_; "處 
    // 不符合條件時,將丟擲異常

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
}
contract mortal is owned {
    function close() public onlyOwner {
        selfdestruct(owner); // 用於銷燬合約。所以只需要暴露出自毀介面即可:
    }
}

contract priced {
    modifier costs (uint price) {
        if (msg.value >= price) {
            _;
        }
    }
}

contract Register is priced, owned {
    mapping (address => bool) registerAddresses;
    uint price;
    
    function Register (uint initialPrice) public {
        price = initialPrice;
    }
    
    function register() public payable costs(price) {
        registerAddresses[msg.sender] = true;
    }
    
    function changePrice(uint _price) public onlyOwner {
        price = _price;
    }
    
    function getPrice() public returns (uint){
        return price;
    }
    
    function getMapping(address addr) returns (bool){
        return registerAddresses[addr];
    }
}

上面的 onlyOwner 就是一個函式修改器。當用這個修改器修飾一個函式時, 函式必須滿足 onlyOwner 的條件才能執行。 這裡的條件是: 函式必須是合約創 建的才能呼叫,否則丟擲異常。

多個函式修改器

如果同一個函式有多個修改器,並且他們之間以空格隔開,那麼修改器會 依次檢查執行。
在修改器中或函式內的顯式的 return語句, 僅僅跳出當前的修改器或函式。 返回的變數會被賦值,但執行流會在前一個修改器後面定義的"_"後繼續執行,

pragma solidity ^0.4.24;

contract Mutex {
    bool locked;
    modifier noReentrancy() {
        require(!locked);
        locked = true;
        _;
        locked = false;
    }
    // 防止遞迴呼叫 
    //return 7 之後, locked = false 依然會執行 
    function f() public noReentrancy returns (uint) {
        require(msg.sender.call());
        return 7;
    }
}

理解修改器的執行次序

pragma solidity ^0.4.24;

contract modifysample {
    uint a = 10;
    modifier mf1 (uint b) {
        uint c = b;
        _;
        c = a;
        a = 11;
    }
    modifier mf2() {
        uint c = a;
        _;
    }
    modifier mf3() {
        a = 12;
        return ;
        _;
        a = 13;
    }
    function test1() mf1(a) mf2 mf3 public {
        a = 1;
    }
    
    function test2() public constant returns (uint) {
        return a;
    }
}

結果是11。
順序表為:

狀態常量

狀態常量可以被定義為 constant。不過表示式有一些限制。

  1. 不允許訪問 storage。
  2. 不允許訪問 區塊鏈資料,如 now、 this.balance、 block.number。
  3. 不允許訪問合約執行中的資料,如 msg.value 或 gasleft()。
  4. 不允許向外部合約發起呼叫 。
    但內建的函式 keccak256、keccak256、ripemd160、ecrecover、addmod、mulmod 可以允許呼叫,即使它們呼叫的是外部合約。 編譯器並不會為常量在 storage 上預留空間, 每個使用的常量都會被對應的 常量表達式所替換。

檢視函式(View Function)

函式可以宣告為 view,表示它不能修改狀態。下面幾種情況被認為是修改了狀態。

  1. 寫狀態變數。
  2. 觸發事件。
  3. 建立其他的合約。
  4. call 呼叫附加了以太幣 。
  5. 呼叫了任何沒有 view 或 pure 修飾的函式。
  6. 使用了低級別的呼叫( low-level call)。
  7. 使用 了包含特定操作符的內聯彙編。
pragma solidity ^0.4.16;
contract C {
	function f(uint a, uint b) public view returns (uint) {
		return a * (b + 42) + now;
	}
}

有幾個地方需要注意一下:

  1. 宣告為 view 和宣告為 constant 是等價的, constant 是 view 的別名 。 constant 計劃在 Solidity 0.5.0 版本之後被棄用 。
  2. 訪問函式都被標記為 view。

純函式(Pure Function)

函式可以宣告為 view, 表示它既不能讀取狀態,也不能修改狀態。

  1. 讀狀態變數。
  2. 訪問了 this.balance 或
    .balance。
  3. 訪問了 block, tx, msg 的成員 Cmsg.sig 和 msg.data 除外)。
  4. 呼叫了任何沒有 pure 修飾的函式。
  5. 使用了包含特定操作符的內聯彙編。
pragma solidity ^0.4.16;
contract C {
	function f(uint a, uint b) public view returns (uint) {
		return a * (b + 42);
	}
}

回退函式(Fallback Function)

一個合約可以有一個(最多也只能有一個)沒有名字的函式。 這個函式無引數,也無返回值。
如果呼叫合約時,沒有匹配上任何一個函式(或者沒有提供資料),則會呼叫回退函式。
另外,儘管回退函式沒有引數,其依然可以使用 msg.data 讀取呼叫時提供 的 payload。

函式過載(Function Overloading)

函式過載是指一個合約可以有多個引數不同的同名函式

pragma solidity ^0.4.16;

contract A{
    function f(uint _in) public pure returns (uint out) {
        out = 1;
    }
    
    function f(uint _in,bytes32 _key) public pure returns (uint out) {
        out = 2;
    }
    
}

有一種情況需要注意,如果僅僅是 Solidity型別不一樣,但在外部介面(ABI) 上表現一樣的話,那麼是無法函式過載的。 例如,下面的例子就是無法編譯的。

pragma solidity ^0.4.24;

contract B{}
contract A{
    function f(B _in) public pure returns (B out) {
        out = _in;
    }
    
    function f(address _in) public pure returns (address out) {
        out = _in;
    }
}

過載的引數匹配問題
過載函式在呼叫時, 會根據呼叫提供的引數的型別去匹配過載函式, 這期間可能會發生隱式的型別轉換。 如果可以匹配上多個過載函式,則會呼叫失敗。 來看一個例子:

pragma solidity ^0.4.24;

contract A{
    function f(uint8 _in) public pure returns (uint8 out) {
        out = _in;
    }
    
    function f(uint256 _in) public pure returns (uint256 out) {
        out = _in;
    }
}

f(50)會失敗, 因為 50 可以隱式轉換為 uint8 和 uint256。 而f(256)則會呼叫 f(uint256), 因為 256 不能轉換為 uint8。 注意: 返回值不參與匹配。

事件

事件主題(Topic)
主題是用來把事件索引化( Index)的數值。沒有主題,就不能搜尋事件 (主題的作用是用來檢索事件的)。一個事件最多可以有四個主題。

第一個主題是事件簽名 ,剩下三個主題是索引化的引數數值(即最多有三個引數接收屬性可以被設定為索引)。設定為主題後,可以允許通過這個引數來 查詢日誌,甚至可以按特定的值過濾。如果引數是字串、位元組或者陣列,那 麼主題是它的 keccak-256 雜湊。

注意: 1. 事件簽名 hash 是其中一個主題,匿名事件除外,這意味著對於匿 名事件無法通過名字來過濾。 2. 被索引的引數將不會儲存它們自己,可以搜尋它們的值,但不能檢索值本身。所有未被索引的引數將被作為日誌的一部分被儲存起來。假設下面這個例子

event PersonCreated(uint indexed age,uint indexed height)
// 通過引數觸發
emit PersonCreated(26,176);

這裡會生成 3 個主題:

  • 0x6bel5e8568869blel00750dd5079151b32637268ec08d199b318b793181b8a7d 它是事件的簽名,計算方法是 Keccak-256(“PersonCreated(uint256, uint256)”)。
  • 0x36383cc9cfbfl dc87c78c2529ae2fcd4e3fc4e575el 54b357ae3a8b2739113cf, 他是年齡 26 的Keccak-256 值。
  • 0x048dd4d5794e69cea63353d940276ad6 lf89c65942226a2bb5bd352536892f82, 它是身高 176 的 Keccak-256 值。
    在節點上就會建立這樣的可搜尋的索引,之後可以在 Web3 中對索引進行 過濾搜尋,比如可以過濾出所有 26 歲的人,只需要使用 以下程式碼:
var createdEvent = myContract.PersonCreated({age: 26});
createdEvent.watch(function(err,result) {
	if(err) {
		console.log(err)
		return;
	}
	console.log("Found",result);
})

底層的日誌介面(Low-level Interface to Log)

通過函式 log0、 log1 、 log2、 log3、 log4 直接訪問底層的日誌。 logi 表示帶 i + 1 個 bytes32 型別的引數, i 表示的就是可帶引數的數目, 從 0 開始計數。
其中第一個引數會被用來作為日誌的資料部分,其他的引數作為主題。

繼承

Solidity繼承使用的是關鍵字 is。Solidity 是通過複製包括多型的程式碼來支援多重繼承的。如果沒有明確指定呼叫哪一個合約的函式,那麼最終被派生的方法通常會被呼叫 。如果一個合約從多個其他合約那裡繼承,那麼在區塊鏈上僅會建立一個合約,並且在父合約裡的程式碼會被複制用來建立被繼承合約。

pragma solidity ^0.4.16;

contract owned {
    function owned() {
        owner = msg.sender;
    }  
    address owner;
}

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Config {
    function loopup(uint id) public returns (address adr);
}

contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
}

contract named is owned,mortal {
    function named(bytes32 name) {
        Config config = Config(0xD5dfD8D94886E70b06E474c3fB14Fd43E2E23970);
        NameReg(config.loopup(1)).register(name);
    }
    function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5dfD8D94886E70b06E474c3fB14Fd43E2E23970);
            NameReg(config.loopup(1)).unregister();
            mortal.kill();
        }
    }
}

contract PriceFeed is owned,mortal,named("GoldFeed") {
    function updateInfo(uint newInfo) public {
        if (msg.sender == owner)
            info = newInfo;
    }
    
    function get() public view returns (uint r) {
        return info;
    }
    uint info;
}

**注意, 上面例子的 kill() 方法中,我們呼叫了 motal.kill() , 呼叫了父合約的 銷燬函式(destruction)。但這可能會引發一些問題,看下面的例子:
**

pragma solidity ^0.4.0;

contract owned {
	function owned() public {
		owner = msg.sender;
	}
	address owner;
}
contract mortal is owned {
	function kill() public {
		if (msg.sender == owner) selfdestruct(owner);
	}
}
contract Base1 is mortal {
	function kill() public {
		mortal.kill();
	}
}

contract Base2 is mortal {
	function kill() public {
		mortal.kill();
	}
}
contract Final is Base1, Base2 {}

對 Final.kill() 的呼叫只會呼叫 Base2.kill() (最後過載的函式),而派生重寫會跳過 Base1.kill, 因為它根本就不知道有 Basel 。一個變通的方法就是使用 super, 看下面的例子:

pragma solidity ^0.4.0;

contract owned {
	function owned() public {
		owner = msg.sender;
	}
	address owner;
}
contract mortal is owned {
	function kill() public {
		if (msg.sender == owner) selfdestruct(owner);
	}
}
contract Base1 is mortal {
	function kill() public {
		super.kill();
	}
}

contract Base2 is mortal {
	function kill() public {
		super.kill();
	}
}
contract Final is Base1, Base2 {}

如果 Base2 呼叫了函式 super,那麼它不僅會呼叫基類的合約函式,還會調 用繼承關係圖譜上的下一個基類合約,所以會呼叫 Basel.kill() 。需要注意的是 最終的繼承圖譜將會是 Final、 Base2、 Basel 、 mortal、 owned。

建構函式(Constructor)

建構函式也叫“構造器氣通常用來完成合約的初始化變數賦值。它是一個 可選函式,建構函式使用關鍵字 constructor 表示。 它僅在建立合約時執行二次。 如果沒有實現建構函式,那麼合約會新增一個預設的建構函式。
建構函式要麼是 public, 要麼是 internal。 如果是 internal,則作為抽象合約。

pragma solidity ^0.4.22;

contract A {
	uint public a;
	constructor (uint _a) internal {
		a = _a;
	}
}

contract B is A(1) {
	constructor() public{}
}

在 Solidity 0.4.22 版本之前,構造是使用與合約同名的函式來實現的,語法如下所示(不過現在這個寫法已經棄用了)。

pragma solidity ^0.4.22;

contract A {
	uint public a;
	constructor (uint _a) internal {
		a = _a;
	}
}

contract B is A(1) {
	constructor B() public{}
}

父合約建構函式的引數

派生的合約需要呼叫所有父合約的建構函式,並且需要提供所有父合約需 要的引數

pragma solidity ^0.4.22;

contract Base {
    uint x;
    constructor(uint _x) public {
        x = _x;
    }
     function getValue() returns (uint){
        return x;
    }
}

// 第1種方式
contract Derived1 is Base(7) {
    uint y;
    constructor (uint _y) public {
        y = _y;
    }
    function getValue() returns (uint){
        return y;
    }
}

//第2種方式
contract Derived2 is Base {
    uint y;
    constructor (uint _y) Base(_y * _y) public {
        y =_y;
    }
     function getValue() returns (uint){
        return y;
    }
}

一種稱為“繼承列表”式,即直接在繼承列表中使用 is Base(7), 示例程式碼 Derived I 合約就是使用這個方式。

另一種稱為“修改器風格”式,示例程式碼 Derived2 合約就是使用這個方式。 Base(_y * _y)就像是一個函式修改器,它會作為修飾派生建構函式的一部分得到執行。

第一種方式對於構造器是常量的情況比較方便,可以直接說明合約的行為。 第二種方式適用於構造的引數值是由派生合約指定的情況。

多繼承與線性化

支援多繼承的程式語言需要解決幾個問題,其中之一就是菱形繼承問題又 稱“鑽石問題”。 Solidity 的解決方案可以參考 Python, 使用 C3 線性化方式來強制將基類合約轉換為一個有向無環圖(DAG)的特定順序。基類合約在 is 後 的順序變得非常重要。

pragma solidity ^0.4.0;
contract X {}
contract A is X {}
contract C is A, X {}

原因是 C 會請求 X 來重寫 A (因為繼承定義的順序是 A、 X),但 A 自身又是重寫 X 的,所以這是一個不可解決的矛盾。
一個簡單的指定基類合約的繼承順序,原則上是從最接近基類到最接近派生類的。

同名問題

如果在繼承時出現一個合約同時存在多個相同名字的修改器或函式,那麼會產生錯誤。 如果事件與修改器重名或函式與事件重名 ,則都會產生錯誤。
例外的是,狀態變數的 getter 可以覆蓋一個 public 函式。

抽象合約(Abstract Contract)

和java抽象一致。
如果一個合約從一個抽象合約裡繼承,但卻沒實現所有函式,那麼它也是 一個抽象合約。
下面是一個函式型別的例子,宣告一個變數。變數的型別是函式:
function (address) external returns (address) foo;
抽象合約分離了定義和實現,但也提供了擴充套件的能力。

介面(Interface)

介面被限制為合約 ABI 定義可以表示的內容, ABI 和介面定義之間應該可以進行轉換而不會有任何資訊丟失。 合約可以繼承介面,就像合約可以繼承其他的合約一樣。

庫與合約類似,它也部署在一個指定的地址上(僅被部署一次,當然程式碼 可以在不同的合約反覆使用),然後通過 EVM 的特性 DELEGATECALL (Homestead 之前是用 CALLCODE)來複用程式碼。 庫函式在被呼叫時,庫程式碼 是在發起合約(下文稱主調合約: 主動發起DELEGATECALL 呼叫的合約) 的 上下文中執行的,使用 this 將會指向主調合約,而且庫程式碼可以訪問主調合約 的儲存(storage)。

對比普通合約來說, 庫有以下的限制。

  1. 無狀態變數。
  2. 不能繼承或被繼承。
  3. 不能接收以太幣。
  4. 不能銷燬一個庫。

不會修改狀態變數(例如被宣告 view 或 pure), 庫函式只能通過直接呼叫 (不用 DELEGATECALL )。這是因為庫被認為是與狀態無關的。

庫有許多使用場景。 其中兩個主要的場景如下:

  1. 如果有許多合約, 它們有一些共同程式碼,則可以把共同程式碼部署成一個 庫。這將節省 gas, 因為 gas 也依賴於合約的規模。因此, 可以把庫想象成使用 其合約的父合約。
  2. 庫可用於給資料型別新增成員函式。(Using for用法)
    由於庫被當成隱式的父合約(它們不會顯式地出現在繼承關係中 ,但呼叫 庫函式和呼叫父合約的方式是非常類似的,如庫 L 有函式 f(),則使用 L.f() 即可訪問 ), 庫裡面的內部函式被複制給使用它的合約。
    同樣按呼叫內部函式的呼叫方式, 這意味著所有內部型別可以傳進去, memory 型別則通過引用傳遞,而不是拷貝的方式。同樣庫裡面的結構體和列舉 也會被複制給使用它的合約。 因此,如果一個庫裡只包含內部函式或結構體或 列舉,則不需要部署庫,因為庫裡面的所有內容都被複制給使用它的合約了 。
pragma solidity ^0.4.16;

library Set {
    struct Data {
        mapping (uint => bool)flags;
    }
    
    function insert(Data storage self, uint value) public returns (bool) {
        if (self.flags[value])
            return false;
        self.flags[value] = true;
        return true;
    }
    function remove(Data storage self, uint value) public returns (bool) {
        if (!self.flags[value]) 
            return false;
        self.flags[value] = false;
        return true;
    }
    
    function conntains(Data storage self,uint value) public view returns (bool){
        return self.flags[value];
    }
}

contract C {
    Set.Data knownValues;
    
    function register(uint value) public {
        // 庫函式不需要例項化就可以呼叫,因為例項就是當前的合約
        require(Set.insert(knownValues,value));
        // 在這個合約中,如果需要的話,可以直接訪問 knownValues.flags
    }
}

我們也可以不按上面的方式來使用庫函式,可以不定義結構體, 可以不使用 storage 型別引用的引數, 還可以在任何位置有多個 storage 引用型別引數。
呼叫 Set.contains、 Set.remove、 Set.insert 會編譯為 以 DELEGATECALL 的 方式呼叫外部合約和庫。 使用庫時,需要注意的是一個真實的外部函式呼叫發生了 。儘管 msg.sender、 msg.value、 this 還會保持它們在主調合約中的值。
下面的例子演示了在庫中如何使用 memo叩 型別和內部函式來實現一個自 定義型別,而不會用到外部函式呼叫 。

pragma solidity ^0.4.16;

library BigInt {
    struct bigint {
        uint[] limbs;
    }
    
    function fromUint(uint x) internal pure returns (bigint r) {
        r.limbs = new uint[](1);
        r.limbs[0] = x;
    }
    
    function limb(bigint _a,uint _limb) internal pure returns (uint) {
        return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
    }
    
    function max(uint a,uint b) private pure returns (uint) {
        return a > b ? a : b;
    }
    
    function add(bigint _a, bigint _b) internal pure returns (bigint r) {
        r.limbs = new uint[](max(_a.limbs.length,_b.limbs.length));
        uint carry = 0;
        for (uint i = 0; i < r.limbs.length; ++i) {
            uint a = limb(_a,i);
            uint b = limb(_b,i);
            r.limbs[i] = a + b + carry;
            if (a + b < a || (a + b == uint(-1) && carry > 0))
                carry = 1;
            else
                carry = 0;
        }
        if(carry > 0) {
            uint[] memory newLimbs = new uint[](r.limbs.length + 1);
            for (i = 0; i< r.limbs.length; ++i) 
                newLimbs[i] = r.limbs[i];
            newLimbs[i] = r.limbs[i];
            r.limbs = newLimbs;
        }
    }
}

contract C {
    using BigInt for BigInt.bigint;
    function f() public pure {
        var x = BigInt.fromUint(7);
        var y = BigInt.fromUint(uint(-1));
        var z = x.add(y);
    }
}

在合約的原始碼中不能新增庫地址,它是在編譯時向編譯器以引數形式提供的。

Using for 指令

指令 using A for B;用來把庫函式(從庫 A)關聯到型別 B。。這些函式將會 把呼叫函式的例項作為第一個引數。 語法與 Python 中的 self 變數一樣。 例如, 庫 A 有函式 add(B b1, B b2),使用 Using A for B 指令後,如果有 B b1 ,就可以使用 b1.add(b2)。
using A for *表示庫 A 中的函式可以關聯到任意的型別上。
using A for B,指令僅在當前的作用域有效, 即目前僅僅支援當前合約的作用域,後續也非常有可能解除這個限制,允許作用到全域性範圍。如果能作用到全域性範圍, 通過引入一些模組(module),資料型別將能通過庫函式擴充套件功能, 而不需要每個地方都寫一遍類似的程式碼了。
以之前的例子來說:

pragma solidity ^0.4.16;

library Set {
    struct Data {
        mapping (uint => bool)flags;
    }
    
    function insert(Data storage self, uint value) public returns (bool) {
        if (self.flags[value])
            return false;
        self.flags[value] = true;
        return true;
    }
    function remove(Data storage self, uint value) public returns (bool) {
        if (!self.flags[value]) 
            return false;
        self.flags[value] = false;
        return true;
    }
    
    function conntains(Data storage self,uint value) public view returns (bool){
        return self.flags[value];
    }
}

contract C {
    using Set for Set.Data; //這是一個關鍵的變化
    Set.Data knownValues;
    
    function register(uint value) public {
        // 庫函式不需要例項化就可以呼叫,因為例項就是當前的合約
    //   Set.insert(knownValues,value);
        require(knownValues.insert(value));
        // require(Set.insert(knownValues,value));
        // 在這個合約中,如果需要的話,可以直接訪問 knownValues.flags
    }
}

同樣可以使用 Using for 的方式來對基本型別(elementa可 type)進行擴充套件

pragma solidity ^0.4.16;

library Search {
	function indexOf(uint[] storage slef, uint value) public view returns (uint) {
		for (uint i = 0; i<self.length; i++) 
			if(self[i] == value) return i;
		return uint(-1);
	}
}

contract C {
	using Search for uint[];
	uint[] data;
	function append(uint value) public {
		data.push(value);
	}
	function replace(uint _old, uint _new) public {
		// 進行庫呼叫
		uint index = data.indexOf(_old);
		if (index == uint(-1))
			data.push(_new);
		else
			data[index] = _new;
	}
}

需要注意的是所有庫呼叫實際上是 EVM 函式呼叫 。 這意味著如果傳的是 memory 型別或者值型別,那麼進行拷貝時即使是 self變數,解決方法也是使用 儲存(storage)型別的引用來避免拷貝內容的。

借鑑https://blog.csdn.net/lj900911/article/details/83037536