Recommendation:
Since the msg.sender
value do not change when a function is executed with delegatecall, we can create a state variable (named owner
) in the Implementation contract at storage slot 0 and initialize it to the zero address. After this, we can add access control to the delegatecallContract
function:
require(msg.sender == owner);
Now if someone calls the delegatecallContract
function directly in the Implementation contract, the check would fail as no one has access to the zero account, however when the function is called from the proxy contract as a delegatecall, msg.sender
would remain the same, but the owner
state variable would be read from the stroage of the Proxy contract since it is a delegate call and hence the require statement will pass as the slot 0 storage of proxy contract has the address of the proxy contract owner stored.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
contract Implementation {
address constant public owner = address(0); //initialized an address variable at slot 0 with default value of 0 address
function callContract(address a, bytes calldata _calldata) payable external returns (bytes memory) {
(bool success , bytes memory ret) = a.call{value: msg.value}(_calldata);
require(success);
return ret;
}
function delegatecallContract(address a, bytes calldata _calldata) payable external returns (bytes memory) {
require(msg.sender == owner); //implement access control
(bool success, bytes memory ret) = a.delegatecall(_calldata);
require(success);
return ret;
}
}