Skip to content

Commit e19a688

Browse files
committed
feat: add Linear Vesting scenario and Plinth submission
Second real-world smart contract scenario after Two-Party Escrow. Adapted from plutus-benchmark/linear-vesting. The validator controls gradual token release over a time schedule: PartialUnlock for proportional withdrawal during the vesting period, FullUnlock for everything after it ends. - Add scenario spec with validation rules, vesting schedule math, state lifecycle diagrams - Add 27-test suite covering happy paths, auth failures, temporal violations, amount mismatches, datum preservation, double satisfaction - Add Plinth validator using high-level PlutusTx API - Add Plinth 1.45.0.0 submission with compiled UPLC and metrics - Add Plinth 1.60.0.0 preview submission (source only, UPLC pending) Closes #2153 Closes #2154
1 parent 91cb507 commit e19a688

File tree

16 files changed

+8292
-58
lines changed

16 files changed

+8292
-58
lines changed

cape.cabal

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ library
9292
FibonacciIterative
9393
PlutusCore.Data.Compact.Parser
9494
PlutusCore.Data.Compact.Printer
95+
LinearVesting
96+
LinearVesting.Fixture
9597
TwoPartyEscrow
9698
TwoPartyEscrow.Fixture
9799

@@ -163,6 +165,15 @@ library cape-preview
163165
Preview.FibonacciIterative
164166
Preview.TwoPartyEscrow
165167

168+
other-modules:
169+
Ecd
170+
Factorial
171+
Fibonacci
172+
FibonacciIterative
173+
LinearVesting
174+
TwoPartyEscrow
175+
TwoPartyEscrow.Fixture
176+
166177
build-depends:
167178
, cape
168179
, plutus-core
@@ -208,6 +219,8 @@ test-suite cape-tests
208219
FibonacciSpec
209220
PlutusCore.Data.Compact.ParserSpec
210221
PlutusCore.Data.Compact.PrinterSpec
222+
LinearVestingSpec
223+
LinearVestingSpec.Fixture
211224
TwoPartyEscrowSpec
212225
TwoPartyEscrowSpec.Fixture
213226
ValidatorHelpers

lib/Cape/Tests.hs

Lines changed: 50 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -847,11 +847,49 @@ resolveAsBytes dataStructures text
847847
die $
848848
"Failed to parse asset component: "
849849
<> toString (renderParseError parseErr)
850-
Right builtinData ->
851-
let coreData = builtinData
852-
in case coreData of
853-
PLC.B bytestring -> pure $ Builtins.toBuiltin bytestring
854-
_ -> die $ "Expected bytestring for asset component: " <> toString text
850+
Right parsedData ->
851+
case parsedData of
852+
PLC.B bytestring -> pure $ Builtins.toBuiltin bytestring
853+
_ -> die $ "Expected bytestring for asset component: " <> toString text
854+
855+
-- * Datum Resolution
856+
857+
{- | Resolve a JSON string value to a Datum.
858+
859+
Supports @references (resolving from data_structures)
860+
and inline BuiltinData text literals.
861+
-}
862+
resolveDatumValue ::
863+
HaskellMap.Map Text DataStructureEntry -> Text -> Text -> IO V3.Datum
864+
resolveDatumValue dataStructures context dataText
865+
| Text.isPrefixOf "@" dataText && Text.length dataText > 1 = do
866+
resolvedBuiltinData <- resolveBuiltinDataReference dataStructures dataText
867+
pure $ V3.Datum resolvedBuiltinData
868+
| otherwise =
869+
case parseBuiltinDataText dataText of
870+
Left parseErr ->
871+
die $
872+
"Failed to parse "
873+
<> toString context
874+
<> ": "
875+
<> toString (renderParseError parseErr)
876+
Right parsedData ->
877+
pure $ V3.Datum (BI.BuiltinData parsedData)
878+
879+
{- | Resolve an optional JSON value to OutputDatum.
880+
881+
Nothing becomes NoOutputDatum; Just (String ...) resolves via 'resolveDatumValue'.
882+
-}
883+
resolveOutputDatum ::
884+
HaskellMap.Map Text DataStructureEntry ->
885+
Text ->
886+
Maybe AesonTypes.Value ->
887+
IO V3.OutputDatum
888+
resolveOutputDatum _dataStructures _context Nothing = pure V3.NoOutputDatum
889+
resolveOutputDatum dataStructures context (Just (Json.String dataText)) =
890+
V3.OutputDatum <$> resolveDatumValue dataStructures context dataText
891+
resolveOutputDatum _dataStructures context (Just other) =
892+
die $ toString context <> " must be a string, got: " <> show other
855893

856894
-- * Patch Operation Conversion
857895

@@ -955,21 +993,7 @@ convertPatchOperation dataStructures spec =
955993
die ("Failed to parse UTXO reference: " <> show parseErr)
956994
Right txOutRef -> do
957995
value <- buildValue dataStructures valueSpec
958-
outputDatum <- case mDatumValue of
959-
Nothing -> pure V3.NoOutputDatum
960-
Just (Json.String dataText) ->
961-
if Text.isPrefixOf "@" dataText && Text.length dataText > 1
962-
then do
963-
resolvedBuiltinData <-
964-
resolveBuiltinDataReference dataStructures dataText
965-
pure $ V3.OutputDatum (V3.Datum resolvedBuiltinData)
966-
else case parseBuiltinDataText dataText of
967-
Left parseErr ->
968-
die ("Failed to parse input datum: " <> toString (renderParseError parseErr))
969-
Right parsedBuiltinData ->
970-
pure $ V3.OutputDatum (V3.Datum (BI.BuiltinData parsedBuiltinData))
971-
Just other ->
972-
die $ "Input datum must be a string, got: " <> show other
996+
outputDatum <- resolveOutputDatum dataStructures "input datum" mDatumValue
973997
pure $ AddInputUTXO txOutRef value isOwnInput outputDatum
974998
SetValidRangeSpec fromTime toTime -> do
975999
let fromPosix = fmap V3.POSIXTime fromTime
@@ -982,55 +1006,23 @@ convertPatchOperation dataStructures spec =
9821006
AddOutputUTXOWithDatumSpec addressSpec valueSpec datumValue -> do
9831007
value <- buildValue dataStructures valueSpec
9841008
address <- parseAddressSpec dataStructures addressSpec
985-
-- Convert JSON Value to BuiltinData with reference resolution support
986-
case datumValue of
987-
Json.String dataText -> do
988-
-- Check if this is a reference to a builtin_data type
989-
if Text.isPrefixOf "@" dataText && Text.length dataText > 1
990-
then do
991-
-- Reference case: resolve from data structures
992-
resolvedBuiltinData <-
993-
resolveBuiltinDataReference dataStructures dataText
994-
let datum = V3.Datum resolvedBuiltinData
995-
pure $ AddOutputUTXOWithDatum address value datum
996-
else do
997-
-- Direct BuiltinData case
998-
case parseBuiltinDataText dataText of
999-
Left parseErr ->
1000-
die ("Failed to parse BuiltinData: " <> toString (renderParseError parseErr))
1001-
Right parsedBuiltinData -> do
1002-
let datum = V3.Datum (BI.BuiltinData parsedBuiltinData)
1003-
pure $ AddOutputUTXOWithDatum address value datum
1009+
datum <- resolveDatumValue dataStructures "output datum" =<< case datumValue of
1010+
Json.String dataText -> pure dataText
10041011
other ->
10051012
die $
10061013
"Datum must be a string with BuiltinData encoding, got: "
10071014
<> show other
1015+
pure $ AddOutputUTXOWithDatum address value datum
10081016
RemoveOutputUTXOSpec index -> do
10091017
pure $ RemoveOutputUTXO index
10101018
SetScriptDatumSpec datumValue -> do
1011-
-- Convert JSON Value to BuiltinData with reference resolution support
1012-
case datumValue of
1013-
Json.String dataText -> do
1014-
-- Check if this is a reference to a builtin_data type
1015-
if Text.isPrefixOf "@" dataText && Text.length dataText > 1
1016-
then do
1017-
-- Reference case: resolve from data structures
1018-
resolvedBuiltinData <-
1019-
resolveBuiltinDataReference dataStructures dataText
1020-
pure $ SetScriptDatum (V3.Datum resolvedBuiltinData)
1021-
else do
1022-
-- Literal case: parse as BuiltinData
1023-
case parseBuiltinDataText dataText of
1024-
Left parseErr ->
1025-
die $
1026-
"Failed to parse script datum BuiltinData: "
1027-
<> show (renderParseError parseErr)
1028-
Right builtinData ->
1029-
pure $ SetScriptDatum (V3.Datum (BI.BuiltinData builtinData))
1019+
datum <- resolveDatumValue dataStructures "script datum" =<< case datumValue of
1020+
Json.String dataText -> pure dataText
10301021
other ->
10311022
die $
10321023
"Script datum must be a string with BuiltinData encoding, got: "
10331024
<> show other
1025+
pure $ SetScriptDatum datum
10341026

10351027
{- | Parse AddressSpec into Address with reference resolution.
10361028

0 commit comments

Comments
 (0)