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
        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.

1 Answer 1



delegatecall forwards msg.value (besides msg.sender).

I was calling the proxy while sending Ether and even though my functions don't handle Ether per se, I still needed to label them as payable (both functions in the second and third contract). Otherwise, the tx will fail.

So by adding payable to getHi() and getHello(), the problem was fixed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.