In the firstGame
function, we use the "logging" component of the contract to tell
the thread token. It's not obvious to me where this is needed: is there an internal check in the runStep
function that this token matches the one in the parameter, i.e. SecondParams
? I commented out the line where tt
is told
and that results in an endless loop, while I expected a failure.
1 Answer
Because firstGame is the contract that initialises the game, it needs to communicate somehow the generated threadToken to the external world (secondGame in this case). For this kind of communication Contract monad uses tell mechanism.
How does the info flow? You can see it in myTrace function in Week07.TestStateMachine
.
(1)
callEndpoint @"first" h1 fp
- Initialise the game, and firstGame contract internally tells the generated threadToken to anyone interested in it.(2)
tt <- getTT h1
- This function uses observableState function, which "get the observable state w of a contract instance". In our case w in firstGame Contract is (Last ThreadToken).
firstGame :: forall s. FirstParams -> Contract (Last ThreadToken) s Text ()
(3)
let sp = SecondParams {... spToken = tt ...}
- Build params for secondGame using threadToken generated by firstGame contract, tt.(4)
callEndpoint @"second" h2 sp
- secondGame plays.
Notice that communication is not direct Contract to Contract but through observableState function
myTrace :: GameChoice -> GameChoice -> EmulatorTrace ()
myTrace c1 c2 = do
Extras.logInfo $ "first move: " ++ show c1 ++ ", second move: " ++ show c2
let w1 = knownWallet 1
let w2 = knownWallet 2
h1 <- activateContractWallet w1 endpoints
h2 <- activateContractWallet w2 endpoints
let pkh1 = mockWalletPaymentPubKeyHash w1
pkh2 = mockWalletPaymentPubKeyHash w2
stake = 5_000_000
deadline1 = slotToEndPOSIXTime def 5
deadline2 = slotToEndPOSIXTime def 10
fp = FirstParams
{ fpSecond = pkh2
, fpStake = stake
, fpPlayDeadline = deadline1
, fpRevealDeadline = deadline2
, fpNonce = "SECRETNONCE"
, fpChoice = c1
}
callEndpoint @"first" h1 fp -- (1)
tt <- getTT -- (2)
let sp = SecondParams -- (3)
{ spFirst = pkh1
, spStake = stake
, spPlayDeadline = deadline1
, spRevealDeadline = deadline2
, spChoice = c2
, spToken = tt
}
void $ Emulator.waitNSlots 3 -- (4)
callEndpoint @"second" h2 sp
void $ Emulator.waitNSlots 10
where
getTT :: ContractHandle (Last ThreadToken) GameSchema Text -> EmulatorTrace ThreadToken
getTT h = do
void $ Emulator.waitNSlots 1
Last m <- observableState -- (2)
case m of
Nothing -> getTT h
Just tt -> Extras.logInfo ("read thread token " ++ show tt) >> return tt
-
Ouch, my bad, I didn't look at the test. The getTT method also explains the loop when I comment out the tell. Commented Mar 4, 2022 at 12:00