packages feed

cleveland-0.1.0: lorentz-test/Test/Lorentz/Views.hs

-- SPDX-FileCopyrightText: 2021 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

-- | Tests for Lorentz on-chain views.

module Test.Lorentz.Views
  ( test_Views
  ) where

import Lorentz
import Prelude hiding (drop, some, swap, take, view)

import Data.Coerce (coerce)
import Test.Tasty (TestTree, testGroup)

import Morley.Util.Type
import Test.Cleveland
import Test.Cleveland.Lorentz.Requester

data Views1
type instance RevealViews Views1 =
  [ "plusSt" ?:: Natural >-> Natural
  , "fail" ?:: MText >-> Never
  , "stPow" ?:: Natural >-> Natural
  ] ++
  RevealViews PlusView

data PlusView deriving stock (Generic)
type instance RevealViews PlusView =
 '[ "plus" ?:: (Integer, Integer) >-> Integer
  ]
instance ViewsDescriptorHasDoc PlusView

viewedContract1 :: Contract Never Natural Views1
viewedContract1 = compileLorentzContract $
  defaultContractData
    (car # never)
  & setViews
    ( mkView @"plusSt" (unpair # add)
    , mkView @"fail" (car # failWith)
    , mkView @"stPow"
        ( car #
          push @Integer 1 # rsub # isNat # ifSome
            ( -- call the same view recursively
              viewE @"plusSt"
                ! #address do selfAddress # asAddressOf_ viewedContract1
                ! #arg do push 0
              |*|
              -- call another view
              viewE @"stPow"
                ! #address do selfAddress # asAddressOf_ viewedContract1
                ! #arg take
            )
            ( push 1 )
        )
    , mkView @"plus" (car # unpair # add)
    )

data Views2
type instance RevealViews Views2 =
 '[ "plus10"
      -- TODO [#716]: here we should use not TAddress but something else that
      -- 1. does not carry the parameter type
      -- 2. can narrow the list of views when converted from e.g. ContractHandle
      --    (because we are fine with any contract that contains more views, not only
      --     'PlusView')
      --
      -- For now, we have to cast 'TAddress'.
      ?:: (Integer, TAddress () PlusView)
      >-> Integer
  ]

viewedContract2 :: Contract Never () Views2
viewedContract2 = compileLorentzContract $
  defaultContractData
    (car # never)
  & setViews
    ( mkView @"plus10" (car # unpair # push 10 # pair # view @"plus")
    )

data AccessAddrView
type instance RevealViews AccessAddrView =
 '[ "originatedContract" ?:: () >-> TAddress Never PlusView
  ]

-- | On call originates a contract with 'PlusView' view and save its address in storage.
-- The view in the originated contract will sum up two numbers and add extra 100.
view3OriginatorContract :: Contract () (Maybe (TAddress Never PlusView)) AccessAddrView
view3OriginatorContract = compileLorentzContract $
  defaultContractData
    ( drop #
      pairE
        ( createContractE
          ! #storage (push 100)
          ! #delegate none
          ! #balance (push zeroMutez)
          ! #contract newContract
          |:| nil
        , some
        )
    )
  & setViews
    ( mkView @"originatedContract" (cdr # assertSome [mt|Nothing originated|])
    )
  where
    newContract = compileLorentzContract @Never @Natural @PlusView $
      defaultContractData (car # never)
      & setViews
        ( mkView @"plus" (unpair # unpair # add # add)
        )

-- | On call, calls the view in the contract originated by 'view3OriginatorContract'.
view3Caller :: Contract () (TAddress () AccessAddrView, Maybe Integer) ()
view3Caller = mkContract $
  cdr #
  car #
  stackType @'[TAddress () AccessAddrView] #
  viewE @"plus"
    ! #arg do push (10, 1)
    ! #address do
        viewE @"originatedContract"
            ! #arg unit
            ! #address dup
    #
  stackType @[Integer, TAddress () AccessAddrView] #
  swap #
  pairE (take, some) #
  nil # pair

-- | Calls 'view3OriginatorContract' and 'view3Caller' sequentially.
view3CreatorAndCaller :: Contract (ContractRef (), ContractRef ()) () ()
view3CreatorAndCaller = mkContract $
  car #
  unpair #
  transferTokensE
    ! #arg unit
    ! #amount (push zeroMutez)
    ! #contract take
  |:|
  transferTokensE
    ! #arg unit
    ! #amount (push zeroMutez)
    ! #contract take
  |:| nil
  |@| unit

test_Views :: TestTree
test_Views = testGroup "Views"
  [ testScenario "Simple case with requester contract" $ scenario do
      viewed <- originateSimple "viewed" 1 viewedContract1
      requester <- originateSimple "requester" [] (contractRequester @"plusSt" viewed)
      forM_ [5, 10] $ call requester CallDefault
      getStorage requester @@== [11, 6]

  -- TODO [#708]: Call views directly, without requester contract

  , testScenario "Failing view" $ scenario do
      viewed <- originateSimple "viewed" 0 viewedContract1
      requester <- originateSimple "requester" [] (contractRequester @"fail" viewed)
      call requester CallDefault [mt|nyan|]
        & expectFailedWith [mt|nyan|]

  , testScenario "Recursive view" $ scenario do
      viewed <- originateSimple "viewed" 2 viewedContract1
      requester <- originateSimple "requester" [] (contractRequester @"stPow" viewed)
      forM_ [5, 0] $ call requester CallDefault
      getStorage requester @@== [1, 32]

  , testScenario "Calling view in another contract" $ scenario do
      viewedHelper <- originateSimple "viewed" 0 viewedContract1
      viewed <- originateSimple "viewed" () viewedContract2
      requester <- originateSimple "requester" [] (contractRequester @"plus10" viewed)
      -- TODO [#716]: coerce shouldn't be necessary
      call requester CallDefault (20, coerce $ toTAddress viewedHelper)
      getStorage requester @@== [30]

  , testScenario "Calling a just originated view" $ scenario do
      -- It might be that a view is originated and called within the same
      -- global operation, we want to be sure this works
      originator <- originateSimple "originator" Nothing view3OriginatorContract
      caller <- originateSimple "caller" (toTAddress originator, Nothing) view3Caller
      allCaller <- originateSimple "allCaller" () view3CreatorAndCaller

      call allCaller CallDefault (toContractRef originator, toContractRef caller)
      snd <$> getStorage caller @@== Just 111

  ]