Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions campaign/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ pub fn validate_milestone_transition(
#[cfg(test)]
mod test {
pub mod claim_refund_tests;
pub mod error_discriminant_tests;
pub mod get_campaign_status_tests;
pub mod integration_tests;
pub mod invariant_tests;
Expand Down
72 changes: 72 additions & 0 deletions campaign/src/test/error_discriminant_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::types::Error;
use common::ErrorCode;

#[test]
fn campaign_and_common_error_discriminants_do_not_collide() {
let campaign_codes = [
Error::AlreadyInitialized as u32,
Error::NotInitialized as u32,
Error::Unauthorized as u32,
Error::CampaignEnded as u32,
Error::CampaignNotActive as u32,
Error::AssetNotAccepted as u32,
Error::DonationTooSmall as u32,
Error::MilestoneNotFound as u32,
Error::MilestoneNotUnlocked as u32,
Error::PreviousMilestoneNotReleased as u32,
Error::CannotCancelWithFunds as u32,
Error::RefundWindowClosed as u32,
Error::InvalidGoalAmount as u32,
Error::InvalidEndTime as u32,
Error::InvalidMilestones as u32,
Error::InsufficientContractBalance as u32,
Error::Overflow as u32,
Error::InvalidAssets as u32,
Error::InvalidAssetCode as u32,
Error::MilestoneMismatch as u32,
Error::InvalidMilestoneCount as u32,
Error::InvalidCampaignTransition as u32,
Error::InvalidMilestoneTransition as u32,
Error::GoalNotReached as u32,
Error::InvalidStorageValue as u32,
Error::StorageWriteError as u32,
Error::InvalidRecipient as u32,
Error::MissingIssuerAddress as u32,
Error::ZeroReleaseAmount as u32,
Error::NothingToRelease as u32,
Error::MilestoneReleasedExceedsTarget as u32,
Error::MilestoneAlreadyReleased as u32,
Error::UnreleasedMilestonesExist as u32,
Error::RefundNotPermitted as u32,
Error::NoDonorRecord as u32,
Error::RefundAlreadyClaimed as u32,
Error::ReentrantCall as u32,
Error::InvalidAmount as u32,
Error::ContractFrozen as u32,
];

let common_codes = [
ErrorCode::NotInitialized as u32,
ErrorCode::AlreadyInitialized as u32,
ErrorCode::Unauthorized as u32,
ErrorCode::InvalidAmount as u32,
];

for common_code in common_codes {
assert!(
(1000..=1099).contains(&common_code),
"common error code {common_code} must stay in the shared 1000..=1099 namespace"
);
assert!(
!campaign_codes.contains(&common_code),
"common error code {common_code} collides with campaign::Error"
);
}

for campaign_code in campaign_codes {
assert!(
campaign_code < 1000,
"campaign error code {campaign_code} must stay below the shared common namespace"
);
}
}
4 changes: 4 additions & 0 deletions campaign/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use soroban_sdk::{contracterror, contracttype, Address, BytesN, Env, String, Vec
/// Codes are stable — never renumber an existing variant; only append new ones.
/// Each code maps to a `u32` via `contracterror` and is surfaced in transaction
/// results as `Error(Contract, #N)`.
///
/// Campaign owns the `1..=999` contract-local error namespace. Shared workspace
/// errors from `common::ErrorCode` are reserved for `1000..=1099`; keep the two
/// ranges disjoint whenever a new variant is added.
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
Expand Down
15 changes: 9 additions & 6 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Common types shared across the OrbitChain workspace.
//!
//! This crate provides canonical definitions for `CampaignStatus`, `MilestoneStatus`,
//! `AssetInfo`, and `ErrorCode` used by both campaign and core contracts.
//! `AssetInfo`, and the shared error-code range used by both campaign and core
//! contracts.
//!
//! # Versioning
//! All discriminants are stable — never renumber existing variants.
//! All discriminants are stable — never renumber existing variants. Shared
//! workspace errors must stay in the `1000..=1099` range so they cannot collide
//! with contract-local error enums such as `campaign::types::Error`.

#![no_std]
use soroban_sdk::{contracterror, contracttype};
Expand Down Expand Up @@ -44,11 +47,11 @@ pub struct AssetInfo {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ErrorCode {
/// Contract has not been initialized yet.
NotInitialized = 1,
NotInitialized = 1000,
/// Contract has already been initialized.
AlreadyInitialized = 2,
AlreadyInitialized = 1001,
/// Caller is not authorized to perform this operation.
Unauthorized = 3,
Unauthorized = 1002,
/// The amount supplied is invalid (zero, negative, or out of range).
InvalidAmount = 4,
InvalidAmount = 1003,
}
12 changes: 12 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ timestamp. This prevents accidental or malicious `u64`-scale future dates from
making status views, refund-window checks, milestone release arithmetic, and
campaign reports meaningless while still allowing long-running campaigns.

## Error-Code Migration Note

`campaign::types::Error` owns the contract-local `1..=999` error namespace.
`common::ErrorCode` owns the shared workspace `1000..=1099` namespace. This
keeps `Error(Contract, #N)` values unambiguous for off-chain indexers and any
future crate that imports both enums.

If a deployed integration previously interpreted `common::ErrorCode` values as
`1..=4`, migrate that integration before it consumes the shared crate again:
`NotInitialized=1000`, `AlreadyInitialized=1001`, `Unauthorized=1002`, and
`InvalidAmount=1003`. Campaign contract error values are unchanged.

## Troubleshooting

- **`InsufficientFee`**: Add `--fee 1000000` to the deploy command.
Expand Down
Loading