Skip to content
This repository has been archived by the owner on Dec 2, 2024. It is now read-only.

Commit

Permalink
added wiggle room options to NoLockedFunds
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximilianAlgehed committed Feb 14, 2022
1 parent 7f543e2 commit 82f913d
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 47 deletions.
3 changes: 1 addition & 2 deletions plutus-contract/src/Plutus/Contract/Test/ContractModel.hs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,9 @@ module Plutus.Contract.Test.ContractModel
--
-- $noLockedFunds
, NoLockedFundsProof(..)
, defaultNLFP
, checkNoLockedFundsProof
, checkNoLockedFundsProofFast
, checkNoLockedFundsProofWithWiggleRoom
, checkNoLockedFundsProofWithWiggleRoomFast
-- $checkNoPartiality
, Whitelist
, whitelistOk
Expand Down
74 changes: 40 additions & 34 deletions plutus-contract/src/Plutus/Contract/Test/ContractModel/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,9 @@ module Plutus.Contract.Test.ContractModel.Internal
--
-- $noLockedFunds
, NoLockedFundsProof(..)
, defaultNLFP
, checkNoLockedFundsProof
, checkNoLockedFundsProofFast
, checkNoLockedFundsProofWithWiggleRoom
, checkNoLockedFundsProofWithWiggleRoomFast
-- $checkNoPartiality
, Whitelist
, whitelistOk
Expand Down Expand Up @@ -1563,8 +1562,22 @@ data NoLockedFundsProof model = NoLockedFundsProof
, nlfpWalletStrategy :: Wallet -> DL model ()
-- ^ A strategy for each wallet to recover as much (or more) funds as the main strategy would
-- give them in a given state, without the assistance of any other wallet.
, nlfpOverhead :: ModelState model -> SymValue
-- ^ An initial amount of overhead value that may be lost - e.g. setup fees for scripts that
-- can't be recovered.
, nlfpErrorMargin :: ModelState model -> SymValue
-- ^ The total amount of margin for error in the value collected by the WalletStrategy compared
-- to the MainStrategy. This is useful if your contract contains rounding code that makes the order
-- of operations have a small but predictable effect on the value collected by different wallets.
}

-- | The default skeleton of a NoLockedFundsProof - doesn't permit any overhead or error margin.
defaultNLFP :: NoLockedFundsProof model
defaultNLFP = NoLockedFundsProof { nlfpMainStrategy = return ()
, nlfpWalletStrategy = const (return ())
, nlfpOverhead = const mempty
, nlfpErrorMargin = const mempty }

-- | Check a `NoLockedFundsProof`. Each test will generate an arbitrary sequence of actions
-- (`anyActions_`) and ask the `nlfpMainStrategy` to recover all funds locked by the contract
-- after performing those actions. This results in some distribution of the contract funds to the
Expand All @@ -1578,63 +1591,56 @@ checkNoLockedFundsProof
=> CheckOptions
-> NoLockedFundsProof model
-> Property
checkNoLockedFundsProof = checkNoLockedFundsProofWithWiggleRoom 0

checkNoLockedFundsProofFast
:: (ContractModel model)
=> CheckOptions
-> NoLockedFundsProof model
-> Property
checkNoLockedFundsProofFast _ = checkNoLockedFundsProofWithWiggleRoomFast 0

checkNoLockedFundsProofWithWiggleRoom
:: (ContractModel model)
=> Integer
-> CheckOptions
-> NoLockedFundsProof model
-> Property
checkNoLockedFundsProofWithWiggleRoom wiggle options =
checkNoLockedFundsProofWithWiggleRoom' wiggle prop
checkNoLockedFundsProof options =
checkNoLockedFundsProof' prop
where
prop = propRunActionsWithOptions' options defaultCoverageOptions (\ _ -> pure True)

checkNoLockedFundsProofWithWiggleRoomFast
checkNoLockedFundsProofFast
:: (ContractModel model)
=> Integer
=> CheckOptions
-> NoLockedFundsProof model
-> Property
checkNoLockedFundsProofWithWiggleRoomFast wiggle = checkNoLockedFundsProofWithWiggleRoom' wiggle (const $ property True)
checkNoLockedFundsProofFast _ = checkNoLockedFundsProof' (const $ property True)

checkNoLockedFundsProofWithWiggleRoom'
checkNoLockedFundsProof'
:: (ContractModel model)
=> Integer
-> (StateModel.Actions (ModelState model) -> Property)
=> (StateModel.Actions (ModelState model) -> Property)
-> NoLockedFundsProof model
-> Property
checkNoLockedFundsProofWithWiggleRoom' wiggle run NoLockedFundsProof{nlfpMainStrategy = mainStrat,
nlfpWalletStrategy = walletStrat } =
checkNoLockedFundsProof' run NoLockedFundsProof{nlfpMainStrategy = mainStrat,
nlfpWalletStrategy = walletStrat,
nlfpOverhead = overhead,
nlfpErrorMargin = wiggle } =
forAllDL anyActions_ $ \ (Actions as) ->
forAllUniqueDL (nextVarIdx as) (stateAfter $ Actions as) mainStrat $ \ (Actions as') ->
let s0 = (stateAfter $ Actions as) in
forAllUniqueDL (nextVarIdx as) s0 mainStrat $ \ (Actions as') ->
let s = stateAfter $ Actions (as ++ as') in
foldl (QC..&&.) (counterexample "Main run prop" (run (toStateModelActions $ Actions $ as ++ as')) QC..&&. (counterexample "Main strategy" . counterexample (show . Actions $ as ++ as') $ prop s))
[ walletProp as w bal | (w, bal) <- Map.toList (s ^. balanceChanges) ]
foldl (QC..&&.) (counterexample "Main run prop" (run (toStateModelActions $ Actions $ as ++ as')) QC..&&. (counterexample "Main strategy" . counterexample (show . Actions $ as ++ as') $ prop s0 s))
[ walletProp s0 as w bal | (w, bal) <- Map.toList (s ^. balanceChanges) ]
where
nextVarIdx as = 1 + maximum ([0] ++ [ i | Var i <- varOf <$> as ])
prop s =
prop s0 s =
let lockedVal = lockedValue s
in (counterexample ("Locked funds should be zero, but they are\n " ++ show lockedVal) $ symIsZero lockedVal)
in (counterexample ("Locked funds should be at most " ++ show (overhead s0) ++ ", but they are\n " ++ show lockedVal)
$ symLeq lockedVal (overhead s0))

walletProp as w bal = DL.forAllUniqueDL (nextVarIdx as) (stateAfter $ Actions as) (DL.action (Unilateral w) >> walletStrat w) $ \ acts ->
walletProp s0 as w bal =
let wig = wiggle s0
in
DL.forAllUniqueDL (nextVarIdx as) s0 (DL.action (Unilateral w) >> walletStrat w) $ \ acts ->
let Actions as' = fromStateModelActions acts
err = "Unilateral strategy for " ++ show w ++ " should have gotten it at least\n" ++
" " ++ show bal ++ "\n" ++
concat [ "with wiggle room\n" ++ " " ++ show wig ++ "\n"
| wig /= mempty ] ++
"but it got\n" ++
" " ++ show bal'
bal' = stateAfter (Actions $ as ++ as') ^. balanceChange w
smacts = toStateModelActions (Actions as) <> acts
err' = "The ContractModel's Unilateral behaviour for " ++ show w ++ " does not match the actual behaviour for actions:\n"
++ show smacts
in counterexample err (symLeqWiggle wiggle bal bal')
in counterexample err (symLeq bal (bal' <> wig))
QC..&&. counterexample err' (run smacts)

-- | A whitelist entry tells you what final log entry prefixes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Plutus.Contract.Test.ContractModel.Symbolics where

import Ledger.Ada qualified as Ada
import Ledger.Value (AssetClass, Value, assetClassValue, isZero, leq)
import PlutusTx.Monoid qualified as PlutusTx

Expand Down Expand Up @@ -71,5 +72,8 @@ instance SymValueLike Value where
instance SymValueLike SymValue where
toSymValue = id

instance SymValueLike Ada.Ada where
toSymValue = toSymValue . Ada.toValue

inv :: SymValue -> SymValue
inv (SymValue m v) = SymValue (negate <$> m) (PlutusTx.inv v)
2 changes: 1 addition & 1 deletion plutus-use-cases/test/Spec/Auction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ prop_FinishAuction = forAllDL finishAuction prop_Auction
-- seller walks away the buyer will not get their token (unless going around the off-chain code
-- and building a Payout transaction manually).
noLockProof :: NoLockedFundsProof AuctionModel
noLockProof = NoLockedFundsProof
noLockProof = defaultNLFP
{ nlfpMainStrategy = strat
, nlfpWalletStrategy = const strat }
where
Expand Down
2 changes: 1 addition & 1 deletion plutus-use-cases/test/Spec/Escrow.hs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ prop_FinishEscrow :: Property
prop_FinishEscrow = forAllDL finishEscrow prop_Escrow

noLockProof :: NoLockedFundsProof EscrowModel
noLockProof = NoLockedFundsProof
noLockProof = defaultNLFP
{ nlfpMainStrategy = finishingStrategy (const True)
, nlfpWalletStrategy = finishingStrategy . (==) }

Expand Down
2 changes: 1 addition & 1 deletion plutus-use-cases/test/Spec/GameStateMachine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ prop_NoLockedFunds :: Property
prop_NoLockedFunds = forAllDL noLockedFunds prop_Game

noLockProof :: NoLockedFundsProof GameModel
noLockProof = NoLockedFundsProof{
noLockProof = defaultNLFP {
nlfpMainStrategy = mainStrat,
nlfpWalletStrategy = walletStrat }
where
Expand Down
2 changes: 1 addition & 1 deletion plutus-use-cases/test/Spec/Prism.hs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ prop_Prism = propRunActions @PrismModel finalPredicate

-- | The Prism contract does not lock any funds.
noLockProof :: NoLockedFundsProof PrismModel
noLockProof = NoLockedFundsProof
noLockProof = defaultNLFP
{ nlfpMainStrategy = return ()
, nlfpWalletStrategy = \ _ -> return ()
}
Expand Down
17 changes: 11 additions & 6 deletions plutus-use-cases/test/Spec/Uniswap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,18 @@ prop_liquidityValue = forAllDL liquidityValue (const True)

-- This doesn't work
noLockProof :: NoLockedFundsProof UniswapModel
noLockProof = NoLockedFundsProof{
noLockProof = defaultNLFP {
nlfpMainStrategy = mainStrat,
nlfpWalletStrategy = walletStrat }
nlfpWalletStrategy = walletStrat,
nlfpOverhead = const $ toSymValue Ledger.minAdaTxOut,
nlfpErrorMargin = wiggle }
where
wiggle s = fold [symAssetClassValue t1 (toInteger m) <>
symAssetClassValue t2 (toInteger m) <>
toSymValue Ledger.minAdaTxOut
| (PoolIndex t1 t2, p) <- Map.toList (s ^. contractState . pools)
, let numLiqs = length $ p ^. liquidities
m = max 0 (numLiqs - 1) ]
mainStrat = do
pools <- viewContractState pools
forM_ (Map.toList pools) $ \ (PoolIndex t1 t2, p) -> do
Expand All @@ -497,14 +505,11 @@ noLockProof = NoLockedFundsProof{
then action $ ClosePool w t1 t2
else action $ RemoveLiquidity w t1 t2 (unAmount . sum $ Map.lookup w liqs)

-- This doesn't hold
prop_CheckNoLockedFundsProof :: Property
prop_CheckNoLockedFundsProof = checkNoLockedFundsProof defaultCheckOptionsContractModel noLockProof

-- In principle this property does not hold for any wiggle room! You can chain together the issues
-- you get for normal "No locked funds"
prop_CheckNoLockedFundsProofFast :: Property
prop_CheckNoLockedFundsProofFast = checkNoLockedFundsProofWithWiggleRoomFast 3 noLockProof
prop_CheckNoLockedFundsProofFast = checkNoLockedFundsProofFast defaultCheckOptionsContractModel noLockProof

check_propUniswapWithCoverage :: IO ()
check_propUniswapWithCoverage = void $
Expand Down
2 changes: 1 addition & 1 deletion plutus-use-cases/test/Spec/Vesting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ prop_Vesting :: Actions VestingModel -> Property
prop_Vesting = propRunActions_

noLockProof :: NoLockedFundsProof VestingModel
noLockProof = NoLockedFundsProof{
noLockProof = defaultNLFP {
nlfpMainStrategy = mainStrat,
nlfpWalletStrategy = walletStrat }
where
Expand Down

0 comments on commit 82f913d

Please sign in to comment.