View on GitHub
File Changes
import           Ledger.Core (Hash (Hash), Sig (Sig), Slot, VKey, hash, owner)
import           Ledger.Delegation
import           Ledger.Update (ProtVer, STag, UProp, Vote)
-
import           Ledger.UTxO (TxIn, TxOut, TxWits, Wit)
+
import           Ledger.UTxO (TxIn, TxOut, Tx, Wit)

                      
import           Test.Goblin (AddShrinks (..), Goblin (..))
import           Test.Goblin.TH (deriveAddShrinks, deriveGoblin)
  = BlockBody
  { _bDCerts :: ![DCert]
  -- ^ Delegation certificates
-
  , _bUtxo :: ![TxWits]
+
  , _bUtxo :: ![Tx]
  -- ^ UTxO payload
  , _bUpdProp :: !(Maybe UProp)
  -- ^ Update proposal payload
  -- ^ Update proposal
  -> [Vote]
  -- ^ Votes on update proposals
-
  -> [TxWits]
+
  -> [Tx]
  -- ^ UTxO payload
  -> Block
mkBlock
                         , (typeOf (undefined::ProtVer), 1)
                         , (typeOf (undefined::DCert), 1)
                         , (typeOf (undefined::Vote), 1)
-
                         , (typeOf (undefined::TxWits), 1)
+
                         , (typeOf (undefined::Tx), 1)
                         , (typeOf (undefined::Wit), 1)
                         , (typeOf (undefined::TxIn), 1)
                         , (typeOf (undefined::TxOut), 1)]
-- Example typeReps for Block/Header/Body
--------------------------------------------------------------------------------

                      
-
aTx :: Tx
+
aTx :: TxBody
aTx = undefined

                      
aTxId :: TxId
        , _bhUpdHash = undefined :: Hash
     }

                      
-
aTxWits :: TxWits
+
aTxWits :: Tx
aTxWits =
  let (in0,in1) = (TxIn aTxId 0, TxIn aTxId 1)
      outs = []
      wits = []
-
   in TxWits (Tx [in0, in1] outs) wits
+
   in Tx (TxBody [in0, in1] outs) wits

                      
aBody :: BlockBody
aBody = BlockBody
        Seq.fromList [
            typeOf (undefined::BlockBody)
          , typeOf (undefined::[DCert])
-
          , typeOf (undefined::[TxWits]) ]
+
          , typeOf (undefined::[Tx]) ]
        >< typeReps aTxWits
        >< typeReps aTxWits
        >< Seq.fromList [
    body_ = _bBody b
  in do
    abstractSize (mkCost @DCert)  b === length (_bDCerts body_)
-
    abstractSize (mkCost @TxWits) b === length (_bUtxo body_)
+
    abstractSize (mkCost @Tx)     b === length (_bUtxo body_)
    abstractSize (mkCost @Vote)   b === length (_bUpdVotes body_)
    abstractSize (mkCost @UProp)  b === length (maybeToList (_bUpdProp body_))
    -- A STag is a string, so we need to make sure that all the characters are
import           Ledger.Core (Lovelace, dom, range, (∪), (⊆), (⋪), (◁))
import           Ledger.GlobalParams (lovelaceCap)
import           Ledger.Update (PParams)
-
import           Ledger.UTxO (Tx, UTxO, balance, pcMinFee, txins, txouts, unUTxO, value)
+
import           Ledger.UTxO (Tx, UTxO, balance, body, pcMinFee, txins, txouts, unUTxO, value)

                      
import           Test.Goblin (SeedGoblin (..))
import           Test.Goblin.TH (deriveSeedGoblin)
            , tx
            ) <- judgmentContext

                      
-
        txins tx ⊆ dom utxo ?! InputsNotInUTxO
+
        let ins = txins $ body tx
+
            outs = txouts $ body tx

                      
-
        let fee = balance (txins tx ◁ utxo) - balance (txouts tx)
+
        ins ⊆ dom utxo ?! InputsNotInUTxO
+

                      
+
        let fee = balance (ins ◁ utxo) - balance outs

                      
        pcMinFee pps tx <= fee ?! FeeTooLow

                      
-
        (not . null) (txins tx) ?! EmptyTxInputs
+
        (not . null) ins ?! EmptyTxInputs

                      
-
        (not . null . unUTxO) (txouts tx) ?! EmptyTxOutputs
+
        (not . null . unUTxO) outs ?! EmptyTxOutputs

                      
        let
-
          outputValues = fmap value $ Set.toList $ range (txouts tx)
+
          outputValues = fmap value $ Set.toList $ range outs
        all (0<) outputValues ?! NonPositiveOutputs

                      
-
        return $ UTxOState { utxo     = (txins tx ⋪ utxo) ∪ txouts tx
+
        return $ UTxOState { utxo     = (ins ⋪ utxo) ∪ outs
                           , reserves = reserves + fee
                           }

                      
import           Control.State.Transition.Generator (HasTrace, SignalGenerator, coverFailures,
                     envGen, sigGen, tinkerWithSigGen)

                      
-
import           Ledger.Core (Addr (Addr), KeyPair (KeyPair), VKey, keyPair, mkAddr, owner, sign,
-
                     verify)
+
import           Ledger.Core (Addr (Addr),VKey, mkAddr, verify)
import qualified Ledger.Update.Generators as UpdateGen
import           Ledger.Util (mkGoblinGens)
-
import           Ledger.UTxO (Tx, TxIn, TxOut (TxOut), TxWits (TxWits), UTxO (UTxO), Wit (Wit),
-
                     body, fromTxOuts, inputs, pcMinFee)
+
import           Ledger.UTxO (Tx(..), TxIn, TxOut (TxOut), UTxO (UTxO), Wit (Wit),
+
                      fromTxOuts, inputs, pcMinFee)
import qualified Ledger.UTxO.Generators as UTxOGen

                      
import           Cardano.Ledger.Spec.STS.UTXO

                      
  type Environment UTXOW = UTxOEnv
  type State UTXOW = UTxOState
-
  type Signal UTXOW = TxWits
+
  type Signal UTXOW = Tx

                      
  -- | These `PredicateFailure`s are all throwable.
  data PredicateFailure UTXOW
    [ do
        TRC (env, [email protected] {utxo}, tw) <- judgmentContext
        witnessed tw utxo ?! InsufficientWitnesses
-
        utxoSt' <- trans @UTXO $ TRC (env, utxoSt, body tw)
+
        utxoSt' <- trans @UTXO $ TRC (env, utxoSt, tw)
        return utxoSt'
    ]

                      
-- |Given a ledger state, determine if the UTxO witnesses in a given
-- transaction are sufficient.
-- TODO - should we only check for one witness for each unique input address?
-
witnessed :: TxWits -> UTxO -> Bool
-
witnessed (TxWits tx wits) utxo =
+
witnessed :: Tx -> UTxO -> Bool
+
witnessed (Tx tx wits) utxo =
  length wits == length ins && all (isWitness tx utxo) (zip ins wits)
 where
  ins = inputs tx
        -- come from we use the hash of the address as transaction id.
        pure $ fromTxOuts txOuts

                      
-
  sigGen UTxOEnv { pps } st = do
-
    tx <- UTxOGen.genTxFromUTxO traceAddrs (pcMinFee pps) (utxo st)
-
    let wits = witnessForTxIn tx (utxo st) <$> inputs tx
-
    pure $ TxWits tx wits
-

                      
-
witnessForTxIn :: Tx -> UTxO -> TxIn -> Wit
-
witnessForTxIn tx (UTxO utxo) txin =
-
  case Map.lookup txin utxo of
-
    Just (TxOut (Addr pay) _) ->
-
      witnessForTx (keyPair $ owner pay) tx
-
    Nothing                   ->
-
      error "The generators must ensure that we are spending unspent inputs"
-

                      
-
witnessForTx :: KeyPair -> Tx -> Wit
-
witnessForTx (KeyPair sk vk) tx = Wit vk (sign sk tx)
+
  sigGen UTxOEnv { pps } st =
+
    UTxOGen.genTxFromUTxO traceAddrs (pcMinFee pps) (utxo st)

                      

                      
--------------------------------------------------------------------------------
  , "UtxoFailure_NonPositiveOutputs"
  ]

                      
-
tamperedTxWitsList :: UTxOEnv -> UTxOState -> Gen [TxWits]
-
tamperedTxWitsList env st = do
+
tamperedTxList :: UTxOEnv -> UTxOState -> Gen [Tx]
+
tamperedTxList env st = do
  gen <- Gen.element (map (\sg -> sg env st) goblinGensUTXOW)
  Gen.list (Range.linear 1 10) gen

                      
                     transitionRules, wrapFailed)
import           Control.State.Transition.Generator (HasTrace, envGen, genTrace, sigGen)
import           Control.State.Transition.Trace (TraceOrder (OldestFirst), traceSignals)
-
import           Ledger.UTxO (TxWits)
+
import           Ledger.UTxO (Tx)

                      
data UTXOWS deriving (Data, Typeable)

                      
instance STS UTXOWS where
  type State UTXOWS = UTxOState
-
  type Signal UTXOWS = [TxWits]
+
  type Signal UTXOWS = [Tx]
  type Environment UTXOWS = UTxOEnv
  data PredicateFailure UTXOWS
    = UtxowFailure (PredicateFailure UTXOW)
  transitionRules =
    [ do
        TRC (env, utxo, txWits) <- judgmentContext
-
        case (txWits :: [TxWits]) of
+
        case (txWits :: [Tx]) of
          []     -> return utxo
          (tx:gamma) -> do
            utxo'  <- trans @UTXOW  $ TRC (env, utxo, tx)
  where mkId = TxId . hash . addr

                      
-- | A raw transaction
-
data Tx = Tx
+
data TxBody = TxBody
  { inputs  :: [TxIn]
  , outputs :: [TxOut]
  } deriving (Eq, Show, Ord, Generic, Hashable, HasTypeReps, Data, Typeable)

                      
-
txid :: Tx -> TxId
+
txid :: TxBody -> TxId
txid = TxId . hash

                      
-- | Total value of a transaction.
-
txValue :: Tx -> Lovelace
-
txValue Tx { outputs } = sum $ fmap value outputs
+
txValue :: TxBody -> Lovelace
+
txValue TxBody { outputs } = sum $ fmap value outputs

                      
-- |Compute the UTxO inputs of a transaction.
-
txins :: Tx -> [TxIn]
+
txins :: TxBody -> [TxIn]
txins = inputs

                      
-- |Compute the UTxO outputs of a transaction.
-
txouts :: Tx -> UTxO
+
txouts :: TxBody -> UTxO
txouts tx = UTxO $ Map.fromList
  [ (TxIn transId idx, out) | (out, idx) <- zip (outputs tx) [0 ..] ]
  where transId = txid tx
balance (UTxO utxo) = Map.foldl' addValues mempty utxo
  where addValues b (TxOut _ a) = b <> a

                      
-
instance Ledger.Core.HasHash Tx where
+
instance Ledger.Core.HasHash TxBody where
  hash = Hash . Just . H.hash

                      
---------------------------------------------------------------------------------
txsize = abstractSize costs
  where costs = Map.fromList [ (typeOf (undefined :: TxIn) , 1)
                             , (typeOf (undefined :: TxOut), 1)
+
                             , (typeOf (undefined :: Wit), 1)
                             ]

                      

                      
---------------------------------------------------------------------------------

                      
-- |Proof/Witness that a transaction is authorized by the given key holder.
-
data Wit = Wit VKey (Sig Tx)
+
data Wit = Wit VKey (Sig TxBody)
  deriving (Show, Eq, Ord, Generic, Hashable, HasTypeReps, Data, Typeable)

                      
-- |A fully formed transaction.
-
--
-
--     * __TODO__ - Would it be better to name this type Tx, and rename Tx to TxBody?
-
data TxWits = TxWits
-
  { body      :: Tx
+
data Tx = Tx
+
  { body      :: TxBody
  , witnesses :: [Wit]
  } deriving (Show, Eq, Generic, Hashable, HasTypeReps, Data, Typeable)

                      
-
instance HasHash [TxWits] where
+
instance HasHash [Tx] where
  hash = Hash . Just . H.hash

                      
-- |Create a witness for transaction
-
makeWitness :: KeyPair -> Tx -> Wit
+
makeWitness :: KeyPair -> TxBody -> Wit
makeWitness keys tx = Wit (vKey keys) (sign (sKey keys) tx)

                      
-
makeTxWits :: UTxO -> Tx -> TxWits
-
makeTxWits (UTxO utxo) tx = TxWits
+
makeTxWits :: UTxO -> TxBody -> Tx
+
makeTxWits (UTxO utxo) tx = Tx
  { body      = tx
  , witnesses = wits
  }

                      
deriveGoblin ''TxIn
deriveGoblin ''TxOut
-
deriveGoblin ''TxWits
+
deriveGoblin ''Tx
deriveGoblin ''Wit
deriveGoblin ''TxId

                      
-
instance GeneOps g => Goblin g Tx where
+
instance GeneOps g => Goblin g TxBody where
  tinker gen = do
    fIs <- fillEmptyList
    fOs <- fillEmptyList
    is <- tinkerRummagedOrConjureOrSave
            (fIs <$$>
-
              (tinker ((\(Tx x _) -> x) <$> gen)))
+
              (tinker ((\(TxBody x _) -> x) <$> gen)))
    os <- tinkerRummagedOrConjureOrSave
            (fOs <$$>
-
              (tinker ((\(Tx _ x) -> x) <$> gen)))
+
              (tinker ((\(TxBody _ x) -> x) <$> gen)))
    tinkerRummagedOrConjureOrSave
-
      (pure (Tx <$> is <*> os))
+
      (pure (TxBody <$> is <*> os))
   where
    -- This function will insert a conjured value to an empty list. We can
    -- thus use it to ensure that the `txIns` and `txOuts` will never be
    listLenO <- (+1) <$> transcribeGenesAsInt 15
    inputs <- replicateM listLenI conjure
    outputs <- replicateM listLenO conjure
-
    pure (Tx inputs outputs)
+
    pure (TxBody inputs outputs)

                      
--------------------------------------------------------------------------------
-- AddShrinks instances
--------------------------------------------------------------------------------

                      
-
deriveAddShrinks ''Tx
+
deriveAddShrinks ''TxBody
deriveAddShrinks ''TxId
deriveAddShrinks ''TxIn
deriveAddShrinks ''TxOut
-
deriveAddShrinks ''TxWits
+
deriveAddShrinks ''Tx
deriveAddShrinks ''Wit

                      

                      
  -- ^ UTxO used to determine which unspent outputs can be used in the
  -- transaction.
  -> Gen Tx
-
genTxFromUTxO addrs txfee utxo = subtractFees txfee $ uncurry Tx <$> Gen.filter
-
  (not . null . fst)
-
  (genInputOutput
-
    (M.keys $ unUTxO utxo)
-
    (maybe 0 (unLovelace . value) . flip M.lookup (unUTxO utxo))
-
    (fmap (. Lovelace) $ TxOut <$> Gen.element addrs)
-
    (unLovelace . value)
-
    (\f out -> out { value = Lovelace . f . unLovelace $ value out })
-
  )
+
genTxFromUTxO addrs txfee (UTxO utxo) = do
+
    txbody <- genTxBody
+
    let wits = witnessForTxIn txbody <$> inputs txbody
+
    let tx = Tx txbody wits
+
    subtractFees $ pure tx
+
  where
+
    genTxBody = uncurry TxBody
+
      <$> Gen.filter
+
      (not . null . fst)
+
      (genInputOutput
+
        (M.keys utxo)
+
        (maybe 0 (unLovelace . value) . flip M.lookup utxo)
+
        (fmap (. Lovelace) $ TxOut <$> Gen.element addrs)
+
        (unLovelace . value)
+
        (\f out -> out { value = Lovelace . f . unLovelace $ value out })
+
      )

                      
-
-- | Ensure we generate a 'Tx' which utilizes enough 'Lovelace' via its inputs
-
-- to cover its outputs as well as the transaction fee.
-
-- This function potentially removes outputs from the generated transaction
-
-- such that it is able to cover the transaction fee.
-
subtractFees :: (Tx -> Lovelace) -> Gen Tx -> Gen Tx
-
subtractFees txfee = fmap subtractFees'
-
  -- In Byron, we must disallow empty outputs in transactions in order to
-
  -- maintain compatability with the `cardano-sl` implementation.
-
  -- In order to do this, while also potentially removing some outputs from
-
  -- the transaction to ensure that the transaction fee is covered, we only
-
  -- generate transactions whose sum of outputs is greater than the
-
  -- transaction fee. This way, we ensure that there will always remain at
-
  -- least 1 'Lovelace' in the outputs.
-
  . Gen.filter (\tx -> sum (value <$> outputs tx) > txfee tx)
-
 where
-
  subtractFees' tx =
-
    tx { outputs = subFromList (txfee tx) value updateValue (outputs tx) }
-
  updateValue f out = out { value = f (value out) }
+
    witnessForTxIn :: TxBody -> TxIn -> Wit
+
    witnessForTxIn tx txin =
+
      case M.lookup txin utxo of
+
        Just (TxOut (Addr pay) _) ->
+
          witnessForTx (keyPair $ owner pay) tx
+
        Nothing                   ->
+
          error "The generators must ensure that we are spending unspent inputs"
+

                      
+
    witnessForTx :: KeyPair -> TxBody -> Wit
+
    witnessForTx (KeyPair sk vk) tx = Wit vk (sign sk tx)
+

                      
+
    -- | Ensure we generate a 'Tx' which utilizes enough 'Lovelace' via its inputs
+
    -- to cover its outputs as well as the transaction fee.
+
    -- This function potentially removes outputs from the generated transaction
+
    -- such that it is able to cover the transaction fee.
+
    subtractFees :: Gen Tx -> Gen Tx
+
    subtractFees = fmap subtractFees'
+
        -- In Byron, we must disallow empty outputs in transactions in order to
+
        -- maintain compatability with the `cardano-sl` implementation.
+
        -- In order to do this, while also potentially removing some outputs from
+
        -- the transaction to ensure that the transaction fee is covered, we only
+
        -- generate transactions whose sum of outputs is greater than the
+
        -- transaction fee. This way, we ensure that there will always remain at
+
        -- least 1 'Lovelace' in the outputs.
+
        . Gen.filter (\[email protected](Tx txb _) -> sum (value <$> outputs txb) > txfee tx)
+
      where
+
        subtractFees' [email protected](Tx txb _) = let
+
            newBody = txb { outputs = subFromList (txfee tx) value updateValue (outputs txb) }
+
          in Tx newBody (witnessForTxIn newBody <$> inputs newBody)
+

                      
+
        updateValue f out = out { value = f (value out) }

                      
-- | A property to test that the entire shrink tree generated by
--   'genInputOutput' maintains the invariant that the inputs and outputs have
-- Example HasTypeReps.typeReps for TxIn, Tx
--------------------------------------------------------------------------------

                      
-
aTx :: Tx
+
aTx :: TxBody
aTx = undefined

                      
aTxId :: TxId
    (in0,in1) = (TxIn aTxId 0, TxIn aTxId 1)
    outs = []
    wits = []
-
    tx = TxWits (Tx [in0, in1] outs) wits
-
  in typeReps tx @?= typeOf (undefined::TxWits)
-
                       <| typeOf (undefined::Tx)
+
    tx = Tx (TxBody [in0, in1] outs) wits
+
  in typeReps tx @?= typeOf (undefined::Tx)
+
                       <| typeOf (undefined::TxBody)
                       <| typeOf (undefined::[TxIn])
                       <| typeReps in0
                       >< typeReps in1
-- | Tests that the size of a 'TxWits' term, computed with the combined costs
--   of 'TxIn/TxOut/Wit', is the sum of costs of all 'TxIn/TxOut/Wit' contained
--   in the 'TxWits'.
-
propSumOfSizesTxWits
-
  :: MonadTest m => TxWits -> m ()
-
propSumOfSizesTxWits txw
+
propSumOfSizesTx
+
  :: MonadTest m => Tx -> m ()
+
propSumOfSizesTx txw
  = abstractSize (txInCosts <> txOutCosts <> witCosts) txw
         === abstractSize txInCosts (body txw)
             + abstractSize txOutCosts (body txw)
    txOutCosts = Map.unions [ mkCost @TxOut, mkCost @Addr, mkCost @VKey, mkCost @Lovelace ]

                      
    witCosts :: Map TypeRep Size
-
    witCosts = Map.unions [ mkCost @Wit, mkCost @VKey, mkCost @(Sig Tx) ]
+
    witCosts = Map.unions [ mkCost @Wit, mkCost @VKey, mkCost @(Sig TxBody) ]

                      
-- | A TxWits contains multiple inputs, outputs and witnesses.
--   This property tests that
--   - types that are shared (e.g. VKey appears in both TxOut and Wit)
--     should be counted for each appearance
propMultipleOfSizes
-
  :: MonadTest m => TxWits -> m ()
+
  :: MonadTest m => Tx -> m ()
propMultipleOfSizes txw =
  let
    body_ = body txw

                      
    -- we should account for each Wit/Sig in a TxWits's size
    abstractSize (mkCost @Wit)      txw === length wits_
-
    abstractSize (mkCost @(Sig Tx)) txw === length wits_
+
    abstractSize (mkCost @(Sig TxBody)) txw === length wits_
    -- the combined cost is the sum of individual costs
-
    abstractSize (Map.unions [ mkCost @Wit, mkCost @(Sig Tx) ]) txw
+
    abstractSize (Map.unions [ mkCost @Wit, mkCost @(Sig TxBody) ]) txw
       === abstractSize (mkCost @Wit) txw
-
           + abstractSize (mkCost @(Sig Tx)) txw
+
           + abstractSize (mkCost @(Sig TxBody)) txw

                      
    -- since Vkey appears in each input _and_ each witness, the size of
    -- TxWits should be the total number of inputs and wits
propTxAbstractSize
  = withTests 50 $ property $ do
    tr <- forAll (trace @UTXOW () 100)
-
    let txs = traceSignals OldestFirst tr :: [TxWits]
+
    let txs = traceSignals OldestFirst tr :: [Tx]
    mapM_ propSize txs
  where
-
    propSize txw = propSumOfSizesTxWits txw >> propMultipleOfSizes txw
+
    propSize txw = propSumOfSizesTx txw >> propMultipleOfSizes txw

                      
testTxHasTypeReps :: TestTree
testTxHasTypeReps = testGroup "Test HasTypeReps instances"
import qualified Data.Map.Strict as Map
import           Data.Set (Set, empty, fromList, union)
import           Ledger.Core (Lovelace, dom, unLovelace, (∩), (∪), (⋪), (◁))
-
import           Ledger.UTxO (Tx (Tx), TxIn (TxIn), TxOut (TxOut), TxWits, UTxO (UTxO), balance,
-
                     body, inputs, outputs, pcMinFee, txins, txouts)
+
import           Ledger.UTxO (Tx (..), TxBody (TxBody), TxIn (TxIn), TxOut (TxOut), UTxO (UTxO),
+
                     balance, body, inputs, outputs, pcMinFee, txins, txouts)

                      
--------------------------------------------------------------------------------
-- UTxO Properties
  when (all (\ti -> dom (txouts ti) ∩ dom utxo0 == empty) txs) $
    traverse_ (noCommonInputsTxs txs) (zip txs [0 .. ])
   where
-
    noCommonInputsTxs :: MonadTest m => [Tx] -> (Tx, Int) -> m ()
+
    noCommonInputsTxs :: MonadTest m => [TxBody] -> (TxBody, Int) -> m ()
    noCommonInputsTxs txs (tx, i) =
      traverse_ (\txj -> txins' txj ∩ txins' tx === empty) (take i txs)

                      
-
    txins' :: Tx -> Set TxIn
+
    txins' :: TxBody -> Set TxIn
    txins' = fromList . txins

                      
-- | Check that UTxO is outputs minus inputs
  when (all (\ti -> dom (txouts ti) ∩ dom utxo0 == empty) txs) $
    foldl' union' empty txs ⋪ (utxo0 ∪ allTxOuts txs) === utxoSt
 where
-
  union' :: Set TxIn -> Tx -> Set TxIn
+
  union' :: Set TxIn -> TxBody -> Set TxIn
  union' s tx = s `union` fromList (txins tx)

                      
-
  allTxOuts :: [Tx] -> UTxO
+
  allTxOuts :: [TxBody] -> UTxO
  allTxOuts txs = foldl' (∪) (UTxO Map.empty) (map txouts txs)

                      
utxoAndTxoutsMustBeDisjoint :: Property
      where
        pps_ = pps (tr ^. traceEnv)

                      
-
    -- | The difference between the Tx Fee and the Min Fee
-
    txFeeSurplus :: (Tx -> Lovelace) -> (UTxOState, TxWits) -> Integer
+
    -- | The difference between the TxBody Fee and the Min Fee
+
    txFeeSurplus :: (Tx -> Lovelace) -> (UTxOState, Tx) -> Integer
    txFeeSurplus txMinFee (st, txw)
      = fee - minFee
      where
          tx_ = body txw
          utxo_ = utxo st
          fee = unLovelace $ balance (txins tx_ ◁ utxo_) - balance (txouts tx_)
-
          minFee = unLovelace $ txMinFee tx_
+
          minFee = unLovelace $ txMinFee txw

                      
    -- | The intersection of the starting UTxO and each transaction in
    -- a trace
        (\ti -> dom (txouts ti) ∩ dom utxo0) <$> txs

                      
-- | Returns the average number of inputs and outputs for a list of transactions.
-
avgInputsOutputs :: [Tx] -> (Double, Double)
+
avgInputsOutputs :: [TxBody] -> (Double, Double)
avgInputsOutputs txs
  = case length txs of
      0 -> (0,0)

                      
  let
    pparams = pps (tr ^. traceEnv)
-
    -- Transaction with one input and one output
-
    unitTx = Tx [TxIn undefined 0] [TxOut undefined 100]
+
    -- Transaction with one input, one output and no witnesses
+
    unitTx = Tx (TxBody [TxIn undefined 0] [TxOut undefined 100]) []
    unitTxFee = pcMinFee pparams unitTx
  classify "Unit transaction cost == 0" $ unitTxFee == 0
  classify "Unit transaction cost == 1" $ unitTxFee == 1