---
title: 'Solidity WTF 102 27 ~ 29 單元'
lang: zh-tw
---
Solidity WTF 102 27 ~ 29 單元
===
:::info
:date: 2024/10/08
:::
[TOC]
# 課程學習
## ABI
### 簡介
ABI(Application Binary Interface, 應用二進制接口)是與乙太智能合約交互的標準。
有四種函數
- `abi.encode`
- `abi.encodePacked`
- `abi.encodeWithSignature`
- `abi.encodeWithSelector`
:::success
ABI將每個參數轉成`bytes32`,也就是`64`個`16進制`。
:::
### 使用方式
#### abi.encode
這邊是`ABI` `encode`後出來的內容
```javascript!
// 這邊是變數內容
int x = 10;
address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
string name = "0xAA";
uint[2] array = [5, 6];
// 當我透過encode去轉成與合約交互的數據
function encode() public view returns(bytes memory result) {
result = abi.encode(x, addr, name, array);
}
// 結果
0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
// 整理後
// 因為abi會轉成bytes32 所以就抓64個16進制字符
//
0x // title不看
000000000000000000000000000000000000000000000000000000000000000a
// 0 ~ 32
// 最後面尾數是a,在16進制中代表10,也就表示了這邊是變數X中的10。
0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c71
// 32 ~ 64
// 這邊就是地址addr去掉0x
00000000000000000000000000000000000000000000000000000000000000a0
// 64 ~ 96
// 16的一次方 * 10
// 這邊則是告訴我們name字串的值放在哪一個字節,這邊a0的16進制就是160,可以往160看
0000000000000000000000000000000000000000000000000000000000000005
// 96 ~ 128
// 在array中,第一個數值5
0000000000000000000000000000000000000000000000000000000000000006
// 128 ~ 160
// 在array中,第二個數值6
0000000000000000000000000000000000000000000000000000000000000004
// 160 ~ 192
// 這邊的4代表字串長度
3078414100000000000000000000000000000000000000000000000000000000
// 192 ~ 224
// 把"0XAA"轉成16進制,就會是30784141
```
:::warning
如果你要和合約交互,你要用的就是`abi.encode`。
:::
#### abi.encodePacked
類似`abi.encode`,但會省略很多`0`
當想省空間,並且不跟合約交互,就可以使用。
```javascript!
// 使用方法
function encodePacked() public view returns(bytes memory result) {
result = abi.encodePacked(x, addr, name, array);
}
// 結果
0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006
// 整理後
0x000000000000000000000000000000000000000000000000000000000000000a
7a58c0be72be218b41c608b7fe7c5bb630736c71
30784141
0000000000000000000000000000000000000000000000000000000000000005
0000000000000000000000000000000000000000000000000000000000000006
// 一目瞭然
```
#### abi.encodeWithSignature
第一個參數會是函數簽章,當要調用其他合約時可以使用。
他會比`abi.encode`前面多四字節(`bytes4`),也就是`8`個`16`進制。
> 函數選擇器是使用函數名和參數進行處理(Keccak-Sha3)來標示
> 是4bytes
```javascript!
function encodeWithSignature() public view returns(bytes memory result) {
result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array);
}
// 編碼結果
0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
// 整理
0x
e87082f1 //函數選擇器
000000000000000000000000000000000000000000000000000000000000000a
0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c71
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000005
0000000000000000000000000000000000000000000000000000000000000006
0000000000000000000000000000000000000000000000000000000000000004
3078414100000000000000000000000000000000000000000000000000000000
```
:::success
函數選擇器,就是通過函數名稱和參數進行`Keccak-Sha3`,用於不同函數的辨別
:::
#### abi.encodeWithSelector
與`encodeWithSignature`類似,只不過前面是`Keccak256`後的四字節(`bytes4`)。
結果會與`encodeWithSignature`一致,但使用方法不同。
```javascript!
function encodeWithSelector() public view returns(bytes memory result) {
result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array);
}
// 結果
0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000
// 與encodeWithSignature一致
```
### ABI 解碼
#### abi.decode
看名稱就知道,`encode`加密`decode`解密的這種概念。
要給予`data`(`abi.encode`的結果)與參數,今天如果使用線上編譯器,也是需要這幾樣參數。
```javascript!
function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) {
(dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2]));
}
```
### 重點
> 有一些線上編譯器可以使用,不管是keccak256處理,又或者是decode or encode。
:::success
注意`encodeWithSelector`和`encodeWithSignature`使用方式,很接近但不截然相同,且都是需要知道調用的函數名。
:::
:::warning
在以太坊智能合約中,當合約被部署到區塊鏈上後,合約的代碼會儲存在特定的合約地址中。這段代碼包括了合約的函數邏輯和數據結構。智能合約的函數並不會以顯式的「函數」形式存在,而是轉換為低級的EVM(Ethereum Virtual Machine)字節碼,這段字節碼是合約的執行邏輯。
:::
## Hash
### 簡介
又稱哈希函數,是一種密碼學概念,寫程式是脫離不了的。
他能夠將某任意值轉成一個固定長度的值,並且不太能回推,只能用暴力枚舉。
- 單向性:從輸入的消息到其哈希的正向運算簡單且唯一確定,而反向運算則非常困難,只能通過暴力枚舉來實現。
- 靈敏性:輸入的消息稍微改變,對應的哈希值會發生很大的變化。
- 高效性:從輸入消息到哈希值的計算應該是高效的。
- 均一性:每個哈希值被選中的概率應該基本相等,避免偏差。
- 抗碰撞性:難以找到不同的兩個輸入,其哈希值相同。
> 看過就好,不太需要牢記
>> Hash的應用
>> - 生成唯一標示(有可能碰撞機率極低)
>> - 加密簽名
>> - 安全加密
### Keccak256
是`Solidity`很常用使用的加密方式
#### Keccak256和sha3
`sha3`其實是由`keccak`標準化來的,在早期算是同意詞,但在`2015年8月``SHA3`完成標準化時,`NIST`調整了填充算法。所以`SHA3`就和`Keccak`計算出的結果不同了。
## 函數選擇器Selector
### 簡介
調用合約本質上是向目標合約發送一段`calldata`。
> 存在於input,且前四個字節就是函數選擇器。
### msg.data
是一個全局變量,值是完整的`calldata`(調用函數時傳入的數據)。
假設參數為`0x2c44b726ADF1963cA47Af88B284C06f30380fC78`時,輸出結果回下:
```javascript!
// event 返回msg.data
event Log(bytes data);
function mint(address to) external{
emit Log(msg.data);
}
// 結果
0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78
// 整理
// 前四字節是函數選擇器
0x6a627842
// 後面為輸入的參數
0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78
```
:::info
`calldata`就是告訴智能合約,`我要調用哪一個函數以及參數為何` !
:::
### method id、selector、函數簽名
其實可以當作相同的東西。
> 想要計算函數簽名可以用這種方式
```javascript!
// 這樣即可
bytes4(keccak256("函数名(参数类型1,参数类型2,...)"))
```
### 重點
:::warning
`abi`與`keccak256`類似,但使用場景需要在注意,`abi`可以傳遞參數並給予函數名稱來進行函數選擇器。`keeccak256`處理後需要在`bytes4`取前四字節,才是函數選擇器的簽名。
:::
:::warning
若要選擇調用函數,使用`abi`較為簡潔。
:::
:::success
提前了解調用的函數,否則會有安全上的隱患。
:::