diff --git a/OpenCapTable-v34/daml.yaml b/OpenCapTable-v34/daml.yaml index f16d912a..70b5e670 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.5 +version: 0.0.6 build-options: - -Wno-crypto-text-is-alpha - --typecheck-upgrades=no diff --git a/Test/daml.yaml b/Test/daml.yaml index 64cd009c..0c90f894 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.5.dar + - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.6.dar build-options: - -Wno-template-interface-depends-on-daml-script diff --git a/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml b/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml new file mode 100644 index 00000000..200af723 --- /dev/null +++ b/Test/daml/OpenCapTable/TestBalanceSecurityReferences.daml @@ -0,0 +1,152 @@ +module OpenCapTable.TestBalanceSecurityReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.ConvertibleCancellation (ConvertibleCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationCancellation (EquityCompensationCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockCancellation (StockCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantCancellation (WarrantCancellationOcfData(..)) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers + ( addDefaultConvertibleIssuance + , addDefaultEquityCompensationIssuance + , addDefaultStakeholder + , addDefaultStockIssuance + , addDefaultWarrantIssuance + , addPrerequisites + , defaultStockIssuance + , defaultTestDate + ) +import Daml.Script + +testBalanceSecurityReferences_MissingStockSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockCancellation StockCancellationOcfData with + id = "TX-STOCK-MISSING-BALANCE" + date = defaultTestDate + quantity = 10.0 + reason_text = "Partial cancellation" + security_id = "SEC-STOCK-1" + comments = [] + balance_security_id = Some "SEC-STOCK-MISSING-BALANCE"] + edits = [] + deletes = [] + +testBalanceSecurityReferences_MissingWarrantSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addDefaultWarrantIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "TX-WARRANT-MISSING-BALANCE" + date = defaultTestDate + quantity = 10.0 + reason_text = "Partial cancellation" + security_id = "SEC-WR-1" + comments = [] + balance_security_id = Some "SEC-WARRANT-MISSING-BALANCE"] + edits = [] + deletes = [] + +testBalanceSecurityReferences_MissingConvertibleSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addDefaultConvertibleIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleCancellation ConvertibleCancellationOcfData with + id = "TX-CONVERTIBLE-MISSING-BALANCE" + amount = OcfMonetary with amount = 10.0, currency = "USD" + date = defaultTestDate + reason_text = "Partial cancellation" + security_id = "SEC-CV-1" + comments = [] + balance_security_id = Some "SEC-CONVERTIBLE-MISSING-BALANCE"] + edits = [] + deletes = [] + +testBalanceSecurityReferences_MissingEquityCompensationSuccessorFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultEquityCompensationIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "TX-EC-MISSING-BALANCE" + date = defaultTestDate + quantity = 10.0 + reason_text = "Partial cancellation" + security_id = "SEC-EC-1" + comments = [] + balance_security_id = Some "SEC-EC-MISSING-BALANCE"] + edits = [] + deletes = [] + +testBalanceSecurityReferences_StockSuccessorCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + let balanceIssuance = (defaultStockIssuance + "TX-STOCK-BALANCE-SUCCESSOR" + "SEC-STOCK-BALANCE-SUCCESSOR" + "SH-1" + "SC_COMMON") with quantity = 90.0 + + _ <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance balanceIssuance + , CT.OcfCreateStockCancellation StockCancellationOcfData with + id = "TX-STOCK-CANCELLATION-BALANCE-SUCCESSOR" + date = defaultTestDate + quantity = 10.0 + reason_text = "Partial cancellation" + security_id = "SEC-STOCK-1" + comments = [] + balance_security_id = Some "SEC-STOCK-BALANCE-SUCCESSOR" + ] + edits = [] + deletes = [] + pure () + +testBalanceSecurityReferences_EditCanCreateStockSuccessorInSameBatch = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + let cancellation = StockCancellationOcfData with + id = "TX-STOCK-EDIT-BALANCE" + date = defaultTestDate + quantity = 10.0 + reason_text = "Partial 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 = [] + + let balanceIssuance = (defaultStockIssuance + "TX-STOCK-EDIT-BALANCE-SUCCESSOR" + "SEC-STOCK-EDIT-BALANCE-SUCCESSOR" + "SH-1" + "SC_COMMON") with quantity = 90.0 + + _ <- submit issuer do + exerciseCmd created.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance balanceIssuance] + edits = [CT.OcfEditStockCancellation (cancellation with + balance_security_id = Some "SEC-STOCK-EDIT-BALANCE-SUCCESSOR")] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestConversionMechanisms.daml b/Test/daml/OpenCapTable/TestConversionMechanisms.daml index f0d24123..a1ea8057 100644 --- a/Test/daml/OpenCapTable/TestConversionMechanisms.daml +++ b/Test/daml/OpenCapTable/TestConversionMechanisms.daml @@ -13,7 +13,7 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Stock (OcfInitialSharesAuthorized(..), OcfQuantitySourceType(..), OcfRatio(..), OcfStockClassType(..)) import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, defaultStockClass) testTime : Time testTime = DT.time (DA.date 2024 Jan 1) 0 0 0 @@ -392,22 +392,25 @@ testConversionMechanismRights_AllValidVariantsCreate = script do capTableCid <- addDefaultStakeholder issuer cap_table result1 <- submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_PREFERRED_ALL_CONVERSION_MECHANISMS" - name = "Preferred All Conversion Mechanisms" - class_type = OcfStockClassTypePreferred - default_id_prefix = "PAM-" - initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 - votes_per_share = 1.0 - seniority = 2.0 - board_approval_date = None - stockholder_approval_date = None - par_value = None - price_per_share = None - liquidation_preference_multiple = None - participation_cap_multiple = None - conversion_rights = validStockClassRights - comments = []] + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_COMMON") + , CT.OcfCreateStockClass (StockClassOcfData with + id = "SC_PREFERRED_ALL_CONVERSION_MECHANISMS" + name = "Preferred All Conversion Mechanisms" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PAM-" + initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 + votes_per_share = 1.0 + seniority = 2.0 + board_approval_date = None + stockholder_approval_date = None + par_value = None + price_per_share = None + liquidation_preference_multiple = None + participation_cap_multiple = None + conversion_rights = validStockClassRights + comments = []) + ] edits = [] deletes = [] result2 <- submit issuer do @@ -460,22 +463,25 @@ testConversionMechanismRights_InvalidVariantsReject = script do capTableCid <- addDefaultStakeholder issuer cap_table stockControl <- submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_VALID_CONVERSION_MECHANISM_CONTROL" - name = "Valid Conversion Mechanism Control" - class_type = OcfStockClassTypePreferred - default_id_prefix = "VCM-" - initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 - votes_per_share = 1.0 - seniority = 2.0 - board_approval_date = None - stockholder_approval_date = None - par_value = None - price_per_share = None - liquidation_preference_multiple = None - participation_cap_multiple = None - conversion_rights = [stockClassRightWith OcfConversionMechanismRatioConversion None None (Some validRatio) None None None None None] - comments = []] + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_COMMON") + , CT.OcfCreateStockClass (StockClassOcfData with + id = "SC_VALID_CONVERSION_MECHANISM_CONTROL" + name = "Valid Conversion Mechanism Control" + class_type = OcfStockClassTypePreferred + default_id_prefix = "VCM-" + initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 + votes_per_share = 1.0 + seniority = 2.0 + board_approval_date = None + stockholder_approval_date = None + par_value = None + price_per_share = None + liquidation_preference_multiple = None + participation_cap_multiple = None + conversion_rights = [stockClassRightWith OcfConversionMechanismRatioConversion None None (Some validRatio) None None None None None] + comments = []) + ] edits = [] deletes = [] submitMustFail issuer do diff --git a/Test/daml/OpenCapTable/TestConvertibleCancellation.daml b/Test/daml/OpenCapTable/TestConvertibleCancellation.daml index 8cef2086..ba3c7466 100644 --- a/Test/daml/OpenCapTable/TestConvertibleCancellation.daml +++ b/Test/daml/OpenCapTable/TestConvertibleCancellation.daml @@ -13,12 +13,12 @@ import qualified DA.Time as DT import Daml.Script -- Create a convertible issuance to cancel -createConvertibleIssuance issuer capTableCid securityId amount = +createConvertibleIssuance issuer capTableCid securityId amount issuanceDate = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with creates = [CT.OcfCreateConvertibleIssuance ConvertibleIssuanceOcfData with id = "TX_CONV_ISS_" <> securityId - date = DT.time (DA.date 2022 Jun 01) 0 0 0 + date = issuanceDate security_id = securityId custom_id = "SAFE-1" stakeholder_id = "SH-1" @@ -38,7 +38,7 @@ createConvertibleIssuance issuer capTableCid securityId amount = testConvertibleCancellation_Create = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table - result <- createConvertibleIssuance issuer capTableCid "SEC-1" 25000.0 + result <- createConvertibleIssuance issuer capTableCid "SEC-1" 25000.0 (DT.time (DA.date 2022 Jun 01) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateConvertibleCancellation ConvertibleCancellationOcfData with @@ -56,9 +56,10 @@ testConvertibleCancellation_Create = script do testConvertibleCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table - result <- createConvertibleIssuance issuer capTableCid "SEC-2" 100000.0 + result <- createConvertibleIssuance issuer capTableCid "SEC-2" 100000.0 (DT.time (DA.date 2022 Jun 01) 0 0 0) + balanceResult <- createConvertibleIssuance issuer result.updatedCapTableCid "SEC-CONV-PARTIAL-BAL" 75000.0 (DT.time (DA.date 2024 Jun 15) 0 0 0) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateConvertibleCancellation ConvertibleCancellationOcfData with id = "TX_CONV_CANCEL_PARTIAL" date = DT.time (DA.date 2024 Jun 15) 0 0 0 diff --git a/Test/daml/OpenCapTable/TestConvertibleConversion.daml b/Test/daml/OpenCapTable/TestConvertibleConversion.daml index d92638d2..c6d04d0d 100644 --- a/Test/daml/OpenCapTable/TestConvertibleConversion.daml +++ b/Test/daml/OpenCapTable/TestConvertibleConversion.daml @@ -6,7 +6,14 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Conversion (OcfConversionTriggerType(..), OcfConvertibleConversionRight(..), OcfConvertibleConversionMechanism(..), OcfSAFEConversionMechanism(..), OcfConvertibleType(..), OcfCapitalizationDefinition(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (defaultStockIssuance, setupForStockIssuance) +import OpenCapTable.TestHelpers + ( addDefaultStakeholder + , convertibleSuccessorIssuance + , defaultStockClass + , defaultStockPlan + , setupForStockIssuance + , stockSuccessorIssuance + ) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -78,13 +85,7 @@ testConvertibleConversion_SAFEConversion = script do balance_security_id = None capitalization_definition = None quantity_converted = None - , CT.OcfCreateStockIssuance ((defaultStockIssuance - "TX_CONV_RESULT_1" - resultSecurityId - stakeholderId - stockClassId) with - date = conversionDate - quantity = 100.0) + , stockSuccessorIssuance "TX-CONV-SAFE-RESULT" resultSecurityId stakeholderId stockClassId conversionDate 6250.0 ] edits = [] deletes = [] @@ -93,13 +94,22 @@ testConvertibleConversion_SAFEConversion = script do -- Test 2: Convertible conversion with capitalization definition testConvertibleConversion_WithCapDefinition = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp - (capTableCid, stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table + (capTableCid, _stockClassId, stakeholderId) <- setupForStockIssuance issuer cap_table let securityId = "CONV-SEC-002" let resultSecurityId = "SSEC-PREFERRED-001" - let conversionDate = DT.time (DA.date 2025 Dec 01) 0 0 0 result <- createConvertibleIssuance issuer capTableCid securityId 250000.0 - _ <- submit issuer do + capTableWithCapDefinitionRefs <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "COMMON") + , CT.OcfCreateStockClass (defaultStockClass "PREFERRED") + , CT.OcfCreateStockPlan (defaultStockPlan "ESOP-2024" "COMMON") + ] + edits = [] + deletes = [] + let conversionDate = DT.time (DA.date 2025 Dec 01) 0 0 0 + _ <- submit issuer do + exerciseCmd capTableWithCapDefinitionRefs.updatedCapTableCid CT.UpdateCapTable with creates = [ CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with id = "TX_CONV_CONVERSION_2" @@ -115,19 +125,39 @@ testConvertibleConversion_WithCapDefinition = script do include_security_ids = [] include_stock_class_ids = ["COMMON", "PREFERRED"] include_stock_plans_ids = ["ESOP-2024"]) - quantity_converted = Some 15625.0 - , CT.OcfCreateStockIssuance ((defaultStockIssuance - "TX_CONV_RESULT_2" - resultSecurityId - stakeholderId - stockClassId) with - date = conversionDate - quantity = 15625.0) + quantity_converted = Some 15625.0 -- $250K / $16 per share + , stockSuccessorIssuance "TX-CONV-CAP-RESULT" resultSecurityId stakeholderId "PREFERRED" conversionDate 15625.0 ] edits = [] deletes = [] pure () +testConvertibleConversion_CapDefinitionMissingRefsFails = script do + TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "CONV-SEC-MISSING-CAP-REF" + result <- createConvertibleIssuance issuer capTableCid securityId 250000.0 + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with + id = "TX_CONV_CONVERSION_MISSING_CAP_REF" + date = DT.time (DA.date 2025 Dec 01) 0 0 0 + security_id = securityId + trigger_id = "equity-financing-trigger" + reason_text = "Conversion with an invalid capitalization definition" + resulting_security_ids = ["SSEC-PREFERRED-MISSING-CAP-REF"] + comments = [] + balance_security_id = None + capitalization_definition = Some (OcfCapitalizationDefinition with + exclude_security_ids = [] + include_security_ids = ["SEC-MISSING-CAP-REF"] + include_stock_class_ids = ["SC-MISSING-CAP-REF"] + include_stock_plans_ids = ["SP-MISSING-CAP-REF"]) + quantity_converted = Some 15625.0] + edits = [] + deletes = [] + pure () + -- Test 3: Partial conversion with balance security testConvertibleConversion_PartialConversion = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp @@ -150,13 +180,8 @@ testConvertibleConversion_PartialConversion = script do balance_security_id = Some "CONV-SEC-003-REMAINDER" capitalization_definition = None quantity_converted = Some 300000.0 - , CT.OcfCreateStockIssuance ((defaultStockIssuance - "TX_CONV_RESULT_3" - resultSecurityId - stakeholderId - stockClassId) with - date = conversionDate - quantity = 300000.0) + , stockSuccessorIssuance "TX-CONV-PARTIAL-RESULT" resultSecurityId stakeholderId stockClassId conversionDate 18750.0 + , convertibleSuccessorIssuance "TX-CONV-PARTIAL-BALANCE" "CONV-SEC-003-REMAINDER" stakeholderId conversionDate 200000.0 ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestConvertibleConversionRightReferences.daml b/Test/daml/OpenCapTable/TestConvertibleConversionRightReferences.daml new file mode 100644 index 00000000..79e01f1a --- /dev/null +++ b/Test/daml/OpenCapTable/TestConvertibleConversionRightReferences.daml @@ -0,0 +1,176 @@ +module OpenCapTable.TestConvertibleConversionRightReferences where + +import Fairmint.OpenCapTable.OCF.ConvertibleIssuance +import Fairmint.OpenCapTable.Types.Conversion +import Fairmint.OpenCapTable.Types.Monetary +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +convertibleConversionTriggerTo : Optional Text -> OcfConvertibleConversionTrigger +convertibleConversionTriggerTo stockClassId = OcfConvertibleConversionTrigger with + conversion_right = OcfConvertibleConversionRight with + conversion_mechanism = OcfConvMechSAFE (OcfSAFEConversionMechanism with + capitalization_definition = None + capitalization_definition_rules = None + conversion_discount = Some 0.2 + conversion_mfn = False + conversion_timing = None + conversion_valuation_cap = None + exit_multiple = None) + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + type_ = "CONVERTIBLE_CONVERSION_RIGHT" + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-1" + type_ = OcfTriggerTypeTypeUnspecified + +convertibleIssuanceWithConversionTarget : Text -> Text -> Optional Text -> ConvertibleIssuanceOcfData +convertibleIssuanceWithConversionTarget issuanceId securityId stockClassId = ConvertibleIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "CN-" <> issuanceId + stakeholder_id = defaultStakeholderId + board_approval_date = None + stockholder_approval_date = None + consideration_text = None + security_law_exemptions = [] + investment_amount = OcfMonetary with amount = 100_000.0, currency = "USD" + convertible_type = OcfConvertibleSafe + conversion_triggers = [convertibleConversionTriggerTo stockClassId] + pro_rata = None + seniority = 1 + comments = [] + +testConvertibleConversionRightDuplicateTriggerIdsFail = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + let trigger = convertibleConversionTriggerTo None + let issuance = (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_DUPLICATE_TRIGGER" "CONV-DUPLICATE-TRIGGER" None) with + conversion_triggers = [trigger, trigger] + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance issuance] + edits = [] + deletes = [] + +testConvertibleConversionRightDuplicateTriggerIdsEditFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + let trigger = convertibleConversionTriggerTo None + let issuance = convertibleIssuanceWithConversionTarget + "TX_CONVERTIBLE_DUPLICATE_TRIGGER_EDIT" + "CONV-DUPLICATE-TRIGGER-EDIT" + None + + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance issuance] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditConvertibleIssuance (issuance with conversion_triggers = [trigger, trigger])] + deletes = [] + +testConvertibleConversionRightTargetCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + _ <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_CONVERTIBLE_TARGET") + , CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_TARGET_REF" "CONV-TARGET-REF" (Some "SC_CONVERTIBLE_TARGET")) + ] + edits = [] + deletes = [] + pure () + +testConvertibleConversionRightMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_MISSING_TARGET" "CONV-MISSING-TARGET" (Some "SC_MISSING_CONVERTIBLE_TARGET"))] + edits = [] + deletes = [] + +testConvertibleConversionRightEditMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_EDIT_TARGET" "CONV-EDIT-TARGET" None)] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_EDIT_TARGET" "CONV-EDIT-TARGET" (Some "SC_MISSING_CONVERTIBLE_EDIT_TARGET"))] + deletes = [] + +testConvertibleConversionRightTargetDeleteCannotLeaveDanglingReference = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_CONVERTIBLE_DELETE_TARGET") + , CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_DANGLING_TARGET" "CONV-DANGLING-TARGET" (Some "SC_CONVERTIBLE_DELETE_TARGET")) + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass "SC_CONVERTIBLE_DELETE_TARGET"] + +testConvertibleIssuanceStakeholderDeleteCannotLeaveDanglingReference = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_DANGLING_STAKEHOLDER" "CONV-DANGLING-STAKEHOLDER" None)] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStakeholder defaultStakeholderId] + +testConvertibleIssuanceStakeholderEditCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleIssuance (convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_EDIT_STAKEHOLDER_BATCH" "CONV-EDIT-STAKEHOLDER-BATCH" None)] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStakeholder (defaultStakeholder "SH_CONVERTIBLE_EDIT_BATCH" "Convertible Holder")] + edits = + [ CT.OcfEditConvertibleIssuance + ((convertibleIssuanceWithConversionTarget "TX_CONVERTIBLE_EDIT_STAKEHOLDER_BATCH" "CONV-EDIT-STAKEHOLDER-BATCH" None) with + stakeholder_id = "SH_CONVERTIBLE_EDIT_BATCH") + ] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestConvertibleTransfer.daml b/Test/daml/OpenCapTable/TestConvertibleTransfer.daml index fa70907f..51f8dae2 100644 --- a/Test/daml/OpenCapTable/TestConvertibleTransfer.daml +++ b/Test/daml/OpenCapTable/TestConvertibleTransfer.daml @@ -7,19 +7,19 @@ import Fairmint.OpenCapTable.Types.Conversion (OcfConvertibleType(..)) import OpenCapTable.HelpersTriggers (ocfConvTriggerAutomaticOnDate) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, convertibleSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script -- Create a convertible issuance to transfer (follows real workflow) -createConvertibleIssuance issuer capTableCid securityId amount = +createConvertibleIssuance issuer capTableCid securityId amount issuanceDate = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with creates = [CT.OcfCreateConvertibleIssuance ConvertibleIssuanceOcfData with - id = "TX_CONVERTIBLE_ISS_1" - date = DT.time (DA.date 2024 Mar 01) 0 0 0 + id = "TX_CONVERTIBLE_ISS_" <> securityId + date = issuanceDate security_id = securityId custom_id = "SAFE-1" stakeholder_id = "SH-1" @@ -42,10 +42,11 @@ testConvertibleTransfer_SAFEAssignment = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "CSEC-SAFE-001" - result <- createConvertibleIssuance issuer capTableCid securityId 100000.0 + result <- createConvertibleIssuance issuer capTableCid securityId 100000.0 (DT.time (DA.date 2024 Mar 01) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with + creates = [convertibleSuccessorIssuance "CV-ISS-SAFE-ASSIGNED-001" "CSEC-SAFE-ASSIGNED-001" "SH-1" (DT.time (DA.date 2025 Sep 08) 0 0 0) 100000.0 + , CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with id = "TX_CONVERTIBLE_TRANSFER_1" date = DT.time (DA.date 2025 Sep 08) 0 0 0 amount = OcfMonetary with amount = 100000.0, currency = "USD" @@ -63,10 +64,12 @@ testConvertibleTransfer_PartialSAFE = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "CSEC-SAFE-PARTIAL" - result <- createConvertibleIssuance issuer capTableCid securityId 500000.0 + result <- createConvertibleIssuance issuer capTableCid securityId 500000.0 (DT.time (DA.date 2024 Mar 01) 0 0 0) + balanceResult <- createConvertibleIssuance issuer result.updatedCapTableCid "CSEC-SAFE-RETAINED" 300000.0 (DT.time (DA.date 2025 Oct 15) 0 0 0) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with + exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [convertibleSuccessorIssuance "CV-ISS-SAFE-SPLIT-001" "CSEC-SAFE-SPLIT-001" "SH-1" (DT.time (DA.date 2025 Oct 15) 0 0 0) 200000.0 + , CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with id = "TX_CONVERTIBLE_TRANSFER_PARTIAL" date = DT.time (DA.date 2025 Oct 15) 0 0 0 amount = OcfMonetary with amount = 200000.0, currency = "USD" @@ -107,7 +110,8 @@ testConvertibleTransfer_NoteFullAssignment = script do deletes = [] _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with + creates = [convertibleSuccessorIssuance "CV-ISS-NOTE-ASSIGNED" "CSEC-NOTE-ASSIGNED" "SH-1" (DT.time (DA.date 2025 Dec 01) 0 0 0) 250000.0 + , CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with id = "TX_NOTE_TRANSFER_1" date = DT.time (DA.date 2025 Dec 01) 0 0 0 amount = OcfMonetary with amount = 250000.0, currency = "USD" diff --git a/Test/daml/OpenCapTable/TestDocument.daml b/Test/daml/OpenCapTable/TestDocument.daml index 52f97506..88a29844 100644 --- a/Test/daml/OpenCapTable/TestDocument.daml +++ b/Test/daml/OpenCapTable/TestDocument.daml @@ -2,8 +2,18 @@ module OpenCapTable.TestDocument where import Fairmint.OpenCapTable.OCF.Document import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup +import OpenCapTable.TestHelpers import Daml.Script +documentData : Text -> [OcfObjectReference] -> DocumentOcfData +documentData documentId relatedObjects = DocumentOcfData with + id = documentId + path = Some ("/docs/" <> documentId <> ".pdf") + uri = None + md5 = "0123456789abcdef0123456789abcdef" + related_objects = relatedObjects + comments = [] + testCreateDocument = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp -- Add document to CapTable via batch @@ -52,7 +62,7 @@ testCreateDocumentWithOptionalsSome = script do path = None uri = Some "https://cdn.example.com/docs/acme/charter.pdf" md5 = "0123456789abcdef0123456789abcdef" - related_objects = [ OcfObjectReference with object_type = OcfObjIssuer, object_id = "ISS_FOR_DOC" ] + related_objects = [ OcfObjectReference with object_type = OcfObjIssuer, object_id = "ISSUER_1" ] comments = ["External link"]] edits = [] deletes = [] @@ -85,3 +95,66 @@ testEditDocument = script do comments = ["Updated version"]] deletes = [] pure () + +testDocumentRelatedIssuerReferenceMustExist = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateDocument (documentData "DOC_BAD_ISSUER_REF" [OcfObjectReference with object_type = OcfObjIssuer, object_id = "ISSUER_MISSING"])] + edits = [] + deletes = [] + +testDocumentRelatedObjectCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + _ <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateDocument (documentData "DOC_STOCK_CLASS_REF" [OcfObjectReference with object_type = OcfObjStockClass, object_id = "SC_DOC_REF"]) + , CT.OcfCreateStockClass (defaultStockClass "SC_DOC_REF") + ] + edits = [] + deletes = [] + pure () + +testDocumentPlanSecurityAliasReferencesEquityCompensationIssuance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_DOC_PLAN_SECURITY" "Plan Security Holder" + capTableWithStockClass <- addDefaultStockClass issuer capTableWithStakeholder + capTableWithIssuance <- addEquityCompensationIssuance issuer capTableWithStockClass "EC_DOC_PLAN_SECURITY" "SEC_DOC_PLAN_SECURITY" stakeholderId + + _ <- submit issuer do + exerciseCmd capTableWithIssuance CT.UpdateCapTable with + creates = + [ CT.OcfCreateDocument + (documentData + "DOC_PLAN_SECURITY_REF" + [OcfObjectReference with object_type = OcfObjTxPlanSecurityIssuance, object_id = "EC_DOC_PLAN_SECURITY"]) + ] + edits = [] + deletes = [] + pure () + +testDocumentEditMissingRelatedObjectFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateDocument (documentData "DOC_EDIT_REF" [])] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditDocument (documentData "DOC_EDIT_REF" [OcfObjectReference with object_type = OcfObjStockClass, object_id = "SC_MISSING_FOR_DOC"])] + deletes = [] + +testDocumentUnsupportedRelatedObjectTypeFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateDocument (documentData "DOC_UNSUPPORTED_REF" [OcfObjectReference with object_type = OcfObjFinancing, object_id = "FINANCING_1"])] + edits = [] + deletes = [] diff --git a/Test/daml/OpenCapTable/TestEquityCompensationAcceptance.daml b/Test/daml/OpenCapTable/TestEquityCompensationAcceptance.daml index 7b914598..778a0765 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationAcceptance.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationAcceptance.daml @@ -26,7 +26,7 @@ createEquityCompensationIssuance issuer capTableCid = consideration_text = Some "Services rendered" security_law_exemptions = [] stock_plan_id = None - stock_class_id = Some "SC_COMMON" + stock_class_id = None compensation_type = OcfCompensationTypeOptionISO quantity = 10000.0 exercise_price = Some (OcfMonetary with amount = 0.50, currency = "USD") diff --git a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml index fe63b090..7e06eda7 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationCancellation.daml @@ -5,7 +5,7 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addPrerequisites) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -26,7 +26,7 @@ createEquityCompensationIssuance issuer capTableCid securityId quantity = consideration_text = None security_law_exemptions = [] stock_plan_id = None - stock_class_id = Some "COMMON" + stock_class_id = Some "SC_COMMON" compensation_type = OcfCompensationTypeOptionNSO quantity = quantity exercise_price = Some (OcfMonetary with amount = 0.10, currency = "USD") @@ -59,7 +59,7 @@ createEquityCompensationCancellation issuer capTableCid securityId quantity = -- Basic create and archive by issuer (single signer) - using production data testEquityCompensationCancellation_CreateAndArchiveByIssuer = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp - capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addPrerequisites issuer cap_table let securityId = "db5a26d6-3912-4400-87b0-bda74de361af" result <- createEquityCompensationIssuance issuer capTableCid securityId 250000.0 _ <- createEquityCompensationCancellation issuer result.updatedCapTableCid securityId 214200.0 @@ -68,7 +68,7 @@ testEquityCompensationCancellation_CreateAndArchiveByIssuer = script do -- Archive with built-in choice (requires both signatories) testEquityCompensationCancellation_CreateAndArchiveBuiltIn = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp - capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-EC-002" result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 _ <- submit issuer do @@ -88,7 +88,7 @@ testEquityCompensationCancellation_CreateAndArchiveBuiltIn = script do -- Partial cancellation with optional fields populated testEquityCompensationCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp - capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addPrerequisites issuer cap_table let securityId = "SEC-EC-PARTIAL" let balanceSecurityId = "SEC-EC-PARTIAL-BAL" let cancellationDate = DT.time (DA.date 2024 Jun 30) 0 0 0 @@ -115,7 +115,7 @@ testEquityCompensationCancellation_PartialWithOptionals = script do consideration_text = None security_law_exemptions = [] stock_plan_id = None - stock_class_id = Some "COMMON" + stock_class_id = Some "SC_COMMON" compensation_type = OcfCompensationTypeOptionNSO quantity = 350000.0 exercise_price = Some (OcfMonetary with amount = 0.10, currency = "USD") diff --git a/Test/daml/OpenCapTable/TestEquityCompensationExercise.daml b/Test/daml/OpenCapTable/TestEquityCompensationExercise.daml index 81cec73d..acb53da3 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationExercise.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationExercise.daml @@ -2,7 +2,7 @@ module OpenCapTable.TestEquityCompensationExercise where import Fairmint.OpenCapTable.OCF.EquityCompensationExercise import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites, addEquityCompensationIssuance) +import OpenCapTable.TestHelpers (addPrerequisites, addEquityCompensationIssuance, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -15,7 +15,8 @@ testCreateAndArchiveEquityCompensationExercise = script do cap_table <- addEquityCompensationIssuance issuer cap_table "EC-ISS-1" "EC-1" "SH-1" _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-EC-EX-1" "SEC-RES-1" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Jan 01) 0 0 0) 10.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with id = "TX_EC_EX_1" date = DT.time (DA.date 2024 Jan 01) 0 0 0 security_id = "EC-1" @@ -34,7 +35,8 @@ testCreateAndArchiveEquityCompensationExerciseBuiltIn = script do cap_table <- addEquityCompensationIssuance issuer cap_table "EC-ISS-1" "EC-1" "SH-1" _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-EC-EX-BUILTIN" "SEC-RES-1" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Jan 01) 0 0 0) 10.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with id = "TX_EC_EX_1" date = DT.time (DA.date 2024 Jan 01) 0 0 0 security_id = "EC-1" @@ -54,7 +56,9 @@ testEquityCompensationExercise_OptionalsSome = script do cap_table <- addEquityCompensationIssuance issuer cap_table "EC-ISS-2" "EC-2" "SH-1" _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-EC-EX-2A" "SEC-RES-2" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Feb 01) 0 0 0) 2.0 + , stockSuccessorIssuance "STOCK-ISS-EC-EX-2B" "SEC-RES-3" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Feb 01) 0 0 0) 3.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with id = "TX_EC_EX_2" date = DT.time (DA.date 2024 Feb 01) 0 0 0 security_id = "EC-2" diff --git a/Test/daml/OpenCapTable/TestEquityCompensationIssuance.daml b/Test/daml/OpenCapTable/TestEquityCompensationIssuance.daml index 19ea5815..98e63c55 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationIssuance.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationIssuance.daml @@ -7,6 +7,7 @@ import Fairmint.OpenCapTable.Types.Stakeholder (OcfStakeholderRelationshipType(. import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..), OcfTerminationWindow(..), OcfTerminationWindowType(..), OcfPeriodType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup +import OpenCapTable.TestHelpers (defaultStockClass, defaultStockPlan, defaultVestingTerms) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -109,31 +110,36 @@ testEquityCompensationIssuance_OptionalsSome = script do let capTableCid = result.updatedCapTableCid _ <- submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_ISS_2" - date = DT.time (DA.date 2024 Feb 01) 0 0 0 - security_id = "EC-2" - custom_id = "EC-CERT-2" - stakeholder_id = "SH-1" - base_price = Some (OcfMonetary with amount = 2.0, currency = "USD") - board_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) - consideration_text = Some "Employee incentive" - early_exercisable = Some True - exercise_price = Some (OcfMonetary with amount = 2.5, currency = "USD") - expiration_date = Some (DT.time (DA.date 2034 Feb 01) 0 0 0) - stock_class_id = Some "SC-1" - stock_plan_id = None - stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 20) 0 0 0) - vesting_terms_id = Some "VT-1" - security_law_exemptions = [OcfSecurityExemption with description = "ISO", jurisdiction = "US"] - vestings = [OcfVesting with date = DT.time (DA.date 2025 Jan 01) 0 0 0, amount = 125.0] - termination_exercise_windows = [OcfTerminationWindow with - reason = OcfTermVoluntaryGoodCause - period = 90 - period_type = OcfPeriodDays] - compensation_type = OcfCompensationTypeOptionISO - quantity = 500.0 - comments = ["Initial grant for new hire"]] + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC-1") + , CT.OcfCreateStockPlan (defaultStockPlan "PLAN-1" "SC-1") + , CT.OcfCreateVestingTerms (defaultVestingTerms "VT-1") + , CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with + id = "TX_EC_ISS_2" + date = DT.time (DA.date 2024 Feb 01) 0 0 0 + security_id = "EC-2" + custom_id = "EC-CERT-2" + stakeholder_id = "SH-1" + base_price = Some (OcfMonetary with amount = 2.0, currency = "USD") + board_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) + consideration_text = Some "Employee incentive" + early_exercisable = Some True + exercise_price = Some (OcfMonetary with amount = 2.5, currency = "USD") + expiration_date = Some (DT.time (DA.date 2034 Feb 01) 0 0 0) + stock_class_id = Some "SC-1" + stock_plan_id = Some "PLAN-1" + stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 20) 0 0 0) + vesting_terms_id = Some "VT-1" + security_law_exemptions = [OcfSecurityExemption with description = "ISO", jurisdiction = "US"] + vestings = [OcfVesting with date = DT.time (DA.date 2025 Jan 01) 0 0 0, amount = 125.0] + termination_exercise_windows = [OcfTerminationWindow with + reason = OcfTermVoluntaryGoodCause + period = 90 + period_type = OcfPeriodDays] + compensation_type = OcfCompensationTypeOptionISO + quantity = 500.0 + comments = ["Initial grant for new hire"] + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestEquityCompensationIssuanceReferences.daml b/Test/daml/OpenCapTable/TestEquityCompensationIssuanceReferences.daml new file mode 100644 index 00000000..1a7d121a --- /dev/null +++ b/Test/daml/OpenCapTable/TestEquityCompensationIssuanceReferences.daml @@ -0,0 +1,218 @@ +module OpenCapTable.TestEquityCompensationIssuanceReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance (EquityCompensationIssuanceOcfData(..)) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +equityCompensationIssuance : Text -> Text -> Text -> EquityCompensationIssuanceOcfData +equityCompensationIssuance issuanceId securityId stakeholderId = EquityCompensationIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "EC-" <> issuanceId + stakeholder_id = stakeholderId + base_price = None + board_approval_date = None + consideration_text = None + early_exercisable = Some False + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + expiration_date = None + stock_class_id = None + stock_plan_id = None + stockholder_approval_date = None + vesting_terms_id = None + security_law_exemptions = [] + vestings = [] + termination_exercise_windows = [] + compensation_type = OcfCompensationTypeOptionNSO + quantity = 100.0 + comments = [] + +createReferencedEquityCompensationIssuance : + Party -> + ContractId CT.CapTable -> + Script (ContractId CT.CapTable, EquityCompensationIssuanceOcfData, Text, Text, Text) +createReferencedEquityCompensationIssuance issuer capTableCid = do + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer capTableCid "SH_EC_REF" "Equity Holder" + let stockClassId = "SC_EC_REF" + let stockPlanId = "PLAN_EC_REF" + let vestingTermsId = "VT_EC_REF" + let issuance = (equityCompensationIssuance "TX_EC_REF" "SEC_EC_REF" stakeholderId) with + stock_class_id = Some stockClassId + stock_plan_id = Some stockPlanId + vesting_terms_id = Some vestingTermsId + + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass stockClassId) + , CT.OcfCreateStockPlan (defaultStockPlan stockPlanId stockClassId) + , CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId) + , CT.OcfCreateEquityCompensationIssuance issuance + ] + edits = [] + deletes = [] + pure (result.updatedCapTableCid, issuance, stockClassId, stockPlanId, vestingTermsId) + +testEquityCompensationIssuance_MissingStockClassReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_MISSING_CLASS" "Equity Holder" + let issuance = (equityCompensationIssuance "TX_EC_MISSING_CLASS" "SEC_EC_MISSING_CLASS" stakeholderId) with + stock_class_id = Some "SC_MISSING" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + +testEquityCompensationIssuance_MissingStockPlanReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_MISSING_PLAN" "Equity Holder" + let issuance = (equityCompensationIssuance "TX_EC_MISSING_PLAN" "SEC_EC_MISSING_PLAN" stakeholderId) with + stock_plan_id = Some "PLAN_MISSING" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + +testEquityCompensationIssuance_StockPlanMustIncludeIssuanceClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_PLAN_CLASS" "Equity Holder" + let stockClassId = "SC_EC_ISSUANCE_CLASS" + let otherStockClassId = "SC_EC_PLAN_CLASS" + let stockPlanId = "PLAN_EC_OTHER_CLASS" + let issuance = (equityCompensationIssuance "TX_EC_PLAN_WRONG_CLASS" "SEC_EC_PLAN_WRONG_CLASS" stakeholderId) with + stock_class_id = Some stockClassId + stock_plan_id = Some stockPlanId + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass stockClassId) + , CT.OcfCreateStockClass (defaultStockClass otherStockClassId) + , CT.OcfCreateStockPlan (defaultStockPlan stockPlanId otherStockClassId) + , CT.OcfCreateEquityCompensationIssuance issuance + ] + edits = [] + deletes = [] + +testEquityCompensationIssuance_MissingVestingTermsReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_MISSING_VESTING" "Equity Holder" + let issuance = (equityCompensationIssuance "TX_EC_MISSING_VESTING" "SEC_EC_MISSING_VESTING" stakeholderId) with + vesting_terms_id = Some "VT_MISSING" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + +testEquityCompensationIssuance_ReferencesCreatedInSameBatchSucceed = script do + TestOcp{issuer, cap_table} <- setupTestOcp + _ <- createReferencedEquityCompensationIssuance issuer cap_table + pure () + +testEquityCompensationIssuance_EditMissingReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, issuance, _, _, _) <- createReferencedEquityCompensationIssuance issuer cap_table + let editedIssuance = issuance with stock_plan_id = Some "PLAN_MISSING_ON_EDIT" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditEquityCompensationIssuance editedIssuance] + deletes = [] + +testEquityCompensationIssuance_EditStockPlanMustIncludeIssuanceClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_EDIT_PLAN_CLASS" "Equity Holder" + let issuance = equityCompensationIssuance "TX_EC_EDIT_PLAN_CLASS" "SEC_EC_EDIT_PLAN_CLASS" stakeholderId + createResult <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + + let stockClassId = "SC_EC_EDIT_ISSUANCE_CLASS" + let otherStockClassId = "SC_EC_EDIT_PLAN_CLASS" + let stockPlanId = "PLAN_EC_EDIT_OTHER_CLASS" + let editedIssuance = issuance with + stock_class_id = Some stockClassId + stock_plan_id = Some stockPlanId + + submitMustFail issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass stockClassId) + , CT.OcfCreateStockClass (defaultStockClass otherStockClassId) + , CT.OcfCreateStockPlan (defaultStockPlan stockPlanId otherStockClassId) + ] + edits = [CT.OcfEditEquityCompensationIssuance editedIssuance] + deletes = [] + +testEquityCompensationIssuance_EditReferencesCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_EC_EDIT_BATCH" "Equity Holder" + let issuance = equityCompensationIssuance "TX_EC_EDIT_BATCH" "SEC_EC_EDIT_BATCH" stakeholderId + createResult <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + + let stockClassId = "SC_EC_EDIT_BATCH" + let stockPlanId = "PLAN_EC_EDIT_BATCH" + let vestingTermsId = "VT_EC_EDIT_BATCH" + let editedIssuance = issuance with + stock_class_id = Some stockClassId + stock_plan_id = Some stockPlanId + vesting_terms_id = Some vestingTermsId + + _ <- submit issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass stockClassId) + , CT.OcfCreateStockPlan (defaultStockPlan stockPlanId stockClassId) + , CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId) + ] + edits = [CT.OcfEditEquityCompensationIssuance editedIssuance] + deletes = [] + pure () + +testEquityCompensationIssuance_DeleteReferencedStockClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, _, stockClassId, _, _) <- createReferencedEquityCompensationIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass stockClassId] + +testEquityCompensationIssuance_DeleteReferencedStockPlanFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, _, _, stockPlanId, _) <- createReferencedEquityCompensationIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockPlan stockPlanId] + +testEquityCompensationIssuance_DeleteReferencedVestingTermsFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, _, _, _, vestingTermsId) <- createReferencedEquityCompensationIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteVestingTerms vestingTermsId] diff --git a/Test/daml/OpenCapTable/TestEquityCompensationRelease.daml b/Test/daml/OpenCapTable/TestEquityCompensationRelease.daml index 84c3815a..6634f7e1 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationRelease.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationRelease.daml @@ -4,7 +4,7 @@ import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance import Fairmint.OpenCapTable.OCF.EquityCompensationRelease import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites, defaultStockIssuance) +import OpenCapTable.TestHelpers (addPrerequisites, stockSuccessorIssuance) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified DA.Date as DA @@ -53,9 +53,7 @@ testCreateAndArchiveEquityCompensationRelease = script do _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with creates = - [ CT.OcfCreateStockIssuance ((defaultStockIssuance "STOCK-ISS-EC-RELEASE-1" "SEC_STOCK_101" "SH-1" "SC_COMMON") with - date = DT.time (DA.date 2024 Apr 03) 0 0 0 - quantity = 5000.0) + [ stockSuccessorIssuance "STOCK-ISS-EC-RELEASE-1" "SEC_STOCK_101" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Apr 03) 0 0 0) 5000.0 , CT.OcfCreateEquityCompensationRelease EquityCompensationReleaseOcfData with id = "TX_EC_RELEASE_1" date = DT.time (DA.date 2024 Apr 01) 0 0 0 @@ -82,12 +80,8 @@ testCreateAndArchiveEquityCompensationReleaseQuarterly = script do _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with creates = - [ CT.OcfCreateStockIssuance ((defaultStockIssuance "STOCK-ISS-EC-RELEASE-2A" "SEC_STOCK_102" "SH-1" "SC_COMMON") with - date = DT.time (DA.date 2024 Jul 05) 0 0 0 - quantity = 625.0) - , CT.OcfCreateStockIssuance ((defaultStockIssuance "STOCK-ISS-EC-RELEASE-2B" "SEC_STOCK_103" "SH-1" "SC_COMMON") with - date = DT.time (DA.date 2024 Jul 05) 0 0 0 - quantity = 625.0) + [ stockSuccessorIssuance "STOCK-ISS-EC-RELEASE-2A" "SEC_STOCK_102" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Jul 05) 0 0 0) 625.0 + , stockSuccessorIssuance "STOCK-ISS-EC-RELEASE-2B" "SEC_STOCK_103" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Jul 05) 0 0 0) 625.0 , CT.OcfCreateEquityCompensationRelease EquityCompensationReleaseOcfData with id = "TX_EC_RELEASE_2" date = DT.time (DA.date 2024 Jul 01) 0 0 0 diff --git a/Test/daml/OpenCapTable/TestEquityCompensationRetraction.daml b/Test/daml/OpenCapTable/TestEquityCompensationRetraction.daml index 95f38f7e..7b639000 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationRetraction.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationRetraction.daml @@ -26,7 +26,7 @@ createEquityCompensationIssuance issuer capTableCid = consideration_text = Some "Services rendered" security_law_exemptions = [] stock_plan_id = None - stock_class_id = Some "SC_COMMON" + stock_class_id = None compensation_type = OcfCompensationTypeOptionISO quantity = 10000.0 exercise_price = Some (OcfMonetary with amount = 0.50, currency = "USD") diff --git a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml index 6aa27394..af7a5f13 100644 --- a/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml +++ b/Test/daml/OpenCapTable/TestEquityCompensationTransfer.daml @@ -6,19 +6,19 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, equityCompensationSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script -- Create an equity compensation issuance to transfer (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_EQUITY_COMP_ISS_1" - date = DT.time (DA.date 2022 Jan 15) 0 0 0 + id = "TX_EQUITY_COMP_ISS_" <> securityId + date = issuanceDate security_id = securityId custom_id = "OPT-1" stakeholder_id = "SH-1" @@ -34,7 +34,7 @@ createEquityCompensationIssuance issuer capTableCid securityId quantity = exercise_price = Some (OcfMonetary with amount = 0.50, currency = "USD") early_exercisable = None expiration_date = Some (DT.time (DA.date 2032 Jan 15) 0 0 0) - stock_class_id = Some "COMMON" + stock_class_id = None stock_plan_id = None vesting_terms_id = None stockholder_approval_date = None] @@ -48,10 +48,12 @@ testEquityCompensationTransfer_NSOToFamilyTrust = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ESEC-NSO-001" - result <- createEquityCompensationIssuance issuer capTableCid securityId 10000.0 + 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) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + 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 + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with id = "TX_EQUITY_COMP_TRANSFER_1" date = DT.time (DA.date 2025 Jun 01) 0 0 0 quantity = 5000.0 @@ -69,10 +71,11 @@ testEquityCompensationTransfer_FullTransfer = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ESEC-NSO-FULL" - result <- createEquityCompensationIssuance issuer capTableCid securityId 2500.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 2500.0 (DT.time (DA.date 2022 Jan 15) 0 0 0) _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + creates = [equityCompensationSuccessorIssuance "EC-ISS-NEWHOLDER" "ESEC-NSO-NEWHOLDER" "SH-1" (DT.time (DA.date 2025 Sep 15) 0 0 0) 2500.0 + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with id = "TX_EQUITY_COMP_TRANSFER_FULL" date = DT.time (DA.date 2025 Sep 15) 0 0 0 quantity = 2500.0 @@ -90,10 +93,12 @@ testEquityCompensationTransfer_WithApprovalComments = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ESEC-NSO-APPROVAL" - result <- createEquityCompensationIssuance issuer capTableCid securityId 15000.0 + 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) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + 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 + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with id = "TX_EQUITY_COMP_TRANSFER_APPROVED" date = DT.time (DA.date 2025 Nov 01) 0 0 0 quantity = 8000.0 diff --git a/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml new file mode 100644 index 00000000..cd959b04 --- /dev/null +++ b/Test/daml/OpenCapTable/TestFinalReferenceValidation.daml @@ -0,0 +1,209 @@ +module OpenCapTable.TestFinalReferenceValidation where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.ConvertibleConversion (ConvertibleConversionOcfData(..)) +import Fairmint.OpenCapTable.OCF.Stakeholder (OcfStakeholderStatusType(..)) +import Fairmint.OpenCapTable.OCF.StakeholderStatusChangeEvent (StakeholderStatusChangeEventOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockReissuance (StockReissuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockTransfer (StockTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantExercise (WarrantExerciseOcfData(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers + ( addDefaultConvertibleIssuance + , addDefaultStakeholder + , addDefaultWarrantIssuance + , addPrerequisites + , addStockIssuance + , defaultStockIssuance + ) +import qualified DA.Date as DA +import DA.Date (Month(..)) +import qualified DA.Time as DT +import Daml.Script + +defaultReferenceTestTime : Time +defaultReferenceTestTime = DT.time (DA.date 2024 Jan 01) 0 0 0 + +stockTransferData : Text -> Text -> StockTransferOcfData +stockTransferData transferId securityId = StockTransferOcfData with + id = transferId + date = defaultReferenceTestTime + security_id = securityId + quantity = 10.0 + resulting_security_ids = ["SEC-TRANSFER-RESULT"] + balance_security_id = Some "SEC-TRANSFER-BALANCE" + consideration_text = None + comments = [] + +setupStockIssuanceStakeholderReference : Script (Party, ContractId CT.CapTable) +setupStockIssuanceStakeholderReference = do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addPrerequisites issuer cap_table + capTable <- addStockIssuance issuer capTable "TX-STOCK-STAKEHOLDER-REF" "SEC-STOCK-STAKEHOLDER-REF" "SH-1" "SC_COMMON" + pure (issuer, capTable) + +testFinalReferenceValidation_DeleteStakeholderReferencedByStockIssuanceFails = script do + (issuer, capTable) <- setupStockIssuanceStakeholderReference + + submitMustFail issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStakeholder "SH-1"] + pure () + +testFinalReferenceValidation_DeleteStockIssuanceThenStakeholderSucceeds = script do + (issuer, capTable) <- setupStockIssuanceStakeholderReference + + _ <- submit issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [] + edits = [] + deletes = + [ CT.OcfDeleteStockIssuance "TX-STOCK-STAKEHOLDER-REF" + , CT.OcfDeleteStakeholder "SH-1" + ] + pure () + +testFinalReferenceValidation_DeleteStakeholderThenStockIssuanceSucceeds = script do + (issuer, capTable) <- setupStockIssuanceStakeholderReference + + _ <- submit issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [] + edits = [] + deletes = + [ CT.OcfDeleteStakeholder "SH-1" + , CT.OcfDeleteStockIssuance "TX-STOCK-STAKEHOLDER-REF" + ] + pure () + +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 + date = defaultReferenceTestTime + quantity = 10.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 + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockIssuance "TX-STOCK-FINAL-REF"] + pure () + +testFinalReferenceValidation_DeleteReferencedStakeholderFromStatusEventFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [CT.OcfCreateStakeholderStatusChangeEvent StakeholderStatusChangeEventOcfData with + id = "CE-STATUS-FINAL-REF" + date = defaultReferenceTestTime + stakeholder_id = "SH-1" + new_status = OcfStakeholderStatusActive + comments = []] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStakeholder "SH-1"] + pure () + +testFinalReferenceValidation_ConvertibleConversionTriggerMustExist = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addDefaultStakeholder issuer cap_table + capTable <- addDefaultConvertibleIssuance issuer capTable + + submitMustFail issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with + id = "TX-CONV-MISSING-TRIGGER" + date = defaultReferenceTestTime + security_id = "SEC-CV-1" + trigger_id = "TRIGGER-MISSING" + reason_text = "Missing trigger reference" + resulting_security_ids = ["SEC-CONV-RESULT"] + balance_security_id = None + capitalization_definition = None + quantity_converted = None + comments = []] + edits = [] + deletes = [] + pure () + +testFinalReferenceValidation_WarrantExerciseTriggerMustExist = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addPrerequisites issuer cap_table + capTable <- addDefaultWarrantIssuance issuer capTable + + submitMustFail issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + id = "TX-WARRANT-MISSING-TRIGGER" + date = defaultReferenceTestTime + security_id = "SEC-WR-1" + trigger_id = "TRIGGER-MISSING" + quantity = Some 10.0 + resulting_security_ids = ["SEC-WARRANT-RESULT"] + consideration_text = None + comments = []] + edits = [] + deletes = [] + pure () + +testFinalReferenceValidation_StockReissuanceSplitMustExist = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addPrerequisites issuer cap_table + capTable <- addStockIssuance issuer capTable "TX-STOCK-SPLIT-REF" "SEC-SPLIT-REF" "SH-1" "SC_COMMON" + capTable <- addStockIssuance issuer capTable "TX-STOCK-SPLIT-RESULT" "SEC-SPLIT-REF-NEW" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + id = "TX-REISSUANCE-MISSING-SPLIT" + date = defaultReferenceTestTime + security_id = "SEC-SPLIT-REF" + resulting_security_ids = ["SEC-SPLIT-REF-NEW"] + split_transaction_id = Some "TX-MISSING-SPLIT" + reason_text = Some "Split certificate replacement" + comments = []] + edits = [] + deletes = [] + pure () + +testFinalReferenceValidation_StockReissuanceSplitMustBeStockClassSplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTable <- addPrerequisites issuer cap_table + capTable <- addStockIssuance issuer capTable "TX-STOCK-WRONG-SPLIT-TYPE" "SEC-WRONG-SPLIT-TYPE" "SH-1" "SC_COMMON" + capTable <- addStockIssuance issuer capTable "TX-STOCK-WRONG-SPLIT-RESULT" "SEC-WRONG-SPLIT-RESULT" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + id = "TX-REISSUANCE-WRONG-SPLIT-TYPE" + date = defaultReferenceTestTime + security_id = "SEC-WRONG-SPLIT-TYPE" + resulting_security_ids = ["SEC-WRONG-SPLIT-RESULT"] + split_transaction_id = Some "TX-STOCK-WRONG-SPLIT-TYPE" + reason_text = Some "References a stock issuance instead of a stock-class split" + comments = []] + edits = [] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestHelpers.daml b/Test/daml/OpenCapTable/TestHelpers.daml index dccb931c..0fe8d939 100644 --- a/Test/daml/OpenCapTable/TestHelpers.daml +++ b/Test/daml/OpenCapTable/TestHelpers.daml @@ -7,7 +7,9 @@ import Fairmint.OpenCapTable.OCF.StockClassAuthorizedSharesAdjustment import Fairmint.OpenCapTable.OCF.StockClassSplit (StockClassSplitOcfData(..)) import Fairmint.OpenCapTable.OCF.StockCancellation (StockCancellationOcfData(..)) import Fairmint.OpenCapTable.OCF.StockIssuance (StockIssuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockLegendTemplate (StockLegendTemplateOcfData(..)) import Fairmint.OpenCapTable.OCF.StockPlan (StockPlanOcfData(..)) +import Fairmint.OpenCapTable.OCF.VestingTerms (VestingTermsOcfData(..), OcfAllocationType(..), OcfVestingCondition(..), OcfVestingTrigger(..)) import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData(..)) import Fairmint.OpenCapTable.OCF.ConvertibleIssuance (ConvertibleIssuanceOcfData(..), OcfConvertibleConversionTrigger(..)) import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance (EquityCompensationIssuanceOcfData(..)) @@ -158,9 +160,9 @@ defaultTestDate : Time defaultTestDate = DT.time (DA.date 2024 Jan 01) 0 0 0 -- | Default stock legend IDs for test issuances. --- Using "L1" as a placeholder - tests should override if specific legends matter. +-- Defaults to no legends. Tests that reference legends should create matching StockLegendTemplate records. defaultStockLegendIds : [Text] -defaultStockLegendIds = ["L1"] +defaultStockLegendIds = [] -- | Returns valid StockIssuanceOcfData with sensible defaults. -- Provide IDs for the issuance, security, stakeholder, and stock class. @@ -191,6 +193,106 @@ defaultStockIssuance issuanceId securityId stakeholderId stockClassId = StockIss issuance_type = None comments = [] +stockSuccessorIssuance : Text -> Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +stockSuccessorIssuance issuanceId securityId stakeholderId stockClassId date quantity = + CT.OcfCreateStockIssuance ((defaultStockIssuance issuanceId securityId stakeholderId stockClassId) with + date = date + quantity = quantity) + +warrantSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +warrantSuccessorIssuance issuanceId securityId stakeholderId date quantity = + CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with + id = issuanceId + custom_id = "WR-" <> issuanceId + date = date + purchase_price = OcfMonetary with amount = 1.0, currency = "USD" + security_id = securityId + stakeholder_id = stakeholderId + comments = [] + exercise_triggers = [] + security_law_exemptions = [] + vestings = [] + board_approval_date = None + consideration_text = None + exercise_price = None + quantity = Some quantity + quantity_source = None + stockholder_approval_date = None + vesting_terms_id = None + warrant_expiration_date = None + +convertibleSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +convertibleSuccessorIssuance issuanceId securityId stakeholderId date investmentAmount = + CT.OcfCreateConvertibleIssuance ConvertibleIssuanceOcfData with + id = issuanceId + convertible_type = OcfConvertibleSafe + custom_id = "CV-" <> issuanceId + date = date + investment_amount = OcfMonetary with amount = investmentAmount, currency = "USD" + security_id = securityId + seniority = 1 + stakeholder_id = stakeholderId + comments = [] + conversion_triggers = [OcfConvertibleConversionTrigger with + conversion_right = OcfConvertibleConversionRight with + conversion_mechanism = OcfConvMechSAFE (OcfSAFEConversionMechanism with + capitalization_definition = None + capitalization_definition_rules = None + conversion_discount = Some 0.2 + conversion_mfn = False + conversion_timing = None + conversion_valuation_cap = None + exit_multiple = None) + converts_to_future_round = None + converts_to_stock_class_id = None + type_ = "convertible_conversion_right" + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-" <> issuanceId + type_ = OcfTriggerTypeTypeUnspecified] + security_law_exemptions = [] + board_approval_date = None + consideration_text = None + pro_rata = None + stockholder_approval_date = None + +equityCompensationSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +equityCompensationSuccessorIssuance issuanceId securityId stakeholderId date quantity = + CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with + id = issuanceId + custom_id = "EC-" <> issuanceId + date = date + compensation_type = OcfCompensationTypeOptionNSO + quantity = quantity + security_id = securityId + stakeholder_id = stakeholderId + comments = [] + security_law_exemptions = [] + vestings = [] + termination_exercise_windows = [] + base_price = None + board_approval_date = None + consideration_text = None + early_exercisable = None + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + expiration_date = None + stock_class_id = None + stock_plan_id = None + stockholder_approval_date = None + vesting_terms_id = None + +-- | Returns valid StockLegendTemplateOcfData with sensible defaults. +defaultStockLegendTemplate : Text -> StockLegendTemplateOcfData +defaultStockLegendTemplate legendId = StockLegendTemplateOcfData with + id = legendId + name = "Restricted Stock Legend" + text = "Shares represented by this certificate are subject to transfer restrictions." + comments = [] + -- | Returns valid StockPlanOcfData with sensible defaults for a stock class. defaultStockPlan : Text -> Text -> StockPlanOcfData defaultStockPlan planId stockClassId = StockPlanOcfData with @@ -203,6 +305,48 @@ defaultStockPlan planId stockClassId = StockPlanOcfData with default_cancellation_behavior = None stockholder_approval_date = None +-- | Returns valid VestingTermsOcfData with a single start condition. +defaultVestingTerms : Text -> VestingTermsOcfData +defaultVestingTerms vestingTermsId = + let startCondition = OcfVestingCondition with + id = "start" + trigger = OcfVestingStartTrigger + next_condition_ids = [] + description = Some "All at start" + portion = None + quantity = Some 100.0 + in VestingTermsOcfData with + id = vestingTermsId + allocation_type = OcfAllocationCumulativeRounding + description = "All shares vest at start" + name = "Immediate vesting" + comments = [] + vesting_conditions = [startCondition] + +defaultVestingCondition : Text -> OcfVestingTrigger -> [Text] -> OcfVestingCondition +defaultVestingCondition conditionId trigger nextConditionIds = OcfVestingCondition with + id = conditionId + trigger = trigger + next_condition_ids = nextConditionIds + description = Some conditionId + portion = None + quantity = Some 100.0 + +defaultVestingTermsWithConditions : Text -> [OcfVestingCondition] -> VestingTermsOcfData +defaultVestingTermsWithConditions vestingTermsId conditions = VestingTermsOcfData with + id = vestingTermsId + allocation_type = OcfAllocationCumulativeRounding + description = "Vesting terms " <> vestingTermsId + name = "Vesting terms " <> vestingTermsId + comments = [] + vesting_conditions = conditions + +defaultVestingTermsWithCondition : Text -> Text -> OcfVestingTrigger -> VestingTermsOcfData +defaultVestingTermsWithCondition vestingTermsId conditionId trigger = + defaultVestingTermsWithConditions + vestingTermsId + [defaultVestingCondition conditionId trigger []] + -- ============================================================================= -- SETUP COMBINATORS -- ============================================================================= diff --git a/Test/daml/OpenCapTable/TestIssuerAuthorizedSharesAdjustment.daml b/Test/daml/OpenCapTable/TestIssuerAuthorizedSharesAdjustment.daml index f9628510..c7f4356f 100644 --- a/Test/daml/OpenCapTable/TestIssuerAuthorizedSharesAdjustment.daml +++ b/Test/daml/OpenCapTable/TestIssuerAuthorizedSharesAdjustment.daml @@ -28,17 +28,21 @@ mkIssuer issuer system_operator ctx = initial_shares_authorized = Some (OcfInitialSharesNumeric 1000.0) comments = [] +issuerAuthorizedSharesAdjustment : Text -> Text -> Decimal -> IssuerAuthorizedSharesAdjustmentOcfData +issuerAuthorizedSharesAdjustment adjustmentId issuerId sharesAuthorized = + IssuerAuthorizedSharesAdjustmentOcfData with + id = adjustmentId + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + issuer_id = issuerId + new_shares_authorized = sharesAuthorized + board_approval_date = None + stockholder_approval_date = None + comments = [] + createIssuerAuthorizedSharesAdjustment issuer capTableCid = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateIssuerAuthorizedSharesAdjustment IssuerAuthorizedSharesAdjustmentOcfData with - id = "TX_ISSUER_AUTH_SHARES_ADJ_1" - date = DT.time (DA.date 2024 Jan 01) 0 0 0 - issuer_id = "ISSUER_1" - new_shares_authorized = 2000.0 - board_approval_date = None - stockholder_approval_date = None - comments = []] + creates = [CT.OcfCreateIssuerAuthorizedSharesAdjustment (issuerAuthorizedSharesAdjustment "TX_ISSUER_AUTH_SHARES_ADJ_1" "ISSUER_1" 2000.0)] edits = [] deletes = [] @@ -68,3 +72,22 @@ testIssuerAuthorizedSharesAdjustment_OptionalsSome = script do edits = [] deletes = [] pure () + +testIssuerAuthorizedSharesAdjustment_WrongIssuerIdCreateFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateIssuerAuthorizedSharesAdjustment (issuerAuthorizedSharesAdjustment "TX_BAD_ISSUER_ID" "OTHER_ISSUER" 2000.0)] + edits = [] + deletes = [] + +testIssuerAuthorizedSharesAdjustment_WrongIssuerIdEditFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- createIssuerAuthorizedSharesAdjustment issuer cap_table + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditIssuerAuthorizedSharesAdjustment (issuerAuthorizedSharesAdjustment "TX_ISSUER_AUTH_SHARES_ADJ_1" "OTHER_ISSUER" 2000.0)] + deletes = [] diff --git a/Test/daml/OpenCapTable/TestLifecycleInvariants.daml b/Test/daml/OpenCapTable/TestLifecycleInvariants.daml index 4e55399e..16a54f1a 100644 --- a/Test/daml/OpenCapTable/TestLifecycleInvariants.daml +++ b/Test/daml/OpenCapTable/TestLifecycleInvariants.daml @@ -393,7 +393,7 @@ testUnmaterializedStockResultCannotAliasLiveWarrant = script do edits = [] deletes = [] -testUnmaterializedPartialTransferWithBalancePreservesShareCount = script do +testPartialTransferWithMaterializedSuccessorsPreservesShareCount = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_UNMATERIALIZED_TRANSFER_BALANCE" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_UNMATERIALIZED_TRANSFER_BALANCE" "Unmaterialized Transfer Holder" @@ -406,6 +406,7 @@ testUnmaterializedPartialTransferWithBalancePreservesShareCount = script do exerciseCmd issued CT.UpdateCapTable with creates = [ CT.OcfCreateStockTransfer (lifecycleStockTransfer "TX_UNMATERIALIZED_TRANSFER" sourceSecurityId 40.0 (Some balanceSecurityId) ["SEC_UNMATERIALIZED_TRANSFER_MISSING_RESULT"]) + , stockSuccessorIssuance "TX_UNMATERIALIZED_TRANSFER_RESULT" "SEC_UNMATERIALIZED_TRANSFER_MISSING_RESULT" stakeholderId stockClassId defaultTestDate 40.0 , CT.OcfCreateStockIssuance balanceIssuance ] edits = [] @@ -417,7 +418,7 @@ testUnmaterializedPartialTransferWithBalancePreservesShareCount = script do edits = [] deletes = [] -testUnmaterializedReissuanceCannotOutliveSource = script do +testReissuanceCannotOutliveSource = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_UNMATERIALIZED_REISSUANCE_SOURCE" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_UNMATERIALIZED_REISSUANCE_SOURCE" "Unmaterialized Reissuance Holder" @@ -427,7 +428,10 @@ testUnmaterializedReissuanceCannotOutliveSource = script do ((defaultStockIssuance sourceTransactionId sourceSecurityId stakeholderId stockClassId) with quantity = 100.0) reissued <- submit issuer do exerciseCmd issued CT.UpdateCapTable with - creates = [CT.OcfCreateStockReissuance (lifecycleStockReissuance "TX_UNMATERIALIZED_REISSUANCE" sourceSecurityId ["SEC_UNMATERIALIZED_REISSUANCE_MISSING_RESULT"])] + creates = + [ CT.OcfCreateStockReissuance (lifecycleStockReissuance "TX_UNMATERIALIZED_REISSUANCE" sourceSecurityId ["SEC_UNMATERIALIZED_REISSUANCE_MISSING_RESULT"]) + , stockSuccessorIssuance "TX_UNMATERIALIZED_REISSUANCE_RESULT" "SEC_UNMATERIALIZED_REISSUANCE_MISSING_RESULT" stakeholderId stockClassId defaultTestDate 100.0 + ] edits = [] deletes = [] @@ -437,7 +441,7 @@ testUnmaterializedReissuanceCannotOutliveSource = script do edits = [] deletes = [CT.OcfDeleteStockIssuance sourceTransactionId] -testUnmaterializedConsolidationCannotOutliveSource = script do +testConsolidationCannotOutliveSource = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_UNMATERIALIZED_CONSOLIDATION_SOURCE" 100.0) (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithClass "SH_UNMATERIALIZED_CONSOLIDATION_SOURCE" "Unmaterialized Consolidation Holder" @@ -447,7 +451,10 @@ testUnmaterializedConsolidationCannotOutliveSource = script do ((defaultStockIssuance sourceTransactionId sourceSecurityId stakeholderId stockClassId) with quantity = 100.0) consolidated <- submit issuer do exerciseCmd issued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConsolidation (lifecycleStockConsolidation "TX_UNMATERIALIZED_CONSOLIDATION" [sourceSecurityId] "SEC_UNMATERIALIZED_CONSOLIDATION_MISSING_RESULT")] + creates = + [ CT.OcfCreateStockConsolidation (lifecycleStockConsolidation "TX_UNMATERIALIZED_CONSOLIDATION" [sourceSecurityId] "SEC_UNMATERIALIZED_CONSOLIDATION_MISSING_RESULT") + , stockSuccessorIssuance "TX_UNMATERIALIZED_CONSOLIDATION_RESULT" "SEC_UNMATERIALIZED_CONSOLIDATION_MISSING_RESULT" stakeholderId stockClassId defaultTestDate 100.0 + ] edits = [] deletes = [] @@ -486,7 +493,7 @@ testSameSecurityIdCorrectionRetainsRetractionHistory = script do deletes = [CT.OcfDeleteStockIssuance originalTransactionId] pure () -testStockBalanceSuccessorIncludesPriorUnmaterializedReduction = script do +testStockBalanceSuccessorIncludesPriorMaterializedReduction = script do TestOcp{issuer, cap_table} <- setupTestOcp (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" @@ -494,7 +501,10 @@ testStockBalanceSuccessorIncludesPriorUnmaterializedReduction = script do 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"])] + creates = + [ CT.OcfCreateStockTransfer (lifecycleStockTransfer "TX_STOCK_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 None ["SEC_STOCK_UNMATERIALIZED_HISTORY_MISSING"]) + , stockSuccessorIssuance "TX_STOCK_UNMATERIALIZED_HISTORY_RESULT" "SEC_STOCK_UNMATERIALIZED_HISTORY_MISSING" stakeholderId stockClassId defaultTestDate 30.0 + ] edits = [] deletes = [] @@ -510,7 +520,7 @@ testStockBalanceSuccessorIncludesPriorUnmaterializedReduction = script do edits = [] deletes = [] -testEquityBalanceSuccessorIncludesPriorUnmaterializedReduction = script do +testEquityBalanceSuccessorIncludesPriorMaterializedReduction = script do TestOcp{issuer, cap_table} <- setupTestOcp (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" @@ -522,7 +532,10 @@ testEquityBalanceSuccessorIncludesPriorUnmaterializedReduction = script do deletes = [] 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"])] + creates = + [ CT.OcfCreateEquityCompensationTransfer (lifecycleEquityTransfer "TX_EQUITY_UNMATERIALIZED_HISTORY_TRANSFER" sourceSecurityId 30.0 None ["ESEC_UNMATERIALIZED_HISTORY_MISSING"]) + , CT.OcfCreateEquityCompensationIssuance (lifecycleEquityIssuance "TX_EQUITY_UNMATERIALIZED_HISTORY_RESULT" "ESEC_UNMATERIALIZED_HISTORY_MISSING" stakeholderId stockClassId 30.0) + ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestResultingSecurityReferences.daml b/Test/daml/OpenCapTable/TestResultingSecurityReferences.daml new file mode 100644 index 00000000..6f2beaca --- /dev/null +++ b/Test/daml/OpenCapTable/TestResultingSecurityReferences.daml @@ -0,0 +1,360 @@ +module OpenCapTable.TestResultingSecurityReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.ConvertibleConversion (ConvertibleConversionOcfData(..)) +import Fairmint.OpenCapTable.OCF.ConvertibleTransfer (ConvertibleTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationExercise (EquityCompensationExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationRelease (EquityCompensationReleaseOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationTransfer (EquityCompensationTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockConsolidation (StockConsolidationOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockReissuance (StockReissuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockTransfer (StockTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantExercise (WarrantExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantTransfer (WarrantTransferOcfData(..)) +import Fairmint.OpenCapTable.Types.Conversion +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers + ( addDefaultConvertibleIssuance + , addDefaultEquityCompensationIssuance + , addDefaultStakeholder + , addDefaultStockIssuance + , addDefaultWarrantIssuance + , addPrerequisites + , addStockIssuance + , defaultTestDate + , stockSuccessorIssuance + ) +import Daml.Script + +warrantExerciseSourceIssuance : CT.OcfCreateData +warrantExerciseSourceIssuance = + CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with + id = "WR-ISS-EXERCISE-SOURCE" + custom_id = "WR-EXERCISE-SOURCE" + date = defaultTestDate + purchase_price = OcfMonetary with amount = 1.0, currency = "USD" + security_id = "SEC-WR-EXERCISE-SOURCE" + stakeholder_id = "SH-1" + comments = [] + exercise_triggers = [OcfConversionTrigger with + conversion_right = OcfRightWarrant (OcfWarrantConversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with converts_to_quantity = 100.0 + converts_to_future_round = None + converts_to_stock_class_id = Some "SC_COMMON" + type_ = "WARRANT_CONVERSION_RIGHT") + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "WR-EXERCISE-TRIGGER" + type_ = OcfTriggerTypeTypeElectiveAtWill] + security_law_exemptions = [] + vestings = [] + board_approval_date = None + consideration_text = None + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + quantity = Some 100.0 + quantity_source = None + stockholder_approval_date = None + vesting_terms_id = None + warrant_expiration_date = None + +testResultingSecurityReferences_MissingStockTransferResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX-STOCK-MISSING-RESULT" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-STOCK-MISSING-RESULT"] + balance_security_id = None + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingWarrantTransferResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addDefaultWarrantIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + id = "TX-WARRANT-MISSING-RESULT" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-WR-1" + resulting_security_ids = ["SEC-WARRANT-MISSING-RESULT"] + balance_security_id = None + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingConvertibleTransferResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + capTableCid <- addDefaultConvertibleIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleTransfer ConvertibleTransferOcfData with + id = "TX-CONVERTIBLE-MISSING-RESULT" + date = defaultTestDate + amount = OcfMonetary with amount = 10000.0, currency = "USD" + security_id = "SEC-CV-1" + resulting_security_ids = ["SEC-CONVERTIBLE-MISSING-RESULT"] + balance_security_id = None + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingEquityCompensationTransferResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultEquityCompensationIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + id = "TX-EC-MISSING-RESULT" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-EC-1" + resulting_security_ids = ["SEC-EC-MISSING-RESULT"] + balance_security_id = None + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_StockTransferResultMustBeStock = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + capTableCid <- addDefaultWarrantIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX-STOCK-WRONG-RESULT-TYPE" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-WR-1"] + balance_security_id = None + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingConsolidationResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addStockIssuance issuer capTableCid "TX-STOCK-CONSOLIDATE-A" "SEC-STOCK-CONSOLIDATE-A" "SH-1" "SC_COMMON" + capTableCid <- addStockIssuance issuer capTableCid "TX-STOCK-CONSOLIDATE-B" "SEC-STOCK-CONSOLIDATE-B" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockConsolidation StockConsolidationOcfData with + id = "TX-STOCK-MISSING-CONSOLIDATION-RESULT" + date = defaultTestDate + security_ids = ["SEC-STOCK-CONSOLIDATE-A", "SEC-STOCK-CONSOLIDATE-B"] + resulting_security_id = "SEC-STOCK-MISSING-CONSOLIDATION-RESULT" + reason_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingStockReissuanceResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + id = "TX-STOCK-MISSING-REISSUANCE-RESULT" + date = defaultTestDate + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-STOCK-MISSING-REISSUANCE-RESULT"] + comments = [] + reason_text = None + split_transaction_id = None] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingWarrantExerciseResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + source <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [warrantExerciseSourceIssuance] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd source.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + id = "TX-WARRANT-MISSING-EXERCISE-RESULT" + date = defaultTestDate + security_id = "SEC-WR-EXERCISE-SOURCE" + trigger_id = "WR-EXERCISE-TRIGGER" + quantity = Some 100.0 + resulting_security_ids = ["SEC-STOCK-MISSING-WARRANT-EXERCISE-RESULT"] + comments = [] + consideration_text = None] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingConvertibleConversionResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultConvertibleIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateConvertibleConversion ConvertibleConversionOcfData with + id = "TX-CONVERTIBLE-MISSING-CONVERSION-RESULT" + date = defaultTestDate + security_id = "SEC-CV-1" + trigger_id = "TRIGGER-1" + reason_text = "Conversion test" + resulting_security_ids = ["SEC-STOCK-MISSING-CONVERTIBLE-CONVERSION-RESULT"] + comments = [] + balance_security_id = None + capitalization_definition = None + quantity_converted = Some 100.0] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingEquityCompensationExerciseResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultEquityCompensationIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-EC-MISSING-EXERCISE-RESULT" + date = defaultTestDate + security_id = "SEC-EC-1" + quantity = 100.0 + consideration_text = None + resulting_security_ids = ["SEC-STOCK-MISSING-EC-EXERCISE-RESULT"] + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_MissingEquityCompensationReleaseResultFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultEquityCompensationIssuance issuer capTableCid + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationRelease EquityCompensationReleaseOcfData with + id = "TX-EC-MISSING-RELEASE-RESULT" + date = defaultTestDate + security_id = "SEC-EC-1" + settlement_date = defaultTestDate + release_price = OcfMonetary with amount = 1.0, currency = "USD" + quantity = 100.0 + resulting_security_ids = ["SEC-STOCK-MISSING-EC-RELEASE-RESULT"] + consideration_text = None + comments = []] + edits = [] + deletes = [] + +testResultingSecurityReferences_StockResultCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + + _ <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ stockSuccessorIssuance "TX-STOCK-SAME-BATCH-RESULT" "SEC-STOCK-SAME-BATCH-RESULT" "SH-1" "SC_COMMON" defaultTestDate 100.0 + , CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX-STOCK-SAME-BATCH-TRANSFER" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-STOCK-SAME-BATCH-RESULT"] + balance_security_id = None + consideration_text = None + comments = [] + ] + edits = [] + deletes = [] + pure () + +testResultingSecurityReferences_ResultDeletionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + + result <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ stockSuccessorIssuance "TX-STOCK-RESULT-TO-DELETE" "SEC-STOCK-RESULT-TO-DELETE" "SH-1" "SC_COMMON" defaultTestDate 100.0 + , CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX-STOCK-RESULT-DELETE-TRANSFER" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-STOCK-RESULT-TO-DELETE"] + balance_security_id = None + consideration_text = None + comments = [] + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockIssuance "TX-STOCK-RESULT-TO-DELETE"] + pure () + +testResultingSecurityReferences_EditCanReplaceResultInSameBatch = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addDefaultStockIssuance issuer capTableCid + let originalTransfer = StockTransferOcfData with + id = "TX-STOCK-EDIT-RESULT" + date = defaultTestDate + quantity = 100.0 + security_id = "SEC-STOCK-1" + resulting_security_ids = ["SEC-STOCK-EDIT-RESULT-OLD"] + balance_security_id = None + consideration_text = None + comments = [] + + created <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ stockSuccessorIssuance "TX-STOCK-EDIT-RESULT-OLD" "SEC-STOCK-EDIT-RESULT-OLD" "SH-1" "SC_COMMON" defaultTestDate 100.0 + , CT.OcfCreateStockTransfer originalTransfer + ] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd created.updatedCapTableCid CT.UpdateCapTable with + creates = [stockSuccessorIssuance "TX-STOCK-EDIT-RESULT-NEW" "SEC-STOCK-EDIT-RESULT-NEW" "SH-1" "SC_COMMON" defaultTestDate 100.0] + edits = [CT.OcfEditStockTransfer (originalTransfer with resulting_security_ids = ["SEC-STOCK-EDIT-RESULT-NEW"])] + deletes = [CT.OcfDeleteStockIssuance "TX-STOCK-EDIT-RESULT-OLD"] + pure () diff --git a/Test/daml/OpenCapTable/TestSecurityIdValidation.daml b/Test/daml/OpenCapTable/TestSecurityIdValidation.daml index cec9dbd8..9d1ef85f 100644 --- a/Test/daml/OpenCapTable/TestSecurityIdValidation.daml +++ b/Test/daml/OpenCapTable/TestSecurityIdValidation.daml @@ -17,7 +17,17 @@ import qualified Fairmint.OpenCapTable.OCF.EquityCompensationTransfer as ECT import qualified Fairmint.OpenCapTable.CapTable as CT import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites, addDefaultStockIssuance, addDefaultWarrantIssuance, addDefaultConvertibleIssuance, addDefaultEquityCompensationIssuance, addStockIssuance, defaultStockIssuance) +import OpenCapTable.TestHelpers + ( addPrerequisites + , addDefaultStockIssuance + , addDefaultWarrantIssuance + , addDefaultConvertibleIssuance + , addDefaultEquityCompensationIssuance + , addStockIssuance + , defaultStockIssuance + , stockSuccessorIssuance + , warrantSuccessorIssuance + ) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Map as Map @@ -384,15 +394,18 @@ testStockTransfer_CorrectSecurityIdSucceeds = script do -- Create a stock transfer using the correct stock security_id result <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateStockTransfer ST.StockTransferOcfData with - id = "TX_TRANSFER_VALID" - date = DT.time (DA.date 2024 Jan 01) 0 0 0 - security_id = "SEC-STOCK-1" -- Correct stock security_id - quantity = 50.0 - comments = [] - resulting_security_ids = ["NEW-SEC-1"] - balance_security_id = None - consideration_text = None] + creates = + [ CT.OcfCreateStockTransfer ST.StockTransferOcfData with + id = "TX_TRANSFER_VALID" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-STOCK-1" -- Correct stock security_id + quantity = 100.0 + comments = [] + resulting_security_ids = ["NEW-SEC-1"] + balance_security_id = None + consideration_text = None + , stockSuccessorIssuance "TX_TRANSFER_VALID_RESULT" "NEW-SEC-1" "SH-1" "SC_COMMON" (DT.time (DA.date 2024 Jan 01) 0 0 0) 100.0 + ] edits = [] deletes = [] pure () @@ -406,15 +419,18 @@ testWarrantTransfer_CorrectSecurityIdSucceeds = script do -- Create a warrant transfer using the correct warrant security_id result <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantTransfer WT.WarrantTransferOcfData with - id = "TX_WARRANT_TRANSFER_VALID" - date = DT.time (DA.date 2024 Jan 01) 0 0 0 - security_id = "SEC-WR-1" -- Correct warrant security_id - quantity = 25.0 - comments = [] - resulting_security_ids = ["NEW-WARRANT-1"] - balance_security_id = None - consideration_text = None] + creates = + [ CT.OcfCreateWarrantTransfer WT.WarrantTransferOcfData with + id = "TX_WARRANT_TRANSFER_VALID" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-WR-1" -- Correct warrant security_id + quantity = 100.0 + comments = [] + resulting_security_ids = ["NEW-WARRANT-1"] + balance_security_id = None + consideration_text = None + , warrantSuccessorIssuance "TX_WARRANT_TRANSFER_VALID_RESULT" "NEW-WARRANT-1" "SH-1" (DT.time (DA.date 2024 Jan 01) 0 0 0) 100.0 + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestStockAcceptance.daml b/Test/daml/OpenCapTable/TestStockAcceptance.daml index 88d7c17d..bbf45706 100644 --- a/Test/daml/OpenCapTable/TestStockAcceptance.daml +++ b/Test/daml/OpenCapTable/TestStockAcceptance.daml @@ -33,7 +33,7 @@ createStockIssuance issuer capTableCid = vesting_terms_id = None vestings = [] cost_basis = None - stock_legend_ids = ["L1"] + stock_legend_ids = [] issuance_type = None comments = []] edits = [] diff --git a/Test/daml/OpenCapTable/TestStockCancellation.daml b/Test/daml/OpenCapTable/TestStockCancellation.daml index d468ebe4..cb426ad0 100644 --- a/Test/daml/OpenCapTable/TestStockCancellation.daml +++ b/Test/daml/OpenCapTable/TestStockCancellation.daml @@ -56,7 +56,7 @@ createStockIssuance issuer capTableCid = vesting_terms_id = None vestings = [] cost_basis = None - stock_legend_ids = ["L1"] + stock_legend_ids = [] issuance_type = None comments = []] edits = [] diff --git a/Test/daml/OpenCapTable/TestStockClass.daml b/Test/daml/OpenCapTable/TestStockClass.daml index fbed6ef5..677643ef 100644 --- a/Test/daml/OpenCapTable/TestStockClass.daml +++ b/Test/daml/OpenCapTable/TestStockClass.daml @@ -11,25 +11,28 @@ import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT +commonStockClassData : StockClassOcfData +commonStockClassData = StockClassOcfData with + id = "SC_COMMON" + name = "Common" + class_type = OcfStockClassTypeCommon + default_id_prefix = "CS-" + initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 + votes_per_share = 1.0 + seniority = 1.0 + board_approval_date = None + stockholder_approval_date = None + par_value = None + price_per_share = None + liquidation_preference_multiple = None + participation_cap_multiple = None + conversion_rights = [] + comments = [] + createStockClass issuer capTableCid = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_COMMON" - name = "Common" - class_type = OcfStockClassTypeCommon - default_id_prefix = "CS-" - initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 - votes_per_share = 1.0 - seniority = 1.0 - board_approval_date = None - stockholder_approval_date = None - par_value = None - price_per_share = None - liquidation_preference_multiple = None - participation_cap_multiple = None - conversion_rights = [] - comments = []] + creates = [CT.OcfCreateStockClass commonStockClassData] edits = [] deletes = [] @@ -65,22 +68,25 @@ testCreateStockClass_OptionalsSome = script do valuation_cap = None _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_PREFERRED" - name = "Preferred" - class_type = OcfStockClassTypePreferred - default_id_prefix = "PS-" - initial_shares_authorized = OcfInitialSharesNumeric 2_000_000.0 - votes_per_share = 1.0 - seniority = 2.0 - board_approval_date = Some (DT.time (DA.date 2024 Jan 1) 0 0 0) - stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) - par_value = Some (OcfMonetary with amount = 0.0001, currency = "USD") - price_per_share = Some (OcfMonetary with amount = 1.0, currency = "USD") - liquidation_preference_multiple = Some 1.0 - participation_cap_multiple = None - conversion_rights = [ convRight ] - comments = ["Preferred terms"]] + creates = + [ CT.OcfCreateStockClass commonStockClassData + , CT.OcfCreateStockClass (StockClassOcfData with + id = "SC_PREFERRED" + name = "Preferred" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PS-" + initial_shares_authorized = OcfInitialSharesNumeric 2_000_000.0 + votes_per_share = 1.0 + seniority = 2.0 + board_approval_date = Some (DT.time (DA.date 2024 Jan 1) 0 0 0) + stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) + par_value = Some (OcfMonetary with amount = 0.0001, currency = "USD") + price_per_share = Some (OcfMonetary with amount = 1.0, currency = "USD") + liquidation_preference_multiple = Some 1.0 + participation_cap_multiple = None + conversion_rights = [ convRight ] + comments = ["Preferred terms"]) + ] edits = [] deletes = [] pure () @@ -107,22 +113,25 @@ testCreateStockClass_PpsBasedConversion = script do valuation_cap = None _ <- submit issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_PREFERRED_PPS" - name = "Preferred PPS" - class_type = OcfStockClassTypePreferred - default_id_prefix = "PP-" - initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 - votes_per_share = 1.0 - seniority = 2.0 - board_approval_date = None - stockholder_approval_date = None - par_value = None - price_per_share = None - liquidation_preference_multiple = None - participation_cap_multiple = None - conversion_rights = [ convRight ] - comments = []] + creates = + [ CT.OcfCreateStockClass commonStockClassData + , CT.OcfCreateStockClass (StockClassOcfData with + id = "SC_PREFERRED_PPS" + name = "Preferred PPS" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PP-" + initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 + votes_per_share = 1.0 + seniority = 2.0 + board_approval_date = None + stockholder_approval_date = None + par_value = None + price_per_share = None + liquidation_preference_multiple = None + participation_cap_multiple = None + conversion_rights = [ convRight ] + comments = []) + ] edits = [] deletes = [] pure () @@ -148,22 +157,25 @@ testCreateStockClass_PpsBasedConversionFailsWithoutReferencePrice = script do valuation_cap = None submitMustFail issuer do exerciseCmd cap_table CT.UpdateCapTable with - creates = [CT.OcfCreateStockClass StockClassOcfData with - id = "SC_PREFERRED_PPS_FAIL" - name = "Preferred PPS Fail" - class_type = OcfStockClassTypePreferred - default_id_prefix = "PF-" - initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 - votes_per_share = 1.0 - seniority = 2.0 - board_approval_date = None - stockholder_approval_date = None - par_value = None - price_per_share = None - liquidation_preference_multiple = None - participation_cap_multiple = None - conversion_rights = [ convRight ] - comments = []] + creates = + [ CT.OcfCreateStockClass commonStockClassData + , CT.OcfCreateStockClass (StockClassOcfData with + id = "SC_PREFERRED_PPS_FAIL" + name = "Preferred PPS Fail" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PF-" + initial_shares_authorized = OcfInitialSharesNumeric 1_000_000.0 + votes_per_share = 1.0 + seniority = 2.0 + board_approval_date = None + stockholder_approval_date = None + par_value = None + price_per_share = None + liquidation_preference_multiple = None + participation_cap_multiple = None + conversion_rights = [ convRight ] + comments = []) + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml index dee539f2..94d5722b 100644 --- a/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml +++ b/Test/daml/OpenCapTable/TestStockClassAuthorizedShares.daml @@ -938,7 +938,10 @@ testStockConversionReducesIssuedSharesForCeiling = script do capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CONVERSION_ISSUED" "SEC_CONVERSION_ISSUED" stakeholderId stockClassId) with quantity = 100.0) result <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_REDUCE" "SEC_CONVERSION_ISSUED" 50.0)] + creates = + [ CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_REDUCE" "SEC_CONVERSION_ISSUED" 50.0) + , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CONVERSION_REDUCE_RESULT" "TX_CONVERSION_REDUCE_RESULT" stakeholderId destinationClassId) with quantity = 50.0) + ] edits = [] deletes = [] @@ -958,17 +961,22 @@ testStockConversionCreditsDestinationClassForCeiling = script do submitMustFail issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_DESTINATION" "SEC_CONVERSION_DESTINATION_SOURCE" 150.0)] + creates = + [ stockSuccessorIssuance "TX_CONVERSION_DESTINATION_OUTPUT" "TX_CONVERSION_DESTINATION_RESULT" stakeholderId commonClassId defaultTestDate 150.0 + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_DESTINATION" "SEC_CONVERSION_DESTINATION_SOURCE" 150.0) + ] edits = [] deletes = [] testStockConversionMissingDestinationClassFailsForCeiling = script do TestOcp{issuer, cap_table} <- setupTestOcp + let destinationClassId = "SC_CONVERSION_MISSING_DEST" + (capTableWithDestination, _) <- setupStockClassWith issuer cap_table (limitedStockClass destinationClassId 100.0) let preferredClassId = "SC_CONVERSION_MISSING_DEST_SOURCE" let preferredClass = (limitedStockClass preferredClassId 200.0) with - conversion_rights = [stockClassConversionRightTo "SC_CONVERSION_MISSING_DEST"] - (capTableWithPreferred, _) <- setupStockClassWith issuer cap_table preferredClass + conversion_rights = [stockClassConversionRightTo destinationClassId] + (capTableWithPreferred, _) <- setupStockClassWith issuer capTableWithDestination preferredClass (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithPreferred "SH_CONVERSION_MISSING_DEST" "Conversion Missing Destination Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CONVERSION_MISSING_DEST_SOURCE" "SEC_CONVERSION_MISSING_DEST_SOURCE" stakeholderId preferredClassId) with quantity = 50.0) @@ -976,7 +984,7 @@ testStockConversionMissingDestinationClassFailsForCeiling = script do exerciseCmd capTableIssued CT.UpdateCapTable with creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_MISSING_DEST" "SEC_CONVERSION_MISSING_DEST_SOURCE" 50.0)] edits = [] - deletes = [] + deletes = [CT.OcfDeleteStockClass destinationClassId] testStockConversionMissingSourceIssuanceFailsForCeiling = script do TestOcp{issuer, cap_table} <- setupTestOcp @@ -1013,6 +1021,7 @@ testStockConversionWithPartialResultingIssuancesFailsForCeiling = script do creates = [ CT.OcfCreateStockConversion conversion , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CONVERSION_PARTIAL_RESULT" "SEC_CONVERSION_PARTIAL_RESULT" stakeholderId commonClassId) with quantity = 75.0) + , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_CONVERSION_PARTIAL_MISSING" "SEC_CONVERSION_PARTIAL_MISSING" stakeholderId commonClassId) with quantity = 75.0) ] edits = [] deletes = [] @@ -1099,11 +1108,14 @@ testStockConversionCannotReduceSecurityBelowZero = script do submitMustFail issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_OVER_REDUCE" "SEC_CONVERSION_OVER_REDUCE" 150.0)] + creates = + [ stockSuccessorIssuance "TX_CONVERSION_OVER_REDUCE_OUTPUT" "TX_CONVERSION_OVER_REDUCE_RESULT" stakeholderId destinationClassId defaultTestDate 150.0 + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_OVER_REDUCE" "SEC_CONVERSION_OVER_REDUCE" 150.0) + ] edits = [] deletes = [] -testStockConversionWithAmbiguousConversionRightsFailsForCeiling = script do +testStockConversionWithAmbiguousConversionRightsUsesExplicitResultingSecurity = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithCommon, commonClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_CONVERSION_AMBIG_COMMON" 100.0) @@ -1118,11 +1130,15 @@ testStockConversionWithAmbiguousConversionRightsFailsForCeiling = script do (capTableReady, stakeholderId) <- setupStakeholderWithId issuer capTableWithPreferred "SH_CONVERSION_AMBIG" "Ambiguous Conversion Holder" capTableIssued <- createStockIssuanceWith issuer capTableReady ((defaultStockIssuance "TX_CONVERSION_AMBIG_ISSUED" "SEC_CONVERSION_AMBIG" stakeholderId preferredClassId) with quantity = 50.0) - submitMustFail issuer do + _ <- submit issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_AMBIG" "SEC_CONVERSION_AMBIG" 50.0)] + creates = + [ stockSuccessorIssuance "TX_CONVERSION_AMBIG_OUTPUT" "TX_CONVERSION_AMBIG_RESULT" stakeholderId commonClassId defaultTestDate 50.0 + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_AMBIG" "SEC_CONVERSION_AMBIG" 50.0) + ] edits = [] deletes = [] + pure () testStockConversionWithMultipleRightsAcceptsMatchingExplicitResult = script do TestOcp{issuer, cap_table} <- setupTestOcp @@ -1160,7 +1176,10 @@ testStockConversionWithoutConversionRightsFailsForCeiling = script do submitMustFail issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_NO_RIGHTS" "SEC_CONVERSION_NO_RIGHTS" 50.0)] + creates = + [ stockSuccessorIssuance "TX_CONVERSION_NO_RIGHTS_OUTPUT" "TX_CONVERSION_NO_RIGHTS_RESULT" stakeholderId stockClassId defaultTestDate 50.0 + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_NO_RIGHTS" "SEC_CONVERSION_NO_RIGHTS" 50.0) + ] edits = [] deletes = [] @@ -1179,6 +1198,7 @@ testStockConversionUsesRatioAdjustmentForDestinationCeiling = script do exerciseCmd capTableIssued CT.UpdateCapTable with creates = [ CT.OcfCreateStockClassConversionRatioAdjustment (stockClassConversionRatioAdjustment "TX_CONVERSION_RATIO_ADJUST" preferredClassId 2.0) + , stockSuccessorIssuance "TX_CONVERSION_RATIO_OUTPUT" "TX_CONVERSION_RATIO_RESULT" stakeholderId commonClassId defaultTestDate 150.0 , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_RATIO" "SEC_CONVERSION_RATIO" 75.0) ] edits = [] @@ -1200,6 +1220,7 @@ testSameDateHigherStockConversionRatioAdjustmentWinsForCeiling = script do creates = [ CT.OcfCreateStockClassConversionRatioAdjustment (stockClassConversionRatioAdjustment "TX_CONVERSION_RATIO_TIE_LOW" preferredClassId 1.0) , CT.OcfCreateStockClassConversionRatioAdjustment (stockClassConversionRatioAdjustment "TX_CONVERSION_RATIO_TIE_HIGH" preferredClassId 2.0) + , stockSuccessorIssuance "TX_CONVERSION_RATIO_TIE_OUTPUT" "TX_CONVERSION_RATIO_TIE_RESULT" stakeholderId commonClassId defaultTestDate 150.0 , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_RATIO_TIE" "SEC_CONVERSION_RATIO_TIE" 75.0) ] edits = [] @@ -1222,7 +1243,10 @@ testStockConversionWithNonRatioRightFailsForCeiling = script do submitMustFail issuer do exerciseCmd capTableIssued CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_NON_RATIO" "SEC_CONVERSION_NON_RATIO" 50.0)] + creates = + [ stockSuccessorIssuance "TX_CONVERSION_NON_RATIO_OUTPUT" "TX_CONVERSION_NON_RATIO_RESULT" stakeholderId commonClassId defaultTestDate 50.0 + , CT.OcfCreateStockConversion (stockConversion "TX_CONVERSION_NON_RATIO" "SEC_CONVERSION_NON_RATIO" 50.0) + ] edits = [] deletes = [] @@ -1254,7 +1278,7 @@ testStockConversionSkipsNonComputableRightWhenRatioRightMatchesResult = script d deletes = [] pure () -testFixedConvertibleConversionEnforcesCeilingWithoutMaterializedResult = script do +testFixedConvertibleConversionEnforcesCeilingWithReferencedResult = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_CONVERTIBLE_FIXED" 100.0) @@ -1276,6 +1300,13 @@ testFixedConvertibleConversionEnforcesCeilingWithoutMaterializedResult = script creates = [ CT.OcfCreateConvertibleIssuance excessiveIssuance , CT.OcfCreateConvertibleConversion excessiveConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_FIXED_EXCESS_RESULT" + "SEC-CONVERTIBLE-FIXED-EXCESS-RESULT" + stakeholderId + stockClassId + defaultTestDate + 101.0 ] edits = [] deletes = [] @@ -1297,6 +1328,13 @@ testFixedConvertibleConversionEnforcesCeilingWithoutMaterializedResult = script creates = [ CT.OcfCreateConvertibleIssuance boundaryIssuance , CT.OcfCreateConvertibleConversion boundaryConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_FIXED_BOUNDARY_RESULT" + "SEC-CONVERTIBLE-FIXED-BOUNDARY-RESULT" + stakeholderId + stockClassId + defaultTestDate + 100.0 ] edits = [] deletes = [] @@ -1325,6 +1363,19 @@ testPartialFixedConvertibleConversionScalesOutput = script do creates = [ CT.OcfCreateConvertibleIssuance excessiveIssuance , CT.OcfCreateConvertibleConversion excessiveConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_PARTIAL_FIXED_EXCESS_RESULT" + "SEC-CONVERTIBLE-PARTIAL-FIXED-EXCESS-RESULT" + stakeholderId + stockClassId + defaultTestDate + 101.0 + , convertibleSuccessorIssuance + "TX_CONVERTIBLE_PARTIAL_FIXED_EXCESS_BALANCE" + "CONV-CONVERTIBLE-PARTIAL-FIXED-EXCESS-BALANCE" + stakeholderId + defaultTestDate + 100.0 ] edits = [] deletes = [] @@ -1347,6 +1398,19 @@ testPartialFixedConvertibleConversionScalesOutput = script do creates = [ CT.OcfCreateConvertibleIssuance boundaryIssuance , CT.OcfCreateConvertibleConversion boundaryConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_PARTIAL_FIXED_BOUNDARY_RESULT" + "SEC-CONVERTIBLE-PARTIAL-FIXED-BOUNDARY-RESULT" + stakeholderId + stockClassId + defaultTestDate + 100.0 + , convertibleSuccessorIssuance + "TX_CONVERTIBLE_PARTIAL_FIXED_BOUNDARY_BALANCE" + "CONV-CONVERTIBLE-PARTIAL-FIXED-BOUNDARY-BALANCE" + stakeholderId + defaultTestDate + 100.0 ] edits = [] deletes = [] @@ -1375,6 +1439,13 @@ testConvertibleConversionValidatesConvertedUnitBounds = script do creates = [ CT.OcfCreateConvertibleIssuance excessiveIssuance , CT.OcfCreateConvertibleConversion excessiveConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_UNIT_EXCESS_RESULT" + "SEC-CONVERTIBLE-UNIT-EXCESS-RESULT" + stakeholderId + stockClassId + defaultTestDate + 101.0 ] edits = [] deletes = [] @@ -1397,6 +1468,19 @@ testConvertibleConversionValidatesConvertedUnitBounds = script do creates = [ CT.OcfCreateConvertibleIssuance unknownPartialIssuance , CT.OcfCreateConvertibleConversion unknownPartialConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_UNIT_UNKNOWN_RESULT" + "SEC-CONVERTIBLE-UNIT-UNKNOWN-RESULT" + stakeholderId + stockClassId + defaultTestDate + 100.0 + , convertibleSuccessorIssuance + "TX_CONVERTIBLE_UNIT_UNKNOWN_BALANCE" + "CONV-CONVERTIBLE-UNIT-UNKNOWN-BALANCE" + stakeholderId + defaultTestDate + 50.0 ] edits = [] deletes = [] @@ -1424,6 +1508,13 @@ testRatioConvertibleConversionEnforcesCeiling = script do creates = [ CT.OcfCreateConvertibleIssuance excessiveIssuance , CT.OcfCreateConvertibleConversion excessiveConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_RATIO_EXCESS_RESULT" + "SEC-CONVERTIBLE-RATIO-EXCESS-RESULT" + stakeholderId + stockClassId + defaultTestDate + 102.0 ] edits = [] deletes = [] @@ -1445,6 +1536,13 @@ testRatioConvertibleConversionEnforcesCeiling = script do creates = [ CT.OcfCreateConvertibleIssuance boundaryIssuance , CT.OcfCreateConvertibleConversion boundaryConversion + , stockSuccessorIssuance + "TX_CONVERTIBLE_RATIO_BOUNDARY_RESULT" + "SEC-CONVERTIBLE-RATIO-BOUNDARY-RESULT" + stakeholderId + stockClassId + defaultTestDate + 100.0 ] edits = [] deletes = [] @@ -1626,7 +1724,10 @@ testWarrantExerciseCannotExceedStockClassAuthorizedShares = script do submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise (warrantExercise "TX_WARRANT_OUTPUT_EXERCISE" "WSEC_OUTPUT" 150.0)] + creates = + [ stockSuccessorIssuance "TX_WARRANT_OUTPUT_EXERCISE_OUTPUT" "TX_WARRANT_OUTPUT_EXERCISE_RESULT" stakeholderId stockClassId defaultTestDate 150.0 + , CT.OcfCreateWarrantExercise (warrantExercise "TX_WARRANT_OUTPUT_EXERCISE" "WSEC_OUTPUT" 150.0) + ] edits = [] deletes = [] @@ -1643,7 +1744,10 @@ testFixedWarrantPartialExerciseUsesExerciseQuantityForCeiling = script do _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise (warrantExercise "TX_WARRANT_PARTIAL_EXERCISE" "WSEC_PARTIAL" 50.0)] + creates = + [ CT.OcfCreateWarrantExercise (warrantExercise "TX_WARRANT_PARTIAL_EXERCISE" "WSEC_PARTIAL" 50.0) + , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_WARRANT_PARTIAL_EXERCISE_RESULT" "TX_WARRANT_PARTIAL_EXERCISE_RESULT" stakeholderId stockClassId) with quantity = 50.0) + ] edits = [] deletes = [] pure () @@ -1727,7 +1831,7 @@ testFixedWarrantFullExerciseScalesToRemainingBalance = script do deletes = [] pure () -testWarrantExerciseWithUnknownOutputQuantityFailsForCeiling = script do +testWarrantExerciseWithUnknownInputQuantityUsesResultingSecurity = script do TestOcp{issuer, cap_table} <- setupTestOcp (capTableWithClass, stockClassId) <- setupStockClassWith issuer cap_table (limitedStockClass "SC_WARRANT_UNKNOWN" 100.0) @@ -1741,11 +1845,15 @@ testWarrantExerciseWithUnknownOutputQuantityFailsForCeiling = script do edits = [] deletes = [] - submitMustFail issuer do + _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise ((warrantExercise "TX_WARRANT_UNKNOWN_EXERCISE" "WSEC_UNKNOWN" 50.0) with trigger_id = "TRIGGER_CUSTOM", quantity = None)] + creates = + [ stockSuccessorIssuance "TX_WARRANT_UNKNOWN_EXERCISE_OUTPUT" "TX_WARRANT_UNKNOWN_EXERCISE_RESULT" stakeholderId stockClassId defaultTestDate 50.0 + , CT.OcfCreateWarrantExercise ((warrantExercise "TX_WARRANT_UNKNOWN_EXERCISE" "WSEC_UNKNOWN" 50.0) with trigger_id = "TRIGGER_CUSTOM", quantity = None) + ] edits = [] deletes = [] + pure () testNonFixedWarrantExerciseRequiresResultingStockIssuanceForCeiling = script do TestOcp{issuer, cap_table} <- setupTestOcp @@ -1765,7 +1873,8 @@ testNonFixedWarrantExerciseRequiresResultingStockIssuanceForCeiling = script do creates = [ CT.OcfCreateWarrantExercise ((warrantExercise "TX_WARRANT_NON_FIXED_EXERCISE" "WSEC_NON_FIXED" 1.0) with - trigger_id = "TRIGGER_CUSTOM_NON_FIXED") + trigger_id = "TRIGGER_CUSTOM_NON_FIXED" + resulting_security_ids = []) ] edits = [] deletes = [] @@ -1877,7 +1986,10 @@ testWarrantExerciseWithUnknownTriggerFailsForCeiling = script do submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise ((warrantExercise "TX_WARRANT_MISSING_TRIGGER_EXERCISE" "WSEC_MISSING_TRIGGER" 50.0) with trigger_id = "TRIGGER_DOES_NOT_EXIST")] + creates = + [ stockSuccessorIssuance "TX_WARRANT_MISSING_TRIGGER_OUTPUT" "TX_WARRANT_MISSING_TRIGGER_EXERCISE_RESULT" stakeholderId stockClassId defaultTestDate 50.0 + , CT.OcfCreateWarrantExercise ((warrantExercise "TX_WARRANT_MISSING_TRIGGER_EXERCISE" "WSEC_MISSING_TRIGGER" 50.0) with trigger_id = "TRIGGER_DOES_NOT_EXIST") + ] edits = [] deletes = [] @@ -1894,7 +2006,10 @@ testEquityCompensationExerciseCannotExceedStockClassAuthorizedShares = script do submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_EQUITY_COMP_EXERCISE" "ECSEC_OUTPUT" 150.0)] + creates = + [ stockSuccessorIssuance "TX_EQUITY_COMP_EXERCISE_OUTPUT" "TX_EQUITY_COMP_EXERCISE_RESULT" stakeholderId stockClassId defaultTestDate 150.0 + , CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_EQUITY_COMP_EXERCISE" "ECSEC_OUTPUT" 150.0) + ] edits = [] deletes = [] @@ -2073,7 +2188,7 @@ testClasslessEquityCompensationReleaseUsesSinglePlanClassForCeiling = script do let settlementDate = DT.time (DA.date 2024 Jan 03) 0 0 0 submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationRelease (equityCompensationRelease "TX_CLASSLESS_EQUITY_RELEASE" "ECSEC_CLASSLESS_RELEASE" releaseDate settlementDate 150.0)] + creates = [CT.OcfCreateEquityCompensationRelease ((equityCompensationRelease "TX_CLASSLESS_EQUITY_RELEASE" "ECSEC_CLASSLESS_RELEASE" releaseDate settlementDate 150.0) with resulting_security_ids = [])] edits = [] deletes = [] @@ -2155,7 +2270,7 @@ testClasslessEquityCompensationReleaseRejectsAmbiguousPlanClass = script do let settlementDate = DT.time (DA.date 2024 Jan 03) 0 0 0 submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationRelease (equityCompensationRelease "TX_CLASSLESS_RELEASE_AMBIGUOUS" "ECSEC_CLASSLESS_RELEASE_AMBIGUOUS" releaseDate settlementDate 50.0)] + creates = [CT.OcfCreateEquityCompensationRelease ((equityCompensationRelease "TX_CLASSLESS_RELEASE_AMBIGUOUS" "ECSEC_CLASSLESS_RELEASE_AMBIGUOUS" releaseDate settlementDate 50.0) with resulting_security_ids = [])] edits = [] deletes = [] @@ -2172,7 +2287,7 @@ testNegativeEquityCompensationExerciseCannotOffsetIssuedSharesForCeiling = scrip submitMustFail issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationExercise (equityCompensationExercise "TX_EQUITY_COMP_NEGATIVE_EXERCISE" "ECSEC_NEGATIVE" (-50.0))] + creates = [CT.OcfCreateEquityCompensationExercise ((equityCompensationExercise "TX_EQUITY_COMP_NEGATIVE_EXERCISE" "ECSEC_NEGATIVE" (-50.0)) with resulting_security_ids = [])] edits = [] deletes = [] @@ -2237,6 +2352,9 @@ testEquityCompensationReleaseUsesSettlementDateForCeiling = script do creates = [ CT.OcfCreateStockClassAuthorizedSharesAdjustment ((stockClassAuthorizedSharesAdjustment "TX_EQUITY_COMP_RELEASE_AUTH" stockClassId 150.0) with date = settlementDate) , CT.OcfCreateEquityCompensationRelease (equityCompensationRelease "TX_EQUITY_COMP_RELEASE" "ECSEC_RELEASE_DATE" releaseDate settlementDate 150.0) + , CT.OcfCreateStockIssuance ((defaultStockIssuance "TX_EQUITY_COMP_RELEASE_RESULT" "TX_EQUITY_COMP_RELEASE_RESULT" stakeholderId stockClassId) with + date = settlementDate + quantity = 150.0) ] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestStockClassConversionRightReferences.daml b/Test/daml/OpenCapTable/TestStockClassConversionRightReferences.daml new file mode 100644 index 00000000..b735d713 --- /dev/null +++ b/Test/daml/OpenCapTable/TestStockClassConversionRightReferences.daml @@ -0,0 +1,130 @@ +module OpenCapTable.TestStockClassConversionRightReferences where + +import Fairmint.OpenCapTable.OCF.StockClass +import Fairmint.OpenCapTable.Types.Conversion +import Fairmint.OpenCapTable.Types.Stock +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +stockClassConversionRightTo : Text -> OcfStockClassConversionRight +stockClassConversionRightTo stockClassId = OcfStockClassConversionRight with + ceiling_price_per_share = None + conversion_mechanism = OcfConversionMechanismRatioConversion + conversion_price = None + conversion_trigger = ocfTriggerElectiveAtWill + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + custom_description = None + discount_rate = None + expires_at = None + floor_price_per_share = None + percent_of_capitalization = None + ratio = Some (OcfRatio with numerator = 1.0, denominator = 1.0) + reference_share_price = None + reference_valuation_price_per_share = None + type_ = "STOCK_CLASS_CONVERSION_RIGHT" + valuation_cap = None + +stockClassConversionRightToWithNestedWarrantTarget : Text -> Optional Text -> OcfStockClassConversionRight +stockClassConversionRightToWithNestedWarrantTarget stockClassId nestedStockClassId = + (stockClassConversionRightTo stockClassId) with + conversion_trigger = OcfConversionTrigger with + conversion_right = OcfRightWarrant (OcfWarrantConversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with converts_to_quantity = 100.0 + converts_to_future_round = None + converts_to_stock_class_id = nestedStockClassId + type_ = "WARRANT_CONVERSION_RIGHT") + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-NESTED-WARRANT" + type_ = OcfTriggerTypeTypeElectiveAtWill + +preferredStockClassWithConversionRight : Text -> Text -> StockClassOcfData +preferredStockClassWithConversionRight stockClassId targetStockClassId = + (defaultStockClass stockClassId) with + name = "Preferred Stock" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PS-" + seniority = 2.0 + conversion_rights = [stockClassConversionRightTo targetStockClassId] + +preferredStockClassWithNestedConversionRight : Text -> Text -> Optional Text -> StockClassOcfData +preferredStockClassWithNestedConversionRight stockClassId targetStockClassId nestedTargetStockClassId = + (defaultStockClass stockClassId) with + name = "Preferred Stock" + class_type = OcfStockClassTypePreferred + default_id_prefix = "PS-" + seniority = 2.0 + conversion_rights = [stockClassConversionRightToWithNestedWarrantTarget targetStockClassId nestedTargetStockClassId] + +testStockClassConversionRightTargetCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + _ <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (preferredStockClassWithConversionRight "SC_PREFERRED_CONVERTS" "SC_COMMON_TARGET") + , CT.OcfCreateStockClass (defaultStockClass "SC_COMMON_TARGET") + ] + edits = [] + deletes = [] + pure () + +testStockClassConversionRightMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateStockClass (preferredStockClassWithConversionRight "SC_PREFERRED_MISSING_TARGET" "SC_MISSING_TARGET")] + edits = [] + deletes = [] + +testStockClassConversionRightNestedMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (preferredStockClassWithNestedConversionRight "SC_PREFERRED_NESTED_MISSING" "SC_COMMON_OUTER_TARGET" (Some "SC_MISSING_NESTED_TARGET")) + , CT.OcfCreateStockClass (defaultStockClass "SC_COMMON_OUTER_TARGET") + ] + edits = [] + deletes = [] + +testStockClassConversionRightEditMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateStockClass (defaultStockClass "SC_PREFERRED_EDIT_TARGET")] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockClass (preferredStockClassWithConversionRight "SC_PREFERRED_EDIT_TARGET" "SC_MISSING_EDIT_TARGET")] + deletes = [] + +testStockClassConversionRightTargetDeleteCannotLeaveDanglingReference = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (preferredStockClassWithConversionRight "SC_PREFERRED_DANGLING_TARGET" "SC_COMMON_DELETE_TARGET") + , CT.OcfCreateStockClass (defaultStockClass "SC_COMMON_DELETE_TARGET") + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass "SC_COMMON_DELETE_TARGET"] diff --git a/Test/daml/OpenCapTable/TestStockConsolidation.daml b/Test/daml/OpenCapTable/TestStockConsolidation.daml index dfce1464..2f707d06 100644 --- a/Test/daml/OpenCapTable/TestStockConsolidation.daml +++ b/Test/daml/OpenCapTable/TestStockConsolidation.daml @@ -5,7 +5,7 @@ import Fairmint.OpenCapTable.OCF.StockConsolidation import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites) +import OpenCapTable.TestHelpers (addPrerequisites, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -48,7 +48,8 @@ testStockConsolidation_TwoSecurities = script do result2 <- createStockIssuance issuer result.updatedCapTableCid "SSEC-001B" 25000.0 _ <- submit issuer do exerciseCmd result2.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockConsolidation StockConsolidationOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-CONSOLIDATION-1" "SSEC-001-CONSOLIDATED" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Apr 15) 0 0 0) 50000.0 + , CT.OcfCreateStockConsolidation StockConsolidationOcfData with id = "TX_STOCK_CONSOLIDATION_1" date = DT.time (DA.date 2025 Apr 15) 0 0 0 security_ids = ["SSEC-001A", "SSEC-001B"] @@ -86,7 +87,8 @@ testStockConsolidation_MultipleSecurities = script do r4 <- createStockIssuance issuer r3.updatedCapTableCid "SSEC-002D" 5000.0 _ <- submit issuer do exerciseCmd r4.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockConsolidation StockConsolidationOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-CONSOLIDATION-2" "SSEC-002-CONSOLIDATED" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Jun 30) 0 0 0) 50000.0 + , CT.OcfCreateStockConsolidation StockConsolidationOcfData with id = "TX_STOCK_CONSOLIDATION_2" date = DT.time (DA.date 2025 Jun 30) 0 0 0 security_ids = ["SSEC-002A", "SSEC-002B", "SSEC-002C", "SSEC-002D"] @@ -105,7 +107,8 @@ testStockConsolidation_MinimalFields = script do r2 <- createStockIssuance issuer r1.updatedCapTableCid "SSEC-003B" 70000.0 _ <- submit issuer do exerciseCmd r2.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockConsolidation StockConsolidationOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-CONSOLIDATION-3" "SSEC-003-CONSOLIDATED" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Aug 01) 0 0 0) 100000.0 + , CT.OcfCreateStockConsolidation StockConsolidationOcfData with id = "TX_STOCK_CONSOLIDATION_3" date = DT.time (DA.date 2025 Aug 01) 0 0 0 security_ids = ["SSEC-003A", "SSEC-003B"] diff --git a/Test/daml/OpenCapTable/TestStockConversion.daml b/Test/daml/OpenCapTable/TestStockConversion.daml index 98dcca7b..7ecb90d7 100644 --- a/Test/daml/OpenCapTable/TestStockConversion.daml +++ b/Test/daml/OpenCapTable/TestStockConversion.daml @@ -8,7 +8,7 @@ import Fairmint.OpenCapTable.Types.Stock (OcfRatio(..), OcfStockClassType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites, defaultStockClass, defaultStockIssuance) +import OpenCapTable.TestHelpers (addPrerequisites, defaultStockClass, defaultStockIssuance, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -87,14 +87,17 @@ testStockConversion_PreferredToCommon = script do result <- createStockIssuance issuer capTableCid securityId 100000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion StockConversionOcfData with - id = "TX_STOCK_CONVERSION_1" - date = DT.time (DA.date 2025 Dec 15) 0 0 0 - security_id = securityId - quantity_converted = 100000.0 - resulting_security_ids = ["SSEC-COMMON-001"] - comments = ["Automatic conversion of Series A Preferred to Common upon IPO"] - balance_security_id = None] + creates = + [ CT.OcfCreateStockConversion StockConversionOcfData with + id = "TX_STOCK_CONVERSION_1" + date = DT.time (DA.date 2025 Dec 15) 0 0 0 + security_id = securityId + quantity_converted = 100000.0 + resulting_security_ids = ["SSEC-COMMON-001"] + comments = ["Automatic conversion of Series A Preferred to Common upon IPO"] + balance_security_id = None + , stockSuccessorIssuance "TX_STOCK_CONVERSION_1_RESULT" "SSEC-COMMON-001" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Dec 15) 0 0 0) 100000.0 + ] edits = [] deletes = [] pure () @@ -122,6 +125,7 @@ testStockConversion_PartialConversion = script do 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 = [] @@ -136,14 +140,18 @@ testStockConversion_MultipleResulting = script do result <- createStockIssuance issuer capTableCid securityId 200000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockConversion StockConversionOcfData with - id = "TX_STOCK_CONVERSION_3" - date = DT.time (DA.date 2025 Oct 15) 0 0 0 - security_id = securityId - quantity_converted = 200000.0 - resulting_security_ids = ["SSEC-COMMON-003A", "SSEC-COMMON-003B"] - comments = [] - balance_security_id = None] + creates = + [ CT.OcfCreateStockConversion StockConversionOcfData with + id = "TX_STOCK_CONVERSION_3" + date = DT.time (DA.date 2025 Oct 15) 0 0 0 + security_id = securityId + quantity_converted = 200000.0 + resulting_security_ids = ["SSEC-COMMON-003A", "SSEC-COMMON-003B"] + comments = [] + balance_security_id = None + , stockSuccessorIssuance "TX_STOCK_CONVERSION_3_RESULT_A" "SSEC-COMMON-003A" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Oct 15) 0 0 0) 100000.0 + , stockSuccessorIssuance "TX_STOCK_CONVERSION_3_RESULT_B" "SSEC-COMMON-003B" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Oct 15) 0 0 0) 100000.0 + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestStockIssuance.daml b/Test/daml/OpenCapTable/TestStockIssuance.daml index 4779e0af..d09b2824 100644 --- a/Test/daml/OpenCapTable/TestStockIssuance.daml +++ b/Test/daml/OpenCapTable/TestStockIssuance.daml @@ -49,8 +49,18 @@ testStockIssuance_OptionalsSome = script do -- Setup prerequisites with specific stakeholder ID (cap_table', classId) <- setupStockClass issuer cap_table (cap_table'', stakeholderId) <- setupStakeholderWithId issuer cap_table' "SH-3" "Test Holder" + referenceResult <- submit issuer do + exerciseCmd cap_table'' CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockLegendTemplate (defaultStockLegendTemplate "L1") + , CT.OcfCreateStockLegendTemplate (defaultStockLegendTemplate "L2") + , CT.OcfCreateStockPlan (defaultStockPlan "PLAN_1" classId) + , CT.OcfCreateVestingTerms (defaultVestingTerms "VT_1") + ] + edits = [] + deletes = [] - -- Keep stock_plan_id empty here; plan-backed stock issuance is covered by stock-plan tests. + -- Exercise the optional stock_plan_id reference with the plan created above. let issuance = (defaultStockIssuance "TX_STOCK_ISS_SOME" "SEC-3" stakeholderId classId) with custom_id = "CERT-3" date = DT.time (DA.date 2024 Feb 01) 0 0 0 @@ -58,7 +68,7 @@ testStockIssuance_OptionalsSome = script do stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 16) 0 0 0) consideration_text = Some "Cash consideration" security_law_exemptions = [OcfSecurityExemption with description = "Rule 506(b)", jurisdiction = "US-CA"] - stock_plan_id = None + stock_plan_id = Some "PLAN_1" share_numbers_issued = [OcfShareNumberRange with starting_share_number = 1.0, ending_share_number = 10.0] share_price = OcfMonetary with amount = 2.5, currency = "USD" quantity = 10.0 @@ -69,5 +79,5 @@ testStockIssuance_OptionalsSome = script do issuance_type = Some OcfStockIssuanceRSA comments = ["Initial issuance"] - _ <- createStockIssuanceWith issuer cap_table'' issuance + _ <- createStockIssuanceWith issuer referenceResult.updatedCapTableCid issuance pure () diff --git a/Test/daml/OpenCapTable/TestStockIssuanceReferences.daml b/Test/daml/OpenCapTable/TestStockIssuanceReferences.daml new file mode 100644 index 00000000..81799a02 --- /dev/null +++ b/Test/daml/OpenCapTable/TestStockIssuanceReferences.daml @@ -0,0 +1,217 @@ +module OpenCapTable.TestStockIssuanceReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.StockIssuance (StockIssuanceOcfData) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +data ReferencedStockIssuance = ReferencedStockIssuance with + cap_table_cid: ContractId CT.CapTable + issuance: StockIssuanceOcfData + stock_class_id: Text + stock_legend_id: Text + stock_plan_id: Text + vesting_terms_id: Text + +createReferencedStockIssuance : + Party -> + ContractId CT.CapTable -> + Script ReferencedStockIssuance +createReferencedStockIssuance issuer capTableCid = do + (capTableWithPrereqs, classId, stakeholderId) <- setupForStockIssuance issuer capTableCid + let legendId = "LEGEND_STOCK_REF" + let planId = "PLAN_STOCK_REF" + let vestingTermsId = "VT_STOCK_REF" + let issuance = (defaultStockIssuance "TX_STOCK_REF" "SEC-STOCK-REF" stakeholderId classId) with + stock_legend_ids = [legendId] + stock_plan_id = Some planId + vesting_terms_id = Some vestingTermsId + + result <- submit issuer do + exerciseCmd capTableWithPrereqs CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockLegendTemplate (defaultStockLegendTemplate legendId) + , CT.OcfCreateStockPlan (defaultStockPlan planId classId) + , CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId) + , CT.OcfCreateStockIssuance issuance + ] + edits = [] + deletes = [] + pure ReferencedStockIssuance with + cap_table_cid = result.updatedCapTableCid + issuance = issuance + stock_class_id = classId + stock_legend_id = legendId + stock_plan_id = planId + vesting_terms_id = vestingTermsId + +testStockIssuance_MissingLegendReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = (defaultStockIssuance "TX_MISSING_LEGEND" "SEC-MISSING-LEGEND" stakeholderId classId) with + stock_legend_ids = ["LEGEND_MISSING"] + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance issuance] + edits = [] + deletes = [] + +testStockIssuance_MissingStockPlanReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = (defaultStockIssuance "TX_MISSING_PLAN" "SEC-MISSING-PLAN" stakeholderId classId) with + stock_plan_id = Some "PLAN_MISSING" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance issuance] + edits = [] + deletes = [] + +testStockIssuance_StockPlanMustIncludeIssuanceClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let otherClassId = "SC_OTHER_PLAN_CLASS" + let planId = "PLAN_OTHER_CLASS" + let issuance = (defaultStockIssuance "TX_PLAN_WRONG_CLASS" "SEC-PLAN-WRONG-CLASS" stakeholderId classId) with + stock_plan_id = Some planId + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass otherClassId) + , CT.OcfCreateStockPlan (defaultStockPlan planId otherClassId) + , CT.OcfCreateStockIssuance issuance + ] + edits = [] + deletes = [] + +testStockIssuance_MissingVestingTermsReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = (defaultStockIssuance "TX_MISSING_VESTING" "SEC-MISSING-VESTING" stakeholderId classId) with + vesting_terms_id = Some "VT_MISSING" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance issuance] + edits = [] + deletes = [] + +testStockIssuance_ReferencesCreatedInSameBatchSucceed = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = (defaultStockIssuance "TX_REFERENCES_VALID" "SEC-REFERENCES-VALID" stakeholderId classId) with + stock_legend_ids = ["LEGEND_VALID"] + stock_plan_id = Some "PLAN_VALID" + vesting_terms_id = Some "VT_VALID" + + _ <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockLegendTemplate (defaultStockLegendTemplate "LEGEND_VALID") + , CT.OcfCreateStockPlan (defaultStockPlan "PLAN_VALID" classId) + , CT.OcfCreateVestingTerms (defaultVestingTerms "VT_VALID") + , CT.OcfCreateStockIssuance issuance + ] + edits = [] + deletes = [] + pure () + +testStockIssuance_EditMissingReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = defaultStockIssuance "TX_EDIT_REFERENCE" "SEC-EDIT-REFERENCE" stakeholderId classId + capTableCid' <- createStockIssuanceWith issuer capTableCid issuance + let editedIssuance = issuance with stock_plan_id = Some "PLAN_MISSING_ON_EDIT" + + submitMustFail issuer do + exerciseCmd capTableCid' CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockIssuance editedIssuance] + deletes = [] + +testStockIssuance_EditStockPlanMustIncludeIssuanceClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = defaultStockIssuance "TX_EDIT_PLAN_WRONG_CLASS" "SEC-EDIT-PLAN-WRONG-CLASS" stakeholderId classId + capTableWithIssuance <- createStockIssuanceWith issuer capTableCid issuance + let otherClassId = "SC_EDIT_OTHER_PLAN_CLASS" + let planId = "PLAN_EDIT_OTHER_CLASS" + let editedIssuance = issuance with stock_plan_id = Some planId + + submitMustFail issuer do + exerciseCmd capTableWithIssuance CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass otherClassId) + , CT.OcfCreateStockPlan (defaultStockPlan planId otherClassId) + ] + edits = [CT.OcfEditStockIssuance editedIssuance] + deletes = [] + +testStockIssuance_EditReferencesCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId, stakeholderId) <- setupForStockIssuance issuer cap_table + let issuance = defaultStockIssuance "TX_EDIT_BATCH_REFERENCE" "SEC-EDIT-BATCH-REFERENCE" stakeholderId classId + capTableWithIssuance <- createStockIssuanceWith issuer capTableCid issuance + + let legendId = "LEGEND_EDIT_BATCH" + let planId = "PLAN_EDIT_BATCH" + let vestingTermsId = "VT_EDIT_BATCH" + let editedIssuance = issuance with + stock_legend_ids = [legendId] + stock_plan_id = Some planId + vesting_terms_id = Some vestingTermsId + + _ <- submit issuer do + exerciseCmd capTableWithIssuance CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockLegendTemplate (defaultStockLegendTemplate legendId) + , CT.OcfCreateStockPlan (defaultStockPlan planId classId) + , CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId) + ] + edits = [CT.OcfEditStockIssuance editedIssuance] + deletes = [] + pure () + +testStockIssuance_DeleteReferencedStockClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + ref <- createReferencedStockIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd ref.cap_table_cid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass ref.stock_class_id] + +testStockIssuance_DeleteReferencedStockLegendFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + ref <- createReferencedStockIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd ref.cap_table_cid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockLegendTemplate ref.stock_legend_id] + +testStockIssuance_DeleteReferencedStockPlanFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + ref <- createReferencedStockIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd ref.cap_table_cid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockPlan ref.stock_plan_id] + +testStockIssuance_DeleteReferencedVestingTermsFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + ref <- createReferencedStockIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd ref.cap_table_cid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteVestingTerms ref.vesting_terms_id] diff --git a/Test/daml/OpenCapTable/TestStockPlanReferences.daml b/Test/daml/OpenCapTable/TestStockPlanReferences.daml new file mode 100644 index 00000000..177bf21e --- /dev/null +++ b/Test/daml/OpenCapTable/TestStockPlanReferences.daml @@ -0,0 +1,80 @@ +module OpenCapTable.TestStockPlanReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +testStockPlan_MissingStockClassReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateStockPlan (defaultStockPlan "PLAN_MISSING_CLASS" "SC_MISSING")] + edits = [] + deletes = [] + +testStockPlan_StockClassCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + _ <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockPlan (defaultStockPlan "PLAN_VALID" "SC_VALID") + , CT.OcfCreateStockClass (defaultStockClass "SC_VALID") + ] + edits = [] + deletes = [] + pure () + +testStockPlan_EditMissingStockClassReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId) <- setupStockClass issuer cap_table + let plan = defaultStockPlan "PLAN_EDIT_CLASS" classId + createResult <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockPlan plan] + edits = [] + deletes = [] + let editedPlan = plan with stock_class_ids = ["SC_MISSING_ON_EDIT"] + + submitMustFail issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockPlan editedPlan] + deletes = [] + +testStockPlan_EditStockClassCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId) <- setupStockClass issuer cap_table + let plan = defaultStockPlan "PLAN_EDIT_BATCH_CLASS" classId + createResult <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockPlan plan] + edits = [] + deletes = [] + + let newClassId = "SC_EDIT_BATCH" + let editedPlan = plan with stock_class_ids = [newClassId] + _ <- submit issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockClass (defaultStockClass newClassId)] + edits = [CT.OcfEditStockPlan editedPlan] + deletes = [] + pure () + +testStockPlan_DeleteReferencedStockClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, classId) <- setupStockClass issuer cap_table + let plan = defaultStockPlan "PLAN_DELETE_CLASS" classId + createResult <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockPlan plan] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass classId] diff --git a/Test/daml/OpenCapTable/TestStockReissuance.daml b/Test/daml/OpenCapTable/TestStockReissuance.daml index 50304f51..77a23781 100644 --- a/Test/daml/OpenCapTable/TestStockReissuance.daml +++ b/Test/daml/OpenCapTable/TestStockReissuance.daml @@ -5,7 +5,7 @@ import Fairmint.OpenCapTable.OCF.StockReissuance import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites) +import OpenCapTable.TestHelpers (addPrerequisites, stockClassSplit, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -47,7 +47,8 @@ testStockReissuance_CertificateReplacement = script do result <- createStockIssuance issuer capTableCid securityId 50000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-REISSUANCE-1" "SSEC-001-REISSUED" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Mar 15) 0 0 0) 50000.0 + , CT.OcfCreateStockReissuance StockReissuanceOcfData with id = "TX_STOCK_REISSUANCE_1" date = DT.time (DA.date 2025 Mar 15) 0 0 0 security_id = securityId @@ -65,9 +66,15 @@ testStockReissuance_StockSplit = script do capTableCid <- addPrerequisites issuer cap_table let securityId = "SSEC-002" result <- createStockIssuance issuer capTableCid securityId 25000.0 - _ <- submit issuer do + splitResult <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + creates = [CT.OcfCreateStockClassSplit (stockClassSplit "TX_STOCK_CLASS_SPLIT_1" "SC_COMMON" 2.0 1.0)] + edits = [] + deletes = [] + _ <- submit issuer do + exerciseCmd splitResult.updatedCapTableCid CT.UpdateCapTable with + creates = [stockSuccessorIssuance "STOCK-ISS-REISSUANCE-2" "SSEC-002-SPLIT" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Jul 01) 0 0 0) 50000.0 + , CT.OcfCreateStockReissuance StockReissuanceOcfData with id = "TX_STOCK_REISSUANCE_2" date = DT.time (DA.date 2025 Jul 01) 0 0 0 security_id = securityId @@ -87,7 +94,9 @@ testStockReissuance_MultipleResulting = script do result <- createStockIssuance issuer capTableCid securityId 100000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockReissuance StockReissuanceOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-REISSUANCE-3A" "SSEC-003-FREE-A" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Sep 01) 0 0 0) 50000.0 + , stockSuccessorIssuance "STOCK-ISS-REISSUANCE-3B" "SSEC-003-FREE-B" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Sep 01) 0 0 0) 50000.0 + , CT.OcfCreateStockReissuance StockReissuanceOcfData with id = "TX_STOCK_REISSUANCE_3" date = DT.time (DA.date 2025 Sep 01) 0 0 0 security_id = securityId diff --git a/Test/daml/OpenCapTable/TestStockRetraction.daml b/Test/daml/OpenCapTable/TestStockRetraction.daml index 0eb26a78..78be2584 100644 --- a/Test/daml/OpenCapTable/TestStockRetraction.daml +++ b/Test/daml/OpenCapTable/TestStockRetraction.daml @@ -57,7 +57,7 @@ createStockIssuance issuer capTableCid = vesting_terms_id = None vestings = [] cost_basis = None - stock_legend_ids = ["L1"] + stock_legend_ids = [] issuance_type = None comments = []] edits = [] diff --git a/Test/daml/OpenCapTable/TestStockTransfer.daml b/Test/daml/OpenCapTable/TestStockTransfer.daml index d8474d1e..a5a7b7b2 100644 --- a/Test/daml/OpenCapTable/TestStockTransfer.daml +++ b/Test/daml/OpenCapTable/TestStockTransfer.daml @@ -5,7 +5,7 @@ import Fairmint.OpenCapTable.OCF.StockTransfer import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites, defaultStockIssuance) +import OpenCapTable.TestHelpers (addPrerequisites, defaultStockIssuance, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -48,15 +48,19 @@ testStockTransfer_SplitTransfer = script do result <- createStockIssuance issuer capTableCid securityId 400000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockTransfer StockTransferOcfData with - id = "TX_STOCK_TRANSFER_1" - date = DT.time (DA.date 2025 Aug 05) 0 0 0 - quantity = 400000.0 - security_id = securityId - resulting_security_ids = ["SSEC-NEW-001", "SSEC-NEW-002"] - comments = [] - balance_security_id = None -- No remainder: transferring all 400,000 shares - consideration_text = None] + creates = + [ CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX_STOCK_TRANSFER_1" + date = DT.time (DA.date 2025 Aug 05) 0 0 0 + quantity = 400000.0 + security_id = securityId + resulting_security_ids = ["SSEC-NEW-001", "SSEC-NEW-002"] + comments = [] + balance_security_id = None -- No remainder: transferring all 400,000 shares + consideration_text = None + , stockSuccessorIssuance "TX_STOCK_TRANSFER_1_RESULT_A" "SSEC-NEW-001" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Aug 05) 0 0 0) 200000.0 + , stockSuccessorIssuance "TX_STOCK_TRANSFER_1_RESULT_B" "SSEC-NEW-002" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Aug 05) 0 0 0) 200000.0 + ] edits = [] deletes = [] pure () @@ -85,6 +89,7 @@ testStockTransfer_FounderTransferWithConsideration = script do 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 balanceIssuance + , stockSuccessorIssuance "TX_STOCK_TRANSFER_2_RESULT" "SSEC-TRANSFEREE-001" "SH-1" "SC_COMMON" transferDate 75.0 ] edits = [] deletes = [] @@ -98,15 +103,18 @@ testStockTransfer_FullTransfer = script do result <- createStockIssuance issuer capTableCid securityId 10000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateStockTransfer StockTransferOcfData with - id = "TX_STOCK_TRANSFER_FULL" - date = DT.time (DA.date 2025 Jan 15) 0 0 0 - quantity = 10000.0 - security_id = securityId - resulting_security_ids = ["SSEC-NEWOWNER-001"] - comments = [] - balance_security_id = None - consideration_text = Some "Secondary sale at $1.50 per share."] + creates = + [ CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TX_STOCK_TRANSFER_FULL" + date = DT.time (DA.date 2025 Jan 15) 0 0 0 + quantity = 10000.0 + security_id = securityId + resulting_security_ids = ["SSEC-NEWOWNER-001"] + comments = [] + balance_security_id = None + consideration_text = Some "Secondary sale at $1.50 per share." + , stockSuccessorIssuance "TX_STOCK_TRANSFER_FULL_RESULT" "SSEC-NEWOWNER-001" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Jan 15) 0 0 0) 10000.0 + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestValuationReferences.daml b/Test/daml/OpenCapTable/TestValuationReferences.daml new file mode 100644 index 00000000..6ecf66ef --- /dev/null +++ b/Test/daml/OpenCapTable/TestValuationReferences.daml @@ -0,0 +1,94 @@ +module OpenCapTable.TestValuationReferences where + +import Fairmint.OpenCapTable.OCF.Valuation +import Fairmint.OpenCapTable.Types.Monetary +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +valuationForStockClass : Text -> Text -> ValuationOcfData +valuationForStockClass valuationId stockClassId = ValuationOcfData with + id = valuationId + effective_date = defaultTestDate + price_per_share = OcfMonetary with amount = 1.0, currency = "USD" + stock_class_id = stockClassId + valuation_type = OcfValuationType409A + comments = [] + board_approval_date = None + provider = None + stockholder_approval_date = None + +testValuationStockClassCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + _ <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_VALUATION_TARGET") + , CT.OcfCreateValuation (valuationForStockClass "VAL_TARGET_REF" "SC_VALUATION_TARGET") + ] + edits = [] + deletes = [] + pure () + +testValuationMissingStockClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateValuation (valuationForStockClass "VAL_MISSING_TARGET" "SC_MISSING_VALUATION_TARGET")] + edits = [] + deletes = [] + +testValuationEditMissingStockClassFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_VALUATION_EDIT_TARGET") + , CT.OcfCreateValuation (valuationForStockClass "VAL_EDIT_TARGET" "SC_VALUATION_EDIT_TARGET") + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditValuation (valuationForStockClass "VAL_EDIT_TARGET" "SC_MISSING_VALUATION_EDIT_TARGET")] + deletes = [] + +testValuationEditStockClassCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_VALUATION_EDIT_INITIAL") + , CT.OcfCreateValuation (valuationForStockClass "VAL_EDIT_BATCH_TARGET" "SC_VALUATION_EDIT_INITIAL") + ] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockClass (defaultStockClass "SC_VALUATION_EDIT_BATCH_TARGET")] + edits = [CT.OcfEditValuation (valuationForStockClass "VAL_EDIT_BATCH_TARGET" "SC_VALUATION_EDIT_BATCH_TARGET")] + deletes = [] + pure () + +testValuationStockClassDeleteCannotLeaveDanglingReference = script do + TestOcp{issuer, cap_table} <- setupTestOcp + result <- submit issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_VALUATION_DELETE_TARGET") + , CT.OcfCreateValuation (valuationForStockClass "VAL_DANGLING_TARGET" "SC_VALUATION_DELETE_TARGET") + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass "SC_VALUATION_DELETE_TARGET"] diff --git a/Test/daml/OpenCapTable/TestVestingAcceleration.daml b/Test/daml/OpenCapTable/TestVestingAcceleration.daml index 177c424b..99aa5f77 100644 --- a/Test/daml/OpenCapTable/TestVestingAcceleration.daml +++ b/Test/daml/OpenCapTable/TestVestingAcceleration.daml @@ -27,7 +27,7 @@ createEquityCompensationIssuance issuer capTableCid securityId quantity = consideration_text = None security_law_exemptions = [] stock_plan_id = None - stock_class_id = Some "COMMON" + stock_class_id = None compensation_type = OcfCompensationTypeOptionISO quantity = quantity base_price = None @@ -36,7 +36,7 @@ createEquityCompensationIssuance issuer capTableCid securityId quantity = expiration_date = Some (DT.time (DA.date 2034 Jan 01) 0 0 0) termination_exercise_windows = [] vestings = [] - vesting_terms_id = Some "standard-4yr-1yr-cliff" + vesting_terms_id = None comments = []] edits = [] deletes = [] diff --git a/Test/daml/OpenCapTable/TestVestingEvent.daml b/Test/daml/OpenCapTable/TestVestingEvent.daml index f5cbcd49..00462c43 100644 --- a/Test/daml/OpenCapTable/TestVestingEvent.daml +++ b/Test/daml/OpenCapTable/TestVestingEvent.daml @@ -2,42 +2,47 @@ module OpenCapTable.TestVestingEvent where import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance import Fairmint.OpenCapTable.OCF.VestingEvent +import Fairmint.OpenCapTable.OCF.VestingTerms (OcfVestingTrigger(..)) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, defaultVestingTermsWithCondition) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script -- Create an equity compensation issuance with milestone-based vesting -createEquityCompensationIssuance issuer capTableCid securityId quantity = +createEquityCompensationIssuance issuer capTableCid securityId quantity vestingConditionId = + let vestingTermsId = "VT-" <> securityId in submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_ISS_MILESTONE" - date = DT.time (DA.date 2025 Jan 01) 0 0 0 - security_id = securityId - custom_id = "OPT-MILESTONE-001" - 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 "COMMON" - compensation_type = OcfCompensationTypeOptionISO - quantity = quantity - base_price = None - early_exercisable = Some False - exercise_price = Some (OcfMonetary with amount = 2.50, currency = "USD") - expiration_date = Some (DT.time (DA.date 2035 Jan 01) 0 0 0) - termination_exercise_windows = [] - vestings = [] - vesting_terms_id = Some "milestone-based" - comments = []] + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition vestingTermsId vestingConditionId OcfVestingEventTrigger) + , CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with + id = "TX_EC_ISS_MILESTONE" + date = DT.time (DA.date 2025 Jan 01) 0 0 0 + security_id = securityId + custom_id = "OPT-MILESTONE-001" + 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 = None + compensation_type = OcfCompensationTypeOptionISO + quantity = quantity + base_price = None + early_exercisable = Some False + exercise_price = Some (OcfMonetary with amount = 2.50, currency = "USD") + expiration_date = Some (DT.time (DA.date 2035 Jan 01) 0 0 0) + termination_exercise_windows = [] + vestings = [] + vesting_terms_id = Some vestingTermsId + comments = [] + ] edits = [] deletes = [] @@ -46,7 +51,7 @@ testVestingEvent_MilestoneAchieved = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ECSEC-MILESTONE-001" - result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 "revenue-milestone-1m" _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateVestingEvent VestingEventOcfData with @@ -64,7 +69,7 @@ testVestingEvent_ProductLaunchMilestone = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ECSEC-MILESTONE-002" - result <- createEquityCompensationIssuance issuer capTableCid securityId 50000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 50000.0 "product-launch-v2" _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateVestingEvent VestingEventOcfData with @@ -82,7 +87,7 @@ testVestingEvent_IPOTrigger = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ECSEC-MILESTONE-003" - result <- createEquityCompensationIssuance issuer capTableCid securityId 200000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 200000.0 "ipo-trigger" _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateVestingEvent VestingEventOcfData with diff --git a/Test/daml/OpenCapTable/TestVestingStart.daml b/Test/daml/OpenCapTable/TestVestingStart.daml index f4678b13..88b84de5 100644 --- a/Test/daml/OpenCapTable/TestVestingStart.daml +++ b/Test/daml/OpenCapTable/TestVestingStart.daml @@ -2,42 +2,47 @@ module OpenCapTable.TestVestingStart where import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance import Fairmint.OpenCapTable.OCF.VestingStart +import Fairmint.OpenCapTable.OCF.VestingTerms (OcfVestingTrigger(..)) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, defaultVestingTermsWithCondition) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT import Daml.Script -- Create an equity compensation issuance with vesting -createEquityCompensationIssuance issuer capTableCid securityId quantity = +createEquityCompensationIssuance issuer capTableCid securityId quantity vestingConditionId = + let vestingTermsId = "VT-" <> securityId in submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_ISS_1" - date = DT.time (DA.date 2025 Jul 01) 0 0 0 - security_id = securityId - custom_id = "OPT-001" - 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 "COMMON" - compensation_type = OcfCompensationTypeOptionISO - quantity = quantity - base_price = None - early_exercisable = Some False - exercise_price = Some (OcfMonetary with amount = 1.25, currency = "USD") - expiration_date = Some (DT.time (DA.date 2035 Jul 01) 0 0 0) - termination_exercise_windows = [] - vestings = [] - vesting_terms_id = Some "standard-4yr-1yr-cliff" - comments = []] + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition vestingTermsId vestingConditionId OcfVestingStartTrigger) + , CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with + id = "TX_EC_ISS_1" + date = DT.time (DA.date 2025 Jul 01) 0 0 0 + security_id = securityId + custom_id = "OPT-001" + 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 = None + compensation_type = OcfCompensationTypeOptionISO + quantity = quantity + base_price = None + early_exercisable = Some False + exercise_price = Some (OcfMonetary with amount = 1.25, currency = "USD") + expiration_date = Some (DT.time (DA.date 2035 Jul 01) 0 0 0) + termination_exercise_windows = [] + vestings = [] + vesting_terms_id = Some vestingTermsId + comments = [] + ] edits = [] deletes = [] @@ -47,7 +52,7 @@ testVestingStart_BasicStart = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ec7d8e9f-0a1b-2c3d-4e5f-6a7b8c9d0e1f" - result <- createEquityCompensationIssuance issuer capTableCid securityId 50000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 50000.0 "vesting-start" _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateVestingStart VestingStartOcfData with @@ -65,7 +70,7 @@ testVestingStart_WithComments = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ECSEC-002" - result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 + result <- createEquityCompensationIssuance issuer capTableCid securityId 100000.0 "vesting-commencement" _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with creates = [CT.OcfCreateVestingStart VestingStartOcfData with @@ -83,30 +88,34 @@ testVestingStart_RSUGrant = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "ECSEC-RSU-001" + let vestingTermsId = "VT-" <> securityId result <- submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with - id = "TX_EC_ISS_RSU" - date = DT.time (DA.date 2025 Sep 01) 0 0 0 - security_id = securityId - custom_id = "RSU-001" - 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 "COMMON" - compensation_type = OcfCompensationTypeRSU - quantity = 25000.0 - base_price = None - early_exercisable = None - exercise_price = None - expiration_date = None - termination_exercise_windows = [] - vestings = [] - vesting_terms_id = Some "quarterly-4yr" - comments = []] + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition vestingTermsId "rsu-vesting-start" OcfVestingStartTrigger) + , CT.OcfCreateEquityCompensationIssuance EquityCompensationIssuanceOcfData with + id = "TX_EC_ISS_RSU" + date = DT.time (DA.date 2025 Sep 01) 0 0 0 + security_id = securityId + custom_id = "RSU-001" + 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 = None + compensation_type = OcfCompensationTypeRSU + quantity = 25000.0 + base_price = None + early_exercisable = None + exercise_price = None + expiration_date = None + termination_exercise_windows = [] + vestings = [] + vesting_terms_id = Some vestingTermsId + comments = [] + ] edits = [] deletes = [] _ <- submit issuer do diff --git a/Test/daml/OpenCapTable/TestVestingTransactionConditionReferences.daml b/Test/daml/OpenCapTable/TestVestingTransactionConditionReferences.daml new file mode 100644 index 00000000..7cd8e18d --- /dev/null +++ b/Test/daml/OpenCapTable/TestVestingTransactionConditionReferences.daml @@ -0,0 +1,259 @@ +module OpenCapTable.TestVestingTransactionConditionReferences where + +import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance +import Fairmint.OpenCapTable.OCF.VestingEvent +import Fairmint.OpenCapTable.OCF.VestingStart +import Fairmint.OpenCapTable.OCF.VestingTerms +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers + ( addDefaultStakeholder + , defaultTestDate + , defaultVestingCondition + , defaultVestingTermsWithCondition + , defaultVestingTermsWithConditions + ) +import qualified DA.Date as DA +import DA.Date (Month(..)) +import qualified DA.Time as DT +import Daml.Script + +equityCompensationIssuance : Text -> Text -> Optional Text -> EquityCompensationIssuanceOcfData +equityCompensationIssuance issuanceId securityId vestingTermsId = + EquityCompensationIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "EC-" <> issuanceId + 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 = None + compensation_type = OcfCompensationTypeOptionISO + quantity = 100.0 + base_price = None + early_exercisable = Some False + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + expiration_date = Some (DT.time (DA.date 2034 Jan 01) 0 0 0) + termination_exercise_windows = [] + vestings = [] + vesting_terms_id = vestingTermsId + comments = [] + +vestingStart : Text -> Text -> Text -> VestingStartOcfData +vestingStart txId securityId conditionId = + VestingStartOcfData with + id = txId + date = defaultTestDate + security_id = securityId + vesting_condition_id = conditionId + comments = [] + +vestingEvent : Text -> Text -> Text -> VestingEventOcfData +vestingEvent txId securityId conditionId = + VestingEventOcfData with + id = txId + date = defaultTestDate + security_id = securityId + vesting_condition_id = conditionId + comments = [] + +createSecurityWithVestingTerms : + Party -> + ContractId CT.CapTable -> + Text -> + Text -> + VestingTermsOcfData -> + Script (ContractId CT.CapTable) +createSecurityWithVestingTerms issuer capTable issuanceId securityId vestingTermsData = do + result <- submit issuer do + exerciseCmd capTable CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms vestingTermsData + , CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance issuanceId securityId (Some vestingTermsData.id)) + ] + edits = [] + deletes = [] + pure result.updatedCapTableCid + +testVestingStartConditionReferenceSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-START-BATCH" + _ <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition "VT_START_BATCH" "start" OcfVestingStartTrigger) + , CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "EC_START_BATCH" securityId (Some "VT_START_BATCH")) + , CT.OcfCreateVestingStart (vestingStart "VS_START_BATCH" securityId "start") + ] + edits = [] + deletes = [] + pure () + +testVestingEventConditionReferenceSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-EVENT-BATCH" + _ <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition "VT_EVENT_BATCH" "event" OcfVestingEventTrigger) + , CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "EC_EVENT_BATCH" securityId (Some "VT_EVENT_BATCH")) + , CT.OcfCreateVestingEvent (vestingEvent "VE_EVENT_BATCH" securityId "event") + ] + edits = [] + deletes = [] + pure () + +testVestingStartMissingConditionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-START-MISSING" + capTableWithSecurity <- createSecurityWithVestingTerms + issuer + capTableCid + "EC_START_MISSING" + securityId + (defaultVestingTermsWithCondition "VT_START_MISSING" "start" OcfVestingStartTrigger) + submitMustFail issuer do + exerciseCmd capTableWithSecurity CT.UpdateCapTable with + creates = [CT.OcfCreateVestingStart (vestingStart "VS_START_MISSING" securityId "missing")] + edits = [] + deletes = [] + pure () + +testVestingEventMissingConditionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-EVENT-MISSING" + capTableWithSecurity <- createSecurityWithVestingTerms + issuer + capTableCid + "EC_EVENT_MISSING" + securityId + (defaultVestingTermsWithCondition "VT_EVENT_MISSING" "event" OcfVestingEventTrigger) + submitMustFail issuer do + exerciseCmd capTableWithSecurity CT.UpdateCapTable with + creates = [CT.OcfCreateVestingEvent (vestingEvent "VE_EVENT_MISSING" securityId "missing")] + edits = [] + deletes = [] + pure () + +testVestingStartWrongTriggerFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-START-WRONG-TRIGGER" + capTableWithSecurity <- createSecurityWithVestingTerms + issuer + capTableCid + "EC_START_WRONG_TRIGGER" + securityId + (defaultVestingTermsWithCondition "VT_START_WRONG_TRIGGER" "event" OcfVestingEventTrigger) + submitMustFail issuer do + exerciseCmd capTableWithSecurity CT.UpdateCapTable with + creates = [CT.OcfCreateVestingStart (vestingStart "VS_START_WRONG_TRIGGER" securityId "event")] + edits = [] + deletes = [] + pure () + +testVestingEventWrongTriggerFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-EVENT-WRONG-TRIGGER" + capTableWithSecurity <- createSecurityWithVestingTerms + issuer + capTableCid + "EC_EVENT_WRONG_TRIGGER" + securityId + (defaultVestingTermsWithCondition "VT_EVENT_WRONG_TRIGGER" "start" OcfVestingStartTrigger) + submitMustFail issuer do + exerciseCmd capTableWithSecurity CT.UpdateCapTable with + creates = [CT.OcfCreateVestingEvent (vestingEvent "VE_EVENT_WRONG_TRIGGER" securityId "start")] + edits = [] + deletes = [] + pure () + +testVestingTermsMissingNextConditionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms + (defaultVestingTermsWithConditions + "VT_MISSING_NEXT_CONDITION" + [defaultVestingCondition "start" OcfVestingStartTrigger ["missing-next"]]) + ] + edits = [] + deletes = [] + pure () + +testVestingTermsMissingRelativeConditionFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let relativeCondition = defaultVestingCondition + "relative" + (OcfVestingScheduleRelativeTrigger with + period = OcfVestingPeriodMonths with + length_ = 1 + occurrences = 4 + day_of_month = OcfVestingDay15 + cliff_installment = Some 1 + relative_to_condition_id = "missing-relative") + [] + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms + (defaultVestingTermsWithConditions + "VT_MISSING_RELATIVE_CONDITION" + [relativeCondition]) + ] + edits = [] + deletes = [] + pure () + +testVestingTransactionSecurityWithoutVestingTermsFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-NO-TERMS" + result <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "EC_NO_TERMS" securityId None)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingStart (vestingStart "VS_NO_TERMS" securityId "start")] + edits = [] + deletes = [] + pure () + +testVestingTermsEditCannotRemoveReferencedCondition = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addDefaultStakeholder issuer cap_table + let securityId = "SEC-VESTING-EDIT-REMOVES-CONDITION" + result <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTermsWithCondition "VT_EDIT_REFERENCED" "start" OcfVestingStartTrigger) + , CT.OcfCreateEquityCompensationIssuance (equityCompensationIssuance "EC_EDIT_REFERENCED" securityId (Some "VT_EDIT_REFERENCED")) + , CT.OcfCreateVestingStart (vestingStart "VS_EDIT_REFERENCED" securityId "start") + ] + edits = [] + deletes = [] + let editedTerms = defaultVestingTermsWithConditions + "VT_EDIT_REFERENCED" + [defaultVestingCondition "replacement-start" OcfVestingStartTrigger []] + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditVestingTerms editedTerms] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestWarrantCancellation.daml b/Test/daml/OpenCapTable/TestWarrantCancellation.daml index 1e79b783..079dba69 100644 --- a/Test/daml/OpenCapTable/TestWarrantCancellation.daml +++ b/Test/daml/OpenCapTable/TestWarrantCancellation.daml @@ -12,12 +12,12 @@ import qualified DA.Time as DT import Daml.Script -- Create a warrant issuance to cancel (follows real workflow) -createWarrantIssuance issuer capTableCid securityId quantity = +createWarrantIssuance issuer capTableCid securityId quantity issuanceDate = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with - id = "TX_WARRANT_ISS_1" - date = DT.time (DA.date 2023 Jun 01) 0 0 0 + id = "TX_WARRANT_ISS_" <> securityId + date = issuanceDate security_id = securityId custom_id = "W-1" stakeholder_id = "SH-1" @@ -57,7 +57,7 @@ testWarrantCancellation_CreateAndArchiveByIssuer = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-001" - result <- createWarrantIssuance issuer capTableCid securityId 50000.0 + result <- createWarrantIssuance issuer capTableCid securityId 50000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) _ <- createWarrantCancellation issuer result.updatedCapTableCid securityId 50000.0 pure () @@ -66,7 +66,7 @@ testWarrantCancellation_CreateAndArchiveBuiltIn = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-002" - result <- createWarrantIssuance issuer capTableCid securityId 100000.0 + result <- createWarrantIssuance issuer capTableCid securityId 100000.0 (DT.time (DA.date 2023 Jun 01) 0 0 0) _ <- createWarrantCancellation issuer result.updatedCapTableCid securityId 100000.0 pure () @@ -75,9 +75,10 @@ testWarrantCancellation_PartialWithOptionals = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-PARTIAL" - result <- createWarrantIssuance issuer capTableCid securityId 75000.0 + 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) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + 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 diff --git a/Test/daml/OpenCapTable/TestWarrantConversionRightReferences.daml b/Test/daml/OpenCapTable/TestWarrantConversionRightReferences.daml new file mode 100644 index 00000000..0c7fcd5c --- /dev/null +++ b/Test/daml/OpenCapTable/TestWarrantConversionRightReferences.daml @@ -0,0 +1,266 @@ +module OpenCapTable.TestWarrantConversionRightReferences where + +import Fairmint.OpenCapTable.OCF.WarrantIssuance +import Fairmint.OpenCapTable.Types.Conversion +import Fairmint.OpenCapTable.Types.Monetary +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +warrantConversionTriggerTo : Optional Text -> OcfConversionTrigger +warrantConversionTriggerTo stockClassId = OcfConversionTrigger with + conversion_right = OcfRightWarrant (OcfWarrantConversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with converts_to_quantity = 100.0 + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + type_ = "WARRANT_CONVERSION_RIGHT") + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-1" + type_ = OcfTriggerTypeTypeElectiveAtWill + +convertibleConversionTriggerTo : Optional Text -> OcfConversionTrigger +convertibleConversionTriggerTo stockClassId = OcfConversionTrigger with + conversion_right = OcfRightConvertible (OcfConvertibleConversionRight with + conversion_mechanism = OcfConvMechCustom (OcfCustomConversionMechanism with custom_conversion_description = "Custom") + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + type_ = "CONVERTIBLE_CONVERSION_RIGHT") + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-CONVERTIBLE" + type_ = OcfTriggerTypeTypeElectiveAtWill + +stockClassConversionTriggerTo : Text -> OcfConversionTrigger +stockClassConversionTriggerTo stockClassId = OcfConversionTrigger with + conversion_right = OcfRightStockClass (OcfStockClassConversionRight with + ceiling_price_per_share = None + conversion_mechanism = OcfConversionMechanismCustomConversion + conversion_price = None + conversion_trigger = warrantConversionTriggerTo None + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + custom_description = Some "Custom" + discount_rate = None + expires_at = None + floor_price_per_share = None + percent_of_capitalization = None + ratio = None + reference_share_price = None + reference_valuation_price_per_share = None + type_ = "STOCK_CLASS_CONVERSION_RIGHT" + valuation_cap = None) + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-STOCK-CLASS" + type_ = OcfTriggerTypeTypeElectiveAtWill + +stockClassConversionTriggerWithNestedWarrantTarget : Text -> Optional Text -> OcfConversionTrigger +stockClassConversionTriggerWithNestedWarrantTarget stockClassId nestedStockClassId = OcfConversionTrigger with + conversion_right = OcfRightStockClass (OcfStockClassConversionRight with + ceiling_price_per_share = None + conversion_mechanism = OcfConversionMechanismCustomConversion + conversion_price = None + conversion_trigger = warrantConversionTriggerTo nestedStockClassId + converts_to_future_round = None + converts_to_stock_class_id = stockClassId + custom_description = Some "Custom" + discount_rate = None + expires_at = None + floor_price_per_share = None + percent_of_capitalization = None + ratio = None + reference_share_price = None + reference_valuation_price_per_share = None + type_ = "STOCK_CLASS_CONVERSION_RIGHT" + valuation_cap = None) + end_date = None + nickname = None + start_date = None + trigger_condition = None + trigger_date = None + trigger_description = None + trigger_id = "TRIGGER-STOCK-CLASS-NESTED" + type_ = OcfTriggerTypeTypeElectiveAtWill + +warrantIssuanceWithExerciseTriggers : Text -> Text -> [OcfConversionTrigger] -> WarrantIssuanceOcfData +warrantIssuanceWithExerciseTriggers issuanceId securityId exerciseTriggers = WarrantIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "W-" <> issuanceId + stakeholder_id = defaultStakeholderId + board_approval_date = None + stockholder_approval_date = None + consideration_text = None + security_law_exemptions = [] + quantity = Some 100.0 + quantity_source = None + exercise_price = Some (OcfMonetary with amount = 1.0, currency = "USD") + purchase_price = OcfMonetary with amount = 0.0, currency = "USD" + exercise_triggers = exerciseTriggers + warrant_expiration_date = None + vesting_terms_id = None + vestings = [] + comments = [] + +warrantIssuanceWithConversionTarget : Text -> Text -> Optional Text -> WarrantIssuanceOcfData +warrantIssuanceWithConversionTarget issuanceId securityId stockClassId = + warrantIssuanceWithExerciseTriggers issuanceId securityId [warrantConversionTriggerTo stockClassId] + +testWarrantConversionRightTargetCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + _ <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_WARRANT_TARGET") + , CT.OcfCreateWarrantIssuance (warrantIssuanceWithConversionTarget "TX_WARRANT_TARGET_REF" "WARRANT-TARGET-REF" (Some "SC_WARRANT_TARGET")) + ] + edits = [] + deletes = [] + pure () + +testWarrantConversionRightDuplicateTriggerIdsFail = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + let trigger = warrantConversionTriggerTo None + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance + (warrantIssuanceWithExerciseTriggers + "TX_WARRANT_DUPLICATE_TRIGGER" + "WARRANT-DUPLICATE-TRIGGER" + [trigger, trigger]) + ] + edits = [] + deletes = [] + +testWarrantConversionRightDuplicateTriggerIdsEditFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + let trigger = warrantConversionTriggerTo None + let issuance = warrantIssuanceWithExerciseTriggers + "TX_WARRANT_DUPLICATE_TRIGGER_EDIT" + "WARRANT-DUPLICATE-TRIGGER-EDIT" + [trigger] + + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditWarrantIssuance (issuance with exercise_triggers = [trigger, trigger])] + deletes = [] + +testWarrantConversionRightMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance (warrantIssuanceWithConversionTarget "TX_WARRANT_MISSING_TARGET" "WARRANT-MISSING-TARGET" (Some "SC_MISSING_WARRANT_TARGET"))] + edits = [] + deletes = [] + +testWarrantConvertibleConversionRightMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance + (warrantIssuanceWithExerciseTriggers + "TX_WARRANT_CONVERTIBLE_MISSING_TARGET" + "WARRANT-CONVERTIBLE-MISSING-TARGET" + [convertibleConversionTriggerTo (Some "SC_MISSING_WARRANT_CONVERTIBLE_TARGET")]) + ] + edits = [] + deletes = [] + +testWarrantStockClassConversionRightMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateWarrantIssuance + (warrantIssuanceWithExerciseTriggers + "TX_WARRANT_STOCK_CLASS_MISSING_TARGET" + "WARRANT-STOCK-CLASS-MISSING-TARGET" + [stockClassConversionTriggerTo "SC_MISSING_WARRANT_STOCK_CLASS_TARGET"]) + ] + edits = [] + deletes = [] + +testWarrantStockClassConversionRightNestedMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_WARRANT_OUTER_TARGET") + , CT.OcfCreateWarrantIssuance + (warrantIssuanceWithExerciseTriggers + "TX_WARRANT_STOCK_CLASS_NESTED_MISSING_TARGET" + "WARRANT-STOCK-CLASS-NESTED-MISSING-TARGET" + [stockClassConversionTriggerWithNestedWarrantTarget "SC_WARRANT_OUTER_TARGET" (Some "SC_MISSING_WARRANT_NESTED_TARGET")]) + ] + edits = [] + deletes = [] + +testWarrantConversionRightEditMissingTargetFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance (warrantIssuanceWithConversionTarget "TX_WARRANT_EDIT_TARGET" "WARRANT-EDIT-TARGET" None)] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditWarrantIssuance (warrantIssuanceWithConversionTarget "TX_WARRANT_EDIT_TARGET" "WARRANT-EDIT-TARGET" (Some "SC_MISSING_WARRANT_EDIT_TARGET"))] + deletes = [] + +testWarrantConversionRightTargetDeleteCannotLeaveDanglingReference = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableWithStakeholder <- addDefaultStakeholder issuer cap_table + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClass (defaultStockClass "SC_WARRANT_DELETE_TARGET") + , CT.OcfCreateWarrantIssuance (warrantIssuanceWithConversionTarget "TX_WARRANT_DANGLING_TARGET" "WARRANT-DANGLING-TARGET" (Some "SC_WARRANT_DELETE_TARGET")) + ] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStockClass "SC_WARRANT_DELETE_TARGET"] diff --git a/Test/daml/OpenCapTable/TestWarrantExercise.daml b/Test/daml/OpenCapTable/TestWarrantExercise.daml index 809017b8..40e8664d 100644 --- a/Test/daml/OpenCapTable/TestWarrantExercise.daml +++ b/Test/daml/OpenCapTable/TestWarrantExercise.daml @@ -7,7 +7,7 @@ import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addPrerequisites) +import OpenCapTable.TestHelpers (addPrerequisites, stockSuccessorIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -65,7 +65,8 @@ testWarrantExercise_BasicExercise = script do result <- createWarrantIssuance issuer capTableCid securityId 10000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-WARRANT-EXERCISE-1" "SSEC-FROM-WARRANT-001" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Jun 15) 0 0 0) 10000.0 + , CT.OcfCreateWarrantExercise WarrantExerciseOcfData with id = "TX_WARRANT_EXERCISE_1" date = DT.time (DA.date 2025 Jun 15) 0 0 0 security_id = securityId @@ -86,7 +87,8 @@ testWarrantExercise_PartialWithComments = script do result <- createWarrantIssuance issuer capTableCid securityId 50000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-WARRANT-EXERCISE-2" "SSEC-FROM-WARRANT-002" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Aug 01) 0 0 0) 25000.0 + , CT.OcfCreateWarrantExercise WarrantExerciseOcfData with id = "TX_WARRANT_EXERCISE_2" date = DT.time (DA.date 2025 Aug 01) 0 0 0 security_id = securityId @@ -107,7 +109,8 @@ testWarrantExercise_MinimalFields = script do result <- createWarrantIssuance issuer capTableCid securityId 5000.0 _ <- submit issuer do exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + creates = [stockSuccessorIssuance "STOCK-ISS-WARRANT-EXERCISE-3" "SSEC-FROM-WARRANT-003" "SH-1" "SC_COMMON" (DT.time (DA.date 2025 Sep 30) 0 0 0) 5000.0 + , CT.OcfCreateWarrantExercise WarrantExerciseOcfData with id = "TX_WARRANT_EXERCISE_3" date = DT.time (DA.date 2025 Sep 30) 0 0 0 security_id = securityId diff --git a/Test/daml/OpenCapTable/TestWarrantIssuance.daml b/Test/daml/OpenCapTable/TestWarrantIssuance.daml index fea17c9d..19d9a8a3 100644 --- a/Test/daml/OpenCapTable/TestWarrantIssuance.daml +++ b/Test/daml/OpenCapTable/TestWarrantIssuance.daml @@ -6,7 +6,7 @@ import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType(..), OcfSecurity import Fairmint.OpenCapTable.Types.Vesting (OcfVesting(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup -import OpenCapTable.TestHelpers (addDefaultStakeholder) +import OpenCapTable.TestHelpers (addDefaultStakeholder, defaultVestingTerms) import qualified DA.Date as DA import DA.Date (Month(..)) import qualified DA.Time as DT @@ -57,25 +57,28 @@ testWarrantIssuance_OptionalsSome_ArraysNonEmpty = script do capTableCid <- addDefaultStakeholder issuer cap_table _ <- submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with - id = "TX_WARRANT_SOME" - date = DT.time (DA.date 2024 Feb 01) 0 0 0 - security_id = "WSEC-3" - custom_id = "W-3" - stakeholder_id = "SH-1" - board_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) - stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 20) 0 0 0) - consideration_text = Some "Cash" - security_law_exemptions = [ OcfSecurityExemption with description = "Reg D", jurisdiction = "US-DE" ] - quantity = Some 100.0 - quantity_source = Some OcfQuantityInstrumentFixed - exercise_price = Some (OcfMonetary with amount = 1.5, currency = "USD") - purchase_price = OcfMonetary with amount = 0.1, currency = "USD" - exercise_triggers = [ ocfTriggerElectiveAtWill ] - warrant_expiration_date = Some (DT.time (DA.date 2025 Jan 01) 0 0 0) - vesting_terms_id = Some "VT_W" - vestings = [ OcfVesting with date = DT.time (DA.date 2024 Mar 01) 0 0 0, amount = 10.0 ] - comments = ["Issued"]] + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTerms "VT_W") + , CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with + id = "TX_WARRANT_SOME" + date = DT.time (DA.date 2024 Feb 01) 0 0 0 + security_id = "WSEC-3" + custom_id = "W-3" + stakeholder_id = "SH-1" + board_approval_date = Some (DT.time (DA.date 2024 Jan 15) 0 0 0) + stockholder_approval_date = Some (DT.time (DA.date 2024 Jan 20) 0 0 0) + consideration_text = Some "Cash" + security_law_exemptions = [ OcfSecurityExemption with description = "Reg D", jurisdiction = "US-DE" ] + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + exercise_price = Some (OcfMonetary with amount = 1.5, currency = "USD") + purchase_price = OcfMonetary with amount = 0.1, currency = "USD" + exercise_triggers = [ ocfTriggerElectiveAtWill ] + warrant_expiration_date = Some (DT.time (DA.date 2025 Jan 01) 0 0 0) + vesting_terms_id = Some "VT_W" + vestings = [ OcfVesting with date = DT.time (DA.date 2024 Mar 01) 0 0 0, amount = 10.0 ] + comments = ["Issued"] + ] edits = [] deletes = [] pure () diff --git a/Test/daml/OpenCapTable/TestWarrantIssuanceReferences.daml b/Test/daml/OpenCapTable/TestWarrantIssuanceReferences.daml new file mode 100644 index 00000000..597686e2 --- /dev/null +++ b/Test/daml/OpenCapTable/TestWarrantIssuanceReferences.daml @@ -0,0 +1,141 @@ +module OpenCapTable.TestWarrantIssuanceReferences where + +import qualified Fairmint.OpenCapTable.CapTable as CT +import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData(..)) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType(..)) +import OpenCapTable.HelpersTriggers (ocfTriggerElectiveAtWill) +import OpenCapTable.Setup +import OpenCapTable.TestHelpers +import Daml.Script + +warrantIssuance : Text -> Text -> Text -> WarrantIssuanceOcfData +warrantIssuance issuanceId securityId stakeholderId = WarrantIssuanceOcfData with + id = issuanceId + date = defaultTestDate + security_id = securityId + custom_id = "W-" <> issuanceId + stakeholder_id = stakeholderId + board_approval_date = None + stockholder_approval_date = None + consideration_text = None + security_law_exemptions = [] + quantity = Some 100.0 + quantity_source = Some OcfQuantityUnspecified + 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 = [] + +createReferencedWarrantIssuance : + Party -> + ContractId CT.CapTable -> + Script (ContractId CT.CapTable, WarrantIssuanceOcfData, Text) +createReferencedWarrantIssuance issuer capTableCid = do + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer capTableCid "SH_WARRANT_REF" "Warrant Holder" + let vestingTermsId = "VT_WARRANT_REF" + let issuance = (warrantIssuance "TX_WARRANT_REF" "WARRANT-REF" stakeholderId) with + vesting_terms_id = Some vestingTermsId + + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = + [ CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId) + , CT.OcfCreateWarrantIssuance issuance + ] + edits = [] + deletes = [] + pure (result.updatedCapTableCid, issuance, vestingTermsId) + +testWarrantIssuance_MissingVestingTermsReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_WARRANT_MISSING_VESTING" "Warrant Holder" + let issuance = (warrantIssuance "TX_WARRANT_MISSING_VESTING" "WARRANT-MISSING-VESTING" stakeholderId) with + vesting_terms_id = Some "VT_MISSING_WARRANT" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + +testWarrantIssuance_VestingTermsCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + _ <- createReferencedWarrantIssuance issuer cap_table + pure () + +testWarrantIssuance_EditMissingVestingTermsReferenceFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, issuance, _) <- createReferencedWarrantIssuance issuer cap_table + let editedIssuance = issuance with vesting_terms_id = Some "VT_MISSING_WARRANT_ON_EDIT" + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditWarrantIssuance editedIssuance] + deletes = [] + +testWarrantIssuance_EditVestingTermsCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_WARRANT_EDIT_BATCH" "Warrant Holder" + let issuance = warrantIssuance "TX_WARRANT_EDIT_BATCH" "WARRANT-EDIT-BATCH" stakeholderId + createResult <- submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + + let vestingTermsId = "VT_WARRANT_EDIT_BATCH" + let editedIssuance = issuance with vesting_terms_id = Some vestingTermsId + _ <- submit issuer do + exerciseCmd createResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingTerms (defaultVestingTerms vestingTermsId)] + edits = [CT.OcfEditWarrantIssuance editedIssuance] + deletes = [] + pure () + +testWarrantIssuance_DeleteReferencedVestingTermsFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableCid, _, vestingTermsId) <- createReferencedWarrantIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteVestingTerms vestingTermsId] + +testWarrantIssuance_DeleteReferencedStakeholderFails = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_WARRANT_DELETE_STAKEHOLDER" "Warrant Holder" + let issuance = warrantIssuance "TX_WARRANT_DELETE_STAKEHOLDER" "WARRANT-DELETE-STAKEHOLDER" stakeholderId + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [CT.OcfDeleteStakeholder stakeholderId] + +testWarrantIssuance_EditStakeholderCreatedInSameBatchSucceeds = script do + TestOcp{issuer, cap_table} <- setupTestOcp + (capTableWithStakeholder, stakeholderId) <- setupStakeholderWithId issuer cap_table "SH_WARRANT_EDIT_OLD" "Warrant Holder" + let issuance = warrantIssuance "TX_WARRANT_EDIT_STAKEHOLDER_BATCH" "WARRANT-EDIT-STAKEHOLDER-BATCH" stakeholderId + result <- submit issuer do + exerciseCmd capTableWithStakeholder CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + + _ <- submit issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStakeholder (defaultStakeholder "SH_WARRANT_EDIT_NEW" "New Warrant Holder")] + edits = [CT.OcfEditWarrantIssuance (issuance with stakeholder_id = "SH_WARRANT_EDIT_NEW")] + deletes = [] + pure () diff --git a/Test/daml/OpenCapTable/TestWarrantTransfer.daml b/Test/daml/OpenCapTable/TestWarrantTransfer.daml index 579a669d..4112803d 100644 --- a/Test/daml/OpenCapTable/TestWarrantTransfer.daml +++ b/Test/daml/OpenCapTable/TestWarrantTransfer.daml @@ -6,19 +6,19 @@ 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, warrantSuccessorIssuance) 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 = +createWarrantIssuance issuer capTableCid securityId quantity issuanceDate = submit issuer do exerciseCmd capTableCid CT.UpdateCapTable with creates = [CT.OcfCreateWarrantIssuance WarrantIssuanceOcfData with - id = "TX_WARRANT_ISS_1" - date = DT.time (DA.date 2023 Jun 01) 0 0 0 + id = "TX_WARRANT_ISS_" <> securityId + date = issuanceDate security_id = securityId custom_id = "W-1" stakeholder_id = "SH-1" @@ -45,10 +45,11 @@ testWarrantTransfer_SecondarySale = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-001" - result <- createWarrantIssuance issuer capTableCid securityId 10000.0 + 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 = [CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + creates = [warrantSuccessorIssuance "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 quantity = 10000.0 @@ -66,10 +67,12 @@ testWarrantTransfer_PartialTransfer = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-PARTIAL" - result <- createWarrantIssuance issuer capTableCid securityId 50000.0 + 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) _ <- submit issuer do - exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with - creates = [CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + 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 + , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with id = "TX_WARRANT_TRANSFER_PARTIAL" date = DT.time (DA.date 2025 Jun 01) 0 0 0 quantity = 20000.0 @@ -87,10 +90,11 @@ testWarrantTransfer_FullTransferNoConsideration = script do TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp capTableCid <- addDefaultStakeholder issuer cap_table let securityId = "WSEC-FULL" - result <- createWarrantIssuance issuer capTableCid securityId 5000.0 + 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 = [CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + creates = [warrantSuccessorIssuance "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 quantity = 5000.0 diff --git a/dars/OpenCapTable-v34/0.0.6/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.6/OpenCapTable-v34.dar new file mode 100644 index 00000000..72131352 --- /dev/null +++ b/dars/OpenCapTable-v34/0.0.6/OpenCapTable-v34.dar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f321638da45f5ad87e3f91c8701eaf6ac2b846bcef7be1e8b9221144972e3997 +size 3070303 diff --git a/dars/dars.lock b/dars/dars.lock index b1346629..8351cbed 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -38,6 +38,13 @@ "sdkVersion": "3.4.10", "uploadedAt": "2026-07-11T02:46:51.000Z", "networks": [] + }, + "OpenCapTable-v34/0.0.6/OpenCapTable-v34.dar": { + "sha256": "f321638da45f5ad87e3f91c8701eaf6ac2b846bcef7be1e8b9221144972e3997", + "size": 3070303, + "sdkVersion": "3.4.10", + "uploadedAt": "2026-07-11T03:07:54Z", + "networks": [] } } } diff --git a/scripts/codegen/captable-config.yaml b/scripts/codegen/captable-config.yaml index 9ad67e1a..011f7744 100644 --- a/scripts/codegen/captable-config.yaml +++ b/scripts/codegen/captable-config.yaml @@ -6,8 +6,8 @@ # (e.g., create Stakeholder then StockIssuance referencing that stakeholder in same batch) # # Tier 1: Base objects with no OCF dependencies -# Tier 2: Objects that depend on Tier 1 (stock class adjustments, issuer adjustments, stakeholder events) -# Tier 3: Issuances (reference stakeholders and/or stock classes) +# Tier 2: Objects that depend on Tier 1 objects +# Tier 3: Issuances and stock plan adjustments # Tier 4: All other transactions (depend on issuances via security_id) tiers: 1: @@ -15,7 +15,6 @@ tiers: - Stakeholder - StockClass - StockLegendTemplate - - StockPlan - VestingTerms 2: - IssuerAuthorizedSharesAdjustment @@ -24,13 +23,14 @@ tiers: - StockClassAuthorizedSharesAdjustment - StockClassConversionRatioAdjustment - StockClassSplit - - StockPlanPoolAdjustment - - StockPlanReturnToPool + - StockPlan - Valuation 3: - ConvertibleIssuance - EquityCompensationIssuance - StockIssuance + - StockPlanPoolAdjustment + - StockPlanReturnToPool - WarrantIssuance 4: - ConvertibleAcceptance @@ -68,16 +68,24 @@ tiers: # Validation syntax: # - Simple: "field_id" - looks up field_id in auto-derived map (e.g., stakeholder_id -> stakeholders) # - Explicit: "field:map_name" - looks up field in specified map (e.g., security_id:stock_issuances_by_security_id) +# - Array: "field_ids[]:map_name" - validates every ID in the list, if any +# - Optional: "field_id?:map_name" - validates the referenced ID only when present # # Security ID validations use the type-specific index maps (*_by_security_id) to validate # that the security exists in the correct issuance type. This ensures type safety - a # StockTransfer can only reference a stock security, not a warrant security. validations: - # Issuances - validate stakeholder exists - StockIssuance: [stakeholder_id, stock_class_id] - EquityCompensationIssuance: [stakeholder_id] - ConvertibleIssuance: [stakeholder_id] - WarrantIssuance: [stakeholder_id] + # These reference-rich objects are validated against final CapTable maps so + # same-batch edits can point at objects created later in the batch. + StockPlan: [] + StockIssuance: [] + EquityCompensationIssuance: [] + Valuation: [] + + # These issuance types are validated against final CapTable maps so same-batch + # edits can point at stakeholders or other objects created later in the batch. + ConvertibleIssuance: [] + WarrantIssuance: [] # Stakeholder events - validate stakeholder exists StakeholderRelationshipChangeEvent: [stakeholder_id] StakeholderStatusChangeEvent: [stakeholder_id] @@ -88,41 +96,70 @@ validations: # Stock plan adjustments - validate stock plan exists StockPlanPoolAdjustment: [stock_plan_id] StockPlanReturnToPool: [stock_plan_id] - # Valuations - validate stock class exists - Valuation: [stock_class_id] - - # Stock transactions - validate security_id exists in stock_issuances_by_security_id - StockTransfer: [security_id:stock_issuances_by_security_id] - StockCancellation: [security_id:stock_issuances_by_security_id] + # Security lifecycle transactions validate source, balance, and resulting + # securities against their type-specific final issuance maps. + StockTransfer: + - security_id:stock_issuances_by_security_id + - balance_security_id?:stock_issuances_by_security_id + - 'resulting_security_ids[]:stock_issuances_by_security_id' + StockCancellation: [security_id:stock_issuances_by_security_id, 'balance_security_id?:stock_issuances_by_security_id'] StockAcceptance: [security_id:stock_issuances_by_security_id] StockRetraction: [security_id:stock_issuances_by_security_id] - StockConversion: [security_id:stock_issuances_by_security_id] - StockRepurchase: [security_id:stock_issuances_by_security_id] - StockReissuance: [security_id:stock_issuances_by_security_id] + StockConversion: + - security_id:stock_issuances_by_security_id + - balance_security_id?:stock_issuances_by_security_id + - 'resulting_security_ids[]:stock_issuances_by_security_id' + StockRepurchase: [security_id:stock_issuances_by_security_id, 'balance_security_id?:stock_issuances_by_security_id'] + StockReissuance: + [security_id:stock_issuances_by_security_id, 'resulting_security_ids[]:stock_issuances_by_security_id'] # StockConsolidation: uses security_ids (plural array) - validate each element - StockConsolidation: ['security_ids[]:stock_issuances_by_security_id'] + StockConsolidation: + ['security_ids[]:stock_issuances_by_security_id', 'resulting_security_id:stock_issuances_by_security_id'] # Warrant transactions - validate security_id exists in warrant_issuances_by_security_id - WarrantTransfer: [security_id:warrant_issuances_by_security_id] - WarrantCancellation: [security_id:warrant_issuances_by_security_id] + WarrantTransfer: + - security_id:warrant_issuances_by_security_id + - balance_security_id?:warrant_issuances_by_security_id + - 'resulting_security_ids[]:warrant_issuances_by_security_id' + WarrantCancellation: + [security_id:warrant_issuances_by_security_id, 'balance_security_id?:warrant_issuances_by_security_id'] WarrantAcceptance: [security_id:warrant_issuances_by_security_id] WarrantRetraction: [security_id:warrant_issuances_by_security_id] - WarrantExercise: [security_id:warrant_issuances_by_security_id] + WarrantExercise: + [security_id:warrant_issuances_by_security_id, 'resulting_security_ids[]:stock_issuances_by_security_id'] # Convertible transactions - validate security_id exists in convertible_issuances_by_security_id - ConvertibleTransfer: [security_id:convertible_issuances_by_security_id] - ConvertibleCancellation: [security_id:convertible_issuances_by_security_id] + ConvertibleTransfer: + - security_id:convertible_issuances_by_security_id + - balance_security_id?:convertible_issuances_by_security_id + - 'resulting_security_ids[]:convertible_issuances_by_security_id' + ConvertibleCancellation: + [security_id:convertible_issuances_by_security_id, 'balance_security_id?:convertible_issuances_by_security_id'] ConvertibleAcceptance: [security_id:convertible_issuances_by_security_id] ConvertibleRetraction: [security_id:convertible_issuances_by_security_id] - ConvertibleConversion: [security_id:convertible_issuances_by_security_id] + ConvertibleConversion: + - security_id:convertible_issuances_by_security_id + - balance_security_id?:convertible_issuances_by_security_id + - 'resulting_security_ids[]:stock_issuances_by_security_id' # Equity compensation transactions - validate security_id exists in equity_compensation_issuances_by_security_id - EquityCompensationTransfer: [security_id:equity_compensation_issuances_by_security_id] - EquityCompensationCancellation: [security_id:equity_compensation_issuances_by_security_id] + EquityCompensationTransfer: + - security_id:equity_compensation_issuances_by_security_id + - balance_security_id?:equity_compensation_issuances_by_security_id + - 'resulting_security_ids[]:equity_compensation_issuances_by_security_id' + EquityCompensationCancellation: + [ + security_id:equity_compensation_issuances_by_security_id, + 'balance_security_id?:equity_compensation_issuances_by_security_id', + ] EquityCompensationAcceptance: [security_id:equity_compensation_issuances_by_security_id] EquityCompensationRetraction: [security_id:equity_compensation_issuances_by_security_id] - EquityCompensationExercise: [security_id:equity_compensation_issuances_by_security_id] - EquityCompensationRelease: [security_id:equity_compensation_issuances_by_security_id] + EquityCompensationExercise: + - security_id:equity_compensation_issuances_by_security_id + - 'resulting_security_ids[]:stock_issuances_by_security_id' + EquityCompensationRelease: + - security_id:equity_compensation_issuances_by_security_id + - 'resulting_security_ids[]:stock_issuances_by_security_id' EquityCompensationRepricing: [security_id:equity_compensation_issuances_by_security_id] # Vesting transactions - validate security_id exists in any issuance type that supports vesting diff --git a/scripts/codegen/generate-captable.ts b/scripts/codegen/generate-captable.ts index ccd9d1b0..d807a8ef 100644 --- a/scripts/codegen/generate-captable.ts +++ b/scripts/codegen/generate-captable.ts @@ -33,6 +33,8 @@ interface Validation { thirdMap?: string; // For array field validation (validates each element in the array) is_array?: boolean; + // For Optional Text reference fields + is_optional?: boolean; } // Maps issuance type names to their security_id index map names @@ -43,6 +45,11 @@ const SECURITY_ID_INDEX_MAPS: Record = { WarrantIssuance: 'warrant_issuances_by_security_id', }; +const OBJECT_TYPE_CONSTRUCTOR_OVERRIDES: Record = { + StakeholderRelationshipChangeEvent: 'OcfObjCeStakeholderRelationship', + StakeholderStatusChangeEvent: 'OcfObjCeStakeholderStatus', +}; + interface TypeDef { name: string; module: string; @@ -51,6 +58,10 @@ interface TypeDef { map_field: string; tier: number; validations: Validation[]; + has_validations: boolean; + validates_issuer_id_reference: boolean; + is_document: boolean; + object_type_constructor: string; // For issuance types, the name of the security_id index map (e.g., 'stock_issuances_by_security_id') security_id_index_map: string | null; } @@ -96,6 +107,27 @@ function pluralize(str: string): string { return `${str}s`; } +function objectTypeConstructor(name: string): string { + if (OBJECT_TYPE_CONSTRUCTOR_OVERRIDES[name]) return OBJECT_TYPE_CONSTRUCTOR_OVERRIDES[name]; + if ( + /^(Convertible|EquityCompensation|Stock|Warrant).*(Acceptance|Cancellation|Conversion|Exercise|Issuance|Release|Repricing|Retraction|Transfer|Consolidation|Repurchase|Reissuance)$/.test( + name + ) || + name.startsWith('IssuerAuthorizedSharesAdjustment') || + name.startsWith('StockClassAuthorizedSharesAdjustment') || + name.startsWith('StockClassConversionRatioAdjustment') || + name.startsWith('StockClassSplit') || + name.startsWith('StockPlanPoolAdjustment') || + name.startsWith('StockPlanReturnToPool') || + name === 'VestingAcceleration' || + name === 'VestingEvent' || + name === 'VestingStart' + ) { + return `OcfObjTx${name}`; + } + return `OcfObj${name}`; +} + /** Parse a DAML file to find its main data type and field name */ function parseTypeInfo(filePath: string, typeName: string): { dataType: string; fieldName: string } | null { const content = fs.readFileSync(filePath, 'utf-8'); @@ -172,20 +204,27 @@ function discoverTypes(config: Config): TypeDef[] { map_field: pluralize(snakeName), tier, validations: validationFields.map((fieldSpec) => { - // Support four formats: + // Support five formats: // - Simple: "stakeholder_id" -> field: stakeholder_id, map: stakeholders (auto-derived) // - Explicit: "security_id:stock_issuances_by_security_id" -> field: security_id, map: stock_issuances_by_security_id // - OR-based: "security_id:map1|map2|map3" -> field: security_id, checks all three maps with OR logic // - Array: "security_ids[]:stock_issuances_by_security_id" -> field: security_ids, validates each element in array + // - Optional: "stock_plan_id?:stock_plans" -> field: stock_plan_id, validates only when Some if (fieldSpec.includes(':')) { const parts = fieldSpec.split(':'); let field = parts[0]; const mapSpec = parts[1]; + // Check for optional syntax (e.g., "stock_plan_id?") + const is_optional = field.endsWith('?'); + if (is_optional) { + field = field.slice(0, -1); // Remove ? suffix + } // Check for array syntax (e.g., "security_ids[]") const is_array = field.endsWith('[]'); if (is_array) { field = field.slice(0, -2); // Remove [] suffix } + const error = field.includes('security_id') ? 'Security not found' : `${toTitleCase(field)} not found`; // Check for OR-based multiple maps (e.g., "map1|map2|map3") if (mapSpec.includes('|')) { const maps = mapSpec.split('|'); @@ -198,19 +237,21 @@ function discoverTypes(config: Config): TypeDef[] { return { field, map: maps[0], // Default map (not used when or_maps is true) - error: `Security not found`, + error, or_maps: true, firstMap: maps[0], secondMap: maps[1], thirdMap: maps[2], is_array, + is_optional, }; } return { field, map: mapSpec, - error: `Security not found`, + error, is_array, + is_optional, }; } return { @@ -219,6 +260,10 @@ function discoverTypes(config: Config): TypeDef[] { error: `${toTitleCase(fieldSpec)} not found`, }; }), + has_validations: validationFields.length > 0, + validates_issuer_id_reference: name === 'IssuerAuthorizedSharesAdjustment', + is_document: name === 'Document', + object_type_constructor: objectTypeConstructor(name), security_id_index_map: SECURITY_ID_INDEX_MAPS[name] ?? null, }; diff --git a/scripts/codegen/templates/CapTable.daml.template b/scripts/codegen/templates/CapTable.daml.template index e6784c71..5476881f 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -18,15 +18,19 @@ import DA.Foldable (mapA_) import DA.Optional (fromOptional, isNone, isSome) import Fairmint.OpenCapTable.Types.Core (Context) -import Fairmint.OpenCapTable.Types.Conversion (OcfAnyConversionRight(..), OcfConversionMechanism(..), OcfConversionTrigger(..), OcfConversionTriggerType(..), OcfConvertibleConversionMechanism(..), OcfConvertibleConversionRight(..), OcfStockClassConversionRight(..), OcfWarrantConversionMechanism(..), OcfWarrantConversionRight(..)) +import Fairmint.OpenCapTable.Types.Conversion (OcfAnyConversionRight(..), OcfCapitalizationDefinition, OcfConversionMechanism(..), OcfConversionTrigger(..), OcfConversionTriggerType(..), OcfConvertibleConversionMechanism(..), OcfConvertibleConversionRight(..), OcfStockClassConversionRight(..), OcfWarrantConversionMechanism(..), OcfWarrantConversionRight(..)) import Fairmint.OpenCapTable.Types.Stock (OcfAuthorizedShares(..), OcfInitialSharesAuthorized(..), OcfRatio(..)) import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) 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(..)) +import qualified Fairmint.OpenCapTable.OCF.VestingTerms as VT -- OCF Types {{#each types_alpha}} +{{#unless is_document}} import {{module}} ({{name}}(..), {{data_type}}) +{{/unless}} {{/each}} @@ -2533,6 +2537,448 @@ validateIssuerAuthorizedShareAdjustmentReferences maps issuerCid = do (adjustment.adjustment_data.issuer_id == issuerContract.issuer_data.id)) (Map.values maps.issuer_authorized_shares_adjustments) +validateIssuerReference : ContractId Issuer -> Text -> Update () +validateIssuerReference issuerCid issuerId = do + issuerContract <- fetch issuerCid + assertMsg + ("Issuer ID mismatch: " <> issuerId <> " does not match cap table issuer " <> issuerContract.issuer_data.id) + (issuerId == issuerContract.issuer_data.id) + +validateObjectReference : CapTableMaps -> ContractId Issuer -> OcfObjectReference -> Update () +validateObjectReference maps issuerCid ref = case ref.object_type of + OcfObjIssuer -> do + validateIssuerReference issuerCid ref.object_id + OcfObjTxPlanSecurityAcceptance -> + assertMsg ("EquityCompensationAcceptance not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_acceptances)) + OcfObjTxPlanSecurityCancellation -> + assertMsg ("EquityCompensationCancellation not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_cancellations)) + OcfObjTxPlanSecurityExercise -> + assertMsg ("EquityCompensationExercise not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_exercises)) + OcfObjTxPlanSecurityIssuance -> + assertMsg ("EquityCompensationIssuance not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_issuances)) + OcfObjTxPlanSecurityRelease -> + assertMsg ("EquityCompensationRelease not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_releases)) + OcfObjTxPlanSecurityRetraction -> + assertMsg ("EquityCompensationRetraction not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_retractions)) + OcfObjTxPlanSecurityTransfer -> + assertMsg ("EquityCompensationTransfer not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.equity_compensation_transfers)) +{{#each types_alpha}} + {{object_type_constructor}} -> + assertMsg ("{{name}} not found: " <> ref.object_id) (isSome (Map.lookup ref.object_id maps.{{map_field}})) +{{/each}} + _ -> assertMsg ("Unsupported related object type " <> show ref.object_type <> " for object id " <> ref.object_id) False + +validateDocumentRelatedObjects : CapTableMaps -> ContractId Issuer -> Update () +validateDocumentRelatedObjects maps issuerCid = + mapA_ + (\documentCid -> do + document <- fetch documentCid + mapA_ (validateObjectReference maps issuerCid) document.document_data.related_objects) + (Map.values maps.documents) + +validateStockClassReference : Text -> CapTableMaps -> Text -> Update () +validateStockClassReference label maps stockClassId = + validateReference label maps.stock_classes stockClassId + +validateReference : Text -> Map Text a -> Text -> Update () +validateReference label refMap refId = + assertMsg + (label <> " not found: " <> refId) + (isSome (Map.lookup refId refMap)) + +hasDuplicateText : [Text] -> Bool +hasDuplicateText ids = case ids of + [] -> False + id :: rest -> id `elem` rest || hasDuplicateText rest + +validateUniqueTextList : Text -> [Text] -> Update () +validateUniqueTextList label ids = + assertMsg + (label <> " contains duplicate id") + (not (hasDuplicateText ids)) + +validateStakeholderReference : Text -> CapTableMaps -> Text -> Update () +validateStakeholderReference label maps stakeholderId = + validateReference label maps.stakeholders stakeholderId + +validateOptionalReference : Text -> Map Text a -> Optional Text -> Update () +validateOptionalReference label refMap refId = case refId of + Some id -> validateReference label refMap id + None -> pure () + +validateOptionalStockClassReference : Text -> CapTableMaps -> Optional Text -> Update () +validateOptionalStockClassReference label maps stockClassId = case stockClassId of + Some id -> validateStockClassReference label maps id + None -> pure () + +validateStockClassConversionRightReferences : CapTableMaps -> Update () +validateStockClassConversionRightReferences maps = + mapA_ + (\stockClassCid -> do + stockClass <- fetch stockClassCid + mapA_ + (\conversionRight -> validateAnyConversionRightStockClassReferences ("Stock class conversion right for " <> stockClass.stock_class_data.id) maps (OcfRightStockClass conversionRight)) + stockClass.stock_class_data.conversion_rights) + (Map.values maps.stock_classes) + +validateAnyConversionRightStockClassReferences : Text -> CapTableMaps -> OcfAnyConversionRight -> Update () +validateAnyConversionRightStockClassReferences label maps conversionRight = case conversionRight of + OcfRightConvertible convertibleRight -> + validateOptionalStockClassReference (label <> " convertible conversion target") maps convertibleRight.converts_to_stock_class_id + OcfRightWarrant warrantRight -> + validateOptionalStockClassReference (label <> " warrant conversion target") maps warrantRight.converts_to_stock_class_id + OcfRightStockClass stockClassRight -> do + validateStockClassReference (label <> " stock class conversion target") maps stockClassRight.converts_to_stock_class_id + validateAnyConversionRightStockClassReferences (label <> " stock class nested trigger") maps stockClassRight.conversion_trigger.conversion_right + +validateConvertibleConversionRightReferences : CapTableMaps -> Update () +validateConvertibleConversionRightReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateUniqueTextList + ("Convertible conversion triggers for " <> issuance.issuance_data.id) + (map (\trigger -> trigger.trigger_id) issuance.issuance_data.conversion_triggers) + mapA_ + (\trigger -> validateOptionalStockClassReference "Convertible conversion target" maps trigger.conversion_right.converts_to_stock_class_id) + issuance.issuance_data.conversion_triggers) + (Map.values maps.convertible_issuances) + +validateConvertibleIssuanceReferences : CapTableMaps -> Update () +validateConvertibleIssuanceReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateStakeholderReference "Convertible issuance stakeholder" maps issuance.issuance_data.stakeholder_id) + (Map.values maps.convertible_issuances) + +validateWarrantConversionRightReferences : CapTableMaps -> Update () +validateWarrantConversionRightReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateUniqueTextList + ("Warrant exercise triggers for " <> issuance.issuance_data.id) + (map (\trigger -> trigger.trigger_id) issuance.issuance_data.exercise_triggers) + mapA_ + (\trigger -> validateAnyConversionRightStockClassReferences ("Warrant exercise trigger for " <> issuance.issuance_data.id) maps trigger.conversion_right) + issuance.issuance_data.exercise_triggers) + (Map.values maps.warrant_issuances) + +validateValuationStockClassReferences : CapTableMaps -> Update () +validateValuationStockClassReferences maps = + mapA_ + (\valuationCid -> do + valuation <- fetch valuationCid + validateStockClassReference "Valuation stock class" maps valuation.valuation_data.stock_class_id) + (Map.values maps.valuations) + +validateStockPlanReferences : CapTableMaps -> Update () +validateStockPlanReferences maps = + mapA_ + (\stockPlanCid -> do + stockPlan <- fetch stockPlanCid + mapA_ + (validateStockClassReference ("Stock plan stock class for " <> stockPlan.plan_data.id) maps) + stockPlan.plan_data.stock_class_ids) + (Map.values maps.stock_plans) + +validateStockIssuanceReferences : CapTableMaps -> Update () +validateStockIssuanceReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateStakeholderReference "Stock issuance stakeholder" maps issuance.issuance_data.stakeholder_id + validateStockClassReference "Stock issuance stock class" maps issuance.issuance_data.stock_class_id + mapA_ + (validateReference "Stock issuance stock legend" maps.stock_legend_templates) + issuance.issuance_data.stock_legend_ids + validateOptionalReference "Stock issuance vesting terms" maps.vesting_terms issuance.issuance_data.vesting_terms_id) + (Map.values maps.stock_issuances) + +validateEquityCompensationIssuanceReferences : CapTableMaps -> Update () +validateEquityCompensationIssuanceReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateStakeholderReference "Equity compensation stakeholder" maps issuance.issuance_data.stakeholder_id + validateOptionalStockClassReference "Equity compensation stock class" maps issuance.issuance_data.stock_class_id + validateOptionalReference "Equity compensation stock plan" maps.stock_plans issuance.issuance_data.stock_plan_id + validateOptionalReference "Equity compensation vesting terms" maps.vesting_terms issuance.issuance_data.vesting_terms_id) + (Map.values maps.equity_compensation_issuances) + +validateWarrantIssuanceReferences : CapTableMaps -> Update () +validateWarrantIssuanceReferences maps = + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + validateStakeholderReference "Warrant issuance stakeholder" maps issuance.issuance_data.stakeholder_id + validateOptionalReference "Warrant vesting terms" maps.vesting_terms issuance.issuance_data.vesting_terms_id) + (Map.values maps.warrant_issuances) + +vestingTermsIdForSecurity : CapTableMaps -> Text -> Update (Optional Text) +vestingTermsIdForSecurity maps securityId = do + let stockCidOpt = Map.lookup securityId maps.stock_issuances_by_security_id + let warrantCidOpt = Map.lookup securityId maps.warrant_issuances_by_security_id + let equityCompensationCidOpt = Map.lookup securityId maps.equity_compensation_issuances_by_security_id + let stockMatch = if isSome stockCidOpt then 1 else 0 + let warrantMatch = if isSome warrantCidOpt then 1 else 0 + let equityCompensationMatch = if isSome equityCompensationCidOpt then 1 else 0 + assertMsg + ("Vesting transaction security id is ambiguous: " <> securityId) + (stockMatch + warrantMatch + equityCompensationMatch <= 1) + case (stockCidOpt, warrantCidOpt, equityCompensationCidOpt) of + (Some stockCid, None, None) -> do + issuance <- fetch stockCid + pure issuance.issuance_data.vesting_terms_id + (None, Some warrantCid, None) -> do + issuance <- fetch warrantCid + pure issuance.issuance_data.vesting_terms_id + (None, None, Some equityCompensationCid) -> do + issuance <- fetch equityCompensationCid + pure issuance.issuance_data.vesting_terms_id + (None, None, None) -> do + assertMsg ("Vesting transaction security not found: " <> securityId) False + pure None + _ -> do + assertMsg ("Vesting transaction security id is ambiguous: " <> securityId) False + pure None + +vestingTermsDataForSecurity : CapTableMaps -> Text -> Update (Optional VestingTermsOcfData) +vestingTermsDataForSecurity maps securityId = do + vestingTermsIdOpt <- vestingTermsIdForSecurity maps securityId + case vestingTermsIdOpt of + Some vestingTermsId -> case Map.lookup vestingTermsId maps.vesting_terms of + Some vestingTermsCid -> do + vestingTerms <- fetch vestingTermsCid + pure (Some vestingTerms.vesting_terms_data) + None -> do + assertMsg ("Vesting transaction references missing VestingTerms " <> vestingTermsId <> " for security " <> securityId) False + pure None + None -> do + assertMsg ("Vesting transaction security has no VestingTerms: " <> securityId) False + pure None + +vestingConditionMatchesExpectedTrigger : VT.OcfVestingTrigger -> VT.OcfVestingCondition -> Bool +vestingConditionMatchesExpectedTrigger expectedTrigger condition = + case (expectedTrigger, condition.trigger) of + (VT.OcfVestingStartTrigger, VT.OcfVestingStartTrigger) -> True + (VT.OcfVestingEventTrigger, VT.OcfVestingEventTrigger) -> True + _ -> False + +vestingConditionById : [VT.OcfVestingCondition] -> Text -> Optional VT.OcfVestingCondition +vestingConditionById conditions conditionId = case conditions of + [] -> None + condition :: rest -> + if condition.id == conditionId + then Some condition + else vestingConditionById rest conditionId + +validateVestingTransactionCondition : + Text -> + CapTableMaps -> + Text -> + Text -> + VT.OcfVestingTrigger -> + Update () +validateVestingTransactionCondition label maps securityId vestingConditionId expectedTrigger = do + vestingTermsDataOpt <- vestingTermsDataForSecurity maps securityId + case vestingTermsDataOpt of + Some vestingTermsData -> do + let conditionOpt = vestingConditionById vestingTermsData.vesting_conditions vestingConditionId + case conditionOpt of + Some condition -> + assertMsg + (label <> " references VestingCondition " <> vestingConditionId <> " with the wrong trigger type") + (vestingConditionMatchesExpectedTrigger expectedTrigger condition) + None -> + assertMsg + (label <> " references missing VestingCondition " <> vestingConditionId <> " for security " <> securityId) + False + None -> pure () + +validateVestingStartConditionReferences : CapTableMaps -> Update () +validateVestingStartConditionReferences maps = + mapA_ + (\vestingStartCid -> do + vestingStart <- fetch vestingStartCid + validateVestingTransactionCondition + "VestingStart" + maps + vestingStart.vesting_data.security_id + vestingStart.vesting_data.vesting_condition_id + VT.OcfVestingStartTrigger) + (Map.values maps.vesting_starts) + +validateVestingEventConditionReferences : CapTableMaps -> Update () +validateVestingEventConditionReferences maps = + mapA_ + (\vestingEventCid -> do + vestingEvent <- fetch vestingEventCid + validateVestingTransactionCondition + "VestingEvent" + maps + vestingEvent.vesting_data.security_id + vestingEvent.vesting_data.vesting_condition_id + VT.OcfVestingEventTrigger) + (Map.values maps.vesting_events) + +validateVestingTransactionConditionReferences : CapTableMaps -> Update () +validateVestingTransactionConditionReferences maps = do + validateVestingStartConditionReferences maps + validateVestingEventConditionReferences maps + +vestingConditionReferenceExists : Text -> [Text] -> Text -> Update () +vestingConditionReferenceExists label conditionIds conditionId = + assertMsg + (label <> " references missing VestingCondition: " <> conditionId) + (conditionId `elem` conditionIds) + +validateVestingConditionTriggerReferences : Text -> [Text] -> VT.OcfVestingCondition -> Update () +validateVestingConditionTriggerReferences label conditionIds condition = + case condition.trigger of + VT.OcfVestingScheduleRelativeTrigger with relative_to_condition_id -> + vestingConditionReferenceExists label conditionIds relative_to_condition_id + _ -> pure () + +validateVestingTermsConditionGraphReferences : CapTableMaps -> Update () +validateVestingTermsConditionGraphReferences maps = + mapA_ + (\vestingTermsCid -> do + vestingTerms <- fetch vestingTermsCid + let conditions = vestingTerms.vesting_terms_data.vesting_conditions + let conditionIds = map (\condition -> condition.id) conditions + validateUniqueTextList + ("VestingTerms conditions for " <> vestingTerms.vesting_terms_data.id) + conditionIds + mapA_ + (\condition -> do + validateVestingConditionTriggerReferences + ("VestingTerms " <> vestingTerms.vesting_terms_data.id <> " condition " <> condition.id) + conditionIds + condition + mapA_ + (vestingConditionReferenceExists ("VestingTerms " <> vestingTerms.vesting_terms_data.id <> " condition " <> condition.id) conditionIds) + condition.next_condition_ids) + conditions) + (Map.values maps.vesting_terms) + +validateSecurityReference : Text -> CapTableMaps -> Text -> Update () +validateSecurityReference label maps securityId = + assertMsg + (label <> " security not found: " <> securityId) + (isSome (Map.lookup securityId maps.convertible_issuances_by_security_id) + || isSome (Map.lookup securityId maps.equity_compensation_issuances_by_security_id) + || isSome (Map.lookup securityId maps.stock_issuances_by_security_id) + || isSome (Map.lookup securityId maps.warrant_issuances_by_security_id)) + +validateCapitalizationDefinitionReferences : Text -> CapTableMaps -> OcfCapitalizationDefinition -> Update () +validateCapitalizationDefinitionReferences label maps capitalizationDefinition = do + mapA_ + (validateSecurityReference (label <> " included") maps) + capitalizationDefinition.include_security_ids + mapA_ + (validateSecurityReference (label <> " excluded") maps) + capitalizationDefinition.exclude_security_ids + mapA_ + (validateStockClassReference (label <> " stock class") maps) + capitalizationDefinition.include_stock_class_ids + mapA_ + (validateReference (label <> " stock plan") maps.stock_plans) + capitalizationDefinition.include_stock_plans_ids + +validateConvertibleConversionCapitalizationReferences : CapTableMaps -> Update () +validateConvertibleConversionCapitalizationReferences maps = + mapA_ + (\conversionCid -> do + conversion <- fetch conversionCid + case conversion.conversion_data.capitalization_definition of + Some capitalizationDefinition -> + validateCapitalizationDefinitionReferences + ("Convertible conversion capitalization definition for " <> conversion.conversion_data.id) + maps + capitalizationDefinition + None -> pure ()) + (Map.values maps.convertible_conversions) + +validateConvertibleConversionTriggerReferences : CapTableMaps -> Update () +validateConvertibleConversionTriggerReferences maps = + mapA_ + (\conversionCid -> do + conversion <- fetch conversionCid + case Map.lookup conversion.conversion_data.security_id maps.convertible_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Convertible conversion trigger not found: " <> conversion.conversion_data.trigger_id) + (conversion.conversion_data.trigger_id `elem` map (\trigger -> trigger.trigger_id) issuance.issuance_data.conversion_triggers) + None -> assertMsg ("Security not found: " <> conversion.conversion_data.security_id) False) + (Map.values maps.convertible_conversions) + +validateWarrantExerciseTriggerReferences : CapTableMaps -> Update () +validateWarrantExerciseTriggerReferences maps = + mapA_ + (\exerciseCid -> do + exercise <- fetch exerciseCid + case Map.lookup exercise.exercise_data.security_id maps.warrant_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Warrant exercise trigger not found: " <> exercise.exercise_data.trigger_id) + (exercise.exercise_data.trigger_id `elem` map (\trigger -> trigger.trigger_id) issuance.issuance_data.exercise_triggers) + None -> assertMsg ("Security not found: " <> exercise.exercise_data.security_id) False) + (Map.values maps.warrant_exercises) + +validateStockReissuanceSplitReferences : CapTableMaps -> Update () +validateStockReissuanceSplitReferences maps = + mapA_ + (\reissuanceCid -> do + reissuance <- fetch reissuanceCid + validateOptionalReference "Stock reissuance split transaction" maps.stock_class_splits reissuance.reissuance_data.split_transaction_id) + (Map.values maps.stock_reissuances) + +{{#each types_alpha}} +{{#if has_validations}} +validate{{name}}ConfiguredReferences : CapTableMaps -> Update () +validate{{name}}ConfiguredReferences maps = + mapA_ + (\contractCid -> do + contract <- fetch contractCid +{{#each validations}} +{{#if is_optional}} +{{#if or_maps}} + case contract.{{../data_param}}.{{field}} of + Some refId -> assertMsg ("{{error}}: " <> refId) (isSome (Map.lookup refId maps.{{firstMap}}) || isSome (Map.lookup refId maps.{{secondMap}}) || isSome (Map.lookup refId maps.{{thirdMap}})) + None -> pure () +{{else}} + case contract.{{../data_param}}.{{field}} of + Some refId -> assertMsg ("{{error}}: " <> refId) (isSome (Map.lookup refId maps.{{map}})) + None -> pure () +{{/if}} +{{else}} +{{#if is_array}} + mapA_ (\refId -> assertMsg ("{{error}}: " <> refId) (isSome (Map.lookup refId maps.{{map}}))) contract.{{../data_param}}.{{field}} +{{else}} +{{#if or_maps}} + assertMsg ("{{error}}: " <> contract.{{../data_param}}.{{field}}) (isSome (Map.lookup contract.{{../data_param}}.{{field}} maps.{{firstMap}}) || isSome (Map.lookup contract.{{../data_param}}.{{field}} maps.{{secondMap}}) || isSome (Map.lookup contract.{{../data_param}}.{{field}} maps.{{thirdMap}})) +{{else}} + assertMsg ("{{error}}: " <> contract.{{../data_param}}.{{field}}) (isSome (Map.lookup contract.{{../data_param}}.{{field}} maps.{{map}})) +{{/if}} +{{/if}} +{{/if}} +{{/each}} ) + (Map.values maps.{{map_field}}) + +{{/if}} +{{/each}} +validateConfiguredReferences : CapTableMaps -> Update () +validateConfiguredReferences maps = do +{{#each types_alpha}} +{{#if has_validations}} + validate{{name}}ConfiguredReferences maps +{{/if}} +{{/each}} + addQuantityById : Text -> Decimal -> Map Text Decimal -> Map Text Decimal addQuantityById key quantity totals = let current = fromOptional 0.0 (Map.lookup key totals) @@ -4821,16 +5267,16 @@ getCreateTier createData = case createData of {{/each}} -- | Process a single create operation, returning updated maps and the created ContractId -processCreate : Context -> CapTableMaps -> OcfCreateData -> Update (CapTableMaps, OcfContractId) -processCreate ctx maps createData = case createData of +processCreate : Context -> ContractId Issuer -> CapTableMaps -> OcfCreateData -> Update (CapTableMaps, OcfContractId) +processCreate ctx issuerCid maps createData = case createData of {{#each types_tier}} {{> create-case}} {{/each}} -- | Process a single edit operation, returning updated maps and the edited ContractId. -- Note: OcfEditIssuer is handled separately in UpdateCapTable since issuer is not a map -processEdit : Context -> CapTableMaps -> OcfEditData -> Update (CapTableMaps, OcfContractId) -processEdit ctx maps editData = case editData of +processEdit : Context -> ContractId Issuer -> CapTableMaps -> OcfEditData -> Update (CapTableMaps, OcfContractId) +processEdit ctx issuerCid maps editData = case editData of OcfEditIssuer _ -> error "OcfEditIssuer should be handled separately in UpdateCapTable" {{#each types_alpha}} {{> edit-case}} @@ -4914,7 +5360,7 @@ template CapTable -- Process regular edits (map-based entities) (mapsAfterEdits, regularEditedCids) <- foldlA (\(maps, cids) editData -> do - (newMaps, cid) <- processEdit context maps editData + (newMaps, cid) <- processEdit context issuer maps editData pure (newMaps, cids ++ [cid])) (mapsAfterDeletes, []) regularEdits @@ -4941,11 +5387,28 @@ template CapTable -- Process creates last (after deletes freed constraints) (finalMaps, createdCids) <- foldlA (\(maps, cids) createData -> do - (newMaps, cid) <- processCreate context maps createData + (newMaps, cid) <- processCreate context newIssuerCid maps createData pure (newMaps, cids ++ [cid])) (mapsAfterEdits, []) sortedCreates + validateDocumentRelatedObjects finalMaps newIssuerCid + validateStockClassConversionRightReferences finalMaps + validateConvertibleIssuanceReferences finalMaps + validateConvertibleConversionRightReferences finalMaps + validateWarrantConversionRightReferences finalMaps + validateValuationStockClassReferences finalMaps + validateStockPlanReferences finalMaps + validateStockIssuanceReferences finalMaps + validateEquityCompensationIssuanceReferences finalMaps + validateWarrantIssuanceReferences finalMaps + validateVestingTransactionConditionReferences finalMaps + validateVestingTermsConditionGraphReferences finalMaps + validateConvertibleConversionCapitalizationReferences finalMaps + validateConvertibleConversionTriggerReferences finalMaps + validateWarrantExerciseTriggerReferences finalMaps + validateStockReissuanceSplitReferences finalMaps + validateConfiguredReferences finalMaps authorizedShareAdjustmentIndex <- stockClassAuthorizedAdjustmentIndex finalMaps validateAuthorizedShareCeilings finalMaps diff --git a/scripts/codegen/templates/loops/create-case.daml b/scripts/codegen/templates/loops/create-case.daml index d40a429c..d218315a 100644 --- a/scripts/codegen/templates/loops/create-case.daml +++ b/scripts/codegen/templates/loops/create-case.daml @@ -6,6 +6,17 @@ OcfCreate{{name}} d -> do assertMsg "{{name}} security_id already exists" (isNone (Map.lookup d.security_id maps.{{security_id_index_map}})) {{/if}} {{#each validations}} +{{#if is_optional}} +{{#if or_maps}} + case d.{{field}} of + Some refId -> assertMsg ("{{error}}: " <> refId) (isSome (Map.lookup refId maps.{{firstMap}}) || isSome (Map.lookup refId maps.{{secondMap}}) || isSome (Map.lookup refId maps.{{thirdMap}})) + None -> pure () +{{else}} + case d.{{field}} of + Some refId -> assertMsg ("{{error}}: " <> refId) (isSome (Map.lookup refId maps.{{map}})) + None -> pure () +{{/if}} +{{else}} {{#if is_array}} -- Validate each element in array field {{field}} mapA_ (\secId -> assertMsg ("{{error}}: " <> secId) (isSome (Map.lookup secId maps.{{map}}))) d.{{field}} @@ -16,7 +27,11 @@ OcfCreate{{name}} d -> do assertMsg ("{{error}}: " <> d.{{field}}) (isSome (Map.lookup d.{{field}} maps.{{map}})) {{/if}} {{/if}} +{{/if}} {{/each}} +{{#if validates_issuer_id_reference}} + validateIssuerReference issuerCid d.issuer_id +{{/if}} cid <- create {{name}} with context = ctx, {{data_param}} = d {{#if security_id_index_map}} let newMaps = maps with diff --git a/scripts/codegen/templates/loops/edit-case.daml b/scripts/codegen/templates/loops/edit-case.daml index 73321301..288e1c43 100644 --- a/scripts/codegen/templates/loops/edit-case.daml +++ b/scripts/codegen/templates/loops/edit-case.daml @@ -4,18 +4,10 @@ OcfEdit{{name}} d -> do let oldCidOpt = Map.lookup d.id maps.{{map_field}} assertMsg "{{name}} not found" (isSome oldCidOpt) let Some oldCid = oldCidOpt -{{#each validations}} -{{#if is_array}} - -- Validate each element in array field {{field}} - mapA_ (\secId -> assertMsg ("{{error}}: " <> secId) (isSome (Map.lookup secId maps.{{map}}))) d.{{field}} -{{else}} -{{#if or_maps}} - assertMsg ("{{error}}: " <> d.{{field}}) (isSome (Map.lookup d.{{field}} maps.{{firstMap}}) || isSome (Map.lookup d.{{field}} maps.{{secondMap}}) || isSome (Map.lookup d.{{field}} maps.{{thirdMap}})) -{{else}} - assertMsg ("{{error}}: " <> d.{{field}}) (isSome (Map.lookup d.{{field}} maps.{{map}})) -{{/if}} + -- validateConfiguredReferences checks edited contracts after finalMaps includes batch creates. +{{#if validates_issuer_id_reference}} + validateIssuerReference issuerCid d.issuer_id {{/if}} -{{/each}} {{#if security_id_index_map}} -- Fetch old contract to get old security_id for index map cleanup oldContract <- fetch oldCid