可見性和訪問限制符(Visibility And Accessors)
因為Solidity可以理解兩種函數(shù)調(diào)用(“內(nèi)部調(diào)用”组哩,不創(chuàng)建一個真實的EVM調(diào)用(也稱為“消息調(diào)用”);“外部的調(diào)用”-要創(chuàng)建一個真實的EMV調(diào)用), 有四種的函數(shù)和狀態(tài)變量的可見性嘹黔。
函數(shù)可以被定義為external, public, internal or private骨杂,缺省是 public涂身。對狀態(tài)變量而言, external是不可能的,默認(rèn)是 internal。
external: 外部函數(shù)是合約接口的一部分,這意味著它們可以從其他合約調(diào)用, 也可以通過事務(wù)調(diào)用搓蚪。外部函數(shù)f不能被內(nèi)部調(diào)用(即 f()不執(zhí)行,但this.f()執(zhí)行)蛤售。外部函數(shù),當(dāng)他們接收大數(shù)組時,更有效悍抑。
public:公共函數(shù)是合約接口的一部分,可以通過內(nèi)部調(diào)用或通過消息調(diào)用。對公共狀態(tài)變量而言杜耙,會有的自動訪問限制符的函數(shù)生成(見下文)搜骡。
internal:這些函數(shù)和狀態(tài)變量只能內(nèi)部訪問(即在當(dāng)前合約或由它派生的合約),而不使用(關(guān)鍵字)this 。
private:私有函數(shù)和狀態(tài)變量僅僅在定義該合約中可見佑女, 在派生的合約中不可見记靡。
請注意
在外部觀察者中,合約的內(nèi)部的各項均可見团驱。用 private 僅僅防止其他合約來訪問和修改(該合約中)信息, 但它對blockchain之外的整個世界仍然可見摸吠。
可見性說明符是放在在狀態(tài)變量的類型之后,(也可以放在)參數(shù)列表和函數(shù)返回的參數(shù)列表之間嚎花。
contract c {
function f(uint a) private returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
其他合約可以調(diào)用c.data()來檢索狀態(tài)存儲中data的值,但不能訪問(函數(shù))f寸痢。由c派生的合約可以訪問(合約中)setData(函數(shù)),以便改變data的值(僅僅在它們自己的范圍里)紊选。
訪問函數(shù)(Getter Functions)
編譯器為自動為所有的public的狀態(tài)變量創(chuàng)建訪問函數(shù)啼止。下面的合約例子中,編譯器會生成一個名叫data的無參兵罢,返回值是uint的類型的值data献烦。狀態(tài)變量的初始化可以在定義時完成。
pragma solidity ^0.4.0;
contract C{
uint public c = 10;
}
contract D{
C c = new C();
function getDataUsingAccessor() returns (uint){
return c.c();
}
}
訪問函數(shù)有外部(external)可見性卖词。如果通過內(nèi)部(internal)的方式訪問巩那,比如直接訪問,你可以直接把它當(dāng)一個變量進(jìn)行使用此蜈,但如果使用外部(external)的方式來訪問即横,如通過this.,那么它必須通過函數(shù)的方式來調(diào)用舶替。
pragma solidity ^0.4.0;
contract C{
uint public c = 10;
function accessInternal() returns (uint){
return c;
}
function accessExternal() returns (uint){
return this.c();
}
}
在acessExternal函數(shù)中令境,如果直接返回return this.c;,會出現(xiàn)報錯Return argument type function () constant external returns (uint256) is not implicitly convertible to expected type (type of first return variable) uint256.顾瞪。原因應(yīng)該是通過外部(external)的方式只能訪問到this.c作為函數(shù)的對象舔庶,所以它認(rèn)為你是想把一個函數(shù)轉(zhuǎn)為uint故而報錯。
函數(shù)修改器(Function Modifiers)
修改器(Modifiers)可以用來輕易的改變一個函數(shù)的行為陈醒。比如用于在函數(shù)執(zhí)行前檢查某種前置條件惕橙。修改器是一種合約屬性,可被繼承钉跷,同時還可被派生的合約重寫(override)弥鹦。下面我們來看一段示例代碼:
pragma solidity ^0.4.0;
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; }
// 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;
}
}
修改器可以被繼承,使用將modifier置于參數(shù)后,返回值前即可彬坏。
特殊_表示使用修改符的函數(shù)體的替換位置朦促。
從合約Register可以看出全約可以多繼承,通過,號分隔兩個被繼承的對象栓始。
修改器也是可以接收參數(shù)的务冕,如priced的costs。
使用修改器實現(xiàn)的一個防重復(fù)進(jìn)入的例子幻赚。
pragma solidity ^0.4.0;
contract Mutex {
bool locked;
modifier noReentrancy() {
if (locked) throw;
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) {
if (!msg.sender.call()) throw;
return 7;
}
}
例子中禀忆,由于call()方法有可能會調(diào)回當(dāng)前方法,修改器實現(xiàn)了防重入的檢查落恼。
如果同一個函數(shù)有多個修改器箩退,他們之間以空格隔開,修飾器會依次檢查執(zhí)行佳谦。
需要注意的是戴涝,在Solidity的早期版本中,有修改器的函數(shù)吠昭,它的return語句的行為有些不同喊括。
在修改器中和函數(shù)體內(nèi)的顯式的return語句,僅僅跳出當(dāng)前的修改器和函數(shù)體矢棚。返回的變量會被賦值郑什,但整個執(zhí)行邏輯會在前一個修改器后面定義的"_"后繼續(xù)執(zhí)行。
修改器的參數(shù)可以是任意表達(dá)式蒲肋。在對應(yīng)的上下文中蘑拯,所有的函數(shù)中引入的符號,在修改器中均可見兜粘。但修改器中引入的符號在函數(shù)中不可見申窘,因為它們有可能被重寫。
常量(constant state variables)
狀態(tài)變量可以被定義為constant孔轴,常量剃法。這樣的話,它必須在編譯期間通過一個表達(dá)式賦值路鹰。賦值的表達(dá)式不允許:1)訪問storage贷洲;2)區(qū)塊鏈數(shù)據(jù),如now晋柱,this.balance优构,block.number;3)合約執(zhí)行的中間數(shù)據(jù)雁竞,如msg.gas钦椭;4)向外部合約發(fā)起調(diào)用。也許會造成內(nèi)存分配副作用表達(dá)式是允許的,但不允許產(chǎn)生其它內(nèi)存對象的副作用的表達(dá)式彪腔。內(nèi)置的函數(shù)keccak256侥锦,keccak256,ripemd160德挣,ecrecover捎拯,addmod,mulmod可以允許調(diào)用盲厌,即使它們是調(diào)用的外部合約。
允許內(nèi)存分配祸泪,從而帶來可能的副作用的原因是因為這將允許構(gòu)建復(fù)雜的對象吗浩,比如,查找表没隘。雖然當(dāng)前的特性尚未完整支持懂扼。
編譯器并不會為常量在storage上預(yù)留空間,每個使用的常量都會被對應(yīng)的常量表達(dá)式所替換(也許優(yōu)化器會直接替換為常量表達(dá)式的結(jié)果值)右蒲。
不是所有的類型都支持常量阀湿,當(dāng)前支持的僅有值類型和字符串。
pragma solidity ^0.4.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
}
常函數(shù)(Constant Functions)
函數(shù)也可被聲明為常量瑰妄,這類函數(shù)將承諾自己不修改區(qū)塊鏈上任何狀態(tài)陷嘴。
pragma solidity ^0.4.0;
contract C {
function f(uint a, uint b) constant returns (uint) {
return a * (b + 42);
}
}
訪問器(Accessor)方法默認(rèn)被標(biāo)記為constant。當(dāng)前編譯器并未強(qiáng)制一個constant的方法不能修改狀態(tài)间坐。但建議大家對于不會修改數(shù)據(jù)的標(biāo)記為constant灾挨。
回退函數(shù)(fallback function)
每一個合約有且僅有一個沒有名字的函數(shù)。這個函數(shù)無參數(shù)竹宋,也無返回值劳澄。如果調(diào)用合約時,沒有匹配上任何一個函數(shù)(或者沒有傳哪怕一點(diǎn)數(shù)據(jù))蜈七,就會調(diào)用默認(rèn)的回退函數(shù)秒拔。
此外,當(dāng)合約收到ether時(沒有任何其它數(shù)據(jù))飒硅,這個函數(shù)也會被執(zhí)行砂缩。在此時,一般僅有少量的gas剩余狡相,用于執(zhí)行這個函數(shù)(準(zhǔn)確的說梯轻,還剩2300gas)。所以應(yīng)該盡量保證回退函數(shù)使用少的gas尽棕。
下述提供給回退函數(shù)可執(zhí)行的操作會比常規(guī)的花費(fèi)得多一點(diǎn)喳挑。
- 寫入到存儲(storage)
- 創(chuàng)建一個合約
- 執(zhí)行一個外部(external)函數(shù)調(diào)用,會花費(fèi)非常多的gas
- 發(fā)送ether
請在部署合約到網(wǎng)絡(luò)前,保證透徹的測試你的回退函數(shù)伊诵,來保證函數(shù)執(zhí)行的花費(fèi)控制在2300gas以內(nèi)单绑。
一個沒有定義一個回退函數(shù)的合約。如果接收ether曹宴,會觸發(fā)異常搂橙,并返還ether(solidity v0.4.0開始)。所以合約要接收ether笛坦,必須實現(xiàn)回退函數(shù)区转。下面來看個例子:
pragma solidity ^0.4.0;
contract Test {
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the "payable"
// modifier.
function() { x = 1; }
uint x;
}
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() payable { }
}
contract Caller {
function callTest(Test test) {
test.call(0xabcdef01); // hash does not exist
// results in test.x becoming == 1.
// The following call will fail, reject the
// Ether and return false:
test.send(2 ether);
}
}
事件(Event)
事件
在介紹事件前,我們先明確事件版扩,日志這兩個概念废离。事件發(fā)生后被記錄到區(qū)塊鏈上成為了日志〗嘎總的來說蜻韭,事件強(qiáng)調(diào)功能,一種行為柿扣;日志強(qiáng)調(diào)存儲肖方,內(nèi)容。
事件是以太坊EVM提供的一種日志基礎(chǔ)設(shè)施未状。事件可以用來做操作記錄俯画,存儲為日志。也可以用來實現(xiàn)一些交互功能司草,比如通知UI活翩,返回函數(shù)調(diào)用結(jié)果等。
當(dāng)定義的事件觸發(fā)時翻伺,我們可以將事件存儲到EVM的交易日志中材泄,日志是區(qū)塊鏈中的一種特殊數(shù)據(jù)結(jié)構(gòu)。日志與合約關(guān)聯(lián)吨岭,與合約的存儲合并存入?yún)^(qū)塊鏈中拉宗。只要某個區(qū)塊可以訪問,其相關(guān)的日志就可以訪問辣辫。但在合約中旦事,我們不能直接訪問日志和事件數(shù)據(jù)(即便是創(chuàng)建日志的合約)。下面我們來看看急灭,如何在Solidity中實現(xiàn)一個事件:
pragma solidity ^0.4.0;
contract Transfer{
event transfer(address indexed _from, address indexed _to, uint indexed value);
function deposit() payable {
address current = this;
uint value = msg.value;
transfer(msg.sender, current, value);
}
function getBanlance() constant returns(uint) {
return this.balance;
}
/* fallback function */
function(){}
}
從上面的例子中姐浮,我們使用event關(guān)鍵字定義一個事件,參數(shù)列表中為要記錄的日志參數(shù)的名稱及類型葬馋。
監(jiān)聽事件
在web3.js中卖鲤,提供了響應(yīng)事件的方法肾扰,如下:
var event = myContract.transfer();
// 監(jiān)聽
event.watch(function(error, result){
console.log("Event are as following:-------");
for(let key in result){
console.log(key + " : " + result[key]);
}
console.log("Event ending-------");
});
另外一種簡便寫法是直接加入事件回調(diào),這樣就不用再寫watch的部分:
var event = myContract.transfer(function(error, result){
console.log("Event are as following:-------");
for(let key in result){
console.log(key + " : " + result[key]);
}
console.log("Event ending-------");
});
檢索日志
Indexed屬性
可以在事件參數(shù)上增加indexed
屬性蛋逾,最多可以對三個參數(shù)增加這樣的屬性集晚。加上這個屬性,可以允許你在web3.js中通過對加了這個屬性的參數(shù)進(jìn)行值過濾区匣,方式如下:
var event = myContract.transfer({value: "100"});
上面實現(xiàn)的是對value值為100的日志偷拔,過濾后的返回。
如果你想同時匹配多個值亏钩,還可以傳入一個要匹配的數(shù)組莲绰。
var event = myContract.transfer({value: ["99","100","101"]});
增加了indexed
的參數(shù)值會存到日志結(jié)構(gòu)的Topic
部分,便于快速查找姑丑。而未加indexed
的參數(shù)值會存在data
部分钉蒲,成為原始日志。需要注意的是彻坛,如果增加indexed
屬性的是數(shù)組類型(包括string
和bytes
),那么只會在Topic
存儲對應(yīng)的數(shù)據(jù)的web3.sha3
哈希值踏枣,將不會再存原始數(shù)據(jù)昌屉。因為Topic是用于快速查找的,不能存任意長度的數(shù)據(jù)茵瀑,所以通過Topic實際存的是數(shù)組這種非固定長度數(shù)據(jù)哈希結(jié)果间驮。要查找時,是將要查找內(nèi)容哈希后與Topic內(nèi)容進(jìn)行匹配马昨,但我們不能反推哈希結(jié)果竞帽,從而得不到原始值。
View
函數(shù)可以被聲明為view鸿捧,在這種情況下屹篓,它們承諾不修改狀態(tài)。
以下語句被認(rèn)為是修改狀態(tài):
- 寫入狀態(tài)變量匙奴。
- 發(fā)射事件堆巧。
- 創(chuàng)建其他合約。
- 使用selfdestruct泼菌。
- 通過調(diào)用發(fā)送Ether谍肤。
- 調(diào)用其他函數(shù)不被標(biāo)記view或者pure。
- 使用低等級調(diào)用哗伯。
- 使用包含某些操作碼的內(nèi)聯(lián)匯編荒揣。
contract C {
function f(uint a, uint b) view returns (uint) {
return a * (b + 42) + now;
}
}
constant是view的別名。
Getter方法被標(biāo)記為view焊刹。
編譯器并沒有強(qiáng)制執(zhí)行view方法不修改狀態(tài)系任。
Pure
函數(shù)可以聲明為pure恳蹲,在這種情況下,它們承諾不會從該狀態(tài)中讀取或修改該狀態(tài)赋除。
除了上述狀態(tài)修改語句的列表外阱缓,以下是從狀態(tài)讀取的:
- 從狀態(tài)變量讀取。
- 訪問this.balance或.balance举农。
- 訪問block荆针,tx,msg的任何成員(除了msg.sig和msg.data)颁糟。
- 調(diào)用任何未標(biāo)記為pure的功能航背。
- 使用包含某些操作碼的內(nèi)聯(lián)匯編。
contract C {
function f(uint a, uint b) pure returns (uint) {
return a * (b + 42);
}
}
編譯器并沒有強(qiáng)制執(zhí)行pure方法不是從狀態(tài)中讀取的棱貌。
繼承(Inheritance)
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);
}
}
抽象(Abstract Contracts)
抽象函數(shù)是沒有函數(shù)體的的函數(shù)玖媚。如下:
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
}
這樣的合約不能通過編譯,即使合約內(nèi)也包含一些正常的函數(shù)婚脱。但它們可以做為基合約被繼承今魔。
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
function getContractName() returns (string){
return "Feline";
}
}
contract Cat is Feline {
function utterance() returns (bytes32) { return "miaow"; }
}
如果一個合約從一個抽象合約里繼承,但卻沒實現(xiàn)所有函數(shù)障贸,那么它也是一個抽象合約错森。