2

I have a logic contract (Box.sol) that can store/retrieve a value and I want to interact with it via a ERC1967Proxy

Problem: after updating once the box value directly, retrieving the value via the proxy returns the default value instead as it it was a different box instance

behavior: calling proxy.retrieve returns 0 (the default box value)

expected: calling proxy.retrieve returns 5 (the previously manually updated value)


this is what I do

  1. deploy logic contract (Box.sol) => stored value is 0
  2. interact with logic contract directly => stored value is updated (ex: 5)
  3. deploy proxy contract (MyProxy.sol) with logic contract address in the constructor

Proxy params

  1. With remix I retrieve the value with the proxy contract address by using the Box ABI (select Box.sol from file explorer tab, select "at address" button from transaction tab, but use the address of the proxy instead of the address of the deployed box)

Final Deployment status

deployed contracts


Box.sol

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Box {
    uint256 private _value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 value);

    // Stores a new value in the contract
    function store(uint256 value) public {
        _value = value;
        emit ValueChanged(value);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return _value;
    }
    
}

MyProxy.sol

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MyProxy is ERC1967Proxy {
   
    constructor(address _logic, bytes memory _data)  ERC1967Proxy(_logic, _data) {}
  
    // debug method to expose the address
    function getImplementationAddress() public view returns(address) {
        return ERC1967Upgrade._getImplementation();
    }   
}

2 Answers 2

3

You are misunderstanding how the state storage is handled with the proxy contract.

deploy logic contract (Box.sol) => stored value is 0

interact with logic contract directly => stored value is updated (ex: 5)

Both of these actions need to happen through the proxy contract. Never interact with the implementation contract directly. This way the state handling is managed correctly.

1
  • thank you for your answer, if possible could you please point me to something I could study to understand the issue ? I would have expected that since the proxy delegates the calls to the logic contract, the logic contract storage would be accessed
    – Stormsson
    Commented Aug 26, 2021 at 9:58
1

Mikko Ohtamaa is right. His answer pointed me to the correct direction:

The document that helped is OpenZeppelin documentation on proxy upgrades

A very important thing to note is that the code makes use of the EVM’s delegatecall opcode which executes the callee’s code in the context of the caller’s state.

So what is happening here is that the code executed is the one created by the bytecode of the logic contract, but it is executed on the proxy storage state, and that's also why you need an initializer instead of using a constructor (see Constructor Caveat )

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.