Solidity
檢測.call、view、純執行模式
是否可以在合約中檢測到函式正在以只讀模式執行?
背景:
考慮一個類似於 Diamond 模式的模組化系統,它使用基於代理的轉發方案將消息發送到實現契約。考慮到每個實現的功能都有一個由基於角色的訪問控制處理的相應角色 (1:1)。到目前為止,一切都很好,但是對於基於角色的守衛來說
view
,這是不可取的。pure
它們可以對所有人開放。代理/轉發器能否檢測到消息是只讀且無害的?如果可以檢測到,那麼轉發器可以安全地跳過訪問控制保護。
/*************************************************************************** * Forward transactions ***************************************************************************/ fallback() external { _fallback(); } receive() external payable { _fallback(); } function _fallback() private { bytes4 sel = msg.sig; // cannot call an uncontrolled function require(exists(sel)); // must be authorized to call the function require(hasRole(getRole(sel), msg.sender)); <== unless this is a view/pure, but how to know? // carry on to DELEGATECALL
有沒有辦法檢查我們是否處於只讀、視圖/純上下文或狀態更改事務中?
當然有可能,會花費你幾百塊油。我發現最便宜的方法是嘗試在外部子呼叫中發出事件
this
。首先聲明事件和方法:
event StaticCallCheck(); function staticCallChecker() external { require(msg.sender == address(this), "Access denied"); emit StaticCallCheck(); }
然後嘗試用try外部呼叫這個方法:
bool isStaticCall = false; try this.staticCallChecker() {} catch { isStaticCall = true; }
順便說一句,view 和 pure 的區別只存在於編譯時,在執行時也是一樣的。因此,您可以使用
pure
介面呼叫view
方法,反之亦然。
響應k06a
我明白為什麼這種方法在原則上可行,但我沒有成功地讓它在實踐中發揮作用。
這個小實驗使用 hacky check (
// HACK
) 來動態檢查模式。像這樣部署時,意外的結果:
- 部署實施.sol
- 使用 Implementation.address 部署轉發器
- 實例化 Implementation.at(Forwarder.address)
當
require
檢查staticCall
並被seeSomething
呼叫(使用 Remix)時,它會失敗並顯示that was not a static call
.pragma solidity 0.7.6; // SPDX-License-Identifier: UNLICENSED contract Implementation { uint public _nonce; function seeSomething() public view returns(address) { return address(this); } function saySomething() public returns(uint nonce) { nonce = _nonce++; } } contract Forwarder { address public implementation; event StaticCallCheck(); constructor(address _implementation) { implementation = _implementation; } function staticCallChecker() external { require(msg.sender == address(this), "Access denied"); emit StaticCallCheck(); } function isStaticCall() public returns(bool isStatic) { try this.staticCallChecker() {} catch { isStatic = true; } } fallback() external { _fallback(); } receive() external payable { _fallback(); } function _fallback() private { // HACK require(isStaticCall(), "That was not a static call"); // require(!isStaticCall(), "That was not a static call"); address _implementation = address(implementation); uint256 value = msg.value; // solhint-disable-next-line no-inline-assembly assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := call(gas(), _implementation, value, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }
如果
require
下面的兩個語句// HACK
都被註釋掉,沒有檢查,那麼轉發器按預期工作 - 愉快地返回視圖/純值並在指示時更新狀態。因此,貨運代理正在工作,並且
isStaticCall()
在false
所有情況下都是如此。客戶是否隱含要求使用非標準方法?