Addresses

正確的 ABI 有效負載打包以呼叫另一個合約的地址

  • December 28, 2019

假設我有以下兩個契約,Dest並且Proxy

pragma solidity ^0.5.0;

contract Dest {
 function foo(string memory name, string memory symbol, uint256 decimals, uint256 totalSupply, address sender, uint256 value) public {
   /*
    * Logic code Here
    */
   }

 function bar(address a, address b, address c, address d, uint256 e, address f, address sender, uint256 value) public {
   /*
    * Logic code Here
    */
 }
}

contract Proxy {
 address private _owner;
 address private _dest;

 constructor(address dest) public {
   _owner = msg.sender;
   _dest = dest;
 }

 function getDest() public view returns(address) {
   return _dest;
 }

 function setDest(address dest) public {
   require(msg.sender == _owner);
   _dest = dest;
 }

 // Invoke takes msg.sender and msg.value and concatenates both to payload before to call the correct method
 function invoke(string memory method, bytes memory data) public payable {
   bytes memory payload = abi.encodePacked(bytes4(keccak256(bytes(method)))), data, abi.encode(msg.sender), abi.encode(msg.value));
   (bool ok, bytes memory response) = _dest.call(payload);
 }
}

合約中的foobar方法都作為最終輸入參數。Dest``address sender, uint256 value

邏輯是使用者將呼叫傳遞完整的方法簽名和除最後兩個之外的Proxy所有 abi 編碼參數。

代理按順序將方法簽名、部分呼叫參數打包在一起,msg.sendermsg.value在一個唯一的打包有效負載中,然後使用打包有效負載作為輸入bytes呼叫該call方法。Dest address

問題是當foo方法被呼叫時,sender輸入參數的值不是原來的msg.sender address,而是包含一些不一致的值。

bar但是當呼叫該方法時一切正常。

我懷疑這個問題與foo方法的輸入參數的頭部有長度可變的字元串有關,而bar只有固定長度的參數。

關於如何建構通用解決方案的任何建議?

通常,您不能只連接部分 ABI 編碼,因為 ABI 編碼將可變長度參數拆分為固定和可變部分。對於函式Foo,正確的編碼是:

selector (4 bytes)
name offset (32 bytes)
symbol offset (32 bytes)
decimals (32 bytes)
totalSupply (32 bytes)
address (32 bytes)
value (32 bytes)
name length (32 bytes)
name body (? bytes)
symbol length (32 bytes)
symbol body (? bytes)

當使用者Foo對除最後兩個之外的所有參數進行編碼時,編碼可能如下所示:

selector (4 bytes)
name offset (32 bytes)
symbol offset (32 bytes)
decimals (32 bytes)
totalSupply (32 bytes)
name length (32 bytes)
name body (? bytes)
symbol length (32 bytes)
symbol body (? bytes)

當您附加msg.senderandmsg.value時,您會得到:

selector (4 bytes)
name offset (32 bytes)
symbol offset (32 bytes)
decimals (32 bytes)
totalSupply (32 bytes)
address (32 bytes)
value (32 bytes)
name length (32 bytes)
name body (? bytes)
symbol length (32 bytes)
symbol body (? bytes)
msg.sender (32 bytes)
msg.value (32 bytes)

所以結果顯然是不正確的。

正確的解決方案是首先使用 將所有參數解碼為單獨的局部變數abi.decode,然後使用msg.sendermsg.valueusing重新打包回來abi.encodeWithSignature。像這樣的東西:

string memory name;
string memory symbol;
uint256 decimals;
uint256 totalSupply;

(name, symbol, decimals, totalSupply) = abi.decode (
 data,
 (string, string, uint256, uint256));
bytes memory payload = abi.encodeWithSignature (
 method, name, symbol, decimals, totalSupply, msg.sender, msg.value);

引用自:https://ethereum.stackexchange.com/questions/78462