4. Fund Me
Sending ETH through a function & reverts
contract FundMe {
// Smart Contracts can hold funds like wallets can
function fund() public payable {
// Want to be able to set a mininum fund amount in USD
// 1. How do we send ETH to this contract?
// require means this function needs this specific condition to fulfill to run
// else revert : undo any action before, and send remaining gas back
// 1e18 === 1 * 10 ** 18 === 1000000000000000000 === value in wei of 1 ethereum
require(msg.value > 1e18, "Didn't send enough!");
}
}
Chainlinks & Oracles
Oracles are centralized network which introduce single point of failure
Chainlinks are Decentralized Oracle Network
Chainlink has many features out of the box
Chainlink Price Feeds
Chainlink Data Feeds are the quickest way to connect your smart contracts to the real-world market prices of assets. https://docs.chain.link/docs/data-feeds/price-feeds/
Chainlink VRF (Verifiable Random Function)
It is a provably fair and verifiable random number generator (RNG) that enables smart contracts to access random values without compromising security or usability. They are also used in building blockchain games and NFTs.
Chainlink Automation (Chainlink Keepers)
Enables conditional execution of your smart contracts based on trigger of an event. https://docs.chain.link/docs/chainlink-automation/
Chainlink Any API
Chainlink nodes can request any API using any api. It requires LINK
tokens for the transaction. https://docs.chain.link/docs/any-api/
Interfaces and Price Feeds
contract FundMe {
function getVersion() public view returns (uint256) {
// ABI
// Address (Goerli ETH Testnet) 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
// Method 1 of ABI : Copy the Interface code and paste it in this contract
// Interface is the template code (.h file in c++) which includes
// function definations and not the actual implementation
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
return priceFeed.version();
}
}
https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
Importing from GitHub & NPM
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract FundMe {
function getVersion() public view returns (uint256) {
// ABI
// Address (Goerli ETH Testnet) 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
// Method 2 (Proper Way) : Import directly from Github via NPM Package
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
return priceFeed.version();
}
}
Floating Point Math in Solidity
contract FundMe {
uint256 public mininumUsd = 50 * 1e18 ; // 1 * 10 ** 18
function fund() public payable {
require(getConversionRate(msg.value) >= mininumUsd, "Didn't send enough!");
// 18 decimals
}
function getPrice() public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
(,int256 price,,,) = priceFeed.latestRoundData();
// ETH in terms of USD
// 1500.00000000 (8 decimal places)
return uint256(price * 1e10); // 1**10 == 1000000000 == 18 decimal places
}
function getConversionRate(uint256 ethAmount) public view returns (uint256) {
uint256 ethPrice = getPrice();
// Always multiply before devide
uint256 ethAmountInUsd = (ethPrice * ethAmount); // 36 decimals
ethAmountInUsd /= 1e18; // 18 decimals
return ethAmountInUsd;
}
}
Libraries
Similar to contracts but you can't declare any state variable and you can't send ether
It is embedded into contract if all library functions are internal
Otherwise, library must be deployed and then linked before the contract is deployed
PriceConverter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
library PriceConverter {
function getPrice() internal view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
(,int256 price,,,) = priceFeed.latestRoundData();
return uint256(price * 1e10);
}
function getConversionRate(uint256 ethAmount) internal view returns (uint256) {
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount);
ethAmountInUsd /= 1e18;
return ethAmountInUsd;
}
}
FundMe.sol
contract FundMe {
using PriceConverter for uint256;
uint256 public mininumUsd = 50 * 1e18 ;
address[] public funders;
mapping(address => uint256) public addressToAmountFunded;
function fund() public payable {
// If function had 2 parameters like
// getConversionRate(uint256 value, uint somethingElse) {}
// We can call like this
// msg.value.getConversionRate(12);
require(msg.value.getConversionRate() >= mininumUsd, "Didn't send enough!");
funders.push(msg.sender);
addressToAmountFunded[msg.sender] = msg.value;
}
}
SafeMath, Overflow Checking, and the "unchecked" keyword
Was used widely before version 0.8 of solidity
Unsigned integers were going unchecked which led to overflows
uint256 bigNumber = 255
bigNumber = bigNumber + 1
// value becomes 0
Safemath library was used for checking the upper limits to avoid overflows
Onward version 0.8, this check was automatically added
We can still use
unchecked {}
keyword to revert back to unchecked mode
For Loops
function withdraw() public {
for(uint256 funderIndex = 0; funderIndex < funders.length; funderIndex++) {
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
}
Resetting an Array
// 0 means reset to blank array, 1 means reset to array of size 1
funders = new address[](0);
Withdrawing Ethereum
// Three Different Ways
// 1. Transfer
// capped at 2300 gas, if more gas used, throws error
// Automatically returns if transaction failed
// msg.sender = address
// payable(msg.sender) = payable address
// payable(msg.sender).transfer(address(this).balance);
// 2. Send
// capped at 2300 gas, returns boolean
// Don't automatically returns if transaction failed
// bool sendSuccess = payable(msg.sender).send(address(this).balance);
// require(sendSuccess, "Send failed");
// 3. Call
// forwards all gas or set gas, returns boolean
// it is a lower level command which returns 2 variables i.e bool & bytes memory dataReturned
(bool callSuccess,) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
Constructor
contract FundMe {
address public owner;
constructor() {
owner = msg.sender;
}
}
Modifiers
function withdraw() public onlyOwner {
for(uint256 funderIndex = 0; funderIndex < funders.length; funderIndex++) {
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
funders = new address[](0);
(bool callSuccess,) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
}
modifier onlyOwner {
require(msg.sender == owner, "Sender is now owner!");
_; // Doing rest of code
}
Advanced Solidity Concepts
Immutable & Constant
Used to make contract gas efficient
Instead of storing these variables in the storage class, we save them in the byte code of the contract hence gas efficient.
If a variable is assigned value at compile time and is never changed, then we can add
constant
keyword with it.Naming convention is usually all caps
Variables that we set one time but not on the same line where they are declared, we add
immutable
keyword with it.Name convention is usually
i_<name-of-var>
// Initialized on same line
uint256 public constant MINIMUM_USD = 50 * 1e18 ;
// Initialized later on in contract
address public immutable i_owner;
constructor() {
i_owner = msg.sender;
}
Custom Errors
Introduced in version 0.8.4
We can declare custom errors on reverts
Used for gas efficiency
Since a string is called in require statement
error NotOwner();
contract FundMe {
modifier onlyOwner {
// require(msg.sender == i_owner, "Sender is now owner!");
if(msg.sender != i_owner) { revert NotOwner(); }
_;
}
}
Receive & Fallback
What happens if someone send this contract ETH without calling any function
contract FallbackExample {
uint256 public result;
// Dont add function keyword with receive since it is a special function
// Other special funtions are Constructor(), Fallback(), etc
// Trigerred when someone send ETH to contract without calling any ftn and
// also when there is no call data with the transaction
receive() external payable {
result = 1;
}
// Trigerred when someone send ETH to contract without calling any ftn and
// also when there is call data with the transaction
fallback() external payable {
result = 2;
}
}
Final Contracts Samples
FundMe.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./PriceConverter.sol";
error NotOwner();
contract FundMe {
using PriceConverter for uint256;
uint256 public constant MINIMUM_USD = 50 * 1e18 ;
address[] public funders;
mapping(address => uint256) public addressToAmountFunded;
address public immutable i_owner;
constructor() {
i_owner = msg.sender;
}
function fund() public payable {
require(msg.value.getConversionRate() >= MINIMUM_USD, "Didn't send enough!");
funders.push(msg.sender);
addressToAmountFunded[msg.sender] = msg.value;
}
function withdraw() public onlyOwner {
for(uint256 funderIndex = 0; funderIndex < funders.length; funderIndex++) {
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
funders = new address[](0);
(bool callSuccess,) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
}
modifier onlyOwner {
// require(msg.sender == i_owner, "Sender is now owner!");
if(msg.sender != i_owner) { revert NotOwner(); }
_;
}
receive() external payable {
fund();
}
fallback() external payable {
fund();
}
}
PriceConverter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
library PriceConverter {
function getPrice() internal view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
(,int256 price,,,) = priceFeed.latestRoundData();
return uint256(price * 1e10);
}
function getConversionRate(uint256 ethAmount) internal view returns (uint256) {
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount);
ethAmountInUsd /= 1e18;
return ethAmountInUsd;
}
}
Last updated