Solidity 是一種靜態類型語言,這意味著每個變數(狀態變數和局部變數)都需要在編譯時指定變數的類型
Solidity 提供了幾種基本類型,並且基本類型可以用來組合出複雜類型。除此之外,類型之間可以在包含運算符號的表達式中進行交互, undefined 或 null 值的概念在Solidity中不存在,但是新聲明的變數總是有一個 默認值,具體的預設值跟類型相關。 要處理任何意外的值,應該使用 錯誤處理來恢復整個交易,或者返回一個帶有第二個 bool 值的元組表示成功
類型可以說是所有合約都會使用到的基本概念,他非常簡單也經常被使用,各位讀者請確保自己牢記這些簡單的知識點
下列運算子是經常使用到的,每個的用法都必須搞清楚
!(邏輯非)&&(邏輯與, "and" )||(邏輯或, "or" )比較運算子:
<=,<,==,!=,>,=>( 傳回布林值 )算數運算子:
+,-,*,/,%( 取餘或叫模運算 )**(指數)位運算子:
&,|,^( 異或)~( 位取反 )移位運算子:
<<(左移位),>>(右移位)
而關於Solidity的運算溢出,可以參考此篇解析
解析 SafeMath 合約
bool : 可能的取值為字面常量值 true 和 false
int / uint :分別表示有符號和無符號的不同位數的整型變數。 支援關鍵字 uint8到 uint256,以 8 位為遞增
uint 和 int 分別表示 uint256 和 int256
uint256 有最大值 2^256-1 以及最小值 0
int256 有最大值 2^255-1 以及最小值 -2^255
函數類型是一種表示函數的類型。 可以將一個函數賦值給另一個函數類型的變數,也可以將一個函數作為參數進行傳遞,還能在函數調用中返回函數類型變數。 函數類型有兩類:
內部(internal) 函數類型外部(external) 函數類型
內部函數只能在當前合約內被調用,更具體來說,在當前合約內,包括內部庫函數和繼承的函數中
外部函數由一個位址和一個函數簽名組成,可以通過外部函數調用傳遞或者返回。
函數類型表示成如下的形式
**function** (<parameter types>) {**internal**|**external**} [**pure**|**view**|**payable**] [**returns** (<**return** types>)]
與參數類型相反,返回類型不能為空 —— 如果函數類型不需要返回,則需要刪除整個 returns (<return types>) 部分。
函數類型預設是內部函數,因此不需要聲明 internal 關鍵字。
💡 請注意,這僅適用於函數類型,合約中定義的函數明確指定可見性,它們沒有預設值。
如果當函數類型的變數還沒有初始化時就調用它的話會引發一個 Panic 異常。 如果在一個函數被delete之後調用它也會發生相同的情況。
請注意,當前合約的 public 函數既可以被當作內部函數也可以被當作外部函數使用。 如果想將函式當作內部函數使用,就用f呼叫,如果想將其當作外部函數,使用 this.f
成員方法:
public(或 external)函數都有下面的成員:
.address- 返回函數的合約位址。.selector- 返回 ABI 函數選擇器
View, Pure, Payable
View 函數是只讀函數***,不會修改區塊鏈的狀態。***換句話說,如果你想從區塊鏈中讀取數據,可以使用 View 函數
Pure 函數比 View 函數更具限制性,***並且不修改狀態,也不讀取區塊鏈的狀態。***換句話說,他是純粹幫助運算、執行其他函數的函數
Payable 函數的話請記住以下幾點
在函數或狀態變數中使用payable來發送和接收乙太幣
在狀態變數中包含payable,以便從合同中退出
在構造函數中包含payable,以便在創建/部署合約時能夠存入合約
在函數中包含payable,以允許將存款存入合同
內部函數使用例子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Azuki is Ownable, ERC721A, ReentrancyGuard {
//...
string private _baseTokenURI;
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
//...
}
這個函數非常簡單就是將原本的函數 _baseURI() 從回傳空值覆寫成回傳 _baseTokenURI ,並且因為他被宣告為 internal ,所以理論上我們可以在合約找到這個函數被誰使用,在 ERC721A.sol 中:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC721A is
Context,
ERC165,
IERC721,
IERC721Metadata,
IERC721Enumerable
{
//...
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
string memory baseURI = _baseURI();
return
bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, tokenId.toString()))
: "";
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
//...
}
首先看到函數 _baseURI() 他確實在宣告時帶有 internal & virtual 兩個性質,也印證了上面的說法,接著,看到函數 tokenURI() 他會先確認 tokenId 是否存在,如果存在的話就會回傳 tokenURI (注意到他帶有 view 表示該函數不會修改區塊鏈的狀態),裡面用到的 abi.encodePacked 會在後面內容中解釋
外部函數使用例子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Azuki is Ownable, ERC721A, ReentrancyGuard {
//...
function allowlistMint() external payable callerIsUser {
uint256 price = uint256(saleConfig.mintlistPrice);
require(price != 0, "allowlist sale has not begun yet");
require(allowlist[msg.sender] > 0, "not eligible for allowlist mint");
require(totalSupply() + 1 <= collectionSize, "reached max supply");
allowlist[msg.sender]--;
_safeMint(msg.sender, 1);
refundIfOver(price);
}
//...
}
外部函數如上述定義所描述,通常不會是合約內部去使用它,而是提供外面的使用者跟合約互動的函數,以函數 allowlistMint() 為例,前面三個 require 先檢查了使用者使否有資格 mint,然後再去呼叫 _safeMint() 來讓使用者鑄造NFT,是很常見的白單公售NFT的函數(這個函數看他的功能就應該能知道他是 payable ,因為他需要添加新的鏈上狀態,紀錄這個使用者鑄造了一個NFT)
每一個contract定義都有他自己的類型
如果聲明一個合約類型的局部變數(MyContract c),則可以調用該合約的函數。
注意需要賦相同合約類型的值給它
合約特性:
可以實例化合約(即新創建一個合約物件),參考 『使用new創建合約』
合約和
address的數據表示是相同的, 參考 ABI合約不支援任何運算符
合約類型的成員是合約的外部函數及 public 的 狀態變數
對於合約
C可以使用type(C)取得合約的類型資訊,參考 類型資訊
位址類型有兩種形式,他們大致相同:
address:保存一個20位元組的值(乙太坊位址的大小)
address payable :可支付位址,與
address相同,不過有成員函數transfer和send
地址類型成員變數
查看所有的成員,可參考 地址成員
balance: 查詢一個地址的餘額
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Azuki is Ownable, ERC721A, ReentrancyGuard {
//...
function withdrawMoney() external onlyOwner nonReentrant {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
//...
}
transfer: 向一個可支付位址(payable address)發送乙太幣Ether (以 wei 為單位)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Azuki is Ownable, ERC721A, ReentrancyGuard {
//...
function refundIfOver(uint256 price) private {
require(msg.value >= price, "Need to send more ETH.");
if (msg.value > price) {
payable(msg.sender).transfer(msg.value - price);
}
}
//...
}
💡 如果當前合約的餘額不夠多,則`transfer`函數會執行失敗,或者如果乙太轉移被接收帳戶拒絕, `transfer`函數同樣會失敗而進行回退
call: 是與其他合約交互的低級函數,當你只是通過調用函數發送乙太幣時要使用的推薦方法,但是,這不是調用現有函數的推薦方法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Azuki is Ownable, ERC721A, ReentrancyGuard {
//...
function withdrawMoney() external onlyOwner nonReentrant {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
//...
}
上述的函數 withdrawMoney 中 msg.sender.call 就是要領出合約中的ETH,不過他繼承自 onlyOwner 與 nonReentrant 所以想要呼叫這函數會有限制,之後再作詳盡的解釋
delegatecall: 使用起來並不容易,錯誤的用法或不正確的理解會導致毀滅性的結果,詳細使用方式可以參考這篇
💡 不管是讀取狀態還是寫入狀態,最好避免在合約代碼中硬編碼使用的 gas 值。 這可能會引入"陷阱",而且 gas 的消耗也是可能會改變的
種類有以下幾種:bytes1, bytes2, ...bytes32
除了第一部分講到的通用運算子,他能進行索引訪問:
索引訪問:如果
x是bytesI類型,那麼x[k](其中0 <= k < I)返回第k個字節(只讀)
成員變數:
.length表示這個位元組陣組的長度(只讀).
bytes: Dynamically-sized ****位元組陣列string: Dynamically-sized UTF-8 編碼字串類型以上可參閱 陣列 他們不是值類型
枚舉是在Solidity中創建使用者定義類型的一種方法。 它們是顯示所有整型相互轉換,但不允許隱式轉換。 從整型顯式轉換枚舉,會在運行時檢查整數時候在枚舉範圍內,否則會導致異常( Panic異常 )。 枚舉需要至少一個成員,預設值是第一個成員,枚舉不能多於256個成員。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
//get max value in enums
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
//get max value in enums
return type(ActionChoices).min;
}
}
