Skip to content

fix: CONTRACT_NOT_FOUND for system contracts (0x1, 0x2)#3788

Open
thiagodeev wants to merge 1 commit into
mainfrom
thiagodeev/fix-rpc-calls-to-system-contracts
Open

fix: CONTRACT_NOT_FOUND for system contracts (0x1, 0x2)#3788
thiagodeev wants to merge 1 commit into
mainfrom
thiagodeev/fix-rpc-calls-to-system-contracts

Conversation

@thiagodeev

@thiagodeev thiagodeev commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #3777. System contracts 0x1/0x2 hold storage but have no Cairo class, so Juno returned 0x0 instead of CONTRACT_NOT_FOUND.

Fixes

  • getClassHashAt, getNonce: 0x0 > CONTRACT_NOT_FOUND for 0x1/0x2
  • getClassAt: CLASS_HASH_NOT_FOUND > CONTRACT_NOT_FOUND (invalid error per spec)

Changes

  • core/class.go: new shared SystemContracts vars + IsSystemContract helper (consolidates identity duplicated across state packages)
  • core/state + core/deprecatedstate: use the core symbols instead of local copies
  • rpc/v8,v9,v10: filter system contracts in ClassHashAt/Nonce (after block resolution, so BLOCK_NOT_FOUND keeps priority); ClassAt converts CLASS_HASH_NOT_FOUND to CONTRACT_NOT_FOUND

@thiagodeev thiagodeev marked this pull request as ready for review July 3, 2026 04:02
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @thiagodeev's task in 3m 51s —— View job


Review: PR #3788 — unify system contracts and fix related rpc logic

  • Read the full diff
  • Review core state / class changes
  • Review rpc v8/v9/v10 class + nonce changes
  • Build changed packages (go build ./core/... ./rpc/... ✅)
  • Post inline comments + summary

Overall this is a clean, well-scoped refactor. It consolidates the duplicated system-contract (0x1/0x2) definitions into core (SystemContract*Address, SystemContracts, IsSystemContract) and makes the RPC handlers return CONTRACT_NOT_FOUND for these classless protocol contracts, which matches the spec (they hold storage but have no Cairo class). The state-commit purge loop was simplified from an inner range over the map to a direct IsSystemContract(&addr) check — behaviorally equivalent since the old inner loop only acted when addr.Equal(&nAddr). Good test coverage was added across all three RPC versions and both handlers. go build passes for the affected packages.

Blocker

None.

Important

None.

Nits

  • Error comparison style (rpc/{v8,v9,v10}/class.go): ClassAt uses pointer equality err == rpccore.ErrClassHashNotFound; the rest of the RPC layer compares .Code. Correct today (the singleton is returned directly), just a consistency/robustness point — see inline comment.
  • Exported mutable globals (core/class.go): SystemContract1Address, SystemContract2Address, and SystemContractsClassHash are exported felt.Felt value vars. Because felt.Felt methods mutate through a pointer receiver, a stray core.SystemContract1Address.SetX(...) would corrupt the global. This mirrors the existing felt.One/felt.Zero convention, so low concern — but a doc note or unexported backing value would remove the footgun.

Nothing here blocks merge.

Note: I couldn't execute go test (test commands weren't permitted in this environment), but the mock setup uses HeadState()/Class() with AnyTimes() so the new sub-tests are correctly wired.
· thiagodeev/fix-rpc-calls-to-system-contracts

@thiagodeev thiagodeev changed the title fix(core): unify system contracts and fix related rpc logic fix: CONTRACT_NOT_FOUND for system contracts (0x1, 0x2) Jul 3, 2026
Comment thread rpc/v10/class.go
if err != nil {
// getClassAt only returns CONTRACT_NOT_FOUND / BLOCK_NOT_FOUND per spec;
// a class-hash miss here means the contract is not properly deployed.
if err == rpccore.ErrClassHashNotFound {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (consistency): this uses pointer equality err == rpccore.ErrClassHashNotFound, whereas the rest of the RPC code compares by code (e.g. err.Code == rpccore.ErrTransactionExecutionError.Code in estimate_fee.go). It's correct here because Class returns the singleton directly, but comparing .Code would be more robust if the error is ever cloned/wrapped in the future. Same applies to rpc/v8/class.go:166 and rpc/v9/class.go:150.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.14%. Comparing base (7dbcfd9) to head (a2187e6).

Files with missing lines Patch % Lines
core/state/state.go 70.00% 1 Missing and 2 partials ⚠️
rpc/v10/class.go 87.50% 1 Missing ⚠️
rpc/v8/class.go 87.50% 1 Missing ⚠️
rpc/v9/class.go 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3788      +/-   ##
==========================================
- Coverage   75.34%   75.14%   -0.20%     
==========================================
  Files         438      438              
  Lines       39528    39664     +136     
==========================================
+ Hits        29781    29806      +25     
- Misses       7676     7787     +111     
  Partials     2071     2071              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread core/class.go
Comment on lines +25 to +42
// System contracts are protocol-level contracts (0x1, 0x2) that hold storage
// but have no Cairo class; their class hash is always zero. They are not part
// of StateDiff.DeployedContracts and are auto-created during state commit so
// their storage trie can exist.
var (
SystemContract1Address = felt.One // block-hash storage contract
// https://community.starknet.io/t/starknet-v0-13-4-pre-release-notes/115257#p-2358763-stateful-compression-11
SystemContract2Address = felt.FromUint64[felt.Felt](2) // global counter
SystemContractsClassHash = felt.Zero

SystemContracts = [...]felt.Felt{SystemContract1Address, SystemContract2Address}
)

// IsSystemContract reports whether addr is a protocol system contract (0x1, 0x2)
// that has no Cairo class.
func IsSystemContract(addr *felt.Felt) bool {
return addr.Equal(&SystemContract1Address) || addr.Equal(&SystemContract2Address)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System contract seem to be state specific logic, why not have them implemented in the states? Maybe it can be in just one of them, while the other just imports it?

@rodrodros

Copy link
Copy Markdown
Contributor

System contracts 0x1/0x2 hold storage but have no Cairo class, so Juno returned 0x0 instead of CONTRACT_NOT_FOUND.

I don't think this holds true, system contracts are empty classes, empty class doesn't necessarily mean non-existent. I would agree with Juno behaviour in this case

@rodrodros

Copy link
Copy Markdown
Contributor

I am not sure our behaviour is wrong? Are we violating the specs in any way?

@thiagodeev

Copy link
Copy Markdown
Contributor Author

The CLASS_HASH_NOT_FOUND error being returned by the ClassAt method is a true violation of the spec. However, the spec doesn't mention anything related to the system contracts edge cases.
Let me know what you want

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getClassHashAt / getNonce return 0x0 for system addresses (0x1, 0x2) instead of CONTRACT_NOT_FOUND

2 participants