0

I have the following TransparentUgradeable contract on ArbitrumOne:

https://arbiscan.io/address/0x4582f67698843Dfb6A9F195C0dDee05B0A8C973F

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

https://arbiscan.io/address/0x8fe63b94401fafea83cbe82e6024c2836c3d1679#code

/**
 *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

https://dashboard.tenderly.co/nicob/project/tx/arbitrum/0x1328197ae401ff070620e14ba3e5d72fdeb9cc2d3c44436aa9d6644b4b313e92

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.

1 Answer 1

0

Transparent proxies have an address that points to the implementation, that is set in the constructor and is stored in a special slot of the storage.

If fiat24ContractAddress is a TransparentUpgradeableProxy, and you do fiat24ContractAddress.delegatecall(...) from your WrapperContract, the bytecode of the proxy will be executed in the context of the WrapperContract, where the address of the implementation in that special slot does not exist and will be zero.

The execution reverts in the point that the code of the proxy tries to delegatecall() the address(0)

Trying to do these kind of things with delegatecall can be tricky, and dangerous. If you really need it, you could try to extend your WrapperContract as ERC1967Proxy, and set the same implementation address.

1
  • 1
    Thank you very much, @ceseshi for that detailed explanation. That makes fully sense.
    – user66732
    Commented Jul 11, 2023 at 13:27

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.