Solidity

對外部合約的可選多參數呼叫

  • August 13, 2018

出於商業原因,我想一半實施 ERC223。這意味著能夠在收到代幣時通知其他合約,但如果接收方是未實現 tokenFallback 功能的合約,則無法恢復。因此,以下情況應該起作用:

  1. 使用者將令牌轉移到一個地址。沒有呼叫 tokenFallback(因為它只是一個地址),傳輸成功。
  2. 使用者將代幣轉移到沒有實現 tokenFallback 的合約。沒有來電,轉接成功。
  3. 使用者將代幣轉移到執行 tokenFallback 的合約。函式被呼叫,傳輸成功。

以下是我迄今為止的發現:

  • 我可以邊做邊打電話TokenRecipient(address).tokenFallback(msg.sender, value, 0),這適用於 1 和 3,但不適用於 2,因為我找不到檢查契約是否符合 ERC223 介面的方法。
  • 我可以通過做來呼叫to.call(<parameters>),如果我省略 bytes 參數,這將有效。但問題是 ERC223 呼叫包含這個 bytes 參數,甚至他們的參考實現似乎並沒有真正以這種方式工作:https ://github.com/Dexaran/ERC223-token-standard/issues/51我的行為我看到的是,只要有一個bytes參數,call()就開始返回 false 並且不做任何事情。
  • 理論上我可以使用內聯彙編來進行呼叫,但我找不到任何關於如何正確編碼bytes參數的文件。我知道這是可能的,因為這裡的第一個選項是跨契約的,我只需要知道如何實際進行程序集呼叫。

任何人都可以提供有關前往這裡的最佳方式的任何指導嗎?call()如果該函式不存在,是否有解決方法或阻止正常呼叫恢復的方法?或者,有人可以向我指出一些關於如何準備從內聯彙編呼叫的文件嗎?

編輯:這是我為大會嘗試過的。似乎不起作用,因為該函式存在時沒有被呼叫:

function _internalNoThrowTokenFallback(address contractRecipient, address from, uint value, bytes data)
   internal
{
   // We need to do this in assembly because:
   //   1. Calling the function as normal would result in a revert if the destination contract doesn't
   //      implement tokenFallback. We want this case to be ignored.
   //   2. We could use contractRecipient.call() but that doesn't work with bytes parameters: 
   //      https://github.com/ethereum/solidity/issues/2884
   //   3. So we need to do the call the same way 
   assembly {
       // Get some free memory to copy our params over to.
       let free_ptr := mload(0x40)

       // Tee up the function selector in the memory we've gotten
       mstore(free_ptr, 0xc0ee0b8a)

       // Load our calldata across to the new call, except for
       //  - The function selector (4 bytes)
       //  - The first address parameter (64 bytes when padded)
       //
       // Thus making a total of 68 bytes we want to ignore, and we want to copy the
       // data to our free_ptr after our function selector
       calldatacopy(add(free_ptr, 4), 68, sub(calldatasize, 68))

       // Ok, call the function without allowing the other contract to alter our state
       let result := staticcall(
           30000,                  // Give the other contract 30k gas to work with
           contractRecipient,      // The other contract's address
           free_ptr,               // We've prepared the inputs at free_ptr.
           sub(calldatasize, 64),  // They're the same length as our inputs were minus the address parameter.
           0,                      // There's no return from the function
           0
       )

       // Don't check the result or revert if the call failed. We want to proceed regardless.
   }
}

在與這個問題的核心團隊合作後,我已經能夠解決這個問題。我正在為將來偶然發現此問題的任何人分享解決方案。

語境

  • call()有一個錯誤,如果函式參數是動態類型(例如bytes.
  • 在solidity v0.5 中使用call()編碼參數的版本將不再被允許並且不會編譯。call()只會採用一個bytes參數,即帶有選擇器的預編碼函式呼叫和所有參數,符合 abi 規範。
  • abi.encodeWithSignature()已為此目的引入,它沒有具有call()並正確編碼函式參數的錯誤,即使是動態參數。

解決方案

所以,如果你想在另一個合約上選擇性地呼叫這個函式:

function tokenFallback(address from, uint amount, bytes data) public {
   // Do stuff here
}

您有以下選擇:


**正常呼叫:**將接收合約轉換為 abi 中具有此函式簽名的類型,並正常呼叫該函式,例如

CallReceiver receiver = CallReceiver(to);
receiver.tokenFallback(msg.sender, value, data);

在這種情況下,如果接收方沒有實現tokenFallback整個事務,那麼整個事務將被還原,這使得它不是很可選。


可選呼叫: call()例如

to.call(abi.encodeWithSignature(
   "tokenFallback(address,uint256,bytes)", 
   msg.sender,
   value,
   data
));

這將返回truefalse取決於呼叫是否成功,這使您可以忽略失敗並繼續執行。請注意,即使函式是使用uint參數聲明的,也是uint256在計算函式選擇器時。規範類型始終用於參數,並且函式簽名中的類型之間沒有空格。abi 規範中的更多資訊。


**彙編呼叫:**您可以為此使用內聯彙編,但鑑於我能夠在不這樣做的情況下找到解決方案,因此我選擇不走那條路。上面的程序集的第一個問題是它移動了靜態參數並且沒有更新指向bytes參數的動態指針,但我不確定這是否是唯一的問題,因為我沒有費心去解決這個問題,因為call()with abi.encodeWithSignature()works .

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