smartcontractkit / full-blockchain-solidity-course-js

Learn Blockchain, Solidity, and Full Stack Web3 Development with Javascript
12.31k stars 2.96k forks source link

Lesson 7 - TypeError: Cannot read properties of undefined (reading 'priceFeed') #2059

Closed RomeDomeYome closed 2 years ago

RomeDomeYome commented 2 years ago

I follewed the instructions in the post below, but im still running into this issue.

"Hey @MasterofBlockchain Replace line 27 with const response = await FundMe.getPriceFeed()

You are trying to call s_priceFeed which is marked private in your contract hence it cannot be accessed outside the contract. Instead, use the getter function getPriceFeed() that you created at the bottom of the contract to retrieve s_priceFeed "

Originally posted by @othaime-en in https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/2047#discussioncomment-3473459

Here is what my FundMe.sol looks like:

// SPDX-License-Identifier: MIT
// 1. Pragma
pragma solidity ^0.8.0;
// 2. Imports
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./PriceConverter.sol";

// 3. Interfaces, Libraries, Contracts
error FundMe__NotOwner();

/**@title A sample Funding Contract
 * @author Patrick Collins
 * @notice This contract is for creating a sample funding contract
 * @dev This implements price feeds as our library
 */
contract FundMe {
    // Type Declarations
    using PriceConverter for uint256;

    // State variables
    uint256 public constant MINIMUM_USD = 50 * 10**18;
    address public immutable i_owner;
    address[] public s_funders;
    mapping(address => uint256) public s_addressToAmountFunded;
    AggregatorV3Interface public s_priceFeed;

    // Events (we have none!)

    // Modifiers
    modifier onlyOwner() {
        // require(msg.sender == i_owner);
        if (msg.sender != i_owner) revert FundMe__NotOwner();
        _;
    }

    // Functions Order:
    //// constructor
    //// receive
    //// fallback
    //// external
    //// public
    //// internal
    //// private
    //// view / pure

    //constructor functions automatically get called when we deploy our contracts
    //AggregatorV3Interface called earlier gets us the pricefeed here
    constructor(address priceFeed) {
        s_priceFeed = AggregatorV3Interface(priceFeed);
        i_owner = msg.sender;
    }

    /// @notice Funds our contract based on the ETH/USD price
    function fund() public payable {
        require(
            msg.value.getConversionRate(s_priceFeed) >= MINIMUM_USD,
            "You need to spend more ETH!"
        );
        // require(PriceConverter.getConversionRate(msg.value) >= MINIMUM_USD, "You need to spend more ETH!");
        s_addressToAmountFunded[msg.sender] += msg.value;
        s_funders.push(msg.sender);
    }

    function withdraw() public onlyOwner {
        for (
            uint256 funderIndex = 0;
            funderIndex < s_funders.length;
            funderIndex++
        ) {
            address funder = s_funders[funderIndex];
            s_addressToAmountFunded[funder] = 0;
        }
        s_funders = new address[](0);
        // Transfer vs call vs Send
        // payable(msg.sender).transfer(address(this).balance);
        (bool success, ) = i_owner.call{value: address(this).balance}("");
        require(success);
    }

    function cheaperWithdraw() public onlyOwner {
        address[] memory funders = s_funders;
        // mappings can't be in memory, sorry!
        for (
            uint256 funderIndex = 0;
            funderIndex < funders.length;
            funderIndex++
        ) {
            address funder = funders[funderIndex];
            s_addressToAmountFunded[funder] = 0;
        }
        s_funders = new address[](0);
        // payable(msg.sender).transfer(address(this).balance);
        (bool success, ) = i_owner.call{value: address(this).balance}("");
        require(success);
    }

    /** @notice Gets the amount that an address has funded
     *  @param fundingAddress the address of the funder
     *  @return the amount funded
     */
    function getAddressToAmountFunded(address fundingAddress)
        public
        view
        returns (uint256)
    {
        return s_addressToAmountFunded[fundingAddress];
    }

    function getVersion() public view returns (uint256) {
        return s_priceFeed.version();
    }

    function getFunder(uint256 index) public view returns (address) {
        return s_funders[index];
    }

    function getOwner() public view returns (address) {
        return i_owner;
    }

    function getPriceFeed() public view returns (AggregatorV3Interface) {
        return s_priceFeed;
    }
}

and my FundMe.test.js

const { inputToConfig } = require("@ethereum-waffle/compiler")
const { assert } = require("chai")
const { deployments, ethers, getNamedAccounts } = require("hardhat")
const { developmentChains } = require("../../helper-hardhat-config")

describe("FundMe", async function () {
    let FundMe
    let deployer
    let mockV3Aggregator
    describe("constructor", async function () {
        //deploy FundMe
        //using hardhat deploy
        // const accounts = await ethers.getSigners() // returns whatever is in "accounts:" in your networks you defined
        // const accountZero = account[0] //specifies which account you want to use
        const { deployer } = (await getNamedAccounts()).deployer
        await deployments.fixture(["all"]) //identfy the tags in fixture() so it knows what to deploy
        FundMe = await ethers.getContract("FundMe", deployer) //getContract gets the most recent deployment of whatever contract you give it
        mockV3Aggregator = await ethers.getContract(
            "MockV3Aggregator",
            deployer
        )
    })

    describe("constructor", async function () {
        it("sets the aggregator addresses correctly", async function () {
            const response = await FundMe.s_priceFeed
            assert.equal(response, mockV3Aggregator.address)
        })
    })
})
br0wnD3v commented 2 years ago

Change the line const response = await FundMe.s_priceFeed to const response = await FundMe.s_priceFeed() And then do assert.equal(response, mockV3Aggregator.address)

RomeDomeYome commented 2 years ago

@bot-eth-dev still throws this:

TypeError: Cannot read properties of undefined (reading 's_priceFeed')
br0wnD3v commented 2 years ago

I think I know what's up, After the first describe("FundMe",async() => {}) You didn't make use of beforeEach() to initialize the contract and did it in the next describe("constructor", async function () {} Because of this when you ran the next describe("constructor", async function () {} you get TypeError: Cannot read properties of undefined (reading 's_priceFeed')

TLDR; Add a beforeEach() after the top most descibe() and initialize the contract in that section and NOT in the next describe()

RomeDomeYome commented 2 years ago

@bot-eth-dev yes youre completely correct. it works now. thank you!!!

RomeDomeYome commented 2 years ago

@bot-eth-dev i wouldve caught that had I just compared it to the original. Ill be sure to do that if I get stuck again