什么是智能合约?它与传统程序有什么区别?
What is a smart contract? How does it differ from traditional programs?
*考察点:智能合约基本概念。*
共 30 道题目
What is a smart contract? How does it differ from traditional programs?
What is a smart contract? How does it differ from traditional programs?
考察点:智能合约基本概念。
答案:
智能合约是运行在区块链网络上的自动执行程序,其中合约条款直接写入代码。它是一种去中心化的、不可篡改的程序,一旦部署到区块链上,就会按照预设的规则自动执行,无需中介机构参与。
智能合约具有透明性、不可篡改性和去信任化的特点,所有交易和执行过程都记录在区块链上,任何人都可以验证。
主要区别:
执行环境:
信任机制:
可修改性:
执行成本:
常见应用场景:
What are the main differences between Web3.js and Ethers.js? What are their respective advantages?
What are the main differences between Web3.js and Ethers.js? What are their respective advantages?
考察点:Web3库选择与对比。
答案:
Web3.js 和 Ethers.js 都是用于与以太坊区块链交互的 JavaScript 库,但在设计理念和使用方式上有显著区别。Web3.js 是较早出现的库,而 Ethers.js 是后来开发的现代化替代方案。
主要区别:
架构设计:
API设计:
类型安全:
Web3.js 优势:
Ethers.js 优势:
代码示例对比:
// Web3.js
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
// Ethers.js
const { ethers } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
What is ABI (Application Binary Interface)? What role does it play in smart contract interaction?
What is ABI (Application Binary Interface)? What role does it play in smart contract interaction?
考察点:ABI基础概念与作用。
答案:
ABI(Application Binary Interface,应用二进制接口)是一个JSON格式的描述文件,定义了智能合约中函数和事件的接口规范。它描述了如何与智能合约的字节码进行交互,包括函数名、参数类型、返回值类型等信息。
ABI 充当了前端应用程序与区块链上智能合约之间的桥梁,使得外部应用能够正确地调用合约函数和解析合约事件。
主要作用:
函数调用编码:
返回值解码:
事件解析:
类型验证:
ABI 结构示例:
[
{
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "from", "type": "address"},
{"indexed": true, "name": "to", "type": "address"},
{"indexed": false, "name": "value", "type": "uint256"}
],
"name": "Transfer",
"type": "event"
}
]
使用示例:
// 使用 ABI 创建合约实例
const contract = new ethers.Contract(contractAddress, abi, provider);
// 调用合约函数
const result = await contract.transfer("0x...", ethers.utils.parseEther("1.0"));
How to connect to the Ethereum network? What are the differences between mainnet and testnets?
How to connect to the Ethereum network? What are the differences between mainnet and testnets?
考察点:网络连接基础。
答案:
连接以太坊网络需要通过 RPC 节点提供商或本地节点。常见的连接方式包括使用 Infura、Alchemy、QuickNode 等服务提供商,或者连接到本地运行的以太坊客户端。
连接方法:
// 使用 Ethers.js
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
// 使用 Web3.js
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
// 连接到用户的钱包
const provider = new ethers.providers.Web3Provider(window.ethereum);
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
主网和测试网区别:
| 特征 | 主网 (Mainnet) | 测试网 (Testnet) |
|---|---|---|
| 真实价值 | ETH 具有真实经济价值 | 测试 ETH 无经济价值 |
| 用途 | 生产环境,真实交易 | 开发测试,实验功能 |
| Gas 费用 | 需要真实 ETH 支付 | 免费获取测试 ETH |
| 数据持久性 | 永久保存 | 可能定期重置 |
| 网络稳定性 | 高度稳定 | 可能不稳定 |
常见测试网:
网络配置示例:
const networks = {
mainnet: {
name: 'Ethereum Mainnet',
rpcUrl: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
chainId: 1
},
goerli: {
name: 'Goerli Testnet',
rpcUrl: 'https://goerli.infura.io/v3/YOUR-PROJECT-ID',
chainId: 5
},
sepolia: {
name: 'Sepolia Testnet',
rpcUrl: 'https://sepolia.infura.io/v3/YOUR-PROJECT-ID',
chainId: 11155111
}
};
最佳实践:
What is a contract address? How to obtain and verify contract addresses?
What is a contract address? How to obtain and verify contract addresses?
考察点:合约地址概念与验证。
答案:
合约地址是智能合约在区块链上的唯一标识符,是一个42字符的十六进制字符串(包含0x前缀)。与普通账户地址不同,合约地址是在部署合约时由区块链网络自动生成的,基于部署者地址和其nonce值计算得出。
合约地址是与智能合约交互的必要条件,所有对合约的调用都需要指定正确的合约地址。
地址生成规则:
合约地址 = keccak256(rlp.encode([部署者地址, nonce]))[12:]
获取合约地址的方法:
// 部署合约时获取地址
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);
const contract = await contractFactory.deploy();
await contract.deployed();
console.log("合约地址:", contract.address);
// 从部署交易收据中获取
const receipt = await provider.getTransactionReceipt(deployTxHash);
console.log("合约地址:", receipt.contractAddress);
验证合约地址的方法:
function isValidAddress(address) {
return ethers.utils.isAddress(address);
}
async function isContract(address, provider) {
const code = await provider.getCode(address);
return code !== '0x';
}
// 获取合约字节码
const bytecode = await provider.getCode(contractAddress);
if (bytecode === '0x') {
console.log("该地址不是合约地址");
} else {
console.log("合约字节码:", bytecode);
}
安全注意事项:
常见验证工具:
How to read public state variables of smart contracts?
How to read public state variables of smart contracts?
考察点:合约状态读取基础。
答案:
读取智能合约的公共状态变量是与区块链交互的基础操作。公共状态变量会自动生成对应的 getter 函数,可以通过这些函数读取变量的当前值。读取操作不需要消耗 Gas,因为它们不会改变区块链状态。
读取方法:
// 创建合约实例
const contract = new ethers.Contract(contractAddress, abi, provider);
// 读取公共变量
const totalSupply = await contract.totalSupply();
const owner = await contract.owner();
const balance = await contract.balanceOf(userAddress);
console.log("总供应量:", totalSupply.toString());
console.log("合约所有者:", owner);
console.log("用户余额:", ethers.utils.formatEther(balance));
const contract = new web3.eth.Contract(abi, contractAddress);
// 读取状态变量
const totalSupply = await contract.methods.totalSupply().call();
const owner = await contract.methods.owner().call();
const balance = await contract.methods.balanceOf(userAddress).call();
// 使用 eth_call 方法
const data = contract.interface.encodeFunctionData("totalSupply", []);
const result = await provider.call({
to: contractAddress,
data: data
});
const decoded = contract.interface.decodeFunctionResult("totalSupply", result);
Solidity 合约示例:
contract MyToken {
string public name = "MyToken"; // 自动生成 name() 函数
uint256 public totalSupply = 1000000; // 自动生成 totalSupply() 函数
address public owner; // 自动生成 owner() 函数
mapping(address => uint256) public balanceOf; // 自动生成 balanceOf(address) 函数
constructor() {
owner = msg.sender;
}
}
批量读取示例:
// 批量读取多个状态变量
async function getContractInfo(contract) {
const [name, symbol, totalSupply, owner] = await Promise.all([
contract.name(),
contract.symbol(),
contract.totalSupply(),
contract.owner()
]);
return {
name,
symbol,
totalSupply: totalSupply.toString(),
owner
};
}
注意事项:
最佳实践:
What are the differences between calling view and pure functions in contracts?
What are the differences between calling view and pure functions in contracts?
考察点:合约函数类型理解。
答案:
view 函数和 pure 函数是 Solidity 中两种不同的只读函数修饰符,它们都不会修改区块链状态,因此调用时不需要消耗 Gas 费用。但它们在对状态数据的访问权限上有重要区别。
主要区别:
状态访问权限:
允许的操作:
view 函数可以:
pure 函数只能:
代码示例:
contract Example {
uint256 public balance = 1000;
string public name = "MyContract";
// view 函数 - 可以读取状态变量
function getBalance() public view returns (uint256) {
return balance; // 读取状态变量
}
function getInfo() public view returns (string memory, uint256, address) {
return (name, balance, msg.sender); // 读取状态和区块信息
}
// pure 函数 - 只能处理传入参数
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // 纯数学计算
}
function calculateHash(string memory text) public pure returns (bytes32) {
return keccak256(abi.encodePacked(text)); // 纯函数计算
}
}
JavaScript 调用示例:
// 调用 view 函数
const balance = await contract.getBalance();
const info = await contract.getInfo();
// 调用 pure 函数
const sum = await contract.add(10, 20);
const hash = await contract.calculateHash("hello");
console.log("余额:", balance.toString());
console.log("计算结果:", sum.toString());
性能特点:
使用场景:
view 函数适用于:
pure 函数适用于:
注意事项:
What are Gas limit and Gas price? How to set them reasonably?
What are Gas limit and Gas price? How to set them reasonably?
考察点:Gas机制基础理解。
答案:
Gas limit 和 Gas price 是以太坊网络中控制交易执行成本和优先级的两个重要参数。它们共同决定了交易的最终费用和处理速度。
Gas Limit:
Gas limit 是用户愿意为一笔交易支付的最大计算单位数量。它设定了交易可以消耗的计算资源上限,防止无限循环或恶意代码消耗过多资源。
Gas Price:
Gas price 是用户愿意为每单位 Gas 支付的价格,通常以 Gwei(1 Gwei = 10^-9 ETH)为单位。它决定了交易的优先级,矿工/验证者倾向于优先处理 Gas price 更高的交易。
总费用计算:
交易费用 = Gas Used × Gas Price
设置策略:
// 简单转账
const gasLimit = 21000;
// 合约交互 - 使用估算
const estimatedGas = await contract.estimateGas.transfer(to, amount);
const gasLimit = estimatedGas.mul(120).div(100); // 增加20%缓冲
// 复杂操作
const gasLimit = 500000; // 根据具体合约功能设定
// 获取网络建议价格
const gasPrice = await provider.getGasPrice();
// 手动设置(快速确认)
const gasPrice = ethers.utils.parseUnits('20', 'gwei');
// 分级设置
const gasPrices = {
slow: ethers.utils.parseUnits('10', 'gwei'), // 慢速
standard: ethers.utils.parseUnits('20', 'gwei'), // 标准
fast: ethers.utils.parseUnits('40', 'gwei') // 快速
};
实际应用示例:
// 发送交易时设置 Gas 参数
const tx = {
to: recipientAddress,
value: ethers.utils.parseEther("1.0"),
gasLimit: 21000,
gasPrice: ethers.utils.parseUnits('20', 'gwei')
};
const transaction = await signer.sendTransaction(tx);
EIP-1559 后的变化:
// 使用 maxFeePerGas 和 maxPriorityFeePerGas
const tx = {
to: recipientAddress,
value: ethers.utils.parseEther("1.0"),
gasLimit: 21000,
maxFeePerGas: ethers.utils.parseUnits('30', 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits('2', 'gwei')
};
合理设置原则:
Gas Limit:
Gas Price:
常用工具:
注意事项:
How to estimate the Gas cost required for transactions?
How to estimate the Gas cost required for transactions?
考察点:Gas估算方法。
答案:
Gas 费用估算是区块链交易中的重要环节,准确的估算可以避免交易失败,同时防止支付过高的手续费。估算方法包括使用内置函数、查询网络状态和参考历史数据等。
主要估算方法:
// Ethers.js 估算
const contract = new ethers.Contract(contractAddress, abi, signer);
// 估算合约函数调用
const estimatedGas = await contract.estimateGas.transfer(
recipientAddress,
ethers.utils.parseEther("1.0")
);
console.log("估算 Gas:", estimatedGas.toString());
// 估算普通转账
const estimatedGas = await signer.estimateGas({
to: recipientAddress,
value: ethers.utils.parseEther("1.0")
});
// 估算合约调用
const gasEstimate = await contract.methods.transfer(to, amount).estimateGas({
from: userAddress
});
// 估算转账
const gasEstimate = await web3.eth.estimateGas({
to: recipientAddress,
from: userAddress,
value: web3.utils.toWei('1', 'ether')
});
// 增加 20% 安全缓冲
const gasLimit = estimatedGas.mul(120).div(100);
// 或使用固定增量
const gasLimit = estimatedGas.add(50000);
费用计算:
// 获取当前 Gas 价格
const gasPrice = await provider.getGasPrice();
// 计算总费用
const totalCost = gasLimit.mul(gasPrice);
const costInEther = ethers.utils.formatEther(totalCost);
console.log(`预估费用: ${costInEther} ETH`);
不同交易类型的典型 Gas 消耗:
| 交易类型 | 典型 Gas 消耗 |
|---|---|
| ETH 转账 | 21,000 |
| ERC-20 转账 | 60,000 - 80,000 |
| Uniswap 交换 | 150,000 - 300,000 |
| NFT 铸造 | 100,000 - 200,000 |
| 合约部署 | 500,000 - 2,000,000+ |
动态价格获取:
// 获取网络建议的 Gas 价格
async function getGasPrices(provider) {
const gasPrice = await provider.getGasPrice();
return {
slow: gasPrice.mul(80).div(100), // 慢速 (80%)
standard: gasPrice, // 标准 (100%)
fast: gasPrice.mul(120).div(100) // 快速 (120%)
};
}
// EIP-1559 网络的费用估算
async function estimateEIP1559Fees(provider) {
const feeData = await provider.getFeeData();
return {
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
};
}
完整估算示例:
async function estimateTransactionCost(contract, method, params) {
try {
// 估算 Gas limit
const gasLimit = await contract.estimateGas[method](...params);
const safeGasLimit = gasLimit.mul(110).div(100); // +10% 缓冲
// 获取 Gas 价格
const gasPrice = await contract.provider.getGasPrice();
// 计算费用
const cost = safeGasLimit.mul(gasPrice);
return {
gasLimit: safeGasLimit,
gasPrice: gasPrice,
cost: cost,
costInEther: ethers.utils.formatEther(cost)
};
} catch (error) {
console.error("估算失败:", error);
throw error;
}
}
第三方 API 服务:
// 使用 ETH Gas Station API
async function getGasStationData() {
const response = await fetch('https://ethgasstation.info/api/ethgasAPI.json');
const data = await response.json();
return {
slow: data.safeLow / 10, // Gwei
standard: data.average / 10, // Gwei
fast: data.fast / 10 // Gwei
};
}
注意事项:
最佳实践:
What is a transaction hash? How to query transaction status through transaction hash?
What is a transaction hash? How to query transaction status through transaction hash?
考察点:交易跟踪基础。
答案:
交易哈希(Transaction Hash)是每笔区块链交易的唯一标识符,是一个64字符的十六进制字符串。它是通过对交易数据进行 Keccak-256 哈希算法计算得出的,具有唯一性和不可篡改性,用于在区块链网络中追踪和验证交易。
交易哈希在交易提交到网络时即刻生成,无论交易最终成功还是失败,都会有对应的哈希值。
交易哈希的获取:
// 发送交易时获取哈希
const tx = await signer.sendTransaction({
to: recipientAddress,
value: ethers.utils.parseEther("1.0")
});
console.log("交易哈希:", tx.hash);
// 示例: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
查询交易状态的方法:
// 获取交易详细信息
const transaction = await provider.getTransaction(txHash);
console.log("交易信息:", transaction);
// 获取交易收据(仅已确认的交易)
const receipt = await provider.getTransactionReceipt(txHash);
console.log("交易收据:", receipt);
// 等待交易确认
const receipt = await provider.waitForTransaction(txHash);
console.log("交易已确认:", receipt);
async function getTransactionStatus(txHash, provider) {
try {
const receipt = await provider.getTransactionReceipt(txHash);
if (!receipt) {
return "pending"; // 交易待确认
}
if (receipt.status === 1) {
return "success"; // 交易成功
} else {
return "failed"; // 交易失败
}
} catch (error) {
return "not_found"; // 交易不存在
}
}
// 等待指定确认数
async function waitForConfirmations(txHash, confirmations = 6) {
const receipt = await provider.waitForTransaction(txHash, confirmations);
console.log(`交易已获得 ${confirmations} 个确认`);
return receipt;
}
// 实时监听交易状态
function watchTransaction(txHash) {
provider.on(txHash, (transaction) => {
console.log("交易已确认:", transaction);
});
}
交易收据信息解析:
const receipt = await provider.getTransactionReceipt(txHash);
console.log({
transactionHash: receipt.transactionHash, // 交易哈希
blockNumber: receipt.blockNumber, // 区块号
blockHash: receipt.blockHash, // 区块哈希
status: receipt.status, // 状态 (1=成功, 0=失败)
from: receipt.from, // 发送方地址
to: receipt.to, // 接收方地址
gasUsed: receipt.gasUsed.toString(), // 实际消耗的 Gas
effectiveGasPrice: receipt.effectiveGasPrice, // 有效 Gas 价格
logs: receipt.logs // 事件日志
});
错误处理和重试机制:
async function queryTransactionWithRetry(txHash, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const receipt = await provider.getTransactionReceipt(txHash);
return receipt;
} catch (error) {
if (i === maxRetries - 1) throw error;
// 等待后重试
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
使用区块浏览器 API:
// Etherscan API 查询示例
async function queryEtherscan(txHash, apiKey) {
const url = `https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash=${txHash}&apikey=${apiKey}`;
const response = await fetch(url);
const data = await response.json();
return data.result;
}
交易状态的应用场景:
最佳实践:
What is the role of Events in smart contracts? How to listen to events?
What is the role of Events in smart contracts? How to listen to events?
考察点:事件机制基础。
答案:
智能合约事件是一种特殊的日志机制,用于记录合约执行过程中的重要信息。事件在区块链上永久存储,但不占用合约存储空间,提供了一种高效的数据记录和通信方式。前端应用可以通过监听事件来获取合约状态变化的实时通知。
事件的主要作用:
Solidity 事件定义示例:
contract MyToken {
// 定义事件
event Transfer(
address indexed from, // indexed 参数可以被过滤
address indexed to,
uint256 value // 非 indexed 参数存储在日志数据中
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
function transfer(address to, uint256 amount) public {
// 执行转账逻辑...
// 触发事件
emit Transfer(msg.sender, to, amount);
}
}
监听事件的方法:
// 监听所有 Transfer 事件
contract.on("Transfer", (from, to, value, event) => {
console.log({
from: from,
to: to,
value: ethers.utils.formatEther(value),
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
});
// 监听特定地址的转入事件
const filter = contract.filters.Transfer(null, userAddress);
contract.on(filter, (from, to, value, event) => {
console.log(`${userAddress} 收到转账:`, ethers.utils.formatEther(value));
});
// 查询指定区块范围的事件
const filter = contract.filters.Transfer();
const events = await contract.queryFilter(filter, fromBlock, toBlock);
events.forEach(event => {
console.log({
from: event.args.from,
to: event.args.to,
value: event.args.value.toString(),
blockNumber: event.blockNumber
});
});
// 监听所有新区块中的事件
provider.on("block", async (blockNumber) => {
const events = await contract.queryFilter("Transfer", blockNumber);
events.forEach(event => {
console.log("新的转账事件:", event.args);
});
});
高级事件监听:
// 复合条件过滤
const multiFilter = {
address: contractAddress,
topics: [
ethers.utils.id("Transfer(address,address,uint256)"),
null, // from (任意)
ethers.utils.hexZeroPad(userAddress, 32) // to (特定用户)
]
};
provider.on(multiFilter, (log) => {
const parsed = contract.interface.parseLog(log);
console.log("解析后的事件:", parsed);
});
事件数据解析:
// 手动解析事件日志
function parseTransferEvent(log) {
const iface = new ethers.utils.Interface([
"event Transfer(address indexed from, address indexed to, uint256 value)"
]);
try {
const parsed = iface.parseLog(log);
return {
eventName: parsed.name,
from: parsed.args.from,
to: parsed.args.to,
value: parsed.args.value
};
} catch (error) {
console.error("事件解析失败:", error);
return null;
}
}
实际应用场景:
// DeFi 应用中监听流动性变化
contract.on("LiquidityAdded", (provider, tokenA, tokenB, amount, event) => {
updateLiquidityUI(tokenA, tokenB, amount);
showNotification("流动性已增加");
});
// NFT 市场监听交易
nftContract.on("Transfer", (from, to, tokenId) => {
if (from === "0x0000000000000000000000000000000000000000") {
console.log(`NFT ${tokenId} 被铸造给 ${to}`);
} else {
console.log(`NFT ${tokenId} 从 ${from} 转移给 ${to}`);
}
});
性能优化建议:
注意事项:
How to send Ether to a contract address?
How to send Ether to a contract address?
考察点:基础转账操作。
答案:
向智能合约发送以太币有多种方式,主要取决于合约是否实现了接收以太币的特殊函数。合约可以通过 receive() 函数、fallback() 函数或带有 payable 修饰符的普通函数来接收以太币。
发送以太币的方法:
// 使用 Ethers.js 发送以太币
const tx = await signer.sendTransaction({
to: contractAddress,
value: ethers.utils.parseEther("1.0") // 发送 1 ETH
});
console.log("交易哈希:", tx.hash);
await tx.wait(); // 等待确认
// 调用带有 payable 修饰符的函数并发送以太币
const contract = new ethers.Contract(contractAddress, abi, signer);
const tx = await contract.deposit({
value: ethers.utils.parseEther("0.5")
});
await tx.wait();
// Web3.js 发送以太币到合约
const tx = await web3.eth.sendTransaction({
from: userAddress,
to: contractAddress,
value: web3.utils.toWei('1', 'ether'),
gas: 21000
});
合约端接收以太币的实现:
contract MyContract {
mapping(address => uint256) public balances;
// receive 函数 - 接收纯以太币转账
receive() external payable {
balances[msg.sender] += msg.value;
emit Received(msg.sender, msg.value);
}
// fallback 函数 - 接收带有数据的转账
fallback() external payable {
balances[msg.sender] += msg.value;
emit FallbackReceived(msg.sender, msg.value, msg.data);
}
// 显式的存款函数
function deposit() public payable {
require(msg.value > 0, "必须发送一些以太币");
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
}
// 提取以太币
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "余额不足");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
// 获取合约余额
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
event Received(address sender, uint256 amount);
event FallbackReceived(address sender, uint256 amount, bytes data);
event Deposited(address sender, uint256 amount);
}
发送以太币的完整示例:
async function sendEtherToContract() {
try {
// 检查合约是否能接收以太币
const code = await provider.getCode(contractAddress);
if (code === '0x') {
throw new Error("目标地址不是合约");
}
// 获取当前 Gas 价格
const gasPrice = await provider.getGasPrice();
// 发送交易
const tx = await signer.sendTransaction({
to: contractAddress,
value: ethers.utils.parseEther("1.0"),
gasLimit: 100000, // 适当设置 Gas limit
gasPrice: gasPrice
});
console.log(`交易已发送: ${tx.hash}`);
// 等待确认
const receipt = await tx.wait();
console.log(`交易已确认,区块号: ${receipt.blockNumber}`);
return receipt;
} catch (error) {
console.error("发送失败:", error);
throw error;
}
}
批量发送示例:
// 向多个合约发送以太币
async function batchSendEther(recipients, amount) {
const txPromises = recipients.map(address =>
signer.sendTransaction({
to: address,
value: ethers.utils.parseEther(amount.toString())
})
);
const transactions = await Promise.all(txPromises);
// 等待所有交易确认
const receipts = await Promise.all(
transactions.map(tx => tx.wait())
);
return receipts;
}
安全检查和错误处理:
async function safeSendEther(contractAddress, amount) {
// 1. 验证地址格式
if (!ethers.utils.isAddress(contractAddress)) {
throw new Error("无效的合约地址");
}
// 2. 检查账户余额
const balance = await signer.getBalance();
if (balance.lt(amount)) {
throw new Error("账户余额不足");
}
// 3. 检查合约是否存在
const code = await provider.getCode(contractAddress);
if (code === '0x') {
throw new Error("目标地址不是合约");
}
// 4. 估算 Gas 费用
const gasEstimate = await provider.estimateGas({
to: contractAddress,
value: amount
});
// 5. 发送交易
const tx = await signer.sendTransaction({
to: contractAddress,
value: amount,
gasLimit: gasEstimate.mul(110).div(100) // +10% 缓冲
});
return tx;
}
注意事项:
receive() 或 fallback() 函数才能接收直接转账payable 函数时可以同时发送以太币常见错误:
What is nonce? What role does it play in transactions?
What is nonce? What role does it play in transactions?
考察点:交易nonce理解。
答案:
nonce 是"number only used once"的缩写,在以太坊中指账户发送交易的序列号。每个账户都有一个 nonce 值,从0开始,每发送一笔交易就递增1。nonce 确保交易的唯一性和顺序性,防止重放攻击和双重支付问题。
nonce 是交易安全机制的重要组成部分,它确保了每笔交易只能被执行一次,并且必须按照正确的顺序执行。
nonce 的主要作用:
nonce 的获取和使用:
// 获取账户当前的 nonce
const currentNonce = await provider.getTransactionCount(userAddress);
console.log("当前 nonce:", currentNonce);
// 获取包含待确认交易的 nonce
const pendingNonce = await provider.getTransactionCount(userAddress, "pending");
console.log("待确认 nonce:", pendingNonce);
手动设置 nonce:
// 发送交易时指定 nonce
const tx = await signer.sendTransaction({
to: recipientAddress,
value: ethers.utils.parseEther("1.0"),
nonce: currentNonce // 手动指定 nonce
});
nonce 管理策略:
// Ethers.js 自动管理 nonce
const tx = await signer.sendTransaction({
to: recipientAddress,
value: ethers.utils.parseEther("1.0")
// 不指定 nonce,由库自动处理
});
async function sendMultipleTransactions(transactions) {
let nonce = await provider.getTransactionCount(userAddress);
const txPromises = transactions.map((txData, index) => {
return signer.sendTransaction({
...txData,
nonce: nonce + index // 依次递增 nonce
});
});
return Promise.all(txPromises);
}
class NonceManager {
constructor(provider, address) {
this.provider = provider;
this.address = address;
this.currentNonce = null;
}
async init() {
this.currentNonce = await this.provider.getTransactionCount(this.address);
}
getNextNonce() {
return this.currentNonce++;
}
async reset() {
this.currentNonce = await this.provider.getTransactionCount(this.address);
}
}
// 使用示例
const nonceManager = new NonceManager(provider, userAddress);
await nonceManager.init();
const tx1 = await signer.sendTransaction({
to: address1,
value: amount1,
nonce: nonceManager.getNextNonce()
});
const tx2 = await signer.sendTransaction({
to: address2,
value: amount2,
nonce: nonceManager.getNextNonce()
});
nonce 相关问题和解决方案:
// 错误:nonce too low
// 解决:获取最新的 nonce
try {
const tx = await signer.sendTransaction(txData);
} catch (error) {
if (error.code === 'NONCE_EXPIRED') {
// 重新获取 nonce 并重试
const newNonce = await provider.getTransactionCount(userAddress);
const retryTx = await signer.sendTransaction({
...txData,
nonce: newNonce
});
}
}
// 如果有 nonce 间隙,后续交易会被卡住
async function checkNonceGap(address) {
const confirmedNonce = await provider.getTransactionCount(address);
const pendingNonce = await provider.getTransactionCount(address, "pending");
if (pendingNonce > confirmedNonce) {
console.log(`存在 ${pendingNonce - confirmedNonce} 个待确认交易`);
}
}
// 使用相同 nonce 但更高 Gas 价格替换交易
async function replaceTransaction(originalTx, newGasPrice) {
const replaceTx = await signer.sendTransaction({
to: originalTx.to,
value: originalTx.value,
nonce: originalTx.nonce, // 使用相同 nonce
gasPrice: newGasPrice, // 提高 Gas 价格
gasLimit: originalTx.gasLimit
});
return replaceTx;
}
nonce 监控和调试:
// 监控账户 nonce 变化
async function monitorNonce(address) {
let lastNonce = await provider.getTransactionCount(address);
setInterval(async () => {
const currentNonce = await provider.getTransactionCount(address);
if (currentNonce !== lastNonce) {
console.log(`Nonce 更新: ${lastNonce} -> ${currentNonce}`);
lastNonce = currentNonce;
}
}, 10000); // 每10秒检查一次
}
最佳实践:
注意事项:
What are the common reasons for contract call failures?
What are the common reasons for contract call failures?
考察点:错误处理基础。
答案:
智能合约调用失败是开发过程中的常见问题,了解失败原因有助于快速定位和解决问题。合约调用失败主要分为交易级别失败和合约逻辑失败两大类。
常见失败原因分类:
1. Gas 相关问题:
// Gas limit 过低
try {
const tx = await contract.complexFunction({
gasLimit: 50000 // 可能过低
});
} catch (error) {
// Error: out of gas
console.log("Gas 不足,需要增加 gasLimit");
}
// Gas price 过低导致交易长时间不被确认
const tx = await contract.someFunction({
gasPrice: ethers.utils.parseUnits('1', 'gwei') // 过低的 Gas 价格
});
2. 权限和访问控制:
// 合约中的权限检查
contract MyContract {
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "只有所有者可以调用");
_;
}
function adminFunction() public onlyOwner {
// 只有 owner 可以调用
}
}
// 调用时权限不足
try {
await contract.adminFunction(); // 非 owner 调用
} catch (error) {
console.log("权限不足:", error.message);
}
3. 参数验证失败:
contract TokenContract {
function transfer(address to, uint256 amount) public {
require(to != address(0), "接收地址不能为零地址");
require(amount > 0, "转账金额必须大于零");
require(balanceOf[msg.sender] >= amount, "余额不足");
// ...
}
}
4. 状态条件不满足:
// 合约状态检查失败
try {
await contract.withdraw(ethers.utils.parseEther("100"));
} catch (error) {
// 可能的错误:"余额不足"、"合约已暂停"等
console.log("状态检查失败:", error.reason);
}
5. 重入攻击保护:
contract SecureContract {
bool private locked;
modifier noReentrancy() {
require(!locked, "重入攻击检测");
locked = true;
_;
locked = false;
}
function withdraw() external noReentrancy {
// 防重入的提取逻辑
}
}
6. 网络和连接问题:
// 网络超时或连接问题
async function robustContractCall() {
const maxRetries = 3;
let retries = 0;
while (retries < maxRetries) {
try {
const result = await contract.someFunction();
return result;
} catch (error) {
if (error.code === 'NETWORK_ERROR' && retries < maxRetries - 1) {
retries++;
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
} else {
throw error;
}
}
}
}
7. ABI 不匹配:
// ABI 与合约实际接口不匹配
try {
await contract.nonExistentFunction(); // 函数不存在
} catch (error) {
console.log("函数不存在或 ABI 不匹配");
}
8. 合约地址错误:
// 检查合约地址有效性
async function validateContract(address) {
if (!ethers.utils.isAddress(address)) {
throw new Error("无效的合约地址格式");
}
const code = await provider.getCode(address);
if (code === '0x') {
throw new Error("地址不是合约或合约已被销毁");
}
return true;
}
错误处理最佳实践:
async function safeContractCall(contract, functionName, params = []) {
try {
// 1. 预检查
await validateContract(contract.address);
// 2. 估算 Gas
const gasEstimate = await contract.estimateGas[functionName](...params);
// 3. 执行调用
const tx = await contract[functionName](...params, {
gasLimit: gasEstimate.mul(110).div(100) // +10% 缓冲
});
// 4. 等待确认
const receipt = await tx.wait();
return receipt;
} catch (error) {
// 错误分类处理
if (error.code === 'UNPREDICTABLE_GAS_LIMIT') {
console.error("交易可能会失败,检查合约条件");
} else if (error.code === 'INSUFFICIENT_FUNDS') {
console.error("账户余额不足");
} else if (error.reason) {
console.error("合约拒绝:", error.reason);
} else {
console.error("未知错误:", error.message);
}
throw error;
}
}
调试工具和方法:
// 使用 callStatic 进行无状态调用测试
try {
const result = await contract.callStatic.someFunction(params);
console.log("模拟调用成功:", result);
} catch (error) {
console.log("模拟调用失败:", error.reason);
// 不会消耗 Gas,可以安全测试
}
// 检查交易失败原因
async function analyzeFailedTx(txHash) {
try {
const tx = await provider.getTransaction(txHash);
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt.status === 0) {
console.log("交易失败");
// 尝试重新执行以获取错误信息
try {
await provider.call(tx, tx.blockNumber);
} catch (error) {
console.log("失败原因:", error.reason || error.message);
}
}
} catch (error) {
console.log("分析失败:", error);
}
}
预防措施:
callStatic 进行预调用验证What is a contract constructor? How to pass parameters during deployment?
What is a contract constructor? How to pass parameters during deployment?
考察点:合约部署基础。
答案:
构造函数是智能合约中的一个特殊函数,在合约部署时自动执行一次且仅执行一次。它用于初始化合约的状态变量、设置初始配置、分配权限等。构造函数使用 constructor 关键字定义,可以接收参数来灵活配置合约的初始状态。
构造函数在合约部署完成后就不能再被调用,这使得它成为设置不可变配置和一次性初始化操作的理想位置。
Solidity 构造函数示例:
contract MyToken {
string public name;
string public symbol;
uint256 public totalSupply;
address public owner;
mapping(address => uint256) public balanceOf;
// 构造函数 - 部署时执行
constructor(
string memory _name,
string memory _symbol,
uint256 _totalSupply
) {
name = _name;
symbol = _symbol;
totalSupply = _totalSupply;
owner = msg.sender; // 部署者成为所有者
balanceOf[msg.sender] = _totalSupply; // 将所有代币分配给部署者
emit TokenCreated(_name, _symbol, _totalSupply, msg.sender);
}
event TokenCreated(
string name,
string symbol,
uint256 totalSupply,
address owner
);
}
使用 Ethers.js 部署带参数的合约:
// 1. 准备合约工厂
const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);
// 2. 部署合约并传入构造函数参数
const contract = await contractFactory.deploy(
"MyToken", // name 参数
"MTK", // symbol 参数
ethers.utils.parseEther("1000000") // totalSupply 参数
);
// 3. 等待部署完成
await contract.deployed();
console.log("合约地址:", contract.address);
console.log("部署交易:", contract.deployTransaction.hash);
使用 Web3.js 部署:
// 创建合约实例
const contract = new web3.eth.Contract(abi);
// 部署合约
const deployedContract = await contract.deploy({
data: bytecode,
arguments: [
"MyToken", // name
"MTK", // symbol
web3.utils.toWei('1000000', 'ether') // totalSupply
]
}).send({
from: deployerAddress,
gas: 2000000,
gasPrice: web3.utils.toWei('20', 'gwei')
});
console.log("合约地址:", deployedContract.options.address);
复杂构造函数参数示例:
contract DeFiProtocol {
struct Config {
uint256 fee;
uint256 minAmount;
bool paused;
}
Config public config;
address[] public supportedTokens;
mapping(address => bool) public admins;
constructor(
Config memory _config,
address[] memory _supportedTokens,
address[] memory _admins
) {
// 设置配置
config = _config;
// 添加支持的代币
for (uint i = 0; i < _supportedTokens.length; i++) {
supportedTokens.push(_supportedTokens[i]);
}
// 设置管理员
for (uint i = 0; i < _admins.length; i++) {
admins[_admins[i]] = true;
}
// 部署者也是管理员
admins[msg.sender] = true;
}
}
// 部署复杂参数合约
const config = {
fee: 100, // 1% fee (basis points)
minAmount: ethers.utils.parseEther("0.1"),
paused: false
};
const supportedTokens = [
"0xA0b86a33E6441e2b000E85C8b60E54E48e48B2Bb", // USDT
"0xdAC17F958D2ee523a2206206994597C13D831ec7" // USDC
];
const admins = [
"0x1234567890123456789012345678901234567890",
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef"
];
const contract = await contractFactory.deploy(
config, // struct 参数
supportedTokens, // array 参数
admins // array 参数
);
参数编码和验证:
// 验证构造函数参数
function validateConstructorParams(name, symbol, totalSupply) {
if (!name || name.length === 0) {
throw new Error("代币名称不能为空");
}
if (!symbol || symbol.length === 0) {
throw new Error("代币符号不能为空");
}
if (totalSupply.lte(0)) {
throw new Error("总供应量必须大于0");
}
return true;
}
// 安全部署函数
async function deployTokenContract(params) {
const { name, symbol, totalSupply } = params;
// 参数验证
validateConstructorParams(name, symbol, totalSupply);
try {
// 估算部署 Gas
const deployTx = contractFactory.getDeployTransaction(
name, symbol, totalSupply
);
const gasEstimate = await signer.estimateGas(deployTx);
// 部署合约
const contract = await contractFactory.deploy(
name,
symbol,
totalSupply,
{
gasLimit: gasEstimate.mul(110).div(100) // +10% 缓冲
}
);
// 等待确认
await contract.deployed();
console.log(`${name} 合约部署成功:`);
console.log(`地址: ${contract.address}`);
console.log(`交易: ${contract.deployTransaction.hash}`);
return contract;
} catch (error) {
console.error("部署失败:", error);
throw error;
}
}
获取构造函数参数(合约验证):
// 从部署交易中提取构造函数参数
async function getConstructorParams(contractAddress) {
// 获取合约创建交易
const contract = new ethers.Contract(contractAddress, abi, provider);
const deployTx = await provider.getTransaction(contract.deployTransaction.hash);
// 解码构造函数参数
const iface = new ethers.utils.Interface(abi);
const constructorFragment = iface.deploy;
// 从 input data 中解析参数
const params = iface.decodeFunctionData(constructorFragment, deployTx.data);
return params;
}
构造函数最佳实践:
注意事项:
msg.sender 是部署者地址How to implement batch contract calls to improve efficiency?
How to implement batch contract calls to improve efficiency?
考察点:批量操作优化。
答案:
批量合约调用是提高 DApp 性能的重要技术,通过减少网络往返次数、降低 Gas 消耗和提升用户体验来优化应用效率。主要实现方式包括使用 Multicall 合约、Promise.all() 并发调用和自定义批量处理逻辑。
主要实现方式:
// Multicall2 合约接口
const multicallABI = [
"function aggregate(tuple(address target, bytes callData)[] calls) returns (uint256 blockNumber, bytes[] returnData)",
"function tryAggregate(bool requireSuccess, tuple(address target, bytes callData)[] calls) returns (tuple(bool success, bytes returnData)[] returnData)"
];
const multicallAddress = "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696"; // Ethereum mainnet
async function batchContractCalls(calls) {
const multicall = new ethers.Contract(multicallAddress, multicallABI, provider);
// 准备批量调用数据
const callData = calls.map(call => ({
target: call.contract.address,
callData: call.contract.interface.encodeFunctionData(call.method, call.params)
}));
try {
const [blockNumber, returnData] = await multicall.aggregate(callData);
// 解码返回数据
const results = returnData.map((data, index) => {
const call = calls[index];
return call.contract.interface.decodeFunctionResult(call.method, data);
});
return results;
} catch (error) {
console.error("批量调用失败:", error);
throw error;
}
}
// 使用示例
const batchCalls = [
{ contract: tokenContract, method: "balanceOf", params: [userAddress] },
{ contract: tokenContract, method: "totalSupply", params: [] },
{ contract: tokenContract, method: "allowance", params: [userAddress, spenderAddress] }
];
const results = await batchContractCalls(batchCalls);
// 并发执行多个只读调用
async function parallelReadCalls(userAddress) {
const promises = [
tokenContract.balanceOf(userAddress),
tokenContract.totalSupply(),
tokenContract.name(),
tokenContract.symbol(),
tokenContract.decimals()
];
try {
const [balance, totalSupply, name, symbol, decimals] = await Promise.all(promises);
return {
balance: ethers.utils.formatUnits(balance, decimals),
totalSupply: ethers.utils.formatUnits(totalSupply, decimals),
name,
symbol,
decimals
};
} catch (error) {
console.error("并发调用失败:", error);
throw error;
}
}
// 自定义批量处理合约
contract BatchProcessor {
struct Call {
address target;
bytes data;
}
struct Result {
bool success;
bytes data;
}
function batchCall(Call[] calldata calls)
external
returns (Result[] memory results)
{
results = new Result[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory data) = calls[i].target.call(calls[i].data);
results[i] = Result(success, data);
}
}
function batchStaticCall(Call[] calldata calls)
external
view
returns (Result[] memory results)
{
results = new Result[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory data) = calls[i].target.staticcall(calls[i].data);
results[i] = Result(success, data);
}
}
}
// 批量发送交易(按序执行)
async function batchTransactions(transactions) {
const results = [];
let nonce = await provider.getTransactionCount(signer.getAddress());
for (let i = 0; i < transactions.length; i++) {
try {
const tx = await signer.sendTransaction({
...transactions[i],
nonce: nonce + i
});
results.push(tx);
console.log(`交易 ${i + 1} 已发送: ${tx.hash}`);
} catch (error) {
console.error(`交易 ${i + 1} 失败:`, error);
results.push({ error });
}
}
// 等待所有交易确认
const receipts = await Promise.all(
results.filter(result => result.hash).map(tx => tx.wait())
);
return receipts;
}
高级批量调用实现:
class BatchCallManager {
constructor(provider, multicallAddress) {
this.provider = provider;
this.multicall = new ethers.Contract(multicallAddress, multicallABI, provider);
this.calls = [];
}
// 添加调用
addCall(contract, method, params = []) {
this.calls.push({
contract,
method,
params,
callData: contract.interface.encodeFunctionData(method, params)
});
return this;
}
// 执行批量调用
async execute() {
if (this.calls.length === 0) {
return [];
}
const callData = this.calls.map(call => ({
target: call.contract.address,
callData: call.callData
}));
try {
const [blockNumber, returnData] = await this.multicall.aggregate(callData);
const results = returnData.map((data, index) => {
const call = this.calls[index];
try {
const decoded = call.contract.interface.decodeFunctionResult(call.method, data);
return { success: true, data: decoded };
} catch (error) {
return { success: false, error: error.message };
}
});
this.calls = []; // 清空调用队列
return results;
} catch (error) {
console.error("批量调用执行失败:", error);
throw error;
}
}
// 清空调用队列
clear() {
this.calls = [];
return this;
}
}
// 使用示例
const batchManager = new BatchCallManager(provider, multicallAddress);
const results = await batchManager
.addCall(tokenContract, "balanceOf", [userAddress])
.addCall(tokenContract, "totalSupply")
.addCall(uniswapContract, "getAmountsOut", [amount, [tokenA, tokenB]])
.execute();
性能优化技巧:
// 批量数据获取优化
async function optimizedDataFetch(userAddresses) {
const batchSize = 50; // 控制批次大小
const results = [];
for (let i = 0; i < userAddresses.length; i += batchSize) {
const batch = userAddresses.slice(i, i + batchSize);
const batchCalls = batch.map(address => ({
contract: tokenContract,
method: "balanceOf",
params: [address]
}));
const batchResults = await batchContractCalls(batchCalls);
results.push(...batchResults);
// 添加延迟避免速率限制
if (i + batchSize < userAddresses.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}
适用场景:
最佳实践:
What is the multicall pattern? How does it reduce the number of network requests?
What is the multicall pattern? How does it reduce the number of network requests?
考察点:高效调用模式。
答案:
Multicall 模式是一种将多个合约调用聚合到单个交易中执行的设计模式。它通过一个中介合约来批量执行多个合约调用,将原本需要多次网络请求的操作合并为一次请求,显著提高了效率并降低了网络延迟。
Multicall 特别适用于只读查询操作,可以在一个区块的状态下获取多个合约的数据,确保数据的一致性。
Multicall 工作原理:
Multicall 合约实现:
pragma solidity ^0.8.0;
contract Multicall {
struct Call {
address target;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
// 标准聚合调用(任一失败则全部失败)
function aggregate(Call[] calldata calls)
public
returns (uint256 blockNumber, bytes[] memory returnData)
{
blockNumber = block.number;
returnData = new bytes[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
require(success, "Multicall aggregate: call failed");
returnData[i] = ret;
}
}
// 尝试聚合调用(允许部分失败)
function tryAggregate(bool requireSuccess, Call[] calldata calls)
public
returns (Result[] memory returnData)
{
returnData = new Result[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
if (requireSuccess) {
require(success, "Multicall tryAggregate: call failed");
}
returnData[i] = Result(success, ret);
}
}
// 静态聚合调用(只读)
function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls)
external
returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)
{
blockNumber = block.number;
blockHash = blockhash(block.number);
returnData = tryAggregate(requireSuccess, calls);
}
}
JavaScript 实现示例:
class MulticallManager {
constructor(provider, multicallAddress) {
this.provider = provider;
this.multicallAddress = multicallAddress;
this.multicall = new ethers.Contract(
multicallAddress,
multicallABI,
provider
);
this.calls = [];
}
// 添加合约调用
call(contract, method, params = []) {
const callData = contract.interface.encodeFunctionData(method, params);
this.calls.push({
target: contract.address,
callData: callData,
contract: contract,
method: method,
decode: (data) => contract.interface.decodeFunctionResult(method, data)
});
return this;
}
// 执行聚合调用
async execute() {
if (this.calls.length === 0) {
return [];
}
const callRequests = this.calls.map(call => ({
target: call.target,
callData: call.callData
}));
try {
// 使用 tryAggregate 允许部分失败
const results = await this.multicall.tryAggregate(false, callRequests);
return results.map((result, index) => {
const call = this.calls[index];
if (result.success) {
try {
return {
success: true,
data: call.decode(result.returnData)
};
} catch (error) {
return {
success: false,
error: `解码失败: ${error.message}`
};
}
} else {
return {
success: false,
error: "调用失败"
};
}
});
} finally {
this.calls = []; // 清空调用列表
}
}
}
实际应用示例:
// DeFi 协议数据聚合
async function getDeFiPortfolio(userAddress) {
const multicaller = new MulticallManager(provider, MULTICALL_ADDRESS);
// 添加多个协议的查询
multicaller
.call(usdcContract, "balanceOf", [userAddress])
.call(daiContract, "balanceOf", [userAddress])
.call(uniswapV3Position, "balanceOf", [userAddress])
.call(aaveContract, "getUserAccountData", [userAddress])
.call(compoundContract, "getAccountLiquidity", [userAddress]);
const results = await multicaller.execute();
return {
usdcBalance: results[0].success ? ethers.utils.formatUnits(results[0].data[0], 6) : "0",
daiBalance: results[1].success ? ethers.utils.formatUnits(results[1].data[0], 18) : "0",
nftCount: results[2].success ? results[2].data[0].toString() : "0",
aaveData: results[3].success ? results[3].data : null,
compoundLiquidity: results[4].success ? results[4].data : null
};
}
效率对比分析:
// 传统方式(多次网络请求)
async function traditionalApproach(userAddress) {
const start = Date.now();
const balance1 = await token1.balanceOf(userAddress);
const balance2 = await token2.balanceOf(userAddress);
const balance3 = await token3.balanceOf(userAddress);
const allowance1 = await token1.allowance(userAddress, spenderAddress);
const allowance2 = await token2.allowance(userAddress, spenderAddress);
const end = Date.now();
console.log(`传统方式耗时: ${end - start}ms`);
return { balance1, balance2, balance3, allowance1, allowance2 };
}
// Multicall 方式(单次网络请求)
async function multicallApproach(userAddress) {
const start = Date.now();
const multicaller = new MulticallManager(provider, MULTICALL_ADDRESS);
const results = await multicaller
.call(token1, "balanceOf", [userAddress])
.call(token2, "balanceOf", [userAddress])
.call(token3, "balanceOf", [userAddress])
.call(token1, "allowance", [userAddress, spenderAddress])
.call(token2, "allowance", [userAddress, spenderAddress])
.execute();
const end = Date.now();
console.log(`Multicall 方式耗时: ${end - start}ms`);
return results;
}
Multicall 的优势:
网络效率:
数据一致性:
成本优化:
使用场景:
注意事项:
How to handle reentrancy attack risks in contract calls?
How to handle reentrancy attack risks in contract calls?
考察点:安全防护意识。
答案:
重入攻击是智能合约安全中的一个重要威胁,发生在合约调用外部合约时,外部合约反过来调用原合约的函数,可能导致状态不一致和资产损失。处理重入攻击需要在合约设计和前端交互中采用多层防护措施。
重入攻击的工作原理:
// 存在重入风险的合约
contract Vulnerable {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "余额不足");
// 危险:在更新状态前进行外部调用
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "转账失败");
// 状态更新在外部调用之后(太晚了)
balances[msg.sender] -= amount;
}
}
// 攻击者合约
contract Attacker {
Vulnerable vulnerable;
uint256 public attackCount;
constructor(address _vulnerable) {
vulnerable = Vulnerable(_vulnerable);
}
function attack() external payable {
vulnerable.withdraw(msg.value);
}
// 重入攻击的核心:在 receive 函数中再次调用 withdraw
receive() external payable {
if (attackCount < 3) { // 限制攻击次数避免 gas 耗尽
attackCount++;
vulnerable.withdraw(msg.value);
}
}
}
合约端防护措施:
contract Secure {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
// 1. 检查 (Checks)
require(balances[msg.sender] >= amount, "余额不足");
// 2. 效果 (Effects) - 先更新状态
balances[msg.sender] -= amount;
// 3. 交互 (Interactions) - 最后进行外部调用
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "转账失败");
}
}
contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "重入攻击检测");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
contract SecureWithGuard is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount, "余额不足");
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "转账失败");
}
}
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MySecureContract is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount, "余额不足");
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "提取失败");
}
}
前端防护措施:
class ReentrancyProtection {
constructor() {
this.pendingTransactions = new Set();
}
async safeWithdraw(contract, amount) {
const txKey = `withdraw_${amount}_${Date.now()}`;
// 检查是否有相同操作在进行
if (this.pendingTransactions.has(txKey)) {
throw new Error("已有相同操作在进行中");
}
try {
this.pendingTransactions.add(txKey);
// 执行提取操作
const tx = await contract.withdraw(amount);
await tx.wait();
return tx;
} finally {
this.pendingTransactions.delete(txKey);
}
}
}
class WithdrawManager {
constructor(contract) {
this.contract = contract;
this.isWithdrawing = false;
}
async withdraw(amount) {
if (this.isWithdrawing) {
throw new Error("提取操作进行中,请等待完成");
}
try {
this.isWithdrawing = true;
// 禁用提取按钮
this.updateUI({ withdrawing: true });
// 执行提取
const tx = await this.contract.withdraw(amount, {
gasLimit: 100000 // 限制 Gas 使用
});
console.log("提取交易发送:", tx.hash);
// 等待确认
const receipt = await tx.wait();
console.log("提取完成:", receipt);
return receipt;
} finally {
this.isWithdrawing = false;
this.updateUI({ withdrawing: false });
}
}
updateUI(state) {
// 更新用户界面状态
const withdrawButton = document.getElementById('withdrawButton');
withdrawButton.disabled = state.withdrawing;
withdrawButton.textContent = state.withdrawing ? '提取中...' : '提取';
}
}
async function secureContractCall(contract, method, params, options = {}) {
try {
// 估算 Gas 使用量
const gasEstimate = await contract.estimateGas[method](...params);
// 设置合理的 Gas 限制(防止无限循环)
const gasLimit = gasEstimate.mul(110).div(100); // +10% 缓冲
const maxGasLimit = 500000; // 设置绝对上限
const finalGasLimit = gasLimit.gt(maxGasLimit) ? maxGasLimit : gasLimit;
// 执行调用
const tx = await contract[method](...params, {
gasLimit: finalGasLimit,
...options
});
return tx;
} catch (error) {
if (error.code === 'UNPREDICTABLE_GAS_LIMIT') {
throw new Error("交易可能失败,请检查合约状态");
}
throw error;
}
}
检测和监控:
// 监控重入攻击迹象
class ReentrancyMonitor {
constructor(contract, provider) {
this.contract = contract;
this.provider = provider;
this.suspiciousPatterns = new Map();
}
async monitorTransactions() {
this.contract.on("*", (event) => {
this.analyzeEvent(event);
});
}
analyzeEvent(event) {
const { transactionHash, blockNumber } = event;
// 检测短时间内的多次调用
const key = `${event.args?.user || event.address}_${blockNumber}`;
if (this.suspiciousPatterns.has(key)) {
const count = this.suspiciousPatterns.get(key) + 1;
this.suspiciousPatterns.set(key, count);
if (count > 3) {
console.warn(`检测到可疑的重入模式: ${transactionHash}`);
this.alertSecurityTeam(event);
}
} else {
this.suspiciousPatterns.set(key, 1);
}
// 清理旧数据
setTimeout(() => {
this.suspiciousPatterns.delete(key);
}, 60000); // 1分钟后清理
}
alertSecurityTeam(event) {
// 发送安全警报
console.error("安全警报:检测到潜在的重入攻击", event);
}
}
最佳实践:
合约设计:
前端开发:
安全审计:
注意事项:
transfer() 和 send() 虽然有 Gas 限制,但不是完美的解决方案What is the proxy contract pattern? How to interact with upgradeable contracts?
What is the proxy contract pattern? How to interact with upgradeable contracts?
考察点:合约升级机制。
答案:
代理合约模式是一种允许智能合约升级的设计模式,通过将合约逻辑与存储分离来实现。代理合约负责存储数据和接收调用,而将实际的业务逻辑委托给可替换的实现合约。这种模式使得在不改变合约地址的情况下升级业务逻辑成为可能。
代理合约工作原理:
// 简单代理合约示例
contract SimpleProxy {
address public implementation;
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin, "只有管理员可以操作");
_;
}
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
// 升级实现合约
function upgrade(address newImplementation) external onlyAdmin {
implementation = newImplementation;
}
// 委托调用
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
OpenZeppelin 透明代理模式:
// 使用 OpenZeppelin 的透明代理
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
// 实现合约 V1
contract ImplementationV1 {
uint256 public value;
function initialize(uint256 _value) public {
value = _value;
}
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
// 实现合约 V2 (升级版本)
contract ImplementationV2 {
uint256 public value;
uint256 public newFeature; // 新增功能
function initialize(uint256 _value) public {
value = _value;
}
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
// 新增功能
function setNewFeature(uint256 _newFeature) public {
newFeature = _newFeature;
}
}
部署和升级流程:
// 1. 部署实现合约
const ImplementationV1 = await ethers.getContractFactory("ImplementationV1");
const implementationV1 = await ImplementationV1.deploy();
// 2. 部署代理管理员
const ProxyAdmin = await ethers.getContractFactory("ProxyAdmin");
const proxyAdmin = await ProxyAdmin.deploy();
// 3. 准备初始化数据
const initData = implementationV1.interface.encodeFunctionData("initialize", [100]);
// 4. 部署透明代理
const TransparentUpgradeableProxy = await ethers.getContractFactory("TransparentUpgradeableProxy");
const proxy = await TransparentUpgradeableProxy.deploy(
implementationV1.address,
proxyAdmin.address,
initData
);
console.log("代理合约地址:", proxy.address);
console.log("实现合约地址:", implementationV1.address);
前端与代理合约交互:
class UpgradeableContractManager {
constructor(proxyAddress, currentABI, provider, signer) {
this.proxyAddress = proxyAddress;
this.provider = provider;
this.signer = signer;
this.contract = new ethers.Contract(proxyAddress, currentABI, signer);
}
// 获取当前实现合约地址
async getImplementation() {
// 透明代理的实现地址存储在特定槽位
const IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
const implementationHex = await this.provider.getStorageAt(
this.proxyAddress,
IMPLEMENTATION_SLOT
);
return ethers.utils.getAddress(implementationHex.slice(-40));
}
// 检查合约版本
async getVersion() {
try {
// 如果实现合约有版本函数
return await this.contract.version();
} catch (error) {
console.log("合约没有版本函数");
return null;
}
}
// 安全调用合约函数
async safeCall(methodName, params = []) {
try {
// 检查函数是否存在
if (!this.contract.interface.getFunction(methodName)) {
throw new Error(`函数 ${methodName} 不存在`);
}
const result = await this.contract[methodName](...params);
return result;
} catch (error) {
console.error(`调用 ${methodName} 失败:`, error);
// 检查是否因为升级导致 ABI 不匹配
await this.checkForUpgrade();
throw error;
}
}
// 检查合约是否已升级
async checkForUpgrade() {
const currentImpl = await this.getImplementation();
if (this.lastKnownImplementation && this.lastKnownImplementation !== currentImpl) {
console.log("检测到合约升级:");
console.log("旧实现:", this.lastKnownImplementation);
console.log("新实现:", currentImpl);
// 触发升级处理逻辑
await this.handleUpgrade(currentImpl);
}
this.lastKnownImplementation = currentImpl;
}
// 处理合约升级
async handleUpgrade(newImplementation) {
// 通知用户合约已升级
console.log("合约已升级,可能需要刷新 ABI");
// 这里可以实现自动获取新 ABI 的逻辑
// 或者提示用户手动更新
}
// 更新 ABI(升级后)
updateABI(newABI) {
this.contract = new ethers.Contract(this.proxyAddress, newABI, this.signer);
}
}
升级流程示例:
async function upgradeContract() {
try {
// 1. 部署新的实现合约
const ImplementationV2 = await ethers.getContractFactory("ImplementationV2");
const implementationV2 = await ImplementationV2.deploy();
await implementationV2.deployed();
console.log("新实现合约地址:", implementationV2.address);
// 2. 通过 ProxyAdmin 执行升级
const proxyAdmin = new ethers.Contract(proxyAdminAddress, proxyAdminABI, signer);
const upgradeTx = await proxyAdmin.upgrade(
proxyAddress, // 代理合约地址
implementationV2.address // 新实现合约地址
);
await upgradeTx.wait();
console.log("升级完成,交易哈希:", upgradeTx.hash);
// 3. 验证升级
const contractManager = new UpgradeableContractManager(
proxyAddress,
implementationV2ABI,
provider,
signer
);
const newImplementation = await contractManager.getImplementation();
console.log("当前实现地址:", newImplementation);
// 4. 测试新功能
await contractManager.safeCall("setNewFeature", [200]);
const newFeatureValue = await contractManager.safeCall("newFeature");
console.log("新功能值:", newFeatureValue.toString());
} catch (error) {
console.error("升级失败:", error);
}
}
升级兼容性检查:
class UpgradeCompatibilityChecker {
constructor(oldABI, newABI) {
this.oldABI = oldABI;
this.newABI = newABI;
}
checkCompatibility() {
const issues = [];
// 检查现有函数是否保持兼容
const oldFunctions = this.getFunctions(this.oldABI);
const newFunctions = this.getFunctions(this.newABI);
oldFunctions.forEach(oldFunc => {
const newFunc = newFunctions.find(f => f.name === oldFunc.name);
if (!newFunc) {
issues.push(`函数 ${oldFunc.name} 在新版本中被移除`);
} else if (!this.isSignatureCompatible(oldFunc, newFunc)) {
issues.push(`函数 ${oldFunc.name} 的签名发生了不兼容的变化`);
}
});
return {
compatible: issues.length === 0,
issues: issues
};
}
getFunctions(abi) {
return abi.filter(item => item.type === 'function');
}
isSignatureCompatible(oldFunc, newFunc) {
// 检查参数数量和类型
if (oldFunc.inputs.length !== newFunc.inputs.length) {
return false;
}
for (let i = 0; i < oldFunc.inputs.length; i++) {
if (oldFunc.inputs[i].type !== newFunc.inputs[i].type) {
return false;
}
}
return true;
}
}
监听升级事件:
// 监听合约升级事件
async function monitorUpgrades(proxyAddress) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
// 监听 Upgraded 事件
const upgradeEventSignature = "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b";
const filter = {
address: proxyAddress,
topics: [upgradeEventSignature]
};
provider.on(filter, (log) => {
console.log("检测到合约升级:");
// 解析事件数据
const newImplementation = ethers.utils.getAddress("0x" + log.topics[1].slice(-40));
console.log("新实现地址:", newImplementation);
// 通知应用处理升级
handleContractUpgrade(newImplementation);
});
}
function handleContractUpgrade(newImplementation) {
// 更新应用中的合约实例
// 可能需要重新加载 ABI 或刷新界面
console.log("处理合约升级...");
}
最佳实践:
升级前准备:
前端适配:
安全考虑:
注意事项:
How to gracefully handle transaction failures and rollbacks?
How to gracefully handle transaction failures and rollbacks?
考察点:错误处理最佳实践。
答案:
优雅地处理交易失败和回滚是构建可靠 DApp 的关键技能。需要在合约设计、前端交互和用户体验等多个层面实施完善的错误处理机制,确保用户资金安全和操作的可预测性。
交易失败的分类:
合约层面的错误处理:
contract SafeTransactionHandler {
event TransactionAttempted(address indexed user, string operation, bool success);
event TransactionReverted(address indexed user, string reason);
// 自定义错误(Gas 效率更高)
error InsufficientBalance(uint256 required, uint256 available);
error InvalidOperation(string reason);
error OperationTimeout();
mapping(address => uint256) public balances;
mapping(bytes32 => bool) public processedTransactions;
modifier validAmount(uint256 amount) {
if (amount == 0) revert InvalidOperation("Amount cannot be zero");
if (balances[msg.sender] < amount) {
revert InsufficientBalance(amount, balances[msg.sender]);
}
_;
}
modifier nonceProtection(bytes32 txHash) {
require(!processedTransactions[txHash], "Transaction already processed");
processedTransactions[txHash] = true;
_;
}
// 安全的提取函数
function safeWithdraw(uint256 amount, bytes32 nonce)
external
validAmount(amount)
nonceProtection(nonce)
{
uint256 oldBalance = balances[msg.sender];
try this.performWithdraw(msg.sender, amount) {
emit TransactionAttempted(msg.sender, "withdraw", true);
} catch Error(string memory reason) {
// 处理 require 失败
balances[msg.sender] = oldBalance; // 恢复状态
emit TransactionReverted(msg.sender, reason);
revert(reason);
} catch (bytes memory lowLevelData) {
// 处理低级别错误
balances[msg.sender] = oldBalance;
emit TransactionReverted(msg.sender, "Low level error");
revert("Transaction failed");
}
}
function performWithdraw(address user, uint256 amount) external {
require(msg.sender == address(this), "Internal only");
balances[user] -= amount;
(bool success, ) = user.call{value: amount}("");
if (!success) {
revert("Transfer failed");
}
}
}
前端错误处理框架:
class TransactionManager {
constructor(provider, signer) {
this.provider = provider;
this.signer = signer;
this.pendingTransactions = new Map();
this.errorHandlers = new Map();
}
// 注册错误处理器
registerErrorHandler(errorType, handler) {
this.errorHandlers.set(errorType, handler);
}
// 安全执行交易
async safeExecute(contract, method, params = [], options = {}) {
const txId = this.generateTxId();
try {
// 1. 预检查
await this.preflightCheck(contract, method, params, options);
// 2. 估算 Gas
const gasEstimate = await this.estimateGas(contract, method, params);
const finalOptions = {
...options,
gasLimit: gasEstimate.mul(110).div(100) // +10% 缓冲
};
// 3. 执行交易
this.pendingTransactions.set(txId, { status: 'pending' });
const tx = await contract[method](...params, finalOptions);
this.pendingTransactions.set(txId, {
status: 'submitted',
hash: tx.hash,
transaction: tx
});
// 4. 等待确认
const receipt = await this.waitForConfirmation(tx, txId);
this.pendingTransactions.set(txId, {
status: 'confirmed',
receipt: receipt
});
return receipt;
} catch (error) {
await this.handleError(error, txId, { contract, method, params, options });
throw error;
} finally {
// 清理
setTimeout(() => this.pendingTransactions.delete(txId), 300000); // 5分钟后清理
}
}
// 预检查
async preflightCheck(contract, method, params, options) {
// 检查网络连接
try {
await this.provider.getBlockNumber();
} catch (error) {
throw new Error("网络连接失败");
}
// 检查账户余额
const balance = await this.signer.getBalance();
const requiredBalance = options.value || 0;
if (balance.lt(requiredBalance)) {
throw new Error("账户余额不足");
}
// 模拟执行
try {
await contract.callStatic[method](...params, options);
} catch (error) {
throw new Error(`模拟执行失败: ${error.reason || error.message}`);
}
}
// Gas 估算
async estimateGas(contract, method, params, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await contract.estimateGas[method](...params);
} catch (error) {
if (i === retries - 1) {
throw new Error(`Gas 估算失败: ${error.reason || error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// 等待确认
async waitForConfirmation(tx, txId, confirmations = 1) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("交易确认超时")), 300000); // 5分钟超时
});
const confirmationPromise = tx.wait(confirmations);
try {
const receipt = await Promise.race([confirmationPromise, timeoutPromise]);
if (receipt.status === 0) {
throw new Error("交易执行失败");
}
return receipt;
} catch (error) {
this.pendingTransactions.set(txId, { status: 'failed', error });
throw error;
}
}
// 错误处理
async handleError(error, txId, context) {
const errorType = this.classifyError(error);
this.pendingTransactions.set(txId, {
status: 'failed',
error: error,
errorType: errorType,
context: context
});
// 调用注册的错误处理器
const handler = this.errorHandlers.get(errorType);
if (handler) {
await handler(error, context);
}
// 记录错误
console.error(`交易失败 [${errorType}]:`, error);
}
// 错误分类
classifyError(error) {
if (error.code === 'INSUFFICIENT_FUNDS') return 'INSUFFICIENT_FUNDS';
if (error.code === 'UNPREDICTABLE_GAS_LIMIT') return 'GAS_ESTIMATION_FAILED';
if (error.code === 'NETWORK_ERROR') return 'NETWORK_ERROR';
if (error.code === 4001) return 'USER_REJECTED';
if (error.reason) return 'CONTRACT_ERROR';
return 'UNKNOWN_ERROR';
}
generateTxId() {
return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
重试机制实现:
class RetryableTransaction {
constructor(txManager, maxRetries = 3) {
this.txManager = txManager;
this.maxRetries = maxRetries;
}
async executeWithRetry(contract, method, params, options = {}) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
console.log(`交易尝试 ${attempt}/${this.maxRetries}`);
const result = await this.txManager.safeExecute(
contract,
method,
params,
this.adjustOptionsForRetry(options, attempt)
);
return result;
} catch (error) {
lastError = error;
if (!this.shouldRetry(error, attempt)) {
throw error;
}
// 等待后重试
const delay = this.calculateDelay(attempt);
console.log(`${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
shouldRetry(error, attempt) {
if (attempt >= this.maxRetries) return false;
// 不重试的错误类型
const nonRetryableErrors = [
'USER_REJECTED',
'INSUFFICIENT_FUNDS',
'CONTRACT_ERROR'
];
const errorType = this.classifyError(error);
return !nonRetryableErrors.includes(errorType);
}
adjustOptionsForRetry(options, attempt) {
// 逐步增加 Gas 价格
if (options.gasPrice) {
const multiplier = 1 + (attempt - 1) * 0.2; // 每次增加20%
return {
...options,
gasPrice: options.gasPrice.mul(Math.floor(multiplier * 100)).div(100)
};
}
return options;
}
calculateDelay(attempt) {
return Math.min(1000 * Math.pow(2, attempt - 1), 30000); // 指数退避,最大30秒
}
}
用户界面状态管理:
class TransactionStateManager {
constructor() {
this.transactions = new Map();
this.listeners = new Set();
}
// 添加交易
addTransaction(txId, txData) {
this.transactions.set(txId, {
...txData,
timestamp: Date.now(),
status: 'pending'
});
this.notifyListeners('added', txId);
}
// 更新交易状态
updateTransaction(txId, updates) {
const existing = this.transactions.get(txId);
if (existing) {
this.transactions.set(txId, { ...existing, ...updates });
this.notifyListeners('updated', txId);
}
}
// 获取交易状态
getTransaction(txId) {
return this.transactions.get(txId);
}
// 获取所有待处理交易
getPendingTransactions() {
return Array.from(this.transactions.entries())
.filter(([_, tx]) => tx.status === 'pending')
.map(([id, tx]) => ({ id, ...tx }));
}
// 添加监听器
addListener(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
notifyListeners(event, txId) {
this.listeners.forEach(listener => {
try {
listener(event, txId, this.getTransaction(txId));
} catch (error) {
console.error("监听器错误:", error);
}
});
}
}
// React Hook 示例
function useTransactionState() {
const [transactions, setTransactions] = useState(new Map());
const stateManager = useRef(new TransactionStateManager());
useEffect(() => {
const unsubscribe = stateManager.current.addListener((event, txId, txData) => {
setTransactions(prev => new Map(prev.set(txId, txData)));
});
return unsubscribe;
}, []);
const submitTransaction = useCallback(async (contract, method, params, options) => {
const txId = generateTxId();
try {
stateManager.current.addTransaction(txId, {
contract: contract.address,
method,
params,
status: 'pending'
});
const result = await txManager.safeExecute(contract, method, params, options);
stateManager.current.updateTransaction(txId, {
status: 'confirmed',
result: result
});
return result;
} catch (error) {
stateManager.current.updateTransaction(txId, {
status: 'failed',
error: error.message
});
throw error;
}
}, []);
return {
transactions: Array.from(transactions.values()),
submitTransaction
};
}
最佳实践:
注意事项:
What is event filtering? How to efficiently query historical events?
What is event filtering? How to efficiently query historical events?
考察点:事件查询优化。
答案:
事件过滤是通过指定条件来筛选智能合约事件的技术,允许高效地从大量区块链数据中提取特定信息。通过合理的过滤策略和查询优化,可以显著提高历史数据检索的性能和准确性。
事件过滤基础概念:
// 示例合约事件
contract TokenContract {
event Transfer(
address indexed from, // indexed 参数可以被过滤
address indexed to, // indexed 参数可以被过滤
uint256 value // 非 indexed 参数不能直接过滤
);
event Approval(
address indexed owner, // indexed 参数
address indexed spender, // indexed 参数
uint256 value
);
event CustomEvent(
address indexed user,
uint256 indexed eventType,
string data // 非 indexed 数据
);
}
基本事件过滤:
// 1. 查询所有 Transfer 事件
const transferFilter = contract.filters.Transfer();
const allTransfers = await contract.queryFilter(transferFilter, fromBlock, toBlock);
// 2. 查询特定用户的转入事件
const userReceiveFilter = contract.filters.Transfer(null, userAddress);
const userReceives = await contract.queryFilter(userReceiveFilter, fromBlock, toBlock);
// 3. 查询特定用户的转出事件
const userSendFilter = contract.filters.Transfer(userAddress, null);
const userSends = await contract.queryFilter(userSendFilter, fromBlock, toBlock);
// 4. 查询两个地址之间的转账
const specificTransferFilter = contract.filters.Transfer(senderAddress, receiverAddress);
const specificTransfers = await contract.queryFilter(specificTransferFilter, fromBlock, toBlock);
高级过滤技术:
class EventQueryManager {
constructor(contract, provider) {
this.contract = contract;
this.provider = provider;
this.cache = new Map();
}
// 高效的历史事件查询
async queryEventsOptimized(eventName, filters = {}, options = {}) {
const {
fromBlock = 0,
toBlock = 'latest',
batchSize = 10000,
useCache = true
} = options;
const cacheKey = this.getCacheKey(eventName, filters, fromBlock, toBlock);
if (useCache && this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
try {
const events = await this.batchQueryEvents(
eventName,
filters,
fromBlock,
toBlock,
batchSize
);
if (useCache) {
this.cache.set(cacheKey, events);
}
return events;
} catch (error) {
console.error("事件查询失败:", error);
throw error;
}
}
// 批量查询事件(分页处理)
async batchQueryEvents(eventName, filters, fromBlock, toBlock, batchSize) {
const allEvents = [];
let currentBlock = typeof fromBlock === 'number' ? fromBlock : 0;
const endBlock = toBlock === 'latest' ? await this.provider.getBlockNumber() : toBlock;
while (currentBlock <= endBlock) {
const batchEndBlock = Math.min(currentBlock + batchSize - 1, endBlock);
try {
const batchEvents = await this.querySingleBatch(
eventName,
filters,
currentBlock,
batchEndBlock
);
allEvents.push(...batchEvents);
console.log(`查询区块 ${currentBlock} - ${batchEndBlock}: ${batchEvents.length} 个事件`);
currentBlock = batchEndBlock + 1;
// 添加延迟避免速率限制
await this.rateLimitDelay();
} catch (error) {
if (error.code === -32005) { // 查询范围过大
batchSize = Math.floor(batchSize / 2);
if (batchSize < 100) {
throw new Error("查询范围过小仍然失败");
}
continue;
}
throw error;
}
}
return allEvents;
}
// 单批次查询
async querySingleBatch(eventName, filters, fromBlock, toBlock) {
const filter = this.contract.filters[eventName](...Object.values(filters));
return await this.contract.queryFilter(filter, fromBlock, toBlock);
}
// 速率限制延迟
async rateLimitDelay() {
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms 延迟
}
// 生成缓存键
getCacheKey(eventName, filters, fromBlock, toBlock) {
return `${eventName}_${JSON.stringify(filters)}_${fromBlock}_${toBlock}`;
}
// 清除缓存
clearCache() {
this.cache.clear();
}
}
复杂事件查询示例:
// 用户交易历史查询
async function getUserTransactionHistory(userAddress, tokenContract) {
const queryManager = new EventQueryManager(tokenContract, provider);
// 查询用户相关的所有转账事件
const [sentTransfers, receivedTransfers] = await Promise.all([
queryManager.queryEventsOptimized('Transfer', { from: userAddress }),
queryManager.queryEventsOptimized('Transfer', { to: userAddress })
]);
// 合并和排序事件
const allTransfers = [...sentTransfers, ...receivedTransfers]
.sort((a, b) => a.blockNumber - b.blockNumber);
// 处理事件数据
const transactionHistory = allTransfers.map(event => ({
hash: event.transactionHash,
blockNumber: event.blockNumber,
from: event.args.from,
to: event.args.to,
amount: ethers.utils.formatEther(event.args.value),
type: event.args.from.toLowerCase() === userAddress.toLowerCase() ? 'sent' : 'received',
timestamp: null // 需要获取区块时间戳
}));
// 批量获取时间戳
const timestamps = await getBatchTimestamps(
transactionHistory.map(tx => tx.blockNumber)
);
transactionHistory.forEach((tx, index) => {
tx.timestamp = timestamps[tx.blockNumber];
});
return transactionHistory;
}
// 批量获取区块时间戳
async function getBatchTimestamps(blockNumbers) {
const uniqueBlocks = [...new Set(blockNumbers)];
const timestampMap = new Map();
// 并发获取区块信息
const blockPromises = uniqueBlocks.map(async (blockNumber) => {
try {
const block = await provider.getBlock(blockNumber);
return { blockNumber, timestamp: block.timestamp };
} catch (error) {
console.error(`获取区块 ${blockNumber} 失败:`, error);
return { blockNumber, timestamp: null };
}
});
const blocks = await Promise.all(blockPromises);
blocks.forEach(({ blockNumber, timestamp }) => {
timestampMap.set(blockNumber, timestamp);
});
return timestampMap;
}
实时事件监听优化:
class RealTimeEventMonitor {
constructor(contract, provider) {
this.contract = contract;
this.provider = provider;
this.listeners = new Map();
this.isListening = false;
}
// 开始监听特定事件
startListening(eventName, filters = {}, callback) {
const filterKey = this.getFilterKey(eventName, filters);
if (this.listeners.has(filterKey)) {
console.warn("该过滤器已在监听中");
return;
}
const filter = this.contract.filters[eventName](...Object.values(filters));
const wrappedCallback = (log) => {
try {
const parsedLog = this.contract.interface.parseLog(log);
callback(parsedLog, log);
} catch (error) {
console.error("事件解析失败:", error);
}
};
this.provider.on(filter, wrappedCallback);
this.listeners.set(filterKey, { filter, callback: wrappedCallback });
console.log(`开始监听事件: ${eventName}`);
this.isListening = true;
}
// 停止监听特定事件
stopListening(eventName, filters = {}) {
const filterKey = this.getFilterKey(eventName, filters);
const listener = this.listeners.get(filterKey);
if (listener) {
this.provider.off(listener.filter, listener.callback);
this.listeners.delete(filterKey);
console.log(`停止监听事件: ${eventName}`);
}
if (this.listeners.size === 0) {
this.isListening = false;
}
}
// 停止所有监听
stopAllListening() {
this.listeners.forEach((listener) => {
this.provider.off(listener.filter, listener.callback);
});
this.listeners.clear();
this.isListening = false;
console.log("停止所有事件监听");
}
getFilterKey(eventName, filters) {
return `${eventName}_${JSON.stringify(filters)}`;
}
}
事件查询性能优化:
// 智能区块范围优化
class SmartBlockRangeOptimizer {
constructor(provider) {
this.provider = provider;
this.maxBatchSize = 50000;
this.minBatchSize = 1000;
}
// 自适应批次大小
async getOptimalBatchSize(contract, eventName, startBlock) {
let batchSize = this.maxBatchSize;
while (batchSize >= this.minBatchSize) {
try {
const filter = contract.filters[eventName]();
const testEndBlock = startBlock + batchSize - 1;
// 测试查询
await contract.queryFilter(filter, startBlock, testEndBlock);
return batchSize;
} catch (error) {
if (error.code === -32005) { // 范围过大
batchSize = Math.floor(batchSize / 2);
} else {
throw error;
}
}
}
return this.minBatchSize;
}
// 基于事件密度调整批次大小
adjustBatchSizeByDensity(eventCount, batchSize) {
const density = eventCount / batchSize;
if (density > 0.1) { // 事件密度高,减小批次
return Math.max(Math.floor(batchSize * 0.7), this.minBatchSize);
} else if (density < 0.01) { // 事件密度低,增大批次
return Math.min(Math.floor(batchSize * 1.5), this.maxBatchSize);
}
return batchSize;
}
}
// 使用布隆过滤器预筛选
class BloomFilterEventQuery {
constructor() {
this.addressBloom = new Set(); // 简化版布隆过滤器
}
// 添加地址到过滤器
addAddress(address) {
this.addressBloom.add(address.toLowerCase());
}
// 快速检查地址是否可能存在
mightContain(address) {
return this.addressBloom.has(address.toLowerCase());
}
// 预过滤事件
preFilterEvents(events, targetAddresses) {
targetAddresses.forEach(addr => this.addAddress(addr));
return events.filter(event => {
const { from, to } = event.args || event;
return this.mightContain(from) || this.mightContain(to);
});
}
}
最佳实践:
性能考虑:
How to implement transaction retry mechanisms? What issues need attention?
How to implement transaction retry mechanisms? What issues need attention?
考察点:交易重试策略。
答案:
交易重试机制是处理区块链网络不稳定性和临时故障的重要技术。合理的重试策略可以提高交易成功率和用户体验,但需要注意避免重复执行、nonce 管理、Gas 费用控制等关键问题。
重试机制的核心组件:
class TransactionRetryManager {
constructor(signer, options = {}) {
this.signer = signer;
this.options = {
maxRetries: options.maxRetries || 3,
baseDelay: options.baseDelay || 1000,
maxDelay: options.maxDelay || 30000,
backoffMultiplier: options.backoffMultiplier || 2,
gasPriceIncrement: options.gasPriceIncrement || 1.2, // 每次重试增加20%
...options
};
this.activeTransactions = new Map();
this.nonceManager = new NonceManager(signer);
}
// 执行带重试的交易
async executeWithRetry(transactionRequest, retryOptions = {}) {
const txId = this.generateTxId();
const finalOptions = { ...this.options, ...retryOptions };
let attempt = 0;
let lastError;
while (attempt < finalOptions.maxRetries) {
attempt++;
try {
console.log(`交易尝试 ${attempt}/${finalOptions.maxRetries}`);
// 获取并锁定 nonce
const nonce = await this.nonceManager.getNextNonce();
const adjustedTx = await this.adjustTransactionForRetry(
transactionRequest,
attempt,
finalOptions
);
adjustedTx.nonce = nonce;
// 记录活跃交易
this.activeTransactions.set(txId, {
nonce,
attempt,
originalRequest: transactionRequest,
status: 'pending'
});
const tx = await this.signer.sendTransaction(adjustedTx);
// 等待确认
const receipt = await this.waitForConfirmation(tx, finalOptions);
// 清理
this.activeTransactions.delete(txId);
this.nonceManager.confirmNonce(nonce);
return receipt;
} catch (error) {
lastError = error;
// 检查是否应该重试
if (!this.shouldRetry(error, attempt, finalOptions)) {
break;
}
// 释放 nonce(如果交易未提交)
if (this.isPreSubmissionError(error)) {
this.nonceManager.releaseNonce(
this.activeTransactions.get(txId)?.nonce
);
}
// 等待后重试
const delay = this.calculateRetryDelay(attempt, finalOptions);
console.log(`${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// 清理失败的交易
this.activeTransactions.delete(txId);
throw new Error(`交易重试失败,尝试次数: ${attempt}, 最后错误: ${lastError.message}`);
}
// 调整重试参数
async adjustTransactionForRetry(originalTx, attempt, options) {
const adjustedTx = { ...originalTx };
// 调整 Gas 价格
if (originalTx.gasPrice) {
const multiplier = Math.pow(options.gasPriceIncrement, attempt - 1);
adjustedTx.gasPrice = originalTx.gasPrice.mul(
Math.floor(multiplier * 100)
).div(100);
}
// EIP-1559 网络的费用调整
if (originalTx.maxFeePerGas) {
const multiplier = Math.pow(options.gasPriceIncrement, attempt - 1);
adjustedTx.maxFeePerGas = originalTx.maxFeePerGas.mul(
Math.floor(multiplier * 100)
).div(100);
if (originalTx.maxPriorityFeePerGas) {
adjustedTx.maxPriorityFeePerGas = originalTx.maxPriorityFeePerGas.mul(
Math.floor(multiplier * 100)
).div(100);
}
}
// 调整 Gas Limit(如果之前因为 Gas 不足失败)
if (originalTx.gasLimit && attempt > 1) {
adjustedTx.gasLimit = originalTx.gasLimit.mul(110).div(100); // 增加10%
}
return adjustedTx;
}
// 判断是否应该重试
shouldRetry(error, attempt, options) {
if (attempt >= options.maxRetries) {
return false;
}
// 不应重试的错误类型
const nonRetryableErrors = [
'ACTION_REJECTED', // 用户拒绝
'INSUFFICIENT_FUNDS', // 余额不足
'INVALID_ARGUMENT', // 参数错误
'CONTRACT_ERROR' // 合约逻辑错误
];
const errorCode = error.code || error.reason;
// 检查错误码
if (nonRetryableErrors.some(code =>
errorCode.toString().includes(code) ||
error.message.includes(code)
)) {
return false;
}
// 可重试的错误类型
const retryableErrors = [
'NETWORK_ERROR',
'TIMEOUT',
'SERVER_ERROR',
'REPLACEMENT_UNDERPRICED',
'NONCE_EXPIRED'
];
return retryableErrors.some(code =>
errorCode.toString().includes(code) ||
error.message.includes(code)
);
}
// 计算重试延迟
calculateRetryDelay(attempt, options) {
const delay = options.baseDelay * Math.pow(options.backoffMultiplier, attempt - 1);
return Math.min(delay, options.maxDelay);
}
// 判断是否为提交前错误
isPreSubmissionError(error) {
const preSubmissionErrors = [
'INSUFFICIENT_FUNDS',
'INVALID_ARGUMENT',
'UNPREDICTABLE_GAS_LIMIT'
];
return preSubmissionErrors.some(code =>
error.code?.includes(code) ||
error.message.includes(code)
);
}
// 等待交易确认
async waitForConfirmation(tx, options) {
const timeout = options.confirmationTimeout || 300000; // 5分钟
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("交易确认超时")), timeout);
});
try {
const receipt = await Promise.race([
tx.wait(options.confirmations || 1),
timeoutPromise
]);
if (receipt.status === 0) {
throw new Error("交易执行失败");
}
return receipt;
} catch (error) {
// 如果是超时错误,尝试查询交易状态
if (error.message.includes("超时")) {
const receipt = await this.signer.provider.getTransactionReceipt(tx.hash);
if (receipt) {
return receipt;
}
}
throw error;
}
}
generateTxId() {
return `retry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
Nonce 管理器:
class NonceManager {
constructor(signer) {
this.signer = signer;
this.pendingNonces = new Set();
this.currentNonce = null;
this.lock = false;
}
async getNextNonce() {
// 等待锁释放
while (this.lock) {
await new Promise(resolve => setTimeout(resolve, 10));
}
this.lock = true;
try {
if (this.currentNonce === null) {
this.currentNonce = await this.signer.getTransactionCount('pending');
}
// 找到下一个可用的 nonce
while (this.pendingNonces.has(this.currentNonce)) {
this.currentNonce++;
}
const nonce = this.currentNonce;
this.pendingNonces.add(nonce);
this.currentNonce++;
return nonce;
} finally {
this.lock = false;
}
}
confirmNonce(nonce) {
this.pendingNonces.delete(nonce);
}
releaseNonce(nonce) {
if (nonce !== undefined) {
this.pendingNonces.delete(nonce);
}
}
async reset() {
this.lock = true;
try {
this.currentNonce = await this.signer.getTransactionCount('pending');
this.pendingNonces.clear();
} finally {
this.lock = false;
}
}
}
交易替换机制:
class TransactionReplacement {
constructor(signer) {
this.signer = signer;
this.pendingReplacements = new Map();
}
// 替换卡住的交易
async replaceTransaction(originalTxHash, newGasPrice, speedUp = true) {
try {
// 获取原始交易
const originalTx = await this.signer.provider.getTransaction(originalTxHash);
if (!originalTx) {
throw new Error("原始交易不存在");
}
// 检查交易是否已确认
const receipt = await this.signer.provider.getTransactionReceipt(originalTxHash);
if (receipt) {
throw new Error("交易已确认,无法替换");
}
// 构造替换交易
const replacementTx = {
...originalTx,
gasPrice: newGasPrice,
nonce: originalTx.nonce // 使用相同的 nonce
};
// 删除交易哈希字段
delete replacementTx.hash;
delete replacementTx.r;
delete replacementTx.s;
delete replacementTx.v;
if (speedUp) {
// 加速交易 - 保持相同的目标和数据
console.log("加速交易:", originalTxHash);
} else {
// 取消交易 - 发送给自己
replacementTx.to = await this.signer.getAddress();
replacementTx.value = 0;
replacementTx.data = "0x";
console.log("取消交易:", originalTxHash);
}
const newTx = await this.signer.sendTransaction(replacementTx);
this.pendingReplacements.set(originalTxHash, {
replacementHash: newTx.hash,
type: speedUp ? 'speedup' : 'cancel',
timestamp: Date.now()
});
return newTx;
} catch (error) {
console.error("交易替换失败:", error);
throw error;
}
}
// 监控替换结果
async monitorReplacement(originalTxHash) {
const replacement = this.pendingReplacements.get(originalTxHash);
if (!replacement) {
throw new Error("没有找到替换交易");
}
try {
const receipt = await this.signer.provider.waitForTransaction(
replacement.replacementHash
);
console.log(`交易替换成功: ${originalTxHash} -> ${replacement.replacementHash}`);
this.pendingReplacements.delete(originalTxHash);
return receipt;
} catch (error) {
console.error("替换交易失败:", error);
throw error;
}
}
}
智能重试策略:
class SmartRetryStrategy {
constructor(provider) {
this.provider = provider;
this.networkConditions = {
gasPrice: null,
blockTime: null,
congestionLevel: 'normal'
};
}
// 更新网络状况
async updateNetworkConditions() {
try {
const [gasPrice, latestBlock, previousBlock] = await Promise.all([
this.provider.getGasPrice(),
this.provider.getBlock('latest'),
this.provider.getBlock('latest').then(block =>
this.provider.getBlock(block.number - 1)
)
]);
this.networkConditions = {
gasPrice,
blockTime: latestBlock.timestamp - previousBlock.timestamp,
congestionLevel: this.assessCongestionLevel(gasPrice)
};
} catch (error) {
console.error("网络状况更新失败:", error);
}
}
// 评估网络拥堵程度
assessCongestionLevel(gasPrice) {
const gasPriceGwei = parseFloat(ethers.utils.formatUnits(gasPrice, 'gwei'));
if (gasPriceGwei > 100) return 'high';
if (gasPriceGwei > 50) return 'medium';
return 'normal';
}
// 根据网络状况调整重试参数
adjustRetryParameters(baseOptions) {
const { congestionLevel } = this.networkConditions;
switch (congestionLevel) {
case 'high':
return {
...baseOptions,
maxRetries: 5,
baseDelay: 3000,
gasPriceIncrement: 1.5
};
case 'medium':
return {
...baseOptions,
maxRetries: 4,
baseDelay: 2000,
gasPriceIncrement: 1.3
};
default:
return baseOptions;
}
}
}
使用示例:
// 创建重试管理器
const retryManager = new TransactionRetryManager(signer, {
maxRetries: 3,
baseDelay: 1000,
gasPriceIncrement: 1.2
});
// 执行交易
async function executeTransactionWithRetry() {
try {
const txRequest = {
to: contractAddress,
data: contract.interface.encodeFunctionData("transfer", [
recipientAddress,
ethers.utils.parseEther("1.0")
]),
gasLimit: 100000,
gasPrice: ethers.utils.parseUnits("20", "gwei")
};
const receipt = await retryManager.executeWithRetry(txRequest);
console.log("交易成功:", receipt.transactionHash);
return receipt;
} catch (error) {
console.error("交易最终失败:", error);
// 可以考虑其他措施,如用户通知、数据回滚等
throw error;
}
}
重要注意事项:
最佳实践:
What is the transaction pool (Mempool)? How to handle transaction congestion?
What is the transaction pool (Mempool)? How to handle transaction congestion?
考察点:网络拥堵处理。
答案:
交易池(Mempool,Memory Pool)是区块链网络中存储待确认交易的内存区域。当用户提交交易后,交易首先进入 Mempool 等待矿工或验证者选择打包到区块中。处理交易拥堵需要理解 Mempool 机制,并采用动态调整策略来优化交易确认时间。
Mempool 工作机制:
// Mempool 状态监控
class MempoolMonitor {
constructor(provider) {
this.provider = provider;
this.congestionMetrics = {
pendingTxCount: 0,
averageGasPrice: 0,
congestionLevel: 'normal',
estimatedWaitTime: 0
};
}
// 监控 Mempool 状态
async updateMempoolStatus() {
try {
// 获取待处理交易数量(某些提供商支持)
const pendingBlock = await this.provider.getBlock('pending');
const latestBlock = await this.provider.getBlock('latest');
// 计算拥堵指标
this.congestionMetrics = {
pendingTxCount: pendingBlock?.transactions?.length || 0,
blockUtilization: this.calculateBlockUtilization(latestBlock),
averageGasPrice: await this.getAverageGasPrice(),
congestionLevel: this.assessCongestionLevel(),
estimatedWaitTime: this.estimateWaitTime()
};
console.log("Mempool 状态:", this.congestionMetrics);
} catch (error) {
console.error("Mempool 监控失败:", error);
}
}
// 计算区块利用率
calculateBlockUtilization(block) {
if (!block || !block.gasUsed || !block.gasLimit) {
return 0;
}
return block.gasUsed.div(block.gasLimit).mul(100).toNumber();
}
// 获取平均 Gas 价格
async getAverageGasPrice() {
try {
const gasPrice = await this.provider.getGasPrice();
return parseFloat(ethers.utils.formatUnits(gasPrice, 'gwei'));
} catch (error) {
return 0;
}
}
// 评估拥堵级别
assessCongestionLevel() {
const { averageGasPrice, blockUtilization } = this.congestionMetrics;
if (averageGasPrice > 100 || blockUtilization > 95) {
return 'severe';
} else if (averageGasPrice > 50 || blockUtilization > 80) {
return 'high';
} else if (averageGasPrice > 25 || blockUtilization > 60) {
return 'medium';
}
return 'normal';
}
// 估算等待时间
estimateWaitTime() {
const { congestionLevel, averageGasPrice } = this.congestionMetrics;
// 基于历史数据的简单估算
const waitTimeMap = {
'severe': 30, // 30+ 分钟
'high': 15, // 15 分钟
'medium': 5, // 5 分钟
'normal': 2 // 2 分钟
};
return waitTimeMap[congestionLevel] || 2;
}
}
动态 Gas 策略:
class DynamicGasStrategy {
constructor(provider, mempoolMonitor) {
this.provider = provider;
this.mempoolMonitor = mempoolMonitor;
this.gasHistory = [];
}
// 获取推荐的 Gas 设置
async getRecommendedGas(urgency = 'standard') {
await this.mempoolMonitor.updateMempoolStatus();
const { congestionLevel, averageGasPrice } = this.mempoolMonitor.congestionMetrics;
const baseGasPrice = await this.provider.getGasPrice();
const urgencyMultipliers = {
'slow': 0.8,
'standard': 1.0,
'fast': 1.2,
'urgent': 1.5
};
const congestionMultipliers = {
'normal': 1.0,
'medium': 1.2,
'high': 1.5,
'severe': 2.0
};
const multiplier = urgencyMultipliers[urgency] * congestionMultipliers[congestionLevel];
const recommendedGasPrice = baseGasPrice.mul(Math.floor(multiplier * 100)).div(100);
return {
gasPrice: recommendedGasPrice,
estimatedTime: this.getEstimatedConfirmationTime(urgency, congestionLevel),
confidence: this.calculateConfidence(congestionLevel)
};
}
// EIP-1559 费用建议
async getEIP1559Recommendation(urgency = 'standard') {
try {
const feeData = await this.provider.getFeeData();
const { congestionLevel } = this.mempoolMonitor.congestionMetrics;
const urgencyMultipliers = {
'slow': { base: 0.9, priority: 0.5 },
'standard': { base: 1.0, priority: 1.0 },
'fast': { base: 1.1, priority: 2.0 },
'urgent': { base: 1.2, priority: 3.0 }
};
const multiplier = urgencyMultipliers[urgency];
const maxFeePerGas = feeData.maxFeePerGas.mul(
Math.floor(multiplier.base * 100)
).div(100);
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas.mul(
Math.floor(multiplier.priority * 100)
).div(100);
return {
maxFeePerGas,
maxPriorityFeePerGas,
estimatedTime: this.getEstimatedConfirmationTime(urgency, congestionLevel)
};
} catch (error) {
console.error("EIP-1559 费用获取失败:", error);
throw error;
}
}
getEstimatedConfirmationTime(urgency, congestionLevel) {
const baseTimeMap = {
'slow': { normal: 10, medium: 20, high: 30, severe: 45 },
'standard': { normal: 3, medium: 8, high: 15, severe: 25 },
'fast': { normal: 1, medium: 3, high: 8, severe: 15 },
'urgent': { normal: 0.5, medium: 1, high: 3, severe: 8 }
};
return baseTimeMap[urgency][congestionLevel] || 3;
}
calculateConfidence(congestionLevel) {
const confidenceMap = {
'normal': 0.9,
'medium': 0.7,
'high': 0.5,
'severe': 0.3
};
return confidenceMap[congestionLevel] || 0.7;
}
}
拥堵处理策略:
class CongestionHandler {
constructor(provider, gasStrategy) {
this.provider = provider;
this.gasStrategy = gasStrategy;
this.queuedTransactions = [];
this.isProcessing = false;
}
// 智能交易调度
async scheduleTransaction(txRequest, options = {}) {
const {
urgency = 'standard',
maxWaitTime = 300000, // 5分钟
allowQueuing = true
} = options;
// 检查当前网络状况
const gasRecommendation = await this.gasStrategy.getRecommendedGas(urgency);
if (gasRecommendation.confidence < 0.5 && allowQueuing) {
// 网络拥堵严重,考虑排队
return this.queueTransaction(txRequest, options);
}
// 立即执行
return this.executeImmediately(txRequest, gasRecommendation);
}
// 排队交易
async queueTransaction(txRequest, options) {
const queueId = this.generateQueueId();
this.queuedTransactions.push({
id: queueId,
txRequest,
options,
timestamp: Date.now(),
status: 'queued'
});
console.log(`交易已排队: ${queueId}`);
// 开始处理队列
if (!this.isProcessing) {
this.processQueue();
}
return { queueId, status: 'queued' };
}
// 处理交易队列
async processQueue() {
this.isProcessing = true;
while (this.queuedTransactions.length > 0) {
const queuedTx = this.queuedTransactions[0];
try {
// 检查是否应该执行
const gasRecommendation = await this.gasStrategy.getRecommendedGas(
queuedTx.options.urgency
);
if (gasRecommendation.confidence > 0.6) {
// 网络状况改善,执行交易
await this.executeQueuedTransaction(queuedTx);
this.queuedTransactions.shift();
} else {
// 继续等待
console.log("网络仍然拥堵,继续等待...");
await new Promise(resolve => setTimeout(resolve, 30000)); // 等待30秒
}
} catch (error) {
console.error("队列处理错误:", error);
queuedTx.status = 'failed';
queuedTx.error = error.message;
this.queuedTransactions.shift();
}
}
this.isProcessing = false;
}
// 执行排队的交易
async executeQueuedTransaction(queuedTx) {
queuedTx.status = 'executing';
const gasRecommendation = await this.gasStrategy.getRecommendedGas(
queuedTx.options.urgency
);
const result = await this.executeImmediately(
queuedTx.txRequest,
gasRecommendation
);
queuedTx.status = 'completed';
queuedTx.result = result;
return result;
}
// 立即执行交易
async executeImmediately(txRequest, gasRecommendation) {
const finalTxRequest = {
...txRequest,
gasPrice: gasRecommendation.gasPrice
};
const signer = this.provider.getSigner();
const tx = await signer.sendTransaction(finalTxRequest);
console.log(`交易已提交: ${tx.hash}, 预计确认时间: ${gasRecommendation.estimatedTime} 分钟`);
return tx;
}
generateQueueId() {
return `queue_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
批处理优化:
class BatchTransactionOptimizer {
constructor(provider) {
this.provider = provider;
this.pendingBatch = [];
this.batchTimer = null;
this.batchDelay = 5000; // 5秒批处理延迟
}
// 添加交易到批处理
addToBatch(txRequest, callback) {
this.pendingBatch.push({
txRequest,
callback,
timestamp: Date.now()
});
// 重置计时器
if (this.batchTimer) {
clearTimeout(this.batchTimer);
}
this.batchTimer = setTimeout(() => {
this.processBatch();
}, this.batchDelay);
// 如果批次已满,立即处理
if (this.pendingBatch.length >= 10) {
this.processBatch();
}
}
// 处理批次
async processBatch() {
if (this.pendingBatch.length === 0) return;
const batch = [...this.pendingBatch];
this.pendingBatch = [];
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
console.log(`处理批次: ${batch.length} 个交易`);
// 获取最优 Gas 价格
const gasRecommendation = await this.getOptimalBatchGasPrice();
// 获取起始 nonce
const signer = this.provider.getSigner();
let nonce = await signer.getTransactionCount('pending');
// 并发发送交易
const txPromises = batch.map((item, index) =>
this.sendBatchTransaction(item, gasRecommendation.gasPrice, nonce + index)
);
try {
const results = await Promise.allSettled(txPromises);
// 处理结果
results.forEach((result, index) => {
const { callback } = batch[index];
if (result.status === 'fulfilled') {
callback(null, result.value);
} else {
callback(result.reason);
}
});
} catch (error) {
console.error("批处理失败:", error);
}
}
async sendBatchTransaction(item, gasPrice, nonce) {
const signer = this.provider.getSigner();
const tx = await signer.sendTransaction({
...item.txRequest,
gasPrice,
nonce
});
return tx;
}
async getOptimalBatchGasPrice() {
// 为批处理获取适中的 Gas 价格
const baseGasPrice = await this.provider.getGasPrice();
return {
gasPrice: baseGasPrice.mul(110).div(100) // +10% 确保及时确认
};
}
}
用户体验优化:
// 拥堵状况用户提示
class CongestionUI {
constructor(congestionHandler) {
this.congestionHandler = congestionHandler;
}
async showCongestionWarning() {
const status = await this.congestionHandler.mempoolMonitor.updateMempoolStatus();
const { congestionLevel, estimatedWaitTime } = status;
if (congestionLevel === 'high' || congestionLevel === 'severe') {
return {
show: true,
level: congestionLevel,
message: `网络当前拥堵程度: ${congestionLevel}`,
estimatedWait: `预计等待时间: ${estimatedWaitTime} 分钟`,
suggestions: this.getCongestionSuggestions(congestionLevel)
};
}
return { show: false };
}
getCongestionSuggestions(level) {
const suggestions = {
'high': [
"考虑稍后重试",
"使用更高的 Gas 价格加速",
"合并多个操作以节省费用"
],
'severe': [
"建议延后非紧急交易",
"考虑使用 Layer 2 方案",
"等待网络拥堵缓解"
]
};
return suggestions[level] || [];
}
}
监控和预警:
class CongestionAlerts {
constructor(mempoolMonitor) {
this.mempoolMonitor = mempoolMonitor;
this.subscribers = new Set();
this.alertThresholds = {
gasPrice: 80, // Gwei
blockUtilization: 90, // %
waitTime: 20 // minutes
};
}
subscribe(callback) {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
}
startMonitoring() {
setInterval(async () => {
await this.checkAndAlert();
}, 60000); // 每分钟检查
}
async checkAndAlert() {
const metrics = this.mempoolMonitor.congestionMetrics;
const alerts = [];
if (metrics.averageGasPrice > this.alertThresholds.gasPrice) {
alerts.push({
type: 'highGasPrice',
message: `Gas 价格异常高: ${metrics.averageGasPrice} Gwei`
});
}
if (metrics.blockUtilization > this.alertThresholds.blockUtilization) {
alerts.push({
type: 'highBlockUtilization',
message: `区块利用率过高: ${metrics.blockUtilization}%`
});
}
if (alerts.length > 0) {
this.notifySubscribers(alerts);
}
}
notifySubscribers(alerts) {
this.subscribers.forEach(callback => {
try {
callback(alerts);
} catch (error) {
console.error("Alert callback error:", error);
}
});
}
}
最佳实践:
注意事项:
How to implement local caching of contract state to reduce RPC calls?
How to implement local caching of contract state to reduce RPC calls?
考察点:性能优化策略。
答案:
本地缓存合约状态是提升 DApp 性能的关键技术,通过智能缓存策略可以显著减少 RPC 调用次数,提高响应速度并降低 API 成本。需要考虑缓存有效性、数据一致性、存储策略和缓存失效机制等多个方面。
缓存系统架构:
class ContractStateCache {
constructor(options = {}) {
this.cache = new Map();
this.blockCache = new Map();
this.eventListeners = new Map();
this.options = {
defaultTTL: options.defaultTTL || 300000, // 5分钟默认TTL
maxCacheSize: options.maxCacheSize || 1000,
enableBlockCache: options.enableBlockCache !== false,
enableEventInvalidation: options.enableEventInvalidation !== false,
storage: options.storage || 'memory' // 'memory', 'localStorage', 'indexedDB'
};
this.stats = {
hits: 0,
misses: 0,
evictions: 0
};
}
// 生成缓存键
generateCacheKey(contractAddress, method, params = [], blockNumber) {
const paramsStr = JSON.stringify(params);
const blockStr = blockNumber ? `@${blockNumber}` : '';
return `${contractAddress.toLowerCase()}:${method}:${paramsStr}${blockStr}`;
}
// 获取缓存值
async get(key) {
const entry = this.cache.get(key);
if (!entry) {
this.stats.misses++;
return null;
}
// 检查是否过期
if (entry.expiry && Date.now() > entry.expiry) {
this.cache.delete(key);
this.stats.misses++;
return null;
}
// 更新访问时间和计数
entry.lastAccessed = Date.now();
entry.accessCount++;
this.stats.hits++;
return entry.data;
}
// 设置缓存值
set(key, data, options = {}) {
const ttl = options.ttl || this.options.defaultTTL;
const blockNumber = options.blockNumber;
const tags = options.tags || [];
const entry = {
data,
timestamp: Date.now(),
expiry: ttl > 0 ? Date.now() + ttl : null,
lastAccessed: Date.now(),
accessCount: 1,
blockNumber,
tags: new Set(tags)
};
// 检查缓存大小限制
if (this.cache.size >= this.options.maxCacheSize) {
this.evictLRU();
}
this.cache.set(key, entry);
// 持久化存储
if (this.options.storage !== 'memory') {
this.persistToStorage(key, entry);
}
}
// LRU驱逐策略
evictLRU() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (entry.lastAccessed < oldestTime) {
oldestTime = entry.lastAccessed;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
this.stats.evictions++;
}
}
// 按标签清除缓存
invalidateByTags(tags) {
const keysToDelete = [];
for (const [key, entry] of this.cache.entries()) {
const hasMatchingTag = tags.some(tag => entry.tags.has(tag));
if (hasMatchingTag) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.cache.delete(key));
console.log(`按标签清除缓存: ${keysToDelete.length} 个条目`);
}
// 按区块号清除缓存
invalidateByBlock(blockNumber) {
const keysToDelete = [];
for (const [key, entry] of this.cache.entries()) {
if (entry.blockNumber && entry.blockNumber <= blockNumber) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.cache.delete(key));
}
// 获取缓存统计
getStats() {
const hitRate = this.stats.hits / (this.stats.hits + this.stats.misses) || 0;
return {
...this.stats,
hitRate: Math.round(hitRate * 100) / 100,
cacheSize: this.cache.size
};
}
// 清空缓存
clear() {
this.cache.clear();
if (this.options.storage !== 'memory') {
this.clearStorage();
}
}
}
合约状态缓存管理器:
class ContractStateCacheManager {
constructor(provider, cache) {
this.provider = provider;
this.cache = cache;
this.contracts = new Map();
this.blockNumberCache = null;
this.eventFilters = new Map();
}
// 注册合约
registerContract(address, abi, cacheConfig = {}) {
const contract = new ethers.Contract(address, abi, this.provider);
this.contracts.set(address.toLowerCase(), {
contract,
abi,
config: {
staticMethods: cacheConfig.staticMethods || [], // 永不变化的方法
viewMethods: cacheConfig.viewMethods || [], // 只读方法
ttlOverrides: cacheConfig.ttlOverrides || {}, // 方法特定TTL
eventInvalidation: cacheConfig.eventInvalidation || {} // 事件失效映射
}
});
// 设置事件监听
if (this.cache.options.enableEventInvalidation) {
this.setupEventListeners(address, cacheConfig.eventInvalidation);
}
}
// 缓存合约调用
async cachedCall(contractAddress, method, params = [], options = {}) {
const address = contractAddress.toLowerCase();
const contractInfo = this.contracts.get(address);
if (!contractInfo) {
throw new Error("合约未注册");
}
const { contract, config } = contractInfo;
// 确定缓存策略
const cacheStrategy = this.determineCacheStrategy(method, config, options);
if (!cacheStrategy.cacheable) {
// 不缓存,直接调用
return await contract[method](...params);
}
// 生成缓存键
const blockNumber = cacheStrategy.useBlockNumber ?
await this.getCurrentBlockNumber() : null;
const cacheKey = this.cache.generateCacheKey(
address,
method,
params,
blockNumber
);
// 尝试从缓存获取
const cachedResult = await this.cache.get(cacheKey);
if (cachedResult !== null) {
return cachedResult;
}
// 缓存未命中,执行调用
try {
const result = await contract[method](...params);
// 存储到缓存
const cacheOptions = {
ttl: cacheStrategy.ttl,
blockNumber,
tags: cacheStrategy.tags
};
this.cache.set(cacheKey, result, cacheOptions);
return result;
} catch (error) {
console.error(`合约调用失败: ${method}`, error);
throw error;
}
}
// 确定缓存策略
determineCacheStrategy(method, config, options) {
// 静态方法 - 永久缓存
if (config.staticMethods.includes(method)) {
return {
cacheable: true,
ttl: 0, // 永不过期
useBlockNumber: false,
tags: ['static', method]
};
}
// 视图方法 - 按区块缓存
if (config.viewMethods.includes(method)) {
return {
cacheable: true,
ttl: config.ttlOverrides[method] || 60000, // 1分钟默认
useBlockNumber: true,
tags: ['view', method]
};
}
// 自定义选项
if (options.cache !== false) {
return {
cacheable: true,
ttl: options.ttl || this.cache.options.defaultTTL,
useBlockNumber: options.useBlockNumber || false,
tags: options.tags || [method]
};
}
return { cacheable: false };
}
// 获取当前区块号(带缓存)
async getCurrentBlockNumber() {
const now = Date.now();
if (this.blockNumberCache &&
(now - this.blockNumberCache.timestamp) < 15000) { // 15秒缓存
return this.blockNumberCache.number;
}
const blockNumber = await this.provider.getBlockNumber();
this.blockNumberCache = {
number: blockNumber,
timestamp: now
};
return blockNumber;
}
// 设置事件监听器
setupEventListeners(contractAddress, eventInvalidation) {
const address = contractAddress.toLowerCase();
const contractInfo = this.contracts.get(address);
if (!contractInfo) return;
const { contract } = contractInfo;
// 为每个事件设置监听器
Object.entries(eventInvalidation).forEach(([eventName, invalidationConfig]) => {
const filter = contract.filters[eventName]();
const listener = (...args) => {
const event = args[args.length - 1]; // 最后一个参数是事件对象
this.handleEventInvalidation(
address,
eventName,
event,
invalidationConfig
);
};
contract.on(filter, listener);
// 记录监听器以便清理
const filterKey = `${address}:${eventName}`;
this.eventFilters.set(filterKey, { filter, listener });
});
}
// 处理事件失效
handleEventInvalidation(contractAddress, eventName, event, config) {
console.log(`处理事件失效: ${contractAddress}:${eventName}`);
if (config.invalidateAll) {
// 清除该合约的所有缓存
this.invalidateContract(contractAddress);
} else if (config.invalidateTags) {
// 按标签清除
this.cache.invalidateByTags(config.invalidateTags);
} else if (config.invalidateMethods) {
// 按方法清除
this.invalidateMethods(contractAddress, config.invalidateMethods);
}
}
// 清除特定合约的缓存
invalidateContract(contractAddress) {
const pattern = contractAddress.toLowerCase();
const keysToDelete = [];
for (const key of this.cache.cache.keys()) {
if (key.startsWith(pattern)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.cache.cache.delete(key));
}
// 清除特定方法的缓存
invalidateMethods(contractAddress, methods) {
const address = contractAddress.toLowerCase();
const keysToDelete = [];
for (const key of this.cache.cache.keys()) {
if (key.startsWith(address)) {
const hasMethod = methods.some(method =>
key.includes(`:${method}:`)
);
if (hasMethod) {
keysToDelete.push(key);
}
}
}
keysToDelete.forEach(key => this.cache.cache.delete(key));
}
}
批量数据缓存:
class BatchDataCache {
constructor(cacheManager) {
this.cacheManager = cacheManager;
this.batchQueue = [];
this.batchTimer = null;
this.batchDelay = 100; // 100ms 批处理延迟
}
// 批量获取数据
async batchGet(requests) {
// 检查缓存命中
const results = new Array(requests.length);
const uncachedRequests = [];
for (let i = 0; i < requests.length; i++) {
const request = requests[i];
const cacheKey = this.cacheManager.cache.generateCacheKey(
request.contract,
request.method,
request.params
);
const cached = await this.cacheManager.cache.get(cacheKey);
if (cached !== null) {
results[i] = cached;
} else {
uncachedRequests.push({ index: i, ...request });
}
}
// 批量执行未缓存的请求
if (uncachedRequests.length > 0) {
const batchResults = await this.executeBatch(uncachedRequests);
// 填充结果并更新缓存
batchResults.forEach((result, batchIndex) => {
const request = uncachedRequests[batchIndex];
results[request.index] = result;
// 缓存结果
const cacheKey = this.cacheManager.cache.generateCacheKey(
request.contract,
request.method,
request.params
);
this.cacheManager.cache.set(cacheKey, result, {
ttl: request.ttl || 300000
});
});
}
return results;
}
// 执行批量请求
async executeBatch(requests) {
// 使用 multicall 或并发执行
const promises = requests.map(request =>
this.cacheManager.cachedCall(
request.contract,
request.method,
request.params,
{ cache: false } // 避免重复缓存检查
)
);
return Promise.all(promises);
}
}
持久化存储:
class PersistentCacheStorage {
constructor(storageType = 'localStorage') {
this.storageType = storageType;
this.keyPrefix = 'contract_cache_';
}
// 保存到存储
async save(key, data) {
const storageKey = this.keyPrefix + key;
const serialized = JSON.stringify({
...data,
timestamp: Date.now()
});
switch (this.storageType) {
case 'localStorage':
localStorage.setItem(storageKey, serialized);
break;
case 'sessionStorage':
sessionStorage.setItem(storageKey, serialized);
break;
case 'indexedDB':
await this.saveToIndexedDB(storageKey, serialized);
break;
}
}
// 从存储加载
async load(key) {
const storageKey = this.keyPrefix + key;
let serialized = null;
switch (this.storageType) {
case 'localStorage':
serialized = localStorage.getItem(storageKey);
break;
case 'sessionStorage':
serialized = sessionStorage.getItem(storageKey);
break;
case 'indexedDB':
serialized = await this.loadFromIndexedDB(storageKey);
break;
}
if (serialized) {
try {
const data = JSON.parse(serialized);
// 检查是否过期
if (data.expiry && Date.now() > data.expiry) {
this.remove(key);
return null;
}
return data;
} catch (error) {
console.error("缓存数据解析失败:", error);
this.remove(key);
}
}
return null;
}
// 移除存储
remove(key) {
const storageKey = this.keyPrefix + key;
switch (this.storageType) {
case 'localStorage':
localStorage.removeItem(storageKey);
break;
case 'sessionStorage':
sessionStorage.removeItem(storageKey);
break;
case 'indexedDB':
this.removeFromIndexedDB(storageKey);
break;
}
}
// IndexedDB 操作
async saveToIndexedDB(key, data) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('ContractCache', 1);
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
store.put({ key, data });
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
};
request.onerror = () => reject(request.error);
});
}
}
使用示例:
// 初始化缓存系统
const cache = new ContractStateCache({
defaultTTL: 300000, // 5分钟
maxCacheSize: 2000, // 最大缓存条目数
storage: 'localStorage' // 持久化存储
});
const cacheManager = new ContractStateCacheManager(provider, cache);
// 注册合约
cacheManager.registerContract(tokenAddress, tokenABI, {
staticMethods: ['name', 'symbol', 'decimals'],
viewMethods: ['balanceOf', 'allowance', 'totalSupply'],
ttlOverrides: {
'balanceOf': 60000, // 余额缓存1分钟
'totalSupply': 300000 // 总供应量缓存5分钟
},
eventInvalidation: {
'Transfer': {
invalidateMethods: ['balanceOf']
},
'Approval': {
invalidateMethods: ['allowance']
}
}
});
// 使用缓存调用
async function getUserBalance(userAddress) {
return await cacheManager.cachedCall(
tokenAddress,
'balanceOf',
[userAddress]
);
}
// 批量数据获取
const batchCache = new BatchDataCache(cacheManager);
const requests = [
{ contract: tokenAddress, method: 'balanceOf', params: [user1] },
{ contract: tokenAddress, method: 'balanceOf', params: [user2] },
{ contract: tokenAddress, method: 'totalSupply', params: [] }
];
const results = await batchCache.batchGet(requests);
最佳实践:
注意事项:
What is signature verification? How to verify message signatures on the frontend?
What is signature verification? How to verify message signatures on the frontend?
考察点:签名验证机制。
答案:
签名验证是区块链中确认消息来源和完整性的密码学机制。通过椭圆曲线数字签名算法(ECDSA),可以验证消息确实由特定私钥的持有者签名且未被篡改。在 Web3 应用中,签名验证广泛用于身份认证、消息验证和离线授权等场景。
签名验证原理:
// 基础签名验证流程
class MessageSignatureVerifier {
constructor(provider) {
this.provider = provider;
}
// 创建消息签名
async signMessage(signer, message) {
try {
// 使用 EIP-191 标准签名
const signature = await signer.signMessage(message);
return {
message,
signature,
address: await signer.getAddress(),
timestamp: Date.now()
};
} catch (error) {
console.error("消息签名失败:", error);
throw error;
}
}
// 验证消息签名
verifyMessageSignature(message, signature, expectedAddress) {
try {
// 恢复签名者地址
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
// 比较地址(不区分大小写)
const isValid = recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
return {
isValid,
recoveredAddress,
expectedAddress
};
} catch (error) {
console.error("签名验证失败:", error);
return {
isValid: false,
error: error.message
};
}
}
// EIP-712 结构化数据签名
async signTypedData(signer, domain, types, value) {
try {
// 使用 EIP-712 标准
const signature = await signer._signTypedData(domain, types, value);
return {
domain,
types,
value,
signature,
address: await signer.getAddress()
};
} catch (error) {
console.error("结构化数据签名失败:", error);
throw error;
}
}
// 验证 EIP-712 签名
verifyTypedData(domain, types, value, signature, expectedAddress) {
try {
const recoveredAddress = ethers.utils.verifyTypedData(
domain,
types,
value,
signature
);
const isValid = recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
return {
isValid,
recoveredAddress,
expectedAddress
};
} catch (error) {
console.error("EIP-712 签名验证失败:", error);
return {
isValid: false,
error: error.message
};
}
}
}
EIP-712 结构化签名:
// EIP-712 域和类型定义
class EIP712SignatureHandler {
constructor(contractName, version, chainId, verifyingContract) {
this.domain = {
name: contractName,
version: version,
chainId: chainId,
verifyingContract: verifyingContract
};
// 定义常用的数据类型
this.types = {
// 授权类型
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
],
// 投票类型
Vote: [
{ name: 'voter', type: 'address' },
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'bool' },
{ name: 'weight', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
],
// 订单类型
Order: [
{ name: 'maker', type: 'address' },
{ name: 'taker', type: 'address' },
{ name: 'tokenA', type: 'address' },
{ name: 'tokenB', type: 'address' },
{ name: 'amountA', type: 'uint256' },
{ name: 'amountB', type: 'uint256' },
{ name: 'expiration', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
]
};
}
// 创建 Permit 签名
async createPermitSignature(signer, permitData) {
const value = {
owner: permitData.owner,
spender: permitData.spender,
value: permitData.value,
nonce: permitData.nonce,
deadline: permitData.deadline
};
const signature = await signer._signTypedData(
this.domain,
{ Permit: this.types.Permit },
value
);
// 分解签名
const { r, s, v } = ethers.utils.splitSignature(signature);
return {
...value,
signature,
r,
s,
v
};
}
// 验证 Permit 签名
verifyPermitSignature(permitData, signature) {
const recoveredAddress = ethers.utils.verifyTypedData(
this.domain,
{ Permit: this.types.Permit },
{
owner: permitData.owner,
spender: permitData.spender,
value: permitData.value,
nonce: permitData.nonce,
deadline: permitData.deadline
},
signature
);
return {
isValid: recoveredAddress.toLowerCase() === permitData.owner.toLowerCase(),
recoveredAddress
};
}
// 创建投票签名
async createVoteSignature(signer, voteData) {
const value = {
voter: voteData.voter,
proposalId: voteData.proposalId,
support: voteData.support,
weight: voteData.weight,
nonce: voteData.nonce
};
return await signer._signTypedData(
this.domain,
{ Vote: this.types.Vote },
value
);
}
}
身份认证系统:
class Web3AuthSystem {
constructor() {
this.activeSessions = new Map();
this.nonces = new Map();
this.verifier = new MessageSignatureVerifier();
}
// 生成认证随机数
generateAuthNonce(address) {
const nonce = Math.random().toString(36).substring(2, 15);
const expiry = Date.now() + 300000; // 5分钟有效期
this.nonces.set(address.toLowerCase(), {
nonce,
expiry,
used: false
});
return nonce;
}
// 创建认证消息
createAuthMessage(address, nonce, timestamp) {
return `请签名登录到我们的应用
地址: ${address}
随机数: ${nonce}
时间戳: ${timestamp}
此签名仅用于身份验证,不会触发任何区块链交易。`;
}
// 请求用户认证
async requestAuth(signer) {
const address = await signer.getAddress();
const nonce = this.generateAuthNonce(address);
const timestamp = Date.now();
const message = this.createAuthMessage(address, nonce, timestamp);
try {
const signature = await signer.signMessage(message);
return {
address,
message,
signature,
nonce,
timestamp
};
} catch (error) {
// 清理 nonce
this.nonces.delete(address.toLowerCase());
throw error;
}
}
// 验证认证签名
verifyAuth(authData) {
const { address, message, signature, nonce } = authData;
const addressKey = address.toLowerCase();
// 检查 nonce
const nonceData = this.nonces.get(addressKey);
if (!nonceData) {
return { isValid: false, reason: 'Invalid nonce' };
}
if (nonceData.used) {
return { isValid: false, reason: 'Nonce already used' };
}
if (Date.now() > nonceData.expiry) {
this.nonces.delete(addressKey);
return { isValid: false, reason: 'Nonce expired' };
}
if (nonceData.nonce !== nonce) {
return { isValid: false, reason: 'Nonce mismatch' };
}
// 验证签名
const signatureResult = this.verifier.verifyMessageSignature(
message,
signature,
address
);
if (signatureResult.isValid) {
// 标记 nonce 为已使用
nonceData.used = true;
// 创建会话
const sessionToken = this.createSession(address);
return {
isValid: true,
sessionToken,
address
};
}
return {
isValid: false,
reason: 'Invalid signature'
};
}
// 创建会话
createSession(address) {
const sessionToken = ethers.utils.id(`${address}_${Date.now()}_${Math.random()}`);
const expiry = Date.now() + 86400000; // 24小时
this.activeSessions.set(sessionToken, {
address: address.toLowerCase(),
expiry,
createdAt: Date.now()
});
return sessionToken;
}
// 验证会话
verifySession(sessionToken) {
const session = this.activeSessions.get(sessionToken);
if (!session) {
return { isValid: false, reason: 'Session not found' };
}
if (Date.now() > session.expiry) {
this.activeSessions.delete(sessionToken);
return { isValid: false, reason: 'Session expired' };
}
return {
isValid: true,
address: session.address,
createdAt: session.createdAt
};
}
// 撤销会话
revokeSession(sessionToken) {
return this.activeSessions.delete(sessionToken);
}
}
合约验证集成:
// 智能合约中的签名验证
const verificationContract = `
pragma solidity ^0.8.0;
contract SignatureVerifier {
mapping(address => uint256) public nonces;
// 验证消息签名
function verifyMessageSignature(
string memory message,
bytes memory signature,
address expectedSigner
) public pure returns (bool) {
bytes32 messageHash = keccak256(abi.encodePacked(
"\\x19Ethereum Signed Message:\\n",
Strings.toString(bytes(message).length),
message
));
address recoveredSigner = recoverSigner(messageHash, signature);
return recoveredSigner == expectedSigner;
}
// 验证 EIP-712 签名
function verifyTypedDataSignature(
bytes32 domainSeparator,
bytes32 structHash,
bytes memory signature,
address expectedSigner
) public pure returns (bool) {
bytes32 digest = keccak256(abi.encodePacked(
"\\x19\\x01",
domainSeparator,
structHash
));
address recoveredSigner = recoverSigner(digest, signature);
return recoveredSigner == expectedSigner;
}
// 恢复签名者地址
function recoverSigner(
bytes32 hash,
bytes memory signature
) internal pure returns (address) {
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := byte(0, mload(add(signature, 96)))
}
return ecrecover(hash, v, r, s);
}
}
`;
// 前端与合约验证的集成
class ContractSignatureVerification {
constructor(contract, signer) {
this.contract = contract;
this.signer = signer;
}
// 在合约中验证签名
async verifyOnContract(message, signature, expectedSigner) {
try {
const isValid = await this.contract.verifyMessageSignature(
message,
signature,
expectedSigner
);
return { isValid };
} catch (error) {
console.error("合约签名验证失败:", error);
return { isValid: false, error: error.message };
}
}
// 提交经过签名验证的交易
async submitVerifiedTransaction(signedData) {
const { message, signature, action } = signedData;
const userAddress = await this.signer.getAddress();
// 先在前端验证
const frontendVerification = new MessageSignatureVerifier();
const verifyResult = frontendVerification.verifyMessageSignature(
message,
signature,
userAddress
);
if (!verifyResult.isValid) {
throw new Error("前端签名验证失败");
}
// 提交到合约
const tx = await this.contract.executeVerifiedAction(
message,
signature,
action
);
return tx;
}
}
实际应用示例:
// 完整的签名认证流程
async function implementSignatureAuth() {
// 1. 初始化系统
const authSystem = new Web3AuthSystem();
const signer = provider.getSigner();
// 2. 用户登录
try {
const authData = await authSystem.requestAuth(signer);
console.log("用户签名完成:", authData);
// 3. 验证签名
const authResult = authSystem.verifyAuth(authData);
if (authResult.isValid) {
console.log("认证成功,会话令牌:", authResult.sessionToken);
// 4. 使用会话进行后续操作
const sessionCheck = authSystem.verifySession(authResult.sessionToken);
console.log("会话验证:", sessionCheck);
} else {
console.error("认证失败:", authResult.reason);
}
} catch (error) {
console.error("签名认证过程失败:", error);
}
}
// EIP-712 Permit 示例
async function usePermitSignature() {
const eip712Handler = new EIP712SignatureHandler(
"MyToken", // 合约名称
"1", // 版本
1, // 链ID (Mainnet)
tokenAddress // 验证合约地址
);
const permitData = {
owner: userAddress,
spender: spenderAddress,
value: ethers.utils.parseEther("100"),
nonce: 0,
deadline: Math.floor(Date.now() / 1000) + 3600 // 1小时后过期
};
const signedPermit = await eip712Handler.createPermitSignature(
signer,
permitData
);
console.log("Permit 签名:", signedPermit);
// 验证签名
const verification = eip712Handler.verifyPermitSignature(
permitData,
signedPermit.signature
);
console.log("签名验证结果:", verification);
}
最佳实践:
安全注意事项:
How to handle contract address mapping across different networks?
How to handle contract address mapping across different networks?
考察点:多网络适配。
答案:
处理不同网络间的合约地址映射是构建多链 DApp 的核心技术。由于同一协议在不同网络上的合约地址通常不同,需要建立完善的地址管理系统来确保应用能够在各网络间正确切换和操作。
网络配置管理:
class NetworkConfigManager {
constructor() {
this.networks = new Map();
this.currentNetwork = null;
this.contractMappings = new Map();
// 初始化网络配置
this.initializeNetworks();
}
// 初始化支持的网络
initializeNetworks() {
const networkConfigs = {
1: {
name: 'Ethereum Mainnet',
symbol: 'ETH',
rpcUrls: [
'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
'https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY'
],
blockExplorerUrls: ['https://etherscan.io'],
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18
}
},
5: {
name: 'Goerli Testnet',
symbol: 'ETH',
rpcUrls: ['https://goerli.infura.io/v3/YOUR-PROJECT-ID'],
blockExplorerUrls: ['https://goerli.etherscan.io'],
nativeCurrency: {
name: 'Goerli Ether',
symbol: 'ETH',
decimals: 18
}
},
137: {
name: 'Polygon Mainnet',
symbol: 'MATIC',
rpcUrls: ['https://polygon-rpc.com'],
blockExplorerUrls: ['https://polygonscan.com'],
nativeCurrency: {
name: 'MATIC',
symbol: 'MATIC',
decimals: 18
}
},
42161: {
name: 'Arbitrum One',
symbol: 'ETH',
rpcUrls: ['https://arb1.arbitrum.io/rpc'],
blockExplorerUrls: ['https://arbiscan.io'],
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18
}
},
56: {
name: 'Binance Smart Chain',
symbol: 'BNB',
rpcUrls: ['https://bsc-dataseed.binance.org'],
blockExplorerUrls: ['https://bscscan.com'],
nativeCurrency: {
name: 'BNB',
symbol: 'BNB',
decimals: 18
}
}
};
Object.entries(networkConfigs).forEach(([chainId, config]) => {
this.networks.set(parseInt(chainId), config);
});
}
// 获取网络配置
getNetworkConfig(chainId) {
return this.networks.get(chainId);
}
// 获取所有支持的网络
getSupportedNetworks() {
return Array.from(this.networks.entries()).map(([chainId, config]) => ({
chainId,
...config
}));
}
// 设置当前网络
setCurrentNetwork(chainId) {
if (this.networks.has(chainId)) {
this.currentNetwork = chainId;
return true;
}
return false;
}
// 检查网络是否支持
isNetworkSupported(chainId) {
return this.networks.has(chainId);
}
}
合约地址映射管理:
class ContractAddressManager {
constructor() {
this.contractMappings = new Map();
this.contractABIs = new Map();
// 初始化合约映射
this.initializeContractMappings();
}
// 初始化合约地址映射
initializeContractMappings() {
// 定义合约在不同网络上的地址
const contractConfigs = {
// USDC 代币
'USDC': {
1: '0xA0b86a33E6441e2b000E85C8b60E54E48e48B2BB', // Ethereum
137: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // Polygon
42161: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', // Arbitrum
56: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d' // BSC
},
// Uniswap V3 Router
'UNISWAP_V3_ROUTER': {
1: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Ethereum
5: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Goerli
137: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Polygon
42161: '0xE592427A0AEce92De3Edee1F18E0157C05861564' // Arbitrum
},
// 自定义协议合约
'MY_PROTOCOL': {
1: '0x1234567890123456789012345678901234567890', // Ethereum
5: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', // Goerli
137: '0x9876543210987654321098765432109876543210', // Polygon
42161: '0xfedcbafedcbafedcbafedcbafedcbafedcbafedcba' // Arbitrum
}
};
// 存储映射关系
Object.entries(contractConfigs).forEach(([contractName, networkMappings]) => {
this.contractMappings.set(contractName, new Map(
Object.entries(networkMappings).map(([chainId, address]) => [
parseInt(chainId),
address.toLowerCase()
])
));
});
}
// 获取合约地址
getContractAddress(contractName, chainId) {
const mapping = this.contractMappings.get(contractName);
if (!mapping) {
throw new Error(`未知的合约: ${contractName}`);
}
const address = mapping.get(chainId);
if (!address) {
throw new Error(`合约 ${contractName} 在网络 ${chainId} 上不存在`);
}
return address;
}
// 批量获取合约地址
getMultipleContractAddresses(contractNames, chainId) {
const result = {};
contractNames.forEach(contractName => {
try {
result[contractName] = this.getContractAddress(contractName, chainId);
} catch (error) {
result[contractName] = null;
console.warn(`获取合约地址失败: ${contractName} on ${chainId}`, error);
}
});
return result;
}
// 检查合约在网络上是否存在
isContractAvailableOnNetwork(contractName, chainId) {
try {
this.getContractAddress(contractName, chainId);
return true;
} catch {
return false;
}
}
// 获取合约支持的所有网络
getSupportedNetworks(contractName) {
const mapping = this.contractMappings.get(contractName);
if (!mapping) {
return [];
}
return Array.from(mapping.keys());
}
// 添加新的合约映射
addContractMapping(contractName, chainId, address) {
if (!this.contractMappings.has(contractName)) {
this.contractMappings.set(contractName, new Map());
}
const mapping = this.contractMappings.get(contractName);
mapping.set(chainId, address.toLowerCase());
console.log(`添加合约映射: ${contractName} -> ${address} (链ID: ${chainId})`);
}
// 移除合约映射
removeContractMapping(contractName, chainId) {
const mapping = this.contractMappings.get(contractName);
if (mapping) {
mapping.delete(chainId);
if (mapping.size === 0) {
this.contractMappings.delete(contractName);
}
}
}
}
多网络合约实例管理:
class MultiNetworkContractManager {
constructor(networkManager, addressManager) {
this.networkManager = networkManager;
this.addressManager = addressManager;
this.providers = new Map();
this.contracts = new Map();
this.currentChainId = null;
}
// 初始化提供商
async initializeProvider(chainId) {
const config = this.networkManager.getNetworkConfig(chainId);
if (!config) {
throw new Error(`不支持的网络: ${chainId}`);
}
// 尝试多个 RPC 端点
for (const rpcUrl of config.rpcUrls) {
try {
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
await provider.getNetwork(); // 测试连接
this.providers.set(chainId, provider);
console.log(`网络 ${chainId} 提供商初始化成功`);
return provider;
} catch (error) {
console.warn(`RPC 端点 ${rpcUrl} 连接失败:`, error);
}
}
throw new Error(`无法连接到网络 ${chainId}`);
}
// 获取提供商
async getProvider(chainId) {
if (!this.providers.has(chainId)) {
await this.initializeProvider(chainId);
}
return this.providers.get(chainId);
}
// 获取合约实例
async getContract(contractName, chainId, signer = null) {
const contractKey = `${contractName}_${chainId}`;
// 检查缓存
if (this.contracts.has(contractKey)) {
const cachedContract = this.contracts.get(contractKey);
// 如果需要签名者但缓存的合约没有,则重新创建
if (signer && !cachedContract.signer) {
return this.createContract(contractName, chainId, signer);
}
return cachedContract;
}
return this.createContract(contractName, chainId, signer);
}
// 创建合约实例
async createContract(contractName, chainId, signer = null) {
try {
// 获取合约地址
const address = this.addressManager.getContractAddress(contractName, chainId);
// 获取 ABI
const abi = this.getContractABI(contractName);
// 获取提供商或签名者
const providerOrSigner = signer || await this.getProvider(chainId);
// 创建合约实例
const contract = new ethers.Contract(address, abi, providerOrSigner);
// 缓存合约实例
const contractKey = `${contractName}_${chainId}`;
this.contracts.set(contractKey, contract);
console.log(`合约 ${contractName} 在网络 ${chainId} 上初始化成功`);
return contract;
} catch (error) {
console.error(`创建合约实例失败: ${contractName} on ${chainId}`, error);
throw error;
}
}
// 获取合约 ABI
getContractABI(contractName) {
// 这里应该从存储的 ABI 映射中获取
// 为了示例,返回一个通用的 ERC20 ABI
if (contractName === 'USDC') {
return [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
"function name() view returns (string)"
];
}
throw new Error(`未找到合约 ${contractName} 的 ABI`);
}
// 切换网络
async switchNetwork(newChainId) {
if (!this.networkManager.isNetworkSupported(newChainId)) {
throw new Error(`不支持的网络: ${newChainId}`);
}
// 清除当前网络的合约缓存
if (this.currentChainId) {
this.clearNetworkCache(this.currentChainId);
}
this.currentChainId = newChainId;
this.networkManager.setCurrentNetwork(newChainId);
console.log(`切换到网络: ${newChainId}`);
}
// 清除特定网络的缓存
clearNetworkCache(chainId) {
const keysToDelete = [];
for (const key of this.contracts.keys()) {
if (key.endsWith(`_${chainId}`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.contracts.delete(key));
}
// 批量获取多网络合约
async getMultiNetworkContracts(contractName, chainIds) {
const contracts = {};
const promises = chainIds.map(async (chainId) => {
try {
const contract = await this.getContract(contractName, chainId);
return { chainId, contract };
} catch (error) {
console.warn(`网络 ${chainId} 上的合约 ${contractName} 不可用:`, error);
return { chainId, contract: null };
}
});
const results = await Promise.all(promises);
results.forEach(({ chainId, contract }) => {
contracts[chainId] = contract;
});
return contracts;
}
}
网络切换处理:
class NetworkSwitchHandler {
constructor(multiNetworkManager) {
this.multiNetworkManager = multiNetworkManager;
this.listeners = new Set();
this.isHandlingSwitchRequest = false;
}
// 监听网络切换
onNetworkChange(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
// 请求用户切换网络
async requestNetworkSwitch(targetChainId) {
if (this.isHandlingSwitchRequest) {
throw new Error("网络切换请求正在进行中");
}
this.isHandlingSwitchRequest = true;
try {
const networkConfig = this.multiNetworkManager.networkManager.getNetworkConfig(targetChainId);
if (!networkConfig) {
throw new Error(`不支持的网络: ${targetChainId}`);
}
// 尝试切换到目标网络
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${targetChainId.toString(16)}` }]
});
console.log(`成功切换到网络: ${targetChainId}`);
} catch (switchError) {
// 如果网络不存在,尝试添加网络
if (switchError.code === 4902) {
await this.addNetwork(targetChainId, networkConfig);
// 添加后再次尝试切换
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${targetChainId.toString(16)}` }]
});
} else {
throw switchError;
}
}
// 更新内部状态
await this.multiNetworkManager.switchNetwork(targetChainId);
// 通知监听者
this.notifyListeners(targetChainId);
return true;
} finally {
this.isHandlingSwitchRequest = false;
}
}
// 添加网络到钱包
async addNetwork(chainId, networkConfig) {
const params = {
chainId: `0x${chainId.toString(16)}`,
chainName: networkConfig.name,
nativeCurrency: networkConfig.nativeCurrency,
rpcUrls: networkConfig.rpcUrls,
blockExplorerUrls: networkConfig.blockExplorerUrls
};
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [params]
});
console.log(`网络 ${networkConfig.name} 已添加到钱包`);
}
// 检测当前网络
async detectCurrentNetwork() {
if (!window.ethereum) {
throw new Error("未检测到钱包");
}
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});
return parseInt(chainId, 16);
}
// 通知监听者网络变化
notifyListeners(newChainId) {
this.listeners.forEach(callback => {
try {
callback(newChainId);
} catch (error) {
console.error("网络变化监听器错误:", error);
}
});
}
}
使用示例:
// 初始化多网络管理系统
const networkManager = new NetworkConfigManager();
const addressManager = new ContractAddressManager();
const multiNetworkManager = new MultiNetworkContractManager(networkManager, addressManager);
const switchHandler = new NetworkSwitchHandler(multiNetworkManager);
// 使用示例
async function demonstrateMultiNetwork() {
try {
// 1. 检测当前网络
const currentChainId = await switchHandler.detectCurrentNetwork();
console.log("当前网络:", currentChainId);
// 2. 获取 USDC 合约(当前网络)
const usdcContract = await multiNetworkManager.getContract('USDC', currentChainId);
const balance = await usdcContract.balanceOf(userAddress);
console.log("USDC 余额:", ethers.utils.formatUnits(balance, 6));
// 3. 切换到 Polygon 网络
await switchHandler.requestNetworkSwitch(137);
// 4. 获取 Polygon 上的 USDC 合约
const polygonUsdcContract = await multiNetworkManager.getContract('USDC', 137);
const polygonBalance = await polygonUsdcContract.balanceOf(userAddress);
console.log("Polygon USDC 余额:", ethers.utils.formatUnits(polygonBalance, 6));
// 5. 批量获取多网络合约
const multiContracts = await multiNetworkManager.getMultiNetworkContracts(
'USDC',
[1, 137, 42161] // Ethereum, Polygon, Arbitrum
);
console.log("多网络合约:", multiContracts);
} catch (error) {
console.error("多网络操作失败:", error);
}
}
// 监听网络变化
switchHandler.onNetworkChange((newChainId) => {
console.log(`网络已切换至: ${newChainId}`);
// 更新 UI、重新加载数据等
});
最佳实践:
注意事项:
What is EIP-1559? How does it affect Gas fee calculation?
What is EIP-1559? How does it affect Gas fee calculation?
考察点:新Gas机制理解。
答案:
EIP-1559 是以太坊的一个重要改进提案,于 2021年8月随伦敦硬分叉实施。它彻底改变了以太坊的 Gas 费用机制,引入了基础费用(Base Fee)和优先费用(Priority Fee)的概念,替代了之前简单的 Gas Price 拍卖机制。
核心机制变化:
// EIP-1559 前的传统 Gas 机制
const legacyTransaction = {
gasPrice: ethers.utils.parseUnits('20', 'gwei'), // 固定 Gas 价格
gasLimit: 21000
};
// EIP-1559 新机制
const eip1559Transaction = {
maxFeePerGas: ethers.utils.parseUnits('25', 'gwei'), // 最大愿意支付的费用
maxPriorityFeePerGas: ethers.utils.parseUnits('2', 'gwei'), // 给矿工的小费
gasLimit: 21000,
type: 2 // 标识为 EIP-1559 交易
};
基础费用计算逻辑:
class BaseFeeCalculator {
constructor() {
this.targetGasUsed = 15_000_000; // 目标区块Gas使用量
this.maxGasUsed = 30_000_000; // 最大区块Gas使用量
this.baseFeeChangeDenominator = 8; // 变化分母(12.5%最大变化)
}
// 计算下一个区块的基础费用
calculateNextBaseFee(currentBaseFee, parentGasUsed, parentGasLimit) {
const parentGasTarget = parentGasLimit / 2;
if (parentGasUsed === parentGasTarget) {
// Gas 使用量等于目标,基础费用不变
return currentBaseFee;
} else if (parentGasUsed > parentGasTarget) {
// Gas 使用量超过目标,基础费用增加
const gasUsedDelta = parentGasUsed - parentGasTarget;
const targetGasUsed = parentGasTarget;
const baseFeePerGasDelta = Math.max(
currentBaseFee * gasUsedDelta / targetGasUsed / this.baseFeeChangeDenominator,
1
);
return currentBaseFee + baseFeePerGasDelta;
} else {
// Gas 使用量低于目标,基础费用减少
const gasUsedDelta = parentGasTarget - parentGasUsed;
const targetGasUsed = parentGasTarget;
const baseFeePerGasDelta = currentBaseFee * gasUsedDelta / targetGasUsed / this.baseFeeChangeDenominator;
return Math.max(currentBaseFee - baseFeePerGasDelta, 0);
}
}
// 模拟基础费用变化
simulateBaseFeeChanges(initialBaseFee, gasUsagePattern, blocks = 10) {
let currentBaseFee = initialBaseFee;
const changes = [currentBaseFee];
for (let i = 0; i < blocks; i++) {
const gasUsed = gasUsagePattern[i] || this.targetGasUsed;
currentBaseFee = this.calculateNextBaseFee(
currentBaseFee,
gasUsed,
this.maxGasUsed
);
changes.push(currentBaseFee);
}
return changes;
}
}
智能费用管理器:
class SmartFeeManager {
constructor(provider) {
this.provider = provider;
this.feeHistory = [];
this.predictionCache = new Map();
}
// 获取费用历史数据
async getFeeHistory(blockCount = 20, rewardPercentiles = [25, 50, 75]) {
try {
const feeHistory = await this.provider.send('eth_feeHistory', [
blockCount,
'latest',
rewardPercentiles
]);
return {
baseFeePerGas: feeHistory.baseFeePerGas.map(fee => ethers.BigNumber.from(fee)),
gasUsedRatio: feeHistory.gasUsedRatio,
reward: feeHistory.reward?.map(rewards =>
rewards.map(reward => ethers.BigNumber.from(reward))
),
oldestBlock: parseInt(feeHistory.oldestBlock, 16)
};
} catch (error) {
console.error('获取费用历史失败:', error);
return null;
}
}
// 预测优先费用
async predictPriorityFee(urgency = 'standard') {
const feeHistory = await this.getFeeHistory();
if (!feeHistory || !feeHistory.reward) {
// 回退到默认值
const defaults = {
slow: ethers.utils.parseUnits('1', 'gwei'),
standard: ethers.utils.parseUnits('2', 'gwei'),
fast: ethers.utils.parseUnits('3', 'gwei')
};
return defaults[urgency] || defaults.standard;
}
// 分析历史优先费用
const percentileMap = { slow: 0, standard: 1, fast: 2 };
const percentileIndex = percentileMap[urgency] || 1;
const recentRewards = feeHistory.reward.slice(-10); // 最近10个区块
let totalReward = ethers.BigNumber.from(0);
let count = 0;
recentRewards.forEach(rewards => {
if (rewards && rewards[percentileIndex]) {
totalReward = totalReward.add(rewards[percentileIndex]);
count++;
}
});
if (count === 0) {
return ethers.utils.parseUnits('2', 'gwei');
}
return totalReward.div(count);
}
// 估算最优 Gas 费用
async estimateOptimalFee(urgency = 'standard', blocksAhead = 3) {
try {
// 获取当前基础费用
const currentBlock = await this.provider.getBlock('pending');
const currentBaseFee = currentBlock.baseFeePerGas;
// 预测未来基础费用
const feeHistory = await this.getFeeHistory();
const predictedBaseFee = this.predictFutureBaseFee(
currentBaseFee,
feeHistory,
blocksAhead
);
// 计算推荐优先费用
const priorityFee = await this.predictPriorityFee(urgency);
// 计算最大费用(包含安全边际)
const safetyMultiplier = {
slow: 1.1,
standard: 1.25,
fast: 1.5
}[urgency] || 1.25;
const maxFeePerGas = predictedBaseFee.mul(Math.floor(safetyMultiplier * 100)).div(100).add(priorityFee);
return {
maxFeePerGas,
maxPriorityFeePerGas: priorityFee,
predictedBaseFee,
currentBaseFee,
estimatedGasPrice: predictedBaseFee.add(priorityFee)
};
} catch (error) {
console.error('费用估算失败:', error);
throw error;
}
}
// 预测未来基础费用
predictFutureBaseFee(currentBaseFee, feeHistory, blocksAhead) {
if (!feeHistory || !feeHistory.gasUsedRatio) {
return currentBaseFee;
}
// 计算最近的平均Gas使用率
const recentUsage = feeHistory.gasUsedRatio.slice(-5);
const avgUsageRatio = recentUsage.reduce((sum, ratio) => sum + ratio, 0) / recentUsage.length;
// 基于使用率趋势预测费用变化
let predictedFee = currentBaseFee;
for (let i = 0; i < blocksAhead; i++) {
if (avgUsageRatio > 0.5) {
// 网络拥堵,费用可能上升
const increaseRate = Math.min((avgUsageRatio - 0.5) * 0.25, 0.125); // 最多12.5%
predictedFee = predictedFee.mul(Math.floor((1 + increaseRate) * 10000)).div(10000);
} else {
// 网络空闲,费用可能下降
const decreaseRate = Math.min((0.5 - avgUsageRatio) * 0.25, 0.125);
predictedFee = predictedFee.mul(Math.floor((1 - decreaseRate) * 10000)).div(10000);
}
}
return predictedFee;
}
// 动态调整交易费用
async createDynamicTransaction(txParams, urgency = 'standard') {
const feeEstimate = await this.estimateOptimalFee(urgency);
const optimizedTx = {
...txParams,
maxFeePerGas: feeEstimate.maxFeePerGas,
maxPriorityFeePerGas: feeEstimate.maxPriorityFeePerGas,
type: 2 // EIP-1559 类型
};
// 记录费用信息用于分析
console.log('EIP-1559 交易费用:', {
maxFeePerGas: ethers.utils.formatUnits(feeEstimate.maxFeePerGas, 'gwei') + ' Gwei',
maxPriorityFeePerGas: ethers.utils.formatUnits(feeEstimate.maxPriorityFeePerGas, 'gwei') + ' Gwei',
predictedBaseFee: ethers.utils.formatUnits(feeEstimate.predictedBaseFee, 'gwei') + ' Gwei',
estimatedTotal: ethers.utils.formatUnits(feeEstimate.estimatedGasPrice, 'gwei') + ' Gwei'
});
return optimizedTx;
}
}
费用监控和分析:
class GasFeeAnalyzer {
constructor(provider) {
this.provider = provider;
this.transactionHistory = [];
this.feeEfficiencyMetrics = {
totalSaved: ethers.BigNumber.from(0),
averageEfficiency: 0,
transactionCount: 0
};
}
// 分析交易费用效率
async analyzeFeeEfficiency(txHash) {
const receipt = await this.provider.getTransactionReceipt(txHash);
const transaction = await this.provider.getTransaction(txHash);
if (!receipt || !transaction) {
throw new Error('交易不存在');
}
const actualGasUsed = receipt.gasUsed;
const effectiveGasPrice = receipt.effectiveGasPrice || transaction.gasPrice;
const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice;
// 计算实际费用 vs 最大可能费用
const actualCost = actualGasUsed.mul(effectiveGasPrice);
const maxPossibleCost = actualGasUsed.mul(maxFeePerGas);
const savedAmount = maxPossibleCost.sub(actualCost);
// 计算效率百分比
const efficiency = actualCost.mul(10000).div(maxPossibleCost).toNumber() / 100;
const analysis = {
txHash,
actualCost: ethers.utils.formatEther(actualCost),
maxPossibleCost: ethers.utils.formatEther(maxPossibleCost),
savedAmount: ethers.utils.formatEther(savedAmount),
efficiency: efficiency.toFixed(2) + '%',
gasUsed: actualGasUsed.toString(),
effectiveGasPrice: ethers.utils.formatUnits(effectiveGasPrice, 'gwei') + ' Gwei',
timestamp: new Date()
};
// 更新统计数据
this.updateEfficiencyMetrics(savedAmount, efficiency);
this.transactionHistory.push(analysis);
console.log('交易费用分析:', analysis);
return analysis;
}
// 更新效率指标
updateEfficiencyMetrics(savedAmount, efficiency) {
this.feeEfficiencyMetrics.totalSaved = this.feeEfficiencyMetrics.totalSaved.add(savedAmount);
const currentAvg = this.feeEfficiencyMetrics.averageEfficiency;
const count = this.feeEfficiencyMetrics.transactionCount;
this.feeEfficiencyMetrics.averageEfficiency =
(currentAvg * count + efficiency) / (count + 1);
this.feeEfficiencyMetrics.transactionCount = count + 1;
}
// 生成费用报告
generateFeeReport() {
if (this.transactionHistory.length === 0) {
return { message: '暂无交易数据' };
}
const totalTransactions = this.transactionHistory.length;
const totalSaved = ethers.utils.formatEther(this.feeEfficiencyMetrics.totalSaved);
const avgEfficiency = this.feeEfficiencyMetrics.averageEfficiency.toFixed(2);
// 计算费用趋势
const recentTxs = this.transactionHistory.slice(-10);
const avgRecentEfficiency = recentTxs.reduce(
(sum, tx) => sum + parseFloat(tx.efficiency.replace('%', '')), 0
) / recentTxs.length;
return {
summary: {
totalTransactions,
totalSaved: totalSaved + ' ETH',
averageEfficiency: avgEfficiency + '%',
recentEfficiency: avgRecentEfficiency.toFixed(2) + '%'
},
recommendations: this.generateOptimizationRecommendations(avgRecentEfficiency),
recentTransactions: recentTxs.map(tx => ({
hash: tx.txHash.slice(0, 10) + '...',
efficiency: tx.efficiency,
saved: tx.savedAmount,
timestamp: tx.timestamp.toLocaleString()
}))
};
}
// 生成优化建议
generateOptimizationRecommendations(recentEfficiency) {
const recommendations = [];
if (recentEfficiency < 70) {
recommendations.push('考虑使用更保守的 maxFeePerGas 设置');
recommendations.push('监控网络拥堵情况,选择合适的交易时机');
}
if (recentEfficiency > 95) {
recommendations.push('可以适当提高 maxPriorityFeePerGas 以加快确认速度');
}
recommendations.push('定期分析费用历史数据,调整费用策略');
recommendations.push('考虑使用费用预测API优化交易时机');
return recommendations;
}
}
实际应用示例:
// EIP-1559 完整使用流程
async function demonstrateEIP1559Usage() {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const feeManager = new SmartFeeManager(provider);
const feeAnalyzer = new GasFeeAnalyzer(provider);
try {
// 1. 准备交易参数
const baseTransaction = {
to: '0x742d35Cc6155A0532DF17E1E2B6b46e010e13E57',
value: ethers.utils.parseEther('0.1'),
gasLimit: 21000
};
// 2. 创建优化的 EIP-1559 交易
const optimizedTransaction = await feeManager.createDynamicTransaction(
baseTransaction,
'standard'
);
console.log('准备发送交易:', optimizedTransaction);
// 3. 发送交易
const tx = await wallet.sendTransaction(optimizedTransaction);
console.log('交易已发送:', tx.hash);
// 4. 等待确认
const receipt = await tx.wait();
console.log('交易已确认:', receipt.transactionHash);
// 5. 分析费用效率
const analysis = await feeAnalyzer.analyzeFeeEfficiency(receipt.transactionHash);
// 6. 生成费用报告
const report = feeAnalyzer.generateFeeReport();
console.log('费用效率报告:', report);
} catch (error) {
console.error('EIP-1559 交易失败:', error);
}
}
// 监控费用变化
async function monitorFeeChanges() {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const feeManager = new SmartFeeManager(provider);
setInterval(async () => {
try {
const feeEstimate = await feeManager.estimateOptimalFee('standard');
console.log('当前费用状况:', {
baseFee: ethers.utils.formatUnits(feeEstimate.currentBaseFee, 'gwei') + ' Gwei',
priorityFee: ethers.utils.formatUnits(feeEstimate.maxPriorityFeePerGas, 'gwei') + ' Gwei',
totalEstimate: ethers.utils.formatUnits(feeEstimate.estimatedGasPrice, 'gwei') + ' Gwei'
});
} catch (error) {
console.error('费用监控失败:', error);
}
}, 30000); // 每30秒检查一次
}
EIP-1559 的关键优势:
最佳实践建议:
maxFeePerGas(通常为预测基础费用的1.5-2倍)maxPriorityFeePerGasHow to implement atomic operations for contract calls?
How to implement atomic operations for contract calls?
考察点:原子性操作设计。
答案:
合约调用的原子性操作是指一系列操作要么全部成功,要么全部失败回滚的机制。这在 DeFi 协议中尤其重要,确保复杂的金融操作不会因部分失败而导致不一致的状态。实现原子性主要通过智能合约内部逻辑、Multicall 模式和事务性操作设计。
智能合约层面的原子性:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract AtomicSwapContract is ReentrancyGuard {
struct SwapParams {
address tokenA;
address tokenB;
uint256 amountA;
uint256 minAmountB;
address recipient;
uint256 deadline;
}
// 原子性代币交换
function atomicSwap(SwapParams calldata params)
external
nonReentrant
{
require(block.timestamp <= params.deadline, "交易已过期");
require(params.amountA > 0, "数量必须大于0");
require(params.minAmountB > 0, "最小接收数量必须大于0");
// 1. 转入代币A
IERC20(params.tokenA).transferFrom(
msg.sender,
address(this),
params.amountA
);
// 2. 执行交换逻辑(这里简化为1:2的固定比例)
uint256 amountB = params.amountA * 2;
require(amountB >= params.minAmountB, "滑点过大");
// 3. 检查合约是否有足够的代币B
require(
IERC20(params.tokenB).balanceOf(address(this)) >= amountB,
"流动性不足"
);
// 4. 转出代币B
IERC20(params.tokenB).transfer(params.recipient, amountB);
emit AtomicSwapCompleted(
msg.sender,
params.tokenA,
params.tokenB,
params.amountA,
amountB
);
// 注意:如果任何步骤失败,整个交易都会回滚
}
// 原子性多步骤操作
function atomicMultiStep(
address[] calldata tokens,
uint256[] calldata amounts,
bytes[] calldata data
) external nonReentrant {
require(
tokens.length == amounts.length &&
amounts.length == data.length,
"参数长度不匹配"
);
// 记录初始状态
mapping(address => uint256) storage initialBalances;
// 执行所有步骤
for (uint256 i = 0; i < tokens.length; i++) {
_executeStep(tokens[i], amounts[i], data[i]);
}
// 验证最终状态
_validateFinalState();
emit MultiStepCompleted(tokens, amounts);
}
function _executeStep(
address token,
uint256 amount,
bytes calldata data
) internal {
// 执行具体的步骤逻辑
// 任何失败都会导致整个交易回滚
}
function _validateFinalState() internal view {
// 验证最终状态的合理性
// 例如:检查余额变化是否符合预期
}
event AtomicSwapCompleted(
address indexed user,
address indexed tokenA,
address indexed tokenB,
uint256 amountA,
uint256 amountB
);
event MultiStepCompleted(
address[] tokens,
uint256[] amounts
);
}
Multicall 原子性模式:
class AtomicMulticall {
constructor(provider, multicallAddress) {
this.provider = provider;
this.multicallAddress = multicallAddress;
// Multicall3 ABI
this.multicallABI = [
"function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)"
];
this.multicallContract = new ethers.Contract(
multicallAddress,
this.multicallABI,
provider
);
}
// 构建原子性调用
async buildAtomicCalls(operations) {
const calls = [];
for (const operation of operations) {
const { contract, method, params, allowFailure = false } = operation;
// 编码函数调用
const callData = contract.interface.encodeFunctionData(method, params);
calls.push({
target: contract.address,
allowFailure,
callData
});
}
return calls;
}
// 执行原子性调用
async executeAtomicCalls(operations, signer, options = {}) {
try {
// 构建调用数据
const calls = await this.buildAtomicCalls(operations);
console.log(`准备执行 ${calls.length} 个原子性操作`);
// 估算 Gas
const gasEstimate = await this.multicallContract.connect(signer)
.estimateGas.aggregate3(calls, options);
// 添加 Gas 缓冲
const gasLimit = gasEstimate.mul(120).div(100);
// 执行交易
const tx = await this.multicallContract.connect(signer)
.aggregate3(calls, { ...options, gasLimit });
console.log(`原子性交易已提交: ${tx.hash}`);
// 等待确认
const receipt = await tx.wait();
// 解析结果
const results = await this.parseMulticallResults(receipt, operations);
console.log('原子性操作完成:', results);
return results;
} catch (error) {
console.error('原子性操作失败:', error);
throw error;
}
}
// 解析 Multicall 结果
async parseMulticallResults(receipt, operations) {
const results = [];
// 获取交易日志
const logs = receipt.logs;
operations.forEach((operation, index) => {
const { contract, method, expectedEvents = [] } = operation;
// 查找相关事件
const relatedLogs = logs.filter(log =>
log.address.toLowerCase() === contract.address.toLowerCase()
);
const parsedEvents = relatedLogs.map(log => {
try {
return contract.interface.parseLog(log);
} catch (error) {
return null;
}
}).filter(event => event !== null);
results.push({
operationIndex: index,
method,
success: true,
events: parsedEvents,
gasUsed: receipt.gasUsed.div(operations.length) // 平均分配
});
});
return results;
}
// 构建条件原子性操作
async buildConditionalAtomicCalls(conditions, operations) {
const calls = [];
for (let i = 0; i < operations.length; i++) {
const operation = operations[i];
const condition = conditions[i];
// 如果有条件,先添加条件检查
if (condition) {
const conditionCall = await this.buildConditionCheck(condition);
calls.push(conditionCall);
}
// 添加实际操作
const operationCall = await this.buildOperationCall(operation);
calls.push(operationCall);
}
return calls;
}
async buildConditionCheck(condition) {
const { contract, method, params, expectedResult } = condition;
// 构建条件检查调用
return {
target: contract.address,
allowFailure: false, // 条件检查不允许失败
callData: contract.interface.encodeFunctionData(method, params)
};
}
async buildOperationCall(operation) {
const { contract, method, params } = operation;
return {
target: contract.address,
allowFailure: false,
callData: contract.interface.encodeFunctionData(method, params)
};
}
}
原子性 DEX 交易实现:
class AtomicDEXTrader {
constructor(provider, signer, contracts) {
this.provider = provider;
this.signer = signer;
this.contracts = contracts; // { router, factory, weth, tokens }
this.multicall = new AtomicMulticall(provider, contracts.multicall);
}
// 原子性代币交换
async atomicSwap(swapParams) {
const {
tokenA,
tokenB,
amountIn,
minAmountOut,
deadline,
slippageTolerance = 0.005 // 0.5%
} = swapParams;
try {
// 1. 计算交换路径
const path = await this.calculateOptimalPath(tokenA, tokenB);
// 2. 获取预期输出
const expectedOutput = await this.getAmountsOut(amountIn, path);
const minAmountOutWithSlippage = expectedOutput[expectedOutput.length - 1]
.mul(Math.floor((1 - slippageTolerance) * 10000))
.div(10000);
// 确保满足最小输出要求
if (minAmountOutWithSlippage.lt(minAmountOut)) {
throw new Error('滑点过大,交易取消');
}
// 3. 构建原子性操作序列
const operations = [
// 授权代币
{
contract: new ethers.Contract(tokenA, ERC20_ABI, this.signer),
method: 'approve',
params: [this.contracts.router, amountIn],
expectedEvents: ['Approval']
},
// 执行交换
{
contract: this.contracts.router,
method: 'swapExactTokensForTokens',
params: [
amountIn,
minAmountOutWithSlippage,
path,
await this.signer.getAddress(),
deadline
],
expectedEvents: ['Swap']
}
];
// 4. 执行原子性交换
const result = await this.multicall.executeAtomicCalls(
operations,
this.signer
);
return {
success: true,
transactionHash: result.transactionHash,
amountIn,
amountOut: this.extractAmountOut(result),
gasUsed: result.gasUsed,
path
};
} catch (error) {
console.error('原子性交换失败:', error);
throw error;
}
}
// 原子性流动性操作
async atomicLiquidityOperation(liquidityParams) {
const {
operation, // 'add' | 'remove'
tokenA,
tokenB,
amountA,
amountB,
minAmountA,
minAmountB,
deadline
} = liquidityParams;
const operations = [];
if (operation === 'add') {
// 添加流动性的原子性操作
operations.push(
// 授权代币A
{
contract: new ethers.Contract(tokenA, ERC20_ABI, this.signer),
method: 'approve',
params: [this.contracts.router, amountA]
},
// 授权代币B
{
contract: new ethers.Contract(tokenB, ERC20_ABI, this.signer),
method: 'approve',
params: [this.contracts.router, amountB]
},
// 添加流动性
{
contract: this.contracts.router,
method: 'addLiquidity',
params: [
tokenA,
tokenB,
amountA,
amountB,
minAmountA,
minAmountB,
await this.signer.getAddress(),
deadline
]
}
);
} else if (operation === 'remove') {
// 移除流动性的原子性操作
const pairAddress = await this.contracts.factory.getPair(tokenA, tokenB);
const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, this.signer);
operations.push(
// 授权 LP 代币
{
contract: pairContract,
method: 'approve',
params: [this.contracts.router, amountA] // amountA 这里是 LP 代币数量
},
// 移除流动性
{
contract: this.contracts.router,
method: 'removeLiquidity',
params: [
tokenA,
tokenB,
amountA,
minAmountA,
minAmountB,
await this.signer.getAddress(),
deadline
]
}
);
}
return await this.multicall.executeAtomicCalls(operations, this.signer);
}
// 原子性套利操作
async atomicArbitrage(arbitrageParams) {
const {
tokenA,
tokenB,
amountIn,
exchanges, // [exchange1, exchange2]
minProfit
} = arbitrageParams;
// 计算预期利润
const profit = await this.calculateArbitrageProfit(
tokenA,
tokenB,
amountIn,
exchanges
);
if (profit.lt(minProfit)) {
throw new Error('套利机会不足');
}
// 构建原子性套利操作
const operations = [
// 在交易所1买入
{
contract: exchanges[0].router,
method: 'swapExactTokensForTokens',
params: [
amountIn,
0, // 最小输出在后面计算
[tokenA, tokenB],
await this.signer.getAddress(),
Math.floor(Date.now() / 1000) + 300
]
},
// 在交易所2卖出
{
contract: exchanges[1].router,
method: 'swapExactTokensForTokens',
params: [
0, // 将使用第一步的输出
amountIn.add(minProfit), // 确保有利润
[tokenB, tokenA],
await this.signer.getAddress(),
Math.floor(Date.now() / 1000) + 300
]
}
];
return await this.multicall.executeAtomicCalls(operations, this.signer);
}
// 计算最优路径
async calculateOptimalPath(tokenA, tokenB) {
// 直接路径
const directPath = [tokenA, tokenB];
// 通过 WETH 的路径
const wethPath = [tokenA, this.contracts.weth, tokenB];
// 比较两个路径的输出
try {
const directOutput = await this.contracts.router.getAmountsOut(
ethers.utils.parseEther('1'),
directPath
);
const wethOutput = await this.contracts.router.getAmountsOut(
ethers.utils.parseEther('1'),
wethPath
);
return wethOutput[wethOutput.length - 1].gt(directOutput[directOutput.length - 1])
? wethPath : directPath;
} catch (error) {
return directPath;
}
}
async getAmountsOut(amountIn, path) {
return await this.contracts.router.getAmountsOut(amountIn, path);
}
extractAmountOut(result) {
// 从交易结果中提取实际输出数量
// 这需要根据具体的事件日志来解析
return ethers.BigNumber.from(0);
}
async calculateArbitrageProfit(tokenA, tokenB, amountIn, exchanges) {
// 计算套利利润的逻辑
return ethers.BigNumber.from(0);
}
}
原子性状态管理:
class AtomicStateManager {
constructor() {
this.stateSnapshots = new Map();
this.operationQueue = [];
this.rollbackHandlers = [];
}
// 创建状态快照
async createSnapshot(key, stateReader) {
const snapshot = await stateReader();
this.stateSnapshots.set(key, {
timestamp: Date.now(),
state: snapshot
});
console.log(`状态快照已创建: ${key}`);
return snapshot;
}
// 原子性操作包装器
async executeAtomicOperation(operation, rollbackHandler) {
const operationId = Math.random().toString(36).substr(2, 9);
try {
// 添加到操作队列
this.operationQueue.push({
id: operationId,
operation,
rollbackHandler,
startTime: Date.now()
});
// 执行操作
const result = await operation();
// 操作成功,从队列移除
this.operationQueue = this.operationQueue.filter(op => op.id !== operationId);
console.log(`原子性操作完成: ${operationId}`);
return result;
} catch (error) {
console.error(`原子性操作失败: ${operationId}`, error);
// 执行回滚
if (rollbackHandler) {
try {
await rollbackHandler();
console.log(`回滚成功: ${operationId}`);
} catch (rollbackError) {
console.error(`回滚失败: ${operationId}`, rollbackError);
}
}
// 从队列移除
this.operationQueue = this.operationQueue.filter(op => op.id !== operationId);
throw error;
}
}
// 批量原子性操作
async executeBatchAtomicOperations(operations) {
const batchId = Math.random().toString(36).substr(2, 9);
const completedOperations = [];
try {
console.log(`开始批量原子性操作: ${batchId}`);
for (let i = 0; i < operations.length; i++) {
const { operation, rollbackHandler } = operations[i];
const result = await operation();
completedOperations.push({ result, rollbackHandler });
console.log(`批量操作 ${i + 1}/${operations.length} 完成`);
}
console.log(`批量原子性操作完成: ${batchId}`);
return completedOperations.map(op => op.result);
} catch (error) {
console.error(`批量原子性操作失败: ${batchId}`, error);
// 逆序回滚已完成的操作
console.log('开始回滚已完成的操作...');
for (let i = completedOperations.length - 1; i >= 0; i--) {
const { rollbackHandler } = completedOperations[i];
if (rollbackHandler) {
try {
await rollbackHandler();
console.log(`回滚操作 ${i + 1} 成功`);
} catch (rollbackError) {
console.error(`回滚操作 ${i + 1} 失败:`, rollbackError);
}
}
}
throw error;
}
}
// 清理过期快照
cleanupExpiredSnapshots(maxAge = 3600000) { // 1小时
const now = Date.now();
const expiredKeys = [];
for (const [key, snapshot] of this.stateSnapshots) {
if (now - snapshot.timestamp > maxAge) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => this.stateSnapshots.delete(key));
if (expiredKeys.length > 0) {
console.log(`清理了 ${expiredKeys.length} 个过期快照`);
}
}
}
使用示例:
// 使用原子性操作进行复杂的 DeFi 交易
async function demonstrateAtomicOperations() {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const contracts = {
router: new ethers.Contract(ROUTER_ADDRESS, ROUTER_ABI, signer),
factory: new ethers.Contract(FACTORY_ADDRESS, FACTORY_ABI, provider),
multicall: MULTICALL_ADDRESS,
weth: WETH_ADDRESS
};
const atomicTrader = new AtomicDEXTrader(provider, signer, contracts);
const stateManager = new AtomicStateManager();
try {
// 1. 原子性代币交换
const swapResult = await atomicTrader.atomicSwap({
tokenA: TOKEN_A_ADDRESS,
tokenB: TOKEN_B_ADDRESS,
amountIn: ethers.utils.parseEther('1'),
minAmountOut: ethers.utils.parseEther('0.95'),
deadline: Math.floor(Date.now() / 1000) + 300,
slippageTolerance: 0.01
});
console.log('原子性交换完成:', swapResult);
// 2. 原子性流动性操作
const liquidityResult = await atomicTrader.atomicLiquidityOperation({
operation: 'add',
tokenA: TOKEN_A_ADDRESS,
tokenB: TOKEN_B_ADDRESS,
amountA: ethers.utils.parseEther('1'),
amountB: ethers.utils.parseEther('2'),
minAmountA: ethers.utils.parseEther('0.95'),
minAmountB: ethers.utils.parseEther('1.9'),
deadline: Math.floor(Date.now() / 1000) + 300
});
console.log('原子性流动性操作完成:', liquidityResult);
} catch (error) {
console.error('原子性操作演示失败:', error);
}
}
原子性操作的关键要点:
最佳实践:
What is the Factory contract pattern? How to interact with factory contracts?
What is the Factory contract pattern? How to interact with factory contracts?
考察点:合约设计模式。
答案:
Factory(工厂)合约模式是一种创建型设计模式,用于标准化和自动化其他合约的部署过程。工厂合约作为合约部署的中央管理器,可以创建具有相似功能但不同参数的合约实例,广泛应用于 DEX、代币发行、NFT 集合等场景。
基础工厂合约实现:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
contract TokenFactory is Ownable {
// 部署记录
mapping(address => bool) public isDeployedByFactory;
mapping(bytes32 => address) public deployedContracts;
address[] public allDeployedContracts;
// 统计信息
uint256 public totalDeployed;
mapping(address => uint256) public deployerCount;
uint256 public deploymentFee = 0.01 ether;
bool public feeRequired = false;
event ContractDeployed(
address indexed deployer,
address indexed contractAddress,
string name,
string symbol,
uint256 totalSupply,
bytes32 salt
);
// 部署代币合约
function deployToken(
string memory name,
string memory symbol,
uint256 totalSupply,
bytes32 salt
) external payable returns (address) {
if (feeRequired) {
require(msg.value >= deploymentFee, "部署费用不足");
}
// 检查重复部署
bytes32 contractId = keccak256(
abi.encodePacked(name, symbol, totalSupply, msg.sender)
);
require(deployedContracts[contractId] == address(0), "合约已存在");
// 准备字节码
bytes memory bytecode = abi.encodePacked(
type(StandardERC20).creationCode,
abi.encode(name, symbol, totalSupply, msg.sender)
);
// 使用 CREATE2 部署
address tokenAddress = Create2.deploy(0, salt, bytecode);
// 记录部署信息
isDeployedByFactory[tokenAddress] = true;
deployedContracts[contractId] = tokenAddress;
allDeployedContracts.push(tokenAddress);
totalDeployed++;
deployerCount[msg.sender]++;
emit ContractDeployed(
msg.sender, tokenAddress, name, symbol, totalSupply, salt
);
return tokenAddress;
}
// 预计算地址
function computeAddress(
string memory name,
string memory symbol,
uint256 totalSupply,
address deployer,
bytes32 salt
) external view returns (address) {
bytes memory bytecode = abi.encodePacked(
type(StandardERC20).creationCode,
abi.encode(name, symbol, totalSupply, deployer)
);
return Create2.computeAddress(salt, keccak256(bytecode), address(this));
}
// 批量部署
function batchDeployTokens(
TokenParams[] memory tokens,
bytes32[] memory salts
) external payable returns (address[] memory) {
require(tokens.length == salts.length, "参数长度不匹配");
require(tokens.length <= 10, "批量限制10个");
uint256 totalFee = feeRequired ? deploymentFee * tokens.length : 0;
require(msg.value >= totalFee, "批量费用不足");
address[] memory addresses = new address[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
addresses[i] = _deployTokenInternal(
tokens[i].name,
tokens[i].symbol,
tokens[i].totalSupply,
salts[i]
);
}
return addresses;
}
struct TokenParams {
string name;
string symbol;
uint256 totalSupply;
}
}
工厂合约交互管理器:
class FactoryManager {
constructor(provider, factoryAddress, factoryABI) {
this.provider = provider;
this.factoryAddress = factoryAddress;
this.factoryContract = new ethers.Contract(
factoryAddress,
factoryABI,
provider
);
this.deployedContracts = new Map();
this.eventCache = new Map();
}
// 部署单个代币
async deployToken(params, signer) {
const { name, symbol, totalSupply, salt } = params;
try {
// 检查部署费用
const [feeRequired, deploymentFee] = await Promise.all([
this.factoryContract.feeRequired(),
this.factoryContract.deploymentFee()
]);
const value = feeRequired ? deploymentFee : 0;
// 预计算地址
const saltBytes = salt || ethers.utils.id(`${name}-${symbol}-${Date.now()}`);
const predictedAddress = await this.factoryContract.computeAddress(
name,
symbol,
ethers.utils.parseEther(totalSupply.toString()),
await signer.getAddress(),
saltBytes
);
console.log(`预计合约地址: ${predictedAddress}`);
// 估算 Gas
const gasEstimate = await this.factoryContract.connect(signer)
.estimateGas.deployToken(
name,
symbol,
ethers.utils.parseEther(totalSupply.toString()),
saltBytes,
{ value }
);
// 执行部署
const tx = await this.factoryContract.connect(signer).deployToken(
name,
symbol,
ethers.utils.parseEther(totalSupply.toString()),
saltBytes,
{
value,
gasLimit: gasEstimate.mul(120).div(100)
}
);
console.log(`部署交易: ${tx.hash}`);
// 等待确认
const receipt = await tx.wait();
const deployedAddress = this.extractContractAddress(receipt);
// 缓存合约信息
this.cacheContractInfo(deployedAddress, {
name, symbol, totalSupply,
deployer: await signer.getAddress(),
txHash: tx.hash,
blockNumber: receipt.blockNumber
});
return {
success: true,
contractAddress: deployedAddress,
transactionHash: tx.hash,
gasUsed: receipt.gasUsed,
predictedCorrect: deployedAddress.toLowerCase() === predictedAddress.toLowerCase()
};
} catch (error) {
console.error('代币部署失败:', error);
throw error;
}
}
// 批量部署代币
async batchDeployTokens(tokensParams, signer) {
try {
const tokens = tokensParams.map(token => ({
name: token.name,
symbol: token.symbol,
totalSupply: ethers.utils.parseEther(token.totalSupply.toString())
}));
const salts = tokensParams.map((token, index) =>
token.salt || ethers.utils.id(`${token.name}-batch-${Date.now()}-${index}`)
);
// 计算总费用
const [feeRequired, deploymentFee] = await Promise.all([
this.factoryContract.feeRequired(),
this.factoryContract.deploymentFee()
]);
const totalValue = feeRequired ? deploymentFee.mul(tokens.length) : 0;
console.log(`批量部署 ${tokens.length} 个代币,总费用: ${ethers.utils.formatEther(totalValue)} ETH`);
// 执行批量部署
const tx = await this.factoryContract.connect(signer)
.batchDeployTokens(tokens, salts, { value: totalValue });
console.log(`批量部署交易: ${tx.hash}`);
const receipt = await tx.wait();
const addresses = this.extractBatchAddresses(receipt);
// 缓存所有合约信息
addresses.forEach((address, index) => {
this.cacheContractInfo(address, {
...tokensParams[index],
deployer: signer.address,
txHash: tx.hash,
blockNumber: receipt.blockNumber,
batchIndex: index
});
});
return {
success: true,
contractAddresses: addresses,
transactionHash: tx.hash,
gasUsed: receipt.gasUsed
};
} catch (error) {
console.error('批量部署失败:', error);
throw error;
}
}
// 获取用户部署的所有合约
async getUserContracts(userAddress) {
try {
const contractCount = await this.factoryContract.deployerCount(userAddress);
if (contractCount.eq(0)) {
return [];
}
// 获取工厂部署的所有合约
const totalContracts = await this.factoryContract.totalDeployed();
const userContracts = [];
for (let i = 0; i < totalContracts.toNumber(); i++) {
const contractAddress = await this.factoryContract.allDeployedContracts(i);
// 检查是否为指定用户部署
if (await this.isContractOwnedBy(contractAddress, userAddress)) {
const contractInfo = await this.getContractDetails(contractAddress);
userContracts.push(contractInfo);
}
}
return userContracts;
} catch (error) {
console.error('获取用户合约失败:', error);
throw error;
}
}
// 获取合约详细信息
async getContractDetails(contractAddress) {
try {
// 检查缓存
const cached = this.deployedContracts.get(contractAddress);
if (cached) {
return cached;
}
// 创建合约实例获取信息
const contract = new ethers.Contract(
contractAddress,
[
'function name() view returns (string)',
'function symbol() view returns (string)',
'function totalSupply() view returns (uint256)',
'function owner() view returns (address)',
'function balanceOf(address) view returns (uint256)'
],
this.provider
);
const [name, symbol, totalSupply, owner] = await Promise.all([
contract.name(),
contract.symbol(),
contract.totalSupply(),
contract.owner()
]);
const info = {
address: contractAddress,
name,
symbol,
totalSupply: ethers.utils.formatEther(totalSupply),
owner,
isVerified: await this.factoryContract.isDeployedByFactory(contractAddress)
};
// 缓存信息
this.deployedContracts.set(contractAddress, info);
return info;
} catch (error) {
console.error('获取合约详情失败:', error);
throw error;
}
}
// 监听工厂事件
async startEventMonitoring() {
const filter = this.factoryContract.filters.ContractDeployed();
// 监听新部署事件
this.factoryContract.on(filter, (deployer, contractAddress, name, symbol, totalSupply, salt, event) => {
const deploymentInfo = {
deployer,
contractAddress,
name,
symbol,
totalSupply: ethers.utils.formatEther(totalSupply),
salt,
txHash: event.transactionHash,
blockNumber: event.blockNumber,
timestamp: new Date()
};
console.log('🚀 新合约部署:', deploymentInfo);
// 缓存事件信息
this.eventCache.set(event.transactionHash, deploymentInfo);
this.cacheContractInfo(contractAddress, deploymentInfo);
});
console.log('开始监听工厂合约事件...');
}
// 停止事件监听
stopEventMonitoring() {
this.factoryContract.removeAllListeners();
console.log('已停止事件监听');
}
// 获取工厂统计信息
async getFactoryStats() {
try {
const [
totalDeployed,
deploymentFee,
feeRequired,
owner
] = await Promise.all([
this.factoryContract.totalDeployed(),
this.factoryContract.deploymentFee(),
this.factoryContract.feeRequired(),
this.factoryContract.owner()
]);
return {
totalDeployed: totalDeployed.toNumber(),
deploymentFee: ethers.utils.formatEther(deploymentFee) + ' ETH',
feeRequired,
factoryOwner: owner,
factoryAddress: this.factoryAddress
};
} catch (error) {
console.error('获取工厂统计失败:', error);
throw error;
}
}
// 辅助方法
extractContractAddress(receipt) {
const deployEvent = receipt.logs.find(log => {
try {
const parsed = this.factoryContract.interface.parseLog(log);
return parsed.name === 'ContractDeployed';
} catch {
return false;
}
});
if (deployEvent) {
const parsed = this.factoryContract.interface.parseLog(deployEvent);
return parsed.args.contractAddress;
}
throw new Error('未找到部署事件');
}
extractBatchAddresses(receipt) {
const addresses = [];
receipt.logs.forEach(log => {
try {
const parsed = this.factoryContract.interface.parseLog(log);
if (parsed.name === 'ContractDeployed') {
addresses.push(parsed.args.contractAddress);
}
} catch {
// 忽略解析失败的日志
}
});
return addresses;
}
cacheContractInfo(address, info) {
this.deployedContracts.set(address, {
...info,
cachedAt: new Date()
});
}
async isContractOwnedBy(contractAddress, userAddress) {
try {
const contract = new ethers.Contract(
contractAddress,
['function owner() view returns (address)'],
this.provider
);
const owner = await contract.owner();
return owner.toLowerCase() === userAddress.toLowerCase();
} catch {
return false;
}
}
}
高级工厂模式应用:
// NFT 集合工厂
class NFTCollectionFactory {
constructor(provider, factoryAddress) {
this.provider = provider;
this.factoryContract = new ethers.Contract(
factoryAddress,
NFT_FACTORY_ABI,
provider
);
this.collections = new Map();
}
// 部署 NFT 集合
async deployNFTCollection(params, signer) {
const {
name, symbol, baseURI, maxSupply,
mintPrice, royaltyReceiver, royaltyFee
} = params;
try {
const tx = await this.factoryContract.connect(signer)
.deployCollection(
name, symbol, baseURI, maxSupply,
ethers.utils.parseEther(mintPrice.toString()),
royaltyReceiver, royaltyFee * 100
);
const receipt = await tx.wait();
const collectionAddress = this.extractCollectionAddress(receipt);
// 缓存集合信息
this.collections.set(collectionAddress, {
name, symbol, baseURI, maxSupply,
mintPrice, royaltyReceiver, royaltyFee,
creator: await signer.getAddress(),
deployedAt: new Date()
});
return {
success: true,
collectionAddress,
transactionHash: tx.hash
};
} catch (error) {
console.error('NFT 集合部署失败:', error);
throw error;
}
}
// 获取集合信息
async getCollectionInfo(collectionAddress) {
const cached = this.collections.get(collectionAddress);
if (cached) return cached;
const collection = new ethers.Contract(
collectionAddress,
NFT_ABI,
this.provider
);
const [name, symbol, totalSupply, maxSupply] = await Promise.all([
collection.name(),
collection.symbol(),
collection.totalSupply(),
collection.maxSupply()
]);
return {
address: collectionAddress,
name, symbol,
totalSupply: totalSupply.toNumber(),
maxSupply: maxSupply.toNumber(),
progress: (totalSupply.toNumber() / maxSupply.toNumber() * 100).toFixed(2)
};
}
extractCollectionAddress(receipt) {
// 实现提取逻辑
return receipt.logs[0].address; // 简化实现
}
}
// DEX 交易对工厂
class DEXFactory {
constructor(provider, factoryAddress) {
this.provider = provider;
this.factoryContract = new ethers.Contract(
factoryAddress,
DEX_FACTORY_ABI,
provider
);
this.pairs = new Map();
}
// 创建交易对
async createPair(tokenA, tokenB, signer) {
try {
// 检查是否已存在
const existingPair = await this.factoryContract.getPair(tokenA, tokenB);
if (existingPair !== ethers.constants.AddressZero) {
return existingPair;
}
const tx = await this.factoryContract.connect(signer)
.createPair(tokenA, tokenB);
const receipt = await tx.wait();
const pairAddress = this.extractPairAddress(receipt);
// 缓存交易对
this.pairs.set(`${tokenA}-${tokenB}`, pairAddress);
this.pairs.set(`${tokenB}-${tokenA}`, pairAddress);
return pairAddress;
} catch (error) {
console.error('创建交易对失败:', error);
throw error;
}
}
// 获取所有交易对
async getAllPairs() {
const pairCount = await this.factoryContract.allPairsLength();
const pairs = [];
for (let i = 0; i < pairCount.toNumber(); i++) {
const pairAddress = await this.factoryContract.allPairs(i);
const pairInfo = await this.getPairInfo(pairAddress);
pairs.push(pairInfo);
}
return pairs;
}
async getPairInfo(pairAddress) {
const pair = new ethers.Contract(pairAddress, PAIR_ABI, this.provider);
const [token0, token1, reserves] = await Promise.all([
pair.token0(),
pair.token1(),
pair.getReserves()
]);
return {
address: pairAddress,
token0, token1,
reserve0: reserves[0],
reserve1: reserves[1]
};
}
extractPairAddress(receipt) {
// 从 PairCreated 事件中提取地址
const event = receipt.logs.find(log => {
try {
const parsed = this.factoryContract.interface.parseLog(log);
return parsed.name === 'PairCreated';
} catch {
return false;
}
});
if (event) {
const parsed = this.factoryContract.interface.parseLog(event);
return parsed.args.pair;
}
throw new Error('未找到 PairCreated 事件');
}
}
使用示例:
async function demonstrateFactoryPattern() {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
// 1. 代币工厂演示
const tokenFactory = new FactoryManager(
provider,
TOKEN_FACTORY_ADDRESS,
TOKEN_FACTORY_ABI
);
try {
// 开始监听事件
await tokenFactory.startEventMonitoring();
// 部署单个代币
const deployResult = await tokenFactory.deployToken({
name: 'MyFactoryToken',
symbol: 'MFT',
totalSupply: 1000000
}, signer);
console.log('部署结果:', deployResult);
// 批量部署
const batchResult = await tokenFactory.batchDeployTokens([
{ name: 'BatchToken1', symbol: 'BT1', totalSupply: 100000 },
{ name: 'BatchToken2', symbol: 'BT2', totalSupply: 200000 }
], signer);
console.log('批量部署结果:', batchResult);
// 获取统计信息
const stats = await tokenFactory.getFactoryStats();
console.log('工厂统计:', stats);
// 获取用户合约
const userContracts = await tokenFactory.getUserContracts(await signer.getAddress());
console.log('用户合约:', userContracts);
} catch (error) {
console.error('工厂演示失败:', error);
} finally {
tokenFactory.stopEventMonitoring();
}
}
// 执行演示
demonstrateFactoryPattern();
Factory 模式的关键优势:
最佳实践:
How to monitor and analyze contract Gas usage?
How to monitor and analyze contract Gas usage?
考察点:Gas使用分析与优化。
答案:
Gas 使用监控和分析是智能合约优化的重要环节。通过系统性地跟踪和分析 Gas 消耗模式,可以识别性能瓶颈、优化合约设计、预测交易成本,并确保最佳的用户体验。
Gas 监控基础架构:
class GasMonitor {
constructor(provider, contractAddress, contractABI) {
this.provider = provider;
this.contractAddress = contractAddress;
this.contract = new ethers.Contract(contractAddress, contractABI, provider);
// Gas 使用历史记录
this.gasHistory = [];
this.functionGasStats = new Map();
this.dailyStats = new Map();
// 监控配置
this.alertThresholds = {
high: 500000, // 高 Gas 使用警告
extreme: 1000000, // 极高 Gas 使用警告
efficiency: 0.7 // 效率警告阈值
};
this.isMonitoring = false;
}
// 开始监控合约 Gas 使用
async startMonitoring() {
if (this.isMonitoring) {
console.log('监控已在运行');
return;
}
this.isMonitoring = true;
console.log(`开始监控合约 ${this.contractAddress} 的 Gas 使用`);
// 监听所有交易
this.provider.on('block', async (blockNumber) => {
if (!this.isMonitoring) return;
try {
await this.analyzeBlockTransactions(blockNumber);
} catch (error) {
console.error(`分析区块 ${blockNumber} 失败:`, error);
}
});
// 定期生成报告
this.reportInterval = setInterval(() => {
this.generatePeriodicReport();
}, 300000); // 每5分钟
console.log('Gas 监控已启动');
}
// 停止监控
stopMonitoring() {
this.isMonitoring = false;
this.provider.removeAllListeners('block');
if (this.reportInterval) {
clearInterval(this.reportInterval);
}
console.log('Gas 监控已停止');
}
// 分析区块中的相关交易
async analyzeBlockTransactions(blockNumber) {
try {
const block = await this.provider.getBlock(blockNumber, true);
if (!block || !block.transactions) {
return;
}
// 过滤与监控合约相关的交易
const relevantTxs = block.transactions.filter(tx =>
tx.to && tx.to.toLowerCase() === this.contractAddress.toLowerCase()
);
if (relevantTxs.length === 0) {
return;
}
console.log(`区块 ${blockNumber} 发现 ${relevantTxs.length} 个相关交易`);
// 分析每个交易
for (const tx of relevantTxs) {
await this.analyzeTransaction(tx);
}
} catch (error) {
console.error(`分析区块 ${blockNumber} 失败:`, error);
}
}
// 分析单个交易的 Gas 使用
async analyzeTransaction(tx) {
try {
const receipt = await this.provider.getTransactionReceipt(tx.hash);
if (!receipt) {
return;
}
// 解析函数调用
const functionCall = this.parseFunctionCall(tx.data);
// 计算 Gas 效率
const gasEfficiency = receipt.gasUsed.mul(10000).div(tx.gasLimit);
// 计算实际费用
const actualCost = receipt.gasUsed.mul(receipt.effectiveGasPrice || tx.gasPrice);
const gasAnalysis = {
txHash: tx.hash,
blockNumber: receipt.blockNumber,
timestamp: new Date(),
functionName: functionCall.name,
functionParams: functionCall.params,
// Gas 数据
gasLimit: tx.gasLimit,
gasUsed: receipt.gasUsed,
gasPrice: receipt.effectiveGasPrice || tx.gasPrice,
// 效率指标
efficiency: gasEfficiency.toNumber() / 100, // 转换为百分比
actualCost: ethers.utils.formatEther(actualCost),
// 状态
status: receipt.status,
success: receipt.status === 1
};
// 记录到历史
this.recordGasUsage(gasAnalysis);
// 检查警告条件
this.checkAlerts(gasAnalysis);
console.log(`交易 ${tx.hash.slice(0, 10)}... Gas分析:`, {
function: functionCall.name,
gasUsed: receipt.gasUsed.toString(),
efficiency: (gasAnalysis.efficiency).toFixed(2) + '%',
cost: gasAnalysis.actualCost + ' ETH'
});
return gasAnalysis;
} catch (error) {
console.error(`分析交易 ${tx.hash} 失败:`, error);
}
}
// 解析函数调用
parseFunctionCall(data) {
try {
if (!data || data.length < 10) {
return { name: 'unknown', params: [] };
}
const functionSelector = data.slice(0, 10);
// 尝试从合约 ABI 中匹配函数
for (const fragment of this.contract.interface.fragments) {
if (fragment.type === 'function') {
const selector = this.contract.interface.getSighash(fragment);
if (selector === functionSelector) {
try {
const decoded = this.contract.interface.decodeFunctionData(fragment, data);
return {
name: fragment.name,
params: decoded,
signature: fragment.format()
};
} catch (decodeError) {
return { name: fragment.name, params: [], error: 'decode_failed' };
}
}
}
}
return { name: 'unknown', selector: functionSelector, params: [] };
} catch (error) {
return { name: 'parse_error', error: error.message, params: [] };
}
}
// 记录 Gas 使用数据
recordGasUsage(gasAnalysis) {
// 添加到历史记录
this.gasHistory.push(gasAnalysis);
// 限制历史记录大小
if (this.gasHistory.length > 1000) {
this.gasHistory = this.gasHistory.slice(-1000);
}
// 更新函数级别统计
this.updateFunctionStats(gasAnalysis);
// 更新日常统计
this.updateDailyStats(gasAnalysis);
}
// 更新函数级别统计
updateFunctionStats(gasAnalysis) {
const functionName = gasAnalysis.functionName;
if (!this.functionGasStats.has(functionName)) {
this.functionGasStats.set(functionName, {
callCount: 0,
totalGasUsed: ethers.BigNumber.from(0),
totalCost: ethers.BigNumber.from(0),
avgGasUsed: ethers.BigNumber.from(0),
avgCost: ethers.BigNumber.from(0),
minGasUsed: ethers.constants.MaxUint256,
maxGasUsed: ethers.BigNumber.from(0),
successRate: 0,
successCount: 0
});
}
const stats = this.functionGasStats.get(functionName);
stats.callCount++;
stats.totalGasUsed = stats.totalGasUsed.add(gasAnalysis.gasUsed);
const cost = gasAnalysis.gasUsed.mul(gasAnalysis.gasPrice);
stats.totalCost = stats.totalCost.add(cost);
if (gasAnalysis.gasUsed.lt(stats.minGasUsed)) {
stats.minGasUsed = gasAnalysis.gasUsed;
}
if (gasAnalysis.gasUsed.gt(stats.maxGasUsed)) {
stats.maxGasUsed = gasAnalysis.gasUsed;
}
if (gasAnalysis.success) {
stats.successCount++;
}
// 计算平均值
stats.avgGasUsed = stats.totalGasUsed.div(stats.callCount);
stats.avgCost = stats.totalCost.div(stats.callCount);
stats.successRate = (stats.successCount / stats.callCount) * 100;
}
// 更新日常统计
updateDailyStats(gasAnalysis) {
const dateKey = gasAnalysis.timestamp.toISOString().split('T')[0];
if (!this.dailyStats.has(dateKey)) {
this.dailyStats.set(dateKey, {
date: dateKey,
transactionCount: 0,
totalGasUsed: ethers.BigNumber.from(0),
totalCost: ethers.BigNumber.from(0),
avgGasPrice: ethers.BigNumber.from(0),
successCount: 0
});
}
const dailyStat = this.dailyStats.get(dateKey);
dailyStat.transactionCount++;
dailyStat.totalGasUsed = dailyStat.totalGasUsed.add(gasAnalysis.gasUsed);
const cost = gasAnalysis.gasUsed.mul(gasAnalysis.gasPrice);
dailyStat.totalCost = dailyStat.totalCost.add(cost);
if (gasAnalysis.success) {
dailyStat.successCount++;
}
// 计算平均 Gas 价格
dailyStat.avgGasPrice = dailyStat.totalCost.div(dailyStat.totalGasUsed);
}
// 检查警告条件
checkAlerts(gasAnalysis) {
const alerts = [];
// 高 Gas 使用警告
if (gasAnalysis.gasUsed.gte(this.alertThresholds.high)) {
alerts.push({
type: 'HIGH_GAS_USAGE',
severity: gasAnalysis.gasUsed.gte(this.alertThresholds.extreme) ? 'critical' : 'warning',
message: `函数 ${gasAnalysis.functionName} 使用了 ${gasAnalysis.gasUsed.toString()} Gas`,
gasUsed: gasAnalysis.gasUsed.toString(),
threshold: this.alertThresholds.high
});
}
// 低效率警告
if (gasAnalysis.efficiency < this.alertThresholds.efficiency) {
alerts.push({
type: 'LOW_EFFICIENCY',
severity: 'info',
message: `交易效率较低: ${(gasAnalysis.efficiency * 100).toFixed(2)}%`,
efficiency: gasAnalysis.efficiency,
threshold: this.alertThresholds.efficiency
});
}
// 失败交易警告
if (!gasAnalysis.success) {
alerts.push({
type: 'TRANSACTION_FAILED',
severity: 'error',
message: '交易执行失败',
txHash: gasAnalysis.txHash
});
}
if (alerts.length > 0) {
console.warn('🚨 Gas 监控警告:', alerts);
}
}
// 生成定期报告
generatePeriodicReport() {
const recentTransactions = this.gasHistory.slice(-50); // 最近50笔交易
if (recentTransactions.length === 0) {
return;
}
console.log('\n📊 Gas 使用报告 (最近50笔交易):');
console.log('=' .repeat(50));
// 总体统计
const totalGas = recentTransactions.reduce(
(sum, tx) => sum.add(tx.gasUsed),
ethers.BigNumber.from(0)
);
const avgGas = totalGas.div(recentTransactions.length);
const successfulTxs = recentTransactions.filter(tx => tx.success).length;
const successRate = (successfulTxs / recentTransactions.length) * 100;
console.log(`总交易数: ${recentTransactions.length}`);
console.log(`成功率: ${successRate.toFixed(2)}%`);
console.log(`平均 Gas 使用: ${avgGas.toString()}`);
console.log(`总 Gas 消耗: ${totalGas.toString()}`);
// 函数级别统计
console.log('\n📋 函数 Gas 使用统计:');
this.functionGasStats.forEach((stats, functionName) => {
console.log(`${functionName}:`);
console.log(` 调用次数: ${stats.callCount}`);
console.log(` 平均 Gas: ${stats.avgGasUsed.toString()}`);
console.log(` Gas 范围: ${stats.minGasUsed.toString()} - ${stats.maxGasUsed.toString()}`);
console.log(` 成功率: ${stats.successRate.toFixed(2)}%`);
});
console.log('=' .repeat(50) + '\n');
}
}
Gas 分析工具:
class GasAnalyzer {
constructor(provider) {
this.provider = provider;
this.analysisCache = new Map();
this.benchmarks = new Map();
}
// 深度分析单个交易的 Gas 使用
async analyzeTransactionGas(txHash) {
try {
console.log(`开始深度分析交易: ${txHash}`);
const [tx, receipt] = await Promise.all([
this.provider.getTransaction(txHash),
this.provider.getTransactionReceipt(txHash)
]);
if (!tx || !receipt) {
throw new Error('交易不存在或未确认');
}
// 基础 Gas 分析
const basicAnalysis = this.calculateBasicGasMetrics(tx, receipt);
// 如果是合约调用,进行详细分析
let detailedAnalysis = null;
if (tx.to && tx.data && tx.data !== '0x') {
detailedAnalysis = await this.analyzeContractCall(tx, receipt);
}
// 与基准对比
const benchmarkComparison = this.compareToBenchmarks(basicAnalysis, detailedAnalysis);
const analysis = {
txHash,
basic: basicAnalysis,
detailed: detailedAnalysis,
benchmark: benchmarkComparison,
recommendations: this.generateOptimizationRecommendations(basicAnalysis, detailedAnalysis),
timestamp: new Date()
};
// 缓存分析结果
this.analysisCache.set(txHash, analysis);
console.log('交易 Gas 分析完成:', analysis);
return analysis;
} catch (error) {
console.error('交易 Gas 分析失败:', error);
throw error;
}
}
// 计算基础 Gas 指标
calculateBasicGasMetrics(tx, receipt) {
const gasLimit = tx.gasLimit;
const gasUsed = receipt.gasUsed;
const gasPrice = receipt.effectiveGasPrice || tx.gasPrice;
// Gas 效率(使用率)
const gasEfficiency = gasUsed.mul(10000).div(gasLimit).toNumber() / 100;
// 实际费用
const actualCost = gasUsed.mul(gasPrice);
const maxPossibleCost = gasLimit.mul(gasPrice);
const savedAmount = maxPossibleCost.sub(actualCost);
// Gas 价格分析
const gasPriceGwei = parseFloat(ethers.utils.formatUnits(gasPrice, 'gwei'));
return {
gasLimit: gasLimit.toString(),
gasUsed: gasUsed.toString(),
gasPrice: gasPriceGwei + ' Gwei',
efficiency: gasEfficiency.toFixed(2) + '%',
actualCost: ethers.utils.formatEther(actualCost) + ' ETH',
maxPossibleCost: ethers.utils.formatEther(maxPossibleCost) + ' ETH',
savedAmount: ethers.utils.formatEther(savedAmount) + ' ETH',
status: receipt.status === 1 ? 'success' : 'failed'
};
}
// 分析合约调用
async analyzeContractCall(tx, receipt) {
try {
// 获取合约字节码以分析复杂度
const contractCode = await this.provider.getCode(tx.to);
const codeSize = (contractCode.length - 2) / 2; // 移除 '0x' 前缀
// 分析调用数据
const callDataSize = (tx.data.length - 2) / 2;
// 分析事件日志
const eventAnalysis = this.analyzeEventLogs(receipt.logs);
// 估算操作复杂度
const complexityAnalysis = this.estimateOperationComplexity(
tx, receipt, codeSize, callDataSize
);
return {
contractAddress: tx.to,
codeSize,
callDataSize,
eventCount: receipt.logs.length,
eventAnalysis,
complexityAnalysis,
gasPerByte: receipt.gasUsed.div(Math.max(callDataSize, 1)).toString(),
storageOperations: this.estimateStorageOperations(receipt.logs)
};
} catch (error) {
console.error('合约调用分析失败:', error);
return null;
}
}
// 分析事件日志
analyzeEventLogs(logs) {
const topicCounts = new Map();
const addressCounts = new Map();
logs.forEach(log => {
// 统计事件类型(通过第一个 topic)
if (log.topics.length > 0) {
const eventSignature = log.topics[0];
topicCounts.set(eventSignature, (topicCounts.get(eventSignature) || 0) + 1);
}
// 统计合约地址
addressCounts.set(log.address, (addressCounts.get(log.address) || 0) + 1);
});
return {
totalEvents: logs.length,
uniqueEventTypes: topicCounts.size,
uniqueContracts: addressCounts.size,
topEventTypes: Array.from(topicCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5),
topContracts: Array.from(addressCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
};
}
// 估算操作复杂度
estimateOperationComplexity(tx, receipt, codeSize, callDataSize) {
const gasUsed = receipt.gasUsed.toNumber();
// 基础操作成本估算
const baseCost = 21000; // 基础交易成本
const callDataCost = callDataSize * 16; // 大约每字节16 Gas
const remainingGas = Math.max(gasUsed - baseCost - callDataCost, 0);
// 复杂度评级
let complexityRating;
if (remainingGas < 50000) {
complexityRating = 'simple';
} else if (remainingGas < 200000) {
complexityRating = 'moderate';
} else if (remainingGas < 500000) {
complexityRating = 'complex';
} else {
complexityRating = 'very_complex';
}
return {
baseCost,
callDataCost,
computationCost: remainingGas,
complexityRating,
gasPerComputationUnit: remainingGas / Math.max(codeSize, 1),
estimatedStorageCost: this.estimateStorageCost(receipt.logs)
};
}
// 估算存储操作
estimateStorageOperations(logs) {
// 基于日志数量和类型估算存储操作
let storageWrites = 0;
let storageReads = 0;
logs.forEach(log => {
// 简化估算:每个事件通常涉及1-2个存储写入
storageWrites += 1;
// 基于 topics 数量估算读取操作
storageReads += Math.max(log.topics.length - 1, 0);
});
return {
estimatedWrites: storageWrites,
estimatedReads: storageReads,
totalOperations: storageWrites + storageReads
};
}
// 估算存储成本
estimateStorageCost(logs) {
const SSTORE_SET_COST = 20000; // 新存储槽成本
const SSTORE_RESET_COST = 5000; // 修改现有存储槽成本
// 基于日志估算存储成本
const estimatedStorageOps = this.estimateStorageOperations(logs);
return {
minCost: estimatedStorageOps.estimatedWrites * SSTORE_RESET_COST,
maxCost: estimatedStorageOps.estimatedWrites * SSTORE_SET_COST,
avgCost: estimatedStorageOps.estimatedWrites * ((SSTORE_SET_COST + SSTORE_RESET_COST) / 2)
};
}
// 与基准对比
compareToBenchmarks(basicAnalysis, detailedAnalysis) {
// 这里可以与已知的基准数据对比
// 例如:标准 ERC20 转账、Uniswap 交换等
const gasUsed = parseInt(basicAnalysis.gasUsed);
const benchmarks = {
'ETH Transfer': { min: 21000, max: 21000, avg: 21000 },
'ERC20 Transfer': { min: 45000, max: 65000, avg: 55000 },
'Uniswap V2 Swap': { min: 100000, max: 150000, avg: 125000 },
'Uniswap V3 Swap': { min: 120000, max: 180000, avg: 150000 },
'NFT Mint': { min: 80000, max: 200000, avg: 140000 }
};
const comparisons = [];
Object.entries(benchmarks).forEach(([operation, benchmark]) => {
const efficiency = (benchmark.avg / gasUsed) * 100;
let status;
if (gasUsed <= benchmark.min) {
status = 'excellent';
} else if (gasUsed <= benchmark.avg) {
status = 'good';
} else if (gasUsed <= benchmark.max) {
status = 'acceptable';
} else {
status = 'poor';
}
comparisons.push({
operation,
benchmark,
actualGas: gasUsed,
efficiency: efficiency.toFixed(2) + '%',
status
});
});
return comparisons;
}
// 生成优化建议
generateOptimizationRecommendations(basicAnalysis, detailedAnalysis) {
const recommendations = [];
const gasUsed = parseInt(basicAnalysis.gasUsed);
const efficiency = parseFloat(basicAnalysis.efficiency);
// 基于效率的建议
if (efficiency < 50) {
recommendations.push({
type: 'efficiency',
priority: 'high',
suggestion: '交易 Gas 效率较低,考虑优化 Gas Limit 设置',
impact: 'cost_reduction'
});
}
// 基于 Gas 使用量的建议
if (gasUsed > 200000) {
recommendations.push({
type: 'optimization',
priority: 'medium',
suggestion: 'Gas 使用量较高,考虑拆分复杂操作或使用更高效的算法',
impact: 'performance'
});
}
// 基于详细分析的建议
if (detailedAnalysis) {
if (detailedAnalysis.callDataSize > 1000) {
recommendations.push({
type: 'data_optimization',
priority: 'medium',
suggestion: '调用数据较大,考虑压缩参数或使用 IPFS 存储大数据',
impact: 'cost_reduction'
});
}
if (detailedAnalysis.eventCount > 10) {
recommendations.push({
type: 'event_optimization',
priority: 'low',
suggestion: '事件数量较多,考虑合并相关事件减少日志成本',
impact: 'cost_reduction'
});
}
}
return recommendations;
}
// 批量分析多个交易
async batchAnalyzeTransactions(txHashes) {
console.log(`开始批量分析 ${txHashes.length} 个交易`);
const analyses = [];
for (let i = 0; i < txHashes.length; i++) {
try {
console.log(`分析进度: ${i + 1}/${txHashes.length}`);
const analysis = await this.analyzeTransactionGas(txHashes[i]);
analyses.push(analysis);
// 避免请求过于频繁
if (i < txHashes.length - 1) {
await this.delay(1000); // 1秒延迟
}
} catch (error) {
console.error(`分析交易 ${txHashes[i]} 失败:`, error);
}
}
// 生成批量分析报告
const batchReport = this.generateBatchAnalysisReport(analyses);
console.log('批量分析完成');
return {
analyses,
report: batchReport
};
}
// 生成批量分析报告
generateBatchAnalysisReport(analyses) {
if (analyses.length === 0) {
return { message: '没有成功分析的交易' };
}
// 统计数据
const totalGas = analyses.reduce((sum, analysis) =>
sum + parseInt(analysis.basic.gasUsed), 0
);
const avgGas = totalGas / analyses.length;
const gasUsageDistribution = this.calculateGasDistribution(analyses);
const complexityDistribution = this.calculateComplexityDistribution(analyses);
return {
summary: {
totalTransactions: analyses.length,
totalGasUsed: totalGas,
averageGasUsed: Math.round(avgGas),
gasRange: {
min: Math.min(...analyses.map(a => parseInt(a.basic.gasUsed))),
max: Math.max(...analyses.map(a => parseInt(a.basic.gasUsed)))
}
},
distributions: {
gasUsage: gasUsageDistribution,
complexity: complexityDistribution
},
recommendations: this.generateBatchRecommendations(analyses)
};
}
// 计算 Gas 使用分布
calculateGasDistribution(analyses) {
const ranges = [
{ min: 0, max: 50000, label: 'Low (0-50K)' },
{ min: 50000, max: 100000, label: 'Medium (50K-100K)' },
{ min: 100000, max: 200000, label: 'High (100K-200K)' },
{ min: 200000, max: Infinity, label: 'Very High (200K+)' }
];
const distribution = ranges.map(range => ({
...range,
count: 0,
percentage: 0
}));
analyses.forEach(analysis => {
const gasUsed = parseInt(analysis.basic.gasUsed);
const rangeIndex = ranges.findIndex(range =>
gasUsed >= range.min && gasUsed < range.max
);
if (rangeIndex !== -1) {
distribution[rangeIndex].count++;
}
});
// 计算百分比
distribution.forEach(range => {
range.percentage = ((range.count / analyses.length) * 100).toFixed(2);
});
return distribution;
}
// 计算复杂度分布
calculateComplexityDistribution(analyses) {
const complexityCount = {
simple: 0,
moderate: 0,
complex: 0,
very_complex: 0
};
analyses.forEach(analysis => {
if (analysis.detailed && analysis.detailed.complexityAnalysis) {
const complexity = analysis.detailed.complexityAnalysis.complexityRating;
if (complexityCount.hasOwnProperty(complexity)) {
complexityCount[complexity]++;
}
}
});
const total = Object.values(complexityCount).reduce((sum, count) => sum + count, 0);
return Object.entries(complexityCount).map(([complexity, count]) => ({
complexity,
count,
percentage: total > 0 ? ((count / total) * 100).toFixed(2) : '0.00'
}));
}
// 生成批量建议
generateBatchRecommendations(analyses) {
const recommendations = [];
// 分析整体趋势
const avgGas = analyses.reduce((sum, analysis) =>
sum + parseInt(analysis.basic.gasUsed), 0
) / analyses.length;
const highGasCount = analyses.filter(analysis =>
parseInt(analysis.basic.gasUsed) > 200000
).length;
const lowEfficiencyCount = analyses.filter(analysis =>
parseFloat(analysis.basic.efficiency) < 70
).length;
if (highGasCount > analyses.length * 0.3) {
recommendations.push({
type: 'batch_optimization',
priority: 'high',
suggestion: '超过30%的交易Gas使用量过高,建议全面优化合约逻辑',
affectedTransactions: highGasCount
});
}
if (lowEfficiencyCount > analyses.length * 0.5) {
recommendations.push({
type: 'gas_estimation',
priority: 'medium',
suggestion: '超过50%的交易Gas效率较低,建议改进Gas估算策略',
affectedTransactions: lowEfficiencyCount
});
}
return recommendations;
}
// 延迟函数
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
使用示例:
// Gas 监控和分析完整示例
async function demonstrateGasMonitoring() {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
// 1. 启动 Gas 监控
const gasMonitor = new GasMonitor(
provider,
CONTRACT_ADDRESS,
CONTRACT_ABI
);
// 配置警告阈值
gasMonitor.alertThresholds = {
high: 300000,
extreme: 800000,
efficiency: 0.6
};
// 开始监控
await gasMonitor.startMonitoring();
// 2. Gas 分析器
const gasAnalyzer = new GasAnalyzer(provider);
try {
// 分析特定交易
const singleAnalysis = await gasAnalyzer.analyzeTransactionGas(SAMPLE_TX_HASH);
console.log('单个交易分析:', singleAnalysis);
// 批量分析多个交易
const batchResult = await gasAnalyzer.batchAnalyzeTransactions([
TX_HASH_1,
TX_HASH_2,
TX_HASH_3
]);
console.log('批量分析报告:', batchResult.report);
// 运行一段时间后生成报告
setTimeout(() => {
gasMonitor.generatePeriodicReport();
}, 60000); // 1分钟后
// 设置定时停止监控
setTimeout(() => {
gasMonitor.stopMonitoring();
console.log('Gas 监控演示结束');
}, 300000); // 5分钟后停止
} catch (error) {
console.error('Gas 分析演示失败:', error);
}
}
// 执行演示
demonstrateGasMonitoring();
Gas 监控的关键要点:
最佳实践: