morley-0.3.0: test/Test/Interpreter.hs
{-# LANGUAGE DeriveAnyClass, DerivingStrategies #-}
module Test.Interpreter
( spec_Interpreter
) where
import Data.Singletons (SingI)
import Fmt (pretty, (+|), (|+))
import Test.Hspec (Expectation, Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Property, label, (.&&.), (===))
import Lorentz (( # ))
import qualified Lorentz as L
import Michelson.Interpret
(ContractEnv(..), ContractReturn, MichelsonFailed(..), RemainingSteps, interpret)
import Michelson.Test (ContractPropValidator, contractProp, specWithTypedContract)
import Michelson.Test.Dummy (dummyContractEnv)
import Michelson.Test.Util (failedProp)
import Michelson.Typed (CT(..), CValue(..), IsoValue(..), T(..))
import Michelson.Text
import qualified Michelson.Typed as T
import Test.Interpreter.A1.Feather (featherSpec)
import Test.Interpreter.CallSelf (selfCallerSpec)
import Test.Interpreter.Compare (compareSpec)
import Test.Interpreter.Conditionals (conditionalsSpec)
import Test.Interpreter.ContractOp (contractOpSpec)
import Test.Interpreter.EnvironmentSpec (environmentSpec)
import Test.Interpreter.StringCaller (stringCallerSpec)
spec_Interpreter :: Spec
spec_Interpreter = do
let contractResShouldBe (res, _) expected =
case res of
Left err -> expectationFailure $ "Unexpected failure: " <> pretty err
Right (_ops, v) -> v `shouldBe` expected
specWithTypedContract "contracts/basic5.tz" $ \contract ->
it "Basic test" $
interpret contract T.VUnit (toVal [1 :: Integer]) dummyContractEnv
`contractResShouldBe` (toVal [13 :: Integer, 100])
specWithTypedContract "contracts/increment.tz" $ \contract ->
it "Basic test" $
interpret contract T.VUnit (toVal @Integer 23) dummyContractEnv
`contractResShouldBe` (toVal @Integer 24)
specWithTypedContract "contracts/tezos_examples/fail.tz" $ \contract ->
it "Fail test" $
interpret contract T.VUnit T.VUnit dummyContractEnv
`shouldSatisfy` (isLeft . fst)
specWithTypedContract "contracts/mutez_add_overflow.tz" $ \contract ->
it "Mutez add overflow test" $
interpret contract T.VUnit T.VUnit dummyContractEnv
`shouldSatisfy` (isLeft . fst)
specWithTypedContract "contracts/mutez_sub_underflow.tz" $ \contract ->
it "Mutez sub underflow test" $
interpret contract T.VUnit T.VUnit dummyContractEnv
`shouldSatisfy` (isLeft . fst)
specWithTypedContract "contracts/basic1.tz" $ \contract -> do
prop "Random check" $ \input ->
contractProp @_ @[Integer] contract (validateBasic1 input)
dummyContractEnv () input
specWithTypedContract "contracts/lsl.tz" $ \contract -> do
it "LSL shouldn't overflow test" $
interpret contract (toVal @Natural 5) (toVal @Natural 2) dummyContractEnv
`contractResShouldBe` (toVal @Natural 20)
it "LSL should overflow test" $
interpret contract (toVal @Natural 5) (toVal @Natural 257) dummyContractEnv
`shouldSatisfy` (isLeft . fst)
specWithTypedContract "contracts/lsr.tz" $ \contract -> do
it "LSR shouldn't underflow test" $
interpret contract (toVal @Natural 30) (toVal @Natural 3) dummyContractEnv
`contractResShouldBe` (toVal @Natural 3)
it "LSR should underflow test" $
interpret contract (toVal @Natural 1000) (toVal @Natural 257) dummyContractEnv
`shouldSatisfy` (isLeft . fst)
describe "FAILWITH" $ do
specWithTypedContract "contracts/failwith_message.tz" $ \contract ->
it "Failwith message test" $ do
let msg = [mt|An error occurred.|] :: MText
contractProp contract (validateMichelsonFailsWith msg) dummyContractEnv msg ()
specWithTypedContract "contracts/failwith_message2.tz" $ \contract -> do
it "Conditional failwith message test" $ do
let msg = [mt|An error occurred.|]
contractProp contract (validateMichelsonFailsWith msg) dummyContractEnv (True, msg) ()
it "Conditional success test" $ do
let param = (False, [mt|Err|] :: MText)
contractProp contract validateSuccess dummyContractEnv param ()
compareSpec
conditionalsSpec
stringCallerSpec
selfCallerSpec
environmentSpec
contractOpSpec
featherSpec
specWithTypedContract "contracts/steps_to_quota_test1.tz" $ \contract -> do
it "Amount of steps should reduce" $ do
validateStepsToQuotaTest
(interpret contract T.VUnit (T.VC (CvNat 0)) dummyContractEnv) 4
specWithTypedContract "contracts/steps_to_quota_test2.tz" $ \contract -> do
it "Amount of steps should reduce" $ do
validateStepsToQuotaTest
(interpret contract T.VUnit (T.VC (CvNat 0)) dummyContractEnv) 8
specWithTypedContract "contracts/gas_exhaustion.tz" $ \contract -> do
it "Contract should fail due to gas exhaustion" $ do
let dummyStr = toVal [mt|x|]
case fst $ interpret contract dummyStr dummyStr dummyContractEnv of
Right _ -> expectationFailure "expecting contract to fail"
Left MichelsonGasExhaustion -> pass
Left _ -> expectationFailure "expecting another failure reason"
specWithTypedContract "contracts/tezos_examples/add1_list.tz" $ \contract -> do
let
validate ::
[Integer] -> ContractPropValidator (ToT [Integer]) Property
validate param (res, _) =
case res of
Left failed -> failedProp $
"add1_list unexpectedly failed: " <> pretty failed
Right (fromVal . snd -> finalStorage) ->
map succ param === finalStorage
prop "Random check" $ \param ->
contractProp contract (validate param) dummyContractEnv param param
it "mkStackRef does not segfault" $ do
let contract = L.drop # L.push () # L.dup # L.printComment (L.stackRef @1)
# L.drop # L.nil @T.Operation # L.pair
contractProp (L.compileLorentzContract @() contract) (isRight . fst) dummyContractEnv () ()
specWithTypedContract "contracts/union.mtz" $ \contract -> do
describe "Union corresponds to Haskell types properly" $ do
let caseTest param =
contractProp contract validateSuccess dummyContractEnv param ()
it "Case 1" $ caseTest (Case1 3)
it "Case 2" $ caseTest (Case2 [mt|a|])
it "Case 3" $ caseTest (Case3 $ Just [mt|b|])
it "Case 4" $ caseTest (Case4 $ Left [mt|b|])
it "Case 5" $ caseTest (Case5 [[mt|q|]])
specWithTypedContract "contracts/case.mtz" $ \contract -> do
describe "CASE instruction" $ do
let caseTest param expectedStorage =
contractProp contract (validateStorageIs @MText expectedStorage)
dummyContractEnv param [mt||]
it "Case 1" $ caseTest (Case1 5) [mt|int|]
it "Case 2" $ caseTest (Case2 [mt|a|]) [mt|string|]
it "Case 3" $ caseTest (Case3 $ Just [mt|aa|]) [mt|aa|]
it "Case 4" $ caseTest (Case4 $ Right [mt|b|]) [mt|or string string|]
it "Case 5" $ caseTest (Case5 $ [[mt|a|], [mt|b|]]) [mt|ab|]
specWithTypedContract "contracts/tag.mtz" $ \contract -> do
it "TAG instruction" $
let expected = mconcat [[mt|unit|], [mt|o|], [mt|ab|], [mt|nat|], [mt|int|]]
in contractProp contract (validateStorageIs expected) dummyContractEnv
() [mt||]
specWithTypedContract "contracts/tezos_examples/split_bytes.tz" $ \contract -> do
it "splits given byte sequence into parts" $
let expected = ["\11", "\12", "\13"] :: [ByteString]
in contractProp contract (validateStorageIs expected) dummyContractEnv
("\11\12\13" :: ByteString) ([] :: [ByteString])
specWithTypedContract "contracts/split_string_simple.tz" $ \contract -> do
it "applies SLICE instruction" $ do
let
oneTest :: Natural -> Natural -> MText -> Maybe MText -> Expectation
oneTest o l str expected =
contractProp contract (validateStorageIs expected) dummyContractEnv
(o, l) (Just str)
-- These values have been tested using alphanet.sh
oneTest 0 0 [mt|aaa|] (Just [mt||])
oneTest 2 0 [mt|aaa|] (Just [mt||])
oneTest 3 0 [mt|aaa|] Nothing
oneTest 0 5 [mt|aaa|] Nothing
oneTest 1 2 [mt|abc|] (Just [mt|bc|])
oneTest 1 1 [mt|abc|] (Just [mt|b|])
oneTest 2 1 [mt|abc|] (Just [mt|c|])
oneTest 2 2 [mt|abc|] Nothing
oneTest 1 1 [mt|a""|] (Just [mt|"|])
oneTest 1 2 [mt|a\n|] Nothing
specWithTypedContract "contracts/complex_strings.tz" $ \contract ->
prop "Complex string" $
contractProp contract
(validateStorageIs [mt|text: "aa" \\\n|])
dummyContractEnv [mt|text: |] [mt||]
data Union1
= Case1 Integer
| Case2 MText
| Case3 (Maybe MText)
| Case4 (Either MText MText)
| Case5 [MText]
deriving stock (Generic)
deriving anyclass (IsoValue)
validateSuccess :: ContractPropValidator st Expectation
validateSuccess (res, _) = res `shouldSatisfy` isRight
validateStorageIs
:: IsoValue st
=> st -> ContractPropValidator (ToT st) Expectation
validateStorageIs expected (res, _) =
case res of
Left err ->
expectationFailure $ "Unexpected interpretation failure: " +| err |+ ""
Right (_ops, got) ->
got `shouldBe` toVal expected
validateBasic1
:: [Integer] -> ContractPropValidator ('TList ('Tc 'CInt)) Property
validateBasic1 input (Right (ops, res), _) =
(fromVal res === [sum input + 12, 100])
.&&.
(label "returned no ops" $ null ops)
validateBasic1 _ (Left e, _) = failedProp $ show e
validateStepsToQuotaTest ::
ContractReturn ('Tc 'CNat) -> RemainingSteps -> Expectation
validateStepsToQuotaTest res numOfSteps =
case fst res of
Right ([], T.VC (CvNat x)) ->
(fromInteger . toInteger) x `shouldBe` ceMaxSteps dummyContractEnv - numOfSteps
_ -> expectationFailure "unexpected contract result"
validateMichelsonFailsWith
:: (T.IsoValue v, Typeable (ToT v), SingI (ToT v))
=> v -> ContractPropValidator st Expectation
validateMichelsonFailsWith v (res, _) = res `shouldBe` Left (MichelsonFailedWith $ toVal v)