Semantic Web and Web 3.0 · class 11 · demo
The contract, deployed
examples/03-contract/contracts/Log.sol, compiled by solc 0.8.37 to 1412 bytes and deployed on
the in-memory chain. Then the certificates of the domain were registered, one transaction per block.
The transactions, block by block
The issuer is the fixed account 0x1111111111111111111111111111111111111111; on this virtual machine nothing is signed,
the caller is declared. The transaction hashes are stand-ins — a real one comes from the receipt of a signed transaction.
| Block | Time | Call | Result | Tx |
|---|---|---|---|---|
| 8412990 | 2026-10-30 23:59:48 UTC | deploy Log | contract created | 0xe153…57542f |
| 8412991 | 2026-10-31 00:00:00 UTC | register(0x1d38a2…94aef7, certificate:2026-001) | event LogCreated | 0x5ebc…937c1d |
| 8412992 | 2026-10-31 00:00:12 UTC | register(0x559cc4…e5555a, certificate:2026-002) | event LogCreated | 0x607d…57203e |
| 8412993 | 2026-10-31 00:00:24 UTC | register(0x1d38a2…94aef7, certificate:2026-001) | reverted AlreadyRegistered(0x1d38a2…94aef7) no event, no change to the state — and on a real chain still in a block, and still paid for | 0xcd41…60cac6 |
| 8412994 | 2026-10-31 00:00:36 UTC | register(0x736e39…ab42d6, revocation:2026-002) | event LogCreated | 0xb8f2…1fd509 |
The state, read back with get(digest)
A view call: no transaction, no block. A digest nobody registered does not fail — it reads as an entry of zeros
(highlighted). That is why existence is tested by the author.
| Digest | Document | author | registeredAt | uri |
|---|---|---|---|---|
| 0x1d38a2…94aef7 | certificate:2026-001 | 0x111111…111111 | 1793404800 2026-10-31 00:00:00 UTC | https://data.csoares.me/certificate/2026-001 |
| 0x559cc4…e5555a | certificate:2026-002 | 0x111111…111111 | 1793404812 2026-10-31 00:00:12 UTC | https://data.csoares.me/certificate/2026-002 |
| 0x736e39…ab42d6 | revocation:2026-002 | 0x111111…111111 | 1793404836 2026-10-31 00:00:36 UTC | https://data.csoares.me/revocation/2026-002 |
| 0xc29e5c…8bb569 | certificate:2026-003 | 0x000…000 | 0 | "" |
private is not secret
entries is private, and yet the storage slot of the first certificate, keccak256(digest · 0), reads
straight from the state, with no ABI (example 04). The author and the timestamp are packed together in it:
slot 0x79a8298a3d002eae54728b21b3812c3a6be516217830289278cdddeb4bd74104 value 0x6ae52f801111111111111111111111111111111111111111
The ABI: what the outside world sees
| Kind | Declaration | Id | |
|---|---|---|---|
| error | error AlreadyRegistered(bytes32 digest) | 0x77caf672 | selector: the first 4 bytes of the call data |
| event | event LogCreated(bytes32 indexed digest, address indexed author, string uri) | 0x9b9e191a…5a8db1 | topic 0: what a node filters on |
| function | function get(bytes32 digest) view returns ((address author, uint64 registeredAt, string uri)) | 0x8eaa6ac0 | selector: the first 4 bytes of the call data |
| function | function register(bytes32 digest, string uri) | 0xcf2d31fb | selector: the first 4 bytes of the call data |
The source
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title Log -- an anchor of existence and integrity.
/// @notice Keeps on chain only the document digest and the URI where it
/// lives. The document stays off chain: costly, public and immutable are three problems.
contract Log {
// A struct groups fields, like a record. uint64 is a 64-bit unsigned
// integer: enough for a Unix timestamp, and cheaper to store than uint256.
struct Entry {
address author;
uint64 registeredAt;
string uri;
}
// A mapping is a key -> value table kept in the contract's storage, on
// chain. Every key "exists": a missing one reads as a struct of zeros.
mapping(bytes32 => Entry) private entries;
// An event is written into the transaction log, not into storage. It is
// cheap, contracts cannot read it, but off-chain programs (the indexer)
// can. `indexed` fields can be used as filters.
event LogCreated(bytes32 indexed digest, address indexed author, string uri);
// A custom error: cheaper than a revert string, and it carries data.
error AlreadyRegistered(bytes32 digest);
// `external`: callable from outside (a transaction or another contract).
// `calldata`: the string is read straight from the call input, not copied.
function register(bytes32 digest, string calldata uri) external {
// Existence is tested by the author: address(0) means "never written".
if (entries[digest].author != address(0)) revert AlreadyRegistered(digest);
// msg.sender is whoever called; block.timestamp is the block's time.
entries[digest] = Entry(msg.sender, uint64(block.timestamp), uri);
emit LogCreated(digest, msg.sender, uri);
}
// `view`: reads the state and changes nothing, so calling it costs no gas
// when done off chain. `memory`: the returned struct is a temporary copy.
function get(bytes32 digest) external view returns (Entry memory) {
return entries[digest];
}
}
An in-memory chain (the Ethereum virtual machine, @ethereumjs/evm), in this one process: no test network, no wallet, no keys, no money. Nothing a visitor does is kept.