delegateCall to a function of an upgradeable contract (TransparentUpgradeable)

I have the following TransparentUgradeable contract on ArbitrumOne:

I want to call the function depositTokenViaUsdc() from a wrapper contract

/**
 *Submitted for verification at Arbiscan on 2023-07-10
*/

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

contract WrapperContract {
    address fiat24ContractAddress;  // Address of the first contract

    // person who deploys contract is the owner
    address payable public owner;

    // allows owner only
    modifier onlyOwner(){
        require(owner == msg.sender, "Sender not authorized");
        _;
    }

    constructor(address _fiat24ContractAddress) {
        fiat24ContractAddress = _fiat24ContractAddress;
        // owner = payable(msg.sender);
    }

    function depositTokenViaUsdc(address _inputToken, address _outputToken, uint256 _amount) external returns (uint256) {
        // Call the depositTokenViaUsdc() method in the first contract
        
        (bool success, bytes memory result) = fiat24ContractAddress.delegatecall(
            abi.encodeWithSignature("depositTokenViaUsdc(address,address,uint256)",
            _inputToken, 
            _outputToken, 
            _amount
            )
        );
        
        require(success, "Failed to call depositTokenViaUsdc() in the fiat 24 contract");
        
        // Decode and return the result (if necessary)
        return abi.decode(result, (uint256));
    }
}

Since msg.sender is used in the called contract, I call this function using a delegateCall.

All transactions get immediately reverted.

When I check the transaction in Tenderly

I see the following in the debugger:

{
  "[OPCODE]": "DELEGATECALL",
  "from": {
    "address": "0x4582f67698843dfb6a9f195c0ddee05b0a8c973f",
    "balance": "0"
  },
  "to": {
    "address": "0x0000000000000000000000000000000000000000",
    "balance": "9789006687229935974"
  },
  "caller": {
    "address": "0xc0c8a5b44fa4b5a3dcecc5324cfe23b1a78fcbb8",
    "balance": "31945795148300856"
  },
  "input": {
    "_inputToken": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8",
    "_outputToken": "0x2c5d06f591d0d8cd43ac232c2b654475a142c7da",
    "_amount": "5000000"
  },
  "[OUTPUT]": "0x",
  "gas": {
    "gas_left": 813967,
    "gas_used": 0,
    "total_gas_used": 386033
  }
}

The "to" address is 0x0000000000000000000000000000000000000000

Is there a problem calling a function of a TransparentUpgradeable contract by a delegateCall?

The function depositTokenViaUsdcdoes no state updates to the called contract.

The target is to keep the original caller (EOA) msg.sender in the called upgradeable contract.

This value, which is the input of your delegate call, is the output of the previous phase in your transaction, where the address of the Logic contract (AKA implementation) is retrieved.

In other words, it is the value returned from the call to function _getImplementation on the Proxy contract.

And as you can see, this function call returns a zero-address:

So my guess would be that your Proxy contract is not pointing to your Logic contract, because you deployed the former without specifying the address of the latter.

You might be able to resolve this by executing proxy.upgradeTo(logic.address) with your proxy admin account.

Hi @barakman

Thank you very much for your reply.
If I do calls right to the contract I get the correct implementation address. Here a call directly posted to the contract

This proxy contract is linked to the implementation at

I only get this problem of a 0x0 implantation address when I do the delegate call to depositTokenViaUsdc() from the wrapper contract at

Is there something wrong with the delegateCall from the wrapper contract?

I think the problem is simply that the wrapper contract has the variable with the address of the proxy contract, which in the proxy contract, that is called by delegateCall from the wrapper contract, does not exist at the same slot. The delegateCall is executed in the context of the wrapper contract. Since that slot does not exist in the proxy, the address 0x0 is returned.

You can verify your conjecture by changing this:

address fiat24ContractAddress;

To this:

address immutable fiat24ContractAddress;

This is correct.

Delegatecall preserves msg.sender but it does much more than that. It executes the code of the target in the context of the caller.

You can't use it for what you intended.

The contract has to provide some way for you to act on behalf of another account. This could be via a signature provided by that account, for example.