Overview
campaign/src/types.rs::Error and common/src/lib.rs::ErrorCode are both #[contracterror] enums with overlapping discriminant assignments, and the existing comment in common/src/lib.rs says "All discriminants are stable — never renumber existing variants", yet two distinct crates depend on this invariant independently. If campaign/ ever pulls common into its dependency graph (the lib comment already claims the crate is "used by both campaign and core contracts"), a downstream panic site could fire the wrong discriminant code.
Evidence
common::ErrorCode::NotInitialized = 1
campaign::Error::AlreadyInitialized = 1
common::ErrorCode::AlreadyInitialized = 2
campaign::Error::NotInitialized = 2
common::ErrorCode::Unauthorized = 3
campaign::Error::Unauthorized = 3
common::ErrorCode::InvalidAmount = 4
campaign::Error::CampaignEnded = 4
campaign::Error::CampaignNotActive = 5
common::ErrorCode::Asset... (none)
The values 1 and 2 are swapped between the two enums. Even where they align (Unauthorized = 3, InvalidAmount = 4), the meaning diverges: campaign's InvalidAmount = 70 lives far past common's InvalidAmount = 4. Both crates declare their errors as stable.
Impact
- If a third crate (likely
crates/contracts/core/ or a future shared "orbit-error" crate) ever imports BOTH, discriminant conflicts produce skewed panic_with_error!(env, Error::Foo) semantics: panics fire the wrong u32, off-chain indexers misattribute failures, and Error(Contract, #N) symbolic names misalign.
- Off-host tooling that grep-builds
Error(Contract, #N) per discriminant can produce a confusing mix of two definitions of "discs 1-3".
- The stability promise of one crate silently threatens the stability promise of the other.
Recommended Approach
Pick one of these, in order of preference:
- De-duplicate or alias. Move the canonical
Error enum into common/ under the existing name (ErrorCode is more accurate since this is shared error-space) and have campaign/src/types.rs::Error re-export the common type. This is the right answer when the two intent over the same concepts. Workspace-wide adoption.
- Pick a disjoint discriminant range. Re-number one of the two (e.g. push
common::ErrorCode to start at 1000) so they cannot collide even if both are imported. This is a non-stable-discriminant-promise change, so it must migrate the deployment sequence carefully.
- Drop one of the two. If
common::ErrorCode is unused outside its own crate, delete it entirely and document the explicit choice. (Grep for use common::ErrorCode to confirm.)
Acceptance Criteria
Affected Files
campaign/src/types.rs
common/src/lib.rs
crates/contracts/core/src/lib.rs (to check whether it imports common — it does not, currently, but future work might)
docs/deployment.md
Overview
campaign/src/types.rs::Errorandcommon/src/lib.rs::ErrorCodeare both#[contracterror]enums with overlapping discriminant assignments, and the existing comment incommon/src/lib.rssays "All discriminants are stable — never renumber existing variants", yet two distinct crates depend on this invariant independently. Ifcampaign/ever pullscommoninto its dependency graph (the lib comment already claims the crate is "used by both campaign and core contracts"), a downstream panic site could fire the wrong discriminant code.Evidence
The values
1and2are swapped between the two enums. Even where they align (Unauthorized = 3,InvalidAmount = 4), the meaning diverges: campaign'sInvalidAmount = 70lives far past common'sInvalidAmount = 4. Both crates declare their errors as stable.Impact
crates/contracts/core/or a future shared "orbit-error" crate) ever imports BOTH, discriminant conflicts produce skewedpanic_with_error!(env, Error::Foo)semantics: panics fire the wrong u32, off-chain indexers misattribute failures, andError(Contract, #N)symbolic names misalign.Error(Contract, #N)per discriminant can produce a confusing mix of two definitions of "discs 1-3".Recommended Approach
Pick one of these, in order of preference:
Errorenum intocommon/under the existing name (ErrorCodeis more accurate since this is shared error-space) and havecampaign/src/types.rs::Errorre-export the common type. This is the right answer when the two intent over the same concepts. Workspace-wide adoption.common::ErrorCodeto start at 1000) so they cannot collide even if both are imported. This is a non-stable-discriminant-promise change, so it must migrate the deployment sequence carefully.common::ErrorCodeis unused outside its own crate, delete it entirely and document the explicit choice. (Grep foruse common::ErrorCodeto confirm.)Acceptance Criteria
common::ErrorCodeis removed entirely — and the chosen approach is captured incommon/src/lib.rsandcampaign/src/types.rsdoc commentscargo expandor compile-time grep of#[contracterror]discriminants shows no duplicates across the workspacedocs/deployment.mdif any renumbering is required for stabilityAffected Files
campaign/src/types.rscommon/src/lib.rscrates/contracts/core/src/lib.rs(to check whether it imports common — it does not, currently, but future work might)docs/deployment.md