I have this second-layer delegatecall (delegatecall on a contract that already did delegatecall) that continues to fail even after checking all the main reasons why it could fail (storage layout, uint alias, contract, etc.).
And when I switch from delegatecall to call, it runs perfectly (but I need delegatecall).
I simplified the troubling function to a simple getHi()/getHello() (no args) with a console.log in the body, but it still fails. This is a brief summary of the contracts involved in order to show the basic setup:
(I wouldn't bother too much with the first contract. It's just a proxy that redirect calls)
contract Diamond { AppStorage s; fallback() external payable { LibDiamond.DiamondStorage storage ds; bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } address facet = ds.facets[msg.sig]; require(facet != address(0), "Diamond: Function does not exist"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } } contract FacetX { AppStorage s; using SafeERC20 for IERC20; function getHi() public { console.log(s.facetY); //-----> logs the address of FacetY (bool success, ) = s.facetY.delegatecall( //-----> with `call`, runs smoothly abi.encodeWithSignature('getHello()') ); require(success, 'failed'); //-----> fails with this error } } contract FacetY { AppStorage s; using SafeERC20 for IERC20; function getHello() public view { console.log('hello world'); //-----> never gets logged } } AppStorage is just a struct with all the state vars in a separate file that I import on each contract. Something like:
struct AppStorage { address facetY; address usdt; ... } They're defined elsewhere without issues. That's why s.facetY logs the address of FacetY.