{ "name": "hardhat-fund-me-fcc", "devDependencies": { "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", "@nomiclabs/hardhat-etherscan": "^3.0.0", "@nomiclabs/hardhat-waffle": "^2.0.2", "chai": "^4.3.4", "ethereum-waffle": "^3.4.0", "ethers": "^5.5.3", "hardhat": "^2.8.3", "hardhat-deploy": "^0.9.29", "hardhat-gas-reporter": "^1.0.7", "solidity-coverage": "^0.7.18", "@chainlink/contracts": "^0.3.1", "dotenv": "^14.2.0", "prettier-plugin-solidity": "^1.0.0-beta.19" }, "scripts": { "test": "hardhat test", "test:staging": "hardhat test --network goerli", "lint": "solhint 'contracts/*.sol'", "lint:fix": "solhint 'contracts/**/*.sol' --fix", "format": "prettier --write .", "coverage": "hardhat coverage" } }
将第三方的.sol引入进来
yarn add @chainlink/contracts
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; library PriceConverter { function getPrice(AggregatorV3Interface priceFeed) internal view returns (uint256) { (, int256 answer, , , ) = priceFeed.latestRoundData(); // ETH/USD rate in 18 digit return uint256(answer * 10000000000); } // 1000000000 // call it get fiatConversionRate, since it assumes something about decimals // It wouldn't work for every aggregator function getConversionRate(uint256 ethAmount, AggregatorV3Interface priceFeed) internal view returns (uint256) { uint256 ethPrice = getPrice(priceFeed); uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000; // the actual ETH/USD conversation rate, after adjusting the extra 0s. return ethAmountInUsd; } }
// SPDX-License-Identifier: MIT // 1. Pragma pragma solidity ^0.8.7; // 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 private immutable i_owner; address[] private s_funders; mapping(address => uint256) private s_addressToAmountFunded; AggregatorV3Interface private 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(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; } }
const { network } = require("hardhat") const DECIMALS = "8" const INITIAL_PRICE = "200000000000" // 2000 module.exports = async ({ getNamedAccounts, deployments }) => { const { deploy, log } = deployments const { deployer } = await getNamedAccounts() const chainId = network.config.chainId // If we are on a local development network, we need to deploy mocks! if (chainId == 31337) { log("Local network detected! Deploying mocks...") await deploy("MockV3Aggregator", { contract: "MockV3Aggregator", from: deployer, log: true, args: [DECIMALS, INITIAL_PRICE], }) log("Mocks Deployed!") log("------------------------------------------------") log( "You are deploying to a local network, you'll need a local network running to interact" ) log( "Please run `npx hardhat console` to interact with the deployed smart contracts!" ) log("------------------------------------------------") } } module.exports.tags = ["all", "mocks"]
const { network } = require("hardhat") const { networkConfig, developmentChains } = require("../helper-hardhat-config") const { verify } = require("../utils/verify") module.exports = async ({ getNamedAccounts, deployments }) => { const { deploy, log } = deployments const { deployer } = await getNamedAccounts() const chainId = network.config.chainId let ethUsdPriceFeedAddress if (chainId == 31337) { const ethUsdAggregator = await deployments.get("MockV3Aggregator") ethUsdPriceFeedAddress = ethUsdAggregator.address } else { ethUsdPriceFeedAddress = networkConfig[chainId]["ethUsdPriceFeed"] } log("----------------------------------------------------") log("Deploying FundMe and waiting for confirmations...") const fundMe = await deploy("FundMe", { from: deployer, args: [ethUsdPriceFeedAddress], log: true, // we need to wait if on a live network so we can verify properly waitConfirmations: network.config.blockConfirmations || 1, }) log(`FundMe deployed at ${fundMe.address}`) if ( !developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY ) { await verify(fundMe.address, [ethUsdPriceFeedAddress]) } } module.exports.tags = ["all", "fundme"]
const { ethers, getNamedAccounts } = require("hardhat") async function main() { const { deployer } = await getNamedAccounts() const fundMe = await ethers.getContract("FundMe", deployer) console.log(`Got contract FundMe at ${fundMe.address}`) console.log("Funding contract...") const transactionResponse = await fundMe.fund({ value: ethers.utils.parseEther("0.1"), }) await transactionResponse.wait() console.log("Funded!") } main() .then(() => process.exit(0)) .catch((error) => { console.error(error) process.exit(1) })
const { ethers, getNamedAccounts } = require("hardhat") async function main() { const { deployer } = await getNamedAccounts() const fundMe = await ethers.getContract("FundMe", deployer) console.log(`Got contract FundMe at ${fundMe.address}`) console.log("Withdrawing from contract...") const transactionResponse = await fundMe.withdraw() await transactionResponse.wait() console.log("Got it back!") } main() .then(() => process.exit(0)) .catch((error) => { console.error(error) process.exit(1) })
const { assert, expect } = require("chai") const { network, deployments, ethers } = require("hardhat") const { developmentChains } = require("../../helper-hardhat-config") !developmentChains.includes(network.name) ? describe.skip : describe("FundMe", function () { let fundMe let mockV3Aggregator let deployer const sendValue = ethers.utils.parseEther("1") beforeEach(async () => { // const accounts = await ethers.getSigners() // deployer = accounts[0] deployer = (await getNamedAccounts()).deployer await deployments.fixture(["all"]) fundMe = await ethers.getContract("FundMe", deployer) mockV3Aggregator = await ethers.getContract( "MockV3Aggregator", deployer ) }) describe("constructor", function () { it("sets the aggregator addresses correctly", async () => { const response = await fundMe.getPriceFeed() assert.equal(response, mockV3Aggregator.address) }) }) describe("fund", function () { // https://ethereum-waffle.readthedocs.io/en/latest/matchers.html // could also do assert.fail it("Fails if you don't send enough ETH", async () => { await expect(fundMe.fund()).to.be.revertedWith( "You need to spend more ETH!" ) }) // we could be even more precise here by making sure exactly $50 works // but this is good enough for now it("Updates the amount funded data structure", async () => { await fundMe.fund({ value: sendValue }) const response = await fundMe.getAddressToAmountFunded( deployer ) assert.equal(response.toString(), sendValue.toString()) }) it("Adds funder to array of funders", async () => { await fundMe.fund({ value: sendValue }) const response = await fundMe.getFunder(0) assert.equal(response, deployer) }) }) describe("withdraw", function () { beforeEach(async () => { await fundMe.fund({ value: sendValue }) }) it("withdraws ETH from a single funder", async () => { // Arrange const startingFundMeBalance = await fundMe.provider.getBalance(fundMe.address) const startingDeployerBalance = await fundMe.provider.getBalance(deployer) // Act const transactionResponse = await fundMe.withdraw() const transactionReceipt = await transactionResponse.wait() const { gasUsed, effectiveGasPrice } = transactionReceipt const gasCost = gasUsed.mul(effectiveGasPrice) const endingFundMeBalance = await fundMe.provider.getBalance( fundMe.address ) const endingDeployerBalance = await fundMe.provider.getBalance(deployer) // Assert // Maybe clean up to understand the testing assert.equal(endingFundMeBalance, 0) assert.equal( startingFundMeBalance .add(startingDeployerBalance) .toString(), endingDeployerBalance.add(gasCost).toString() ) }) // this test is overloaded. Ideally we'd split it into multiple tests // but for simplicity we left it as one it("is allows us to withdraw with multiple funders", async () => { // Arrange const accounts = await ethers.getSigners() for (i = 1; i < 6; i++) { const fundMeConnectedContract = await fundMe.connect( accounts[i] ) await fundMeConnectedContract.fund({ value: sendValue }) } const startingFundMeBalance = await fundMe.provider.getBalance(fundMe.address) const startingDeployerBalance = await fundMe.provider.getBalance(deployer) // Act const transactionResponse = await fundMe.cheaperWithdraw() // Let's comapre gas costs :) // const transactionResponse = await fundMe.withdraw() const transactionReceipt = await transactionResponse.wait() const { gasUsed, effectiveGasPrice } = transactionReceipt const withdrawGasCost = gasUsed.mul(effectiveGasPrice) console.log(`GasCost: ${withdrawGasCost}`) console.log(`GasUsed: ${gasUsed}`) console.log(`GasPrice: ${effectiveGasPrice}`) const endingFundMeBalance = await fundMe.provider.getBalance( fundMe.address ) const endingDeployerBalance = await fundMe.provider.getBalance(deployer) // Assert assert.equal( startingFundMeBalance .add(startingDeployerBalance) .toString(), endingDeployerBalance.add(withdrawGasCost).toString() ) // Make a getter for storage variables await expect(fundMe.getFunder(0)).to.be.reverted for (i = 1; i < 6; i++) { assert.equal( await fundMe.getAddressToAmountFunded( accounts[i].address ), 0 ) } }) it("Only allows the owner to withdraw", async function () { const accounts = await ethers.getSigners() const fundMeConnectedContract = await fundMe.connect( accounts[1] ) await expect( fundMeConnectedContract.withdraw() ).to.be.revertedWith("FundMe__NotOwner") }) }) })
const { assert } = require("chai") const { network, ethers, getNamedAccounts } = require("hardhat") const { developmentChains } = require("../../helper-hardhat-config") developmentChains.includes(network.name) ? describe.skip : describe("FundMe Staging Tests", function () { let deployer let fundMe const sendValue = ethers.utils.parseEther("0.1") beforeEach(async () => { deployer = (await getNamedAccounts()).deployer fundMe = await ethers.getContract("FundMe", deployer) }) it("allows people to fund and withdraw", async function () { const fundTxResponse = await fundMe.fund({ value: sendValue }) await fundTxResponse.wait(1) const withdrawTxResponse = await fundMe.withdraw() await withdrawTxResponse.wait(1) const endingFundMeBalance = await fundMe.provider.getBalance( fundMe.address ) console.log( endingFundMeBalance.toString() + " should equal 0, running assert equal..." ) assert.equal(endingFundMeBalance.toString(), "0") }) })
require("@nomiclabs/hardhat-waffle") require("hardhat-gas-reporter") require("@nomiclabs/hardhat-etherscan") require("dotenv").config() require("solidity-coverage") require("hardhat-deploy") // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more /** * @type import('hardhat/config').HardhatUserConfig */ const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY || "" const GOERLI_RPC_URL = process.env.GOERLI_RPC_URL || "https://eth-mainnet.alchemyapi.io/v2/your-api-key" const PRIVATE_KEY = process.env.PRIVATE_KEY || "0x11ee3108a03081fe260ecdc106554d09d9d1209bcafd46942b10e02943effc4a" const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "" module.exports = { defaultNetwork: "hardhat", networks: { hardhat: { chainId: 31337, // gasPrice: 130000000000, }, goerli: { url: GOERLI_RPC_URL, accounts: [PRIVATE_KEY], chainId: 5, blockConfirmations: 6, }, }, solidity: { compilers: [ { version: "0.8.7", }, { version: "0.6.6", }, ], }, etherscan: { apiKey: ETHERSCAN_API_KEY, }, gasReporter: { enabled: true, currency: "USD", outputFile: "gas-report.txt", noColors: true, // coinmarketcap: COINMARKETCAP_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, }, }
const networkConfig = { 31337: { name: "localhost", }, // Price Feed Address, values can be obtained at https://docs.chain.link/docs/reference-contracts 5: { name: "goerli", ethUsdPriceFeed: "0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e", }, } const developmentChains = ["hardhat", "localhost"] module.exports = { networkConfig, developmentChains, }
本文作者:前端小毛
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!