Skip to content

Commit 713fc36

Browse files
authored
Merge pull request #113 from mlabs-haskell/euonymos/dynamic-test
Improve quickcheck-dynamic tests for CEM Script
2 parents 3774144 + 79fc3b6 commit 713fc36

File tree

10 files changed

+483
-156
lines changed

10 files changed

+483
-156
lines changed

docs/goals_and_soa.md

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ what we use to demonstrate problems in following:
5252
* Fracada
5353
* JPG Vesting Contract
5454
* Indigo Protocol
55-
* DApp project we participate, audited or otherwise know it codebase
55+
* DApp projects we participated, audited or otherwise know their codebase
5656
* Hydra Auction
5757
* POCRE
5858
* CNS
@@ -64,8 +64,6 @@ what we use to demonstrate problems in following:
6464
* [Game Model](https://github.com/IntersectMBO/plutus-apps/blob/dbafa0ffdc1babcf8e9143ca5a7adde78d021a9a/doc/plutus/tutorials/GameModel.hs)
6565
* plutus-usecases
6666

67-
@todo #3: Add more links to specific bugs and code size blowups in existing DApps.
68-
6967
## On-chain correctness
7068

7169
### Known common vulnerabilities
@@ -85,14 +83,31 @@ taking that burden from developers and auditors.
8583

8684
Those problems are similar to previous in that they tend to
8785
arise in naive Plutus implementations,
88-
if developer was did not make measures to prevent them.
86+
if developer was did not take measures to prevent them.
87+
88+
Plutus forces developers to write TxIn/TxOut constraints from scratch,
89+
leading by subtle bugs from copy-pasting logic or
90+
trying to optimize them by hand.
91+
92+
Examples:
93+
94+
* Security bug in MinSwap audit - 2.2.1.3 Unauthorized Hijacking of Pools Funds
95+
* Non-security bug in MinSwap audit - 2.2.2.2 Batcher Is Not Allowed to Apply Their Own Order
96+
97+
Such constraints naturally lead to conditions
98+
for which more performant implementation should
99+
omit some constraints always following from others.
100+
Such kind of manual SMT solving exercises are
101+
known source for security bugs and complicated code.
89102

90-
Almost all transactions which require fungible tokens as input,
103+
One of important cases is maintaining invariants of token value.
104+
TODO - add explanation
105+
106+
Most of transactions which require fungible tokens as input,
91107
should not depend from exact UTxO coin-change distribution.
92108

93109
Failure to expect that leads to prohibition of correct transactions.
94-
On other side too broad constraint might lead to
95-
fund stealing.
110+
On other side too broad constraint might lead to fund stealing.
96111

97112
Example of bugs:
98113

@@ -117,6 +132,9 @@ Examples:
117132

118133
* Non-security bug: https://github.com/mlabs-haskell/hydra-auction/issues/129
119134
* Non-security bug: https://github.com/mlabs-haskell/hydra-auction/commit/8152720c43732f8fb74181b7509de503b8371997
135+
* Non-intentionally under-specified behavior in MinSwap audit:
136+
* `2.2.2.1 Batchers Can Choose Batching Order`
137+
* Triggered by `2.2.4.1 "Reliance on Indexes Into ScriptContexts' txInputs and txOutputs"`
120138
* Multiple kind of code complication was observed in CNS audit.
121139
* Utilities [from Indigo](https://github.com/IndigoProtocol/indigo-smart-contracts/blob/main/src/Indigo/Utils/Spooky/Helpers.hs)
122140

@@ -182,6 +200,19 @@ Our script stages abstraction cover all those kind of problems.
182200
* @todo #3: document problems with slots in Plutus/Cardano API
183201
* https://github.com/mlabs-haskell/hydra-auction/issues/236
184202

203+
## Matching off-chain logic
204+
205+
Problem of duplicating logic between on- and off-chain is twofold.
206+
Testing is essentially offchain, thus, you may miss that your onchain code
207+
is not actually enforcing part of Tx provided in tests.
208+
209+
CEM Script is constructing Tx for submission from same specification,
210+
which is used for onchain script. Thus it is much harder to miss constraint
211+
to be checked.
212+
213+
Examples:
214+
215+
* MinSwap audit - 2.2.1.2 LP Tokens Can Be Duplicated
185216

186217
## Logic duplication and spec subtleness
187218

@@ -211,6 +242,33 @@ is much less obvious to implement,
211242
and out of scope of current Catalyst project,
212243
but it is very much possible feature as well.
213244

245+
Examples of diagrams in DApp specifications:
246+
247+
* ...
248+
* ...
249+
* ...
250+
251+
### On-/Off-chain and spec logic duplication
252+
253+
Writing on-chain contracts manually encodes non-deterministic state machine,
254+
which cannot be used for off-chain transaction construction.
255+
Thus developer need to write them again in different style in off-chain code,
256+
which is tedious and error prone.
257+
258+
They should add checks for all errors possible,
259+
like coins being available and correct script states being present,
260+
to prevent cryptic errors and provide retrying strategies
261+
for possible utxo changes.
262+
263+
Our project encodes scripts in deterministic machine,
264+
containing enough information to construct transaction automatically.
265+
This also gives a way to check for potential on/off-chain logic differences
266+
semi-automatically.
267+
268+
Example:
269+
* MinSwap Audit - 2.2.4.3 Large Refactoring Opportunities
270+
* `availableVestings` - пример чего-то подобного для SimpleAuction
271+
214272
Examples of this done by hand:
215273

216274
* [State graph for Agora](https://github.com/Liqwid-Labs/agora/blob/staging/docs/diagrams/ProposalStateMachine.png)
@@ -244,10 +302,8 @@ Examples of boilerplate:
244302

245303
* https://github.com/MELD-labs/cardano-defi-public/tree/eacaa527823031105eba1730f730e1b32f1470bc/lending-index/src/Lending/Index
246304

247-
### Correct off-chain Tx construction logic
305+
Timing ...
248306

249-
A lot of on-chain problems, like timing and coin change issues
250-
have their counterpart on Tx submission side.
251307

252308
@todo #3: Add more off-chain code duplication examples from existing PABs.
253309
Include problems with coin-selection, tests, retrying and errors.
@@ -305,12 +361,19 @@ and multiple commiters schemes (used in `hydra`).
305361

306362
### Atlas
307363

308-
Atlas provides (emulate-everything) and overall more humane DX
309-
on top of cardano-api. But it has no feature related to goals
364+
Atlas provides more humane DX on top of cardano-api.
365+
But it has no features related to goals
310366
(synced-by-construction), (secure-by-construction)
311367
and (declarative-spec).
368+
(emulate-everything) is planned, but is not implemented currently.
369+
370+
Atlas includes connectors to Blockfrost and other backends,
371+
which our project lacks.
372+
373+
Also our project has slight differences in API design decisions.
374+
Our monad interfaces is meant to be slightly more modular.
375+
We use much less custom type wrappers, resorting to Plutus types where possible.
312376

313-
@todo #3: Add more specifics on Atlas to docs.
314377

315378
## Testing tools
316379

example/CEM/Example/Auction.hs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ data SimpleAuction
1616

1717
-- | A bid
1818
data Bid = MkBet
19-
{ better :: PubKeyHash
20-
, betAmount :: Integer
19+
{ bidder :: PubKeyHash -- FIXME: rename to bidder
20+
, bidAmount :: Integer -- FIXME: rename to bidder
2121
}
2222
deriving stock (Prelude.Eq, Prelude.Show)
2323

@@ -69,8 +69,8 @@ instance CEMScript SimpleAuction where
6969
initialBid =
7070
cOfSpine
7171
MkBetSpine
72-
[ #better ::= ctxParams.seller
73-
, #betAmount ::= lift 0
72+
[ #bidder ::= ctxParams.seller
73+
, #bidAmount ::= lift 0
7474
]
7575

7676
auctionValue = cMinLovelace @<> ctxParams.lot
@@ -96,7 +96,7 @@ instance CEMScript SimpleAuction where
9696
,
9797
[ input (ownUtxo $ inState CurrentBidSpine) auctionValue
9898
, byFlagError
99-
(ctxTransition.bid.betAmount @<= ctxState.bid.betAmount)
99+
(ctxTransition.bid.bidAmount @<= ctxState.bid.bidAmount)
100100
"Bid amount is less or equal to current bid"
101101
, output
102102
( ownUtxo
@@ -105,7 +105,7 @@ instance CEMScript SimpleAuction where
105105
[#bid ::= ctxTransition.bid]
106106
)
107107
auctionValue
108-
, signedBy ctxTransition.bid.better
108+
, signedBy ctxTransition.bid.bidder
109109
]
110110
)
111111
,
@@ -124,21 +124,29 @@ instance CEMScript SimpleAuction where
124124
( BuyoutSpine
125125
,
126126
[ input (ownUtxo $ inState WinnerSpine) auctionValue
127+
, byFlagError (lift False) "Some err"
128+
, byFlagError (lift False) "Another err"
127129
, -- Example: In constraints redundant for on-chain
128130
offchainOnly
129-
( spentBy
130-
buyoutBid.better
131-
( cMkAdaOnlyValue buyoutBid.betAmount
132-
@<> cMinLovelace
133-
)
131+
(if'
132+
(ctxParams.seller `eq'` buyoutBid.bidder)
133+
(signedBy ctxParams.seller)
134+
(spentBy
135+
buyoutBid.bidder
136+
(cMinLovelace @<> cMkAdaOnlyValue buyoutBid.bidAmount)
134137
cEmptyValue
138+
)
135139
)
136140
, output
137-
(userUtxo ctxParams.seller)
138-
(cMinLovelace @<> cMkAdaOnlyValue buyoutBid.betAmount)
139-
, output
140-
(userUtxo buyoutBid.better)
141-
(cMinLovelace @<> ctxParams.lot)
141+
(userUtxo buyoutBid.bidder) -- NOTE: initial zero bidder is seller
142+
auctionValue
143+
, if'
144+
(ctxParams.seller `eq'` buyoutBid.bidder)
145+
noop
146+
( output
147+
(userUtxo ctxParams.seller)
148+
(cMinLovelace @<> cMkAdaOnlyValue buyoutBid.bidAmount)
149+
)
142150
]
143151
)
144152
]

src/Cardano/CEM/DSL.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module Cardano.CEM.DSL (
1717
UtxoKind (..),
1818
SameScriptArg (..),
1919
getMainSigner,
20+
getMbMainSigner,
2021

2122
-- * DSL
2223
ConstraintDSL (..),
@@ -298,7 +299,19 @@ getMainSigner cs = case mapMaybe f cs of
298299
[pkh] -> pkh
299300
_ ->
300301
error
301-
"Transition should have exactly one MainSignerCoinSelection constraint"
302+
"Transition should have exactly one MainSigner* constraint"
303+
where
304+
f (MainSignerNoValue pkh) = Just pkh
305+
f (MainSignerCoinSelect pkh _ _) = Just pkh
306+
f _ = Nothing
307+
308+
getMbMainSigner :: [TxConstraint True script] -> Maybe PubKeyHash
309+
getMbMainSigner cs = case mapMaybe f cs of
310+
[] -> Nothing
311+
[pkh] -> Just pkh
312+
_ ->
313+
error
314+
"Transition should have exactly one MainSigner* constraint"
302315
where
303316
f (MainSignerNoValue pkh) = Just pkh
304317
f (MainSignerCoinSelect pkh _ _) = Just pkh

src/Cardano/CEM/Monads.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ data TxSubmittingError
124124

125125
-- | Error occurred while trying to execute CEMScript transition
126126
data TransitionError
127-
= CannotFindTransitionInput
127+
= CannotFindTransitionInput String
128128
| CompilationError String
129129
| SpecExecutionError {errorMessage :: String}
130130
deriving stock (Show, Eq)

src/Cardano/CEM/OffChain.hs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ construct rs = constructGo rs emptyResolvedTx
138138
, toMint = TxMintNone
139139
, additionalSigners = []
140140
, signer = error "TODO: Unreachable laziness trick"
141-
, -- FIXME
142-
interval = always
141+
, interval = always -- TODO: use validity intervals
143142
}
144143
constructGo (r : rest) !acc =
145144
let newAcc = case r of
@@ -197,7 +196,7 @@ process (MkCEMAction params transition) ec = case ec of
197196
map (withKeyWitness . fst) $ Map.toList $ unUTxO utxo
198197
-- FIXME: do actuall coin selection
199198
return $ TxInR $ head utxoPairs
200-
(Utxo kind fanFilter value) -> do
199+
c@(Utxo kind fanFilter value) -> do
201200
case kind of
202201
Out -> do
203202
let value' = convertTxOut $ fromPlutusValue value
@@ -208,6 +207,7 @@ process (MkCEMAction params transition) ec = case ec of
208207
someIn -> do
209208
utxo <- lift $ queryUtxo $ ByAddresses [address]
210209
let
210+
-- utxoPairs = traceShowId $ Map.toList $ unUTxO utxo
211211
utxoPairs = Map.toList $ unUTxO utxo
212212
matchingUtxos =
213213
map (addWittness . fst) $ filter predicate utxoPairs
@@ -217,7 +217,7 @@ process (MkCEMAction params transition) ec = case ec of
217217
In -> TxInR x
218218
InRef -> TxInRefR x
219219
[] ->
220-
throwError $ PerTransitionErrors [CannotFindTransitionInput]
220+
throwError $ PerTransitionErrors [CannotFindTransitionInput $ show c]
221221
where
222222
predicate (_, txOut) =
223223
txOutValue txOut == fromPlutusValue value
@@ -264,11 +264,11 @@ process (MkCEMAction params transition) ec = case ec of
264264
-- -----------------------------------------------------------------------------
265265

266266
data TxResolutionError
267-
= CEMScriptTxInResolutionError
268-
| -- FIXME: record transition and action involved
267+
= NoSignerError
268+
| CEMScriptTxInResolutionError
269+
| -- TODO: record transition and action involved
269270
PerTransitionErrors [TransitionError]
270-
| -- FIXME: this is weird
271-
UnhandledSubmittingError TxSubmittingError
271+
| UnhandledSubmittingError TxSubmittingError
272272
deriving stock (Show)
273273

274274
resolveTx ::

src/Cardano/CEM/Smart.hs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import Plutarch.Prelude (
1919
(#&&),
2020
)
2121
import PlutusLedgerApi.V2 (PubKeyHash, ToData (..), Value)
22-
import Prelude hiding (error)
23-
import Prelude qualified (error)
22+
import Prelude hiding (Eq, error)
23+
import Prelude qualified (Eq, error)
2424

2525
-- -----------------------------------------------------------------------------
2626
-- Helpers to be used in actual definitions
@@ -118,7 +118,7 @@ inState ::
118118
inState spine = UnsafeUpdateOfSpine ctxState spine []
119119

120120
(@==) ::
121-
(Eq x) => ConstraintDSL script x -> ConstraintDSL script x -> ConstraintDSL script Bool
121+
(Prelude.Eq x) => ConstraintDSL script x -> ConstraintDSL script x -> ConstraintDSL script Bool
122122
(@==) = Eq
123123

124124
(@<=) ::
@@ -254,3 +254,19 @@ match ::
254254
Map (Spine datatype) (TxConstraint resolved script) ->
255255
TxConstraint resolved script
256256
match = MatchBySpine
257+
258+
if' ::
259+
forall (resolved :: Bool) script.
260+
DSLPattern resolved script Bool ->
261+
TxConstraint resolved script ->
262+
TxConstraint resolved script ->
263+
TxConstraint resolved script
264+
if' = If
265+
266+
eq' ::
267+
forall x script.
268+
(Prelude.Eq x) =>
269+
ConstraintDSL script x ->
270+
ConstraintDSL script x ->
271+
ConstraintDSL script Bool
272+
eq' = Eq

0 commit comments

Comments
 (0)