3.0 ERC20、NFT
一个收款合约有点类似银行,将付款用户的地址实现类似姓名的唯一标识,这个银行大家都可以存钱进来(装钱的袋子类似)
3.0.1 通过函数发送ETH
换算关系如下:
收款函数payable关键字:表示该函数具有收款功能。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract FundMe{
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
}
}
3.0.2 预言机、设置最小额度
1. 设置最小额度
使用require关键字(接收两个参数):
1. **condition**表示一个状态**true**或者**false。**(条件判断)
1. "",表示字符串,表示当**condition**状态为**false**时**revert**(回退)交易的错误设置。
require(condition,"");
使用require设置最小额度,代码如下
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract FundMe{
//使用mapping记录收款账户
mapping (address => uint256) public fundersToAcount;
//现在收款最低额度
uint256 MINMUM_VALUE = 1 * 10 **18;//(1ETH)
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
//使用预言机
require(msg.value >= MINMUM_VALUE, "Send more ETH");
//存入mapping,使用账户地址作为key
fundersToAcount[msg.sender] = msg.value;
}
}
2. 预言机
原因:链上合约不能获取现实世界(链下世界)的价格。
解决:引入预言机,因为用户一般使用USD来转账,预言机作用就是查询ETH和USD的汇率。
预言机:区块链外信息写入区块链内的机制一般被称为预言机;作用类似与一个食堂今日菜价的报板。
喂价(feed): 多个预言机组成DON(Decentralized Oracle Network)(防止一个预言机宕机,达到共识的操作,也需要实现去中心化的要求)进行查询价格之后聚合,将显示价格提供给类似ChainLink
使用ChainLink查询ETH->USD价格
示例代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract FundMe{
//使用mapping记录收款账户
mapping (address => uint256) public fundersToAcount;
//
AggregatorV3Interface internal dataFeed;
constructor() {
//sepolia测试网 ETH->USD 的地址
dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
}
//现在收款最低额度
uint256 MINMUM_VALUE = 100 * 10 **18;//最少现在100USD
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
require(convertEthToUsd(msg.value) >= MINMUM_VALUE, "Send more ETH");
//存入mapping,使用账户地址作为key
fundersToAcount[msg.sender] = msg.value;
}
//获取eth->usd的当前价格
function getChainlinkDataFeedLatestAnswer() public view returns (int) {
// prettier-ignore
(
/* uint80 roundId */,
int256 answer,
/*uint256 startedAt*/,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = dataFeed.latestRoundData();
//answer是当前eth对usd的价格
return answer;
}
//转换函数ETH->USD
function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {
//这个类似是查询ETH对USD的汇率 单位是USD
uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());
//ethAmount * ethPrice 为eth是多少usd的价格
return ethAmount * ethPrice / (10 ** 8 );
// ETH / USD 的精度是10^8 ethAmount是单位是wei即10^-18eth
}
}
3.0.2 transfer、send、call转账区别(提款转账的功能)
transfer和send纯转账,即一个地址到另一个地址;call需要传参时用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract FundMe{
//使用mapping记录收款账户
mapping (address => uint256) public fundersToAcount;
//
AggregatorV3Interface internal dataFeed;
//现在收款最低额度
uint256 constant MINMUM_VALUE = 100 * 10 ** 18;//最少现在100USD
//目标额度 constant 为常量关键字
uint256 constant TARGET = 1000 *10 ** 18;//筹集目标为1000USD
//合约所有权
address public onwner;
constructor() {
//sepolia测试网 ETH->USD 的地址
dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
//获取部署合约地址的人的地址用作身份验证
onwner = msg.sender;
}
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
require(convertEthToUsd(msg.value) >= MINMUM_VALUE, "Send more ETH");
//存入mapping,使用账户地址作为key
fundersToAcount[msg.sender] = msg.value;
}
//获取eth->usd的当前价格
function getChainlinkDataFeedLatestAnswer() public view returns (int) {
// prettier-ignore
(
/* uint80 roundId */,
int256 answer,
/*uint256 startedAt*/,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = dataFeed.latestRoundData();
//answer是当前eth对usd的价格
return answer;
}
//转换函数ETH->USD
function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {
//这个类似是查询ETH对USD的汇率 单位是USD
uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());
//ethAmount * ethPrice 为eth是多少usd的价格
return ethAmount * ethPrice / (10 ** 8 );
// ETH / USD 的精度是10^8 ethAmount是单位是wei即10^-18eth
}
//修改地址
function transferOwnerShip(address newAddress) public {
require(msg.sender == onwner, "Only Owners can do");
onwner = newAddress;
}
//从智能合约中提款
function getFund() external {
require(convertEthToUsd(address(this).balance) >= TARGET , "No Ether to transfer");
require(msg.sender == onwner, "Not owner of the contract");
//转账---transfer写法 如果失败会revert
payable(onwner).transfer(address(this).balance);
//转账---send写法 如果失败不会revert,会返回一个bool状态,可以根据这个状态判断交易是否成功
bool success = payable(onwner).send(address(this).balance);
//转账---call写法 transfer with data return value of function 格式:(bool(transfer的状态), result(是调用函数返回的结果)) = address.call{value:value}("传递的数据")
(bool success, result) = payable(onwner).call{value:address(this).balance}("");
require(success);
}
}
3.0.3 退款功能
//退款函数
function reFund() external {
require(convertEthToUsd(address(this).balance) < TARGET , "Target is reached");
require(uint256(fundersToAcount[msg.sender]) != 0,"there is not fund for you");
(bool success,) = payable(msg.sender).call{value:fundersToAcount[msg.sender]}("");
require(success,"transfer tx failed");
//把记录中提款的账户 数额清零
fundersToAcount[msg.sender] = 0;
}
3.0.4 时间锁
使用block.timestamp获取当前时间戳实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract FundMe{
//使用mapping记录收款账户
mapping (address => uint256) public fundersToAcount;
//
AggregatorV3Interface internal dataFeed;
//现在收款最低额度
uint256 constant MINMUM_VALUE = 100 * 10 ** 18;//最少现在100USD
//目标额度 constant 为常量关键字
uint256 constant TARGET = 1000 *10 ** 18;//筹集目标为1000USD
//合约所有权
address public onwner;
//时间锁
uint256 deploymentTimesTamp;
uint256 lockTime;
constructor(uint256 _lockTime) {
//sepolia测试网 ETH->USD 的地址
dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
//获取部署合约地址的人的地址用作身份验证
onwner = msg.sender;
//获取当前合约部署时间戳
deploymentTimesTamp = block.timestamp;
lockTime = _lockTime;
}
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
require(convertEthToUsd(msg.value) >= MINMUM_VALUE, "Send more ETH");
require(block.timestamp < deploymentTimesTamp + lockTime,"Timeout");
//存入mapping,使用账户地址作为key
fundersToAcount[msg.sender] = msg.value;
}
//获取eth->usd的当前价格
function getChainlinkDataFeedLatestAnswer() public view returns (int) {
// prettier-ignore
(
/* uint80 roundId */,
int256 answer,
/*uint256 startedAt*/,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = dataFeed.latestRoundData();
//answer是当前eth对usd的价格
return answer;
}
//转换函数ETH->USD
function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {
//这个类似是查询ETH对USD的汇率 单位是USD
uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());
//ethAmount * ethPrice 为eth是多少usd的价格
return ethAmount * ethPrice / (10 ** 8 );
// ETH / USD 的精度是10^8 ethAmount是单位是wei即10^-18eth
}
//修改地址
function transferOwnerShip(address newAddress) public {
require(msg.sender == onwner, "Only Owners can do");
require(block.timestamp < deploymentTimesTamp + lockTime,"Timeout");
onwner = newAddress;
}
//从智能合约中提款
function getFund() external {
require(convertEthToUsd(address(this).balance) >= TARGET , "Target is not reached");
require(msg.sender == onwner, "Not owner of the contract");
require(block.timestamp >= deploymentTimesTamp + lockTime,"Timeout");
//转账---transfer写法 如果失败会revert
//payable(onwner).transfer(address(this).balance);
//转账---send写法 如果失败不会revert,会返回一个bool状态,可以根据这个状态判断交易是否成功
//bool success = payable(onwner).send(address(this).balance);
//转账---call写法 transfer with data return value of function 格式:(bool , result) = address.call{value:value}("传递的数据")
(bool success, ) = payable(onwner).call{value:address(this).balance}("");
require(success,"transfer tx failed");
}
//退款函数
function reFund() external {
require(convertEthToUsd(address(this).balance) < TARGET , "Target is reached");
require(uint256(fundersToAcount[msg.sender]) != 0,"there is not fund for you");
require(block.timestamp >= deploymentTimesTamp + lockTime,"Timeout");
(bool success,) = payable(msg.sender).call{value:fundersToAcount[msg.sender]}("");
require(success,"transfer tx failed");
//把记录中提款的账户 数额清零
fundersToAcount[msg.sender] = 0;
}
}
3.0.5 修改器---modifier
因为上面重复的使用require来做类似于if判断,为使代码更加优雅引入修改器。
//从智能合约中提款
function getFund() external windowClose{
require(convertEthToUsd(address(this).balance) >= TARGET , "Target is not reached");
require(msg.sender == onwner, "Not owner of the contract");
//转账---transfer写法 如果失败会revert
//payable(onwner).transfer(address(this).balance);
//转账---send写法 如果失败不会revert,会返回一个bool状态,可以根据这个状态判断交易是否成功
//bool success = payable(onwner).send(address(this).balance);
//转账---call写法 transfer with data return value of function 格式:(bool , result) = address.call{value:value}("传递的数据")
(bool success, ) = payable(onwner).call{value:address(this).balance}("");
require(success,"transfer tx failed");
}
//退款函数
function reFund() external windowClose{
require(convertEthToUsd(address(this).balance) < TARGET , "Target is reached");
require(uint256(fundersToAcount[msg.sender]) != 0,"there is not fund for you");
(bool success,) = payable(msg.sender).call{value:fundersToAcount[msg.sender]}("");
require(success,"transfer tx failed");
//把记录中提款的账户 数额清零
fundersToAcount[msg.sender] = 0;
}
//修改器
modifier windowClose(){
require(block.timestamp >= deploymentTimesTamp + lockTime,"Timeout");
_;
}
在windowClose中 _ (下划线)代表执行的代码,如果下划线在require下面,则说明先执行require在进行下面的代码,反之。意思是 _ 代表了其他代码的占位符。windowClose直接当作关键字使用即可。
3.1 token、coin
| coin(代币) | token(通证) |
|---|---|
| 一个区块链网络只能拥有一个 | 一个区块链网络可以拥有无数个 |
| 可以支付交易的gas、fee | 不可支付交易的gas、fee |
| 代表区块链网络 | 代表第三方产品 |
| 类似人民币,在中国就只能有一个货币就是人民币,也代表了我大中国 | 类似Q币等等各种虚拟货币,毕竟你去小卖部买辣条是用不了Q币的 |
3.1.1创建通证合约
一个简易的通证合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract FundTonken {
//通证的名字
string public tonkenName;
//通证的简写
string public tokenSymbol;
// 通证发行量
uint public tokenSupply;
//owner地址
address public ownerAddress;
//balance
mapping (address => uint256) public balances;
//初始化
constructor(string memory _tonkenName,string memory _tokenSymbol){
tonkenName = _tonkenName;
tokenSymbol = _tokenSymbol;
ownerAddress = msg.sender;
}
//mint:获取通证
function mint (uint256 amountToMint) public {
balances[msg.sender] += amountToMint;
tokenSupply += amountToMint;
}
//transfer通证
function transfer(address payee,uint256 amount) public {
require(balancesOf(msg.sender) >= amount,"You do not have enough balance to transfer");
balances[msg.sender] -= amount;
balances[payee] += amount;
}
//查询某个地址的通证数量
function balancesOf (address addr) public view returns (uint256){
return balances[addr];
}
}
3.1.2 继承
使用is关键字来继承父类,修饰符为private的不可被继承,被子类继承的拥有和父类一样的属性public、external、internal;
//SPDX-License-Identifier:MIT
pragma solidity ^0.8.20;
//父合约
contract Parent{
uint256 public a;
uint256 private b;
function addToOne() public {
a++;
}
}
//子合约继承 使用is关键字
contract Child is Parent{
function addToTow() public {
a+=2;
}
}
3.1.3 ERC20 (Fungible Token)可交换性
该合约下的通证是一样的,没有什么区别,例如:我有十块钱,你有十块钱,咱俩交换,钱没变都是十块钱。
3.1.4 ERC721(NFT---Not Fungible Token)不可交换的
该合约下的通证是不一样的,有区别的,例如:我有一副画,你有一副画,咱俩交换,我的画跟你的画已经不一样了,因为每幅画都是不同的有特性的。
3.1.5 抽象合约&虚函数
抽象合约:里面的方法不需要写清楚他的功能,即里面有虚函数时,该合约必须为抽象合约;
虚函数:当被子合约继承时,虚函数可以被重载(子合约可以定义具体的方法,父合约只是相当于占位);
//SPDX-License-Identifier:MIT
pragma solidity ^0.8.20;
//抽象合约 abstract
abstract contract Parent{
//虚函数
function addToOne() public virtual;
function addToTow() public {
xxx
}
function addToThree() public virtual{
xxx
}
}
//子合约继承抽象合约 也是使用is关键字
contract Child is Parent{
//子合约继承抽象合约的时候必须重写虚函数,函数同名且加上关键字override
function addToOne() public override {
a++;
}
//addToThree 在抽象父合约有函数体,则子合约继承的时候可以不写,默认使用抽象父合约里面的函数体
//function addToThree() public virtual{
// xxx
//}
}
继承:子合约继承抽象合约的时候必须重写虚函数,函数同名且加上关键字override,若抽象父合约定义有函数体,则子合约继承是不强制要求override,相当于默认执行抽象符合有里虚函数的函数体
3.2 书写NRC20合约
_mint:接受两个参数,一个是address,一个是uint256
3.2.1 继承NRC20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
//引入ERC20
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.Sol";
contract FundTokenERC20 is ERC20{
//构造函数 constructor() ERC20("KITE","KT"){}代表意思是调用父合约的构造函数
// _name 是token的名称,_symbol 是符号,初始化为Kite
constructor() ERC20("KITE","KT"){}
}
3.2.2 自定义ERC20合约
FundMe.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract FundMe{
//使用mapping记录收款账户
mapping (address => uint256) public fundersToAcount;
//
AggregatorV3Interface internal dataFeed;
//现在收款最低额度
uint256 constant MINMUM_VALUE = 100 * 10 ** 18;//最少现在100USD
//目标额度 constant 为常量关键字
uint256 constant TARGET = 1000 *10 ** 18;//筹集目标为1000USD
//合约所有权
address public onwner;
//时间锁
uint256 deploymentTimesTamp;
uint256 lockTime;
//记录ERC20地址
address _addressERC20;
//当前合约是否结束
bool public isOver = false;
constructor(uint256 _lockTime) {
//sepolia测试网 ETH->USD 的地址
dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
//获取部署合约地址的人的地址用作身份验证
onwner = msg.sender;
//获取当前合约部署时间戳
deploymentTimesTamp = block.timestamp;
lockTime = _lockTime;
}
//收款函数 这样这个FundMe合约即可实现收款,类似一个钱袋子了
function fund() external payable{
require(convertEthToUsd(msg.value) >= MINMUM_VALUE, "Send more ETH");
require(block.timestamp < deploymentTimesTamp + lockTime,"Timeout");
//存入mapping,使用账户地址作为key
fundersToAcount[msg.sender] = msg.value;
}
//获取eth->usd的当前价格
function getChainlinkDataFeedLatestAnswer() public view returns (int) {
// prettier-ignore
(
/* uint80 roundId */,
int256 answer,
/*uint256 startedAt*/,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = dataFeed.latestRoundData();
//answer是当前eth对usd的价格
return answer;
}
//转换函数ETH->USD
function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {
//这个类似是查询ETH对USD的汇率 单位是USD
uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());
//ethAmount * ethPrice 为eth是多少usd的价格
return ethAmount * ethPrice / (10 ** 8 );
// ETH / USD 的精度是10^8 ethAmount是单位是wei即10^-18eth
}
//修改地址
function transferOwnerShip(address newAddress) public {
require(msg.sender == onwner, "Only Owners can do");
require(block.timestamp < deploymentTimesTamp + lockTime,"Timeout");
onwner = newAddress;
}
//从智能合约中提款
function getFund() external windowClose{
require(convertEthToUsd(address(this).balance) >= TARGET , "Target is not reached");
require(msg.sender == onwner, "Not owner of the contract");
//转账---transfer写法 如果失败会revert
//payable(onwner).transfer(address(this).balance);
//转账---send写法 如果失败不会revert,会返回一个bool状态,可以根据这个状态判断交易是否成功
//bool success = payable(onwner).send(address(this).balance);
//转账---call写法 transfer with data return value of function 格式:(bool , result) = address.call{value:value}("传递的数据")
(bool success, ) = payable(onwner).call{value:address(this).balance}("");
require(success,"transfer tx failed");
fundersToAcount[msg.sender] = 0;
isOver = true;
}
//退款函数
function reFund() external windowClose{
require(convertEthToUsd(address(this).balance) < TARGET , "Target is reached");
require(uint256(fundersToAcount[msg.sender]) != 0,"there is not fund for you");
(bool success,) = payable(msg.sender).call{value:fundersToAcount[msg.sender]}("");
require(success,"transfer tx failed");
//把记录中提款的账户 数额清零
fundersToAcount[msg.sender] = 0;
}
//修改器
modifier windowClose(){
require(block.timestamp >= deploymentTimesTamp + lockTime,"Timeout");
_;
}
modifier onlyOwner(){
require(msg.sender == onwner, "Not owner of the contract");
_;
}
//ERC20地址调用
//当ERC20生产相应的凭证之后需要给ERC20一个方法去更新账户的代币
function setFundertoAmount (address funder,uint256 amount) external {
require(msg.sender == _addressERC20,"you do not have permisson to call this function");
fundersToAcount[funder] = amount;
}
function setERC20Address(address _NewERC20Address) public onlyOwner{
_addressERC20 = _NewERC20Address;
}
}
FundMeFromERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
//引入ERC20
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.Sol";
import { FundMe } from "./FoundMe.sol";
contract FundTokenERC20 is ERC20{
FundMe fundMe;
//构造函数 constructor() ERC20("KITE","KT"){}代表意思是调用父合约的构造函数
// _name 是token的名称,_symbol 是符号,初始化为Kite
constructor(address funderMeAddress) ERC20("KITE","KT"){
fundMe = FundMe(funderMeAddress);//将合约地址传递进去
}
//Funder参与者,根据mapping来领取相应通证(相当于冲Q币)
function mint(uint256 amountToMint) public {
require(fundMe.fundersToAcount(msg.sender) >= amountToMint,"you can not mint this many token"); // 判断账户是否足够
require(fundMe.isOver(),"the funde is not completed yet");
_mint(msg.sender, amountToMint);//暂时没有什么用
//updeta
fundMe.setFundertoAmount(msg.sender,fundMe.fundersToAcount(msg.sender) - amountToMint);
}
//Funder参与者可以transfer通证
//ERC20提供了transfer这里不实现
//使用通证只会将通证销毁(相当于冲点券,Q币就要消失了)
function claim (uint256 amountToClaim) public {
require(balanceOf(msg.sender) >= amountToClaim,"you dont have enough ERC20 tokens");
require(fundMe.isOver(),"the funde is not completed yet");
//接下来可以换成点券了
/*这里的逻辑是换成点券啥的*/
//将通证相应的销毁
_burn(msg.sender,amountToClaim);
}
}