Creator: Kappos Angelos
Credits to: Sapiopool Cardano Community
State machines can be very useful for writing shorter and more concise contracts, both on-chain and off-chain. There is higher level support for state machines in the Plutus libraries that builds on top of the lower level mechanisms. Many common function with the appropriate definition, which are used for State Machine, can be found here State Machine Plutus Contracts
Implementation (without using State Machines) - Week07.EvenOdd
-
- UTxO with token used to track state of game
-
- Play: where the second player moves
- Reveal: is for the case where the fisrts player has won - how they will reveal their nonce which is the ByteString
- ClaimFirst: when 1st player claims back the stake when the 2nd doesn't move by the gPlayDeadline
- ClaimSecond: when the 1st player doesn't reveal by the gRevealDeadline
-
- gameDatum behaves similarly with oracleValue
- oracleValue helper function -->
- takes an output transaction and a function which looks up the datum, and then
- returns an Integer.
- oracleValue helper function -->
- gameDatum behaves similarly with oracleValue
-
-
- game
- bsZero' bsOne': Used as somewhat of nuisance. USe them as string literals to get ByteStrings (represent 0 adn 1 respectively)
- dat: Datum
- red: Redeemer
- ctx: Context
-
- info
- ownInput : should never fail - helps to validate the UTxO
- ownOutput
- outputDatum makes use of the GameDatum type
- checkNonce: is for the case where the first player as won and wants to prove it by revealing their nonce.
- to check the hash of nonce weth GameChoicem we use a function to convert GameChoice to a ByteString
- cFirst and cSecond
- Difference of them, one uis a GameChoice and second is ByteString
- nftToFirst: gme is over. NFT return back to 1st player
- Conditions that cover all the cases that will be handled without state machines
- pattern match on different states
- 2nd player is moving (has not moved yet)
- 1st player has won, reveals his nonce in order to claim
- 2nd player has not moved before
gPlayDeadline
,- so 1st can claim back money
- Both have moved, 1st has lost or not revealed in time, so 2nd can claim stake
- rest cases will fail
- pattern match on different states
- pattern match on different states
- 2nd player is moving (has not moved yet)
- 1st player has won, reveals his nonce in order to claim
- 2nd player has not moved before
gPlayDeadline
, so 1st can claim back money - Both have moved, 1st has lost, so 2nd can claim stake
-
-
- findGameOutput: Contract: used to find the UTxO with the NFT (returns Maybe || anything)
-
- Params for 1st player
- Contract - firstGame
-
get the public key hash
-
populate the fields of the game
-
set value v where stake + NFT both go to the UTxO
-
calculate the hash that we need to send as our disguised move -- c
-
Submit transaction -- ledgerTx
-
wait deadline slot --
waitUntilTimeHasPassed $ fpPlayDeadline fp
-
-
- 2nd player hasn't moved, 1st player reclaimed stake
- 2nd player moved, and lost
- 2nd player played and won
-
-
-
- NO NEED of the second player’s public key hash
- NO NEED of nonce required
- Check conditions of contract
- try to find the UTXO that contains the NFT (case m of:)
-
- log message no running game found.
-
- assign NFT to Token
- calculate the value that we must put in the new output -- check CONSTRAINTS
- consume existing UTXO and
- create a new one at the same address
- keep NFT in there
- submit transaction
- confirmation of the transaction
- log message
- wait deadline
-
-
- 1st player revealed and won.
-
- Must spend the UTXO that we have found after the deadline has passed
- Must back hand the NFT to the first player -- check constraints
- submit transaction
- confirmation of the transaction
- log message --> 2nd player won.
-
The first scenario is that both play zero, so the first wallet should win.
The first wallet creates the initial UTxO with its stake, and logs a message that it made the move.
While the first wallet is waiting, the second wallet kicks in and finds the UTxO, sees that it can make a move, and does so.
The first player realizes that they have won, and so must reveal. And we see in the final balances that Wallet 1 does indeed have the NFT back and it also has almost 5 ada more than it started with. The difference is, of course, due to transaction fees. And the second wallet has a little more than 5 ada less.
##State Machine
- Definition of State Machine
A state machine is a behavior model. It consists of a finite number of states and is therefore also called finite-state machine (FSM). Based on the current state and a given input the machine performs state transitions and produces outputs. Some states can be final states, from which there can be no further transitions. If we look again at how our games works, then we can consider it to be a state machine. From the initial state, there are two possible transitions. One where Bob plays, and the other where Bob does not player and Alice can reclaim.
In the diagram, all the nodes correspond to states, and all the arrows correspond to transitions.
In the blockchain,
- the state machine will be represented by a UTxO sitting at the state machine address.
-
- using a redeemer that characterizes the transition, and then
- the datum now reflects the new state.
By using State Machine approach, code will be much shorter. To write your contract as a state machine you need Two types
-
state and input for the state and
-
inputs of the machine .
- s = state, Datum
- i = input, Redeemer
-
- Add a Final state to GameDatum
- Now: transition functions derived from former validator function
- No more check for token required, already checked within StateMachine
- Uses the mustBeSignedBy, ... contrains already used in off-chain code
- Check for nonce can not be expressed as a constraint (second case):
- Added to smCheck function, function check
- Defining the StateMachine
gameStateMachine :: Game -> ByteString -> ByteString -> StateMachine GameDatum GameRedeemer
- Requires a function for final states, final
gameStateMachine game bsZero' bsOne' = StateMachine
{ smTransition = transition game
, smFinal = final
, smCheck = check bsZero' bsOne'
, smThreadToken = Just $ gToken game
}
- Create validator from it with mkValidator $ gameStateMachine ...
mkGameValidator :: Game -> ByteString -> ByteString -> GameDatum -> GameRedeemer -> ScriptContext -> Bool
mkGameValidator game bsZero' bsOne' = mkValidator $ gameStateMachine game bsZero' bsOne'
-
Create a StateMachineClient
-
- scInstance :: StateMachineInstance s i : The instance of the state machine, defining the machine's transitions, its final states and its check function.
-
- scChooser :: [OnChainState s i] -> Either SMContractError (OnChainState s i): A function that chooses the relevant on-chain state, given a list of all potential on-chain states found at the contract address. Is the mechanism , from the off-chain code, for finding the UTxO that represents our state machine. In general there will be a list of UTxOs at the address of the state machine, and scChooser is a function that specifies which to pick.
-
- runInitialise: Start state machine with (with client, datum and value) && creates a UTxO at the state machine address.
- takes the client as its first argument and then it needs the initial datum and the initial value for the UTxO sitting at that address.
- put the NFT there as well.
- Wait until play the deadline.
-
Get the current UTxO by using getOnChainState: It returns (a Just OnchainState)
-
- returns OnChainState Similar to what utxoMap returns, but typed
- Also provides Datum, i.e. no manual searching and checking
-
- Nothing
- Check the TypedScriptTxOut parameter, which we assign to o, which is a parameter to lookup the datum tyTxOutData
-
- In state pattern match (Check what 1st && 2nd player has done):
- Use runStep to move state machine forward, just Redeemer required
- Takes StateMachineClient, input (Redeemer)
- Submits transaction that will transition the state machine.
- No lookups, less helper functions, ...
- Reduces code heavily
- No duplicated logic anymore
-
- They haven't moved --> 1st player reclaims stake
- 1st player revealed and won
- All other cases 2nd player wins
- Use runStep to move state machine forward, just Redeemer required
-
- Check additional cases:
- Check if game hasn't played yet
- Case of NO STATE --> 1st player won.
- Case of STATE --> 2nd player won if there is a state.
- Check if game hasn't played yet
- Check additional cases:
-
- On-chain checks if transaction is valid
- Off-chain constructs transaction
- So different purposes
- But by defining via constraints, used for both
- In State Machine approach:
- No need to replicate logic anymore.
- By using State Machine, we define logic that can be used to On chain and Off Chain.
Before run the test import the TestStateMachine.hs module
Prelude Week07.StateMachine Week07.EvenOdd> :l src/Week07/TestStateMachine.hs
Prelude Week07.StateMachine Week07.TestStateMachine> test
Linkedin : Kappos Angelos
Twitter accounts:
Who is SapioPool Cardano Community : Sapiopool Website
Participate/collaborate in Sapiopool Greek Community Discord channel here :
Youtube: