Skip to content

Commit 9ca5939

Browse files
authored
Merge pull request #92 from mlabs-haskell/misha/return-ex-units
Misha/return ex units
2 parents 99d52dd + 6928644 commit 9ca5939

File tree

13 files changed

+212
-46
lines changed

13 files changed

+212
-46
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ main = do
100100
, -- Protocol params file location relative to the cardano-cli working directory (needed for the cli)
101101
, pcProtocolParamsFile = "./protocol.json"
102102
, pcEnableTxEndpoint = True
103+
-- Save some stats during contract run (only transactions execution budgets supported atm)
104+
, pcCollectStats = False
103105
}
104106
BotPlutusInterface.runPAB @MyContracts pabConf
105107
```

bot-plutus-interface.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ test-suite bot-plutus-interface-test
152152
other-modules:
153153
Spec.BotPlutusInterface.Balance
154154
Spec.BotPlutusInterface.Contract
155+
Spec.BotPlutusInterface.ContractStats
155156
Spec.BotPlutusInterface.Server
156157
Spec.BotPlutusInterface.UtxoParser
157158
Spec.MockContract

examples/plutus-game/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ main = do
6969
, pcLogLevel = Debug
7070
, pcProtocolParamsFile = "./protocol.json"
7171
, pcEnableTxEndpoint = True
72+
, pcCollectStats = False
7273
}
7374
BotPlutusInterface.runPAB @GameContracts pabConf

examples/plutus-nft/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ main = do
6969
, pcLogLevel = Debug
7070
, pcProtocolParamsFile = "./protocol.json"
7171
, pcEnableTxEndpoint = True
72+
, pcCollectStats = False
7273
}
7374
BotPlutusInterface.runPAB @MintNFTContracts pabConf

examples/plutus-transfer/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ main = do
6868
, pcLogLevel = Debug
6969
, pcProtocolParamsFile = "./protocol.json"
7070
, pcEnableTxEndpoint = True
71+
, pcCollectStats = False
7172
}
7273
BotPlutusInterface.runPAB @TransferContracts pabConf

src/BotPlutusInterface/CardanoCLI.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ import BotPlutusInterface.Files (
2424
txFilePath,
2525
validatorScriptFilePath,
2626
)
27-
import BotPlutusInterface.Types (MintBudgets, PABConfig, SpendBudgets, Tip, TxBudget, mintBudgets, spendBudgets)
27+
import BotPlutusInterface.Types (
28+
MintBudgets,
29+
PABConfig,
30+
SpendBudgets,
31+
Tip,
32+
TxBudget,
33+
mintBudgets,
34+
spendBudgets,
35+
)
2836
import BotPlutusInterface.UtxoParser qualified as UtxoParser
2937
import Cardano.Api.Shelley (NetworkId (Mainnet, Testnet), NetworkMagic (..), serialiseAddress)
3038
import Control.Monad (join)

src/BotPlutusInterface/Contract.hs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,24 @@ import BotPlutusInterface.Effects (
1111
ShellArgs (..),
1212
callCommand,
1313
createDirectoryIfMissing,
14+
estimateBudget,
1415
handlePABEffect,
1516
logToContract,
1617
printLog,
1718
queryChainIndex,
1819
readFileTextEnvelope,
20+
saveBudget,
1921
threadDelay,
2022
uploadDir,
2123
)
2224
import BotPlutusInterface.Files (DummyPrivKey (FromSKey, FromVKey))
2325
import BotPlutusInterface.Files qualified as Files
24-
import BotPlutusInterface.Types (ContractEnvironment (..), LogLevel (Debug, Warn), Tip (block, slot))
26+
import BotPlutusInterface.Types (
27+
ContractEnvironment (..),
28+
LogLevel (Debug, Warn),
29+
Tip (block, slot),
30+
TxFile (Signed),
31+
)
2532
import Cardano.Api (AsType (..), EraInMode (..), Tx (Tx))
2633
import Control.Lens (preview, (^.))
2734
import Control.Monad (join, void, when)
@@ -271,8 +278,14 @@ writeBalancedTx contractEnv (Right tx) = do
271278
newEitherT $ CardanoCLI.submitTx @w pabConf tx
272279

273280
-- We need to replace the outfile we created at the previous step, as it currently still has the old (incorrect) id
274-
mvFiles (Files.txFilePath pabConf "raw" (Tx.txId tx)) (Files.txFilePath pabConf "raw" (Ledger.getCardanoTxId $ Left cardanoTx))
275-
when signable $ mvFiles (Files.txFilePath pabConf "signed" (Tx.txId tx)) (Files.txFilePath pabConf "signed" (Ledger.getCardanoTxId $ Left cardanoTx))
281+
let cardanoTxId = Ledger.getCardanoTxId $ Left cardanoTx
282+
signedSrcPath = Files.txFilePath pabConf "signed" (Tx.txId tx)
283+
signedDstPath = Files.txFilePath pabConf "signed" cardanoTxId
284+
mvFiles (Files.txFilePath pabConf "raw" (Tx.txId tx)) (Files.txFilePath pabConf "raw" cardanoTxId)
285+
when signable $ mvFiles signedSrcPath signedDstPath
286+
287+
when contractEnv.cePABConfig.pcCollectStats $
288+
collectBudgetStats cardanoTxId signedDstPath
276289

277290
pure cardanoTx
278291
where
@@ -286,6 +299,11 @@ writeBalancedTx contractEnv (Right tx) = do
286299
, cmdOutParser = const ()
287300
}
288301

302+
collectBudgetStats txId txPath = do
303+
let path = Text.unpack txPath
304+
b <- firstEitherT (Text.pack . show) $ newEitherT $ estimateBudget @w (Signed path)
305+
void $ newEitherT (Right <$> saveBudget @w txId b)
306+
289307
pkhToText :: Ledger.PubKey -> Text
290308
pkhToText = encodeByteString . fromBuiltin . Ledger.getPubKeyHash . Ledger.pubKeyHash
291309

src/BotPlutusInterface/Effects.hs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module BotPlutusInterface.Effects (
2020
writeFileTextEnvelope,
2121
callCommand,
2222
estimateBudget,
23+
saveBudget,
2324
) where
2425

2526
import BotPlutusInterface.ChainIndex (handleChainIndexReq)
@@ -32,11 +33,12 @@ import BotPlutusInterface.Types (
3233
LogLevel (..),
3334
TxBudget,
3435
TxFile,
36+
addBudget,
3537
)
3638
import Cardano.Api (AsType, FileError, HasTextEnvelope, TextEnvelopeDescr, TextEnvelopeError)
3739
import Cardano.Api qualified
3840
import Control.Concurrent qualified as Concurrent
39-
import Control.Concurrent.STM (atomically, modifyTVar)
41+
import Control.Concurrent.STM (atomically, modifyTVar, modifyTVar')
4042
import Control.Monad (void, when)
4143
import Control.Monad.Freer (Eff, LastMember, Member, interpretM, send, type (~>))
4244
import Data.Aeson (ToJSON)
@@ -47,6 +49,7 @@ import Data.Maybe (catMaybes)
4749
import Data.String (IsString, fromString)
4850
import Data.Text (Text)
4951
import Data.Text qualified as Text
52+
import Ledger qualified
5053
import Plutus.Contract.Effects (ChainIndexQuery, ChainIndexResponse)
5154
import Plutus.PAB.Core.ContractInstance.STM (Activity)
5255
import System.Directory qualified as Directory
@@ -88,6 +91,7 @@ data PABEffect (w :: Type) (r :: Type) where
8891
UploadDir :: Text -> PABEffect w ()
8992
QueryChainIndex :: ChainIndexQuery -> PABEffect w ChainIndexResponse
9093
EstimateBudget :: TxFile -> PABEffect w (Either BudgetEstimationError TxBudget)
94+
SaveBudget :: Ledger.TxId -> TxBudget -> PABEffect w ()
9195

9296
handlePABEffect ::
9397
forall (w :: Type) (effs :: [Type -> Type]).
@@ -132,6 +136,7 @@ handlePABEffect contractEnv =
132136
handleChainIndexReq contractEnv.cePABConfig query
133137
EstimateBudget txPath ->
134138
ExBudget.estimateBudget contractEnv.cePABConfig txPath
139+
SaveBudget txId exBudget -> saveBudgetImpl contractEnv txId exBudget
135140
)
136141

137142
printLog' :: LogLevel -> LogLevel -> String -> IO ()
@@ -168,6 +173,11 @@ readProcessEither path args =
168173
mapToEither (ExitFailure exitCode, _, stderr) =
169174
Left $ "ExitCode " <> Text.pack (show exitCode) <> ": " <> Text.pack stderr
170175

176+
saveBudgetImpl :: ContractEnvironment w -> Ledger.TxId -> TxBudget -> IO ()
177+
saveBudgetImpl contractEnv txId budget =
178+
atomically $
179+
modifyTVar' contractEnv.ceContractStats (addBudget txId budget)
180+
171181
-- Couldn't use the template haskell makeEffect here, because it caused an OverlappingInstances problem.
172182
-- For some reason, we need to manually propagate the @w@ type variable to @send@
173183

@@ -267,3 +277,11 @@ queryChainIndex ::
267277
ChainIndexQuery ->
268278
Eff effs ChainIndexResponse
269279
queryChainIndex = send @(PABEffect w) . QueryChainIndex
280+
281+
saveBudget ::
282+
forall (w :: Type) (effs :: [Type -> Type]).
283+
Member (PABEffect w) effs =>
284+
Ledger.TxId ->
285+
TxBudget ->
286+
Eff effs ()
287+
saveBudget txId budget = send @(PABEffect w) $ SaveBudget txId budget

src/BotPlutusInterface/Server.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ handleContract ::
272272
handleContract pabConf state@(AppState st) contract = liftIO $ do
273273
contractInstanceID <- liftIO $ ContractInstanceId <$> UUID.nextRandom
274274
contractState <- newTVarIO (ContractState Active mempty)
275+
contractStats <- newTVarIO mempty
275276

276277
atomically $ modifyTVar st (Map.insert contractInstanceID (SomeContractState contractState))
277278

@@ -280,6 +281,7 @@ handleContract pabConf state@(AppState st) contract = liftIO $ do
280281
{ cePABConfig = pabConf
281282
, ceContractState = contractState
282283
, ceContractInstanceId = contractInstanceID
284+
, ceContractStats = contractStats
283285
}
284286
void $
285287
forkIO $ do

src/BotPlutusInterface/Types.hs

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module BotPlutusInterface.Types (
2020
BudgetEstimationError (..),
2121
SpendBudgets,
2222
MintBudgets,
23+
ContractStats (..),
24+
addBudget,
2325
) where
2426

2527
import Cardano.Api (NetworkId (Testnet), NetworkMagic (..), ScriptExecutionError, ScriptWitnessIndex)
@@ -31,13 +33,15 @@ import Data.Aeson.TH (Options (..), defaultOptions, deriveJSON)
3133
import Data.Default (Default (def))
3234
import Data.Kind (Type)
3335
import Data.Map (Map)
36+
import Data.Map qualified as Map
3437
import Data.Text (Text)
3538
import GHC.Generics (Generic)
3639
import Ledger (
3740
ExBudget,
3841
MintingPolicyHash,
3942
PubKeyHash,
4043
StakePubKeyHash,
44+
TxId,
4145
TxOutRef,
4246
)
4347
import Ledger.TimeSlot (SlotConfig)
@@ -77,13 +81,72 @@ data PABConfig = PABConfig
7781
, pcTipPollingInterval :: !Natural
7882
, pcPort :: !Port
7983
, pcEnableTxEndpoint :: !Bool
84+
, pcCollectStats :: !Bool
8085
}
8186
deriving stock (Show, Eq)
8287

88+
-- Budget estimation types
89+
90+
{- | Error returned in case any error happened during budget estimation
91+
(wraps whatever received in `Text`)
92+
-}
93+
data BudgetEstimationError
94+
= -- | general error for `Cardano.Api` errors
95+
BudgetEstimationError !Text
96+
| -- | script evaluation failed during budget estimation
97+
ScriptFailure ScriptExecutionError
98+
| {- budget for input or policy was not found after estimation
99+
(arguably should not happen at all) -}
100+
BudgetNotFound ScriptWitnessIndex
101+
deriving stock (Show)
102+
103+
-- | Type of transaction file used for budget estimation
104+
data TxFile
105+
= -- | for using with ".raw" files
106+
Raw !FilePath
107+
| -- | for using with ".signed" files
108+
Signed !FilePath
109+
110+
-- | Result of budget estimation
111+
data TxBudget = TxBudget
112+
{ -- | budgets for spending inputs
113+
spendBudgets :: !SpendBudgets
114+
, -- | budgets for minting policies
115+
mintBudgets :: !MintBudgets
116+
}
117+
deriving stock (Show)
118+
119+
addBudget :: TxId -> TxBudget -> ContractStats -> ContractStats
120+
addBudget txId budget stats =
121+
stats {estimatedBudgets = Map.insert txId budget (estimatedBudgets stats)}
122+
123+
instance Semigroup TxBudget where
124+
TxBudget s m <> TxBudget s' m' = TxBudget (s <> s') (m <> m')
125+
126+
instance Monoid TxBudget where
127+
mempty = TxBudget mempty mempty
128+
129+
type SpendBudgets = Map TxOutRef ExBudget
130+
131+
type MintBudgets = Map MintingPolicyHash ExBudget
132+
133+
{- | Collection of stats that could be collected py `bpi`
134+
about contract it runs
135+
-}
136+
newtype ContractStats = ContractStats
137+
{ estimatedBudgets :: Map TxId TxBudget
138+
}
139+
deriving stock (Show)
140+
deriving newtype (Semigroup, Monoid)
141+
142+
instance Show (TVar ContractStats) where
143+
show _ = "<ContractStats>"
144+
83145
data ContractEnvironment w = ContractEnvironment
84146
{ cePABConfig :: PABConfig
85147
, ceContractInstanceId :: ContractInstanceId
86148
, ceContractState :: TVar (ContractState w)
149+
, ceContractStats :: TVar ContractStats
87150
}
88151
deriving stock (Show)
89152

@@ -140,6 +203,7 @@ instance Default PABConfig where
140203
, pcOwnStakePubKeyHash = Nothing
141204
, pcPort = 9080
142205
, pcEnableTxEndpoint = False
206+
, pcCollectStats = False
143207
}
144208

145209
data RawTx = RawTx
@@ -152,44 +216,3 @@ data RawTx = RawTx
152216
-- type is a reserved keyword in haskell and can not be used as a field name
153217
-- when converting this to JSON we drop the _ prefix from each field
154218
deriveJSON defaultOptions {fieldLabelModifier = drop 1} ''RawTx
155-
156-
-- Budget estimation types
157-
158-
{- | Error returned in case any error happened during budget estimation
159-
(wraps whatever received in `Text`)
160-
-}
161-
data BudgetEstimationError
162-
= -- | general error for `Cardano.Api` errors
163-
BudgetEstimationError !Text
164-
| -- | script evaluation failed during budget estimation
165-
ScriptFailure ScriptExecutionError
166-
| {- budget for input or policy was not found after estimation
167-
(arguably should not happen at all) -}
168-
BudgetNotFound ScriptWitnessIndex
169-
deriving stock (Show)
170-
171-
-- | Type of transaction file used for budget estimation
172-
data TxFile
173-
= -- | for using with ".raw" files
174-
Raw !FilePath
175-
| -- | for using with ".signed" files
176-
Signed !FilePath
177-
178-
-- | Result of budget estimation
179-
data TxBudget = TxBudget
180-
{ -- | budgets for spending inputs
181-
spendBudgets :: !SpendBudgets
182-
, -- | budgets for minting policies
183-
mintBudgets :: !MintBudgets
184-
}
185-
deriving stock (Show)
186-
187-
instance Semigroup TxBudget where
188-
TxBudget s m <> TxBudget s' m' = TxBudget (s <> s') (m <> m')
189-
190-
instance Monoid TxBudget where
191-
mempty = TxBudget mempty mempty
192-
193-
type SpendBudgets = Map TxOutRef ExBudget
194-
195-
type MintBudgets = Map MintingPolicyHash ExBudget

0 commit comments

Comments
 (0)