Withdraw from upgradeable contract

Thank you @abcoathup.
Could you have a look into this error here? I redesign the contract to be upgradeable. The upgrade seems to work, as the balance is still there after the upgrade, but the contract will not let me withdraw the funds and will revert when calling the withdraw function. :thinking:
Also, when interacting from the console, I did following your advice from the previous post, I get:
withdraw is not a function. Is it because of the Instanbul fork? But I don’t quite understand why, as I try to withdraw funds to a user account, and @nventuro states here that

Note that sending funds from external accounts, or from other contracts via either payable functions or call will continue to work correctly.

I just also tried to change transfer() to call() using this approach:

  function withdraw(uint256 amount) external onlyOwner {
    require(
        amount <= address(this).balance,
        "Requested amount is higher than currently deposited value"
    );
   
 (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed.");
    
    emit Withdrawn(msg.sender, amount);
}

…but no luck, still reverts.

Or is it because the call is sent from the proxy to the implementation?

errorMessage
  '0x223fc5929eb1ca408838ed3b3306706063e3d47779d3561d29233a9368deb013': { error: 'revert', program_counter: 1123, return: '0x' },
  stack: 'RuntimeError: VM Exception while processing transaction: revert\n' +
'    at Function.RuntimeError.fromResults (C:\\Users\\sound\\AppData\\Local\\Programs\\Ganache\\resources\\static\\node\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:13)\n' +
'    at BlockchainDouble.processBlock (C:\\Users\\sound\\AppData\\Local\\Programs\\Ganache\\resources\\static\\node\\node_modules\\ganache-core\\lib\\blockchain_double.js:627:24)\n' +
'    at runMicrotasks (<anonymous>)\n' +
'    at processTicksAndRejections (internal/process/task_queues.js:93:5)',
  name: 'RuntimeError'
}
√ should allow owner to withdraw (196ms)
contract.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import “…/node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol”;
import “…/node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol”;

contract MOTDTreasury is OwnableUpgradeable{

event Paid(address from, uint256 amount);
event Withdrawn(address by, uint256 amount);

function initialize() public initializer {
    __Ownable_init();
}

receive() external payable {
    emit Paid(msg.sender, msg.value);
}

/**
 * @notice Withdraws some funds from this contracy
 * @param amount the amount of funds to winthdraw
 */
//THIS FUNCTION REVERTS IN UNIT TEST, ALSO IN CONSOLE
function withdraw(uint256 amount) external onlyOwner {
    require(
        amount <= address(this).balance,
        "Requested amount is higher than currently deposited value"
    );
   
    payable(msg.sender).transfer(amount);
    

    emit Withdrawn(msg.sender, amount);
}

}

test.js
const { deployProxy, upgradeProxy } = require('@openzeppelin/truffle-upgrades');

const { assert } = require('chai');

const truffleAssert = require('truffle-assertions');

const Treasury = artifacts.require('MOTDTreasury');

const Treasury2 = artifacts.require('MOTDTreasuryV2');

const Web3 = require("web3");

let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));  // 

contract('Treasury (proxy)', async function (accounts) {

    it('should deploy proxy and set owner to accounts[0]', async function () {

        console.log("Deploying Proxy...");

        this.treasury1 = await deployProxy(Treasury, {initializer: 'initialize'});

        console.log("Treasury1 Proxy Address: " + this.treasury1.address);

        let owner = await this.treasury1.owner();

        console.log("Owner is: " + owner);

        assert.equal(owner, accounts[0]);

    });

    it('should allow to deposit ETH', async function() {

        let treasBalance = await web3.eth.getBalance(this.treasury1.address);

        console.log("Balance Treasury1 BEFORE TX: " + treasBalance);

        acc1Balance = await web3.eth.getBalance(accounts[1]);

        console.log("Balance accounts[1] BEFORE TX: " + acc1Balance);

        console.log("Sending Ether from accounts[1] to treasury");

        await web3.eth.sendTransaction({

            from: accounts[1],

            to: this.treasury1.address,

            value: web3.utils.toWei("2", "ether"),

        });

        console.log("New Balance accounts[1]: " + await web3.eth.getBalance(accounts[1]));

    });

    it('stores Eth', async function () {

        let treasuryBalance = await web3.eth.getBalance(this.treasury1.address);

        console.log("Treasury1 Balance: " + treasuryBalance);

    });

    it('should revert if withdraw by not owner!', async function () {

        let treasuryBalance = await web3.eth.getBalance(this.treasury1.address);

        console.log("Balance Treasury1 BEFORE withdraw: " + treasuryBalance);

        try {

            await this.treasury1.withdraw(web3.utils.toWei("1", "ether"), {from: accounts[1]});

        } catch(err){

            console.log(err.reason);

        }

        console.log("Balance should stay the same");

        console.log("Balance Treasury AFTER: " + treasuryBalance);

    });

    it('should revert if withdraw amount is too high', async function () {

        let treasuryBalance = await web3.eth.getBalance(this.treasury1.address);

        console.log("Balance Treasury BEFORE: " + treasuryBalance);

        try {

            await this.treasury1.withdraw(web3.utils.toWei("5", "ether"), {from: accounts[0]});

        }

        catch (err2){

            console.log("Err2" + err2);

        }

        console.log("Balance should stay the same");

        console.log("Balance Treasury1 AFTER: " + treasuryBalance);

    });

    it('should keep balance after proxy upgrade', async function () {

        console.log("Upgrading Proxy...");

        this.treasury2 = await upgradeProxy(this.treasury1.address, Treasury2, {initializer: 'initialize'});

        console.log(await this.treasury2.sayHello());

        console.log("Treasury2 Address: " + this.treasury2.address);

        owner = await this.treasury2.owner();

        console.log("Owner is: " + owner);

        console.log("Balance should still be 1 ETH:");

        console.log("Treasury2 Balance: " + await web3.eth.getBalance(this.treasury2.address));

    })
    //THIS ONE KEEPS REVERTING
    it('should allow owner to withdraw', async function () {

        // console.log(await web3.eth.getBalance(this.treasury2.address));

        // truffleAssert.passes(

        //    await this.treasury2.withdraw(web3.utils.toWei("1", "ether"), {from: owner})

        // )

        // console.log(await web3.eth.getBalance(this.treasury2.address));

        let tresInst = await Treasury2.at(this.treasury2.address);

        try{

            await tresInst.withdraw(web3.utils.toWei("1", "ether"), {from: accounts[0]});

        } catch(err){

            console.log(err.data);

        }

    })

})
1 Like

Hi @ThomasEnder,

I used your withdraw function (without the only owner modifier) with a simple example contract.

I suggest trying the following or create your own simple example.

Box.sol

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

contract Box {
    uint256 private value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);
    event Withdrawn(address withdrawer, uint256 amount);

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

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

    function withdraw(uint256 amount) external {
        require(
            amount <= address(this).balance,
            "Requested amount is higher than currently deposited value"
        );

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");

        emit Withdrawn(msg.sender, amount);
    }

    receive() external payable { }
}

2_deploy_box.js

// migrations/2_deploy_box.js
const Box = artifacts.require('Box');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  await deployProxy(Box, [42], { deployer, initializer: 'store' });
};

Deploy and interact

$ npx truffle develop
...

truffle(develop)> migrate

Compiling your contracts...
===========================
...

truffle(develop)> box = await Box.deployed()
undefined
truffle(develop)> web3.eth.sendTransaction({to:box.address, from:accounts[0], value: web3.utils.toWei('1')})
{ transactionHash:
...
truffle(develop)> web3.eth.getBalance(box.address)
'1000000000000000000'
truffle(develop)> box.withdraw(web3.utils.toWei('1'))
{ tx:
...
truffle(develop)> web3.eth.getBalance(box.address)
'0'
1 Like

Thank you so much @abcoathup ! How can we ever thank you for this amazing support!!! :pray:

(bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed.");

This solution works! Now also the tests are passing!

1 Like