0301-solidity进阶-区块链-web3
文章目录
- 序言
- 通过函数发送ETH
- 通过预言机设定最小额度
- 通过函数提取合约中的ETH
- 函数修饰符和时间锁
- Token和Coin的区别
- 创建一个Token合约
- 继承ERC-20合约
- 部署和验证合约
- 关于
序言
通过一个众筹项目来学习solidity进阶知识。
通过函数发送ETH
- payable关键字
- 以太坊账户:EOA和合约账户
- Wei,GWei,Finney和ether
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;// 1.创建一个收款函数
// 2.记录投资人并且查看
// 3.在锁定期内,达到目标值,生产商可以提款
// 4.在锁定期内,没有达到目标值,投资人可以在锁定期以后退款contract FundMe {mapping (address => uint256) public fundersToAount;function fund() external payable {fundersToAount[msg.sender] = msg.value;}}
指fund函数收款0.002ETH,如下图所示:
通过预言机设定最小额度
限制最小额度,更直观的(容易理解)稳定的是我们自己使用的货币,比如USD或RMB等。那我们需要了解ETH和USD兑换比例,这里需要引入预言机。
- 预言机(oracle)定义
- Chainlink技术文档
- Chainlink喂价文档
- Chainlink喂价合约地址列表
-Solidity的数据类型没有double或者floating(小数),如果想要表示带有小数的以太币,把wei当成最小单位,它是ether的10e-18,也就是0.000000000000000001。
- 以太币面额转换器
- 以太币面额
- Solidity 中如何使用浮点数
代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";// 1.创建一个收款函数
// 2.记录投资人并且查看
// 3.在锁定期内,达到目标值,生产商可以提款
// 4.在锁定期内,没有达到目标值,投资人可以在锁定期以后退款contract FundMe {mapping (address => uint256) public fundersToAount;uint256 MINIMUM_VALUE = 4 * 10 ** 18; // USDAggregatorV3Interface internal dataFeed;constructor() {// sepolia testnetdataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);}function fund() external payable {// 控制台打印等价usd值uint256 usdValue = convertEthToUsd(msg.value);require(usdValue >= MINIMUM_VALUE, "cannot be less than the minimum limit");fundersToAount[msg.sender] += msg.value;}/*** Returns the latest answer.*/function getChainlinkDataFeedLatestAnswer() public view returns (int) {// prettier-ignore(/* uint80 roundId */,int256 answer,/*uint256 startedAt*/,/*uint256 updatedAt*/,/*uint80 answeredInRound*/) = dataFeed.latestRoundData();return answer;}function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());uint256 ethAmountInUsd = (ethPrice * ethAmount) / (10 ** 8);return ethAmountInUsd;}
}
当金额小于最小额度时,如下图所示:
当金额大于等于最小额度,如下图所示:
通过函数提取合约中的ETH
- 如何发送和接受ETH
- 三种转账方式: transfer, send, call
提取ETH和退款代码如下所示:
// 达到目标值,生产商可以提款function getFund() external {require(owner == msg.sender, "this function can only be called by owner");// 判断筹款金额大于等于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) >= TARGET, "Target is not reached.");// payable(msg.sender).transfer(balance);// bool success = payable(msg.sender).send(balance);bool success;(success, ) = payable(msg.sender).call{value: balance}("");require(success, "transfer failed");// 余额清空fundersToAount[msg.sender] = 0;}// 没有达到目标值,投资人可以在锁定期以后退款function refund() external {// 判断筹款金额小于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) < TARGET, "Target is reached.");uint256 amount = fundersToAount[msg.sender];require(amount != 0, "there is not fund for you");bool success;(success, ) = payable(msg.sender).call{value: amount}("");require(success, "transfer failed");fundersToAount[msg.sender] = 0;}
函数修饰符和时间锁
- 函数修饰符是什么
- 怎样开发智能合约中的时间锁
- Uinx时间戳
时间戳和锁定时间相关逻辑:
- 定时合约部署时间和锁定时间
- 初始化合约部署时间和锁定时间
- 在fund中判断合约未到期才可以捐款
- 在getFund中判断合约到期才可以提款
- reFund中判断合约到期才可以退款
代码如下所示:
// 合约部署时间uint256 deplomentTimestamp;// 合约锁定时间uint256 lockTime;constructor(uint256 _lockTime) {// sepolia testnetdataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);owner = msg.sender;deplomentTimestamp = block.timestamp;lockTime = _lockTime;}function fund() external payable {// 判断锁定时间require( (block.timestamp < deplomentTimestamp + lockTime), "the lock time is over");// 控制台打印等价usd值uint256 usdValue = convertEthToUsd(msg.value);require(usdValue >= MINIMUM_VALUE, "cannot be less than the minimum limit");fundersToAount[msg.sender] += msg.value;}// 达到目标值,生产商可以提款function getFund() external {// 判断锁定时间require( (block.timestamp > deplomentTimestamp + lockTime), "the lock time is not over");require(owner == msg.sender, "this function can only be called by owner");// 判断筹款金额大于等于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) >= TARGET, "Target is not reached.");// payable(msg.sender).transfer(balance);// bool success = payable(msg.sender).send(balance);bool success;(success, ) = payable(msg.sender).call{value: balance}("");require(success, "transfer failed");// 余额清空fundersToAount[msg.sender] = 0;}// 没有达到目标值,投资人可以在锁定期以后退款function refund() external {// 判断锁定时间require( (block.timestamp > deplomentTimestamp + lockTime), "the lock time is not over");// 判断筹款金额小于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) < TARGET, "Target is reached.");uint256 amount = fundersToAount[msg.sender];require(amount != 0, "there is not fund for you");bool success;(success, ) = payable(msg.sender).call{value: amount}("");require(success, "transfer failed");fundersToAount[msg.sender] = 0;}
通过上述代码,我们注意到判定锁定时间和判断是否合约所有者多处用到,为实现代码复用,这里引入modified(修改器)。代码如下所示:
// 达到目标值,生产商可以提款function getFund() external windowClosed onlyOwner{ // 判断筹款金额大于等于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) >= TARGET, "Target is not reached.");// payable(msg.sender).transfer(balance);// bool success = payable(msg.sender).send(balance);bool success;(success, ) = payable(msg.sender).call{value: balance}("");require(success, "transfer failed");// 余额清空fundersToAount[msg.sender] = 0;}// 没有达到目标值,投资人可以在锁定期以后退款function refund() external windowClosed{// 判断筹款金额小于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) < TARGET, "Target is reached.");uint256 amount = fundersToAount[msg.sender];require(amount != 0, "there is not fund for you");bool success;(success, ) = payable(msg.sender).call{value: amount}("");require(success, "transfer failed");fundersToAount[msg.sender] = 0;}modifier windowClosed() {// 判断锁定时间require( (block.timestamp > deplomentTimestamp + lockTime), "the lock time is not over");_;}modifier onlyOwner() {require(owner == msg.sender, "this function can only be called by owner");_;}
Token和Coin的区别
- Token和Coin的区别
创建一个Token合约
- Token介绍
- Solidity中的继承
FundToken代码如下所示:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;contract FundToken {// 1. 通证的名称string public tokenName;// 2. 通证的简称string public tokenSymbol;// 3. 通证的发行数量uint256 public totalSupply;// 4. owner地址address public owner;// 5. balance address => uint256mapping(address => uint256) public balances;constructor(string memory _tokenName, string memory _tokenSymbol) {tokenName = _tokenName;tokenSymbol = _tokenSymbol;owner = msg.sender;}// mint: 获取通证function mint(uint256 _amount) public {balances[msg.sender] += _amount;totalSupply += _amount;}// transfer:transfer 通证function transfer(address payee, uint256 amount) public {// 验证数量require(balances[msg.sender] >= amount, "You don't have enough balance to transfer");balances[msg.sender] -= amount;balances[payee] += amount;}// balanceOf:查看某一个地址的通证数量function balanceOf(address _address) public view returns (uint256) {return balances[_address];}
}
继承ERC-20合约
- ERC-20标准合约
- ERC-20标准合约代码
- virtual和override
FundMe.sol 代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";// 1.创建一个收款函数
// 2.记录投资人并且查看
// 3.在锁定期内,达到目标值,生产商可以提款
// 4.在锁定期内,没有达到目标值,投资人可以在锁定期以后退款contract FundMe {// 筹款人 => 筹款金额mapping (address => uint256) public fundersToAount;// 最小筹款金额uint256 constant MINIMUM_VALUE = 4 * 10 ** 18; // USDAggregatorV3Interface internal dataFeed;// 目标金额uint256 constant TARGET = 10 * 10 ** 18;// 合约所有者address public owner;// 合约部署时间uint256 deplomentTimestamp;// 合约锁定时间uint256 lockTime;address erc20Addr;bool public getFundSuccess = false;constructor(uint256 _lockTime) {// sepolia testnetdataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);owner = msg.sender;deplomentTimestamp = block.timestamp;lockTime = _lockTime;}function fund() external payable {// 判断锁定时间require( (block.timestamp < deplomentTimestamp + lockTime), "the lock time is over");// 控制台打印等价usd值uint256 usdValue = convertEthToUsd(msg.value);require(usdValue >= MINIMUM_VALUE, "cannot be less than the minimum limit");fundersToAount[msg.sender] += msg.value;}/*** Returns the latest answer.*/function getChainlinkDataFeedLatestAnswer() public view returns (int) {// prettier-ignore(/* uint80 roundId */,int256 answer,/*uint256 startedAt*/,/*uint256 updatedAt*/,/*uint80 answeredInRound*/) = dataFeed.latestRoundData();return answer;}function convertEthToUsd(uint256 ethAmount) internal view returns (uint256) {uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer());uint256 ethAmountInUsd = (ethPrice * ethAmount) / (10 ** 8);return ethAmountInUsd;}// 判断合约所有者可以提取function transferOwnship(address newOwner) public {require(owner == msg.sender, "this function can only be called by owner");owner = newOwner;}// 达到目标值,生产商可以提款function getFund() external windowClosed onlyOwner{ // 判断筹款金额大于等于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) >= TARGET, "Target is not reached.");// payable(msg.sender).transfer(balance);// bool success = payable(msg.sender).send(balance);bool success;(success, ) = payable(msg.sender).call{value: balance}("");require(success, "transfer failed");// 余额清空fundersToAount[msg.sender] = 0;getFundSuccess = true;}// 没有达到目标值,投资人可以在锁定期以后退款function refund() external windowClosed{// 判断筹款金额小于目标金额uint256 balance = address(this).balance;require(convertEthToUsd(balance) < TARGET, "Target is reached.");uint256 amount = fundersToAount[msg.sender];require(amount != 0, "there is not fund for you");bool success;(success, ) = payable(msg.sender).call{value: amount}("");require(success, "transfer failed");fundersToAount[msg.sender] = 0;}function setFunderToAmount(address funder, uint256 amountToUpdate) external {require(msg.sender == erc20Addr, "You don't have permission to call this funciton.");fundersToAount[funder] = amountToUpdate;}function setErc20Addr(address _erc20Addr) public onlyOwner {erc20Addr = _erc20Addr;}modifier windowClosed() {// 判断锁定时间require( (block.timestamp > deplomentTimestamp + lockTime), "the lock time is not over");_;}modifier onlyOwner() {require(owner == msg.sender, "this function can only be called by owner");_;}
}
FundTokenERC20.sol代码如下所示:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;// FundMe
// 1. 让FundMe的参与者,基于mapping领取相应数量的通证
// 2. 让FundMe的参与者,transfer通证
// 3. 在使用完成以后,需要burn通证import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {FundMe} from "./FundMe.sol";contract FundTokenERC20 is ERC20 {FundMe fundMe;constructor(address fundMeAddr) ERC20("FundTokenERC20", "FT") {fundMe = FundMe(fundMeAddr);}function mint(uint256 amountToMint) public {require(fundMe.fundersToAount(msg.sender) >= amountToMint, "You cannot mint this many tokens.");require(fundMe.getFundSuccess(), "The fundme is not complete yet.");_mint(msg.sender, amountToMint);fundMe.setFunderToAmount(msg.sender, fundMe.fundersToAount(msg.sender)-amountToMint);}function claim(uint256 amountToClaim) public {require(balanceOf(msg.sender) >= amountToClaim, "You dont have enough ERC20 tokens.");require(fundMe.getFundSuccess(), "The fundme is not complete yet.");_burn(msg.sender, amountToClaim);}}
部署和验证合约
- 注册从区块链浏览器(Etherscan)账户,并且获取API key
- 怎样通过 Etherscan 验证智能合约
关于
❓QQ:806797785
⭐️仓库地址:https://gitee.com/gaogzhen
⭐️仓库地址:https://github.com/gaogzhen
[1]Web3教程:ERC20,NFT,Hardhat,CCIP跨链[CP/OL].
[2]remix[CP/OL].