Passing parameters in Proxy

I am writing a toy example of UUPS upgrade using the ERC1977Proxy. I am unable to understand how do I pass the initdata to the contract.

```
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract BoxV1 is UUPSUpgradeable {

address internal owner;
uint256 internal length;
uint256 internal width;
uint256 internal height;

function initialize(uint256 l, uint256 w, uint256 h) public initializer {
    owner = msg.sender;
    length = l;
    width = w;
    height = h;
}

function volume() public returns (uint256) {
    return length * width * height;
}

function _authorizeUpgrade(address newImplementation) internal override virtual {
    require(msg.sender == owner, "Unauthorized Upgrade");
}

And my proxy is defined as

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";


contract BoxProxy is ERC1967Proxy {

    constructor (address _delegate, bytes memory _data  )  ERC1967Proxy(_delegate, _data)  {

    }

    function getImplementation()  public returns (address) {
        return _getImplementation();
    }

    function upgradeTo(address newImplementation) public {
        _upgradeTo(newImplementation);
    }
}

What I want to achieve is something like below

box = await BoxV1.new();
const proxy1 = await BoxProxy.new(box.address, Buffer.from(""));

In the Buffer.from() I think I have to pass the values of l, w, h but I cant seem to find a way. I tried with Buffer.from([l, w, h]) and Buffer.from(l, w, h) but they all seem to be throwihg exceptions.

I think I may not have a full understanding here, so please bear with me, and help me out.

My env from package.json looks like

dependencies": {
    "@openzeppelin/contracts": "^4.1.0-rc.0",
    "@openzeppelin/contracts-upgradeable": "^4.1.0-rc.0",
    "@truffle/compile-solidity": "^5.2.6",
    "dotenv": "^8.2.0"
  },
  "engines": {
    "node": "16.0.x",
    "npm": "7.10.x"
  },

It seems like for the _data, it should be the data encoded with the function
initialize(uint256 l, uint256 w, uint256 h),
I think you can use web3.js, like
web3.eth.abi.encodeParameters(['uint256','uint256', 'uint256'], [1,2,3]);
similar for the ethers.js

2 Likes

I used following

let fc = web3.eth.abi.encodeFunctionCall({
                name: 'initialize',
                type: 'function',
                inputs: [{
                    type: 'uint256',
                    name: 'l'
                },{
                    type: 'uint256',
                    name: 'w'
                },{
                    type: 'uint256',
                    name: 'h'
                }]
            }, ['1', '2', '3']);

            const proxy1 = await BoxProxy.new(box.address, fc);
            console.log(  (await proxy1.volume()).toString()   );

When I do await proxy1.volume() I get an error saying that TypeError: proxy1.volume is not a function

Okay, So I tried some things and changed the BoxProxy implementation to below

import “@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol”;

contract BoxProxy is ERC1967UpgradeUpgradeable{

    function initialize(address _logic, address _admin, bytes memory _data) public initializer {
        ERC1967UpgradeUpgradeable.__ERC1967Upgrade_init();
        _upgradeTo(_logic);
        //this._setImplementation(_logic);
    }

    function getImplementation() public returns (address) {
        return _getImplementation();
    }
}

Now when I am trying to get it working in a test case, I am doing the following

        admin = accounts[0];
        box = await BoxV1.new();
        proxy = await BoxProxy.new();
        
        let initData = web3.eth.abi.encodeParameters(
            ['uint256','uint256', 'uint256'],
            [4,5,6]);
        
        let _initData =  web3.eth.abi.encodeFunctionCall({
                                        name: 'initialize',
                                        type: 'function',
                                        inputs: [{
                                            type: 'uint256',
                                            name: 'l'
                                        },{
                                            type: 'uint256',
                                            name: 'w'
                                        },{
                                            type: 'uint256',
                                            name: 'h'
                                        }]
                                    }, [1, 2, 3]);

        await proxy.initialize(box.address, admin, _initData);

        let implAddr = await proxy.getImplementation.call();

        let _box = await BoxV1.at(implAddr);

        console.log(   (await _box.volume.call() ).toString()    ); // is 0 everytime regardless of what initData I pass

       
          console.log(   (await proxy.volume.call()).toString()         );  // Throws Exception

Two questions here

  1. Is the right way to use proxy, first get the address of implementation and then calling the implementation? I dont believe so. Because the state is maintained in proxy.
  2. When I try calling the methods like volume directly from proxy, I get an exception saying TypeError: Cannot read property 'call' of undefined.

Please help me understand this better.

Thanks.

Emmmm, I do not think so, I think you just deploy the implementation contract and then set it in the proxy contract, and then call in the proxy contract, just like you said: the state is maintained in proxy.. Maybe you should call initialize() in the implementation contract if has.

Maybe you can have a try like this:

console.log((await proxy.volume()).toString())