Solidity-native tooling to generate Solidity source. Builds a valid .sol file
(pragma + foundry-clean formatting) hosting build-time constants that a contract
imports and the compiler inlines: prebuilt function-pointer tables (the case
that motivates it — runtime gas efficiency in the Rain interpreter), a
deployed-bytecode hash, a described-by meta hash, and plain
address/uint8/bytes32/bytes constants.
Also exposes the tooling interfaces (IIntegrityToolingV1, IOpcodeToolingV1,
IParserToolingV1, ISubParserToolingV1) that Rain contracts implement to
build the pointers this library caches.
A consumer drives this library from a build script, and the path of that script
is not a free choice: it must be script/Build.sol, the path org CI regenerates
and currency-checks committed generated sources from — see
rainix-copy-artifacts.yaml.
Any other path is an unsupported layout.
The org's worked example is
rainlanguage/rain.deploy:
script/Build.sol
generates into its committed src/generated/. This repo carries no example of
its own — it is the library, and nothing here is generated.
Generated code is imported downstream by contracts that themselves expose pointers, which pointers feed back into the generation. This cycle means pointers may need to be regenerated several times until they reach a fixed point where neither pointer values nor the codehash of any consuming contract shift.
Reaching that fixed point is the consumer's job: nothing bounds or iterates the
loop. A tree that has not settled fails the currency check exactly as a tree
nobody regenerated does — see
rainix-copy-artifacts.yaml
— so regenerate until the working tree stops changing before committing, and
read a tree that never stops changing as a cycle that does not converge rather
than one more pass to run. Committing part-way records a deployed-bytecode hash
for a contract compiled against a different pass of the same file.
LibFs.pathForContract names src/generated/<Contract>.sol, and
buildFileForContract writes that file and only that file. Consumers commit it
and import it by path from src/**, so the path is a cross repo contract:
moving it, or moving GENERATED_DIR, breaks every repo holding the artifact at
the old path.
buildFileForContract therefore refuses to generate while src/generated/
holds another artifact for the same contract — any direct child named for that
contract, in full, followed by a . and anything other than sol. Nothing
regenerates such a file, while the consumer's imports keep resolving to it, so
the build fails with OrphanedGeneratedArtifact naming the file rather than
generating beside it. Delete it and repoint the imports at
src/generated/<Contract>.sol in the same commit.
Only direct children are read, so a generation into src/generated/ never reads
inside the per release snapshot directories that sit there, and never refuses
one of them either, because a tag carries no .. buildFileForTaggedContract
writes into one of those directories, and reads that directory rather than
src/generated/, so each is checked against its own contents.
Every generated file opens with a licence tag, a copyright tag,
pragma solidity ^0.8.25; and an autogenerated notice.
The file lands in the consuming repo, so the licence it is under and the
copyright holder it names are that repo's statement about its own source, and
LibCodeGen.filePrefix, LibFs.buildFileForContract and
LibFs.buildFileForTaggedContract all take them as arguments. Each value has to
be a non-empty single line: an empty one leaves a tag that reuse lint accepts
while it names nothing and solc refuses, and a line break ends the tag's line
so that whatever follows lands as source.
Each of those three also has an overload taking neither, which declares
RAIN_SPDX_LICENSE_IDENTIFIER (LicenseRef-DCL-1.0) and RAIN_COPYRIGHT_TEXT
(Copyright (c) 2020 Rain Open Source Software Ltd) — the values every repo in
this org declares for its own source. A Rain repo generates through those and
passes nothing. Both constants are exported from LibCodeGen, so a consumer
that threads the header down through its own build library names them rather
than restating the strings.
A repo this org does not own passes its own two values. The defaulting overloads
would stamp Rain's licence and Rain's copyright holder into its files, and
reuse lint there would still pass, because it checks that the tag is present
rather than that it is right.
LibCodeGen wraps the declarations it emits itself, deciding against
MAX_LINE_LENGTH and NEWLINE_DUE_TO_MAX_LENGTH. Those two encode
forge fmt's line_length and tab_width, which this repo states in [fmt]
of foundry.toml rather than inheriting.
A consuming repo whose [fmt] disagrees gets generated sources its own
forge fmt reflows away from what this library emits. Consumers therefore need
line_length = 120 and tab_width = 4.
Via soldeer:
forge soldeer install rain-sol-codegen~<version>This repo uses nix. The default shell is the
slim sol-shell from rainix.
nix develop # enter the shell
forge soldeer install # install deps declared in foundry.toml
forge buildChecks, each of which CI also runs:
forge testforge fmt --checkslither .reuse lint
forge test writes scratch files under src/generated/, creating the directory
if it is absent. Nothing there is committed, and every test removes its own
file, so a completed run leaves the directory empty and invisible to git. Same
arrangement as meta/, which the meta-hash tests use the same way. Anything
left there after an interrupted run is scratch, and git status will say so.
.github/workflows/rainix.yaml
is what runs all four in CI, via rainix's rainix-sol.yaml. CI also applies
org-wide gates that none of the four covers — see
rainix-sol-static.yaml
— so a green local run is necessary but not sufficient.
Use the nix-pinned forge for all development.
Publishing is merge-driven, not tag-driven.
Package Release
calls rainix's
rainix-autopublish.yaml
reusable on every push to main, passing the package name explicitly as
soldeer-package: rain-sol-codegen.
That workflow owns both the version and the release tag, so neither is set by
hand. [external.package].version in foundry.toml is therefore the next,
unpublished version rather than the last published one.
DecentraLicense 1.0 (DCL-1.0) — full text in
LICENSES/. Roughly CAL-1.0
(opensource.org) plus user-data
disclosure obligations consistent with permissionless-blockchain assumptions.
This repo is REUSE 3.3 compliant. Verify locally:
nix develop -c reuse lint