import {Test, console} from "forge-std/Test.sol"; import {FundMe} from "../src/FundMe.sol";
contract FundMeTest is Test { FundMe fundMe; // 定义 FundMe 合约变量
function setUp() external { fundMe = new FundMe(); // 实例化一个 FundMe 合约 fundme }
function testMininumDollarIsFive() public { assertEq(fundMe.MINIMUM_USD(), 5e18); // 对 FundMe 合约进行调试 } function testOwnerIsMsgSender() public { assertEq(fundMe.i_owner(), msg.sender); } }
$ forge test --mt testFundFailsWithoutEnoughETH [⠢] Compiling... [⠃] Compiling 1 files with 0.8.21 [⠊] Solc 0.8.21 finished in 670.25ms Compiler run successful with warnings: Warning (2072): Unused local variable. --> test/FundeMeTest.t.sol:44:9: | 44 | uint256 cat = 1; | ^^^^^^^^^^^
Running 1 testfortest/FundeMeTest.t.sol:FundMeTest [FAIL. Reason: Call did not revert as expected] testFundFailsWithoutEnoughETH() (gas: 3047) Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.32ms Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests: Encountered 1 failing testintest/FundeMeTest.t.sol:FundMeTest [FAIL. Reason: Call did not revert as expected] testFundFailsWithoutEnoughETH() (gas: 3047)
Encountered a total of 1 failing tests, 0 tests succeeded
如上所示,上面的测试失败,因为vm.expectRevert()的下一行语句没有发生回滚。
1 2 3 4 5
function testFundFailsWithoutEnoughETH() public{ vm.expectRevert(); // the next line, should revert! // 等价于: assert(This tx fails/reverts) fundMe.fund(); // send 0 value,根据fundme合约中的fund函数,会发生回滚。 }
结果如下:
1 2 3 4 5 6 7 8 9 10 11
$ forge test --mt testFundFailsWithoutEnoughETH [⠢] Compiling... [⠊] Compiling 1 files with 0.8.21 [⠒] Solc 0.8.21 finished in 727.78ms Compiler run successful!
Running 1 testfortest/FundeMeTest.t.sol:FundMeTest [PASS] testFundFailsWithoutEnoughETH() (gas: 13305) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.92ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
// FundMe.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.18;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import {PriceConverter} from "./PriceConverter.sol";
error FundMe_NotOwner();
contract FundMe { using PriceConverter for uint256;
mapping(address => uint256) public addressToAmountFunded; address[] public funders;
// Could we make this constant? /* hint: no! We should make it immutable! */ address public /* immutable */ i_owner; uint256 public constant MINIMUM_USD = 5 * 10 ** 18; constructor() { i_owner = msg.sender; }
function fund() public payable { require(msg.value.getConversionRate() >= 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 getVersion() public view returns (uint256){ AggregatorV3Interface priceFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); return priceFeed.version(); } modifier onlyOwner { // require(msg.sender == owner); if (msg.sender != i_owner) revert FundMe_NotOwner(); _; } function withdraw() public onlyOwner { for (uint256 funderIndex=0; funderIndex < funders.length; funderIndex++){ address funder = funders[funderIndex]; addressToAmountFunded[funder] = 0; } funders = new address[](0); // // transfer // payable(msg.sender).transfer(address(this).balance); // // send // bool sendSuccess = payable(msg.sender).send(address(this).balance); // require(sendSuccess, "Send failed");
// call (bool callSuccess, ) = payable(msg.sender).call{value: address(this).balance}(""); require(callSuccess, "Call failed"); } // Explainer from: https://solidity-by-example.org/fallback/ // Ether is sent to contract // is msg.data empty? // / \ // yes no // / \ // receive()? fallback() // / \ // yes no // / \ //receive() fallback()
fallback() external payable { fund(); }
receive() external payable { fund(); }
}
// 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
// PriceConverter.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.18;
import {AggregatorV3Interface} from "@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() internal view returns (uint256) { // Sepolia ETH / USD Address // https://docs.chain.link/data-feeds/price-feeds/addresses AggregatorV3Interface priceFeed = AggregatorV3Interface( 0x694AA1769357215DE4FAC081bf1f309aDC325306 ); (, int256 answer, , , ) = priceFeed.latestRoundData(); // ETH/USD rate in 18 digit return uint256(answer * 10000000000); }
// 1000000000 function getConversionRate( uint256 ethAmount ) internal view returns (uint256) { uint256 ethPrice = getPrice(); uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000; // the actual ETH/USD conversion rate, after adjusting the extra 0s. return ethAmountInUsd; } }