前言
在 2026 年初的 Web3 领域,DePIN(去中心化物理基础设施网络) 已成为最具增长潜力的赛道之一。作为其中的领军项目,由 Andrena 团队开发的 DAWN (Decentralized Autonomous Wireless Network) 凭借其 “去中心化宽带” 的愿景和超过 4850 万美元的融资背景(Polychain 领投),吸引了全球开发者的关注。本文将从项目核心逻辑出发,只做项目核心运行逻辑拆解,不做项目投资建议,演示如何使用 Solidity 0.8.24 与 OpenZeppelin V5 实现其激励机制,并分享在使用 Viem + Hardhat 进行自动化测试时的避坑指南。
一、 DAWN 的业务本质:带宽即服务
与 Grass 等数据爬虫类 DePIN 不同,DAWN 的目标是成为去中心化 ISP(互联网服务供应商) 。
- 核心逻辑:用户通过验证节点(Validator)共享闲置带宽,系统通过“带宽证明”(Proof of Bandwidth)核实贡献。
- 激励模型:后端服务器定期统计用户贡献的累计总额(Total Earned),并生成 EIP-712 签名。用户凭此签名在链上领取(Claim)代币。
二、 核心机制实现:DawnNetworkToken 合约
基于安全性与 Gas 效率的考虑,DAWN 采用了增量领取机制。合约不记录单次奖励,而是记录用户的“生涯总领金额”,通过差额发放代币。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
/**
* @title DawnNetworkToken
* @dev 模拟 DAWN DePIN 激励合约
*/
contract DawnNetworkToken is ERC20, Ownable, EIP712 {
using ECDSA for bytes32;
address public validator; // 官方验证者地址
mapping(address => uint256) public totalClaimed;
bytes32 private constant CLAIM_TYPEHASH = keccak256(
"Claim(address user,uint256 totalAmount)"
);
constructor(address _validator)
ERC20("DAWN Network", "DAWN")
Ownable(msg.sender)
EIP712("DAWN_Network", "1")
{
validator = _validator;
}
// 设置验证节点地址
function setValidator(address _validator) external onlyOwner {
validator = _validator;
}
/**
* @notice 领取带宽奖励
* @param totalAmount 验证者核实的累计奖励总额
* @param signature 验证者的加密签名
*/
function claimRewards(uint256 totalAmount, bytes calldata signature) external {
uint256 claimable = totalAmount - totalClaimed[msg.sender];
require(claimable > 0, "No new rewards");
// 验证 EIP-712 签名
bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, msg.sender, totalAmount));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
require(signer == validator, "Invalid validator signature");
totalClaimed[msg.sender] = totalAmount;
_mint(msg.sender, claimable);
}
}
三、 工程化测试实践:Viem + Node:test 避坑指南
测试用例:DAWN DePIN 激励合约深度测试
- 成功逻辑:验证有效签名并更新领取额度
- 重放攻击测试:禁止使用同一签名重复领取
- 安全边界:修改金额后原签名应失效
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import { network } from "hardhat";
import { parseEther, type Address, getAddress } from "viem";
describe("DAWN DePIN 激励合约深度测试", function () {
let token: any, dawnDistributor: any;
let admin: any, user: any, validator: any;
let vClient: any, pClient: any;
// EIP-712 类型定义常量化
const DOMAIN_NAME = "DAWN_Network";
const VERSION = "1";
beforeEach(async function () {
const { viem } = await (network as any).connect();
vClient = viem;
[admin, user, validator] = await vClient.getWalletClients();
pClient = await vClient.getPublicClient();
// 部署 DAWN 代币与分配器(此处假设为统一合约)
dawnDistributor = await vClient.deployContract("DawnNetworkToken", [
validator.account.address
]);
});
it("成功逻辑:验证有效签名并更新领取额度", async function () {
const totalAmount = parseEther("1000"); // 验证者核实的用户生涯总收益
const chainId = await pClient.getChainId();
const domain = {
name: DOMAIN_NAME,
version: VERSION,
chainId,
verifyingContract: dawnDistributor.address as Address,
} as const;
const types = {
Claim: [
{ name: 'user', type: 'address' },
{ name: 'totalAmount', type: 'uint256' },
],
} as const;
// 验证者签名
const signature = await validator.signTypedData({
domain,
types,
primaryType: 'Claim',
message: {
user: user.account.address,
totalAmount: totalAmount,
},
});
// 执行领取
const hash = await dawnDistributor.write.claimRewards([totalAmount, signature], {
account: user.account
});
await pClient.waitForTransactionReceipt({ hash });
// 验证余额
const balance = await dawnDistributor.read.balanceOf([user.account.address]);
assert.strictEqual(balance, totalAmount, "用户应获得全部初始奖励");
// 验证累计领取记录
const claimed = await dawnDistributor.read.totalClaimed([user.account.address]);
assert.strictEqual(claimed, totalAmount, "合约应记录已领取总额");
});
it("重放攻击测试:禁止使用同一签名重复领取", async function () {
const totalAmount = parseEther("100");
const chainId = await pClient.getChainId();
const domain = { name: "DAWN_Network", version: "1", chainId, verifyingContract: dawnDistributor.address };
const types = { Claim: [{ name: 'user', type: 'address' }, { name: 'totalAmount', type: 'uint256' }] };
const signature = await validator.signTypedData({
domain,
types,
primaryType: 'Claim',
message: { user: user.account.address, totalAmount }
});
// 1. 第一次领取:应当成功
const hash = await dawnDistributor.write.claimRewards([totalAmount, signature], { account: user.account });
await pClient.waitForTransactionReceipt({ hash });
// 2. 第二次领取:使用 simulateContract 捕获具体的合约错误
await assert.rejects(
async () => {
// simulateContract 会执行 eth_call,比直接 write 更容易拿到 revert reason
await pClient.simulateContract({
address: dawnDistributor.address,
abi: dawnDistributor.abi,
functionName: 'claimRewards',
args: [totalAmount, signature],
account: user.account,
});
},
(err: any) => {
// 打印错误以防万一
// console.log("Caught Error:", err.message);
// 检查错误信息。在 simulate 模式下,viem 通常能抓到 "No new rewards"
// 或者检查是否包含 "reverted" 关键字
return err.message.includes("No new rewards") || err.message.includes("revert");
},
"应当因为没有新奖励额度而回滚"
);
});
it("安全边界:修改金额后原签名应失效", async function () {
const totalAmount = parseEther("100");
const tamperedAmount = parseEther("200"); // 尝试篡改金额
const chainId = await pClient.getChainId();
const signature = await validator.signTypedData({
domain: { name: DOMAIN_NAME, version: VERSION, chainId, verifyingContract: dawnDistributor.address },
types: { Claim: [{ name: 'user', type: 'address' }, { name: 'totalAmount', type: 'uint256' }] },
primaryType: 'Claim',
message: { user: user.account.address, totalAmount }
});
await assert.rejects(
async () => {
// 提交被篡改的金额 tamperedAmount,但使用 totalAmount 的签名
await dawnDistributor.write.claimRewards([tamperedAmount, signature], { account: user.account });
},
/Invalid validator signature/,
"签名恢复的地址应与验证者不符"
);
});
});
四、部署脚本
// scripts/deploy.js
import { network, artifacts } from "hardhat";
async function main() {
// 连接网络
const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端
const [deployer] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address;
console.log("部署者的地址:", deployerAddress);
// 加载合约
const DawnNetworkArtifact = await artifacts.readArtifact("DawnNetworkToken");
// 部署(构造函数参数:recipient, initialOwner)
const DawnNetworkHash = await deployer.deployContract({
abi: DawnNetworkArtifact.abi,//获取abi
bytecode: DawnNetworkArtifact.bytecode,//硬编码
args: [deployerAddress],//process.env.RECIPIENT, process.env.OWNER
});
// 等待确认并打印地址
const DawnNetworkReceipt = await publicClient.waitForTransactionReceipt({ hash:DawnNetworkHash });
console.log("合约地址:", DawnNetworkReceipt.contractAddress);
}
main().catch(console.error);
五、DAWN 与 Grass 的横向对比
| 维度 | Grass (小草) | DAWN (黎明) |
|---|---|---|
| 底层链 | Solana | Solana |
| 激励方式 | 带宽挖矿积分 (早期) | 带宽挖矿积分 (当前) |
| 技术核心 | 数据采集与 AI 模型对齐 | 去中心化无线网状网络 (Mesh) |
| 合约挑战 | 高频、小额奖励发放 | 大规模节点的身份校验与带宽证明 |
六、DAWN 项目的风险提示
- 隐私与安全风险:长期运行浏览器插件可能导致个人上网行为被追踪,或因插件漏洞造成数据泄露。
- 违约封号风险:家用宽带协议通常禁止“带宽转售”,运行该项目可能触发运营商(如电信、联通)的监测,导致宽带被限速或停机。
- 收益不确定性:项目融资虽高,但发币时间(TGE)未知。如果后期参与人数爆棚或币价破发,挂机累积的积分可能并不值钱。
- 落地难度大:DAWN 最终需要用户安装物理基站(硬件),这比纯软件挂机难得多。如果硬件普及失败,项目愿景可能无法实现。
一句话建议:用闲置设备“零成本”参与,不要为了它专门升级宽带或购买贵重硬件。
七、 结语
DAWN 不仅仅是一个挂机项目,它是对传统中心化电信运营商(ISP)的有力挑战。对于开发者而言,理解其底层的 EIP-712 签名机制 和 增量领取逻辑,是构建健壮 DePIN 应用的基础。随着 2026 年主网上线的临近,这类结合了硬件贡献与链上激励的模型将成为 Web3 落地的重要标杆。