lesson 6: AttributeError: Contract 'FundMe' object has no attribute 'getEntranceFee' #1399
Answered
by
cromewar
rishigarg256
asked this question in
Q&A
-
I am having troubles with this error. Below is my Fundme.Sol // SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol"; //importing data feeds from chainlink from NPM/Github
//we can lookup npm @chainlink/contracts and then go to the github repository and if we follow the import path, we get on the github page
//these files start with the interface keyword. Interfaces dont have full function implementation
//we can either copy the whole code and paste before contract FundMe or just import it like above
//if we ever get confused about how to import, we can check the code on docs.chain.link/docs/the-latest-price
//matching units (GWEI/WEI/ETH)
//to prevent the integer overflow issue happening(check Overflow.sol), similar to how we imported chainlink, we can import package called SafeMath from tool called openzeppelin
//https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol
//openzepplin is an open source tool that allows us to use a lot of pre built contracts
//SafeMath is generally not needed starting with Solidity 0.8, since the compiler now has built in overflow checking.
//SafeMathChainlink is basically the same as Openzepplin SafeMath
//Library: a library is similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused
//Using keyword: the directive using A for B;can be used to attach library functions (from the library A) to any type(B) in the context of a contract
//in this case we're attaching the safemath chain link library to uint256so that these overflows are automatically checked for
//keeping in mind if we're using anything less than 0.8 solidity version, we're gonna wanna use some type of safemath just to check for overflows
//we will not call functions safemath provides like div, add, mul, because 0.8 forwards, we dont ahave to use those
import "@chainlink/contracts/src/v0.6/vendor/SafeMathChainlink.sol";
contract FundMe {
//we want this contract to accept some type of payment
using SafeMathChainlink for uint256; //what this will do is it will use safemath chainlink for all of our uint256 and safemath chainlink doesnt allow for that overflow to occur
mapping(address => uint256) public addressToAmountFunded; //keeping track of who sent us some funding. uint256 represents the value
//below is an array of all the funders addresses
address[] public funders; //we created this funders array so that when we withraw all the money, this resets to 0
address public owner;
AggregatorV3Interface public priceFeed; //global price feed one
//a constructor is a function that gets called the instant your contract gets deployed. we use a constructor for becoming the owner of this contract
//so we could have an owner be set the instant we deploy this smart contract.
constructor(address _priceFeed) public {
//global price feed address parameter
//so instead of us creating the aggregatorV3 interface contracts right in the functions, we create a global one
priceFeed = AggregatorV3Interface(_priceFeed);
owner = msg.sender; //owner is going to be us, whoever deploys this contract
}
function fund() public payable {
//$50
//setting a threshold in terms of US dollar but how do we garantee that the user sends a minimum usd amount
//first we set up a minimum value
uint256 minimumUSD = 50 * 10**18; //we are using everything in terms of gwei, so we multiply everything by 10^18
//we use the require statement to check the truthiness of whatever require we have asked, in this case, if the minimum amount sent by the user is atleast $50 or not
// 1gwei < $50
require(
getConversionRate(msg.value) >= minimumUSD,
"You need to spend more ETH!"
); //this line says that if the conversion rate of message on value to usd, if they didnt send us enough ether, then we are going to stop executing.
//we will do a revert, we will revert the transaction, means the user get its money back as well as any unspent gas
//keyword payable means specifically, payable with ETH?Ethereum. wei is the smallest denomination of ether
addressToAmountFunded[msg.sender] += msg.value; //msg.sender and msg.value are keywords in every contract call
//msg.sender is the sender of the function call and msg.value is how much they sent
//now we need to set a minimum value which the people can fund for our endeveours and we need to use usd.
//for that we need to know what the ETH -> USD conversion rate. this is where decentralized oracle network chainlink comes to help
//The Oracle Ploblem (smart contract connectivity problem) - Smart contracts are unable to connect with external systems, data feeds, APIs, existing payment systems or any off-chain resources on their own
//Blockchain oracle- ANy device that interacts with the off-chain world to provide external data or computation to smart contracts
//centralized oracles are a point of failure
//Interfaces compile down to ABI
//working with interfaces is similar to interacting with structs or variables
//now whenever a funder funds this contract, we can go ahead and push them onto our funders array
funders.push(msg.sender);
}
function getVersion() public view returns (uint256) {
//TO WORK WITH INTERFACE contract, its gonna work the exact same way as interacting with a struct or a variable,
//WE CREATE A FUNCTION getversion and we call the version function in the AggregatorV3Interface.sol file pn this contract
//AggregatorV3Interface priceFeed = AggregatorV3Interface(
// 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e have done the exact same thing above in the constructor
// ); //this line says we have a contract that has the functions defined in the AggregatorV3Interface file located at that address
//now we define variable and structs in the same way we define working with other contracts and interfaces
//in the braket we put the address of where this contract is located. Finding Price feed addresses, we look at the eth price feeds on docs.chain.link website and look for rinkby testnet3
//THis address is located on an actual testnet its located on an actual network, its not gonna be located on our simulated chain
//so we need to deploy it on injected web3
return priceFeed.version();
}
function getPrice() public view returns (uint256) {
//AggregatorV3Interface priceFeed = AggregatorV3Interface(
// 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
// );
(
,
int256 answer, //a tuple is a list of objects of potentially different types whose number is a constant at compile-time //this is a syntax for getting a tuple //because the latest round data returns 5 variables and we dont need the other 4, we just remove them, keep them blank, and just keep int256 answer
,
,
) = priceFeed.latestRoundData();
return uint256(answer * 10000000000); //multiplying by 10000000000 because converting to gwei //there is an error because we are returning uint256 and the answer is in int256, so we type cast it
//it will return 18 digits (GWEI) matching GWEI(wei is the smallest unit of measure)/Wei/Eth
}
//lets say they send 1 gwei
function getConversionRate(uint256 ethAmount)
public
view
returns (uint256)
{
//function to convert eth to usd which the sender sends (ethamount)
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000; //because 1 eth = 1000000000000000000 wei
return ethAmountInUsd;
}
function getEntranceFee() public view returns (uint256) {
//minimumUSD
uint256 minimumUSD = 50 * 10**18;
uint256 price = getPrice();
uint256 precision = 1 * 10**18;
return (minimumUSD * precision) / price;
}
//Modifier: A modifier is used to change the behavior of a function in a declarative way.
modifier onlyOwner() {
//what this modifier does is that before we run the function below, run this modifier first
require(msg.sender == owner); //and then wherever your underscore is in the modifier, run the rest of the code
_; //we can have the underscore before the require statement as well, but we want to run the require statement first
}
function withdraw() public payable {
//we are creating a function withdraw to withdraw eth, we use payable to transfer eth
// require(msg.sender == owner); //we need to require that the owner is the person withrawing the funds. We dont need this anymore because we used a modifier with the require statement and the _;
msg.sender.transfer(address(this).balance); //transfer is a function we can call on any address to send eth from one address to another.
//this transfer function sends some amount of ethereum to whoever its being called on, in this case we are transferring ethereum to msg.sender
//we wanna send all the money thats been funded
//this is a keyword in solidity. whenever you refer to this, you're talking about the contract that you're currently in
//and when we add address(this), we want the address of the contract that we're currently in
//.balance is the balance attribute using which we can see the balance in ether of a contract.
//so with this line, we're saying whoever called the withdraw function (in this case msg.sender), transfer them all of our money.
//so what is happening is that we can send any value to this contract (eg. 1 eth, it will get deducted from the account,) and then we use the withraw function to get that money back
//maybe we want only the funding admin to withraw all the funds, not everyone. so we setup a function in such a way that only the contract owner can withraw funds
//require msg.sender = owner
//after all the money is withdrawn, we want to reset everyone's balance in that mapping to 0. we do that by using a for loop.
//FOr loop is a way to loop through a range to do things at each "loop".
//we use .length to get the length of our array
for (
uint256 funderIndex = 0;
funderIndex < funders.length;
funderIndex++
) {
//this loop will finish when funder index is greater than the length of the funders array
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0; //so now our mapping is going to be all updated to people having zero funded in there
}
//we have to reset our funders array as well
funders = new address[](0);
}
} below is helpful_scripts.py from brownie import network, config, accounts, MockV3Aggregator
from web3 import Web3
DECIMALS = 8 #this resembles the fusd price feed, its only 8
STARTING_PRICE = 200000000000
# we add a flag
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]
def get_account(): # in here we will natively check if we are WORKING on a development chain, we'll use accounts[0], and if not, we will use the method that pulls from our config
if (
network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS #we are saying that if the network we are working on is development or a ganache local, then return accounts[0]
): # network is another keyword which brownie has which allows us to interact with other networks
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...")
# deploying the MockV3Aggregator, with the parameters that the constructor takes in the MockV3Aggregator contract
if len(MockV3Aggregator) <= 0:
MockV3Aggregator.deploy(
DECIMALS,
Web3.toWei(STARTING_PRICE, "ether"),
{
"from": get_account()
}, # this toWei function will add 18 decimals to this 2000
) # 18 is the number of decimals, initial value is 2000000000000000000000
print("Mocks Deployed!") below is fund_and_withraw.py from brownie import FundMe
from scripts.helpful_scripts import get_account
def fund():
fund_me = FundMe[-1]
account = get_account() #added another function called getEntranceFee in the FundMe.sol. brownie run scripts/fund_and_withraw.py --network ganache-local
entrance_fee = fund_me.getEntranceFee()
print(entrance_fee)
def main():
fund() below is brownie-config.yaml dependencies:
# - <organization/repo>@<version>
# we can look up chainlink brownie contracts
# https://github.com/smartcontractkit/chainlink-brownie-contracts
- smartcontractkit/[email protected]
# we also need to tell brownie what the @chainlink means, which is whenever we are using @chainlink, we're actually referring to this import
# so we need to tell the compiler this
compiler:
solc:
remappings: # we remap, means whenever we see @chainlink, we mean - smartcontractkit/[email protected] package
- '@chainlink=smartcontractkit/[email protected]'
#then we brownie compile
#did npm install solc and npm install @chainlink/contracts
#dont have spaces before and after the = sign above because that's a string and if you added in extra spaces, so whatever is parsing it won't be able to use it properly
dotenv: .env
networks:
rinkeby:
eth_usd_price_feed: '0x8A753747A1Fa494EC906cE90E9f37563A8AF630e'
verify: True
development:
verify: False
ganache-local:
verify: False
wallets:
from_key: ${PRIVATE_KEY} below is deploy.py # building a simple deploy script to work with rinkeby and then deploying it to our own local ganache development chain
from brownie import FundMe, MockV3Aggregator, network, config
# from brownie_fund_me.scripts.helpful_scripts import deploy_mocks
from scripts.helpful_scripts import (
get_account,
deploy_mocks,
LOCAL_BLOCKCHAIN_ENVIRONMENTS,
)
# adding a network to brownie
# brownie networks list
# to add a new network to brownie, we run brownie networks add Ethereum ganache-local host=http://127.0.0.1:8545 chainid=1337 (we choose development or ethereum, we give it a name, we then give it a host, aka an http address(get it from ganache app) and then a chain/network id )
# keep the ganache UI up
# then we can run brownie run scripts/deploy.py --network ganache-local
def deploy_fund_me():
account = get_account() # add get_account() in another file called helpful scripts
# we need to 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
): # it is saying that whatwver network we are on, if it isnt development or ganache-local, then go ahead and use a config. if it is one of these 2, we go ahead and deploy a mock here. brownie run scripts/deploy.py --network ganache-local
price_feed_address = config["networks"][network.show_active()][
"eth_usd_price_feed"
] # we wanna parameterize where we get these addresses from, so we can use brownie config to add different addressess for different network
# else, if we are not on development chain, we're gonna have to deploy a mock, we deploy our own version of the price feed contract, known as mocking and we can interact with it accordingly.
# mock contracts go under the text folder in the contracts folder
# if we close the ganache UI, all the contracts will get deleted and we wont be able to interact with them.
else:
deploy_mocks()
price_feed_address = MockV3Aggregator[
-1
].address # we use the most recently deployed
# then we do brownie run scripts/deploy.py, we dont need a network flag here. we run into an issue because we are trying to verify a contract on a chain that doesnt exist. etherscan doesnt know about our local ganache chain
# to fix this, instead of doing publish_source = True, we go back to config, undernetworks, verify
fund_me = FundMe.deploy(
"0x8A753747A1Fa494EC906cE90E9f37563A8AF630e", # this is how you pass variables to constructors, anything inside the constructor function can be passed through brownie in our deploy script
{"from": account},
publish_source=config["networks"][network.show_active()].get(
"verify"
), # means we would like to publish our source code. Now when we go to rinkeby etherscan, we would have a checkmark besides our contract option and when we click contract, we can see all of the code in there
) # this deploy is going to make a state change to the blockchain
print(f"Contract deployed to {fund_me.address}")
# brownie run scripts/deploy.py --network rinkeby
# our fundme contract that uses the pricefeed contract to our own local development environment with a mock, aka, a fake price feed contract
# Refactoring
# way to deploy to our own ganache blockchain network or brownie's built in development chain so we can test quicker
# problems: aggregator address hard coded in FundMe.sol, these pricefeed contracts dont exist on a local ganache chain or a ganache chain that brownie spins up
# 2 ways to get around this
# Forking: work on a forked simulated chain
# Mocking: deploy a mock or deploy a fake price feed contract on our ganache local development chain
# 1. Mocks: deploying fake version of something and interacting with it as if its real
# if i do brownie run scripts/deploy.py without the network, it will default spin up a local ganache chain but error because we cant verify on a ganache chain
# 1st we parameterize our fundme.sol so that we dont have this hard-coded address in there
# we can have constructor parameters
# we can tell it what price feed address it should use right when we call our deploy function
def main():
deploy_fund_me()
# brownie run scripts/deploy.py --network rinkeby
# contract verification
# on etherscan we can manually verify and publish after clicking the contract button. COmpiler type: solidity, Compiler version: 0.6.6, open source license type: MIT
# optimization yes
# for entering solidity contract code, imports with "@" dont work in etherscan
# so importing fundme as it is wont actually work, because etherscan doesnt know what @chainlink/contracts is
# so we copy paste these code from these imports to the top of our contract
# replacing imports with the actual code is known as "flattening"
# important for verifying our smart contracts on platforms like etherscan
# brownie has a way to solve this
# go to etherscan.io, sign up and get an API key by going to my profile
# we create a new API token that allows us to verify our smart contracts
# we set the key ass an environment variable (EG. ETHERSCAN_TOKEN) to use it in brownie
# then to verify this, once we deploy this contract, we'll tell brownie whether we want to verify the contract or not
# IF I WANt to deploy is to my ganache chain, open ganache, and then brownie run scripts/deploy.py. Brownie is smart enough to detect if you're running yur ganache instance i am getting this error when i run brownie run scripts/fund_and_withraw.py --network ganache-local INFO: Could not find files for the given pattern(s).
Brownie v1.18.1 - Python development framework for Ethereum
BrownieFundMeProject is the active project.
Running 'scripts\fund_and_withraw.py::main'...
File "C:\anaconda\lib\site-packages\brownie\_cli\run.py", line 51, in main
return_value, frame = run(
File "C:\anaconda\lib\site-packages\brownie\project\scripts.py", line 103, in run
return_value = f_locals[method_name](*args, **kwargs)
File ".\scripts\fund_and_withraw.py", line 12, in main
fund()
File ".\scripts\fund_and_withraw.py", line 8, in fund
entrance_fee = fund_me.getEntranceFee()
File "C:\anaconda\lib\site-packages\brownie\network\contract.py", line 765, in __getattribute__
raise AttributeError(f"Contract '{self._name}' object has no attribute '{name}'")
AttributeError: Contract 'FundMe' object has no attribute 'getEntranceFee |
Beta Was this translation helpful? Give feedback.
Answered by
cromewar
Apr 13, 2022
Replies: 1 comment 5 replies
-
Hello @rishigarg256
|
Beta Was this translation helpful? Give feedback.
5 replies
Answer selected by
cromewar
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello @rishigarg256
As you are using: fund_me = FundMe[-1] I think the entrance Fee is not taken as you can remember we add this at the middle of the lesson, so please: