轉載請注明出處:https://www.cnblogs.com/zhizaixingzou/p/10122350.html
目錄
1. 調用已部署的智能合約
相關截圖來自:
https://solidity-cn.readthedocs.io/zh/develop/abi-spec.html
https://github.com/ethereum/ethereumj
1.1. ABI是payload編碼的依據
ABI,應用二進制接口(Application Binary Interface)。它是從區塊鏈外部與合約進行交互以及合約與合約間進行交互的一種標准方式。根據它可以得到函數簽名編碼,同樣根據它以及實際參數得到參數編碼,兩部分的字節序列拼接而得payload。
1.2. 函數簽名編碼
函數簽名編碼又叫函數選擇器,是函數簽名的 Keccak(SHA-3)哈希的前 4 字節。函數簽名:函數名稱加上由括號括起來的參數類型列表,參數類型間由一個逗號分隔開,且沒有空格。函數的返回類型並不是這個簽名的一部分。
1.3. 參數編碼
參數類型可以分為:
l 基礎類型,如uint<M>、int<M>、address、uint、int、bool、fixed<M>x<N>、ufixed<M>x<N>、fixed、ufixed、bytes<M>、function。
l 定長數組類型:<type>[M]。
l 非定長數組類型:bytes、string(UTF-8)、<type>[]。
以下類型被稱為“動態”:
l bytes
l string
l 任意類型 T 的變長數組 T[]
l 任意動態類型 T 的定長數組 T[k] (k >= 0)
l 由動態的 Ti (1 <= i <= k)構成的元組 (T1,...,Tk)
所有其他類型都被稱為“靜態”。
不同類型的參數編碼方式不一樣,下面以實際例子講解。
1.4. payload編碼原理
合約如下:
pragma solidity ^0.4.16;
contract Foo {
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function bar(bytes3[2]) public pure {}
function sam(bytes, bool, uint[]) public pure {}
}
想用 69 和 true 做參數調用 baz,我們總共需要傳送 68 字節,可以分解為:
l 0xcdcd77c0:方法ID。這源自ASCII格式的 baz(uint32,bool) 簽名的 Keccak 哈希的前 4 字節。
l 0x0000000000000000000000000000000000000000000000000000000000000045:第一個參數,一個被用 0 值字節補充到 32 字節的 uint32 值 69。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第二個參數,一個被用 0 值字節補充到 32 字節的 boolean 值 true。
合起來就是:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
如它返回 false,那么它的輸出將是一個字節數組,一個bool值:
0x0000000000000000000000000000000000000000000000000000000000000000
想用 ["abc", "def"] 做參數調用 bar,我們總共需要傳送68字節,可以分解為:
l 0xfce353f6:方法ID。源自 bar(bytes3[2]) 的簽名。
l 0x6162630000000000000000000000000000000000000000000000000000000000:第一個參數的第一部分,一個 bytes3 值 "abc" (左對齊)。
l 0x6465660000000000000000000000000000000000000000000000000000000000:第一個參數的第二部分,一個 bytes3 值 "def" (左對齊)。
合起來就是:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
想用 "dave"、true 和 [1,2,3] 作為參數調用 sam,我們總共需要傳送 292 字節,可以分解為:
l 0xa5643bf2:方法ID。源自 sam(bytes,bool,uint256[]) 的簽名。注意,uint 被替換為了它的權威代表 uint256。
l 0x0000000000000000000000000000000000000000000000000000000000000060:第一個參數(動態類型)的數據部分的位置,即從參數編碼塊開始位置算起的字節數。在這里,是 0x60 。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第二個參數:boolean 的 true。
l 0x00000000000000000000000000000000000000000000000000000000000000a0:第三個參數(動態類型)的數據部分的位置,由字節數計量。在這里,是 0xa0。
l 0x0000000000000000000000000000000000000000000000000000000000000004:第一個參數的數據部分,以字節數組的元素個數作為開始,在這里,是 4。
l 0x6461766500000000000000000000000000000000000000000000000000000000:第一個參數的內容:"dave" 的 UTF-8 編碼(在這里等同於 ASCII 編碼),並在右側(低位)用 0 值字節補充到 32 字節。
l 0x0000000000000000000000000000000000000000000000000000000000000003:第三個參數的數據部分,以數組的元素個數作為開始,在這里,是 3。
l 0x0000000000000000000000000000000000000000000000000000000000000001:第三個參數的第一個數組元素。
l 0x0000000000000000000000000000000000000000000000000000000000000002:第三個參數的第二個數組元素。
l 0x0000000000000000000000000000000000000000000000000000000000000003:第三個參數的第三個數組元素。
合起來就是:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
實際上,函數返回值和事件的參數也會被用同樣的方式進行編碼。
1.5. ABI的JSON表示
一個描述函數的ABI的JSON表示含如下構成:
當源碼被編譯后,就能得到函數的ABI,如Remix上的情況如下:
1.6. EthereumJ內置的payload編碼工具
Java版Ethereum已經實現了編碼功能的內置:
可以看到,內置的功能可以直接輸入ABI的JSON表示,然后給出實際的參數就可以得到整個的payload。
1.7. 合約執行過程及結果解析
合約執行過程分析:
如果data小於4字節,則返回程序結果Return為空,並置為REVERT。
加載data,取出前4字節,與合約的所有方法的4字節碼編碼對比,匹配上則跳轉到指定方法的入口並執行。然后執行,如果有返回值,則得到后放到程序結果的Return中。
Abi.Function的decodeResult方法就可以解析返回的Return數據,得到的是List<?>。