diff --git a/src/EVault/Dispatch.sol b/src/EVault/Dispatch.sol index db778c9d..8753efc4 100644 --- a/src/EVault/Dispatch.sol +++ b/src/EVault/Dispatch.sol @@ -112,13 +112,15 @@ abstract contract Dispatch is function delegateToModuleView(address module) private view { assembly { // Construct optimized custom call data for `this.viewDelegate()` - // [selector 4B][module address 32B][calldata with stripped proxy metadata] + // [selector 4B][module address 32B][calldata with stripped proxy metadata][caller address 32B] // Proxy metadata will be appended back by the proxy on staticcall mstore(0, 0x1fe8b95300000000000000000000000000000000000000000000000000000000) mstore(4, module) - calldatacopy(36, 0, calldatasize()) - // insize: calldatasize + 36 (signature and address) - proxy metadata size - let result := staticcall(gas(), address(), 0, sub(add(calldatasize(), 36), PROXY_METADATA_LENGTH), 0, 0) + let strippedCalldataSize := sub(calldatasize(), PROXY_METADATA_LENGTH) + calldatacopy(36, 0, strippedCalldataSize) + mstore(add(36, strippedCalldataSize), caller()) + // insize: stripped calldatasize + 36 (signature and module address) + 32 (caller address) + let result := staticcall(gas(), address(), 0, add(strippedCalldataSize, 68), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } diff --git a/src/EVault/shared/Base.sol b/src/EVault/shared/Base.sol index 2856b34b..1ce9f4d0 100644 --- a/src/EVault/shared/Base.sol +++ b/src/EVault/shared/Base.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {EVCClient} from "./EVCClient.sol"; import {Cache} from "./Cache.sol"; +import {ProxyUtils} from "./lib/ProxyUtils.sol"; import {RevertBytes} from "./lib/RevertBytes.sol"; import {IProtocolConfig} from "../../ProtocolConfig/IProtocolConfig.sol"; @@ -45,7 +46,18 @@ abstract contract Base is EVCClient, Cache { } modifier nonReentrantView() { - if (vaultStorage.reentrancyLocked) revert E_Reentrancy(); + if (vaultStorage.reentrancyLocked) { + address hookTarget = vaultStorage.hookTarget; + + // the hook target is allowed to bypass the RO-reentrancy lock. the hook target can either be a msg.sender + // when the view function is inlined in the EVault.sol or the hook target should be taked from the trailing + // data appended by the delegateToModuleView function used by useView modifier. in the latter case, it is + // safe to consume the trailing data as we know we are inside the useView because msg.sender == address(this) + if (msg.sender != hookTarget && !(msg.sender == address(this) && ProxyUtils.useViewCaller() == hookTarget)) + { + revert E_Reentrancy(); + } + } _; } diff --git a/src/EVault/shared/lib/ProxyUtils.sol b/src/EVault/shared/lib/ProxyUtils.sol index 61ad56b6..2557e9b8 100644 --- a/src/EVault/shared/lib/ProxyUtils.sol +++ b/src/EVault/shared/lib/ProxyUtils.sol @@ -5,6 +5,8 @@ pragma solidity ^0.8.0; import {IERC20} from "../../IEVault.sol"; import {IPriceOracle} from "../../../interfaces/IPriceOracle.sol"; +import "../Constants.sol"; + /// @title ProxyUtils Library /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice The library provides a helper function for working with proxy meta data @@ -16,4 +18,10 @@ library ProxyUtils { unitOfAccount := shr(96, calldataload(sub(calldatasize(), 20))) } } + + function useViewCaller() internal pure returns (address viewCaller) { + assembly { + viewCaller := shr(96, calldataload(sub(calldatasize(), add(PROXY_METADATA_LENGTH, 20)))) + } + } } diff --git a/test/unit/evault/EVaultTestBase.t.sol b/test/unit/evault/EVaultTestBase.t.sol index 8285884f..390f2ce3 100644 --- a/test/unit/evault/EVaultTestBase.t.sol +++ b/test/unit/evault/EVaultTestBase.t.sol @@ -146,6 +146,10 @@ contract MockHook { function deposit(uint256, address) external view { address asset = IEVault(msg.sender).asset(); + // these calls are just to test if there's no RO-reentrancy for the hook target + IEVault(msg.sender).totalBorrows(); + IEVault(msg.sender).balanceOf(address(this)); + if (asset != caller()) revert E_OnlyAssetCanDeposit(); }