Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lesson 6 VM Exception Running fund and withdraw #901

Open
strudelPie opened this issue Jan 30, 2022 · 9 comments
Open

Lesson 6 VM Exception Running fund and withdraw #901

strudelPie opened this issue Jan 30, 2022 · 9 comments

Comments

@strudelPie
Copy link

strudelPie commented Jan 30, 2022

Posting as a new issue as requested here: https://github.com/smartcontractkit/full-blockchain-solidity-course-py/discussions/422#discussioncomment-2066946. However this is a follow-on from #422.

I'm getting a VM error when calling fund_me.getEntranceFee() within fund_and_withdraw.py. I have been using ganache-local but it replicates. Here's the walkthrough:

deploy.py working fine:
deploy py

VM error when running fund_and_withdraw.py
VMerror

You can see that the entrance fee is pulled in as zero and the code errors (as with #422 linked above). Now, when I set the variable 'entrance_fee' manually as immediately below, the script works:

entrancefee

fundwithdraw

The main fix suggested in #422 was to change the hardfork within ganache, however I am already on MUIRGLACIER:

ganachetransactions

So my code: it differs from the code available in the course repo for this lesson in three ways:
(1) MockAggregatorV3.sol: I've made minor changes to my code in line with #875. However I have tried reverting it back to the the lesson 6 code available in the course repo and that doesn't make any difference to the error as above.
(2) FundMe.sol: my code now matches the suggestion from this stack post: https://ethereum.stackexchange.com/a/120224/92713 This was in an attempt to see if the fix works for me. It does not. Again, reverting back the the course repo code still produces the error.

(3) helpful_scripts.py now includes this suggsted fix: https://github.com/smartcontractkit/full-blockchain-solidity-course-py/discussions/422#discussioncomment-1723728

In case it's easier to review, here's the code as is:

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.6;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@chainlink/contracts/src/v0.6/vendor/SafeMathChainlink.sol";

contract FundMe {
    using SafeMathChainlink for uint256;
    // using SafeMath for uint256;

    address public owner;
    mapping(address => uint256) public addressToAmountFunded;
    address[] public funders;
    AggregatorV3Interface public priceFeed;

    constructor(address _priceFeed) public {
        owner = msg.sender;
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    function fund() public payable {
        uint256 minimumUSD = 50 * 1_000_000_000_000_000_000;
        require(
            getConversionRate(msg.value) >= minimumUSD,
            "You need to spend more ETH!"
        );
        addressToAmountFunded[msg.sender] += msg.value;
        funders.push(msg.sender);
    }

    // Price of ETH/USD on Rinkeby
    function getVersion() public view returns (uint256) {
        return priceFeed.version();
    }

    function getPrice() public view returns (uint256) {
        (, int256 answer, , , ) = priceFeed.latestRoundData(); // returns 8 decimal places
        return uint256(answer * 10_000_000_000); // add 10 more decimal places to convert this unit to wei (18 zeroes)
    }

    function getEntranceFee() public view returns (uint256) {
        uint256 minUSD = 50 * 1_000_000_000_000_000_000;
        uint256 price = getPrice();
        uint256 precision = 1 * 1_000_000_000_000_000_000;
        return (minUSD * precision) / price;
    }

    function getConversionRate(uint256 ethAmount)
        public
        view
        returns (uint256)
    {
        uint256 ethPrice = getPrice();
        uint256 ethAmountinUsd = (ethPrice * ethAmount) /
            1_000_000_000_000_000_000;
        return ethAmountinUsd;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function withdraw() public payable onlyOwner {
        payable(msg.sender).transfer(address(this).balance);

        for (
            uint256 funderIndex = 0;
            funderIndex < funders.length;
            funderIndex++
        ) {
            address funder = funders[funderIndex];
            addressToAmountFunded[funder] = 0;
        }

        funders = new address[](0);
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorInterface.sol";
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

/**
 * @title MockV3Aggregator
 * @notice Based on the FluxAggregator contract
 * @notice Use this contract when you need to test
 * other contract's ability to read data from an
 * aggregator contract, but how the aggregator got
 * its answer is unimportant
 */
contract MockV3Aggregator is AggregatorInterface, AggregatorV3Interface {
    uint256 public constant override version = 0;
    uint8 public override decimals;
    int256 public override latestAnswer;
    uint256 public override latestTimestamp;
    uint256 public override latestRound;
    mapping(uint256 => int256) public override getAnswer;
    mapping(uint256 => uint256) public override getTimestamp;
    mapping(uint256 => uint256) private getStartedAt;

    constructor(uint8 _decimals, int256 _initialAnswer) public {
        decimals = _decimals;
        updateAnswer(_initialAnswer);
    }

    function updateAnswer(int256 _answer) public {
        latestAnswer = _answer;
        latestTimestamp = block.timestamp;
        latestRound++;
        getAnswer[latestRound] = _answer;
        getTimestamp[latestRound] = block.timestamp;
        getStartedAt[latestRound] = block.timestamp;
    }

    function updateRoundData(
        uint80 _roundId,
        int256 _answer,
        uint256 _timestamp,
        uint256 _startedAt
    ) public {
        latestRound = _roundId;
        latestAnswer = _answer;
        latestTimestamp = _timestamp;
        getAnswer[latestRound] = _answer;
        getTimestamp[latestRound] = _timestamp;
        getStartedAt[latestRound] = _startedAt;
    }

    function getRoundData(uint80 _roundId)
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        return (
            _roundId,
            getAnswer[_roundId],
            getStartedAt[_roundId],
            getTimestamp[_roundId],
            _roundId
        );
    }

    function latestRoundData()
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        return (
            uint80(latestRound),
            getAnswer[latestRound],
            getStartedAt[latestRound],
            getTimestamp[latestRound],
            uint80(latestRound)
        );
    }

    function description() external view override returns (string memory) {
        return "v0.6/tests/MockV3Aggregator.sol";
    }
}
// MockOracle
// Function signatures, event signatures, log topics

helpful_scripts.py

from brownie import network, config, accounts, MockV3Aggregator
from web3 import Web3

FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

DECIMALS = 8
STARTING_PRICE = 200000000000


def get_account():
    if (
        network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS
        or network.show_active() in FORKED_LOCAL_ENVIRONMENTS
    ):
        return accounts[0]
    else:
        return accounts.add(config["wallets"]["from_key"])


def deploy_mocks():
    print(f"The active network is {network.show_active()}")
    print("Deploying mocks...")
    if len(MockV3Aggregator) <= 0:
        MockV3Aggregator.deploy(
            DECIMALS, Web3.toWei(STARTING_PRICE, "ether"), {"from": get_account()}
        )
    print("Mocks Deployed!")

fund_and_withdraw.py

from brownie import FundMe
from scripts.helpful_scripts import get_account

def fund():
    fund_me = FundMe[-1]
    account = get_account()
    # entrance_fee = fund_me.getEntranceFee()
    entrance_fee = 25000000000000000
    print(f"The entrance_fee is {entrance_fee}")
    print("Funding")
    fund_me.fund({"from": account, "value": entrance_fee})


def withdraw():
    fund_me = FundMe[-1]
    account = get_account()
    # fund_me.withdraw({"from": account})
    fund_me.withdraw({"from": account, "gas_limit": 6721975, "allow_revert": True})


def main():
    fund()
    withdraw()

deploy.py

from brownie import FundMe, MockV3Aggregator, network, config
from scripts.helpful_scripts import (
    get_account,
    deploy_mocks,
    LOCAL_BLOCKCHAIN_ENVIRONMENTS,
)


def deploy_fund_me():
    account = get_account()
    if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
        price_feed_address = config["networks"][network.show_active()][
            "eth_usd_price_feed"
        ]
    else:
        deploy_mocks()
        price_feed_address = MockV3Aggregator[-1].address

    fund_me = FundMe.deploy(
        price_feed_address,
        {"from": account},
        publish_source=config["networks"][network.show_active()].get("verify"),
    )
    print(f"Contract deployed to {fund_me.address}")
    return fund_me


def main():
    deploy_fund_me()
@cromewar
Copy link
Contributor

Hello @strudelPie Based on the error I have 2 hypothesis, meanwhile I do more research on this plz check the following:

  1. Increase the gas limit on ganache to something higher.
  2. it marks the entrance fee as 0, why? are the mocks getting you a correct answer?

@strudelPie
Copy link
Author

@cromewar thank you. So adjusting gas price (added a whole bunch of zeros) didn't make a difference. I agree the issue must be with the mocks, but I have no idea why.

In case it helps, I have noticed that if there are already existing build files then running deploy.py looks like it doesn't run correctly and I only get the output below. As above, I still get the error either way, but I add it in case it might help(?)

reRun error

@cromewar
Copy link
Contributor

Try deleting the build folder and try again I'll do more research on this meanwhile.

@strudelPie
Copy link
Author

strudelPie commented Jan 31, 2022

Yeh no joy with deleting the build folder I'm afraid. That's what I was attempting when I discovered the issue with deploy.py (my most recent comment)...

@cromewar
Copy link
Contributor

I think the problem is most likely here:

image

you've commented the getEntranceFee,r the minimum entrance fee is $50, use this page and you'll get something like this:

image

which

is this amount of ETH

image

I suggest you to use the actual function to get the entrance fee and here:

fund_me.fund({"from": account, "value": entrance_fee+100000 }) add a little bit more.

Also if you are going to use a fixed amount is better to use Web3.toWei.

@strudelPie
Copy link
Author

Hello again. Thanks but I think you slightly mis-read my original. Commenting out as I did the getEntranceFee() call made the code work fine. Hence I was showing it to highlight where the issue is.

I have just tried using the original line: entrance_fee = fund_me.getEntranceFee() together with your suggested amend (fund_me.fund({"from": account, "value": entrance_fee+100000 })

As you can see below, this does work, but the entrance fee variable is still being brought in as zero. But, as with me replacing the entrance_fee variable, this is really just a bit of a fix and still doesn't tell me why the function call isn't working. I m happy at this point to move onto Lesson 7, but still a little puzzled.

working

@cromewar
Copy link
Contributor

Oh, dont' worry about it, this happens as we are using Chainlink, is normal for it to return zero, this is asynchronous function, you don't have to worry about it, if you want to wait, you could manually set the script to wait maybe 5 minutes.

@strudelPie
Copy link
Author

strudelPie commented Jan 31, 2022

Sorry- I don't quite understand. You're saying it's normal for getEntranceFee() to return zero? But this gives me the VM error. Why even have the function? It also seems to work fine for Patrick in the tutorial.

@cromewar
Copy link
Contributor

cromewar commented Feb 1, 2022

As normal I'm saying that if you don't wait enough it will give you that, I strongly suggest you to test this on Rinkeby and you'll see the entrance fee will work just gine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants