Hi @bwbush, I just finish the spending script and I have a few questions:
{-|
Module : Horrocubes.Counter.
Description : Plutus script that keeps track of an internal counter.
License : Apache-2.0
Maintainer : angel.castillo@horrocubes.io
Stability : experimental
This script keeps a counter and increases it everytime the eUTXO is spent.
-}
-- LANGUAGE EXTENSIONS --------------------------------------------------------
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ViewPatterns #-}
-- MODULE DEFINITION ----------------------------------------------------------
module Horrocubes.Counter
(
counterScript,
counterScriptShortBs,
CounterParameter(..)
) where
-- IMPORTS --------------------------------------------------------------------
import Cardano.Api.Shelley (PlutusScript (..), PlutusScriptV1)
import Codec.Serialise
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Short as SBS
import Ledger hiding (singleton)
import qualified Ledger.Typed.Scripts as Scripts
import Ledger.Value as Value
import qualified PlutusTx
import PlutusTx.Prelude as P hiding (Semigroup (..), unless)
import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics (Generic)
import qualified Ledger.Contexts as Validation
import Text.Show
-- DATA TYPES -----------------------------------------------------------------
-- | The parameters for the counter contract.
data CounterParameter = CounterParameter {
ownerPkh :: !PubKeyHash, -- ^ The transaction that spends this output must be signed by the private key
identityNft :: !AssetClass -- ^ The NFT that identifies the correct eUTXO.
} deriving (Show, Generic, FromJSON, ToJSON)
PlutusTx.makeLift ''CounterParameter
-- | This Datum represents the state of the counter.
data CounterDatum = CounterDatum {
counter :: !Integer -- ^ The current counter value.
}
deriving Show
PlutusTx.unstableMakeIsData ''CounterDatum
-- | The Counter script type. Sets the Redeemer and Datum types for this script.
data Counter
instance Scripts.ValidatorTypes Counter where
type instance DatumType Counter = CounterDatum
type instance RedeemerType Counter = ()
-- DEFINITIONS ----------------------------------------------------------------
-- | Maybe gets the datum from the transatcion output.
{-# INLINABLE counterDatum #-}
counterDatum :: TxOut -> (DatumHash -> Maybe Datum) -> Maybe CounterDatum
counterDatum o f = do
dh <- txOutDatum o
Datum d <- f dh
PlutusTx.fromBuiltinData d
-- | Checks that the identity NFT is locked again in the contract.
{-# INLINABLE isIdentityNftRelocked #-}
isIdentityNftRelocked:: CounterParameter -> Value -> Bool
isIdentityNftRelocked params valueLockedByScript = assetClassValueOf valueLockedByScript (identityNft params) == 1
-- | Creates the validator script for the outputs on this contract.
{-# INLINABLE mkCounterValidator #-}
mkCounterValidator :: CounterParameter -> CounterDatum -> () -> ScriptContext -> Bool
mkCounterValidator parameters oldDatum _ ctx =
let oldCounterValue = counter oldDatum
isRightNexCounterValue = (newDatumValue == (oldCounterValue + 1))
isIdentityLocked = isIdentityNftRelocked parameters valueLockedByScript
in traceIfFalse "Wrong counter value" isRightNexCounterValue &&
traceIfFalse "Wrong balance" isIdentityLocked &&
traceIfFalse "Missing signature" isTransactionSignedByOwner
where
info :: TxInfo
info = scriptContextTxInfo ctx
ownOutput :: TxOut
ownOutput = case getContinuingOutputs ctx of
[o] -> o
_ -> traceError "Expected exactly one output"
newDatumValue :: Integer
newDatumValue = case counterDatum ownOutput (`findDatum` info) of
Nothing -> traceError "Counter output datum not found"
Just datum -> counter datum
valueLockedByScript :: Value
valueLockedByScript = Validation.valueLockedBy info (Validation.ownHash ctx)
isTransactionSignedByOwner :: Bool
isTransactionSignedByOwner = txSignedBy info (ownerPkh parameters)
-- | The script instance of the counter. It contains the mkCounterValidator function
-- compiled to a Plutus core validator script.
counterInstance :: CounterParameter -> Scripts.TypedValidator Counter
counterInstance counter = Scripts.mkTypedValidator @Counter
($$(PlutusTx.compile [|| mkCounterValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode counter) $$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator @CounterDatum @()
-- | Gets the counter validator script that matches the given parameters.
counterValidator :: CounterParameter -> Validator
counterValidator params = Scripts.validatorScript . counterInstance $ params
-- | Generates the plutus script.
counterPlutusScript :: CounterParameter -> Script
counterPlutusScript params = unValidatorScript $ counterValidator params
-- | Serializes the contract in CBOR format.
counterScriptShortBs :: CounterParameter -> SBS.ShortByteString
counterScriptShortBs params = SBS.toShort . LBS.toStrict $ serialise $ counterPlutusScript params
-- | Gets a serizlized plutus script from the given parameters.
counterScript :: PubKeyHash -> AssetClass -> PlutusScript PlutusScriptV1
counterScript pkh ac = PlutusScriptSerialised $ counterScriptShortBs $ CounterParameter { ownerPkh = pkh, identityNft = ac }
type or paste code here
I already created an instance of the script on the testnet with the appropriate identity NFT inside, you can check the script at the following address:
addr_test1wqesjavxsh7g2q8lf92ptyt7rnrhh07ghnyjq65ra50uwwqsssy2q
cardano-cli query utxo --address addr_test1wqesjavxsh7g2q8lf92ptyt7rnrhh07ghnyjq65ra50uwwqsssy2q --testnet-magic 1097911063
TxHash TxIx Amount
--------------------------------------------------------------------------------------
b2997baf426caa94762e4baeed051ac13bad7994f2f3a43f8c43299d2ba8f050 1 2000000 lovelace + 1 a1c6cefca22b4527acdf17a1d44674b6d7cf17c3e7e35cbd1a57d8b5.Horrocube09997 + TxOutDatumHash ScriptDataInAlonzoEra "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
The parameters of the script are:
PubKeyHash: 52cdb93f3e798d9c73866450e6e2bfb4a3da911f702ea055e3042dab
AssetClass: a1c6cefca22b4527acdf17a1d44674b6d7cf17c3e7e35cbd1a57d8b5.Horrocube09997
And it was initialized with the datum value 0 (I know this is wrong, as my datum is not an integral type but of CounterDatum type instead).
This is how I am constructing the transaction:
cardano-cli transaction build-raw --alonzo-era --fee 500000 --tx-in b2997baf426caa94762e4baeed051ac13bad7994f2f3a43f8c43299d2ba8f050#0 --tx-in b2997baf426caa94762e4baeed051ac13bad7994f2f3a43f8c43299d2ba8f050#1 --tx-in-script-file ./counter/out2.plutus --tx-in-execution-units "(491845099,1197950)" --tx-in-datum-value 0 --tx-in-redeemer-value 0 --tx-in-collateral b2997baf426caa94762e4baeed051ac13bad7994f2f3a43f8c43299d2ba8f050#0 --tx-out "addr_test1wqesjavxsh7g2q8lf92ptyt7rnrhh07ghnyjq65ra50uwwqsssy2q+2000000+1 a1c6cefca22b4527acdf17a1d44674b6d7cf17c3e7e35cbd1a57d8b5.Horrocube09997" --tx-out-datum-hash ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25 --protocol-params-file protocol.json --out-file tx-script2.build
And after I sign and try to send the transaction I get an error (of course).
I noticed this part in particular:
Datums: [ ( 03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314\n , 0 )]
There is only reference to the input datum, but not to the new datum and now that I think about it carefully I am not giving it the new value of the datum, only the hash (which for now is also wrong as it is the datum hash of the integral type 1 and not CounterDatum):
--tx-out-datum-hash ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25
Sorry for the long preamble, now on to the actual questions:
1.- How do you specify the actual value of the new datum using the CLI? It seems it will only let me specify the datum hash of the output, however, if this is the case, how can the script get the actual value of the new datum? hash can not be reversed.
2.- How can we specify non-integral types to the CLI (this one I can probably find on my own).
I apologize for the lengthy post, but I wanted to share as much detail as possible to both share my progress and set the right context for the questions.
Thanks