diff --git a/OpenCapTable-v34/daml.yaml b/OpenCapTable-v34/daml.yaml index 4654c331..2ae623af 100644 --- a/OpenCapTable-v34/daml.yaml +++ b/OpenCapTable-v34/daml.yaml @@ -1,7 +1,7 @@ sdk-version: 3.4.10 name: OpenCapTable-v34 source: daml -version: 0.0.11 +version: 0.0.12 build-options: - -Wno-crypto-text-is-alpha - --typecheck-upgrades=no diff --git a/Test/daml.yaml b/Test/daml.yaml index 6d66d15f..7b41ee5e 100644 --- a/Test/daml.yaml +++ b/Test/daml.yaml @@ -7,6 +7,6 @@ dependencies: - daml-stdlib - daml-script data-dependencies: - - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.11.dar + - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.12.dar build-options: - -Wno-template-interface-depends-on-daml-script diff --git a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml index 7e06eda7..7a462a03 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml @@ -12,12 +12,12 @@ import qualified DA.Time as DT import Daml.Script -- Create an equity compensation issuance to cancel (follows real workflow) -createEquityCompensationIssuance issuer capTableCid securityId quantity = +createEquityCompensationIssuance issuer capTableCid securityId quantity issuanceDate = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with creates = [CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_ISS_1" - date = DT.time (DA.date 2020 Jan 15) 0 0 0 + id = "TX_EC_ISS_" <> securityId + date = issuanceDate security_id = securityId custom_id = "GRANT-001" stakeholder_id = "SH-1" @@ -61,7 +61,7 @@ testEquityCompensationCancellation_CreateAndArchiveByIssuer = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table let securityId = "db5a26d6-3912-4400-87b0-bda74de361af" - result <- createEquityCompensationIssuance issuer capTableCid securityId 250000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 250000.0 (DT.time (DA.date 2020 Jan 15) 0 0 0) _ <- createEquityCompensationCancellation issuer result.updatedCapTableCid securityId 214200.0 pure () @@ -70,7 +70,7 @@ testEquityCompensationCancellation_CreateAndArchiveBuiltIn = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-EC-002" - result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 (DT.time (DA.date 2020 Jan 15) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with @@ -92,41 +92,18 @@ testEquityCompensationCancellation_PartialWithOptionals = script do let securityId = "SEC-EC-PARTIAL" let balanceSecurityId = "SEC-EC-PARTIAL-BAL" let cancellationDate = DT.time (DA.date 2024 Jun 30) 0 0 0 - result <- createEquityCompensationIssuance issuer capTableCid securityId 500000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 500000.0 (DT.time (DA.date 2020 Jan 15) 0 0 0) + balanceResult <- createEquityCompensationIssuance issuer result.updatedCapTableCid balanceSecurityId 350000.0 cancellationDate _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = - [ CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with - id = "TX_EC_CANCEL_PARTIAL" - date = cancellationDate - security_id = securityId - quantity = 150000.0 - reason_text = "Partial forfeiture due to early departure" - balance_security_id = Some balanceSecurityId - comments = ["Partial cancellation per separation agreement", "Remaining vested options: 350,000"] - , CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_CANCEL_PARTIAL_BALANCE" - date = cancellationDate - security_id = balanceSecurityId - custom_id = "GRANT-001-BALANCE" - stakeholder_id = "SH-1" - board_approval_date = None - stockholder_approval_date = None - consideration_text = None - security_law_exemptions = [] - stock_plan_id = None - stock_class_id = Some "SC_COMMON" - compensation_type = OcfCompensationTypeOptionNSO - quantity = 350000.0 - exercise_price = Some (OcfMonetary with amount = 0.10, currency = "USD") - base_price = None - early_exercisable = Some False - vesting_terms_id = None - vestings = [] - expiration_date = None - termination_exercise_windows = [] - comments = [] - ] + exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "TX_EC_CANCEL_PARTIAL" + date = cancellationDate + security_id = securityId + quantity = 150000.0 + reason_text = "Partial forfeiture due to early departure" + balance_security_id = Some balanceSecurityId + comments = ["Partial cancellation per separation agreement", "Remaining vested options: 350,000"]] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml index af7a5f13..19d9b5f5 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml @@ -49,13 +49,14 @@ testEquityCompensationTransfer_NSOToFamilyTrust = script do capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ESEC-NSO-001" result <- createEquityCompensationIssuance issuer capTableCid securityId 10000.0 (DT.time (DA.date 2022 Jan 15) 0 0 0) - balanceResult <- createEquityCompensationIssuance issuer result.updatedCapTableCid "ESEC-NSO-REMAIN" 5000.0 (DT.time (DA.date 2025 Jun 01) 0 0 0) + let transferDate = DT.time (DA.date 2025 Jun 01) 0 0 0 + balanceResult <- createEquityCompensationIssuance issuer result.updatedCapTableCid "ESEC-NSO-REMAIN" 5000.0 transferDate _ <- submit issuer do exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [equityCompensationSuccessorIssuance "EC-ISS-TRANSFERRED-001" "ESEC-NSO-TRANSFERRED-001" "SH-1" (DT.time (DA.date 2025 Jun 01) 0 0 0) 5000.0 + creates = [equityCompensationSuccessorIssuance "EC-ISS-TRANSFERRED-001" "ESEC-NSO-TRANSFERRED-001" "SH-1" transferDate 5000.0 , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with id = "TX_EQUITY_COMP_TRANSFER_1" - date = DT.time (DA.date 2025 Jun 01) 0 0 0 + date = transferDate quantity = 5000.0 security_id = securityId resulting_security_ids = ["ESEC-NSO-TRANSFERRED-001"] @@ -94,13 +95,14 @@ testEquityCompensationTransfer_WithApprovalComments = script do capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ESEC-NSO-APPROVAL" result <- createEquityCompensationIssuance issuer capTableCid securityId 15000.0 (DT.time (DA.date 2022 Jan 15) 0 0 0) - balanceResult <- createEquityCompensationIssuance issuer result.updatedCapTableCid "ESEC-NSO-REMAINING" 7000.0 (DT.time (DA.date 2025 Nov 01) 0 0 0) + let transferDate = DT.time (DA.date 2025 Nov 01) 0 0 0 + balanceResult <- createEquityCompensationIssuance issuer result.updatedCapTableCid "ESEC-NSO-REMAINING" 7000.0 transferDate _ <- submit issuer do exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [equityCompensationSuccessorIssuance "EC-ISS-TRUST" "ESEC-NSO-TRUST" "SH-1" (DT.time (DA.date 2025 Nov 01) 0 0 0) 8000.0 + creates = [equityCompensationSuccessorIssuance "EC-ISS-TRUST" "ESEC-NSO-TRUST" "SH-1" transferDate 8000.0 , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with id = "TX_EQUITY_COMP_TRANSFER_APPROVED" - date = DT.time (DA.date 2025 Nov 01) 0 0 0 + date = transferDate quantity = 8000.0 security_id = securityId resulting_security_ids = ["ESEC-NSO-TRUST"] diff --git a/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml index cd959b04..cb861140 100644 --- a/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml +++ b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml @@ -82,18 +82,18 @@ testFinalReferenceValidation_DeleteReferencedStockIssuanceFails = script do TestOcp{issuer, cap_table} <- setupTestOcp capTable <- addPrerequisites issuer cap_table capTable <- addStockIssuance issuer capTable "TX-STOCK-FINAL-REF" "SEC-FINAL-REF" "SH-1" "SC_COMMON" - let balanceIssuance = (defaultStockIssuance "TX-STOCK-TRANSFER-BALANCE" "SEC-TRANSFER-BALANCE" "SH-1" "SC_COMMON") with - date = defaultReferenceTestTime - quantity = 90.0 - let resultIssuance = (defaultStockIssuance "TX-STOCK-TRANSFER-RESULT" "SEC-TRANSFER-RESULT" "SH-1" "SC_COMMON") with + let resultIssuance = (defaultStockIssuance "TX-STOCK-TRANSFER-FINAL-RESULT" "SEC-TRANSFER-RESULT" "SH-1" "SC_COMMON") with date = defaultReferenceTestTime quantity = 10.0 + let balanceIssuance = (defaultStockIssuance "TX-STOCK-TRANSFER-FINAL-BALANCE" "SEC-TRANSFER-BALANCE" "SH-1" "SC_COMMON") with + date = defaultReferenceTestTime + quantity = 90.0 result <- submit issuer do exerciseCmd capTable CT.UpdateCapTable with creates = [ CT.OcfCreateStockTransfer (stockTransferData "TX-STOCK-TRANSFER-FINAL-REF" "SEC-FINAL-REF") - , CT.OcfCreateStockIssuance balanceIssuance , CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateStockIssuance balanceIssuance ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestHelpers.daml b/Test/daml/OpenCapTable/TestHelpers.daml index 0fe8d939..6beb5688 100644 --- a/Test/daml/OpenCapTable/TestHelpers.daml +++ b/Test/daml/OpenCapTable/TestHelpers.daml @@ -13,11 +13,13 @@ import Fairmint.OpenCapTable.OCF.VestingTerms (VestingTermsOcfData(..), OcfAlloc import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData(..)) import Fairmint.OpenCapTable.OCF.ConvertibleIssuance (ConvertibleIssuanceOcfData(..), OcfConvertibleConversionTrigger(..)) import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance (EquityCompensationIssuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationRelease (EquityCompensationReleaseOcfData(..)) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Stock (OcfStockClassType(..), OcfInitialSharesAuthorized(..), OcfRatio(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import Fairmint.OpenCapTable.Types.Conversion (OcfConvertibleType(..), OcfConversionTriggerType(..), OcfConvertibleConversionRight(..), OcfConvertibleConversionMechanism(..), OcfSAFEConversionMechanism(..)) import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -92,24 +94,32 @@ stockClassAuthorizedSharesAdjustment adjustmentId stockClassId authorizedShares stockholder_approval_date = None comments = [] -stockCancellation : Text -> Text -> Decimal -> StockCancellationOcfData -stockCancellation cancellationId securityId quantity = StockCancellationOcfData with +stockCancellationOn : Text -> Text -> Decimal -> Time -> StockCancellationOcfData +stockCancellationOn cancellationId securityId quantity date = StockCancellationOcfData with id = cancellationId - date = defaultTestDate + date = date security_id = securityId quantity = quantity balance_security_id = None reason_text = "Cancelled shares" comments = [] -stockClassSplit : Text -> Text -> Decimal -> Decimal -> StockClassSplitOcfData -stockClassSplit splitId stockClassId numerator denominator = StockClassSplitOcfData with +stockCancellation : Text -> Text -> Decimal -> StockCancellationOcfData +stockCancellation cancellationId securityId quantity = + stockCancellationOn cancellationId securityId quantity defaultTestDate + +stockClassSplitOn : Text -> Text -> Decimal -> Decimal -> Time -> StockClassSplitOcfData +stockClassSplitOn splitId stockClassId numerator denominator date = StockClassSplitOcfData with id = splitId - date = defaultTestDate + date = date stock_class_id = stockClassId split_ratio = OcfRatio with numerator = numerator, denominator = denominator comments = [] +stockClassSplit : Text -> Text -> Decimal -> Decimal -> StockClassSplitOcfData +stockClassSplit splitId stockClassId numerator denominator = + stockClassSplitOn splitId stockClassId numerator denominator defaultTestDate + stockPlanEquityCompensationIssuance : Text -> Text -> Text -> Text -> Text -> Decimal -> EquityCompensationIssuanceOcfData stockPlanEquityCompensationIssuance issuanceId securityId stakeholderId stockClassId stockPlanId quantity = EquityCompensationIssuanceOcfData with id = issuanceId @@ -134,6 +144,18 @@ stockPlanEquityCompensationIssuance issuanceId securityId stakeholderId stockCla quantity = quantity comments = [] +equityCompensationRelease : Text -> Text -> Time -> Time -> Decimal -> EquityCompensationReleaseOcfData +equityCompensationRelease releaseId securityId releaseDate settlementDate quantity = EquityCompensationReleaseOcfData with + id = releaseId + date = releaseDate + security_id = securityId + settlement_date = settlementDate + release_price = OcfMonetary with amount = 1.0, currency = "USD" + quantity = quantity + resulting_security_ids = [releaseId <> "_RESULT"] + consideration_text = None + comments = [] + -- | Returns valid StakeholderOcfData with sensible defaults. -- Provide ID and name; all other fields use reasonable test values. -- @@ -221,6 +243,32 @@ warrantSuccessorIssuance issuanceId securityId stakeholderId date quantity = vesting_terms_id = None warrant_expiration_date = None +createWarrantIssuance : Party -> ContractId CT.CapTable -> Text -> Decimal -> Time -> Script CT.UpdateCapTableResult +createWarrantIssuance issuer capTableCid securityId quantity issuanceDate = + submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with + id = "TX_WARRANT_ISS_" <> securityId + date = issuanceDate + security_id = securityId + custom_id = "W-1" + stakeholder_id = "SH-1" + board_approval_date = None + stockholder_approval_date = None + consideration_text = None + security_law_exemptions = [] + quantity = Some quantity + quantity_source = None + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + purchase_price = OcfMonetary with amount = 0.0, currency = "USD" + exercise_triggers = [ocfTriggerElectiveAtWill] + warrant_expiration_date = None + vesting_terms_id = None + vestings = [] + comments = []] + edits = [] + deletes = [] + convertibleSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData convertibleSuccessorIssuance issuanceId securityId stakeholderId date investmentAmount = CT.OcfCreateConvertibleIssuance ConvertibleIssuanceOcfData with diff --git a/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml b/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml new file mode 100644 index 00000000..623de5da --- /dev/null +++ b/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml @@ -0,0 +1,1059 @@ +module OpenCapTable.TestNonStockSecurityBalances where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.EquityCompensationCancellation (EquityCompensationCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationExercise (EquityCompensationExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance (EquityCompensationIssuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationRetraction (EquityCompensationRetractionOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationTransfer (EquityCompensationTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantCancellation (WarrantCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantExercise (WarrantExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantRetraction (WarrantRetractionOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantTransfer (WarrantTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.VestingTerms (OcfVestingConditionPortion(..), OcfVestingTrigger(..)) +import Fairmint.OpenCapTable.Types.Conversion (OcfAnyConversionRight(..), OcfConversionTrigger(..), OcfConversionTriggerType(..), OcfWarrantConversionMechanism(..), OcfWarrantConversionRight(..)) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import qualified DA.Date as DA +import DA.Date (Month(..)) +import qualified DA.Time as DT +import Daml.Script + +jan02 : Time +jan02 = DT.time (DA.date 2024 Jan 02) 0 0 0 + +jan03 : Time +jan03 = DT.time (DA.date 2024 Jan 03) 0 0 0 + +jan04 : Time +jan04 = DT.time (DA.date 2024 Jan 04) 0 0 0 + +preIssuanceDate : Time +preIssuanceDate = DT.time (DA.date 2023 Dec 31) 0 0 0 + +warrantFixedExerciseTrigger : Text -> Text -> Decimal -> OcfConversionTrigger +warrantFixedExerciseTrigger triggerId stockClassId convertsToQuantity = + OcfConversionTrigger with + conversion_right = OcfRightWarrant (OcfWarrantConversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with converts_to_quantity = convertsToQuantity + converts_to_future_round = None + converts_to_stock_class_id = Some stockClassId + type_ = "WARRANT_CONVERSION_RIGHT") + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = triggerId + type_ = OcfTriggerTypeTypeElectiveAtWill + +warrantIssuance : Text -> Text -> Text -> Decimal -> WarrantIssuanceOcfData +warrantIssuance issuanceId securityId stakeholderId quantity = WarrantIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "WR-" <> issuanceId + stakeholder_id = stakeholderId + purchase_price = OcfMonetary with amount = 0.0, currency = "USD" + quantity = Some quantity + quantity_source = Some OcfQuantityUnspecified + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + exercise_triggers = [] + security_law_exemptions = [] + vestings = [] + board_approval_date = None + consideration_text = None + stockholder_approval_date = None + vesting_terms_id = None + warrant_expiration_date = None + comments = [] + +warrantCancellation : Text -> Text -> Decimal -> Time -> WarrantCancellationOcfData +warrantCancellation cancellationId securityId quantity date = WarrantCancellationOcfData with + id = cancellationId + date = date + security_id = securityId + quantity = quantity + balance_security_id = None + reason_text = "Cancelled warrants" + comments = [] + +warrantTransfer : Text -> Text -> Decimal -> Time -> WarrantTransferOcfData +warrantTransfer transferId securityId quantity date = WarrantTransferOcfData with + id = transferId + date = date + security_id = securityId + quantity = quantity + resulting_security_ids = [transferId <> "_RESULT"] + balance_security_id = None + consideration_text = None + comments = [] + +warrantExercise : Text -> Text -> Optional Decimal -> Time -> WarrantExerciseOcfData +warrantExercise exerciseId securityId quantity date = WarrantExerciseOcfData with + id = exerciseId + date = date + security_id = securityId + trigger_id = "TEST_TRIGGER" + quantity = quantity + resulting_security_ids = [] + consideration_text = None + comments = [] + +warrantRetraction : Text -> Text -> Time -> WarrantRetractionOcfData +warrantRetraction retractionId securityId date = WarrantRetractionOcfData with + id = retractionId + date = date + security_id = securityId + reason_text = "Retracted warrant issuance" + comments = [] + +equityCompensationIssuance : Text -> Text -> Text -> Decimal -> EquityCompensationIssuanceOcfData +equityCompensationIssuance issuanceId securityId stakeholderId quantity = EquityCompensationIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "EC-" <> issuanceId + stakeholder_id = stakeholderId + compensation_type = OcfCompensationTypeOptionNSO + quantity = quantity + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + base_price = None + early_exercisable = None + stock_class_id = None + stock_plan_id = None + vesting_terms_id = None + expiration_date = None + termination_exercise_windows = [] + security_law_exemptions = [] + vestings = [] + board_approval_date = None + consideration_text = None + stockholder_approval_date = None + comments = [] + +equityCompensationCancellation : Text -> Text -> Decimal -> Time -> EquityCompensationCancellationOcfData +equityCompensationCancellation cancellationId securityId quantity date = EquityCompensationCancellationOcfData with + id = cancellationId + date = date + security_id = securityId + quantity = quantity + balance_security_id = None + reason_text = "Cancelled equity compensation" + comments = [] + +equityCompensationTransfer : Text -> Text -> Decimal -> Time -> EquityCompensationTransferOcfData +equityCompensationTransfer transferId securityId quantity date = EquityCompensationTransferOcfData with + id = transferId + date = date + security_id = securityId + quantity = quantity + resulting_security_ids = [transferId <> "_RESULT"] + balance_security_id = None + consideration_text = None + comments = [] + +equityCompensationExercise : Text -> Text -> Decimal -> Time -> EquityCompensationExerciseOcfData +equityCompensationExercise exerciseId securityId quantity date = EquityCompensationExerciseOcfData with + id = exerciseId + date = date + security_id = securityId + quantity = quantity + resulting_security_ids = [] + consideration_text = None + comments = [] + +equityCompensationRetraction : Text -> Text -> Time -> EquityCompensationRetractionOcfData +equityCompensationRetraction retractionId securityId date = EquityCompensationRetractionOcfData with + id = retractionId + date = date + security_id = securityId + reason_text = "Retracted equity compensation issuance" + comments = [] + +testWarrantSecurityBalance_CancellationCannotExceedIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_CANCEL" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_CANCEL" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_TOO_MUCH" securityId 101.0 jan02) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_CumulativeExerciseAndTransferCannotExceedIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_CUMULATIVE" + let transferResult = (warrantIssuance "TX_W_TRANSFER_50_RESULT_ISSUANCE" "TX_W_TRANSFER_50_RESULT" defaultStakeholderId 50.0) with date = jan03 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_CUMULATIVE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_EXERCISE_60" securityId (Some 60.0) jan02) + , CT.OcfCreateWarrantIssuance transferResult + , CT.OcfCreateWarrantTransfer (warrantTransfer "TX_W_TRANSFER_50" securityId 50.0 jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_ReductionAfterFullExerciseFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_FULL_EXERCISE" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_FULL_EXERCISE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_FULL_EXERCISE" securityId None jan02) + , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_AFTER_EXERCISE" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_RepeatedFullExerciseFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_REPEATED_FULL_EXERCISE" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_REPEATED_FULL_EXERCISE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_FULL_EXERCISE_FIRST" securityId None jan02) + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_FULL_EXERCISE_SECOND" securityId None jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_ReductionAfterRetractionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_RETRACTED" + let transferResult = (warrantIssuance "TX_W_TRANSFER_AFTER_RETRACT_RESULT_ISSUANCE" "TX_W_TRANSFER_AFTER_RETRACT_RESULT" defaultStakeholderId 1.0) with date = jan03 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_RETRACTED" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantRetraction (warrantRetraction "TX_W_RETRACT_ALL" securityId jan02) + , CT.OcfCreateWarrantIssuance transferResult + , CT.OcfCreateWarrantTransfer (warrantTransfer "TX_W_TRANSFER_AFTER_RETRACT" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_RetractionAfterFullExerciseFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_REPEAT_TERMINAL" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_REPEAT_TERMINAL" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_FULL_EXERCISE_REPEAT" securityId None jan02) + , CT.OcfCreateWarrantRetraction (warrantRetraction "TX_W_RETRACT_AFTER_FULL_EXERCISE" securityId jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_TransferWithBalanceSecurityRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_TRANSFER_BALANCE_SECURITY" + let resultIssuance = (warrantIssuance "TX_W_TRANSFER_BALANCE_RESULT" "TX_W_TRANSFER_BALANCE_SECURITY_RESULT" defaultStakeholderId 60.0) with date = jan02 + let balanceIssuance = (warrantIssuance "TX_W_TRANSFER_BALANCE_REMAINDER" "WSEC_BALANCE_TRANSFER" defaultStakeholderId 40.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_TRANSFER_BALANCE_SECURITY" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantIssuance resultIssuance + , CT.OcfCreateWarrantIssuance balanceIssuance + , CT.OcfCreateWarrantTransfer ((warrantTransfer "TX_W_TRANSFER_BALANCE_SECURITY" securityId 60.0 jan02) with balance_security_id = Some "WSEC_BALANCE_TRANSFER") + , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_AFTER_BALANCE_TRANSFER" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_CancellationWithBalanceSecurityRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_CANCELLATION_BALANCE_SECURITY" + let balanceIssuance = (warrantIssuance "TX_W_CANCEL_BALANCE_REMAINDER" "WSEC_BALANCE_CANCELLATION" defaultStakeholderId 40.0) with date = jan02 + let laterResult = (warrantIssuance "TX_W_TRANSFER_AFTER_CANCEL_RESULT" "TX_W_TRANSFER_AFTER_BALANCE_CANCELLATION_RESULT" defaultStakeholderId 1.0) with date = jan03 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_CANCELLATION_BALANCE_SECURITY" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantIssuance balanceIssuance + , CT.OcfCreateWarrantIssuance laterResult + , CT.OcfCreateWarrantCancellation ((warrantCancellation "TX_W_CANCEL_BALANCE_SECURITY" securityId 60.0 jan02) with balance_security_id = Some "WSEC_BALANCE_CANCELLATION") + , CT.OcfCreateWarrantTransfer (warrantTransfer "TX_W_TRANSFER_AFTER_BALANCE_CANCELLATION" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_BalanceSecurityRequiresPositiveRemainder = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_ZERO_REMAINDER" + let transferResult = (warrantIssuance "TX_W_BALANCE_TRANSFER_REMAINDER_RESULT_ISSUANCE" "TX_W_BALANCE_TRANSFER_REMAINDER_RESULT" defaultStakeholderId 60.0) with date = jan02 + let balanceResult = (warrantIssuance "TX_W_BALANCE_TRANSFER_REMAINDER_BALANCE_ISSUANCE" "WSEC_BALANCE_ZERO_REMAINDER" defaultStakeholderId 1.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_ZERO_REMAINDER" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_BEFORE_BALANCE_TRANSFER" securityId 40.0 jan02) + , CT.OcfCreateWarrantIssuance transferResult + , CT.OcfCreateWarrantIssuance balanceResult + , CT.OcfCreateWarrantTransfer ((warrantTransfer "TX_W_BALANCE_TRANSFER_REMAINDER" securityId 60.0 jan02) with balance_security_id = Some "WSEC_BALANCE_ZERO_REMAINDER") + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_ExactBalanceSuccessorSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_EXACT_SUCCESSOR" + let balanceSecurityId = "WSEC_BAL_EXACT_REMAINDER" + let balanceIssuance = (warrantIssuance "TX_W_BAL_EXACT_REMAINDER" balanceSecurityId defaultStakeholderId 40.0) with date = jan02 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_EXACT_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantIssuance balanceIssuance + , CT.OcfCreateWarrantCancellation ((warrantCancellation "TX_W_BAL_EXACT_CANCEL" securityId 60.0 jan02) with balance_security_id = Some balanceSecurityId) + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_WrongBalanceSuccessorQuantityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_WRONG_SUCCESSOR" + let balanceSecurityId = "WSEC_BAL_WRONG_REMAINDER" + let wrongBalance = (warrantIssuance "TX_W_BAL_WRONG_REMAINDER" balanceSecurityId defaultStakeholderId 39.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_WRONG_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantIssuance wrongBalance + , CT.OcfCreateWarrantCancellation ((warrantCancellation "TX_W_BAL_WRONG_CANCEL" securityId 60.0 jan02) with balance_security_id = Some balanceSecurityId) + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_MissingBalanceSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC_BAL_MISSING_SUCCESSOR" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_MISSING_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantCancellation ((warrantCancellation "TX_W_BAL_MISSING_CANCEL" securityId 60.0 jan02) with balance_security_id = Some "WSEC_BAL_MISSING_REMAINDER") + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_TransferResultsPreserveQuantityDateAndTerms = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let sourceSecurityId = "WSEC_TRANSFER_CONTINUITY_SOURCE" + let transferId = "TX_W_TRANSFER_CONTINUITY" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_TRANSFER_CONTINUITY_BALANCE" + let sourceTrigger = warrantFixedExerciseTrigger "TRANSFER_TRIGGER" stockClassId 25.0 + let sourceVesting = OcfVesting with date = jan04, amount = 100.0 + let sourceIssuance = (warrantIssuance "TX_W_TRANSFER_CONTINUITY_SOURCE" sourceSecurityId stakeholderId 100.0) with + exercise_triggers = [sourceTrigger] + vestings = [sourceVesting] + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + let validResult = (warrantIssuance "TX_W_TRANSFER_CONTINUITY_RESULT" resultSecurityId stakeholderId 40.0) with + date = jan02 + exercise_triggers = [warrantFixedExerciseTrigger "TRANSFER_TRIGGER" stockClassId 10.0] + vestings = [sourceVesting with amount = 40.0] + let validBalance = (warrantIssuance "TX_W_TRANSFER_CONTINUITY_BALANCE" balanceSecurityId stakeholderId 60.0) with + date = jan02 + exercise_triggers = [warrantFixedExerciseTrigger "TRANSFER_TRIGGER" stockClassId 15.0] + vestings = [sourceVesting with amount = 60.0] + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance (validResult with quantity = Some 41.0) + , CT.OcfCreateWarrantIssuance validBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance (validResult with vestings = [sourceVesting]) + , CT.OcfCreateWarrantIssuance validBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance (validResult with date = jan03) + , CT.OcfCreateWarrantIssuance validBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance (validResult with + exercise_triggers = [sourceTrigger]) + , CT.OcfCreateWarrantIssuance validBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance validResult + , CT.OcfCreateWarrantIssuance validBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_FixedQuantityVestingTermsCannotBeReusedByPartialSuccessors = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let sourceSecurityId = "WSEC_FIXED_VESTING_TERMS_SOURCE" + let transferId = "TX_W_FIXED_VESTING_TERMS_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_FIXED_VESTING_TERMS_BALANCE" + let fixedTermsId = "VT_W_FIXED_QUANTITY" + let resultTermsId = "VT_W_FIXED_QUANTITY_RESULT" + let balanceTermsId = "VT_W_FIXED_QUANTITY_BALANCE" + let sourceCondition = defaultVestingCondition "start" OcfVestingStartTrigger [] + let sourceTerms = (defaultVestingTerms fixedTermsId) with + vesting_conditions = [sourceCondition] + let resultTerms = sourceTerms with + id = resultTermsId + vesting_conditions = [sourceCondition with quantity = Some 40.0] + let balanceTerms = sourceTerms with + id = balanceTermsId + vesting_conditions = [sourceCondition with quantity = Some 60.0] + let fixedSource = (warrantIssuance "TX_W_FIXED_VESTING_TERMS_SOURCE" sourceSecurityId defaultStakeholderId 100.0) with + vesting_terms_id = Some fixedTermsId + let fixedResult = (warrantIssuance "TX_W_FIXED_VESTING_TERMS_RESULT" resultSecurityId defaultStakeholderId 40.0) with + date = jan02 + vesting_terms_id = Some fixedTermsId + let fixedBalance = (warrantIssuance "TX_W_FIXED_VESTING_TERMS_BALANCE" balanceSecurityId defaultStakeholderId 60.0) with + date = jan02 + vesting_terms_id = Some fixedTermsId + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms sourceTerms + , CT.OcfCreateWarrantIssuance fixedSource + , CT.OcfCreateWarrantIssuance fixedResult + , CT.OcfCreateWarrantIssuance fixedBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms sourceTerms + , CT.OcfCreateVestingTerms resultTerms + , CT.OcfCreateVestingTerms balanceTerms + , CT.OcfCreateWarrantIssuance fixedSource + , CT.OcfCreateWarrantIssuance (fixedResult with vesting_terms_id = Some resultTermsId) + , CT.OcfCreateWarrantIssuance (fixedBalance with vesting_terms_id = Some balanceTermsId) + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_ProportionalVestingTermsCanBeReusedByPartialSuccessors = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let sourceSecurityId = "WSEC_PORTION_VESTING_TERMS_SOURCE" + let transferId = "TX_W_PORTION_VESTING_TERMS_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_PORTION_VESTING_TERMS_BALANCE" + let portionTermsId = "VT_W_PORTION" + let portionCondition = (defaultVestingCondition "start" OcfVestingStartTrigger []) with + portion = Some (OcfVestingConditionPortion with numerator = 1.0, denominator = 1.0, remainder = False) + quantity = None + let portionTerms = defaultVestingTermsWithConditions portionTermsId [portionCondition] + let portionSource = (warrantIssuance "TX_W_PORTION_VESTING_TERMS_SOURCE" sourceSecurityId defaultStakeholderId 100.0) with + vesting_terms_id = Some portionTermsId + let portionResult = (warrantIssuance "TX_W_PORTION_VESTING_TERMS_RESULT" resultSecurityId defaultStakeholderId 40.0) with + date = jan02 + vesting_terms_id = Some portionTermsId + let portionBalance = (warrantIssuance "TX_W_PORTION_VESTING_TERMS_BALANCE" balanceSecurityId defaultStakeholderId 60.0) with + date = jan02 + vesting_terms_id = Some portionTermsId + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms portionTerms + , CT.OcfCreateWarrantIssuance portionSource + , CT.OcfCreateWarrantIssuance portionResult + , CT.OcfCreateWarrantIssuance portionBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_InlineVestingsOverrideReferencedTermsForSuccessors = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let sourceSecurityId = "WSEC_INLINE_AND_TERMS_SOURCE" + let transferId = "TX_W_INLINE_AND_TERMS_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_INLINE_AND_TERMS_BALANCE" + let termsId = "VT_W_IGNORED_FIXED_QUANTITY" + let sourceVesting = OcfVesting with date = jan03, amount = 100.0 + let resultVesting = OcfVesting with date = jan03, amount = 40.0 + let balanceVesting = OcfVesting with date = jan03, amount = 60.0 + let sourceIssuance = (warrantIssuance "TX_W_INLINE_AND_TERMS_SOURCE" sourceSecurityId defaultStakeholderId 100.0) with + vesting_terms_id = Some termsId + vestings = [sourceVesting] + let resultIssuance = (warrantIssuance "TX_W_INLINE_AND_TERMS_RESULT" resultSecurityId defaultStakeholderId 40.0) with + date = jan02 + vesting_terms_id = None + vestings = [resultVesting] + let balanceIssuance = (warrantIssuance "TX_W_INLINE_AND_TERMS_BALANCE" balanceSecurityId defaultStakeholderId 60.0) with + date = jan02 + vesting_terms_id = None + vestings = [balanceVesting] + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTerms termsId) + , CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance resultIssuance + , CT.OcfCreateWarrantIssuance balanceIssuance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_UnknownQuantityTransferPreservesUnknownBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let sourceSecurityId = "WSEC_UNKNOWN_TRANSFER_SOURCE" + let transferId = "TX_W_UNKNOWN_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_UNKNOWN_TRANSFER_BALANCE" + let sourceIssuance = (warrantIssuance "TX_W_UNKNOWN_TRANSFER_SOURCE" sourceSecurityId defaultStakeholderId 100.0) with + quantity = None + quantity_source = None + let resultIssuance = (warrantIssuance "TX_W_UNKNOWN_TRANSFER_RESULT" resultSecurityId defaultStakeholderId 40.0) with date = jan02 + let unknownBalance = (warrantIssuance "TX_W_UNKNOWN_TRANSFER_BALANCE" balanceSecurityId defaultStakeholderId 100.0) with + date = jan02 + quantity = None + quantity_source = None + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance resultIssuance + , CT.OcfCreateWarrantIssuance (unknownBalance with quantity = Some 60.0) + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance resultIssuance + , CT.OcfCreateWarrantIssuance unknownBalance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_UnknownQuantityFixedAmountSuccessorsFail = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let sourceSecurityId = "WSEC_UNKNOWN_FIXED_TRANSFER_SOURCE" + let transferId = "TX_W_UNKNOWN_FIXED_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "WSEC_UNKNOWN_FIXED_TRANSFER_BALANCE" + let sourceTrigger = warrantFixedExerciseTrigger "UNKNOWN_FIXED_TRIGGER" stockClassId 25.0 + let sourceIssuance = (warrantIssuance "TX_W_UNKNOWN_FIXED_TRANSFER_SOURCE" sourceSecurityId stakeholderId 100.0) with + quantity = None + quantity_source = None + exercise_triggers = [sourceTrigger] + let resultIssuance = (warrantIssuance "TX_W_UNKNOWN_FIXED_TRANSFER_RESULT" resultSecurityId stakeholderId 40.0) with + date = jan02 + exercise_triggers = [sourceTrigger] + let balanceIssuance = (warrantIssuance "TX_W_UNKNOWN_FIXED_TRANSFER_BALANCE" balanceSecurityId stakeholderId 100.0) with + date = jan02 + quantity = None + quantity_source = None + exercise_triggers = [sourceTrigger] + let transfer = (warrantTransfer transferId sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance resultIssuance + , CT.OcfCreateWarrantIssuance balanceIssuance + , CT.OcfCreateWarrantTransfer transfer + ] + edits = [] + deletes = [] + +testWarrantSecurityBalance_UnknownQuantityCancellationPreservesUnknownBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let sourceSecurityId = "WSEC_UNKNOWN_CANCEL_SOURCE" + let balanceSecurityId = "WSEC_UNKNOWN_CANCEL_BALANCE" + let sourceIssuance = (warrantIssuance "TX_W_UNKNOWN_CANCEL_SOURCE" sourceSecurityId defaultStakeholderId 100.0) with + quantity = None + quantity_source = None + let unknownBalance = (warrantIssuance "TX_W_UNKNOWN_CANCEL_BALANCE" balanceSecurityId defaultStakeholderId 100.0) with + date = jan02 + quantity = None + quantity_source = None + let cancellation = (warrantCancellation "TX_W_UNKNOWN_CANCEL" sourceSecurityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance sourceIssuance + , CT.OcfCreateWarrantIssuance unknownBalance + , CT.OcfCreateWarrantCancellation cancellation + ] + edits = [] + deletes = [] + pure () + +testWarrantSecurityBalance_UnknownQuantityExerciseBeforeIssuanceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "WSEC_BAL_UNKNOWN_PRE_ISSUE" + let issuance = (warrantIssuance "TX_W_BAL_UNKNOWN_PRE_ISSUE" securityId stakeholderId 100.0) with + quantity = None + exercise_triggers = [warrantFixedExerciseTrigger "TEST_TRIGGER" stockClassId 25.0] + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance issuance + , CT.OcfCreateWarrantExercise (warrantExercise "TX_W_EXERCISE_BEFORE_ISSUE" securityId (Some 25.0) preIssuanceDate) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_CancellationCannotExceedIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_CANCEL" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_CANCEL" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationCancellation (equityCompensationCancellation "TX_E_CANCEL_TOO_MUCH" securityId 101.0 jan02) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_NegativeExerciseFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_NEGATIVE_EXERCISE" + let issuance = (equityCompensationIssuance "TX_E_BAL_NEGATIVE_EXERCISE" securityId defaultStakeholderId 50.0) with + compensation_type = OcfCompensationTypeCSAR + base_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_E_NEGATIVE_EXERCISE" securityId (-1.0) jan02) + ] + edits = [] + deletes = [] + +testNonStockSecurityBalance_NegativeReductionsCannotIncreaseBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let warrantSecurityId = "WSEC_BAL_NEGATIVE_CANCEL" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BAL_NEGATIVE_CANCEL" warrantSecurityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_NEGATIVE" warrantSecurityId (-1.0) jan02) + ] + edits = [] + deletes = [] + + let equityCancellationSecurityId = "ESEC_BAL_NEGATIVE_CANCEL" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance + (equityCompensationIssuance "TX_E_BAL_NEGATIVE_CANCEL" equityCancellationSecurityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationCancellation + (equityCompensationCancellation "TX_E_CANCEL_NEGATIVE" equityCancellationSecurityId (-1.0) jan02) + ] + edits = [] + deletes = [] + + let equityReleaseSecurityId = "ESEC_BAL_NEGATIVE_RELEASE" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance + (equityCompensationIssuance "TX_E_BAL_NEGATIVE_RELEASE" equityReleaseSecurityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationRelease + ((equityCompensationRelease "TX_E_RELEASE_NEGATIVE" equityReleaseSecurityId jan02 jan02 (-1.0)) with resulting_security_ids = []) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_CumulativeReductionsCannotExceedIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "ESEC_BAL_CUMULATIVE" + let issuance = (equityCompensationIssuance "TX_E_BAL_CUMULATIVE" securityId stakeholderId 100.0) with + stock_class_id = Some stockClassId + let transferResult = (equityCompensationIssuance "TX_E_TRANSFER_25_RESULT_ISSUANCE" "TX_E_TRANSFER_25_RESULT" stakeholderId 25.0) with + date = jan04 + stock_class_id = Some stockClassId + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_E_EXERCISE_40" securityId 40.0 jan02) + , CT.OcfCreateEquityCompensationRelease ((equityCompensationRelease "TX_E_RELEASE_40" securityId jan03 jan03 40.0) with resulting_security_ids = []) + , CT.OcfCreateEquityCompensationIssuance transferResult + , CT.OcfCreateEquityCompensationTransfer (equityCompensationTransfer "TX_E_TRANSFER_25" securityId 25.0 jan04) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_ReductionAfterRetractionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_RETRACTED" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_RETRACTED" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationRetraction (equityCompensationRetraction "TX_E_RETRACT_ALL" securityId jan02) + , CT.OcfCreateEquityCompensationCancellation (equityCompensationCancellation "TX_E_CANCEL_AFTER_RETRACT" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_ReleaseBeforeRetractionUsesReleaseDate = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "ESEC_BAL_RELEASE_SETTLES_AFTER_RETRACTION" + let issuance = (equityCompensationIssuance "TX_E_BAL_RELEASE_AFTER_RETRACTION" securityId stakeholderId 100.0) with + stock_class_id = Some stockClassId + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateEquityCompensationRelease ((equityCompensationRelease "TX_E_RELEASE_AFTER_RETRACTION" securityId jan02 jan04 50.0) with resulting_security_ids = []) + , CT.OcfCreateEquityCompensationRetraction (equityCompensationRetraction "TX_E_RETRACT_BEFORE_SETTLEMENT" securityId jan03) + ] + edits = [] + deletes = [] + pure () + +testEquityCompensationSecurityBalance_ReleaseBeforeBalanceCancellationUsesReleaseDate = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "ESEC_BAL_RELEASE_BEFORE_CANCELLATION" + let balanceSecurityId = "ESEC_BAL_RELEASE_CANCELLATION_REMAINDER" + let sourceIssuance = + (equityCompensationIssuance "TX_E_BAL_RELEASE_BEFORE_CANCELLATION" securityId stakeholderId 100.0) with + stock_class_id = Some stockClassId + let balanceIssuance = + (equityCompensationIssuance "TX_E_BAL_RELEASE_CANCELLATION_REMAINDER" balanceSecurityId stakeholderId 50.0) with + date = jan03 + stock_class_id = Some stockClassId + let cancellation = + (equityCompensationCancellation "TX_E_CANCEL_AFTER_RELEASE_BEFORE_SETTLEMENT" securityId 10.0 jan03) with + balance_security_id = Some balanceSecurityId + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance sourceIssuance + , CT.OcfCreateEquityCompensationRelease + ((equityCompensationRelease "TX_E_RELEASE_BEFORE_CANCELLATION" securityId jan02 jan04 40.0) with resulting_security_ids = []) + , CT.OcfCreateEquityCompensationIssuance balanceIssuance + , CT.OcfCreateEquityCompensationCancellation cancellation + ] + edits = [] + deletes = [] + pure () + +testEquityCompensationSecurityBalance_RepeatedRetractionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_REPEATED_RETRACTED" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_REPEATED_RETRACTED" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationRetraction (equityCompensationRetraction "TX_E_RETRACT_FIRST" securityId jan02) + , CT.OcfCreateEquityCompensationRetraction (equityCompensationRetraction "TX_E_RETRACT_SECOND" securityId jan03) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_TransferWithBalanceSecurityRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_TRANSFER_BALANCE_SECURITY" + let resultIssuance = (equityCompensationIssuance "TX_E_TRANSFER_BALANCE_RESULT" "TX_E_TRANSFER_BALANCE_SECURITY_RESULT" defaultStakeholderId 60.0) with date = jan02 + let balanceIssuance = (equityCompensationIssuance "TX_E_TRANSFER_BALANCE_REMAINDER" "ESEC_BALANCE_TRANSFER" defaultStakeholderId 40.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_TRANSFER_BALANCE_SECURITY" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance resultIssuance + , CT.OcfCreateEquityCompensationIssuance balanceIssuance + , CT.OcfCreateEquityCompensationTransfer ((equityCompensationTransfer "TX_E_TRANSFER_BALANCE_SECURITY" securityId 60.0 jan02) with balance_security_id = Some "ESEC_BALANCE_TRANSFER") + , CT.OcfCreateEquityCompensationCancellation (equityCompensationCancellation "TX_E_CANCEL_AFTER_BALANCE_TRANSFER" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_CancellationWithBalanceSecurityRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_CANCELLATION_BALANCE_SECURITY" + let balanceIssuance = (equityCompensationIssuance "TX_E_CANCEL_BALANCE_REMAINDER" "ESEC_BALANCE_CANCELLATION" defaultStakeholderId 40.0) with date = jan02 + let laterResult = (equityCompensationIssuance "TX_E_TRANSFER_AFTER_CANCEL_RESULT" "TX_E_TRANSFER_AFTER_BALANCE_CANCELLATION_RESULT" defaultStakeholderId 1.0) with date = jan03 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_CANCELLATION_BALANCE_SECURITY" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance balanceIssuance + , CT.OcfCreateEquityCompensationIssuance laterResult + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_CANCEL_BALANCE_SECURITY" securityId 60.0 jan02) with balance_security_id = Some "ESEC_BALANCE_CANCELLATION") + , CT.OcfCreateEquityCompensationTransfer (equityCompensationTransfer "TX_E_TRANSFER_AFTER_BALANCE_CANCELLATION" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_BalanceSecurityRequiresPositiveRemainder = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_ZERO_REMAINDER" + let transferResult = (equityCompensationIssuance "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL_RESULT_ISSUANCE" "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL_RESULT" defaultStakeholderId 40.0) with date = jan02 + let balanceResult = (equityCompensationIssuance "TX_E_BALANCE_CANCEL_REMAINDER_BALANCE_ISSUANCE" "ESEC_BALANCE_ZERO_REMAINDER" defaultStakeholderId 1.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_ZERO_REMAINDER" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance transferResult + , CT.OcfCreateEquityCompensationTransfer (equityCompensationTransfer "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL" securityId 40.0 jan02) + , CT.OcfCreateEquityCompensationIssuance balanceResult + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BALANCE_CANCEL_REMAINDER" securityId 60.0 jan02) with balance_security_id = Some "ESEC_BALANCE_ZERO_REMAINDER") + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_ExactBalanceSuccessorSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_EXACT_SUCCESSOR" + let balanceSecurityId = "ESEC_BAL_EXACT_REMAINDER" + let balanceIssuance = (equityCompensationIssuance "TX_E_BAL_EXACT_REMAINDER" balanceSecurityId defaultStakeholderId 40.0) with date = jan02 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_EXACT_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance balanceIssuance + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BAL_EXACT_CANCEL" securityId 60.0 jan02) with balance_security_id = Some balanceSecurityId) + ] + edits = [] + deletes = [] + pure () + +testEquityCompensationSecurityBalance_WrongBalanceSuccessorQuantityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_WRONG_SUCCESSOR" + let balanceSecurityId = "ESEC_BAL_WRONG_REMAINDER" + let wrongBalance = (equityCompensationIssuance "TX_E_BAL_WRONG_REMAINDER" balanceSecurityId defaultStakeholderId 39.0) with date = jan02 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_WRONG_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance wrongBalance + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BAL_WRONG_CANCEL" securityId 60.0 jan02) with balance_security_id = Some balanceSecurityId) + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_MissingBalanceSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableReady <- addDefaultStakeholder issuer cap_table + let securityId = "ESEC_BAL_MISSING_SUCCESSOR" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BAL_MISSING_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BAL_MISSING_CANCEL" securityId 60.0 jan02) with balance_security_id = Some "ESEC_BAL_MISSING_REMAINDER") + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_StockPlanTransferRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let stockPlanId = "PLAN_EQUITY_TRANSFER_BALANCE" + let sourceSecurityId = "ESEC_PLAN_TRANSFER_SOURCE" + let successorSecurityId = "ESEC_PLAN_TRANSFER_SUCCESSOR" + let stockPlan = (defaultStockPlan stockPlanId stockClassId) with initial_shares_reserved = 100.0 + let sourceIssuance = (equityCompensationIssuance "TX_E_PLAN_TRANSFER_SOURCE" sourceSecurityId stakeholderId 100.0) with + stock_class_id = Some stockClassId + stock_plan_id = Some stockPlanId + + sourceResult <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockPlan stockPlan + , CT.OcfCreateEquityCompensationIssuance sourceIssuance + ] + edits = [] + deletes = [] + + let transfer = (equityCompensationTransfer "TX_E_PLAN_TRANSFER" sourceSecurityId 100.0 jan02) with + resulting_security_ids = [successorSecurityId] + let successorIssuance = (equityCompensationIssuance "TX_E_PLAN_TRANSFER_SUCCESSOR" successorSecurityId stakeholderId 100.0) with + date = jan02 + stock_class_id = Some stockClassId + let sourceExercise = (equityCompensationExercise "TX_E_PLAN_TRANSFER_SOURCE_EXERCISE" sourceSecurityId 1.0 jan03) with resulting_security_ids = [] + + submitMustFail issuer do + exerciseCmd sourceResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationTransfer transfer + , CT.OcfCreateEquityCompensationIssuance successorIssuance + , CT.OcfCreateEquityCompensationExercise sourceExercise + ] + edits = [] + deletes = [] + +testEquityCompensationSecurityBalance_SplitAdjustedExerciseSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "ESEC_BAL_SPLIT" + let issuance = (equityCompensationIssuance "TX_E_BAL_SPLIT" securityId stakeholderId 100.0) with + stock_class_id = Some stockClassId + let split = (stockClassSplit "TX_E_BAL_SPLIT" stockClassId 2.0 1.0) with date = jan02 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateStockClassSplit split + , CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_E_EXERCISE_AFTER_SPLIT" securityId 200.0 jan03) + ] + edits = [] + deletes = [] + pure () + +testEquityCompensationSecurityBalance_ClasslessSingleClassPlanSplitAdjustedExerciseSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let stockPlanId = "PLAN_E_BAL_CLASSLESS_SPLIT" + let securityId = "ESEC_BAL_CLASSLESS_SPLIT" + let stockPlan = (defaultStockPlan stockPlanId stockClassId) with initial_shares_reserved = 200.0 + let issuance = (equityCompensationIssuance "TX_E_BAL_CLASSLESS_SPLIT" securityId stakeholderId 100.0) with + stock_plan_id = Some stockPlanId + let split = (stockClassSplit "TX_E_BAL_CLASSLESS_SPLIT" stockClassId 2.0 1.0) with date = jan02 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockPlan stockPlan + , CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateStockClassSplit split + , CT.OcfCreateEquityCompensationExercise + (equityCompensationExercise "TX_E_CLASSLESS_EXERCISE_AFTER_SPLIT" securityId 200.0 jan03) + ] + edits = [] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml index 5c54b1f0..e3388c88 100644 --- a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml +++ b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml @@ -6,7 +6,6 @@ import Fairmint.OpenCapTable.OCF.StockConversion import Fairmint.OpenCapTable.OCF.StockConsolidation import Fairmint.OpenCapTable.OCF.EquityCompensationExercise import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance -import Fairmint.OpenCapTable.OCF.EquityCompensationRelease import Fairmint.OpenCapTable.OCF.StockRepurchase import Fairmint.OpenCapTable.OCF.StockRetraction import Fairmint.OpenCapTable.OCF.StockReissuance @@ -275,19 +274,6 @@ equityCompensationExercise exerciseId securityId quantity = resulting_security_ids = [exerciseId <> "_RESULT"] consideration_text = None -equityCompensationRelease : Text -> Text -> Time -> Time -> Decimal -> EquityCompensationReleaseOcfData -equityCompensationRelease releaseId securityId releaseDate settlementDate quantity = - EquityCompensationReleaseOcfData with - id = releaseId - date = releaseDate - security_id = securityId - settlement_date = settlementDate - release_price = OcfMonetary with amount = 1.0, currency = "USD" - quantity = quantity - resulting_security_ids = [releaseId <> "_RESULT"] - consideration_text = None - comments = [] - testStockIssuanceCannotExceedStockClassInitialAuthorizedShares = script do TestOcp{issuer, cap_table} <- setupTestOcp @@ -662,6 +648,27 @@ testStockRepurchaseReducesIssuedSharesForCeiling = script do _ <- createStockIssuanceWith issuer result.updatedCapTableCid ((defaultStockIssuance "TX_REPURCHASE_REISSUE" "SEC_REPURCHASE_REISSUE" stakeholderId stockClassId) with quantity = 50.0) pure () +testStockTransferDoesNotReduceIssuedSharesForCeiling = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_TRANSFER" 100.0) + (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_TRANSFER" "Transfer Holder" + capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_TRANSFER_ISSUED" "SEC_TRANSFER_ISSUED" stakeholderId stockClassId) with quantity = 100.0) + result <- submit issuer do + exerciseCmd capTableIssued CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockTransfer (stockTransfer "TX_TRANSFER_REPLACE" "SEC_TRANSFER_ISSUED" 100.0 ["TX_TRANSFER_REPLACE_RESULT"]) + , stockSuccessorIssuance "TX_TRANSFER_REPLACE_OUTPUT" "TX_TRANSFER_REPLACE_RESULT" stakeholderId stockClassId defaultTestDate 100.0 + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_TRANSFER_EXTRA" "SEC_TRANSFER_EXTRA" stakeholderId stockClassId) with quantity = 1.0)] + edits = [] + deletes = [] + testStockTransferWithResultingIssuanceDoesNotDoubleCountForCeiling = script do TestOcp{issuer, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestStockConversion.daml b/Test/daml/OpenCapTable/TestStockConversion.daml index 50ae361b..cef679aa 100644 --- a/Test/daml/OpenCapTable/TestStockConversion.daml +++ b/Test/daml/OpenCapTable/TestStockConversion.daml @@ -147,13 +147,18 @@ testStockConversion_PartialConversion = script do let securityId = "SSEC-PREF-002" result <- createStockIssuance issuer capTableCid securityId 50000.0 let conversionDate = DT.time (DA.date 2025 Nov 01) 0 0 0 + let resultIssuance = (defaultStockIssuance "TX_STOCK_CONVERSION_2_RESULT" "SSEC-COMMON-002" "SH-1" "SC_COMMON") with + date = conversionDate + quantity = 30000.0 let balanceIssuance = (defaultStockIssuance "TX_STOCK_CONVERSION_2_BALANCE" "SSEC-PREF-002-REMAIN" "SH-1" "SC_PREF") with date = conversionDate quantity = 20000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = - [ CT.OcfCreateStockConversion StockConversionOcfData with + [ CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateStockIssuance balanceIssuance + , CT.OcfCreateStockConversion StockConversionOcfData with id = "TX_STOCK_CONVERSION_2" date = conversionDate security_id = securityId @@ -161,8 +166,6 @@ testStockConversion_PartialConversion = script do resulting_security_ids = ["SSEC-COMMON-002"] comments = ["Elective partial conversion by investor"] balance_security_id = Some balanceIssuance.security_id - , CT.OcfCreateStockIssuance balanceIssuance - , stockSuccessorIssuance "TX_STOCK_CONVERSION_2_RESULT" "SSEC-COMMON-002" "SH-1" "SC_COMMON" conversionDate 30000.0 ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockSecurityBalances.daml b/Test/daml/OpenCapTable/TestStockSecurityBalances.daml new file mode 100644 index 00000000..bd7ce17e --- /dev/null +++ b/Test/daml/OpenCapTable/TestStockSecurityBalances.daml @@ -0,0 +1,507 @@ +module OpenCapTable.TestStockSecurityBalances where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.StockConsolidation (StockConsolidationOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockConversion (StockConversionOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockRepurchase (StockRepurchaseOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockReissuance (StockReissuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockRetraction (StockRetractionOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockTransfer (StockTransferOcfData(..)) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import qualified DA.Date as DA +import DA.Date (Month(..)) +import qualified DA.Time as DT +import Daml.Script + +stockRepurchase : Text -> Text -> Decimal -> Time -> StockRepurchaseOcfData +stockRepurchase repurchaseId securityId quantity date = StockRepurchaseOcfData with + id = repurchaseId + date = date + security_id = securityId + price = OcfMonetary with amount = 1.0, currency = "USD" + quantity = quantity + balance_security_id = None + consideration_text = None + comments = [] + +stockTransfer : Text -> Text -> Decimal -> Time -> StockTransferOcfData +stockTransfer transferId securityId quantity date = StockTransferOcfData with + id = transferId + date = date + security_id = securityId + quantity = quantity + resulting_security_ids = [transferId <> "_RESULT"] + balance_security_id = None + consideration_text = None + comments = [] + +stockTransferWithBalance : Text -> Text -> Decimal -> Text -> Time -> StockTransferOcfData +stockTransferWithBalance transferId securityId quantity balanceSecurityId date = + (stockTransfer transferId securityId quantity date) with + balance_security_id = Some balanceSecurityId + +stockConversion : Text -> Text -> Decimal -> Time -> StockConversionOcfData +stockConversion conversionId securityId quantity date = StockConversionOcfData with + id = conversionId + date = date + security_id = securityId + quantity_converted = quantity + resulting_security_ids = [conversionId <> "_RESULT"] + balance_security_id = None + comments = [] + +stockRetraction : Text -> Text -> Time -> StockRetractionOcfData +stockRetraction retractionId securityId date = StockRetractionOcfData with + id = retractionId + date = date + security_id = securityId + reason_text = "Retracted stock issuance" + comments = [] + +stockReissuance : Text -> Text -> Time -> StockReissuanceOcfData +stockReissuance reissuanceId securityId date = StockReissuanceOcfData with + id = reissuanceId + date = date + security_id = securityId + resulting_security_ids = [reissuanceId <> "_RESULT"] + split_transaction_id = None + reason_text = None + comments = [] + +stockConsolidation : Text -> [Text] -> Time -> StockConsolidationOcfData +stockConsolidation consolidationId securityIds date = StockConsolidationOcfData with + id = consolidationId + date = date + security_ids = securityIds + resulting_security_id = consolidationId <> "_RESULT" + reason_text = None + comments = [] + +jan02 : Time +jan02 = DT.time (DA.date 2024 Jan 02) 0 0 0 + +jan03 : Time +jan03 = DT.time (DA.date 2024 Jan 03) 0 0 0 + +testStockSecurityBalance_CancellationCannotExceedIssuedShares = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_CANCEL" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_CANCEL" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_TOO_MUCH" securityId 101.0 jan02) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_NegativeReductionsCannotIncreaseBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let cancellationSecurityId = "SEC_STOCK_BAL_NEGATIVE_CANCEL" + let cancellationIssuance = + (defaultStockIssuance "TX_STOCK_BAL_NEGATIVE_CANCEL" cancellationSecurityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance cancellationIssuance + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_NEGATIVE" cancellationSecurityId (-1.0) jan02) + ] + edits = [] + deletes = [] + + let repurchaseSecurityId = "SEC_STOCK_BAL_NEGATIVE_REPURCHASE" + let repurchaseIssuance = + (defaultStockIssuance "TX_STOCK_BAL_NEGATIVE_REPURCHASE" repurchaseSecurityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance repurchaseIssuance + , CT.OcfCreateStockRepurchase (stockRepurchase "TX_REPURCHASE_NEGATIVE" repurchaseSecurityId (-1.0) jan02) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_CumulativeReductionsCannotExceedIssuedShares = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_CUMULATIVE" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_CUMULATIVE" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_60" securityId 60.0 jan02) + , CT.OcfCreateStockRepurchase (stockRepurchase "TX_REPURCHASE_50" securityId 50.0 jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_TransferCannotExceedIssuedShares = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_TRANSFER" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockTransfer (stockTransfer "TX_TRANSFER_TOO_MUCH" securityId 101.0 jan02) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_BalanceSecurityRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_TRANSFER_BALANCE" + let transferId = "TX_TRANSFER_WITH_BALANCE" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "SEC_STOCK_BAL_REMAINDER" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER_BALANCE" securityId stakeholderId stockClassId) with quantity = 100.0 + let resultIssuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER_RESULT" resultSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 40.0 + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER_REMAINDER" balanceSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 60.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockTransfer (stockTransferWithBalance transferId securityId 40.0 balanceSecurityId jan02) + , CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateStockIssuance balanceIssuance + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_OLD_SOURCE_AFTER_BALANCE" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_FullReductionWithoutBalanceCanConsumeRemainingSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_ZERO_REMAINDER" + let transferId = "TX_TRANSFER_FINAL_REMAINDER" + let resultSecurityId = transferId <> "_RESULT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_ZERO_REMAINDER" securityId stakeholderId stockClassId) with quantity = 100.0 + let resultIssuance = (defaultStockIssuance "TX_STOCK_BAL_FINAL_RESULT" resultSecurityId stakeholderId stockClassId) with + date = jan03 + quantity = 60.0 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_BEFORE_BALANCE_TRANSFER" securityId 40.0 jan02) + , CT.OcfCreateStockTransfer (stockTransfer transferId securityId 60.0 jan03) + , CT.OcfCreateStockIssuance resultIssuance + ] + edits = [] + deletes = [] + pure () + +testStockSecurityBalance_BalanceSecurityRetirementUpdatesIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + let stockClassId = "SC_STOCK_BAL_TERMINAL_INDEX" + (capTableWithClass, _) <- setupStockClassWith issuer cap_table (limitedStockClass stockClassId 100.0) + (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_STOCK_BAL_TERMINAL_INDEX" "Balance Holder" + let securityId = "SEC_STOCK_BAL_TERMINAL_INDEX" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_TERMINAL_INDEX" securityId stakeholderId stockClassId) with quantity = 100.0 + capTableIssued <- createStockIssuanceWith issuer capTableReady issuance + + let cancellation = (stockCancellationOn "TX_STOCK_BAL_TERMINAL_CANCEL" securityId 40.0 jan02) with + balance_security_id = Some "SEC_STOCK_BAL_TERMINAL_REMAINDER" + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_TERMINAL_REMAINDER" "SEC_STOCK_BAL_TERMINAL_REMAINDER" stakeholderId stockClassId) with + date = jan02 + quantity = 60.0 + let replacement = (defaultStockIssuance "TX_STOCK_BAL_TERMINAL_REPLACE" "SEC_STOCK_BAL_TERMINAL_REPLACE" stakeholderId stockClassId) with + date = jan03 + quantity = 40.0 + + _ <- submit issuer do + exerciseCmd capTableIssued CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockCancellation cancellation + , CT.OcfCreateStockIssuance balanceIssuance + , CT.OcfCreateStockIssuance replacement + ] + edits = [] + deletes = [] + pure () + +testStockSecurityBalance_MissingCancellationBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_MISSING_CANCEL_REMAINDER" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_MISSING_CANCEL_REMAINDER" securityId stakeholderId stockClassId) with quantity = 100.0 + let cancellation = (stockCancellationOn "TX_STOCK_BAL_MISSING_CANCEL_REMAINDER_CANCEL" securityId 1.0 jan02) with + balance_security_id = Some "SEC_STOCK_BAL_MISSING_CANCEL_REMAINDER_RESULT" + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation cancellation + ] + edits = [] + deletes = [] + +testStockSecurityBalance_BalanceSecurityMustMatchRemainingQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_WRONG_REMAINDER" + let balanceSecurityId = "SEC_STOCK_BAL_WRONG_REMAINDER_RESULT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_WRONG_REMAINDER" securityId stakeholderId stockClassId) with quantity = 100.0 + let cancellation = (stockCancellationOn "TX_STOCK_BAL_WRONG_REMAINDER_CANCEL" securityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_WRONG_REMAINDER_RESULT" balanceSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 1.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation cancellation + , CT.OcfCreateStockIssuance balanceIssuance + ] + edits = [] + deletes = [] + +testStockSecurityBalance_BalanceSecurityMustMatchSourceStockClass = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let otherStockClassId = "SC_STOCK_BAL_WRONG_CLASS" + (capTableWithOtherClass, _) <- setupStockClassWith issuer capTableReady (defaultStockClass otherStockClassId) + let securityId = "SEC_STOCK_BAL_WRONG_CLASS" + let balanceSecurityId = "SEC_STOCK_BAL_WRONG_CLASS_RESULT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_WRONG_CLASS" securityId stakeholderId stockClassId) with quantity = 100.0 + let cancellation = (stockCancellationOn "TX_STOCK_BAL_WRONG_CLASS_CANCEL" securityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_WRONG_CLASS_RESULT" balanceSecurityId stakeholderId otherStockClassId) with + date = jan02 + quantity = 60.0 + + submitMustFail issuer do + exerciseCmd capTableWithOtherClass CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation cancellation + , CT.OcfCreateStockIssuance balanceIssuance + ] + edits = [] + deletes = [] + +testStockSecurityBalance_MissingTransferBalanceCannotFreeAuthorizedShares = script do + TestOcp{issuer, cap_table} <- setupTestOcp + let stockClassId = "SC_STOCK_BAL_MISSING_TRANSFER_BALANCE" + (capTableWithClass, _) <- setupStockClassWith issuer cap_table (limitedStockClass stockClassId 100.0) + (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_STOCK_BAL_MISSING_TRANSFER_BALANCE" "Transfer Holder" + let securityId = "SEC_STOCK_BAL_MISSING_TRANSFER_BALANCE" + let transferId = "TX_STOCK_BAL_MISSING_TRANSFER_BALANCE" + let resultSecurityId = transferId <> "_RESULT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_MISSING_TRANSFER_BALANCE_SOURCE" securityId stakeholderId stockClassId) with quantity = 100.0 + let transfer = stockTransferWithBalance transferId securityId 40.0 "SEC_STOCK_BAL_MISSING_TRANSFER_REMAINDER" jan02 + let transferredIssuance = (defaultStockIssuance "TX_STOCK_BAL_MISSING_TRANSFER_RESULT" resultSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 40.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockTransfer transfer + , CT.OcfCreateStockIssuance transferredIssuance + ] + edits = [] + deletes = [] + +testStockSecurityBalance_TransferBalanceRetirementUpdatesIssuedQuantity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + let stockClassId = "SC_STOCK_BAL_TRANSFER_TERMINAL_INDEX" + (capTableWithClass, _) <- setupStockClassWith issuer cap_table (limitedStockClass stockClassId 100.0) + (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_STOCK_BAL_TRANSFER_TERMINAL_INDEX" "Transfer Holder" + let securityId = "SEC_STOCK_BAL_TRANSFER_TERMINAL_INDEX" + let transferId = "TX_STOCK_BAL_TERMINAL_TRANSFER" + let resultSecurityId = transferId <> "_RESULT" + let balanceSecurityId = "SEC_STOCK_BAL_TRANSFER_TERMINAL_REMAINDER" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER_TERMINAL_INDEX" securityId stakeholderId stockClassId) with quantity = 100.0 + let transfer = stockTransferWithBalance transferId securityId 40.0 balanceSecurityId jan02 + let transferredIssuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFERRED" resultSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 40.0 + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_TRANSFER_REMAINDER" balanceSecurityId stakeholderId stockClassId) with + date = jan02 + quantity = 60.0 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockTransfer transfer + , CT.OcfCreateStockIssuance transferredIssuance + , CT.OcfCreateStockIssuance balanceIssuance + ] + edits = [] + deletes = [] + pure () + +testStockSecurityBalance_ConversionCannotExceedIssuedShares = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_CONVERT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_CONVERT" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERT_TOO_MUCH" securityId 101.0 jan02) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_ReductionAfterRetractionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_RETRACTED" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_RETRACTED" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockRetraction (stockRetraction "TX_RETRACT_ALL" securityId jan02) + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_AFTER_RETRACT" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_ReductionAfterReissuanceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_REISSUED" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_REISSUED" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockReissuance (stockReissuance "TX_REISSUE_ALL" securityId jan02) + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_AFTER_REISSUE" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_TerminalEventRequiresRemainingBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_TERMINAL_ONCE" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_TERMINAL_ONCE" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockReissuance (stockReissuance "TX_REISSUE_FIRST" securityId jan02) + , CT.OcfCreateStockReissuance (stockReissuance "TX_REISSUE_SECOND" securityId jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_ReductionAfterConsolidationFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_CONSOLIDATED" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_CONSOLIDATED" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockConsolidation (stockConsolidation "TX_CONSOLIDATE_ALL" [securityId] jan02) + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_AFTER_CONSOLIDATE" securityId 1.0 jan03) + ] + edits = [] + deletes = [] + +testStockSecurityBalance_SplitAdjustedBalanceAllowsReduction = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_SPLIT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_SPLIT" securityId stakeholderId stockClassId) with quantity = 100.0 + + _ <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockClassSplit (stockClassSplitOn "TX_SPLIT_2_FOR_1" stockClassId 2.0 1.0 jan02) + , CT.OcfCreateStockRepurchase (stockRepurchase "TX_REPURCHASE_AFTER_SPLIT" securityId 150.0 jan03) + ] + edits = [] + deletes = [] + pure () + +testStockSecurityBalance_ReissuanceSuccessorIgnoresSameDaySplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let sourceSecurityId = "SEC_STOCK_BAL_SAME_DAY_SPLIT_SOURCE" + reissuanceId = "TX_STOCK_BAL_SAME_DAY_SPLIT_REISSUANCE" + reissuedSecurityId = reissuanceId <> "_RESULT" + splitId = "TX_STOCK_BAL_SAME_DAY_SPLIT" + transferId = "TX_STOCK_BAL_AFTER_SAME_DAY_SPLIT" + transferResultSecurityId = transferId <> "_RESULT" + finalDate = DT.time (DA.date 2024 Jan 04) 0 0 0 + issuance = (defaultStockIssuance "TX_STOCK_BAL_SAME_DAY_SPLIT_SOURCE" sourceSecurityId stakeholderId stockClassId) with quantity = 25.0 + + reissued <- submit issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockClassSplit (stockClassSplitOn splitId stockClassId 2.0 1.0 jan02) + , stockSuccessorIssuance "TX_STOCK_BAL_SAME_DAY_SPLIT_RESULT" reissuedSecurityId stakeholderId stockClassId jan02 50.0 + , CT.OcfCreateStockReissuance ((stockReissuance reissuanceId sourceSecurityId jan02) with split_transaction_id = Some splitId) + ] + edits = [] + deletes = [] + + transferred <- submit issuer do + exerciseCmd reissued.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockTransfer (stockTransfer transferId reissuedSecurityId 50.0 jan03) + , stockSuccessorIssuance "TX_STOCK_BAL_AFTER_SAME_DAY_SPLIT_RESULT" transferResultSecurityId stakeholderId stockClassId jan03 50.0 + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd transferred.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockCancellation (stockCancellationOn "TX_STOCK_BAL_REUSE_CONSUMED_REISSUANCE" reissuedSecurityId 1.0 finalDate)] + edits = [] + deletes = [] + +testStockSecurityBalance_ReverseSplitReducesAvailableBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + let securityId = "SEC_STOCK_BAL_REVERSE_SPLIT" + let issuance = (defaultStockIssuance "TX_STOCK_BAL_REVERSE_SPLIT" securityId stakeholderId stockClassId) with quantity = 100.0 + + submitMustFail issuer do + exerciseCmd capTableReady CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockClassSplit (stockClassSplitOn "TX_REVERSE_SPLIT" stockClassId 1.0 2.0 jan02) + , CT.OcfCreateStockCancellation (stockCancellationOn "TX_CANCEL_AFTER_REVERSE_SPLIT" securityId 75.0 jan03) + ] + edits = [] + deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockTransfer.daml b/Test/daml/OpenCapTable/TestStockTransfer.daml index c05e5a99..41a5e42e 100644 --- a/Test/daml/OpenCapTable/TestStockTransfer.daml +++ b/Test/daml/OpenCapTable/TestStockTransfer.daml @@ -70,17 +70,28 @@ testStockTransfer_DuplicateResultingSecurityIdsRejected = script do capTableCid <- addPrerequisites issuer cap_table let securityId = "SSEC-DUP-TRANSFER-001" result <- createStockIssuance issuer capTableCid securityId 100.0 + let transferDate = DT.time (DA.date 2025 Aug 05) 0 0 0 + let resultIssuance = (defaultStockIssuance "TX_STOCK_TRANSFER_DUP_RESULT" "SSEC-DUP-RESULT" "SH-1" "SC_COMMON") with + date = transferDate + quantity = 50.0 + let balanceIssuance = (defaultStockIssuance "TX_STOCK_TRANSFER_DUP_BALANCE" "SSEC-DUP-BALANCE" "SH-1" "SC_COMMON") with + date = transferDate + quantity = 50.0 submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockTransfer StockTransferOcfData with - id = "TX_STOCK_TRANSFER_DUP_RESULT" - date = DT.time (DA.date 2025 Aug 05) 0 0 0 - quantity = 50.0 - security_id = securityId - resulting_security_ids = ["SSEC-DUP-RESULT", "SSEC-DUP-RESULT"] - comments = [] - balance_security_id = Some "SSEC-DUP-BALANCE" - consideration_text = None] + creates = + [ CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX_STOCK_TRANSFER_DUP_RESULT" + date = transferDate + quantity = 50.0 + security_id = securityId + resulting_security_ids = ["SSEC-DUP-RESULT", "SSEC-DUP-RESULT"] + comments = [] + balance_security_id = Some "SSEC-DUP-BALANCE" + consideration_text = None + , CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateStockIssuance balanceIssuance + ] edits = [] deletes = [] pure () @@ -93,6 +104,9 @@ testStockTransfer_FounderTransferWithConsideration = script do let securityId = "SSEC-FOUNDER-001" result <- createStockIssuance issuer capTableCid securityId 1000.0 let transferDate = DT.time (DA.date 2024 Apr 18) 0 0 0 + let resultIssuance = (defaultStockIssuance "TX_STOCK_TRANSFER_2_RESULT" "SSEC-TRANSFEREE-001" "SH-1" "SC_COMMON") with + date = transferDate + quantity = 75.0 let balanceIssuance = (defaultStockIssuance "TX_STOCK_TRANSFER_2_BALANCE" "SSEC-FOUNDER-REMAIN" "SH-1" "SC_COMMON") with date = transferDate quantity = 925.0 @@ -108,8 +122,8 @@ testStockTransfer_FounderTransferWithConsideration = script do comments = ["Stock transfer from Founder A to Founder B per Stock Transfer Agreement. Transferee assumes all obligations including vesting schedule and repurchase option."] balance_security_id = Some balanceIssuance.security_id consideration_text = Some "Transfer of 75 shares at $0.0001 per share for aggregate consideration of $0.01." + , CT.OcfCreateStockIssuance resultIssuance , CT.OcfCreateStockIssuance balanceIssuance - , stockSuccessorIssuance "TX_STOCK_TRANSFER_2_RESULT" "SSEC-TRANSFEREE-001" "SH-1" "SC_COMMON" transferDate 75.0 ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestWarrantCancellation.daml b/Test/daml/OpenCapTable/TestWarrantCancellation.daml index 079dba69..1db1ba2e 100644 --- a/Test/daml/OpenCapTable/TestWarrantCancellation.daml +++ b/Test/daml/OpenCapTable/TestWarrantCancellation.daml @@ -1,42 +1,13 @@ module OpenCapTable.TestWarrantCancellation where -import Fairmint.OpenCapTable.OCF.WarrantIssuance import Fairmint.OpenCapTable.OCF.WarrantCancellation -import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) -import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, createWarrantIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script --- Create a warrant issuance to cancel (follows real workflow) -createWarrantIssuance issuer capTableCid securityId quantity issuanceDate = - submit issuer do - exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with - id = "TX_WARRANT_ISS_" <> securityId - date = issuanceDate - security_id = securityId - custom_id = "W-1" - stakeholder_id = "SH-1" - board_approval_date = None - stockholder_approval_date = None - consideration_text = None - security_law_exemptions = [] - quantity = Some quantity - quantity_source = None - exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") - purchase_price = OcfMonetary with amount = 0.0, currency = "USD" - exercise_triggers = [ ocfTriggerElectiveAtWill ] - warrant_expiration_date = None - vesting_terms_id = None - vestings = [] - comments = []] - edits = [] - deletes = [] - -- Create cancellation based on realistic data pattern createWarrantCancellation issuer capTableCid securityId quantity = submit issuer do @@ -76,12 +47,13 @@ testWarrantCancellation_PartialWithOptionals = script do capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-PARTIAL" result <- createWarrantIssuance issuer capTableCid securityId 75000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) - balanceResult <- createWarrantIssuance issuer result.updatedCapTableCid "WSEC-PARTIAL-BAL" 50000.0 (DT.time (DA.date 2024 Jun 30) 0 0 0) + let cancellationDate = DT.time (DA.date 2024 Jun 30) 0 0 0 + balanceResult <- createWarrantIssuance issuer result.updatedCapTableCid "WSEC-PARTIAL-BAL" 50000.0 cancellationDate _ <- submit issuer do exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with id = "TX_WARRANT_CANCEL_PARTIAL" - date = DT.time (DA.date 2024 Jun 30) 0 0 0 + date = cancellationDate security_id = securityId quantity = 25000.0 reason_text = "Partial cancellation per warrant amendment agreement" diff --git a/Test/daml/OpenCapTable/TestWarrantTransfer.daml b/Test/daml/OpenCapTable/TestWarrantTransfer.daml index 4112803d..59be4b64 100644 --- a/Test/daml/OpenCapTable/TestWarrantTransfer.daml +++ b/Test/daml/OpenCapTable/TestWarrantTransfer.daml @@ -6,37 +6,33 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder, warrantSuccessorIssuance) +import OpenCapTable.TestHelpers (addDefaultStakeholder, createWarrantIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script --- Create a warrant issuance to transfer (follows real workflow) -createWarrantIssuance issuer capTableCid securityId quantity issuanceDate = - submit issuer do - exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with - id = "TX_WARRANT_ISS_" <> securityId - date = issuanceDate - security_id = securityId - custom_id = "W-1" - stakeholder_id = "SH-1" - board_approval_date = None - stockholder_approval_date = None - consideration_text = None - security_law_exemptions = [] - quantity = Some quantity - quantity_source = None - exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") - purchase_price = OcfMonetary with amount = 0.0, currency = "USD" - exercise_triggers = [ ocfTriggerElectiveAtWill ] - warrant_expiration_date = None - vesting_terms_id = None - vestings = [] - comments = []] - edits = [] - deletes = [] +warrantTransferSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +warrantTransferSuccessorIssuance issuanceId securityId stakeholderId date quantity = + CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with + id = issuanceId + date = date + security_id = securityId + custom_id = "WR-" <> issuanceId + stakeholder_id = stakeholderId + board_approval_date = None + stockholder_approval_date = None + consideration_text = None + security_law_exemptions = [] + quantity = Some quantity + quantity_source = None + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + purchase_price = OcfMonetary with amount = 0.0, currency = "USD" + exercise_triggers = [ocfTriggerElectiveAtWill] + warrant_expiration_date = None + vesting_terms_id = None + vestings = [] + comments = [] -- Test: Warrant transfer per secondary sale agreement -- No prod/dev examples (warrants usually exercised, not transferred) @@ -48,7 +44,7 @@ testWarrantTransfer_SecondarySale = script do result <- createWarrantIssuance issuer capTableCid securityId 10000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [warrantSuccessorIssuance "WR-ISS-TRANSFERRED-001" "WSEC-TRANSFERRED-001" "SH-1" (DT.time (DA.date 2025 Mar 15) 0 0 0) 10000.0 + creates = [warrantTransferSuccessorIssuance "WR-ISS-TRANSFERRED-001" "WSEC-TRANSFERRED-001" "SH-1" (DT.time (DA.date 2025 Mar 15) 0 0 0) 10000.0 , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with id = "TX_WARRANT_TRANSFER_1" date = DT.time (DA.date 2025 Mar 15) 0 0 0 @@ -68,13 +64,14 @@ testWarrantTransfer_PartialTransfer = script do capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-PARTIAL" result <- createWarrantIssuance issuer capTableCid securityId 50000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) - balanceResult <- createWarrantIssuance issuer result.updatedCapTableCid "WSEC-PARTIAL-REMAIN" 30000.0 (DT.time (DA.date 2025 Jun 01) 0 0 0) + let transferDate = DT.time (DA.date 2025 Jun 01) 0 0 0 + balanceResult <- createWarrantIssuance issuer result.updatedCapTableCid "WSEC-PARTIAL-REMAIN" 30000.0 transferDate _ <- submit issuer do exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [warrantSuccessorIssuance "WR-ISS-NEW-HOLDER" "WSEC-NEW-HOLDER" "SH-1" (DT.time (DA.date 2025 Jun 01) 0 0 0) 20000.0 + creates = [warrantTransferSuccessorIssuance "WR-ISS-NEW-HOLDER" "WSEC-NEW-HOLDER" "SH-1" transferDate 20000.0 , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with id = "TX_WARRANT_TRANSFER_PARTIAL" - date = DT.time (DA.date 2025 Jun 01) 0 0 0 + date = transferDate quantity = 20000.0 security_id = securityId resulting_security_ids = ["WSEC-NEW-HOLDER"] @@ -93,7 +90,7 @@ testWarrantTransfer_FullTransferNoConsideration = script do result <- createWarrantIssuance issuer capTableCid securityId 5000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [warrantSuccessorIssuance "WR-ISS-NEWOWNER" "WSEC-NEWOWNER" "SH-1" (DT.time (DA.date 2025 Jul 20) 0 0 0) 5000.0 + creates = [warrantTransferSuccessorIssuance "WR-ISS-NEWOWNER" "WSEC-NEWOWNER" "SH-1" (DT.time (DA.date 2025 Jul 20) 0 0 0) 5000.0 , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with id = "TX_WARRANT_TRANSFER_FULL" date = DT.time (DA.date 2025 Jul 20) 0 0 0 diff --git a/dars/OpenCapTable-v34/0.0.12/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.12/OpenCapTable-v34.dar new file mode 100644 index 00000000..2840bf0c --- /dev/null +++ b/dars/OpenCapTable-v34/0.0.12/OpenCapTable-v34.dar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a760b56e3a5e50224a956fba2b0abfbcc84b75233178cf471c290ceced290ffc +size 3169473 diff --git a/dars/dars.lock b/dars/dars.lock index 5e6343c1..b74e2739 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -25,6 +25,13 @@ "uploadedAt": "2026-07-11T14:46:26Z", "networks": [] }, + "OpenCapTable-v34/0.0.12/OpenCapTable-v34.dar": { + "sha256": "a760b56e3a5e50224a956fba2b0abfbcc84b75233178cf471c290ceced290ffc", + "size": 3169473, + "sdkVersion": "3.4.10", + "uploadedAt": "2026-07-11T15:08:11.000Z", + "networks": [] + }, "OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar": { "sha256": "2ce462b8afcb1bcdbe351c56b0a47de478477f3c655da00e41cb397f63e997d6", "size": 2446238, diff --git a/scripts/codegen/templates/CapTable.daml.template b/scripts/codegen/templates/CapTable.daml.template index 95ae0167..ec9aae8b 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -20,7 +20,7 @@ import DA.Optional (fromOptional, isNone, isSome) import Fairmint.OpenCapTable.Types.Core (Context) import Fairmint.OpenCapTable.Types.Conversion (OcfAnyConversionRight(..), OcfCapitalizationDefinition, OcfConversionMechanism(..), OcfConversionTrigger(..), OcfConvertibleConversionMechanism(..), OcfConvertibleConversionRight(..), OcfStockClassConversionRight(..), OcfWarrantConversionMechanism(..), OcfWarrantConversionRight(..)) import Fairmint.OpenCapTable.Types.Stock (OcfAuthorizedShares(..), OcfInitialSharesAuthorized(..), OcfRatio(..), OcfShareNumberRange(..), shareNumberRangeCoveredByRanges, shareNumberRangesOverlap) -import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..)) import qualified Fairmint.OpenCapTable.OCF.ConvertibleIssuance as ConvertibleIssuance (OcfConvertibleConversionTrigger(..)) import Fairmint.OpenCapTable.OCF.Issuer (Issuer(..), IssuerOcfData) import Fairmint.OpenCapTable.OCF.Document (Document(..), DocumentOcfData, OcfObjectReference(..), OcfObjectType(..)) @@ -180,11 +180,9 @@ data StockClassShareEventIndex = StockClassShareEventIndex with stock_class_split_events_by_stock_class_id: Map Text [StockClassShareEvent] stock_plan_retraction_events_by_security_id: Optional (Map Text [StockClassShareEvent]) stock_successor_sort_keys_by_security_id: Optional (Map Text (Time, Int, Int)) + stock_security_balance_events_by_security_id: Optional (Map Text [StockClassShareEvent]) deriving (Eq, Show) -type StockSuccessorEdge = (Text, Time, [Text]) -type StockSuccessorEdgeIndex = Map Text [(Time, [Text])] - data SecurityQuantityEvent = SecurityQuantityDelta with event_date: Time @@ -203,6 +201,14 @@ data SecurityQuantityEvent event_priority: Int deriving (Eq, Show) +data NonStockSecurityBalanceEventIndex = NonStockSecurityBalanceEventIndex with + warrant_events_by_security_id: Map Text [SecurityQuantityEvent] + equity_compensation_events_by_security_id: Map Text [SecurityQuantityEvent] + deriving (Eq, Show) + +type StockSuccessorEdge = (Text, Time, [Text]) +type StockSuccessorEdgeIndex = Map Text [(Time, [Text])] + data StockPlanSuccessorIndex = StockPlanSuccessorIndex with stock_successors_by_security_id: Map Text (Time, Optional Text) equity_compensation_successors_by_security_id: Map Text (Time, Optional Text) @@ -590,103 +596,16 @@ convertibleConversionOutputShareLot stockClassIndex issuanceData conversionData False pure None -securityQuantityReductionEvent : Time -> Decimal -> Optional Text -> SecurityQuantityEvent -securityQuantityReductionEvent eventDate quantity balanceSecurityId = - case balanceSecurityId of - Some _ -> - SecurityQuantityTerminalDelta with - event_date = eventDate - event_priority = 2 - quantity_delta = 0.0 - quantity - None -> - SecurityQuantityDelta with - event_date = eventDate - event_priority = 1 - quantity_delta = 0.0 - quantity - -securityQuantityEventSortKey : SecurityQuantityEvent -> (Time, Int, Int) -securityQuantityEventSortKey event = case event of - SecurityQuantityDelta with event_date; event_priority -> (event_date, event_priority, 0) - SecurityQuantityTerminalDelta with event_date; event_priority -> (event_date, event_priority, 1) - SecurityQuantityMultiplier with event_date; event_priority -> (event_date, event_priority, 2) - SecurityQuantityRetraction with event_date; event_priority -> (event_date, event_priority, 3) - -securityQuantityEventDate : SecurityQuantityEvent -> Time -securityQuantityEventDate event = case event of - SecurityQuantityDelta with event_date -> event_date - SecurityQuantityTerminalDelta with event_date -> event_date - SecurityQuantityMultiplier with event_date -> event_date - SecurityQuantityRetraction with event_date -> event_date - -assertSecurityQuantityEventsOnOrAfter : Text -> Time -> [SecurityQuantityEvent] -> Update () -assertSecurityQuantityEventsOnOrAfter securityId earliest events = - mapA_ - (\event -> - assertMsg - ("Security quantity event cannot precede issuance for " <> securityId <> ": event date " <> show (securityQuantityEventDate event) <> " is before " <> show earliest) - (securityQuantityEventDate event >= earliest)) - events - -validateSecurityQuantityBalanceEvent : Text -> Optional Decimal -> SecurityQuantityEvent -> Update (Optional Decimal) -validateSecurityQuantityBalanceEvent securityId currentOpt event = case currentOpt of - Some current -> case event of - SecurityQuantityDelta with quantity_delta -> do - let next = current + quantity_delta - assertMsg - ("Security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) - (next >= 0.0) - pure (Some next) - SecurityQuantityTerminalDelta with quantity_delta -> do - let next = current + quantity_delta - assertMsg - ("Security terminal reduction requires a positive balance for " <> securityId <> ": current balance is " <> show current) - (current > 0.0) - assertMsg - ("Security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) - (next >= 0.0) - pure (Some 0.0) - SecurityQuantityMultiplier with ratio -> - pure (Some (current * ratio.numerator / ratio.denominator)) - SecurityQuantityRetraction _ _ -> do - assertMsg - ("Security retraction requires a positive balance for " <> securityId <> ": current balance is " <> show current) - (current > 0.0) - pure (Some 0.0) - None -> case event of - SecurityQuantityDelta {} -> pure None - SecurityQuantityTerminalDelta {} -> pure (Some 0.0) - SecurityQuantityMultiplier {} -> pure None - SecurityQuantityRetraction _ _ -> pure (Some 0.0) - -validateSecurityQuantityBalance : Text -> Optional Decimal -> [SecurityQuantityEvent] -> Update () -validateSecurityQuantityBalance securityId initialQuantity events = do - _ <- foldlA - (validateSecurityQuantityBalanceEvent securityId) - initialQuantity - (sortOn securityQuantityEventSortKey events) - pure () - -securityQuantityBeforeEvent : - Text -> - Optional Decimal -> - [SecurityQuantityEvent] -> - (Time, Int, Int) -> - Update (Optional Decimal) -securityQuantityBeforeEvent securityId initialQuantity events targetSortKey = - foldlA - (validateSecurityQuantityBalanceEvent securityId) - initialQuantity - (filter - (\event -> securityQuantityEventSortKey event < targetSortKey) - (sortOn securityQuantityEventSortKey events)) - -warrantSecurityQuantityEvents : CapTableMaps -> WarrantIssuanceOcfData -> Update [SecurityQuantityEvent] -warrantSecurityQuantityEvents maps issuanceData = do +warrantExerciseOutputSecurityEvents : CapTableMaps -> WarrantIssuanceOcfData -> Update [StockClassShareEvent] +warrantExerciseOutputSecurityEvents maps issuanceData = do transferEvents <- foldlA (\events transferCid -> do transfer <- fetch transferCid if transfer.transfer_data.security_id == issuanceData.security_id - then pure (securityQuantityReductionEvent transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id :: events) + then pure (stockSecurityReductionEvents + transfer.transfer_data.date + transfer.transfer_data.quantity + transfer.transfer_data.balance_security_id ++ events) else pure events) [] (Map.values maps.warrant_transfers) @@ -695,9 +614,12 @@ warrantSecurityQuantityEvents maps issuanceData = do (\events cancellationCid -> do cancellation <- fetch cancellationCid if cancellation.cancellation_data.security_id == issuanceData.security_id - then pure (securityQuantityReductionEvent cancellation.cancellation_data.date cancellation.cancellation_data.quantity cancellation.cancellation_data.balance_security_id :: events) + then pure (stockSecurityReductionEvents + cancellation.cancellation_data.date + cancellation.cancellation_data.quantity + cancellation.cancellation_data.balance_security_id ++ events) else pure events) - [] + transferEvents (Map.values maps.warrant_cancellations) exerciseEvents <- foldlA @@ -706,80 +628,56 @@ warrantSecurityQuantityEvents maps issuanceData = do if exercise.exercise_data.security_id == issuanceData.security_id then case exercise.exercise_data.quantity of Some quantity -> - pure ((SecurityQuantityDelta with + pure ((StockClassShareDelta with event_date = exercise.exercise_data.date event_priority = 1 quantity_delta = 0.0 - quantity) :: events) None -> - pure ((SecurityQuantityRetraction with + pure ((StockClassShareRetraction with event_date = exercise.exercise_data.date event_priority = 3) :: events) else pure events) - [] + cancellationEvents (Map.values maps.warrant_exercises) - retractionEvents <- foldlA + foldlA (\events retractionCid -> do retraction <- fetch retractionCid if retraction.retraction_data.security_id == issuanceData.security_id - then pure ((SecurityQuantityRetraction with + then pure ((StockClassShareRetraction with event_date = retraction.retraction_data.date event_priority = 3) :: events) else pure events) - [] + exerciseEvents (Map.values maps.warrant_retractions) - pure (transferEvents ++ cancellationEvents ++ exerciseEvents ++ retractionEvents) - -validateWarrantSecurityBalance : CapTableMaps -> WarrantIssuanceOcfData -> Update () -validateWarrantSecurityBalance maps issuanceData = do - events <- warrantSecurityQuantityEvents maps issuanceData - case issuanceData.quantity of - Some quantity -> - validateSecurityQuantityBalance - issuanceData.security_id - (Some 0.0) - ((SecurityQuantityDelta with - event_date = issuanceData.date - event_priority = 0 - quantity_delta = quantity) - :: events) - None -> do - assertSecurityQuantityEventsOnOrAfter issuanceData.security_id issuanceData.date events - validateSecurityQuantityBalance issuanceData.security_id None events - -validateWarrantSecurityBalances : CapTableMaps -> Update () -validateWarrantSecurityBalances maps = - mapA_ - (\issuanceCid -> do - issuance <- fetch issuanceCid - validateWarrantSecurityBalance maps issuance.issuance_data) - (Map.values maps.warrant_issuances) - -warrantSecurityQuantityBeforeTerminal : +warrantExerciseOutputQuantityBeforeTerminal : CapTableMaps -> WarrantIssuanceOcfData -> Time -> Update (Optional Decimal) -warrantSecurityQuantityBeforeTerminal maps issuanceData eventDate = do - events <- warrantSecurityQuantityEvents maps issuanceData - case issuanceData.quantity of - Some quantity -> - securityQuantityBeforeEvent - issuanceData.security_id - (Some 0.0) - ((SecurityQuantityDelta with +warrantExerciseOutputQuantityBeforeTerminal maps issuanceData eventDate = do + events <- warrantExerciseOutputSecurityEvents maps issuanceData + let initialEvents = case issuanceData.quantity of + Some quantity -> + (StockClassShareDelta with event_date = issuanceData.date event_priority = 0 quantity_delta = quantity) :: events - ) - (eventDate, 2, 1) - None -> - securityQuantityBeforeEvent issuanceData.security_id None events (eventDate, 2, 1) + None -> events + let initialQuantity = case issuanceData.quantity of + Some _ -> Some 0.0 + None -> None + pure (foldl + applyStockClassShareEventOptional + initialQuantity + (filter + (\event -> stockClassShareEventSortKey event < (eventDate, 2, 1)) + (sortOn stockClassShareEventSortKey initialEvents))) warrantExerciseQuantity : CapTableMaps -> WarrantIssuanceOcfData -> WarrantExerciseOcfData -> OcfWarrantConversionRight -> Update (Optional Decimal) warrantExerciseQuantity maps issuanceData exerciseData conversionRight = @@ -787,7 +685,7 @@ warrantExerciseQuantity maps issuanceData exerciseData conversionRight = OcfWarrantMechanismFixedAmount with converts_to_quantity -> do exercisedQuantityOpt <- case exerciseData.quantity of Some exercisedQuantity -> pure (Some exercisedQuantity) - None -> warrantSecurityQuantityBeforeTerminal maps issuanceData exerciseData.date + None -> warrantExerciseOutputQuantityBeforeTerminal maps issuanceData exerciseData.date case (issuanceData.quantity, exercisedQuantityOpt) of (Some issuedQuantity, Some exercisedQuantity) -> if issuedQuantity > 0.0 @@ -1062,22 +960,8 @@ stockClassShareLotsByStockClass maps stockClassIndex = do (\index transferCid -> do transfer <- fetch transferCid let label = "Stock transfer " <> transfer.transfer_data.id - assertNoPartialResultingStockIssuanceCoverage label maps transfer.transfer_data.resulting_security_ids - if allResultingStockIssuancesExist maps transfer.transfer_data.resulting_security_ids - then pure index - else case Map.lookup transfer.transfer_data.security_id maps.stock_issuances_by_security_id of - Some sourceIssuanceCid -> do - sourceIssuance <- fetch sourceIssuanceCid - pure (addStockClassShareLotByStockClass - (StockClassShareLot with - stock_class_id = sourceIssuance.issuance_data.stock_class_id - security_id = None - event_date = transfer.transfer_data.date - quantity = transfer.transfer_data.quantity) - index) - None -> do - assertMsg (label <> " references missing source stock issuance: " <> transfer.transfer_data.security_id) False - pure index) + assertAllResultingStockIssuancesExist label maps transfer.transfer_data.resulting_security_ids + pure index) stockIssuanceLots (Map.values maps.stock_transfers) @@ -1420,13 +1304,6 @@ stockClassShareEventDate event = case event of StockClassShareMultiplier with event_date -> event_date StockClassShareRetraction with event_date -> event_date -stockClassShareEventOnOrAfter : Time -> StockClassShareEvent -> Bool -stockClassShareEventOnOrAfter earliest event = case event of - StockClassShareDelta with event_date -> event_date >= earliest - StockClassShareTerminalDelta with event_date -> event_date >= earliest - StockClassShareMultiplier with event_date -> event_date >= earliest - StockClassShareRetraction with event_date -> event_date >= earliest - stockClassShareEventOnOrBefore : Time -> StockClassShareEvent -> Bool stockClassShareEventOnOrBefore latest event = case event of StockClassShareDelta with event_date -> event_date <= latest @@ -1466,6 +1343,20 @@ stockSecurityReductionEvents eventDate quantity balanceSecurityId = case balance quantity_delta = 0.0 - quantity ] +securityQuantityReductionEvent : Time -> Decimal -> Optional Text -> SecurityQuantityEvent +securityQuantityReductionEvent eventDate quantity balanceSecurityId = + case balanceSecurityId of + Some _ -> + SecurityQuantityTerminalDelta with + event_date = eventDate + event_priority = 2 + quantity_delta = 0.0 - quantity + None -> + SecurityQuantityDelta with + event_date = eventDate + event_priority = 1 + quantity_delta = 0.0 - quantity + addStockClassShareEvent : Text -> StockClassShareEvent -> Map Text [StockClassShareEvent] -> Map Text [StockClassShareEvent] addStockClassShareEvent key event index = Map.insert key (event :: fromOptional [] (Map.lookup key index)) index @@ -1530,85 +1421,114 @@ propagateStockSuccessorSortKeys edges initialSortKeys = stockClassShareEventIndex : CapTableMaps -> Update StockClassShareEventIndex stockClassShareEventIndex maps = do - cancellationEventsBySecurityId <- foldlA - (\index cancellationCid -> do + (cancellationEventsBySecurityId, cancellationBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) cancellationCid -> do cancellation <- fetch cancellationCid let events = stockSecurityReductionEvents cancellation.cancellation_data.date cancellation.cancellation_data.quantity cancellation.cancellation_data.balance_security_id - if stockBalanceSecurityIssuanceExists maps cancellation.cancellation_data.balance_security_id - then pure (addStockClassShareEvents cancellation.cancellation_data.security_id events index) - else pure index) - Map.empty + let nextShareIndex = + if stockBalanceSecurityIssuanceExists maps cancellation.cancellation_data.balance_security_id + then addStockClassShareEvents cancellation.cancellation_data.security_id events shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEvents cancellation.cancellation_data.security_id events balanceIndex + )) + (Map.empty, Map.empty) (Map.values maps.stock_cancellations) - repurchaseEventsBySecurityId <- foldlA - (\index repurchaseCid -> do + (repurchaseEventsBySecurityId, repurchaseBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) repurchaseCid -> do repurchase <- fetch repurchaseCid let events = stockSecurityReductionEvents repurchase.repurchase_data.date repurchase.repurchase_data.quantity repurchase.repurchase_data.balance_security_id - if stockBalanceSecurityIssuanceExists maps repurchase.repurchase_data.balance_security_id - then pure (addStockClassShareEvents repurchase.repurchase_data.security_id events index) - else pure index) - cancellationEventsBySecurityId + let nextShareIndex = + if stockBalanceSecurityIssuanceExists maps repurchase.repurchase_data.balance_security_id + then addStockClassShareEvents repurchase.repurchase_data.security_id events shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEvents repurchase.repurchase_data.security_id events balanceIndex + )) + (cancellationEventsBySecurityId, cancellationBalanceEventsBySecurityId) (Map.values maps.stock_repurchases) - conversionEventsBySecurityId <- foldlA - (\index conversionCid -> do + (conversionEventsBySecurityId, conversionBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) conversionCid -> do conversion <- fetch conversionCid let events = stockSecurityReductionEvents conversion.conversion_data.date conversion.conversion_data.quantity_converted conversion.conversion_data.balance_security_id - if stockBalanceSecurityIssuanceExists maps conversion.conversion_data.balance_security_id - then pure (addStockClassShareEvents conversion.conversion_data.security_id events index) - else pure index) - repurchaseEventsBySecurityId + let nextShareIndex = + if stockBalanceSecurityIssuanceExists maps conversion.conversion_data.balance_security_id + then addStockClassShareEvents conversion.conversion_data.security_id events shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEvents conversion.conversion_data.security_id events balanceIndex + )) + (repurchaseEventsBySecurityId, repurchaseBalanceEventsBySecurityId) (Map.values maps.stock_conversions) - transferEventsBySecurityId <- foldlA - (\index transferCid -> do + (transferEventsBySecurityId, transferBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) transferCid -> do transfer <- fetch transferCid assertNoPartialResultingStockIssuanceCoverage ("Stock transfer " <> transfer.transfer_data.id) maps transfer.transfer_data.resulting_security_ids - let events = stockSecurityReductionEvents transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id - pure (addStockClassShareEvents transfer.transfer_data.security_id events index)) - conversionEventsBySecurityId + let shareEvents = stockSecurityReductionEvents transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id + let nextShareIndex = + if stockBalanceSecurityIssuanceExists maps transfer.transfer_data.balance_security_id + then addStockClassShareEvents transfer.transfer_data.security_id shareEvents shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEvents transfer.transfer_data.security_id shareEvents balanceIndex + )) + (conversionEventsBySecurityId, conversionBalanceEventsBySecurityId) (Map.values maps.stock_transfers) - reissuanceEventsBySecurityId <- foldlA - (\index reissuanceCid -> do + (reissuanceEventsBySecurityId, reissuanceBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) reissuanceCid -> do reissuance <- fetch reissuanceCid assertNoPartialResultingStockIssuanceCoverage ("Stock reissuance " <> reissuance.reissuance_data.id) maps reissuance.reissuance_data.resulting_security_ids - if allResultingStockIssuancesExist maps reissuance.reissuance_data.resulting_security_ids - then pure (addStockClassShareEvent - reissuance.reissuance_data.security_id - (StockClassShareRetraction with + let event = StockClassShareRetraction with event_date = reissuance.reissuance_data.date - event_priority = 3) - index) - else pure index) - transferEventsBySecurityId + event_priority = 3 + let nextShareIndex = + if allResultingStockIssuancesExist maps reissuance.reissuance_data.resulting_security_ids + then addStockClassShareEvent reissuance.reissuance_data.security_id event shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEvent reissuance.reissuance_data.security_id event balanceIndex + )) + (transferEventsBySecurityId, transferBalanceEventsBySecurityId) (Map.values maps.stock_reissuances) - consolidationEventsBySecurityId <- foldlA - (\index consolidationCid -> do + (consolidationEventsBySecurityId, consolidationBalanceEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex) consolidationCid -> do consolidation <- fetch consolidationCid - if isSome (Map.lookup consolidation.consolidation_data.resulting_security_id maps.stock_issuances_by_security_id) - then pure (addStockClassShareEventForKeys - consolidation.consolidation_data.security_ids - (StockClassShareRetraction with + let event = StockClassShareRetraction with event_date = consolidation.consolidation_data.date - event_priority = 3) - index) - else pure index) - reissuanceEventsBySecurityId + event_priority = 3 + let nextShareIndex = + if isSome (Map.lookup consolidation.consolidation_data.resulting_security_id maps.stock_issuances_by_security_id) + then addStockClassShareEventForKeys consolidation.consolidation_data.security_ids event shareIndex + else shareIndex + pure + ( nextShareIndex + , addStockClassShareEventForKeys consolidation.consolidation_data.security_ids event balanceIndex + )) + (reissuanceEventsBySecurityId, reissuanceBalanceEventsBySecurityId) (Map.values maps.stock_consolidations) - (retractionEventsBySecurityId, planRetractionEventsBySecurityId) <- foldlA - (\(shareIndex, planIndex) retractionCid -> do + (retractionEventsBySecurityId, retractionBalanceEventsBySecurityId, planRetractionEventsBySecurityId) <- foldlA + (\(shareIndex, balanceIndex, planIndex) retractionCid -> do retraction <- fetch retractionCid let event = StockClassShareRetraction with event_date = retraction.retraction_data.date event_priority = 3 pure ( addStockClassShareEvent retraction.retraction_data.security_id event shareIndex + , addStockClassShareEvent retraction.retraction_data.security_id event balanceIndex , addStockClassShareEvent retraction.retraction_data.security_id event planIndex )) - (consolidationEventsBySecurityId, Map.empty) + (consolidationEventsBySecurityId, consolidationBalanceEventsBySecurityId, Map.empty) (Map.values maps.stock_retractions) splitEventsByStockClassId <- foldlA @@ -1688,6 +1608,7 @@ stockClassShareEventIndex maps = do pure StockClassShareEventIndex with stock_security_events_by_security_id = retractionEventsBySecurityId + stock_security_balance_events_by_security_id = Some retractionBalanceEventsBySecurityId stock_plan_retraction_events_by_security_id = Some planRetractionEventsBySecurityId stock_class_split_events_by_stock_class_id = splitEventsByStockClassId stock_successor_sort_keys_by_security_id = Some propagatedSuccessorSortKeys @@ -1759,129 +1680,36 @@ stockClassShareLotPlanAllocatedQuantityAt eventIndex lot asOf = (filter (stockClassShareEventOnOrBefore asOf) (stockClassShareLotPlanAllocationEvents eventIndex lot))) else Some 0.0 -stockClassShareLotCurrentQuantity : StockClassShareEventIndex -> StockClassShareLot -> Optional Decimal -stockClassShareLotCurrentQuantity eventIndex lot = - foldl - applyStockClassShareEventOptional - (Some 0.0) - (sortOn stockClassShareEventSortKey (stockClassShareLotEvents eventIndex lot)) - -stockClassShareLotQuantityAt : StockClassShareEventIndex -> StockClassShareLot -> Time -> Optional Decimal -stockClassShareLotQuantityAt eventIndex lot asOf = - if lot.event_date <= asOf - then - foldl - applyStockClassShareEventOptional - (Some 0.0) - (sortOn - stockClassShareEventSortKey - (filter (stockClassShareEventOnOrBefore asOf) (stockClassShareLotEvents eventIndex lot))) - else Some 0.0 - -stockSecurityShareEvents : StockClassShareEventIndex -> CapTableMaps -> StockIssuanceOcfData -> Update [StockClassShareEvent] -stockSecurityShareEvents eventIndex maps issuanceData = do - cancellationEvents <- foldlA - (\events cancellationCid -> do - cancellation <- fetch cancellationCid - if cancellation.cancellation_data.security_id == issuanceData.security_id - then pure (stockSecurityReductionEvents cancellation.cancellation_data.date cancellation.cancellation_data.quantity cancellation.cancellation_data.balance_security_id ++ events) - else pure events) - [] - (Map.values maps.stock_cancellations) - - repurchaseEvents <- foldlA - (\events repurchaseCid -> do - repurchase <- fetch repurchaseCid - if repurchase.repurchase_data.security_id == issuanceData.security_id - then pure (stockSecurityReductionEvents repurchase.repurchase_data.date repurchase.repurchase_data.quantity repurchase.repurchase_data.balance_security_id ++ events) - else pure events) - [] - (Map.values maps.stock_repurchases) - - transferEvents <- foldlA - (\events transferCid -> do - transfer <- fetch transferCid - if transfer.transfer_data.security_id == issuanceData.security_id - then pure (stockSecurityReductionEvents transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id ++ events) - else pure events) - [] - (Map.values maps.stock_transfers) - - conversionEvents <- foldlA - (\events conversionCid -> do - conversion <- fetch conversionCid - if conversion.conversion_data.security_id == issuanceData.security_id - then pure (stockSecurityReductionEvents conversion.conversion_data.date conversion.conversion_data.quantity_converted conversion.conversion_data.balance_security_id ++ events) - else pure events) - [] - (Map.values maps.stock_conversions) - - retractionEvents <- foldlA - (\events retractionCid -> do - retraction <- fetch retractionCid - if retraction.retraction_data.security_id == issuanceData.security_id - then pure ((StockClassShareRetraction with - event_date = retraction.retraction_data.date - event_priority = 3) - :: events) - else pure events) - [] - (Map.values maps.stock_retractions) - - reissuanceEvents <- foldlA - (\events reissuanceCid -> do - reissuance <- fetch reissuanceCid - if reissuance.reissuance_data.security_id == issuanceData.security_id - then pure ((StockClassShareRetraction with - event_date = reissuance.reissuance_data.date - event_priority = 3) - :: events) - else pure events) - [] - (Map.values maps.stock_reissuances) - - consolidationEvents <- foldlA - (\events consolidationCid -> do - consolidation <- fetch consolidationCid - if issuanceData.security_id `elem` consolidation.consolidation_data.security_ids - then pure ((StockClassShareRetraction with - event_date = consolidation.consolidation_data.date - event_priority = 3) - :: events) - else pure events) - [] - (Map.values maps.stock_consolidations) - - let splitCutoffSortKey = stockClassShareLotSplitCutoffSortKey eventIndex (stockIssuanceShareLot issuanceData) - splitEvents <- foldlA - (\events splitCid -> do - split <- fetch splitCid - let splitSortKey = (split.split_data.date, 2, 2) - if split.split_data.stock_class_id == issuanceData.stock_class_id && splitSortKey >= splitCutoffSortKey - then pure ((StockClassShareMultiplier with - event_date = split.split_data.date - event_priority = 2 - ratio = split.split_data.split_ratio) - :: events) - else pure events) - [] - (Map.values maps.stock_class_splits) - - pure ((StockClassShareDelta with +stockSecurityShareEvents : StockClassShareEventIndex -> StockIssuanceOcfData -> [StockClassShareEvent] +stockSecurityShareEvents eventIndex issuanceData = + let balanceEventsBySecurityId = fromOptional Map.empty eventIndex.stock_security_balance_events_by_security_id + securityEvents = fromOptional [] (Map.lookup issuanceData.security_id balanceEventsBySecurityId) + splitCutoffSortKey = stockClassShareLotSplitCutoffSortKey eventIndex (stockIssuanceShareLot issuanceData) + splitEvents = + filter + (\event -> stockClassShareEventSortKey event >= splitCutoffSortKey) + (fromOptional [] (Map.lookup issuanceData.stock_class_id eventIndex.stock_class_split_events_by_stock_class_id)) + in (StockClassShareDelta with event_date = issuanceData.date event_priority = 0 quantity_delta = issuanceData.quantity) - :: (cancellationEvents ++ repurchaseEvents ++ transferEvents ++ conversionEvents ++ retractionEvents ++ reissuanceEvents ++ consolidationEvents ++ splitEvents)) + :: (securityEvents ++ splitEvents) validateStockSecurityBalanceEvent : Text -> Decimal -> StockClassShareEvent -> Update Decimal validateStockSecurityBalanceEvent securityId current event = case event of - StockClassShareDelta with quantity_delta -> do + StockClassShareDelta with event_priority; quantity_delta -> do + assertMsg + ("Stock security reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) + (event_priority == 0 || quantity_delta <= 0.0) let next = current + quantity_delta assertMsg ("Stock security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) (next >= 0.0) pure next StockClassShareTerminalDelta with quantity_delta -> do + assertMsg + ("Stock security terminal reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) + (quantity_delta <= 0.0) let next = current + quantity_delta assertMsg ("Stock security terminal reduction requires a positive balance for " <> securityId <> ": current balance is " <> show current) @@ -1898,37 +1726,937 @@ validateStockSecurityBalanceEvent securityId current event = case event of (current > 0.0) pure 0.0 -validateStockSecurityBalance : StockClassShareEventIndex -> CapTableMaps -> StockIssuanceOcfData -> Update () -validateStockSecurityBalance eventIndex maps issuanceData = do - events <- stockSecurityShareEvents eventIndex maps issuanceData +validateStockSecurityBalance : StockClassShareEventIndex -> StockIssuanceOcfData -> Update () +validateStockSecurityBalance eventIndex issuanceData = do + let events = stockSecurityShareEvents eventIndex issuanceData _ <- foldlA (validateStockSecurityBalanceEvent issuanceData.security_id) 0.0 (sortOn stockClassShareEventSortKey events) pure () -validateStockSecurityBalances : StockClassShareEventIndex -> CapTableMaps -> Update () -validateStockSecurityBalances eventIndex maps = - mapA_ - (\issuanceCid -> do +validateStockSecurityBalances : StockClassShareEventIndex -> CapTableMaps -> Update () +validateStockSecurityBalances eventIndex maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateStockSecurityBalance eventIndex issuance.issuance_data) + (Map.values maps.stock_issuances) + +stockSecurityQuantityBeforeEvent : + StockClassShareEventIndex -> + CapTableMaps -> + StockIssuanceOcfData -> + (Time, Int, Int) -> + Update Decimal +stockSecurityQuantityBeforeEvent eventIndex _maps issuanceData targetSortKey = + foldlA + (validateStockSecurityBalanceEvent issuanceData.security_id) + 0.0 + (filter + (\event -> stockClassShareEventSortKey event < targetSortKey) + (sortOn stockClassShareEventSortKey (stockSecurityShareEvents eventIndex issuanceData))) + +stockClassQuantityAtSortKey : + CapTableMaps -> + Text -> + Time -> + Decimal -> + (Time, Int, Int) -> + Update Decimal +stockClassQuantityAtSortKey maps stockClassId quantityDate quantity targetSortKey = + let (targetDate, _, _) = targetSortKey + quantitySortKey = if quantityDate == targetDate then targetSortKey else (quantityDate, 0, 0) + in foldlA + (\adjustedQuantity splitCid -> do + split <- fetch splitCid + let splitSortKey = (split.split_data.date, 2, 2) + ratio = split.split_data.split_ratio + if split.split_data.stock_class_id /= stockClassId + then pure adjustedQuantity + else if quantitySortKey < targetSortKey && quantitySortKey < splitSortKey && splitSortKey <= targetSortKey + then pure (adjustedQuantity * ratio.numerator / ratio.denominator) + else if targetSortKey < quantitySortKey && targetSortKey < splitSortKey && splitSortKey < quantitySortKey + then pure (adjustedQuantity * ratio.denominator / ratio.numerator) + else pure adjustedQuantity) + quantity + (Map.values maps.stock_class_splits) + +validateStockBalanceSuccessorQuantity : + CapTableMaps -> + Text -> + StockIssuanceOcfData -> + Time -> + (Time, Int, Int) -> + Decimal -> + Text -> + Update () +validateStockBalanceSuccessorQuantity maps label sourceIssuanceData eventDate targetSortKey expectedQuantity securityId = + case Map.lookup securityId maps.stock_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + (label <> " balance result date mismatch for " <> securityId <> ": expected " <> show eventDate <> ", found " <> show issuance.issuance_data.date) + (issuance.issuance_data.date == eventDate) + assertMsg + (label <> " balance stock class mismatch for " <> securityId <> ": expected " <> sourceIssuanceData.stock_class_id <> ", found " <> issuance.issuance_data.stock_class_id) + (issuance.issuance_data.stock_class_id == sourceIssuanceData.stock_class_id) + actualQuantity <- stockClassQuantityAtSortKey + maps + issuance.issuance_data.stock_class_id + issuance.issuance_data.date + issuance.issuance_data.quantity + targetSortKey + assertMsg + (label <> " balance quantity mismatch for " <> securityId <> ": expected " <> show expectedQuantity <> ", found " <> show actualQuantity) + (actualQuantity == expectedQuantity) + case sourceIssuanceData.share_numbers_issued of + [] -> pure () + sourceShareNumberRanges -> + assertMsg + (label <> " balance result share numbers are outside the source issuance range for " <> securityId) + (not (null issuance.issuance_data.share_numbers_issued) && + all + (shareNumberRangeCoveredByRanges sourceShareNumberRanges) + issuance.issuance_data.share_numbers_issued) + None -> + assertMsg (label <> " balance stock issuance not found: " <> securityId) False + +validateStockTerminalBalanceSuccessor : + StockClassShareEventIndex -> + CapTableMaps -> + Text -> + StockIssuanceOcfData -> + Time -> + Decimal -> + Optional Text -> + Update () +validateStockTerminalBalanceSuccessor eventIndex maps label sourceIssuanceData eventDate reductionQuantity balanceSecurityId = + case balanceSecurityId of + Some securityId -> do + let targetSortKey = (eventDate, 2, 1) + currentQuantity <- stockSecurityQuantityBeforeEvent eventIndex maps sourceIssuanceData targetSortKey + let expectedQuantity = currentQuantity - reductionQuantity + assertMsg + (label <> " exceeds the current source quantity for " <> sourceIssuanceData.security_id) + (expectedQuantity >= 0.0) + validateStockBalanceSuccessorQuantity + maps + label + sourceIssuanceData + eventDate + targetSortKey + expectedQuantity + securityId + None -> pure () + +validateStockTerminalBalanceSuccessorForSource : + StockClassShareEventIndex -> + CapTableMaps -> + Text -> + Text -> + Time -> + Decimal -> + Optional Text -> + Update () +validateStockTerminalBalanceSuccessorForSource eventIndex maps label sourceSecurityId eventDate reductionQuantity balanceSecurityId = + case Map.lookup sourceSecurityId maps.stock_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + validateStockTerminalBalanceSuccessor + eventIndex + maps + label + sourceIssuance.issuance_data + eventDate + reductionQuantity + balanceSecurityId + None -> assertMsg (label <> " source stock issuance not found: " <> sourceSecurityId) False + +validateStockBalanceSuccessorQuantities : StockClassShareEventIndex -> CapTableMaps -> Update () +validateStockBalanceSuccessorQuantities eventIndex maps = do + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateStockTerminalBalanceSuccessorForSource + eventIndex + maps + ("Stock cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + cancellation.cancellation_data.date + cancellation.cancellation_data.quantity + cancellation.cancellation_data.balance_security_id) + (Map.values maps.stock_cancellations) + + mapA_ + (\repurchaseCid -> do + repurchase <- fetch repurchaseCid + validateStockTerminalBalanceSuccessorForSource + eventIndex + maps + ("Stock repurchase " <> repurchase.repurchase_data.id) + repurchase.repurchase_data.security_id + repurchase.repurchase_data.date + repurchase.repurchase_data.quantity + repurchase.repurchase_data.balance_security_id) + (Map.values maps.stock_repurchases) + + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateStockTerminalBalanceSuccessorForSource + eventIndex + maps + ("Stock transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + transfer.transfer_data.date + transfer.transfer_data.quantity + transfer.transfer_data.balance_security_id) + (Map.values maps.stock_transfers) + + mapA_ + (\conversionCid -> do + conversion <- fetch conversionCid + validateStockTerminalBalanceSuccessorForSource + eventIndex + maps + ("Stock conversion " <> conversion.conversion_data.id) + conversion.conversion_data.security_id + conversion.conversion_data.date + conversion.conversion_data.quantity_converted + conversion.conversion_data.balance_security_id) + (Map.values maps.stock_conversions) + +securityQuantityEventSortKey : SecurityQuantityEvent -> (Time, Int, Int) +securityQuantityEventSortKey event = case event of + SecurityQuantityDelta with event_date; event_priority -> (event_date, event_priority, 0) + SecurityQuantityTerminalDelta with event_date; event_priority -> (event_date, event_priority, 1) + SecurityQuantityMultiplier with event_date; event_priority -> (event_date, event_priority, 2) + SecurityQuantityRetraction with event_date; event_priority -> (event_date, event_priority, 3) + +securityQuantityEventDate : SecurityQuantityEvent -> Time +securityQuantityEventDate event = case event of + SecurityQuantityDelta with event_date -> event_date + SecurityQuantityTerminalDelta with event_date -> event_date + SecurityQuantityMultiplier with event_date -> event_date + SecurityQuantityRetraction with event_date -> event_date + +assertSecurityQuantityEventsOnOrAfter : Text -> Time -> [SecurityQuantityEvent] -> Update () +assertSecurityQuantityEventsOnOrAfter securityId earliest events = + mapA_ + (\event -> + assertMsg + ("Security quantity event cannot precede issuance for " <> securityId <> ": event date " <> show (securityQuantityEventDate event) <> " is before " <> show earliest) + (securityQuantityEventDate event >= earliest)) + events + +validateSecurityQuantityBalanceEvent : Text -> Optional Decimal -> SecurityQuantityEvent -> Update (Optional Decimal) +validateSecurityQuantityBalanceEvent securityId currentOpt event = do + case event of + SecurityQuantityDelta with event_priority; quantity_delta -> + assertMsg + ("Security reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) + (event_priority == 0 || quantity_delta <= 0.0) + SecurityQuantityTerminalDelta with quantity_delta -> + assertMsg + ("Security terminal reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) + (quantity_delta <= 0.0) + _ -> pure () + case currentOpt of + Some current -> case event of + SecurityQuantityDelta with quantity_delta -> do + let next = current + quantity_delta + assertMsg + ("Security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) + (next >= 0.0) + pure (Some next) + SecurityQuantityTerminalDelta with quantity_delta -> do + let next = current + quantity_delta + assertMsg + ("Security terminal reduction requires a positive balance for " <> securityId <> ": current balance is " <> show current) + (current > 0.0) + assertMsg + ("Security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) + (next >= 0.0) + pure (Some 0.0) + SecurityQuantityMultiplier with ratio -> + pure (Some (current * ratio.numerator / ratio.denominator)) + SecurityQuantityRetraction _ _ -> do + assertMsg + ("Security retraction requires a positive balance for " <> securityId <> ": current balance is " <> show current) + (current > 0.0) + pure (Some 0.0) + None -> case event of + SecurityQuantityDelta {} -> + pure None + SecurityQuantityTerminalDelta {} -> + pure (Some 0.0) + SecurityQuantityMultiplier {} -> + pure None + SecurityQuantityRetraction _ _ -> + pure (Some 0.0) + +validateSecurityQuantityBalance : Text -> Optional Decimal -> [SecurityQuantityEvent] -> Update () +validateSecurityQuantityBalance securityId initialQuantity events = do + _ <- foldlA + (validateSecurityQuantityBalanceEvent securityId) + initialQuantity + (sortOn securityQuantityEventSortKey events) + pure () + +securityQuantityBeforeEvent : + Text -> + Optional Decimal -> + [SecurityQuantityEvent] -> + (Time, Int, Int) -> + Update (Optional Decimal) +securityQuantityBeforeEvent securityId initialQuantity events targetSortKey = + foldlA + (validateSecurityQuantityBalanceEvent securityId) + initialQuantity + (filter + (\event -> securityQuantityEventSortKey event < targetSortKey) + (sortOn securityQuantityEventSortKey events)) + +addSecurityQuantityEvent : Text -> SecurityQuantityEvent -> Map Text [SecurityQuantityEvent] -> Map Text [SecurityQuantityEvent] +addSecurityQuantityEvent key event index = + Map.insert key (event :: fromOptional [] (Map.lookup key index)) index + +nonStockSecurityBalanceEventIndex : CapTableMaps -> Update NonStockSecurityBalanceEventIndex +nonStockSecurityBalanceEventIndex maps = do + warrantTransferEvents <- foldlA + (\index transferCid -> do + transfer <- fetch transferCid + pure (addSecurityQuantityEvent + transfer.transfer_data.security_id + (securityQuantityReductionEvent transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id) + index)) + Map.empty + (Map.values maps.warrant_transfers) + + warrantCancellationEvents <- foldlA + (\index cancellationCid -> do + cancellation <- fetch cancellationCid + pure (addSecurityQuantityEvent + cancellation.cancellation_data.security_id + (securityQuantityReductionEvent cancellation.cancellation_data.date cancellation.cancellation_data.quantity cancellation.cancellation_data.balance_security_id) + index)) + warrantTransferEvents + (Map.values maps.warrant_cancellations) + + warrantExerciseEvents <- foldlA + (\index exerciseCid -> do + exercise <- fetch exerciseCid + let event = case exercise.exercise_data.quantity of + Some quantity -> SecurityQuantityDelta with + event_date = exercise.exercise_data.date + event_priority = 1 + quantity_delta = 0.0 - quantity + None -> SecurityQuantityRetraction with + event_date = exercise.exercise_data.date + event_priority = 3 + pure (addSecurityQuantityEvent exercise.exercise_data.security_id event index)) + warrantCancellationEvents + (Map.values maps.warrant_exercises) + + warrantEvents <- foldlA + (\index retractionCid -> do + retraction <- fetch retractionCid + pure (addSecurityQuantityEvent + retraction.retraction_data.security_id + (SecurityQuantityRetraction with + event_date = retraction.retraction_data.date + event_priority = 3) + index)) + warrantExerciseEvents + (Map.values maps.warrant_retractions) + + equityTransferEvents <- foldlA + (\index transferCid -> do + transfer <- fetch transferCid + pure (addSecurityQuantityEvent + transfer.transfer_data.security_id + (securityQuantityReductionEvent transfer.transfer_data.date transfer.transfer_data.quantity transfer.transfer_data.balance_security_id) + index)) + Map.empty + (Map.values maps.equity_compensation_transfers) + + equityCancellationEvents <- foldlA + (\index cancellationCid -> do + cancellation <- fetch cancellationCid + pure (addSecurityQuantityEvent + cancellation.cancellation_data.security_id + (securityQuantityReductionEvent cancellation.cancellation_data.date cancellation.cancellation_data.quantity cancellation.cancellation_data.balance_security_id) + index)) + equityTransferEvents + (Map.values maps.equity_compensation_cancellations) + + equityExerciseEvents <- foldlA + (\index exerciseCid -> do + exercise <- fetch exerciseCid + assertNonNegativeQuantity + ("Equity compensation exercise quantity for " <> exercise.exercise_data.id) + exercise.exercise_data.quantity + pure (addSecurityQuantityEvent + exercise.exercise_data.security_id + (SecurityQuantityDelta with + event_date = exercise.exercise_data.date + event_priority = 1 + quantity_delta = 0.0 - exercise.exercise_data.quantity) + index)) + equityCancellationEvents + (Map.values maps.equity_compensation_exercises) + + equityReleaseEvents <- foldlA + (\index releaseCid -> do + release <- fetch releaseCid + pure (addSecurityQuantityEvent + release.release_data.security_id + (SecurityQuantityDelta with + event_date = release.release_data.date + event_priority = 1 + quantity_delta = 0.0 - release.release_data.quantity) + index)) + equityExerciseEvents + (Map.values maps.equity_compensation_releases) + + equityEvents <- foldlA + (\index retractionCid -> do + retraction <- fetch retractionCid + pure (addSecurityQuantityEvent + retraction.retraction_data.security_id + (SecurityQuantityRetraction with + event_date = retraction.retraction_data.date + event_priority = 3) + index)) + equityReleaseEvents + (Map.values maps.equity_compensation_retractions) + + pure NonStockSecurityBalanceEventIndex with + warrant_events_by_security_id = warrantEvents + equity_compensation_events_by_security_id = equityEvents + +warrantSecurityQuantityEvents : NonStockSecurityBalanceEventIndex -> WarrantIssuanceOcfData -> [SecurityQuantityEvent] +warrantSecurityQuantityEvents eventIndex issuanceData = + fromOptional [] (Map.lookup issuanceData.security_id eventIndex.warrant_events_by_security_id) + +warrantSecurityQuantityBeforeTerminal : + NonStockSecurityBalanceEventIndex -> + WarrantIssuanceOcfData -> + Time -> + Update (Optional Decimal) +warrantSecurityQuantityBeforeTerminal eventIndex issuanceData eventDate = + let events = warrantSecurityQuantityEvents eventIndex issuanceData + targetSortKey = (eventDate, 2, 1) + in case issuanceData.quantity of + Some quantity -> + securityQuantityBeforeEvent + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = quantity) + :: events) + targetSortKey + None -> securityQuantityBeforeEvent issuanceData.security_id None events targetSortKey + +validateWarrantSecurityBalance : NonStockSecurityBalanceEventIndex -> WarrantIssuanceOcfData -> Update () +validateWarrantSecurityBalance eventIndex issuanceData = do + let events = warrantSecurityQuantityEvents eventIndex issuanceData + case issuanceData.quantity of + Some quantity -> + validateSecurityQuantityBalance + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = quantity) + :: events) + None -> do + assertSecurityQuantityEventsOnOrAfter issuanceData.security_id issuanceData.date events + validateSecurityQuantityBalance issuanceData.security_id None events + +validateWarrantSecurityBalances : NonStockSecurityBalanceEventIndex -> CapTableMaps -> Update () +validateWarrantSecurityBalances eventIndex maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateWarrantSecurityBalance eventIndex issuance.issuance_data) + (Map.values maps.warrant_issuances) + +stockClassSplitSecurityQuantityEvents : StockClassShareEventIndex -> Text -> Time -> [SecurityQuantityEvent] +stockClassSplitSecurityQuantityEvents eventIndex stockClassId earliest = + concatMap + (\event -> case event of + StockClassShareMultiplier with event_date; event_priority; ratio -> + if event_date >= earliest + then [SecurityQuantityMultiplier with event_date; event_priority; ratio] + else [] + _ -> []) + (fromOptional [] (Map.lookup stockClassId eventIndex.stock_class_split_events_by_stock_class_id)) + +equityCompensationSecurityQuantityEvents : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + Update [SecurityQuantityEvent] +equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData = do + stockClassIds <- equityCompensationPlanStockClassIds maps issuanceData + let securityEvents = fromOptional [] (Map.lookup issuanceData.security_id balanceEventIndex.equity_compensation_events_by_security_id) + planSplitEvents = concatMap + (\stockClassId -> stockClassSplitSecurityQuantityEvents stockEventIndex stockClassId issuanceData.date) + stockClassIds + case (issuanceData.stock_class_id, stockClassIds, planSplitEvents) of + (None, _ :: _ :: _, _ :: _) -> + assertMsg + ("Equity compensation issuance without stock_class_id cannot be split-adjusted across multiple stock-plan classes: " <> issuanceData.id) + False + _ -> pure () + let splitEvents = case stockClassIds of + [_] -> planSplitEvents + _ -> [] + pure (securityEvents ++ splitEvents) + +equityCompensationSecurityQuantityBeforeTerminal : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + Time -> + Update Decimal +equityCompensationSecurityQuantityBeforeTerminal stockEventIndex balanceEventIndex maps issuanceData eventDate = do + events <- equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData + quantityOpt <- securityQuantityBeforeEvent + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = issuanceData.quantity) + :: events) + (eventDate, 2, 1) + case quantityOpt of + Some quantity -> pure quantity + None -> do + assertMsg ("Equity compensation quantity unexpectedly became unknown: " <> issuanceData.security_id) False + pure 0.0 + +validateEquityCompensationSecurityBalance : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + Update () +validateEquityCompensationSecurityBalance stockEventIndex balanceEventIndex maps issuanceData = do + events <- equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData + validateSecurityQuantityBalance + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = issuanceData.quantity) + :: events) + +validateEquityCompensationSecurityBalances : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Update () +validateEquityCompensationSecurityBalances stockEventIndex balanceEventIndex maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateEquityCompensationSecurityBalance stockEventIndex balanceEventIndex maps issuance.issuance_data) + (Map.values maps.equity_compensation_issuances) + +scaleWarrantSuccessorTrigger : + Decimal -> + Decimal -> + OcfConversionTrigger -> + OcfConversionTrigger +scaleWarrantSuccessorTrigger sourceQuantity successorQuantity trigger = + case trigger.conversion_right of + OcfRightWarrant conversionRight -> + case conversionRight.conversion_mechanism of + OcfWarrantMechanismFixedAmount with converts_to_quantity -> + trigger with + conversion_right = OcfRightWarrant (conversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with + converts_to_quantity = converts_to_quantity * successorQuantity / sourceQuantity) + _ -> trigger + _ -> trigger + +warrantTriggerRequiresQuantityScaling : OcfConversionTrigger -> Bool +warrantTriggerRequiresQuantityScaling trigger = + case trigger.conversion_right of + OcfRightWarrant conversionRight -> + case conversionRight.conversion_mechanism of + OcfWarrantMechanismFixedAmount {} -> True + _ -> False + _ -> False + +expectedWarrantSuccessorTriggers : + WarrantIssuanceOcfData -> + WarrantIssuanceOcfData -> + Optional [OcfConversionTrigger] +expectedWarrantSuccessorTriggers sourceIssuanceData successorIssuanceData = + if any warrantTriggerRequiresQuantityScaling sourceIssuanceData.exercise_triggers + then case (sourceIssuanceData.quantity, successorIssuanceData.quantity) of + (Some sourceQuantity, Some successorQuantity) -> + if sourceQuantity > 0.0 + then Some (map + (scaleWarrantSuccessorTrigger sourceQuantity successorQuantity) + sourceIssuanceData.exercise_triggers) + else None + _ -> None + else Some sourceIssuanceData.exercise_triggers + +scaleWarrantSuccessorVesting : Decimal -> Decimal -> OcfVesting -> OcfVesting +scaleWarrantSuccessorVesting sourceQuantity successorQuantity vesting = + vesting with amount = vesting.amount * successorQuantity / sourceQuantity + +expectedWarrantSuccessorVestings : + WarrantIssuanceOcfData -> + WarrantIssuanceOcfData -> + Optional [OcfVesting] +expectedWarrantSuccessorVestings sourceIssuanceData successorIssuanceData = + if null sourceIssuanceData.vestings + then Some [] + else case (sourceIssuanceData.quantity, successorIssuanceData.quantity) of + (Some sourceQuantity, Some successorQuantity) -> + if sourceQuantity > 0.0 + then Some (map + (scaleWarrantSuccessorVesting sourceQuantity successorQuantity) + sourceIssuanceData.vestings) + else None + _ -> None + +vestingConditionUsesPositiveFixedQuantity : VT.OcfVestingCondition -> Bool +vestingConditionUsesPositiveFixedQuantity condition = + case condition.quantity of + Some quantity -> quantity > 0.0 + None -> False + +scaleWarrantSuccessorVestingCondition : + Decimal -> + Decimal -> + VT.OcfVestingCondition -> + VT.OcfVestingCondition +scaleWarrantSuccessorVestingCondition sourceQuantity successorQuantity condition = + let scaledQuantity = case condition.quantity of + Some quantity -> Some (quantity * successorQuantity / sourceQuantity) + None -> None + in condition with quantity = scaledQuantity + +expectedWarrantSuccessorVestingTerms : + Text -> + Decimal -> + Decimal -> + VT.VestingTermsOcfData -> + VT.VestingTermsOcfData +expectedWarrantSuccessorVestingTerms successorTermsId sourceQuantity successorQuantity sourceTerms = + sourceTerms with + id = successorTermsId + vesting_conditions = map + (scaleWarrantSuccessorVestingCondition sourceQuantity successorQuantity) + sourceTerms.vesting_conditions + +validateWarrantSuccessorVestingTerms : + CapTableMaps -> + Text -> + WarrantIssuanceOcfData -> + WarrantIssuanceOcfData -> + Update () +validateWarrantSuccessorVestingTerms maps label sourceIssuanceData successorIssuanceData = + if not (null sourceIssuanceData.vestings) + then pure () + else case sourceIssuanceData.vesting_terms_id of + Some sourceTermsId -> case Map.lookup sourceTermsId maps.vesting_terms of + Some sourceTermsCid -> do + sourceTerms <- fetch sourceTermsCid + if any vestingConditionUsesPositiveFixedQuantity sourceTerms.vesting_terms_data.vesting_conditions + then case (sourceIssuanceData.quantity, successorIssuanceData.quantity, successorIssuanceData.vesting_terms_id) of + (Some sourceQuantity, Some successorQuantity, Some successorTermsId) -> + if sourceQuantity > 0.0 + then case Map.lookup successorTermsId maps.vesting_terms of + Some successorTermsCid -> do + successorTerms <- fetch successorTermsCid + let expectedTerms = expectedWarrantSuccessorVestingTerms + successorTermsId + sourceQuantity + successorQuantity + sourceTerms.vesting_terms_data + assertMsg + (label <> " successor fixed-quantity vesting terms are not scaled for " <> successorIssuanceData.security_id) + (successorTerms.vesting_terms_data == expectedTerms) + None -> + assertMsg + (label <> " successor warrant references missing VestingTerms: " <> successorTermsId) + False + else + assertMsg + (label <> " cannot scale fixed-quantity vesting terms from a non-positive source quantity for " <> successorIssuanceData.security_id) + False + _ -> + assertMsg + (label <> " cannot scale fixed-quantity vesting terms for successor " <> successorIssuanceData.security_id <> " without known source and successor quantities and successor terms") + False + else + assertMsg + (label <> " successor vesting terms mismatch for " <> successorIssuanceData.security_id) + (successorIssuanceData.vesting_terms_id == sourceIssuanceData.vesting_terms_id) + None -> + assertMsg + (label <> " source warrant references missing VestingTerms: " <> sourceTermsId) + False + None -> + assertMsg + (label <> " successor adds vesting terms not present on source warrant " <> successorIssuanceData.security_id) + (isNone successorIssuanceData.vesting_terms_id) + +validateWarrantSuccessorTerms : + CapTableMaps -> + Text -> + WarrantIssuanceOcfData -> + WarrantIssuanceOcfData -> + Update () +validateWarrantSuccessorTerms maps label sourceIssuanceData successorIssuanceData = do + assertMsg + (label <> " successor exercise price mismatch for " <> successorIssuanceData.security_id) + (successorIssuanceData.exercise_price == sourceIssuanceData.exercise_price) + case expectedWarrantSuccessorTriggers sourceIssuanceData successorIssuanceData of + Some expectedTriggers -> + assertMsg + (label <> " successor exercise triggers mismatch for " <> successorIssuanceData.security_id) + (successorIssuanceData.exercise_triggers == expectedTriggers) + None -> + assertMsg + (label <> " cannot scale fixed-amount exercise triggers for successor " <> successorIssuanceData.security_id <> " without known positive source and successor quantities") + False + assertMsg + (label <> " successor expiration date mismatch for " <> successorIssuanceData.security_id) + (successorIssuanceData.warrant_expiration_date == sourceIssuanceData.warrant_expiration_date) + validateWarrantSuccessorVestingTerms maps label sourceIssuanceData successorIssuanceData + case expectedWarrantSuccessorVestings sourceIssuanceData successorIssuanceData of + Some expectedVestings -> + assertMsg + (label <> " successor vestings mismatch for " <> successorIssuanceData.security_id) + (successorIssuanceData.vestings == expectedVestings) + None -> + assertMsg + (label <> " cannot scale inline vestings for successor " <> successorIssuanceData.security_id <> " without known positive source and successor quantities") + False + +fetchValidatedWarrantSuccessor : + CapTableMaps -> + Text -> + WarrantIssuanceOcfData -> + Time -> + Text -> + Update WarrantIssuanceOcfData +fetchValidatedWarrantSuccessor maps label sourceIssuanceData eventDate securityId = + case Map.lookup securityId maps.warrant_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + (label <> " successor date mismatch for " <> securityId <> ": expected " <> show eventDate <> ", found " <> show issuance.issuance_data.date) + (issuance.issuance_data.date == eventDate) + validateWarrantSuccessorTerms maps label sourceIssuanceData issuance.issuance_data + pure issuance.issuance_data + None -> do + assertMsg (label <> " successor warrant issuance not found: " <> securityId) False + pure sourceIssuanceData + +validateWarrantBalanceSuccessorQuantity : + CapTableMaps -> + Text -> + WarrantIssuanceOcfData -> + Time -> + Optional Decimal -> + Text -> + Update () +validateWarrantBalanceSuccessorQuantity maps label sourceIssuanceData eventDate expectedQuantityOpt securityId = do + successorIssuanceData <- fetchValidatedWarrantSuccessor maps label sourceIssuanceData eventDate securityId + case (expectedQuantityOpt, successorIssuanceData.quantity) of + (Some expectedQuantity, Some actualQuantity) -> + assertMsg + (label <> " balance quantity mismatch for " <> securityId <> ": expected " <> show expectedQuantity <> ", found " <> show actualQuantity) + (actualQuantity == expectedQuantity) + (Some _, None) -> assertMsg (label <> " balance warrant quantity is missing for " <> securityId) False + (None, Some actualQuantity) -> + assertMsg + (label <> " balance warrant quantity must remain unknown for " <> securityId <> ", found " <> show actualQuantity) + False + (None, None) -> pure () + +validateWarrantTransferResultIssuances : + CapTableMaps -> + Text -> + WarrantIssuanceOcfData -> + Time -> + Decimal -> + [Text] -> + Update () +validateWarrantTransferResultIssuances maps label sourceIssuanceData eventDate expectedQuantity securityIds = do + actualQuantity <- foldlA + (\total securityId -> do + successorIssuanceData <- fetchValidatedWarrantSuccessor maps label sourceIssuanceData eventDate securityId + case successorIssuanceData.quantity of + Some quantity -> pure (total + quantity) + None -> do + assertMsg (label <> " resulting warrant quantity is missing for " <> securityId) False + pure total) + 0.0 + securityIds + assertMsg + (label <> " resulting warrant quantity mismatch: expected " <> show expectedQuantity <> ", found " <> show actualQuantity) + (actualQuantity == expectedQuantity) + +validateWarrantTerminalBalanceSuccessor : + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Text -> + Text -> + Time -> + Decimal -> + Optional Text -> + Update () +validateWarrantTerminalBalanceSuccessor eventIndex maps label sourceSecurityId eventDate reductionQuantity balanceSecurityId = + case balanceSecurityId of + Some securityId -> case Map.lookup sourceSecurityId maps.warrant_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + currentQuantityOpt <- warrantSecurityQuantityBeforeTerminal eventIndex sourceIssuance.issuance_data eventDate + case currentQuantityOpt of + Some currentQuantity -> do + let expectedQuantity = currentQuantity - reductionQuantity + assertMsg (label <> " balance successor requires a positive remainder") (expectedQuantity > 0.0) + validateWarrantBalanceSuccessorQuantity maps label sourceIssuance.issuance_data eventDate (Some expectedQuantity) securityId + None -> + validateWarrantBalanceSuccessorQuantity maps label sourceIssuance.issuance_data eventDate None securityId + None -> assertMsg (label <> " source warrant issuance not found: " <> sourceSecurityId) False + None -> pure () + +validateEquityCompensationSecurityBalanceSuccessorQuantity : + CapTableMaps -> + Text -> + EquityCompensationIssuanceOcfData -> + Time -> + Decimal -> + Text -> + Update () +validateEquityCompensationSecurityBalanceSuccessorQuantity maps label sourceIssuanceData eventDate expectedQuantity securityId = + case Map.lookup securityId maps.equity_compensation_issuances_by_security_id of + Some issuanceCid -> do issuance <- fetch issuanceCid - validateStockSecurityBalance eventIndex maps issuance.issuance_data) - (Map.values maps.stock_issuances) + assertMsg + (label <> " balance result date mismatch for " <> securityId <> ": expected " <> show eventDate <> ", found " <> show issuance.issuance_data.date) + (issuance.issuance_data.date == eventDate) + expectedStockClassIds <- equityCompensationSuccessorStockClassIds maps sourceIssuanceData + assertMsg + (label <> " balance stock class mismatch for " <> securityId) + (issuance.issuance_data.stock_class_id `elem` expectedStockClassIds) + let targetSortKey = (eventDate, 2, 1) + actualQuantity <- case issuance.issuance_data.stock_class_id of + Some stockClassId -> stockClassQuantityAtSortKey maps stockClassId issuance.issuance_data.date issuance.issuance_data.quantity targetSortKey + None -> pure issuance.issuance_data.quantity + assertMsg + (label <> " balance quantity mismatch for " <> securityId <> ": expected " <> show expectedQuantity <> ", found " <> show actualQuantity) + (actualQuantity == expectedQuantity) + None -> assertMsg (label <> " balance equity compensation issuance not found: " <> securityId) False -stockSecurityQuantityBeforeEvent : +validateEquityCompensationTerminalBalanceSuccessor : StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> CapTableMaps -> - StockIssuanceOcfData -> - (Time, Int, Int) -> - Update Decimal -stockSecurityQuantityBeforeEvent eventIndex maps issuanceData targetSortKey = do - events <- stockSecurityShareEvents eventIndex maps issuanceData - foldlA - (validateStockSecurityBalanceEvent issuanceData.security_id) - 0.0 - (filter - (\event -> stockClassShareEventSortKey event < targetSortKey) - (sortOn stockClassShareEventSortKey events)) + Text -> + Text -> + Time -> + Decimal -> + Optional Text -> + Update () +validateEquityCompensationTerminalBalanceSuccessor stockEventIndex balanceEventIndex maps label sourceSecurityId eventDate reductionQuantity balanceSecurityId = + case balanceSecurityId of + Some securityId -> case Map.lookup sourceSecurityId maps.equity_compensation_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + currentQuantity <- equityCompensationSecurityQuantityBeforeTerminal + stockEventIndex balanceEventIndex maps sourceIssuance.issuance_data eventDate + let expectedQuantity = currentQuantity - reductionQuantity + assertMsg (label <> " balance successor requires a positive remainder") (expectedQuantity > 0.0) + validateEquityCompensationSecurityBalanceSuccessorQuantity + maps label sourceIssuance.issuance_data eventDate expectedQuantity securityId + None -> assertMsg (label <> " source equity compensation issuance not found: " <> sourceSecurityId) False + None -> pure () + +validateNonStockBalanceSuccessorQuantities : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Update () +validateNonStockBalanceSuccessorQuantities stockEventIndex balanceEventIndex maps = do + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + let label = "Warrant transfer " <> transfer.transfer_data.id + case Map.lookup transfer.transfer_data.security_id maps.warrant_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + validateWarrantTransferResultIssuances + maps label sourceIssuance.issuance_data transfer.transfer_data.date + transfer.transfer_data.quantity transfer.transfer_data.resulting_security_ids + validateWarrantTerminalBalanceSuccessor + balanceEventIndex maps label + transfer.transfer_data.security_id transfer.transfer_data.date transfer.transfer_data.quantity + transfer.transfer_data.balance_security_id + None -> assertMsg (label <> " source warrant issuance not found: " <> transfer.transfer_data.security_id) False) + (Map.values maps.warrant_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateWarrantTerminalBalanceSuccessor + balanceEventIndex maps ("Warrant cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id cancellation.cancellation_data.date cancellation.cancellation_data.quantity + cancellation.cancellation_data.balance_security_id) + (Map.values maps.warrant_cancellations) + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateEquityCompensationTerminalBalanceSuccessor + stockEventIndex balanceEventIndex maps ("Equity compensation transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id transfer.transfer_data.date transfer.transfer_data.quantity + transfer.transfer_data.balance_security_id) + (Map.values maps.equity_compensation_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateEquityCompensationTerminalBalanceSuccessor + stockEventIndex balanceEventIndex maps ("Equity compensation cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id cancellation.cancellation_data.date cancellation.cancellation_data.quantity + cancellation.cancellation_data.balance_security_id) + (Map.values maps.equity_compensation_cancellations) +stockClassShareLotCurrentQuantity : StockClassShareEventIndex -> StockClassShareLot -> Optional Decimal +stockClassShareLotCurrentQuantity eventIndex lot = + foldl + applyStockClassShareEventOptional + (Some 0.0) + (sortOn stockClassShareEventSortKey (stockClassShareLotEvents eventIndex lot)) + +stockClassShareLotQuantityAt : StockClassShareEventIndex -> StockClassShareLot -> Time -> Optional Decimal +stockClassShareLotQuantityAt eventIndex lot asOf = + if lot.event_date <= asOf + then + foldl + applyStockClassShareEventOptional + (Some 0.0) + (sortOn + stockClassShareEventSortKey + (filter (stockClassShareEventOnOrBefore asOf) (stockClassShareLotEvents eventIndex lot))) + else Some 0.0 stockSecurityQuantityBeforeTerminal : StockClassShareEventIndex -> @@ -2066,31 +2794,6 @@ validateNumberedStockPartialReductions eventIndex maps = do transfer.transfer_data.balance_security_id) (Map.values maps.stock_transfers) -stockClassQuantityAtSortKey : - CapTableMaps -> - Text -> - Time -> - Decimal -> - (Time, Int, Int) -> - Update Decimal -stockClassQuantityAtSortKey maps stockClassId quantityDate quantity targetSortKey = - let (targetDate, _, _) = targetSortKey - quantitySortKey = if quantityDate == targetDate then targetSortKey else (quantityDate, 0, 0) - in foldlA - (\adjustedQuantity splitCid -> do - split <- fetch splitCid - let splitSortKey = (split.split_data.date, 2, 2) - ratio = split.split_data.split_ratio - if split.split_data.stock_class_id /= stockClassId - then pure adjustedQuantity - else if quantitySortKey < targetSortKey && quantitySortKey < splitSortKey && splitSortKey <= targetSortKey - then pure (adjustedQuantity * ratio.numerator / ratio.denominator) - else if targetSortKey < quantitySortKey && targetSortKey < splitSortKey && splitSortKey < quantitySortKey - then pure (adjustedQuantity * ratio.denominator / ratio.numerator) - else pure adjustedQuantity) - quantity - (Map.values maps.stock_class_splits) - stockIssuanceQuantityAtSortKey : CapTableMaps -> StockIssuanceOcfData -> (Time, Int, Int) -> Update Decimal stockIssuanceQuantityAtSortKey maps issuanceData = stockClassQuantityAtSortKey maps issuanceData.stock_class_id issuanceData.date issuanceData.quantity @@ -2172,141 +2875,33 @@ stockIssuanceQuantityTotal maps label expectedStockClassId expectedDate targetSo 0.0 securityIds -validateStockBalanceSuccessor : - CapTableMaps -> - Text -> - StockIssuanceOcfData -> - Time -> - Decimal -> - Text -> - Update () -validateStockBalanceSuccessor maps label sourceIssuanceData eventDate expectedQuantity balanceSecurityId = - case Map.lookup balanceSecurityId maps.stock_issuances_by_security_id of - Some balanceIssuanceCid -> do - balanceIssuance <- fetch balanceIssuanceCid - assertMsg - (label <> " balance result date mismatch for " <> balanceSecurityId <> ": expected " <> show eventDate <> ", found " <> show balanceIssuance.issuance_data.date) - (balanceIssuance.issuance_data.date == eventDate) - assertMsg - (label <> " balance stock class mismatch for " <> balanceSecurityId <> ": expected " <> sourceIssuanceData.stock_class_id <> ", found " <> balanceIssuance.issuance_data.stock_class_id) - (balanceIssuance.issuance_data.stock_class_id == sourceIssuanceData.stock_class_id) - assertMsg - (label <> " balance quantity mismatch for " <> balanceSecurityId <> ": expected " <> show expectedQuantity <> ", found " <> show balanceIssuance.issuance_data.quantity) - (balanceIssuance.issuance_data.quantity == expectedQuantity) - validateStockSuccessorShareNumberRanges label sourceIssuanceData balanceIssuance.issuance_data - None -> - assertMsg (label <> " balance stock issuance not found: " <> balanceSecurityId) False - -validateStockTerminalBalanceSuccessor : - StockClassShareEventIndex -> - CapTableMaps -> - Text -> - StockIssuanceOcfData -> - Time -> - Decimal -> - Optional Text -> - Update () -validateStockTerminalBalanceSuccessor eventIndex maps label sourceIssuanceData eventDate reductionQuantity balanceSecurityId = - case balanceSecurityId of - Some securityId -> do - currentQuantity <- stockSecurityQuantityBeforeTerminal eventIndex maps sourceIssuanceData eventDate - let expectedQuantity = currentQuantity - reductionQuantity - assertMsg - (label <> " exceeds the current source quantity for " <> sourceIssuanceData.security_id) - (expectedQuantity >= 0.0) - validateStockBalanceSuccessor maps label sourceIssuanceData eventDate expectedQuantity securityId - None -> pure () - -validateStockBalanceSuccessors : StockClassShareEventIndex -> CapTableMaps -> Update () -validateStockBalanceSuccessors eventIndex maps = do - mapA_ - (\cancellationCid -> do - cancellation <- fetch cancellationCid - case cancellation.cancellation_data.balance_security_id of - Some _ -> case Map.lookup cancellation.cancellation_data.security_id maps.stock_issuances_by_security_id of - Some sourceIssuanceCid -> do - sourceIssuance <- fetch sourceIssuanceCid - validateStockTerminalBalanceSuccessor - eventIndex maps ("Stock cancellation " <> cancellation.cancellation_data.id) - sourceIssuance.issuance_data cancellation.cancellation_data.date cancellation.cancellation_data.quantity - cancellation.cancellation_data.balance_security_id - None -> assertMsg ("Stock cancellation source issuance not found: " <> cancellation.cancellation_data.security_id) False - None -> pure ()) - (Map.values maps.stock_cancellations) - - mapA_ - (\repurchaseCid -> do - repurchase <- fetch repurchaseCid - case repurchase.repurchase_data.balance_security_id of - Some _ -> case Map.lookup repurchase.repurchase_data.security_id maps.stock_issuances_by_security_id of - Some sourceIssuanceCid -> do - sourceIssuance <- fetch sourceIssuanceCid - validateStockTerminalBalanceSuccessor - eventIndex maps ("Stock repurchase " <> repurchase.repurchase_data.id) - sourceIssuance.issuance_data repurchase.repurchase_data.date repurchase.repurchase_data.quantity - repurchase.repurchase_data.balance_security_id - None -> assertMsg ("Stock repurchase source issuance not found: " <> repurchase.repurchase_data.security_id) False - None -> pure ()) - (Map.values maps.stock_repurchases) - - mapA_ - (\conversionCid -> do - conversion <- fetch conversionCid - case conversion.conversion_data.balance_security_id of - Some _ -> case Map.lookup conversion.conversion_data.security_id maps.stock_issuances_by_security_id of - Some sourceIssuanceCid -> do - sourceIssuance <- fetch sourceIssuanceCid - validateStockTerminalBalanceSuccessor - eventIndex maps ("Stock conversion " <> conversion.conversion_data.id) - sourceIssuance.issuance_data conversion.conversion_data.date conversion.conversion_data.quantity_converted - conversion.conversion_data.balance_security_id - None -> assertMsg ("Stock conversion source issuance not found: " <> conversion.conversion_data.security_id) False - None -> pure ()) - (Map.values maps.stock_conversions) - +validateStockTransferResultIssuances : CapTableMaps -> Update () +validateStockTransferResultIssuances maps = mapA_ (\transferCid -> do transfer <- fetch transferCid + let label = "Stock transfer " <> transfer.transfer_data.id + assertNoPartialResultingStockIssuanceCoverage label maps transfer.transfer_data.resulting_security_ids case Map.lookup transfer.transfer_data.security_id maps.stock_issuances_by_security_id of Some sourceIssuanceCid -> do sourceIssuance <- fetch sourceIssuanceCid - let label = "Stock transfer " <> transfer.transfer_data.id validateStockResultShareNumberRanges maps label sourceIssuance.issuance_data transfer.transfer_data.resulting_security_ids - case transfer.transfer_data.balance_security_id of - Some _ -> - validateStockTerminalBalanceSuccessor - eventIndex maps label - sourceIssuance.issuance_data transfer.transfer_data.date transfer.transfer_data.quantity - transfer.transfer_data.balance_security_id - None -> pure () - None -> assertMsg ("Stock transfer source issuance not found: " <> transfer.transfer_data.security_id) False) - (Map.values maps.stock_transfers) - -validateStockTransferResultIssuances : CapTableMaps -> Update () -validateStockTransferResultIssuances maps = - mapA_ - (\transferCid -> do - transfer <- fetch transferCid - let label = "Stock transfer " <> transfer.transfer_data.id - assertNoPartialResultingStockIssuanceCoverage label maps transfer.transfer_data.resulting_security_ids - if allResultingStockIssuancesExist maps transfer.transfer_data.resulting_security_ids - then case Map.lookup transfer.transfer_data.security_id maps.stock_issuances_by_security_id of - Some sourceIssuanceCid -> do - sourceIssuance <- fetch sourceIssuanceCid - let targetSortKey = stockReductionSortKey transfer.transfer_data.date transfer.transfer_data.balance_security_id - resultingQuantity <- stockIssuanceQuantityTotal - maps - label - sourceIssuance.issuance_data.stock_class_id - transfer.transfer_data.date - targetSortKey - transfer.transfer_data.resulting_security_ids - assertMsg - (label <> " resulting quantity mismatch: expected " <> show transfer.transfer_data.quantity <> ", found " <> show resultingQuantity) - (resultingQuantity == transfer.transfer_data.quantity) - None -> assertMsg (label <> " source stock issuance not found: " <> transfer.transfer_data.security_id) False - else pure ()) + if allResultingStockIssuancesExist maps transfer.transfer_data.resulting_security_ids + then do + let targetSortKey = stockReductionSortKey transfer.transfer_data.date transfer.transfer_data.balance_security_id + resultingQuantity <- stockIssuanceQuantityTotal + maps + label + sourceIssuance.issuance_data.stock_class_id + transfer.transfer_data.date + targetSortKey + transfer.transfer_data.resulting_security_ids + assertMsg + (label <> " resulting quantity mismatch: expected " <> show transfer.transfer_data.quantity <> ", found " <> show resultingQuantity) + (resultingQuantity == transfer.transfer_data.quantity) + else pure () + None -> assertMsg (label <> " source stock issuance not found: " <> transfer.transfer_data.security_id) False) (Map.values maps.stock_transfers) validateStockReissuanceResultIssuances : StockClassShareEventIndex -> CapTableMaps -> Update () @@ -2952,9 +3547,8 @@ validateStockIssuanceShareNumberLotsDoNotOverlap eventIndex lots = (validateStockClassShareNumberLotsDoNotOverlap eventIndex) (Map.values (stockIssuanceShareNumberLotIndex lots)) -validateStockIssuanceShareNumberRanges : CapTableMaps -> Update () -validateStockIssuanceShareNumberRanges maps = do - eventIndex <- stockClassShareEventIndex maps +validateStockIssuanceShareNumberRanges : StockClassShareEventIndex -> CapTableMaps -> Update () +validateStockIssuanceShareNumberRanges eventIndex maps = do validateNumberedStockPartialReductions eventIndex maps lots <- stockIssuanceShareNumberLots eventIndex maps validateStockIssuanceShareNumberLotsDoNotOverlap eventIndex lots @@ -4094,7 +4688,7 @@ equityCompensationSecurityEvents maps issuanceData = do release <- fetch releaseCid if release.release_data.security_id == issuanceData.security_id then pure ((StockClassShareDelta with - event_date = release.release_data.settlement_date + event_date = release.release_data.date event_priority = 1 quantity_delta = 0.0 - release.release_data.quantity) :: events) else pure events) @@ -4116,8 +4710,8 @@ equityCompensationSecurityEvents maps issuanceData = do pure (retractionEvents ++ splitEvents) -equityCompensationSecurityQuantityEvents : CapTableMaps -> EquityCompensationIssuanceOcfData -> Update [SecurityQuantityEvent] -equityCompensationSecurityQuantityEvents maps issuanceData = do +equityCompensationLifecycleQuantityEvents : CapTableMaps -> EquityCompensationIssuanceOcfData -> Update [SecurityQuantityEvent] +equityCompensationLifecycleQuantityEvents maps issuanceData = do transferEvents <- foldlA (\events transferCid -> do transfer <- fetch transferCid @@ -4158,7 +4752,7 @@ equityCompensationSecurityQuantityEvents maps issuanceData = do release <- fetch releaseCid if release.release_data.security_id == issuanceData.security_id then pure ((SecurityQuantityDelta with - event_date = release.release_data.settlement_date + event_date = release.release_data.date event_priority = 1 quantity_delta = 0.0 - release.release_data.quantity) :: events) @@ -4200,44 +4794,6 @@ equityCompensationSecurityQuantityEvents maps issuanceData = do pure (transferEvents ++ cancellationEvents ++ exerciseEvents ++ releaseEvents ++ retractionEvents ++ splitEvents) -validateEquityCompensationSecurityBalance : CapTableMaps -> EquityCompensationIssuanceOcfData -> Update () -validateEquityCompensationSecurityBalance maps issuanceData = do - events <- equityCompensationSecurityQuantityEvents maps issuanceData - validateSecurityQuantityBalance - issuanceData.security_id - (Some 0.0) - ((SecurityQuantityDelta with - event_date = issuanceData.date - event_priority = 0 - quantity_delta = issuanceData.quantity) - :: events) - -validateEquityCompensationSecurityBalances : CapTableMaps -> Update () -validateEquityCompensationSecurityBalances maps = - mapA_ - (\issuanceCid -> do - issuance <- fetch issuanceCid - validateEquityCompensationSecurityBalance maps issuance.issuance_data) - (Map.values maps.equity_compensation_issuances) - -equityCompensationCurrentQuantity : CapTableMaps -> EquityCompensationIssuanceOcfData -> Update (Optional Decimal) -equityCompensationCurrentQuantity maps issuanceData = do - events <- equityCompensationSecurityEvents maps issuanceData - pure (foldl - applyStockClassShareEventOptional - (Some issuanceData.quantity) - (sortOn stockClassShareEventSortKey events)) - -equityCompensationQuantityAt : CapTableMaps -> EquityCompensationIssuanceOcfData -> Time -> Update (Optional Decimal) -equityCompensationQuantityAt maps issuanceData asOf = do - if issuanceData.date <= asOf - then do - events <- equityCompensationSecurityEvents maps issuanceData - pure (foldl - applyStockClassShareEventOptional - (Some issuanceData.quantity) - (sortOn stockClassShareEventSortKey (filter (stockClassShareEventOnOrBefore asOf) events))) - else pure (Some 0.0) equityCompensationSecurityQuantityBeforeEvent : CapTableMaps -> @@ -4245,7 +4801,7 @@ equityCompensationSecurityQuantityBeforeEvent : (Time, Int, Int) -> Update Decimal equityCompensationSecurityQuantityBeforeEvent maps issuanceData targetSortKey = do - events <- equityCompensationSecurityQuantityEvents maps issuanceData + events <- equityCompensationLifecycleQuantityEvents maps issuanceData quantityOpt <- securityQuantityBeforeEvent issuanceData.security_id (Some 0.0) @@ -4379,6 +4935,25 @@ validateEquityCompensationTransferSuccessorContinuity maps = else pure ()) (Map.values maps.equity_compensation_transfers) +equityCompensationCurrentQuantity : CapTableMaps -> EquityCompensationIssuanceOcfData -> Update (Optional Decimal) +equityCompensationCurrentQuantity maps issuanceData = do + events <- equityCompensationSecurityEvents maps issuanceData + pure (foldl + applyStockClassShareEventOptional + (Some issuanceData.quantity) + (sortOn stockClassShareEventSortKey events)) + +equityCompensationQuantityAt : CapTableMaps -> EquityCompensationIssuanceOcfData -> Time -> Update (Optional Decimal) +equityCompensationQuantityAt maps issuanceData asOf = do + if issuanceData.date <= asOf + then do + events <- equityCompensationSecurityEvents maps issuanceData + pure (foldl + applyStockClassShareEventOptional + (Some issuanceData.quantity) + (sortOn stockClassShareEventSortKey (filter (stockClassShareEventOnOrBefore asOf) events))) + else pure (Some 0.0) + validateStockPlanEquityCompensationSourceQuantities : CapTableMaps -> Update () validateStockPlanEquityCompensationSourceQuantities maps = do successorIndex <- stockPlanSuccessorIndex maps @@ -5490,18 +6065,12 @@ validateSuccessorSecurityIdContinuity maps = do (Map.values maps.equity_compensation_releases) validateAuthorizedShareCeilings : + StockClassShareEventIndex -> CapTableMaps -> Map Text StockClassAuthorizedSharesAdjustmentOcfData -> ContractId Issuer -> Update () -validateAuthorizedShareCeilings maps adjustmentIndex issuerCid = do - eventIndex <- stockClassShareEventIndex maps - validateFinalSecurityIdUniqueness maps - validateSuccessorSecurityIdContinuity maps - validateStockSecurityBalances eventIndex maps - validateWarrantSecurityBalances maps - validateEquityCompensationSecurityBalances maps - validateStockBalanceSuccessors eventIndex maps +validateAuthorizedShareCeilings eventIndex maps adjustmentIndex issuerCid = do validateStockTransferResultIssuances maps validateStockReissuanceResultIssuances eventIndex maps validateStockConsolidationResultIssuances eventIndex maps @@ -5663,7 +6232,9 @@ template CapTable validateValuationStockClassReferences finalMaps validateStockPlanReferences finalMaps validateStockIssuanceReferences finalMaps - validateStockIssuanceShareNumberRanges finalMaps + stockEventIndex <- stockClassShareEventIndex finalMaps + nonStockBalanceEventIndex <- nonStockSecurityBalanceEventIndex finalMaps + validateStockIssuanceShareNumberRanges stockEventIndex finalMaps validateEquityCompensationIssuanceReferences finalMaps validateWarrantIssuanceReferences finalMaps validateVestingTransactionConditionReferences finalMaps @@ -5673,8 +6244,16 @@ template CapTable validateWarrantExerciseTriggerReferences finalMaps validateStockReissuanceSplitReferences finalMaps validateConfiguredReferences finalMaps + validateFinalSecurityIdUniqueness finalMaps + validateSuccessorSecurityIdContinuity finalMaps + validateStockBalanceSuccessorQuantities stockEventIndex finalMaps + validateNonStockBalanceSuccessorQuantities stockEventIndex nonStockBalanceEventIndex finalMaps + validateStockSecurityBalances stockEventIndex finalMaps + validateWarrantSecurityBalances nonStockBalanceEventIndex finalMaps + validateEquityCompensationSecurityBalances stockEventIndex nonStockBalanceEventIndex finalMaps authorizedShareAdjustmentIndex <- stockClassAuthorizedAdjustmentIndex finalMaps validateAuthorizedShareCeilings + stockEventIndex finalMaps authorizedShareAdjustmentIndex newIssuerCid