​ 一. CrowdFund.sol(智能合约)

功能:定义业务逻辑和状态管理

关键内容:

  • 合约状态变量(admin、paused、campaigns 等)

  • 业务函数(createCampaign、contribute、withdraw、refund 等)

  • 事件定义(CampaignCreated、Contributed 等)

  • 错误定义(Paused、NotAdmin 等)

编译输出:

  • 编译后生成 CrowdFund.json和contract-address.json

执行流程:

  1. 读取编译后的合约 Artifact

    ↓

  2. 获取部署账户(Signer)

    ↓

  3. 创建合约工厂并部署

    ↓

  4. 等待部署确认

    ↓

  5. 获取部署信息(地址、区块号、时间戳)

    ↓

  6. 调用 exportFrontendArtifacts() 生成配置文件

    ↓

  7. 输出部署结果

智能合约关键代码详解

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 记录部署时间。

​

更多推荐