Skip to content

Flag storage prefixes that collide with an inherited base's reserved prefix#1773

Merged
shargon merged 11 commits into
neo-project:master-n3from
Jim8y:feat/reserved-prefix-collision
Jun 26, 2026
Merged

Flag storage prefixes that collide with an inherited base's reserved prefix#1773
shargon merged 11 commits into
neo-project:master-n3from
Jim8y:feat/reserved-prefix-collision

Conversation

@Jim8y

@Jim8y Jim8y commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

NC4056 (StorageKeyCollisionAnalyzer) only compared a contract's own StorageMap/LocalStorageMap prefixes against each other, so it missed the more common footgun: a contract that inherits Ownable or Pausable and then builds a StorageMap with that base class's reserved prefix.

Ownable stores its owner under 0xFF and Pausable its paused flag under 0xFE. A derived contract that reuses those prefixes silently shares the base's storage namespace — corrupting the stored owner or paused state — with no diagnostic today.

How

Before scanning the contract's own StorageMap fields, seed the per-class prefix set with the reserved prefixes of any inherited framework base (Ownable0xFF, Pausable0xFE). The existing collision check then fires when a derived contract reuses one. This reuses the existing NC4056 rule (no new diagnostic id); the message names the base class whose prefix is being reused.

A contract that does not inherit the base is unaffected and may use any prefix, so there are no false positives.

Testing

Adds positive tests for both reserved prefixes and negatives: a non-inheriting contract using 0xFF (no warning) and an inheriting contract using a different prefix (no warning). Full analyzer suite green (171).

…prefix

NC4056 (StorageKeyCollisionAnalyzer) only compared a contract's own
StorageMap/LocalStorageMap prefixes against each other, so it missed the more
common footgun: a contract that inherits Ownable or Pausable and then builds
a StorageMap with that base class's reserved prefix. Ownable stores its owner
under 0xFF and Pausable its paused flag under 0xFE, so a derived contract that
reuses those prefixes silently shares the base's storage namespace and can
corrupt the owner or paused state with no diagnostic.

Seed the per-class prefix set with the reserved prefixes of any inherited
framework base (Ownable 0xFF, Pausable 0xFE) before scanning the contract's
own StorageMap fields, so the existing collision check fires when a derived
contract reuses one. Reuses the existing NC4056 rule; the message names the
base class whose prefix is being reused. A contract that does not inherit the
base is unaffected, so there are no false positives.

Adds tests for both reserved prefixes plus negatives (a non-inheriting
contract using 0xFF, and an inheriting contract using a different prefix).
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.70%. Comparing base (9c7157b) to head (2c4083c).

Additional details and impacted files
@@            Coverage Diff             @@
##           master-n3    #1773   +/-   ##
==========================================
  Coverage      82.70%   82.70%           
==========================================
  Files            305      305           
  Lines          27076    27076           
  Branches        3769     3769           
==========================================
  Hits           22394    22394           
  Misses          3530     3530           
  Partials        1152     1152           

☔ 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.

@Jim8y Jim8y requested review from Wi1l-B0t and shargon June 23, 2026 12:59
Wi1l-B0t
Wi1l-B0t previously approved these changes Jun 24, 2026

@Wi1l-B0t Wi1l-B0t left a comment

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.

Simple Patch.

ajara87
ajara87 previously approved these changes Jun 24, 2026

@shargon shargon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I love it, could you add also Tokens nep17 and 11?

@Jim8y Jim8y dismissed stale reviews from ajara87 and Wi1l-B0t via 0576450 June 24, 2026 09:10
@Jim8y Jim8y requested review from Wi1l-B0t and shargon June 24, 2026 09:14
@Jim8y

Jim8y commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Added the reserved prefixes for Ownable2Step, PausableOwnable, AccessControl, and RoyaltyNep11Token too.

["global::Neo.SmartContract.Framework.AccessControl"] = [0xFB],
["global::Neo.SmartContract.Framework.TokenContract"] = [0x00, 0x01],
["global::Neo.SmartContract.Framework.Nep17Token"] = [0x00, 0x01],
["global::Neo.SmartContract.Framework.Nep11Token`1"] = [0x02, 0x03, 0x04],

@Wi1l-B0t Wi1l-B0t Jun 24, 2026

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.

Why 'Nep11Token`1', not 'Nep11Token' ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It uses Roslyn's MetadataName. Generic types include their arity there, so Nep11Token is Nep11Token`1.

@ajara87

ajara87 commented Jun 26, 2026

Copy link
Copy Markdown
Member

@shargon could you merge?

@shargon shargon merged commit 883a0d8 into neo-project:master-n3 Jun 26, 2026
5 checks passed
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.

4 participants