Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BZAghalarov committed Mar 15, 2023
0 parents commit cf7b79c
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.sol linguist-language=Solidity
*.vy linguist-language=Python
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
.env
.history
.hypothesis/
build/
reports/
23 changes: 23 additions & 0 deletions brownie-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
dependencies:
# - <organization/repo>@<version>
- smartcontractkit/[email protected]
compiler:
solc:
remappings:
- '@chainlink=smartcontractkit/[email protected]'

dotenv: .env
networks:
goerli:
eth_usd_price_feed: '0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e'
verify: True
mainnet-fork-dev:
eth_usd_price_feed: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
verify: False
development:
verify: False
ganache-local:
verify: False
eth_usd_price_feed: '0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e'
wallets:
from_key: ${PRIVATE_KEY}
108 changes: 108 additions & 0 deletions contracts/FundMe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

// SPDX-License-Identifier: MIT

// Smart contract that lets anyone deposit ETH into the contract
// Only the owner of the contract can withdraw the ETH
pragma solidity ^0.6.6;

// Get the latest ETH/USD price from chainlink price feed

// IMPORTANT: This contract has been updated to use the Goerli testnet
// Please see: https://docs.chain.link/docs/get-the-latest-price/
// For more information

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@chainlink/contracts/src/v0.6/vendor/SafeMathChainlink.sol";

contract FundMe {
// safe math library check uint256 for integer overflows
using SafeMathChainlink for uint256;

//mapping to store which address depositeded how much ETH
mapping(address => uint256) public addressToAmountFunded;
// array of addresses who deposited
address[] public funders;
//address of the owner (who deployed the contract)
address public owner;

AggregatorV3Interface public priceFeed;

// the first person to deploy the contract is
// the owner
constructor(address _priceFeed) public {
priceFeed = AggregatorV3Interface(_priceFeed);
owner = msg.sender;
}

function fund() public payable {
// 18 digit number to be compared with donated amount
uint256 minimumUSD = 50 * 10**18;
//is the donated amount less than 50USD?
require(
getConversionRate(msg.value) >= minimumUSD,
"You need to spend more ETH!"
);
//if not, add to mapping and funders array
addressToAmountFunded[msg.sender] += msg.value;
funders.push(msg.sender);
}

//function to get the version of the chainlink pricefeed
function getVersion() public view returns (uint256) {
return priceFeed.version();
}

function getPrice() public view returns (uint256) {
(, int256 answer, , , ) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
}

// 1000000000
function getConversionRate(uint256 ethAmount)
public
view
returns (uint256)
{
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000;
// the actual ETH/USD conversation rate, after adjusting the extra 0s.
return ethAmountInUsd;
}

function getEntranceFee() public view returns (uint256) {
// minimum USD
uint256 minimumUSD = 50 * 10**18;
uint256 price = getPrice();
uint256 precision = 1 * 10**18;
return (minimumUSD * precision) / price;
}

//modifier: https://medium.com/coinmonks/solidity-tutorial-all-about-modifiers-a86cf81c14cb
modifier onlyOwner() {
//is the message sender owner of the contract?
require(msg.sender == owner);

_;
}

// onlyOwner modifer will first check the condition inside it
// and
// if true, withdraw function will be executed
function withdraw() public payable onlyOwner {
msg.sender.transfer(address(this).balance);

//iterate through all the mappings and make them 0
//since all the deposited amount has been withdrawn
for (
uint256 funderIndex = 0;
funderIndex < funders.length;
funderIndex++
) {
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
//funders array will be initialized to 0
funders = new address[](0);
}
}
112 changes: 112 additions & 0 deletions contracts/test/MockV3Aggregator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV2V3Interface.sol";

/**
* @title MockV3Aggregator
* @notice Based on the FluxAggregator contract
* @notice Use this contract when you need to test
* other contract's ability to read data from an
* aggregator contract, but how the aggregator got
* its answer is unimportant
*/
contract MockV3Aggregator is AggregatorV2V3Interface {
uint256 constant public override version = 0;

uint8 public override decimals;
int256 public override latestAnswer;
uint256 public override latestTimestamp;
uint256 public override latestRound;

mapping(uint256 => int256) public override getAnswer;
mapping(uint256 => uint256) public override getTimestamp;
mapping(uint256 => uint256) private getStartedAt;

constructor(
uint8 _decimals,
int256 _initialAnswer
) public {
decimals = _decimals;
updateAnswer(_initialAnswer);
}

function updateAnswer(
int256 _answer
) public {
latestAnswer = _answer;
latestTimestamp = block.timestamp;
latestRound++;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = block.timestamp;
getStartedAt[latestRound] = block.timestamp;
}

function updateRoundData(
uint80 _roundId,
int256 _answer,
uint256 _timestamp,
uint256 _startedAt
) public {
latestRound = _roundId;
latestAnswer = _answer;
latestTimestamp = _timestamp;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = _timestamp;
getStartedAt[latestRound] = _startedAt;
}

function getRoundData(uint80 _roundId)
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (
_roundId,
getAnswer[_roundId],
getStartedAt[_roundId],
getTimestamp[_roundId],
_roundId
);
}

function latestRoundData()
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (
uint80(latestRound),
getAnswer[latestRound],
getStartedAt[latestRound],
getTimestamp[latestRound],
uint80(latestRound)
);
}

function description()
external
view
override
returns (string memory)
{
return "v0.6/tests/MockV3Aggregator.sol";
}
}

// MockOracle
// Function signatures, event signatures, log topics
Empty file added scripts/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions scripts/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from brownie import FundMe, MockV3Aggregator, network, config
from scripts.helpful_scripts import (get_account,
deploy_mocks,
LOCAL_BLOCKCHAIN_ENVIRONMENTS,
)


def deploy_fund_me():
account = get_account()
# pass the price feed address to our fundme contract

# if we are on a persistent network like rinkeby, use the associated address
# otherwise, deploy mocks
if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
price_feed_address = config['networks'][network.show_active()]['eth_usd_price_feed']
else:
deploy_mocks()
price_feed_address = MockV3Aggregator[-1].address

fund_me = FundMe.deploy(
price_feed_address,
{"from": account},
publish_source=config["networks"][network.show_active()].get("verify")
)
print(f"Contract deployed to {fund_me.address}")
return fund_me


def main():
deploy_fund_me()
22 changes: 22 additions & 0 deletions scripts/fund_and_withdraw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from brownie import FundMe
from scripts.helpful_scripts import get_account


def fund():
fund_me = FundMe[-1]
account = get_account()
entrance_fee = fund_me.getEntranceFee()
print(entrance_fee)
print(f"The current entry fee is {entrance_fee}")
print("funding")
fund_me.fund({"from": account, "value": entrance_fee})


def withdraw():
fund_me = FundMe[-1]
account = get_account()
fund_me.withdraw({"from": account})


def main():
fund()
27 changes: 27 additions & 0 deletions scripts/helpful_scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from brownie import accounts, config, network, MockV3Aggregator
from web3 import Web3

FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

DECIMALS = 18
STARTING_PRICE = 2000


def get_account():
if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS\
or network.show_active() in FORKED_LOCAL_ENVIRONMENTS:
return accounts[0]
else:
return accounts.add(config["wallets"]["from_key"])


def deploy_mocks():
print(f"The active network is {network.show_active()}")
print("Deploying mocks...")
if len(MockV3Aggregator) <= 0:
MockV3Aggregator.deploy(DECIMALS,
Web3.toWei(STARTING_PRICE, "ether"),
{"from": get_account()}
)
print("Mocks Deployed!")
27 changes: 27 additions & 0 deletions tests/test_fund_me.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from scripts.helpful_scripts import get_account, LOCAL_BLOCKCHAIN_ENVIRONMENTS
from scripts.deploy import deploy_fund_me
from brownie import network, accounts, exceptions
import pytest


def test_can_fund_and_withdraw():
account = get_account()
fund_me = deploy_fund_me()
entrance_fee = fund_me.getEntranceFee() + 100
print("entrance_fee...", entrance_fee)
tx = fund_me.fund({"from": account, "value": entrance_fee})
tx.wait(1)
assert fund_me.addressToAmountFunded(account.address) == entrance_fee
tx2 = fund_me.withdraw({"from": account})
tx2.wait(1)
assert fund_me.addressToAmountFunded(account.address) == 0


def test_only_owner_can_withdraw():
if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
pytest.skip("only for local testing")

fund_me = deploy_fund_me()
bad_actor = accounts.add()
with pytest.raises(exceptions.VirtualMachineError):
fund_me.withdraw({"from": bad_actor})

0 comments on commit cf7b79c

Please sign in to comment.