mirror of
https://github.com/FiloSpaTeam/plutus-pioneer-program.git
synced 2024-12-22 13:31:59 +01:00
started with solutions for week 8
This commit is contained in:
parent
ba40342f1d
commit
99cbc0b8ca
5 changed files with 510 additions and 0 deletions
|
@ -13,6 +13,7 @@ library
|
|||
exposed-modules: Week08.Lens
|
||||
, Week08.QuickCheck
|
||||
, Week08.TokenSale
|
||||
, Week08.TokenSaleWithClose
|
||||
build-depends: aeson
|
||||
, base ^>=4.14.1.0
|
||||
, containers
|
||||
|
@ -35,7 +36,9 @@ test-suite plutus-pioneer-program-week08-tests
|
|||
main-is: Spec.hs
|
||||
hs-source-dirs: test
|
||||
other-modules: Spec.Model
|
||||
, Spec.ModelWithClose
|
||||
, Spec.Trace
|
||||
, Spec.TraceWithClose
|
||||
default-language: Haskell2010
|
||||
ghc-options: -Wall -fobject-code -fno-ignore-interface-pragmas -fno-omit-interface-pragmas
|
||||
build-depends: base ^>=4.14.1.0
|
||||
|
|
188
code/week08/src/Week08/TokenSaleWithClose.hs
Normal file
188
code/week08/src/Week08/TokenSaleWithClose.hs
Normal file
|
@ -0,0 +1,188 @@
|
|||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
|
||||
module Week08.TokenSaleWithClose
|
||||
( TokenSale (..)
|
||||
, TSRedeemer (..)
|
||||
, nftName
|
||||
, TSStartSchema
|
||||
, TSStartSchema'
|
||||
, TSUseSchema
|
||||
, startEndpoint
|
||||
, startEndpoint'
|
||||
, useEndpoints
|
||||
) where
|
||||
|
||||
import Control.Monad hiding (fmap)
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import Data.Monoid (Last (..))
|
||||
import Data.Text (Text, pack)
|
||||
import GHC.Generics (Generic)
|
||||
import Plutus.Contract as Contract hiding (when)
|
||||
import Plutus.Contract.StateMachine
|
||||
import qualified Plutus.Contracts.Currency as C
|
||||
import qualified PlutusTx
|
||||
import PlutusTx.Prelude hiding (Semigroup(..), check, unless)
|
||||
import Ledger hiding (singleton)
|
||||
import Ledger.Ada as Ada
|
||||
import Ledger.Constraints as Constraints
|
||||
import qualified Ledger.Typed.Scripts as Scripts
|
||||
import Ledger.Value
|
||||
import Prelude (Semigroup (..), Show (..), uncurry)
|
||||
import qualified Prelude
|
||||
|
||||
data TokenSale = TokenSale
|
||||
{ tsSeller :: !PubKeyHash
|
||||
, tsToken :: !AssetClass
|
||||
, tsNFT :: !AssetClass
|
||||
} deriving (Show, Generic, FromJSON, ToJSON, Prelude.Eq, Prelude.Ord)
|
||||
|
||||
PlutusTx.makeLift ''TokenSale
|
||||
|
||||
data TSRedeemer =
|
||||
SetPrice Integer
|
||||
| AddTokens Integer
|
||||
| BuyTokens Integer
|
||||
| Withdraw Integer Integer
|
||||
deriving (Show, Prelude.Eq)
|
||||
|
||||
PlutusTx.unstableMakeIsData ''TSRedeemer
|
||||
|
||||
{-# INLINABLE lovelaces #-}
|
||||
lovelaces :: Value -> Integer
|
||||
lovelaces = Ada.getLovelace . Ada.fromValue
|
||||
|
||||
{-# INLINABLE transition #-}
|
||||
transition :: TokenSale -> State Integer -> TSRedeemer -> Maybe (TxConstraints Void Void, State Integer)
|
||||
transition ts s r = case (stateValue s, stateData s, r) of
|
||||
(v, _, SetPrice p) | p >= 0 -> Just ( Constraints.mustBeSignedBy (tsSeller ts)
|
||||
, State p $
|
||||
v <>
|
||||
nft (negate 1)
|
||||
)
|
||||
(v, p, AddTokens n) | n > 0 -> Just ( mempty
|
||||
, State p $
|
||||
v <>
|
||||
nft (negate 1) <>
|
||||
assetClassValue (tsToken ts) n
|
||||
)
|
||||
(v, p, BuyTokens n) | n > 0 -> Just ( mempty
|
||||
, State p $
|
||||
v <>
|
||||
nft (negate 1) <>
|
||||
assetClassValue (tsToken ts) (negate n) <>
|
||||
lovelaceValueOf (n * p)
|
||||
)
|
||||
(v, p, Withdraw n l) | n >= 0 && l >= 0 -> Just ( Constraints.mustBeSignedBy (tsSeller ts)
|
||||
, State p $
|
||||
v <>
|
||||
nft (negate 1) <>
|
||||
assetClassValue (tsToken ts) (negate n) <>
|
||||
lovelaceValueOf (negate l)
|
||||
)
|
||||
_ -> Nothing
|
||||
where
|
||||
nft :: Integer -> Value
|
||||
nft = assetClassValue (tsNFT ts)
|
||||
|
||||
{-# INLINABLE tsStateMachine #-}
|
||||
tsStateMachine :: TokenSale -> StateMachine Integer TSRedeemer
|
||||
tsStateMachine ts = mkStateMachine (Just $ tsNFT ts) (transition ts) (const False)
|
||||
|
||||
{-# INLINABLE mkTSValidator #-}
|
||||
mkTSValidator :: TokenSale -> Integer -> TSRedeemer -> ScriptContext -> Bool
|
||||
mkTSValidator = mkValidator . tsStateMachine
|
||||
|
||||
type TS = StateMachine Integer TSRedeemer
|
||||
|
||||
tsInst :: TokenSale -> Scripts.ScriptInstance TS
|
||||
tsInst ts = Scripts.validator @TS
|
||||
($$(PlutusTx.compile [|| mkTSValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode ts)
|
||||
$$(PlutusTx.compile [|| wrap ||])
|
||||
where
|
||||
wrap = Scripts.wrapValidator @Integer @TSRedeemer
|
||||
|
||||
tsValidator :: TokenSale -> Validator
|
||||
tsValidator = Scripts.validatorScript . tsInst
|
||||
|
||||
tsAddress :: TokenSale -> Ledger.Address
|
||||
tsAddress = scriptAddress . tsValidator
|
||||
|
||||
tsClient :: TokenSale -> StateMachineClient Integer TSRedeemer
|
||||
tsClient ts = mkStateMachineClient $ StateMachineInstance (tsStateMachine ts) (tsInst ts)
|
||||
|
||||
mapErrorC :: Contract w s C.CurrencyError a -> Contract w s Text a
|
||||
mapErrorC = mapError $ pack . show
|
||||
|
||||
mapErrorSM :: Contract w s SMContractError a -> Contract w s Text a
|
||||
mapErrorSM = mapError $ pack . show
|
||||
|
||||
nftName :: TokenName
|
||||
nftName = "NFT"
|
||||
|
||||
startTS :: HasBlockchainActions s => Maybe CurrencySymbol -> AssetClass -> Contract (Last TokenSale) s Text TokenSale
|
||||
startTS mcs token = do
|
||||
pkh <- pubKeyHash <$> Contract.ownPubKey
|
||||
cs <- case mcs of
|
||||
Nothing -> C.currencySymbol <$> mapErrorC (C.forgeContract pkh [(nftName, 1)])
|
||||
Just cs' -> return cs'
|
||||
let ts = TokenSale
|
||||
{ tsSeller = pkh
|
||||
, tsToken = token
|
||||
, tsNFT = AssetClass (cs, nftName)
|
||||
}
|
||||
client = tsClient ts
|
||||
void $ mapErrorSM $ runInitialise client 0 mempty
|
||||
tell $ Last $ Just ts
|
||||
logInfo $ "started token sale " ++ show ts
|
||||
return ts
|
||||
|
||||
setPrice :: HasBlockchainActions s => TokenSale -> Integer -> Contract w s Text ()
|
||||
setPrice ts p = void $ mapErrorSM $ runStep (tsClient ts) $ SetPrice p
|
||||
|
||||
addTokens :: HasBlockchainActions s => TokenSale -> Integer -> Contract w s Text ()
|
||||
addTokens ts n = void (mapErrorSM $ runStep (tsClient ts) $ AddTokens n)
|
||||
|
||||
buyTokens :: HasBlockchainActions s => TokenSale -> Integer -> Contract w s Text ()
|
||||
buyTokens ts n = void $ mapErrorSM $ runStep (tsClient ts) $ BuyTokens n
|
||||
|
||||
withdraw :: HasBlockchainActions s => TokenSale -> Integer -> Integer -> Contract w s Text ()
|
||||
withdraw ts n l = void $ mapErrorSM $ runStep (tsClient ts) $ Withdraw n l
|
||||
|
||||
type TSStartSchema = BlockchainActions
|
||||
.\/ Endpoint "start" (CurrencySymbol, TokenName)
|
||||
type TSStartSchema' = BlockchainActions
|
||||
.\/ Endpoint "start" (CurrencySymbol, CurrencySymbol, TokenName)
|
||||
type TSUseSchema = BlockchainActions
|
||||
.\/ Endpoint "set price" Integer
|
||||
.\/ Endpoint "add tokens" Integer
|
||||
.\/ Endpoint "buy tokens" Integer
|
||||
.\/ Endpoint "withdraw" (Integer, Integer)
|
||||
|
||||
startEndpoint :: Contract (Last TokenSale) TSStartSchema Text ()
|
||||
startEndpoint = startTS' >> startEndpoint
|
||||
where
|
||||
startTS' = handleError logError $ endpoint @"start" >>= void . startTS Nothing . AssetClass
|
||||
|
||||
startEndpoint' :: Contract (Last TokenSale) TSStartSchema' Text ()
|
||||
startEndpoint' = startTS' >> startEndpoint'
|
||||
where
|
||||
startTS' = handleError logError $ endpoint @"start" >>= \(cs1, cs2, tn) -> void $ startTS (Just cs1) $ AssetClass (cs2, tn)
|
||||
|
||||
useEndpoints :: TokenSale -> Contract () TSUseSchema Text ()
|
||||
useEndpoints ts = (setPrice' `select` addTokens' `select` buyTokens' `select` withdraw') >> useEndpoints ts
|
||||
where
|
||||
setPrice' = handleError logError $ endpoint @"set price" >>= setPrice ts
|
||||
addTokens' = handleError logError $ endpoint @"add tokens" >>= addTokens ts
|
||||
buyTokens' = handleError logError $ endpoint @"buy tokens" >>= buyTokens ts
|
||||
withdraw' = handleError logError $ endpoint @"withdraw" >>= uncurry (withdraw ts)
|
|
@ -3,7 +3,9 @@ module Main
|
|||
) where
|
||||
|
||||
import qualified Spec.Model
|
||||
import qualified Spec.ModelWithClose
|
||||
import qualified Spec.Trace
|
||||
import qualified Spec.TraceWithClose
|
||||
import Test.Tasty
|
||||
|
||||
main :: IO ()
|
||||
|
@ -12,5 +14,7 @@ main = defaultMain tests
|
|||
tests :: TestTree
|
||||
tests = testGroup "token sale"
|
||||
[ Spec.Trace.tests
|
||||
, Spec.TraceWithClose.tests
|
||||
, Spec.Model.tests
|
||||
, Spec.ModelWithClose.tests
|
||||
]
|
||||
|
|
222
code/week08/test/Spec/ModelWithClose.hs
Normal file
222
code/week08/test/Spec/ModelWithClose.hs
Normal file
|
@ -0,0 +1,222 @@
|
|||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE NumericUnderscores #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
|
||||
module Spec.ModelWithClose
|
||||
( tests
|
||||
, test
|
||||
, TSModel (..)
|
||||
) where
|
||||
|
||||
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 (..))
|
||||
import Data.Text (Text)
|
||||
import Plutus.Contract.Test
|
||||
import Plutus.Contract.Test.ContractModel
|
||||
import Plutus.Trace.Emulator
|
||||
import Ledger hiding (singleton)
|
||||
import Ledger.Ada as Ada
|
||||
import Ledger.Value
|
||||
import Test.QuickCheck
|
||||
import Test.Tasty
|
||||
import Test.Tasty.QuickCheck
|
||||
|
||||
import Week08.TokenSaleWithClose (TokenSale (..), TSStartSchema', TSUseSchema, startEndpoint', useEndpoints, nftName)
|
||||
|
||||
data TSState = TSState
|
||||
{ _tssPrice :: !Integer
|
||||
, _tssLovelace :: !Integer
|
||||
, _tssToken :: !Integer
|
||||
} deriving Show
|
||||
|
||||
makeLenses ''TSState
|
||||
|
||||
newtype TSModel = TSModel {_tsModel :: Map Wallet TSState}
|
||||
deriving Show
|
||||
|
||||
makeLenses ''TSModel
|
||||
|
||||
tests :: TestTree
|
||||
tests = testProperty "token sale model" prop_TS
|
||||
|
||||
instance ContractModel TSModel where
|
||||
|
||||
data Action TSModel =
|
||||
Start Wallet
|
||||
| SetPrice Wallet Wallet Integer
|
||||
| AddTokens Wallet Wallet Integer
|
||||
| Withdraw Wallet Wallet Integer Integer
|
||||
| BuyTokens Wallet Wallet Integer
|
||||
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
|
||||
|
||||
arbitraryAction _ = oneof $
|
||||
(Start <$> genWallet) :
|
||||
[ SetPrice <$> genWallet <*> genWallet <*> genNonNeg ] ++
|
||||
[ AddTokens <$> genWallet <*> genWallet <*> genNonNeg ] ++
|
||||
[ BuyTokens <$> genWallet <*> genWallet <*> genNonNeg ] ++
|
||||
[ Withdraw <$> genWallet <*> genWallet <*> genNonNeg <*> genNonNeg ]
|
||||
|
||||
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
|
||||
wait 1
|
||||
|
||||
nextState (AddTokens v w n) = do
|
||||
started <- hasStarted v -- has the token sale started?
|
||||
when (n > 0 && started) $ do
|
||||
bc <- askModelState $ view $ balanceChange w
|
||||
let token = tokens Map.! v
|
||||
when (tokenAmt + assetClassValueOf bc token >= n) $ do -- does the wallet have the tokens to give?
|
||||
withdraw w $ assetClassValue token n
|
||||
(tsModel . ix v . tssToken) $~ (+ n)
|
||||
wait 1
|
||||
|
||||
nextState (BuyTokens v w n) = do
|
||||
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)
|
||||
(tsModel . ix v . tssToken) $~ (+ (- n))
|
||||
_ -> 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 ()
|
||||
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
|
||||
|
||||
precondition s (Start w) = isNothing $ getTSState' s w
|
||||
precondition s (SetPrice v _ _) = isJust $ getTSState' s v
|
||||
precondition s (AddTokens v _ _) = isJust $ getTSState' s v
|
||||
precondition s (BuyTokens v _ _) = isJust $ getTSState' s v
|
||||
precondition s (Withdraw v _ _ _) = isJust $ getTSState' s v
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
tokenNames :: Map Wallet TokenName
|
||||
tokenNames = Map.fromList $ zip wallets ["A", "B"]
|
||||
|
||||
tokens :: Map Wallet AssetClass
|
||||
tokens = Map.fromList [(w, AssetClass (tokenCurrencies Map.! w, tokenNames Map.! w)) | w <- wallets]
|
||||
|
||||
nftAssets :: Map Wallet AssetClass
|
||||
nftAssets = Map.fromList [(w, AssetClass (nftCurrencies Map.! w, nftName)) | w <- wallets]
|
||||
|
||||
nfts :: Map Wallet Value
|
||||
nfts = Map.fromList [(w, assetClassValue (nftAssets Map.! w) 1) | w <- wallets]
|
||||
|
||||
tss :: Map Wallet TokenSale
|
||||
tss = Map.fromList
|
||||
[ (w, TokenSale { tsSeller = pubKeyHash $ walletPubKey w
|
||||
, tsToken = tokens Map.! w
|
||||
, tsNFT = nftAssets Map.! w
|
||||
})
|
||||
| w <- wallets
|
||||
]
|
||||
|
||||
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]
|
||||
|
||||
genWallet :: Gen Wallet
|
||||
genWallet = elements wallets
|
||||
|
||||
genNonNeg :: Gen Integer
|
||||
genNonNeg = getNonNegative <$> arbitrary
|
||||
|
||||
tokenAmt :: Integer
|
||||
tokenAmt = 1_000
|
||||
|
||||
prop_TS :: Actions TSModel -> Property
|
||||
prop_TS = withMaxSuccess 100 . propRunActionsWithOptions
|
||||
(defaultCheckOptions & emulatorConfig .~ EmulatorConfig (Left d))
|
||||
instanceSpec
|
||||
(const $ pure True)
|
||||
where
|
||||
d :: InitialDistribution
|
||||
d = Map.fromList $ [ ( w
|
||||
, lovelaceValueOf 1000_000_000 <>
|
||||
(nfts Map.! w) <>
|
||||
mconcat [assetClassValue t tokenAmt | t <- Map.elems tokens])
|
||||
| w <- wallets
|
||||
]
|
||||
|
||||
test :: IO ()
|
||||
test = quickCheck prop_TS
|
93
code/week08/test/Spec/TraceWithClose.hs
Normal file
93
code/week08/test/Spec/TraceWithClose.hs
Normal file
|
@ -0,0 +1,93 @@
|
|||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE NumericUnderscores #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
|
||||
module Spec.TraceWithClose
|
||||
( tests
|
||||
, runMyTrace
|
||||
) where
|
||||
|
||||
import Control.Lens
|
||||
import Control.Monad hiding (fmap)
|
||||
import Control.Monad.Freer.Extras as Extras
|
||||
import Data.Default (Default (..))
|
||||
import qualified Data.Map as Map
|
||||
import Data.Monoid (Last (..))
|
||||
import Ledger
|
||||
import Ledger.Value
|
||||
import Ledger.Ada as Ada
|
||||
import Plutus.Contract.Test
|
||||
import Plutus.Trace.Emulator as Emulator
|
||||
import PlutusTx.Prelude
|
||||
import Prelude (IO, String, Show (..))
|
||||
import Test.Tasty
|
||||
|
||||
import Week08.TokenSaleWithClose
|
||||
|
||||
tests :: TestTree
|
||||
tests = checkPredicateOptions
|
||||
(defaultCheckOptions & emulatorConfig .~ emCfg)
|
||||
"token sale trace"
|
||||
( walletFundsChange (Wallet 1) (Ada.lovelaceValueOf 10_000_000 <> assetClassValue token (-60))
|
||||
.&&. walletFundsChange (Wallet 2) (Ada.lovelaceValueOf (-20_000_000) <> assetClassValue token 20)
|
||||
.&&. walletFundsChange (Wallet 3) (Ada.lovelaceValueOf (- 5_000_000) <> assetClassValue token 5)
|
||||
)
|
||||
myTrace
|
||||
|
||||
runMyTrace :: IO ()
|
||||
runMyTrace = runEmulatorTraceIO' def emCfg myTrace
|
||||
|
||||
emCfg :: EmulatorConfig
|
||||
emCfg = EmulatorConfig $ Left $ Map.fromList [(Wallet w, v) | w <- [1 .. 3]]
|
||||
where
|
||||
v :: Value
|
||||
v = Ada.lovelaceValueOf 1000_000_000 <> assetClassValue token 1000
|
||||
|
||||
currency :: CurrencySymbol
|
||||
currency = "aa"
|
||||
|
||||
name :: TokenName
|
||||
name = "A"
|
||||
|
||||
token :: AssetClass
|
||||
token = AssetClass (currency, name)
|
||||
|
||||
myTrace :: EmulatorTrace ()
|
||||
myTrace = do
|
||||
h <- activateContractWallet (Wallet 1) startEndpoint
|
||||
callEndpoint @"start" h (currency, name)
|
||||
void $ Emulator.waitNSlots 5
|
||||
Last m <- observableState h
|
||||
case m of
|
||||
Nothing -> Extras.logError @String "error starting token sale"
|
||||
Just ts -> do
|
||||
Extras.logInfo $ "started token sale " ++ show ts
|
||||
|
||||
h1 <- activateContractWallet (Wallet 1) $ useEndpoints ts
|
||||
h2 <- activateContractWallet (Wallet 2) $ useEndpoints ts
|
||||
h3 <- activateContractWallet (Wallet 3) $ useEndpoints ts
|
||||
|
||||
callEndpoint @"set price" h1 1_000_000
|
||||
void $ Emulator.waitNSlots 5
|
||||
|
||||
callEndpoint @"add tokens" h1 100
|
||||
void $ Emulator.waitNSlots 5
|
||||
|
||||
callEndpoint @"buy tokens" h2 20
|
||||
void $ Emulator.waitNSlots 5
|
||||
|
||||
callEndpoint @"buy tokens" h3 5
|
||||
void $ Emulator.waitNSlots 5
|
||||
|
||||
callEndpoint @"withdraw" h1 (40, 10_000_000)
|
||||
void $ Emulator.waitNSlots 5
|
Loading…
Reference in a new issue