Web3 速查表
本章提供 Web3 开发中常用的命令、代码片段和概念速查,方便快速查阅。
以太坊单位换算
| 单位 | Wei 值 | 说明 |
|---|---|---|
| wei | 1 | 最小单位 |
| gwei | 10^9 | Gas 价格单位 |
| ether | 10^18 | 以太币 |
// ethers.js 单位换算
ethers.parseEther("1.0") // "1000000000000000000"
ethers.formatEther(1000000000000000000n) // "1.0"
ethers.parseUnits("1.0", "gwei") // "1000000000"
ethers.formatUnits(1000000000n, "gwei") // "1.0"
// Solidity 单位
uint256 a = 1 ether; // 10^18 wei
uint256 b = 1 gwei; // 10^9 wei
uint256 c = 1 wei; // 1 wei
Solidity 数据类型速查
值类型
| 类型 | 说明 | 示例 |
|---|---|---|
| bool | 布尔值 | bool isActive = true; |
| uint | 无符号整数 | uint256 amount = 100; |
| int | 有符号整数 | int256 temperature = -10; |
| address | 地址 | address owner = 0x...; |
| bytes1-bytes32 | 定长字节 | bytes32 hash = keccak256(...); |
| string | 字符串 | string name = "Token"; |
| enum | 枚举 | enum Status { Active, Paused } |
引用类型
| 类型 | 说明 | 示例 |
|---|---|---|
| array | 数组 | uint256[] items; |
| struct | 结构体 | struct User { string name; } |
| mapping | 映射 | mapping(address => uint256) balances; |
可见性修饰符
| 修饰符 | 本合约 | 子合约 | 外部 |
|---|---|---|---|
| private | ✓ | ✗ | ✗ |
| internal | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ |
| external | ✗ | ✗ | ✓ |
状态可变性
| 修饰符 | 说明 |
|---|---|
| pure | 不读取也不修改状态 |
| view | 读取但不修改状态 |
| payable | 可以接收 ETH |
| 无 | 可以修改状态 |
常用 Solidity 模式
访问控制
// 仅所有者
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 角色控制
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "Not authorized");
_;
}
重入保护
bool private locked;
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
提款模式
function withdraw() public {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0; // 先更新状态
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
事件定义
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
// 发出事件
emit Transfer(msg.sender, to, amount);
错误处理
// 自定义错误(节省 Gas)
error InsufficientBalance(uint256 available, uint256 required);
// 使用
if (balance < amount) {
revert InsufficientBalance(balance, amount);
}
// 传统方式
require(balance >= amount, "Insufficient balance");
ethers.js 常用代码
连接网络
const { ethers } = require("ethers");
// JSON-RPC 提供者
const provider = new ethers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
// 浏览器钱包(MetaMask)
const browserProvider = new ethers.BrowserProvider(window.ethereum);
账户操作
// 创建钱包
const wallet = ethers.Wallet.createRandom();
console.log(wallet.address);
console.log(wallet.privateKey);
console.log(wallet.mnemonic.phrase);
// 从私钥恢复
const wallet = new ethers.Wallet(privateKey, provider);
// 从助记词恢复
const wallet = ethers.Wallet.fromPhrase(mnemonic, provider);
// 获取余额
const balance = await provider.getBalance(address);
console.log(ethers.formatEther(balance));
// 发送交易
const tx = await wallet.sendTransaction({
to: recipientAddress,
value: ethers.parseEther("0.1")
});
await tx.wait();
合约交互
// 合约实例
const contract = new ethers.Contract(address, abi, provider);
// 读取数据
const name = await contract.name();
const balance = await contract.balanceOf(userAddress);
// 写入数据(需要签名者)
const contractWithSigner = contract.connect(wallet);
const tx = await contractWithSigner.transfer(recipient, amount);
await tx.wait();
// 监听事件
contract.on("Transfer", (from, to, amount) => {
console.log(`Transfer: ${from} -> ${to}: ${amount}`);
});
签名验证
// 签名消息
const message = "Hello, Ethereum!";
const signature = await wallet.signMessage(message);
// 验证签名
const recoveredAddress = ethers.verifyMessage(message, signature);
console.log(recoveredAddress === wallet.address); // true
// 签名 Typed Data(EIP-712)
const domain = {
name: "My App",
version: "1",
chainId: 1,
verifyingContract: contractAddress
};
const types = {
Message: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" }
]
};
const value = {
from: wallet.address,
to: recipientAddress,
amount: ethers.parseEther("1.0")
};
const signature = await wallet.signTypedData(domain, types, value);
Hardhat 常用命令
# 编译合约
npx hardhat compile
# 运行测试
npx hardhat test
# 运行特定测试
npx hardhat test test/Token.test.js
# 显示 Gas 报告
REPORT_GAS=true npx hardhat test
# 启动本地节点
npx hardhat node
# 部署到本地网络
npx hardhat run scripts/deploy.js
# 部署到测试网
npx hardhat run scripts/deploy.js --network sepolia
# 打开控制台
npx hardhat console
# 清理编译产物
npx hardhat clean
# 验证合约
npx hardhat verify --network sepolia CONTRACT_ADDRESS CONSTRUCTOR_ARGS
OpenZeppelin 常用合约
ERC20 代币
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("My Token", "MTK") {
_mint(msg.sender, initialSupply * 10 ** decimals());
}
}
ERC721 NFT
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("My NFT", "MNFT") {}
function mint() public {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
}
}
访问控制
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is Ownable, AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
constructor() Ownable(msg.sender) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
}
function adminFunction() public onlyRole(ADMIN_ROLE) {
// 管理员功能
}
}
可暂停
import "@openzeppelin/contracts/utils/Pausable.sol";
contract MyContract is Pausable {
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function deposit() public whenNotPaused {
// 存款逻辑
}
}
常用全局变量
区块信息
| 变量 | 类型 | 说明 |
|---|---|---|
| block.coinbase | address | 出块者地址 |
| block.difficulty | uint256 | 区块难度 |
| block.gaslimit | uint256 | 区块 Gas 上限 |
| block.number | uint256 | 区块高度 |
| block.timestamp | uint256 | 区块时间戳(秒) |
| blockhash(uint) | bytes32 | 指定区块的哈希 |
交易信息
| 变量 | 类型 | 说明 |
|---|---|---|
| msg.data | bytes | 完整调用数据 |
| msg.sender | address | 调用者地址 |
| msg.sig | bytes4 | 函数选择器 |
| msg.value | uint256 | 发送的 Wei 数量 |
| tx.gasprice | uint256 | Gas 价格 |
| tx.origin | address | 交易发起者 |
其他
| 变量/函数 | 说明 |
|---|---|
| gasleft() | 剩余 Gas |
| now (已弃用) | 使用 block.timestamp |
| this | 当前合约地址 |
| super | 父合约 |
常用 ERC 标准接口
ERC20
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
ERC721
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
ERC1155
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
常见网络配置
测试网
| 网络 | Chain ID | RPC URL |
|---|---|---|
| Sepolia | 11155111 | https://rpc.sepolia.org |
| Goerli | 5 | https://rpc.goerli.mudit.blog/ |
| Holesky | 17000 | https://rpc.holesky.ethpandaops.io |
主网
| 网络 | Chain ID | RPC URL |
|---|---|---|
| Ethereum | 1 | https://eth.llamarpc.com |
| Polygon | 137 | https://polygon-rpc.com |
| Arbitrum | 42161 | https://arb1.arbitrum.io/rpc |
| Optimism | 10 | https://mainnet.optimism.io |
| BSC | 56 | https://bsc-dataseed.binance.org |
Gas 优化技巧
存储优化
// 不优化:3 个存储槽
contract Bad {
uint128 a;
uint256 b;
uint128 c;
}
// 优化:2 个存储槽
contract Good {
uint128 a;
uint128 c;
uint256 b;
}
使用 calldata
// 不优化:复制到内存
function process(uint256[] memory arr) public pure returns (uint256) {
return arr[0];
}
// 优化:直接使用 calldata
function process(uint256[] calldata arr) public pure returns (uint256) {
return arr[0];
}
使用 unchecked
// 0.8.0 之后自动检查溢出
for (uint256 i = 0; i < length; i++) {
// ...
}
// 跳过溢出检查(节省 Gas)
for (uint256 i = 0; i < length;) {
// ...
unchecked { i++; }
}
使用自定义错误
// 不优化:字符串错误
require(balance >= amount, "Insufficient balance");
// 优化:自定义错误
error InsufficientBalance();
if (balance < amount) revert InsufficientBalance();
安全检查清单
- 使用 ReentrancyGuard 防止重入攻击
- 先更新状态再发送 ETH
- 使用 SafeMath 或 Solidity 0.8.0+
- 验证外部调用的返回值
- 限制循环次数防止 DoS
- 使用 pull 模式而非 push 模式
- 验证输入参数
- 设置合理的访问控制
- 使用时间锁管理关键功能
- 进行充分的单元测试
- 进行专业安全审计
小结
本章提供了 Web3 开发的常用速查信息,包括 Solidity 语法、ethers.js 代码片段、Hardhat 命令和 OpenZeppelin 合约模板。建议将此页面加入书签,在开发过程中快速查阅。