Skip to content

Commit fd114b6

Browse files
mw2000claude
andauthored
chore: polish mix.exs and add ExDoc + Pages workflows (#129)
* chore: polish mix.exs and add ExDoc + Pages workflows - mix.exs: add description, source/homepage URLs, elixirc_paths(:test), docs config with module groups, ex_doc dep, preferred_envs via def cli/0 - test/test_helper.exs: drop manual Code.require_file in favor of elixirc_paths(:test) including test/support - lib/eevm.ex: rewrite landing moduledoc — drop stale "learning project" framing, list actual top-level modules - lib/eevm/block/receipt.ex: fix broken doc reference (t:EEVM.Block.Bloom.log_entry/0) - .github/workflows/elixir.yml -> ci.yml: rename and add a Docs job that fails on ExDoc warnings - .github/workflows/pages.yml: new — builds and deploys docs to GitHub Pages on push to main Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ci: scope docs warning gate to ExDoc output only The Docs job tripped on `use Bitwise is deprecated` from deps/bn — a transitive hex dep, not our code. Lint already enforces `mix compile --warnings-as-errors` for our own code (deps are excluded by default), so the docs job should only fail on warnings emitted by ExDoc itself (broken `t:.../0` refs, missing modules, etc.), not on compile-time warnings that surface during a fresh dep build. Split compile and docs into separate steps and use awk to grep only the lines after the "Generating docs..." marker. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent edc1df6 commit fd114b6

7 files changed

Lines changed: 184 additions & 33 deletions

File tree

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# separate terms of service, privacy policy, and support
44
# documentation.
55

6-
name: Elixir CI
6+
name: CI
77

88
on:
99
push:
@@ -82,3 +82,44 @@ jobs:
8282
run: mix deps.get
8383
- name: Run tests
8484
run: mix test
85+
86+
docs:
87+
name: Docs
88+
runs-on: ubuntu-latest
89+
90+
steps:
91+
- uses: actions/checkout@v4
92+
- name: Set up Elixir
93+
uses: erlef/setup-beam@v1
94+
with:
95+
elixir-version: '1.18.3'
96+
otp-version: '27.0'
97+
- name: Restore dependencies cache
98+
uses: actions/cache@v4
99+
with:
100+
path: deps
101+
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
102+
restore-keys: ${{ runner.os }}-mix-
103+
- name: Restore build cache
104+
uses: actions/cache@v4
105+
with:
106+
path: |
107+
_build
108+
priv/native
109+
native/eevm_bls/target
110+
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock', 'native/**/Cargo.lock', 'native/**/Cargo.toml', 'native/**/src/**') }}
111+
restore-keys: ${{ runner.os }}-build-
112+
- name: Install dependencies
113+
run: mix deps.get
114+
- name: Compile project
115+
run: mix compile
116+
- name: Generate docs (fail on ExDoc warnings)
117+
run: |
118+
mix docs 2>&1 | tee docs.log
119+
# Only fail on warnings emitted by ExDoc itself (after "Generating docs..."),
120+
# not on compile warnings from deps that may surface during a fresh build.
121+
if awk '/Generating docs\.\.\./{flag=1; next} flag' docs.log \
122+
| grep -E "^[[:space:]]+warning:"; then
123+
echo "::error::ExDoc emitted warnings — see log above"
124+
exit 1
125+
fi

.github/workflows/pages.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
name: Build docs
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
- name: Set up Elixir
25+
uses: erlef/setup-beam@v1
26+
with:
27+
elixir-version: '1.18.3'
28+
otp-version: '27.0'
29+
- name: Restore dependencies cache
30+
uses: actions/cache@v4
31+
with:
32+
path: deps
33+
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
34+
restore-keys: ${{ runner.os }}-mix-
35+
- name: Restore build cache
36+
uses: actions/cache@v4
37+
with:
38+
path: |
39+
_build
40+
priv/native
41+
native/eevm_bls/target
42+
key: ${{ runner.os }}-build-${{ hashFiles('**/mix.lock', 'native/**/Cargo.lock', 'native/**/Cargo.toml', 'native/**/src/**') }}
43+
restore-keys: ${{ runner.os }}-build-
44+
- name: Install dependencies
45+
run: mix deps.get
46+
- name: Generate docs
47+
run: mix docs
48+
- name: Configure Pages
49+
uses: actions/configure-pages@v5
50+
- name: Upload artifact
51+
uses: actions/upload-pages-artifact@v3
52+
with:
53+
path: doc
54+
55+
deploy:
56+
name: Deploy to Pages
57+
needs: build
58+
runs-on: ubuntu-latest
59+
environment:
60+
name: github-pages
61+
url: ${{ steps.deployment.outputs.page_url }}
62+
63+
steps:
64+
- name: Deploy
65+
id: deployment
66+
uses: actions/deploy-pages@v4

lib/eevm.ex

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,31 @@
11
defmodule EEVM do
22
@moduledoc """
3-
EEVM — An Ethereum Virtual Machine implementation in Elixir.
3+
EEVM — Ethereum Virtual Machine implementation in Elixir.
44
5-
This is a learning project that implements the core EVM execution engine.
6-
It supports basic arithmetic, stack manipulation, memory operations,
7-
and control flow opcodes.
5+
Hardfork-aware execution engine covering Frontier through Prague: full
6+
opcode set, all precompiles (including BLS12-381 via NIF), transaction
7+
validation and processing, MPT-backed state root, and a harness for the
8+
official `ethereum/tests` `GeneralStateTests` suite.
89
910
## Quick Start
1011
11-
# Execute raw bytecode: PUSH1 2, PUSH1 3, ADD, STOP
1212
iex> result = EEVM.execute(<<0x60, 2, 0x60, 3, 0x01, 0x00>>)
1313
iex> result.status
1414
:stopped
1515
iex> EEVM.stack_values(result)
1616
[5]
1717
18-
## Architecture
19-
20-
- `EEVM.Interpreter.Stack` — LIFO stack (max 1024, uint256 values)
21-
- `EEVM.Interpreter.Memory` — Byte-addressable linear memory
22-
- `EEVM.Interpreter.MachineState` — Combined execution state
23-
- `EEVM.Interpreter.Instructions.Registry` — Opcode definitions and metadata
24-
- `EEVM.Interpreter` — The fetch-decode-execute loop
25-
26-
## Elixir Concepts Demonstrated
27-
28-
- **Structs & Maps** — Data structures with compile-time field checks
29-
- **Pattern Matching** — Multi-clause functions, destructuring
30-
- **Tagged Tuples** — `{:ok, val}` / `{:error, reason}` error handling
31-
- **Recursion** — Tail-recursive execution loop (no mutable state)
32-
- **Bitwise Operations** — Working with 256-bit integers
33-
- **Module Attributes** — Compile-time constants
34-
- **Typespecs** — `@spec` annotations for documentation and Dialyzer
18+
## Module Map
19+
20+
- `EEVM.Interpreter` — fetch-decode-execute loop and call frames
21+
- `EEVM.Interpreter.MachineState` — frame-local state (pc, stack, memory, gas)
22+
- `EEVM.Precompiles` — registered precompiles (`0x01`–`0x11`)
23+
- `EEVM.Transaction` — envelope decoding, validation, signing, intrinsic gas
24+
- `EEVM.Block.Processor` — end-to-end transaction-to-receipt pipeline
25+
- `EEVM.Database`, `EEVM.WorldState`, `EEVM.Storage` — account and storage state
26+
- `EEVM.MPT.Trie` + `EEVM.StateRoot` — Merkle-Patricia trie and state-root hash
27+
- `EEVM.HardforkConfig` — per-hardfork EIP activation flags
28+
- `EEVM.SystemContracts` — EIP-2935 (block hashes) and EIP-4788 (beacon roots)
3529
"""
3630

3731
alias EEVM.{Interpreter, Tracer}

lib/eevm/block/receipt.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule EEVM.Block.Receipt do
1515
this transaction. Per the Yellow Paper, receipts are ordered and the
1616
`cumulative_gas_used` of the last receipt equals the block's `gas_used`.
1717
- `logs` — every log entry emitted by this transaction, in emission order.
18-
Each log is a map matching `EEVM.Block.Bloom.log_entry/0`.
18+
Each log is a map matching `t:EEVM.Block.Bloom.log_entry/0`.
1919
- `logs_bloom` — the 2048-bit bloom filter over this receipt's logs, i.e.
2020
`EEVM.Block.Bloom.from_logs(logs)`. We store it pre-computed so block-level
2121
aggregation is a fast bytewise OR.

mix.exs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
11
defmodule EEVM.MixProject do
22
use Mix.Project
33

4+
@version "0.1.0"
5+
@source_url "https://github.com/mw2000/eevm"
6+
47
def project do
58
[
69
app: :eevm,
7-
version: "0.1.0",
10+
name: "eevm",
11+
version: @version,
812
elixir: "~> 1.18",
13+
description: "Ethereum Virtual Machine implementation in Elixir.",
14+
source_url: @source_url,
15+
homepage_url: @source_url,
916
start_permanent: Mix.env() == :prod,
17+
elixirc_paths: elixirc_paths(Mix.env()),
1018
deps: deps(),
1119
dialyzer: [
1220
plt_add_deps: :app_tree,
1321
plt_add_apps: [:ex_unit, :mix],
1422
ignore_warnings: "dialyzer_ignore.exs",
1523
list_unused_filters: true
1624
],
17-
aliases: aliases()
25+
aliases: aliases(),
26+
docs: docs()
27+
]
28+
end
29+
30+
def cli do
31+
[
32+
preferred_envs: [
33+
dialyzer: :dev,
34+
docs: :dev,
35+
"hex.publish": :dev
36+
]
1837
]
1938
end
2039

@@ -24,18 +43,20 @@ defmodule EEVM.MixProject do
2443
]
2544
end
2645

46+
defp elixirc_paths(:test), do: ["lib", "test/support"]
47+
defp elixirc_paths(_), do: ["lib"]
48+
2749
defp deps do
2850
[
2951
{:ex_rlp, "~> 0.6.0"},
30-
# Keccak-256 hash (Ethereum uses Keccak, not SHA3-256)
3152
{:ex_keccak, "~> 0.7"},
3253
{:ex_secp256k1, "~> 0.7"},
3354
{:jason, "~> 1.4"},
34-
# BN128 (alt_bn128) elliptic curve operations for EVM precompiles 0x06-0x08
3555
{:bn, "~> 0.2.2"},
3656
{:rustler, "~> 0.36", runtime: false},
3757
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
38-
{:dialyxir, "~> 1.4", only: [:dev], runtime: false}
58+
{:dialyxir, "~> 1.4", only: [:dev], runtime: false},
59+
{:ex_doc, "~> 0.34", only: :dev, runtime: false}
3960
]
4061
end
4162

@@ -50,4 +71,31 @@ defmodule EEVM.MixProject do
5071
]
5172
]
5273
end
74+
75+
defp docs do
76+
[
77+
main: "EEVM",
78+
source_url: @source_url,
79+
source_ref: "v#{@version}",
80+
extras: ["README.md", "LICENSE"],
81+
groups_for_modules: [
82+
Interpreter: [~r/^EEVM\.Interpreter($|\.)/],
83+
Instructions: [~r/^EEVM\.Interpreter\.Instructions/],
84+
Precompiles: [~r/^EEVM\.Precompile($|s)/],
85+
Block: [~r/^EEVM\.Block($|\.)/],
86+
Transaction: [~r/^EEVM\.Transaction($|\.)/],
87+
Context: [~r/^EEVM\.Context($|\.)/],
88+
"System Contracts": [~r/^EEVM\.SystemContracts($|\.)/],
89+
"State & Storage": [
90+
EEVM.Database,
91+
EEVM.Database.InMemory,
92+
EEVM.Storage,
93+
EEVM.WorldState,
94+
EEVM.StateRoot,
95+
~r/^EEVM\.MPT($|\.)/
96+
],
97+
Gas: [~r/^EEVM\.Gas($|\.)/, EEVM.HardforkConfig]
98+
]
99+
]
100+
end
53101
end

mix.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
"castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"},
55
"credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"},
66
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
7+
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
78
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
9+
"ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"},
810
"ex_keccak": {:hex, :ex_keccak, "0.7.8", "be1cf194d3158f0a305eaed0334e478d0d0f2c827e7c1f8f0e1e2a667da5a8ac", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "52de5b42b718df2534fb9a55780d8a05bbaea539f867c3e7c0a8e7e1d5f149d9"},
911
"ex_rlp": {:hex, :ex_rlp, "0.6.0", "985391d2356a7cb8712a4a9a2deb93f19f2fbca0323f5c1203fcaf64d077e31e", [:mix], [], "hexpm", "7135db93b861d9e76821039b60b00a6a22d2c4e751bf8c444bffe7a042f1abaf"},
1012
"ex_secp256k1": {:hex, :ex_secp256k1, "0.8.0", "aade42e790638de82a2b951a83f55b9cc6545b8c105b20a0112ff6b78e1800cd", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "87257ef7110c45ac396a110eb93023371db96b6f65ed0676046a11e866d1e3a0"},
1113
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
1214
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
15+
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
16+
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
17+
"makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
18+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
1319
"rustler": {:hex, :rustler, "0.37.3", "5f4e6634d43b26f0a69834dd1d3ed4e1710b022a053bf4a670220c9540c92602", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a6872c6f53dcf00486d1e7f9e046e20e01bf1654bdacc4193016c2e8002b32a2"},
1420
"rustler_precompiled": {:hex, :rustler_precompiled, "0.8.4", "700a878312acfac79fb6c572bb8b57f5aae05fe1cf70d34b5974850bbf2c05bf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3b33d99b540b15f142ba47944f7a163a25069f6d608783c321029bc1ffb09514"},
1521
}

test/test_helper.exs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
"test/support/**/*.ex"
2-
|> Path.wildcard()
3-
|> Enum.each(&Code.require_file/1)
4-
51
ExUnit.start()

0 commit comments

Comments
 (0)