Solidity
abi.encodePacked() 產生與 abi.encodeWithSignature() 不同的結果
我在玩 Ethernaut Gatekeeper 2:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.6.0; import "forge-std/Test.sol"; contract GatekeeperTwo is Test { address public entrant; modifier gateOne() { require(msg.sender != tx.origin, "err1"); _; } modifier gateTwo() { uint x; assembly { x := extcodesize(caller()) } require(x == 0, "err2"); _; } modifier gateThree(bytes8 _gateKey) { emit log_named_uint("part1", uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))); require( uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1, "err3" ); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } } contract Attack is Test { constructor() public { GatekeeperTwo gk = new GatekeeperTwo(); address g = address(gk); bytes4 sig = bytes4(keccak256("enter(bytes8)")); log_named_bytes("first method", abi.encode(sig, key())); log_named_bytes("second method", abi.encodePacked(sig, key())); log_named_bytes("third method", abi.encodeWithSignature("enter(bytes8)", key())); (bool success, ) = g.call(abi.encodePacked(sig, key())); require(success); } function key() private returns (bytes8) { uint64 val = uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (type(uint64).max); return bytes8(val); } } contract ContractTest is Test { function setUp() public {} function testExample() public { new Attack(); } }
在我的測試程式碼中,這三行,
log_named_bytes("first method", abi.encode(sig, key())); log_named_bytes("second method", abi.encodePacked(sig, key())); log_named_bytes("third method", abi.encodeWithSignature("enter(bytes8)", key()));
產生不同的輸出:
Logs: first method: 0x3370204e00000000000000000000000000000000000000000000000000000000f719f96375b9c994000000000000000000000000000000000000000000000000 second method: 0x3370204ef719f96375b9c994 third method: 0x3370204ef719f96375b9c994000000000000000000000000000000000000000000000000
只有第三個產生正確的呼叫數據並破解關卡。為什麼其他兩個不工作?
合約函式呼叫需要一個 bytes4 函式選擇器以及 abi 編碼參數(每個填充到 32 個字節)。
abi.encode(sig, key());
將
sig
(bytes4) 和key()
(bytes8) 都填充到 32 個字節。因為sig
被視為 bytes32,這會產生不正確的編碼。abi.encodePacked(sig, key());
將
sig
(bytes4) 和key()
(bytes8) 都打包,沒有任何額外的填充。因為key()
不是正確的 (bytes32) 格式,所以這是不正確的編碼。abi.encodeWithSelector(sig, key());
返回正確的編碼,一個 bytes4
sig
以及一個 32 字節的填充值key()
。以下是等價的:
abi.encodeWithSelector(sig, key()); abi.encodeWithSignature("enter(bytes8)", key()); abi.encodePacked(sig, abi.encode(key()));