From a2487c47c4889fbbb44b605fa6f106787922e42c Mon Sep 17 00:00:00 2001 From: HardlyDifficult Date: Sat, 11 Jul 2026 22:41:01 -0400 Subject: [PATCH 1/2] feat: enforce OCF terminal transaction completeness --- OpenCapTable-v34/daml.yaml | 2 +- Test/daml.yaml | 2 +- .../TestBalanceSecurityReferences.daml | 32 ++++- .../TestConvertibleCancellation.daml | 19 +++ .../TestConvertibleConversion.daml | 34 +++-- .../OpenCapTable/TestConvertibleTransfer.daml | 27 ++++ .../TestEquityCompensationCancellation.daml | 34 ++++- .../TestEquityCompensationTransfer.daml | 25 ++++ .../TestFinalReferenceValidation.daml | 2 +- .../OpenCapTable/TestLifecycleInvariants.daml | 12 +- .../TestNonStockSecurityBalances.daml | 14 +- .../OpenCapTable/TestStockCancellation.daml | 20 ++- .../TestStockClassAuthorizedShares.daml | 24 +++- .../OpenCapTable/TestStockConversion.daml | 24 ++++ .../TestStockPlanReservedShares.daml | 27 +++- .../OpenCapTable/TestStockRepurchase.daml | 20 +++ .../TestStockSecurityBalances.daml | 18 ++- Test/daml/OpenCapTable/TestStockTransfer.daml | 25 ++++ .../TestVestingQuantityIntegrity.daml | 91 ++++++++---- .../OpenCapTable/TestWarrantCancellation.daml | 58 +++++++- .../OpenCapTable/TestWarrantTransfer.daml | 25 ++++ .../0.0.19/OpenCapTable-v34.dar | 3 + dars/dars.lock | 7 + .../codegen/templates/CapTable.daml.template | 130 +++++++++--------- 24 files changed, 550 insertions(+), 125 deletions(-) create mode 100644 dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar diff --git a/OpenCapTable-v34/daml.yaml b/OpenCapTable-v34/daml.yaml index 236edb72..5689b30a 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.18 +version: 0.0.19 build-options: - -Wno-crypto-text-is-alpha - --typecheck-upgrades=no diff --git a/Test/daml.yaml b/Test/daml.yaml index 8fcddbad..8b45aeeb 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.18.dar + - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.19.dar build-options: - -Wno-template-interface-depends-on-daml-script diff --git a/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml b/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml index 10b1e406..97cdc380 100644 --- a/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml +++ b/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml @@ -125,7 +125,7 @@ testBalanceSecurityReferences_EditCanCreateStockSuccessorInSameBatch = script do let cancellation = StockCancellationOcfData with id = "TX-STOCK-EDIT-BALANCE" date = defaultTestDate - quantity = 10.0 + quantity = 100.0 reason_text = "Partial cancellation" security_id = "SEC-STOCK-1" comments = [] @@ -147,6 +147,7 @@ testBalanceSecurityReferences_EditCanCreateStockSuccessorInSameBatch = script do exerciseCmd created.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateStockIssuance balanceIssuance] edits = [CT.OcfEditStockCancellation (cancellation with + quantity = 10.0 balance_security_id = Some "SEC-STOCK-EDIT-BALANCE-SUCCESSOR")] deletes = [] pure () @@ -158,7 +159,7 @@ testBalanceSecurityReferences_EditMissingStockSuccessorFails = script do let cancellation = StockCancellationOcfData with id = "TX-STOCK-EDIT-MISSING-BALANCE" date = defaultTestDate - quantity = 10.0 + quantity = 100.0 reason_text = "Partial cancellation" security_id = "SEC-STOCK-1" comments = [] @@ -174,5 +175,32 @@ testBalanceSecurityReferences_EditMissingStockSuccessorFails = script do exerciseCmd created.updatedCapTableCid CT.UpdateCapTable with creates = [] edits = [CT.OcfEditStockCancellation (cancellation with + quantity = 10.0 balance_security_id = Some "SEC-STOCK-EDIT-MISSING-BALANCE")] deletes = [] + +testTerminalTransactionCompleteness_EditFullCancellationToPartialWithoutBalanceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + let cancellation = StockCancellationOcfData with + id = "TX-STOCK-EDIT-PARTIAL-NO-BALANCE" + date = defaultTestDate + quantity = 100.0 + reason_text = "Initially a full cancellation" + security_id = "SEC-STOCK-1" + comments = [] + balance_security_id = None + + created <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockCancellation cancellation] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd created.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockCancellation (cancellation with + quantity = 10.0)] + deletes = [] diff --git a/Test/daml/OpenCapTable/TestConvertibleCancellation.daml b/Test/daml/OpenCapTable/TestConvertibleCancellation.daml index ba3c7466..aa598495 100644 --- a/Test/daml/OpenCapTable/TestConvertibleCancellation.daml +++ b/Test/daml/OpenCapTable/TestConvertibleCancellation.daml @@ -53,6 +53,25 @@ testConvertibleCancellation_Create = script do deletes = [] pure () +testConvertibleCancellation_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-CONVERTIBLE-PARTIAL-NO-BALANCE" + result <- createConvertibleIssuance issuer capTableCid securityId 100000.0 (DT.time (DA.date 2022 Jun 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleCancellation ConvertibleCancellationOcfData with + id = "TX-CONVERTIBLE-CANCEL-PARTIAL-NO-BALANCE" + date = DT.time (DA.date 2022 Nov 03) 0 0 0 + security_id = securityId + amount = OcfMonetary with amount = 40000.0, currency = "USD" + reason_text = "Invalid partial cancellation" + balance_security_id = None + comments = []] + edits = [] + deletes = [] + testConvertibleCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table diff --git a/Test/daml/OpenCapTable/TestConvertibleConversion.daml b/Test/daml/OpenCapTable/TestConvertibleConversion.daml index 07e3b28d..a6d5f23f 100644 --- a/Test/daml/OpenCapTable/TestConvertibleConversion.daml +++ b/Test/daml/OpenCapTable/TestConvertibleConversion.daml @@ -248,6 +248,7 @@ testConvertibleConversion_FullFixedUsesCurrentPrincipal = script do capTableCid <- addPrerequisites issuer cap_table capTableCid <- addPreferredStockClass issuer capTableCid let securityId = "CONV-SEC-FULL-CURRENT-PRINCIPAL" + reducedSecurityId = "CONV-SEC-FULL-CURRENT-PRINCIPAL-BALANCE" reductionDate = DT.time (DA.date 2025 Nov 09) 0 0 0 conversionDate = DT.time (DA.date 2025 Nov 10) 0 0 0 sourceResult <- createConvertibleIssuanceWithMechanism @@ -258,13 +259,21 @@ testConvertibleConversion_FullFixedUsesCurrentPrincipal = script do (DT.time (DA.date 2023 Mar 15) 0 0 0) (OcfConvMechFixedAmount (OcfFixedAmountConversionMechanism with converts_to_quantity = 10000.0)) (Some "SC_PREFERRED") + reducedSourceResult <- createConvertibleIssuanceWithMechanism + issuer + sourceResult.updatedCapTableCid + reducedSecurityId + 80000.0 + reductionDate + (OcfConvMechFixedAmount (OcfFixedAmountConversionMechanism with converts_to_quantity = 8000.0)) + (Some "SC_PREFERRED") reductionResult <- submit issuer do - exerciseCmd sourceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateConvertibleCancellation (convertibleCancellation + exerciseCmd reducedSourceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleCancellation ((convertibleCancellation "TX-CONV-CANCEL-BEFORE-FULL-CONVERSION" securityId 20000.0 - reductionDate)] + reductionDate) with balance_security_id = Some reducedSecurityId)] edits = [] deletes = [] @@ -275,7 +284,7 @@ testConvertibleConversion_FullFixedUsesCurrentPrincipal = script do , CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with id = "TX-CONV-CONVERSION-FULL-CURRENT-PRINCIPAL" date = conversionDate - security_id = securityId + security_id = reducedSecurityId trigger_id = "equity-financing-trigger" reason_text = "Convert the remaining principal after an earlier reduction" resulting_security_ids = ["SSEC-CONV-FULL-CURRENT-PRINCIPAL"] @@ -293,6 +302,7 @@ testConvertibleConversion_PartialFixedUsesCurrentPrincipal = script do capTableCid <- addPrerequisites issuer cap_table capTableCid <- addPreferredStockClass issuer capTableCid let securityId = "CONV-SEC-PARTIAL-CURRENT-PRINCIPAL" + reducedSecurityId = "CONV-SEC-PARTIAL-CURRENT-PRINCIPAL-AFTER-REDUCTION" balanceSecurityId = "CONV-SEC-PARTIAL-CURRENT-PRINCIPAL-BALANCE" reductionDate = DT.time (DA.date 2025 Nov 09) 0 0 0 conversionDate = DT.time (DA.date 2025 Nov 10) 0 0 0 @@ -304,13 +314,21 @@ testConvertibleConversion_PartialFixedUsesCurrentPrincipal = script do (DT.time (DA.date 2023 Mar 15) 0 0 0) (OcfConvMechFixedAmount (OcfFixedAmountConversionMechanism with converts_to_quantity = 10000.0)) (Some "SC_PREFERRED") + reducedSourceResult <- createConvertibleIssuanceWithMechanism + issuer + sourceResult.updatedCapTableCid + reducedSecurityId + 80000.0 + reductionDate + (OcfConvMechFixedAmount (OcfFixedAmountConversionMechanism with converts_to_quantity = 8000.0)) + (Some "SC_PREFERRED") reductionResult <- submit issuer do - exerciseCmd sourceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateConvertibleCancellation (convertibleCancellation + exerciseCmd reducedSourceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleCancellation ((convertibleCancellation "TX-CONV-CANCEL-BEFORE-PARTIAL-CONVERSION" securityId 20000.0 - reductionDate)] + reductionDate) with balance_security_id = Some reducedSecurityId)] edits = [] deletes = [] balanceResult <- createConvertibleIssuanceWithMechanism @@ -329,7 +347,7 @@ testConvertibleConversion_PartialFixedUsesCurrentPrincipal = script do , CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with id = "TX-CONV-CONVERSION-PARTIAL-CURRENT-PRINCIPAL" date = conversionDate - security_id = securityId + security_id = reducedSecurityId trigger_id = "equity-financing-trigger" reason_text = "Partially convert the remaining principal after an earlier reduction" resulting_security_ids = ["SSEC-CONV-PARTIAL-CURRENT-PRINCIPAL"] diff --git a/Test/daml/OpenCapTable/TestConvertibleTransfer.daml b/Test/daml/OpenCapTable/TestConvertibleTransfer.daml index 9419c297..f7c135db 100644 --- a/Test/daml/OpenCapTable/TestConvertibleTransfer.daml +++ b/Test/daml/OpenCapTable/TestConvertibleTransfer.daml @@ -101,6 +101,33 @@ testConvertibleTransfer_PartialSAFE = script do deletes = [] pure () +testConvertibleTransfer_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + (capTableCid, assigneeId) <- setupStakeholderWithId issuer capTableCid "SH-CONVERTIBLE-PARTIAL-NO-BALANCE" "Convertible Assignee" + let securityId = "CSEC-PARTIAL-NO-BALANCE" + transferDate = DT.time (DA.date 2025 Oct 15) 0 0 0 + result <- createConvertibleIssuance issuer capTableCid securityId 100000.0 (DT.time (DA.date 2024 Mar 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateConvertibleIssuance (convertibleTransferIssuance + "CV-ISS-PARTIAL-NO-BALANCE" "CSEC-PARTIAL-NO-BALANCE-RESULT" assigneeId 40000.0 + transferDate OcfConvertibleSafe (DT.time (DA.date 2025 Dec 31) 0 0 0)) + , CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with + id = "TX-CONVERTIBLE-TRANSFER-PARTIAL-NO-BALANCE" + date = transferDate + amount = OcfMonetary with amount = 40000.0, currency = "USD" + security_id = securityId + resulting_security_ids = ["CSEC-PARTIAL-NO-BALANCE-RESULT"] + comments = [] + balance_security_id = None + consideration_text = None + ] + edits = [] + deletes = [] + -- Test: Convertible note transfer with full assignment testConvertibleTransfer_NoteFullAssignment = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml index 7a462a03..6cd49ffa 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml @@ -1,6 +1,7 @@ module OpenCapTable.TestEquityCompensationCancellation where import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance import Fairmint.OpenCapTable.OCF.EquityCompensationCancellation +import Fairmint.OpenCapTable.OCF.EquityCompensationExercise import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT @@ -62,7 +63,19 @@ testEquityCompensationCancellation_CreateAndArchiveByIssuer = script do capTableCid <- addPrerequisites issuer cap_table let securityId = "db5a26d6-3912-4400-87b0-bda74de361af" result <- createEquityCompensationIssuance issuer capTableCid securityId 250000.0 (DT.time (DA.date 2020 Jan 15) 0 0 0) - _ <- createEquityCompensationCancellation issuer result.updatedCapTableCid securityId 214200.0 + exerciseResult <- submit issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX_EC_EX_BEFORE_CANCEL" + date = DT.time (DA.date 2021 Nov 30) 0 0 0 + security_id = securityId + quantity = 35800.0 + consideration_text = None + resulting_security_ids = [] + comments = []] + edits = [] + deletes = [] + _ <- createEquityCompensationCancellation issuer exerciseResult.updatedCapTableCid securityId 214200.0 pure () -- Archive with built-in choice (requires both signatories) @@ -85,6 +98,25 @@ testEquityCompensationCancellation_CreateAndArchiveBuiltIn = script do deletes = [] pure () +testEquityCompensationCancellation_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SEC-EC-PARTIAL-NO-BALANCE" + result <- createEquityCompensationIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2020 Jan 15) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "TX-EC-CANCEL-PARTIAL-NO-BALANCE" + date = DT.time (DA.date 2024 Mar 15) 0 0 0 + security_id = securityId + quantity = 40.0 + reason_text = "Invalid partial cancellation" + balance_security_id = None + comments = []] + edits = [] + deletes = [] + -- Partial cancellation with optional fields populated testEquityCompensationCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml index e82e6cf6..195c6fa0 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml @@ -95,6 +95,31 @@ testEquityCompensationTransfer_ResultQuantityMustMatchTransfer = script do edits = [] deletes = [] +testEquityCompensationTransfer_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + (capTableCid, transfereeId) <- setupStakeholderWithId issuer capTableCid "SH-EC-PARTIAL-NO-BALANCE" "EC Transferee" + let securityId = "ESEC-TRANSFER-PARTIAL-NO-BALANCE" + transferDate = DT.time (DA.date 2025 Jun 01) 0 0 0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2022 Jan 15) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ equityCompensationSuccessorIssuance "EC-ISS-PARTIAL-NO-BALANCE" "ESEC-PARTIAL-NO-BALANCE-RESULT" transfereeId transferDate 40.0 (Some equityCompensationExpiration) + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + id = "TX-EC-TRANSFER-PARTIAL-NO-BALANCE" + date = transferDate + quantity = 40.0 + security_id = securityId + resulting_security_ids = ["ESEC-PARTIAL-NO-BALANCE-RESULT"] + comments = [] + balance_security_id = None + consideration_text = None + ] + edits = [] + deletes = [] + testEquityCompensationTransfer_BalanceMustKeepSourceStakeholder = script do TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table diff --git a/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml index ed3e0158..cb861140 100644 --- a/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml +++ b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml @@ -31,7 +31,7 @@ stockTransferData transferId securityId = StockTransferOcfData with security_id = securityId quantity = 10.0 resulting_security_ids = ["SEC-TRANSFER-RESULT"] - balance_security_id = None + balance_security_id = Some "SEC-TRANSFER-BALANCE" consideration_text = None comments = [] diff --git a/Test/daml/OpenCapTable/TestLifecycleInvariants.daml b/Test/daml/OpenCapTable/TestLifecycleInvariants.daml index 39452355..3be3668a 100644 --- a/Test/daml/OpenCapTable/TestLifecycleInvariants.daml +++ b/Test/daml/OpenCapTable/TestLifecycleInvariants.daml @@ -518,18 +518,20 @@ testStockBalanceSuccessorIncludesPriorMaterializedReduction = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_STOCK_UNMATERIALIZED_HISTORY" 1_000.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_STOCK_UNMATERIALIZED_HISTORY" "Stock Unmaterialized History Holder" let sourceSecurityId = "SEC_STOCK_UNMATERIALIZED_HISTORY_SOURCE" + let transferBalanceSecurityId = "SEC_STOCK_UNMATERIALIZED_HISTORY_TRANSFER_BALANCE" issued <- createStockIssuanceWith issuer capTableReady (defaultStockIssuance "TX_STOCK_UNMATERIALIZED_HISTORY_SOURCE" sourceSecurityId stakeholderId stockClassId) transferred <- submit issuer do exerciseCmd issued CT.UpdateCapTable with creates = - [ CT.OcfCreateStockTransfer (lifecycleStockTransfer "TX_STOCK_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 None ["SEC_STOCK_UNMATERIALIZED_HISTORY_MISSING"]) + [ CT.OcfCreateStockTransfer (lifecycleStockTransfer "TX_STOCK_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 (Some transferBalanceSecurityId) ["SEC_STOCK_UNMATERIALIZED_HISTORY_MISSING"]) , stockSuccessorIssuance "TX_STOCK_UNMATERIALIZED_HISTORY_RESULT" "SEC_STOCK_UNMATERIALIZED_HISTORY_MISSING" stakeholderId stockClassId defaultTestDate 30.0 + , stockSuccessorIssuance "TX_STOCK_UNMATERIALIZED_HISTORY_TRANSFER_BALANCE" transferBalanceSecurityId stakeholderId stockClassId defaultTestDate 70.0 ] edits = [] deletes = [] let balanceSecurityId = "SEC_STOCK_UNMATERIALIZED_HISTORY_BALANCE" - let cancellation = (stockCancellation "TX_STOCK_UNMATERIALIZED_HISTORY_CANCEL" sourceSecurityId 20.0) with + let cancellation = (stockCancellation "TX_STOCK_UNMATERIALIZED_HISTORY_CANCEL" transferBalanceSecurityId 20.0) with balance_security_id = Some balanceSecurityId submitMustFail issuer do exerciseCmd transferred.updatedCapTableCid CT.UpdateCapTable with @@ -545,6 +547,7 @@ testEquityBalanceSuccessorIncludesPriorMaterializedReduction = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_EQUITY_UNMATERIALIZED_HISTORY" 1_000.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_EQUITY_UNMATERIALIZED_HISTORY" "Equity Unmaterialized History Holder" let sourceSecurityId = "ESEC_UNMATERIALIZED_HISTORY_SOURCE" + let transferBalanceSecurityId = "ESEC_UNMATERIALIZED_HISTORY_TRANSFER_BALANCE" issued <- submit issuer do exerciseCmd capTableReady CT.UpdateCapTable with creates = [CT.OcfCreateEquityCompensationIssuance (lifecycleEquityIssuance "TX_EQUITY_UNMATERIALIZED_HISTORY_SOURCE" sourceSecurityId stakeholderId stockClassId 100.0)] @@ -553,8 +556,9 @@ testEquityBalanceSuccessorIncludesPriorMaterializedReduction = script do transferred <- submit issuer do exerciseCmd issued.updatedCapTableCid CT.UpdateCapTable with creates = - [ CT.OcfCreateEquityCompensationTransfer (lifecycleEquityTransfer "TX_EQUITY_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 None ["ESEC_UNMATERIALIZED_HISTORY_MISSING"]) + [ CT.OcfCreateEquityCompensationTransfer (lifecycleEquityTransfer "TX_EQUITY_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 (Some transferBalanceSecurityId) ["ESEC_UNMATERIALIZED_HISTORY_MISSING"]) , CT.OcfCreateEquityCompensationIssuance (lifecycleEquityIssuance "TX_EQUITY_UNMATERIALIZED_HISTORY_RESULT" "ESEC_UNMATERIALIZED_HISTORY_MISSING" stakeholderId stockClassId 30.0) + , CT.OcfCreateEquityCompensationIssuance (lifecycleEquityIssuance "TX_EQUITY_UNMATERIALIZED_HISTORY_TRANSFER_BALANCE" transferBalanceSecurityId stakeholderId stockClassId 70.0) ] edits = [] deletes = [] @@ -563,7 +567,7 @@ testEquityBalanceSuccessorIncludesPriorMaterializedReduction = script do submitMustFail issuer do exerciseCmd transferred.updatedCapTableCid CT.UpdateCapTable with creates = - [ CT.OcfCreateEquityCompensationCancellation (lifecycleEquityCancellation "TX_EQUITY_UNMATERIALIZED_HISTORY_CANCEL" sourceSecurityId 20.0 balanceSecurityId) + [ CT.OcfCreateEquityCompensationCancellation (lifecycleEquityCancellation "TX_EQUITY_UNMATERIALIZED_HISTORY_CANCEL" transferBalanceSecurityId 20.0 balanceSecurityId) , CT.OcfCreateEquityCompensationIssuance (lifecycleEquityIssuance "TX_EQUITY_UNMATERIALIZED_HISTORY_BALANCE" balanceSecurityId stakeholderId stockClassId 80.0) ] edits = [] diff --git a/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml b/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml index 22501969..e41b1429 100644 --- a/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml +++ b/Test/daml/OpenCapTable/TestNonStockSecurityBalances.daml @@ -311,15 +311,18 @@ testWarrantSecurityBalance_BalanceSecurityReductionPreservesRemainder = script d TestOcp{issuer, cap_table} <- setupTestOcp capTableReady <- addDefaultStakeholder issuer cap_table let securityId = "WSEC_BALANCE_REMAINDER_SOURCE" + let cancellationBalanceSecurityId = "WSEC_BALANCE_AFTER_CANCELLATION" _ <- submit issuer do exerciseCmd capTableReady CT.UpdateCapTable with creates = [ CT.OcfCreateWarrantIssuance (warrantIssuance "TX_W_BALANCE_REMAINDER_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateWarrantIssuance ((warrantIssuance "TX_W_BALANCE_AFTER_CANCELLATION" cancellationBalanceSecurityId defaultStakeholderId 60.0) with date = jan02) , CT.OcfCreateWarrantIssuance ((warrantIssuance "TX_W_BALANCE_REMAINDER" "WSEC_BALANCE_REMAINDER" defaultStakeholderId 30.0) with date = jan02) - , CT.OcfCreateWarrantCancellation (warrantCancellation "TX_W_CANCEL_BEFORE_BALANCE_TRANSFER" securityId 40.0 jan02) + , CT.OcfCreateWarrantCancellation ((warrantCancellation "TX_W_CANCEL_BEFORE_BALANCE_TRANSFER" securityId 40.0 jan02) with + balance_security_id = Some cancellationBalanceSecurityId) , CT.OcfCreateWarrantIssuance ((warrantIssuance "WR-ISS-BALANCE-TRANSFER-RESULT" "TX_W_BALANCE_TRANSFER_RESULT" defaultStakeholderId 30.0) with date = jan02) - , CT.OcfCreateWarrantTransfer ((warrantTransfer "TX_W_BALANCE_TRANSFER" securityId 30.0 jan02) with balance_security_id = Some "WSEC_BALANCE_REMAINDER") + , CT.OcfCreateWarrantTransfer ((warrantTransfer "TX_W_BALANCE_TRANSFER" cancellationBalanceSecurityId 30.0 jan02) with balance_security_id = Some "WSEC_BALANCE_REMAINDER") ] edits = [] deletes = [] @@ -934,15 +937,18 @@ testEquityCompensationSecurityBalance_BalanceSecurityReductionPreservesRemainder TestOcp{issuer, cap_table} <- setupTestOcp capTableReady <- addDefaultStakeholder issuer cap_table let securityId = "ESEC_BALANCE_REMAINDER_SOURCE" + let transferBalanceSecurityId = "ESEC_BALANCE_AFTER_TRANSFER" _ <- submit issuer do exerciseCmd capTableReady CT.UpdateCapTable with creates = [ CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "TX_E_BALANCE_REMAINDER_SOURCE" securityId defaultStakeholderId 100.0) + , CT.OcfCreateEquityCompensationIssuance ((equityCompensationIssuance "TX_E_BALANCE_AFTER_TRANSFER" transferBalanceSecurityId defaultStakeholderId 60.0) with date = jan02) , CT.OcfCreateEquityCompensationIssuance ((equityCompensationIssuance "TX_E_BALANCE_REMAINDER" "ESEC_BALANCE_REMAINDER" defaultStakeholderId 30.0) with date = jan02) , equityCompensationSuccessorIssuance "EC-ISS-TRANSFER-BEFORE-BALANCE-CANCEL" "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL_RESULT" defaultStakeholderId jan02 40.0 None - , CT.OcfCreateEquityCompensationTransfer (equityCompensationTransfer "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL" securityId 40.0 jan02) - , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BALANCE_CANCEL_REMAINDER" securityId 30.0 jan02) with balance_security_id = Some "ESEC_BALANCE_REMAINDER") + , CT.OcfCreateEquityCompensationTransfer ((equityCompensationTransfer "TX_E_TRANSFER_BEFORE_BALANCE_CANCEL" securityId 40.0 jan02) with + balance_security_id = Some transferBalanceSecurityId) + , CT.OcfCreateEquityCompensationCancellation ((equityCompensationCancellation "TX_E_BALANCE_CANCEL_REMAINDER" transferBalanceSecurityId 30.0 jan02) with balance_security_id = Some "ESEC_BALANCE_REMAINDER") ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockCancellation.daml b/Test/daml/OpenCapTable/TestStockCancellation.daml index 6a7b30e1..a29c4f26 100644 --- a/Test/daml/OpenCapTable/TestStockCancellation.daml +++ b/Test/daml/OpenCapTable/TestStockCancellation.daml @@ -69,7 +69,7 @@ createStockCancellation issuer capTableCid = id = "TX_STOCK_CANCEL_1" date = DT.time (DA.date 2024 Jan 02) 0 0 0 security_id = "SEC-1" - quantity = 50.0 + quantity = 100.0 balance_security_id = None reason_text = "Forfeit" comments = []] @@ -90,6 +90,24 @@ testStockCancellation_CreateAndArchiveBuiltIn = script do _ <- createStockCancellation issuer result.updatedCapTableCid pure () +testStockCancellation_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + result <- createStockIssuance issuer capTableCid "SEC-PARTIAL-NO-BALANCE" 100.0 (DT.time (DA.date 2024 Jan 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockCancellation StockCancellationOcfData with + id = "TX-STOCK-CANCEL-PARTIAL-NO-BALANCE" + date = DT.time (DA.date 2024 Jan 02) 0 0 0 + security_id = "SEC-PARTIAL-NO-BALANCE" + quantity = 50.0 + balance_security_id = None + reason_text = "Invalid partial cancellation" + comments = []] + edits = [] + deletes = [] + -- Optionals Some and arrays non-empty testStockCancellation_OptionalsSome = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml index 50281a90..b062487d 100644 --- a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml +++ b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml @@ -566,11 +566,14 @@ testSameDateCancellationAppliesBeforeSplitForCeiling = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_CANCEL_SPLIT" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_CANCEL_SPLIT" "Cancel Split Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CANCEL_SPLIT_ISSUED" "SEC_CANCEL_SPLIT_ISSUED" stakeholderId stockClassId) with quantity = 100.0) + let balanceSecurityId = "SEC_CANCEL_SPLIT_BALANCE" _ <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with creates = - [ CT.OcfCreateStockCancellation (stockCancellation "TX_CANCEL_BEFORE_SPLIT" "SEC_CANCEL_SPLIT_ISSUED" 50.0) + [ CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CANCEL_SPLIT_BALANCE" balanceSecurityId stakeholderId stockClassId) with quantity = 50.0) + , CT.OcfCreateStockCancellation ((stockCancellation "TX_CANCEL_BEFORE_SPLIT" "SEC_CANCEL_SPLIT_ISSUED" 50.0) with + balance_security_id = Some balanceSecurityId) , CT.OcfCreateStockClassSplit (stockClassSplit "TX_SPLIT_AFTER_CANCEL" stockClassId 2.0 1.0) ] edits = [] @@ -583,9 +586,14 @@ testStockCancellationReducesIssuedSharesForCeiling = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_CANCEL" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_CANCEL" "Cancellation Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CANCEL_ISSUED" "SEC_CANCEL_ISSUED" stakeholderId stockClassId) with quantity = 100.0) + let balanceSecurityId = "SEC_CANCEL_BALANCE" result <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockCancellation (stockCancellation "TX_CANCEL_REDUCE" "SEC_CANCEL_ISSUED" 50.0)] + creates = + [ CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CANCEL_BALANCE" balanceSecurityId stakeholderId stockClassId) with quantity = 50.0) + , CT.OcfCreateStockCancellation ((stockCancellation "TX_CANCEL_REDUCE" "SEC_CANCEL_ISSUED" 50.0) with + balance_security_id = Some balanceSecurityId) + ] edits = [] deletes = [] @@ -631,9 +639,14 @@ testStockRepurchaseReducesIssuedSharesForCeiling = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_REPURCHASE" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_REPURCHASE" "Repurchase Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_REPURCHASE_ISSUED" "SEC_REPURCHASE_ISSUED" stakeholderId stockClassId) with quantity = 100.0) + let balanceSecurityId = "SEC_REPURCHASE_BALANCE" result <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockRepurchase (stockRepurchase "TX_REPURCHASE_REDUCE" "SEC_REPURCHASE_ISSUED" 50.0)] + creates = + [ CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_REPURCHASE_BALANCE" balanceSecurityId stakeholderId stockClassId) with quantity = 50.0) + , CT.OcfCreateStockRepurchase ((stockRepurchase "TX_REPURCHASE_REDUCE" "SEC_REPURCHASE_ISSUED" 50.0) with + balance_security_id = Some balanceSecurityId) + ] edits = [] deletes = [] @@ -935,11 +948,14 @@ testStockConversionReducesIssuedSharesForCeiling = script do (capTableWithClass, stockClassId) <- setupStockClassWith issuer capTableWithDestination sourceClass (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_CONVERSION" "Conversion Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CONVERSION_ISSUED" "SEC_CONVERSION_ISSUED" stakeholderId stockClassId) with quantity = 100.0) + let balanceSecurityId = "SEC_CONVERSION_BALANCE" result <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with creates = [ stockSuccessorIssuance "TX_CONVERSION_REDUCE_OUTPUT" "TX_CONVERSION_REDUCE_RESULT" stakeholderId destinationClassId defaultTestDate 50.0 - , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_REDUCE" "SEC_CONVERSION_ISSUED" 50.0) + , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CONVERSION_BALANCE" balanceSecurityId stakeholderId stockClassId) with quantity = 50.0) + , CT.OcfCreateStockConversion ((stockConversion "TX_CONVERSION_REDUCE" "SEC_CONVERSION_ISSUED" 50.0) with + balance_security_id = Some balanceSecurityId) ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockConversion.daml b/Test/daml/OpenCapTable/TestStockConversion.daml index 6bbc1dc7..6e056b8e 100644 --- a/Test/daml/OpenCapTable/TestStockConversion.daml +++ b/Test/daml/OpenCapTable/TestStockConversion.daml @@ -204,6 +204,30 @@ testStockConversion_PartialConversion = script do deletes = [] pure () +testStockConversion_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addPreferredStockClass issuer capTableCid + let securityId = "SSEC-PREF-PARTIAL-NO-BALANCE" + conversionDate = DT.time (DA.date 2025 Nov 01) 0 0 0 + result <- createStockIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2023 Jan 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ stockSuccessorIssuance "STOCK-ISS-CONVERSION-PARTIAL-NO-BALANCE" "SSEC-COMMON-PARTIAL-NO-BALANCE" "SH-1" "SC_COMMON" conversionDate 40.0 + , CT.OcfCreateStockConversion StockConversionOcfData with + id = "TX-STOCK-CONVERSION-PARTIAL-NO-BALANCE" + date = conversionDate + security_id = securityId + quantity_converted = 40.0 + resulting_security_ids = ["SSEC-COMMON-PARTIAL-NO-BALANCE"] + comments = [] + balance_security_id = None + ] + edits = [] + deletes = [] + -- Test 3: Conversion with multiple resulting securities (split conversion) testStockConversion_MultipleResulting = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestStockPlanReservedShares.daml b/Test/daml/OpenCapTable/TestStockPlanReservedShares.daml index 07adf16a..4488c524 100644 --- a/Test/daml/OpenCapTable/TestStockPlanReservedShares.daml +++ b/Test/daml/OpenCapTable/TestStockPlanReservedShares.daml @@ -697,7 +697,10 @@ testStockPlanReservedShares_ReturnToPoolAllowsReuse = script do let stockPlanId = "PLAN_RETURN_REUSE" let plan = (defaultStockPlan stockPlanId stockClassId) with initial_shares_reserved = 100.0 let firstIssuance = stockPlanEquityCompensationIssuance "TX_EC_ORIGINAL_POOL" "SEC-EC-ORIGINAL-POOL" stakeholderId stockClassId stockPlanId 100.0 - let cancellation = equityCompensationCancellation "TX_EC_CANCEL_RETURN_TO_POOL" firstIssuance.security_id 40.0 + let balanceSecurityId = "SEC-EC-ORIGINAL-POOL-BALANCE" + let balanceIssuance = stockPlanEquityCompensationIssuance "TX_EC_ORIGINAL_POOL_BALANCE" balanceSecurityId stakeholderId stockClassId stockPlanId 60.0 + let cancellation = (equityCompensationCancellation "TX_EC_CANCEL_RETURN_TO_POOL" firstIssuance.security_id 40.0) with + balance_security_id = Some balanceSecurityId let returnToPool = stockPlanReturnToPool "TX_RETURN_TO_POOL" firstIssuance.security_id stockPlanId 40.0 let secondIssuance = stockPlanEquityCompensationIssuance "TX_EC_REUSED_POOL" "SEC-EC-REUSED-POOL" stakeholderId stockClassId stockPlanId 40.0 @@ -706,6 +709,7 @@ testStockPlanReservedShares_ReturnToPoolAllowsReuse = script do creates = [ CT.OcfCreateStockPlan plan , CT.OcfCreateEquityCompensationIssuance firstIssuance + , CT.OcfCreateEquityCompensationIssuance balanceIssuance , CT.OcfCreateEquityCompensationCancellation cancellation , CT.OcfCreateStockPlanReturnToPool returnToPool , CT.OcfCreateEquityCompensationIssuance secondIssuance @@ -760,7 +764,12 @@ testStockPlanReservedShares_StockCancellationReturnAllowsReuse = script do let firstIssuance = (defaultStockIssuance "TX_STOCK_RETURN_REUSE_FIRST" "SEC-STOCK-RETURN-REUSE-FIRST" stakeholderId stockClassId) with stock_plan_id = Some stockPlanId quantity = 100.0 - let cancellation = stockCancellation "TX_CANCEL_STOCK_RETURN_REUSE" firstIssuance.security_id 40.0 + let balanceSecurityId = "SEC-STOCK-RETURN-REUSE-BALANCE" + let balanceIssuance = (defaultStockIssuance "TX_STOCK_RETURN_REUSE_BALANCE" balanceSecurityId stakeholderId stockClassId) with + stock_plan_id = Some stockPlanId + quantity = 60.0 + let cancellation = (stockCancellation "TX_CANCEL_STOCK_RETURN_REUSE" firstIssuance.security_id 40.0) with + balance_security_id = Some balanceSecurityId let returnToPool = stockPlanReturnToPool "TX_RETURN_STOCK_REUSE" firstIssuance.security_id stockPlanId 40.0 let secondIssuance = (defaultStockIssuance "TX_STOCK_RETURN_REUSE_SECOND" "SEC-STOCK-RETURN-REUSE-SECOND" stakeholderId stockClassId) with stock_plan_id = Some stockPlanId @@ -771,6 +780,7 @@ testStockPlanReservedShares_StockCancellationReturnAllowsReuse = script do creates = [ CT.OcfCreateStockPlan plan , CT.OcfCreateStockIssuance firstIssuance + , CT.OcfCreateStockIssuance balanceIssuance , CT.OcfCreateStockCancellation cancellation , CT.OcfCreateStockPlanReturnToPool returnToPool , CT.OcfCreateStockIssuance secondIssuance @@ -2256,14 +2266,25 @@ testStockPlanReservedShares_ClasslessEquityCancellationReturnUsesPlanClassSplit stockClassId stockPlanId 100.0) with stock_class_id = None + let balanceSecurityId = "SEC-CLASSLESS-EQUITY-RETURN-SPLIT-BALANCE" + let balanceIssuance = (stockPlanEquityCompensationIssuance + "TX_CLASSLESS_EQUITY_RETURN_SPLIT_BALANCE" + balanceSecurityId + stakeholderId + stockClassId + stockPlanId + 50.0) with stock_class_id = None _ <- submit issuer do exerciseCmd capTableReady CT.UpdateCapTable with creates = [ CT.OcfCreateStockPlan plan , CT.OcfCreateEquityCompensationIssuance issuance + , CT.OcfCreateEquityCompensationIssuance balanceIssuance , CT.OcfCreateEquityCompensationCancellation - ((equityCompensationCancellation "TX_CLASSLESS_EQUITY_RETURN_CANCEL" securityId 50.0) with date = defaultTestDate) + ((equityCompensationCancellation "TX_CLASSLESS_EQUITY_RETURN_CANCEL" securityId 50.0) with + date = defaultTestDate + balance_security_id = Some balanceSecurityId) , CT.OcfCreateStockClassSplit ((stockClassSplit "TX_CLASSLESS_EQUITY_RETURN_SPLIT_EVENT" stockClassId 2.0 1.0) with date = splitDate) , CT.OcfCreateStockPlanReturnToPool diff --git a/Test/daml/OpenCapTable/TestStockRepurchase.daml b/Test/daml/OpenCapTable/TestStockRepurchase.daml index 4d1b235f..011e09a0 100644 --- a/Test/daml/OpenCapTable/TestStockRepurchase.daml +++ b/Test/daml/OpenCapTable/TestStockRepurchase.daml @@ -61,6 +61,26 @@ testStockRepurchase_FullRepurchase = script do deletes = [] pure () +testStockRepurchase_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SSEC-REPURCHASE-PARTIAL-NO-BALANCE" + result <- createStockIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2022 Jan 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockRepurchase StockRepurchaseOcfData with + id = "TX-STOCK-REPURCHASE-PARTIAL-NO-BALANCE" + date = DT.time (DA.date 2023 Feb 25) 0 0 0 + security_id = securityId + price = OcfMonetary with amount = 0.01, currency = "USD" + quantity = 40.0 + comments = [] + balance_security_id = None + consideration_text = None] + edits = [] + deletes = [] + -- Test 2: Partial repurchase with balance security testStockRepurchase_PartialRepurchase = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestStockSecurityBalances.daml b/Test/daml/OpenCapTable/TestStockSecurityBalances.daml index ceb0fd62..74c84e96 100644 --- a/Test/daml/OpenCapTable/TestStockSecurityBalances.daml +++ b/Test/daml/OpenCapTable/TestStockSecurityBalances.daml @@ -218,19 +218,25 @@ testStockSecurityBalance_FullReductionWithoutBalanceCanConsumeRemainingSource = TestOcp{issuer, cap_table} <- setupTestOcp (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table let securityId = "SEC_STOCK_BAL_ZERO_REMAINDER" + let balanceSecurityId = "SEC_STOCK_BAL_AFTER_CANCELLATION" 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 + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_AFTER_CANCELLATION" balanceSecurityId stakeholderId stockClassId) with + date = jan02 + 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 balanceIssuance + , CT.OcfCreateStockCancellation ((stockCancellationOn "TX_CANCEL_BEFORE_BALANCE_TRANSFER" securityId 40.0 jan02) with + balance_security_id = Some balanceSecurityId) + , CT.OcfCreateStockTransfer (stockTransfer transferId balanceSecurityId 60.0 jan03) , CT.OcfCreateStockIssuance resultIssuance ] edits = [] @@ -474,14 +480,20 @@ testStockSecurityBalance_SplitAdjustedBalanceAllowsReduction = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableReady, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table let securityId = "SEC_STOCK_BAL_SPLIT" + let balanceSecurityId = "SEC_STOCK_BAL_SPLIT_REMAINDER" let issuance = (defaultStockIssuance "TX_STOCK_BAL_SPLIT" securityId stakeholderId stockClassId) with quantity = 100.0 + let balanceIssuance = (defaultStockIssuance "TX_STOCK_BAL_SPLIT_REMAINDER" balanceSecurityId stakeholderId stockClassId) with + date = jan03 + quantity = 50.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) + , CT.OcfCreateStockIssuance balanceIssuance + , CT.OcfCreateStockRepurchase ((stockRepurchase "TX_REPURCHASE_AFTER_SPLIT" securityId 150.0 jan03) with + balance_security_id = Some balanceSecurityId) ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockTransfer.daml b/Test/daml/OpenCapTable/TestStockTransfer.daml index 4d1679a8..840a000a 100644 --- a/Test/daml/OpenCapTable/TestStockTransfer.daml +++ b/Test/daml/OpenCapTable/TestStockTransfer.daml @@ -144,6 +144,31 @@ testStockTransfer_ResultQuantityMustMatchTransfer = script do edits = [] deletes = [] +testStockTransfer_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + (capTableCid, transfereeId) <- setupStakeholderWithId issuer capTableCid "SH-PARTIAL-NO-BALANCE-TRANSFEREE" "Partial Transferee" + let securityId = "SSEC-TRANSFER-PARTIAL-NO-BALANCE" + transferDate = DT.time (DA.date 2025 Aug 05) 0 0 0 + result <- createStockIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2023 Jan 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ stockSuccessorIssuance "STOCK-ISS-PARTIAL-NO-BALANCE" "SSEC-PARTIAL-NO-BALANCE-RESULT" transfereeId "SC_COMMON" transferDate 40.0 + , CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX-STOCK-TRANSFER-PARTIAL-NO-BALANCE" + date = transferDate + quantity = 40.0 + security_id = securityId + resulting_security_ids = ["SSEC-PARTIAL-NO-BALANCE-RESULT"] + comments = [] + balance_security_id = None + consideration_text = None + ] + edits = [] + deletes = [] + testStockTransfer_ResultQuantityNormalizesInterveningSplit = script do TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table diff --git a/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml index ca07fa32..42a9166f 100644 --- a/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml +++ b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml @@ -110,6 +110,13 @@ equityCompensationSuccessorIssuance issuanceId securityId stakeholderId date qua quantity = quantity expiration_date = expirationDate) +warrantSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +warrantSuccessorIssuance issuanceId securityId stakeholderId date quantity = + CT.OcfCreateWarrantIssuance ((defaultWarrantIssuance issuanceId securityId stakeholderId) with + date = date + quantity = Some quantity + quantity_source = Some OcfQuantityInstrumentFixed) + warrantExerciseTrigger : Decimal -> OcfConversionTrigger warrantExerciseTrigger quantity = OcfConversionTrigger with conversion_right = OcfRightWarrant (OcfWarrantConversionRight with @@ -569,6 +576,7 @@ testVestingQuantityIntegrity_StockBalanceSuccessorCarriesSplitAdjustedAccelerati capTableCid <- addPrerequisites issuer cap_table let sourceSecurityId = "SEC-ACC-STOCK-LINEAGE-SOURCE" successorSecurityId = "SEC-ACC-STOCK-LINEAGE-BALANCE" + chainedSuccessorSecurityId = "SEC-ACC-STOCK-LINEAGE-BALANCE-2" sourceIssuance = (defaultStockIssuance "TX-ACC-STOCK-LINEAGE-SOURCE" sourceSecurityId "SH-1" "SC_COMMON") with quantity = 100.0 @@ -593,21 +601,29 @@ testVestingQuantityIntegrity_StockBalanceSuccessorCarriesSplitAdjustedAccelerati , CT.OcfCreateStockCancellation ((stockCancellationOn "CANCEL-ACC-STOCK-LINEAGE" sourceSecurityId 40.0 splitDate) with balance_security_id = Some successorSecurityId) + , stockSuccessorIssuance + "TX-ACC-STOCK-LINEAGE-BALANCE-2" + chainedSuccessorSecurityId + "SH-1" + "SC_COMMON" + splitDate + 50.0 , CT.OcfCreateStockCancellation - (stockCancellationOn "CANCEL-ACC-STOCK-LINEAGE-SUCCESSOR" successorSecurityId 10.0 splitDate) + ((stockCancellationOn "CANCEL-ACC-STOCK-LINEAGE-SUCCESSOR" successorSecurityId 10.0 splitDate) with + balance_security_id = Some chainedSuccessorSecurityId) ] [] [] exactResult <- applyUpdate issuer balanceResult.updatedCapTableCid - [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-EXACT" successorSecurityId 60.0) with + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-EXACT" chainedSuccessorSecurityId 50.0) with date = postSplitAccelerationDate)] [] [] submitMustFail issuer do exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-OVER" successorSecurityId 1.0) with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-OVER" chainedSuccessorSecurityId 1.0) with date = postSplitAccelerationDate)] edits = [] deletes = [] @@ -1044,6 +1060,7 @@ testVestingQuantityIntegrity_StockReductionConsumesPriorAccelerations = script d TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-ACC-STOCK-REDUCTION" + balanceSecurityId = "SEC-ACC-STOCK-REDUCTION-BALANCE" issuance = (defaultStockIssuance "TX-ACC-STOCK-REDUCTION" securityId "SH-1" "SC_COMMON") with quantity = 100.0 @@ -1057,19 +1074,22 @@ testVestingQuantityIntegrity_StockReductionConsumesPriorAccelerations = script d reductionResult <- applyUpdate issuer accelerationResult.updatedCapTableCid - [CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-ACC-STOCK-REDUCTION" securityId 40.0 postSplitAccelerationDate)] + [ stockSuccessorIssuance "TX-ACC-STOCK-REDUCTION-BALANCE" balanceSecurityId "SH-1" "SC_COMMON" postSplitAccelerationDate 60.0 + , CT.OcfCreateStockCancellation ((stockCancellationOn "CANCEL-ACC-STOCK-REDUCTION" securityId 40.0 postSplitAccelerationDate) with + balance_security_id = Some balanceSecurityId) + ] [] [] exactResult <- applyUpdate issuer reductionResult.updatedCapTableCid - [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-EXACT" securityId 40.0) with + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-EXACT" balanceSecurityId 24.0) with date = successorAccelerationDate)] [] [] submitMustFail issuer do exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-OVER" securityId 1.0) with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-OVER" balanceSecurityId 1.0) with date = successorAccelerationDate)] edits = [] deletes = [] @@ -1078,6 +1098,7 @@ testVestingQuantityIntegrity_EquityCompensationReductionConsumesPriorAcceleratio TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-ACC-EC-REDUCTION" + balanceSecurityId = "SEC-ACC-EC-REDUCTION-BALANCE" issuance = (defaultEquityCompensationIssuance "TX-ACC-EC-REDUCTION" securityId "SH-1") with quantity = 100.0 @@ -1091,26 +1112,28 @@ testVestingQuantityIntegrity_EquityCompensationReductionConsumesPriorAcceleratio reductionResult <- applyUpdate issuer accelerationResult.updatedCapTableCid - [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with - id = "CANCEL-ACC-EC-REDUCTION" - date = postSplitAccelerationDate - security_id = securityId - quantity = 40.0 - reason_text = "Partial cancellation" - balance_security_id = None - comments = []] + [ equityCompensationSuccessorIssuance "TX-ACC-EC-REDUCTION-BALANCE" balanceSecurityId "SH-1" postSplitAccelerationDate 60.0 None + , CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "CANCEL-ACC-EC-REDUCTION" + date = postSplitAccelerationDate + security_id = securityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = Some balanceSecurityId + comments = [] + ] [] [] exactResult <- applyUpdate issuer reductionResult.updatedCapTableCid - [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-EXACT" securityId 40.0) with + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-EXACT" balanceSecurityId 24.0) with date = successorAccelerationDate)] [] [] submitMustFail issuer do exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-OVER" securityId 1.0) with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-OVER" balanceSecurityId 1.0) with date = successorAccelerationDate)] edits = [] deletes = [] @@ -1119,6 +1142,7 @@ testVestingQuantityIntegrity_KnownWarrantReductionConsumesPriorAccelerations = s TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-ACC-WARRANT-REDUCTION" + balanceSecurityId = "SEC-ACC-WARRANT-REDUCTION-BALANCE" issuance = (defaultWarrantIssuance "TX-ACC-WARRANT-REDUCTION" securityId "SH-1") with quantity = Some 100.0 @@ -1133,26 +1157,28 @@ testVestingQuantityIntegrity_KnownWarrantReductionConsumesPriorAccelerations = s reductionResult <- applyUpdate issuer accelerationResult.updatedCapTableCid - [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with - id = "CANCEL-ACC-WARRANT-REDUCTION" - date = postSplitAccelerationDate - security_id = securityId - quantity = 40.0 - reason_text = "Partial cancellation" - balance_security_id = None - comments = []] + [ warrantSuccessorIssuance "TX-ACC-WARRANT-REDUCTION-BALANCE" balanceSecurityId "SH-1" postSplitAccelerationDate 60.0 + , CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "CANCEL-ACC-WARRANT-REDUCTION" + date = postSplitAccelerationDate + security_id = securityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = Some balanceSecurityId + comments = [] + ] [] [] exactResult <- applyUpdate issuer reductionResult.updatedCapTableCid - [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-EXACT" securityId 40.0) with + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-EXACT" balanceSecurityId 24.0) with date = successorAccelerationDate)] [] [] submitMustFail issuer do exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-OVER" securityId 1.0) with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-OVER" balanceSecurityId 1.0) with date = successorAccelerationDate)] edits = [] deletes = [] @@ -1163,17 +1189,20 @@ testVestingQuantityIntegrity_AccelerationCannotExceedLiveStockBalance = script d let issuance = (defaultStockIssuance "TX-ACC-LIVE-BALANCE" "SEC-ACC-LIVE-BALANCE" "SH-1" "SC_COMMON") with quantity = 100.0 + balanceSecurityId = "SEC-ACC-LIVE-BALANCE-SUCCESSOR" balanceResult <- applyUpdate issuer capTableCid [ CT.OcfCreateStockIssuance issuance - , CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-ACC-LIVE-BALANCE" issuance.security_id 80.0 firstVestingDate) + , stockSuccessorIssuance "TX-ACC-LIVE-BALANCE-SUCCESSOR" balanceSecurityId "SH-1" "SC_COMMON" firstVestingDate 20.0 + , CT.OcfCreateStockCancellation ((stockCancellationOn "CANCEL-ACC-LIVE-BALANCE" issuance.security_id 80.0 firstVestingDate) with + balance_security_id = Some balanceSecurityId) ] [] [] submitMustFail issuer do exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-LIVE-BALANCE" issuance.security_id 50.0)] + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-LIVE-BALANCE" balanceSecurityId 50.0)] edits = [] deletes = [] @@ -1183,6 +1212,7 @@ testVestingQuantityIntegrity_LaterBalanceReductionDoesNotRetroactivelyInvalidate let issuance = (defaultStockIssuance "TX-ACC-BEFORE-REDUCTION" "SEC-ACC-BEFORE-REDUCTION" "SH-1" "SC_COMMON") with quantity = 100.0 + balanceSecurityId = "SEC-ACC-AFTER-REDUCTION" issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] accelerationResult <- applyUpdate issuer @@ -1193,7 +1223,10 @@ testVestingQuantityIntegrity_LaterBalanceReductionDoesNotRetroactivelyInvalidate _ <- applyUpdate issuer accelerationResult.updatedCapTableCid - [CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-AFTER-ACCELERATION" issuance.security_id 50.0 secondVestingDate)] + [ stockSuccessorIssuance "TX-ACC-AFTER-REDUCTION" balanceSecurityId "SH-1" "SC_COMMON" secondVestingDate 50.0 + , CT.OcfCreateStockCancellation ((stockCancellationOn "CANCEL-AFTER-ACCELERATION" issuance.security_id 50.0 secondVestingDate) with + balance_security_id = Some balanceSecurityId) + ] [] [] pure () diff --git a/Test/daml/OpenCapTable/TestWarrantCancellation.daml b/Test/daml/OpenCapTable/TestWarrantCancellation.daml index 1db1ba2e..9e912603 100644 --- a/Test/daml/OpenCapTable/TestWarrantCancellation.daml +++ b/Test/daml/OpenCapTable/TestWarrantCancellation.daml @@ -2,7 +2,7 @@ module OpenCapTable.TestWarrantCancellation where import Fairmint.OpenCapTable.OCF.WarrantCancellation import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder, createWarrantIssuance) +import OpenCapTable.TestHelpers (addDefaultStakeholder, createWarrantIssuance, defaultWarrantIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -41,6 +41,62 @@ testWarrantCancellation_CreateAndArchiveBuiltIn = script do _ <- createWarrantCancellation issuer result.updatedCapTableCid securityId 100000.0 pure () +testWarrantCancellation_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC-PARTIAL-NO-BALANCE" + result <- createWarrantIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "TX-WARRANT-CANCEL-PARTIAL-NO-BALANCE" + date = DT.time (DA.date 2024 Mar 15) 0 0 0 + security_id = securityId + quantity = 40.0 + reason_text = "Invalid partial cancellation" + balance_security_id = None + comments = []] + edits = [] + deletes = [] + +testWarrantCancellation_UnknownQuantityWithoutBalanceRetiresSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "WSEC-UNKNOWN-TERMINAL" + issued <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance ((defaultWarrantIssuance "TX-WARRANT-UNKNOWN-TERMINAL" securityId "SH-1") with + quantity = None + quantity_source = None)] + edits = [] + deletes = [] + cancelled <- submit issuer do + exerciseCmd issued.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "TX-WARRANT-UNKNOWN-TERMINAL-FIRST" + date = DT.time (DA.date 2024 Mar 15) 0 0 0 + security_id = securityId + quantity = 10.0 + reason_text = "Dispose unknown warrant quantity" + balance_security_id = None + comments = []] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd cancelled.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "TX-WARRANT-UNKNOWN-TERMINAL-SECOND" + date = DT.time (DA.date 2024 Mar 16) 0 0 0 + security_id = securityId + quantity = 1.0 + reason_text = "Cannot reuse a retired source" + balance_security_id = None + comments = []] + edits = [] + deletes = [] + -- Partial cancellation with optional fields populated testWarrantCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp diff --git a/Test/daml/OpenCapTable/TestWarrantTransfer.daml b/Test/daml/OpenCapTable/TestWarrantTransfer.daml index db39d35a..90c2eccb 100644 --- a/Test/daml/OpenCapTable/TestWarrantTransfer.daml +++ b/Test/daml/OpenCapTable/TestWarrantTransfer.daml @@ -89,6 +89,31 @@ testWarrantTransfer_PartialTransfer = script do deletes = [] pure () +testWarrantTransfer_PartialWithoutBalanceSecurityFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + (capTableCid, assigneeId) <- setupStakeholderWithId issuer capTableCid "SH-WARRANT-PARTIAL-NO-BALANCE" "Warrant Assignee" + let securityId = "WSEC-TRANSFER-PARTIAL-NO-BALANCE" + transferDate = DT.time (DA.date 2025 Jun 01) 0 0 0 + result <- createWarrantIssuance issuer capTableCid securityId 100.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ warrantTransferSuccessorIssuance "WR-ISS-PARTIAL-NO-BALANCE" "WSEC-PARTIAL-NO-BALANCE-RESULT" assigneeId transferDate 40.0 + , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + id = "TX-WARRANT-TRANSFER-PARTIAL-NO-BALANCE" + date = transferDate + quantity = 40.0 + security_id = securityId + resulting_security_ids = ["WSEC-PARTIAL-NO-BALANCE-RESULT"] + comments = [] + balance_security_id = None + consideration_text = None + ] + edits = [] + deletes = [] + testWarrantTransfer_BalanceMustKeepSourceStakeholder = script do TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table diff --git a/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar new file mode 100644 index 00000000..981e4091 --- /dev/null +++ b/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a1a8a0183b1ffef5aa873fea309468efc3f240d3560e0c54d2bfcb2d5f30edb +size 3451383 diff --git a/dars/dars.lock b/dars/dars.lock index 03ba7c86..a135e82d 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -74,6 +74,13 @@ "uploadedAt": "2026-07-12T00:40:48.908Z", "networks": [] }, + "OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar": { + "sha256": "1a1a8a0183b1ffef5aa873fea309468efc3f240d3560e0c54d2bfcb2d5f30edb", + "size": 3451383, + "sdkVersion": "3.4.10", + "uploadedAt": "2026-07-12T02:35:44.906Z", + "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 4c648795..0c70187d 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -1489,33 +1489,21 @@ applyStockClassShareEventOptional currentOpt event = None -> None stockSecurityReductionEvents : Time -> Decimal -> Optional Text -> [StockClassShareEvent] -stockSecurityReductionEvents eventDate quantity balanceSecurityId = case balanceSecurityId of - Some _ -> - [ StockClassShareTerminalDelta with - event_date = eventDate - event_priority = 2 - quantity_delta = 0.0 - quantity - ] - None -> - [ StockClassShareDelta with - event_date = eventDate - event_priority = 1 - quantity_delta = 0.0 - quantity - ] +-- OCF terminal transactions retire the source security. Any remainder is +-- reissued under balance_security_id rather than left on the original ID. +stockSecurityReductionEvents eventDate quantity _ = + [ StockClassShareTerminalDelta with + event_date = eventDate + event_priority = 2 + 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 +securityQuantityReductionEvent eventDate quantity _ = + SecurityQuantityTerminalDelta with + event_date = eventDate + event_priority = 2 + quantity_delta = 0.0 - quantity addStockClassShareEvent : Text -> StockClassShareEvent -> Map Text [StockClassShareEvent] -> Map Text [StockClassShareEvent] addStockClassShareEvent key event index = @@ -2005,15 +1993,15 @@ validateStockTerminalBalanceSuccessor : Decimal -> Optional Text -> Update () -validateStockTerminalBalanceSuccessor eventIndex maps label sourceIssuanceData eventDate reductionQuantity balanceSecurityId = +validateStockTerminalBalanceSuccessor eventIndex maps label sourceIssuanceData eventDate reductionQuantity balanceSecurityId = 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) 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) assertMsg (label <> " balance security requires a positive remaining source quantity") (expectedQuantity > 0.0) @@ -2025,7 +2013,10 @@ validateStockTerminalBalanceSuccessor eventIndex maps label sourceIssuanceData e targetSortKey expectedQuantity securityId - None -> pure () + None -> + assertMsg + (label <> " without a balance security must consume the full current source quantity for " <> sourceIssuanceData.security_id <> ": current " <> show currentQuantity <> ", reduction " <> show reductionQuantity) + (expectedQuantity == 0.0) validateStockTerminalBalanceSuccessorForSource : StockClassShareEventIndex -> @@ -2216,7 +2207,7 @@ convertibleBalanceEvents maps issuanceData = do then pure ((ConvertibleMonetaryReduction with event_label = "Convertible transfer " <> transfer.transfer_data.id event_date = transfer.transfer_data.date - event_priority = if isSome transfer.transfer_data.balance_security_id then 2 else 1 + event_priority = 2 amount = transfer.transfer_data.amount.amount currency = transfer.transfer_data.amount.currency balance_security_id = transfer.transfer_data.balance_security_id @@ -2234,7 +2225,7 @@ convertibleBalanceEvents maps issuanceData = do then pure ((ConvertibleMonetaryReduction with event_label = "Convertible cancellation " <> cancellation.cancellation_data.id event_date = cancellation.cancellation_data.date - event_priority = if isSome cancellation.cancellation_data.balance_security_id then 2 else 1 + event_priority = 2 amount = cancellation.cancellation_data.amount.amount currency = cancellation.cancellation_data.amount.currency balance_security_id = cancellation.cancellation_data.balance_security_id @@ -2479,7 +2470,11 @@ validateConvertibleBalanceEvent maps issuanceData currentAmount event = case eve remainingAmount balanceSecurityId pure 0.0 - None -> pure remainingAmount + None -> do + assertMsg + (event_label <> " without a balance security must consume the full current source balance for " <> issuanceData.security_id <> ": current " <> show currentAmount <> ", reduction " <> show amount) + (remainingAmount == 0.0) + pure 0.0 ConvertibleRetirement with event_label; event_date; balance_security_id -> do assertMsg (event_label <> " requires a positive source balance for " <> issuanceData.security_id) @@ -3076,20 +3071,27 @@ validateWarrantTerminalBalanceSuccessor : 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 () + 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 balanceSecurityId of + Some securityId -> + 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 -> + case currentQuantityOpt of + Some currentQuantity -> + assertMsg + (label <> " without a balance security must consume the full current source quantity for " <> sourceSecurityId <> ": current " <> show currentQuantity <> ", reduction " <> show reductionQuantity) + (currentQuantity == reductionQuantity) + None -> pure () + None -> assertMsg (label <> " source warrant issuance not found: " <> sourceSecurityId) False validateEquityCompensationSecurityBalanceSuccessorQuantity : CapTableMaps -> @@ -3133,18 +3135,22 @@ validateEquityCompensationTerminalBalanceSuccessor : 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 () + 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 + case balanceSecurityId of + Some securityId -> do + assertMsg (label <> " balance successor requires a positive remainder") (expectedQuantity > 0.0) + validateEquityCompensationSecurityBalanceSuccessorQuantity + maps label sourceIssuance.issuance_data eventDate expectedQuantity securityId + None -> + assertMsg + (label <> " without a balance security must consume the full current source quantity for " <> sourceSecurityId <> ": current " <> show currentQuantity <> ", reduction " <> show reductionQuantity) + (expectedQuantity == 0.0) + None -> assertMsg (label <> " source equity compensation issuance not found: " <> sourceSecurityId) False validateNonStockBalanceSuccessorQuantities : StockClassShareEventIndex -> From 84716e5efc19bf8da68c53c2d35fa3dc2b5798d4 Mon Sep 17 00:00:00 2001 From: HardlyDifficult Date: Sat, 11 Jul 2026 23:24:02 -0400 Subject: [PATCH 2/2] fix: align terminal vesting predecessor ordering --- .../TestVestingQuantityIntegrity.daml | 74 +++++++++++++++++++ .../0.0.19/OpenCapTable-v34.dar | 4 +- dars/dars.lock | 6 +- .../codegen/templates/CapTable.daml.template | 5 +- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml index 42a9166f..3897be8e 100644 --- a/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml +++ b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml @@ -1056,6 +1056,80 @@ testVestingQuantityIntegrity_NonStockTransferResultsCarryAccelerations = script edits = [] deletes = [] +testVestingQuantityIntegrity_SameDayReleaseBeforeNoBalanceTransferUsesLiveSource = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-RELEASE-TRANSFER-SOURCE" + releaseResultSecurityId = "SEC-ACC-RELEASE-TRANSFER-STOCK" + transferResultSecurityId = "SEC-ACC-RELEASE-TRANSFER-RESULT" + sourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-RELEASE-TRANSFER-SOURCE" sourceSecurityId "SH-1") with + compensation_type = OcfCompensationTypeRSU + exercise_price = None + stock_class_id = Some "SC_COMMON" + quantity = 100.0 + issuanceResult <- applyUpdate + issuer + capTableCid + [CT.OcfCreateEquityCompensationIssuance sourceIssuance] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-RELEASE-TRANSFER-SOURCE" sourceSecurityId 60.0)] + [] + [] + transferResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ fullyVestedStockSuccessorIssuance + "TX-ACC-RELEASE-TRANSFER-STOCK" + releaseResultSecurityId + "SH-1" + "SC_COMMON" + successorAccelerationDate + 60.0 + , CT.OcfCreateEquityCompensationIssuance ((defaultEquityCompensationIssuance + "TX-ACC-RELEASE-TRANSFER-RESULT" + transferResultSecurityId + "SH-1") with + date = postSplitAccelerationDate + compensation_type = OcfCompensationTypeRSU + exercise_price = None + stock_class_id = Some "SC_COMMON" + quantity = 40.0) + , CT.OcfCreateEquityCompensationRelease EquityCompensationReleaseOcfData with + id = "TX-ACC-RELEASE-BEFORE-TRANSFER" + date = postSplitAccelerationDate + security_id = sourceSecurityId + settlement_date = successorAccelerationDate + release_price = OcfMonetary with amount = 1.0; currency = "USD" + quantity = 60.0 + resulting_security_ids = [releaseResultSecurityId] + consideration_text = None + comments = [] + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + id = "TX-ACC-TRANSFER-AFTER-RELEASE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + resulting_security_ids = [transferResultSecurityId] + balance_security_id = None + consideration_text = None + comments = [] + ] + [] + [] + _ <- applyUpdate + issuer + transferResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-RELEASE-TRANSFER-RESULT" transferResultSecurityId 40.0) with + date = successorAccelerationDate)] + [] + [] + pure () + testVestingQuantityIntegrity_StockReductionConsumesPriorAccelerations = script do TestOcp{issuer, cap_table} <- setupTestOcp capTableCid <- addPrerequisites issuer cap_table diff --git a/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar index 981e4091..59e6656a 100644 --- a/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar +++ b/dars/OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a1a8a0183b1ffef5aa873fea309468efc3f240d3560e0c54d2bfcb2d5f30edb -size 3451383 +oid sha256:4292c6e093ef58d9c612670b079519be431c93180582cdb9e80bbdf6f4be9cdd +size 3451178 diff --git a/dars/dars.lock b/dars/dars.lock index a135e82d..f082aeaf 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -75,10 +75,10 @@ "networks": [] }, "OpenCapTable-v34/0.0.19/OpenCapTable-v34.dar": { - "sha256": "1a1a8a0183b1ffef5aa873fea309468efc3f240d3560e0c54d2bfcb2d5f30edb", - "size": 3451383, + "sha256": "4292c6e093ef58d9c612670b079519be431c93180582cdb9e80bbdf6f4be9cdd", + "size": 3451178, "sdkVersion": "3.4.10", - "uploadedAt": "2026-07-12T02:35:44.906Z", + "uploadedAt": "2026-07-12T03:20:45.033Z", "networks": [] }, "OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar": { diff --git a/scripts/codegen/templates/CapTable.daml.template b/scripts/codegen/templates/CapTable.daml.template index 0c70187d..6568c649 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -3286,10 +3286,7 @@ validateStockConsolidationShareNumberRanges label sourceIssuanceData resultIssua (null resultIssuanceData.share_numbers_issued) stockReductionSortKey : Time -> Optional Text -> (Time, Int, Int) -stockReductionSortKey eventDate balanceSecurityId = - case balanceSecurityId of - Some _ -> (eventDate, 2, 1) - None -> (eventDate, 1, 0) +stockReductionSortKey eventDate _ = (eventDate, 2, 1) validateNumberedStockPartialReduction : StockClassShareEventIndex ->