下面,我们将一起在 Remix 上编写、部署和测试年轻人的第二个智能合约,该合约实现了V我50的功能:
- 其它人可以给合约转账;
- 可以查询每个人的转账金额;
- 合约创建人可以提取所有金额;
合约概览
合约分为两个部分,一个是合约的主体,另外一个是合约主体调用的库。
合约主体
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import {PriceConverter} from "./PriceConverter.sol";
error NotOwner();
contract V50ToMe {
using PriceConverter for uint256;
mapping(address => uint256) public addressToAmountFunded;
address[] public funders;
address public /* immutable */ i_owner;
uint256 public constant MINIMUM_CNY = 50 * 10 ** 18;
constructor() {
i_owner = msg.sender;
}
function V50() public payable {
require(msg.value.getConversionRate() >= MINIMUM_CNY, "You need to spend more ETH!");
addressToAmountFunded[msg.sender] += msg.value;
funders.push(msg.sender);
}
function getVersion() public view returns (uint256){
AggregatorV3Interface priceFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306);
return priceFeed.version();
}
function getEthPrinceInCNY(uint256 ethAmount) public view returns (uint256){
return ethAmount.getConversionRate();
}
modifier onlyOwner {
if (msg.sender != i_owner) revert NotOwner();
_;
}
function withdraw() public onlyOwner {
for (uint256 funderIndex=0; funderIndex < funders.length; funderIndex++){
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
funders = new address[](0);
(bool callSuccess, ) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
}
fallback() external payable {
V50();
}
receive() external payable {
V50();
}
}
调用的库
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
library PriceConverter {
uint256 public constant USD_TO_CNY_RATE = 7.22861955 * 10 ** 18;
function getPrice() internal view returns (uint256) {
// Sepolia ETH / USD Address
// https://docs.chain.link/data-feeds/price-feeds/addresses
AggregatorV3Interface priceFeed = AggregatorV3Interface(
0x694AA1769357215DE4FAC081bf1f309aDC325306
);
(, int256 answer, , , ) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
}
function getConversionRate(
uint256 ethAmount
) internal view returns (uint256) {
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount * USD_TO_CNY_RATE) / 1000000000000000000 / 10**18;
// the actual ETH/USD conversion rate, after adjusting the extra 0s.
return ethAmountInUsd;
}
}
代码详解
合约主体
首先,我们来看合约主体部分。这段Solidity智能合约实现了一个简单的众筹合约,使用Chainlink预言机获取以太坊的实时价格,并以此来确定用户发送的ETH是否等值于或超过50人民币(CNY)的最低限额。下面是合约的详细解释:
合约声明和导入
pragma solidity ^0.8.24;
指定了合约兼容的Solidity编译器版本。- 通过**
import
语句引入了Chainlink的AggregatorV3Interface
接口和一个名为PriceConverter
**的本地库,后者用于将ETH转换为等值的CNY金额。
状态变量和错误声明
- **
addressToAmountFunded
**映射记录了每个资助者地址及其资助的金额(以ETH计)。 - **
funders
**数组存储了所有资助者的地址。 i_owner
变量存储了合约的所有者地址。注释中提到的/* immutable */
是被注释掉的,意味着在这个版本中i_owner
不是不可变的,但实际上,将其设置为不可变(immutable
)是个好做法,因为它在构造函数中被赋值且之后不会改变。- **
MINIMUM_CNY
**常量定义了资助的最低人民币金额,这里设定为50 CNY,考虑到以太坊和人民币的换算比例,此值需根据实时汇率进行ETH转换。
构造函数
- 在部署时,构造函数会设置**
i_owner
**为部署合约的地址。
功能函数
V50
:允许用户发送ETH作为资助。通过调用**PriceConverter
库的getConversionRate
**函数(这个函数应该是将ETH转换为CNY的金额),判断发送的ETH是否满足最小50 CNY的要求。满足条件的话,用户的地址和发送的金额会被记录。getVersion
:调用Chainlink预言机以获取其版本信息,这是一个验证预言机是否成功接入的辅助函数。getEthPrinceInCNY
:接受一个以太坊金额(ethAmount
),返回其等值的人民币金额。实际上这个函数名字可能有误,应该是**getEthPriceInCNY
**。onlyOwner
:一个修饰符,用于限制某些函数只能由合约的所有者调用。withdraw
:允许合约的所有者提取合约中的所有ETH。在提取之前,它会重置所有资助者的资助金额记录,并清空**funders
**数组。
收款函数
fallback
和receive
:这两个特殊的函数允许合约接收ETH,并在收到ETH时自动调用**V50
**函数来处理资助逻辑。
主要逻辑和流程
- 用户调用**
V50
**函数发送ETH,函数内部检查发送的ETH是否达到了50 CNY的最低要求。 - 如果达到要求,用户的地址和资助金额会被记录;否则,交易将被拒绝。
- 合约所有者可以随时调用**
withdraw
**函数,提取合约中累积的所有ETH。 - 任何发送到合约地址的ETH(不通过特定函数调用)都会触发**
fallback
或receive
函数,进而调用V50
**来处理资助逻辑。
调用的库
这段Solidity代码定义了一个名为**PriceConverter
**的库,该库提供了与Chainlink预言机交互的功能,用于获取以太坊(ETH)相对于美元(USD)的实时价格,并将特定数量的ETH转换成等值的人民币(CNY)金额。这里是详细的解释:
状态变量
- **
USD_TO_CNY_RATE
是一个常量,代表美元兑换成人民币的汇率。这里假设1美元等于7.22861955人民币,并且这个比率被乘以了10**18
**以适应Solidity的无小数点数值处理方式。这意味着所有的货币值都假定为拥有18位小数,这是以太坊中的标准做法。
函数
getPrice
- 这个函数调用Chainlink预言机来获取当前ETH相对于USD的市场汇率。
- 它首先实例化一个**
AggregatorV3Interface
对象,使用的是Sepolia测试网络上的ETH/USD价格预言机地址。通过这个对象,函数调用latestRoundData
**方法来获取最新的汇率数据。 - 返回的汇率**
answer
被乘以10000000000
**(10亿),这是为了将其从Chainlink的标准格式(通常是8位小数)转换为18位小数的格式,以匹配Solidity和以太坊智能合约中常用的货币单位处理方式。
getConversionRate
- 这个函数接受一个以ETH为单位的金额(
ethAmount
),并返回该金额按当前ETH/USD汇率和USD/CNY汇率转换后的CNY价值。 - 首先,它调用**
getPrice
**函数来获取当前的ETH/USD汇率。 - 然后,它使用这个汇率和传入的ETH金额,以及**
USD_TO_CNY_RATE
**常量来计算ETH对应的CNY价值。 - 计算时,首先将ETH价格乘以ETH金额,再乘以USD到CNY的转换率,最后将结果除以**
10**18
两次。第一次除法是为了抵消ETH金额乘以ETH价格带来的额外的10**18
倍数,第二次除法是为了抵消将USD转换为CNY带来的10**18
**倍数。
这个库通过Chainlink预言机提供的ETH/USD汇率,以及一个固定的USD/CNY汇率,计算出一个给定量的ETH等于多少CNY。这个过程涉及到将链上获取的实时数据(ETH/USD汇率)与链下定义的静态数据(USD/CNY汇率)相结合,以实现跨货币的价值转换。
USD/CNY 的汇率在测试网络 Sepolia 上是没有查询的接口的,因此**USD_TO_CNY_RATE
**在这个库中是硬编码的,这意味着如果实际汇率发生变化,合约代码需要进行相应的更新和重新部署以反映这一变化。在实际应用中,可能需要定期更新这一汇率,或者通过某种机制动态获取它,以保证转换的准确性。
本文由博客一文多发平台 OpenWrite 发布!