morley-1.19.1: app/Main.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
module Main
( main
) where
import Data.Default (def)
import Data.Singletons (demote)
import Data.Text.Lazy.IO.Utf8 qualified as Utf8 (writeFile)
import Data.Version (showVersion)
import Fmt (pretty, unlinesF)
import Named (paramF)
import Options.Applicative
(execParser, footerDoc, fullDesc, header, help, helper, info, infoOption, long, progDesc, short,
subparser, switch)
import Options.Applicative qualified as Opt
import Options.Applicative.Help.Pretty (Doc, linebreak)
import Paths_morley (version)
import Text.Hex (encodeHex)
import Morley.CLI
import Morley.Michelson.Analyzer (analyze)
import Morley.Michelson.Interpret (RemainingSteps(..))
import Morley.Michelson.Optimizer (OptimizerConf(..), optimizeVerboseWithConf)
import Morley.Michelson.Printer (printSomeContract, printTypedContract, printUntypedContract)
import Morley.Michelson.Runtime
import Morley.Michelson.Runtime.GState (genesisAddress)
import Morley.Michelson.TypeCheck (tcVerbose, typeCheckContract, typeCheckValue, typeCheckingWith)
import Morley.Michelson.TypeCheck qualified as TypeCheck
import Morley.Michelson.Typed (Contract'(..), SomeContract(..), unContractCode)
import Morley.Michelson.Typed qualified as T
import Morley.Michelson.Typed.Contract (mapContractCodeM)
import Morley.Tezos.Address
import Morley.Tezos.Address.Alias
import Morley.Tezos.Core (Mutez, tz)
import Morley.Tezos.Crypto.Timelock (chestBytes, chestKeyBytes, createChestAndChestKey)
import Morley.Util.CLI (mkCLOptionParser, mkCommandParser, outputOption)
import Morley.Util.Main (wrapMain)
import Morley.Util.Named
import REPL
data DryRunOrWrite = DryRun | Write
mkCommandParser' :: String -> String -> Opt.Parser a -> Opt.Mod Opt.CommandFields a
mkCommandParser' = flip . mkCommandParser
argParser :: Opt.Parser (IO ())
argParser = subparser $ mconcat
[ printSubCmd
, typecheckSubCmd
, emulateSubCmd
, optimizeSubCmd
, analyzeSubCmd
, createChestSubCmd
, replSubCmd
]
--------------------------------------------------------------------------------
-- Subcommands
--------------------------------------------------------------------------------
-- NB: in case this looks mysterious, in IOCmd functions, outer 'do' is
-- Opt.Parser using ApplicativeDo, which then returns an IO action, i.e. inner
-- 'do' is 'IO ()'. -- @lierdakil
type IOCmd = Opt.Mod Opt.CommandFields (IO ())
typecheckSubCmd :: IOCmd
typecheckSubCmd = mkCommandParser' "typecheck" "Typecheck passed contract." do
contractFile <- optional contractFileOption
tcOptions <- typeCheckOptionsOption
pure do
morleyContract <- prepareContract contractFile
-- At the moment of writing, 'tcStrict' option does not change anything
-- because it affects only values parsing; but this may change
contract <- either throwM pure . typeCheckingWith tcOptions
$ typeCheckContract morleyContract
when (TypeCheck.tcVerbose tcOptions) $
putStrLn $ printSomeContract False contract
putTextLn "Contract is well-typed"
printSubCmd :: IOCmd
printSubCmd = mkCommandParser' "print"
"Parse a Morley contract and print corresponding Michelson \
\contract that can be parsed by the OCaml reference client: octez-client."
do
mInputFile <- optional contractFileOption
mOutputFile <- outputOption
forceSingleLine <- onelineOption
pure do
contract <- prepareContract mInputFile
let write = maybe putStrLn Utf8.writeFile mOutputFile
write $ printUntypedContract forceSingleLine contract
emulateSubCmd :: IOCmd
emulateSubCmd = mkCommandParser' "emulate" "Set of commands to run in an emulated environment." $
subparser $ mconcat
[ runSubCmd
, originateSubCmd
, transferSubCmd
, transferTicketSubCmd
, runViewSubCmd
]
runSubCmd :: IOCmd
runSubCmd = mkCommandParser' "run"
"Run passed contract. It's originated first and then a transaction is sent to it." do
txData <- txDataOption
contract <- contractSimpleOriginationDataOption
cro <- commonRunOptions Write
pure do
michelsonContract <- traverse prepareContract contract
void $ runContract cro michelsonContract txData
runViewSubCmd :: IOCmd
runViewSubCmd = mkCommandParser' "view"
"Run some view on a contract either supplied directly or identified \
\by an address. It's originated first if supplied directly." do
cro <- commonRunOptions Write
addressOrContract <-
ContractSpecAddressOrAlias <$> addressOrAliasOption Nothing
! #name "contract-addr"
! #help "Contract address to call view on."
<|> ContractSpecOrigination <$> contractSimpleOriginationDataOption
viewName <- mkCLOptionParser Nothing
! #name "name"
! #help "View name."
sender <- someAddressOrAliasOption
(Just $ SAOAKindSpecified $ AddressResolved genesisAddress)
! #name "sender"
! #help "Sender address."
viewArg <- TxUntypedParam <$> valueOption Nothing
! #name "arg"
! #help "View call argument."
pure do
contractSpec <- (traverse . traverse) prepareContract addressOrContract
void $ runView cro contractSpec viewName sender viewArg
replSubCmd :: IOCmd
replSubCmd = mkCommandParser' "repl" "Start a Morley REPL." $ pure runRepl
originateSubCmd :: IOCmd
originateSubCmd = mkCommandParser' "originate" "Originate passed contract. Add it to passed DB." do
simpleOriginationData <- contractSimpleOriginationDataOption
dbPath <- dbPathOption
tcOptions <- typeCheckOptionsOption
verbose <- verboseFlag
originator <- addressOption (Just genesisAddress)
! #name "originator"
! #help "Contract's originator."
alias <- optional (aliasOption "alias")
delegate <- optional $ keyHashOption Nothing
! #name "delegate"
! #help "Contract's optional delegate."
pure do
michelsonContract <- traverse prepareContract simpleOriginationData
addr <-
originateContract
! #dbPath dbPath
! #tcOpts tcOptions
! #originator originator
! paramF #alias alias
! paramF #delegate delegate
! #csod michelsonContract
! #verbose verbose
putTextLn $ "Originated contract " <> pretty addr
transferSubCmd :: IOCmd
transferSubCmd = mkCommandParser' "transfer" "Transfer tokens to given address." do
destination <- someAddressOrAliasOption Nothing
! #name "to"
! #help "Address or alias of the transfer's destination."
txData <- txDataOption
cro <- commonRunOptions DryRun
pure $ transfer cro destination txData
transferTicketSubCmd :: IOCmd
transferTicketSubCmd = mkCommandParser' "transfer-ticket" "Transfer ticket to given address." do
destination <- someAddressOrAliasOption Nothing
! #name "to"
! #help "Address or alias of the transfer's destination."
tdSenderAddress :: L1Address <- Constrained <$> addressOption (Just genesisAddress)
! #name "sender" ! #help "Sender address"
ticketer <- mkCLOptionParser @Address Nothing
! #name "ticketer" ! #help "Ticketer"
value <- valueOption Nothing
! #name "value" ! #help "Ticket value"
ty <- mkCLOptionParser Nothing
! #name "type" ! #help "Ticket argument type"
tAmount <- mkCLOptionParser @Natural Nothing
! #name "amount" ! #help "Amount of tickets"
tdAmount <- mutezOption (Just minBound)
! #name "mutez" ! #help "Mutez amount additionally sent by a transaction. \
\Note that on network, as of Mumbai, implicit accounts can't send tickets \
\and mutez in the same operation, however the Morley emulator allows it."
tdEntrypoint <- entrypointOption
! #name "entrypoint" ! #help "Entrypoint to call"
cro <- commonRunOptions DryRun
pure $ T.withUType ty \(_ :: T.Notes t) -> do
tValue <- either throwM pure . typeCheckingWith (croTCOpts cro) $
typeCheckValue @t value
T.Dict <- either (throwM . TypeCheck.UnsupportedTypeForScope (demote @t)) pure $
T.checkScope @(T.ParameterScope t, T.Comparable t)
let tdParameter = TxTypedParam $ T.VTicket ticketer tValue tAmount
transfer cro destination TxData{..}
optimizeSubCmd :: IOCmd
optimizeSubCmd = mkCommandParser' "optimize" "Optimize the contract." do
contractFile <- optional contractFileOption
output <- outputOption
singleLine <- onelineOption
maxStageIterations <- mkCLOptionParser (Just $ ocMaxIterations def)
! #name "max-stage-iterations"
! #help "Maximum number of iterations per optimizer stage. \
\The default is usually adequate, but you want to try raising it \
\to see if it affects the result."
verbose <- verboseFlag
pure do
untypedContract <- prepareContract contractFile
SomeContract checkedContract <-
either throwM pure . typeCheckingWith laxTcOptions $
typeCheckContract untypedContract
let (logs, optimizedContract) =
mapContractCodeM (optimizeVerboseWithConf conf) checkedContract
conf = def { ocMaxIterations = maxStageIterations }
maybe putStrLn Utf8.writeFile output $ printTypedContract singleLine optimizedContract
when verbose $ hPutStrLn stderr $ pretty @_ @Text $ unlinesF logs
analyzeSubCmd :: IOCmd
analyzeSubCmd = mkCommandParser' "analyze" "Analyze the contract." do
contractFile <- optional contractFileOption
pure do
untypedContract <- prepareContract contractFile
SomeContract contract <-
either throwM pure . typeCheckingWith laxTcOptions $
typeCheckContract untypedContract
putTextLn $ pretty $ analyze (unContractCode $ cCode contract)
createChestSubCmd :: IOCmd
createChestSubCmd = mkCommandParser' "create_chest" "Create a timelocked chest and key." do
payload <- payloadOption
time <- timeOption
pure do
(chest, key) <- createChestAndChestKey payload time
putStrLn $ "Chest: 0x" <> encodeHex (chestBytes chest)
putStrLn $ "Key: 0x" <> encodeHex (chestKeyBytes key)
--------------------------------------------------------------------------------
-- Parsers
--------------------------------------------------------------------------------
verboseFlag :: Opt.Parser Bool
verboseFlag = switch $
short 'v' <>
long "verbose" <>
help "Whether output should be verbose."
typeCheckOptionsOption :: Opt.Parser TypeCheck.TypeCheckOptions
typeCheckOptionsOption = do
tcVerbose <- verboseFlag
tcStrict <- fmap not . switch $
long "typecheck-lax" <>
help "Whether actions permitted in `octez-client run` but forbidden in \
\e.g. `octez-client originate` should be allowed here."
return TypeCheck.TypeCheckOptions{..}
commonRunOptions :: DryRunOrWrite -> Opt.Parser CommonRunOptions
commonRunOptions defaultDryRun = do
croNow <- nowOption
croLevel <- fromMaybe (croLevel def) <$> levelOption
croMinBlockTime <- fromMaybe (croMinBlockTime def) <$> minBlockTimeOption
croMaxSteps <- RemainingSteps <$> maxStepsOption
croDBPath <- dbPathOption
croTCOpts <- typeCheckOptionsOption
croVerbose <- verboseFlag
croDryRun <- case defaultDryRun of
Write -> fmap not . switch $
long "write" <>
help "Write updated DB to the DB file."
DryRun -> switch $
long "dry-run" <>
help "Do not write updated DB to the DB file."
pure CommonRunOptions{..}
contractSimpleOriginationDataOption :: Opt.Parser (ContractSimpleOriginationData (Maybe FilePath))
contractSimpleOriginationDataOption = do
csodStorage <- valueOption Nothing
! #name "storage"
! #help "Initial storage of the contract."
csodContract <- optional contractFileOption
csodBalance <- mutezOption (Just defaultBalance)
! #name "balance"
! #help "Initial balance of the contract."
pure ContractSimpleOriginationData{..}
--------------------------------------------------------------------------------
-- Constants
--------------------------------------------------------------------------------
defaultBalance :: Mutez
defaultBalance = [tz|4|]
-- | Most permitting options, when we don't care much about typechecking.
laxTcOptions :: TypeCheck.TypeCheckOptions
laxTcOptions = TypeCheck.TypeCheckOptions
{ TypeCheck.tcVerbose = False
, TypeCheck.tcStrict = False
}
--------------------------------------------------------------------------------
-- Actual main
--------------------------------------------------------------------------------
main :: IO ()
main = wrapMain $ join $ execParser programInfo
where
programInfo = info (helper <*> versionOption <*> argParser) $
mconcat
[ fullDesc
, progDesc "Morley: Haskell implementation of Michelson typechecker and interpreter"
, header "Morley tools"
, footerDoc $ usageDoc
]
versionOption = infoOption ("morley-" <> showVersion version)
(long "version" <> help "Show version.")
usageDoc :: Maybe Doc
usageDoc = Just $ mconcat
[ "You can use help for specific COMMAND", linebreak
, "EXAMPLE:", linebreak
, " morley emulate run --help", linebreak
, linebreak
, "Documentation for morley tools can be found at the following links:", linebreak
, " https://gitlab.com/morley-framework/morley/blob/master/README.md", linebreak
, " https://gitlab.com/morley-framework/morley/tree/master/docs", linebreak
, linebreak
, "Sample contracts for running can be found at the following link:", linebreak
, " https://gitlab.com/morley-framework/morley/tree/master/contracts", linebreak
, linebreak
, "USAGE EXAMPLE:", linebreak
, " morley parse --contract add1.tz", linebreak
, linebreak
, " This command will parse contract stored in add1.tz", linebreak
, " and return its representation in haskell types", linebreak
, linebreak
, " morley emulate originate --contract add1.tz --storage 1 --verbose", linebreak
, linebreak
, " This command will originate contract with code stored in add1.tz", linebreak
, " with initial storage value set to 1 and return info about", linebreak
, " originated contract: its balance, storage and contract code"]