Solidity Fallback – Why Is Fallback Function Not Executing?

delegatecallfallback-functionhardhatsolidity

I have a test project with proxy pattern (fallback/delegate call). The purpose is to have upgradeable contracts.
Here is the proxy contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "hardhat/console.sol";

contract StorageContract {

    address private implementation;

    function setImplementation(address storageimplementation) external
    {
        implementation = storageimplementation;
    }


    fallback() external
    {
        console.log("executing fallback-------");
        delegate(implementation);
    }

    function delegate(address a) internal
    {
        assembly
        {
            calldatacopy(0, 0, calldatasize())

            let result := delegatecall(gas(), a, 0, calldatasize(), 0, 0)

            returndatacopy(0, 0, returndatasize())

            switch result
            case 0
            {
                revert(0, returndatasize())
            }
            default
            {
                return(0, returndatasize())
            }
        }
    }

}

Implementation contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "hardhat/console.sol";

contract StorageImplementation
{

    function add(uint256 a, uint256 b) public returns (uint256)
    {
        console.log("add function called");
        return a+b;
    }

    function hello() public returns (string memory)
    {
        console.log("hello function called");
        return "hello";
    }

}

Test:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Storage", function () {
  it("delegatecall test", async function () {
   const StorageContract = await ethers.getContractFactory("StorageContract");
   const storage = await StorageContract.deploy();
   await storage.deployed();

   const StorageImplementation = await ethers.getContractFactory("StorageImplementation");
   const storageImpl = await StorageImplementation.deploy();
   await storageImpl.deployed();

   storage.setImplementation(storageImpl.address);

   let impl = await storage.getImplementation();
   console.log("impl:" + impl);

    let helloResp = await storage.hello();

    expect(helloResp).to.equal("hello");
  });
});

Fallback function is not executing when I call a function that does not exist in the contract.

After executing tests via npx hardhat test I get an error:

TypeError: storage.hello is not a function

And the log message in fallback is not printed.

I have also tried using receive function and payable modifier with fallback but it didn't help.

I hope I was clear enough.
Thanks in advance for any suggestions.

Best Answer

This fails because the abi of StorageContract has no hello function. So ethers.js doesn't populate the storage object with a hello function, and your call to storage.hello() fails.

You are on the good track given that storage is indeed a proxy for storageImpl, you only need to let ethers "know" about it. One way of solving it is to create an instance of storageImplementation attached to the address of storage :

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Storage", function () {
  it("delegatecall test", async function () {
    const StorageContract = await ethers.getContractFactory("StorageContract");
    const storage = await StorageContract.deploy();
    await storage.deployed();

    const StorageImplementation = await ethers.getContractFactory(
      "StorageImplementation"
    );
    const storageImpl = await StorageImplementation.deploy();
    await storageImpl.deployed();

    storage.setImplementation(storageImpl.address);

    let impl = await storage.getImplementation();
    console.log("impl:" + impl);

    // Proxy handle : StorageImplementation attached to storage address
    // Trough proxy, you can interact with storage as if it was an instance
    // of StorageImplementation.
    const proxy = await StorageImplementation.attach(storage.address);
    let helloResp = await proxy.hello();

    expect(helloResp).to.equal("hello");
  });
});

I hope this answers your question.

Related Topic