Tightly packing Ethereum event logs
Events on Ethereum are a emitted from contracts. It’s a great place to put data you want to query historical data for.
For example, ERC-20 tokens (like UCDC) use the Transfer
event to log when a token has been transferred. It consists of three arguments and is structured like this: Transfer(address from, address to, uint256 amount)
.
This is how a sample Transfer
event looks like:
1 000000000000000000000000B42C838346Ce40aa3B22582A635c01D56C8C7943 (from)
2 000000000000000000000000728713b41fcEc5C071359Ecf2802Fa0B62bec5a8 (to)
3 0000000000000000000000000000000000000000000000000000000000000001 (amount)
As you can see, all arguments are padded with 0
s to take up 32 bytes. Isn’t this a lot of useless space?
Even if you define an event with many small data types, it won’t be tightly packed.
This is how an event TestEvent(uint8 someNumber, bool someBool)
could look like:
1 000000000000000000000000000000000000000000000000000000000000000F (someNumber)
2 0000000000000000000000000000000000000000000000000000000000000001 (someBool)
Lots of unused space…
Why doesn’t Solidity allow tightly packing event arguments?
I don’t know…
Maybe it is because this data is only stored in transaction receipts and is therefore not so important?
Manually packing event arguments
Even if Solidity doesn’t automatically pack event arguments, we could always do it ourselves.
Imagine we have an event with four uint64
communityIds
.
We would have Communities(uint64 communityId1, uint64 communityId2, uint64 communityId3, uint64 communityId4)
1 0000000000000000000000000000000000000000000000000000000000000000 (communityId1) // the min number 0
2 0000000000000000000000000000000000000000000000001111111111111111 (communityId2)
3 0000000000000000000000000000000000000000000000001234567890abcdef (communityId2)
4 000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFF (communityId4) // the max number
Instead of these taking four 32-byte words, we could manually put them all into the same space.
Communities(uint256 communities)
1 000000000000000011111111111111111234567890abcdefFFFFFFFFFFFFFFFF (communityIds)
// (communityId) (communityId2) (communityId3) (communityId4)
The problem with this is that it would be harder to parse.
Tools like Viem or Ethers would automatically parse the whole communityIds
hex as one giant number.
In Dune however, you could be able to parse it with this query:
Take a look at the event here: https://goerli.etherscan.io/address/0xf39011e462241b31bfc0018eb6913c8d6b87a3b1#events
Conclusion
It's a shame that Solidity doesn't automatically tightly pack event data. Doing it manually comes with trade-offs.
The best option in most cases is probably to stick with the default, don’t worry about the extra 0000
s and not outsmart ourselves.