Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

functionDelegateCall Fails During Proxy Initialization #2092

Open
gsabboz opened this issue Aug 14, 2024 · 0 comments
Open

functionDelegateCall Fails During Proxy Initialization #2092

gsabboz opened this issue Aug 14, 2024 · 0 comments
Assignees
Labels
needs-review issue/PR needs review from maintainer

Comments

@gsabboz
Copy link

gsabboz commented Aug 14, 2024

I'm encountering an issue when using the OpenZeppelin proxy pattern. Specifically, the upgradeToAndCall function in ERC1967Utils.sol fails to correctly initialize my contract using functionDelegateCall.

Problem Description

I am using the following setup:

Contract: TestContractUpgradableV1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./contracts/proxy/utils/Initializable.sol";

contract TestContractUpgradableV1 is Initializable {

    uint256 public constant VERSION = 1;
    uint256 public bankBalances;

    function initialize(uint256 _bankBalances) public initializer {
        bankBalances = _bankBalances;
    }

    function getBalance() public view returns (uint256) {
        return bankBalances;
    }
}

upgradeToAndCall Function in ERC1967Utils.sol:

function upgradeToAndCall(address newImplementation, bytes memory data) internal {
    _setImplementation(newImplementation);
    emit Upgraded(newImplementation);

    if (data.length > 0) {
        Address.functionDelegateCall(newImplementation, data);
    } else {
        _checkNonPayable();
    }
}

Problem Encountered

When trying to initialize the contract during deployment via the upgradeToAndCall function, the initialization does not work as expected when passing initialization data. Specifically, calling the initialization method during the proxy contract deployment fails to execute.

Java/Web3j Code

The following code attempts to deploy a proxy and initialize the contract:

public BlockchainAddress deployProxyHandler(Wallet wallet, BlockchainAddress address) throws Exception {

    Function initializeFunction = new Function(
        "initialize",
        Arrays.asList(new Uint256(BigInteger.valueOf(1000))),
        Collections.emptyList());

    String encodedInitializeFunction = FunctionEncoder.encode(initializeFunction);
    byte[] encodedFunctionBytes = Numeric.hexStringToByteArray(encodedInitializeFunction);

    Credentials credentials = Credentials.create(wallet.key().value());

    // This works (deploys the proxy without initialization)
    AdminUpgradeabilityProxy contract = AdminUpgradeabilityProxy.deploy(
        web3j, credentials, new DefaultGasProvider(), BigInteger.valueOf(0),
        address.value(), wallet.address().value(), new byte[0]
    ).send();

    // These do not work (attempting to initialize during deployment)
    AdminUpgradeabilityProxy.deploy(
        web3j, credentials, new DefaultGasProvider(), BigInteger.valueOf(0),
        address.value(), wallet.address().value(), encodedFunctionBytes
    ).send();

    AdminUpgradeabilityProxy.deploy(
        web3j, credentials, new DefaultGasProvider(), BigInteger.valueOf(0),
        address.value(), wallet.address().value(), encodedInitializeFunction.getBytes()
    ).send();

    return new BlockchainAddress(contract.getContractAddress());
}

Generated Deployment Function:

public static RemoteCall<AdminUpgradeabilityProxy> deploy(Web3j web3j, Credentials credentials,
        ContractGasProvider contractGasProvider, BigInteger initialWeiValue, String logic,
        String admin, byte[] data) {
    String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.<Type>asList(
        new org.web3j.abi.datatypes.Address(160, logic), 
        new org.web3j.abi.datatypes.Address(160, admin), 
        new org.web3j.abi.datatypes.DynamicBytes(data)));
    return deployRemoteCall(AdminUpgradeabilityProxy.class, web3j, credentials, contractGasProvider, getDeploymentBinary(), encodedConstructor, initialWeiValue);
}

Expected Behavior

The expectation is that the proxy contract should be deployed and initialized in one transaction using the upgradeToAndCall method. The contract should be ready for use immediately after deployment, with the bankBalances value initialized to the provided value.

Observed Behavior

  1. The proxy contract is deployed, but the initialization logic does not execute correctly when passing the initialization data as part of the deployment process.
  2. The workaround of deploying the contract first and then calling the initialize function manually works, but it requires two separate transactions, which is not ideal.

Workaround

As a temporary workaround, I can:

  1. Manually call the initialize function after deployment of the contract
  2. Verify the state of the contract after initialization.
  3. Assign contract to the proxy

However, this workaround is cumbersome and ideally should be done in a single transaction.

Request for Help

This issue might not be a bug, but rather a misunderstanding or a gap in the documentation. I've searched through the documentation and issues but couldn't find a clear solution. Any guidance on why the initialization is failing and how to properly pass initialization data during deployment would be greatly appreciated.

@gsabboz gsabboz added the needs-review issue/PR needs review from maintainer label Aug 14, 2024
@NickSneo NickSneo self-assigned this Sep 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-review issue/PR needs review from maintainer
Projects
None yet
Development

No branches or pull requests

2 participants