timezone |
---|
Asia/Shanghai |
-
自我介绍 我是Univ,想藉由這次共學機會深入學習Solidity與智能合約相關知識,跟大家一起的感覺比較有學習的動力
-
你认为你会完成本次残酷学习吗? 我可以完成這次共學,每天排出時間更新學習進度
// SPDX-License-Identifier: MIT
// 這一行是註釋,表示了MIT license,不寫的話會有warning,但程式還是可以跑
pragma solidity ^0.8.21; // 這一行表示版本
contract HelloWeb3 { // 這裡是合約的定義
string public _string = "Hello Web3!"; // 宣告一個public的string型態變數,並賦值
}
有分三種
- (a) boolean:布林型別(! && || == !=)
- (b) int、uint、uint256:整數型別
- (c) address:地址型別
- (d) byte1、byte32:字節型別
- (e) enum:列舉型別
包括 array、struct、mapping 等複雜資料型別
一種特殊的資料型別,用來建立鍵值對映的數據結構
Solidity 中的函數結構有點複雜,但按照固定的格式來寫函數就可以:
function <函數名>(<參數類型>) {internal|external|public|private} [pure|view|payable] [returns (<返回類型>)]
Solidity 提供了四種函數可見性修飾符,來控制函數的訪問範圍:
- public:內外部均可見
- private:僅限於本合約內部,繼承合約也無法使用
- external:僅限於合約外部訪問(但內部可以通過
this.f()
來調用) - internal:僅限於合約內部,繼承的合約可以使用
Solidity 引入 pure
和 view
關鍵字來控制函數是否改變區塊鏈上的狀態,這是因為以太坊的交易需要支付 gas 費用:
- pure:不能讀取也不能修改合約中的狀態變量。它僅僅執行不依賴合約狀態的邏輯運算,因為不需要讀取或改變鏈上的數據,所以不需要支付 gas
- view:僅能讀取合約的狀態變量,但不能修改狀態。這種函數的運行也不會花費 gas,除非是合約內部調用時
- internal:只能在合約內部或者繼承的合約中被調用,這在需要隱藏某些內部邏輯時非常有用
- external:只能從合約外部訪問,但可以在合約內部通過
this.functionName()
調用
- payable:允許合約接收 ETH,帶有這個關鍵字的函數在執行時可以接收以太幣並更新合約的餘額
教程中提供了數個簡單的實例來展示如何使用 pure
、view
、internal
、external
以及 payable
等關鍵字修飾函數:
add()
函數展示了如何操作狀態變量並引入pure
和view
的用法minus()
函數展示了internal
函數的使用minusPayable()
函數展示了payable
函數如何接收 ETH 並更新合約餘額
- returns 這個關鍵字用在函數名稱後面,用來告訴大家這個函數會返回什麼樣的數據,比如返回什麼類型的數字、陣列或布林值(true 或 false),就像你在函數開始之前,先跟大家說:「這個函數會給你什麼東西」
- return 這個關鍵字用在函數的主體裡面,用來實際把數據返回出去。就像是函數結束時,你用 return 把事先說好的東西交給大家
- 在 Solidity 中,你可以在 returns 這裡直接給返回的值取名字,這樣你在函數裡面只要把這些變數賦值,系統會自動把這些值返回,不用再手動用 return
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array) {
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}
function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array) {
return (1, true, [uint256(1),2,5]);
}
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
引用類型包括:array(數組)和 struct(結構體)。 這些類型的變數較為複雜,佔用較多存儲空間,因此需要明確聲明數據存儲的位置。
Solidity 中的數據存儲位置有三類:storage、memory 和 calldata,每種位置的 gas 消耗不同:
- storage:合約中的狀態變數默認存儲在 storage,數據存在鏈上,消耗較多 gas,類似電腦的硬碟。
- memory:函數中的參數和臨時變數通常用 memory,數據存在內存中,不上鏈,消耗較少 gas,適用於返回變長數據類型。
- calldata:類似於 memory,但數據不可修改,用於函數參數。
- 引用:當 storage 賦值給另一個 storage 或 memory 賦值給另一個 memory 時,會創建引用,修改一方會影響另一方
- 副本:其他情況下賦值會創建副本,修改其中一方不會影響另一方
- 狀態變數:存儲在鏈上,合約內所有函數可訪問,gas 消耗高,合約內、函數外聲明
- 局部變數:僅在函數執行過程中有效,存儲在內存中,不上鏈,gas 消耗低,函數內聲明
- 全局變數:Solidity 預留的關鍵字,在函數內可直接使用,如 msg.sender、block.number 等
- wei: 1
- gwei: 1e9
- ether: 1e18
- seconds: 1
- minutes: 60
- hours: 3600
- days: 86400
- weeks: 604800
- 固定長度數組: 在聲明時指定長度,格式為 T[k],其中 T 是元素類型,k 是長度,例如:uint[8] array1;
- 可變長度數組: 在聲明時不指定長度,格式為 T[],例如:uint[] array4;
- 使用 memory 修飾的動態數組可以用 new 操作符創建,必須指定長度,且創建後長度不能改變,例如:uint; 數組字面常數用來初始化數組,例如 [uint(1), 2, 3],元素類型以第一個元素為準
- length: 表示數組的長度
- push(): 在動態數組末尾添加一個元素
- pop(): 移除數組末尾的元素
結構體允許自定義類型,其元素可以是原始類型或引用類型。結構體可以作為數組或映射的元素
- 存儲引用:在函數中創建一個 storage 的 struct 引用
Student storage _student = student;
_student.id = 11;
_student.score = 100;
- 直接引用狀態變量的 struct
student.id = 1;
student.score = 80;
- 構造函數式
student = Student(3, 90);
- 鍵值對方式
student = Student({id: 4, score: 60});
在 Solidity 中,映射 (Mapping) 是一種資料結構,可以通過鍵(Key)查詢對應的值(Value)。例如,可以通過一個人的 ID 查詢他的钱包地址。
mapping(_KeyType => _ValueType)
_KeyType 和 _ValueType 分別代表鍵和值的變量類型。
mapping(uint => address) public idToAddress; // ID 映射到地址
mapping(address => address) public swapPair; // 幣對的映射,地址到地址
-
鍵類型限制:映射的 _KeyType 只能是 Solidity 內置的值類型,如 uint、address 等,不能使用自定義的結構體。
-
錯誤範例:
struct Student {
uint256 id;
uint256 score;
}
mapping(Student => uint) public testVar; // 錯誤,因為 _KeyType 是自定義結構體
- 存儲位置限制:映射的存儲位置必須是 storage,因此可以用於合約的狀態變量、函數中的 storage 變量和 library 函數的參數中。
- 自動生成 Getter 函數:如果映射聲明為 public,Solidity 會自動生成一個 getter 函數,可以通過鍵來查詢對應的值。
_Var[_Key] = _Value;
_Var 是映射變量名,_Key 和 _Value 對應新增的鍵值對。
function writeMap(uint _Key, address _Value) public {
idToAddress[_Key] = _Value;
}
- 不儲存鍵的資訊:映射不儲存任何鍵的資訊,也沒有長度資訊。
- 存取方式:映射使用 keccak256(abi.encodePacked(key, slot)) 作為偏移量來存取值,其中 slot 是映射變量所在的插槽位置。
- 默認值:未賦值的鍵初始值都是該類型的默認值,例如 uint 的默認值是 0。
在 Solidity 中,宣告但未賦值的變量會有其初始值或預設值
- boolean:
false
- string:
""
- int:
0
- uint:
0
- enum: 枚舉中的第一個元素
- address:
0x0000000000000000000000000000000000000000
(或address(0)
) - function
- internal: 空白函數
- external: 空白函數
bool public _bool; // false
string public _string; // ""
int public _int; // 0
uint public _uint; // 0
address public _address; // 0x0000000000000000000000000000000000000000
enum ActionSet { Buy, Hold, Sell }
ActionSet public _enum; // 第1個內容Buy的索引0
function fi() internal {} // internal空白函數
function fe() external {} // external空白函數
- 映射 (mapping): 所有元素為其預設值的 mapping。
- 結構體 (struct): 所有成員設為其預設值的結構體。
- 數組 (array):
- 動態數組: []
- 靜態數組(定長): 所有成員設為其預設值的靜態數組。
// Reference Types
uint[8] public _staticArray; // 所有成員設為其預設值的靜態數組 [0,0,0,0,0,0,0,0]
uint[] public _dynamicArray; // []
mapping(uint => address) public _mapping; // 所有元素都為其預設值的 mapping
// 結構體,所有成員設為預設值 0, 0
struct Student {
uint256 id;
uint256 score;
}
Student public student;
- delete 操作符 delete a 會將變量 a 的值重設為初始值。
bool public _bool2 = true;
function d() external {
delete _bool2; // delete 會將 _bool2 重設為預設值 false
}
- Solidity 中兩個與常量相關的關鍵字:
constant
(常量)和immutable
(不變量) - 當狀態變量聲明了這兩個關鍵字後,初始化後便不能更改數值,這樣可以提升合約的安全性並節省 gas。
- constant:必須在聲明時初始化,之後無法再更改值。
- immutable:可以在聲明時或透過構造函數(constructor)初始化,初始化後也不能更改值,比
constant
更靈活。
- constant 變量需要在聲明的同時初始化,之後無法再更改。
// constant 變量在聲明時初始化,之後不能更改
uint256 constant CONSTANT_NUM = 10;
string constant CONSTANT_STRING = "0xAA";
bytes constant CONSTANT_BYTES = "WTF";
address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000;
immutable 變量可以在聲明時或構造函數中初始化。 自 Solidity v8.0.21 之後,immutable 變量不需要顯式初始化,未初始化的 immutable 變量將使用數值類型的初始值。 若在聲明時和構造函數中都進行初始化,會以構造函數中的值為準。
// immutable 變量可在構造函數中初始化,之後不能更改
uint256 public immutable IMMUTABLE_NUM = 9999999999;
address public immutable IMMUTABLE_ADDRESS;
uint256 public immutable IMMUTABLE_BLOCK;
uint256 public immutable IMMUTABLE_TEST;
constructor() {
IMMUTABLE_ADDRESS = address(this);
IMMUTABLE_NUM = 1118;
IMMUTABLE_TEST = test();
}
function test() public pure returns (uint256) {
return 9;
}
- 使用 Remix 上的 getter 函數檢視 constant 和 immutable 變量的初始化值。 嘗試更改 constant 變量的值會導致編譯失敗,並拋出錯誤:TypeError: Cannot assign to a constant variable. 嘗試更改 immutable 變量的值也會導致編譯失敗,並拋出錯誤:TypeError: Immutable state variable already initialized.