Skip to content

Commit

Permalink
Foundry fundme course
Browse files Browse the repository at this point in the history
  • Loading branch information
icyfry committed Apr 8, 2024
1 parent 96c13e6 commit 1dc2d43
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 4 deletions.
18 changes: 18 additions & 0 deletions contracts/script/DeployFundMe.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {HelperConfig} from "./HelperConfig.s.sol";
import {FundMe} from "../src/fundme/FundMe.sol";

contract DeployFundMe is Script {
function run() external returns (FundMe, HelperConfig) {
HelperConfig helperConfig = new HelperConfig(); // This comes with our mocks!
address priceFeed = helperConfig.activeNetworkConfig();

vm.startBroadcast();
FundMe fundMe = new FundMe(priceFeed);
vm.stopBroadcast();
return (fundMe, helperConfig);
}
}
45 changes: 45 additions & 0 deletions contracts/script/HelperConfig.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {MockV3Aggregator} from "../test/mock/MockV3Aggregator.sol";
import {Script} from "forge-std/Script.sol";

contract HelperConfig is Script {
NetworkConfig public activeNetworkConfig;

uint8 public constant DECIMALS = 8;
int256 public constant INITIAL_PRICE = 2000e8;

struct NetworkConfig {
address priceFeed;
}

event HelperConfig__CreatedMockPriceFeed(address priceFeed);

constructor() {
if (block.chainid == 11155111) {
activeNetworkConfig = getSepoliaEthConfig();
} else {
activeNetworkConfig = getOrCreateAnvilEthConfig();
}
}

function getSepoliaEthConfig() public pure returns (NetworkConfig memory sepoliaNetworkConfig) {
sepoliaNetworkConfig = NetworkConfig({
priceFeed: 0x694AA1769357215DE4FAC081bf1f309aDC325306 // ETH / USD
});
}

function getOrCreateAnvilEthConfig() public returns (NetworkConfig memory anvilNetworkConfig) {
// Check to see if we set an active network config
if (activeNetworkConfig.priceFeed != address(0)) {
return activeNetworkConfig;
}
vm.startBroadcast();
MockV3Aggregator mockPriceFeed = new MockV3Aggregator(DECIMALS, INITIAL_PRICE);
vm.stopBroadcast();
emit HelperConfig__CreatedMockPriceFeed(address(mockPriceFeed));

anvilNetworkConfig = NetworkConfig({priceFeed: address(mockPriceFeed)});
}
}
37 changes: 37 additions & 0 deletions contracts/script/InteractionsFundMe.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Script, console} from "forge-std/Script.sol";
import {FundMe} from "../src/fundme/FundMe.sol";
//import {DevOpsTools} from "foundry-devops/src/DevOpsTools.sol";

contract FundFundMe is Script {
uint256 SEND_VALUE = 0.1 ether;

function fundFundMe(address mostRecentlyDeployed) public {
vm.startBroadcast();
FundMe(payable(mostRecentlyDeployed)).fund{value: SEND_VALUE}();
vm.stopBroadcast();
console.log("Funded FundMe with %s", SEND_VALUE);
}

function run() external {
address contractAddress = vm.envAddress("FUNDME_ADDRESS");
fundFundMe(contractAddress);
}
}

contract WithdrawFundMe is Script {
function withdrawFundMe(address mostRecentlyDeployed) public {
vm.startBroadcast();
FundMe(payable(mostRecentlyDeployed)).withdraw();
vm.stopBroadcast();
console.log("Withdraw FundMe balance!");
}

function run() external {
address contractAddress = vm.envAddress("FUNDME_ADDRESS");
withdrawFundMe(contractAddress);
}
}
4 changes: 0 additions & 4 deletions contracts/src/cryptozombies/zombieownership.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ contract ZombieOwnership is ZombieAttack, IERC721 {
emit Approval(msg.sender, _approved, _tokenId);
}

function test() external pure returns (uint256) {
return 777;
}

function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external {
_transfer(from, to, tokenId);
}
Expand Down
114 changes: 114 additions & 0 deletions contracts/src/fundme/FundMe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
// 1. Pragma
pragma solidity ^0.8.19;
// 2. Imports

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {PriceConverter} from "./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 = 5 * 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);
}

/**
* Getter Functions
*/

/**
* @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;
}
}
25 changes: 25 additions & 0 deletions contracts/src/fundme/PriceConverter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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(AggregatorV3Interface priceFeed) internal view returns (uint256) {
// Sepolia ETH / USD Address
// https://docs.chain.link/data-feeds/price-feeds/addresses
(, int256 answer,,,) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
}

// 1000000000
function getConversionRate(uint256 ethAmount, AggregatorV3Interface priceFeed) internal view returns (uint256) {
uint256 ethPrice = getPrice(priceFeed);
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000;
// the actual ETH/USD conversion rate, after adjusting the extra 0s.
return ethAmountInUsd;
}
}
41 changes: 41 additions & 0 deletions contracts/test/fundme.integration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import {DeployFundMe} from "../script/DeployFundMe.s.sol";
import {FundFundMe, WithdrawFundMe} from "../script/InteractionsFundMe.s.sol";
import {FundMe} from "../src/fundme/FundMe.sol";
import {HelperConfig} from "../script/HelperConfig.s.sol";
import {Test, console} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";

contract InteractionsTest is StdCheats, Test {
FundMe public fundMe;
HelperConfig public helperConfig;

uint256 public constant SEND_VALUE = 0.1 ether; // just a value to make sure we are sending enough!
uint256 public constant STARTING_USER_BALANCE = 10 ether;
uint256 public constant GAS_PRICE = 1;

address public constant USER = address(1);

// uint256 public constant SEND_VALUE = 1e18;
// uint256 public constant SEND_VALUE = 1_000_000_000_000_000_000;
// uint256 public constant SEND_VALUE = 1000000000000000000;

function setUp() external {
DeployFundMe deployer = new DeployFundMe();
(fundMe, helperConfig) = deployer.run();
vm.deal(USER, STARTING_USER_BALANCE);
}

function testUserCanFundAndOwnerWithdraw() public {
FundFundMe fundFundMe = new FundFundMe();
fundFundMe.fundFundMe(address(fundMe));

WithdrawFundMe withdrawFundMe = new WithdrawFundMe();
withdrawFundMe.withdrawFundMe(address(fundMe));

assert(address(fundMe).balance == 0);
}
}
Loading

0 comments on commit 1dc2d43

Please sign in to comment.