Skip to content

Commit c283fe8

Browse files
committed
Check if specified amount exceeds UTxO balance
Signed-off-by: Sasha Bogicevic <[email protected]>
1 parent 0c8c303 commit c283fe8

File tree

6 files changed

+57
-19
lines changed

6 files changed

+57
-19
lines changed

hydra-cluster/src/Hydra/Cluster/Scenarios.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1334,8 +1334,11 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
13341334
-- Get some L1 funds
13351335
(walletVk, walletSk) <- generate genKeyPair
13361336
commitUTxO <- seedFromFaucet backend walletVk 5_000_000 (contramap FromFaucet tracer)
1337-
commitUTxO2 <- seedFromFaucet backend walletVk 5_000_000 (contramap FromFaucet tracer)
1337+
-- This one is expected to fail since there is 5 ADA at the wallet address but we specified 6 ADA to commit
1338+
sendRequest 2 commitUTxO (Just 6_000_000)
1339+
`shouldThrow` expectErrorStatus 400 (Just "AmountTooLow")
13381340

1341+
commitUTxO2 <- seedFromFaucet backend walletVk 5_000_000 (contramap FromFaucet tracer)
13391342
let expectedCommit = fst $ capUTxO commitUTxO 2_000_000
13401343
let expectedCommit2 = fst $ capUTxO commitUTxO2 3_000_000
13411344

@@ -1383,6 +1386,16 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
13831386
where
13841387
hydraTracer = contramap FromHydraNode tracer
13851388

1389+
sendRequest :: MonadIO m => Int -> UTxO.UTxO -> Maybe Coin -> m (JsonResponse Aeson.Value)
1390+
sendRequest hydraNodeId utxo amt =
1391+
runReq defaultHttpConfig $
1392+
req
1393+
POST
1394+
(http "127.0.0.1" /: "commit")
1395+
(ReqBodyJson $ SimpleCommitRequest @Tx utxo amt)
1396+
(Proxy :: Proxy (JsonResponse Aeson.Value))
1397+
(port $ 4000 + hydraNodeId)
1398+
13861399
rejectCommit :: ChainBackend backend => Tracer IO EndToEndLog -> FilePath -> NominalDiffTime -> backend -> [TxId] -> IO ()
13871400
rejectCommit tracer workDir blockTime backend hydraScriptsTxId =
13881401
(`finally` returnFundsToFaucet tracer backend Alice) $ do

hydra-node/src/Hydra/API/HTTPServer.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,6 @@ handleDraftCommitUtxo env pparams directChain getCommitInfo body = do
279279
FullCommitRequest{blueprintTx, utxo} -> do
280280
draftCommit headId utxo blueprintTx Nothing
281281
SimpleCommitRequest{utxoToCommit, amount} -> do
282-
-- TODO: check if the provided amount is actually possible to extract from the utxoToCommit
283282
let blueprintTx = txSpendingUTxO utxoToCommit
284283
draftCommit headId utxoToCommit blueprintTx amount
285284
IncrementalCommit headId -> do
@@ -309,6 +308,7 @@ handleDraftCommitUtxo env pparams directChain getCommitInfo body = do
309308
UnsupportedLegacyOutput _ -> badRequest e
310309
CannotFindOwnInitial _ -> badRequest e
311310
DepositTooLow _ _ -> badRequest e
311+
AmountTooLow _ _ -> badRequest e
312312
_ -> responseLBS status500 [] (Aeson.encode $ toJSON e)
313313
Right commitTx ->
314314
okJSON $ DraftCommitTxResponse commitTx

hydra-node/src/Hydra/Chain.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ data PostTxError tx
203203
| FailedToConstructDecrementTx {failureReason :: Text}
204204
| FailedToConstructFanoutTx
205205
| DepositTooLow {providedValue :: Coin, minimumValue :: Coin}
206+
| AmountTooLow {providedValue :: Coin, totalUTxOValue :: Coin}
206207
deriving stock (Generic)
207208

208209
deriving stock instance IsChainState tx => Eq (PostTxError tx)

hydra-node/src/Hydra/Chain/Direct/Handlers.hs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,28 +182,32 @@ mkChain tracer queryTimeHandle wallet ctx LocalChainState{getLatest} submitTx =
182182
, draftCommitTx = \headId commitBlueprintTx amount -> do
183183
ChainStateAt{spendableUTxO} <- atomically getLatest
184184
let CommitBlueprintTx{lookupUTxO} = commitBlueprintTx
185-
traverse (finalizeTx wallet ctx spendableUTxO lookupUTxO) $
185+
traverse (finalizeTx wallet ctx spendableUTxO lookupUTxO) $ do
186+
checkAmount lookupUTxO amount
186187
commit' ctx headId spendableUTxO commitBlueprintTx amount
187188
, draftDepositTx = \headId pparams commitBlueprintTx deadline amount -> do
188189
let CommitBlueprintTx{lookupUTxO} = commitBlueprintTx
189190
ChainStateAt{spendableUTxO} <- atomically getLatest
190191
TimeHandle{currentPointInTime} <- queryTimeHandle
191192
-- XXX: What an error handling mess
192-
runExceptT $ do
193-
liftEither $ rejectLowDeposits pparams lookupUTxO amount
194-
(currentSlot, currentTime) <- case currentPointInTime of
195-
Left failureReason -> throwError FailedToConstructDepositTx{failureReason}
196-
Right (s, t) -> pure (s, t)
197-
-- NOTE: Use a smaller upper bound than maxGraceTime to allow for
198-
-- shorter than 200 slot deposit periods. This is only important on
199-
-- fast moving networks (e.g. in testing). XXX: Making maxGraceTime
200-
-- configurable would avoid this.
201-
let untilDeadline = diffUTCTime deadline currentTime
202-
let graceTime = maxGraceTime `min` untilDeadline / 2
203-
-- -- NOTE: But also not make it smaller than 10 slots.
204-
let validBeforeSlot = currentSlot + fromInteger (truncate graceTime `max` 10)
205-
lift . finalizeTx wallet ctx spendableUTxO lookupUTxO $
206-
depositTx (networkId ctx) headId commitBlueprintTx validBeforeSlot deadline amount
193+
runExceptT $
194+
do
195+
liftEither $ do
196+
checkAmount lookupUTxO amount
197+
rejectLowDeposits pparams lookupUTxO amount
198+
(currentSlot, currentTime) <- case currentPointInTime of
199+
Left failureReason -> throwError FailedToConstructDepositTx{failureReason}
200+
Right (s, t) -> pure (s, t)
201+
-- NOTE: Use a smaller upper bound than maxGraceTime to allow for
202+
-- shorter than 200 slot deposit periods. This is only important on
203+
-- fast moving networks (e.g. in testing). XXX: Making maxGraceTime
204+
-- configurable would avoid this.
205+
let untilDeadline = diffUTCTime deadline currentTime
206+
let graceTime = maxGraceTime `min` untilDeadline / 2
207+
-- -- NOTE: But also not make it smaller than 10 slots.
208+
let validBeforeSlot = currentSlot + fromInteger (truncate graceTime `max` 10)
209+
lift . finalizeTx wallet ctx spendableUTxO lookupUTxO $
210+
depositTx (networkId ctx) headId commitBlueprintTx validBeforeSlot deadline amount
207211
, -- Submit a cardano transaction to the cardano-node using the
208212
-- LocalTxSubmission protocol.
209213
submitTx
@@ -228,6 +232,15 @@ rejectLowDeposits pparams utxo amount = do
228232
[] -> pure ()
229233
(e : _) -> Left e
230234

235+
checkAmount :: UTxO.UTxO -> Maybe Coin -> Either (PostTxError Tx) ()
236+
checkAmount utxo amount =
237+
case amount of
238+
Nothing -> pure ()
239+
Just amt -> do
240+
let totalLovelace = UTxO.totalLovelace utxo
241+
when (totalLovelace < amt) $
242+
Left (AmountTooLow{providedValue = amt, totalUTxOValue = totalLovelace} :: PostTxError Tx)
243+
231244
-- | Balance and sign the given partial transaction.
232245
finalizeTx ::
233246
MonadThrow m =>

hydra-node/test/Hydra/API/HTTPServerSpec.hs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import Hydra.Cardano.Api (
3131
serialiseToTextEnvelope,
3232
)
3333
import Hydra.Chain (Chain (draftCommitTx), PostTxError (..), draftDepositTx)
34-
import Hydra.Chain.Direct.Handlers (rejectLowDeposits)
34+
import Hydra.Chain.Direct.Handlers (checkAmount, rejectLowDeposits)
3535
import Hydra.HeadLogic.State (ClosedState (..), HeadState (..), SeenSnapshot (..))
3636
import Hydra.HeadLogicSpec (inIdleState)
3737
import Hydra.JSONSchema (SchemaSelector, prop_validateJSONSchema, validateJSON, withJsonSpecifications)
@@ -535,6 +535,16 @@ apiServerSpec = do
535535
& counterexample ("Minimum value: " <> show minimumValue <> " Provided value: " <> show providedValue)
536536
_ -> property True
537537

538+
prop "reject partial deposits with less ADA then in the UTxO" $ \amt ->
539+
forAll (genUTxOAdaOnlyOfSize 1) $ \(utxo :: UTxO.UTxO) -> do
540+
let result = checkAmount utxo (Just amt)
541+
case result of
542+
Left AmountTooLow{providedValue, totalUTxOValue} ->
543+
property $
544+
providedValue < totalUTxOValue
545+
& counterexample ("Total UTxO value: " <> show totalUTxOValue <> " Provided value: " <> show providedValue)
546+
_ -> property True
547+
538548
prop "handles PostTxErrors accordingly" $ \request postTxError -> do
539549
let coverage = case postTxError of
540550
CommittedTooMuchADAForMainnet{} -> cover 1 True "CommittedTooMuchADAForMainnet"

hydra-tx/test/Hydra/Tx/Contract/Deposit.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ genHealthyDepositTx = do
3030
(mkSimpleBlueprintTx toDeposit)
3131
slot
3232
healthyDeadline
33+
Nothing
3334
pure (tx, toDeposit)
3435
where
3536
slot = chooseEnum (0, healthyDeadlineSlot) `generateWith` 42

0 commit comments

Comments
 (0)