plutus-pioneer-program/code/week08/src/Week08/TestTokenSale.hs

211 lines
7.9 KiB
Haskell
Raw Normal View History

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
2021-05-24 22:21:05 +02:00
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
2021-05-24 22:21:05 +02:00
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
2021-05-24 22:21:05 +02:00
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Week08.TestTokenSale where
2021-05-24 22:21:05 +02:00
import Control.Lens hiding (elements)
import Control.Monad (void, when)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (isJust, isNothing)
import Data.Monoid (Last (..))
import Data.String (IsString (..))
2021-05-24 22:21:05 +02:00
import Data.Text (Text)
import Plutus.Contract.Test
2021-05-24 22:21:05 +02:00
import Plutus.Contract.Test.ContractModel
import Plutus.Trace.Emulator
import Ledger hiding (singleton)
import Ledger.Ada as Ada
import Ledger.Value
2021-05-24 22:21:05 +02:00
import Test.QuickCheck
import Week08.TokenSale (TokenSale (..), TSStartSchema', TSUseSchema, startEndpoint', useEndpoints, nftName)
2021-05-24 22:21:05 +02:00
data TSState = TSState
{ _tssPrice :: !Integer
, _tssLovelace :: !Integer
, _tssToken :: !Integer
} deriving Show
makeLenses ''TSState
newtype TSModel = TSModel {_tsModel :: Map Wallet TSState}
deriving Show
makeLenses ''TSModel
instance ContractModel TSModel where
2021-05-25 00:13:56 +02:00
data Action TSModel =
Start Wallet
| SetPrice Wallet Wallet Integer
| AddTokens Wallet Wallet Integer
| Withdraw Wallet Wallet Integer Integer
2021-05-25 00:13:56 +02:00
| BuyTokens Wallet Wallet Integer
2021-05-24 22:21:05 +02:00
deriving (Show, Eq)
data ContractInstanceKey TSModel w s e where
StartKey :: Wallet -> ContractInstanceKey TSModel (Last TokenSale) TSStartSchema' Text
UseKey :: Wallet -> Wallet -> ContractInstanceKey TSModel () TSUseSchema Text
instanceTag key _ = fromString $ "instance tag for: " ++ show key
2021-05-24 22:21:05 +02:00
arbitraryAction _ = oneof $
(Start <$> genWallet) :
[ SetPrice <$> genWallet <*> genWallet <*> genNonNeg ] ++
[ AddTokens <$> genWallet <*> genWallet <*> genNonNeg ] ++
[ BuyTokens <$> genWallet <*> genWallet <*> genNonNeg ] ++
[ Withdraw <$> genWallet <*> genWallet <*> genNonNeg <*> genNonNeg ]
2021-05-24 22:21:05 +02:00
initialState = TSModel Map.empty
nextState (Start w) = do
withdraw w $ nfts Map.! w
(tsModel . at w) $= Just (TSState 0 0 0)
wait 1
nextState (SetPrice v w p) = do
when (v == w) $
(tsModel . ix v . tssPrice) $= p
2021-05-24 22:21:05 +02:00
wait 1
nextState (AddTokens v w n) = do
started <- hasStarted v -- has the token sale started?
2021-05-24 22:21:05 +02:00
when (n > 0 && started) $ do
bc <- askModelState $ view $ balanceChange w
let token = tokens Map.! v
2021-05-25 00:13:56 +02:00
when (tokenAmt + assetClassValueOf bc token >= n) $ do -- does the wallet have the tokens to give?
2021-05-24 22:21:05 +02:00
withdraw w $ assetClassValue token n
(tsModel . ix v . tssToken) $~ (+ n)
2021-05-24 22:21:05 +02:00
wait 1
2021-05-25 00:13:56 +02:00
nextState (BuyTokens v w n) = do
2021-05-24 22:21:05 +02:00
when (n > 0) $ do
m <- getTSState v
case m of
Just t
| t ^. tssToken >= n -> do
let p = t ^. tssPrice
l = p * n
withdraw w $ lovelaceValueOf l
deposit w $ assetClassValue (tokens Map.! v) n
(tsModel . ix v . tssLovelace) $~ (+ l)
2021-05-25 00:13:56 +02:00
(tsModel . ix v . tssToken) $~ (+ (- n))
2021-05-24 22:21:05 +02:00
_ -> return ()
wait 1
nextState (Withdraw v w n l) = do
when (v == w) $ do
m <- getTSState v
case m of
Just t
| t ^. tssToken >= n && t ^. tssLovelace >= l -> do
deposit w $ lovelaceValueOf l <> assetClassValue (tokens Map.! w) n
(tsModel . ix v . tssLovelace) $~ (+ (- l))
(tsModel . ix v . tssToken) $~ (+ (- n))
_ -> return ()
2021-05-24 22:21:05 +02:00
wait 1
perform h _ cmd = case cmd of
(Start w) -> callEndpoint @"start" (h $ StartKey w) (nftCurrencies Map.! w, tokenCurrencies Map.! w, tokenNames Map.! w) >> delay 1
(SetPrice v w p) -> callEndpoint @"set price" (h $ UseKey v w) p >> delay 1
(AddTokens v w n) -> callEndpoint @"add tokens" (h $ UseKey v w) n >> delay 1
(BuyTokens v w n) -> callEndpoint @"buy tokens" (h $ UseKey v w) n >> delay 1
(Withdraw v w n l) -> callEndpoint @"withdraw" (h $ UseKey v w) (n, l) >> delay 1
2021-05-24 22:21:05 +02:00
precondition s (Start w) = isNothing $ getTSState' s w
precondition _ _ = True
deriving instance Eq (ContractInstanceKey TSModel w s e)
deriving instance Show (ContractInstanceKey TSModel w s e)
getTSState' :: ModelState TSModel -> Wallet -> Maybe TSState
getTSState' s v = s ^. contractState . tsModel . at v
getTSState :: Wallet -> Spec TSModel (Maybe TSState)
getTSState v = do
s <- getModelState
return $ getTSState' s v
hasStarted :: Wallet -> Spec TSModel Bool
hasStarted v = isJust <$> getTSState v
w1, w2 :: Wallet
2021-05-24 22:21:05 +02:00
w1 = Wallet 1
w2 = Wallet 2
wallets :: [Wallet]
wallets = [w1, w2]
tokenCurrencies, nftCurrencies :: Map Wallet CurrencySymbol
tokenCurrencies = Map.fromList $ zip wallets ["aa", "bb"]
nftCurrencies = Map.fromList $ zip wallets ["01", "02"]
2021-05-24 22:21:05 +02:00
tokenNames :: Map Wallet TokenName
tokenNames = Map.fromList $ zip wallets ["A", "B"]
2021-05-24 22:21:05 +02:00
tokens :: Map Wallet AssetClass
tokens = Map.fromList [(w, AssetClass (tokenCurrencies Map.! w, tokenNames Map.! w)) | w <- wallets]
2021-05-24 22:21:05 +02:00
nftAssets :: Map Wallet AssetClass
nftAssets = Map.fromList [(w, AssetClass (nftCurrencies Map.! w, nftName)) | w <- wallets]
2021-05-24 22:21:05 +02:00
nfts :: Map Wallet Value
nfts = Map.fromList [(w, assetClassValue (nftAssets Map.! w) 1) | w <- wallets]
2021-05-24 22:21:05 +02:00
tss :: Map Wallet TokenSale
tss = Map.fromList
[ (w, TokenSale { tsSeller = pubKeyHash $ walletPubKey w
, tsToken = tokens Map.! w
, tsNFT = nftAssets Map.! w
})
| w <- wallets
2021-05-24 22:21:05 +02:00
]
delay :: Int -> EmulatorTrace ()
delay = void . waitNSlots . fromIntegral
instanceSpec :: [ContractInstanceSpec TSModel]
instanceSpec =
[ContractInstanceSpec (StartKey w) w startEndpoint' | w <- wallets] ++
[ContractInstanceSpec (UseKey v w) w $ useEndpoints $ tss Map.! v | v <- wallets, w <- wallets]
2021-05-24 22:21:05 +02:00
genWallet :: Gen Wallet
genWallet = elements wallets
2021-05-24 22:21:05 +02:00
2021-05-25 00:13:56 +02:00
genNonNeg :: Gen Integer
genNonNeg = getNonNegative <$> arbitrary
tokenAmt :: Integer
tokenAmt = 1_000
2021-05-24 22:21:05 +02:00
prop_TS :: Actions TSModel -> Property
2021-05-25 00:13:56 +02:00
prop_TS = withMaxSuccess 1000 . propRunActionsWithOptions
2021-05-24 22:21:05 +02:00
(defaultCheckOptions & emulatorConfig .~ EmulatorConfig (Left d))
instanceSpec
(const $ pure True)
where
d :: InitialDistribution
d = Map.fromList $ [ ( w
, lovelaceValueOf 1000_000_000 <>
(nfts Map.! w) <>
2021-05-25 00:13:56 +02:00
mconcat [assetClassValue t tokenAmt | t <- Map.elems tokens])
| w <- wallets
]
2021-05-24 22:21:05 +02:00
test :: IO ()
test = quickCheck prop_TS