PatrickAlphaC / hardhat-fund-me-fcc

82 stars 183 forks source link

Error: VM Exception while processing transaction: reverted with reason string 'You need to spend more ETH!' #111

Closed michaelclubman515 closed 1 year ago

michaelclubman515 commented 1 year ago

At the unit testing section around 11:20 failed the test "updated the amount funded data structure". I think the reason is not because I send enough Ether, cause I declare the sendValue for 1 Ether.

Error Info below

(base) liwei@liweideMac-mini-2 hardhat-fund-me % yarn hardhat test
yarn run v1.22.19
warning ../package.json: No license field
$ /Users/liwei/hardhat-fund-me/node_modules/.bin/hardhat test
Compiled 8 Solidity files successfully

FundMe
constructor
喂价合约地址是: 0x5FbDB2315678afecb367f032d93F642f64180aa3

  ✔ set the aggregator addresses correctly
fund

  ✔ Fails if you don't send enough ETH

  1) updated the amount funded data structure
2 passing (938ms)
1 failing

FundMe
fund
updated the amount funded data structure:
Error: VM Exception while processing transaction: reverted with reason string 'You need to spend more ETH!'
at FundMe.fund (contracts/FundMe.sol:40)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at runNextTicks (node:internal/process/task_queues:65:3)
at listOnTimeout (node:internal/timers:528:9)
at processTimers (node:internal/timers:502:7)
at HardhatNode._mineBlockWithPendingTxs (node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:1802:23)
at HardhatNode.mineBlock (node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:491:16)
at EthModule._sendTransactionAndReturnHash (node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:1522:18)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

test script FundMe.test.js

const { deployments, ethers, getNamedAccounts } = require("hardhat")
const { assert, expect } = require("chai")
describe("FundMe", async function () {
let fundMe
let deployer

let mockV3Aggregator
const sendValue = "1000000000000000000" // 1ether,更易读的写法,见下一句
// const sendValue = ethers.utils.parseEther("1") // 1ETH,sendValue 暂时hardcode

// 部署合约
beforeEach(async function () {
    // deploy our fundMe contract
    // using Hardhat-deploy

    const accounts = await ethers.getSigners() // 一种直接获取账户信息的方式
    const accountZero = accounts[0]
    //const {deployer} = await getNamedAccounts()
    deployer = (await getNamedAccounts()).deployer //将deployer从get NamedAccounts)获取后赋值
    await deployments.fixture("all")
    /* fixture 允许使用deploy文件夹 as many tags as we want,
     * 这里会运行deploy文件夹里的全部脚本并部署到本地区块链上,很高效的一条命令
     */
    fundMe = await ethers.getContract("FundMe", deployer) // getContract可以获取最近部署的合约
    // 告诉ethers 要使用哪个账户来发起交易,使用NameAccount,获取账户deployer,还要把这些账户和合约连接起来
    mockV3Aggregator = await ethers.getContract(
        "MockV3Aggregator",
        deployer
    )
})

describe("constructor", async function () {
    it("set the aggregator addresses correctly", async function () {
        const response = await fundMe.getPriceFeed() //获取FundMepriceFeed
        assert.equal(response, mockV3Aggregator.address)
    })
})
/* 测试2.1 当fund的金额太小,没有达到最低要求,revert,使用ether-waffle的expect revert
 * 测试2.2 当fund的金额达到最低要求,是否addressToAmountFunded的映射是否得到正确的数值更新
        这里不止要考虑sendValue,还要考虑gas费
 */
describe("fund", async function () {
    it("Fails if you don't send enough ETH", async function () {
        await expect(fundMe.fund()).to.be.revertedWith(
            "You need to spend more ETH!"
        )
    })

    it("updated the amount funded data structure", async function () {
        // 执行fund,获取funder的地址=>金额映射,和fund的金额做对比
        await fundMe.fund({ value: sendValue })
        const response = await fundMe.addressToAmountFunded(deployer)
        assert.equal(response.toString(), sendValue.toString())
    })
})
})

contract FundMe.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./PriceConverter.sol";

error FundMe_NotOwner();

/** @title A contract for crowd funding

@author Mike
@notice This contract is to demo a sample funding contract
@dev This implements price feeds as our library
*/
contract FundMe {
// type declarations
using PriceConverter for uint256;
// State Variables
mapping(address => uint256) public addressToAmountFunded;
address[] public funders;
AggregatorV3Interface private s_priceFeed;

// Could we make this constant?  /* hint: no! We should make it immutable! */
address public immutable i_owner;
uint256 public constant MINIMUM_USD = 50 * 10**18;

constructor(address priceFeed) {
    i_owner = msg.sender;
    s_priceFeed = AggregatorV3Interface(priceFeed); // 重构构造函数
}

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

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!");
    addressToAmountFunded[msg.sender] += msg.value;
    funders.push(msg.sender);
}

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

    (bool callSuccess, ) = payable(msg.sender).call{
        value: address(this).balance
    }("");
    require(callSuccess, "Call failed");
}

fallback() external payable {
    fund();
}

receive() external payable {
    fund();
}

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

// Concepts we didn't cover yet (will cover in later sections)
// 1. Enum
// 2. Events
// 3. Try / Catch
// 4. Function Selector
// 5. abi.encode / decode
// 6. Hash with keccak256
// 7. Yul / Assembly

library priceConverter.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

// Why is this a library and not abstract?
// Why not an interface?
library PriceConverter {
// We could make this public, but then we'd have to deploy it
function getPrice(AggregatorV3Interface priceFeed)
internal
view
returns (uint256)
{
// Goerli ETH / USD Address
// https://docs.chain.link/docs/ethereum-addresses/
(, int256 answer, , , ) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
// or (Both will do the same thing)
// return uint256(answer * 1e10); // 1* 10 ** 10 == 10000000000
}

// 1000000000
function getConversionRate(
    uint256 ethAmount,
    AggregatorV3Interface priceFeed
) internal view returns (uint256) {
    uint256 ethPrice = getPrice(priceFeed);
    uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000;
    // or (Both will do the same thing)
    // uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1e18; // 1 * 10 ** 18 == 1000000000000000000
    // the actual ETH/USD conversion rate, after adjusting the extra 0s.
    return ethAmountInUsd;
}
}

hardhat.config.js

require("@nomicfoundation/hardhat-toolbox")
require("hardhat-deploy")
require("dotenv").config()
require("@nomicfoundation/hardhat-chai-matchers")
require("@nomiclabs/hardhat-etherscan")
require("hardhat-gas-reporter")
require("solidity-coverage")
const GOERLI_RPC_URL = process.env.GOERLI_RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    // solidity: "0.8.17",
    solidity: {
        compilers: [{ version: "0.8.8" }, { version: "0.6.6" }],
    },
    defaultNetwork: "hardhat",
    networks: {
        hardhat: {
            chainId: 31337,
            // gasPrice: 130000000000,
        },
        goerli: {
            url: GOERLI_RPC_URL,
            accounts: [PRIVATE_KEY],
            chainId: 5,
            blockConfirmations: 6, // 等待区块确认的时间,在本项目中便于部署后验证verify
        },
    },

    namedAccounts: {
        deployer: {
            default: 0, // 这样会默认把第一个账户作为部署合约的账户
            1: 0, //在主网上也会把第一个账户作为部署账户,不管conifg怎么配置,不同网络上的account 0 并不一样
        },
    },
    gasReporter: {
        enabled: false,
        currency: "USD",
        showMethodSig: true,
        showTimeSpent: true,
        outputFile: "gas-report.txt",
        noColors: true,
        coinmarketcap: COINMARKETCAP_API_KEY,
    },

    etherscan: {
        apiKey: ETHERSCAN_API_KEY,
    },
    namedAccounts: {
        deployer: {
            default: 0, // here this will by default take the first account as deployer
            1: 0, // similarly on mainnet it will take the first account as deployer. Note though that depending on how hardhat network are configured, the account 0 on one network can be different than on another
        },
    },
    mocha: {
        timeout: 500000,
    },
}
PatrickAlphaC commented 1 year ago

Can you:

  1. Make this a discusson on the full repo? https://github.com/smartcontractkit/full-blockchain-solidity-course-js/
  2. Follow this section for formatting questions? https://www.youtube.com/watch?t=19846&v=gyMwXuJrbJQ&feature=youtu.be Thanks!
michaelclubman515 commented 1 year ago

Okay,thanks Patrick for making this amazing course.! I upload this much code cause I didn't positioning the problem is from the test script or from the contract . Anyaway I will ask the question again in a better formatting way.