文章目录
序言
通过一个众筹项目来学习solidity进阶知识。
通过函数发送ETH
// 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技术文档
-Solidity的数据类型没有double或者floating(小数),如果想要表示带有小数的以太币,把wei当成最小单位,它是ether的10e-18,也就是0.000000000000000001。
代码如下:
// 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; // USD
AggregatorV3Interface internal dataFeed;
constructor() {
// sepolia testnet
dataFeed = 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和退款代码如下所示:
// 达到目标值,生产商可以提款
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;
}
函数修饰符和时间锁
时间戳和锁定时间相关逻辑:
- 定时合约部署时间和锁定时间
- 初始化合约部署时间和锁定时间
- 在fund中判断合约未到期才可以捐款
- 在getFund中判断合约到期才可以提款
- reFund中判断合约到期才可以退款
代码如下所示:
// 合约部署时间
uint256 deplomentTimestamp;
// 合约锁定时间
uint256 lockTime;
constructor(uint256 _lockTime) {
// sepolia testnet
dataFeed = 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合约
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 => uint256
mapping(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合约
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; // USD
AggregatorV3Interface 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 testnet
dataFeed = 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: MIT
pragma 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].