packages feed

cleveland-0.3.1: src/Hedgehog/Gen/Michelson/Typed.hs

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

{-# OPTIONS_GHC -Wno-redundant-constraints #-}

module Hedgehog.Gen.Michelson.Typed
  ( genBigMap
  , genEpAddress
  , genValueBigMap
  , genValueChestAndKey
  , genValueInt
  , genValueKeyHash
  , genValueList
  , genValueMap
  , genValueMutez
  , genValueNat
  , genValuePair
  , genValueSet
  , genValueString
  , genValueTimestamp
  , genValueUnit

  , genValue
  , genValue'

  , genSimpleInstr
  ) where

import Control.Exception qualified as Ex
import Data.Singletons (Sing)
import Hedgehog (GenBase, MonadGen)
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range (Range)
import Hedgehog.Range qualified as Range

import Hedgehog.Gen.Michelson.Untyped (genEpName)
import Hedgehog.Gen.Tezos.Address
import Hedgehog.Gen.Tezos.Core (genChainId, genMutez, genTimestamp)
import Hedgehog.Gen.Tezos.Crypto (genKeyHash, genPublicKey, genSignature)
import Hedgehog.Gen.Tezos.Crypto.BLS12381 (genBls12381Fr, genBls12381G1, genBls12381G2)
import Morley.Michelson.Text (mkMText)
import Morley.Michelson.Typed
  (Instr(DROP, FAILWITH, PUSH, SWAP, Seq, UNIT), SingT(..), T(..), Value'(..), mkVLam, toVal)
import Morley.Michelson.Typed.Entrypoints (EpAddress(..), unsafeSepcCallRoot)
import Morley.Michelson.Typed.Haskell.Value (BigMap(..), BigMapId(..), ToT, WellTypedToT)
import Morley.Michelson.Typed.Scope
import Morley.Michelson.Typed.Value (RemFail(..))
import Morley.Tezos.Address (Constrained(..))
import Morley.Tezos.Core (Mutez, Timestamp)
import Morley.Tezos.Crypto.BLS12381 qualified as BLS

import Hedgehog.Gen.Tezos.Crypto.Timelock (genChestAndKey)
import Hedgehog.Range.Defaults

genBigMap
  :: forall k v m.
     (MonadGen m, Ord k, WellTypedToT k, WellTypedToT v, Comparable (ToT k))
  => Range Length -> m k -> m v -> m (BigMap k v)
genBigMap rangeLen genK genV =
  BigMap
    <$> Gen.maybe (fmap BigMapId genBigMapId)
    <*> Gen.map (unLength <$> rangeLen) (liftA2 (,) genK genV)

genValueMap
  :: forall (k :: T) (v :: T) m instr.
     (MonadGen m, WellTyped k, WellTyped v, Comparable k)
  => Range Length -> m (Value' instr k) -> m (Value' instr v) -> m (Value' instr ('TMap k v))
genValueMap len genKey genVal =
  VMap <$> Gen.map (unLength <$> len) (liftA2 (,) genKey genVal)

genValueBigMap
  :: forall (k :: T) (v :: T) m instr.
     (MonadGen m, WellTyped k, WellTyped v, HasNoBigMap v, Comparable k)
  => Range Length -> m (Value' instr k) -> m (Value' instr v) -> m (Value' instr ('TBigMap k v))
genValueBigMap len genKey genVal =
  VBigMap
    <$> Gen.maybe genBigMapId
    <*> Gen.map (unLength <$> len) (liftA2 (,) genKey genVal)

genEpAddress :: (MonadGen m, GenBase m ~ Identity) => m EpAddress
genEpAddress = EpAddress' <$> genAddress <*> genEpName

genValueKeyHash :: MonadGen m => m (Value' instr 'TKeyHash)
genValueKeyHash = VKeyHash <$> genKeyHash

genValueMutez :: MonadGen m => Range Mutez -> m (Value' instr 'TMutez)
genValueMutez = fmap VMutez . genMutez

genValueInt :: MonadGen m => Range (Value' instr 'TInt) -> m (Value' instr 'TInt)
genValueInt range = VInt <$> Gen.integral (unVal <$> range)
  where
    unVal :: Value' instr 'TInt -> Integer
    unVal (VInt val) = val

genValueNat :: MonadGen m => Range (Value' instr 'TNat) -> m (Value' instr 'TNat)
genValueNat range = VNat <$> Gen.integral (unVal <$> range)
  where
    unVal :: Value' instr 'TNat -> Natural
    unVal (VNat val) = val

genValueString :: MonadGen f => Range Length -> f (Value' instr 'TString)
genValueString rangeLen =
  VString . unsafe. mkMText . fromString <$> Gen.string (unLength <$> rangeLen) Gen.alphaNum

genValueBytes :: MonadGen f => Range Length -> f (Value' instr 'TBytes)
genValueBytes rangeLen = VBytes <$> Gen.bytes (unLength <$> rangeLen)

genValueList
  :: (MonadGen m, SingI a) => Range Length -> m (Value' instr a) -> m (Value' instr ('TList a))
genValueList rangeLen genA = VList <$> Gen.list (unLength <$> rangeLen) genA

genValueSet
  :: (MonadGen m, Comparable a, SingI a)
  => Range Length -> m (Value' instr a) -> m (Value' instr ('TSet a))
genValueSet rangeLen genA = VSet <$> Gen.set (unLength <$> rangeLen) genA

genValueUnit :: Applicative m => m (Value' instr 'TUnit)
genValueUnit = pure VUnit

genValuePair
  :: MonadGen m => m (Value' instr a) -> m (Value' instr b) -> m (Value' instr ('TPair a b))
genValuePair genA genB = VPair ... (,) <$> genA <*> genB

genValueTimestamp :: MonadGen m => Range Timestamp -> m (Value' instr 'TTimestamp)
genValueTimestamp = fmap VTimestamp . genTimestamp

genValueTicket
  :: (MonadGen m, Comparable a)
  => Range TicketAmount -> m (Value' instr a) -> m (Value' instr ('TTicket a))
genValueTicket range genVal = VTicket . MkAddress <$> genContractAddress <*> genVal
  <*> Gen.integral (unTicketAmount <$> range)

genValueChestAndKey :: MonadGen m => m (Value' instr 'TChest, Value' instr 'TChestKey)
genValueChestAndKey = bimap VChest VChestKey <$> genChestAndKey

genValue
  :: forall t m.
      (MonadGen m, GenBase m ~ Identity, HasNoOp t, WellTyped t)
  => m (Value' Instr t)
genValue = genValue' (sing @t)

-- | Generate a simple instruction.
-- Ideally instruction generator should produce instructions containing
-- all possible primitive instructions.
-- In our case we consider only a few primitive instructions and
-- pick one from a hardcoded list. Hence we call it "simple".
-- Another limitation is that input stack and output stack types must be
-- identical and non-empty.
genSimpleInstr :: (MonadGen m, inp ~ (x ': xs), SingI x) => m (Instr inp inp)
genSimpleInstr = Gen.element
  [ UNIT `Seq` FAILWITH
  , PUSH (VInt 5) `Seq` DROP
  , UNIT `Seq` SWAP `Seq` SWAP `Seq` DROP
  ]

genBigMapId :: MonadGen m => m Natural
genBigMapId = Gen.integral (Range.constant 0 100000000)

genValue'
  :: (MonadGen m, GenBase m ~ Identity, HasNoOp t, WellTyped t)
  => Sing t -> m (Value' Instr t)
genValue' = \case
  STKey -> VKey <$> genPublicKey
  STUnit -> genValueUnit
  STSignature -> VSignature <$> genSignature
  STChainId -> VChainId <$> genChainId
  STOption st -> Gen.choice
    [ pure $ VOption Nothing
    , VOption . Just <$> genValue' st
    ]
  STList (STPair STBls12381G1 STBls12381G2) -> genBls12Pairing
  STList st -> genValueList def $ genValue' st
  STSet st -> genValueSet def $ genValue' st
  STContract STUnit -> VContract <$> genAddress <*> pure unsafeSepcCallRoot
  STContract _ -> VContract <$> (MkAddress <$> genContractAddress) <*> pure unsafeSepcCallRoot
  STTicket s -> genValueTicket def $ genValue' s
  STPair STChestKey STChest -> VPair . bimap VChestKey VChest . swap <$> genChestAndKey
  STPair l r -> VPair <$> ((,) <$> genNoOpValue l <*> genNoOpValue r)
  STOr l r -> VOr <$> Gen.choice
    [ Left <$> genNoOpValue l
    , Right <$> genNoOpValue r
    ]
  -- It's quite hard to generate proper lambda of given type, so it always returns FAILWITH.
  -- Such implementation is sufficient for now.
  STLambda{} -> pure $ mkVLam $ RfAlwaysFails $ Seq UNIT FAILWITH
  STMap k v -> genValueMap def (genNoOpValue k) (genNoOpValue v)
  STBigMap k v -> genValueBigMap def (genNoOpValue k) (genNoOpValue v)
  STInt -> genValueInt def
  STNat -> genValueNat def
  STString -> genValueString def
  STBytes -> genValueBytes def
  STMutez -> genValueMutez def
  STKeyHash -> genValueKeyHash
  STBls12381Fr -> VBls12381Fr <$> genBls12381Fr
  STBls12381G1 -> VBls12381G1 <$> genBls12381G1
  STBls12381G2 -> VBls12381G2 <$> genBls12381G2
  -- Note that we also have a special case for a list of BLS12 pairings
  STTimestamp -> genValueTimestamp def
  STAddress -> VAddress <$> genEpAddress
  STBool -> VBool <$> Gen.bool
  STChest -> VChest . fst <$> genChestAndKey
  STChestKey -> VChestKey . snd <$> genChestAndKey
  STNever -> Gen.discard
  STSaplingState _ -> error "genValue': Cannot generate `sapling_state` value."
  STSaplingTransaction _ -> error "genValue': Cannot generate `sapling_transaction` value."
  where
    genNoOpValue
      :: (MonadGen m, GenBase m ~ Identity, WellTyped t')
      => Sing t' -> m (Value' Instr t')
    genNoOpValue st = case checkOpPresence st of
      OpAbsent -> genValue' st
      _ -> Gen.discard

    genBls12Pairing
      :: MonadGen m
      => m (Value' Instr ('TList $ 'TPair 'TBls12381G1 'TBls12381G2))
    genBls12Pairing = Gen.frequency
      [ -- random pairing (likely incorrect one)
        ( 1
        , fmap toVal $ Gen.list (Range.linear 0 10) $
            (,) <$> genBls12381G1 <*> genBls12381G2
        )
      , -- correct pairing case
        ( 1
        , do
            g1 <- genBls12381G1
            g2 <- genBls12381G2
            let pairing = [(g1, g2), (g1, BLS.negate g2)]
            Ex.assert (BLS.checkPairing pairing) $
              return (toVal pairing)
        )
      ]