Skip to content

Commit 3172743

Browse files
committed
refactor(standing_bid): improve errors
1 parent 7fe8d9b commit 3172743

File tree

4 files changed

+136
-141
lines changed

4 files changed

+136
-141
lines changed

hydra-auction-onchain.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ common common-lang
102102
library
103103
import: common-lang
104104
exposed-modules:
105+
HydraAuctionOnchain.Errors.StandingBid
105106
HydraAuctionOnchain.Helpers
106107
HydraAuctionOnchain.MintingPolicies.Auction
107108
HydraAuctionOnchain.Scripts
@@ -127,7 +128,10 @@ library
127128
, ply-core
128129
, ply-plutarch
129130
, pretty-simple
131+
, safe
132+
, template-haskell
130133
, text
134+
, universe
131135

132136
hs-source-dirs: src
133137

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module HydraAuctionOnchain.Errors.StandingBid
2+
( PStandingBidError (..)
3+
) where
4+
5+
import Data.Universe (Universe (universe), universeGeneric)
6+
import HydraAuctionOnchain.Types.Error (ErrorCodePrefix (errorCodePrefix))
7+
8+
data PStandingBidError (s :: S)
9+
= -- Common errors
10+
StandingBid'Error'OwnInputMissingToken
11+
| StandingBid'Error'UnexpectedTokensMintedBurned
12+
| -- NewBid errors
13+
StandingBid'NewBid'Error'MissingOwnOutput
14+
| StandingBid'NewBid'Error'OwnOutputMissingToken
15+
| StandingBid'NewBid'Error'FailedToDecodeNewBid
16+
| StandingBid'NewBid'Error'InvalidNewBidState
17+
| StandingBid'NewBid'Error'IncorrectValidityInterval
18+
| -- MoveToHydra errors
19+
StandingBid'MoveToHydra'Error'MissingDelegateSignatures
20+
| StandingBid'MoveToHydra'Error'IncorrectValidityInterval
21+
| -- ConcludeAuction errors
22+
StandingBid'ConcludeAuction'Error'MissingAuctionEscrowInput
23+
| StandingBid'ConcludeAuction'Error'InvalidAuctionEscrowRedeemer
24+
deriving stock (Generic, Eq)
25+
deriving anyclass (PlutusType)
26+
27+
instance DerivePlutusType PStandingBidError where
28+
type DPTStrat _ = PlutusTypeScott
29+
30+
instance Universe (PStandingBidError s) where
31+
universe = universeGeneric
32+
33+
instance ErrorCodePrefix (PStandingBidError s) where
34+
errorCodePrefix = "STBD"
Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,63 @@
1+
{-# LANGUAGE AllowAmbiguousTypes #-}
2+
{-# LANGUAGE UndecidableInstances #-}
3+
14
module HydraAuctionOnchain.Types.Error
2-
( ToErrorCode (toErrorCode)
3-
, err
4-
, perrMaybe
5+
( ErrorCode (fromErrorCode, toErrorCode)
6+
, ErrorCodePrefix (errorCodePrefix)
7+
, errCode
8+
, passert
9+
, passertMaybe
510
) where
611

12+
import Data.List (elemIndex)
13+
import Data.Maybe (fromJust)
14+
import Data.Text (Text)
15+
import Data.Text qualified as Text (pack, stripPrefix, unpack)
16+
import Data.Universe (Universe (universe))
17+
import Language.Haskell.TH (Exp (LitE), Lit (StringL), Q)
718
import Plutarch.Extra.Bool (passert)
19+
import Plutarch.Extra.Maybe (passertPJust)
20+
import Safe (atMay)
21+
22+
-- | Prefix an error code with a short text tag.
23+
-- Typically, this should be defined like this:
24+
-- errorCodePrefix = const "ABCD"
25+
--
26+
-- Make sure that error code prefixes are unique per error type.
27+
class ErrorCodePrefix a where
28+
errorCodePrefix :: Text
29+
30+
-- | Types which are used to describe errors as short error codes in scripts.
31+
-- Laws:
32+
-- 1. fromErrorCode . toErrorCode = Just
33+
-- 2. (toErrorCode <$> fromErrorCode x) == (const x <$> fromErrorCode x)
34+
class (ErrorCodePrefix a, Eq a, Universe a) => ErrorCode a where
35+
-- | Get the short error code used in a script for given error type.
36+
toErrorCode :: a -> Text
37+
38+
-- | Get the error type from an error code,
39+
-- assuming that the error code produced from that error type.
40+
fromErrorCode :: Text -> Maybe a
41+
42+
-- | Sequentially ordered types have sequentially ordered error codes.
43+
-- Assuming that Universe implementation is correct, this instance should
44+
-- satisfy the ErrorCode laws.
45+
instance (Universe a, Eq a, ErrorCodePrefix a) => ErrorCode a where
46+
toErrorCode x = prefix <> numericCode
47+
where
48+
-- fromJust should not result in an error here if Universe is correct.
49+
numericCode = Text.pack $ show $ fromJust $ elemIndex x universe
50+
prefix = errorCodePrefix @a
851

9-
class ToErrorCode a where
10-
toErrorCode :: Term s (a :--> PString)
52+
fromErrorCode x = atMay universe =<< numericCode
53+
where
54+
numericCode = read . Text.unpack <$> Text.stripPrefix prefix x
55+
prefix = errorCodePrefix @a
1156

12-
err :: (PlutusType e, ToErrorCode e) => e s -> Term s PBool -> Term s a -> Term s a
13-
err e = passert (toErrorCode # pcon e)
57+
-- | Get the string literal from given error 'e'.
58+
-- Use this with template haskell splices, e.g. $(errCode MyError)
59+
errCode :: ErrorCode e => e -> Q Exp
60+
errCode e = pure (LitE (StringL (Text.unpack (toErrorCode e))))
1461

15-
perrMaybe :: ToErrorCode e => Term s (e :--> PMaybe a :--> a)
16-
perrMaybe = phoistAcyclic $
17-
plam $ \err mval -> pmatch mval $ \case
18-
PJust val -> val
19-
PNothing -> ptraceError $ toErrorCode # err
62+
passertMaybe :: Text -> Term s (PMaybe a) -> Term s a
63+
passertMaybe err mval = passertPJust # pconstant err # mval

src/HydraAuctionOnchain/Validators/StandingBid.hs

Lines changed: 42 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
{-# LANGUAGE TemplateHaskell #-}
2+
13
module HydraAuctionOnchain.Validators.StandingBid
24
( standingBidValidator
35
) where
46

7+
import HydraAuctionOnchain.Errors.StandingBid (PStandingBidError (..))
58
import HydraAuctionOnchain.Helpers
69
( pdecodeInlineDatum
710
, pfindUniqueInputWithToken
@@ -13,7 +16,7 @@ import HydraAuctionOnchain.MintingPolicies.Auction
1316
, standingBidTokenName
1417
)
1518
import HydraAuctionOnchain.Types.AuctionTerms (PAuctionTerms, pbiddingPeriod)
16-
import HydraAuctionOnchain.Types.Error (ToErrorCode (toErrorCode), err, perrMaybe)
19+
import HydraAuctionOnchain.Types.Error (errCode, passert, passertMaybe)
1720
import HydraAuctionOnchain.Types.StandingBidState (PStandingBidState, pvalidateNewBid)
1821
import HydraAuctionOnchain.Validators.AuctionEscrow (pisConcluding)
1922
import Plutarch.Api.V1.Value (pvalueOf)
@@ -44,90 +47,6 @@ data PStandingBidRedeemer (s :: S)
4447
instance DerivePlutusType PStandingBidRedeemer where
4548
type DPTStrat _ = PlutusTypeData
4649

47-
--------------------------------------------------------------------------------
48-
-- Errors
49-
--------------------------------------------------------------------------------
50-
51-
data PStandingBidError (s :: S)
52-
= StandingBid'Error'OwnInputMissingToken
53-
| StandingBid'Error'UnexpectedTokensMintedBurned
54-
deriving stock (Generic)
55-
deriving anyclass (PlutusType)
56-
57-
instance DerivePlutusType PStandingBidError where
58-
type DPTStrat _ = PlutusTypeScott
59-
60-
instance ToErrorCode PStandingBidError where
61-
toErrorCode = phoistAcyclic $
62-
plam $ \err -> pmatch err $ \case
63-
StandingBid'Error'OwnInputMissingToken ->
64-
pconstant "StandingBid01"
65-
StandingBid'Error'UnexpectedTokensMintedBurned ->
66-
pconstant "StandingBid02"
67-
68-
-- NewBid ----------------------------------------------------------------------
69-
data PStandingBid'NewBid'Error (s :: S)
70-
= StandingBid'NewBid'Error'MissingOwnOutput
71-
| StandingBid'NewBid'Error'OwnOutputMissingToken
72-
| StandingBid'NewBid'Error'FailedToDecodeNewBid
73-
| StandingBid'NewBid'Error'InvalidNewBidState
74-
| StandingBid'NewBid'Error'IncorrectValidityInterval
75-
deriving stock (Generic)
76-
deriving anyclass (PlutusType)
77-
78-
instance DerivePlutusType PStandingBid'NewBid'Error where
79-
type DPTStrat _ = PlutusTypeScott
80-
81-
instance ToErrorCode PStandingBid'NewBid'Error where
82-
toErrorCode = phoistAcyclic $
83-
plam $ \err -> pmatch err $ \case
84-
StandingBid'NewBid'Error'MissingOwnOutput ->
85-
pconstant "StandingBid_NewBid_01"
86-
StandingBid'NewBid'Error'OwnOutputMissingToken ->
87-
pconstant "StandingBid_NewBid_02"
88-
StandingBid'NewBid'Error'FailedToDecodeNewBid ->
89-
pconstant "StandingBid_NewBid_03"
90-
StandingBid'NewBid'Error'InvalidNewBidState ->
91-
pconstant "StandingBid_NewBid_04"
92-
StandingBid'NewBid'Error'IncorrectValidityInterval ->
93-
pconstant "StandingBid_NewBid_05"
94-
95-
-- MoveToHydra -----------------------------------------------------------------
96-
data PStandingBid'MoveToHydra'Error (s :: S)
97-
= StandingBid'MoveToHydra'Error'MissingDelegateSignatures
98-
| StandingBid'MoveToHydra'Error'IncorrectValidityInterval
99-
deriving stock (Generic)
100-
deriving anyclass (PlutusType)
101-
102-
instance DerivePlutusType PStandingBid'MoveToHydra'Error where
103-
type DPTStrat _ = PlutusTypeScott
104-
105-
instance ToErrorCode PStandingBid'MoveToHydra'Error where
106-
toErrorCode = phoistAcyclic $
107-
plam $ \err -> pmatch err $ \case
108-
StandingBid'MoveToHydra'Error'MissingDelegateSignatures ->
109-
pconstant "StandingBid_MoveToHydra_01"
110-
StandingBid'MoveToHydra'Error'IncorrectValidityInterval ->
111-
pconstant "StandingBid_MoveToHydra_02"
112-
113-
-- ConcludeAuction -------------------------------------------------------------
114-
data PStandingBid'ConcludeAuction'Error (s :: S)
115-
= StandingBid'ConcludeAuction'Error'MissingAuctionEscrowInput
116-
| StandingBid'ConcludeAuction'Error'InvalidAuctionEscrowRedeemer
117-
deriving stock (Generic)
118-
deriving anyclass (PlutusType)
119-
120-
instance DerivePlutusType PStandingBid'ConcludeAuction'Error where
121-
type DPTStrat _ = PlutusTypeScott
122-
123-
instance ToErrorCode PStandingBid'ConcludeAuction'Error where
124-
toErrorCode = phoistAcyclic $
125-
plam $ \err -> pmatch err $ \case
126-
StandingBid'ConcludeAuction'Error'MissingAuctionEscrowInput ->
127-
pconstant "StandingBid_ConcludeAuction_01"
128-
StandingBid'ConcludeAuction'Error'InvalidAuctionEscrowRedeemer ->
129-
pconstant "StandingBid_ConcludeAuction_02"
130-
13150
--------------------------------------------------------------------------------
13251
-- Validator
13352
--------------------------------------------------------------------------------
@@ -147,15 +66,14 @@ standingBidValidator = phoistAcyclic $
14766
ownInput <- plet $ ptryOwnInput # ctx
14867
txInfo <- plet $ pfield @"txInfo" # ctx
14968

150-
-- (StandingBid01)
151-
-- The standing bid input should contain the standing bid token.
152-
err StandingBid'Error'OwnInputMissingToken $
69+
-- (STBD0) The standing bid input should contain the standing
70+
-- bid token.
71+
passert $(errCode StandingBid'Error'OwnInputMissingToken) $
15372
ptxOutContainsStandingBidToken # auctionCs #$ pfield @"resolved" # ownInput
15473

155-
-- (StandingBid02)
156-
-- There should be no tokens minted or burned.
74+
-- (STBD1) There should be no tokens minted or burned.
15775
mintValue <- plet $ pfield @"mint" # txInfo
158-
err StandingBid'Error'UnexpectedTokensMintedBurned $
76+
passert $(errCode StandingBid'Error'UnexpectedTokensMintedBurned) $
15977
pfromData mintValue #== mempty
16078

16179
pmatch redeemer $ \case
@@ -182,38 +100,35 @@ pcheckNewBid
182100
)
183101
pcheckNewBid = phoistAcyclic $
184102
plam $ \txInfo auctionCs auctionTerms ownInput oldBidState -> P.do
185-
-- (StandingBid_NewBid_01)
186-
-- The standing bid output should exist.
103+
-- (STBD2) The standing bid output should exist.
187104
ownOutput <-
188105
plet $
189-
perrMaybe
190-
# pcon StandingBid'NewBid'Error'MissingOwnOutput
191-
# (pfindUniqueOutputWithAddress # (putxoAddress # ownInput) # txInfo)
106+
passertMaybe
107+
$(errCode StandingBid'NewBid'Error'MissingOwnOutput)
108+
(pfindUniqueOutputWithAddress # (putxoAddress # ownInput) # txInfo)
192109

193-
-- (StandingBid_NewBid_02)
194-
-- The standing bid output should contain the standing bid token.
195-
err StandingBid'NewBid'Error'OwnOutputMissingToken $
110+
-- (STBD3) The standing bid output should contain the standing
111+
-- bid token.
112+
passert $(errCode StandingBid'NewBid'Error'OwnOutputMissingToken) $
196113
ptxOutContainsStandingBidToken # auctionCs # ownOutput
197114

198-
-- (StandingBid_NewBid_03)
199-
-- The standing bid output's datum should be decodable
115+
-- (STBD4) The standing bid output's datum should be decodable
200116
-- as a standing bid state.
201117
newBidState <-
202118
plet $
203-
perrMaybe
204-
# pcon StandingBid'NewBid'Error'FailedToDecodeNewBid
205-
# (pdecodeInlineDatum # ownOutput)
119+
passertMaybe
120+
$(errCode StandingBid'NewBid'Error'FailedToDecodeNewBid)
121+
(pdecodeInlineDatum # ownOutput)
206122

207-
-- (StandingBid_NewBid_04)
208-
-- The transition from the old bid state to the new bid state
209-
-- should be valid.
210-
err StandingBid'NewBid'Error'InvalidNewBidState $
123+
-- (STBD5) The transition from the old bid state to the new
124+
-- bid state should be valid.
125+
passert $(errCode StandingBid'NewBid'Error'InvalidNewBidState) $
211126
pvalidateNewBid # auctionCs # auctionTerms # oldBidState # newBidState
212127

213-
-- (StandingBid_NewBid_05)
214-
-- The transaction validity should end before the bidding end time.
128+
-- (STBD6) The transaction validity should end before the
129+
-- bidding end time.
215130
txInfoValidRange <- plet $ pfield @"validRange" # txInfo
216-
err StandingBid'NewBid'Error'IncorrectValidityInterval $
131+
passert $(errCode StandingBid'NewBid'Error'IncorrectValidityInterval) $
217132
pcontains # (pbiddingPeriod # auctionTerms) # txInfoValidRange
218133

219134
pcon PUnit
@@ -227,15 +142,14 @@ pcheckMoveToHydra = phoistAcyclic $
227142
plam $ \txInfo auctionTerms -> P.do
228143
txInfoFields <- pletFields @["signatories", "validRange"] txInfo
229144

230-
-- (StandingBid_MoveToHydra_01)
231-
-- The transaction should be signed by all the delegates.
145+
-- (STBD7) The transaction should be signed by all the delegates.
232146
delegates <- plet $ pfield @"delegates" # auctionTerms
233-
err StandingBid'MoveToHydra'Error'MissingDelegateSignatures $
147+
passert $(errCode StandingBid'MoveToHydra'Error'MissingDelegateSignatures) $
234148
pall # plam (\sig -> ptxSignedBy # txInfoFields.signatories # sig) # delegates
235149

236-
-- (StandingBid_MoveToHydra_02)
237-
-- The transaction validity should end before the bidding end time.
238-
err StandingBid'MoveToHydra'Error'IncorrectValidityInterval $
150+
-- (STBD8) The transaction validity should end before the
151+
-- bidding end time.
152+
passert $(errCode StandingBid'MoveToHydra'Error'IncorrectValidityInterval) $
239153
pcontains # (pbiddingPeriod # auctionTerms) # txInfoFields.validRange
240154

241155
pcon PUnit
@@ -247,24 +161,23 @@ pcheckMoveToHydra = phoistAcyclic $
247161
pcheckConcludeAuction :: Term s (PTxInfo :--> PCurrencySymbol :--> PUnit)
248162
pcheckConcludeAuction = phoistAcyclic $
249163
plam $ \txInfo auctionCs -> P.do
250-
-- (StandingBid_ConcludeAuction_01)
251-
-- There is an input that contains the auction escrow token.
164+
-- (STBD9) There is an input that contains
165+
-- the auction escrow token.
252166
auctionEscrowUtxo <-
253167
plet $
254-
perrMaybe
255-
# pcon StandingBid'ConcludeAuction'Error'MissingAuctionEscrowInput
256-
# (pfindUniqueInputWithToken # auctionCs # auctionEscrowTokenName # txInfo)
257-
258-
-- (StandingBid_ConcludeAuction_02)
259-
-- The auction escrow input is being spent with the `BidderBuys` or
260-
-- `SellerReclaims` redeemer. Implicitly, this means that the auction is
261-
-- concluding with either the winning bidder buying the auction lot or the
262-
-- seller reclaiming it.
168+
passertMaybe
169+
$(errCode StandingBid'ConcludeAuction'Error'MissingAuctionEscrowInput)
170+
(pfindUniqueInputWithToken # auctionCs # auctionEscrowTokenName # txInfo)
171+
172+
-- (STBD10) The auction escrow input is being spent with the
173+
-- `BidderBuys` or `SellerReclaims` redeemer. Implicitly, this
174+
-- means that the auction is concluding with either the winning
175+
-- bidder buying the auction lot or the seller reclaiming it.
263176
redeemers <- plet $ pfield @"redeemers" # txInfo
264177
auctionEscrowOref <- plet $ pfield @"outRef" # auctionEscrowUtxo
265178
spendsAuctionEscrow <- plet $ pcon $ PSpending $ pdcons @"_0" # auctionEscrowOref # pdnil
266179
auctionEscrowRedeemer <- plet $ ptryFromRedeemer # spendsAuctionEscrow # redeemers
267-
err StandingBid'ConcludeAuction'Error'InvalidAuctionEscrowRedeemer $
180+
passert $(errCode StandingBid'ConcludeAuction'Error'InvalidAuctionEscrowRedeemer) $
268181
pmaybe
269182
# pcon PFalse
270183
# plam (\redeemer -> pisConcluding # pfromData redeemer)

0 commit comments

Comments
 (0)