本篇預計閱讀時間 10 分鐘, 實作時間 10 分鐘。
在開始之前先來思考底下的練習題,閱讀後即可練習完成。
練習題
-
練習題 1
-
請觀察 0.8 和 0.7 版本分別如何處理 Overflow and Underflow?
-
// SPDX-License-Identifier: MIT
pragma solidity 0.7.0;
// pragma solidity ^0.8.0;
contract UnderorOver {
uint8 public myUint8;
function decrement() public {
myUint8 -= 1;
}
function increment() public {
myUint8 += 1;
}
}
-
練習題 2
-
如何使用一個智能合約來 Deploy 另一個智能合約?
-
a, b = b, a
可以在 python 成功把 a, b 兩個值交換,
在 Function 的章節提過同時賦值多個變數的方法,而這段程式碼在 Solidity 的運作會怎麼樣呢?
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract myContract {
uint public a = 1;
uint public b = 0;
function swap() public{
(a, b) = (b, a);
}
}
new
我們可以透過 new
來宣告任何新的物件,前面我們也有嘗試過使用 new
關鍵字來動態記憶體配一個陣列,當然我們也可以以此創建一個合約。
Call by Reference vs. Call by Value
在 Solidity 中,函數的參數傳遞也和其他程式語言相同,有傳址(Call by Reference)和傳值(Call by Value)的區別:
-
傳址(Call by Reference):代表我們真的把此變數地址傳入函數的參數中,在函式中進行運作時如果我們去改變這個參數的值,那離開函數後這個傳入的變數也會改變。
-
傳值(Call by Value):在 Solidity 的編譯器中,會新建一個複製此參數的值的變數,然後傳入我們的函式中進行運算。也就是說,我們如果在這個函式中改變這個變數,在函數結束後,這個傳入的參數不會被影響。
變數型態的參數(bool
, uint
, bytes
, addres
)會是以傳值(Call by Value)傳入函數,而非變數型態像是我們之後會介紹到的資料結構(array
, struct
, mapping
, string
)們,則是傳址(Call by Reference)。
為了和狀態變數區辨,我們會習慣在函數的參數名稱前加上一個底線(_, underscore)。
Data Locations - Storage, Memory and Calldata
在 Solidity 裡面我們有三種宣告記憶體的方法:Storage, Memory 和 Calldata。
合約裡的 storage
會在合約建置時預先配置記憶體,如果是在函數裡面被宣告的記憶體不能被配置為 storage
。相對的,memory
不能在合約建置時預先配置。
-
Storage
代表這個變數會永遠地儲存在區塊鏈中 -
Memory
變數是暫時存在的,在external
函數在合約內被 call 並結束之後他們也會隨之被刪除 -
Storage
和Memory
的關係就像電腦的硬碟和 RAM
大多數的時候我們都不需要特別去敘述變數在記憶體的儲存類型,因為 Solidity 非常聰明會幫我們處理這些記憶體問題。
就像狀態變數(State variables)會預設為 Storage
,永遠地儲存在區塊鏈上。而區域變數會預設以 memory
的形式儲存,在函數結束後就會消失。
而 calldata
:一般只有 external
函數的參數(不包括返回參數)被強制指定為 calldata
。這種變數是唯讀不可改變的,不會持久化到區塊鏈,效果類似 memory
。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
contract Test {
function memoryTest(string memory _exampleString) public returns (string memory) {
_exampleString = "example";
// 可以修改 memory // You can modify memory
string memory newString = _exampleString;
// 可以在函式中使用 memory // You can use memory within a function's logic
return newString;
// 可以回傳 memory // You can return memory
}
function calldataTest(string calldata _exampleString) external returns (string memory) {
// 無法改變 _exampleString // cannot modify _exampleString
// 也無法回傳 // but can return it
return _exampleString;
}
}
Storage Slot
在 Solidity 中的 Storage 記憶體配置模式是以 slot 為單位的,一個 slot 能容納的資料量為 32 bytes。我們可以把智能合約的記憶體想成是一個雜湊表(mapping),容量為 2^256 個 32-bytes 的 slots。
依照相對應的型別會有不同存入 slot 的規則。
練習題解答
-
練習題解答 1
-
請觀察 0.8 和 0.7 版本分別如何處理 Overflow and Underflow?
-
版本不同會有不同的整數溢位錯誤:
- Solidity 版本 < 0.8: 不會出現任何 overflow / underflow 錯誤
- Solidity 版本 >= 0.8: 會出現 overflow / underflow 錯誤
- 我們應該要使用 SafeMath 來避免運算時的 overflow 和 underflow
-
練習題解答 2
-
如何使用一個智能合約來 Deploy 另一個智能合約?
-
如果要佈署另一個合約的話從底層的角度你得讓 Deployer 知道 ABI 跟 bytecode,但在 Solidity 裡面用 new 跟賦值的方式就可以省掉中間複製貼上的部分。
contract A { // ...
}
contract DeployA {
function Deploy() public {
mapping(address => address) addr2Contract;
address contractAaddress = new A(); addr2Contract[msg.sender] = contractAaddress ;
}
}
/*
Reference:
https://docs.soliditylang.org/en/v0.8.9/contracts.html#creating-contracts
*/