Solidity学习笔记

111 阅读14分钟

3.0 ERC20、NFT

一个收款合约有点类似银行,将付款用户的地址实现类似姓名的唯一标识,这个银行大家都可以存钱进来(装钱的袋子类似)

3.0.1 通过函数发送ETH

换算关系如下:

1ETH=103Finney=109Gwei=109109Wei1ETH = 10^3Finney = 10^9Gwei = 10^9*10^9Wei

收款函数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);
    }
}