Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
7e6d657
Enforce vesting condition graph integrity
HardlyDifficult Jul 9, 2026
2bd0754
Include relative-trigger edges in vesting cycle checks
Copilot Jul 9, 2026
af49a94
Fix vesting relative schedule fixture
HardlyDifficult Jul 9, 2026
6c9ace4
Refresh vesting graph DAR backup
HardlyDifficult Jul 9, 2026
0d8b82f
Fix vesting relative trigger cycle edge direction
Copilot Jul 9, 2026
1c902f4
Refine vesting graph dependency naming
Copilot Jul 9, 2026
5b4d293
Precompute vesting graph edges for cycle checks
Copilot Jul 9, 2026
a856cd6
Use map-backed vesting graph edge lookup
Copilot Jul 9, 2026
316c6f9
Simplify vesting edge map option handling
Copilot Jul 9, 2026
b1f84b9
Refresh vesting graph DAR after review fixes
HardlyDifficult Jul 9, 2026
a39d17f
Merge remote-tracking branch 'origin/codex/ocf-vesting-graph-integrit…
HardlyDifficult Jul 9, 2026
19d1a05
Refresh vesting graph DAR after edge lookup fixes
HardlyDifficult Jul 9, 2026
d8a08e6
Enforce relative trigger predecessor linkage in vesting graph
Copilot Jul 9, 2026
f0d6e90
Use visited set for vesting predecessor reachability check
Copilot Jul 9, 2026
40ee048
Refresh vesting graph DAR after Copilot fixes
HardlyDifficult Jul 9, 2026
0d4c722
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
38bc0db
Require relative vesting anchors on all paths
HardlyDifficult Jul 9, 2026
2146fc9
Restack vesting graph integrity category
HardlyDifficult Jul 9, 2026
ad50872
Optimize vesting duplicate ID check
Copilot Jul 9, 2026
b5058fa
Clarify duplicate-check short-circuit logic
Copilot Jul 9, 2026
5fc77d1
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
0e1073f
Address vesting graph review comments
HardlyDifficult Jul 9, 2026
e84ec6d
Merge remote vesting graph review fixes
HardlyDifficult Jul 9, 2026
9a81d9c
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
149766e
Restack vesting graph on terminal balance fixes
HardlyDifficult Jul 9, 2026
8a268b1
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
2c7e986
Restack vesting graph on same-day balance ordering
HardlyDifficult Jul 9, 2026
3c6c465
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
5d7aab8
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
f7ede7e
Update vesting graph DAR backup
HardlyDifficult Jul 9, 2026
d50524b
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
3cc03a0
Tighten vesting graph failure fixtures
HardlyDifficult Jul 9, 2026
2d4d55c
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
b3254ef
Refresh vesting graph DAR after ceiling update
HardlyDifficult Jul 9, 2026
5e31afd
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
30356e1
Tighten vesting graph review tests
HardlyDifficult Jul 9, 2026
4e07718
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
41f47de
Refresh vesting DAR after security balance merge
HardlyDifficult Jul 9, 2026
896476a
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
8a8ed7b
Refresh vesting DAR after security balance update
HardlyDifficult Jul 9, 2026
61dfa2b
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
20f1ed6
Refresh vesting DAR after security fixes
HardlyDifficult Jul 9, 2026
c6dc686
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
d06004d
Refresh vesting DAR after reference fold
HardlyDifficult Jul 9, 2026
57981da
Cover self-referential vesting triggers
HardlyDifficult Jul 9, 2026
31cb928
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
3334141
Refresh vesting DAR after reference updates
HardlyDifficult Jul 9, 2026
d73a1cd
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 9, 2026
06e3ecf
Refresh vesting DAR after balance updates
HardlyDifficult Jul 9, 2026
2c1a7c8
Merge branch 'codex/ocf-stock-security-balance' into codex/ocf-vestin…
HardlyDifficult Jul 10, 2026
6677984
chore: refresh vesting graph DAR
HardlyDifficult Jul 10, 2026
5b4a2eb
Merge commit 'f48b251' into codex/ocf-vesting-graph-integrity
HardlyDifficult Jul 10, 2026
a5fbd9d
chore: refresh vesting graph DAR after restack
HardlyDifficult Jul 10, 2026
8b1992f
Merge commit 'ff46c61' into codex/ocf-vesting-graph-integrity
HardlyDifficult Jul 10, 2026
e673e67
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
5af3992
chore: refresh vesting graph DAR after restack
HardlyDifficult Jul 11, 2026
b1fbb06
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
032efd5
chore: refresh deterministic vesting graph DAR
HardlyDifficult Jul 11, 2026
1ab25e6
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
20d7d4d
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
4c68482
chore: refresh deterministic vesting graph DAR
HardlyDifficult Jul 11, 2026
f184ee4
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
e5ac95e
chore: refresh vesting graph DAR after restack
HardlyDifficult Jul 11, 2026
1b0fb42
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
31894a1
chore: refresh vesting graph DAR after parent fix
HardlyDifficult Jul 11, 2026
a103748
fix: reject multiple vesting start triggers
HardlyDifficult Jul 11, 2026
11217b8
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
4cd10c0
chore: refresh vesting graph DAR after parent update
HardlyDifficult Jul 11, 2026
48c2ff9
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
a77b1f5
chore: refresh vesting graph DAR after balance fix
HardlyDifficult Jul 11, 2026
fff0714
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
66ed547
chore: refresh vesting graph DAR after parent updates
HardlyDifficult Jul 11, 2026
4a23c62
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
e960746
chore: refresh vesting graph DAR after parent update
HardlyDifficult Jul 11, 2026
54433b3
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
0ae9f4b
chore: refresh vesting graph DAR after parent update
HardlyDifficult Jul 11, 2026
c517bb4
Merge remote-tracking branch 'origin/codex/ocf-stock-security-balance…
HardlyDifficult Jul 11, 2026
1591822
chore: refresh vesting graph DAR
HardlyDifficult Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion OpenCapTable-v34/daml.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sdk-version: 3.4.10
name: OpenCapTable-v34
source: daml
version: 0.0.12
version: 0.0.13
build-options:
- -Wno-crypto-text-is-alpha
- --typecheck-upgrades=no
Expand Down
160 changes: 159 additions & 1 deletion OpenCapTable-v34/daml/Fairmint/OpenCapTable/OCF/VestingTerms.daml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Fairmint.OpenCapTable.OCF.VestingTerms where
import Fairmint.OpenCapTable.Types.Core (Context)
import Fairmint.OpenCapTable.Types.Validation (validateNonEmptyStringsArray)
import Fairmint.Shared.TypeHelpers (validateOptionalText)
import qualified DA.Map as Map
import qualified DA.Optional as Optional
import qualified DA.Set as Set


template VestingTerms
Expand Down Expand Up @@ -51,7 +54,8 @@ validateOcfVestingTermsData d =
d.name /= "" && d.description /= "" &&
validateNonEmptyStringsArray d.comments &&
(not (null d.vesting_conditions)) &&
all validateOcfVestingCondition d.vesting_conditions
all validateOcfVestingCondition d.vesting_conditions &&
validateOcfVestingConditionGraph d.vesting_conditions

-- Enum - Allocation Type
-- Enumeration of allocation types for vesting terms. Using an example of 18 shares split across 4 tranches, each allocation type results in a different schedule.
Expand Down Expand Up @@ -221,3 +225,157 @@ validateOcfVestingCondition c =
optional True validateOcfVestingConditionPortion c.portion &&
validateOcfVestingTrigger c.trigger &&
validateNonEmptyStringsArray c.next_condition_ids

vestingConditionIds : [OcfVestingCondition] -> [Text]
vestingConditionIds conditions = map (\condition -> condition.id) conditions

vestingGraphRootConditionId : [OcfVestingCondition] -> Optional Text
vestingGraphRootConditionId conditions = case conditions of
root :: _ -> Some root.id
[] -> None
Comment thread
coderabbitai[bot] marked this conversation as resolved.

hasAtMostOneVestingStartTrigger : [OcfVestingCondition] -> Bool
hasAtMostOneVestingStartTrigger conditions =
foldl
(\count condition -> case condition.trigger of
OcfVestingStartTrigger -> count + 1
_ -> count)
0
conditions <= 1

hasDuplicateTexts : [Text] -> Bool
hasDuplicateTexts texts = hasDuplicateTextsSeen Set.empty texts

hasDuplicateTextsSeen : Set.Set Text -> [Text] -> Bool
hasDuplicateTextsSeen seen remaining = case remaining of
[] -> False
text :: rest ->
Set.member text seen || hasDuplicateTextsSeen (Set.insert text seen) rest

conditionEdgeIds : Map.Map Text [Text] -> Text -> [Text]
conditionEdgeIds edges conditionId =
Optional.fromOptional [] (Map.lookup conditionId edges)

reachableConditionIds : Map.Map Text [Text] -> Set.Set Text -> [Text] -> Set.Set Text
reachableConditionIds edges visited pending = case pending of
[] -> visited
currentId :: rest ->
if Set.member currentId visited
then reachableConditionIds edges visited rest
else reachableConditionIds edges (Set.insert currentId visited) (conditionEdgeIds edges currentId ++ rest)

reachableConditionIdsAvoiding : Map.Map Text [Text] -> Text -> Set.Set Text -> [Text] -> Set.Set Text
reachableConditionIdsAvoiding edges blockedId visited pending = case pending of
[] -> visited
currentId :: rest ->
if currentId == blockedId || Set.member currentId visited
then reachableConditionIdsAvoiding edges blockedId visited rest
else reachableConditionIdsAvoiding edges blockedId (Set.insert currentId visited) (conditionEdgeIds edges currentId ++ rest)

conditionReachesTarget : Map.Map Text [Text] -> Text -> Text -> Bool
conditionReachesTarget edges sourceId targetId =
Set.member targetId (reachableConditionIds edges Set.empty [sourceId])

conditionReachesTargetAvoiding : Map.Map Text [Text] -> Text -> Text -> Text -> Bool
conditionReachesTargetAvoiding edges blockedId sourceId targetId =
Set.member targetId (reachableConditionIdsAvoiding edges blockedId Set.empty [sourceId])

allConditionsReachableFromStart : Map.Map Text [Text] -> Text -> [OcfVestingCondition] -> Bool
allConditionsReachableFromStart nextEdges startConditionId conditions =
let reachableIds = reachableConditionIds nextEdges Set.empty [startConditionId]
in all (\condition -> Set.member condition.id reachableIds) conditions

vestingTriggerReferencesKnownCondition : [Text] -> Map.Map Text [Text] -> OcfVestingCondition -> Bool
vestingTriggerReferencesKnownCondition conditionIds nextEdges condition = case condition.trigger of
OcfVestingScheduleRelativeTrigger{relative_to_condition_id} ->
relative_to_condition_id /= condition.id &&
relative_to_condition_id `elem` conditionIds &&
conditionReachesTarget nextEdges relative_to_condition_id condition.id
_ -> True
Comment thread
HardlyDifficult marked this conversation as resolved.

vestingConditionReferencesKnownConditions : [Text] -> Map.Map Text [Text] -> OcfVestingCondition -> Bool
vestingConditionReferencesKnownConditions conditionIds nextEdges condition =
not (hasDuplicateTexts condition.next_condition_ids) &&
all (`elem` conditionIds) condition.next_condition_ids &&
vestingTriggerReferencesKnownCondition conditionIds nextEdges condition

vestingTriggerAnchorDominatesCondition : Map.Map Text [Text] -> Text -> OcfVestingCondition -> Bool
vestingTriggerAnchorDominatesCondition nextEdges startConditionId condition = case condition.trigger of
OcfVestingScheduleRelativeTrigger{relative_to_condition_id} ->
not (conditionReachesTargetAvoiding nextEdges relative_to_condition_id startConditionId condition.id)
_ -> True

addDependentEdge : Text -> Text -> Map.Map Text [Text] -> Map.Map Text [Text]
addDependentEdge fromConditionId toConditionId edges =
let conditionEdges = Optional.fromOptional [] (Map.lookup fromConditionId edges)
updatedEdges =
if toConditionId `elem` conditionEdges
then conditionEdges
else toConditionId :: conditionEdges
in Map.insert fromConditionId updatedEdges edges

vestingConditionEdges : [OcfVestingCondition] -> Map.Map Text [Text]
vestingConditionEdges conditions =
let baseEdges = Map.fromList (map (\condition -> (condition.id, condition.next_condition_ids)) conditions)
in foldl addRelativeEdge baseEdges conditions
where
addRelativeEdge : Map.Map Text [Text] -> OcfVestingCondition -> Map.Map Text [Text]
addRelativeEdge edges condition = case condition.trigger of
OcfVestingScheduleRelativeTrigger{relative_to_condition_id} ->
addDependentEdge relative_to_condition_id condition.id edges
_ -> edges

data CycleCheckResult = CycleCheckResult with
has_cycle: Bool
checked: Set.Set Text
deriving (Eq, Show)

vestingConditionCycleCheck :
Map.Map Text [Text] ->
Set.Set Text ->
Set.Set Text ->
Text ->
CycleCheckResult
vestingConditionCycleCheck edges visiting checked conditionId
| Set.member conditionId visiting = CycleCheckResult with has_cycle = True; checked = checked
| Set.member conditionId checked = CycleCheckResult with has_cycle = False; checked = checked
| otherwise =
let result =
foldl
(\current nextId ->
if current.has_cycle
then current
else vestingConditionCycleCheck edges (Set.insert conditionId visiting) current.checked nextId)
(CycleCheckResult with has_cycle = False; checked = checked)
(conditionEdgeIds edges conditionId)
in
if result.has_cycle
then result
else result with checked = Set.insert conditionId result.checked

vestingConditionGraphHasCycle : Map.Map Text [Text] -> [Text] -> Bool
vestingConditionGraphHasCycle edges conditionIds =
let result =
foldl
(\current conditionId ->
if current.has_cycle
then current
else vestingConditionCycleCheck edges Set.empty current.checked conditionId)
(CycleCheckResult with has_cycle = False; checked = Set.empty)
conditionIds
in result.has_cycle

validateOcfVestingConditionGraph : [OcfVestingCondition] -> Bool
validateOcfVestingConditionGraph conditions =
let conditionIds = vestingConditionIds conditions
nextEdges = Map.fromList (map (\condition -> (condition.id, condition.next_condition_ids)) conditions)
edges = vestingConditionEdges conditions
in not (hasDuplicateTexts conditionIds) &&
Comment thread
HardlyDifficult marked this conversation as resolved.
hasAtMostOneVestingStartTrigger conditions &&
all (vestingConditionReferencesKnownConditions conditionIds nextEdges) conditions &&
(case vestingGraphRootConditionId conditions of
Some startConditionId ->
allConditionsReachableFromStart nextEdges startConditionId conditions &&
all (vestingTriggerAnchorDominatesCondition nextEdges startConditionId) conditions
None -> False) &&
not (vestingConditionGraphHasCycle edges conditionIds)
Comment thread
cursor[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion Test/daml.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ dependencies:
- daml-stdlib
- daml-script
data-dependencies:
- ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.12.dar
- ../OpenCapTable-v34/.daml/dist/OpenCapTable-v34-0.0.13.dar
build-options:
- -Wno-template-interface-depends-on-daml-script
169 changes: 169 additions & 0 deletions Test/daml/OpenCapTable/TestVestingTerms.daml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,40 @@ createVestingTerms issuer capTableCid =
edits = []
deletes = []

vestingCondition : Text -> OcfVestingTrigger -> [Text] -> OcfVestingCondition
vestingCondition conditionId trigger nextConditionIds =
OcfVestingCondition with
id = conditionId
description = Some conditionId
portion = None
quantity = Some 1.0
trigger = trigger
next_condition_ids = nextConditionIds

vestingTermsData : Text -> [OcfVestingCondition] -> VestingTermsOcfData
vestingTermsData vestingTermsId conditions =
VestingTermsOcfData with
id = vestingTermsId
name = vestingTermsId
description = vestingTermsId
allocation_type = OcfAllocationCumulativeRounding
vesting_conditions = conditions
comments = []

createVestingTermsData issuer capTableCid data_ =
submit issuer do
exerciseCmd capTableCid CT.UpdateCapTable with
creates = [CT.OcfCreateVestingTerms data_]
edits = []
deletes = []

createVestingTermsDataMustFail issuer capTableCid data_ =
submitMustFail issuer do
exerciseCmd capTableCid CT.UpdateCapTable with
creates = [CT.OcfCreateVestingTerms data_]
edits = []
deletes = []

testCreateAndArchiveVestingTerms = script do
TestOcp{system_operator, issuer, ctx, cap_table} <- setupTestOcp
_ <- createVestingTerms issuer cap_table
Expand Down Expand Up @@ -113,3 +147,138 @@ testVestingTerms_ZeroDenominatorFails = script do
comments = []]
edits = []
deletes = []

testVestingTerms_DuplicateConditionIdsFail = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let duplicateA = vestingCondition "dup" OcfVestingStartTrigger []
let duplicateB = vestingCondition "dup" (OcfVestingEventTrigger) []
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_DUP_COND" [duplicateA, duplicateB])
pure ()

testVestingTerms_DuplicateNextConditionIdsFail = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["next", "next"]
let next = vestingCondition "next" OcfVestingEventTrigger []
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_DUP_NEXT" [start, next])
pure ()

testVestingTerms_MissingNextConditionIdFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["missing"]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_MISSING_NEXT" [start])
pure ()

testVestingTerms_MissingRelativeConditionIdFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["relative"]
let relative = vestingCondition
"relative"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "missing")
[]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_MISSING_RELATIVE" [start, relative])
pure ()

testVestingTerms_RelativeConditionCannotReferenceSelf = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let selfRelative = vestingCondition
"self"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "self")
[]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_SELF_RELATIVE" [selfRelative])
pure ()

testVestingTerms_RelativeConditionMustBePredecessorFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["other", "relative"]
let other = vestingCondition "other" OcfVestingEventTrigger []
let relative = vestingCondition
"relative"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "other")
[]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_RELATIVE_PREDECESSOR" [start, other, relative])
pure ()

testVestingTerms_RelativeAnchorMustDominateAllPathsFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["anchor", "bypass"]
let anchor = vestingCondition "anchor" OcfVestingEventTrigger ["relative"]
let bypass = vestingCondition "bypass" OcfVestingEventTrigger ["relative"]
let relative = vestingCondition
"relative"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "anchor")
[]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_RELATIVE_DOMINATOR" [start, anchor, bypass, relative])
pure ()

testVestingTerms_EventOnlyRootIsValid = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let eventRoot = vestingCondition "qualifying-sale" OcfVestingEventTrigger []
_ <- createVestingTermsData issuer cap_table (vestingTermsData "VT_EVENT_ONLY_ROOT" [eventRoot])
pure ()

testVestingTerms_OrphanConditionFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger []
let orphan = vestingCondition "orphan" OcfVestingEventTrigger []
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_ORPHAN" [start, orphan])
pure ()

testVestingTerms_FirstConditionIsGraphRoot = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let start = vestingCondition "start" OcfVestingStartTrigger ["next"]
let next = vestingCondition "next" OcfVestingEventTrigger []
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_UNORDERED_START" [next, start])
pure ()

testVestingTerms_ReachableSecondStartConditionFails = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let firstStart = vestingCondition "start_one" OcfVestingStartTrigger ["start_two"]
let secondStart = vestingCondition "start_two" OcfVestingStartTrigger ["next"]
let next = vestingCondition "next" OcfVestingEventTrigger []
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_MULTIPLE_STARTS" [firstStart, secondStart, next])
pure ()

testVestingTerms_CyclicNextConditionsFail = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let first = vestingCondition "first" OcfVestingStartTrigger ["second"]
let second = vestingCondition "second" OcfVestingEventTrigger ["first"]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_CYCLE" [first, second])
pure ()

testVestingTerms_MutualRelativeConditionsWithoutRootFail = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let first = vestingCondition
"first"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "second")
[]
let second = vestingCondition
"second"
(OcfVestingScheduleRelativeTrigger with
period = OcfVestingPeriodDays with length_ = 30, occurrences = 1, cliff_installment = None
relative_to_condition_id = "first")
[]
createVestingTermsDataMustFail issuer cap_table (vestingTermsData "VT_CYCLE_RELATIVE" [first, second])
pure ()

testVestingTerms_EditCannotCreateInvalidGraph = script do
TestOcp{issuer, cap_table} <- setupTestOcp
let validStart = vestingCondition "start" OcfVestingStartTrigger ["next"]
let validNext = vestingCondition "next" OcfVestingEventTrigger []
result <- createVestingTermsData issuer cap_table (vestingTermsData "VT_EDIT_GRAPH" [validStart, validNext])
let invalidEdit = vestingTermsData "VT_EDIT_GRAPH" [(validStart with next_condition_ids = ["missing"]), validNext]
submitMustFail issuer do
exerciseCmd result.updatedCapTableCid CT.UpdateCapTable with
creates = []
edits = [CT.OcfEditVestingTerms invalidEdit]
deletes = []
pure ()
Comment thread
HardlyDifficult marked this conversation as resolved.
3 changes: 3 additions & 0 deletions dars/OpenCapTable-v34/0.0.13/OpenCapTable-v34.dar
Git LFS file not shown
7 changes: 7 additions & 0 deletions dars/dars.lock
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
"uploadedAt": "2026-07-11T15:08:11.000Z",
"networks": []
},
"OpenCapTable-v34/0.0.13/OpenCapTable-v34.dar": {
"sha256": "24923ebd1e8699a23d1e5c23ac94ecfd8fd465655fbd13aaa593079a942d0334",
"size": 3193070,
"sdkVersion": "3.4.10",
"uploadedAt": "2026-07-11T15:31:43.000Z",
"networks": []
},
"OpenCapTable-v34/0.0.2/OpenCapTable-v34.dar": {
"sha256": "2ce462b8afcb1bcdbe351c56b0a47de478477f3c655da00e41cb397f63e997d6",
"size": 2446238,
Expand Down
Loading