原文鏈接
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
,addmod
和mulmod
是允許的(盡管他們調(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ù)組(包含string
和bytes
)可以被用來(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
察纯,log1
,log2
针肥,log3
和log4
饼记。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ù)哗总。以下是一些限制條件:
- 不能繼承自其他合約或者接口
- 不能定義構(gòu)造函數(shù)
- 不能定義變量
- 不能定義結(jié)構(gòu)體
- 不能定義枚舉類型
將來(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.insert
和Set.remove
調(diào)用(DELEGATECALL
)都被編譯為外部合約/庫(kù)調(diào)用占遥。如果你使用庫(kù),需要小心的是可能會(huì)調(diào)用一個(gè)外部函數(shù)输瓜。msg.sender
,msg.value
和this
可以保留它們的值(Homestead之前的版本瓦胎,由于使用CALLCODE,msg.sender
和msg.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引用變量宜岛。