paint-brush
Sự kiện Solidity: Tổng quan toàn diệntừ tác giả@rareskills
649 lượt đọc
649 lượt đọc

Sự kiện Solidity: Tổng quan toàn diện

từ tác giả RareSkills13m2023/05/31
Read on Terminal Reader

dài quá đọc không nổi

Các sự kiện của Solidity là thứ gần nhất với câu lệnh “in” hoặc “bảng điều khiển” trong Ethereum. Chúng tôi sẽ giải thích cách chúng hoạt động và khi nào nên sử dụng chúng. Chúng tôi cũng đi vào nhiều chi tiết kỹ thuật thường bị bỏ qua trong các tài nguyên khác. Đây là một ví dụ tối thiểu để phát ra một sự kiện solidity.
featured image - Sự kiện Solidity: Tổng quan toàn diện
RareSkills HackerNoon profile picture
0-item
1-item

sự kiện đoàn kết

Các sự kiện của Solidity là thứ gần nhất với câu lệnh “print” hoặc “console.log” trong Ethereum. Chúng tôi sẽ giải thích cách chúng hoạt động và khi nào nên sử dụng chúng. Chúng tôi cũng sẽ đi sâu vào nhiều chi tiết kỹ thuật thường bị bỏ qua trong các tài nguyên khác.


Đây là một ví dụ tối thiểu để phát ra một sự kiện solidity.

 contract ExampleContract { // We will explain the significance of the indexed parameter later. event ExampleEvent(address indexed sender, uint256 someValue); function exampleFunction(uint256 someValue) public { emit ExampleEvent(sender, someValue); } }



Có lẽ các sự kiện nổi tiếng nhất là những sự kiện được phát ra bởi mã thông báo ERC20 khi chúng được chuyển. Người gửi, người nhận và số tiền được ghi lại trong một sự kiện.


 emit Transfer(from, to, amount);


Đây không phải là dư thừa sao? Chúng tôi đã có thể xem qua các giao dịch trong quá khứ để xem các lần chuyển tiền và sau đó chúng tôi có thể xem xét calldata của giao dịch để xem thông tin tương tự.


Điều này đúng, người ta có thể xóa các sự kiện và không ảnh hưởng đến logic nghiệp vụ của hợp đồng thông minh. Tuy nhiên, đây sẽ không phải là một cách hiệu quả để nhìn vào lịch sử.


Mục lục

  • sự kiện đoàn kết
  • Truy xuất giao dịch nhanh hơn
  • Lắng nghe các sự kiện
  • Các sự kiện được lập chỉ mục solidity so với các sự kiện không được lập chỉ mục
  • Thực hành tốt nhất cho sự kiện Solidity
  • Sự kiện không thể được sử dụng trong chức năng
  • Một sự kiện có thể có bao nhiêu đối số?
  • Tên biến trong các sự kiện là tùy chọn nhưng được khuyến nghị
  • Các sự kiện có thể được kế thừa thông qua các hợp đồng và giao diện gốc
  • bộ chọn sự kiện
  • sự kiện ẩn danh
  • Chuyên đề nâng cao về sự kiện
  • Chi phí gas để phát ra sự kiện rắn
  • Phần kết luận


Truy xuất giao dịch nhanh hơn

Ứng dụng khách Ethereum không có API để liệt kê các giao dịch theo “loại”, ví dụ: “tất cả các giao dịch chuyển khoản của ERC20”. Đây là các tùy chọn của bạn nếu bạn muốn truy vấn các giao dịch:


  • nhận giao dịch

  • getTransactionFromBlock


API getTransactionFromBlock chỉ có thể cho bạn biết giao dịch nào đã xảy ra trên một khối cụ thể, nó không thể nhắm mục tiêu nhiều khối địa chỉ hợp đồng thông minh.


getTransaction chỉ có thể kiểm tra các giao dịch mà bạn biết hàm băm giao dịch.


Mặt khác, các sự kiện có thể được truy xuất dễ dàng hơn nhiều.


Dưới đây là các tùy chọn máy khách Ethereum:


  • sự kiện

  • sự kiện.allEvents

  • getPastSự kiện


Mỗi trong số này yêu cầu chỉ định địa chỉ hợp đồng thông minh mà người truy vấn muốn kiểm tra và trả về một tập hợp con (hoặc tất cả) các sự kiện mà một hợp đồng thông minh phát ra theo các tham số truy vấn được chỉ định.


Đây là thông tin chi tiết chính về lý do tại sao bạn sử dụng Sự kiện để theo dõi giao dịch thay vì bản thân giao dịch: Ethereum không cung cấp cơ chế để nhận tất cả giao dịch cho hợp đồng thông minh, nhưng nó cung cấp cơ chế để nhận tất cả sự kiện từ hợp đồng thông minh.


Tại sao lại thế này? Làm cho các sự kiện có thể truy xuất nhanh chóng yêu cầu chi phí lưu trữ bổ sung. Nếu Ethereum làm điều này cho mọi giao dịch, điều này sẽ làm cho chuỗi lớn hơn đáng kể. Với các sự kiện, các lập trình viên solidity có thể chọn lọc loại thông tin nào đáng để trả phí lưu trữ bổ sung, để cho phép truy xuất nhanh chóng ngoài chuỗi.


Lắng nghe các sự kiện

Đây là một ví dụ về việc sử dụng API được mô tả ở trên. Trong mã này, khách hàng đăng ký các sự kiện từ một hợp đồng thông minh. Những ví dụ này đều có trong Javascript.


Ví dụ 1: Lắng nghe các sự kiện Chuyển giao ERC20.

Mã này kích hoạt lệnh gọi lại mỗi khi mã thông báo ERC20 phát ra Sự kiện chuyển giao.

 const { ethers } = require("ethers"); // const provider = your provider const abi = [ "event Transfer(address indexed from, address indexed to, uint256 value)" ]; const tokenAddress = "0x..."; const contract = new ethers.Contract(tokenAddress, abi, provider); contract.on("Transfer", (from, to, value, event) => { console.log(`Transfer event detected: from=${from}, to=${to}, value=${value}`); });


Ví dụ 2: Lọc phê duyệt ERC20 cho một địa chỉ cụ thể

Nếu chúng ta muốn xem xét các sự kiện hồi tố, chúng ta có thể sử dụng đoạn mã sau. Trong ví dụ này, chúng tôi xem xét quá khứ đối với các giao dịch Phê duyệt bằng mã thông báo ERC20.

 const ethers = require('ethers'); const tokenAddress = '0x...'; const filterAddress = '0x...'; const tokenAbi = [ { "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" } ]; const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, provider); // this line filters for Approvals for a particular address. const filter = tokenContract.filters.Approval(filterAddress, null, null); tokenContract.queryFilter(filter).then((events) => { console.log(events); });



Nếu bạn muốn tìm kiếm giao dịch giữa hai địa chỉ cụ thể đã biết (nếu giao dịch đó tồn tại), ethers.js. mã sẽ như sau:


 tokenContract.filters.Transfer(address1, address2, null);


Đây là một ví dụ tương tự trong web3.js thay vì ethers.js. Lưu ý rằng các tham số truy vấn fromBlocktoBlock được thêm vào (để biểu thị rằng chúng tôi chỉ quan tâm đến các sự kiện giữa các khối này) và chúng tôi sẽ chứng minh khả năng lắng nghe nhiều địa chỉ là người gửi. Các địa chỉ được kết hợp với điều kiện "HOẶC".


 const Web3 = require('web3'); const web3 = new Web3('https://rpc-endpoint'); const contractAddress = '0x...'; // The address of the ERC20 contract const contractAbi = [ { "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" } ]; const contract = new web3.eth.Contract(contractAbi, contractAddress); const senderAddressesToWatch = ['0x...', '0x...', '0x...']; // The addresses to watch for transfers from const filter = { fromBlock: 0, toBlock: 'latest', topics: [ web3.utils.sha3('Transfer(address,address,uint256)'), null, senderAddressesToWatch, ] }; contract.getPastEvents('Transfer', { filter: filter, fromBlock: 0, toBlock: 'latest', }, (error, events) => { if (!error) { console.log(events); } });


Các sự kiện được lập chỉ mục solidity so với các sự kiện không được lập chỉ mục

Ví dụ trên hoạt động vì sự kiện Phê duyệt (và Chuyển) trong ERC20 đặt người gửi được lập chỉ mục. Đây là khai báo sự kiện Phê duyệt ERC20 trong Solidity.


 event Approval(address indexed owner, address indexed spender, uint256 value);


Nếu đối số "chủ sở hữu" không được lập chỉ mục, mã javascript trước đó sẽ âm thầm bị lỗi. Ngụ ý ở đây là bạn không thể lọc các sự kiện ERC20 có value cụ thể (số lượng mã thông báo) để chuyển vì giá trị đó không được lập chỉ mục. Bạn phải lấy tất cả các sự kiện và lọc chúng bên javascript; nó không thể được thực hiện trong ứng dụng khách Ethereum.


Đối số được lập chỉ mục cho khai báo sự kiện được gọi là chủ đề .


Thực hành tốt nhất cho sự kiện Solidity

Phương pháp hay nhất được chấp nhận chung cho các sự kiện là ghi nhật ký chúng bất cứ khi nào xảy ra thay đổi trạng thái có thể dẫn đến hậu quả. Một số ví dụ bao gồm:


  • Thay đổi chủ sở hữu hợp đồng

  • Di chuyển ether

  • Tiến hành giao dịch


Không phải mọi thay đổi trạng thái đều yêu cầu một sự kiện. Câu hỏi mà các nhà phát triển Solidity nên tự đặt ra là “liệu ai đó có quan tâm đến việc truy xuất hoặc khám phá giao dịch này một cách nhanh chóng không?”

Lập chỉ mục các thông số sự kiện phù hợp

Điều này sẽ đòi hỏi một số đánh giá chủ quan. Hãy nhớ rằng, không thể tìm kiếm trực tiếp tham số chưa lập chỉ mục, nhưng vẫn có thể là dữ liệu hữu ích khi đi kèm với tham số đã lập chỉ mục. Một cách hay để có được trực giác về điều này là xem cách các cơ sở mã được thành lập thiết kế các sự kiện của họ



Theo nguyên tắc chung, số lượng tiền điện tử không nên được lập chỉ mục và địa chỉ nên được lập chỉ mục, nhưng quy tắc này không nên được áp dụng một cách mù quáng. Các chỉ mục chỉ cho phép bạn nhanh chóng nhận được các giá trị chính xác, không phải là một phạm vi giá trị.

Tránh các sự kiện dư thừa

Một ví dụ về điều này sẽ là thêm một sự kiện khi mã thông báo được đúc vì các thư viện cơ bản đã phát ra sự kiện này.

Sự kiện không thể được sử dụng trong chức năng xem

Các sự kiện đang thay đổi trạng thái; họ thay đổi trạng thái của chuỗi khối bằng cách lưu trữ nhật ký. Do đó, chúng không thể được sử dụng trong các chức năng xem (hoặc thuần túy).


Các sự kiện không hữu ích cho việc gỡ lỗi cách console.log và print ở các ngôn ngữ khác; bởi vì bản thân các sự kiện đang thay đổi trạng thái, chúng không được phát ra nếu một giao dịch hoàn nguyên.


Một sự kiện có thể có bao nhiêu đối số?

Đối với các đối số không được lập chỉ mục, không có giới hạn nội tại đối với số lượng đối số, mặc dù tất nhiên có các giới hạn về kích thước hợp đồng và gas được áp dụng. Ví dụ vô nghĩa sau đây là solidity hợp lệ:


 contract ExampleContract { event Numbers(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); }


Tương tự, không có giới hạn nội tại đối với độ dài của chuỗi hoặc mảng được lưu trữ trong nhật ký.


Tuy nhiên, không thể có nhiều hơn ba đối số được lập chỉ mục (chủ đề) trong một sự kiện. Một sự kiện ẩn danh có thể có 4 đối số được lập chỉ mục (chúng tôi sẽ đề cập đến sự khác biệt này sau).

Một đối số không có sự kiện nào cũng hợp lệ.


Tên biến trong các sự kiện là tùy chọn nhưng nên dùng

Các sự kiện sau đây hoạt động giống hệt nhau

 event NewOwner(address newOwner); event NewOwner(address);


Nói chung, bao gồm tên biến sẽ là lý tưởng vì ngữ nghĩa đằng sau ví dụ sau rất mơ hồ (đây không phải là cách bạn nên khai báo các sự kiện!)


 event Trade(address,address,address,uint256,uint256);


Chúng tôi có thể đoán rằng các địa chỉ tương ứng với người gửi và địa chỉ mã thông báo, trong khi uint256es tương ứng với số tiền, nhưng điều này rất khó giải mã.


Thông thường viết hoa tên của một sự kiện, nhưng trình biên dịch không yêu cầu điều đó. Đây sẽ là một tuyên bố tốt hơn nhiều:


 event Trade(address trader, address token1, address token2, uint256 token1Amount, uint256 token2Amount);


Các sự kiện có thể được kế thừa thông qua các hợp đồng và giao diện gốc

Khi một sự kiện được khai báo trong hợp đồng cha, nó có thể được phát ra bởi hợp đồng con. Các sự kiện là nội bộ và không thể sửa đổi thành riêng tư hoặc công khai.


Đây là một ví dụ:

 contract ParentContract { event NewNumber(uint256 number); function doSomething(uint256 number) public { emit NewNumber(number); } } contract ChildContract is ParentContract { function doSomethingElse(uint256 number) public { emit NewNumber(number); // valid } }


Tương tự, các sự kiện có thể được khai báo trong một giao diện và được sử dụng trong giao diện con, như trong ví dụ sau.


 interface IExampleInterface { event Deposit(address indexed sender, uint256 amount); } contract ExampleContract is IExampleInterface { function deposit() external payable { emit Deposit(msg.sender, msg.value); // also valid } }


bộ chọn sự kiện

EVM (Ethereum Virtual Machine) xác định các sự kiện bằng chữ ký keccak256 của chúng.

Đối với các phiên bản solidity 0.8.15 trở lên, bạn cũng có thể truy xuất bộ chọn bằng cách sử dụng thành viên .selector.


 pragma solidity ^0.8.15; contract ExampleContract { event SomeEvent(uint256 blocknum, uint256 indexed timestamp); function selector() external pure returns (bool) { // true return SomeEvent.selector == keccak256("SomeEvent(uint256,uint256)"); } }


Bộ chọn sự kiện thực sự là một chủ đề (chúng ta sẽ thảo luận thêm về điều này trong phần sau).

Đánh dấu các biến là được lập chỉ mục hay không không thay đổi bộ chọn.


sự kiện ẩn danh

Các sự kiện có thể được đánh dấu là ẩn danh, trong trường hợp đó, chúng sẽ không có bộ chọn. Điều này có nghĩa là mã phía máy khách không thể tách riêng chúng thành một tập hợp con như các ví dụ trước đây của chúng tôi. Cách duy nhất để mã phía máy khách nhìn thấy một sự kiện ẩn danh là lấy tất cả các sự kiện cho hợp đồng thông minh.


 pragma solidity ^0.8.15; contract ExampleContract { event SomeEvent(uint256 blocknum, uint256 timestamp) anonymous; function selector() public pure returns (bool) { // ERROR: does not compile, anonymous events don't have selectors return SomeEvent.selector == keccak256("SomeEvent(uint256,uint256)"); } }


Vì chữ ký sự kiện được sử dụng làm một trong các chỉ mục, nên một hàm ẩn danh có thể có bốn chủ đề được lập chỉ mục, vì chữ ký hàm được "giải phóng" như một trong các chủ đề.


Một sự kiện ẩn danh có thể có tối đa bốn chủ đề được lập chỉ mục. Một sự kiện không ẩn danh có thể có tối đa ba.


 contract ExampleContract { // valid event SomeEvent(uint256 indexed, uint256 indexed, address indexed, address indexed) anonymous; }


Các sự kiện ẩn danh hiếm khi được sử dụng trong thực tế.


Chuyên đề nâng cao về sự kiện

Phần này mô tả các sự kiện ở mức lắp ráp của EVM. Phần này có thể bỏ qua đối với các lập trình viên mới bắt đầu phát triển chuỗi khối .


Chi tiết triển khai: Bộ lọc Bloom

Để truy xuất mọi giao dịch đã xảy ra với một hợp đồng thông minh, ứng dụng khách Ethereum sẽ phải quét mọi khối, đây sẽ là một thao tác I/O cực kỳ nặng nề; nhưng Ethereum sử dụng một tối ưu hóa quan trọng.


Các sự kiện được lưu trữ trong cấu trúc dữ liệu Bộ lọc Bloom cho mỗi khối. Bộ lọc Bloom là một tập hợp xác suất trả lời hiệu quả nếu một thành viên có trong tập hợp hay không. Thay vì quét toàn bộ khối, khách hàng có thể hỏi bộ lọc nở nếu một sự kiện được phát ra trong khối. Điều này cho phép khách hàng quét chuỗi khối nhanh hơn nhiều để tìm các sự kiện.


Bộ lọc Bloom mang tính xác suất: đôi khi chúng trả về sai rằng một mục là thành viên của tập hợp ngay cả khi không phải vậy. Càng nhiều thành viên được lưu trữ trong Bộ lọc Bloom, khả năng xảy ra lỗi càng cao và bộ lọc Bloom phải càng lớn (lưu trữ khôn ngoan) để bù đắp cho điều này. Do đó, Ethereum không lưu trữ tất cả các giao dịch trong Bộ lọc Bloom. Có ít sự kiện hơn nhiều so với giao dịch. Điều này giữ cho kích thước lưu trữ trên blockchain có thể quản lý được.


Khi khách hàng nhận được phản hồi thành viên tích cực từ bộ lọc nở, khách hàng phải quét khối để xác minh sự kiện đã diễn ra. Tuy nhiên, điều này sẽ chỉ xảy ra đối với một tập hợp con nhỏ các khối, do đó, trung bình, ứng dụng khách Ethereum tiết kiệm rất nhiều tính toán bằng cách kiểm tra bộ lọc nở để biết sự hiện diện của sự kiện trước.


sự kiện yul

Trong biểu diễn trung gian của Yul, sự khác biệt giữa các đối số được lập chỉ mục (chủ đề) và các đối số không được lập chỉ mục trở nên rõ ràng.


Các hàm yul sau đây có sẵn để phát ra các sự kiện (và mã lệnh EVM của chúng có cùng tên). Bảng được sao chép từ tài liệu yul với một số đơn giản hóa.

mã op

Cách sử dụng

log0(p, s)

nhật ký không có chủ đề và dữ liệu mem[p…(p+s))

log1(p, s, t1)

log với chủ đề t1 và dữ liệu mem[p…(p+s))

log2(p, s, t1, t2)

log với chủ đề t1, t2 và data mem[p…(p+s))

log3(p, s, t1, t2, t3)

log với các chủ đề t1, t2, t3 và data mem[p…(p+s))

log4(p, s, t1, t2, t3, t4)

log với chủ đề t1, t2, t3, t4 và data mem[p…(p+s))


Một nhật ký có thể có tối đa 4 chủ đề, nhưng một sự kiện solidity không ẩn danh có thể có tối đa 3 đối số được lập chỉ mục. Đó là bởi vì chủ đề đầu tiên được sử dụng để lưu chữ ký sự kiện. Không có chức năng opcode hoặc Yul để phát ra hơn bốn chủ đề.


Các tham số không được lập chỉ mục chỉ đơn giản là abi được mã hóa trong vùng bộ nhớ [p…(p+s)) và được phát ra dưới dạng một chuỗi byte dài.


Nhớ lại trước đó rằng về nguyên tắc không có giới hạn về số lượng đối số chưa lập chỉ mục mà một sự kiện trong Solidity có thể có. Lý do cơ bản là không có giới hạn rõ ràng về khoảng thời gian mà vùng bộ nhớ được trỏ tới trong hai tham số đầu tiên của mã hoạt động đăng nhập mất bao lâu. Tất nhiên, có các giới hạn được cung cấp bởi quy mô hợp đồng và chi phí khí mở rộng bộ nhớ.

Chi phí gas để phát ra sự kiện rắn

Các sự kiện rẻ hơn đáng kể so với việc ghi vào các biến lưu trữ. Các sự kiện không dành cho các hợp đồng thông minh có thể truy cập được, do đó, việc thiếu chi phí tương đối chứng minh chi phí gas thấp hơn.

Công thức tính chi phí gas cho một sự kiện như sau ( source ):


 375 + 375 * num_topics + 8 * data_size + mem_expansion cost


Mỗi sự kiện tiêu tốn ít nhất 375 gas. Thêm 375 được trả cho mỗi tham số được lập chỉ mục. Một sự kiện không ẩn danh có bộ chọn sự kiện làm thông số được lập chỉ mục, do đó chi phí thường được bao gồm trong hầu hết thời gian. Sau đó, chúng tôi trả gấp 8 lần số từ 32 byte được ghi vào chuỗi. Bởi vì vùng này được lưu trữ trong bộ nhớ trước khi được phát ra, chi phí mở rộng bộ nhớ cũng phải được tính đến.


Nói chung, trực giác rằng bạn càng đăng nhập nhiều thì bạn càng trả nhiều tiền xăng, là chính xác.

Phần kết luận

Các sự kiện dành cho khách hàng để nhanh chóng truy xuất các giao dịch có thể được quan tâm. Mặc dù chúng không thay đổi chức năng của hợp đồng thông minh, nhưng chúng cho phép lập trình viên chỉ định giao dịch nào sẽ có thể truy xuất nhanh chóng. Điều này giúp các dapp dễ dàng nhanh chóng tóm tắt thông tin quan trọng.


Các sự kiện tương đối rẻ về mặt chi phí so với dung lượng lưu trữ, nhưng yếu tố quan trọng nhất trong chi phí của chúng là số lượng tham số được lập chỉ mục, giả sử người viết mã không sử dụng quá nhiều bộ nhớ.

Tìm hiểu thêm

Giống như những gì bạn thấy ở đây? Xem Solidity Bootcamp nâng cao của chúng tôi để tìm hiểu thêm.


Hình ảnh chính cho bài viết này được tạo bởi Trình tạo hình ảnh AI của HackerNoon thông qua lời nhắc "Sự kiện trong Solidity"