This is a writeup about the Plutus Pioneer Program lectures. I use it to be able to quickly reread or review important or more complex topics. As ymmv about what is important or complex you might miss some things. If you want you can add them here as well by providing pull requests. 0. YouTube video shortcuts
-
Sometimes it's quite convenient, to speed up the playback to safe time because one can understand it at a faster pace.
-
At the other hand - sometimes one would like to type while Lars is speaking. Then one might want to slow down the video to keep up the typing pace with Lars' speaking pace.
-
With QWERTY keyboards it is no problem to change the playback speed.
-
With QWERTZ keyboards it doesn't work unfortunately. In that case you need a specific plugin. E.g. the one linked below. But there are others as well.
- Shortcut overview:
?
- 10 sec forward / backward =
f
/j
- 5 sec forward / backward = cursor left / cursor right
- QWERTY: faster / slower =
>
/<
- QWERTZ: faster / slower doesn't work out of the box
- Install YouTube Playback Speed Control for Chrome
- Works not only for YouTube
- faster / slower =
+
/-
(or setup custom key mappings
- Shortcut overview:
-
EUTxO Model
-
Low Level, Untyped Validation Scripts
- UTxO model
- A Tx can consume a UTxO as an input if it includes the signature of that public key address
- Extended UTxO
- Allows to have not only public key addresses but also script addresses that can run arbitrary logic
- When the Tx is validated, the node will run the script and check if it's valid or not.
- Also allows to have
- Redeemers on the input side and
- Datum on the output side of the TX
- Plutus script data type
- Redeemers, Datum and Context all have the same generic low level
BuildinData
data type. - It can be converted to higher level data types but it comes at a cost and some projects have choosen not to do that.
- The
Data
type is similar to a JSON format and can be used to convert toBuildinData
ByteString
B
constructor and overloaded Strings with:set -XOverloadedStrings
- Literal Strings are just lists of characters
- With this extension one can use literal Strings for other Stringlike types as well and one of those types is the
ByteString
type. - E.g.
B "Haskell"
returns"B Haskell" :: Data
- Redeemers, Datum and Context all have the same generic low level
- Writing the validator -
mkValidator
- Return type is unit. In case it's invalid an error is thrown otherwise nothing happens.
- A script address is more or less the hash of the validator
- Writing the validator -
validator
- Works like a makro.
- It converts
mkValidator
expression to an abstract syntax tree using the oxford brackets||
- Compiles it to the Plutus Core syntax (-tree) using
PlutusTx.compile
. $$
takes the Plutus Core syntax tree and inserts (splices) it into the source code at that point as a Plutus Core expression.- The
mkValidatorScript
function uses that as a parameter and turns it into an actual validator. {-# INLINABLE mkValidator #-}
pragma allows to reuse helper or library function also in the off-chain code by seperating the definitions and not inline everything.- Everything that belongs to the on-chain code needs to be inlineable.
- Off-chain part
mustPayToOtherScript
is a transaction contstraintvalHash
is the hash of the "other script"- next param: empty datum
- last param the actual ada value
grab
- If you want to consume UTxO sitting at a script address then the spending transaction needs to provide the actual validator code whereas the producing transaction only has to provide the hash.
- [```
endpoints :: Contract () GiftSchema Text ()
endpoints = awaitPromise (give'
select
grab') >> endpoints where give' = endpoint @"give" give grab' = endpoint @"grab" $ const grab1. `endpoints` waits for user interaction, calles the endpoint according to the user input and waits again
- Test in the playground
- Burn
- Prelude name clashes
- UTxO model
-
- Homework 1
- Homework 2
-
- Doku: haddock
- Purposes
Spending
the script outputMinting
a new native tokenRewarding
for staking ADACertifying
e.g. for delegation certificates
- TxInfo
- Describes the spending transaction
txInfoInputs
,txInfoOutPuts
txInfoFee
- Transaction feetxInfoForge
- forged (positive) or burned (negative) native tokenstxInfoDCert
- certificates e.g. for stakingtInfoWdrl
- staking withdrawals from rewardstxInfoValidRange
- the time range this transaction will be validtxInfoSignatories
- pub keys that have signed the transactiontxInfoData
- Dict from DatumHash to Datum to include the full Datum. Spending tx'es need to include the datum of the script output whereas producing transactions that send money to a script address and have an output at that script address only have to include the hash and can optionally include the full datum.txInfoId
- the id of this transaction
-
Handling time - txInfoValidRange
- Validation fails already on wallet level
- How can the validation of the time range be deterministic if it can succeed in the wallet but not potentially much later on-chain?
- We can always run the script under exactly the same conditions in the wallet already.
- ==>
txInfoValidRange
of typePOSIXTimeRange
- The transaction is valid this the specified time range.
- This time range is checked on the node in the "pre-checks" before the validation script runs.
- The node checks the current time and checks if the time range is within
txInfoValidRange
. If it fails, then validation fails immediately without running the validator scripts. - The validator script can assume that the current time does fall into this interval as the "pre-checks" would have prevented running the script otherwise.
- This makes validation deterministic.
- I guess that's the case as the validator script doesn't check against the current time anymore but always against the validity interval.
- It's a static piece of data attached to the transaction so the result of the validation does not depend on when it is run. Weather it's run in the wallet before submission or in one of the nodes when validating the transaction.
- ==> The validation script is deterministic because the time rage doesn't affect it as it can be assumed to always be valid when the script is run?
- The trick is, to do the time check before the validation is done and then during the execution of the validator scripts we don't have to worry about it anymore and can just assume that the current time falls into this interval. Because if it wouldn't then the validation wouldn't even run in the first place because then the validation of the transaction would have failed before.
- While Plutus uses Posix time for the valid range, Ouroboros, the underlaying consensus protocol uses slots. This is why it is translated to slots in the final transaction.
- It is a type synonym for
Interval POSIXTime
- http://localhost:8002/haddock/plutus-ledger-api/html/Plutus-V1-Ledger-Interval.html#t:Interval
Interval ivFrom :: LowerBound a ivTo :: UpperBound a
LowerBound
:- http://localhost:8002/haddock/plutus-ledger-api/html/Plutus-V1-Ledger-Interval.html#t:LowerBound
LowerBound (Extended a) Closure
- See members further below
POSIXTime
is an instance ofOrd
- Members of
LowerBound
interval
is a smart constructor ofInterval
that gives us an interval with a lower and upper bound included.from a
= froma
and lasts until eternetyto a
= from eternety untila
- Playing with the intervall api
- http://localhost:8002/haddock/plutus-ledger-api/html/Plutus-V1-Ledger-Interval.html#t:Interval
- ==>
- When the tx gets submitted to the blockchain and validated by a node, then - before any validation scripts are run some general checks are done. E.g.
- all the inputs are present
- the balences add up,
- the fees are included
- also the time range is checked
- If the current time does not fall into that time range then validation fails immediately without ever running the validator scripts.
- By default all transaction use the infinity time range that would always be valid.
- The consensus protocol Ouroboros uses slots instead of posix time ==> Slot is the native measure of time in Cardano. But Plutus uses realtime.
- To convert between realtime and slot numbers.
- If the slot length would change the conversion could be wrong. To be on the safe side - don't use a longer time horizon than 36 hours in the future (or infinity). Because if the protocol parameters would change one would know it at least 36 hours in advance.
- POSIXTimeRange
-
- The deadline will be within the valid time interval when the validation script runs. But that time might still be before the deadline:
- The whole interval must be after the deadline.
- The interval from the deadline until infinity must contain the transaction interval
- This way the current time will be after the deadline.
- The interval from the deadline until infinity must contain the transaction interval
grab
- query all UTxOs where the wallet is the beneficiary and where the deadline has been reached.
- Create a transaction that collect all of them in the same transaction
- In reality the transaction can only be of a limited size so not many UTxOs could be used at the same time in reality.
Constraints.mustValidateIn (from now)
- Simulation
- Wallet 1 gives ADA to script for wallet 2 at slot 10
- Wallet 1 gives ADA to script for wallet 2 at slot 20
- Wallet 1 gives ADA to script for wallet 3 at slot 10
- Wait for 11 / 21 slots
- Wallet 2 grabs
- Wallet 3 grabs
- Wait for 5 slots
- Retrieve the data to input in the repl
- Get the
getPubKeyHash
import Wallet.Emulator
:i Wallet
wraps a walletId- For real wallets but also for mock wallets that are used in the playground
:i WalletId
knownWallet 2
returns aWallet
instance of wallet 2mockWalletPaymentPubKeyHash
returns the PaymentPubKeyHash of a mock wallet and takes a wallet as a parameter.mockWalletPaymentPubKeyHash $ knownWallet 2
- Convert slots to PosixTime
import Ledger.Time
import Ledger.TimeSlot
import Data.Default
slotToBeginPOSIXTime def 10
- Get the
- The deadline will be within the valid time interval when the validation script runs. But that time might still be before the deadline:
-
- The validator script will get a parameter compiled into it
- For more about the
.
operator see function composition below - Can only be used for datalike types but cannot contain functions
-
- If a transaction fails it normally fails already when submitting it to the blockchain. But one could insist to send it anyways and if it actually fails someone needs to handle the transaction costs. For that case a colleteral is needed.
- Using Plutus in the Cardano CLI
- It uses the Cardano API - the Haskell lib that the Cardano CLI uses under the hood to
- talk to the node
- submit transactions
- query the blockchain
- Has its own data type that is very similar to the Plutus data types
dataToScriptData :: Data -> ScriptData
- converts from Plutus
Data
toScriptData
of the Cardano API
- converts from Plutus
writeJSON()
- ...
- Generate script address via the CLI
- It uses the Cardano API - the Haskell lib that the Cardano CLI uses under the hood to
-
- First part
- Second part
-
- Executing a Monad like
foo :: IO Int
just builds the same recipe every time and still doesn't have any sideeffects.IO
is a type constructor that takes one argument likeMaybe
or Generics likeOptional<Integer>
in Java.
- Can only be execute in the main entry point similar to the
main
method in Java and in the repl. - Combining IO actions
toUpper :: Char -> Char
|map :: (a -> b) -> [a] -> [b]
map toUpper "Haskell"
passes the two parameters tomap
, executes thetoUpper
function for every char element of the"Haskell"
array and returns the result.- Functor
fmap :: Functor f => (a -> b) -> f a -> f b
:t fmap (map toUpper) getLine
:fmap (map toUpper) getLine :: IO [Char]
fmap (map toUpper) getLine
providing "Haskell" returns "HASKELL"- The difference to
map
is, that the second parameter can be a function that provides a parameter. fmap
can map existing IO actions and turn them into other IO actions.
- Sequence operator >>
:t putStrLn
:putStrLn :: String -> IO ()
- Expects a String and outputs it.
- Is an IO action with a unit result.
>>
chains two IO actions together ignoring the result of the first- Executes both actions in sequence
- Bind operator >>=
- See also: http://learnyouahaskell.com/a-fistful-of-monads
:t (>>=)
:(>>=) :: Monad m => m a -> (a -> m b) -> m b
m a
IO action that returns ana
(a -> m b)
a function that - given ana
- returns an IO action that returnsb
-> m b
will be combined to an IO action that produces ab
- Example
getLine >>= putStrLn
- without infix:
(>>=) getLine putStrLn
:t getLine
:getLine :: IO String
:t putStrLn
:putStrLn :: String -> IO ()
- Returns an IO action that produces a String
IO String (String -> IO()) -> IO ()
- without infix:
return
return :: Monad m => a -> m a
- Will not perform any sideeffects and immediately return the given
a
- Wraps a pure value into a typed function.
- Example
main :: IO () main = bar bar :: IO () bar = getLine >>= \s -> getLine >>= \t -> putStrLn (s ++ '-' ++ t)
- asdf
Maybe
Either
Writer
Tell
- Monad summary
- The possibility to bind two computations together
- The possibility to construct a computation from a pure value without making use of any of the potential side-effects.
Applicative
pure
<*>
called "ap" operator- Superclass is
Functor
- Has
fmap
function
- Has
- Use bind for all kinds of Monads
threeInts
forMaybe
,Either
, (Writer
),...
threeInts :: Monad m => m Int -> m Int -> m Int -> m Int threeInts mx my mz = mx >>= \k -> my >>= \l -> mz >>= \m -> let s = k + l + m in return s foo'' :: String -> String -> String -> Maybe Int foo'' x y z = threeInts (readMaybe x) (readMaybe y) (readMaybe z) foo'' :: String -> String -> String -> Either String Int foo'' x y z = threeInts (readEither x) (readEither y) (readEither z)
- Monad summary based on
threeInts
- Computation with a super power (side-effect, fail with an error msg, log messages,...)
- Kind of an "Overloaded semicolon" ;-)
do
notationthreeInts' :: Monad m => m Int -> m Int -> m Int -> m Int threeInts' mx my mz = do k <- mx l <- my m <- mz let s = k + l + m return s
- Executing a Monad like
-
Contract w s e a
w
- Allows the contract to write log messages of type
w
- Is an event to the outside world.
- Allows the contract to write log messages of type
s
Specifies the endpoints that are available to the contracte
Type of error messages. E.g.Text
a
return type
- Example contract
String
/Text
handlingOverloadedStrings
,TypeApplications
to disambiguate literalString
s@String "hello from the contract"
()
= Unit = a valueVoid
has no valueContract.handleError()
unpack
convertsData.Text
toString
- The schema parameter
- Type synonym
type MySchema = Endpoint "foo" Int .\/ Endpoint "bar" String
- That's an advanced Haskell feature where even
"foo"
is a type.- Made possible by the
DataKinds
extension
- Made possible by the
- It's an endpoint "foo" of type
Int
- That's an advanced Haskell feature where even
- Type synonym
endpoint
callEndpoint
w
observable state parameterMonoid
,mempty
,mappend
tell
mappend
s to the existing state- One can communicate to the contract by invoking the endpoint and it can communicate back to the emulator trace or the browser / user interface of the dapp by calling
tell
-
Native Tokens
- What means "value" in Cardano
- Each UTxO has an address and a
Value
.- (... and
Datum
andRedeemer
)
- (... and
- Each UTxO has an address and a
- "Values"
Value
constructorgetValue :: Map CurrencySymbol (Map TokenName Integer)
- A map of
CurrencySymbol
to a map ofTokenName
toInteger
CurrencySymbol
constructorunCurrencySymbol :: BuiltinByteString
- NewType wrapper around a
BuiltinByteString
- NewType wrapper around a
- It is actually the hash of a script - the minting policy
- It is needed for transactions who want to mint / create or burn native tokens
TokenName
constructorunTokenName :: BuiltinByteString
- NewType wrapper around a
BuiltinByteString
- A map of
- Means: How many units of each asset class are contained in the UTxO
AssetClass
is the combination ofCurrencySymbol
andTokenName
- ADA is an asset class and custom native tokes are other asset classes
unAssetClass :: (CurrencySymbol, TokenName)
- Equivalent to
Map CurrencySymbol (Map TokenName Integer)
- Equivalent to
- Playing in the repl
-
import Plutus.V1.Ledger.Value import Plutus.V1.Ledger.Ada :set -XOverloadedStrings -- to enter ByteStrings as literal strings
CurrencySymbol
andTokenName
implement theisString
class. This means we can enter both of them as literal Strings as well. adaSymbol
returns an empty ByteString as theCurrencySymbol
of ADAadaToken
returns an emtpy ByteString as theTokenName
of ADAlovelaceValueOf
given an Integer returns aValue
of ADAlovelaceValueOf 1234
Value (Map [(,Map [("",1324)])])
- a map of an empty ByteString to a map of an empty ByteString to 1234
- [Combine / add up with a
mappend
of Monoid (https://youtu.be/4iNTgjovMRg?t=301) <>
themappend
operator from the superclass ofMonoid
lovelaceValueOf 10 <> lovelaceValueOf 20
Value (Map [(,Map [("",30)])])
- Create values containing native tokens
:t singleton
:singleton :: CurrencySymbol -> TokenName -> Integer -> Value
- Specifies the amount of an asset class that is there.
singleton "a8ff" "ABG" 7
:Value (Map [(a8ff,Map [("ABG",7)])])
singleton "a8ff" "ABG" 7 <> lovelaceValueOf 42 <> singleton "a8ff" "XYZ" 100
-
Value (Map [ (,Map [("",42)]) (a8ff, Map [ ("ABG",7), ("XYZ",100) ]) ])
-
valueOf
given aValue
extract the amount of a given asset class:t valueOf
:valueOf :: Value -> CurrencySymbol -> TokenName -> Integer
valueOf v "a8ff" "XYZ"
: 100valueOf v "a8ff" "ABG"
: 7valueOf v "a8ff" ""
: 42
:t flattenValue
:flattenValue :: Value -> [(CurrencySymbol, TokenName, Integer)]
- Flattens the map into a list of triples
flattenValue v
:[(,"",42),(a8ff,"XYZ",100),(a8ff,"ABG",7)]
- Minting Policy
ScriptContext
HaddockScriptContext
==>scriptContextPurpose
hadSpending
until now.ScriptContext
==>scriptContextTxInfo
has all the context info about the tx that is being validated- Minting / monetary policies is/are triggered when
TxInfo
==>txInfoMint
contains a non-zeroValue
- For each
CurrencySymbol
the corresponding script is executed- The minting policy scripts have only two inputs. The
Redeemer
and theScriptContext
(no Datum). - The
Redeemer
is provided by the transaction for all the scripts inputs ScriptContext
==>scriptContextPurpose
==>Minting
will be theCurrencySymbol
whose minting/burning currently being checked.- All the policies need to pass. If one of them fails the whole transaction will fail.
- The minting policy scripts have only two inputs. The
- Example: Free minting (Free.hs)
- Example: Signed minting (Signed.hs)
- See also parameterized validators.
- Function composition with the dot (
.
) infix operator- http://learnyouahaskell.com/higher-order-functions
- Composing two functions produces a new function
- Uses currying?
- Definition:
-
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (g x)
-
let x = negate . (* 3) x 2 ==> -6
- Returns a function (
x
) that can have parameters (2
in the example) x
first applies the parameter to the second function ((g x)
) and then runs the first function on the result (f (g x)
)- One of the uses for function composition is making functions on the fly to pass to other functions. Sure, can use lambdas for that, but many times, function composition is clearer and more concise.
- Equivalent:
(\x -> negate ((* 3) x))
- Examples:
-
let x = negate . abs x (-2) ==> -2 ```
map (negate . abs) [5,-3,-6,7,-3,2,-19,24]
[-5,-3,-6,-7,-3,-2,-19,-24]
- It's equivalent to
map (\x -> negate (abs x))
-
-
scriptCurrencySymbol policy
callsscriptCurrencySymbol
withpolicy
as a parameter.scriptCurrencySymbol . policy
returns a function that can have parameters.- When the function is called, then first
policy
will be called with the parameters and thenscriptCurrencySymbol
will be called on the result.
- When the function is called, then first
:t txSignedBy
:txSignedBy :: TxInfo -> PubKeyHash -> Bool
:t scriptContextTxInfo
:scriptContextTxInfo :: ScriptContext -> TxInfo
mkPolicy pkh () ctx = txSignedBy (scriptContextTxInfo ctx) $ unPaymentPubKeyHash pkh
- Checks wether the tx that is minting the native token has been signed by the given PubKeyHash ()
- ==>! The minting policy script is now parameterized with the
PaymentPubKeyHash
.- This hash is different for every wallet. ==> Thus it produces a different policy script hash. ==> Thus is results in a different
CurrencySymbol
. - In the emulator trace example the token name
ABC
is the same but the generatedCurrencySymbol
is different. Resulting in a different asset class. - ==> Even though the token name is the same, the asset class will still depend on the wallet minting it.
- This hash is different for every wallet. ==> Thus it produces a different policy script hash. ==> Thus is results in a different
- NFTs (NFT.hs) / max token amount
- Solution 1 (Mary era): create a monetary policy script with a
PaymentPubKeyHash
that doesn't allow to mint more of it after a certain deadline.- This way: Before the deadline no one else can mint more as it's only allowed to be done with the
PaymentPubKeyHash
of the wallet. - After the deadline it's not allowed to add more as the policy script will deny it.
- This way: Before the deadline no one else can mint more as it's only allowed to be done with the
- Solution 2 (Plutus):
- The UTxO is unique as it's the nth index output of a transaction which itself is unique and as an own id.
- A transaction is only unique because of fees.
- A transaction that doesn't have inputs and only outputs without value might not be unique.
- But as one always need to pay fees for transactions that is not possible. One always need to have an input to be consumed to cover the transaction fees.
- Such an input always needs to come from an output of another transaction.
- The inputs are used to create the transaction hash. As they are unique the transaction hash is unique.
- This is recursive.
- Check in the minting policy that the transaction consumes a specific UTxO. This way the minting policy can only be called once.
- Additionally check that the
txInfoMint
has a value with an amount of only 1. - Parameterized policy function with two parameters
- Offchain contract that mints the NFT
- The UTxO is unique as it's the nth index output of a transaction which itself is unique and as an own id.
- Solution 1 (Mary era): create a monetary policy script with a
-
- What means "value" in Cardano
-
Lecture 6: End to end PAB and minting native tokens
- Mint via CLI
- Unwraps the minting policy to get the script in
src/Week06/Utils.hs
writeMintingPolicy()
- The policy looks like that
mkTokenPolicy :: TxOutRef -> TokenName -> Integer -> () -> ScriptContext -> Bool
- It needs the UTxO, the token name and the amount to be compiled into the script
- Will be used to write the policy to a file by this app
app/token-policy.hs
- A TxOutRef consists of the transaction id of the transaction which created it and the index of the output of that transaction.
- The order of outputs of a transaction matters. The order of input doesn't.
- e.g. the TxOutRef here is
0c60c01eef31f00bd7f8dffbdc59e141d5db7a523105734cee37a700d5354e56#0
TxHash TxIx ------------------------------------------------------------------------- 0c60c01eef31f00bd7f8dffbdc59e141d5db7a523105734cee37a700d5354e56 0
- Whenever we see that some type has an
isString
instance then we could use a String literal in combination with theOverloadedStrings
extension to construct a value of this type. - Or programmatically we can use the
fromString()
function to turn a String into this type.
AssetName
is the Cardano API type that corresponds to the Plutus token name type.- Example result:
- https://testnet.adatools.io/tokens/asset1yza3cuv5thlfr9as06avqgggwv4sch9e0lheq3
Asset ID
= policy id + hash of the token name- policy id is equivalent to the currency symbol
- https://explorer.cardano-testnet.iohkdev.io/de/transaction?id=702b250c69a924f9baffab93c44c0c128013fcdd7a11b2679561356621370b75
- PAB Deployment Scenarios
- See
- PAB
- Needs the offchain code written in the contract monad that the PAB can execute.
- Wallet Backend
- E.g. used by the Daedalus wallet
- Can administer multiple wallets
- hd wallets
- infinity of potential addresses
- The public keys get derived with each (step?) so one usually never uses the same one again
- hd wallets
- Chain index
- The SQL database that stores the blockchain data of the node
- Something like a lightweight version of dbsync
- Allows to lookup the datum belonging to a given datum hash
- Needed by the PAB
- Deployment scenarios
- WBE / Hosted by the dapp provider (currently suppored)
- The PAB has full control over one or more of the wallets.
- Also over their spending passwords.
- It can sign transactions with the with the wallets.
- The user could host that by himself but then it would rather be a bundle of apps that would run on the users machine.
- Consume quite a lot of resources (memory, cpu, harddisk space,...)
- Not the decentralized way it should be.
- Browser wallet (currently not supported)
- Serverside
- PAB
- Creates transaction without being able to sign them because it doesn't have access to a wallet.
- It exposes this unsigned transaction
- The browser picks it up and signs it using a browser wallet
- Currently only exposes unbalanced transactions
- The browser would have to balance it and that is very tricky
- Chainindex
- Node
- Not the wallet
- PAB
- Serverside
- WBE / Hosted by the dapp provider (currently suppored)
- The contracts
- Utility functions for the off-chain part
- The Plutus address
- http://localhost:8002/haddock/plutus-ledger/html/Ledger.html#t:Address
-
data Address Constructors Address addressCredential :: Credential addressStakingCredential :: Maybe StakingCredential
-
data Credential Constructors PubKeyCredential PubKeyHash ScriptCredential ValidatorHash
- The Plutus address
Token.OffChain
formintToken :: TokenParams -> Contract w s Text CurrencySymbol
TokenParams
typetpAddress
- the address of a wallet that receives the tokens
- The change-address is picked by the wallet
- The
mintToken
function- The returned
CurrencySymbol
is not needed. It's a leftover from trying to implement the Oracle example. o <- fromJust <$> Contract.txOutFromRef oref
<$>
isfmap
as an infix operator
- For the case where
addressStakingCredential
is present there is a separateConstraints
function to be used:Constraints.mustPayToPubKeyAddress x y val
Constraints.mustSpendPubKeyOutput oref
to make sure that there is exactly one minting tx for this currency symbol.- This
oref
will be "compiled" into the minting policy. This way the minting policy will be successfully validated only with thisoref
. - As this
oref
will be spent with that transaction the minting policy can only be successfully run once.
- This
- Submitting the tx with
adjustAndSubmitWith
unbalanced <- adjustUnbalancedTx <$> mkTxConstraints lookups constraints
- Adds the min-ADA to all the outputs as the minting transactions wouldn't have any ADA otherwise but need them.
- The returned
Trace
emulator traceMonitor
contract- Why an additional contract?
- The idea of the contract
- Queries the blockchain and reports a value sitting on that address constantly
- Minting with the PAB
-
data TokenContracts = Mint Token.TokenParams | Monitor Address deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON, ToSchema)
- Defines which contracts the PAB will expose
- Two constructors for the two contracts
Mint Token.TokenParams
- Constructor name:
Mint
- Parameter:
Token.TokenParams
- Constructor name:
HasDefinitions
getDefinitions = [Mint exampleTP, Monitor exampleAddr]
- Example values for the Swagger UI
getContract
- Tells us which contract to run given the value of this type
Main
app- Start the wallet backend, the chain index and create the wallet
- See: https://github.com/input-output-hk/plutus-apps/tree/main/plutus-pab/test-node
- Start the wallet backend server
./start-testnet-wallet.sh
- Create the wallet
./create-wallet.sh SandrosWalletName SandrosPassphrase testnet/restore-wallet.json
- Inform the backend about the wallet
./load-wallet.sh
- Get the wallet id from the response of the backend
..."name": "PAB testing wallet", "id": "this-is-the-wallet-id"...
- Add it into the
env.sh
file.
- Start the chain index
./start-testnet-chain-index.sh
- In case of error
cabal: The program '' is required but it could not be found.
thencabal install plutus-chain-index
is needed. - The database file for the chain index is now also in the testnet folder. This way it doesn't have to reindex from genesis when the index is restarted but from the place were the script has been stopped.
- Start the PAB
- Set the above passphrase in
start-testnet-pab.sh
- It uses the app in
app/token-pab.hs
./start-testnet-pab.sh
/testnet/pab-config.yml
pabResumeFrom
originally was"tag" : "PointAtGenesis" }
- Use the block id and slot logged from the node there to shorten the syncing
- Migrate the database before starting the PAB
migrate-pab.sh
start-testnet-pab.sh
- Swagger-UI
- Set the above passphrase in
- Copy the
curl
command from Swagger-UI and call it mint-token-curl.sh
get-address.sh
- pick one and add it into the
env.sh
file.
- pick one and add it into the
- Uses
app/payment-key-hash.hs
andapp/stake-key-hash.hs
- Uses
unsafeReadAddress
fromsrc/Week06/Utils.hs
- Uses
- The Yoroi wallet
mint-token-curl.sh 10 MyTokenName
- In check the instances in the Swagger UI by executing the endpoint
GET /api/contract/instances
- CLI vs. PAB
- Calling the PAB with Haskell
app/mint-token.hs
- Uses
Network.HTTP.Req
instead ofcurl
mint-token-haskell.sh
shell script- Uses
app/mint-token.hs
from above. mint-token-haskell.sh 1000 gold
- Uses
- Calling the monitor contract
- Stop the PAB
- Clear the PAB from log messages in the Swagger UI
- Clear the database:
rm testnet/plutus-pab.db
./migrate-pab.sh
- Clear the database:
./start-testnet-pab.sh
- The code that builds the request to the PAB
app/monitor.hs
- Activate the monitoring contract
./monitor.sh
- Get the instances in the Swagger UI
GET /api/contract/instances
getMonitorState
code
-
- Utility functions for the off-chain part
- Summary
-
Lecture 7: State Machines
- Intro and current resource caveat
- Commit Schemes
- Both choose 1 or both choose 0 (the sum is even) then Alice wins otherwise (the sum is odd) Bob wins
- Alice hashes her choice with a nonce (number used once) and sends it to Bob
- Bob makes his choic
- Alice reveals her choice and nonce for Bob to check if its hash is similar to Alice' initial choice
- There are additional options to consider for a full state machine
- If Alice doesn't reveal her choice (e.g. because she lost) Bob is able to claim the win after a deadline has passed
- If Bob doesn't replay because he has lost interest Alice is able to claim the win after a deadline has passed
- Implementation without state machines
gPlayDeadline
- Bobs deadline to movegRevealDeadline
- Alice' deadline to revealgToken
- The NFT identifying the game- By definition there can only be one NFT in circulation.
- It can be owned by only one address = attached to only one UTxO.
- This is the UTxO containing the current state of the game.
-
data GameDatum = GameDatum BuiltinByteString (Maybe GameChoice) deriving Show
BuiltinByteString
is the hash of AliceMaybe GameChoice
is the move of BobMaybe
because at the beginning the second player hasn't yet moved.
-
data GameRedeemer = Play GameChoice | Reveal BuiltinByteString | ClaimFirst | ClaimSecond deriving Show
Play GameChoice
- When the 2nd player moves.GameChoice
(zero or one) is the argument.Reveal BuiltinByteString
- When the 1st player reveals because he has won.BuiltinByteString
is the nonce of the 1st players choice.- The choice of the first player is not part of the argument because he will only reveal if he has won.
- We know the move of the 2nd player, and thus we know what move of the 1st player makes him win.
- Adding this move as a parameter would make it redundant.
ClaimFirst
- When the 2nd player doesn't make a move because he lost interestClaimSecond
- When the 1st player doesn't reveal because he knows he has lost.
- The validator function
- More notes and video links are directly in
EvenOdd.hs
.
- More notes and video links are directly in
-
{-# INLINABLE gameDatum #-} gameDatum :: Maybe Datum -> Maybe GameDatum gameDatum md = do Datum d <- md PlutusTx.fromBuiltinData d
- Emulator Trace
runEmulatorTraceIO'
is a variant for defining an initial condition.runEmulatorTraceIO' :: TraceConfig -> EmulatorConfig -> EmulatorTrace () -> IO ()
- See: http://localhost:8002/haddock/plutus-contract/html/Plutus-Trace-Emulator.html#v:runEmulatorTraceIO-39-
- Repl
- State Machine
7.
- The nodes are states and the arrows are transitions.
- The state machine is represented by the UTxO sitting at the script address
- The state is the datum of that UTxO
- The transition is the transaction that consumes the current state (/ the current UTxO?) using a redeemer that characterizes the transition and then produces a new UTxO at the same address where the datum now reflects the new state.
data StateMachine s i
s
- Datum type,i
- Redeemer type
-
StateMachine smTransition :: State s -> i -> Maybe (TxConstraints Void Void, State s) smFinal :: s -> Bool smCheck :: s -> i -> ScriptContext -> Bool smThreadToken :: Maybe ThreadToken
- local Haddock
smTransition :: State s -> i -> Maybe (TxConstraints Void Void, State s)
State s
-
State stateData :: s -- Datum stateValue :: Value
smFinal :: s -> Bool
- If we transition into a final state then there mustn't be any value attached with it.
- If the new state is final then we don't produce a new UTxO.
smCheck :: s -> i -> ScriptContext -> Bool
- Additional checks that can't be expressed in terms of the
TxConstraints
.
- Additional checks that can't be expressed in terms of the
smThreadToken :: Maybe ThreadToken
- Anyone can send money to the script address and create UTxOs by doing this.
- To identify the right game UTxO it has to have the NFT specified in the contract.
- The state machine will care about minting the NFT and cares about passing it along.
- The state token will not be visible in the
stateValue
of theState
- The state machin will burn the token when the "final" state is reached.
- EvenOdd as state machine
-
data GameDatum = GameDatum BuiltinByteString (Maybe GameChoice) | Finished deriving Show
- GameDatum now contains the
Finished
State. It won't correspond to an UTxO but it's needed for the state machine mechanism to work.
- GameDatum now contains the
- The
transition
functiontransition :: Game -> State GameDatum -> GameRedeemer -> Maybe (TxConstraints Void Void, State GameDatum)
- Corresponds to the
mkGameValidator
function - Contains the core business logic.
- The
final
andcheck
functions gameStateMachine
mkGameValidator
gameClient
firstGame
secondGame
- No need to replicate logic...
- Test of the state machine
slotToEndPOSIXTime
- Plutus uses POSIXTime with a precision of milliseconds
- Ouroboros uses slots with a length of 1 second
- When converting from POSIXTime to Ouroboros and back precision is lost
- ==>
Constraints.mustValidateIn
used in off-chain code to construct the transaction is not guaranteed to make on-chain validation pass as expected from the state machine - Using
slotToBeginPOSIXTime
the off-chain part of the state machine would have constructed transactions that wouldn't validate.slotToBeginPOSIXTime def 10
POSIXTime {getPOSIXTime = 1596059101000}
slotToEndPOSIXTime def 10
POSIXTime {getPOSIXTime = 1596059101999}
- I guess the reason why
slotToEndPOSIXTime
has to be used is because it rounds up in a way that matches to the on-chain code.
-
- Homework
-
Lecture 8: Another State Machine, Testing
- Introduction
- Token Sale State Machine
- State transitions
- Implementation
TokenSale
,TSRedeemer
,lovelaces
transition
functionState Integer
is the state machineState
that stores the price asDatum
. See previous lecture.
tsStateMachine
function- This time the smart constructor
mkStateMachine
can be used. - Last time we had to use the actual constructor of the state machine because there was something we couldn't express as a constraint.
(const False)
- the function that determines weather the states are final or not.- In this case there isn't a final state and the state machine is inteded to run forever
- This time the smart constructor
tsCovIdx
- Provides coverage information for tests
mapErrorSM
startTS
- One liners corresponding to the redeemer.
- Bundling endpoints up
uncurry
:t uncurry
:uncurry :: (a -> b -> c) -> (a, b) -> c
:t (+)
:(+) :: Num a => a -> a -> a
:t uncurry (+)
:uncurry (+) :: Num c => (c, c) -> c
uncurry (+) (3 :: Int, 4)
:7
- The tupel
(3, 4)
will be passed to the(+)
function as individual parameters instead of a map and executed.
- Emulator Trace
- cabal file and repl
cabal repl plutus-pioneer-program-week08.cabal:test:plutus-pioneer-program-week08-tests
:l test/Spec/Trace.hs
runMyTrace
- Automatic testing using Emulator Traces
- Tasty
- Group tests
- Supported by Plutus
- Various types supported e.g.
- One which works with emulator traces (this chapter)
- Property based testing (next chapter)
- https://hackage.haskell.org/package/tasty
- http://localhost:8002/haddock/plutus-contract/html/Plutus-Contract-Test.html
- Checking predicates
-
checkPredicate :: String -- Descriptive name of the test -> TracePredicate -- The predicate to check -> EmulatorTrace () -> TestTree
-
- With options
-
checkPredicateOptions :: CheckOptions -- Options to use -> String -- Descriptive name of the test -> TracePredicate -- The predicate to check -> EmulatorTrace () -> TestTree
-
- Checking predicates
TracePredicate
function- Assertions
- http://localhost:8002/haddock/plutus-contract/html/Plutus-Contract-Test.html#g:1
- We mainly use
walletFundsChange
- Assertions
- Out Tasty tests with emulator trace
-
tests :: TestTree tests = checkPredicateOptions myOptions "token sale trace" myPredicate myTrace
myOptions = defaultCheckOptions & emulatorConfig .~ emCfg
- Sets the
emulatorConfig
part ofdefaultCheckOptions
with the value ofemCfg
- The
.~
operator is part of Optics (see later chapter for Optics)
- Sets the
-
myPredicate :: TracePredicate myPredicate = walletFundsChange w1 (Ada.lovelaceValueOf 10_000_000 <> assetClassValue token (-60) <> Plutus.negate (toValue minAdaTxOut)) .&&. walletFundsChange w2 (Ada.lovelaceValueOf (-20_000_000) <> assetClassValue token 20) .&&. walletFundsChange w3 (Ada.lovelaceValueOf (- 5_000_000) <> assetClassValue token 5)
- Specifies how much the funds of the wallets should have changed.
-
- Tasty
- Test coverage
-
The version with the
checkPredicateCoverageSource :: String -- Descriptive name of the test -> CoverageRef -> TracePredicate -- The predicate to check -> EmulatorTrace () -> TestTree
CheckOptions
is not available yet.- Own implementation
- A
TokenSaleTrace.html
file is generated.- White colored code is covered by tests.
- Green colored conditions are not covered.
- Red code on black background is not covered.
-
- Optics
- This one is used: https://hackage.haskell.org/package/lens-5.0.1.html
- They are about reaching deeply into hierarchical datatypes
- See Lens.hs for an example data structure
- Explanation from Lars
- Lenses expect the underscore convention for fields.
- The names of the Lenses will be the names of the fields without the original underscore.
- The code template Haskell writes
- Trying out the Lenses
:l src/Week08/Lens.hs
import Control.Lens
lars ^. name
:"Lars"
lars 1 . address . city
:"Regensburg"
lars & name .~ "LARS"
:Person {_name = "LARS", _address = Address {_city = "Regensburg"}}
lars & address . city .~ "Munich"
:Person {_name = "Lars", _address = Address {_city = "Munich"}}
[1 :: Int, 3, 4] & each .~ 42
:[42,42,42]
iohk ^. staff
:[Person {_name = "Alejandro", _address = Address {_city = "Zacateca"}},Person {_name = "Lars", _address = Address {_city = "Regensburg"}}]
iohk & staff . each . address . city .~ "Athens"
:Company {_staff = [Person {_name = "Alejandro", _address = Address {_city = "Athens"}},Person {_name = "Lars", _address = Address {_city = "Athens"}}]}
- See Lens.hs for an example data structure
- Property based tasting with "QuickCheck"
- See: https://hackage.haskell.org/package/QuickCheck
- Unit tests are a part of property based testing
- See: QuickCheck.hs
:l src/Week08/QuickCheck.hs
import Test.QuickCheck
quickCheck prop_sort_sorts
:-
*** Failed! Falsified (after 5 tests and 3 shrinks): [0,0,-1]
- QuickCheck tried 5 lists with random integers and then found a counter example.
- It didn't just report the counter example but simplified it 3 times before reporting the result.
-
- Random number generation
- By default it generates 100 elements for an argument but it can be configured to a higher number.
- The validity of the QuickCheck result depends on how well the validity of the test is specified.
- This approach is more powerful than unit tests because it can come up with input cases the programmer might not have thought of.
- QuickCheck for Plutus contracts
- "Model" is an idialized model of how the real world system should work
- "System" is the real system that is to be tested
- QuickCheck generates a random sequence of actions.
- In the case of a file system this would be "open file", "close file", "read file",...
- It applies the actions to the Model and the System and checks if they are equal and continues that for more actions.
- Token Sale contract changes
useEndpoints'
useEndpoints''
inModel.hs
- The
init
endpoint delegates to theuseEndpoints'
- The
TSState
represents oneTokenSales
instance-
data TSState = TSState { _tssPrice :: !Integer , _tssLovelace :: !Integer , _tssToken :: !Integer } deriving Show
-
TSModel
represents the model of the expected behavior as described in the system <--> model diagram before.newtype TSModel = TSModel {_tsModel :: Map Wallet TSState} deriving Show
- A map from
Wallet
toTSState
- Two wallets running their own token sale contract each with their own individual tokens.
- Before the contract has started there won't be an entry for that wallet.
- After that there will be an entry with the current state.
ContractModel
contains all the logic.ContractInstanceKey
and why there are additionaluseEndpoints
functions- ???
instanceTag
to identify running instances of contractsarbitraryAction
generates an arbitrary action- applicative style
- Trying in the repl
:l test/Spec/Model.hs
import Test.QuickCheck
import Plutus.Contract.Test.ContractModel
sample (arbitraryAction undefined :: Gen (Action TSModel))
-
SetPrice (Wallet 2) (Wallet 2) 0 Start (Wallet 2) SetPrice (Wallet 2) (Wallet 1) 2 AddTokens (Wallet 1) (Wallet 1) 4 Withdraw (Wallet 1) (Wallet 2) 3 4 Withdraw (Wallet 1) (Wallet 1) 1 3 SetPrice (Wallet 1) (Wallet 2) 9 BuyTokens (Wallet 1) (Wallet 2) 3 Start (Wallet 2) Withdraw (Wallet 2) (Wallet 2) 8 3 Withdraw (Wallet 1) (Wallet 1) 18 11
- [
initialState
](https://youtu.be/49oAwySp6Ys?t=1770 precondition
nextState
perform
- Tells how an action is expressed in the emulator
Start
- Limitations
- Doesn't test all possible off-chain code someone else develops
- E.g. code that allows them to steal funds from our script address
- QuickCheck has to use the contracts we provide in the
perform
method
- Concurrency
- We check it sequentially
- In practice wallets can submit transactions concurrently
- We could do that but we would need to specify what should happen in this case
- That would depend on the order in which the transactions are processed. This might get quite complicated.
- Doesn't test all possible off-chain code someone else develops
- Homework
- Close the contract
- Collect all the remaining tokens and lovelace and the NFT