構造函數?
構造函數(constructor
)是一種特殊的函數,每個合約可以定義一個,并在部署合約的時候自動運行一次。它可以用來初始化合約的一些參數,例如初始化合約的owner
地址:
address owner; // 定義owner變量// 構造函數
constructor(address initialOwner) {owner = initialOwner; // 在部署合約的時候,將owner設置為傳入的initialOwner地址
}
注意:構造函數在不同的Solidity版本中的語法并不一致,在Solidity 0.4.22之前,構造函數不使用?constructor
?而是使用與合約名同名的函數作為構造函數而使用,由于這種舊寫法容易使開發者在書寫時發生疏漏(例如合約名叫?Parents
,構造函數名寫成?parents
),使得構造函數變成普通函數,引發漏洞,所以0.4.22版本及之后,采用了全新的?constructor
?寫法。
構造函數的舊寫法代碼示例:
pragma solidity =0.4.21;
contract Parents {// 與合約名Parents同名的函數就是構造函數function Parents () public {}
}
修飾器?
修飾器(modifier
)是Solidity
特有的語法,類似于面向對象編程中的裝飾器(decorator
),聲明函數擁有的特性,并減少代碼冗余。它就像鋼鐵俠的智能盔甲,穿上它的函數會帶有某些特定的行為。modifier
的主要使用場景是運行函數前的檢查,例如地址,變量,余額等。
?
我們來定義一個叫做onlyOwner的modifier:
// 定義modifier
modifier onlyOwner {require(msg.sender == owner); // 檢查調用者是否為owner地址_; // 如果是的話,繼續運行函數主體;否則報錯并revert交易
}
帶有onlyOwner
修飾符的函數只能被owner
地址調用,比如下面這個例子:
function changeOwner(address _newOwner) external onlyOwner{owner = _newOwner; // 只有owner地址運行這個函數,并改變owner
}
我們定義了一個changeOwner
函數,運行它可以改變合約的owner
,但是由于onlyOwner
修飾符的存在,只有原先的owner
可以調用,別人調用就會報錯。這也是最常用的控制智能合約權限的方法。
OpenZeppelin的Ownable標準實現?
OpenZeppelin
是一個維護Solidity
標準化代碼庫的組織,他的Ownable
標準實現如下:?openzeppelin-contracts/contracts/access/Ownable.sol at master · OpenZeppelin/openzeppelin-contracts · GitHub
事件?
Solidity
中的事件(event
)是EVM
上日志的抽象,它具有兩個特點:
- 響應:應用程序(ethers.js)可以通過
RPC
接口訂閱和監聽這些事件,并在前端做響應。 - 經濟:事件是
EVM
上比較經濟的存儲數據的方式,每個大概消耗2,000?gas
;相比之下,鏈上存儲一個新變量至少需要20,000?gas
。
聲明事件?
事件的聲明由event
關鍵字開頭,接著是事件名稱,括號里面寫好事件需要記錄的變量類型和變量名。以ERC20
代幣合約的Transfer
事件為例:
event Transfer(address indexed from, address indexed to, uint256 value);
我們可以看到,Transfer
事件共記錄了3個變量from
,to
和value
,分別對應代幣的轉賬地址,接收地址和轉賬數量,其中from
和to
前面帶有indexed
關鍵字,他們會保存在以太坊虛擬機日志的topics
中,方便之后檢索。
釋放事件?
我們可以在函數里釋放事件。在下面的例子中,每次用_transfer()
函數進行轉賬操作的時候,都會釋放Transfer
事件,并記錄相應的變量。
// 定義_transfer函數,執行轉賬邏輯
function _transfer(address from,address to,uint256 amount
) external {_balances[from] = 10000000; // 給轉賬地址一些初始代幣_balances[from] -= amount; // from地址減去轉賬數量_balances[to] += amount; // to地址加上轉賬數量// 釋放事件emit Transfer(from, to, amount);
}
EVM日志?Log
?
以太坊虛擬機(EVM)用日志Log
來存儲Solidity
事件,每條日志記錄都包含主題topics
和數據data
兩部分。
主題?topics
?
日志的第一部分是主題數組,用于描述事件,長度不能超過4
。它的第一個元素是事件的簽名(哈希)。對于上面的Transfer
事件,它的事件哈希就是:
keccak256("Transfer(address,address,uint256)")//0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Copy
除了事件哈希,主題還可以包含至多3
個indexed
參數,也就是Transfer
事件中的from
和to
。
indexed
標記的參數可以理解為檢索事件的索引“鍵”,方便之后搜索。每個?indexed
?參數的大小為固定的256比特,如果參數太大了(比如字符串),就會自動計算哈希存儲在主題中。
數據?data
?
事件中不帶?indexed
的參數會被存儲在?data
?部分中,可以理解為事件的“值”。data
?部分的變量不能被直接檢索,但可以存儲任意大小的數據。因此一般?data
?部分可以用來存儲復雜的數據結構,例如數組和字符串等等,因為這些數據超過了256比特,即使存儲在事件的?topics
?部分中,也是以哈希的方式存儲。另外,data
?部分的變量在存儲上消耗的gas相比于?topics
?更少。
測試代碼:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;contract Owner {address public owner; // 定義owner變量// 構造函數constructor(address initialOwner) {owner = initialOwner; // 在部署合約的時候,將owner設置為傳入的initialOwner地址}// 定義modifiermodifier onlyOwner {require(msg.sender == owner); // 檢查調用者是否為owner地址_; // 如果是的話,繼續運行函數主體;否則報錯并revert交易}// 定義一個帶onlyOwner修飾符的函數function changeOwner(address _newOwner) external onlyOwner{owner = _newOwner; // 只有owner地址運行這個函數,并改變owner}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
contract Events {// 定義_balances映射變量,記錄每個地址的持幣數量mapping(address => uint256) public _balances;// 定義Transfer event,記錄transfer交易的轉賬地址,接收地址和轉賬數量event Transfer(address indexed from, address indexed to, uint256 value);// 定義_transfer函數,執行轉賬邏輯function _transfer(address from,address to,uint256 amount) external {_balances[from] = 10000000; // 給轉賬地址一些初始代幣_balances[from] -= amount; // from地址減去轉賬數量_balances[to] += amount; // to地址加上轉賬數量// 釋放事件emit Transfer(from, to, amount);}
}