ethers.js/docs.wrm/concepts/events.wrm

214 lines
7.5 KiB
Plaintext

_section: Events @<events>
_subsection: Logs and Filtering
Logs and filtering are used quite often in blockchain applications,
since they allow for efficient queries of indexed data and provide
lower-cost data storage when the data is not required to be
accessed on-chain.
These can be used in conjunction with the [Provider Events API](Provider--event-methods)
and with the [Contract Events API](Contract--events).
The Contract Events API also provides [higher-level methods](Contract--filters)
to compute and query this data, which should be preferred over the lower-level filter.
_heading: Filters @<events--filters>
When a Contract creates a log, it can include up to 4 pieces of
data to be indexed by. The indexed data is hashed and included in
a [[link-wiki-bloomfilter]], which is a data structure that allows
for efficient filtering.
So, a filter may correspondingly have up to 4 topic-sets, where each
topic-set refers to a condition that must match the indexed log topic
in that position (i.e. each condition is ``AND``-ed together).
If a topic-set is ``null``, a log topic in that position is **not filtered**
at all and **any value** matches.
If a topic-set is a single topic, a log topic in that position **must** match
**that topic**.
If a topic-set is an array of topics, a log topic in that position must
match **any one** of the topics (i.e. the topic in this position are ``OR``-ed).
This may sound complicated at first, but is more easily understood with
some examples.
_table: Example Log Matching @style<full>
$TopicABaCD: **[** (topic[0] = A) **OR** (topic[0] = B) **]** **AND**
**[** (topic[1] = C) **OR** (topic[1] = D) **]**
| **Topic-Sets** | **Matching Logs** <<|
| [ A ] | topic[0] = A <<|
| [ A, null ] | ^ |
| [ null, B ] | topic[1] = B <<|
| [ null, [ B ] ] | ^ |
| [ null, [ B ], null ] | ^ |
| [ A, B ] | (topic[0] = A) **AND** (topic[1] = B) <<|
| [ A, [ B ] ] | ^ |
| [ A, [ B ], null ] | ^ |
| [ [ A, B ] ] | (topic[0] = A) **OR** (topic[0] = B) <<|
| [ [ A, B ], null ] | ^ |
| [ [ A, B ], [ C, D ] ] | $TopicABaCD <<|
_code: ERC-20 Transfer Filter Examples @lang<javascript>
//_hide: const tokenAddress = ethers.constants.AddressZero;
//_hide: const myAddress = ethers.constants.AddressZero;
//_hide: const myOtherAddress = ethers.constants.AddressZero;
//_hide: const id = ethers.utils.id;
//_hide: const hexZeroPad = ethers.utils.hexZeroPad;
// Short example of manually creating filters for an ERC-20
// Transfer event.
//
// Most users should generally use the Contract API to
// compute filters, as it is much simpler, but this is
// provided as an illustration for those curious. See
// below for examples of the equivalent Contract API.
// ERC-20:
// Transfer(address indexed src, address indexed dst, uint val)
//
// -------------------^
// ----------------------------------------^
//
// Notice that only *src* and *dst* are *indexed*, so ONLY they
// qualify for filtering.
//
// Also, note that in Solidity an Event uses the first topic to
// identify the Event name; for Transfer this will be:
// id("Transfer(address,address,uint256)")
//
// Other Notes:
// - A topic must be 32 bytes; so shorter types must be padded
// List all token transfers *from* myAddress
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
hexZeroPad(myAddress, 32)
]
};
// List all token transfers *to* myAddress:
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
null,
hexZeroPad(myAddress, 32)
]
};
// List all token transfers *to* myAddress or myOtherAddress:
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
null,
[
hexZeroPad(myAddress, 32),
hexZeroPad(myOtherAddress, 32),
]
]
};
_null:
To simplify life, ..., explain here, the contract API
_code: ERC-20 Contract Filter Examples @lang<javascript>
//_hide: const tokenAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; /* DAI */
//_hide: const myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
//_hide: const otherAddress = "0xEA517D5a070e6705Cc5467858681Ed953d285Eb9";
//_hide: const provider = ethers.getDefaultProvider();
//_hide: const Contract = ethers.Contract;
abi = [
"event Transfer(address indexed src, address indexed dst, uint val)"
];
contract = new Contract(tokenAddress, abi, provider);
// List all token transfers *from* myAddress
//_result:
contract.filters.Transfer(myAddress)
//_log:
// List all token transfers *to* myAddress:
//_result:
contract.filters.Transfer(null, myAddress)
//_log:
// List all token transfers *from* myAddress *to* otherAddress:
//_result:
contract.filters.Transfer(myAddress, otherAddress)
//_log:
// List all token transfers *to* myAddress OR otherAddress:
//_result:
contract.filters.Transfer(null, [ myAddress, otherAddress ])
//_log:
_subsection: Solidity Topics @<events-solidity>
This is a quick (and non-comprehensive) overview of how events are computed
in Solidity.
This is likely out of the scope for most developers, but may be interesting
to those who want to learn a bit more about the underlying technology.
Solidity provides two types of events, anonymous and non-anonymous. The
default is non-anonymous, and most developers will not need to worry about
anonymous events.
For non-anonymous events, up to 3 topics may be indexed (instead of 4), since
the first topic is reserved to specify the event signature. This allows
non-anonymous events to always be filtered by their event signature.
This topic hash is always in the first slot of the indexed data, and is
computed by normalizing the Event signature and taking the keccak256 hash
of it.
For anonymous events, up to 4 topics may be indexed, and there is no
signature topic hash, so the events cannot be filtered by the event
signature.
Each additional indexed property is processed depending on whether its
length is fixed or dynamic.
For fixed length types (e.g. ``uint``, ``bytes5``), all of which are
internally exactly 32 bytes (shorter types are padded with zeros;
numeric values are padded on the left, data values padded on the right),
these are included directly by their actual value, 32 bytes of data.
For dynamic types (e.g. ``string``, ``uint256[]``) , the value is hashed
using keccak256 and this hash is used.
Because dynamic types are hashed, there are important consequences in
parsing events that should be kept in mind. Mainly that the original
value is lost in the event. So, it is possible to tell is a topic is
equal to a given string, but if they do not match, there is no way
to determine what the value was.
If a developer requires that a string value is required to be both
able to be filtered and also able to be read, the value must be included
in the signature twice, once indexed and once non-indexed (e.g.
``someEvent(string indexed searchBy, string clearText)``).
For a more detailed description, please refer to the
[Solidity Event Documentation](link-solidity-events).
_heading: Other Things? TODO
Explain what happens to strings and bytes, how to filter and retain the value