packages feed

cleveland-0.1.1: morley-test/Test/Import.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

{-# LANGUAGE QuasiQuotes #-}

-- | Test on importing functionality.
module Test.Import
  ( test_importContract
  , test_importSomeContract
  , test_importUntypedContract
  , test_readContract
  , test_readValue
  , test_Basic
  ) where

import Control.Exception (handle)
import Data.List.NonEmpty ()
import Data.Vinyl (Rec(..))
import Test.HUnit ((@?), (@?=))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase)

import Morley.Michelson.ErrorPos (InstrCallStack(..), srcPos)
import Morley.Michelson.Parser (codeSrc)
import Morley.Michelson.Typed
import Morley.Michelson.Untyped qualified as U
import Morley.Util.Interpolate
import Test.Cleveland hiding (importContract, importUntypedContract)
import Test.Cleveland.Instances ()
import Test.Cleveland.Michelson
import Test.Cleveland.Michelson.Import
import Test.Util.Contracts

applyContract :: Contract ('TLambda ('TPair 'TInt 'TInt) 'TInt) 'TInt
applyContract = $$(embedContract (inContractsDir "apply.tz"))

-- | This is a clone of one of the tests but written using 'embedContract'.
test_Basic :: TestTree
test_Basic = testScenarioOnEmulator "" $ scenario do
  hndl <- originateUntypedSimple "apply" (U.ValueInt 0) $ convertContract applyContract
  let
    lam :: Instr '[ ToT (Integer, Integer) ] '[ ToT Integer ]
    lam = DUP `Seq` CAR `Seq` DIP CDR `Seq` SUB
  transfer TransferData
    { tdTo = hndl
    , tdAmount = 0
    , tdEntrypoint = DefEpName
    , tdParameter = VLam $ RfNormal lam }
  getStorage @Integer hndl @@== 2

test_readContract :: TestTree
test_readContract = testGroup "Test 'readContract'"
  [ testCase emptyFilePath $
      second convertContract (readContract @'TUnit @'TUnit codeSrc emptyCode) @?=
        Right emptyContractUPrim
  , testCase basic2FilePath $
      second convertContract (
        readContract @TPairInt @TListPairInt codeSrc basic2Code) @?=
          Right basic2ContractUPrim
  , testCase basic3FilePath $
      isLeft (readContract @TPairInt @TListPairInt codeSrc basic3Code)
        @? "Contract should parse"
  ]

test_importContract :: TestTree
test_importContract = testGroup "Test 'importContract'"
  [ testCase emptyFilePath $ do
      contract <- importContract @'TUnit @'TUnit (contractsDir </> emptyFilePath)
      convertContract contract @?= emptyContractUPrim
  , testCase basic2FilePath $ do
      contract <- importContract @TPairInt @TListPairInt (contractsDir </> basic2FilePath)
      convertContract contract @?= basic2ContractUPrim
  , testCase basic3FilePath $ do
      handle @ContractReadError (\_ -> pass) $ do
        _ <- importContract @TPairInt @TListPairInt (contractsDir </> basic3FilePath)
        assertFailure "No exception is raised"
  ]

test_importSomeContract :: TestTree
test_importSomeContract = testGroup "Test 'importSomeContract'"
  [ testCase emptyFilePath $ do
      SomeContract contract <- importSomeContract (contractsDir </> emptyFilePath)
      convertContract contract @?= emptyContractUPrim
  , testCase basic2FilePath $ do
      SomeContract contract <- importSomeContract (contractsDir </> basic2FilePath)
      convertContract contract @?= basic2ContractUPrim
  , testCase basic3FilePath $ do
      handle @ContractReadError (\_ -> pass) $ do
        _ <- importSomeContract (contractsDir </> basic3FilePath)
        assertFailure "No exception is raised"
  ]


test_importUntypedContract :: TestTree
test_importUntypedContract = testGroup "Test 'importUntypedContract'"
  [ testCase emptyFilePath $ do
      contractU <- importUntypedContract (contractsDir </> emptyFilePath)
      contractU @?= emptyContractU
  , testCase basic2FilePath $ do
      contractU <- importUntypedContract (contractsDir </> basic2FilePath)
      contractU @?= basic2ContractU
  , testCase "Non-parsable contract" $ do
      handle @ContractReadError (\_ -> pass) $ do
        _ <- importUntypedContract (contractsDir </> "unparsable" </> "chain_id_arity.tz")
        assertFailure "No exception is raised"
  ]

test_readValue :: TestTree
test_readValue = testGroup "Test 'readValue'"
  [ testCase "1" $
      (readValue @'TNat codeSrc "1") @?= Right (VNat 1)
  , testCase "{1; 2}" $
      (readValue @('TList 'TString) codeSrc "{\"a\"; \"b\"}") @?=
        Right (VList [VString "a", VString "b"])
  , testCase "Pair 1 \"a\"" $
      (readValue @('TPair 'TInt 'TString) codeSrc "Pair 1 \"a\"") @?=
        Right (VPair (VInt 1, VString "a"))
  , testCase "{something}" $
      isLeft (readValue @'TBool codeSrc "{something}")
        @? "Should parse"
  ]


-- Contracts' relative filePaths

emptyFilePath :: FilePath
emptyFilePath = "tezos_examples/attic/empty.tz"

basic2FilePath :: FilePath
basic2FilePath = "basic2.tz"

basic3FilePath :: FilePath
basic3FilePath = "ill-typed/basic3.tz"


-- Text contracts

emptyCode :: Text
emptyCode =
  [itu|
  parameter unit;
  storage unit;
  code {CDR; NIL operation; PAIR}
  |]

basic2Code :: Text
basic2Code =
  [itu|
  parameter (pair (int %x) (int :x));
  storage (list (pair int (int %y)));
  code {
        DUP; CAR; DIP { CDR; };
        CONS;
        NIL operation; PAIR; };
  |]

basic3Code :: Text
basic3Code =
  [itu|
  parameter (pair (int %x) int);
  storage (list (pair (int %z) int));
  code {
        DUP; CAR; DIP { CDR; };
        CONS;
        NIL operation; PAIR; };
  |]


-- Untyped Michelson contracts

-- We need two versions of each untyped contract for the tests.
-- First is a contract which 'contractCode' is based on 'PrimEx'
-- due to the fact that 'convertContract' uses this constructor
-- to produce untyped contract.
-- Second has 'WithSrcEx' for this purpose
-- because it is a way how untyped contracts are parsed.

emptyContractUPrim :: U.Contract
emptyContractUPrim =
  U.Contract
    { contractParameter = U.ParameterType U.tyunit U.noAnn
    , contractStorage = U.tyunit
    , contractCode = U.PrimEx <$>
        [ U.CDR U.noAnn U.noAnn
        , U.NIL U.noAnn U.noAnn tyoperation
        , U.PAIR U.noAnn U.noAnn U.noAnn U.noAnn
        ]
    , entriesOrder = U.PSC
    , contractViews = []
    }

emptyContractU :: U.Contract
emptyContractU =
  U.Contract
    { contractParameter = U.ParameterType U.tyunit U.noAnn
    , contractStorage = U.tyunit
    , contractCode =
        [ locExpanded 2 6  $ U.CDR U.noAnn U.noAnn
        , locExpanded 2 11 $ U.NIL U.noAnn U.noAnn tyoperation
        , locExpanded 2 26 $ U.PAIR U.noAnn U.noAnn U.noAnn U.noAnn
        ]
    , entriesOrder = U.PSC
    , contractViews = []
    }

basic2ContractUPrim :: U.Contract
basic2ContractUPrim =
  U.Contract
    { contractParameter = U.ParameterType (U.Ty (U.TPair "x" U.noAnn U.noAnn U.noAnn U.tyint (U.Ty U.TInt "x")) U.noAnn) U.noAnn
    , contractStorage = U.Ty (U.TList (U.Ty (U.TPair U.noAnn "y" U.noAnn U.noAnn U.tyint U.tyint) U.noAnn)) U.noAnn
    , contractCode = U.PrimEx <$>
        [ U.DUP U.noAnn
        , U.CAR U.noAnn U.noAnn
        , U.DIP [U.PrimEx $ U.CDR U.noAnn U.noAnn]
        , U.CONS U.noAnn
        , U.NIL U.noAnn U.noAnn tyoperation
        , U.PAIR U.noAnn U.noAnn U.noAnn U.noAnn
        ]
    , entriesOrder = U.PSC
    , contractViews = []
    }

basic2ContractU :: U.Contract
basic2ContractU =
  U.Contract
    { contractParameter = U.ParameterType (U.Ty (U.TPair "x" U.noAnn U.noAnn U.noAnn U.tyint (U.Ty U.TInt "x")) U.noAnn) U.noAnn
    , contractStorage = U.Ty (U.TList (U.Ty (U.TPair U.noAnn "y" U.noAnn U.noAnn U.tyint U.tyint) U.noAnn)) U.noAnn
    , contractCode =
        [ locExpanded 7 6  $ U.DUP U.noAnn
        , locExpanded 7 11 $ U.CAR U.noAnn U.noAnn
        , locExpanded 7 16 $ U.DIP [locExpanded 7 22 $ U.CDR U.noAnn U.noAnn]
        , locExpanded 8 6  $ U.CONS U.noAnn
        , locExpanded 9 6  $ U.NIL U.noAnn U.noAnn tyoperation
        , locExpanded 9 21 $ U.PAIR U.noAnn U.noAnn U.noAnn U.noAnn
        ]
    , entriesOrder = U.PSC
    , contractViews = []
    }


-- Helpers

locExpanded :: Word -> Word -> U.ExpandedInstr -> U.ExpandedOp
locExpanded line col ei =
  let noCallStack = InstrCallStack [] $ srcPos line col
  in U.WithSrcEx noCallStack $ U.PrimEx ei

locInstr :: Word -> Word -> Instr a b -> Instr a b
locInstr line col = WithLoc $ InstrCallStack [] $ srcPos line col

withNotes
  :: forall a inp out. SingI a
  => Notes a
  -> Instr inp (a : out)
  -> Instr inp (a : out)
withNotes notes = InstrWithNotes Proxy (notes :& RNil)

withVarAnn :: U.VarAnn -> Instr inp out -> Instr inp out
withVarAnn = InstrWithVarAnns . U.OneVarAnn

tyoperation :: U.Ty
tyoperation = U.Ty U.TOperation U.noAnn

type TPairInt = 'TPair 'TInt 'TInt

type TListPairInt = 'TList ('TPair 'TInt 'TInt)