AGENTS.md
September 16, 2026 · View on GitHub
Primary entry point for any AI agent working in this codebase. Also read CONTRIBUTING.md for general contribution guidelines.
Project Overview
Gno (Gnolang) is a deterministic, interpreted variant of Go for writing smart contracts. gno.land is the L1 blockchain that runs Gno code.
| Pillar | Directory | What it does |
|---|---|---|
| GnoVM | gnovm/ | Interpreter/VM, parser, type-checker |
| gno.land | gno.land/ | Blockchain node, ABCI app, SDK, CLI tools |
| Tendermint2 | tm2/ | Consensus, p2p, mempool, crypto, RPC |
Other directories: examples/ (sample realms/packages), contribs/ (tools: gnodev, gnofaucet, etc.), misc/, docs/, .github/.
.gno Files Are NOT Go
.gnolooks like Go but runs in a deterministic VM.- No goroutines, channels,
unsafe,net,os. - Realms (
r/) are stateful (on-chain state). - Packages (
p/) are stateless libraries. - Import paths:
gno.land/p/...orgno.land/r/...— nevergithub.com/.... - Module config:
gnomod.toml(notgo.mod). cross(rlm)form for cross-realm calls.realmtype for realm-aware functions.
Build & Test
make install # Install gnokey, gno, gnodev
make test # Run all tests (slow)
make lint # golangci-lint
make fmt # Format all code
| Gno tests | Go tests |
|---|---|
gno test ./path/... | go test ./... |
Test files: _test.gno | Test files: _test.go |
Filetests: _filetest.gno | Integration: .txtar in gno.land/pkg/integration/testdata/ |
VM file tests: gnovm/tests/files/ with // Output: | Standard Go test runner |
Verification Rules
- After changing anything under
gnovm/stdlibs/,gnovm/tests/stdlibs/, or the VM's execution context, always run the Gno example suite before declaring done:cd examples && go run ../gnovm/cmd/gno test ./...Go tests do not cover.gnofiletests. A change can pass everygo testtarget below and still leaveexamples/red — stdlib behavior reaches filetests through a different path.
- After changing gas constants or allocation/GC logic, always run these before declaring done:
go test ./gno.land/pkg/sdk/vm/ -run Gasgo test ./gno.land/pkg/integration/ -run TestTestdatago test ./gnovm/pkg/gnolang/ -run Files -test.short
- Always run
/simplifybefore presenting completed work on non-trivial changes. - When comparing gas numbers or performance metrics before vs after, always verify the test logic has not changed, including loop counts and input sizes. Show reasoning, not just numbers.
- Never claim a percentage improvement without confirming the test is doing the same work in both cases.
Conventions
Gno (.gno)
- Format:
gno fmt. - Import paths:
gno.land/{p,r}/...only. - No OS/network access.
- Before writing Gno code, read the relevant docs in
docs/resources/:gno-interrealm.md— cross-realm calls,cross(rlm)/realmsemanticseffective-gno.md— idiomatic Gno patternsgo-gno-compatibility.md— what works and what doesn't vs Gogno-testing.md— testing patterns for.gnofilesgno-packages.md— package structure and conventionsgno-security-guide.md— security patterns and known vulnerability families (see Gno Security Semantics below)
Go (.go)
- Format:
gofmt/goimports. - Lint:
golangci-lint(.github/golangci.yml).
Universal
- Commits: conventional (
feat:,fix:,docs:,chore:,test:,refactor:) with optional scope (feat(gnovm):). - Branches: kebab-case (
fix-vm-params), always feature branches. - If posting comments or reviews under your owner's GitHub account fully autonomously (no human reviewing the output before it is posted), disclose that you are an AI agent (e.g., prefix with
[bot]or add a "generated by" note) so humans can distinguish agent activity from the account owner. Human-assisted contributions that the account owner reviews and approves before posting do not need this disclosure. - Don't credit the agent as a commit author (
Co-Authored-By,Assisted-By, "Generated by"); the human author owns the commit. Note AI usage in the PR description if relevant. - When writing PR descriptions, grep for all new/modified files in the diff (
git diff --stat) and categorize them. Do not omit major new files like benchmarks, tooling, or calibration scripts. - List all categories of work in PR descriptions, including features, bug fixes, tooling, and tests, not just the headline feature.
- Keep PRs up to date with
master— merge regularly and resolve conflicts promptly. - When merging
masterinto your branch, review incoming changes carefully. IfCONTRIBUTING.mdorAGENTS.mdwas updated, re-read and follow the latest rules. - Be respectful and helpful in PR comments and reviews — optimize for the reviewer's time, especially human maintainers.
- See CONTRIBUTING.md.
Gno Security Semantics
- Before writing or reviewing any caller-authentication, access-control, or cross-realm code in Gno (
/r/,/p/,/e/packages), readdocs/resources/gno-interrealm.md. Do not pattern-match from Soliditymsg.senderor other-language intuition. - For the complete AI-focused checklist (11 cases), read
docs/resources/gno-ai-contract-review.mdbefore reviewing any realm. - Reading one balance is
banker.GetCoin(addr, denom), notGetCoins(addr).AmountOf(denom).GetCoinsreads every denom the address holds, and since anyone can send any address a new denom without consent, its cost is set by a third party — on a caller-supplied address that is a permanent out-of-gas waiting to happen. Hoisting a repeatedGetCoinsout of a loop helps but does not fix it: the second call's per-key reads are cache hits and cost no gas, while its iterator walk is charged again, so what remains still scales with a denom count a third party controls. Two exceptions, both real:GetCoinpanics on a malformed denom whereAmountOfreturns zero, so do not put an unvalidatedRenderpath segment or query parameter through it; and if the surrounding function already calledGetCoins, use its result rather than reading again. Both statements are about the realm-facingchain/bankerAPI — the Go keeper'sGetCoinandstd.Coins.AmountOfbehave the other way round, so do not carry a measurement from one side over to the other. runtime.PreviousRealm()only shifts on explicit cross-calls (fn(cross, ...)) into crossing functions (func fn(cur realm, ...){...}). APreviousRealm().PkgPath() == "..."check inside a non-crossing function does not identify the immediate caller and is a security bug.- In a crossing function, derive caller identity from
cur.Previous()and write nocur.IsCurrent()guard: the runtime putscurinto the current realm-context on entry, so the check is always true.IsCurrent()belongs on a realm value a caller hands over as an ordinary argument,rlmby convention, which may be left over from an earlier call. - Never read
chain/runtime/unsafe.PreviousRealm()in a crossing function: it walks the frame stack, so it answers from wherever it is called, whilecur.Previous()names the realm that was current whencurwas minted. Delete anychain/runtime/unsafeimport sitting beside acur realmparameter. - When editing a realm that accepts payment via
banker.OriginSend(), the caller guard must becur.Previous().IsUserCall(), notIsUser().IsUser()acceptsmaketx runephemeral realms, which can consume the origin-send envelope before calling your function and bypass the payment check. See docs/resources/effective-gno.md § Verifying inbound Coin payments. - When you see an existing realm using
IsUser()plusbanker.OriginSend(), flag it and cross-check nearbyOriginSendusage. - Never return a pointer to a
/p/-type instance stored in realm state if that type has any exported mutation method (e.g.avl.Tree.Set,avl.Tree.Remove). Readonly taint does not block method dispatch — borrow rule #2 fires and the write commits under your realm's authority. Return values or narrow read-only views instead. Render(path string)receives attacker-controlled input. Never write path segments, user-supplied keys, or free-form string values directly into markdown output. Usesanitize.InlineTextfromgno.land/p/nt/markdown/sanitize/v0for inline content.
Read docs/resources/gno-security-guide.md for the full catalog of known vulnerability families. The audit pattern harness in misc/audit-pattern-harness/ has a vulnerable/fixed fixture pair for each family the VM permits — run it against unfamiliar realm code as a quick sanity check. The one exception is a stored realm-typed value (guide §5.7), which the VM refuses outright, so there is no runnable vulnerable variant to compare against.
Its rules match text, not an AST, so it reports both false positives and false negatives — a clean run is a smoke test passing, not a realm being safe. Read the code against the guide's checklist regardless; the harness is there to catch the shapes you would otherwise have to remember.
Architecture Decision Records (ADRs)
Every non-trivial AI-assisted PR must include an ADR. This documents what the AI understood so reviewers can verify assumptions and future contributors can build on it.
| Scope | Directory |
|---|---|
| GnoVM | gnovm/adr/ |
| gno.land | gno.land/adr/ |
| Tendermint2 | tm2/adr/ |
Naming: pr<number>_<description>.md.
Use prxxxx_ if PR number unknown.
Include: context, decision, alternatives considered, consequences.
See gnovm/adr/ for examples.
Match detail to complexity.
Skip ADRs for: trivial bug fixes, formatting, simple tests, docs-only changes.
Don'ts
git push --force— never force push unless explicitly asked.--no-verify— never skip hooks.go generate— slow, large diffs. Only if explicitly asked.- Modify
gno.land/genesis/— only if that's the task. - Goroutines/OS calls in
.gno— never works. - Break
gno.land/p/demo/backwards-compat — needs discussion. - AI-assisted PRs without an ADR.
Navigation
| What | Where |
|---|---|
| Understand a realm | Read its main .gno file |
| VM internals | gnovm/pkg/gnolang/machine.go, gnovm/pkg/gnolang/preprocess.go |
| Node/keeper | gno.land/pkg/sdk/vm/keeper.go |
| Client/RPC | gno.land/pkg/gnoclient/ |
| Example patterns | examples/gno.land/p/nt/ownable/ |
| Past decisions | gnovm/adr/, gno.land/adr/ and tm2/adr/ |
Glossary
- Realm — stateful smart contract (
.gno, underr/) - Package — stateless library (under
p/) - GnoVM — the interpreter
- gno.land — the blockchain
- Gnolang — the language
- ADR — architecture decision record
- ABCI — app-to-consensus interface
- GovDAO — governance DAO
- gnokey — key/tx CLI
- gnoland — node binary
- gnodev — local dev with hot reload
Improving This Document
This file should stay concise, correct, and useful. Both humans and agents should help maintain it.
Humans: if you spot something wrong or missing, fix it directly — small PRs welcome. If an agent got confused by something, add a clarification here so the next one doesn't.
Agents: if you hit something misleading or missing in this doc during your work, flag it to the human and suggest a concrete edit. Include the fix in your PR when practical — future agents will benefit. If your human corrects you on something that this doc could have prevented, propose adding it.
The goal is a living document that gets sharper with each use.