Skip to main content

sumchain_primitives/
equity.rs

1//! SRC-83X Business, Governance & Equity Domain Standards
2//!
3//! This module implements the business and equity domain family:
4//! - SRC-831: Entity Identity Profile
5//! - SRC-832: Governance Action Standard
6//! - SRC-833: Equity Token Standard
7//! - SRC-834: Equity Controller Standard
8//! - SRC-835: Corporate Actions Interface
9//! - SRC-836: Ownership Proof Profiles
10//!
11//! Design Principles:
12//! - Controller-gated operations
13//! - Policy-driven governance
14//! - Privacy-preserving ownership proofs
15//! - Fungible equity with transfer restrictions
16
17use serde::{Deserialize, Serialize};
18
19use crate::{Address, BlockHeight, Timestamp};
20
21// =============================================================================
22// Type Aliases
23// =============================================================================
24
25/// Subject ID (32-byte hash, SRC-801)
26pub type SubjectId = [u8; 32];
27
28/// Policy ID (32-byte hash, SRC-803)
29pub type PolicyId = [u8; 32];
30
31/// Proof ID (32-byte hash)
32pub type ProofId = [u8; 32];
33
34/// Class ID for equity tokens
35pub type ClassId = [u8; 32];
36
37/// Action ID for governance actions
38pub type ActionId = [u8; 32];
39
40/// Snapshot ID
41pub type SnapshotId = [u8; 32];
42
43// =============================================================================
44// Domain Separation Constants
45// =============================================================================
46
47/// Domain separator for entity profiles
48pub const ENTITY_DOMAIN_SEP: &[u8] = b"SRC831-ENTITY:";
49
50/// Domain separator for governance actions
51pub const GOVERNANCE_ACTION_DOMAIN_SEP: &[u8] = b"SRC832-ACTION:";
52
53/// Domain separator for equity token IDs
54pub const EQUITY_TOKEN_DOMAIN_SEP: &[u8] = b"SRC833-TOKEN:";
55
56/// Domain separator for corporate actions
57pub const CORPORATE_ACTION_DOMAIN_SEP: &[u8] = b"SRC835-CORP-ACTION:";
58
59/// Domain separator for ownership proofs
60pub const OWNERSHIP_PROOF_DOMAIN_SEP: &[u8] = b"SRC836-PROOF:";
61
62/// Domain separator for snapshots
63pub const SNAPSHOT_DOMAIN_SEP: &[u8] = b"SRC835-SNAPSHOT:";
64
65// =============================================================================
66// SRC-831: Entity Identity Profile
67// =============================================================================
68
69/// Organization types
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71#[repr(u8)]
72pub enum OrgType {
73    /// Corporation (C-Corp, S-Corp)
74    Corporation = 0,
75    /// Limited Liability Company
76    LLC = 1,
77    /// Partnership (LP, LLP, GP)
78    Partnership = 2,
79    /// Decentralized Autonomous Organization
80    DAO = 3,
81    /// Foundation / Non-profit
82    Foundation = 4,
83    /// Trust
84    Trust = 5,
85    /// Cooperative
86    Cooperative = 6,
87    /// Other
88    Other = 255,
89}
90
91impl OrgType {
92    pub fn from_u8(v: u8) -> Option<Self> {
93        match v {
94            0 => Some(OrgType::Corporation),
95            1 => Some(OrgType::LLC),
96            2 => Some(OrgType::Partnership),
97            3 => Some(OrgType::DAO),
98            4 => Some(OrgType::Foundation),
99            5 => Some(OrgType::Trust),
100            6 => Some(OrgType::Cooperative),
101            255 => Some(OrgType::Other),
102            _ => None,
103        }
104    }
105
106    pub fn name(&self) -> &'static str {
107        match self {
108            OrgType::Corporation => "Corporation",
109            OrgType::LLC => "LLC",
110            OrgType::Partnership => "Partnership",
111            OrgType::DAO => "DAO",
112            OrgType::Foundation => "Foundation",
113            OrgType::Trust => "Trust",
114            OrgType::Cooperative => "Cooperative",
115            OrgType::Other => "Other",
116        }
117    }
118}
119
120/// Controller model hints
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
122#[repr(u8)]
123pub enum ControllerModel {
124    /// Single authorized signer
125    SingleSigner = 0,
126    /// Multi-signature (M-of-N)
127    MultiSig = 1,
128    /// Board of directors
129    BoardMultiSig = 2,
130    /// Token-weighted governance
131    TokenGovernance = 3,
132    /// Hybrid (board + token)
133    Hybrid = 4,
134}
135
136impl ControllerModel {
137    pub fn from_u8(v: u8) -> Option<Self> {
138        match v {
139            0 => Some(ControllerModel::SingleSigner),
140            1 => Some(ControllerModel::MultiSig),
141            2 => Some(ControllerModel::BoardMultiSig),
142            3 => Some(ControllerModel::TokenGovernance),
143            4 => Some(ControllerModel::Hybrid),
144            _ => None,
145        }
146    }
147}
148
149/// Entity service types
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
151#[repr(u8)]
152pub enum EntityServiceType {
153    /// Corporate mailbox
154    Mailbox = 0,
155    /// Investor relations
156    InvestorRelations = 1,
157    /// Transfer agent
158    TransferAgent = 2,
159    /// Cap table management
160    CapTable = 3,
161    /// Governance portal
162    Governance = 4,
163    /// Website
164    Website = 5,
165    /// Other
166    Other = 255,
167}
168
169/// Entity service endpoint
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub struct EntityService {
172    /// Service ID
173    pub service_id: String,
174    /// Service type
175    pub service_type: EntityServiceType,
176    /// Endpoint URI
177    pub endpoint: String,
178}
179
180/// Entity status
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
182#[repr(u8)]
183pub enum EntityStatus {
184    /// Active and in good standing
185    Active = 0,
186    /// Pending registration
187    Pending = 1,
188    /// Suspended
189    Suspended = 2,
190    /// Dissolved
191    Dissolved = 3,
192}
193
194impl EntityStatus {
195    pub fn from_u8(v: u8) -> Option<Self> {
196        match v {
197            0 => Some(EntityStatus::Active),
198            1 => Some(EntityStatus::Pending),
199            2 => Some(EntityStatus::Suspended),
200            3 => Some(EntityStatus::Dissolved),
201            _ => None,
202        }
203    }
204
205    pub fn is_active(&self) -> bool {
206        matches!(self, EntityStatus::Active)
207    }
208}
209
210/// Entity Identity Profile (SRC-831)
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212pub struct EntityProfile {
213    /// Subject ID (SRC-801)
214    pub subject_id: SubjectId,
215    /// Organization type
216    pub org_type: OrgType,
217    /// Legal name commitment (BLAKE3 hash, not actual name)
218    pub name_commitment: [u8; 32],
219    /// Jurisdiction of incorporation (ISO 3166-1/2)
220    pub jurisdiction: Option<String>,
221    /// Registration number commitment (if applicable)
222    pub registration_commitment: Option<[u8; 32]>,
223    /// Controller model hint
224    pub controller_model: ControllerModel,
225    /// Controller address(es)
226    pub controllers: Vec<Address>,
227    /// Multi-sig threshold (if applicable)
228    pub multisig_threshold: Option<u8>,
229    /// Service endpoints
230    pub services: Vec<EntityService>,
231    /// Profile metadata hash
232    pub metadata_hash: [u8; 32],
233    /// Created timestamp
234    pub created_at: Timestamp,
235    /// Updated timestamp
236    pub updated_at: Timestamp,
237    /// Status
238    pub status: EntityStatus,
239}
240
241impl EntityProfile {
242    /// Generate subject ID for an entity
243    pub fn generate_subject_id(
244        org_type: OrgType,
245        name_commitment: &[u8; 32],
246        nonce: &[u8; 32],
247    ) -> SubjectId {
248        let mut hasher = blake3::Hasher::new();
249        hasher.update(ENTITY_DOMAIN_SEP);
250        hasher.update(&[org_type as u8]);
251        hasher.update(b":v1:");
252        hasher.update(name_commitment);
253        hasher.update(nonce);
254        *hasher.finalize().as_bytes()
255    }
256
257    /// Check if controller can act
258    pub fn can_controller_act(&self, controller: &Address) -> bool {
259        self.status.is_active() && self.controllers.contains(controller)
260    }
261}
262
263// =============================================================================
264// SRC-832: Governance Action Standard
265// =============================================================================
266
267/// Governance action types
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
269#[repr(u16)]
270pub enum GovernanceActionType {
271    // Board actions (100-199)
272    /// Board resolution approved
273    BoardResolutionApproved = 100,
274    /// Board meeting minutes recorded
275    BoardMeetingMinutes = 101,
276    /// Board member appointed
277    BoardMemberAppointed = 102,
278    /// Board member removed
279    BoardMemberRemoved = 103,
280
281    // Shareholder actions (200-299)
282    /// Shareholder vote approved
283    ShareholderVoteApproved = 200,
284    /// Annual meeting held
285    AnnualMeetingHeld = 201,
286    /// Special meeting held
287    SpecialMeetingHeld = 202,
288    /// Written consent obtained
289    WrittenConsentObtained = 203,
290
291    // Officer actions (300-399)
292    /// Officer appointed
293    OfficerAppointment = 300,
294    /// Officer removed
295    OfficerRemoval = 301,
296    /// Officer role changed
297    OfficerRoleChanged = 302,
298
299    // Authority actions (400-499)
300    /// Signing authority granted
301    SigningAuthorityGrant = 400,
302    /// Signing authority revoked
303    SigningAuthorityRevoke = 401,
304    /// Authority scope changed
305    AuthorityScopeChanged = 402,
306
307    // Corporate structure (500-599)
308    /// Bylaws amended
309    BylawsAmended = 500,
310    /// Articles amended
311    ArticlesAmended = 501,
312    /// Registered agent changed
313    RegisteredAgentChanged = 502,
314}
315
316impl GovernanceActionType {
317    pub fn from_u16(v: u16) -> Option<Self> {
318        match v {
319            100 => Some(GovernanceActionType::BoardResolutionApproved),
320            101 => Some(GovernanceActionType::BoardMeetingMinutes),
321            102 => Some(GovernanceActionType::BoardMemberAppointed),
322            103 => Some(GovernanceActionType::BoardMemberRemoved),
323            200 => Some(GovernanceActionType::ShareholderVoteApproved),
324            201 => Some(GovernanceActionType::AnnualMeetingHeld),
325            202 => Some(GovernanceActionType::SpecialMeetingHeld),
326            203 => Some(GovernanceActionType::WrittenConsentObtained),
327            300 => Some(GovernanceActionType::OfficerAppointment),
328            301 => Some(GovernanceActionType::OfficerRemoval),
329            302 => Some(GovernanceActionType::OfficerRoleChanged),
330            400 => Some(GovernanceActionType::SigningAuthorityGrant),
331            401 => Some(GovernanceActionType::SigningAuthorityRevoke),
332            402 => Some(GovernanceActionType::AuthorityScopeChanged),
333            500 => Some(GovernanceActionType::BylawsAmended),
334            501 => Some(GovernanceActionType::ArticlesAmended),
335            502 => Some(GovernanceActionType::RegisteredAgentChanged),
336            _ => None,
337        }
338    }
339
340    pub fn name(&self) -> &'static str {
341        match self {
342            GovernanceActionType::BoardResolutionApproved => "Board Resolution Approved",
343            GovernanceActionType::BoardMeetingMinutes => "Board Meeting Minutes",
344            GovernanceActionType::BoardMemberAppointed => "Board Member Appointed",
345            GovernanceActionType::BoardMemberRemoved => "Board Member Removed",
346            GovernanceActionType::ShareholderVoteApproved => "Shareholder Vote Approved",
347            GovernanceActionType::AnnualMeetingHeld => "Annual Meeting Held",
348            GovernanceActionType::SpecialMeetingHeld => "Special Meeting Held",
349            GovernanceActionType::WrittenConsentObtained => "Written Consent Obtained",
350            GovernanceActionType::OfficerAppointment => "Officer Appointment",
351            GovernanceActionType::OfficerRemoval => "Officer Removal",
352            GovernanceActionType::OfficerRoleChanged => "Officer Role Changed",
353            GovernanceActionType::SigningAuthorityGrant => "Signing Authority Grant",
354            GovernanceActionType::SigningAuthorityRevoke => "Signing Authority Revoke",
355            GovernanceActionType::AuthorityScopeChanged => "Authority Scope Changed",
356            GovernanceActionType::BylawsAmended => "Bylaws Amended",
357            GovernanceActionType::ArticlesAmended => "Articles Amended",
358            GovernanceActionType::RegisteredAgentChanged => "Registered Agent Changed",
359        }
360    }
361}
362
363/// Attachment content types
364#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
365#[repr(u8)]
366pub enum AttachmentContentType {
367    Resolution = 0,
368    Minutes = 1,
369    Agreement = 2,
370    Certificate = 3,
371    Other = 255,
372}
373
374/// Governance attachment reference
375#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
376pub struct GovernanceAttachment {
377    /// Attachment hash (BLAKE3)
378    pub hash: [u8; 32],
379    /// Size in bytes
380    pub size: u64,
381    /// Storage hint
382    pub hint_uri: Option<String>,
383    /// Content type
384    pub content_type: AttachmentContentType,
385}
386
387/// Governance action status
388#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
389#[repr(u8)]
390pub enum GovernanceActionStatus {
391    /// Pending approvals
392    Pending = 0,
393    /// Approved and effective
394    Approved = 1,
395    /// Executed
396    Executed = 2,
397    /// Expired
398    Expired = 3,
399    /// Revoked/Cancelled
400    Revoked = 4,
401}
402
403impl GovernanceActionStatus {
404    pub fn from_u8(v: u8) -> Option<Self> {
405        match v {
406            0 => Some(GovernanceActionStatus::Pending),
407            1 => Some(GovernanceActionStatus::Approved),
408            2 => Some(GovernanceActionStatus::Executed),
409            3 => Some(GovernanceActionStatus::Expired),
410            4 => Some(GovernanceActionStatus::Revoked),
411            _ => None,
412        }
413    }
414
415    pub fn is_effective(&self) -> bool {
416        matches!(self, GovernanceActionStatus::Approved | GovernanceActionStatus::Executed)
417    }
418}
419
420/// Governance action record (SRC-832)
421#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
422pub struct GovernanceAction {
423    /// Unique action ID
424    pub action_id: ActionId,
425    /// Organization subject (SRC-801)
426    pub org_subject: SubjectId,
427    /// Action type
428    pub action_type: GovernanceActionType,
429    /// Policy ID that authorized this action (SRC-803)
430    pub policy_id: PolicyId,
431    /// Action commitment (BLAKE3 hash of resolution/minutes/terms)
432    pub action_commitment: [u8; 32],
433    /// Effective timestamp
434    pub effective_at: Timestamp,
435    /// Expiry timestamp (0 = no expiry)
436    pub expires_at: Timestamp,
437    /// Optional attachments reference
438    pub attachments: Option<GovernanceAttachment>,
439    /// Approvers (addresses that signed/approved)
440    pub approvers: Vec<Address>,
441    /// Required threshold
442    pub required_threshold: u8,
443    /// Action status
444    pub status: GovernanceActionStatus,
445    /// Created timestamp
446    pub created_at: Timestamp,
447    /// Block height when recorded
448    pub recorded_at_height: BlockHeight,
449}
450
451impl GovernanceAction {
452    /// Generate action ID
453    pub fn generate_action_id(
454        org_subject: &SubjectId,
455        action_type: GovernanceActionType,
456        action_commitment: &[u8; 32],
457        nonce: &[u8; 32],
458    ) -> ActionId {
459        let mut hasher = blake3::Hasher::new();
460        hasher.update(GOVERNANCE_ACTION_DOMAIN_SEP);
461        hasher.update(&(action_type as u16).to_be_bytes());
462        hasher.update(b":v1:");
463        hasher.update(org_subject);
464        hasher.update(action_commitment);
465        hasher.update(nonce);
466        *hasher.finalize().as_bytes()
467    }
468
469    /// Check if threshold is met
470    pub fn is_threshold_met(&self) -> bool {
471        self.approvers.len() >= self.required_threshold as usize
472    }
473
474    /// Check if action is expired
475    pub fn is_expired(&self, current_time: Timestamp) -> bool {
476        self.expires_at > 0 && current_time > self.expires_at
477    }
478}
479
480// =============================================================================
481// SRC-833: Equity Token Standard
482// =============================================================================
483
484/// Share class type
485#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
486#[repr(u8)]
487pub enum ShareClassType {
488    /// Common shares
489    Common = 0,
490    /// Preferred shares
491    Preferred = 1,
492}
493
494impl ShareClassType {
495    pub fn from_u8(v: u8) -> Option<Self> {
496        match v {
497            0 => Some(ShareClassType::Common),
498            1 => Some(ShareClassType::Preferred),
499            _ => None,
500        }
501    }
502
503    pub fn name(&self) -> &'static str {
504        match self {
505            ShareClassType::Common => "Common",
506            ShareClassType::Preferred => "Preferred",
507        }
508    }
509}
510
511/// Token status
512#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
513#[repr(u8)]
514pub enum TokenStatus {
515    Active = 0,
516    Paused = 1,
517    Retired = 2,
518}
519
520impl TokenStatus {
521    pub fn from_u8(v: u8) -> Option<Self> {
522        match v {
523            0 => Some(TokenStatus::Active),
524            1 => Some(TokenStatus::Paused),
525            2 => Some(TokenStatus::Retired),
526            _ => None,
527        }
528    }
529
530    pub fn is_transferable(&self) -> bool {
531        matches!(self, TokenStatus::Active)
532    }
533}
534
535/// Equity token (SRC-833)
536#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
537pub struct EquityToken {
538    /// Issuer subject (SRC-801 org)
539    pub issuer_subject: SubjectId,
540    /// Share class ID
541    pub class_id: ClassId,
542    /// Share class type
543    pub share_class_type: ShareClassType,
544    /// Class name
545    pub name: String,
546    /// Symbol
547    pub symbol: String,
548    /// Authorized shares cap
549    pub authorized_shares: u128,
550    /// Issued shares (currently outstanding)
551    pub issued_shares: u128,
552    /// Votes per share (0 = non-voting)
553    pub votes_per_share: u64,
554    /// Economic rights hash (required)
555    pub economic_rights_hash: [u8; 32],
556    /// Liquidation preference hash (optional)
557    pub liquidation_preference_hash: Option<[u8; 32]>,
558    /// Dividend policy hash (optional)
559    pub dividend_policy_hash: Option<[u8; 32]>,
560    /// Conversion rules hash (optional)
561    pub conversion_rules_hash: Option<[u8; 32]>,
562    /// Controller address (mandatory)
563    pub controller: Address,
564    /// Par value (if applicable)
565    pub par_value: Option<u128>,
566    /// Created timestamp
567    pub created_at: Timestamp,
568    /// Updated timestamp
569    pub updated_at: Timestamp,
570    /// Status
571    pub status: TokenStatus,
572}
573
574impl EquityToken {
575    /// Generate class ID
576    pub fn generate_class_id(
577        issuer_subject: &SubjectId,
578        name: &str,
579        share_class_type: ShareClassType,
580    ) -> ClassId {
581        let mut hasher = blake3::Hasher::new();
582        hasher.update(EQUITY_TOKEN_DOMAIN_SEP);
583        hasher.update(issuer_subject);
584        hasher.update(b":");
585        hasher.update(name.as_bytes());
586        hasher.update(b":");
587        hasher.update(&[share_class_type as u8]);
588        *hasher.finalize().as_bytes()
589    }
590
591    /// Check if can mint more shares
592    pub fn can_mint(&self, amount: u128) -> bool {
593        self.status.is_transferable()
594            && self.issued_shares.saturating_add(amount) <= self.authorized_shares
595    }
596
597    /// Get remaining authorized shares
598    pub fn remaining_authorized(&self) -> u128 {
599        self.authorized_shares.saturating_sub(self.issued_shares)
600    }
601}
602
603// =============================================================================
604// SRC-834: Equity Controller Standard
605// =============================================================================
606
607/// Transfer type
608#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
609#[repr(u8)]
610pub enum TransferType {
611    /// Regular transfer
612    Regular = 0,
613    /// Transfer via corporate action
614    CorporateAction = 1,
615    /// Transfer via conversion
616    Conversion = 2,
617    /// Transfer via redemption
618    Redemption = 3,
619}
620
621/// Transfer context for controller hooks
622#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
623pub struct TransferContext {
624    /// Transaction initiator
625    pub initiator: Address,
626    /// Optional governance action reference
627    pub governance_action: Option<ActionId>,
628    /// Transfer type
629    pub transfer_type: TransferType,
630    /// Additional data
631    pub data: Vec<u8>,
632}
633
634/// Issuance type
635#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
636#[repr(u8)]
637pub enum IssuanceType {
638    /// Initial issuance
639    Initial = 0,
640    /// Follow-on offering
641    FollowOn = 1,
642    /// Stock option exercise
643    OptionExercise = 2,
644    /// Warrant exercise
645    WarrantExercise = 3,
646    /// Conversion
647    Conversion = 4,
648    /// Stock split
649    StockSplit = 5,
650    /// Stock dividend
651    StockDividend = 6,
652}
653
654/// Issuance reference for minting
655#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
656pub struct IssuanceRef {
657    /// Governance action authorizing issuance
658    pub governance_action_id: ActionId,
659    /// Issuance type
660    pub issuance_type: IssuanceType,
661    /// Price per share (if applicable)
662    pub price_per_share: Option<u128>,
663    /// Round identifier
664    pub round_id: Option<String>,
665}
666
667/// Burn reason
668#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
669#[repr(u8)]
670pub enum BurnReason {
671    /// Redemption
672    Redemption = 0,
673    /// Buyback
674    Buyback = 1,
675    /// Cancellation
676    Cancellation = 2,
677    /// Reverse split
678    ReverseSplit = 3,
679    /// Conversion
680    Conversion = 4,
681}
682
683/// Controller error codes
684#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
685#[repr(u16)]
686pub enum ControllerErrorCode {
687    // Transfer errors (1000-1099)
688    SenderNotWhitelisted = 1000,
689    RecipientNotWhitelisted = 1001,
690    SenderInLockup = 1002,
691    TradingWindowClosed = 1003,
692    TransferAmountExceedsLimit = 1004,
693    InsufficientBalance = 1005,
694
695    // Mint errors (1100-1199)
696    ExceedsAuthorizedCap = 1100,
697    UnauthorizedMinter = 1101,
698    InvalidIssuanceRef = 1102,
699
700    // Burn errors (1200-1299)
701    UnauthorizedBurner = 1200,
702    InvalidBurnReason = 1201,
703
704    // Corporate action errors (1300-1399)
705    InvalidCorporateAction = 1300,
706    InsufficientApprovals = 1301,
707    ActionNotAuthorized = 1302,
708
709    // General errors (9000-9999)
710    PolicyCheckFailed = 9000,
711    ControllerPaused = 9001,
712    Unknown = 9999,
713}
714
715/// Lockup information
716#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
717pub struct LockupInfo {
718    /// Amount locked
719    pub amount: u128,
720    /// Unlock timestamp
721    pub unlock_at: Timestamp,
722    /// Vesting schedule (if applicable)
723    pub vesting: Option<VestingSchedule>,
724}
725
726/// Vesting schedule
727#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
728pub struct VestingSchedule {
729    /// Total amount
730    pub total_amount: u128,
731    /// Already vested
732    pub vested_amount: u128,
733    /// Vesting start
734    pub start_at: Timestamp,
735    /// Cliff duration (seconds)
736    pub cliff_duration: u64,
737    /// Total duration (seconds)
738    pub total_duration: u64,
739    /// Vesting interval (seconds)
740    pub interval: u64,
741}
742
743impl VestingSchedule {
744    /// Calculate vested amount at a given time
745    pub fn vested_at(&self, current_time: Timestamp) -> u128 {
746        if current_time < self.start_at {
747            return 0;
748        }
749
750        let elapsed = current_time.saturating_sub(self.start_at);
751
752        // Check cliff
753        if elapsed < self.cliff_duration {
754            return 0;
755        }
756
757        // Check if fully vested
758        if elapsed >= self.total_duration {
759            return self.total_amount;
760        }
761
762        // Calculate linear vesting
763        let vesting_elapsed = elapsed.saturating_sub(self.cliff_duration);
764        let vesting_duration = self.total_duration.saturating_sub(self.cliff_duration);
765
766        if vesting_duration == 0 {
767            return self.total_amount;
768        }
769
770        // Calculate based on intervals
771        let intervals_passed = vesting_elapsed / self.interval;
772        let total_intervals = vesting_duration / self.interval;
773
774        if total_intervals == 0 {
775            return self.total_amount;
776        }
777
778        (self.total_amount * intervals_passed as u128) / total_intervals as u128
779    }
780}
781
782/// Trading window
783#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
784pub struct TradingWindow {
785    /// Window start (day of month, 1-31)
786    pub start_day: u8,
787    /// Window end (day of month, 1-31)
788    pub end_day: u8,
789    /// Allowed months (bitmask, bit 0 = Jan)
790    pub months: u16,
791}
792
793impl TradingWindow {
794    /// Check if trading is allowed at given day of month and month
795    pub fn is_open(&self, day: u8, month: u8) -> bool {
796        // Check month (0-indexed in bitmask)
797        if month < 1 || month > 12 {
798            return false;
799        }
800        let month_allowed = (self.months & (1 << (month - 1))) != 0;
801        if !month_allowed {
802            return false;
803        }
804
805        // Check day range
806        if self.start_day <= self.end_day {
807            day >= self.start_day && day <= self.end_day
808        } else {
809            // Wraps around month boundary
810            day >= self.start_day || day <= self.end_day
811        }
812    }
813}
814
815/// Equity controller configuration
816#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
817pub struct EquityControllerConfig {
818    /// Controller address
819    pub address: Address,
820    /// Whitelist enabled
821    pub whitelist_enabled: bool,
822    /// Trading windows
823    pub trading_windows: Vec<TradingWindow>,
824    /// Transfer limit per transaction (0 = no limit)
825    pub transfer_limit: u128,
826    /// Policy ID for governance
827    pub governance_policy_id: PolicyId,
828    /// Is paused
829    pub paused: bool,
830}
831
832// =============================================================================
833// SRC-835: Corporate Actions Interface
834// =============================================================================
835
836/// Corporate action types
837#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
838#[repr(u8)]
839pub enum CorporateActionType {
840    /// Stock split
841    StockSplit = 0,
842    /// Reverse split
843    ReverseSplit = 1,
844    /// Cash dividend declaration
845    DividendDeclare = 2,
846    /// Cash dividend distribution
847    DividendDistribute = 3,
848    /// Stock dividend
849    StockDividend = 4,
850    /// Buyback/redemption
851    Buyback = 5,
852    /// Conversion
853    Conversion = 6,
854    /// Record date snapshot
855    RecordDateSnapshot = 7,
856    /// Rights offering
857    RightsOffering = 8,
858}
859
860impl CorporateActionType {
861    pub fn from_u8(v: u8) -> Option<Self> {
862        match v {
863            0 => Some(CorporateActionType::StockSplit),
864            1 => Some(CorporateActionType::ReverseSplit),
865            2 => Some(CorporateActionType::DividendDeclare),
866            3 => Some(CorporateActionType::DividendDistribute),
867            4 => Some(CorporateActionType::StockDividend),
868            5 => Some(CorporateActionType::Buyback),
869            6 => Some(CorporateActionType::Conversion),
870            7 => Some(CorporateActionType::RecordDateSnapshot),
871            8 => Some(CorporateActionType::RightsOffering),
872            _ => None,
873        }
874    }
875
876    pub fn name(&self) -> &'static str {
877        match self {
878            CorporateActionType::StockSplit => "Stock Split",
879            CorporateActionType::ReverseSplit => "Reverse Split",
880            CorporateActionType::DividendDeclare => "Dividend Declaration",
881            CorporateActionType::DividendDistribute => "Dividend Distribution",
882            CorporateActionType::StockDividend => "Stock Dividend",
883            CorporateActionType::Buyback => "Buyback",
884            CorporateActionType::Conversion => "Conversion",
885            CorporateActionType::RecordDateSnapshot => "Record Date Snapshot",
886            CorporateActionType::RightsOffering => "Rights Offering",
887        }
888    }
889}
890
891/// Rounding mode for splits
892#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
893#[repr(u8)]
894pub enum RoundingMode {
895    /// Round down (truncate)
896    Down = 0,
897    /// Round up
898    Up = 1,
899    /// Round to nearest
900    Nearest = 2,
901}
902
903/// Stock split parameters
904#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
905pub struct StockSplitParams {
906    /// Split ratio numerator
907    pub ratio_numerator: u64,
908    /// Split ratio denominator
909    pub ratio_denominator: u64,
910}
911
912impl StockSplitParams {
913    /// Apply split to a balance
914    pub fn apply(&self, balance: u128) -> u128 {
915        (balance * self.ratio_numerator as u128) / self.ratio_denominator as u128
916    }
917}
918
919/// Reverse split parameters
920#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
921pub struct ReverseSplitParams {
922    /// Ratio numerator
923    pub ratio_numerator: u64,
924    /// Ratio denominator
925    pub ratio_denominator: u64,
926    /// Rounding mode
927    pub rounding: RoundingMode,
928    /// Cash out fractional shares
929    pub cash_out_fractional: bool,
930    /// Price per fractional share (if cashing out)
931    pub fractional_price: Option<u128>,
932}
933
934impl ReverseSplitParams {
935    /// Apply reverse split to a balance
936    pub fn apply(&self, balance: u128) -> (u128, u128) {
937        let numerator = self.ratio_numerator as u128;
938        let denominator = self.ratio_denominator as u128;
939
940        let new_balance = (balance * numerator) / denominator;
941        let remainder = balance - (new_balance * denominator) / numerator;
942
943        let fractional = match self.rounding {
944            RoundingMode::Down => 0,
945            RoundingMode::Up => {
946                if remainder > 0 {
947                    1
948                } else {
949                    0
950                }
951            }
952            RoundingMode::Nearest => {
953                if remainder * 2 >= denominator / numerator {
954                    1
955                } else {
956                    0
957                }
958            }
959        };
960
961        (new_balance + fractional, remainder)
962    }
963}
964
965/// Dividend currency
966#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
967pub enum DividendCurrency {
968    /// Native chain token
969    Native,
970    /// SRC-20 token
971    Token(Address),
972}
973
974/// Dividend declaration parameters
975#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
976pub struct DividendDeclareParams {
977    /// Dividend per share (in smallest currency unit)
978    pub amount_per_share: u128,
979    /// Currency
980    pub currency: DividendCurrency,
981    /// Record date
982    pub record_date: Timestamp,
983    /// Payment date
984    pub payment_date: Timestamp,
985}
986
987/// Distribution method
988#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
989#[repr(u8)]
990pub enum DistributionMethod {
991    /// Pro-rata by snapshot
992    ProRataSnapshot = 0,
993    /// Pro-rata by current balance
994    ProRataCurrent = 1,
995}
996
997/// Dividend distribution parameters
998#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
999pub struct DividendDistributeParams {
1000    /// Declaration action ID
1001    pub declaration_id: ActionId,
1002    /// Snapshot ID (from record date)
1003    pub snapshot_id: SnapshotId,
1004    /// Distribution method
1005    pub method: DistributionMethod,
1006}
1007
1008/// Conversion parameters
1009#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1010pub struct ConversionParams {
1011    /// Source class ID
1012    pub from_class_id: ClassId,
1013    /// Target class ID
1014    pub to_class_id: ClassId,
1015    /// Conversion ratio
1016    pub conversion_ratio: u64,
1017    /// Holder address (for single-holder conversion)
1018    pub holder: Option<Address>,
1019}
1020
1021/// Snapshot purpose
1022#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1023#[repr(u8)]
1024pub enum SnapshotPurpose {
1025    /// Dividend distribution
1026    Dividend = 0,
1027    /// Voting record
1028    Voting = 1,
1029    /// Rights offering
1030    Rights = 2,
1031    /// Other
1032    Other = 255,
1033}
1034
1035/// Record snapshot parameters
1036#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1037pub struct RecordSnapshotParams {
1038    /// Purpose of snapshot
1039    pub purpose: SnapshotPurpose,
1040    /// Reference (e.g., proposal ID)
1041    pub reference: Option<[u8; 32]>,
1042}
1043
1044/// Corporate action status
1045#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1046#[repr(u8)]
1047pub enum CorporateActionStatus {
1048    Proposed = 0,
1049    Approved = 1,
1050    Executing = 2,
1051    Completed = 3,
1052    Cancelled = 4,
1053    Failed = 5,
1054}
1055
1056/// Corporate action parameters (union type)
1057#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1058pub enum CorporateActionParams {
1059    StockSplit(StockSplitParams),
1060    ReverseSplit(ReverseSplitParams),
1061    DividendDeclare(DividendDeclareParams),
1062    DividendDistribute(DividendDistributeParams),
1063    Conversion(ConversionParams),
1064    RecordSnapshot(RecordSnapshotParams),
1065}
1066
1067/// Corporate action
1068#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1069pub struct CorporateAction {
1070    /// Unique action ID
1071    pub action_id: ActionId,
1072    /// Share class ID
1073    pub class_id: ClassId,
1074    /// Action type
1075    pub action_type: CorporateActionType,
1076    /// Action parameters
1077    pub params: CorporateActionParams,
1078    /// Record date (for snapshots)
1079    pub record_date: Option<Timestamp>,
1080    /// Execution date
1081    pub execution_date: Timestamp,
1082    /// Governance action authorizing this
1083    pub governance_action_id: ActionId,
1084    /// Status
1085    pub status: CorporateActionStatus,
1086    /// Created timestamp
1087    pub created_at: Timestamp,
1088    /// Executed timestamp
1089    pub executed_at: Option<Timestamp>,
1090}
1091
1092impl CorporateAction {
1093    /// Generate corporate action ID
1094    pub fn generate_action_id(
1095        class_id: &ClassId,
1096        action_type: CorporateActionType,
1097        governance_action_id: &ActionId,
1098    ) -> ActionId {
1099        let mut hasher = blake3::Hasher::new();
1100        hasher.update(CORPORATE_ACTION_DOMAIN_SEP);
1101        hasher.update(&[action_type as u8]);
1102        hasher.update(b":v1:");
1103        hasher.update(class_id);
1104        hasher.update(governance_action_id);
1105        *hasher.finalize().as_bytes()
1106    }
1107}
1108
1109/// Ownership snapshot
1110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1111pub struct OwnershipSnapshot {
1112    /// Snapshot ID
1113    pub snapshot_id: SnapshotId,
1114    /// Class ID
1115    pub class_id: ClassId,
1116    /// Snapshot timestamp
1117    pub timestamp: Timestamp,
1118    /// Block height
1119    pub block_height: BlockHeight,
1120    /// Total supply at snapshot
1121    pub total_supply: u128,
1122    /// Holder count
1123    pub holder_count: u64,
1124    /// Merkle root of balances
1125    pub balances_root: [u8; 32],
1126    /// Purpose
1127    pub purpose: SnapshotPurpose,
1128    /// Reference
1129    pub reference: Option<[u8; 32]>,
1130}
1131
1132impl OwnershipSnapshot {
1133    /// Generate snapshot ID
1134    pub fn generate_snapshot_id(
1135        class_id: &ClassId,
1136        purpose: SnapshotPurpose,
1137        timestamp: Timestamp,
1138    ) -> SnapshotId {
1139        let mut hasher = blake3::Hasher::new();
1140        hasher.update(SNAPSHOT_DOMAIN_SEP);
1141        hasher.update(&[purpose as u8]);
1142        hasher.update(b":v1:");
1143        hasher.update(class_id);
1144        hasher.update(&timestamp.to_be_bytes());
1145        *hasher.finalize().as_bytes()
1146    }
1147}
1148
1149// =============================================================================
1150// SRC-836: Ownership Proof Profiles
1151// =============================================================================
1152
1153/// Ownership proof profile identifiers
1154pub mod proof_profiles {
1155    pub const EQUITY_PROVE_MEMBERSHIP: &str = "equity.prove_membership.v1";
1156    pub const EQUITY_PROVE_OWNERSHIP_THRESHOLD: &str = "equity.prove_ownership_threshold.v1";
1157    pub const EQUITY_PROVE_VOTING_POWER: &str = "equity.prove_voting_power.v1";
1158}
1159
1160/// Public inputs for membership proof
1161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1162pub struct MembershipPublicInputs {
1163    /// Organization subject ID
1164    pub org_subject: SubjectId,
1165    /// Share class ID
1166    pub class_id: ClassId,
1167    /// Membership commitment
1168    pub membership_commitment: [u8; 32],
1169    /// Proof timestamp
1170    pub proof_timestamp: Timestamp,
1171}
1172
1173/// Public inputs for ownership threshold proof
1174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1175pub struct OwnershipThresholdPublicInputs {
1176    /// Organization subject ID
1177    pub org_subject: SubjectId,
1178    /// Share class ID
1179    pub class_id: ClassId,
1180    /// Minimum shares threshold
1181    pub threshold: u128,
1182    /// Ownership commitment
1183    pub ownership_commitment: [u8; 32],
1184    /// Proof timestamp
1185    pub proof_timestamp: Timestamp,
1186}
1187
1188/// Public inputs for voting power proof
1189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1190pub struct VotingPowerPublicInputs {
1191    /// Organization subject ID
1192    pub org_subject: SubjectId,
1193    /// Voting power threshold
1194    pub threshold: u128,
1195    /// Reference to proposal or record date
1196    pub reference: Option<[u8; 32]>,
1197    /// Voting power commitment
1198    pub voting_commitment: [u8; 32],
1199    /// Proof timestamp
1200    pub proof_timestamp: Timestamp,
1201}
1202
1203/// Ownership proof type
1204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1205#[repr(u8)]
1206pub enum OwnershipProofType {
1207    /// Mock proof (for testing)
1208    Mock = 0,
1209    /// Groth16 ZK proof
1210    Groth16 = 1,
1211    /// PLONK ZK proof
1212    Plonk = 2,
1213    /// Signature-based attestation
1214    Signature = 3,
1215}
1216
1217/// Ownership proof envelope
1218#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1219pub struct OwnershipProofEnvelope {
1220    /// Proof ID
1221    pub proof_id: ProofId,
1222    /// Profile ID
1223    pub profile_id: String,
1224    /// Policy ID(s) this proof satisfies
1225    pub policy_ids: Vec<PolicyId>,
1226    /// Public inputs (serialized JSON)
1227    pub public_inputs: Vec<u8>,
1228    /// Proof data
1229    pub proof_data: Vec<u8>,
1230    /// Proof type
1231    pub proof_type: OwnershipProofType,
1232    /// Subject nullifier
1233    pub subject_nullifier: [u8; 32],
1234    /// Generated timestamp
1235    pub generated_at: Timestamp,
1236    /// Expires at
1237    pub expires_at: Timestamp,
1238}
1239
1240impl OwnershipProofEnvelope {
1241    /// Generate proof ID
1242    pub fn generate_proof_id(
1243        profile_id: &str,
1244        public_inputs: &[u8],
1245        nonce: &[u8; 32],
1246    ) -> ProofId {
1247        let mut hasher = blake3::Hasher::new();
1248        hasher.update(OWNERSHIP_PROOF_DOMAIN_SEP);
1249        hasher.update(profile_id.as_bytes());
1250        hasher.update(b":");
1251        hasher.update(public_inputs);
1252        hasher.update(nonce);
1253        *hasher.finalize().as_bytes()
1254    }
1255}
1256
1257// =============================================================================
1258// Equity Domain Operations
1259// =============================================================================
1260
1261/// Equity domain operations
1262#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1263#[repr(u8)]
1264pub enum EquityOperation {
1265    // Entity operations (0-9)
1266    /// Create entity profile
1267    CreateEntity = 0,
1268    /// Update entity profile
1269    UpdateEntity = 1,
1270    /// Add entity controller
1271    AddController = 2,
1272    /// Remove entity controller
1273    RemoveController = 3,
1274
1275    // Governance operations (10-19)
1276    /// Propose governance action
1277    ProposeAction = 10,
1278    /// Approve governance action
1279    ApproveAction = 11,
1280    /// Execute governance action
1281    ExecuteAction = 12,
1282    /// Revoke governance action
1283    RevokeAction = 13,
1284
1285    // Token operations (20-29)
1286    /// Create equity token
1287    CreateToken = 20,
1288    /// Update token
1289    UpdateToken = 21,
1290    /// Pause token
1291    PauseToken = 22,
1292    /// Unpause token
1293    UnpauseToken = 23,
1294
1295    // Transfer operations (30-39)
1296    /// Transfer shares
1297    Transfer = 30,
1298    /// Approve spender
1299    Approve = 31,
1300    /// Transfer from
1301    TransferFrom = 32,
1302
1303    // Mint/Burn operations (40-49)
1304    /// Mint shares
1305    Mint = 40,
1306    /// Burn shares
1307    Burn = 41,
1308
1309    // Controller operations (50-59)
1310    /// Update controller
1311    UpdateController = 50,
1312    /// Add to whitelist
1313    AddToWhitelist = 51,
1314    /// Remove from whitelist
1315    RemoveFromWhitelist = 52,
1316    /// Set lockup
1317    SetLockup = 53,
1318
1319    // Corporate actions (60-69)
1320    /// Execute stock split
1321    ExecuteStockSplit = 60,
1322    /// Execute reverse split
1323    ExecuteReverseSplit = 61,
1324    /// Declare dividend
1325    DeclareDividend = 62,
1326    /// Distribute dividend
1327    DistributeDividend = 63,
1328    /// Execute conversion
1329    ExecuteConversion = 64,
1330    /// Take snapshot
1331    TakeSnapshot = 65,
1332
1333    // Proof operations (70-79)
1334    /// Verify ownership proof
1335    VerifyOwnershipProof = 70,
1336}
1337
1338impl EquityOperation {
1339    pub fn from_u8(v: u8) -> Option<Self> {
1340        match v {
1341            0 => Some(EquityOperation::CreateEntity),
1342            1 => Some(EquityOperation::UpdateEntity),
1343            2 => Some(EquityOperation::AddController),
1344            3 => Some(EquityOperation::RemoveController),
1345            10 => Some(EquityOperation::ProposeAction),
1346            11 => Some(EquityOperation::ApproveAction),
1347            12 => Some(EquityOperation::ExecuteAction),
1348            13 => Some(EquityOperation::RevokeAction),
1349            20 => Some(EquityOperation::CreateToken),
1350            21 => Some(EquityOperation::UpdateToken),
1351            22 => Some(EquityOperation::PauseToken),
1352            23 => Some(EquityOperation::UnpauseToken),
1353            30 => Some(EquityOperation::Transfer),
1354            31 => Some(EquityOperation::Approve),
1355            32 => Some(EquityOperation::TransferFrom),
1356            40 => Some(EquityOperation::Mint),
1357            41 => Some(EquityOperation::Burn),
1358            50 => Some(EquityOperation::UpdateController),
1359            51 => Some(EquityOperation::AddToWhitelist),
1360            52 => Some(EquityOperation::RemoveFromWhitelist),
1361            53 => Some(EquityOperation::SetLockup),
1362            60 => Some(EquityOperation::ExecuteStockSplit),
1363            61 => Some(EquityOperation::ExecuteReverseSplit),
1364            62 => Some(EquityOperation::DeclareDividend),
1365            63 => Some(EquityOperation::DistributeDividend),
1366            64 => Some(EquityOperation::ExecuteConversion),
1367            65 => Some(EquityOperation::TakeSnapshot),
1368            70 => Some(EquityOperation::VerifyOwnershipProof),
1369            _ => None,
1370        }
1371    }
1372}
1373
1374/// Equity domain transaction data
1375#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1376pub struct EquityTxData {
1377    /// Operation type
1378    pub operation: EquityOperation,
1379    /// Operation-specific data (serialized)
1380    pub data: Vec<u8>,
1381    /// Token recipient address - the owner of the minted token
1382    pub recipient: crate::Address,
1383}
1384
1385// =============================================================================
1386// Equity Domain Events
1387// =============================================================================
1388
1389/// Entity profile events
1390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1391pub enum EntityProfileEvent {
1392    EntityCreated {
1393        subject_id: SubjectId,
1394        org_type: OrgType,
1395        controller_model: ControllerModel,
1396    },
1397    EntityUpdated {
1398        subject_id: SubjectId,
1399    },
1400    ControllerChanged {
1401        subject_id: SubjectId,
1402        old_controllers: Vec<Address>,
1403        new_controllers: Vec<Address>,
1404    },
1405    EntityStatusChanged {
1406        subject_id: SubjectId,
1407        old_status: EntityStatus,
1408        new_status: EntityStatus,
1409    },
1410}
1411
1412/// Governance action events
1413#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1414pub enum GovernanceActionEvent {
1415    ActionProposed {
1416        action_id: ActionId,
1417        org_subject: SubjectId,
1418        action_type: GovernanceActionType,
1419        policy_id: PolicyId,
1420        proposer: Address,
1421    },
1422    ActionApproved {
1423        action_id: ActionId,
1424        approver: Address,
1425        approval_count: u32,
1426        threshold: u32,
1427    },
1428    ActionExecuted {
1429        action_id: ActionId,
1430        executor: Address,
1431        effective_at: Timestamp,
1432    },
1433    ActionRevoked {
1434        action_id: ActionId,
1435        revoker: Address,
1436    },
1437}
1438
1439/// Equity token events
1440#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1441pub enum EquityTokenEvent {
1442    TokenCreated {
1443        issuer_subject: SubjectId,
1444        class_id: ClassId,
1445        share_class_type: ShareClassType,
1446        authorized_shares: u128,
1447    },
1448    Transfer {
1449        class_id: ClassId,
1450        from: Address,
1451        to: Address,
1452        amount: u128,
1453    },
1454    Approval {
1455        class_id: ClassId,
1456        owner: Address,
1457        spender: Address,
1458        amount: u128,
1459    },
1460    ControllerUpdated {
1461        class_id: ClassId,
1462        old_controller: Address,
1463        new_controller: Address,
1464        governance_action_id: ActionId,
1465    },
1466    TokenPaused {
1467        class_id: ClassId,
1468    },
1469    TokenUnpaused {
1470        class_id: ClassId,
1471    },
1472}
1473
1474/// Corporate action events
1475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1476pub enum CorporateActionEvent {
1477    ActionProposed {
1478        action_id: ActionId,
1479        class_id: ClassId,
1480        action_type: CorporateActionType,
1481        governance_action_id: ActionId,
1482    },
1483    ActionExecuted {
1484        action_id: ActionId,
1485        action_type: CorporateActionType,
1486        affected_holders: u64,
1487        total_shares_affected: u128,
1488    },
1489    DividendDeclared {
1490        declaration_id: ActionId,
1491        class_id: ClassId,
1492        amount_per_share: u128,
1493        record_date: Timestamp,
1494        payment_date: Timestamp,
1495    },
1496    DividendDistributed {
1497        declaration_id: ActionId,
1498        total_distributed: u128,
1499        recipient_count: u64,
1500    },
1501    SnapshotTaken {
1502        snapshot_id: SnapshotId,
1503        class_id: ClassId,
1504        purpose: SnapshotPurpose,
1505        holder_count: u64,
1506    },
1507}
1508
1509/// Ownership proof events
1510#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1511pub enum OwnershipProofEvent {
1512    ProofVerified {
1513        proof_id: ProofId,
1514        profile_id: String,
1515        org_subject: SubjectId,
1516        verifier: Address,
1517        valid: bool,
1518    },
1519}
1520
1521/// Combined equity event
1522#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1523pub enum EquityEvent {
1524    Entity(EntityProfileEvent),
1525    Governance(GovernanceActionEvent),
1526    Token(EquityTokenEvent),
1527    CorporateAction(CorporateActionEvent),
1528    Proof(OwnershipProofEvent),
1529}
1530
1531// =============================================================================
1532// Tests
1533// =============================================================================
1534
1535#[cfg(test)]
1536mod tests {
1537    use super::*;
1538
1539    #[test]
1540    fn test_entity_subject_id_generation() {
1541        let name_commitment = [1u8; 32];
1542        let nonce = [2u8; 32];
1543
1544        let id1 = EntityProfile::generate_subject_id(
1545            OrgType::Corporation,
1546            &name_commitment,
1547            &nonce,
1548        );
1549        let id2 = EntityProfile::generate_subject_id(
1550            OrgType::Corporation,
1551            &name_commitment,
1552            &nonce,
1553        );
1554        let id3 = EntityProfile::generate_subject_id(
1555            OrgType::LLC,
1556            &name_commitment,
1557            &nonce,
1558        );
1559
1560        assert_eq!(id1, id2);
1561        assert_ne!(id1, id3);
1562    }
1563
1564    #[test]
1565    fn test_governance_action_threshold() {
1566        let action = GovernanceAction {
1567            action_id: [0u8; 32],
1568            org_subject: [0u8; 32],
1569            action_type: GovernanceActionType::BoardResolutionApproved,
1570            policy_id: [0u8; 32],
1571            action_commitment: [0u8; 32],
1572            effective_at: 0,
1573            expires_at: 0,
1574            attachments: None,
1575            approvers: vec![Address::ZERO, Address::ZERO],
1576            required_threshold: 3,
1577            status: GovernanceActionStatus::Pending,
1578            created_at: 0,
1579            recorded_at_height: 0,
1580        };
1581
1582        assert!(!action.is_threshold_met());
1583
1584        let mut action2 = action.clone();
1585        action2.approvers.push(Address::ZERO);
1586        assert!(action2.is_threshold_met());
1587    }
1588
1589    #[test]
1590    fn test_equity_token_mint_check() {
1591        let token = EquityToken {
1592            issuer_subject: [0u8; 32],
1593            class_id: [0u8; 32],
1594            share_class_type: ShareClassType::Common,
1595            name: "Common Shares".to_string(),
1596            symbol: "ACME".to_string(),
1597            authorized_shares: 1_000_000,
1598            issued_shares: 500_000,
1599            votes_per_share: 1,
1600            economic_rights_hash: [0u8; 32],
1601            liquidation_preference_hash: None,
1602            dividend_policy_hash: None,
1603            conversion_rules_hash: None,
1604            controller: Address::ZERO,
1605            par_value: None,
1606            created_at: 0,
1607            updated_at: 0,
1608            status: TokenStatus::Active,
1609        };
1610
1611        assert!(token.can_mint(100_000));
1612        assert!(token.can_mint(500_000));
1613        assert!(!token.can_mint(500_001));
1614        assert_eq!(token.remaining_authorized(), 500_000);
1615    }
1616
1617    #[test]
1618    fn test_vesting_schedule() {
1619        let schedule = VestingSchedule {
1620            total_amount: 1_000_000,
1621            vested_amount: 0,
1622            start_at: 1000,
1623            cliff_duration: 100,
1624            total_duration: 400,
1625            interval: 30,
1626        };
1627
1628        // Before start
1629        assert_eq!(schedule.vested_at(500), 0);
1630
1631        // During cliff
1632        assert_eq!(schedule.vested_at(1050), 0);
1633
1634        // Just after cliff (at exactly cliff time, no vesting yet)
1635        assert_eq!(schedule.vested_at(1100), 0);
1636        // After first interval
1637        assert!(schedule.vested_at(1130) > 0);
1638
1639        // At end
1640        assert_eq!(schedule.vested_at(1400), 1_000_000);
1641
1642        // After end
1643        assert_eq!(schedule.vested_at(2000), 1_000_000);
1644    }
1645
1646    #[test]
1647    fn test_trading_window() {
1648        let window = TradingWindow {
1649            start_day: 1,
1650            end_day: 10,
1651            months: 0b000000000101, // Jan and Mar
1652        };
1653
1654        // January, day 5
1655        assert!(window.is_open(5, 1));
1656
1657        // March, day 1
1658        assert!(window.is_open(1, 3));
1659
1660        // January, day 15
1661        assert!(!window.is_open(15, 1));
1662
1663        // February (not allowed)
1664        assert!(!window.is_open(5, 2));
1665    }
1666
1667    #[test]
1668    fn test_stock_split_params() {
1669        let split = StockSplitParams {
1670            ratio_numerator: 2,
1671            ratio_denominator: 1,
1672        };
1673
1674        assert_eq!(split.apply(100), 200);
1675        assert_eq!(split.apply(1), 2);
1676        assert_eq!(split.apply(0), 0);
1677    }
1678
1679    #[test]
1680    fn test_reverse_split_params() {
1681        let reverse = ReverseSplitParams {
1682            ratio_numerator: 1,
1683            ratio_denominator: 10,
1684            rounding: RoundingMode::Down,
1685            cash_out_fractional: false,
1686            fractional_price: None,
1687        };
1688
1689        let (new_balance, remainder) = reverse.apply(100);
1690        assert_eq!(new_balance, 10);
1691        assert_eq!(remainder, 0);
1692
1693        let (new_balance2, remainder2) = reverse.apply(95);
1694        assert_eq!(new_balance2, 9);
1695        assert!(remainder2 > 0);
1696    }
1697
1698    #[test]
1699    fn test_class_id_determinism() {
1700        let subject = [1u8; 32];
1701
1702        let id1 = EquityToken::generate_class_id(&subject, "Series A", ShareClassType::Preferred);
1703        let id2 = EquityToken::generate_class_id(&subject, "Series A", ShareClassType::Preferred);
1704        let id3 = EquityToken::generate_class_id(&subject, "Series B", ShareClassType::Preferred);
1705
1706        assert_eq!(id1, id2);
1707        assert_ne!(id1, id3);
1708    }
1709
1710    #[test]
1711    fn test_snapshot_id_determinism() {
1712        let class_id = [1u8; 32];
1713        let timestamp = 1704067200000u64;
1714
1715        let id1 = OwnershipSnapshot::generate_snapshot_id(&class_id, SnapshotPurpose::Dividend, timestamp);
1716        let id2 = OwnershipSnapshot::generate_snapshot_id(&class_id, SnapshotPurpose::Dividend, timestamp);
1717        let id3 = OwnershipSnapshot::generate_snapshot_id(&class_id, SnapshotPurpose::Voting, timestamp);
1718
1719        assert_eq!(id1, id2);
1720        assert_ne!(id1, id3);
1721    }
1722}