How to receive Ether to an upgradeable contract from a contract using msg.sender.transfer

@abcoathup I seem to have the same problem. Unfortunately, the contract that sends the Ether is not under my control, and has already been deployed to the mainnet as an unupgradable contract. In this case, is there any way I can receive Ether with a proxy contract?

Minimal code to reproduce

Contracts:

pragma solidity 0.6.12;

import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";

interface IPayer {
    function pay(uint amount) external;
}

/**
 * @dev Mimic the behavior of the mainnet contract that sends Ether
 */
contract Payer is IPayer {
    constructor() public { }

    receive() external payable { }

    function pay(uint amount) external override {
        require(address(this).balance > amount, "pay: NOT_ENOUGH_ETH");
        msg.sender.transfer(amount);
    }
}

contract ReceiverUpgradable is Initializable {
    IPayer public payer;

    event Received(uint amount);

    function initialize(IPayer _payer) public initializer {
        payer = _payer;
    }

    receive() external payable {
        emit Received(msg.value);
    }

    function demandPay(uint amount) external {
        payer.pay(amount);
    }
}

Test script:

describe("Upgradable Receiver", async () => {
  let deployer: Signer;
  let payer: Payer;
  let receiver: ReceiverUpgradable;

  before(async () => {
    [ deployer ] = await ethers.getSigners();

    const payerFactory = await ethers.getContractFactory("Payer", deployer);
    payer = (await payerFactory.deploy()) as Payer;

    const receiverFactory = await ethers.getContractFactory("ReceiverUpgradable", deployer);
    receiver = (await upgrades.deployProxy(
      receiverFactory,
      [ payer.address ]
    )) as ReceiverUpgradable;

    await deployer.sendTransaction({
      to: payer.address,
      value: parseEther("1"),
    });
  });

  it("should receive required amount of Ether from the payer", async () => {
    const balanceBefore = await ethers.provider.getBalance(receiver.address);
    await receiver.connect(deployer).demandPay(parseEther("0.5"));
    const balanceAfter = await ethers.provider.getBalance(receiver.address);

    const balanceChange = bn(balanceAfter).sub(bn(balanceBefore));
    expect(balanceChange).to.equal(parseEther("0.5"));
  });
});

Error message:

1) Upgradable Receiver
     should receive required amount of Ether from the payer:
   Error: Transaction reverted: contract call run out of gas and made the transaction revert
    at <UnrecognizedContract>.<unknown> (unknown)
    at Payer.pay (contracts/Payer.sol:15)
    at ReceiverUpgradable.demandPay (contracts/ReceiverUpgradable.sol:21)
    at <UnrecognizedContract>.<unknown> (0xa513e6e4b8f2a923d98304ec87f64353c4d5c853)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at EthModule._sendTransactionAndReturnHash (node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:1303:9)
    at HardhatNetworkProvider.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:102:18)
    at EthersProviderWrapper.send (node_modules/@nomiclabs/hardhat-ethers/src/ethers-provider-wrapper.ts:13:20)

Thanks!

1 Like

Hi @Larry,

Welcome to the community :wave:

The issue is due to the Istanbul repricing, see: OpenZeppelin upgradeable contracts affected by Istanbul hardfork.

Unfortunately you will need to change how you get Ether to your upgradeable contract. e.g. via another contract.

1 Like

@abcoathup Thanks for the reply!

I am aware of the cause of the issue. I was in fact trying to reply to this post: Msg.sender.transfer runs out of gas on a payable upgradeable proxy contract where you gave the same answer, but somehow made a new post instead.

I found that using msg.sender.call{ value: amount }("") solves the issue, as suggested by the linked you posted. Unfortunately the contract I’m trying to interface with still uses msg.sender.transfer and I can’t change that. I’ll try find another way around this.

1 Like

Hi @Larry,

As the other post had already been marked as solved, I moved your post to a new post instead. Unfortunately I don’t have a better answer.

You could look at using another (non-upgradeable) contract as the recipient and then transfer to your contract but that requires another contract and an additional transaction.

What is the contract you are trying to interact with? Is it WETH?

@abcoathup I’m thinking about exactly the same solution as you mentioned here. I call this middleman contract Relayer because essentially what it does is to relay ETH between the other two contracts.

It’s a big name DeFi protocol which I don’t think I’m allowed to say the name before my currently project is announced :slight_smile:

1 Like