This is playing with fire, but it can be done:
function execute(address _to, bytes calldata _data)
public
payable
returns (bytes memory)
{
(bool success, bytes memory returnData) = _to.call{value: msg.value}(_data);
require(success);
return returnData;
}
Note that "success" returns true any time there is no error... but there is no checking for whether the end address is a contract, or whether the function you are trying to call exists, etc.
Also, you shouldn't change any state after doing this call. You're asking for reentrancy hacks.
How to generate that _data
param? Here's how you'd do it in a default hardhat test environment (using ethers js):
function asExecuteArg(functionName, argTypes, ...args) {
let signature = `function ${functionName}(${argTypes})`
let ABI = [signature];
let interface = new ethers.utils.Interface(ABI);
let output = interface.encodeFunctionData(functionName, args);
return output;
}
const AccountForwarder = await ethers.getContractFactory("AccountForwarder");
contractInstance = await AccountForwarder.deploy();
const executeArg = asExecuteArg('existingFunction', "uint256", 12);
await contractInstance.execute(contractAddress, executeArg);
contractAddress
will be the address of the deployed contract that has existingFunction(uint256 _someArg)
in it.