前言本文将系统梳理WrappedToken(包装代币)的核心理论与实操流程。主要内容涵盖:核心概念:深入浅出地解释包装代币是什么,以及它如何解决原生资产与ERC-20标准之间的“语言不通”痛点。应用场景:探讨包装代币在各类DeFi协议(如DEX、借贷平台)中的关
本文将系统梳理 Wrapped Token(包装代币) 的核心理论与实操流程。
主要内容涵盖:
- 核心概念: 深入浅出地解释包装代币是什么,以及它如何解决原生资产与 ERC-20 标准之间的“语言不通”痛点。
- 应用场景: 探讨包装代币在各类 DeFi 协议(如 DEX、借贷平台)中的关键作用。
- 风险与注意事宜: 分析使用和实现包装代币时需要警惕的潜在风险和安全细节。
- 代码实践: 提供完整的 WETH(Wrapped ETH)智能合约实现(基于 Solidity 0.8.24 和 OpenZeppelin V5),并配套详尽的开发、测试(使用 viem 和 Hardhat)、以及潜在的部署流程代码。
通过理论结合 WETH 的直观代码运行逻辑,旨在帮助读者全面掌握包装代币的设计原理与工程实践。
一、定义与核心机制
包装代币是与原生资产 1:1 挂钩的映射资产,通过锁定原生资产(托管或智能合约锁仓)在目标链发行对应代币,赎回时销毁包装代币并释放原生资产。
核心特征
常见示例
DeFi 生态核心应用
NFT 与 Web3 生态拓展
跨链资产转移与价值传递
中心化金融(CeFi)与交易场景
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
/**
@notice 实现 ETH 与 ERC20 的 1:1 兑换,并支持 EIP-2612 Permit 离线授权。 */ contract WrappedEther is ERC20, ERC20Permit { // 兼容传统 WETH 的事件 event Deposit(address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad);
/**
/**
/**
逻辑:销毁调用者的 WETH 代币,并将对应 ETH 发送回调用者。 */ function withdraw(uint256 wad) public { require(balanceOf(msg.sender) >= wad, "WETH: Burn amount exceeds balance"); _burn(msg.sender, wad);
(bool success, ) = msg.sender.call{value: wad}(""); require(success, "WETH: ETH transfer failed");
emit Withdrawal(msg.sender, wad); }
/**
/**
/**
### 测试脚本
**测试说明**:
* **WETH代币的初始化参数验证**;
* **存款逻辑测试: 原生 ETH -> WETH**;
* **提款逻辑测试: WETH -> 原生 ETH**;
* **Permit 离线签名授权测试**
import assert from "node:assert/strict"; import { describe, it, beforeEach } from "node:test"; import hre from "hardhat"; import { parseEther } from "viem";
describe("WrappedEther Permit Test", async function () {
// 1. 在 describe 顶级通过异步连接获取 viem 和 helpers
// 这是 Hardhat v3 的标准写法
const { viem, networkHelpers } = await hre.network.connect();
let owner: any, otherAccount: any;
let publicClient: any;
// let testClient: any;
let WrappedEther: any;
let TokenLocker: any;
let deployerAddress: 0x${string};
// const lockTime = 3600n;
beforeEach(async function () {
// 使用从 connect() 拿到的 viem 实例
publicClient = await viem.getPublicClient();
// testClient = await viem.getTestClient();
[owner, otherAccount] = await viem.getWalletClients();
deployerAddress = owner.account.address;
// 部署合约
WrappedEther = await viem.deployContract("WrappedEther", []);
console.log("WrappedEther地址:",WrappedEther.address);
});
// 测试用例1:初始化参数验证
it("初始化参数验证", async function () {
console.log(await WrappedEther.read.name());
console.log(await WrappedEther.read.symbol());
console.log(await WrappedEther.read.decimals());
console.log(await WrappedEther.read.totalSupply());
console.log(await WrappedEther.read.balanceOf([deployerAddress]));
});
it("存款逻辑测试: 原生 ETH -> WETH", async function () {
const depositAmount = parseEther("2.5");
// 记录操作前的 ETH 余额
const ethBalanceBefore = await publicClient.getBalance({ address: deployerAddress });
console.log("操作前 ETH 余额:", ethBalanceBefore);
// 执行存款
const hash = await WrappedEther.write.deposit({
value: depositAmount,
account: owner.account
});
console.log("存款交易哈希:", hash);
// 验证 WETH 余额
const wethBalance = await WrappedEther.read.balanceOf([deployerAddress]);
console.log("操作后 WETH 余额:", wethBalance);
assert.equal(wethBalance, depositAmount, "WETH 余额应等于存款金额");
// 验证合约内 ETH 锁定数量
const contractEthBalance = await publicClient.getBalance({ address: WrappedEther.address });
console.log("合约内 ETH 余额:", contractEthBalance);
assert.equal(contractEthBalance, depositAmount, "合约持有的 ETH 应等于总存款数量");
});
it("提款逻辑测试: WETH -> 原生 ETH", async function () {
const amount = parseEther("1.0");
// 记录操作前的 ETH 余额
const ethBalanceBefore = await publicClient.getBalance({ address: deployerAddress });
console.log("操作前 ETH 余额:", ethBalanceBefore);
// 1. 先存入 ETH
await WrappedEther.write.deposit({ value: amount, account: owner.account });
// 2. 执行提款
await WrappedEther.write.withdraw([amount], { account: owner.account });
// 3. 验证余额销毁
const wethBalance = await WrappedEther.read.balanceOf([deployerAddress]);
console.log("操作后 WETH 余额:", wethBalance);
assert.equal(wethBalance, 0n, "提款后 WETH 余额应为 0");
// 4. 验证合约 ETH 减少
const contractEthBalance = await publicClient.getBalance({ address: WrappedEther.address });
console.log("操作后合约内 ETH 余额:", contractEthBalance);
assert.equal(contractEthBalance, 0n, "提款后合约内不应留存 ETH");
});
it("Permit 离线签名授权测试", async function () {
const value = parseEther("1");
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1小时后过期
// 1. 获取当前 Nonce 和 ChainId
const nonce = await WrappedEther.read.nonces([owner.account.address]);
const chainId = BigInt(await publicClient.getChainId());
// 2. 构造 EIP-712 签名数据 (Viem 核心逻辑)
const domain = {
name: "Wrapped Ether",
version: "1",
chainId: chainId,
verifyingContract: WrappedEther.address,
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};
// 3. 生成签名
const signature = await owner.signTypedData({
account: owner.account,
domain,
types,
primaryType: "Permit",
message: {
owner: owner.account.address,
spender: otherAccount.account.address,
value: value,
nonce: nonce,
deadline: deadline,
},
});
// 4. 拆解签名 (r, s, v)
const { r, s, v } = parseSignature(signature);
// 5. 提交 permit 交易 (由 otherAccount 提交,实现无 gas 授权)
await WrappedEther.write.permit([
owner.account.address,
otherAccount.account.address,
value,
deadline,
Number(v),
r,
s,
]);
// 6. 验证 allowance
const allowance = await WrappedEther.read.allowance([owner.account.address, otherAccount.account.address]);
console.log(allowance,value);
assert.equal(allowance, value);
});
});
// 辅助函数:解析签名
function parseSignature(signature: 0x${string}) {
const r = 0x${signature.substring(2, 66)} as 0x${string};
const s = 0x${signature.substring(66, 130)} as 0x${string};
const v = BigInt(0x${signature.substring(130, 132)});
return { r, s, v };
}
### 部署脚本
// 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 WETHArtifact = await artifacts.readArtifact("WrappedEther");
// 部署(构造函数参数:recipient, initialOwner) const hash = await deployer.deployContract({ abi: WETHArtifact.abi,//获取abi bytecode: WETHArtifact.bytecode,//硬编码 args: [],//process.env.RECIPIENT, process.env.OWNER });
// 等待确认并打印地址 const tokenReceipt = await publicClient.waitForTransactionReceipt({ hash }); console.log("合约地址:", tokenReceipt.contractAddress);
}
main().catch(console.error);
# 结语
至此,我们已经完成了从**理论梳理**到**代码实战**的完整闭环。
本文不仅深入探讨了包装代币(Wrapped Token)的**核心原理**、**解决痛点**及**安全风险**,更基于最新的 **Solidity 0.8.24** 与 **OpenZeppelin V5** 标准,手把手实现了一个具备 **Permit 离线签名**功能的生产级 **WETH 合约**。通过配套的 **Viem 测试脚本**,我们直观地验证了资产包装、提取及授权的运行逻辑。
无论你是想理解 DeFi 底层资产的演进,还是寻找可落地的工程实践指南,希望这份全景式的梳理能为你提供清晰的参考。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!