欢迎光临
我们一直在努力

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; // 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
  • 三种转账方式: 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 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和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 => 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合约

  • 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; // 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].

赞(0)
未经允许不得转载:171主机测评 » 0301-solidity进阶-区块链-web3
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址