事件与日志 #

一、事件概述 #

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