Understanding Proxies: how to call functions on Proxies using ethers.js?

Hi. I miss some particular information how Proxies are working and how to deal with that. I read multiple articles about how to deploy Proxy contracts and how to clone and even how to do all this with Defender Autotask... Deploying and cloning is easy and works fine!

However. At the end of the day I want also work with all these newly deployed instances and suddenly I am lost.

I wrote an example project: https://github.com/itinance/study-factory-proxy.

It deploys a factory and enables the factory to clone a contract (BoxV1) with a method-call to createBox:

    import "@openzeppelin/contracts/proxy/Clones.sol";

    // ...

    address clone = Clones.clone(_impl);
    BoxV1(clone).initialize(_length, _width);

Everything works fine so far, until I want to play with the actual Boxes. The deployed Factory-Contract including a box can be found here on Rinkeby.

Lets say I have a script based on ethers.js, which reads all the cloned Box-instances and want to call a function on them:

const { ethers } = require("ethers");

async function main() {
    const provider = new ethers.providers.InfuraProvider(4, process.env.INFURA_API_KEY);
    const fABI = [
        'function createBox(uint _length, uint _width) public returns (address)',
        'function getInstancesCount() public view returns (uint256)',
        'function getInstance(uint256 index) public view returns (address)',
    ];

    const boxABI = [
        'function area() public view returns(uint)',
        'function width() public view returns(uint)',
    ];
    const contract = new ethers.Contract('0xbF8b3572a49FA0dd65ED4B17508c97E9cA09e5c5', fABI, provider);
    const count = (await contract.getInstancesCount()).toNumber();
    console.log(count);

    for(let i = 0; i < count; i++) {
        const impl = await contract.getInstance(i);
        console.log(' -> ' + impl);

        // Loading the Box-Contract at the address in "impl"
        const box =  new ethers.Contract(impl, boxABI, provider);
        const area = await box.area();
        console.log(area);
    }
}

The problematic line is

    const area = await box.area();

It reverts with:

Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (method="area()", data="0x", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.7.0)

What do I wrong? What is my misunderstanding?

What I see when I look at the factory contract is this impl address:

image

Which corresponds to a contract called EIP173Proxy, so it is not BoxV1 like you seem to be assuming. This would explain why it doesn't answer to the area() function call.

1 Like

On GitHub, someone pointed out my actual mistake;

Instead of
const boxFactory = (await upgrades.deployProxy(boxFactoryFactory, [boxV1.address])) as BoxFactory;

I need to fetch the implementation address for the deployProxy-function and do this:

    const implAddress = await getImplementationAddress(ethers.provider, boxV1.address);
    const boxFactory = (await upgrades.deployProxy(boxFactoryFactory, [implAddress])) as BoxFactory;
1 Like