diff --git a/OpenCapTable-v34/daml.yaml b/OpenCapTable-v34/daml.yaml index a8cb9483..5a2ffed7 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.13 +version: 0.0.14 build-options: - -Wno-crypto-text-is-alpha - --typecheck-upgrades=no diff --git a/Test/daml.yaml b/Test/daml.yaml index 16f002f3..533768e2 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.13.dar + - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.14.dar build-options: - -Wno-template-interface-depends-on-daml-script diff --git a/Test/daml/OpenCapTable/TestSecurityIdValidation.daml b/Test/daml/OpenCapTable/TestSecurityIdValidation.daml index f43dcb17..a40eb650 100644 --- a/Test/daml/OpenCapTable/TestSecurityIdValidation.daml +++ b/Test/daml/OpenCapTable/TestSecurityIdValidation.daml @@ -11,17 +11,40 @@ module OpenCapTable.TestSecurityIdValidation where import qualified Fairmint.OpenCapTable.OCF.StockTransfer as ST import qualified Fairmint.OpenCapTable.OCF.WarrantTransfer as WT +import qualified Fairmint.OpenCapTable.OCF.WarrantIssuance as WI import qualified Fairmint.OpenCapTable.OCF.ConvertibleTransfer as CVT 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) +import OpenCapTable.TestHelpers (addPrerequisites, addDefaultStockIssuance, addDefaultWarrantIssuance, addDefaultConvertibleIssuance, addDefaultEquityCompensationIssuance, addStockIssuance, defaultStockIssuance) import qualified DA.Date as DA import DA.Date (Month(..)) +import qualified DA.Map as Map import qualified DA.Time as DT import Daml.Script +warrantIssuanceData : Text -> Text -> Text -> WI.WarrantIssuanceOcfData +warrantIssuanceData issuanceId securityId stakeholderId = WI.WarrantIssuanceOcfData with + id = issuanceId + custom_id = "WR-" <> issuanceId + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + 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 100.0 + quantity_source = None + stockholder_approval_date = None + vesting_terms_id = None + warrant_expiration_date = None + -- ============================================================================= -- Test: Non-existent security_id fails with clear error @@ -204,6 +227,153 @@ testEquityCompensationTransfer_WrongSecurityTypeFails = script do deletes = [] pure () +-- ============================================================================= +-- Test: security_id values are globally unique across security issuance types +-- ============================================================================= + +testSecurityIssuance_DuplicateSecurityIdAcrossTypesFails = script do + TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addDefaultStockIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance (warrantIssuanceData "WR-DUP-STOCK-SECURITY" "SEC-STOCK-1" "SH-1")] + edits = [] + deletes = [] + pure () + +testSecurityIssuance_DuplicateSecurityIdAcrossTypesFailsInSameBatch = script do + TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance (defaultStockIssuance "TX-BATCH-STOCK" "SEC-BATCH-DUP" "SH-1" "SC_COMMON") + , CT.OcfCreateWarrantIssuance (warrantIssuanceData "TX-BATCH-WARRANT" "SEC-BATCH-DUP" "SH-1") + ] + edits = [] + deletes = [] + pure () + +testSecurityIssuance_EditCannotReuseSecurityIdFromAnotherType = script do + TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addDefaultWarrantIssuance issuer cap_table + cap_table <- addStockIssuance issuer cap_table "STOCK-EDIT-SRC" "SEC-STOCK-EDIT" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockIssuance (defaultStockIssuance "STOCK-EDIT-SRC" "SEC-WR-1" "SH-1" "SC_COMMON")] + deletes = [] + pure () + +-- A final-state validation must reject legacy or upgraded cap tables whose +-- security ID index is stale and therefore hides a duplicate issuance. +testSecurityIssuance_FinalStateDuplicateSecurityIdsFail = script do + TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addDefaultStockIssuance issuer cap_table + capTableDataOpt <- queryContractId issuer cap_table + let Some capTableData = capTableDataOpt + + duplicateWarrantCid <- submit (actAs issuer <> actAs system_operator) $ createCmd $ WI.WarrantIssuance with + context = ctx + issuance_data = warrantIssuanceData "WR-UPGRADED-DUPLICATE" "SEC-STOCK-1" "SH-1" + + let corruptedCapTable = capTableData with + warrant_issuances = Map.insert "WR-UPGRADED-DUPLICATE" duplicateWarrantCid capTableData.warrant_issuances + corruptedCapTableCid <- submit (actAs issuer <> actAs system_operator) $ createCmd corruptedCapTable + + submitMustFail issuer do + exerciseCmd corruptedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [] + deletes = [] + pure () + +-- Lifecycle outputs identify newly created securities, so an output cannot +-- reuse the source security's identity. +testSuccessorSecurityId_CannotReuseSourceSecurityId = script do + TestOcp{issuer, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addStockIssuance issuer cap_table "STOCK-SOURCE-REUSE" "SEC-SOURCE-REUSE" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = [CT.OcfCreateStockTransfer ST.StockTransferOcfData with + id = "TX-SOURCE-REUSE" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-SOURCE-REUSE" + quantity = 100.0 + comments = [] + resulting_security_ids = ["SEC-SOURCE-REUSE"] + balance_security_id = None + consideration_text = None] + edits = [] + deletes = [] + pure () + +-- A partial transaction creates distinct recipient and remainder securities. +testSuccessorSecurityId_BalanceCannotAlsoBeResult = script do + TestOcp{issuer, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addStockIssuance issuer cap_table "STOCK-BALANCE-RESULT-SOURCE" "SEC-BALANCE-RESULT-SOURCE" "SH-1" "SC_COMMON" + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance + ((defaultStockIssuance "STOCK-BALANCE-RESULT" "SEC-BALANCE-RESULT" "SH-1" "SC_COMMON") with quantity = 50.0) + , CT.OcfCreateStockTransfer ST.StockTransferOcfData with + id = "TX-BALANCE-RESULT" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-BALANCE-RESULT-SOURCE" + quantity = 50.0 + comments = [] + resulting_security_ids = ["SEC-BALANCE-RESULT"] + balance_security_id = Some "SEC-BALANCE-RESULT" + consideration_text = None + ] + edits = [] + deletes = [] + pure () + +-- Even when successor issuances are not materialized, one security identity +-- cannot be claimed as the output of multiple lifecycle transactions. +testSuccessorSecurityId_CannotBeProducedByMultipleTransactionTypes = script do + TestOcp{issuer, cap_table} <- setupTestOcp + cap_table <- addPrerequisites issuer cap_table + cap_table <- addStockIssuance issuer cap_table "STOCK-SHARED-RESULT-A" "SEC-SHARED-RESULT-A" "SH-1" "SC_COMMON" + cap_table <- addDefaultWarrantIssuance issuer cap_table + + submitMustFail issuer do + exerciseCmd cap_table CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockTransfer ST.StockTransferOcfData with + id = "TX-SHARED-RESULT-A" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-SHARED-RESULT-A" + quantity = 100.0 + comments = [] + resulting_security_ids = ["SEC-SHARED-RESULT"] + balance_security_id = None + consideration_text = None + , CT.OcfCreateWarrantTransfer WT.WarrantTransferOcfData with + id = "TX-SHARED-RESULT-B" + date = DT.time (DA.date 2024 Jan 01) 0 0 0 + security_id = "SEC-WR-1" + quantity = 100.0 + comments = [] + resulting_security_ids = ["SEC-SHARED-RESULT"] + balance_security_id = None + consideration_text = None + ] + edits = [] + deletes = [] + pure () -- ============================================================================= -- Test: Valid security_id succeeds diff --git a/dars/OpenCapTable-v34/0.0.14/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.14/OpenCapTable-v34.dar new file mode 100644 index 00000000..d3e1e0af --- /dev/null +++ b/dars/OpenCapTable-v34/0.0.14/OpenCapTable-v34.dar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d173853748b69a7b4bbb1c291cdf27bde89bcdce1f29ccd98c7601521e28050 +size 3094585 diff --git a/dars/dars.lock b/dars/dars.lock index 01272162..a4a18c86 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -39,6 +39,13 @@ "uploadedAt": "2026-07-09T18:48:49.697Z", "networks": [] }, + "OpenCapTable-v34/0.0.14/OpenCapTable-v34.dar": { + "sha256": "8d173853748b69a7b4bbb1c291cdf27bde89bcdce1f29ccd98c7601521e28050", + "size": 3094585, + "sdkVersion": "3.4.10", + "uploadedAt": "2026-07-09T18:50:58.571Z", + "networks": [] + }, "OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar": { "sha256": "2ce462b8afcb1bcdbe351c56b0a47de478477f3c655da00e41cb397f63e997d6", "size": 2446238, diff --git a/scripts/codegen/templates/CapTable.daml.template b/scripts/codegen/templates/CapTable.daml.template index ff9799d2..600ebfb6 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -105,6 +105,325 @@ toMaps ct = CapTableMaps with stock_issuances_by_security_id = ct.stock_issuances_by_security_id warrant_issuances_by_security_id = ct.warrant_issuances_by_security_id +securityIdExists : CapTableMaps -> Text -> Bool +securityIdExists maps 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) + +assertSecurityIdAvailable : Text -> CapTableMaps -> Update () +assertSecurityIdAvailable securityId maps = + assertMsg ("Security ID already exists: " <> securityId) (not (securityIdExists maps securityId)) + +securityIdsInFinalMaps : CapTableMaps -> Update [Text] +securityIdsInFinalMaps maps = do + convertibleSecurityIds <- foldlA + (\securityIds issuanceCid -> do + issuance <- fetch issuanceCid + pure (issuance.issuance_data.security_id :: securityIds)) + [] + (Map.values maps.convertible_issuances) + equityCompensationSecurityIds <- foldlA + (\securityIds issuanceCid -> do + issuance <- fetch issuanceCid + pure (issuance.issuance_data.security_id :: securityIds)) + [] + (Map.values maps.equity_compensation_issuances) + stockSecurityIds <- foldlA + (\securityIds issuanceCid -> do + issuance <- fetch issuanceCid + pure (issuance.issuance_data.security_id :: securityIds)) + [] + (Map.values maps.stock_issuances) + warrantSecurityIds <- foldlA + (\securityIds issuanceCid -> do + issuance <- fetch issuanceCid + pure (issuance.issuance_data.security_id :: securityIds)) + [] + (Map.values maps.warrant_issuances) + pure (convertibleSecurityIds ++ equityCompensationSecurityIds ++ stockSecurityIds ++ warrantSecurityIds) + +validateFinalSecurityIdUniqueness : CapTableMaps -> Update () +validateFinalSecurityIdUniqueness maps = do + securityIds <- securityIdsInFinalMaps maps + assertMsg "Security IDs must be globally unique" (not (VT.hasDuplicateTexts securityIds)) + +assertSuccessorSecurityIdDistinct : Text -> Text -> Text -> Update () +assertSuccessorSecurityIdDistinct label sourceSecurityId successorSecurityId = + assertMsg + (label <> " must use a new successor security ID instead of reusing " <> sourceSecurityId) + (successorSecurityId /= sourceSecurityId) + +validateSuccessorSecurityIds : Text -> Text -> Optional Text -> [Text] -> Update () +validateSuccessorSecurityIds label sourceSecurityId balanceSecurityId resultingSecurityIds = do + mapA_ (assertSuccessorSecurityIdDistinct label sourceSecurityId) resultingSecurityIds + case balanceSecurityId of + Some balanceId -> do + assertSuccessorSecurityIdDistinct label sourceSecurityId balanceId + assertMsg + (label <> " cannot use its balance security as a resulting security: " <> balanceId) + (balanceId `notElem` resultingSecurityIds) + None -> pure () + +successorSecurityIdsFor : Optional Text -> [Text] -> [Text] +successorSecurityIdsFor balanceSecurityId resultingSecurityIds = + case balanceSecurityId of + Some balanceId -> balanceId :: resultingSecurityIds + None -> resultingSecurityIds + +validateGlobalSuccessorSecurityIdUniqueness : CapTableMaps -> Update () +validateGlobalSuccessorSecurityIdUniqueness maps = do + stockTransferIds <- foldlA + (\securityIds transferCid -> do + transfer <- fetch transferCid + pure (successorSecurityIdsFor transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids ++ securityIds)) + [] + (Map.values maps.stock_transfers) + stockCancellationIds <- foldlA + (\securityIds cancellationCid -> do + cancellation <- fetch cancellationCid + pure (successorSecurityIdsFor cancellation.cancellation_data.balance_security_id [] ++ securityIds)) + stockTransferIds + (Map.values maps.stock_cancellations) + stockRepurchaseIds <- foldlA + (\securityIds repurchaseCid -> do + repurchase <- fetch repurchaseCid + pure (successorSecurityIdsFor repurchase.repurchase_data.balance_security_id [] ++ securityIds)) + stockCancellationIds + (Map.values maps.stock_repurchases) + stockConversionIds <- foldlA + (\securityIds conversionCid -> do + conversion <- fetch conversionCid + pure (successorSecurityIdsFor conversion.conversion_data.balance_security_id conversion.conversion_data.resulting_security_ids ++ securityIds)) + stockRepurchaseIds + (Map.values maps.stock_conversions) + stockReissuanceIds <- foldlA + (\securityIds reissuanceCid -> do + reissuance <- fetch reissuanceCid + pure (reissuance.reissuance_data.resulting_security_ids ++ securityIds)) + stockConversionIds + (Map.values maps.stock_reissuances) + stockConsolidationIds <- foldlA + (\securityIds consolidationCid -> do + consolidation <- fetch consolidationCid + pure (consolidation.consolidation_data.resulting_security_id :: securityIds)) + stockReissuanceIds + (Map.values maps.stock_consolidations) + warrantTransferIds <- foldlA + (\securityIds transferCid -> do + transfer <- fetch transferCid + pure (successorSecurityIdsFor transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids ++ securityIds)) + stockConsolidationIds + (Map.values maps.warrant_transfers) + warrantCancellationIds <- foldlA + (\securityIds cancellationCid -> do + cancellation <- fetch cancellationCid + pure (successorSecurityIdsFor cancellation.cancellation_data.balance_security_id [] ++ securityIds)) + warrantTransferIds + (Map.values maps.warrant_cancellations) + warrantExerciseIds <- foldlA + (\securityIds exerciseCid -> do + exercise <- fetch exerciseCid + pure (exercise.exercise_data.resulting_security_ids ++ securityIds)) + warrantCancellationIds + (Map.values maps.warrant_exercises) + convertibleTransferIds <- foldlA + (\securityIds transferCid -> do + transfer <- fetch transferCid + pure (successorSecurityIdsFor transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids ++ securityIds)) + warrantExerciseIds + (Map.values maps.convertible_transfers) + convertibleCancellationIds <- foldlA + (\securityIds cancellationCid -> do + cancellation <- fetch cancellationCid + pure (successorSecurityIdsFor cancellation.cancellation_data.balance_security_id [] ++ securityIds)) + convertibleTransferIds + (Map.values maps.convertible_cancellations) + convertibleConversionIds <- foldlA + (\securityIds conversionCid -> do + conversion <- fetch conversionCid + pure (successorSecurityIdsFor conversion.conversion_data.balance_security_id conversion.conversion_data.resulting_security_ids ++ securityIds)) + convertibleCancellationIds + (Map.values maps.convertible_conversions) + equityCompensationTransferIds <- foldlA + (\securityIds transferCid -> do + transfer <- fetch transferCid + pure (successorSecurityIdsFor transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids ++ securityIds)) + convertibleConversionIds + (Map.values maps.equity_compensation_transfers) + equityCompensationCancellationIds <- foldlA + (\securityIds cancellationCid -> do + cancellation <- fetch cancellationCid + pure (successorSecurityIdsFor cancellation.cancellation_data.balance_security_id [] ++ securityIds)) + equityCompensationTransferIds + (Map.values maps.equity_compensation_cancellations) + equityCompensationExerciseIds <- foldlA + (\securityIds exerciseCid -> do + exercise <- fetch exerciseCid + pure (exercise.exercise_data.resulting_security_ids ++ securityIds)) + equityCompensationCancellationIds + (Map.values maps.equity_compensation_exercises) + allSuccessorIds <- foldlA + (\securityIds releaseCid -> do + release <- fetch releaseCid + pure (release.release_data.resulting_security_ids ++ securityIds)) + equityCompensationExerciseIds + (Map.values maps.equity_compensation_releases) + assertMsg + "Each successor security ID may be produced by only one lifecycle transaction" + (not (VT.hasDuplicateTexts allSuccessorIds)) + +validateSuccessorSecurityIdUniqueness : CapTableMaps -> Update () +validateSuccessorSecurityIdUniqueness maps = do + validateGlobalSuccessorSecurityIdUniqueness maps + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateSuccessorSecurityIds + ("Stock transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + transfer.transfer_data.balance_security_id + transfer.transfer_data.resulting_security_ids) + (Map.values maps.stock_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateSuccessorSecurityIds + ("Stock cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + cancellation.cancellation_data.balance_security_id + []) + (Map.values maps.stock_cancellations) + mapA_ + (\repurchaseCid -> do + repurchase <- fetch repurchaseCid + validateSuccessorSecurityIds + ("Stock repurchase " <> repurchase.repurchase_data.id) + repurchase.repurchase_data.security_id + repurchase.repurchase_data.balance_security_id + []) + (Map.values maps.stock_repurchases) + mapA_ + (\conversionCid -> do + conversion <- fetch conversionCid + validateSuccessorSecurityIds + ("Stock conversion " <> conversion.conversion_data.id) + conversion.conversion_data.security_id + conversion.conversion_data.balance_security_id + conversion.conversion_data.resulting_security_ids) + (Map.values maps.stock_conversions) + mapA_ + (\reissuanceCid -> do + reissuance <- fetch reissuanceCid + validateSuccessorSecurityIds + ("Stock reissuance " <> reissuance.reissuance_data.id) + reissuance.reissuance_data.security_id + None + reissuance.reissuance_data.resulting_security_ids) + (Map.values maps.stock_reissuances) + mapA_ + (\consolidationCid -> do + consolidation <- fetch consolidationCid + mapA_ + (\sourceSecurityId -> + assertSuccessorSecurityIdDistinct + ("Stock consolidation " <> consolidation.consolidation_data.id) + sourceSecurityId + consolidation.consolidation_data.resulting_security_id) + consolidation.consolidation_data.security_ids) + (Map.values maps.stock_consolidations) + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateSuccessorSecurityIds + ("Warrant transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + transfer.transfer_data.balance_security_id + transfer.transfer_data.resulting_security_ids) + (Map.values maps.warrant_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateSuccessorSecurityIds + ("Warrant cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + cancellation.cancellation_data.balance_security_id + []) + (Map.values maps.warrant_cancellations) + mapA_ + (\exerciseCid -> do + exercise <- fetch exerciseCid + validateSuccessorSecurityIds + ("Warrant exercise " <> exercise.exercise_data.id) + exercise.exercise_data.security_id + None + exercise.exercise_data.resulting_security_ids) + (Map.values maps.warrant_exercises) + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateSuccessorSecurityIds + ("Convertible transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + transfer.transfer_data.balance_security_id + transfer.transfer_data.resulting_security_ids) + (Map.values maps.convertible_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateSuccessorSecurityIds + ("Convertible cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + cancellation.cancellation_data.balance_security_id + []) + (Map.values maps.convertible_cancellations) + mapA_ + (\conversionCid -> do + conversion <- fetch conversionCid + validateSuccessorSecurityIds + ("Convertible conversion " <> conversion.conversion_data.id) + conversion.conversion_data.security_id + conversion.conversion_data.balance_security_id + conversion.conversion_data.resulting_security_ids) + (Map.values maps.convertible_conversions) + mapA_ + (\transferCid -> do + transfer <- fetch transferCid + validateSuccessorSecurityIds + ("Equity compensation transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + transfer.transfer_data.balance_security_id + transfer.transfer_data.resulting_security_ids) + (Map.values maps.equity_compensation_transfers) + mapA_ + (\cancellationCid -> do + cancellation <- fetch cancellationCid + validateSuccessorSecurityIds + ("Equity compensation cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + cancellation.cancellation_data.balance_security_id + []) + (Map.values maps.equity_compensation_cancellations) + mapA_ + (\exerciseCid -> do + exercise <- fetch exerciseCid + validateSuccessorSecurityIds + ("Equity compensation exercise " <> exercise.exercise_data.id) + exercise.exercise_data.security_id + None + exercise.exercise_data.resulting_security_ids) + (Map.values maps.equity_compensation_exercises) + mapA_ + (\releaseCid -> do + release <- fetch releaseCid + validateSuccessorSecurityIds + ("Equity compensation release " <> release.release_data.id) + release.release_data.security_id + None + release.release_data.resulting_security_ids) + (Map.values maps.equity_compensation_releases) + data AuthorizedSharesCeiling = AuthorizedSharesFinite Decimal | AuthorizedSharesUnlimited @@ -4967,6 +5286,8 @@ template CapTable (mapsAfterEdits, []) sortedCreates + validateFinalSecurityIdUniqueness finalMaps + validateSuccessorSecurityIdUniqueness finalMaps validateDocumentRelatedObjects finalMaps newIssuerCid validateStockClassConversionRightReferences finalMaps validateConvertibleIssuanceReferences finalMaps diff --git a/scripts/codegen/templates/loops/create-case.daml b/scripts/codegen/templates/loops/create-case.daml index d218315a..b6d1bb48 100644 --- a/scripts/codegen/templates/loops/create-case.daml +++ b/scripts/codegen/templates/loops/create-case.daml @@ -3,7 +3,7 @@ OcfCreate{{name}} d -> do assertMsg "{{name}} ID already exists" (isNone (Map.lookup d.id maps.{{map_field}})) {{#if security_id_index_map}} - assertMsg "{{name}} security_id already exists" (isNone (Map.lookup d.security_id maps.{{security_id_index_map}})) + assertSecurityIdAvailable d.security_id maps {{/if}} {{#each validations}} {{#if is_optional}} diff --git a/scripts/codegen/templates/loops/edit-case.daml b/scripts/codegen/templates/loops/edit-case.daml index 975309fa..d472369e 100644 --- a/scripts/codegen/templates/loops/edit-case.daml +++ b/scripts/codegen/templates/loops/edit-case.daml @@ -37,7 +37,7 @@ OcfEdit{{name}} d -> do let oldSecurityId = oldContract.{{data_param}}.security_id -- Only check uniqueness if security_id is changing when (d.security_id /= oldSecurityId) do - assertMsg "{{name}} security_id already exists" (isNone (Map.lookup d.security_id maps.{{security_id_index_map}})) + assertSecurityIdAvailable d.security_id maps archive oldCid newCid <- create {{name}} with context = ctx, {{data_param}} = d let newMaps = maps with