深度解析 DAWN:Solana 生态 DePIN 新星的机制实现与合约测试实践

  • 木西
  • 发布于 1小时前
  • 阅读 27

前言在2026年初的Web3领域,DePIN(去中心化物理基础设施网络)已成为最具增长潜力的赛道之一。作为其中的领军项目,由Andrena团队开发的DAWN(DecentralizedAutonomousWirelessNetwork)凭借其“去中心化宽带”的愿景和

前言

在 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 项目的风险提示
1.  **隐私与安全风险**:长期运行浏览器插件可能导致个人上网行为被追踪,或因插件漏洞造成数据泄露。
1.  **违约封号风险**:家用宽带协议通常禁止“带宽转售”,运行该项目可能触发运营商(如电信、联通)的监测,导致宽带被限速或停机。
1.  **收益不确定性**:项目融资虽高,但发币时间(TGE)未知。如果后期参与人数爆棚或币价破发,挂机累积的积分可能并不值钱。
1.  **落地难度大**:DAWN 最终需要用户安装物理基站(硬件),这比纯软件挂机难得多。如果硬件普及失败,项目愿景可能无法实现。

**一句话建议**:用**闲置设备**“零成本”参与,不要为了它专门升级宽带或购买贵重硬件。
# 七、 结语

DAWN 不仅仅是一个挂机项目,它是对传统中心化电信运营商(ISP)的有力挑战。对于开发者而言,理解其底层的 **EIP-712 签名机制** 和 **增量领取逻辑**,是构建健壮 DePIN 应用的基础。随着 2026 年主网上线的临近,这类结合了硬件贡献与链上激励的模型将成为 Web3 落地的重要标杆。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
0x5D5C...2dD7
江湖只有他的大名,没有他的介绍。