二、区块链后端CrowdFund.sol与deploy.js
一. 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记录部署时间。
更多推荐


所有评论(0)