From 7ba294d8b39fc4a58439234f4efbdc0672f221a9 Mon Sep 17 00:00:00 2001 From: HardlyDifficult Date: Wed, 8 Jul 2026 18:48:38 -0400 Subject: [PATCH 1/2] Enforce issuer adjustment issuer reference --- .../TestIssuerAuthorizedSharesAdjustment.daml | 39 +++++++++++++++---- scripts/codegen/generate-captable.ts | 2 + .../codegen/templates/CapTable.daml.template | 19 ++++++--- .../codegen/templates/loops/create-case.daml | 3 ++ .../codegen/templates/loops/edit-case.daml | 3 ++ 5 files changed, 52 insertions(+), 14 deletions(-) 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/scripts/codegen/generate-captable.ts b/scripts/codegen/generate-captable.ts index c8451ca2..206122e5 100644 --- a/scripts/codegen/generate-captable.ts +++ b/scripts/codegen/generate-captable.ts @@ -56,6 +56,7 @@ interface TypeDef { validates_stock_class_initial_authorized_shares: boolean; validates_stock_class_authorized_adjustment: boolean; validates_stock_issuance_authorized_shares: boolean; + validates_issuer_id_reference: boolean; // For issuance types, the name of the security_id index map (e.g., 'stock_issuances_by_security_id') security_id_index_map: string | null; } @@ -236,6 +237,7 @@ function discoverTypes(config: Config): TypeDef[] { validates_stock_class_initial_authorized_shares: name === 'StockClass', validates_stock_class_authorized_adjustment: name === 'StockClassAuthorizedSharesAdjustment', validates_stock_issuance_authorized_shares: name === 'StockIssuance', + validates_issuer_id_reference: name === 'IssuerAuthorizedSharesAdjustment', 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 3619a830..78d6a76d 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -204,6 +204,13 @@ validateCurrentStockClassAuthorizedShares maps stockClassId excludedAdjustmentId limitOpt <- currentStockClassAuthorizedLimit maps stockClassId excludedAdjustmentId stockClassOverride additionalAdjustment assertWithinStockClassAuthorizedShares stockClassId currentIssued limitOpt +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) + -- | Get the processing tier for a create operation getCreateTier : OcfCreateData -> Int getCreateTier createData = case createData of @@ -212,16 +219,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}} @@ -305,7 +312,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 @@ -332,7 +339,7 @@ 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 diff --git a/scripts/codegen/templates/loops/create-case.daml b/scripts/codegen/templates/loops/create-case.daml index 630475ca..69e78119 100644 --- a/scripts/codegen/templates/loops/create-case.daml +++ b/scripts/codegen/templates/loops/create-case.daml @@ -34,6 +34,9 @@ OcfCreate{{name}} d -> do {{/if}} {{#if validates_stock_class_authorized_adjustment}} validateCurrentStockClassAuthorizedShares maps d.stock_class_id None None (Some d) +{{/if}} +{{#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}} diff --git a/scripts/codegen/templates/loops/edit-case.daml b/scripts/codegen/templates/loops/edit-case.daml index b5e403b0..cb52d612 100644 --- a/scripts/codegen/templates/loops/edit-case.daml +++ b/scripts/codegen/templates/loops/edit-case.daml @@ -38,6 +38,9 @@ OcfEdit{{name}} d -> do validateCurrentStockClassAuthorizedShares maps oldAdjustment.{{data_param}}.stock_class_id (Some oldAdjustment.{{data_param}}.id) None (Some d) validateCurrentStockClassAuthorizedShares maps d.stock_class_id (Some oldAdjustment.{{data_param}}.id) None (Some d) {{/if}} +{{#if validates_issuer_id_reference}} + validateIssuerReference issuerCid d.issuer_id +{{/if}} {{#if security_id_index_map}} -- Fetch old contract to get old security_id for index map cleanup oldContract <- fetch oldCid From 68b6b25f3c65302683f997126aeb376c10450451 Mon Sep 17 00:00:00 2001 From: HardlyDifficult Date: Wed, 8 Jul 2026 19:25:18 -0400 Subject: [PATCH 2/2] Update DAR backup for issuer adjustment references --- dars/OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar | 4 ++-- dars/dars.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dars/OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar index 87a68c14..a1d88b01 100644 --- a/dars/OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar +++ b/dars/OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2afe248b351f9b4e7b84bf2b23d6c5d8d20308126de6c694ab95d21490b0d82b -size 2466418 +oid sha256:1ae8fcf22319a4c408ac0a451bbd40127f4d58c33c1aa11d3849b2550226d34a +size 2467970 diff --git a/dars/dars.lock b/dars/dars.lock index 01e6f584..2f09c34f 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -12,8 +12,8 @@ ] }, "OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar": { - "sha256": "2afe248b351f9b4e7b84bf2b23d6c5d8d20308126de6c694ab95d21490b0d82b", - "size": 2466418, + "sha256": "1ae8fcf22319a4c408ac0a451bbd40127f4d58c33c1aa11d3849b2550226d34a", + "size": 2467970, "sdkVersion": "3.4.10", "uploadedAt": "2026-07-07T21:50:59.634Z", "networks": []