Solidity裏的智能合約是面嚮對象語言裏的類。它們持久存放在狀態變量和函數中,(在裏面)能夠經過solidity修改這些變量。在不一樣的智能合約(實例)中調用一個函數(的過程),(實際上)是在EVM(Ether虛擬機)中完成一次調用,而且完成(一次)上下文切換,(此時)狀態變量是不可訪問的。html
合約能夠從「外部」建立,也能夠由Solidity合約創立。在建立合約時,它的構造函數(函具備與合約名稱同名的函數)將被執行。python
web3.js,即 JavaScript API, 是這樣作的:git
// The json abi array generated by the compiler var abiArray = [ { "inputs":[ {"name":"x","type":"uint256"}, {"name":"y","type":"uint256"} ], "type":"constructor" }, { "constant":true, "inputs":[], "name":"x", "outputs":[{"name":"","type":"bytes32"}], "type":"function" } ]; var MyContract = web3.eth.contract(abiArray);// deploy new contractvar contractInstance = MyContract.new( 10, {from: myAccount, gas: 1000000} ); // The json abi array generated by the compiler 由編譯器生成的json abi 數組 var abiArray = [ { "inputs":[ {"name":"x","type":"uint256"}, {"name":"y","type":"uint256"} ], "type":"constructor" }, { "constant":true, "inputs":[], "name":"x", "outputs":[{"name":"","type":"bytes32"}], "type":"function" } ]; var MyContract = web3.eth.contract(abiArray); // deploy new contract 部署一個新合約 var contractInstance = MyContract.new( 10, {from: myAccount, gas: 1000000} );
在內部,在合約的代碼後要接着有構造函數的參數,但若是你使用web3.js,就沒必要關心這個。github
若是是一個合約要創立另一個合約,被創立的合約的源碼(二進制代碼)要能被創立者知曉。這意味着:循環建立依賴就成爲不可能的事情。web
contract OwnedToken { // TokenCreator is a contract type that is defined below. // It is fine to reference it as long as it is not used // to create a new contract. TokenCreator creator; address owner; bytes32 name; // This is the constructor which registers the // creator and the assigned name. function OwnedToken(bytes32 _name) { owner = msg.sender; // We do an explicit type conversion from `address` // to `TokenCreator` and assume that the type of // the calling contract is TokenCreator, there is // no real way to check that. creator = TokenCreator(msg.sender); name = _name; } function changeName(bytes32 newName) { // Only the creator can alter the name -- // the comparison is possible since contracts // are implicitly convertible to addresses. if (msg.sender == creator) name = newName; } function transfer(address newOwner) { // Only the current owner can transfer the token. if (msg.sender != owner) return; // We also want to ask the creator if the transfer // is fine. Note that this calls a function of the // contract defined below. If the call fails (e.g. // due to out-of-gas), the execution here stops // immediately. if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; }} contract TokenCreator { function createToken(bytes32 name) returns (OwnedToken tokenAddress) { // Create a new Token contract and return its address. // From the JavaScript side, the return type is simply // "address", as this is the closest type available in // the ABI. return new OwnedToken(name); } function changeName(OwnedToken tokenAddress, bytes32 name) { // Again, the external type of "tokenAddress" is // simply "address". tokenAddress.changeName(name); } function isTokenTransferOK( address currentOwner, address newOwner ) returns (bool ok) { // Check some arbitrary condition. address tokenAddress = msg.sender; return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); }} contract OwnedToken { // TokenCreator is a contract type that is defined below. TokenCreator是在下面定義的合約類型 // It is fine to reference it as long as it is not used 若它自己不用於建立新的合約的話,它就是一個引用 // to create a new contract. TokenCreator creator; address owner; bytes32 name; // This is the constructor which registers the 這個是一個登記創立者和分配名稱的結構函數 // creator and the assigned name. function OwnedToken(bytes32 _name) { owner = msg.sender; // We do an explicit type conversion from `address` 咱們作一次由`address`到`TokenCreator` 的顯示類型轉換,,確保調用合約的類型是 TokenCreator, (由於沒有真正的方法來檢測這一點) // to `TokenCreator` and assume that the type of // the calling contract is TokenCreator, there is // no real way to check that. creator = TokenCreator(msg.sender); name = _name; } function changeName(bytes32 newName) { // Only the creator can alter the name -- 僅僅是創立者能夠改變名稱-- // the comparison is possible since contracts 由於合約是隱式轉換到地址上,這種比較是可能的 // are implicitly convertible to addresses. if (msg.sender == creator) name = newName; } function transfer(address newOwner) { // Only the current owner can transfer the token. 僅僅是 僅僅是當前(合約)全部者能夠轉移 token if (msg.sender != owner) return; // We also want to ask the creator if the transfer 咱們能夠詢問(合約)創立者"轉移是否成功" // is fine. Note that this calls a function of the 注意下面定義的合約的函數調用 // contract defined below. If the call fails (e.g. 若是函數調用失敗,(如gas用完了等緣由) // due to out-of-gas), the execution here stops 程序的執行將馬上中止 // immediately. if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; }} contract TokenCreator { function createToken(bytes32 name) returns (OwnedToken tokenAddress) { // Create a new Token contract and return its address. 創立一個新的Token合約,而且返回它的地址 // From the JavaScript side, the return type is simply 從 JavaScript觀點看,返回的地址類型是"address" // "address", as this is the closest type available in 這個是和ABI最接近的類型 // the ABI. return new OwnedToken(name); } function changeName(OwnedToken tokenAddress, bytes32 name) { // Again, the external type of "tokenAddress" is "tokenAddress" 的外部類型也是 簡單的"address". // simply "address". tokenAddress.changeName(name); } function isTokenTransferOK( address currentOwner, address newOwner ) returns (bool ok) { // Check some arbitrary condition. 檢查各類條件 address tokenAddress = msg.sender; return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff); } }
由於Solidity能夠理解兩種函數調用(「內部調用」,不建立一個真實的EVM調用(也稱爲「消息調用」);「外部的調用」-要建立一個真實的EMV調用), 有四種的函數和狀態變量的可見性。編程
函數能夠被定義爲external, public, internal or private,缺省是 public。對狀態變量而言, external是不可能的,默認是 internal。json
external: 外部函數是合約接口的一部分,這意味着它們能夠從其餘合約調用, 也能夠經過事務調用。外部函數f不能被內部調用(即 f()不執行,但this.f()執行)。外部函數,當他們接收大數組時,更有效。數組
**public:**公共函數是合約接口的一部分,能夠經過內部調用或經過消息調用。對公共狀態變量而言,會有的自動訪問限制符的函數生成(見下文)。數據結構
**internal:**這些函數和狀態變量只能內部訪問(即在當前合約或由它派生的合約),而不使用(關鍵字)this 。app
private:私有函數和狀態變量僅僅在定義該合約中可見, 在派生的合約中不可見。
請注意
在外部觀察者中,合約的內部的各項都可見。用 private 僅僅防止其餘合約來訪問和修改(該合約中)信息, 但它對blockchain以外的整個世界仍然可見。
可見性說明符是放在在狀態變量的類型以後,(也能夠放在)參數列表和函數返回的參數列表之間。
contract c { function f(uint a) private returns (uint b) { return a + 1; } function setData(uint a) internal { data = a; } uint public data; }
其餘合約能夠調用c.data()來檢索狀態存儲中data的值,但不能訪問(函數)f。由c派生的合約能夠訪問(合約中)setData(函數),以便改變data的值(僅僅在它們本身的範圍裏)。
編譯器會自動建立全部公共狀態變量的訪問限制符功能。下文中的合約中有一個稱做data的函數,它不帶任何參數的,它返回一個uint類型, 狀態變量的值是data。能夠在聲明裏進行狀態變量的初始化。
訪問限制符函數有外部可見性。若是標識符是內部可訪問(即沒有this),則它是一個狀態變量,若是外部可訪問的(即 有this),則它是一個函數。
contract test { uint public data = 42;}
下面的例子複雜些:
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, uint arg3) returns (uint a, bytes3 b){ a = data[arg1][arg2][arg3].a; b = data[arg1][arg2][arg3].b;}
注意 結構體的映射省略了,由於沒有好的方法來提供映射的鍵值。
修飾符能夠用來輕鬆改變函數的行爲, 例如,在執行的函數以前自動檢查條件。他們是可繼承合約的屬性,也可被派生的合約重寫。
contract owned { function owned() { owner = msg.sender; } address owner; // This contract only defines a modifier but does not use // it - it will be used in derived contracts. // The function body is inserted where the special symbol // "_" in the definition of a modifier appears. // This means that if the owner calls this function, the // function is executed and otherwise, an exception is // thrown. modifier onlyowner { if (msg.sender != owner) throw; _ }}contract mortal is owned { // This contract inherits the "onlyowner"-modifier from // "owned" and applies it to the "close"-function, which // causes that calls to "close" only have an effect if // they are made by the stored owner. function close() onlyowner { selfdestruct(owner); }}contract priced { // Modifiers can receive arguments: modifier costs(uint price) { if (msg.value >= price) _ }}contract Register is priced, owned { mapping (address => bool) registeredAddresses; uint price; function Register(uint initialPrice) { price = initialPrice; } function register() costs(price) { registeredAddresses[msg.sender] = true; } function changePrice(uint _price) onlyowner { price = _price; }} contract owned { function owned() { owner = msg.sender; } address owner; // This contract only defines a modifier but does not use 這個合約僅僅定義了修飾符,但沒有使用它 // it - it will be used in derived contracts. 在派生的合約裏使用 // The function body is inserted where the special symbol ,函數體插入到特殊的標識 "_"定義的地方 // "_" in the definition of a modifier appears. // This means that if the owner calls this function, the 這意味着若它本身調用此函數,則函數將被執行 // function is executed and otherwise, an exception is 不然,一個異常將拋出 // thrown. modifier onlyowner { if (msg.sender != owner) throw; _ } } contract mortal is owned { // This contract inherits the "onlyowner"-modifier from 該合約是從"owned" 繼承的"onlyowner"修飾符, // "owned" and applies it to the "close"-function, which 而且應用到"close"函數, 若是他們存儲owner // causes that calls to "close" only have an effect if // they are made by the stored owner. function close() onlyowner { selfdestruct(owner); } } contract priced { // Modifiers can receive arguments: 修飾符能夠接收參數 modifier costs(uint price) { if (msg.value >= price) _ } } contract Register is priced, owned { mapping (address => bool) registeredAddresses; uint price; function Register(uint initialPrice) { price = initialPrice; } function register() costs(price) { registeredAddresses[msg.sender] = true; } function changePrice(uint _price) onlyowner { price = _price; } }
多個修飾符能夠被應用到一個函數中(用空格隔開),並順序地進行計算。當離開整個函數時,顯式返回一個修飾詞或函數體, 同時在「_」以後緊接着的修飾符,直到函數尾部的控制流,或者是修飾體將繼續執行。任意表達式容許修改參數,在修飾符中,全部函數的標識符是可見的。在此函數由修飾符引入的標識符是不可見的(雖然他們能夠經過重寫,改變他們的值)。
狀態變量能夠聲明爲常量(在數組和結構體類型上仍然不能夠這樣作,映射類型也不能夠)。
contract C { uint constant x = 32*\*22 + 8; string constant text = "abc"; }
編譯器不保留這些變量存儲塊, 每到執行到這個語句時,常量值又被替換一次。
表達式的值只能包含整數算術運算。
一個合約能夠有一個匿名函數。若沒有其餘函數和給定的函數標識符一致的話,該函數將沒有參數,將執行一個合約的調用(若是沒有提供數據)。
此外,當合約接收一個普通的Ether時,函數將被執行(沒有數據)。在這樣一個狀況下,幾乎沒有gas用於函數調用,因此調用回退函數是很是廉價的,這點很是重要。
contract Test { function() { x = 1; } uint x;} // This contract rejects any Ether sent to it. It is good // practise to include such a function for every contract // in order not to loose Ether. contract Rejector { function() { throw; } } contract Caller { function callTest(address testAddress) { Test(testAddress).call(0xabcdef01); // hash does not exist // results in Test(testAddress).x becoming == 1. Rejector r = Rejector(0x123); r.send(2 ether); // results in r.balance == 0 } } contract Test { function() { x = 1; } uint x;} // This contract rejects any Ether sent to it. It is good 這個合約拒絕任何發給它的Ether. // practise to include such a function for every contract 爲了嚴管Ether,在每一個合約裏包含一個這樣的函數,是很是好的作法 // in order not to loose Ether. contract Rejector { function() { throw; } } contract Caller { function callTest(address testAddress) { Test(testAddress).call(0xabcdef01); // hash does not exist hash值不存在 // results in Test(testAddress).x becoming == 1. Test(testAddress).x的結果 becoming == 1 Rejector r = Rejector(0x123); r.send(2 ether); // results in r.balance == 0 結果裏r.balance == 0 } }
事件容許EMV寫日誌功能的方便使用, 進而在dapp的用戶接口中用JavaScript順序調用,從而監聽這些事件。
事件是合約中可繼承的成員。當他們調用時,會在致使一些參數在事務日誌上的存儲--在blockchain上的一種特殊的數據結構。這些日誌和合約的地址相關聯, 將被歸入blockchain中,存儲在block裏以便訪問( 在Frontier 和** Homestead裏是永久存儲,但在Serenity**裏有些變化)。在合約內部,日誌和事件數據是不可訪問的(從建立該日誌的合約裏)。
SPV日誌證實是可行的, 若是一個外部實體提供一個這樣的證實給合約, 它能夠檢查blockchain內實際存在的日誌(但要注意這樣一個事實,最終要提供block的headers, 由於合約只能看到最近的256塊hash值)。
最多有三個參數能夠接收屬性索引,它將對各自的參數進行檢索: 能夠對用戶界面中的索引參數的特定值進行過濾。
若是數組(包括string和 bytes)被用做索引參數, 就會以sha3-hash形式存儲,而不是topic。
除了用anonymous聲明事件以外,事件的指紋的hash值都將是topic之一。這意味着,不可能經過名字來過濾特定的匿名事件。
全部非索引參數將被做爲數據日誌記錄的一部分進行存儲。
contract ClientReceipt { event Deposit( address indexed _from, bytes32 indexed _id, uint _value ); function deposit(bytes32 _id) { // Any call to this function (even deeply nested) can // be detected from the JavaScript API by filtering // for `Deposit` to be called. Deposit(msg.sender, _id, msg.value); } } contract ClientReceipt { event Deposit( address indexed _from, bytes32 indexed _id, uint _value ); function deposit(bytes32 _id) { // Any call to this function (even deeply nested) can 任何對這個函數的調用都能經過JavaScipt API , 用`Deposit` 過濾來檢索到(即便深刻嵌套) // be detected from the JavaScript API by filtering // for `Deposit` to be called. Deposit(msg.sender, _id, msg.value); } }
JavaScript API 的使用以下:
var abi = /\ abi as generated by the compiler /; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at(0x123 /\ address /); var event = clientReceipt.Deposit(); // watch for changes event.watch(function(error, result){ // result will contain various information // including the argumets given to the Deposit // call. if (!error) console.log(result);}); // Or pass a callback to start watching immediately var event = clientReceipt.Deposit(function(error, result) { if (!error) console.log(result); }); var abi = /\ abi as generated by the compiler /; /\ 由編譯器生成的abi /; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at(0x123 /\ address /); /\ 地址 /); var event = clientReceipt.Deposit(); // watch for changes 觀察變化 event.watch(function(error, result){ // result will contain various information 結果包含不一樣的信息: 包括給Deposit調用的參數 // including the argumets given to the Deposit // call. if (!error) console.log(result);}); // Or pass a callback to start watching immediately 或者經過callback馬上開始觀察 var event = clientReceipt.Deposit(function(error, result) { if (!error) console.log(result); });
還能夠經過函數log0 log1,log2,log3 log4到 logi,共i+1個bytes32類型的參數來訪問底層日誌機制的接口。第一個參數將用於數據日誌的一部分,其它的參數將用於topic。上面的事件調用能夠以相同的方式執行。.
log3( msg.value, 0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20, msg.sender, _id );
很長的十六進制數等於
sha3(「Deposit(address,hash256,uint256)」), 這個就是事件的指紋。
理解事件的額外的資源
經過包括多態性的複製代碼,Solidity支持多重繼承。
除非合約是顯式給出的,全部的函數調用都是虛擬的,絕大多數派生函數可被調用。
即便合約是繼承了多個其餘合約, 在blockchain上只有一個合約被建立, 基本合約代碼老是被複制到最終的合約上。
通用的繼承機制很是相似於Python裏的繼承,特別是關於多重繼承方面。
下面給出了詳細的例子。
contract owned { function owned() { owner = msg.sender; } address owner;} // Use "is" to derive from another contract. Derived// contracts can access all non-private members including// internal functions and state variables. These cannot be// accessed externally via `this`, though.contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); }} // These abstract contracts are only provided to make the// interface known to the compiler. Note the function// without body. If a contract does not implement all// functions it can only be used as an interface.contract Config { function lookup(uint id) returns (address adr);}contract NameReg { function register(bytes32 name); function unregister(); } // Multiple inheritance is possible. Note that "owned" is// also a base class of "mortal", yet there is only a single// instance of "owned" (as for virtual inheritance in C++).contract named is owned, mortal { function named(bytes32 name) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).register(name); } // Functions can be overridden, both local and // message-based function calls take these overrides // into account. function kill() { if (msg.sender == owner) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific // overridden function. mortal.kill(); } }} // If a constructor takes an argument, it needs to be// provided in the header (or modifier-invocation-style at// the constructor of the derived contract (see below)).contract PriceFeed is owned, mortal, named("GoldFeed") { function updateInfo(uint newInfo) { if (msg.sender == owner) info = newInfo; } function get() constant returns(uint r) { return info; } uint info; } contract owned { function owned() { owner = msg.sender; } address owner;} // Use "is" to derive from another contract. Derived 用"is"是從其餘的合約裏派生出 // contracts can access all non-private members including 派生出的合約可以訪問全部非私有的成員,包括內部函數和狀態變量。 它們不能從外部用'this'來訪問。 // internal functions and state variables. These cannot be // accessed externally via `this`, though. contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); }} // These abstract contracts are only provided to make the 這些抽象的合約僅僅是讓編譯器知道已經生成了接口, // interface known to the compiler. Note the function 注意:函數沒有函數體。若是合約不作實現的話,它就只能看成接口。 // without body. If a contract does not implement all // functions it can only be used as an interface. contract Config { function lookup(uint id) returns (address adr); } contract NameReg { function register(bytes32 name); function unregister(); } // Multiple inheritance is possible. Note that "owned" is 多重繼承也是能夠的,注意"owned" 也是mortal的基類, 雖然 僅僅有"owned"的單個實例,(和C++裏的virtual繼承同樣) // also a base class of "mortal", yet there is only a single // instance of "owned" (as for virtual inheritance in C++). contract named is owned, mortal { function named(bytes32 name) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).register(name); } // Functions can be overridden, both local and 函數被重寫,本地和基於消息的函數調用把這些override帶入帳戶裏。 // message-based function calls take these overrides // into account. function kill() { if (msg.sender == owner) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific 還能夠調用特定的override函數 // overridden function. mortal.kill(); } }} // If a constructor takes an argument, it needs to be 若是構造器裏帶有一個參數,有必要在頭部給出,(或者在派生合約的構造器裏使用修飾符調用方式modifier-invocation-style(見下文)) // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). contract PriceFeed is owned, mortal, named("GoldFeed") { function updateInfo(uint newInfo) { if (msg.sender == owner) info = newInfo; } function get() constant returns(uint r) { return info; } uint info; }
注意:在上文中,咱們使用mortal.kill() 來「forward」 析構請求。這種作法是有問題的,請看下面的例子:
contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /\ do cleanup 1 清除1 / mortal.kill(); } } contract Base2 is mortal { function kill() { /\ do cleanup 2 清除2 / mortal.kill(); } } contract Final is Base1, Base2 { }
Final.kill() 將調用Base2.kill做爲最後的派生重寫,但這個函數繞開了Base1.kill。由於它不知道有Base1。這種狀況下要使用 super
contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /\ do cleanup 1 清除1 \/ super.kill(); } } contract Base2 is mortal { function kill() { /\ do cleanup 2 清除2 \/ super.kill(); } } contract Final is Base2, Base1 { }
若Base1 調用了super函數,它不是簡單地調用基本合約之一的函數, 它是調用最後繼承關係的下一個基本合約的函數。因此它會調用 base2.kill()(注意,最後的繼承順序是–從最後的派生合約開始:Final, Base1, Base2, mortal, owned)。當使用類的上下文中super不知道的狀況下,真正的函數將被調用,雖然它的類型已經知道。這個和普通的virtual方法的查找類似。
派生的合約須要爲基本構造函數提供全部的參數。這能夠在兩處進行:
contract Base { uint x; function Base(uint _x) { x = _x;} } contract Derived is Base(7) { function Derived(uint _y) Base(_y * _y) { } }
第一種方式是直接在繼承列表裏實現(是 Base(7)),第二種方式是在派生的構造器的頭部,修飾符被調用時實現(Base(_y * _y))。若是構造函數參數是一個常量,而且定義了合約的行爲或描述了它的行爲,第一種方式比較方便。 若是基本構造函數參數依賴於派生合約的構造函數,則必須使用第二種方法。若是在這個荒謬的例子中,這兩個地方都被使用,修飾符樣式的參數優先。
容許多重繼承的編程語言,要處理這樣幾個問題,其中一個是Diamond問題。Solidity是沿用Python的方式, 使用「C3線性化」,在基類的DAG強制使用特定的順序。這致使單調但不容許有一些的繼承關係。特別是,在其中的基礎類的順序是直接的,這點很是重要。在下面的代碼中,Solidity會報錯:「繼承關係的線性化是不可能的」。
contract X {}
contract A is X {}
contract C is A, X {}
這個緣由是,C要求X來重寫A(定義A,X這個順序),但A自己的要求重寫X,這是一個矛盾,不能解決。
一個簡單的規則是要指定基類中的順序,從「最基本」到「最近派生」。
合約函數能夠缺乏實現(請注意,函數聲明頭將被終止),見下面的例子:
contract feline { function utterance() returns (bytes32); }
這樣的合約不能被編譯(即便它們包含實現的函數和非實現的函數),但它們能夠用做基本合約:
contract Cat is feline { function utterance() returns (bytes32) { return "miaow"; } }
若是一個合約是從抽象合約中繼承的,而不實現全部非執行功能,則它自己就是抽象的。
庫和合約相似,可是它們的目的主要是在給定地址上部署,以及用EVM的CALLCODE特性來重用代碼。這些代碼是在調用合約的上下文裏執行的,例如調用合約的指針和調用合約的存儲可以被訪問。因爲庫是一片獨立的代碼,若是它們顯示地提供的話,就僅僅能訪問到調用合約的狀態變量(有方法命名它們)
下面的例子解釋了怎樣使用庫(確保用using for 來實現)
library Set { // We define a new struct datatype that will be used to 咱們定義了一個新的結構體數據類型,用於存放調用合約中的數據 // hold its data in the calling contract. struct Data { mapping(uint => bool) flags; } // Note that the first parameter is of type "storage 注意第一個參數是 「存儲引用」類型,這樣僅僅是它的地址,而不是它的內容在調用中被傳入 這是庫函數的特色, // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic 若第一個參數用"self"調用時很笨的的,若是這個函數能夠被對象的方法可見。 // to call the first parameter 'self', if the function can // be seen as a method of that object. function insert(Data storage self, uint value) returns (bool) { if (self.flags[value]) return false; // already there 已經在那裏 self.flags[value] = true; return true; } function remove(Data storage self, uint value) returns (bool) { if (!self.flags[value]) return false; // not there 不在那裏 self.flags[value] = false; return true; } function contains(Data storage self, uint value) returns (bool) { return self.flags[value]; } } contract C { Set.Data knownValues; function register(uint value) { // The library functions can be called without a 這個庫函數沒有特定的函數實例被調用,由於「instance」是當前的合約 // specific instance of the library, since the // "instance" will be the current contract. if (!Set.insert(knownValues, value)) throw; } // In this contract, we can also directly access knownValues.flags, if we want 在這個合約裏,若是咱們要的話,也能夠直接訪問 knownValues.flags .*}
固然,你沒必要這樣使用庫--他們也能夠事前不定義結構體數據類型,就可使用。 沒有任何存儲引入參數,函數也能夠執行。也能夠在任何位置,有多個存儲引用參數。
Set.contains, Set.insert and Set.remove均可編譯到(CALLCODE)外部合約/庫。若是你使用庫,注意真正進行的外部函數調用,因此`msg.sender再也不指向來源的sender了,而是指向了正在調用的合約。msg.value包含了調用庫函數中發送的資金。
由於編譯器不知道庫將部署在哪裏。這些地址不得不禁linker填進最後的字節碼(見使用命令行編譯器如何使用命令行編譯器連接)。若是不給編譯器一個地址作參數,編譯的十六進制碼就會包含__Set __這樣的佔位符(Set是庫的名字)。經過替換全部的40個字符的十六進制編碼的庫合約的地址,地址能夠手動進行填充。
比較合約和庫的限制:
無狀態變量
不能繼承或被繼承
(這些可能在之後會被解除)
msg.sender的值
msg.sender的值將是調用庫函數的合約的值。
例如,若是A調用合約B,B內部調用庫C。在庫C庫的函數調用裏,msg.sender將是合約B的地址。
表達式LibraryName.functionName() 用CALLCODE完成外部函數調用, 它映射到一個真正的EVM調用,就像otherContract.functionName() 或者 this.functionName()。這種調用能夠一級一級擴展調用深度(最多1024級),把msg.sender存儲爲當前的調用者,而後執行庫合約的代碼,而不是執行當前的合約存儲。這種執行方式是發生在一個徹底嶄新的內存環境中,它的內存類型將被複制,而且不能繞過引用。
原則上使用LibraryName.functionName.value(x)()來轉移Ether。但若使用CALLCODE,Ether會在當前合約裏用完。
指令 using A for B; 可用於附加庫函數(從庫A)到任何類型(B)。這些函數將收到一個做爲第一個參數的對象(像Python中self變量)。
using A for *;,是指函數從庫A附加到任何類型。
在這兩種狀況下,全部的函數將被附加,(即便那些第一個參數的類型與對象的類型不匹配)。該被調用函數的入口類型將被檢查,並進行函數重載解析。
using A for B; 指令在當前的範圍裏是有效的,做用範圍限定在如今的合約裏。但(出了當前範圍)在全局範圍裏就被移除。所以,經過 including一個模塊,其數據類型(包括庫函數)都將是可用的,而沒必要添加額外的代碼。
讓咱們用這種方式重寫庫中的set示例:
// This is the same code as before, just without comments library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) returns (bool) { return self.flags[value]; } } contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; function register(uint value) { // Here, all variables of type Set.Data have // corresponding member functions. // The following function call is identical to // Set.insert(knownValues, value) if (!knownValues.insert(value)) throw; } } // This is the same code as before, just without comments 這個代碼和以前的同樣,僅僅是沒有註釋 library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) returns (bool) { if (self.flags[value]) return false; // already there 已經在那裏 self.flags[value] = true; return true; } function remove(Data storage self, uint value) returns (bool) { if (!self.flags[value]) return false; // not there 沒有 self.flags[value] = false; return true; } function contains(Data storage self, uint value) returns (bool) { return self.flags[value]; } } contract C { using Set for Set.Data; // this is the crucial change 這個是關鍵的變化 Set.Data knownValues; function register(uint value) { // Here, all variables of type Set.Data have 這裏,全部Set.Data 的變量都有相應的成員函數 // corresponding member functions. // The following function call is identical to 下面的函數調用和Set.insert(knownValues, value) 做用同樣 // Set.insert(knownValues, value) if (!knownValues.insert(value)) throw; } } It is also possible to extend elementary types in that way: 這個也是一種擴展基本類型的(方式) library Search { function indexOf(uint[] storage self, uint value) { 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) { data.push(value); } function replace(uint _old, uint _new) { // This performs the library function call 這樣完成了庫函數的調用 uint index = data.find(_old); if (index == -1) data.push(_new); else data[index] = _new; }}
注意:全部的庫函數調用都是調用實際的EVM。這意味着,若是你要使用內存或值類型,就必須執行一次拷貝操做,即便是self變量。拷貝沒有完成的狀況多是存儲引用變量已被使用。
Next Previous
© Copyright 2015, Ethereum. Revision 37381072.
Built with Sphinx using a theme provided by Read the Docs.
若是你但願高效的學習以太坊DApp開發,能夠訪問匯智網提供的最熱門在線互動教程:
其餘更多內容也能夠訪問這個以太坊博客。