--- 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 提前了解調用的函數,否則會有安全上的隱患。 :::