Skip to content

Commit

Permalink
Implement Claim redeemer branch for the deposit validator + relevant …
Browse files Browse the repository at this point in the history
…mutations (#1710)

Subtask of #199

- Implement the `Claim` redeemer branch of the deposit validator.

- Add head currency symbol to the `Claim` redeemer in order to check it
against the datum value.

- Add upper validity bound for increment in order to be able to check if
the deposit deadline has not been reached.

- Double the amount of contestation period value when setting the
deposit deadline in order to give enough room for increment
to be valid (before the deadline).

- Add appropriate deposit mutations.

- Left a FIXME to not forget to fix the specification in terms of
changes in the `Claim` redeemer

---

<!-- Consider each and tick it off one way or the other -->
* [x] CHANGELOG updated or not needed
* [x] Documentation updated or not needed
* [x] Haddocks updated or not needed
* [ ] No new TODOs introduced or explained herafter
  • Loading branch information
locallycompact authored Oct 15, 2024
2 parents d846089 + 362f9cd commit e30fc24
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 32 deletions.
7 changes: 4 additions & 3 deletions hydra-cluster/src/Hydra/Cluster/Scenarios.hs
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,8 @@ canRecoverDeposit tracer workDir node hydraScriptsTxId =
`shouldReturn` 0

let path = BSC.unpack $ urlEncode False $ encodeUtf8 $ T.pack $ show (getTxId $ getTxBody tx)

threadDelay $ fromIntegral deadline
-- NOTE: we need to wait for the deadline to pass before we can recover the deposit
threadDelay $ fromIntegral (deadline * 2)

recoverResp <-
parseUrlThrow ("DELETE " <> hydraNodeBaseUrl n1 <> "/commits/" <> path)
Expand Down Expand Up @@ -885,7 +885,8 @@ canSeePendingDeposits tracer workDir node hydraScriptsTxId =

forM_ deposited $ \deposit -> do
let path = BSC.unpack $ urlEncode False $ encodeUtf8 $ T.pack $ show deposit
threadDelay $ fromIntegral deadline
-- NOTE: we need to wait for the deadline to pass before we can recover the deposit
threadDelay $ fromIntegral (deadline * 2)
recoverResp <-
parseUrlThrow ("DELETE " <> hydraNodeBaseUrl n1 <> "/commits/" <> path)
>>= httpJSON
Expand Down
5 changes: 4 additions & 1 deletion hydra-node/src/Hydra/API/HTTPServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ handleDraftCommitUtxo env directChain getCommitInfo body = do
CannotCommit -> pure $ responseLBS status500 [] (Aeson.encode (FailedToDraftTxNotInitializing :: PostTxError tx))
where
deposit headId commitBlueprint = do
deadline <- addUTCTime (toNominalDiffTime contestationPeriod) <$> getCurrentTime
-- NOTE: We double the contestation period and use it for the deadline
-- value in order to give enough time for the increment to be valid in
-- terms of deadline.
deadline <- addUTCTime (toNominalDiffTime contestationPeriod * 2) <$> getCurrentTime
draftDepositTx headId commitBlueprint deadline <&> \case
Left e -> responseLBS status400 [] (Aeson.encode $ toJSON e)
Right depositTx -> okJSON $ DraftCommitTxResponse depositTx
Expand Down
7 changes: 5 additions & 2 deletions hydra-node/src/Hydra/Chain/Direct/Handlers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,11 @@ prepareTxToPost timeHandle wallet ctx spendableUTxO tx =
case collect ctx headId headParameters utxo spendableUTxO of
Left _ -> throwIO (FailedToConstructCollectTx @Tx)
Right collectTx -> pure collectTx
IncrementTx{headId, headParameters, incrementingSnapshot, depositTxId} ->
case increment ctx spendableUTxO headId headParameters incrementingSnapshot depositTxId of
IncrementTx{headId, headParameters, incrementingSnapshot, depositTxId} -> do
(_, currentTime) <- throwLeft currentPointInTime
let HeadParameters{contestationPeriod} = headParameters
(upperBound, _) <- calculateTxUpperBoundFromContestationPeriod currentTime contestationPeriod
case increment ctx spendableUTxO headId headParameters incrementingSnapshot depositTxId upperBound of
Left _ -> throwIO (FailedToConstructIncrementTx @Tx)
Right incrementTx' -> pure incrementTx'
RecoverTx{headId, recoverTxId, deadline} -> do
Expand Down
13 changes: 8 additions & 5 deletions hydra-node/src/Hydra/Chain/Direct/State.hs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,9 @@ increment ::
ConfirmedSnapshot Tx ->
-- | Deposited TxId
TxId ->
SlotNo ->
Either IncrementTxError Tx
increment ctx spendableUTxO headId headParameters incrementingSnapshot depositTxId = do
increment ctx spendableUTxO headId headParameters incrementingSnapshot depositTxId upperValiditySlot = do
pid <- headIdToPolicyId headId ?> InvalidHeadIdInIncrement{headId}
let utxoOfThisHead' = utxoOfThisHead pid spendableUTxO
headUTxO <- UTxO.find (isScriptTxOut headScript) utxoOfThisHead' ?> CannotFindHeadOutputInIncrement
Expand All @@ -516,7 +517,7 @@ increment ctx spendableUTxO headId headParameters incrementingSnapshot depositTx
Just deposit
| null deposit ->
Left SnapshotIncrementUTxOIsNull
| otherwise -> Right $ incrementTx scriptRegistry ownVerificationKey headId headParameters headUTxO sn (UTxO.singleton (depositedIn, depositedOut))
| otherwise -> Right $ incrementTx scriptRegistry ownVerificationKey headId headParameters headUTxO sn (UTxO.singleton (depositedIn, depositedOut)) upperValiditySlot
where
headScript = fromPlutusScript @PlutusScriptV2 Head.validatorScript
depositScript = fromPlutusScript @PlutusScriptV2 Deposit.validatorScript
Expand Down Expand Up @@ -1206,12 +1207,13 @@ genIncrementTx numParties = do
let version = 1
snapshot <- genConfirmedSnapshot headId 2 version openUTxO (Just deposited) Nothing (ctxHydraSigningKeys ctx)
let depositUTxO = utxoFromTx txDeposit
slotNo <- arbitrary
pure
( cctx
, maybe mempty toList (utxoToCommit $ getSnapshot snapshot)
, st
, depositUTxO
, unsafeIncrement cctx (openUTxO <> depositUTxO) headId (ctxHeadParameters ctx) snapshot depositTxId
, unsafeIncrement cctx (openUTxO <> depositUTxO) headId (ctxHeadParameters ctx) snapshot depositTxId slotNo
)

genDecrementTx :: Int -> Gen (ChainContext, [TxOut CtxUTxO], OpenState, UTxO, Tx)
Expand Down Expand Up @@ -1363,9 +1365,10 @@ unsafeIncrement ::
HeadParameters ->
ConfirmedSnapshot Tx ->
TxId ->
SlotNo ->
Tx
unsafeIncrement ctx spendableUTxO headId parameters incrementingSnapshot depositedTxId =
either (error . show) id $ increment ctx spendableUTxO headId parameters incrementingSnapshot depositedTxId
unsafeIncrement ctx spendableUTxO headId parameters incrementingSnapshot depositedTxId slotNo =
either (error . show) id $ increment ctx spendableUTxO headId parameters incrementingSnapshot depositedTxId slotNo

unsafeDecrement ::
HasCallStack =>
Expand Down
4 changes: 2 additions & 2 deletions hydra-plutus/scripts/vDeposit.plutus
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "PlutusScriptV2",
"description": "hydra-vDeposit-0.19.0-96-g4d48208d28",
"cborHex": "590bae590bab0100003233223332223233223232323232323232323232323232323232323232323232323232323232323232222533533322253353232325335333573466e1d2000002031030112200215335333573466e1d20020020310301321223001003375a6ae840044c05c060d55cf0011aab9d001375400a205c6442a66a6a6a6aa00444444444444400a4400444a666a00442660109210344303400333573466e24004d401c8880080c80cc4c061241034430330013018491034430330013300549010344303500333573466e3cdc9180d99a8048009aa8011111111111110051b923023302030273500422200103002f102f13500122002225335001102d1335738004058646464a66a666ae68cdc3a400000405c05a264646464a66a666ae68cdc3a4000004064062264666444246660020080060046eb8d5d08011bad35742002600a6ae84d5d10009aba20011301801935573c0046aae74004dd51aba1002300b23232325335333573466e1d20000020320311332212330010030023013357420026eb8d5d09aba20011301801935573c0046aae74004dd50008980a00a9aab9e00235573a0026ea800cc8c8c94cd4ccd5cd19b87480000080b80b44cc8848cc00400c008c8c8c94cd4ccd5cd19b87480000080c40c04c8c8c8c8c8c8c8c8c8c8cccccccccccc88888888888848cccccccccccc00403403002c02802402001c01801401000c008cc06006cd5d08059980c00d9aba100a3301801e35742012666048eb9408cd5d08041998123ae50233574200e660300346ae84018ccc09007dd69aba10053232325335333573466e1d200000203e03d1332212330010030023232325335333573466e1d20000020410401332212330010030023301d75a6ae84004c070d5d09aba20011302702835573c0046aae74004dd51aba10013232325335333573466e1d20000020410401332212330010030023301d75a6ae84004c070d5d09aba20011302702835573c0046aae74004dd51aba1357440022604804a6aae78008d55ce8009baa3574200866030eb8d5d080199981200c80a9aba100233302475c02a6ae84004c074d5d09aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae880044c05c060d55cf0011aab9d00137546ae84004c030d5d09aba20011301401535573c0046aae74004dd5000880c89808a481035054350033028221225335333573466e24009200002b02a10251533500110252213350260023353006120013370200890010008900090009191919299a999ab9a3370e900000101481408140a99a999ab9a3370e90010010148140814898078081aab9e00235573a0026ea800488c8c8c94cd4ccd5cd19b87480000080a40a04488800854cd4ccd5cd19b87480080080a40a04c84888c00c010c014d5d08008a99a999ab9a3370e900200101481408911000898078081aab9e00235573a0026ea80048c88c008dd60009812911999aab9f0012021233502030043574200460066ae880080408c8c8c94cd4ccd5cd19b87480000080980944c848888c00c014dd71aba100115335333573466e1d20020020260251321222230010053007357420022a66a666ae68cdc3a400800404c04a26424444600400a60146ae8400454cd4ccd5cd19b87480180080980944c848888c010014c014d5d0800898060069aab9e00235573a0026ea80048c8c8c94cd4ccd5cd19b87480000080940904c848888888c014020c024d5d08008a99a999ab9a3370e900100101281209909111111180380418049aba100115335333573466e1d20040020250241332212222222330060090083009357420026eb8d5d09aba200115335333573466e1d2006002025024133221222222233002009008375c6ae84004dd71aba1357440022a66a666ae68cdc3a401000404a0482664424444444660020120106eb8d5d08009bad357426ae8800454cd4ccd5cd19b874802800809409044888888801054cd4ccd5cd19b874803000809409044888888800c4c02c030d55cf0011aab9d00137540024646464a66a666ae68cdc3a4000004048046266442466002006004600a6ae84004c01cd5d09aba20011300a00b35573c0046aae74004dd50009191919299a999ab9a3370e900000101181109991091980080180118029aba1001375a6ae84d5d1000898048051aab9e00235573a0026ea80048c8c8c94cd4ccd5cd19b87480000080880844dd71aba10011300800935573c0046aae74004dd5000991191919299a999ab9a3370e90000010110108991919999111091999800802802001801191919299a999ab9a3370e900000101381309991091980080180118061aba10013300a00b357426ae880044c034038d55cf0011aab9d00137546ae8400cccc035d728061aba10023232325335333573466e1d20000020270261122200315335333573466e1d200200202702613212223001004375c6ae8400454cd4ccd5cd19b874801000809c0984c84888c008010d5d0800898068071aab9e00235573a0026ea8d5d080099803bae357426ae88004d5d10009aba20011300800935573c0046aae74004dd500091191919299a999ab9a3370e900100101101088910008a99a999ab9a3370e9000001011010899091180100198029aba10011300800935573c0046aae74004dd50009191919299a999ab9a3370e900000101000f899091180100198029aba100115335333573466e1d200200202001f132333222122333001005004003375a6ae84008dd69aba1001375a6ae84d5d10009aba20011300600735573c0046aae74004dd50009191919299a999ab9a3370e900000100f80f09909118010019bae357420022a66a666ae68cdc3a400400403e03c26424460020066eb8d5d0800898028031aab9e00235573a0026ea80048c98cd5ce000802a49035054310013300175ceb488c88c008dd5800980d111999aab9f001201623233501633221233001003002300635573a002600a6aae78004c010d5d10019aba1002005120013016225335001100b221337146eccd40088888cdd2a400066ae80d401088cdd2a400066ae80c02c008cd5d019806119a800919ba548000cd5d018068009bb100f2223374a900119aba0375000666ae80dd400119aba037500026ec4044004dd880699aba033300c75266018ea48dd400080199aba05333500213374a90001bb100b213374a900219aba000137620184266e9520023357406ea4004dd880619aba03300a7520026ec402cc0100048cd40048cdd2a400066ae80dd48009bb100423374a900119aba037520026ec4010894cd400484cdd2a400066ae80c00c004dd8802099ba548008dd8801a4c446446e98c008004c05088cd40052f5bded8c0446a0044466ae80cdd8180400118038009803001980891299a800898011801806110a99a800880111098031803802980811299a8008805910a99a800880191099a8071980380200118030009807911299a8010800910a99a801880219110a999a998088028010a801899a807801198040038008a801899a80600119802800801980711299a80088019109a8011119b8a001300600348810035003222001350022220023500122200333230010012212323330022300222230030042300222230020042300222230010042353300400400335003001122223330042533500113350060070072215335001133500800300922153233353300d00500315001133300800333500b00500c002150011333006002233500b00500100122253350011335008335008003002300600932221533353300d00600215003133300800233500b0060050011500313350083350080030023006001222532335002130014988854c8ccd4cc03801c00c540044c011261500113330070022300633500c0070010012335009300333500900400a30070021220021221223300100400332223500222350022235005223500222533353300b00600215335333573466e1c01400404003c403854cd4ccd5cd19b8900500101000f100c100d13300b00600213300b006002225335333573466e3c00800401c018401454cd4ccd5cd19b91002001007006100310041222003122200212220011220021220012323001001230022330020020011"
"description": "hydra-vDeposit-0.19.0-231-gc73175bbd",
"cborHex": "590c15590c12010000323322333222323322323232323232323232323232323232323232323232323232323232323232323232222533533332322223233500325335353530075002220012253335002213300949010344303100333573466e24004d40208880080d80d44c06924103443032001301a4910344303200133006490103443036003301d00135005222003103225335353530075002220022253335002213300949010344303400333573466e24004d40208880080d40d84c06924103443033001301a4910344303300133006490103443035003301d3724603c66a0160026aa0044444444444440146e48c098c08cc0a8d401488800440c84d400488008894cd400440bc4cd5ce00101711a800911111111111002991919299a999ab9a3370e9000001017817099191919299a999ab9a3370e9000001019819099199911091998008020018011bae357420046eb4d5d080098029aba1357440026ae880044c060064d55cf0011aab9d00137546ae84008c02c8c8c8c94cd4ccd5cd19b87480000080cc0c84cc8848cc00400c008c04cd5d08009bae357426ae880044c060064d55cf0011aab9d00137540022602802a6aae78008d55ce8009baa0033232325335333573466e1d200000202f02e1321223002003375c6ae8400454cd4ccd5cd19b87480080080bc0b84c8488c00400cdd69aba10011301401535573c0046aae74004dd5001191919299a999ab9a3370e9000001017817099910919800801801191919299a999ab9a3370e9000001019018899191919191919191919199999999999911111111111091999999999998008068060058050048040038030028020018011980c00d9aba100b3301801b357420146603003c6ae84024ccc091d728119aba100833302475ca0466ae8401ccc060068d5d080319981200fbad3574200a646464a66a666ae68cdc3a400000407e07c266442466002006004646464a66a666ae68cdc3a40000040840822664424660020060046603aeb4d5d0800980e1aba1357440022604e0506aae78008d55ce8009baa35742002646464a66a666ae68cdc3a40000040840822664424660020060046603aeb4d5d0800980e1aba1357440022604e0506aae78008d55ce8009baa357426ae880044c090094d55cf0011aab9d00137546ae84010cc061d71aba100333302401901535742004666048eb8054d5d0800980e9aba1357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10008980b80c1aab9e00235573a0026ea8d5d080098061aba1357440022602802a6aae78008d55ce8009baa001101a13011491035054350033029221225335333573466e24009200002c02b10261533500110262213350270023353006120013370200890010008900090009191919299a999ab9a3370e900000101501488148a99a999ab9a3370e90010010150148815098078081aab9e00235573a0026ea800488c8c8c94cd4ccd5cd19b87480000080a80a44488800854cd4ccd5cd19b87480080080a80a44c84888c00c010c014d5d08008a99a999ab9a3370e900200101501488911000898078081aab9e00235573a0026ea80048c88c008dd60009813111999aab9f0012022233502130043574200460066ae880080408c8c8c94cd4ccd5cd19b874800000809c0984c848888c00c014dd71aba100115335333573466e1d20020020270261321222230010053007357420022a66a666ae68cdc3a400800404e04c26424444600400a60146ae8400454cd4ccd5cd19b874801800809c0984c848888c010014c014d5d0800898060069aab9e00235573a0026ea80048c8c8c94cd4ccd5cd19b87480000080980944c848888888c014020c024d5d08008a99a999ab9a3370e900100101301289909111111180380418049aba100115335333573466e1d20040020260251332212222222330060090083009357420026eb8d5d09aba200115335333573466e1d2006002026025133221222222233002009008375c6ae84004dd71aba1357440022a66a666ae68cdc3a401000404c04a2664424444444660020120106eb8d5d08009bad357426ae8800454cd4ccd5cd19b874802800809809444888888801054cd4ccd5cd19b874803000809809444888888800c4c02c030d55cf0011aab9d00137540024646464a66a666ae68cdc3a400000404a048266442466002006004600a6ae84004c01cd5d09aba20011300a00b35573c0046aae74004dd50009191919299a999ab9a3370e900000101201189991091980080180118029aba1001375a6ae84d5d1000898048051aab9e00235573a0026ea80048c8c8c94cd4ccd5cd19b874800000808c0884dd71aba10011300800935573c0046aae74004dd5000991191919299a999ab9a3370e90000010118110991919999111091999800802802001801191919299a999ab9a3370e900000101401389991091980080180118061aba10013300a00b357426ae880044c034038d55cf0011aab9d00137546ae8400cccc035d728061aba10023232325335333573466e1d20000020280271122200315335333573466e1d200200202802713212223001004375c6ae8400454cd4ccd5cd19b87480100080a009c4c84888c008010d5d0800898068071aab9e00235573a0026ea8d5d080099803bae357426ae88004d5d10009aba20011300800935573c0046aae74004dd500091191919299a999ab9a3370e900100101181108910008a99a999ab9a3370e9000001011811099091180100198029aba10011300800935573c0046aae74004dd50009191919299a999ab9a3370e9000001010810099091180100198029aba100115335333573466e1d2002002021020132333222122333001005004003375a6ae84008dd69aba1001375a6ae84d5d10009aba20011300600735573c0046aae74004dd50009191919299a999ab9a3370e900000101000f89909118010019bae357420022a66a666ae68cdc3a400400404003e26424460020066eb8d5d0800898028031aab9e00235573a0026ea80048c98cd5ce000802a49035054310013300175ceb488c88c008dd5800980d911999aab9f001201723233501733221233001003002300635573a002600a6aae78004c010d5d10019aba10020051200122333573466e3c00800406005cc058894cd4004402c884cdc51bb33500222223374a900019aba035004223374a900019aba0300b00233574066018466a002466e952000335740601a0026ec403c888cdd2a400466ae80dd400199aba0375000466ae80dd40009bb1011001376201a66ae80ccc031d4998063a92375000200666ae814ccd40084cdd2a40006ec402c84cdd2a400866ae80004dd88061099ba548008cd5d01ba9001376201866ae80cc029d48009bb100b3004001233500123374a900019aba037520026ec40108cdd2a400466ae80dd48009bb1004225335001213374a900019aba030030013762008266e952002376200693111911ba630020013014223350014bd6f7b630111a8011119aba0337606010004600e002600c006602244a66a002260046006018442a66a0022004442600c600e00a602044a66a0022016442a66a002200644266a01c6600e008004600c002601e444a66a0042002442a66a006200864442a666a6602200a0042a006266a01e0046601000e0022a006266a0180046600a002006601c44a66a00220064426a0044466e28004c01800d22010035003222001350022220023500122200333230010012212323330022300222230030042300222230020042300222230010042353300400400335003001122223330042533500113350060070072215335001133500800300922153233353300d00500315001133300800333500b00500c002150011333006002233500b00500100122253350011335008335008003002300600932221533353300d00600215003133300800233500b0060050011500313350083350080030023006001222532335002130014988854c8ccd4cc03801c00c540044c011261500113330070022300633500c0070010012335009300333500900400a30070021220021221223300100400332223500222350022235005223500222533353300b00600215335333573466e1c01400404003c403854cd4ccd5cd19b8900500101000f100c100d13300b00600213300b006002225335333573466e3c00800401c018401454cd4ccd5cd19b91002001007006100310041222003122200212220011220021220012323001001230022330020020011"
}
31 changes: 24 additions & 7 deletions hydra-plutus/src/Hydra/Contract/Deposit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import Hydra.Contract.Commit (Commit)
import Hydra.Contract.DepositError (
DepositError (
DepositDeadlineNotReached,
DepositDeadlineSurpassed,
DepositNoLowerBoundDefined,
IncorrectDepositHash
DepositNoUpperBoundDefined,
IncorrectDepositHash,
WrongHeadIdInDepositDatum
),
)
import Hydra.Contract.Error (errorCode)
Expand All @@ -34,6 +37,8 @@ import PlutusLedgerApi.V2 (
ScriptContext (..),
ScriptHash,
SerialisedScript,
UpperBound (..),
ivTo,
serialiseCompiledCode,
txInfoOutputs,
txInfoValidRange,
Expand All @@ -43,7 +48,8 @@ import PlutusTx qualified

data DepositRedeemer
= -- | Claims already deposited funds.
Claim
-- FIXME: Make sure to change the spec and add head CS to the Claim redeemer.
Claim CurrencySymbol
| -- | Recovers m number of deposited outputs.
Recover Integer

Expand All @@ -57,22 +63,26 @@ PlutusTx.unstableMakeIsData ''DepositDatum

-- | v_deposit validator checks
--
-- * Claim redeemer -> more checks will be added
-- * Claim redeemer ->
-- * The deadline has not been reached.
-- * HeadId matches.
--
-- * Recover redeemer
-- * The deadline has been reached.
-- * The hash of recovered outputs are matching the deposited outputs.
validator :: DepositDatum -> DepositRedeemer -> ScriptContext -> Bool
validator depositDatum r ctx =
case r of
Claim ->
-- FIXME: Implement Claim redeemer
True
Claim headId' -> beforeDeadline && checkHeadId headId'
Recover m ->
afterDeadline
&& recoverOutputs m
where
DepositDatum (_headId, dl, deposits) = depositDatum
DepositDatum (headId, dl, deposits) = depositDatum

checkHeadId headId' =
traceIfFalse $(errorCode WrongHeadIdInDepositDatum) $
headId' == headId

recoverOutputs m =
traceIfFalse $(errorCode IncorrectDepositHash) $
Expand All @@ -81,6 +91,13 @@ validator depositDatum r ctx =
hashOfOutputs m =
hashTxOuts $ take m (txInfoOutputs txInfo)

beforeDeadline =
case ivTo (txInfoValidRange txInfo) of
UpperBound (Finite t) _ ->
traceIfFalse $(errorCode DepositDeadlineSurpassed) $
t <= dl
_ -> traceError $(errorCode DepositNoUpperBoundDefined)

afterDeadline =
case ivFrom (txInfoValidRange txInfo) of
LowerBound (Finite t) _ ->
Expand Down
Loading

0 comments on commit e30fc24

Please sign in to comment.