Migrating from V0.8 to V0.9
interface construct, a covenant deploy command, a covenant test --watch flag, a --target-chain wasm target and a cargo install covenant-cli line. None of those exist. It also promised unconditional source compatibility, which the v0.9.4 and v0.9.5 fail-loud passes ended. Everything below was re-checked against the compiler.
What changes for existing contracts
V0.9.0 was source-compatible with V0.8. The later V0.9.x patches are not. V0.9.0 itself added no deprecations and required no edits. Since then the fail-loud passes in v0.9.4 and v0.9.5 have turned a set of previously accepted forms into hard compile errors, because each of them compiled to something that was silently wrong on chain.
Search your V0.8 source for these before upgrading. Every one of them used to build without a diagnostic:
min/max/abs/pow/sqrt(E424). They lowered toa + b, somax(cap, bid)returnedcap + bid. Refused until a real compare-and-branch lowering exists. Write the comparison out:if a > b { a } else { b }.map.length/.keys/.values(E425). They compiled to a constant 0, sofor each k in m.keysran zero iterations.- Division or remainder by a literal zero (E519). EVM
DIVandMODare total, sox / 0silently evaluated to 0. Non-literal divisors now get a runtime zero-divisor guard instead. - A crypto opcode with no helper method behind it (E520).
x in list(E426). It lowered to a single scalar equality, a membership test that passed only for the first element.- Nested map writes,
inner[a][b] = v(E522). They emitted noSSTOREat all, returned success, and read back 0. This is the usual allowance pattern. - Map
.argmax/.argmin(E427). They always returned 0. List.argmaxand.argminstill work. - A non-anonymous event with more than three
indexedparameters (E512). Theemitlowered to an unconditional revert and the contract shipped an invalid ABI.
The compiler's Unreleased entry adds one more: transfer <amount> from <src> to <dst> is refused (E523). The from operand was parsed and then dropped by codegen, so the statement paid <dst> out of the contract's own balance. Use transfer <amount> to <dst>, or model the debit in storage first.
None of these are style changes. Each one is a case where V0.8 produced a contract that did not do what its source said.
What is new that you should adopt
1. Target-chain compilation is explicit
V0.8 compiled for MockChain by default and used legacy precompile addresses on Sepolia, which broke ceremony, FHE, PQ and ZK (KSR-CVN-005). V0.9 introduces explicit --target-chain and routes cryptographic operations to per-chain helper contracts:
covenant build contract.cov --target-chain sepolia # V0.9 helper addresses embedded
covenant build contract.cov --target-chain mockchain # legacy 0x101+ precompiles (V0.8 default)
covenant build contract.cov --target-chain aster_testnet # V0.9 helpers on Aster Testnet
covenant build contract.cov --target-chain aster # Aster native backend
Those are the only accepted values. Mainnet is rejected at parse time: V0.9 is testnet-only, and mainnet helpers are a V1.0 item gated on the external audit. There is no wasm target on this flag. A covenant-wasm-backend crate exists in the workspace, but covenant build does not expose it.
If your V0.8 contracts target Sepolia and use ceremony, FHE, PQ or ZK primitives, rebuild with --target-chain sepolia to pick up the helper bridge. The bytecode embeds CREATE2-deterministic helper addresses, and no source change is required.
2. Ceremony works on Sepolia
V0.8: the ceremony lifecycle compiled but reverted on Sepolia at the precompile call site, because no executor was deployed at the legacy amnesia addresses (0x120 to 0x123).
V0.9: the lifecycle (setup, submit_share, finalize, destroy) executes end to end through CeremonyHelper. That is the M1 milestone, recorded in the compiler's MILESTONES.md.
If you had a ceremony deployed on V0.8 Sepolia, redeploy with V0.9 and --target-chain sepolia. Note that the ceremony's cryptography is mocked, as it was in V0.8: see the limitations below.
3. New top-level constructs
nft is real and compiles. It synthesizes 12 functions from a five-line declaration, mirroring OpenZeppelin's ERC-721 surface, plus 3 events and 5 errors:
-- Verified with `covenant build` on v0.9.7.
nft CoolApes {
name: "Cool Apes"
symbol: "APE"
base_uri: "https://api.example.com/"
}
That emits name, symbol, tokenURI, balanceOf, ownerOf, getApproved, isApprovedForAll, approve, setApprovalForAll, transferFrom, mint and burn. Two things to know before you ship it: mint is open-access by design, so add an only deployer guard yourself if you need one, and safeTransferFrom is not synthesized.
registry parses, but it does not compile. Both its synthesized key_of and register functions use pq_key, which is a dynamic bytes ABI type, and this release's codegen can only read or return a single 32-byte word. The compiler refuses with E505 rather than ship a key-corruption bug. There is no workaround in v0.9.5.
There is no interface construct. The keyword does not parse (E028). The real mechanism for typed cross-contract calls is external contract, declared with function members and called through .at(address):
-- Verified with `covenant build` on v0.9.7.
external contract IAuditNFT {
function balanceOf(address) view returns amount
function ownerOf(amount) view returns address
}
record NftViewer {
field nft_addr: address = zero_address
action set_nft(addr: address) only deployer {
nft_addr = addr
}
view lookup_balance(who: address) returns amount {
IAuditNFT.at(nft_addr).balanceOf(who)
}
view lookup_owner(token_id: amount) returns address {
IAuditNFT.at(nft_addr).ownerOf(token_id)
}
}
Views returning text through an external call raise W507: a non-constant text return is encoded as one raw word, not the offset and length a spec-compliant caller expects. Keep external returns to fixed-width types for now.
See 16-nft, 17-registry and 18-external-call for the longer examples.
4. The CLI
These are the real subcommands and flags, taken from covenant --help on v0.9.5:
covenant test contract.cov # run the inline test blocks, fresh state per test
covenant test contract.cov --coverage # name-heuristic action coverage report
covenant test contract.cov --list # list discovered tests without running them
covenant fmt --check # CI format gate
covenant lint src/ # 6 Solidity-ism rules, L001 to L006
covenant doctor # ten environment probes
covenant doctor --strict # non-zero exit if a probe failed
covenant init my-token # scaffold from a template
covenant explain E421 # long-form explanation for a diagnostic
covenant inspect storage contract.cov # resolved slot of every field
covenant layout diff old.storage.json build/Contract.storage.json
The full set is init, build, check, test, fmt, inspect, layout, lint, clean, completions, explain and doctor. There is no --watch flag on covenant test, and tests are inline blocks inside the contract source, not separate .test.cov files. An inline test is an action of the form action test_name() when <assertion> { }; release builds strip them from the deployed bytecode.
Two renames are worth stating plainly. covenant compile was the V0.6-era name for what is now covenant build, and covenant diff-layout is now covenant layout diff. Neither old spelling works today.
There has never been a covenant deploy. Deployment is done with external EVM tooling against the emitted bytecode and ABI, for example Foundry: cast send --create $BYTECODE --rpc-url $SEPOLIA --private-key $PK.
5. The linter catches Solidity-isms
V0.9 adds a six-rule source-scan linter (L001 to L006) that flags Solidity patterns pasted into a .cov file:
mapping(address => uint)becomesmap<address, amount>function foo() publicbecomesaction foo()// commentbecomes-- comment(a bare//is a hard E003 lexer error)require(cond, "msg")becomes awhenguard orrevert_withpragma solidity, drop the lineimport "...", drop it or replace it with Covenant constructs
Configure it with a .covenantlint.json file, which the linter searches for in the working directory or any ancestor.
6. Language server additions
- Go-to-definition for fields, actions, types and external contracts.
- Since v0.9.4 the server runs the whole pipeline rather than the frontend alone, so the fail-loud codes above appear as editor squiggles instead of surfacing only at build time.
- The language server is a separate binary named
covenant-lsp, not acovenantsubcommand.
What stays the same
- The type system rules and the privacy analysis carried over unchanged.
- The ERC surfaces (8227, 8228, 8229, 8231), now with a reference implementation behind the helper bridge.
- The compiler is a 21-crate Rust workspace.
Recommended migration steps
- Build the new compiler from source. Covenant is not published to crates.io, so there is no
cargo installpath. Clonecovenant-languageand runcargo build --release --bin covenant. See Installation. - Build once and read the errors. The fail-loud codes listed at the top are the whole migration. Anything that still builds was already compiling to what it said.
- Add an explicit target: put
--target-chainon yourcovenant buildinvocations. - Run the linter:
covenant lint src/to surface leftover Solidity-isms. - Run the doctor:
covenant doctorchecks your environment. - Check your storage layout before redeploying anything upgradeable:
covenant layout diffexits non-zero on a storage-incompatible change.
Known limitations in V0.9
- The cryptography is mocked. FHE, post-quantum, ZK, VDF and Shamir are deterministic placeholders with zero confidentiality and zero security. "Encrypted" values are readable straight from chain state, and the PQ and ZK verifiers accept forged inputs. The compiler depends on no cryptography library. Real cryptography is a separate, later release (V2.0), and V1.0 is the external-audit gate that still ships these placeholders.
registrydoes not compile (E505), as described above.safeTransferFromis not synthesized fornft. The receiver-hook callback adds external-call surface that V0.9 keeps audit-explicit.- Dynamic
textandbytesreturns are not ABI-encoded correctly (W507 and E505). Fixed-width returns are safe. - Aster live deploy is deferred. Codegen is ready; deploy is gated on Aster Chain factory verification.
Audit posture
V0.9 is testnet-only by design. Every audit so far is a Kairos Lab internal OMEGA review, never a third-party firm. An external audit is the gate for V1.0 and mainnet. See SECURITY.md for the disclosure policy.