solidity智能合約

智能合約

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存儲爲當前的調用者,而後執行庫合約的代碼,而不是執行當前的合約存儲。這種執行方式是發生在一個徹底嶄新的內存環境中,它的內存類型將被複制,而且不能繞過引用。

轉移Ether

原則上使用LibraryName.functionName.value(x)()來轉移Ether。但若使用CALLCODE,Ether會在當前合約裏用完。

Using For

指令 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開發,能夠訪問匯智網提供的最熱門在線互動教程:

其餘更多內容也能夠訪問這個以太坊博客

相關文章
相關標籤/搜索