Error calling proxy function from proxy: Address: low-level delegate call failed

Here is the setup:
I have a ProxyFactory, which has stores the address of the implementation, It creates a proxyAdmin, then it has a deploy function, which creates proxies for the logic contract.

The logic contract has a function, which needs to verify that the object calling it, is of the same kind: aka something of the like:
IERC165Upgradeable(_dst).supportsInterface(type(IRemix).interfaceId)

If I deploy the logic contract manually, this works just fine, however when I use a proxy, this results in:
Error: VM Exception while processing transaction: reverted with reason string 'Address: low-level delegate call failed'

When I call the proxy functions from "outside" (node.js, ethers.js) it all works fine, and I don't have any error. my Mocha tests are passing fine as well. But when the proxy calls a proxy, then it becomes problematic.

:1234: Code to reproduce

Proxy 1 calls:

IRemix(parent).requestDerivative(authors, address(this))

This comes from this function:

    /// @dev Initialize a split sum to check that sum of all splits is 10000 (100%)
    /// @return _senderIsAuthor if the sender is an author
    function _splitSum() internal returns (bool _senderIsAuthor) {
        uint256 _sum = 0;
        _senderIsAuthor = false;

        for (uint256 _i = 0; _i < authors.length; _i++) {
            // Add splits for authors
            if (msg.sender == authors[_i]) {
                _senderIsAuthor = true;
            }

            splitAddresses.push(authors[_i]);  // Add split address for each author
            splits[authors[_i]] = authorSplits[_i];  // Add split amount for each author
            _sum += authorSplits[_i];  // Add splits to working sum
            _mintBadge(authors[_i]);  // Mint a badge to each author
        }

        for (uint256 _j = 0; _j < parents.length; _j++) {
            // Add splits for parent tokens
            splitAddresses.push(parents[_j]);  // Add split address for each parent token
            splits[parents[_j]] = parentSplits[_j];  // Add split amount for each parent token
            _sum += parentSplits[_j];  // Add splits to working sum

            // Request derivatives from each specified parent
            require(
                IRemix(parents[_j]).requestDerivative(authors, address(this))
            );
        }

        // Ensure valid split total
        require(_sum == 10000, "INVALID_SPLIT_SUM");

        return _senderIsAuthor;
    }

Proxy 2 receives:

    /// @dev Request derivative by a child token contract
    /// @param _authors Address of authors to check for validity of holding a RMX
    /// @param _dst Address of the Remix contract to send the derivative token to
    /// @return _derivativeSuccess Success for the request
    function requestDerivative(address[] memory _authors, address _dst) external override isDeployed returns (bool _derivativeSuccess) {
        // Verifies that the address is a Remix contract
       
        // WHERE I GET THE PROBLEM

        require(
            IERC165Upgradeable(_dst).supportsInterface(type(IRemix).interfaceId),
            "INVALID_REMIX_CONTRACT"
        );
       // IRRELEVANT AS THIS WORKS FINE

        require(msg.sender == _dst, "DERIVATIVE_RIGHTS_MUST_BE_ASKED_BY_DST");

        // License check
        bool _hasLicense = false;

        for (uint256 _i = 0; _i < _authors.length; _i++) {
            if (remixLicenseActive(_authors[_i])) {
                _hasLicense = true;
            }
        }

        require(_hasLicense, "MISSING_AUTHOR_LICENSE");  // TODO_ONE_DAY: Is this safe? Should require pre approval of minter?

        // Mint derivative
        _mintDerivative(_dst);
        emit DerivativeIssued(_dst);

        return true;
    }

execution reverts with message:

eth_estimateGas
  Contract call:       <UnrecognizedContract>
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  To:                  0xc6e7df5e7b4f2a278906862b61205850344d4e7d
  Value:               0 ETH

  Error: VM Exception while processing transaction: reverted with reason string 'Address: low-level delegate call failed'
      at <UnrecognizedContract>.constructor (unknown)
      at <UnrecognizedContract>.<unknown> (0xc6e7df5e7b4f2a278906862b61205850344d4e7d)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async EthModule._estimateGasAction (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:431:7)
      at async HardhatNetworkProvider._sendWithLogging (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:144:22)
      at async HardhatNetworkProvider.request (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:121:18)
      at async JsonRpcHandler._handleRequest (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/jsonrpc/handler.ts:191:20)
      at async JsonRpcHandler._handleSingleRequest (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/jsonrpc/handler.ts:152:17)
      at async Server.JsonRpcHandler.handleHttp (/home/xqua/Documents/Work/Alien/Alias/app/node_modules/hardhat/src/internal/hardhat-network/jsonrpc/handler.ts:52:21)

:computer: Environment

Hardhat, solidity 8.4 or 8.20, tried on hardhat chain and ganache

What I tried

So I tried to do .call as well, same issue. I've tried numerous functions in the contract and none are working as well. It is quite bizarre because proxy 1 succeeds at calling the first proxy (or so it seems to me???) but then proxy 2 can't read proxy 1 data.

I need to check that proxy 1 is really who they say they are as this could be abused otherwise by injecting false authors in the call.

1 Like

instead of this try calling
IRemix(parents[_j]).requestDerivative(authors, address(this))
from perspective of the smart contraxct.

bytes typeString = "requestDerivative(address[])" // getting rid of _dst arg
address(parents[_j].call(abi.encodeWithSignature(typeHash, authors));

this way your proxy will be making the call so you can get rid of the _dst arg and validate msg.sender is your proxy in the implementation of requestDerivative instead. this is also better because it eliminates spoofing. when you want to verify data you should define the protocol inside the contract doing the checks and only pass it the data to do checks on and not any data that you use do the checks against. notice that requestDerivative is external so anyone can call it, so anyone can define _dst. if you call with encoded callData internally you can more safely know where the call is coming from

i havent tried running your code, but do try encodewith sig and use the typehash to identitfy the function u want your proxy to call. your using loops and calling each time. perhaps would be better you pass in all of the raw data at once at the end and check each item individually in the requestDerivative func instead