aere-contracts/.gitea/workflows/ci.yml
Aere Network acac2f00a6
Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Has been cancelled
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Has been cancelled
contracts-ci / PQC known-answer tests (NIST vectors) (push) Has been cancelled
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
The unpublished line of work joins the sanitized public line
The published line and the local line had no common ancestor: the public one
carried the redaction pass, the local one carried three weeks of corrections
that never shipped. This commit ports the local work onto the public line,
keeps every public redaction, and extends the same discretion to seven client
mentions that were still named in published comments.

Carried: LICENSE year and LICENSING.md; the measured burn figures replacing
the deflation claim (the vault holds ~0.137 AERE of 2.8 billion, and burn is
a share of validator coinbase revenue, which is zero today); 'audited' removed
from next to Bouncy Castle; citation paths rewritten to published form with
CITATIONS-UNRESOLVED.md remeasured 2026-08-11; VERIFY-POLICY.md; slashing and
ownership comments brought down to what the code does; the AerePyth repair;
the shutter test helper the tests cite; runnable package.json entries; the CI
file split into a GitHub/Gitea twin pair with a real measured test-run status;
and the .gitignore hardening written after a compiled artifact leaked a local
path in a sibling repository. A false '2-of-3 multisig' description of the
owner account is corrected to what the chain measures: an externally owned
account. The self-audit findings catalog stays unpublished pending an explicit
decision.
2026-08-15 13:59:30 +03:00

521 lines
28 KiB
YAML

# Aere Network contracts: continuous verification (GITEA ACTIONS)
#
# ═════════════════════════════════════════════════════════════════════════════
# WHY THIS FILE EXISTS, AND WHY IT IS A SEPARATE FILE
# ═════════════════════════════════════════════════════════════════════════════
#
# The canonical host is our own Gitea instance at git.aere.network. Gitea
# Actions scans BOTH `.gitea/workflows/` and `.github/workflows/`, and prefers
# `.gitea/workflows/` when both are present. GitHub Actions scans
# `.github/workflows/` only. (SPEC: Gitea Actions documentation.)
#
# An earlier version of this repository relied on that overlap to run ONE file
# on both hosts, and justified pinning `actions/upload-artifact` at v3 with the
# note "On GitHub, v3 still runs."
#
# THAT NOTE IS OUT OF DATE AND THE SINGLE-FILE DESIGN IS THEREFORE BROKEN.
# GitHub retired the v3 artifact backend on 2025-01-30; `upload-artifact@v3`
# now hard-fails on GitHub-hosted runners. So the single-file design did not
# actually give portability, it gave a pipeline that fails on GitHub for a
# reason having nothing to do with our code. A CI that goes red for a
# tooling reason is worse than one that goes red for a real reason, because it
# trains people to ignore the colour.
#
# The split is the fix:
#
# .gitea/workflows/ci.yml (THIS FILE) -> runs on git.aere.network.
# upload-artifact pinned at v3,
# which is the protocol the Gitea
# artifact backend implements.
# .github/workflows/ci.yml -> runs on GitHub mirrors.
# upload-artifact at v4.
#
# Because Gitea prefers `.gitea/`, each host runs exactly one file and each
# file is correct for its host. Nothing is duplicated silently: the two files
# are identical except for the artifact action version and these header notes.
# Drift between them is checkable in one command:
#
# diff <(grep -v '^\s*#' .gitea/workflows/ci.yml | grep -v '^$') \
# <(grep -v '^\s*#' .github/workflows/ci.yml | grep -v '^$')
#
# Note `^\s*#` and not `^#`: indented comments must be stripped too, or the
# diff drowns in commentary and stops being a usable check.
#
# MEASURED 2026-07-20: that command returns exactly THREE differences, all of
# them the `upload-artifact@v3` / `@v4` line. `runs-on:` is currently identical
# in both files (`ubuntu-22.04`); it is called out separately below only because
# it is the line you would change per host if your runner registers a different
# label. If the diff ever returns anything other than those three lines, one of
# the two hosts is no longer running what we claim it runs, and that is a bug.
#
# ═════════════════════════════════════════════════════════════════════════════
# HONESTY STATEMENT: DOES THIS PIPELINE CURRENTLY PASS?
# ═════════════════════════════════════════════════════════════════════════════
#
# NO. Two of the four jobs are RED, deliberately, and will stay red until the
# underlying problems are fixed rather than until the pipeline is edited.
#
# job: build-and-test .......... RED
# job: interop ................. RED
# job: pqc-kat ................. GREEN
# job: coverage ................ RED
#
# Each status below is recorded from a command someone actually ran, with the
# date, and is labelled MEASURED. Nothing here is assumed.
#
# ── build-and-test: RED ──────────────────────────────────────────────────────
# MEASURED 2026-07-20, Windows 11, Node v24.14.1, npm 11.11.0, 15.78 GB RAM:
#
# NODE_OPTIONS=--max-old-space-size=6144 npx hardhat test
#
# 1,448 passing
# 9 failing
# run ABORTED, exit 127
# last line: memory allocation of 3623878656 bytes failed
#
# Read the counts carefully. The process was killed, so mocha never printed its
# summary. "1,448 passing / 9 failing" are result markers scraped from the log,
# NOT the suite's totals. The suite's true totals are currently UNKNOWN. Quoting
# 1,448 as "the number of passing tests" would be a smaller version of exactly
# the fabrication this pipeline exists to correct.
#
# Two distinct causes, kept apart:
#
# (1) MEMORY. The full suite does not fit in ~16 GB. A standard Gitea or
# GitHub runner has 16 GB, so this job is EXPECTED to die the same way
# until the suite is sharded across jobs. That sharding is NOT done.
#
# (2) NINE FAILURES.
# * 6 in `AereEthLightClient (real EIP-2537 BLS, Prague)`. A MISCONFIGURED
# RUN, not a contract defect: that suite lives in test/interop/ and
# ships its own config pinning the `prague` hardfork for the EIP-2537
# BLS12-381 precompiles (0x0b..0x11), while the default config pins
# `cancun` where those precompiles do not exist. `npx hardhat test`
# globs test/ recursively and drags it in under the wrong hardfork.
# MEASURED: under the correct config, one of those six becomes a PASS,
# and the run then SEGFAULTS in the EVM's EIP-2537 path. The `interop`
# job below runs it correctly and is red because of that segfault.
# * 3 undiagnosed (AereCompliancePool fuzz, AereGovernorV2 quorum
# invariant, AereLendingMarket solvency invariant). We have their titles
# and no assertion text, because the run died before mocha's summary. We
# do not know whether they are product defects or run artifacts, and we
# are not going to guess in public.
#
# ── interop: RED ─────────────────────────────────────────────────────────────
# Segmentation fault in node, inside the EVM implementation's EIP-2537 BLS
# path, on `accepts a valid full-participation update and advances finality`.
# A hard crash in the toolchain. Open and undiagnosed. Giving this suite its own
# config is running it the way it was written, NOT an exemption: it gates like
# every other job. If a green wall were the goal, this job would have been
# deleted instead.
#
# ── pqc-kat: GREEN ───────────────────────────────────────────────────────────
# MEASURED 2026-07-20, and INDEPENDENTLY RE-MEASURED the same day by a second
# session running this job's exact commands, in two parts:
#
# part 1 (CryptoRegistry x3 + SPHINCS+): 60 passing (6s) exit 0
# part 2 (Falcon-512/1024 + PQCMessage): 41 passing (28s) exit 0
#
# 101 passing, 0 failing, 0 pending. This is the one job that is green, and it
# is green having been run twice rather than assumed once.
#
# ── coverage: RED ────────────────────────────────────────────────────────────
# MEASURED 2026-07-20. The coverage run FAILED at the COMPILE phase:
#
# Unknown exception during compilation: Dynamic exception type:
# class std::bad_alloc
# Error in plugin solidity-coverage: HardhatError: HH600: Compilation failed
#
# exit 1, after roughly 45 minutes, node observed at 16.04 GB private bytes on a
# 15.78 GB box. WE HAVE NO COVERAGE NUMBER FROM TODAY. Not a low one. None.
#
# CORRECTION CARRIED FORWARD, because an earlier copy of this file contradicted
# itself on exactly this point and that is worth naming rather than quietly
# patching. A previous revision's coverage-job comment asserted "The COMPILE
# phase is solved: all contracts instrument and compile", while its own header
# recorded a compile-phase std::bad_alloc. Both cannot be true. The header was
# right and the job comment was a stale note from a 2026-07-19 run that did NOT
# reproduce on 2026-07-20. An internal note that no longer reproduces is the
# same species of problem as an unmeasured coverage percentage, only with a
# shorter half-life.
#
# A SECOND CORRECTION: `--testfiles` does NOT reduce compile cost.
# solidity-coverage instruments and compiles EVERY contract in the tree
# regardless; `--testfiles` only narrows which tests execute AFTERWARDS. So
# scoping by test file cannot rescue a run that dies during compilation. It
# cannot, by construction. We made that mistake and are recording it so nobody
# repeats it.
#
# THE COVERAGE FIGURE THIS PROJECT USED TO CARRY WAS FABRICATED. A "99.8 percent
# statements / 95 percent branches" figure has been associated with Aere
# Network. It was NEVER produced by a coverage tool. It was a target written as
# though it had been achieved. It is not a claim this repository makes.
# The honest replacement, in full:
#
# * Whole-repository coverage: NOT MEASURED. Not "approximately", not
# "roughly 90 percent": not measured. The run does not complete.
# * Best prior floor, dated 2026-07-19 and NOT reproduced on 2026-07-20:
# 26.82% statements (2,585/9,638), 22.74% branches (2,088/9,184),
# 23.25% functions (573/2,464), 27.21% lines (2,487/9,139), from a merge of
# two completed shards covering 64 of 168 test files against the FULL
# repository denominator. This is a floor from a minority of the test files.
# It must never be quoted as the project's coverage figure in either
# direction.
# * Scoped economic-core cluster (7 contracts of ~285), same 2026-07-19
# provenance, also not reproduced: 98.17% statements, 79.82% branches.
# Seven contracts is not a repository.
#
# 26.82 that reproduces is worth more to this project than 99.8 that evaporates.
#
# ═════════════════════════════════════════════════════════════════════════════
# WHAT THIS PIPELINE IS FORBIDDEN FROM DOING
# ═════════════════════════════════════════════════════════════════════════════
# These are the standard ways a CI file is made green while the software is
# broken. None is used here, and every absence is checkable by reading the file:
#
# * No `continue-on-error` on any compile, test, KAT or coverage step.
# * No `|| true` swallowing a non-zero exit in any gating step.
# * No coverage threshold invented below the real number to manufacture a
# pass. There is no threshold at all, because there is no real number yet.
# * No suite skipped for being slow or inconvenient. `.solcover.js` has an
# EMPTY `skipFiles`, so nothing is removed from the coverage denominator.
# * `if: always()` appears ONLY on artifact-upload steps, so a failing run
# still hands you its evidence. It never appears on a step that decides
# pass or fail. That distinction is the entire difference between a
# debuggable pipeline and a dishonest one.
#
# Verify the first two yourself, and note the exact patterns. A bare
# `grep secrets.` returns hits because this file DISCUSSES secrets in prose; the
# patterns below match the forms that would actually grant access or actually
# suppress a failure:
#
# grep -c '\${{ *secrets\.' .gitea/workflows/ci.yml # expect 0
# grep -cE '^\s*continue-on-error\s*:' .gitea/workflows/ci.yml # expect 0
#
# MEASURED 2026-07-20: both return 0.
#
# If the suite goes red, the correct response is to fix the suite. Editing this
# file to restore green is precisely the failure this design prevents.
name: contracts-ci
on:
push:
pull_request:
workflow_dispatch:
# Least privilege. This pipeline reads code and writes artifacts. Nothing else.
permissions:
contents: read
# ═════════════════════════════════════════════════════════════════════════════
# THE SECRETS BOUNDARY
# ═════════════════════════════════════════════════════════════════════════════
# "Radical open source" invites this question immediately, so it is answered
# here in full rather than as a footnote. Naming the boundary is part of the
# credibility, not a caveat to it. Linux publishes all of its code and none of
# its signing keys.
#
# WHAT THIS PIPELINE DOES NOT HAVE, AND MUST NEVER BE GIVEN:
#
# * validator signing keys for the 7 chain-2800 validators;
# * any deploy key, funded account private key, or mnemonic with a balance;
# * infrastructure credentials: SSH, hosting provider APIs, DNS, TLS private
# keys;
# * RPC admin, or any authenticated write endpoint;
# * explorer, relayer or oracle service credentials.
#
# The `env:` block below is exhaustive and contains no `secrets.*` reference.
# A pull request from a fork runs the identical pipeline against the identical
# (empty) credential surface.
#
# SCOPE OF THAT CLAIM, stated precisely so it cannot be over-read. It covers
# THIS workflow file. It is not a claim that no workflow anywhere in the wider
# Aere monorepo references a secret: the internal `reproducible-besu-build.yml`
# does reference a `WELLKNOWN_DEPLOY_TOKEN` used to publish a build digest to a
# static `.well-known` path. That token publishes a hash to a website. It is not
# a chain key, it cannot sign a transaction, and it is not present in this
# published contracts repository at all. We are naming it because a reader who
# greps only this file and trusts a blanket "no secrets anywhere" claim would
# have been misled, and a boundary that survives only careless reading is not a
# boundary.
#
# WHY A PUBLIC CI STRUCTURALLY CANNOT TOUCH THE LIVE CHAIN, beyond not holding
# keys. Four independent reasons, any one of which is sufficient:
#
# 1. Every job runs against an in-process Hardhat EVM at chain ID 31337. It
# never dials chain 2800. `AERE_RPC_URL` below is pinned to a dead local
# address so a stray config cannot reach a real endpoint.
# 2. The only account material anywhere in the test path is the well-known
# public Hardhat test mnemonic ("test test ... junk"), which holds nothing
# on any real network.
# 3. Writing to chain 2800 requires a signature from a funded key. No such key
# exists in this repository, this pipeline, or the runner environment. A
# compromised workflow file, a malicious dependency, or a hostile pull
# request still cannot produce a valid mainnet transaction.
# 4. Deployment is a separate, human-gated, offline procedure. It is
# deliberately NOT automated from CI. Moving it into CI would be a
# downgrade in safety, not an improvement in convenience.
#
# Aere consensus is CLASSICAL secp256k1 ECDSA QBFT with 7 Foundation-operated
# validators, f=2, quorum 5 of 7, 500 ms blocks (SPEC). Post-quantum
# cryptography exists at the signature, precompile, account and transport layers
# only. Nothing in this pipeline says anything about consensus, and no Aere
# material should ever describe Aere as having post-quantum consensus.
env:
# Bounds only the V8 old-space heap. The bulk of coverage's growth is
# Hardhat's in-process EVM state plus per-statement hit maps, which live
# OUTSIDE this budget. Do not expect this setting alone to make a whole-repo
# coverage run fit; it does not, and section "coverage: RED" above is why.
NODE_OPTIONS: --max-old-space-size=6144
# Never let a stray config or test reach a real endpoint from CI.
AERE_RPC_URL: http://127.0.0.1:0
jobs:
# ───────────────────────────────────────────────────────────────────────────
# 1. THE GATE. Clean-checkout install, compile, full suite. No exceptions.
# ───────────────────────────────────────────────────────────────────────────
build-and-test:
name: Install (lockfile) → compile → full test suite
# GITEA PORTABILITY: the runner must register a label matching this string.
# Ours registers `ubuntu-22.04`. If your Gitea runner registers only
# `ubuntu-latest`, change this line in all four jobs.
runs-on: ubuntu-22.04
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
# Pinned to the version every number in REPRODUCE.md was measured on.
node-version: '24.14.1'
cache: 'npm'
- name: Record toolchain versions
# Printed so any run can be compared against any other run. A number is
# only reproducible if you know what produced it.
run: |
echo "node: $(node --version)"
echo "npm: $(npm --version)"
echo "os: $(uname -a)"
echo "mem: $(free -g | awk '/^Mem:/{print $2" GB"}')"
echo "cores: $(nproc)"
- name: Install from the lockfile
# `npm ci`, NOT `npm install`. ci deletes node_modules and installs the
# exact lockfile tree, failing outright if package.json and
# package-lock.json disagree. That is precisely what an outsider cloning
# this repo does, and it is the step that catches "it works because of
# what was already on my machine".
run: npm ci
- name: Compile contracts
run: npx hardhat compile
- name: Full test suite
# THE GATE. No continue-on-error. A single failing test fails the run.
# Currently RED: see the header. Leaving it red is the correct state.
run: npx hardhat test
- name: Upload compiled artifacts
if: always()
# v3, not v4: the Gitea artifact backend implements the v3 upload
# protocol. The GitHub twin of this file uses v4, because GitHub retired
# v3 on 2025-01-30. This is the ONLY functional difference between the
# two files.
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: |
artifacts/
retention-days: 30
# ───────────────────────────────────────────────────────────────────────────
# 2. ETHEREUM INTEROP, run under the config it was actually written for.
# ───────────────────────────────────────────────────────────────────────────
interop:
name: Ethereum interop (EIP-2537 BLS, prague hardfork)
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '24.14.1'
cache: 'npm'
- name: Install from the lockfile
run: npm ci
- name: Interop suite (own config, prague hardfork)
# No continue-on-error. Expected to fail today, and that failure is the
# accurate signal.
run: npx hardhat test --config hardhat.config.interop.js
# ───────────────────────────────────────────────────────────────────────────
# 3. PQC KNOWN-ANSWER TESTS, isolated so a crypto regression is legible.
# ───────────────────────────────────────────────────────────────────────────
#
# These also run inside the full suite. They are ALSO run alone, because
# "do the post-quantum verifiers still match the official NIST vectors" is the
# question this project is asked most often, and burying its answer in a
# 1,400-line log is a bad answer.
#
# SCOPE HONESTY, because this is the most misread part of the project:
#
# * These KATs exercise the SOLIDITY verifier implementations against the
# official NIST reference vectors (falcon512-KAT.rsp, falcon1024-KAT.rsp,
# the FIPS 205 SLH-DSA ACVP vectors, the FIPS 202 SHAKE256 vector). The
# vectors ship in this repository as test/falcon512_kat0.json and
# test/falcon1024_kat0.json so you can diff them against the NIST
# originals rather than trusting our copy.
# * They assert BOTH directions, which is the part that matters: a genuine
# NIST signature is ACCEPTED, and a single-bit-flipped signature, a
# single-bit-flipped message, and a genuine signature under the wrong
# public key are all REJECTED. An "accepts everything" verifier would pass
# the first check and fail the rest.
# * They run on the local Hardhat EVM, which has NO Aere precompiles. The
# precompile-backed paths are therefore NOT covered by this job. A green
# tick here is evidence about the Solidity implementations, not about the
# deployed precompiles. Verifying those needs a precompile-bearing devnet,
# which a public CI does not have.
# * Precompile status is NOT uniform (SPEC): Falcon-512 0x0AE1,
# Falcon-1024 0x0AE2, ML-DSA-44 0x0AE3, SLH-DSA-128s 0x0AE4 and
# SHAKE256 0x0AE5 are live on mainnet from block 9,189,161. ML-KEM-768
# 0x0AE6 and Falcon HashToPoint 0x0AE7 are TESTNET-ONLY and are NOT live
# on mainnet. P-256 at 0x100 is live.
# * The on-chain ZK verifiers are classical BN254, which is breakable by a
# sufficiently large quantum computer. Post-quantum signature verification
# does not make the ZK stack post-quantum.
pqc-kat:
name: PQC known-answer tests (NIST vectors)
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '24.14.1'
cache: 'npm'
- name: Install from the lockfile
run: npm ci
- name: Compile contracts
run: npx hardhat compile
- name: Falcon / SPHINCS+ / ML-DSA known-answer tests
# No continue-on-error. A KAT mismatch is a hard failure: it means an
# implementation stopped agreeing with the NIST reference.
run: |
npx hardhat test \
test/AereFalcon512Verifier.test.js \
test/AereFalcon1024Verifier.test.js \
test/AereSphincsVerifier.test.js \
test/AerePQCMessageVerifier.test.js \
test/AereCryptoRegistry.test.js \
test/AereCryptoRegistry.agility.test.js \
test/AereCryptoRegistry.newprecompiles.test.js
- name: Upload KAT vectors used
if: always()
uses: actions/upload-artifact@v3
with:
name: pqc-kat-vectors
path: |
test/falcon512_kat0.json
test/falcon1024_kat0.json
retention-days: 90
# ───────────────────────────────────────────────────────────────────────────
# 4. COVERAGE. Read the header's "coverage: RED" section before quoting any
# number that comes out of this job.
# ───────────────────────────────────────────────────────────────────────────
#
# WHAT THIS JOB IS FOR: producing a real, checkable coverage number and the
# raw istanbul JSON alongside it, so anyone can recompute the percentage from
# the counters rather than take our word for the summary line.
#
# WHAT IT CURRENTLY DOES: fails. MEASURED 2026-07-20, it dies at the COMPILE
# phase with std::bad_alloc out of solc itself, exit 1, on a 15.78 GB box.
# It is left doing the real thing and failing honestly rather than made green
# by lowering a threshold, shrinking the denominator via skipFiles, or adding
# continue-on-error.
#
# WHAT IT WILL NOT PRODUCE EVEN WHEN IT RUNS: a whole-repository percentage.
# A single-process whole-repo run does not fit a 16 GB runner. The route to a
# real whole-repo number is sharding with --testfiles plus an istanbul merge.
# That merge is arithmetically valid because instrumentation is deterministic
# over identical sources, so every shard emits identical statement, branch and
# function maps and the per-counter arrays sum. Keeping skipFiles empty in
# every shard is what guarantees those maps match. Until that sharded run is
# taken to completion end to end, the honest whole-repo figure is NOT
# MEASURED, and this pipeline says so rather than substituting a scoped number
# for it.
coverage:
name: Coverage (scoped, with artifacts)
runs-on: ubuntu-22.04
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '24.14.1'
cache: 'npm'
- name: Install from the lockfile
run: npm ci
- name: Coverage over the economic-core cluster
# Scoped by TEST FILE, never by skipFiles. This matters: skipFiles would
# shrink the DENOMINATOR and inflate the percentage, which is the most
# common way a coverage number gets quietly flattered. `--testfiles`
# leaves every contract instrumented and simply runs fewer tests, so the
# number produced is an honest "what these tests reach", measured
# against the full instrumented denominator.
#
# Note the limitation recorded in the header: --testfiles does NOT
# reduce COMPILE cost, so this scoping does not rescue the run from its
# current compile-phase OOM. It is here for when the compile is fixed.
#
# No continue-on-error: if a test in this scope fails, coverage fails.
run: |
npx hardhat coverage \
--config hardhat.config.coverage.js \
--testfiles "{test/aere-fee-burn-vault.test.js,test/aere-sink-branch-coverage.test.js,test/saere-v2-fix.test.js,test/aeresink-conservation-invariant.test.js}"
- name: Print the measured summary
run: |
echo "── Coverage summary (istanbul) ─────────────────────────────"
cat coverage/coverage-summary.json 2>/dev/null \
|| echo "coverage-summary.json not emitted; see the artifact for coverage.json"
- name: Upload the raw coverage report
# `if: always()` is used ONLY on artifact-upload steps, so a FAILED run
# still hands you its evidence. It is never used on a step that decides
# pass or fail.
if: always()
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: |
coverage/
coverage.json
retention-days: 90