morley-0.3.0: test/Test/Interpreter/A1/Feather.hs
{-# LANGUAGE DeriveAnyClass, DerivingStrategies #-}
-- | Tests for feathering as described in TZIP-A1.
module Test.Interpreter.A1.Feather
( featherSpec
) where
import Control.Lens (makeLenses, (%=), (+=), (-=), (.=))
import Control.Monad.Except (Except, runExcept, throwError)
import Data.Singletons (SingI)
import Test.Hspec (Spec)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Gen, arbitrary, forAll, frequency, listOf, suchThat)
import Test.QuickCheck.Arbitrary.Generic (genericArbitrary)
import Test.QuickCheck.Instances.Natural ()
import Lorentz (View, Void_)
import qualified Lorentz as L
import Lorentz.Test
import Michelson.Interpret (MichelsonFailed(..))
import Michelson.Interpret.Pack (packValue')
import Michelson.Test
import qualified Michelson.Typed as T
import qualified Michelson.Untyped as U
import Tezos.Address
import Tezos.Core
import Tezos.Crypto
import Util.Named
data Outcome = Outcome
{ _oCounter :: !Natural
, _oList :: ![Natural]
, _oSum :: !Natural
}
makeLenses ''Outcome
featherSpec :: Spec
featherSpec =
specWithContractL "contracts/A1/counter.mtz" $ \counter ->
specWithContractL "contracts/A1/feather.mtz" $ \feather ->
specWithContractL "contracts/A1/caller_add.mtz" $ \callerAdd ->
specWithContractL "contracts/A1/caller_append.mtz" $ \callerAppend ->
specImpl counter feather callerAdd callerAppend
data CounterParameter
= AntibumpCounter
| BumpCounter
| ResetCounter Natural
| GetCount (View () Natural)
| HashCount (Void_ () ByteString)
deriving stock Generic
deriving anyclass T.IsoValue
type CounterStorage = Natural
type FeatherParameter = ((), Maybe Natural)
type FeatherStorage = (Address, Maybe Address)
type CallerParameter = Either Natural ()
type CallerAddStorage = (Natural, Address)
type CallerAppendStorage = ([Natural], Address)
-- Actions that can be performed by this scenario.
data Action
= Antibump
-- ^ Antibump counter
| Bump
-- ^ Bump counter
| Reset !Natural
-- ^ Reset counter
| HashCounter
-- ^ Get counter's hash using void entry point (which implies that
-- everything else should fail)
| Add !Natural
-- ^ Add a constant to caller-add
| AddRecord
-- ^ Add counter value to caller-add
| Append !Natural
-- ^ Append a constant to caller-append
| AppendRecord
-- ^ Append counter value to caller-append
deriving (Show, Generic)
genAction :: Gen Action
genAction = genericArbitrary
data Fixture = Fixture
{ fActions :: ![Action]
, fInitialCounter :: !Natural
, fInitialList :: ![Natural]
, fInitialSum :: !Natural
} deriving (Show)
-- We generate 'HashCounter' with smaller probablity, because otherwise
-- tests will often stop quickly (they stop as soon as 'HashCounter'
-- occurs).
genFixture :: Gen Fixture
genFixture = do
fActions <-
listOf $ frequency
[ (26, genAction `suchThat` notHashCounter)
, (1, pure HashCounter)
]
fInitialCounter <- arbitrary
fInitialList <- arbitrary
fInitialSum <- arbitrary
pure Fixture {..}
where
notHashCounter =
\case
HashCounter -> False
_ -> True
data ExpectedFail
= NegativeCounter
| HashCounterCalled !ByteString
expectedOutcome :: Fixture -> Either ExpectedFail Outcome
expectedOutcome Fixture {..} =
runExcept $ execStateT (mapM_ step fActions) start
where
start = Outcome
{ _oCounter = fInitialCounter
, _oList = fInitialList
, _oSum = fInitialSum
}
step :: Action -> StateT Outcome (Except ExpectedFail) ()
step = \case
Antibump -> use oCounter >>= \case
0 -> throwError NegativeCounter
_ -> oCounter -= 1
Bump -> oCounter += 1
Reset x -> oCounter .= x
HashCounter ->
throwError .
HashCounterCalled .
sha512 .
packValue' .
T.toVal =<<
use oCounter
Add x -> oSum += x
AddRecord -> (oSum +=) =<< use oCounter
Append x -> oList %= (x:)
AppendRecord -> do
c <- use oCounter
oList %= (c:)
specImpl ::
(U.Contract, L.Contract CounterParameter CounterStorage)
-> (U.Contract, L.Contract FeatherParameter FeatherStorage)
-> (U.Contract, L.Contract CallerParameter CallerAddStorage)
-> (U.Contract, L.Contract CallerParameter CallerAppendStorage)
-> Spec
specImpl (_, counter) (_, feather) (_, callerAdd) (_, callerAppend) =
prop "A mix of random actions is handled as expected" $
forAll genFixture $ \fixture ->
integrationalTestProperty (scenario fixture)
where
scenario :: Fixture -> IntegrationalScenario
scenario fixture@Fixture {..} = do
let
initBalance = unsafeMkMutez 100
counterContractAddress@(T.ContractAddr counterAddress) <-
lOriginate counter "counter" fInitialCounter initBalance
let
defaultFeatherValue :: FeatherStorage
defaultFeatherValue =
(counterAddress, Nothing @Address)
featherContractAddress@(T.ContractAddr featherAddress) <-
lOriginate feather "feather" defaultFeatherValue initBalance
cAddContractAddress <-
lOriginate callerAdd "caller-add"
(fInitialSum, featherAddress) initBalance
cAppendContractAddress <-
lOriginate callerAppend "caller-append"
(fInitialList, featherAddress) initBalance
let
transfer' ::
forall t . (T.IsoValue t, SingI (T.ToT t), T.HasNoOp (T.ToT t))
=> T.ContractAddr t -> t -> IntegrationalScenarioM ()
transfer' addr param =
lTransfer (#from .! genesisAddress) (#to .! addr)
(unsafeMkMutez 0) param
transferToCounter :: CounterParameter -> IntegrationalScenarioM ()
transferToCounter = transfer' counterContractAddress
transferToAdd :: CallerParameter -> IntegrationalScenarioM ()
transferToAdd = transfer' cAddContractAddress
transferToAppend :: CallerParameter -> IntegrationalScenarioM ()
transferToAppend = transfer' cAppendContractAddress
performAction :: Action -> IntegrationalScenarioM ()
performAction = \case
Antibump -> transferToCounter AntibumpCounter
Bump -> transferToCounter BumpCounter
Reset x -> transferToCounter $ ResetCounter x
HashCounter -> transferToCounter $ HashCount (L.mkVoid ())
Add x -> transferToAdd $ Left x
AddRecord -> transferToAdd $ Right ()
Append x -> transferToAppend $ Left x
AppendRecord -> transferToAppend $ Right ()
mapM_ performAction fActions
let
checkHash :: ByteString -> MichelsonFailed -> Bool
checkHash expectedHash = (== MichelsonFailedWith (T.toVal expectedHash))
validator :: IntegrationalValidator
validator = case expectedOutcome fixture of
Left NegativeCounter ->
Left $ expectMichelsonFailed (const True) counterAddress
Left (HashCounterCalled expectedHash) ->
Left $ expectMichelsonFailed (checkHash expectedHash) counterAddress
Right (Outcome {..}) -> Right $ composeValidatorsList
[ -- Feather must always be called twice and the second
-- call must set its storage to its initial value.
lExpectStorageConst featherContractAddress defaultFeatherValue
, -- Counter, caller-add and caller-append have values
-- according to 'Outcome'.
lExpectStorageConst counterContractAddress _oCounter
, lExpectStorageConst cAddContractAddress (_oSum, featherAddress)
, lExpectStorageConst cAppendContractAddress (_oList, featherAddress)
, -- Balances must not change
expectBalance counterAddress initBalance
, expectBalance featherAddress initBalance
, lExpectBalance cAddContractAddress initBalance
, lExpectBalance cAppendContractAddress initBalance
]
validate validator