合約

原文鏈接
date:20170710

Solidity中合約的概念和其他面向?qū)ο笳Z(yǔ)言中的類差不多繁涂。他們都有狀態(tài)變量來(lái)存儲(chǔ)永久數(shù)據(jù),有函數(shù)來(lái)改變這些數(shù)據(jù)二驰。調(diào)用不同的合約(實(shí)例)的函數(shù)其實(shí)是執(zhí)行EVM的函數(shù)調(diào)用扔罪,并且會(huì)改變上下文,原上下文中的變量就變得不可訪問了桶雀。

創(chuàng)建合約

合約可以從“外部”創(chuàng)建或者從Solidity合約中創(chuàng)建矿酵。當(dāng)合約創(chuàng)建的時(shí)候,合約的構(gòu)造函數(shù)(跟合約的名稱相同)就會(huì)執(zhí)行一次矗积。

構(gòu)造函數(shù)是可選的全肮。但是只允許有一個(gè)構(gòu)造函數(shù),這意味著不支持重載棘捣。

web3.js中辜腺,例如javascript API,如下所示:

// 需要指定源碼,包含合約名稱(哪自?Need to specify some source including contract name for the data param below)
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";

// 編譯器生成的json格式的abi 數(shù)組
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(source);
MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition);
// 發(fā)布新合約
var contractInstance = MyContract.new(
    10,
    11,
    {from: myAccount, gas: 1000000}
);

在內(nèi)部,構(gòu)造函數(shù)的參數(shù)在構(gòu)造函數(shù)代碼創(chuàng)建之后才被傳遞進(jìn)去禁熏,但是如果你是使用web3.js壤巷,你就無(wú)須關(guān)心這個(gè)問題。

如果一個(gè)合約想要?jiǎng)?chuàng)建另一個(gè)合約瞧毙,那么必須要給創(chuàng)建者提供創(chuàng)建合約的源碼(和二進(jìn)制數(shù)據(jù))胧华。這意味這循環(huán)依賴創(chuàng)建是不可行的。

pragma solidity ^0.4.0;

contract OwnedToken {
    // TokenCreator 是一個(gè)合約類型數(shù)據(jù)宙彪,在下面代碼中定義
    // 當(dāng)他沒有被用來(lái)創(chuàng)建一個(gè)合約的時(shí)候矩动,這樣引用是沒有問題的。
    TokenCreator creator;
    address owner;
    bytes32 name;

    // 這是一個(gè)構(gòu)造函數(shù)释漆,在其中注冊(cè)了創(chuàng)建者悲没,并給name變量賦值。
    function OwnedToken(bytes32 _name) {
        // 狀態(tài)變量可以通過變量名來(lái)訪問男图,而不是通過this.owner.
        // 這個(gè)準(zhǔn)則也同樣適用于函數(shù)示姿,尤其是在構(gòu)造函數(shù)中。
        // 你只能通過“內(nèi)部調(diào)用”的方式來(lái)調(diào)用他們逊笆。
        // 因?yàn)楹霞s本身都沒有創(chuàng)建栈戳。
        owner = msg.sender;
        // 我們通過顯式類型轉(zhuǎn)換將`address`轉(zhuǎn)換為`TokenCreator`類型。
        // 并且假設(shè)調(diào)用合約的類型是TokenCreator难裆。其實(shí)并沒有方法來(lái)檢驗(yàn)的子檀。
        creator = TokenCreator(msg.sender);
        name = _name;
    }

    function changeName(bytes32 newName) {
        // 只有創(chuàng)建者才能改變名稱。
        // 以下的比較是可行的乃戈,因?yàn)楹霞s隱式的轉(zhuǎn)換為地址類型.
        if (msg.sender == address(creator))
            name = newName;
    }

    function transfer(address newOwner) {
        // 只有當(dāng)前擁有者才能交易token
        if (msg.sender != owner) return;
        // 交易之后褂痰,我們同樣要知道是否交易成功。
        // 注意偏化,這里調(diào)用了其他函數(shù)脐恩,函數(shù)在下面定義。
        // 如果調(diào)用失敗侦讨,例如gas不足驶冒,執(zhí)行會(huì)馬上在此停止。
        if (creator.isTokenTransferOK(owner, newOwner))
            owner = newOwner;
    }
}

contract TokenCreator {
    function createToken(bytes32 name)
       returns (OwnedToken tokenAddress)
    {
        // 創(chuàng)建新的Token合約韵卤,并返回他的地址骗污。
        // 在Javascript側(cè),返回的類型只是簡(jiǎn)單的`address`
        // 因?yàn)樵贏BI中最接近的可用的類型沈条。
        return new OwnedToken(name);
    }

    function changeName(OwnedToken tokenAddress, bytes32 name) {
        // 同樣, 外部類型"tokenAddress"只是簡(jiǎn)單的"address"類型.
        tokenAddress.changeName(name);
    }

    function isTokenTransferOK(
        address currentOwner,
        address newOwner
    ) returns (bool ok) {
        // 監(jiān)測(cè)任意條件
        address tokenAddress = msg.sender;
        return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
    }
}

可見性和getter

我們知道需忿,函數(shù)調(diào)用有兩種方式(內(nèi)部調(diào)用并不會(huì)產(chǎn)生真實(shí)的EVM調(diào)用(也被成為消息調(diào)用)但是外部函數(shù)會(huì)),但是函數(shù)和狀態(tài)變量的可見性有四種。
函數(shù)可以被指定為external,public,internal或者private屋厘,默認(rèn)是public的涕烧。對(duì)于狀態(tài)變量,external是非法的汗洒,默認(rèn)是internal议纯。

external:
外部函數(shù)是合約接口的一部分,意味著可以通過交易從其他合約中調(diào)用溢谤。外部函數(shù)f不能在內(nèi)部被調(diào)用(例如瞻凤,f()是不可行的,但是this.f()是可以的)世杀。有時(shí)候外部函數(shù)的執(zhí)行效率會(huì)更高阀参,當(dāng)他們收到很大的數(shù)組數(shù)據(jù)的時(shí)候。

public:
公有函數(shù)是合約接口的一部分瞻坝,可以通過內(nèi)部或者消息來(lái)調(diào)用蛛壳。對(duì)于公有的狀態(tài)變量许饿,會(huì)自動(dòng)創(chuàng)建getter函數(shù)烁试。

internal:
這些函數(shù)和變量只能在內(nèi)部調(diào)用(例如涣易,從當(dāng)前合約或者從該合約派生的合約)纤掸,不需要this前綴一屋。

private:
私有函數(shù)和狀態(tài)變量只能在他們定義的合約中可見菌湃,在派生的合約中不可見慎宾。

注意:合約里的所有都對(duì)外部觀察者可見狡汉。private只是為了阻止其他合約訪問和改變信息蒸矛,但是可以區(qū)塊鏈中可見瀑罗。

可見性指示在類型和變量名之間,對(duì)于函數(shù)雏掠,可見性的位置在參數(shù)列表和返回值之間斩祭。

pragma solidity ^0.4.0;

contract C {
    function f(uint a) private returns (uint b) { return a + 1; }
    function setData(uint a) internal { data = a; }
    uint public data;
}

在以下的例子中,合約D可以調(diào)用c.getData()來(lái)獲取data中的數(shù)據(jù)乡话。但是不能調(diào)用f函數(shù)摧玫。合約E是從合約C派生而來(lái),因此绑青,可以調(diào)用compute诬像。

// 注釋不會(huì)被編譯

pragma solidity ^0.4.0;

contract C {
    uint private data;

    function f(uint a) private returns(uint b) { 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); // 錯(cuò)誤,f不可見
        c.setData(3);
        local = c.getData();
        local = c.compute(3, 5); // 錯(cuò)誤"compute" 不可見
    }
}


contract E is C {
    function g() {
        C c = new C();
        uint val = compute(3, 5);  // acces to internal member (from derivated to parent contract)
    }
}
getter函數(shù)

編譯器會(huì)給所有的公有變量生成getter函數(shù)闸婴。以下的合約坏挠,編譯器會(huì)生成一個(gè)data函數(shù),沒有其他參數(shù)并返回uint類型的data變量的值邪乍。變量在定義的時(shí)候會(huì)進(jìn)行初始化降狠。

pragma solidity ^0.4.0;

contract C {
    uint public data = 42;
}

contract Caller {
    C c = new C();
    function f() {
        uint local = c.data();
    }
}

getter函數(shù)在外部可見对竣。如果通過內(nèi)部調(diào)用的方式訪問(沒有this),就會(huì)當(dāng)作是狀態(tài)變量榜配。如果通過外部的方式調(diào)用(有this)否纬,就會(huì)當(dāng)作是函數(shù)。

pragma solidity ^0.4.0;

contract C {
    uint public data;
    function x() {
        data = 3; //內(nèi)部訪問
        uint val = this.data(); // 外部訪問
    }
}

以下的例子會(huì)更加復(fù)雜:

pragma solidity ^0.4.0;

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

將會(huì)生成如下函數(shù):

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;
}

注意蛋褥,結(jié)構(gòu)體中的mapping被忽略了烦味,因?yàn)闆]有好的方法來(lái)為mapping提供key。

函數(shù)修改器(function modifiers)

修改器可以用來(lái)輕易的改變函數(shù)的執(zhí)行效果壁拉。例如,他們可以在執(zhí)行函數(shù)之前自動(dòng)的檢查先決條件柏靶。修改器可以繼承弃理,并且可以被派生合約復(fù)寫。

pragma solidity ^0.4.11;

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 {
        require(msg.sender == owner);
        _;
    }
}


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; }

    // It is important to also provide the
    // "payable" keyword here, otherwise the function will
    // automatically reject all Ether sent to it.
    function register() payable costs(price) {
        registeredAddresses[msg.sender] = true;
    }

    function changePrice(uint _price) onlyOwner {
        price = _price;
    }
}

contract Mutex {
    bool locked;
    modifier noReentrancy() {
        require(!locked);
        locked = true;
        _;
        locked = false;
    }

    /// This function is protected by a mutex, which means that
    /// reentrant calls from within msg.sender.call cannot call f again.
    /// The `return 7` statement assigns 7 to the return value but still
    /// executes the statement `locked = false` in the modifier.
    function f() noReentrancy returns (uint) {
        require(msg.sender.call());
        return 7;
    }
}

函數(shù)可以有多個(gè)修改器屎蜓,通過空格隔開痘昌。修改器的執(zhí)行順序?yàn)榕帕许樞颉?/p>

警告:在Solidity早期版本中,在函數(shù)中的return表達(dá)式有修改器的會(huì)有不同的執(zhí)行效果炬转。

Explicit returns from a modifier or function body only leave the current modifier or function body辆苔。返回變量被賦值并且控制流會(huì)在先前修改器的“ _ ”符號(hào)之后繼續(xù)。

修改器允許任意表達(dá)式扼劈,所有對(duì)函數(shù)可見的符號(hào)對(duì)于修改器都是可見的驻啤。修改器中引入的符號(hào)在函數(shù)中并不可見(因?yàn)樗麄兛赡軙?huì)被復(fù)寫)。

靜態(tài)狀態(tài)變量

狀態(tài)變量可以被聲明為constant荐吵。在這個(gè)情況下骑冗,變量必須在編譯的時(shí)候被表達(dá)式賦于靜態(tài)的值。所有訪問storage先煎,區(qū)塊鏈數(shù)據(jù)(如now,this.balance或者block.number)或者執(zhí)行數(shù)據(jù)(msg.gas)或者調(diào)用外部合約都是不允許的贼涩。在內(nèi)存分配上可能會(huì)有單邊效應(yīng)的表達(dá)式是允許的,但是那些對(duì)其他內(nèi)存對(duì)象有單邊效應(yīng)的是不允許的薯蝎。(遥倦?Expressions that might have a side-effect on memory allocation are allowed, but those that might have a side-effect on other memory objects are not. )內(nèi)建函數(shù)keccak256,sha256,ripemd160,ecrecover,addmodmulmod是允許的(盡管他們調(diào)用了外部合約)。

允許內(nèi)存分配單邊效應(yīng)的原因是它可以構(gòu)建更加復(fù)雜的對(duì)象占锯,例如查找表袒哥。這個(gè)功能還沒有完全可用。

編譯器不會(huì)對(duì)這些變量保留內(nèi)存片烟央,并且每個(gè)靜態(tài)變量都會(huì)被替換為各自的靜態(tài)表達(dá)式(優(yōu)化器會(huì)計(jì)算表達(dá)式的值)统诺。

現(xiàn)在還沒有實(shí)現(xiàn)所有的靜態(tài)類型。支持的類型是值類型和字符串疑俭。

pragma solidity ^0.4.0;

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
}

靜態(tài)函數(shù)

函數(shù)可以被聲明為靜態(tài)的粮呢,這種情況下,它們保證不回修改狀態(tài)。(啄寡?Functions can be declared constant in which case they promise not to modify the state.)

pragma solidity ^0.4.0;

contract C {
    function f(uint a, uint b) constant returns (uint) {
        return a * (b + 42);
    }
}

注意:getter函數(shù)被標(biāo)記為constant豪硅。

警告:編譯器不回強(qiáng)制要求靜態(tài)函數(shù)返回值不會(huì)修改。

回調(diào)函數(shù)

合約可以有一個(gè)沒命名的函數(shù)挺物。這個(gè)函數(shù)沒有參數(shù)也不能返回任何值懒浮。這個(gè)函數(shù)在合約被調(diào)用但是合約內(nèi)沒有該函數(shù)(或者不能提供該數(shù)據(jù))的時(shí)候執(zhí)行。
另外识藤,無(wú)論何時(shí)砚著,合約收到了以太幣(沒有數(shù)據(jù)),這個(gè)函數(shù)會(huì)被執(zhí)行痴昧。在這個(gè)上下文中稽穆,這個(gè)函數(shù)調(diào)用只會(huì)消耗掉少量的gas(精確來(lái)說(shuō),是2300gas)赶撰,所以將回調(diào)函數(shù)的開銷降到最低是很重要的舌镶。
另外,下面的操作會(huì)消耗掉更多的gas豪娜。

  • 寫數(shù)據(jù)到storage
  • 創(chuàng)建合約
  • 調(diào)用外部函數(shù)餐胀,該外部函數(shù)可能消耗更多的gas
  • 發(fā)送以太幣

在發(fā)布合約之前,請(qǐng)保證你已經(jīng)對(duì)你的回調(diào)函數(shù)進(jìn)行了充分的測(cè)試瘤载,來(lái)保證執(zhí)行開銷小于2300gas否灾。

警告: 合約收到了以太幣(沒有調(diào)用函數(shù),例如使用send或者transfer)但是沒有定義回調(diào)函數(shù)會(huì)拋出異常鸣奔,并返回以太幣(Solidity v0.4.0之前會(huì)不同)坟冲。所以如果你想要讓合約接收以太幣,你必須要實(shí)現(xiàn)一個(gè)回調(diào)函數(shù)溃蔫。

pragma solidity ^0.4.0;

contract Test {
    // 任何消息發(fā)給該合約都會(huì)執(zhí)行該函數(shù)健提。(合約內(nèi)沒有其他函數(shù))。
    // 給這個(gè)合約發(fā)送以太幣會(huì)產(chǎn)生異常伟叛。
    // 因?yàn)檫@個(gè)函數(shù)沒有實(shí)現(xiàn)payable修改器私痹。
    function() { x = 1; }
    uint x;
}


// 這個(gè)合約會(huì)保留所有發(fā)送到該合約的以太幣,且無(wú)法取回
contract Sink {
    function() payable { }
}


contract Caller {
    function callTest(Test test) {
        test.call(0xabcdef01); // 哈希不存在
        // 導(dǎo)致 test.x 賦值為1.

        // 下面的代碼不會(huì)被編譯统刮。即使有人發(fā)送以太幣到這個(gè)合約紊遵,
        // 交易也會(huì)失敗并退回以太幣
        //test.send(2 ether);
    }
}

事件

事件機(jī)制可以很方便的使用EVM的日志服務(wù),它可以反過來(lái)用于調(diào)用去中心化的用戶接口的javascript回調(diào)函數(shù)侥蒙。
事件可以繼承暗膜。當(dāng)它們被調(diào)用的時(shí)候,它們會(huì)導(dǎo)致參數(shù)保存在交易日志里--區(qū)塊鏈中一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu)鞭衩。這些日志和合約地址相互關(guān)聯(lián)学搜。并且會(huì)被納入到區(qū)塊鏈中娃善,將數(shù)據(jù)一致保持(在Frotier和homestead階段會(huì)一致保存,但是在Serenity階段可能會(huì)修改)瑞佩。日志和事件數(shù)據(jù)在合約(即使是父合約)中是不可訪問的聚磺。

日志的SPV(簡(jiǎn)單支付驗(yàn)證)證明是可行的,所以如果外部的實(shí)體提供了一個(gè)合約來(lái)提供證明炬丸,它可以用來(lái)證明日志確確實(shí)實(shí)存在于區(qū)塊鏈中瘫寝。但是需要注意,區(qū)塊頭必須要提供稠炬,因?yàn)槲覀冎荒茉L問最近的256塊區(qū)塊的hash焕阿。

如果參數(shù)數(shù)量大于3個(gè),就會(huì)有一個(gè)indexed屬性首启,該屬性可以使得參數(shù)可以被索引到:這可以在用戶接口中實(shí)現(xiàn)過濾特定的值捣鲸。
如果數(shù)組(包含stringbytes)可以被用來(lái)索引參數(shù),它的Keccak-256hash值會(huì)保存下來(lái)作為主題闽坡。(?If arrays (including string and bytes) are used as indexed arguments, the Keccak-256 hash of it is stored as topic instead.)

事件簽名的hash作為主題(topic)之一愁溜,除非該事件被聲明為匿名anonymous疾嗅。這意味著無(wú)法通過名字來(lái)過濾匿名事件。
所有非索引的參數(shù)將會(huì)記錄在日志的數(shù)據(jù)部分冕象。

注意:索引參數(shù)不會(huì)被自動(dòng)保存代承。你只可以搜索值,但是不可以獲取值渐扮。(论悴?
Indexed arguments will not be stored themselves. You can only search for the values, but it is impossible to retrieve the values themselves.)

pragma solidity ^0.4.0;

contract ClientReceipt {
    event Deposit(
        address indexed _from,
        bytes32 indexed _id,
        uint _value
    );

    function deposit(bytes32 _id) payable {
        // 該函數(shù)的任何調(diào)用(即使是深度嵌套)都可以通過過濾關(guān)鍵字 `Deposit`,
        // 在Javascript的API端檢索到。
        Deposit(msg.sender, _id, msg.value);
    }
}

Javascript API的使用如下所示:

var abi = /* abi 是編譯器生成的 */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at(0x123 /* 地址 */);

var event = clientReceipt.Deposit();

// 等待改變
event.watch(function(error, result){
    // result會(huì)包含很多信息墓律,包括調(diào)用Deposit函數(shù)的參數(shù)膀估。
    if (!error)
        console.log(result);
});

// 或者傳遞一個(gè)回調(diào)函數(shù),馬上開始監(jiān)聽
var event = clientReceipt.Deposit(function(error, result) {
    if (!error)
        console.log(result);
});
日志低級(jí)接口

也可以通過函數(shù)調(diào)用來(lái)訪問日志機(jī)制的低級(jí)接口耻讽,函數(shù)log0察纯,log1log2针肥,log3log4饼记。logi記錄了i+1個(gè)bytes32類型的參數(shù),第一個(gè)參數(shù)是用來(lái)記錄日志的數(shù)據(jù)部分慰枕。其他則用來(lái)記錄主題(topics)具则。上面例子的事件記錄可以用log來(lái)實(shí)現(xiàn)同樣的功能:

log3(
    msg.value,
    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
    msg.sender,
    _id
);

一大長(zhǎng)串的十六進(jìn)制數(shù)與keccak256("Deposit(address,hash256,uint256)")相同。是事件的簽名具帮。

用來(lái)理解事件的其他資源

繼承

Solidity支持多繼承博肋,通過拷貝代碼低斋,包括多態(tài)。
所有的函數(shù)調(diào)用是虛擬的束昵,這意味著多數(shù)的派生函數(shù)會(huì)被調(diào)用拔稳,除非合約名稱被指定。
當(dāng)合約繼承自多個(gè)合約锹雏,只會(huì)在區(qū)塊鏈中創(chuàng)建一個(gè)合約巴比。所有依賴的合約的代碼會(huì)被拷貝進(jìn)創(chuàng)建的合約。
繼承系統(tǒng)和python的繼承系統(tǒng)相似,尤其是多重繼承礁遵。
詳情參看以下例子:

pragma solidity ^0.4.0;

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


// 使用 "is" 關(guān)鍵字來(lái)繼承自其他合約轻绞。
// 派生合約可以訪問其他所有非私有的成員變量,包括內(nèi)部函數(shù)和狀態(tài)變量
// 盡管它們不能通過`this`佣耐,從外部訪問政勃。
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}


// 這些抽象合約只給編譯器提供了接口。注意兼砖,函數(shù)沒有函數(shù)體奸远。
// 如果合約沒有實(shí)現(xiàn)所有的函數(shù),只能當(dāng)作接口使用讽挟。
contract Config {
    function lookup(uint id) returns (address adr);
}


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


// 多重繼承是可行的懒叛。注意”owned“也是“mortal”的基類,
// 但是只有一個(gè)“owner”實(shí)例(類似C++的虛擬繼承)
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
        NameReg(config.lookup(1)).register(name);
    }

    // 函數(shù)可以被另一個(gè)具有相同名字和參數(shù)的函數(shù)復(fù)寫耽梅。
    // 如果復(fù)寫的函數(shù)有不同的返回值薛窥,就會(huì)產(chǎn)生錯(cuò)誤。
    // 所有本地的或者基于消息的函數(shù)調(diào)用會(huì)把這些復(fù)寫寫入賬戶中(眼姐?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();
            // 這可能調(diào)用一個(gè)特定的復(fù)寫函數(shù)
            mortal.kill();
        }
    }
}


// 如果構(gòu)造函數(shù)有一個(gè)參數(shù)诅迷,這需要在開頭提供 (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;
}

注意上面的代碼,我們調(diào)用mortal.kill()來(lái)前向破壞請(qǐng)求众旗。這種做法是有問題的罢杉,參看下面的例子。(贡歧?Note that above, we call mortal.kill() to “forward” the destruction request. The way this is done is problematic, as seen in the following example:)

pragma solidity ^0.4.0;

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

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

contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ mortal.kill(); }
}


contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ mortal.kill(); }
}


contract Final is Base1, Base2 {
}

調(diào)用final.kill()會(huì)調(diào)用Base2.kill屑那,因?yàn)樗亲罱淮螐?fù)寫。但是函數(shù)還是會(huì)跳過Base1.kill艘款,根本上來(lái)說(shuō)持际,因?yàn)樗恢?code>Base1的存在。解決方法是通過super關(guān)鍵字:

pragma solidity ^0.4.0;

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

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


contract Base1 is mortal {
    function kill() { /* do cleanup 1 */ super.kill(); }
}


contract Base2 is mortal {
    function kill() { /* do cleanup 2 */ super.kill(); }
}


contract Final is Base2, Base1 {
}

如果Base1調(diào)用super函數(shù)哗咆,他不是簡(jiǎn)單的調(diào)用一個(gè)父類的函數(shù)蜘欲。而是,會(huì)調(diào)用最近一次繼承的函數(shù)晌柬,所以會(huì)調(diào)用Base2.kill()(注意最后繼承序列是從派生合約開始:Final姥份,Base1郭脂,Base2,mortal澈歉,owned)展鸡。當(dāng)在執(zhí)行上下文中無(wú)法追蹤父函數(shù)的時(shí)候,使用的函數(shù)是真正執(zhí)行的函數(shù)埃难。(莹弊?The actual function that is called when using super is not known in the context of the class where it is used, although its type is known. )這類似于普通的虛擬函數(shù)查找過程。

父合約的參數(shù)

派生合約需要提供父合約構(gòu)造函數(shù)所需的所有參數(shù)涡尘∪坛冢可以通過如下兩種方式實(shí)現(xiàn):

pragma solidity ^0.4.0;

contract Base {
    uint x;
    function Base(uint _x) { x = _x; }
}


contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) {
    }
}

一種方法是直接通過繼承列表(is Base(7))。另一個(gè)方法是通過修改器來(lái)調(diào)用父合約代碼(Base(_y * _y))考抄。如果參數(shù)是一個(gè)常數(shù)细疚,第一種方法更加方便,并且定義合約的行為或者表述川梅。(疯兼?The first way to do it is more convenient if the constructor argument is a constant and defines the behaviour of the contract or describes it. )第二種方法用于構(gòu)造函數(shù)的參數(shù)是基于派生合約的時(shí)候。如果贫途,在當(dāng)前的例子中吧彪,兩種方法都用了,修改器方式的會(huì)優(yōu)先潮饱。

多重繼承和線性化(Multiple Inheritance and Linearization)

支持多重繼承的語(yǔ)言必須解決幾個(gè)問題。第一個(gè)問題是鉆石問題诫给。Solidity走python的路香拉,并使用C3線性化來(lái)強(qiáng)制指定父類DAG的順序。這達(dá)成了期望的單調(diào)性中狂,但是會(huì)損失一些繼承圖表凫碌。尤其是父類中is指令之后的順序是非常重要的。在如下的代碼中胃榕,Solidity將生成線性化不可實(shí)現(xiàn)的錯(cuò)誤盛险。

// 這不會(huì)被編譯

pragma solidity ^0.4.0;

contract X {}
contract A is X {}
contract C is A, X {}

原因是合約C期望合約X來(lái)復(fù)寫合約A(通過指定繼承順序A,X),但是A本身要求復(fù)寫X勋又,這個(gè)矛盾就是不可以解決的苦掘。
需要記憶的一個(gè)簡(jiǎn)單的規(guī)則是把將父類順序按照最基礎(chǔ)的到最派生的順序排列出來(lái)。(楔壤?A simple rule to remember is to specify the base classes in the order from “most base-like” to “most derived”.)

繼承相同名稱但是不同類型的成員變量

當(dāng)繼承導(dǎo)致合約里的函數(shù)和修改器重名鹤啡,會(huì)被認(rèn)為是錯(cuò)誤。這個(gè)錯(cuò)誤也有可能是事件和修改器重名蹲嚣,以及函數(shù)和事件重名递瑰。因?yàn)闋顟B(tài)變量的getter可能復(fù)寫公有函數(shù)祟牲。

抽象合約

合約函數(shù)可能會(huì)留下接口,如下例所示(注意函數(shù)聲明以;結(jié)尾):

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
}

這類合約不能編譯(即使它們除了包含未實(shí)現(xiàn)的函數(shù)抖部,也包含了已經(jīng)實(shí)現(xiàn)的函數(shù))说贝,但是它們可以用在父類中:

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
}

contract Cat is Feline {
    function utterance() returns (bytes32) { return "miaow"; }
}

如果一個(gè)合約繼承自抽象合約,并且沒有實(shí)現(xiàn)接口慎颗,這個(gè)合約也是抽象的乡恕。

接口

接口和抽象合約類似,但是它們不能有任何以實(shí)現(xiàn)的函數(shù)哗总。以下是一些限制條件:

  1. 不能繼承自其他合約或者接口
  2. 不能定義構(gòu)造函數(shù)
  3. 不能定義變量
  4. 不能定義結(jié)構(gòu)體
  5. 不能定義枚舉類型

將來(lái)几颜,有些限制條件會(huì)被除去。

接口被限制為合約ABI所能呈現(xiàn)的功能讯屈,ABI和接口之間的轉(zhuǎn)換可能沒有任何信息損耗蛋哭。

接口用自己的關(guān)鍵字定義:

pragma solidity ^0.4.11;

interface Token {
    function transfer(address recipient, uint amount);
}

因?yàn)楹霞s可以繼承其他合約,所以合約可以繼承自接口涮母。

庫(kù)

庫(kù)和合約很相似谆趾,但是庫(kù)的目的是將它們發(fā)布到特定地址,并只發(fā)布一次叛本,庫(kù)就可以通過EVM的DELEGATECALL(版本Homestead之前是CALLCODE)功能來(lái)重用沪蓬。這意味著,如果庫(kù)函數(shù)被調(diào)用来候,那么庫(kù)函數(shù)的上下文是當(dāng)前調(diào)用的合約跷叉。例如,this指向當(dāng)前調(diào)用的合約营搅,尤其是當(dāng)前合約的storage可以被訪問云挟。作為與合約源碼分離的庫(kù)函數(shù),它只能被訪問調(diào)用合約的允許訪問的狀態(tài)變量(另外转质,沒有辦法重命名)园欣。
庫(kù)可以被看作是調(diào)用該庫(kù)的合約的隱式的父類。它們?cè)诶^承鏈上不可見休蟹,但是調(diào)用庫(kù)函數(shù)就像是調(diào)用父類的函數(shù)(L.f()中沸枯,如果L是庫(kù)的名稱)。另外赂弓,internal函數(shù)對(duì)任何的合約都可見绑榴,就像庫(kù)是該合約的父類一樣。當(dāng)然盈魁,調(diào)用內(nèi)部函數(shù)使用內(nèi)部調(diào)用的約定彭沼,這意味著可以被傳遞的所有內(nèi)部類型和內(nèi)存類型傳遞的是引用,而不是拷貝的值备埃。在EVM中為了實(shí)現(xiàn)這一點(diǎn)姓惑,內(nèi)部庫(kù)函數(shù)的代碼和所有庫(kù)函數(shù)所需的函數(shù)都會(huì)被引入到當(dāng)前合約中褐奴,JUMP就可以用DELEGATECALL代替了。

如下的例子說(shuō)明了如何使用庫(kù)(但是請(qǐng)確保查看下一小節(jié)應(yīng)用的更多高級(jí)例子來(lái)實(shí)現(xiàn)集合):

pragma solidity ^0.4.11;

library Set {
  // 我們定義了一個(gè)新的數(shù)據(jù)結(jié)構(gòu)用來(lái)保存合約數(shù)據(jù)于毙。
  struct Data { mapping(uint => bool) flags; }

  // 注意敦冬,第一個(gè)參數(shù)是"storage 引用",因此傳遞進(jìn)來(lái)的只是引用地址唯沮,而不是引用的值
  // 這是庫(kù)函數(shù)的一個(gè)特點(diǎn)脖旱。
  // 如果函數(shù)能夠被看作是那個(gè)對(duì)象的方法,那么我們習(xí)慣把它來(lái)命名為`self`介蛉。
  function insert(Data storage self, uint value)
      returns (bool)
  {
      if (self.flags[value])
          return false; // 已經(jīng)有了
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      returns (bool)
  {
      if (!self.flags[value])
          return false; // 不存在
      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) {
        // 庫(kù)函數(shù)可以被調(diào)用而無(wú)須指定庫(kù)實(shí)例萌庆。因?yàn)檫@個(gè)實(shí)例是當(dāng)前合約。
        require(Set.insert(knownValues, value));
    }
    // 在這個(gè)合約中币旧,我們可以直接訪問knownValues.flags践险。
}

當(dāng)然,你不需要用這種方式來(lái)使用庫(kù)--它們也可以不定義結(jié)構(gòu)體數(shù)據(jù)類型吹菱。函數(shù)也可以沒有任何storage引用參數(shù)巍虫,并且也可以有多個(gè)引用參數(shù),并且可以在任何位置鳍刷。

Set.contains,Set.insertSet.remove調(diào)用(DELEGATECALL)都被編譯為外部合約/庫(kù)調(diào)用占遥。如果你使用庫(kù),需要小心的是可能會(huì)調(diào)用一個(gè)外部函數(shù)输瓜。msg.sender,msg.valuethis可以保留它們的值(Homestead之前的版本瓦胎,由于使用CALLCODE,msg.sendermsg.value有所不同)尤揣。

下面的例子說(shuō)明了如何在庫(kù)函數(shù)中使用內(nèi)存類型和內(nèi)部函數(shù)搔啊,而無(wú)須使用外部函數(shù),來(lái)實(shí)現(xiàn)自定義類型芹缔。

pragma solidity ^0.4.0;

library BigInt {
    struct bigint {
        uint[] limbs;
    }

    function fromUint(uint x) internal returns (bigint r) {
        r.limbs = new uint[](1);
        r.limbs[0] = x;
    }

    function add(bigint _a, bigint _b) internal 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) {
            // 太爛了坯癣,我們不得不添加一個(gè)limb
            uint[] memory newLimbs = new uint[](r.limbs.length + 1);
            for (i = 0; i < r.limbs.length; ++i)
                newLimbs[i] = r.limbs[i];
            newLimbs[i] = carry;
            r.limbs = newLimbs;
        }
    }

    function limb(bigint _a, uint _limb) internal returns (uint) {
        return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
    }

    function max(uint a, uint b) private returns (uint) {
        return a > b ? a : b;
    }
}


contract C {
    using BigInt for BigInt.bigint;

    function f() {
        var x = BigInt.fromUint(7);
        var y = BigInt.fromUint(uint(-1));
        var z = x.add(y);
    }
}

由于編譯器不知道庫(kù)會(huì)發(fā)布到哪個(gè)地址瓶盛,所以這個(gè)地址會(huì)被連接器填充(參看使用編譯器命令行章節(jié)學(xué)習(xí)如何使用編譯器命令行來(lái)連接代碼)最欠。如果在編譯階段,地址沒有給定惩猫,編譯器會(huì)生成__set______(其中set是庫(kù)的名稱)占位符芝硬。地址可以手動(dòng)的將這40個(gè)符號(hào)用庫(kù)的地址的十六進(jìn)制編碼替代。

和合約相比轧房,庫(kù)的一些限制條件:

  • 沒有狀態(tài)變量
  • 不能繼承或者被繼承
  • 不能接收以太幣

(這些限制可能在以后的版本中移除)

應(yīng)用

指令using A for B可以用來(lái)把庫(kù)函數(shù)(來(lái)自庫(kù)A)附著到任意類型(B)拌阴。這些函數(shù)會(huì)收到一個(gè)調(diào)用者的對(duì)象實(shí)例,作為它們的第一個(gè)參數(shù)(像Python中的self變量)奶镶。(迟赃? These functions will receive the object they are called on as their first parameter (like the self variable in Python).)

using A for *;的作用是庫(kù)A的所有函數(shù)被附著到任意類型陪拘。

在兩種情況中,所有函數(shù)纤壁,即使那些第一個(gè)參數(shù)不是該對(duì)象的類型左刽,依舊會(huì)被附著。當(dāng)函數(shù)調(diào)用的時(shí)候酌媒,類型會(huì)被檢驗(yàn)欠痴,并執(zhí)行函數(shù)重載解析。

指令using A for B;只在當(dāng)前作用域有效秒咨。目前為止喇辽,是限制在當(dāng)前合約。但是之后會(huì)提升到全局作用域雨席。這樣一來(lái)菩咨,包含一個(gè)模塊,庫(kù)函數(shù)中的數(shù)據(jù)類型舅世,就不需要添加額外的代碼就可以直接使用了旦委。

我們用庫(kù)的形式來(lái)重寫集合例子:

pragma solidity ^0.4.11;

// 這些代碼和之前的相同,只是去除了注釋
library Set {
  struct Data { mapping(uint => bool) flags; }

  function insert(Data storage self, uint value)
      returns (bool)
  {
      if (self.flags[value])
        return false; // 已經(jīng)存在了
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      returns (bool)
  {
      if (!self.flags[value])
          return false; // 尚未存在
      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; // 這是最重要的變化
    Set.Data knownValues;

    function register(uint value) {
        // 這里雏亚,所有的Set.Data類型的變量都有了對(duì)應(yīng)的成員函數(shù)
        // 下面的代碼與Set.insert(knownValues, value)相同
        require(knownValues.insert(value));
    }
}

也可以通過這種方法來(lái)拓展基礎(chǔ)類型:

pragma solidity ^0.4.0;

library Search {
    function indexOf(uint[] storage self, uint value) 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) {
        data.push(value);
    }

    function replace(uint _old, uint _new) {
        // 調(diào)用庫(kù)函數(shù)
        uint index = data.indexOf(_old);
        if (index == uint(-1))
            data.push(_new);
        else
            data[index] = _new;
    }
}

注意缨硝,所有的庫(kù)調(diào)用是真實(shí)的EVM函數(shù)調(diào)用。這意味著罢低,如果你傳遞了內(nèi)存或者值類型查辩,會(huì)傳遞一個(gè)拷貝,即使是self變量网持。沒有發(fā)生拷貝的一種情況是使用storage引用變量宜岛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市功舀,隨后出現(xiàn)的幾起案子萍倡,更是在濱河造成了極大的恐慌,老刑警劉巖辟汰,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列敲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帖汞,警方通過查閱死者的電腦和手機(jī)戴而,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翩蘸,“玉大人所意,你說(shuō)我怎么就攤上這事。” “怎么了扶踊?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵泄鹏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我秧耗,道長(zhǎng)命满,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任绣版,我火速辦了婚禮胶台,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杂抽。我一直安慰自己诈唬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布缩麸。 她就那樣靜靜地躺著铸磅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杭朱。 梳的紋絲不亂的頭發(fā)上阅仔,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音弧械,去河邊找鬼八酒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛刃唐,可吹牛的內(nèi)容都是我干的羞迷。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼画饥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衔瓮!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起抖甘,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤热鞍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后衔彻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薇宠,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年米奸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昼接。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爽篷。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悴晰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铡溪,我是刑警寧澤漂辐,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站棕硫,受9級(jí)特大地震影響髓涯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哈扮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一纬纪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滑肉,春花似錦包各、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至六荒,卻和暖如春护姆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掏击。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工卵皂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砚亭。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓渐裂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钠惩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柒凉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 本文翻譯自:https://github.com/ConsenSys/smart-contract-best-pr...
    tolak閱讀 4,927評(píng)論 4 21
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)篓跛,斷路器膝捞,智...
    卡卡羅2017閱讀 134,638評(píng)論 18 139
  • 翻譯原文date:20170612 一個(gè)簡(jiǎn)單的智能合約 讓我們中最簡(jiǎn)單的例子開始。現(xiàn)在對(duì)所有這一切不了解都沒有關(guān)系...
    gaoer1938閱讀 1,567評(píng)論 0 1
  • 01 古人云:近朱者赤,近墨者黑沐寺。說(shuō)的是人們常被身邊的環(huán)境所影響林艘。蕓蕓眾生,隨波逐流混坞。 孟母為了孟子的成人成才狐援,多...
    半夏兒閱讀 627評(píng)論 0 2
  • 雜貨店店主浪矢雄治老爺爺因?yàn)楹托∨笥验_了個(gè)玩笑(小朋友戲稱雜貨店為煩惱雜貨店)便從此像找到了自己的人生價(jià)值一樣钢坦,幫...
    萬(wàn)花谷閱讀 210評(píng)論 0 0