Solidity
對外部合約的可選多參數呼叫
出於商業原因,我想一半實施 ERC223。這意味著能夠在收到代幣時通知其他合約,但如果接收方是未實現 tokenFallback 功能的合約,則無法恢復。因此,以下情況應該起作用:
- 使用者將令牌轉移到一個地址。沒有呼叫 tokenFallback(因為它只是一個地址),傳輸成功。
- 使用者將代幣轉移到沒有實現 tokenFallback 的合約。沒有來電,轉接成功。
- 使用者將代幣轉移到執行 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 ));
這將返回
true
或false
取決於呼叫是否成功,這使您可以忽略失敗並繼續執行。請注意,即使函式是使用uint
參數聲明的,也是uint256
在計算函式選擇器時。規範類型始終用於參數,並且函式簽名中的類型之間沒有空格。abi 規範中的更多資訊。**彙編呼叫:**您可以為此使用內聯彙編,但鑑於我能夠在不這樣做的情況下找到解決方案,因此我選擇不走那條路。上面的程序集的第一個問題是它移動了靜態參數並且沒有更新指向
bytes
參數的動態指針,但我不確定這是否是唯一的問題,因為我沒有費心去解決這個問題,因為call()
withabi.encodeWithSignature()
works .