diff --git a/OpenCapTable-v34/daml.yaml b/OpenCapTable-v34/daml.yaml index 76f7c6c1..0e021f17 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.16 +version: 0.0.17 build-options: - -Wno-crypto-text-is-alpha - --typecheck-upgrades=no diff --git a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/EquityCompensationIssuance.daml b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/EquityCompensationIssuance.daml index 5f39b12c..cc29ba6d 100644 --- a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/EquityCompensationIssuance.daml +++ b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/EquityCompensationIssuance.daml @@ -6,7 +6,7 @@ import Fairmint.OpenCapTable.Types.Core (Context) import Fairmint.OpenCapTable.Types.Validation (validateNonEmptyStringsArray) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary, validateOcfMonetary) import Fairmint.OpenCapTable.Types.Stock (OcfSecurityExemption, validateOcfSecurityExemption) -import Fairmint.OpenCapTable.Types.Vesting (OcfTerminationWindow, OcfCompensationType(..), OcfVesting, validateOcfTerminationWindow, validateOcfVesting) +import Fairmint.OpenCapTable.Types.Vesting (OcfTerminationWindow, OcfCompensationType(..), OcfVesting, validateOcfTerminationWindow, validateOcfVesting, validateOcfVestingsWithinQuantity) import Fairmint.Shared.TypeHelpers (validateOptionalText) @@ -96,6 +96,7 @@ validateEquityCompensationIssuanceOcfData d = all validateOcfSecurityExemption d.security_law_exemptions && validateNonEmptyStringsArray d.comments && all validateOcfVesting d.vestings && + validateOcfVestingsWithinQuantity d.quantity d.vestings && all validateOcfTerminationWindow d.termination_exercise_windows && -- Optional Text values, if present, must be non-empty validateOptionalText d.consideration_text && diff --git a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/StockIssuance.daml b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/StockIssuance.daml index c08016ad..7089aab2 100644 --- a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/StockIssuance.daml +++ b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/StockIssuance.daml @@ -5,7 +5,7 @@ import Fairmint.OpenCapTable.Types.Core (Context) import Fairmint.OpenCapTable.Types.Validation (validateNonEmptyStringsArray) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary, validateOcfMonetary) import Fairmint.OpenCapTable.Types.Stock (OcfSecurityExemption, OcfShareNumberRange, validateOcfSecurityExemption, validateOcfShareNumberRangesForQuantity) -import Fairmint.OpenCapTable.Types.Vesting (OcfVesting, validateOcfVesting) +import Fairmint.OpenCapTable.Types.Vesting (OcfVesting, validateOcfVesting, validateOcfVestingsWithinQuantity) import Fairmint.Shared.TypeHelpers (validateOptionalText) @@ -92,6 +92,7 @@ validateOcfStockIssuanceData d = all validateOcfSecurityExemption d.security_law_exemptions && validateOcfShareNumberRangesForQuantity d.quantity d.share_numbers_issued && all validateOcfVesting d.vestings && + validateOcfVestingsWithinQuantity d.quantity d.vestings && validateNonEmptyStringsArray d.comments && -- Arrays per schema: schema requires at least 1 legend, but we allow empty validateNonEmptyStringsArray d.stock_legend_ids && diff --git a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/WarrantIssuance.daml b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/WarrantIssuance.daml index 74a22490..60327c0b 100644 --- a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/WarrantIssuance.daml +++ b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/WarrantIssuance.daml @@ -4,8 +4,8 @@ module Fairmint.OpenCapTable.OCF.WarrantIssuance where import Fairmint.OpenCapTable.Types.Core (Context) import Fairmint.OpenCapTable.Types.Validation (validateNonEmptyStringsArray) import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary, validateOcfMonetary) -import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType, OcfSecurityExemption, validateOcfSecurityExemption) -import Fairmint.OpenCapTable.Types.Vesting (OcfVesting, validateOcfVesting) +import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType, OcfSecurityExemption, ocfQuantityUpperBound, validateOcfSecurityExemption) +import Fairmint.OpenCapTable.Types.Vesting (OcfVesting, validateOcfVesting, validateOcfVestingsWithinQuantity) import Fairmint.OpenCapTable.Types.Conversion (OcfConversionTrigger, validateOcfConversionTrigger) import Fairmint.Shared.TypeHelpers (validateOptionalText) import DA.List (unique) @@ -102,4 +102,7 @@ validateOcfWarrantIssuanceDataTx d = -- If vestings array present, enforce items are valid and minItems >= 1 (case d.vestings of [] -> True - xs -> all validateOcfVesting xs) + xs -> all validateOcfVesting xs) && + (case ocfQuantityUpperBound d.quantity d.quantity_source of + Some quantity -> validateOcfVestingsWithinQuantity quantity d.vestings + None -> True) diff --git a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Stock.daml b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Stock.daml index 98ee9e65..f3d36417 100644 --- a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Stock.daml +++ b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Stock.daml @@ -152,6 +152,13 @@ data OcfQuantitySourceType = | OcfQuantityInstrumentMin deriving (Eq, Show) +ocfQuantityUpperBound : Optional Decimal -> Optional OcfQuantitySourceType -> Optional Decimal +ocfQuantityUpperBound quantity quantitySource = + case quantitySource of + Some OcfQuantityInstrumentFixed -> quantity + Some OcfQuantityInstrumentMax -> quantity + _ -> None + -- Enum - File Type -- File types within an OCF manifest -- OCF: https://raw.githubusercontent.com/Open-Cap-Table-Coalition/Open-Cap-Format-OCF/main/schema/enums/FileType.schema.json diff --git a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Vesting.daml b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Vesting.daml index 4d68ac96..35b3c71b 100644 --- a/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Vesting.daml +++ b/OpenCapTable-v34/daml/Fairmint/OpenCapTable/Types/Vesting.daml @@ -61,6 +61,13 @@ data OcfVesting = OcfVesting validateOcfVesting : OcfVesting -> Bool validateOcfVesting v = v.amount > (0.0) +ocfVestingAmountTotal : [OcfVesting] -> Decimal +ocfVestingAmountTotal = foldl (\total vesting -> total + vesting.amount) 0.0 + +validateOcfVestingsWithinQuantity : Decimal -> [OcfVesting] -> Bool +validateOcfVestingsWithinQuantity quantity vestings = + null vestings || ocfVestingAmountTotal vestings <= quantity + -- Enum - Compensation Type -- Equity compensation instrument type -- OCF: https://raw.githubusercontent.com/Open-Cap-Table-Coalition/Open-Cap-Format-OCF/main/schema/enums/CompensationType.schema.json diff --git a/Test/daml.yaml b/Test/daml.yaml index 5693c135..36057ec4 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.16.dar + - ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.17.dar build-options: - -Wno-template-interface-depends-on-daml-script diff --git a/Test/daml/OpenCapTable/TestVestingAcceleration.daml b/Test/daml/OpenCapTable/TestVestingAcceleration.daml index 99aa5f77..915620d9 100644 --- a/Test/daml/OpenCapTable/TestVestingAcceleration.daml +++ b/Test/daml/OpenCapTable/TestVestingAcceleration.daml @@ -3,7 +3,7 @@ module OpenCapTable.TestVestingAcceleration where import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance import Fairmint.OpenCapTable.OCF.VestingAcceleration import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) -import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..)) import qualified Fairmint.OpenCapTable.CapTable as CT import OpenCapTable.Setup import OpenCapTable.TestHelpers (addDefaultStakeholder) @@ -35,7 +35,9 @@ createEquityCompensationIssuance issuer capTableCid securityId quantity = exercise_price = Some (OcfMonetary with amount = 1.25, currency = "USD") expiration_date = Some (DT.time (DA.date 2034 Jan 01) 0 0 0) termination_exercise_windows = [] - vestings = [] + vestings = [OcfVesting with + date = DT.time (DA.date 2030 Jan 01) 0 0 0 + amount = quantity] vesting_terms_id = None comments = []] edits = [] diff --git a/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml new file mode 100644 index 00000000..ca07fa32 --- /dev/null +++ b/Test/daml/OpenCapTable/TestVestingQuantityIntegrity.daml @@ -0,0 +1,1936 @@ +module OpenCapTable.TestVestingQuantityIntegrity where + +import Fairmint.OpenCapTable.OCF.VestingAcceleration (VestingAccelerationOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationCancellation (EquityCompensationCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationExercise (EquityCompensationExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationIssuance (EquityCompensationIssuanceOcfData) +import Fairmint.OpenCapTable.OCF.EquityCompensationRelease (EquityCompensationReleaseOcfData(..)) +import Fairmint.OpenCapTable.OCF.EquityCompensationTransfer (EquityCompensationTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockConsolidation (StockConsolidationOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockConversion (StockConversionOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockIssuance (StockIssuanceOcfData) +import Fairmint.OpenCapTable.OCF.StockReissuance (StockReissuanceOcfData(..)) +import Fairmint.OpenCapTable.OCF.StockTransfer (StockTransferOcfData(..)) +import Fairmint.OpenCapTable.OCF.VestingTerms + ( OcfAllocationType(..) + , OcfVestingCondition(..) + , OcfVestingConditionPortion(..) + , OcfVestingTrigger(..) + , VestingTermsOcfData(..) + ) +import Fairmint.OpenCapTable.OCF.WarrantCancellation (WarrantCancellationOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantExercise (WarrantExerciseOcfData(..)) +import Fairmint.OpenCapTable.OCF.WarrantIssuance (WarrantIssuanceOcfData) +import Fairmint.OpenCapTable.OCF.WarrantTransfer (WarrantTransferOcfData(..)) +import Fairmint.OpenCapTable.Types.Conversion + ( OcfAnyConversionRight(..) + , OcfConversionTrigger(..) + , OcfConversionTriggerType(..) + , OcfWarrantConversionMechanism(..) + , OcfWarrantConversionRight(..) + ) +import Fairmint.OpenCapTable.Types.Monetary (OcfMonetary(..)) +import Fairmint.OpenCapTable.Types.Stock (OcfQuantitySourceType(..)) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..)) +import qualified Fairmint.OpenCapTable.CapTable as CT +import OpenCapTable.Setup +import OpenCapTable.TestHelpers + ( defaultStockPlan + , stockCancellationOn + , stockClassSplitOn + ) +import qualified OpenCapTable.TestHelpers as TestHelpers +import OpenCapTable.TestStockConversion (addPreferredStockClass) +import qualified DA.Date as DA +import DA.Date (Month(..)) +import qualified DA.Time as DT +import DA.Foldable (mapA_) +import Daml.Script + +accelerationVestingTermsId : Text +accelerationVestingTermsId = "VESTING-TERMS-ACCELERATION" + +accelerationVestingTerms : VestingTermsOcfData +accelerationVestingTerms = VestingTermsOcfData with + id = accelerationVestingTermsId + allocation_type = OcfAllocationFractional + description = "All shares follow one vesting condition" + name = "Acceleration test vesting terms" + comments = [] + vesting_conditions = [OcfVestingCondition with + id = "ACCELERATION-TEST-START" + trigger = OcfVestingStartTrigger + next_condition_ids = [] + description = None + portion = Some (OcfVestingConditionPortion with + numerator = 1.0 + denominator = 1.0 + remainder = False) + quantity = None] + +alternateAccelerationVestingTermsId : Text +alternateAccelerationVestingTermsId = "VESTING-TERMS-ACCELERATION-ALTERNATE" + +alternateAccelerationVestingTerms : VestingTermsOcfData +alternateAccelerationVestingTerms = accelerationVestingTerms with + id = alternateAccelerationVestingTermsId + name = "Alternate acceleration test vesting terms" + +defaultStockIssuance : Text -> Text -> Text -> Text -> StockIssuanceOcfData +defaultStockIssuance issuanceId securityId stakeholderId stockClassId = + (TestHelpers.defaultStockIssuance issuanceId securityId stakeholderId stockClassId) with + vesting_terms_id = Some accelerationVestingTermsId + +defaultEquityCompensationIssuance : Text -> Text -> Text -> EquityCompensationIssuanceOcfData +defaultEquityCompensationIssuance issuanceId securityId stakeholderId = + (TestHelpers.defaultEquityCompensationIssuance issuanceId securityId stakeholderId) with + vesting_terms_id = Some accelerationVestingTermsId + +defaultWarrantIssuance : Text -> Text -> Text -> WarrantIssuanceOcfData +defaultWarrantIssuance issuanceId securityId stakeholderId = + (TestHelpers.defaultWarrantIssuance issuanceId securityId stakeholderId) with + vesting_terms_id = Some accelerationVestingTermsId + +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) + +fullyVestedStockSuccessorIssuance : Text -> Text -> Text -> Text -> Time -> Decimal -> CT.OcfCreateData +fullyVestedStockSuccessorIssuance issuanceId securityId stakeholderId stockClassId date quantity = + CT.OcfCreateStockIssuance ((TestHelpers.defaultStockIssuance issuanceId securityId stakeholderId stockClassId) with + date = date + quantity = quantity) + +equityCompensationSuccessorIssuance : Text -> Text -> Text -> Time -> Decimal -> Optional Time -> CT.OcfCreateData +equityCompensationSuccessorIssuance issuanceId securityId stakeholderId date quantity expirationDate = + CT.OcfCreateEquityCompensationIssuance ((defaultEquityCompensationIssuance issuanceId securityId stakeholderId) with + date = date + quantity = quantity + expiration_date = expirationDate) + +warrantExerciseTrigger : Decimal -> OcfConversionTrigger +warrantExerciseTrigger quantity = OcfConversionTrigger with + conversion_right = OcfRightWarrant (OcfWarrantConversionRight with + conversion_mechanism = OcfWarrantMechanismFixedAmount with converts_to_quantity = quantity + 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 = "ELECTIVE_AT_WILL" + type_ = OcfTriggerTypeTypeElectiveAtWill + +firstVestingDate : Time +firstVestingDate = DT.time (DA.date 2025 Jan 01) 0 0 0 + +secondVestingDate : Time +secondVestingDate = DT.time (DA.date 2026 Jan 01) 0 0 0 + +accelerationDate : Time +accelerationDate = DT.time (DA.date 2025 Jun 01) 0 0 0 + +earlyExerciseDate : Time +earlyExerciseDate = DT.time (DA.date 2025 Sep 01) 0 0 0 + +splitDate : Time +splitDate = DT.time (DA.date 2026 Jan 01) 0 0 0 + +postSplitAccelerationDate : Time +postSplitAccelerationDate = DT.time (DA.date 2026 Jun 01) 0 0 0 + +successorAccelerationDate : Time +successorAccelerationDate = DT.time (DA.date 2027 Jan 01) 0 0 0 + +chainedBalanceDate : Time +chainedBalanceDate = DT.time (DA.date 2027 Jun 01) 0 0 0 + +finalSuccessorAccelerationDate : Time +finalSuccessorAccelerationDate = DT.time (DA.date 2028 Jan 01) 0 0 0 + +vestingOn : Time -> Decimal -> OcfVesting +vestingOn date amount = OcfVesting with + date = date + amount = amount + +acceleration : Text -> Text -> Decimal -> VestingAccelerationOcfData +acceleration accelerationId securityId quantity = VestingAccelerationOcfData with + id = accelerationId + date = accelerationDate + security_id = securityId + quantity = quantity + reason_text = "Board-approved acceleration" + comments = [] + +applyUpdate : + Party -> + ContractId CT.CapTable -> + [CT.OcfCreateData] -> + [CT.OcfEditData] -> + [CT.OcfDeleteData] -> + Script CT.UpdateCapTableResult +applyUpdate issuer capTableCid creates edits deletes = + submit issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = creates + edits = edits + deletes = deletes + +addPrerequisites : Party -> ContractId CT.CapTable -> Script (ContractId CT.CapTable) +addPrerequisites issuer capTableCid = do + capTableCid <- TestHelpers.addPrerequisites issuer capTableCid + result <- applyUpdate + issuer + capTableCid + [CT.OcfCreateVestingTerms accelerationVestingTerms] + [] + [] + pure result.updatedCapTableCid + +assertAdditionalAccelerationRejected : + Party -> + ContractId CT.CapTable -> + Text -> + Decimal -> + Decimal -> + Script () +assertAdditionalAccelerationRejected issuer capTableCid securityId firstQuantity secondQuantity = do + firstResult <- applyUpdate + issuer + capTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-FIRST" securityId firstQuantity)] + [] + [] + submitMustFail issuer do + exerciseCmd firstResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-SECOND" securityId secondQuantity)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_InlinePartialExactAndUnknownWarrantAccepted = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockIssuance = + (defaultStockIssuance "TX-INLINE-STOCK" "SEC-INLINE-STOCK" "SH-1" "SC_COMMON") with + quantity = 100.0 + vestings = [vestingOn firstVestingDate 40.0, vestingOn secondVestingDate 60.0] + let equityCompensationIssuance = + (defaultEquityCompensationIssuance "TX-INLINE-EC" "SEC-INLINE-EC" "SH-1") with + quantity = 100.0 + vestings = [vestingOn firstVestingDate 25.0] + let knownWarrantIssuance = + (defaultWarrantIssuance "TX-INLINE-WARRANT" "SEC-INLINE-WARRANT" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + vestings = [vestingOn firstVestingDate 100.0] + let unknownWarrantIssuance = + (defaultWarrantIssuance "TX-INLINE-WARRANT-UNKNOWN" "SEC-INLINE-WARRANT-UNKNOWN" "SH-1") with + quantity = None + vestings = [vestingOn firstVestingDate 500.0] + _ <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance stockIssuance + , CT.OcfCreateEquityCompensationIssuance equityCompensationIssuance + , CT.OcfCreateWarrantIssuance knownWarrantIssuance + , CT.OcfCreateWarrantIssuance unknownWarrantIssuance + ] + [] + [] + pure () + +testVestingQuantityIntegrity_StockInlineOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-INLINE-STOCK-OVER" "SEC-INLINE-STOCK-OVER" "SH-1" "SC_COMMON") with + quantity = 100.0 + vestings = [vestingOn firstVestingDate 60.0, vestingOn secondVestingDate 50.0] + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateStockIssuance issuance] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_EquityCompensationInlineOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultEquityCompensationIssuance "TX-INLINE-EC-OVER" "SEC-INLINE-EC-OVER" "SH-1") with + quantity = 100.0 + vestings = [vestingOn firstVestingDate 101.0] + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationIssuance issuance] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_KnownWarrantInlineOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultWarrantIssuance "TX-INLINE-WARRANT-OVER" "SEC-INLINE-WARRANT-OVER" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentMax + vestings = [vestingOn firstVestingDate 101.0] + submitMustFail issuer do + exerciseCmd capTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateWarrantIssuance issuance] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_NonCeilingWarrantSourcesDoNotCapVestingsOrAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let minimumWarrant = + (defaultWarrantIssuance "TX-WARRANT-MINIMUM" "SEC-WARRANT-MINIMUM" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentMin + vestings = [vestingOn firstVestingDate 150.0] + let humanEstimateWarrant = + (defaultWarrantIssuance "TX-WARRANT-HUMAN-ESTIMATE" "SEC-WARRANT-HUMAN-ESTIMATE" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityHumanEstimated + vestings = [vestingOn firstVestingDate 150.0] + let machineEstimateWarrant = + (defaultWarrantIssuance "TX-WARRANT-MACHINE-ESTIMATE" "SEC-WARRANT-MACHINE-ESTIMATE" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityMachineEstimated + vestings = [vestingOn firstVestingDate 150.0] + let explicitUnspecifiedWarrant = + (defaultWarrantIssuance "TX-WARRANT-UNSPECIFIED" "SEC-WARRANT-UNSPECIFIED" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityUnspecified + vestings = [vestingOn firstVestingDate 150.0] + let defaultUnspecifiedWarrant = + (defaultWarrantIssuance "TX-WARRANT-DEFAULT-UNSPECIFIED" "SEC-WARRANT-DEFAULT-UNSPECIFIED" "SH-1") with + quantity = Some 100.0 + quantity_source = None + vestings = [vestingOn firstVestingDate 150.0] + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateWarrantIssuance minimumWarrant + , CT.OcfCreateWarrantIssuance humanEstimateWarrant + , CT.OcfCreateWarrantIssuance machineEstimateWarrant + , CT.OcfCreateWarrantIssuance explicitUnspecifiedWarrant + , CT.OcfCreateWarrantIssuance defaultUnspecifiedWarrant + ] + [] + [] + _ <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-MINIMUM" minimumWarrant.security_id 200.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-HUMAN-ESTIMATE" humanEstimateWarrant.security_id 200.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-MACHINE-ESTIMATE" machineEstimateWarrant.security_id 200.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-UNSPECIFIED" explicitUnspecifiedWarrant.security_id 200.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-DEFAULT-UNSPECIFIED" defaultUnspecifiedWarrant.security_id 200.0) + ] + [] + [] + pure () + +testVestingQuantityIntegrity_InlineOversubscriptionEditRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-INLINE-EDIT" "SEC-INLINE-EDIT" "SH-1" "SC_COMMON") with + quantity = 100.0 + vestings = [vestingOn firstVestingDate 60.0] + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + let invalidEdit = issuance with + vestings = [vestingOn firstVestingDate 60.0, vestingOn secondVestingDate 50.0] + submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockIssuance invalidEdit] + deletes = [] + +testVestingQuantityIntegrity_AccelerationExactBoundariesAndUnknownWarrantAccepted = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockIssuance = + (defaultStockIssuance "TX-ACC-STOCK" "SEC-ACC-STOCK" "SH-1" "SC_COMMON") with + quantity = 100.0 + let equityCompensationIssuance = + (defaultEquityCompensationIssuance "TX-ACC-EC" "SEC-ACC-EC" "SH-1") with + quantity = 100.0 + let knownWarrantIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT" "SEC-ACC-WARRANT" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + let unknownWarrantIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-UNKNOWN" "SEC-ACC-WARRANT-UNKNOWN" "SH-1") with + quantity = None + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance stockIssuance + , CT.OcfCreateEquityCompensationIssuance equityCompensationIssuance + , CT.OcfCreateWarrantIssuance knownWarrantIssuance + , CT.OcfCreateWarrantIssuance unknownWarrantIssuance + ] + [] + [] + _ <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-1" "SEC-ACC-STOCK" 40.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-2" "SEC-ACC-STOCK" 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-EC" "SEC-ACC-EC" 100.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-1" "SEC-ACC-WARRANT" 70.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-2" "SEC-ACC-WARRANT" 30.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-UNKNOWN" "SEC-ACC-WARRANT-UNKNOWN" 1000.0) + ] + [] + [] + pure () + +testVestingQuantityIntegrity_StockAccelerationsNormalizeAcrossForwardSplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-ACC-STOCK-SPLIT" "SEC-ACC-STOCK-SPLIT" "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + firstAccelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-PRE-SPLIT" issuance.security_id 75.0)] + [] + [] + splitResult <- applyUpdate + issuer + firstAccelerationResult.updatedCapTableCid + [CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-STOCK-ACC" "SC_COMMON" 2.0 1.0 splitDate)] + [] + [] + _ <- applyUpdate + issuer + splitResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-POST-SPLIT" issuance.security_id 50.0) with + date = postSplitAccelerationDate)] + [] + [] + pure () + +testVestingQuantityIntegrity_PlanLinkedEquityCompensationAccelerationsNormalizeAcrossForwardSplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockPlan = defaultStockPlan "PLAN-ACC-EC-FORWARD-SPLIT" "SC_COMMON" + let issuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-FORWARD-SPLIT" "SEC-ACC-EC-FORWARD-SPLIT" "SH-1") with + quantity = 100.0 + stock_plan_id = Some stockPlan.id + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockPlan stockPlan + , CT.OcfCreateEquityCompensationIssuance issuance + ] + [] + [] + firstAccelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EC-PLAN-PRE-SPLIT" issuance.security_id 75.0)] + [] + [] + splitResult <- applyUpdate + issuer + firstAccelerationResult.updatedCapTableCid + [CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-EC-PLAN-ACC" "SC_COMMON" 2.0 1.0 splitDate)] + [] + [] + _ <- applyUpdate + issuer + splitResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-PLAN-POST-SPLIT" issuance.security_id 50.0) with + date = postSplitAccelerationDate)] + [] + [] + pure () + +testVestingQuantityIntegrity_EquityCompensationAccelerationsNormalizeAcrossReverseSplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockPlan = defaultStockPlan "PLAN-ACC-EC-REVERSE-SPLIT" "SC_COMMON" + let issuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-REVERSE-SPLIT" "SEC-ACC-EC-REVERSE-SPLIT" "SH-1") with + quantity = 100.0 + stock_plan_id = Some stockPlan.id + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockPlan stockPlan + , CT.OcfCreateEquityCompensationIssuance issuance + ] + [] + [] + firstAccelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EC-PRE-REVERSE-SPLIT" issuance.security_id 75.0)] + [] + [] + splitResult <- applyUpdate + issuer + firstAccelerationResult.updatedCapTableCid + [CT.OcfCreateStockClassSplit (stockClassSplitOn "REVERSE-SPLIT-EC-ACC" "SC_COMMON" 1.0 2.0 splitDate)] + [] + [] + submitMustFail issuer do + exerciseCmd splitResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-POST-REVERSE-SPLIT" issuance.security_id 20.0) with + date = postSplitAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_SameDaySplitSuccessorUsesPostSplitAccelerationUnits = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-SAME-DAY-SPLIT-SOURCE" + successorSecurityId = "SEC-ACC-SAME-DAY-SPLIT-SUCCESSOR" + splitId = "SPLIT-ACC-SAME-DAY" + reissuanceId = "REISSUE-ACC-SAME-DAY" + sourceIssuance = + (defaultStockIssuance "TX-ACC-SAME-DAY-SPLIT-SOURCE" sourceSecurityId "SH-1" "SC_COMMON") with + quantity = 25.0 + reissuance = StockReissuanceOcfData with + id = reissuanceId + date = splitDate + security_id = sourceSecurityId + resulting_security_ids = [successorSecurityId] + split_transaction_id = Some splitId + reason_text = None + comments = [] + successorResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance sourceIssuance + , CT.OcfCreateStockClassSplit (stockClassSplitOn splitId "SC_COMMON" 2.0 1.0 splitDate) + , stockSuccessorIssuance "TX-ACC-SAME-DAY-SPLIT-SUCCESSOR" successorSecurityId "SH-1" "SC_COMMON" splitDate 50.0 + , CT.OcfCreateStockReissuance reissuance + ] + [] + [] + sameDayAccelerationResult <- applyUpdate + issuer + successorResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-SAME-DAY-SPLIT-SUCCESSOR-FIRST" successorSecurityId 25.0) with + date = splitDate)] + [] + [] + exactResult <- applyUpdate + issuer + sameDayAccelerationResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-SAME-DAY-SPLIT-SUCCESSOR-SECOND" successorSecurityId 25.0) with + date = postSplitAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-SAME-DAY-SPLIT-SUCCESSOR-OVER" successorSecurityId 1.0) with + date = postSplitAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_SameDayReverseSplitAccelerationCannotBeUndercounted = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-ACC-SAME-DAY-REVERSE-SPLIT" "SEC-ACC-SAME-DAY-REVERSE-SPLIT" "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + sameDayAccelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateStockClassSplit (stockClassSplitOn "REVERSE-SPLIT-ACC-SAME-DAY" "SC_COMMON" 1.0 2.0 splitDate) + , CT.OcfCreateVestingAcceleration ((acceleration "ACC-SAME-DAY-REVERSE-SPLIT-FIRST" issuance.security_id 30.0) with + date = splitDate) + ] + [] + [] + submitMustFail issuer do + exerciseCmd sameDayAccelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-SAME-DAY-REVERSE-SPLIT-SECOND" issuance.security_id 30.0) with + date = postSplitAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockBalanceSuccessorCarriesSplitAdjustedAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-STOCK-LINEAGE-SOURCE" + successorSecurityId = "SEC-ACC-STOCK-LINEAGE-BALANCE" + sourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-LINEAGE-SOURCE" sourceSecurityId "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-LINEAGE-SOURCE" sourceSecurityId 50.0)] + [] + [] + balanceResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-ACC-STOCK-LINEAGE" "SC_COMMON" 2.0 1.0 splitDate) + , stockSuccessorIssuance + "TX-ACC-STOCK-LINEAGE-BALANCE" + successorSecurityId + "SH-1" + "SC_COMMON" + splitDate + 60.0 + , CT.OcfCreateStockCancellation + ((stockCancellationOn "CANCEL-ACC-STOCK-LINEAGE" sourceSecurityId 40.0 splitDate) with + balance_security_id = Some successorSecurityId) + , CT.OcfCreateStockCancellation + (stockCancellationOn "CANCEL-ACC-STOCK-LINEAGE-SUCCESSOR" successorSecurityId 10.0 splitDate) + ] + [] + [] + exactResult <- applyUpdate + issuer + balanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-EXACT" successorSecurityId 60.0) with + date = postSplitAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-LINEAGE-BALANCE-OVER" successorSecurityId 1.0) with + date = postSplitAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_EquityCompensationBalanceSuccessorCarriesAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-EC-LINEAGE-SOURCE" + successorSecurityId = "SEC-ACC-EC-LINEAGE-BALANCE" + chainedSuccessorSecurityId = "SEC-ACC-EC-LINEAGE-BALANCE-2" + sourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-LINEAGE-SOURCE" sourceSecurityId "SH-1") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EC-LINEAGE-SOURCE" sourceSecurityId 50.0)] + [] + [] + balanceResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ equityCompensationSuccessorIssuance + "TX-ACC-EC-LINEAGE-BALANCE" + successorSecurityId + "SH-1" + postSplitAccelerationDate + 60.0 + None + , CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "CANCEL-ACC-EC-LINEAGE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = Some successorSecurityId + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + balanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-LINEAGE-BALANCE-EXACT" successorSecurityId 30.0) with + date = successorAccelerationDate)] + [] + [] + chainedResult <- applyUpdate + issuer + exactResult.updatedCapTableCid + [ equityCompensationSuccessorIssuance + "TX-ACC-EC-LINEAGE-BALANCE-2" + chainedSuccessorSecurityId + "SH-1" + chainedBalanceDate + 30.0 + None + , CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "CANCEL-ACC-EC-LINEAGE-2" + date = chainedBalanceDate + security_id = successorSecurityId + quantity = 30.0 + reason_text = "Second partial cancellation" + balance_security_id = Some chainedSuccessorSecurityId + comments = [] + ] + [] + [] + submitMustFail issuer do + exerciseCmd chainedResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-LINEAGE-BALANCE-OVER" chainedSuccessorSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_KnownWarrantBalanceSuccessorCarriesAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-WARRANT-LINEAGE-SOURCE" + successorSecurityId = "SEC-ACC-WARRANT-LINEAGE-BALANCE" + sourceIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-LINEAGE-SOURCE" sourceSecurityId "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + successorIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-LINEAGE-BALANCE" successorSecurityId "SH-1") with + date = postSplitAccelerationDate + quantity = Some 60.0 + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateWarrantIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-LINEAGE-SOURCE" sourceSecurityId 50.0)] + [] + [] + balanceResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ CT.OcfCreateWarrantIssuance successorIssuance + , CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "CANCEL-ACC-WARRANT-LINEAGE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = Some successorSecurityId + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + balanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-LINEAGE-BALANCE-EXACT" successorSecurityId 30.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-LINEAGE-BALANCE-OVER" successorSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockTransferResultCarriesAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-STOCK-TRANSFER-SOURCE" + resultSecurityId = "SEC-ACC-STOCK-TRANSFER-RESULT" + sourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-TRANSFER-SOURCE" sourceSecurityId "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-TRANSFER-SOURCE" sourceSecurityId 60.0)] + [] + [] + transferResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-STOCK-TRANSFER-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TRANSFER-ACC-STOCK-RESULT" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 100.0 + resulting_security_ids = [resultSecurityId] + balance_security_id = None + consideration_text = None + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + transferResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-TRANSFER-RESULT-EXACT" resultSecurityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-TRANSFER-RESULT-OVER" resultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockReissuanceResultCarriesAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-STOCK-REISSUE-SOURCE" + resultSecurityId = "SEC-ACC-STOCK-REISSUE-RESULT" + sourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-REISSUE-SOURCE" sourceSecurityId "SH-1" "SC_COMMON") with + quantity = 100.0 + reissuance = StockReissuanceOcfData with + id = "REISSUE-ACC-STOCK-RESULT" + date = postSplitAccelerationDate + security_id = sourceSecurityId + resulting_security_ids = [resultSecurityId] + split_transaction_id = None + reason_text = Some "Replace certificate" + comments = [] + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-REISSUE-SOURCE" sourceSecurityId 60.0)] + [] + [] + reissuanceResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-STOCK-REISSUE-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateStockReissuance reissuance + ] + [] + [] + exactResult <- applyUpdate + issuer + reissuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REISSUE-RESULT-EXACT" resultSecurityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REISSUE-RESULT-OVER" resultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockConsolidationAggregatesSourceAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let firstSourceSecurityId = "SEC-ACC-STOCK-CONSOLIDATE-SOURCE-A" + secondSourceSecurityId = "SEC-ACC-STOCK-CONSOLIDATE-SOURCE-B" + resultSecurityId = "SEC-ACC-STOCK-CONSOLIDATE-RESULT" + firstSourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-CONSOLIDATE-SOURCE-A" firstSourceSecurityId "SH-1" "SC_COMMON") with + quantity = 40.0 + secondSourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-CONSOLIDATE-SOURCE-B" secondSourceSecurityId "SH-1" "SC_COMMON") with + quantity = 60.0 + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance firstSourceIssuance + , CT.OcfCreateStockIssuance secondSourceIssuance + ] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-CONSOLIDATE-SOURCE-A" firstSourceSecurityId 20.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-CONSOLIDATE-SOURCE-B" secondSourceSecurityId 30.0) + ] + [] + [] + consolidationResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-STOCK-CONSOLIDATE-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateStockConsolidation StockConsolidationOcfData with + id = "CONSOLIDATE-ACC-STOCK-RESULT" + date = postSplitAccelerationDate + security_ids = [firstSourceSecurityId, secondSourceSecurityId] + resulting_security_id = resultSecurityId + reason_text = Some "Combine certificates" + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + consolidationResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-CONSOLIDATE-RESULT-EXACT" resultSecurityId 50.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-CONSOLIDATE-RESULT-OVER" resultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockConversionResultCarriesAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + capTableCid <- addPreferredStockClass issuer capTableCid + let sourceSecurityId = "SEC-ACC-STOCK-CONVERSION-SOURCE" + resultSecurityId = "SEC-ACC-STOCK-CONVERSION-RESULT" + sourceIssuance = + (defaultStockIssuance "TX-ACC-STOCK-CONVERSION-SOURCE" sourceSecurityId "SH-1" "SC_PREF") with + quantity = 100.0 + date = firstVestingDate + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-CONVERSION-SOURCE" sourceSecurityId 60.0)] + [] + [] + conversionResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-STOCK-CONVERSION-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateStockConversion StockConversionOcfData with + id = "CONVERT-ACC-STOCK-RESULT" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity_converted = 100.0 + resulting_security_ids = [resultSecurityId] + balance_security_id = None + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + conversionResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-CONVERSION-RESULT-EXACT" resultSecurityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-CONVERSION-RESULT-OVER" resultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_NonStockTransferResultsCarryAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let equitySourceSecurityId = "SEC-ACC-EC-TRANSFER-SOURCE" + equityResultSecurityId = "SEC-ACC-EC-TRANSFER-RESULT" + warrantSourceSecurityId = "SEC-ACC-WARRANT-TRANSFER-SOURCE" + warrantResultSecurityId = "SEC-ACC-WARRANT-TRANSFER-RESULT" + equitySourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-TRANSFER-SOURCE" equitySourceSecurityId "SH-1") with + quantity = 100.0 + warrantSourceIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-TRANSFER-SOURCE" warrantSourceSecurityId "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + warrantResultIssuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-TRANSFER-RESULT" warrantResultSecurityId "SH-1") with + date = postSplitAccelerationDate + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateEquityCompensationIssuance equitySourceIssuance + , CT.OcfCreateWarrantIssuance warrantSourceIssuance + ] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-EC-TRANSFER-SOURCE" equitySourceSecurityId 50.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-TRANSFER-SOURCE" warrantSourceSecurityId 50.0) + ] + [] + [] + transferResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ equityCompensationSuccessorIssuance + "TX-ACC-EC-TRANSFER-RESULT" + equityResultSecurityId + "SH-1" + postSplitAccelerationDate + 100.0 + None + , CT.OcfCreateWarrantIssuance warrantResultIssuance + , CT.OcfCreateEquityCompensationTransfer EquityCompensationTransferOcfData with + id = "TRANSFER-ACC-EC-RESULT" + date = postSplitAccelerationDate + security_id = equitySourceSecurityId + quantity = 100.0 + resulting_security_ids = [equityResultSecurityId] + balance_security_id = None + consideration_text = None + comments = [] + , CT.OcfCreateWarrantTransfer WarrantTransferOcfData with + id = "TRANSFER-ACC-WARRANT-RESULT" + date = postSplitAccelerationDate + security_id = warrantSourceSecurityId + quantity = 100.0 + resulting_security_ids = [warrantResultSecurityId] + balance_security_id = None + consideration_text = None + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + transferResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-TRANSFER-RESULT-EXACT" equityResultSecurityId 50.0) with + date = successorAccelerationDate) + , CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-TRANSFER-RESULT-EXACT" warrantResultSecurityId 50.0) with + date = successorAccelerationDate) + ] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-TRANSFER-RESULT-OVER" equityResultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-TRANSFER-RESULT-OVER" warrantResultSecurityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_StockReductionConsumesPriorAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SEC-ACC-STOCK-REDUCTION" + issuance = + (defaultStockIssuance "TX-ACC-STOCK-REDUCTION" securityId "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-STOCK-REDUCTION-BEFORE" securityId 60.0)] + [] + [] + reductionResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-ACC-STOCK-REDUCTION" securityId 40.0 postSplitAccelerationDate)] + [] + [] + exactResult <- applyUpdate + issuer + reductionResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-EXACT" securityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-STOCK-REDUCTION-OVER" securityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_EquityCompensationReductionConsumesPriorAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SEC-ACC-EC-REDUCTION" + issuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-REDUCTION" securityId "SH-1") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EC-REDUCTION-BEFORE" securityId 60.0)] + [] + [] + reductionResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "CANCEL-ACC-EC-REDUCTION" + date = postSplitAccelerationDate + security_id = securityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = None + comments = []] + [] + [] + exactResult <- applyUpdate + issuer + reductionResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-EXACT" securityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EC-REDUCTION-OVER" securityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_KnownWarrantReductionConsumesPriorAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SEC-ACC-WARRANT-REDUCTION" + issuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-REDUCTION" securityId "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateWarrantIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-REDUCTION-BEFORE" securityId 60.0)] + [] + [] + reductionResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [CT.OcfCreateWarrantCancellation WarrantCancellationOcfData with + id = "CANCEL-ACC-WARRANT-REDUCTION" + date = postSplitAccelerationDate + security_id = securityId + quantity = 40.0 + reason_text = "Partial cancellation" + balance_security_id = None + comments = []] + [] + [] + exactResult <- applyUpdate + issuer + reductionResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-EXACT" securityId 40.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-REDUCTION-OVER" securityId 1.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_AccelerationCannotExceedLiveStockBalance = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-ACC-LIVE-BALANCE" "SEC-ACC-LIVE-BALANCE" "SH-1" "SC_COMMON") with + quantity = 100.0 + balanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance issuance + , CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-ACC-LIVE-BALANCE" issuance.security_id 80.0 firstVestingDate) + ] + [] + [] + submitMustFail issuer do + exerciseCmd balanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-LIVE-BALANCE" issuance.security_id 50.0)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_LaterBalanceReductionDoesNotRetroactivelyInvalidateAcceleration = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-ACC-BEFORE-REDUCTION" "SEC-ACC-BEFORE-REDUCTION" "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-BEFORE-REDUCTION" issuance.security_id 60.0)] + [] + [] + _ <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [CT.OcfCreateStockCancellation (stockCancellationOn "CANCEL-AFTER-ACCELERATION" issuance.security_id 50.0 secondVestingDate)] + [] + [] + pure () + +testVestingQuantityIntegrity_StockCumulativeAccelerationOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultStockIssuance "TX-ACC-STOCK-OVER" "SEC-ACC-STOCK-OVER" "SH-1" "SC_COMMON") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + assertAdditionalAccelerationRejected issuer issuanceResult.updatedCapTableCid issuance.security_id 60.0 50.0 + +testVestingQuantityIntegrity_EquityCompensationCumulativeAccelerationOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultEquityCompensationIssuance "TX-ACC-EC-OVER" "SEC-ACC-EC-OVER" "SH-1") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance issuance] [] [] + assertAdditionalAccelerationRejected issuer issuanceResult.updatedCapTableCid issuance.security_id 60.0 50.0 + +testVestingQuantityIntegrity_KnownWarrantCumulativeAccelerationOversubscriptionRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultWarrantIssuance "TX-ACC-WARRANT-OVER" "SEC-ACC-WARRANT-OVER" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateWarrantIssuance issuance] [] [] + assertAdditionalAccelerationRejected issuer issuanceResult.updatedCapTableCid issuance.security_id 60.0 50.0 + +testVestingQuantityIntegrity_AccelerationOversubscriptionEditRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultEquityCompensationIssuance "TX-ACC-EDIT" "SEC-ACC-EDIT" "SH-1") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-EDIT-A" issuance.security_id 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-EDIT-B" issuance.security_id 30.0) + ] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditVestingAcceleration (acceleration "ACC-EDIT-A" issuance.security_id 80.0)] + deletes = [] + +testVestingQuantityIntegrity_LoweringKnownQuantityBelowAccelerationsRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultWarrantIssuance "TX-ACC-LOWER" "SEC-ACC-LOWER" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateWarrantIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-LOWER" issuance.security_id 80.0)] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditWarrantIssuance (issuance with quantity = Some 70.0)] + deletes = [] + +testVestingQuantityIntegrity_UnknownWarrantBecomingKnownBelowAccelerationsRejected = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultWarrantIssuance "TX-ACC-UNKNOWN-TO-KNOWN" "SEC-ACC-UNKNOWN-TO-KNOWN" "SH-1") with + quantity = None + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateWarrantIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-UNKNOWN-TO-KNOWN" issuance.security_id 1000.0)] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditWarrantIssuance (issuance with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed)] + deletes = [] + +testVestingQuantityIntegrity_DeletingAccelerationReleasesCapacity = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = + (defaultEquityCompensationIssuance "TX-ACC-DELETE" "SEC-ACC-DELETE" "SH-1") with + quantity = 100.0 + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-DELETE-OLD" issuance.security_id 80.0)] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-DELETE-OVER" issuance.security_id 30.0)] + edits = [] + deletes = [] + _ <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-DELETE-REPLACEMENT" issuance.security_id 100.0)] + [] + [CT.OcfDeleteVestingAcceleration "ACC-DELETE-OLD"] + pure () + +testVestingQuantityIntegrity_FullyVestedSecuritiesCannotBeAccelerated = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockIssuance = + TestHelpers.defaultStockIssuance "TX-ACC-FULLY-VESTED-STOCK" "SEC-ACC-FULLY-VESTED-STOCK" "SH-1" "SC_COMMON" + equityCompensationIssuance = + TestHelpers.defaultEquityCompensationIssuance "TX-ACC-FULLY-VESTED-EC" "SEC-ACC-FULLY-VESTED-EC" "SH-1" + warrantIssuance = + (TestHelpers.defaultWarrantIssuance "TX-ACC-FULLY-VESTED-WARRANT" "SEC-ACC-FULLY-VESTED-WARRANT" "SH-1") with + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance stockIssuance + , CT.OcfCreateEquityCompensationIssuance equityCompensationIssuance + , CT.OcfCreateWarrantIssuance warrantIssuance + ] + [] + [] + submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-FULLY-VESTED-STOCK" stockIssuance.security_id 1.0)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-FULLY-VESTED-EC" equityCompensationIssuance.security_id 1.0)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration "ACC-FULLY-VESTED-WARRANT" warrantIssuance.security_id 1.0)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_RemovingScheduleRequiresRemovingAccelerations = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let issuance = defaultStockIssuance "TX-ACC-SCHEDULE-REMOVAL" "SEC-ACC-SCHEDULE-REMOVAL" "SH-1" "SC_COMMON" + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-SCHEDULE-REMOVAL" issuance.security_id 50.0)] + [] + [] + let fullyVestedIssuance = issuance with + vesting_terms_id = None + vestings = [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockIssuance fullyVestedIssuance] + deletes = [] + _ <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [] + [CT.OcfEditStockIssuance fullyVestedIssuance] + [CT.OcfDeleteVestingAcceleration "ACC-SCHEDULE-REMOVAL"] + pure () + +testVestingQuantityIntegrity_EarlyExerciseRetainsScheduleAndAccelerationHistory = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-EARLY-EXERCISE-SOURCE" + invalidResultSecurityId = "SEC-ACC-EARLY-EXERCISE-FULLY-VESTED" + mismatchedResultSecurityId = "SEC-ACC-EARLY-EXERCISE-MISMATCHED-SCHEDULE" + resultSecurityId = "SEC-ACC-EARLY-EXERCISE-RESULT" + sourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-EARLY-EXERCISE-SOURCE" sourceSecurityId "SH-1") with + early_exercisable = Some True + stock_class_id = Some "SC_COMMON" + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateVestingTerms alternateAccelerationVestingTerms + , CT.OcfCreateEquityCompensationIssuance sourceIssuance + ] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EARLY-EXERCISE-SOURCE" sourceSecurityId 60.0)] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-EXERCISE-UNMATERIALIZED" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [] + comments = []] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ fullyVestedStockSuccessorIssuance + "TX-ACC-EARLY-EXERCISE-FULLY-VESTED" + invalidResultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 40.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-EXERCISE-INVALID" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [invalidResultSecurityId] + comments = [] + ] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance ((defaultStockIssuance + "TX-ACC-EARLY-EXERCISE-MISMATCHED-SCHEDULE" + mismatchedResultSecurityId + "SH-1" + "SC_COMMON") with + date = postSplitAccelerationDate + quantity = 40.0 + vesting_terms_id = Some alternateAccelerationVestingTermsId) + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-EXERCISE-MISMATCHED-SCHEDULE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [mismatchedResultSecurityId] + comments = [] + ] + edits = [] + deletes = [] + exerciseResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-EARLY-EXERCISE-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 40.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-EXERCISE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [resultSecurityId] + comments = [] + ] + [] + [] + submitMustFail issuer do + exerciseCmd exerciseResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-EXERCISE-SOURCE-OVER" sourceSecurityId 25.0) with + date = successorAccelerationDate)] + edits = [] + deletes = [] + sourceExactResult <- applyUpdate + issuer + exerciseResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-EXERCISE-SOURCE-EXACT" sourceSecurityId 24.0) with + date = successorAccelerationDate)] + [] + [] + exactResult <- applyUpdate + issuer + sourceExactResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-EXERCISE-RESULT-EXACT" resultSecurityId 16.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-EXERCISE-RESULT-OVER" resultSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_EarlyExerciseRetainsInlineScheduleProRata = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-EARLY-INLINE-SOURCE" + invalidResultSecurityId = "SEC-ACC-EARLY-INLINE-INVALID" + resultSecurityId = "SEC-ACC-EARLY-INLINE-RESULT" + sourceIssuance = + (TestHelpers.defaultEquityCompensationIssuance "TX-ACC-EARLY-INLINE-SOURCE" sourceSecurityId "SH-1") with + early_exercisable = Some True + stock_class_id = Some "SC_COMMON" + vestings = [vestingOn firstVestingDate 40.0, vestingOn secondVestingDate 60.0] + invalidResultIssuance = + (TestHelpers.defaultStockIssuance "TX-ACC-EARLY-INLINE-INVALID" invalidResultSecurityId "SH-1" "SC_COMMON") with + date = earlyExerciseDate + quantity = 40.0 + vestings = [vestingOn firstVestingDate 20.0, vestingOn secondVestingDate 20.0] + resultIssuance = + (TestHelpers.defaultStockIssuance "TX-ACC-EARLY-INLINE-RESULT" resultSecurityId "SH-1" "SC_COMMON") with + date = earlyExerciseDate + quantity = 40.0 + vestings = [vestingOn firstVestingDate 16.0, vestingOn secondVestingDate 24.0] + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-EARLY-INLINE-SOURCE" sourceSecurityId 60.0)] + [] + [] + submitMustFail issuer do + exerciseCmd accelerationResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockIssuance invalidResultIssuance + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-INLINE-INVALID" + date = earlyExerciseDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [invalidResultSecurityId] + comments = [] + ] + edits = [] + deletes = [] + exerciseResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EARLY-INLINE" + date = earlyExerciseDate + security_id = sourceSecurityId + quantity = 40.0 + consideration_text = None + resulting_security_ids = [resultSecurityId] + comments = [] + ] + [] + [] + submitMustFail issuer do + exerciseCmd exerciseResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-INLINE-SOURCE-OVER" sourceSecurityId 1.0) with + date = earlyExerciseDate)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd exerciseResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EARLY-INLINE-RESULT-OVER" resultSecurityId 1.0) with + date = earlyExerciseDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_FullyVestedEarlyExercisableGrantCanSettleWithoutSchedule = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-FULLY-VESTED-EARLY-FLAG" + sourceIssuance = + (TestHelpers.defaultEquityCompensationIssuance "TX-ACC-FULLY-VESTED-EARLY-FLAG" sourceSecurityId "SH-1") with + early_exercisable = Some True + stock_class_id = Some "SC_COMMON" + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance sourceIssuance] [] [] + _ <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-FULLY-VESTED-EARLY-FLAG-EXERCISE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 100.0 + consideration_text = None + resulting_security_ids = [] + comments = []] + [] + [] + pure () + +testVestingQuantityIntegrity_InlineAccelerationsCannotExceedFutureSchedule = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let stockIssuance = + (defaultStockIssuance "TX-ACC-INLINE-FUTURE-STOCK" "SEC-ACC-INLINE-FUTURE-STOCK" "SH-1" "SC_COMMON") with + vestings = [vestingOn firstVestingDate 40.0, vestingOn secondVestingDate 60.0] + equityCompensationIssuance = + (defaultEquityCompensationIssuance "TX-ACC-INLINE-FUTURE-EC" "SEC-ACC-INLINE-FUTURE-EC" "SH-1") with + stock_class_id = Some "SC_COMMON" + vestings = [vestingOn firstVestingDate 40.0, vestingOn secondVestingDate 60.0] + warrantIssuance = + (defaultWarrantIssuance "TX-ACC-INLINE-FUTURE-WARRANT" "SEC-ACC-INLINE-FUTURE-WARRANT" "SH-1") with + quantity = Some 100.0 + quantity_source = Some OcfQuantityInstrumentFixed + vestings = [vestingOn firstVestingDate 40.0, vestingOn secondVestingDate 60.0] + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockIssuance stockIssuance + , CT.OcfCreateEquityCompensationIssuance equityCompensationIssuance + , CT.OcfCreateWarrantIssuance warrantIssuance + ] + [] + [] + mapA_ + (\securityId -> submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration (acceleration ("ACC-INLINE-FUTURE-OVER-" <> securityId) securityId 61.0)] + edits = [] + deletes = []) + [stockIssuance.security_id, equityCompensationIssuance.security_id, warrantIssuance.security_id] + _ <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-INLINE-FUTURE-STOCK-EXACT" stockIssuance.security_id 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-INLINE-FUTURE-EC-EXACT" equityCompensationIssuance.security_id 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-INLINE-FUTURE-WARRANT-EXACT" warrantIssuance.security_id 60.0) + ] + [] + [] + pure () + +testVestingQuantityIntegrity_StockInlineCapAppliesSameDaySplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let securityId = "SEC-ACC-INLINE-SAME-DAY-SPLIT" + issuance = + (TestHelpers.defaultStockIssuance "TX-ACC-INLINE-SAME-DAY-SPLIT" securityId "SH-1" "SC_COMMON") with + date = splitDate + quantity = 100.0 + vestings = [vestingOn successorAccelerationDate 100.0] + sameDayAcceleration quantity = + (acceleration "ACC-INLINE-SAME-DAY-SPLIT" securityId quantity) with + date = splitDate + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance issuance] [] [] + submitMustFail issuer do + exerciseCmd issuanceResult.updatedCapTableCid CT.UpdateCapTable with + creates = + [ CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-ACC-INLINE-SAME-DAY-OVER" "SC_COMMON" 2.0 1.0 splitDate) + , CT.OcfCreateVestingAcceleration (sameDayAcceleration 201.0) + ] + edits = [] + deletes = [] + _ <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-ACC-INLINE-SAME-DAY-EXACT" "SC_COMMON" 2.0 1.0 splitDate) + , CT.OcfCreateVestingAcceleration (sameDayAcceleration 200.0) + ] + [] + [] + pure () + +testVestingQuantityIntegrity_InheritedOnlySuccessorCannotDropSchedule = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-INHERITED-ONLY-SOURCE" + resultSecurityId = "SEC-ACC-INHERITED-ONLY-RESULT" + sourceIssuance = defaultStockIssuance "TX-ACC-INHERITED-ONLY-SOURCE" sourceSecurityId "SH-1" "SC_COMMON" + resultIssuance = (defaultStockIssuance "TX-ACC-INHERITED-ONLY-RESULT" resultSecurityId "SH-1" "SC_COMMON") with + date = postSplitAccelerationDate + fullyVestedResultIssuance = resultIssuance with + vesting_terms_id = None + vestings = [] + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateStockIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-INHERITED-ONLY-SOURCE" sourceSecurityId 80.0)] + [] + [] + transferResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ CT.OcfCreateStockIssuance resultIssuance + , CT.OcfCreateStockTransfer StockTransferOcfData with + id = "TRANSFER-ACC-INHERITED-ONLY" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 100.0 + resulting_security_ids = [resultSecurityId] + balance_security_id = None + consideration_text = None + comments = [] + ] + [] + [] + submitMustFail issuer do + exerciseCmd transferResult.updatedCapTableCid CT.UpdateCapTable with + creates = [] + edits = [CT.OcfEditStockIssuance fullyVestedResultIssuance] + deletes = [] + +testVestingQuantityIntegrity_FullyVestedExerciseReleaseAndWarrantResultsCannotBeAccelerated = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let exerciseSourceSecurityId = "SEC-ACC-EXERCISE-SOURCE" + releaseSourceSecurityId = "SEC-ACC-RELEASE-SOURCE" + warrantSourceSecurityId = "SEC-ACC-WARRANT-EXERCISE-SOURCE" + exerciseResultSecurityId = "SEC-ACC-EXERCISE-RESULT" + releaseResultSecurityId = "SEC-ACC-RELEASE-RESULT" + warrantResultSecurityId = "SEC-ACC-WARRANT-EXERCISE-RESULT" + exerciseSource = + (defaultEquityCompensationIssuance "TX-ACC-EXERCISE-SOURCE" exerciseSourceSecurityId "SH-1") with + early_exercisable = Some False + stock_class_id = Some "SC_COMMON" + releaseSource = + (defaultEquityCompensationIssuance "TX-ACC-RELEASE-SOURCE" releaseSourceSecurityId "SH-1") with + compensation_type = OcfCompensationTypeRSU + exercise_price = None + stock_class_id = Some "SC_COMMON" + warrantSource = + (defaultWarrantIssuance "TX-ACC-WARRANT-EXERCISE-SOURCE" warrantSourceSecurityId "SH-1") with + exercise_triggers = [warrantExerciseTrigger 100.0] + quantity_source = Some OcfQuantityInstrumentFixed + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateEquityCompensationIssuance exerciseSource + , CT.OcfCreateEquityCompensationIssuance releaseSource + , CT.OcfCreateWarrantIssuance warrantSource + ] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [ CT.OcfCreateVestingAcceleration (acceleration "ACC-EXERCISE-SOURCE" exerciseSourceSecurityId 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-RELEASE-SOURCE" releaseSourceSecurityId 60.0) + , CT.OcfCreateVestingAcceleration (acceleration "ACC-WARRANT-EXERCISE-SOURCE" warrantSourceSecurityId 60.0) + ] + [] + [] + result <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ fullyVestedStockSuccessorIssuance + "TX-ACC-EXERCISE-RESULT" + exerciseResultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , fullyVestedStockSuccessorIssuance + "TX-ACC-RELEASE-RESULT" + releaseResultSecurityId + "SH-1" + "SC_COMMON" + successorAccelerationDate + 100.0 + , fullyVestedStockSuccessorIssuance + "TX-ACC-WARRANT-EXERCISE-RESULT" + warrantResultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-EXERCISE" + date = postSplitAccelerationDate + security_id = exerciseSourceSecurityId + quantity = 100.0 + consideration_text = None + resulting_security_ids = [exerciseResultSecurityId] + comments = [] + , CT.OcfCreateEquityCompensationRelease EquityCompensationReleaseOcfData with + id = "TX-ACC-RELEASE" + date = postSplitAccelerationDate + security_id = releaseSourceSecurityId + settlement_date = successorAccelerationDate + release_price = OcfMonetary with amount = 1.0; currency = "USD" + quantity = 100.0 + resulting_security_ids = [releaseResultSecurityId] + consideration_text = None + comments = [] + , CT.OcfCreateWarrantExercise WarrantExerciseOcfData with + id = "TX-ACC-WARRANT-EXERCISE" + date = postSplitAccelerationDate + security_id = warrantSourceSecurityId + trigger_id = "ELECTIVE_AT_WILL" + quantity = Some 100.0 + resulting_security_ids = [warrantResultSecurityId] + comments = [] + consideration_text = None + ] + [] + [] + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-EXERCISE-RESULT" exerciseResultSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-RELEASE-RESULT" releaseResultSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + submitMustFail issuer do + exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-WARRANT-EXERCISE-RESULT" warrantResultSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_PlanLinkedSuccessorIsNotDoubleScaledAcrossSameDaySplit = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let plan = defaultStockPlan "PLAN-ACC-SAME-DAY-SUCCESSOR" "SC_COMMON" + sourceSecurityId = "SEC-ACC-PLAN-SAME-DAY-SOURCE" + successorSecurityId = "SEC-ACC-PLAN-SAME-DAY-SUCCESSOR" + sourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-PLAN-SAME-DAY-SOURCE" sourceSecurityId "SH-1") with + quantity = 100.0 + stock_plan_id = Some plan.id + successorIssuance = + (defaultEquityCompensationIssuance "TX-ACC-PLAN-SAME-DAY-SUCCESSOR" successorSecurityId "SH-1") with + date = splitDate + quantity = 20.0 + stock_plan_id = Some plan.id + issuanceResult <- applyUpdate + issuer + capTableCid + [ CT.OcfCreateStockPlan plan + , CT.OcfCreateEquityCompensationIssuance sourceIssuance + ] + [] + [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-PLAN-SAME-DAY-SOURCE" sourceSecurityId 50.0)] + [] + [] + successorResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ CT.OcfCreateStockClassSplit (stockClassSplitOn "SPLIT-ACC-PLAN-SAME-DAY" "SC_COMMON" 2.0 1.0 splitDate) + , CT.OcfCreateEquityCompensationIssuance successorIssuance + , CT.OcfCreateEquityCompensationCancellation EquityCompensationCancellationOcfData with + id = "CANCEL-ACC-PLAN-SAME-DAY" + date = splitDate + security_id = sourceSecurityId + quantity = 80.0 + reason_text = "Partial cancellation after split" + balance_security_id = Some successorSecurityId + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + successorResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-PLAN-SAME-DAY-SUCCESSOR-EXACT" successorSecurityId 20.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-PLAN-SAME-DAY-SUCCESSOR-OVER" successorSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] + +testVestingQuantityIntegrity_ScheduledOrdinaryExerciseResultHasIndependentAccelerationHistory = script do + TestOcp{issuer, cap_table} <- setupTestOcp + capTableCid <- addPrerequisites issuer cap_table + let sourceSecurityId = "SEC-ACC-ORDINARY-EXERCISE-SOURCE" + resultSecurityId = "SEC-ACC-ORDINARY-EXERCISE-RESULT" + sourceIssuance = + (defaultEquityCompensationIssuance "TX-ACC-ORDINARY-EXERCISE-SOURCE" sourceSecurityId "SH-1") with + early_exercisable = Some False + stock_class_id = Some "SC_COMMON" + issuanceResult <- applyUpdate issuer capTableCid [CT.OcfCreateEquityCompensationIssuance sourceIssuance] [] [] + accelerationResult <- applyUpdate + issuer + issuanceResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration (acceleration "ACC-ORDINARY-EXERCISE-SOURCE" sourceSecurityId 60.0)] + [] + [] + exerciseResult <- applyUpdate + issuer + accelerationResult.updatedCapTableCid + [ stockSuccessorIssuance + "TX-ACC-ORDINARY-EXERCISE-RESULT" + resultSecurityId + "SH-1" + "SC_COMMON" + postSplitAccelerationDate + 100.0 + , CT.OcfCreateEquityCompensationExercise EquityCompensationExerciseOcfData with + id = "TX-ACC-ORDINARY-EXERCISE" + date = postSplitAccelerationDate + security_id = sourceSecurityId + quantity = 100.0 + consideration_text = None + resulting_security_ids = [resultSecurityId] + comments = [] + ] + [] + [] + exactResult <- applyUpdate + issuer + exerciseResult.updatedCapTableCid + [CT.OcfCreateVestingAcceleration ((acceleration "ACC-ORDINARY-EXERCISE-RESULT-EXACT" resultSecurityId 100.0) with + date = successorAccelerationDate)] + [] + [] + submitMustFail issuer do + exerciseCmd exactResult.updatedCapTableCid CT.UpdateCapTable with + creates = [CT.OcfCreateVestingAcceleration ((acceleration "ACC-ORDINARY-EXERCISE-RESULT-OVER" resultSecurityId 1.0) with + date = finalSuccessorAccelerationDate)] + edits = [] + deletes = [] diff --git a/dars/OpenCapTable-v34/0.0.17/OpenCapTable-v34.dar b/dars/OpenCapTable-v34/0.0.17/OpenCapTable-v34.dar new file mode 100644 index 00000000..e883e710 --- /dev/null +++ b/dars/OpenCapTable-v34/0.0.17/OpenCapTable-v34.dar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8ceb6a778f7fd9a4ce323b3588711ddd180a49088f3e372e6ace0d4b72ae4fe +size 3439832 diff --git a/dars/dars.lock b/dars/dars.lock index 6a5b4a37..ffc9e98d 100644 --- a/dars/dars.lock +++ b/dars/dars.lock @@ -60,6 +60,13 @@ "uploadedAt": "2026-07-11T17:31:18.000Z", "networks": [] }, + "OpenCapTable-v34/0.0.17/OpenCapTable-v34.dar": { + "sha256": "a8ceb6a778f7fd9a4ce323b3588711ddd180a49088f3e372e6ace0d4b72ae4fe", + "size": 3439832, + "sdkVersion": "3.4.10", + "uploadedAt": "2026-07-11T23:54:52.123Z", + "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 474ea856..2bd66162 100644 --- a/scripts/codegen/templates/CapTable.daml.template +++ b/scripts/codegen/templates/CapTable.daml.template @@ -15,14 +15,14 @@ import qualified DA.Map as Map import DA.List (sortOn, unique) import DA.Set (Set) import qualified DA.Set as Set -import DA.Action (foldlA, when) +import DA.Action (foldlA, unless, when) import DA.Foldable (mapA_) import DA.Optional (fromOptional, isNone, isSome) import Fairmint.OpenCapTable.Types.Core (Context) import Fairmint.OpenCapTable.Types.Conversion (OcfAnyConversionRight(..), OcfCapitalizationDefinition, OcfConversionMechanism(..), OcfConversionTrigger(..), OcfConversionTriggerType(..), OcfConvertibleConversionMechanism(..), OcfConvertibleConversionRight(..), OcfStockClassConversionRight(..), OcfWarrantConversionMechanism(..), OcfWarrantConversionRight(..), validateOcfInterestRateSchedule) -import Fairmint.OpenCapTable.Types.Stock (OcfAuthorizedShares(..), OcfInitialSharesAuthorized(..), OcfRatio(..), OcfShareNumberRange(..), shareNumberRangeCoveredByRanges, shareNumberRangesOverlap) -import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..)) +import Fairmint.OpenCapTable.Types.Stock (OcfAuthorizedShares(..), OcfInitialSharesAuthorized(..), OcfRatio(..), OcfShareNumberRange(..), ocfQuantityUpperBound, shareNumberRangeCoveredByRanges, shareNumberRangesOverlap) +import Fairmint.OpenCapTable.Types.Vesting (OcfCompensationType(..), OcfVesting(..), validateOcfVestingsWithinQuantity) 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(..)) @@ -208,8 +208,36 @@ data SecurityQuantityEvent | SecurityQuantityRetraction with event_date: Time event_priority: Int + | SecurityQuantityEarlyExercise with + event_date: Time + event_priority: Int + quantity: Decimal + deriving (Eq, Show) + +data VestingAccelerationTimelineEvent + = VestingAccelerationQuantity with + event_sort_key: (Time, Int, Int) + quantity: Decimal + report_total: Bool + | VestingAccelerationSplit with + event_sort_key: (Time, Int, Int) + ratio: OcfRatio + | VestingAccelerationReduction with + event_sort_key: (Time, Int, Int) + quantity_delta: Decimal + | VestingAccelerationRetirement with + event_sort_key: (Time, Int, Int) deriving (Eq, Show) +data VestingAccelerationPredecessor = VestingAccelerationPredecessor with + source_security_id: Text + source_event_sort_key: (Time, Int, Int) + successor_seed_sort_key: (Time, Int, Int) + successor_fraction: Decimal + deriving (Eq, Show) + +type VestingAccelerationPredecessorIndex = Map Text [VestingAccelerationPredecessor] + data NonStockSecurityBalanceEventIndex = NonStockSecurityBalanceEventIndex with warrant_events_by_security_id: Map Text [SecurityQuantityEvent] equity_compensation_events_by_security_id: Map Text [SecurityQuantityEvent] @@ -822,6 +850,10 @@ equityCompensationExerciseOutputShareLot : EquityCompensationExerciseOcfData -> Update (Optional StockClassShareLot) equityCompensationExerciseOutputShareLot maps issuanceData exerciseData = do + when (hasEarlyExerciseVestingContinuity issuanceData) do + assertMsg + ("Early equity compensation exercise must materialize resulting stock so its vesting schedule remains in effect: " <> exerciseData.id) + False stockClassIdOpt <- equityCompensationOutputStockClassId maps ("Equity compensation exercise " <> exerciseData.id) @@ -899,6 +931,47 @@ validateStockResultIssuances maps label expectedDate expectedStockClassIdOpt exp (actualQuantity == expectedQuantity) None -> pure () +hasDeclaredVestingSchedule : Optional Text -> [OcfVesting] -> Bool +hasDeclaredVestingSchedule vestingTermsId vestings = + isSome vestingTermsId || not (null vestings) + +hasEarlyExerciseVestingContinuity : EquityCompensationIssuanceOcfData -> Bool +hasEarlyExerciseVestingContinuity issuanceData = + issuanceData.early_exercisable == Some True && + hasDeclaredVestingSchedule issuanceData.vesting_terms_id issuanceData.vestings + +scaledEarlyExerciseVestings : Decimal -> Decimal -> [OcfVesting] -> [OcfVesting] +scaledEarlyExerciseVestings sourceQuantity resultQuantity = + map (\vesting -> vesting with amount = vesting.amount * resultQuantity / sourceQuantity) + +validateEarlyExerciseResultVestingSchedules : + CapTableMaps -> + Text -> + EquityCompensationIssuanceOcfData -> + [Text] -> + Update () +validateEarlyExerciseResultVestingSchedules maps label issuanceData securityIds = + when (hasEarlyExerciseVestingContinuity issuanceData) do + assertMsg + (label <> " source must have a positive quantity for early-exercise vesting continuity") + (issuanceData.quantity > 0.0) + mapA_ + (\securityId -> case Map.lookup securityId maps.stock_issuances_by_security_id of + Some stockIssuanceCid -> do + stockIssuance <- fetch stockIssuanceCid + assertMsg + (label <> " result must retain the source vesting terms for early-exercisable grant: " <> securityId) + (stockIssuance.issuance_data.vesting_terms_id == issuanceData.vesting_terms_id) + assertMsg + (label <> " result must retain the source inline vesting schedule pro rata for early-exercisable grant: " <> securityId) + (stockIssuance.issuance_data.vestings == + scaledEarlyExerciseVestings + issuanceData.quantity + stockIssuance.issuance_data.quantity + issuanceData.vestings) + None -> pure ()) + securityIds + validateStockConversionResultIssuances : CapTableMaps -> StockConversionOcfData -> @@ -980,6 +1053,11 @@ validateEquityCompensationExerciseResultIssuances maps issuanceData exerciseData expectedStockClassId (Some exerciseData.quantity) exerciseData.resulting_security_ids + validateEarlyExerciseResultVestingSchedules + maps + label + issuanceData + exerciseData.resulting_security_ids validateEquityCompensationReleaseResultIssuances : CapTableMaps -> EquityCompensationIssuanceOcfData -> EquityCompensationReleaseOcfData -> Update () validateEquityCompensationReleaseResultIssuances maps issuanceData releaseData = do @@ -1823,17 +1901,15 @@ stockSecurityQuantityBeforeEvent eventIndex _maps issuanceData targetSortKey = (\event -> stockClassShareEventSortKey event < targetSortKey) (sortOn stockClassShareEventSortKey (stockSecurityShareEvents eventIndex issuanceData))) -stockClassQuantityAtSortKey : +stockClassQuantityBetweenSortKeys : CapTableMaps -> Text -> - Time -> + (Time, Int, Int) -> Decimal -> (Time, Int, Int) -> Update Decimal -stockClassQuantityAtSortKey maps stockClassId quantityDate quantity targetSortKey = - let (targetDate, _, _) = targetSortKey - quantitySortKey = if quantityDate == targetDate then targetSortKey else (quantityDate, 0, 0) - in foldlA +stockClassQuantityBetweenSortKeys maps stockClassId quantitySortKey quantity targetSortKey = + foldlA (\adjustedQuantity splitCid -> do split <- fetch splitCid let splitSortKey = (split.split_data.date, 2, 2) @@ -1848,6 +1924,18 @@ stockClassQuantityAtSortKey maps stockClassId quantityDate quantity targetSortKe quantity (Map.values maps.stock_class_splits) +stockClassQuantityAtSortKey : + CapTableMaps -> + Text -> + Time -> + Decimal -> + (Time, Int, Int) -> + Update Decimal +stockClassQuantityAtSortKey maps stockClassId quantityDate quantity targetSortKey = + let (targetDate, _, _) = targetSortKey + quantitySortKey = if quantityDate == targetDate then targetSortKey else (quantityDate, 0, 0) + in stockClassQuantityBetweenSortKeys maps stockClassId quantitySortKey quantity targetSortKey + validateStockBalanceSuccessorQuantity : CapTableMaps -> Text -> @@ -2002,6 +2090,7 @@ validateStockBalanceSuccessorQuantities eventIndex maps = do securityQuantityEventSortKey : SecurityQuantityEvent -> (Time, Int, Int) securityQuantityEventSortKey event = case event of SecurityQuantityDelta with event_date; event_priority -> (event_date, event_priority, 0) + SecurityQuantityEarlyExercise with event_date; event_priority -> (event_date, event_priority, 0) SecurityQuantityTerminalDelta with event_date; event_priority -> (event_date, event_priority, 1) SecurityQuantityMultiplier with event_date; event_priority -> (event_date, event_priority, 2) SecurityQuantityRetraction with event_date; event_priority -> (event_date, event_priority, 3) @@ -2009,6 +2098,7 @@ securityQuantityEventSortKey event = case event of securityQuantityEventDate : SecurityQuantityEvent -> Time securityQuantityEventDate event = case event of SecurityQuantityDelta with event_date -> event_date + SecurityQuantityEarlyExercise with event_date -> event_date SecurityQuantityTerminalDelta with event_date -> event_date SecurityQuantityMultiplier with event_date -> event_date SecurityQuantityRetraction with event_date -> event_date @@ -2029,6 +2119,10 @@ validateSecurityQuantityBalanceEvent securityId currentOpt event = do assertMsg ("Security reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) (event_priority == 0 || quantity_delta <= 0.0) + SecurityQuantityEarlyExercise with quantity -> + assertMsg + ("Early exercise quantity cannot be negative for " <> securityId <> ": " <> show quantity) + (quantity >= 0.0) SecurityQuantityTerminalDelta with quantity_delta -> assertMsg ("Security terminal reduction quantity cannot be negative for " <> securityId <> ": derived delta is " <> show quantity_delta) @@ -2042,6 +2136,12 @@ validateSecurityQuantityBalanceEvent securityId currentOpt event = do ("Security balance cannot be negative for " <> securityId <> ": applying delta " <> show quantity_delta <> " leaves " <> show next) (next >= 0.0) pure (Some next) + SecurityQuantityEarlyExercise with quantity -> do + let next = current - quantity + assertMsg + ("Security balance cannot be negative for " <> securityId <> ": applying early exercise " <> show quantity <> " leaves " <> show next) + (next >= 0.0) + pure (Some next) SecurityQuantityTerminalDelta with quantity_delta -> do let next = current + quantity_delta assertMsg @@ -2061,6 +2161,8 @@ validateSecurityQuantityBalanceEvent securityId currentOpt event = do None -> case event of SecurityQuantityDelta {} -> pure None + SecurityQuantityEarlyExercise {} -> + pure None SecurityQuantityTerminalDelta {} -> pure (Some 0.0) SecurityQuantityMultiplier {} -> @@ -2493,12 +2595,26 @@ nonStockSecurityBalanceEventIndex maps = do assertNonNegativeQuantity ("Equity compensation exercise quantity for " <> exercise.exercise_data.id) exercise.exercise_data.quantity - pure (addSecurityQuantityEvent - exercise.exercise_data.security_id - (SecurityQuantityDelta with + event <- case Map.lookup exercise.exercise_data.security_id maps.equity_compensation_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + pure $ + if hasEarlyExerciseVestingContinuity issuance.issuance_data + then SecurityQuantityEarlyExercise with + event_date = exercise.exercise_data.date + event_priority = 1 + quantity = exercise.exercise_data.quantity + else SecurityQuantityDelta with + event_date = exercise.exercise_data.date + event_priority = 1 + quantity_delta = 0.0 - exercise.exercise_data.quantity + None -> pure SecurityQuantityDelta with event_date = exercise.exercise_data.date event_priority = 1 - quantity_delta = 0.0 - exercise.exercise_data.quantity) + quantity_delta = 0.0 - exercise.exercise_data.quantity + pure (addSecurityQuantityEvent + exercise.exercise_data.security_id + event index)) equityCancellationEvents (Map.values maps.equity_compensation_exercises) @@ -2542,8 +2658,15 @@ warrantSecurityQuantityBeforeTerminal : Time -> Update (Optional Decimal) warrantSecurityQuantityBeforeTerminal eventIndex issuanceData eventDate = + warrantSecurityQuantityBeforeSortKey eventIndex issuanceData (eventDate, 2, 1) + +warrantSecurityQuantityBeforeSortKey : + NonStockSecurityBalanceEventIndex -> + WarrantIssuanceOcfData -> + (Time, Int, Int) -> + Update (Optional Decimal) +warrantSecurityQuantityBeforeSortKey eventIndex issuanceData targetSortKey = let events = warrantSecurityQuantityEvents eventIndex issuanceData - targetSortKey = (eventDate, 2, 1) in case issuanceData.quantity of Some quantity -> securityQuantityBeforeEvent @@ -2623,7 +2746,18 @@ equityCompensationSecurityQuantityBeforeTerminal : EquityCompensationIssuanceOcfData -> Time -> Update Decimal -equityCompensationSecurityQuantityBeforeTerminal stockEventIndex balanceEventIndex maps issuanceData eventDate = do +equityCompensationSecurityQuantityBeforeTerminal stockEventIndex balanceEventIndex maps issuanceData eventDate = + equityCompensationSecurityQuantityBeforeSortKey + stockEventIndex balanceEventIndex maps issuanceData (eventDate, 2, 1) + +equityCompensationSecurityQuantityBeforeSortKey : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + (Time, Int, Int) -> + Update Decimal +equityCompensationSecurityQuantityBeforeSortKey stockEventIndex balanceEventIndex maps issuanceData targetSortKey = do events <- equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData quantityOpt <- securityQuantityBeforeEvent issuanceData.security_id @@ -2633,7 +2767,7 @@ equityCompensationSecurityQuantityBeforeTerminal stockEventIndex balanceEventInd event_priority = 0 quantity_delta = issuanceData.quantity) :: events) - (eventDate, 2, 1) + targetSortKey case quantityOpt of Some quantity -> pure quantity None -> do @@ -4155,6 +4289,923 @@ validateVestingLifecycleDates maps = do validateVestingSourceTransactionDate maps ("Vesting acceleration " <> acceleration.acceleration_data.id) acceleration.acceleration_data.date acceleration.acceleration_data.security_id) (Map.values maps.vesting_accelerations) +validateInlineVestingQuantities : CapTableMaps -> Update () +validateInlineVestingQuantities maps = do + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Stock issuance " <> issuance.issuance_data.id <> " inline vestings exceed security quantity") + (validateOcfVestingsWithinQuantity issuance.issuance_data.quantity issuance.issuance_data.vestings)) + (Map.values maps.stock_issuances) + + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Equity compensation issuance " <> issuance.issuance_data.id <> " inline vestings exceed security quantity") + (validateOcfVestingsWithinQuantity issuance.issuance_data.quantity issuance.issuance_data.vestings)) + (Map.values maps.equity_compensation_issuances) + + mapA_ + (\issuanceCid -> do + issuance <- fetch issuanceCid + case ocfQuantityUpperBound issuance.issuance_data.quantity issuance.issuance_data.quantity_source of + Some quantity -> + assertMsg + ("Warrant issuance " <> issuance.issuance_data.id <> " inline vestings exceed security quantity") + (validateOcfVestingsWithinQuantity quantity issuance.issuance_data.vestings) + None -> pure ()) + (Map.values maps.warrant_issuances) + +vestingAccelerationsBySecurityId : CapTableMaps -> Update (Map Text [VestingAccelerationOcfData]) +vestingAccelerationsBySecurityId maps = + foldlA + (\index accelerationCid -> do + acceleration <- fetch accelerationCid + let securityId = acceleration.acceleration_data.security_id + pure (Map.insert + securityId + (acceleration.acceleration_data :: fromOptional [] (Map.lookup securityId index)) + index)) + Map.empty + (Map.values maps.vesting_accelerations) + +insertVestingAccelerationPredecessor : + Text -> + Text -> + VestingAccelerationPredecessor -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +insertVestingAccelerationPredecessor label successorSecurityId predecessor index = do + let existingPredecessors = fromOptional [] (Map.lookup successorSecurityId index) + assertMsg + (label <> " gives vesting acceleration successor " <> successorSecurityId <> " predecessors from different lifecycle events") + (all + (\existing -> existing.successor_seed_sort_key == predecessor.successor_seed_sort_key) + existingPredecessors) + assertMsg + (label <> " gives vesting acceleration successor " <> successorSecurityId <> " duplicate predecessor " <> predecessor.source_security_id) + (all + (\existing -> existing.source_security_id /= predecessor.source_security_id) + existingPredecessors) + pure (Map.insert successorSecurityId (predecessor :: existingPredecessors) index) + +addStockVestingAccelerationPredecessorsAt : + StockClassShareEventIndex -> + CapTableMaps -> + Text -> + Text -> + (Time, Int, Int) -> + (Time, Int, Int) -> + [Text] -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +addStockVestingAccelerationPredecessorsAt eventIndex maps label sourceSecurityId sourceEventSortKey successorSeedSortKey successorSecurityIds index = + case successorSecurityIds of + [] -> pure index + _ -> case Map.lookup sourceSecurityId maps.stock_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + sourceQuantity <- stockSecurityQuantityBeforeEvent + eventIndex maps sourceIssuance.issuance_data sourceEventSortKey + assertMsg (label <> " has a non-positive source quantity") (sourceQuantity > 0.0) + foldlA + (\currentIndex successorSecurityId -> + case Map.lookup successorSecurityId maps.stock_issuances_by_security_id of + Some successorIssuanceCid -> do + successorIssuance <- fetch successorIssuanceCid + successorQuantity <- stockIssuanceQuantityAtSortKey + maps successorIssuance.issuance_data sourceEventSortKey + assertMsg + (label <> " has a non-positive successor quantity for vesting acceleration continuity") + (successorQuantity > 0.0) + insertVestingAccelerationPredecessor + label + successorSecurityId + (VestingAccelerationPredecessor with + source_security_id = sourceSecurityId + source_event_sort_key = sourceEventSortKey + successor_seed_sort_key = successorSeedSortKey + successor_fraction = successorQuantity / sourceQuantity) + currentIndex + None -> pure currentIndex) + index + successorSecurityIds + None -> pure index + +addEquityCompensationVestingAccelerationPredecessors : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Text -> + Text -> + Time -> + Optional Text -> + [Text] -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +addEquityCompensationVestingAccelerationPredecessors stockEventIndex balanceEventIndex maps label sourceSecurityId eventDate balanceSecurityId resultingSecurityIds index = + let successorSecurityIds = successorSecurityIdsFor balanceSecurityId resultingSecurityIds + sourceEventSortKey = stockReductionSortKey eventDate balanceSecurityId + in case successorSecurityIds of + [] -> pure index + _ -> case Map.lookup sourceSecurityId maps.equity_compensation_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + sourceQuantity <- equityCompensationSecurityQuantityBeforeSortKey + stockEventIndex balanceEventIndex maps sourceIssuance.issuance_data sourceEventSortKey + assertMsg (label <> " has a non-positive source quantity") (sourceQuantity > 0.0) + foldlA + (\currentIndex successorSecurityId -> + case Map.lookup successorSecurityId maps.equity_compensation_issuances_by_security_id of + Some successorIssuanceCid -> do + successorIssuance <- fetch successorIssuanceCid + successorQuantity <- equityCompensationIssuanceQuantityAtSortKey + maps successorIssuance.issuance_data sourceEventSortKey + assertMsg + (label <> " has an invalid successor quantity for vesting acceleration continuity") + (successorQuantity > 0.0 && successorQuantity <= sourceQuantity) + insertVestingAccelerationPredecessor + label + successorSecurityId + (VestingAccelerationPredecessor with + source_security_id = sourceSecurityId + source_event_sort_key = sourceEventSortKey + successor_seed_sort_key = (eventDate, 0, 0) + successor_fraction = successorQuantity / sourceQuantity) + currentIndex + None -> pure currentIndex) + index + successorSecurityIds + None -> pure index + +addWarrantVestingAccelerationPredecessors : + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Text -> + Text -> + Time -> + Optional Text -> + [Text] -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +addWarrantVestingAccelerationPredecessors eventIndex maps label sourceSecurityId eventDate balanceSecurityId resultingSecurityIds index = + let successorSecurityIds = successorSecurityIdsFor balanceSecurityId resultingSecurityIds + sourceEventSortKey = stockReductionSortKey eventDate balanceSecurityId + in case successorSecurityIds of + [] -> pure index + _ -> case Map.lookup sourceSecurityId maps.warrant_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + sourceQuantityOpt <- warrantSecurityQuantityBeforeSortKey + eventIndex sourceIssuance.issuance_data sourceEventSortKey + let sourceCeilingOpt = ocfQuantityUpperBound sourceQuantityOpt sourceIssuance.issuance_data.quantity_source + case sourceCeilingOpt of + Some sourceQuantity -> do + assertMsg (label <> " has a non-positive source quantity") (sourceQuantity > 0.0) + foldlA + (\currentIndex successorSecurityId -> + case Map.lookup successorSecurityId maps.warrant_issuances_by_security_id of + Some successorIssuanceCid -> do + successorIssuance <- fetch successorIssuanceCid + let successorCeilingOpt = ocfQuantityUpperBound + successorIssuance.issuance_data.quantity + successorIssuance.issuance_data.quantity_source + case successorCeilingOpt of + Some successorQuantity -> do + assertMsg + (label <> " has an invalid successor quantity for vesting acceleration continuity") + (successorQuantity > 0.0 && successorQuantity <= sourceQuantity) + insertVestingAccelerationPredecessor + label + successorSecurityId + (VestingAccelerationPredecessor with + source_security_id = sourceSecurityId + source_event_sort_key = sourceEventSortKey + successor_seed_sort_key = (eventDate, 0, 0) + successor_fraction = successorQuantity / sourceQuantity) + currentIndex + None -> pure currentIndex + None -> pure currentIndex) + index + successorSecurityIds + None -> pure index + None -> pure index + +addEarlyExerciseStockVestingAccelerationPredecessors : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationExerciseOcfData -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +addEarlyExerciseStockVestingAccelerationPredecessors stockEventIndex balanceEventIndex maps exerciseData index = + case Map.lookup exerciseData.security_id maps.equity_compensation_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + if hasEarlyExerciseVestingContinuity sourceIssuance.issuance_data + then do + let label = "Equity compensation exercise " <> exerciseData.id + sourceEventSortKey = (exerciseData.date, 1, 0) + sourceQuantity <- equityCompensationSecurityQuantityBeforeSortKey + stockEventIndex balanceEventIndex maps sourceIssuance.issuance_data sourceEventSortKey + assertMsg (label <> " has a non-positive source quantity") (sourceQuantity > 0.0) + foldlA + (\currentIndex successorSecurityId -> + case Map.lookup successorSecurityId maps.stock_issuances_by_security_id of + Some successorIssuanceCid -> do + successorIssuance <- fetch successorIssuanceCid + successorQuantity <- stockIssuanceQuantityAtSortKey + maps successorIssuance.issuance_data sourceEventSortKey + assertMsg + (label <> " has an invalid successor quantity for vesting acceleration continuity") + (successorQuantity > 0.0 && successorQuantity <= sourceQuantity) + insertVestingAccelerationPredecessor + label + successorSecurityId + (VestingAccelerationPredecessor with + source_security_id = exerciseData.security_id + source_event_sort_key = sourceEventSortKey + successor_seed_sort_key = (exerciseData.date, 0, 0) + successor_fraction = successorQuantity / sourceQuantity) + currentIndex + None -> pure currentIndex) + index + exerciseData.resulting_security_ids + else pure index + None -> pure index + +addStockConsolidationVestingAccelerationPredecessors : + StockClassShareEventIndex -> + CapTableMaps -> + Text -> + [Text] -> + Text -> + Time -> + VestingAccelerationPredecessorIndex -> + Update VestingAccelerationPredecessorIndex +addStockConsolidationVestingAccelerationPredecessors eventIndex maps label sourceSecurityIds successorSecurityId eventDate index = + case Map.lookup successorSecurityId maps.stock_issuances_by_security_id of + Some _ -> + let sourceEventSortKey = (eventDate, 3, 3) + in foldlA + (\currentIndex sourceSecurityId -> + case Map.lookup sourceSecurityId maps.stock_issuances_by_security_id of + Some sourceIssuanceCid -> do + sourceIssuance <- fetch sourceIssuanceCid + sourceQuantity <- stockSecurityQuantityBeforeEvent + eventIndex maps sourceIssuance.issuance_data sourceEventSortKey + assertMsg (label <> " has a non-positive source quantity") (sourceQuantity > 0.0) + insertVestingAccelerationPredecessor + label + successorSecurityId + (VestingAccelerationPredecessor with + source_security_id = sourceSecurityId + source_event_sort_key = sourceEventSortKey + successor_seed_sort_key = (eventDate, 0, 0) + successor_fraction = 1.0) + currentIndex + None -> pure currentIndex) + index + sourceSecurityIds + None -> pure index + +vestingAccelerationPredecessorIndex : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Update VestingAccelerationPredecessorIndex +vestingAccelerationPredecessorIndex stockEventIndex balanceEventIndex maps = do + stockCancellationIndex <- foldlA + (\index cancellationCid -> do + cancellation <- fetch cancellationCid + addStockVestingAccelerationPredecessorsAt + stockEventIndex maps ("Stock cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id + (cancellation.cancellation_data.date, 2, 1) + (cancellation.cancellation_data.date, 0, 0) + (successorSecurityIdsFor cancellation.cancellation_data.balance_security_id []) + index) + Map.empty + (Map.values maps.stock_cancellations) + stockRepurchaseIndex <- foldlA + (\index repurchaseCid -> do + repurchase <- fetch repurchaseCid + addStockVestingAccelerationPredecessorsAt + stockEventIndex maps ("Stock repurchase " <> repurchase.repurchase_data.id) + repurchase.repurchase_data.security_id + (repurchase.repurchase_data.date, 2, 1) + (repurchase.repurchase_data.date, 0, 0) + (successorSecurityIdsFor repurchase.repurchase_data.balance_security_id []) + index) + stockCancellationIndex + (Map.values maps.stock_repurchases) + stockTransferIndex <- foldlA + (\index transferCid -> do + transfer <- fetch transferCid + let sourceEventSortKey = stockReductionSortKey + transfer.transfer_data.date transfer.transfer_data.balance_security_id + addStockVestingAccelerationPredecessorsAt + stockEventIndex maps ("Stock transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id + sourceEventSortKey + (transfer.transfer_data.date, 0, 0) + (successorSecurityIdsFor + transfer.transfer_data.balance_security_id + transfer.transfer_data.resulting_security_ids) + index) + stockRepurchaseIndex + (Map.values maps.stock_transfers) + stockConversionIndex <- foldlA + (\index conversionCid -> do + conversion <- fetch conversionCid + let sourceEventSortKey = stockReductionSortKey + conversion.conversion_data.date conversion.conversion_data.balance_security_id + addStockVestingAccelerationPredecessorsAt + stockEventIndex maps ("Stock conversion " <> conversion.conversion_data.id) + conversion.conversion_data.security_id + sourceEventSortKey + (conversion.conversion_data.date, 0, 0) + (successorSecurityIdsFor + conversion.conversion_data.balance_security_id + conversion.conversion_data.resulting_security_ids) + index) + stockTransferIndex + (Map.values maps.stock_conversions) + stockReissuanceIndex <- foldlA + (\index reissuanceCid -> do + reissuance <- fetch reissuanceCid + addStockVestingAccelerationPredecessorsAt + stockEventIndex maps ("Stock reissuance " <> reissuance.reissuance_data.id) + reissuance.reissuance_data.security_id + (reissuance.reissuance_data.date, 3, 3) + (reissuance.reissuance_data.date, 0, 0) + reissuance.reissuance_data.resulting_security_ids + index) + stockConversionIndex + (Map.values maps.stock_reissuances) + stockConsolidationIndex <- foldlA + (\index consolidationCid -> do + consolidation <- fetch consolidationCid + addStockConsolidationVestingAccelerationPredecessors + stockEventIndex maps ("Stock consolidation " <> consolidation.consolidation_data.id) + consolidation.consolidation_data.security_ids + consolidation.consolidation_data.resulting_security_id + consolidation.consolidation_data.date + index) + stockReissuanceIndex + (Map.values maps.stock_consolidations) + earlyExerciseIndex <- foldlA + (\index exerciseCid -> do + exercise <- fetch exerciseCid + addEarlyExerciseStockVestingAccelerationPredecessors + stockEventIndex balanceEventIndex maps exercise.exercise_data index) + stockConsolidationIndex + (Map.values maps.equity_compensation_exercises) + equityCancellationIndex <- foldlA + (\index cancellationCid -> do + cancellation <- fetch cancellationCid + addEquityCompensationVestingAccelerationPredecessors + stockEventIndex balanceEventIndex maps ("Equity compensation cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id cancellation.cancellation_data.date + cancellation.cancellation_data.balance_security_id [] index) + earlyExerciseIndex + (Map.values maps.equity_compensation_cancellations) + equityTransferIndex <- foldlA + (\index transferCid -> do + transfer <- fetch transferCid + addEquityCompensationVestingAccelerationPredecessors + stockEventIndex balanceEventIndex maps ("Equity compensation transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id transfer.transfer_data.date + transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids index) + equityCancellationIndex + (Map.values maps.equity_compensation_transfers) + warrantCancellationIndex <- foldlA + (\index cancellationCid -> do + cancellation <- fetch cancellationCid + addWarrantVestingAccelerationPredecessors + balanceEventIndex maps ("Warrant cancellation " <> cancellation.cancellation_data.id) + cancellation.cancellation_data.security_id cancellation.cancellation_data.date + cancellation.cancellation_data.balance_security_id [] index) + equityTransferIndex + (Map.values maps.warrant_cancellations) + foldlA + (\index transferCid -> do + transfer <- fetch transferCid + addWarrantVestingAccelerationPredecessors + balanceEventIndex maps ("Warrant transfer " <> transfer.transfer_data.id) + transfer.transfer_data.security_id transfer.transfer_data.date + transfer.transfer_data.balance_security_id transfer.transfer_data.resulting_security_ids index) + warrantCancellationIndex + (Map.values maps.warrant_transfers) + +vestingAccelerationBuckets : [VestingAccelerationOcfData] -> [(Time, Decimal)] +vestingAccelerationBuckets accelerations = + sortOn + fst + (Map.toList (Map.fromListWithR (+) (map (\accelerationData -> (accelerationData.date, accelerationData.quantity)) accelerations))) + +-- Accelerations are evaluated after every applicable same-day security quantity event. +vestingAccelerationEvaluationSortKey : Time -> (Time, Int, Int) +vestingAccelerationEvaluationSortKey date = (date, 4, 0) + +vestingAccelerationTimelineEvents : + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + [VestingAccelerationTimelineEvent] +vestingAccelerationTimelineEvents inheritedSeed accelerations = + let directEvents = map + (\(date, quantity) -> VestingAccelerationQuantity with + event_sort_key = vestingAccelerationEvaluationSortKey date + quantity + report_total = True) + (vestingAccelerationBuckets accelerations) + in case inheritedSeed of + Some (seedSortKey, seedQuantity) -> + let (seedDate, _, _) = seedSortKey + in + [ VestingAccelerationQuantity with + event_sort_key = seedSortKey + quantity = seedQuantity + report_total = False + , VestingAccelerationQuantity with + event_sort_key = vestingAccelerationEvaluationSortKey seedDate + quantity = 0.0 + report_total = True + ] ++ directEvents + None -> directEvents + +vestingAccelerationTimelineEventSortKey : VestingAccelerationTimelineEvent -> (Time, Int, Int) +vestingAccelerationTimelineEventSortKey event = case event of + VestingAccelerationSplit with event_sort_key -> event_sort_key + VestingAccelerationQuantity with event_sort_key -> event_sort_key + VestingAccelerationReduction with event_sort_key -> event_sort_key + VestingAccelerationRetirement with event_sort_key -> event_sort_key + +applyVestingAccelerationTimelineEvent : Decimal -> VestingAccelerationTimelineEvent -> Decimal +applyVestingAccelerationTimelineEvent total event = case event of + VestingAccelerationSplit with ratio -> + total * ratio.numerator / ratio.denominator + VestingAccelerationQuantity with quantity -> + total + quantity + VestingAccelerationReduction with quantity_delta -> + let nextTotal = total + quantity_delta + in if nextTotal < 0.0 then 0.0 else nextTotal + VestingAccelerationRetirement _ -> 0.0 + +vestingAccelerationTimelineTotals : [VestingAccelerationTimelineEvent] -> [(Time, Decimal)] +vestingAccelerationTimelineTotals events = + let (_, reversedTotals) = foldl + (\(total, totals) event -> + let nextTotal = applyVestingAccelerationTimelineEvent total event + in case event of + VestingAccelerationQuantity with event_sort_key; report_total -> + if report_total + then let (eventDate, _, _) = event_sort_key + in (nextTotal, (eventDate, nextTotal) :: totals) + else (nextTotal, totals) + _ -> (nextTotal, totals)) + (0.0, []) + (sortOn vestingAccelerationTimelineEventSortKey events) + in reverse reversedTotals + +vestingAccelerationTimelineTotalBefore : + (Time, Int, Int) -> + [VestingAccelerationTimelineEvent] -> + Decimal +vestingAccelerationTimelineTotalBefore targetSortKey events = + foldl + applyVestingAccelerationTimelineEvent + 0.0 + (filter + (\event -> + vestingAccelerationTimelineEventSortKey event < targetSortKey || + (vestingAccelerationTimelineEventSortKey event == targetSortKey && case event of + VestingAccelerationQuantity with report_total -> not report_total + _ -> False)) + (sortOn vestingAccelerationTimelineEventSortKey events)) + +stockVestingAccelerationTimelineEvents : + StockClassShareEventIndex -> + StockIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + [VestingAccelerationTimelineEvent] +stockVestingAccelerationTimelineEvents eventIndex issuanceData inheritedSeed accelerations = + let securityEvents = concatMap + (\event -> case event of + StockClassShareDelta with event_date; event_priority; quantity_delta -> + if quantity_delta < 0.0 + then [VestingAccelerationReduction with + event_sort_key = (event_date, event_priority, 0) + quantity_delta] + else [] + StockClassShareTerminalDelta with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 1)] + StockClassShareMultiplier with event_date; event_priority; ratio -> + [VestingAccelerationSplit with + event_sort_key = (event_date, event_priority, 2) + ratio] + StockClassShareRetraction with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 3)]) + (stockSecurityShareEvents eventIndex issuanceData) + in securityEvents ++ vestingAccelerationTimelineEvents inheritedSeed accelerations + +stockVestingAccelerationTotals : + StockClassShareEventIndex -> + StockIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + [(Time, Decimal)] +stockVestingAccelerationTotals eventIndex issuanceData inheritedSeed accelerations = + vestingAccelerationTimelineTotals + (stockVestingAccelerationTimelineEvents eventIndex issuanceData inheritedSeed accelerations) + +securityVestingAccelerationTimelineEvents : + [SecurityQuantityEvent] -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + [VestingAccelerationTimelineEvent] +securityVestingAccelerationTimelineEvents quantityEvents inheritedSeed accelerations = + let securityEvents = concatMap + (\event -> case event of + SecurityQuantityDelta with event_date; event_priority; quantity_delta -> + if quantity_delta < 0.0 + then [VestingAccelerationReduction with + event_sort_key = (event_date, event_priority, 0) + quantity_delta] + else [] + SecurityQuantityEarlyExercise {} -> [] + SecurityQuantityTerminalDelta with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 1)] + SecurityQuantityMultiplier with event_date; event_priority; ratio -> + [VestingAccelerationSplit with + event_sort_key = (event_date, event_priority, 2) + ratio] + SecurityQuantityRetraction with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 3)]) + quantityEvents + in securityEvents ++ vestingAccelerationTimelineEvents inheritedSeed accelerations + +securityQuantityEarlyExerciseBuckets : [SecurityQuantityEvent] -> [((Time, Int), Decimal)] +securityQuantityEarlyExerciseBuckets events = + sortOn + fst + (Map.toList (Map.fromListWithR (+) (concatMap + (\event -> case event of + SecurityQuantityEarlyExercise with event_date; event_priority; quantity -> + [((event_date, event_priority), quantity)] + _ -> []) + events))) + +equityCompensationVestingAccelerationTimelineEvents : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + Update [VestingAccelerationTimelineEvent] +equityCompensationVestingAccelerationTimelineEvents stockEventIndex balanceEventIndex maps issuanceData inheritedSeed accelerations = do + quantityEvents <- equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData + earlyExerciseEvents <- foldlA + (\timelineEvents ((eventDate, eventPriority), exerciseQuantity) -> do + liveQuantityOpt <- securityQuantityBeforeEvent + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = issuanceData.quantity) + :: quantityEvents) + (eventDate, eventPriority, 0) + liveQuantity <- case liveQuantityOpt of + Some quantity -> pure quantity + None -> do + assertMsg ("Equity compensation quantity unexpectedly became unknown: " <> issuanceData.security_id) False + pure 0.0 + assertMsg + ("Early exercise requires a positive source quantity for vesting continuity: " <> issuanceData.security_id) + (liveQuantity > 0.0) + assertMsg + ("Early exercise quantity exceeds its source quantity for vesting continuity: " <> issuanceData.security_id) + (exerciseQuantity <= liveQuantity) + let ratio = OcfRatio with + numerator = liveQuantity - exerciseQuantity + denominator = liveQuantity + timelineEvent = VestingAccelerationSplit with + event_sort_key = (eventDate, eventPriority, 0) + ratio + pure (timelineEvent :: timelineEvents)) + [] + (securityQuantityEarlyExerciseBuckets quantityEvents) + let otherSecurityEvents = concatMap + (\event -> case event of + SecurityQuantityDelta with event_date; event_priority; quantity_delta -> + if quantity_delta < 0.0 + then [VestingAccelerationReduction with + event_sort_key = (event_date, event_priority, 1) + quantity_delta] + else [] + SecurityQuantityEarlyExercise {} -> [] + SecurityQuantityTerminalDelta with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 2)] + SecurityQuantityMultiplier with event_date; event_priority; ratio -> + [VestingAccelerationSplit with + event_sort_key = (event_date, event_priority, 3) + ratio] + SecurityQuantityRetraction with event_date; event_priority -> + [VestingAccelerationRetirement with + event_sort_key = (event_date, event_priority, 4)]) + quantityEvents + pure (earlyExerciseEvents ++ otherSecurityEvents ++ vestingAccelerationTimelineEvents inheritedSeed accelerations) + +vestingAccelerationInheritedSeed : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Map Text [VestingAccelerationOcfData] -> + VestingAccelerationPredecessorIndex -> + Set Text -> + Text -> + Update (Optional ((Time, Int, Int), Decimal)) +vestingAccelerationInheritedSeed stockEventIndex balanceEventIndex maps accelerationIndex predecessorIndex visited securityId = + if Set.member securityId visited + then do + assertMsg ("Vesting acceleration successor lineage contains a cycle at " <> securityId) False + pure None + else case Map.lookup securityId predecessorIndex of + Some (firstPredecessor :: remainingPredecessors) -> do + let predecessors = firstPredecessor :: remainingPredecessors + assertMsg + ("Vesting acceleration successor lineage has inconsistent seed dates for " <> securityId) + (all + (\predecessor -> predecessor.successor_seed_sort_key == firstPredecessor.successor_seed_sort_key) + remainingPredecessors) + inheritedQuantity <- foldlA + (\total predecessor -> do + sourceTotal <- vestingAccelerationQuantityBefore + stockEventIndex + balanceEventIndex + maps + accelerationIndex + predecessorIndex + (Set.insert securityId visited) + predecessor.source_security_id + predecessor.source_event_sort_key + pure (total + sourceTotal * predecessor.successor_fraction)) + 0.0 + predecessors + pure (Some (firstPredecessor.successor_seed_sort_key, inheritedQuantity)) + _ -> pure None + +vestingAccelerationQuantityBefore : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Map Text [VestingAccelerationOcfData] -> + VestingAccelerationPredecessorIndex -> + Set Text -> + Text -> + (Time, Int, Int) -> + Update Decimal +vestingAccelerationQuantityBefore stockEventIndex balanceEventIndex maps accelerationIndex predecessorIndex visited securityId targetSortKey = do + inheritedSeed <- vestingAccelerationInheritedSeed + stockEventIndex balanceEventIndex maps accelerationIndex predecessorIndex visited securityId + let accelerations = fromOptional [] (Map.lookup securityId accelerationIndex) + case Map.lookup securityId maps.stock_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + pure (vestingAccelerationTimelineTotalBefore + targetSortKey + (stockVestingAccelerationTimelineEvents + stockEventIndex + issuance.issuance_data + inheritedSeed + accelerations)) + None -> case Map.lookup securityId maps.equity_compensation_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + events <- equityCompensationVestingAccelerationTimelineEvents + stockEventIndex balanceEventIndex maps issuance.issuance_data inheritedSeed accelerations + pure (vestingAccelerationTimelineTotalBefore targetSortKey events) + None -> case Map.lookup securityId maps.warrant_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + pure (vestingAccelerationTimelineTotalBefore + targetSortKey + (securityVestingAccelerationTimelineEvents + (warrantSecurityQuantityEvents balanceEventIndex issuance.issuance_data) + inheritedSeed + accelerations)) + None -> do + assertMsg ("Vesting acceleration predecessor security not found: " <> securityId) False + pure 0.0 + +inlineVestingQuantityAfter : Time -> [OcfVesting] -> Decimal +inlineVestingQuantityAfter date vestings = + foldl + (\total vesting -> if vesting.date > date then total + vesting.amount else total) + 0.0 + vestings + +validateStockVestingAccelerationQuantities : + StockClassShareEventIndex -> + CapTableMaps -> + StockIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + Update () +validateStockVestingAccelerationQuantities eventIndex maps issuanceData inheritedSeed accelerations = + mapA_ + (\(accelerationDate, acceleratedQuantity) -> do + let targetSortKey = vestingAccelerationEvaluationSortKey accelerationDate + liveQuantity <- stockSecurityQuantityBeforeEvent eventIndex maps issuanceData targetSortKey + assertMsg + ("Vesting accelerations exceed live stock security quantity for " <> issuanceData.security_id <> " at " <> show accelerationDate) + (acceleratedQuantity <= liveQuantity) + unless (null issuanceData.vestings) do + let scheduleSortKey = stockClassShareLotSplitCutoffSortKey eventIndex (stockIssuanceShareLot issuanceData) + futureVestingQuantity <- stockClassQuantityBetweenSortKeys + maps + issuanceData.stock_class_id + scheduleSortKey + (inlineVestingQuantityAfter accelerationDate issuanceData.vestings) + targetSortKey + assertMsg + ("Vesting accelerations exceed stock quantity scheduled after " <> show accelerationDate <> " for " <> issuanceData.security_id) + (acceleratedQuantity <= futureVestingQuantity)) + (stockVestingAccelerationTotals eventIndex issuanceData inheritedSeed accelerations) + +validateEquityCompensationVestingAccelerationQuantities : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + EquityCompensationIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + Update () +validateEquityCompensationVestingAccelerationQuantities stockEventIndex balanceEventIndex maps issuanceData inheritedSeed accelerations = do + quantityEvents <- equityCompensationSecurityQuantityEvents stockEventIndex balanceEventIndex maps issuanceData + timelineEvents <- equityCompensationVestingAccelerationTimelineEvents + stockEventIndex balanceEventIndex maps issuanceData inheritedSeed accelerations + let accelerationTotals = vestingAccelerationTimelineTotals timelineEvents + mapA_ + (\(accelerationDate, acceleratedQuantity) -> do + let targetSortKey = vestingAccelerationEvaluationSortKey accelerationDate + liveQuantityOpt <- securityQuantityBeforeEvent + issuanceData.security_id + (Some 0.0) + ((SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = issuanceData.quantity) + :: quantityEvents) + targetSortKey + liveQuantity <- case liveQuantityOpt of + Some quantity -> pure quantity + None -> do + assertMsg ("Equity compensation quantity unexpectedly became unknown: " <> issuanceData.security_id) False + pure 0.0 + assertMsg + ("Vesting accelerations exceed live equity compensation security quantity for " <> issuanceData.security_id <> " at " <> show accelerationDate) + (acceleratedQuantity <= liveQuantity) + unless (null issuanceData.vestings) do + let scheduleAdjustmentEvents = filter + (\event -> case event of + VestingAccelerationSplit {} -> True + _ -> False) + timelineEvents + scheduleQuantityEvent = VestingAccelerationQuantity with + event_sort_key = (issuanceData.date, 0, 0) + quantity = inlineVestingQuantityAfter accelerationDate issuanceData.vestings + report_total = False + futureVestingQuantity = vestingAccelerationTimelineTotalBefore + targetSortKey + (scheduleQuantityEvent :: scheduleAdjustmentEvents) + assertMsg + ("Vesting accelerations exceed equity compensation quantity scheduled after " <> show accelerationDate <> " for " <> issuanceData.security_id) + (acceleratedQuantity <= futureVestingQuantity)) + accelerationTotals + +validateWarrantVestingAccelerationQuantities : + NonStockSecurityBalanceEventIndex -> + WarrantIssuanceOcfData -> + Optional ((Time, Int, Int), Decimal) -> + [VestingAccelerationOcfData] -> + Update () +validateWarrantVestingAccelerationQuantities eventIndex issuanceData inheritedSeed accelerations = + case ocfQuantityUpperBound issuanceData.quantity issuanceData.quantity_source of + Some quantity -> do + let quantityEvents = + (SecurityQuantityDelta with + event_date = issuanceData.date + event_priority = 0 + quantity_delta = quantity) + :: warrantSecurityQuantityEvents eventIndex issuanceData + accelerationTotals = vestingAccelerationTimelineTotals + (securityVestingAccelerationTimelineEvents + (warrantSecurityQuantityEvents eventIndex issuanceData) + inheritedSeed + accelerations) + mapA_ + (\(accelerationDate, acceleratedQuantity) -> do + liveQuantityOpt <- securityQuantityBeforeEvent + issuanceData.security_id + (Some 0.0) + quantityEvents + (vestingAccelerationEvaluationSortKey accelerationDate) + liveQuantity <- case liveQuantityOpt of + Some currentQuantity -> pure currentQuantity + None -> do + assertMsg ("Warrant quantity unexpectedly became unknown: " <> issuanceData.security_id) False + pure 0.0 + assertMsg + ("Vesting accelerations exceed live warrant security quantity for " <> issuanceData.security_id <> " at " <> show accelerationDate) + (acceleratedQuantity <= liveQuantity) + unless (null issuanceData.vestings) do + assertMsg + ("Vesting accelerations exceed warrant quantity scheduled after " <> show accelerationDate <> " for " <> issuanceData.security_id) + (acceleratedQuantity <= inlineVestingQuantityAfter accelerationDate issuanceData.vestings)) + accelerationTotals + None -> pure () + +validateVestingAccelerationQuantities : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Map Text [VestingAccelerationOcfData] -> + VestingAccelerationPredecessorIndex -> + (Text, [VestingAccelerationOcfData]) -> + Update () +validateVestingAccelerationQuantities stockEventIndex balanceEventIndex maps accelerationIndex predecessorIndex (securityId, accelerations) = do + inheritedSeed <- vestingAccelerationInheritedSeed + stockEventIndex balanceEventIndex maps accelerationIndex predecessorIndex Set.empty securityId + let hasPositiveInheritedSeed = case inheritedSeed of + Some (_, quantity) -> quantity > 0.0 + None -> False + when (not (null accelerations) || hasPositiveInheritedSeed) do + case Map.lookup securityId maps.stock_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Vesting acceleration requires stock security to declare vesting terms or inline vestings: " <> securityId) + (hasDeclaredVestingSchedule issuance.issuance_data.vesting_terms_id issuance.issuance_data.vestings) + validateStockVestingAccelerationQuantities stockEventIndex maps issuance.issuance_data inheritedSeed accelerations + None -> case Map.lookup securityId maps.equity_compensation_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Vesting acceleration requires equity compensation security to declare vesting terms or inline vestings: " <> securityId) + (hasDeclaredVestingSchedule issuance.issuance_data.vesting_terms_id issuance.issuance_data.vestings) + validateEquityCompensationVestingAccelerationQuantities + stockEventIndex + balanceEventIndex + maps + issuance.issuance_data + inheritedSeed + accelerations + None -> case Map.lookup securityId maps.warrant_issuances_by_security_id of + Some issuanceCid -> do + issuance <- fetch issuanceCid + assertMsg + ("Vesting acceleration requires warrant security to declare vesting terms or inline vestings: " <> securityId) + (hasDeclaredVestingSchedule issuance.issuance_data.vesting_terms_id issuance.issuance_data.vestings) + validateWarrantVestingAccelerationQuantities balanceEventIndex issuance.issuance_data inheritedSeed accelerations + None -> + assertMsg + ("Vesting accelerations reference a missing or unsupported security: " <> securityId) + False + +validateVestingQuantityIntegrity : + StockClassShareEventIndex -> + NonStockSecurityBalanceEventIndex -> + CapTableMaps -> + Update () +validateVestingQuantityIntegrity stockEventIndex balanceEventIndex maps = do + validateInlineVestingQuantities maps + accelerationIndex <- vestingAccelerationsBySecurityId maps + predecessorIndex <- vestingAccelerationPredecessorIndex stockEventIndex balanceEventIndex maps + let securityIds = Map.keys (Map.fromList (map + (\securityId -> (securityId, True)) + (Map.keys accelerationIndex ++ Map.keys predecessorIndex))) + mapA_ + (\securityId -> + validateVestingAccelerationQuantities + stockEventIndex + balanceEventIndex + maps + accelerationIndex + predecessorIndex + (securityId, fromOptional [] (Map.lookup securityId accelerationIndex))) + securityIds + validateTransactionLifecycleDates : CapTableMaps -> Update () validateTransactionLifecycleDates maps = do validateStockLifecycleDates maps @@ -7435,6 +8486,7 @@ template CapTable validateConfiguredReferences finalMaps validateFinalSecurityIdUniqueness finalMaps validateSuccessorSecurityIdContinuity finalMaps + validateVestingQuantityIntegrity stockEventIndex nonStockBalanceEventIndex finalMaps validateTransactionLifecycleDates finalMaps validateInstrumentTemporalEligibility finalMaps validateStockBalanceSuccessorQuantities stockEventIndex finalMaps