1

What's wrong with this withdraw function? It keeps reverting with the error below. I have tried different methods to withdraw (see commented lines) but the error is the same.

  • I'm sure I'm parsing the correct contract address. I have copy pasted it from thirdweb dashboard myself.

For the whole code, check out My Repo

Error

Error occurred while withdrawing funds Error: 


╔═══════════════════╗
║ TRANSACTION ERROR ║
╚═══════════════════╝

Reason: missing revert data in call exception; Transaction reverted without a reason string


╔═════════════════════════╗
║ TRANSACTION INFORMATION ║
╚═════════════════════════╝

from:      0x13A19933267ec307c96f3dE8Ff8A2392C39263EB
to:        0x0a1c4c84213CB67C2517d442b93A2f1B0110158D (CrowdFunding)
chain:     sepolia (11155111)
rpc:       sepolia.rpc.thirdweb.com
data:      0x388a7ec10000000000000000000000000000000000000000000000000000000000000000
method:    withdrawDonations(0)


╔═════════════════════╗
║ DEBUGGING RESOURCES ║
╚═════════════════════╝

Need helping debugging? Join our Discord: https://discord.gg/thirdweb


    at ContractWrapper.formatError (contract-publisher-7f0a5ce8.browser.esm.js:7754:12)
    at async ContractWrapper.sendTransactionByFunction (contract-publisher-7f0a5ce8.browser.esm.js:7696:17)
    at async ContractWrapper.sendTransaction (contract-publisher-7f0a5ce8.browser.esm.js:7652:18)
    at async ContractWrapper.call (contract-publisher-7f0a5ce8.browser.esm.js:7611:23)
    at async withdraw (index.jsx:104:20)
    at async handleWithdraw (WithdrawFromCampaigns.jsx:31:7)

Withdraw Function

    // Constructor
  constructor(uint256 _platformFee) payable {
        manager == msg.sender;
        platformFee = _platformFee;
        balances[msg.sender] = msg.value;
    }

    // withdraw donations
    function withdrawDonations(
        uint256 _id
    ) public authorisedPerson(_id) returns (bool) {
        (uint256 raisedAmount, uint256 fee) = calculatePlatformFee(_id);

        //balances[msg.sender] = 0; // updating adress balance before atually withdrawing to prevent re-entracy attacks.

        //send to campaign owner
        //_payTo(campaigns[_id].owner, (raisedAmount - fee));
        //payable(campaigns[_id].owner).transfer(raisedAmount - fee);

        //send to platform
        //_payTo(manager, fee);
        payable(manager).transfer(fee);

        emit Action(_id, "Funds Withdrawn", msg.sender, block.timestamp);

        return true;
    }

    // function _payTo(address to, uint256 amount) internal {
    //     require(amount > 0, "Can't send 0");
    //     (bool success, ) = payable(to).call{value: amount}("");
    //     require(success);
    // }

    /* ... */

    function calculatePlatformFee(
        uint256 _id
    ) public view returns (uint, uint) {
        uint raisedAmount = campaigns[_id].amountCollected;
        uint fee = (raisedAmount * platformFee) / 100;
        return (raisedAmount, fee);
    }
bguiz
  • 27,371
  • 47
  • 154
  • 243
kihiuFrank
  • 69
  • 1
  • 10

1 Answers1

1

Assuming that your commented out code remains commented out, there are 2 likely locations for the error to be thrown:

  • calculatePlatformFee(_id), and
  • payable(manager).transfer(fee)

Looking at the function implementation of calculatePlatformFee, it does not look like there is anything amiss there.

That leaves payable(manager).transfer(fee).

Note that (address payable).transfer is not longer recommended for sending the native currency, and there are two alternatives to doing so:

  • (address payable).send
  • (address payable).call{value: msg.value}("")

The recommended option is the latter. This is what your code would look like when using that approach:

(bool success, bytes memory data) = payable(manager).call{value: fee}("");
require(sent, "transfer failed");

If you try this approach, that might fix your issue. But if it does not, it should reveal more information to figure out the underlying reason. Instead of getting this message, which is basically no reason,

Reason: missing revert data in call exception; Transaction reverted without a reason string

... you will get "transfer failed" in the error message. If this happens, then you can isolate your problem, and confirm if indeed the transfer was the issue.

There are several possible reasons that the transfer might fail:

(1) The manager address is a smart contract address (not an EOA address), and it rejects transfers. This can happen whether or not the smart contract defines receive or fallback functions, and whether or not they have the payable modifier.

(2) The value of fee is calculated such that it is less than the balance of the smart contract

For (1): This is correct, and failing the transaction is indeed the correct outcome/ behaviour for your smart contract to do.

For (2): This is incorrect, and indicates that your smart contract implementation may have errors. Whether or not this is the case, I'd recommend that you employ defensive programming here, and add a require statement to ensure that the pre-requisites are met, after calculating fee, and before performing the transfer.

Your function implementation (without any of the currently commented out code) would look something like this:

    function withdrawDonations(
        uint256 _id
    ) public authorisedPerson(_id) returns (bool) {
        (uint256 raisedAmount, uint256 fee) = calculatePlatformFee(_id);

        require(fee <= (address(this).balance), "fee in excess of balance");

        payable(manager).transfer(fee);
        emit Action(_id, "Funds Withdrawn", msg.sender, block.timestamp);
        return true;
    }

NOTE: I've edited your question to include the function implementation code of calculatePlatformFee, as that is important to assess where the error is.

bguiz
  • 27,371
  • 47
  • 154
  • 243
  • Thanks a lot for your input. I added a reason string in after calling call(), and indeed the issue is with this line here ``` (bool success, ) = payable(manager).call{value: fee}("");``` . Upon checking the error message, the fees are withdrawn to the contract address instead of the person who deployed the contract. How do I call the platform owner's address or should it be the same as the contract address? Error ``` Error occurred while withdrawing funds Error: ╔═══════════════════╗ ║ TRANSACTION ERROR ║ ╚═══════════════════╝ Reason: transfer failed ``` – kihiuFrank Aug 21 '23 at 08:59
  • Or should I withdraw to a hard-coded address since I'm the owner of the platform? – kihiuFrank Aug 21 '23 at 09:03
  • Aha, so mystery solved! Well in that case, the logic in your withdraw function is more or less OK, but the problem seems to be how this smart contract is instantiated. When you deploy, the constructor does this: `manager == msg.sender;` ... and you'll need to ensure that that value is indeed the intended recipient. If you want to avoid setting the value of `manager` in the constructor, and use a hardcoded address instead, that should also work - and it is a design decision that you can make. – bguiz Aug 21 '23 at 09:33
  • I decided to use a hard-coded address and also checked that fee is < campaign address but still the error is the same. Weird. [Check full code](https://github.com/kihiuFrank/CrowdFunding/blob/main/web3/contracts/CrowdFunding.sol) – kihiuFrank Aug 21 '23 at 10:15
  • Error. ``` iMetaMask - RPC Error: execution reverted: transfer failed Error occurred while withdrawing funds Error: ╔═══════════════════╗ ║ TRANSACTION ERROR ║ ╚═══════════════════╝ Reason: transfer failed ╔═════════════════════════╗ ║ TRANSACTION INFORMATION ║ ╚═════════════════════════╝ from: 0x7F000649C3f42C2D80dc3bd99F3F5e7CB737092C to: 0xD26AA5C723fcC978d496AB4aB8517C0921d4670E (CrowdFunding) chain: sepolia (11155111) rpc: sepolia.rpc.thirdweb.com data: 0x388a7ec100000000000000000000000000000 method: withdrawDonations(0) ``` – kihiuFrank Aug 21 '23 at 10:16
  • your balance check is not the one that I wrote above `fee <= (campaigns[_id].owner.balance)`, it should be `fee <= (address(this).balance)` as the intent is to check if the smart contract's balance is sufficient, not what you have in your mapping. (you can do that too, additionally) https://github.com/kihiuFrank/CrowdFunding/blob/03d5f95ddc85e8d428d067131f741f985bc490b5/web3/contracts/CrowdFunding.sol#L219C9-L223C1 – bguiz Aug 21 '23 at 10:29
  • I'm creating a donations platform so donations go to the owner of the campaign not to me (platformOwner). That's why I'm checking fee should be less the balance of the campaign owner ```fee <= (campaigns[_id].owner.balance)``` If I do what you are suggesting, I get the error ```"fee in excess of balance"``` – kihiuFrank Aug 21 '23 at 11:44
  • Aha, that's what I suspected! So that confirms the root cause of the problem: Your smart contract's balance is less than the amount that you are trying to transfer out (`fee`). This means that there is a problem with the logic in the smart contract elsewhere, which is *unrelated* to `withdrawDonations`. You need to check the rest of your smart contract that when donations are received, that they are actually being accounted for properly. – bguiz Aug 21 '23 at 13:13