一. CrowdFund.sol(智能合约)
功能:定义业务逻辑和状态管理
关键内容:
-
合约状态变量(admin、paused、campaigns 等)
-
业务函数(createCampaign、contribute、withdraw、refund 等)
-
事件定义(CampaignCreated、Contributed 等)
-
错误定义(Paused、NotAdmin 等)
编译输出:
- 编译后生成 CrowdFund.json和contract-address.json
执行流程:
读取编译后的合约 Artifact
↓
获取部署账户(Signer)
↓
创建合约工厂并部署
↓
等待部署确认
↓
获取部署信息(地址、区块号、时间戳)
↓
调用 exportFrontendArtifacts() 生成配置文件
↓
输出部署结果
智能合约关键代码详解
1、function createCampaign
- owner:项目发起人(payable)
- title:标题
- description:描述
- goal:目标金额(wei)
- deadline:Unix 秒
- raised:当前项目已筹(wei)
- claimed:是否已提取
function createCampaign(
string memory title,
string memory description,
uint256 goal,
uint256 deadline
) external whenNotPaused returns (uint256 campaignId) {
if (goal == 0) revert InvalidGoal();
if (deadline <= block.timestamp) revert InvalidDeadline();
campaigns.push(
Campaign({
owner: payable(msg.sender),
title: title,
description: description,
goal: goal,
deadline: deadline,
raised: 0,
claimed: false
})
);
campaignId = campaigns.length – 1;
emit CampaignCreated(campaignId, msg.sender, title, goal, deadline);
}
external: 可被任何外部账户调用。
whenNotPaused :确保全局未暂停,否则直接 Paused()。
2、contribute、withdraw、refund 捐款、提款、退款
function contribute(uint256 campaignId) external payable whenNotPaused {
Campaign storage c = _campaign(campaignId);
if (block.timestamp >= c.deadline) revert DeadlinePassed();
require(msg.value > 0, "No ETH sent");
c.raised += msg.value;
totalRaised += msg.value;
contributions[campaignId][msg.sender] += msg.value;
emit Contributed(campaignId, msg.sender, msg.value);
}
function withdraw(uint256 campaignId) external nonReentrant whenNotPaused {//提款
Campaign storage c = _campaign(campaignId);
if (msg.sender != c.owner) revert NotOwner();
if (block.timestamp < c.deadline) revert DeadlineNotReached();
if (c.raised < c.goal) revert GoalNotReached();
if (c.claimed) revert AlreadyClaimed();
c.claimed = true;
uint256 amount = c.raised;
(bool ok, ) = c.owner.call{value: amount}("");
require(ok, "Transfer failed");
emit Withdrawn(campaignId, c.owner, amount);
}
function refund(uint256 campaignId) external nonReentrant whenNotPaused {//退款
Campaign storage c = _campaign(campaignId);
if (block.timestamp < c.deadline) revert DeadlineNotReached();
if (c.raised >= c.goal) revert GoalNotReached();
uint256 amount = contributions[campaignId][msg.sender];
if (amount == 0) revert NothingToRefund();
contributions[campaignId][msg.sender] = 0;
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "Refund failed");
emit Refunded(campaignId, msg.sender, amount);
}
-contribute(uint256 campaignId) external payable whenNotPaused
-
项目必须存在
-
block.timestamp < c.deadline,否则 DeadlinePassed()
-
msg.value > 0,否则 require(“No ETH sent”)
-
状态更新:c.raised += msg.value、totalRaised += msg.value、contributions[campaignId][msg.sender] += msg.value
关键点:允许超额筹集;totalRaised 为平台累计指标,不与退款联动减少。前端提示用户退款规则依赖目标达成与截止时间状态。
-withdraw(uint256 campaignId) external nonReentrant whenNotPaused
- 用途:发起人在达成目标且到期后提取资金。
- 项目存在,调用者 msg.sender == c.owner,否则 NotOwner()
- block.timestamp >= c.deadline,否则 DeadlineNotReached()
- c.raised >= c.goal,否则 GoalNotReached()
-refund(uint256 campaignId) external nonReentrant whenNotPaused
- 用途:未达成目标且到期后,出资人退款。
- 项目存在
- block.timestamp >= c.deadline,否则 DeadlineNotReached()
- c.raised < c.goal,否则 GoalNotReached()
- 个人出资额 amount > 0,否则 NothingToRefund()
- 状态更新:先将 contributions[campaignId][msg.sender] = 0
注意点:为什么退款先清零再转账
-
防止重入攻击:如果先转账再清零,恶意合约可能在 call 时回调 refund
-
第二次调用时,contributions 仍为非零值,可能导致重复退款
deploy.js详解
async function main()
入口函数,负责:获取部署者、部署合约、收集部署元数据、导出前端所需文件、打印提示信息。
const [deployer] = await ethers.getSigners();
console.log("Deploying CrowdFund with account:", deployer.address);
- 含义:从当前网络配置(hardhat.config.js)读取可用账户,拿accounts第一个元素作为部署者。
- 作用:打印部署者地址,便于溯源和审计。
const CrowdFund = await ethers.getContractFactory("CrowdFund");
const contract = await CrowdFund.deploy();
await contract.waitForDeployment();
- 含义:
- getContractFactory("CrowdFund") 从编译产物(artifacts)加载 CrowdFund 的工厂实例。
- deploy() 发送部署交易;waitForDeployment() 阻塞直到部署合约在链上可用(已被打包确认)。
- 边界:若未先 npx hardhat compile,工厂加载会失败。
const address = await contract.getAddress();
const tx = await contract.deploymentTransaction().wait();
const deploymentBlock = tx.blockNumber;
const deploymentTimestamp = (await ethers.provider.getBlock(tx.blockNumber)).timestamp;
- 含义:
- getAddress() 获取合约部署地址。
- deploymentTransaction() 返回部署时的交易对象,wait() 得到收据(包括区块号)。
- 从区块读取 timestamp 记录部署时间。
