事件与日志 #
一、事件概述 #
1.1 什么是事件 #
事件是Solidity中用于记录日志的机制,当事件被触发时,数据会被记录在区块链的交易日志中。
solidity
contract EventDemo {
// 定义事件
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address to, uint256 amount) public {
// 触发事件
emit Transfer(msg.sender, to, amount);
}
}
1.2 事件的作用 #
| 作用 | 说明 |
|---|---|
| 日志记录 | 记录合约操作历史 |
| 前端监听 | DApp监听合约状态变化 |
| 数据索引 | 通过indexed参数快速查询 |
| 节省Gas | 比storage更便宜 |
二、事件定义 #
2.1 基本语法 #
solidity
event EventName(type1 indexed param1, type2 indexed param2, ...);
2.2 定义示例 #
solidity
contract EventDefinition {
// 基本事件
event SimpleEvent();
// 带参数事件
event ValueChanged(uint256 newValue);
// 带indexed参数事件
event Transfer(address indexed from, address indexed to, uint256 value);
// 多个indexed参数(最多3个)
event MultiIndexed(
address indexed sender,
address indexed recipient,
uint256 indexed amount
);
// 复杂类型参数
event UserCreated(address indexed user, string name);
// 数组参数
event BatchProcessed(uint256[] amounts);
}
2.3 indexed限制 #
solidity
contract IndexedLimit {
// indexed参数最多3个
event Good(
address indexed a,
address indexed b,
uint256 indexed c
);
// 错误:超过3个indexed
// event Bad(
// address indexed a,
// address indexed b,
// uint256 indexed c,
// uint256 indexed d // 错误
// );
// 值类型可以indexed
event ValueTypes(
uint256 indexed a,
int256 indexed b,
bool indexed c
);
// 引用类型不能indexed(string、bytes、数组、结构体)
// event BadRefType(string indexed name); // 错误
}
三、触发事件 #
3.1 emit关键字 #
solidity
contract EmitEvent {
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address to, uint256 amount) public {
// 使用emit触发事件
emit Transfer(msg.sender, to, amount);
}
// 多次触发
function batchTransfer(
address[] memory recipients,
uint256[] memory amounts
) public {
for (uint256 i = 0; i < recipients.length; i++) {
emit Transfer(msg.sender, recipients[i], amounts[i]);
}
}
}
3.2 事件参数 #
solidity
contract EventParameters {
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
event Withdrawal(address indexed user, uint256 amount, string reason);
function deposit() public payable {
emit Deposit(msg.sender, msg.value, block.timestamp);
}
function withdraw(uint256 amount, string memory reason) public {
emit Withdrawal(msg.sender, amount, reason);
}
}
四、indexed参数详解 #
4.1 indexed的作用 #
solidity
contract IndexedPurpose {
event Transfer(address indexed from, address indexed to, uint256 value);
// indexed参数可以被过滤查询
// 例如:
// - 查询所有从地址A发出的转账
// - 查询所有发往地址B的转账
// - 查询地址A发给地址B的所有转账
function transfer(address to, uint256 amount) public {
emit Transfer(msg.sender, to, amount);
}
}
4.2 查询示例 #
javascript
// Web3.js查询示例
// 查询所有Transfer事件
const events = await contract.getPastEvents('Transfer', {
fromBlock: 0,
toBlock: 'latest'
});
// 查询特定发送者的转账
const fromEvents = await contract.getPastEvents('Transfer', {
filter: { from: '0x123...' },
fromBlock: 0,
toBlock: 'latest'
});
// 查询特定接收者的转账
const toEvents = await contract.getPastEvents('Transfer', {
filter: { to: '0x456...' },
fromBlock: 0,
toBlock: 'latest'
});
// 查询特定发送者和接收者的转账
const specificEvents = await contract.getPastEvents('Transfer', {
filter: {
from: '0x123...',
to: '0x456...'
},
fromBlock: 0,
toBlock: 'latest'
});
4.3 indexed与Gas #
solidity
contract IndexedGas {
// indexed参数会增加Gas消耗
// 但可以节省查询成本
// 无indexed:Gas较低
event NoIndexed(address from, address to, uint256 value);
// 有indexed:Gas较高
event WithIndexed(address indexed from, address indexed to, uint256 value);
function emitNoIndexed(address to, uint256 amount) public {
emit NoIndexed(msg.sender, to, amount);
}
function emitWithIndexed(address to, uint256 amount) public {
emit WithIndexed(msg.sender, to, amount);
}
}
五、事件监听 #
5.1 前端监听 #
javascript
// Web3.js监听示例
// 监听新事件
contract.events.Transfer({
fromBlock: 'latest'
})
.on('data', (event) => {
console.log('Transfer event:', event);
console.log('From:', event.returnValues.from);
console.log('To:', event.returnValues.to);
console.log('Value:', event.returnValues.value);
})
.on('error', console.error);
// 监听特定地址
contract.events.Transfer({
filter: { from: '0x123...' },
fromBlock: 'latest'
})
.on('data', (event) => {
console.log('Transfer from 0x123...:', event);
});
5.2 Ethers.js监听 #
javascript
// Ethers.js监听示例
// 监听事件
contract.on('Transfer', (from, to, value, event) => {
console.log('Transfer:', from, to, value);
});
// 监听特定过滤条件
const filter = contract.filters.Transfer('0x123...', null);
contract.on(filter, (from, to, value, event) => {
console.log('Transfer from 0x123...:', from, to, value);
});
// 查询历史事件
const events = await contract.queryFilter(filter, 0, 'latest');
events.forEach((event) => {
console.log(event.args);
});
六、实际应用示例 #
6.1 代币合约 #
solidity
contract ERC20Token {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
// 事件定义
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _totalSupply
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _totalSupply;
balanceOf[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function transfer(address to, uint256 amount) public returns (bool) {
require(to != address(0), "Invalid recipient");
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
require(spender != address(0), "Invalid spender");
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
require(from != address(0), "Invalid from");
require(to != address(0), "Invalid to");
require(balanceOf[from] >= amount, "Insufficient balance");
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
balanceOf[from] -= amount;
balanceOf[to] += amount;
allowance[from][msg.sender] -= amount;
emit Transfer(from, to, amount);
return true;
}
}
6.2 众筹合约 #
solidity
contract Crowdfunding {
address public owner;
uint256 public goal;
uint256 public deadline;
uint256 public totalRaised;
bool public claimed;
mapping(address => uint256) public contributions;
// 事件定义
event CampaignCreated(address indexed owner, uint256 goal, uint256 deadline);
event ContributionMade(address indexed contributor, uint256 amount);
event GoalReached(uint256 totalRaised, uint256 goal);
event FundsWithdrawn(address indexed owner, uint256 amount);
event RefundIssued(address indexed contributor, uint256 amount);
constructor(uint256 _goal, uint256 _durationInDays) {
owner = msg.sender;
goal = _goal;
deadline = block.timestamp + _durationInDays * 1 days;
emit CampaignCreated(owner, goal, deadline);
}
function contribute() public payable {
require(block.timestamp < deadline, "Campaign ended");
require(msg.value > 0, "Must send ETH");
contributions[msg.sender] += msg.value;
totalRaised += msg.value;
emit ContributionMade(msg.sender, msg.value);
if (totalRaised >= goal) {
emit GoalReached(totalRaised, goal);
}
}
function claimFunds() public {
require(msg.sender == owner, "Not owner");
require(totalRaised >= goal, "Goal not reached");
require(!claimed, "Already claimed");
require(block.timestamp >= deadline, "Campaign not ended");
claimed = true;
uint256 amount = totalRaised;
payable(owner).transfer(amount);
emit FundsWithdrawn(owner, amount);
}
function refund() public {
require(block.timestamp >= deadline, "Campaign not ended");
require(totalRaised < goal, "Goal was reached");
require(contributions[msg.sender] > 0, "No contribution");
uint256 amount = contributions[msg.sender];
contributions[msg.sender] = 0;
payable(msg.sender).transfer(amount);
emit RefundIssued(msg.sender, amount);
}
}
6.3 NFT合约 #
solidity
contract SimpleNFT {
string public name;
string public symbol;
mapping(uint256 => address) public ownerOf;
mapping(address => uint256) public balanceOf;
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
// 事件定义
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
function mint(uint256 tokenId) public {
require(ownerOf[tokenId] == address(0), "Already minted");
ownerOf[tokenId] = msg.sender;
balanceOf[msg.sender]++;
emit Transfer(address(0), msg.sender, tokenId);
}
function transfer(address to, uint256 tokenId) public {
require(ownerOf[tokenId] == msg.sender, "Not owner");
require(to != address(0), "Invalid recipient");
balanceOf[msg.sender]--;
balanceOf[to]++;
ownerOf[tokenId] = to;
emit Transfer(msg.sender, to, tokenId);
}
function approve(address to, uint256 tokenId) public {
require(ownerOf[tokenId] == msg.sender, "Not owner");
getApproved[tokenId] = to;
emit Approval(msg.sender, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) public {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
}
七、最佳实践 #
7.1 事件命名 #
solidity
contract EventNaming {
// 推荐:使用大驼峰命名
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// 推荐:使用动词过去式
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Minted(address indexed to, uint256 amount);
event Burned(address indexed from, uint256 amount);
}
7.2 参数设计 #
solidity
contract EventParameterDesign {
// 推荐:indexed关键参数
event Transfer(address indexed from, address indexed to, uint256 value);
// 推荐:包含时间戳
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
// 推荐:包含操作者
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner,
address indexed operator
);
// 推荐:包含状态变化
event StatusChanged(uint256 indexed id, Status oldStatus, Status newStatus);
enum Status { Pending, Active, Completed }
}
7.3 事件触发时机 #
solidity
contract EventTiming {
uint256 public value;
event ValueChanged(uint256 oldValue, uint256 newValue, address indexed changer);
// 推荐:在状态变化后触发
function setValue(uint256 newValue) public {
uint256 oldValue = value;
value = newValue;
emit ValueChanged(oldValue, newValue, msg.sender);
}
// 推荐:在关键操作后触发
function criticalOperation() public {
// 执行操作
// ...
// 触发事件
emit CriticalOperationCompleted(msg.sender, block.timestamp);
}
event CriticalOperationCompleted(address indexed operator, uint256 timestamp);
}
八、总结 #
事件要点:
| 特性 | 说明 |
|---|---|
| 定义 | event EventName(...) |
| 触发 | emit EventName(...) |
| indexed | 最多3个,用于过滤查询 |
| 存储 | 存储在交易日志中 |
| Gas | 比storage便宜 |
使用建议:
- 关键操作都应触发事件
- indexed关键查询参数
- 包含必要的上下文信息
- 使用有意义的命名
接下来,让我们学习修饰器!
最后更新:2026-03-27