create initial POC script generating API

This commit is contained in:
Emily Martins 2022-06-21 14:48:31 +02:00
parent 564b1c4e66
commit e862de7e59
9 changed files with 210 additions and 82 deletions

View file

@ -1,68 +1,49 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{- |
Module : API
Maintainer : emi@haskell.fyi
Description: API for script exporter.
API for script exporter. {- | Module : API
Maintainer : emi@haskell.fyi
Description: API for script exporter.
API for script exporter.
-} -}
module API (
AgoraScripts (..),
ScriptParams (..),
API,
agoraScripts,
runServer,
) where
module API (AgoraScripts(..), ScriptParams(..), API, agoraScripts, runServer) where import Agora.AuthorityToken (AuthorityToken (..), authorityTokenPolicy)
import Agora.Governor (Governor (..))
import Servant.API (type (:>), ReqBody, JSON, Post) import Agora.Governor qualified as Governor
import PlutusLedgerApi.V1 (TxOutRef) import Agora.Governor.Scripts (authorityTokenFromGovernor, authorityTokenSymbolFromGovernor, governorPolicy, governorValidator, proposalFromGovernor, stakeFromGovernor)
import Data.Tagged (Tagged) import Agora.Proposal (Proposal (..))
import Agora.SafeMoney (GTTag) import Agora.Proposal.Scripts (proposalPolicy, proposalValidator)
import PlutusLedgerApi.V1.Value (AssetClass, CurrencySymbol) import Agora.ScriptInfo (mkPolicyInfo, mkValidatorInfo)
import qualified Data.Aeson as Aeson import Agora.Stake (Stake (..))
import qualified GHC.Generics as GHC import Agora.Stake.Scripts (stakePolicy, stakeValidator)
import Agora.ScriptInfo (PolicyInfo, ValidatorInfo, mkPolicyInfo, mkValidatorInfo)
import Agora.Stake (Stake(..))
import Agora.Proposal (Proposal(..))
import Agora.AuthorityToken (AuthorityToken(..), authorityTokenPolicy)
import Agora.Governor (Governor(..))
import Development.GitRev (gitBranch, gitHash)
import qualified Agora.Governor as Governor
import qualified PlutusLedgerApi.V1.Value as Value
import Plutarch.Api.V1 (mintingPolicySymbol, mkMintingPolicy)
import Agora.Governor.Scripts (governorPolicy, authorityTokenSymbolFromGovernor, authorityTokenFromGovernor, proposalFromGovernor, stakeFromGovernor, governorValidator)
import Agora.Treasury (treasuryValidator) import Agora.Treasury (treasuryValidator)
import Agora.Proposal.Scripts (proposalValidator, proposalPolicy) import Codec.Serialise.Orphans ()
import Agora.Stake.Scripts (stakeValidator, stakePolicy) import Data.Cache.Cached (cachedFor)
import qualified Servant.Server as Servant
import Data.Proxy (Proxy(Proxy))
import qualified Network.Wai.Handler.Warp as Warp
import Data.Function ((&)) import Data.Function ((&))
import qualified Network.Wai as Wai import Data.Proxy (Proxy (Proxy))
import qualified Network.HTTP.Types as Http import Development.GitRev (gitBranch, gitHash)
import Prettyprinter (layoutPretty, defaultLayoutOptions, hsep, viaShow, (<+>)) import Network.HTTP.Types qualified as Http
import Network.Wai qualified as Wai
import Network.Wai.Handler.Warp qualified as Warp
import Network.Wai.Middleware.Cors (CorsResourcePolicy (corsRequestHeaders), cors, simpleCorsResourcePolicy)
import Options (Options (..))
import Plutarch.Api.V1 (mintingPolicySymbol, mkMintingPolicy)
import PlutusLedgerApi.V1.Value (AssetClass, CurrencySymbol)
import PlutusLedgerApi.V1.Value qualified as Value
import Prettyprinter (defaultLayoutOptions, hsep, layoutPretty, viaShow, (<+>))
import Prettyprinter.Render.String (renderString) import Prettyprinter.Render.String (renderString)
import Options (Options(..)) import Servant.API (JSON, Post, ReqBody, type (:>))
import Servant.Server qualified as Servant
import System.Clock (TimeSpec (TimeSpec))
import Text.Printf (printf) import Text.Printf (printf)
import Types (AgoraScripts (..), ScriptParams (..))
-- | Params required for creating script export.
data ScriptParams = ScriptParams
{ governorInitialSpend :: TxOutRef
, gtClassRef :: Tagged GTTag AssetClass
, maximumCosigners :: Integer
}
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
deriving stock (Show, Eq, GHC.Generic)
-- | Scripts that get exported.
data AgoraScripts = AgoraScripts
{ gitRev :: String
, governorPolicyInfo :: PolicyInfo
, governorValidatorInfo :: ValidatorInfo
, stakePolicyInfo :: PolicyInfo
, stakeValidatorInfo :: ValidatorInfo
, proposalPolicyInfo :: PolicyInfo
, proposalValidatorInfo :: ValidatorInfo
, treasuryValidatorInfo :: ValidatorInfo
, authorityTokenPolicyInfo :: PolicyInfo
}
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
deriving stock (Show, Eq, GHC.Generic)
-- | Servant API type for script generation. -- | Servant API type for script generation.
type API = "generate-scripts" :> ReqBody '[JSON] ScriptParams :> Post '[JSON] AgoraScripts type API = "generate-scripts" :> ReqBody '[JSON] ScriptParams :> Post '[JSON] AgoraScripts
@ -84,8 +65,20 @@ runServer options = do
] ]
) )
corsPolicy =
simpleCorsResourcePolicy
{ -- NOTE: Webpack dev server requires this for CORS workaround.
corsRequestHeaders = "content-type" : corsRequestHeaders simpleCorsResourcePolicy
}
corsMiddleware = cors . const $ Just corsPolicy
-- Scripts stay cached for five minutes
agoraScripts' <- cachedFor (Just $ TimeSpec 300 0) agoraScripts
printf "[info] Running 'agora-scripts' on :%d\n" (Warp.getPort settings) printf "[info] Running 'agora-scripts' on :%d\n" (Warp.getPort settings)
Warp.runSettings settings $ Servant.serve (Proxy @API) (pure . agoraScripts) Servant.serve (Proxy @API) agoraScripts'
& corsMiddleware
& Warp.runSettings settings
-- | Create scripts from params. -- | Create scripts from params.
agoraScripts :: ScriptParams -> AgoraScripts agoraScripts :: ScriptParams -> AgoraScripts
@ -102,6 +95,8 @@ agoraScripts params =
, authorityTokenPolicyInfo = mkPolicyInfo (authorityTokenPolicy authorityToken) , authorityTokenPolicyInfo = mkPolicyInfo (authorityTokenPolicy authorityToken)
} }
where where
-- This encodes the git revision of the server. It's useful for the caller
-- to be able to ensure they are compatible with it.
revision :: String revision :: String
revision = $(gitBranch) <> "@" <> $(gitHash) revision = $(gitBranch) <> "@" <> $(gitHash)

1
agora-scripts/Cache.hs Normal file
View file

@ -0,0 +1 @@
module Cache where

View file

@ -0,0 +1,41 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{- |
Module : Codec.Serialise.Orphans
Maintainer : emi@haskell.fyi
Description: Orphan instances for Serialising and Hashing Cardano types.
Orphan instances for Serialising and Hashing Cardano types.
-}
module Codec.Serialise.Orphans () where
import Codec.Serialise (Serialise, serialise)
import Data.Hashable (Hashable (hashWithSalt))
import Data.Tagged (Tagged (Tagged))
import PlutusLedgerApi.V1 (TxId, TxOutRef)
import PlutusLedgerApi.V1.Value (AssetClass, CurrencySymbol, TokenName)
import Types (ScriptParams)
deriving anyclass instance
Serialise TxOutRef
deriving anyclass instance
Serialise TxId
deriving anyclass instance
Serialise AssetClass
deriving anyclass instance
Serialise CurrencySymbol
deriving anyclass instance
Serialise TokenName
deriving newtype instance
Serialise a =>
Serialise (Tagged s a)
deriving anyclass instance Serialise ScriptParams
instance Hashable ScriptParams where
hashWithSalt s scriptParams = hashWithSalt s (serialise scriptParams)

View file

@ -0,0 +1,41 @@
{- | Module : API
Maintainer : emi@haskell.fyi
Description: API for script exporter.
API for script exporter.
-}
module Data.Cache.Cached (
cached,
cachedFor,
) where
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Cache qualified as Cache
import Data.Functor ((<&>))
import Data.Hashable (Hashable)
import System.Clock (TimeSpec)
{- | 'cachedFor' but items last forever.
Uses a HashMap under the hood.
-}
cached :: (Monad m, MonadIO m, Hashable k, Ord k) => (k -> v) -> IO (k -> m v)
cached = cachedFor Nothing
{- | Create a cached version of a function tainting result with MonadIO context.
Results are cached dependent on the first argument, @'Maybe' 'TimeSpec'@.
Uses a HashMap under the hood.
-}
cachedFor :: (Monad m, MonadIO m, Hashable k, Ord k) => Maybe TimeSpec -> (k -> v) -> IO (k -> m v)
cachedFor t f =
Cache.newCache t <&> \cache k -> do
res <- liftIO $ Cache.lookup cache k
case res of
Nothing -> do
let v = f k
liftIO $ Cache.insert cache k v
pure v
Just v -> do
pure v

View file

@ -7,9 +7,9 @@ Command line options for 'agora-scripts'.
-} -}
module Options (Options (..), parseOptions) where module Options (Options (..), parseOptions) where
import Network.Wai.Handler.Warp qualified as Warp
import Options.Applicative ((<**>)) import Options.Applicative ((<**>))
import Options.Applicative qualified as Opt import Options.Applicative qualified as Opt
import qualified Network.Wai.Handler.Warp as Warp
data Options = Options data Options = Options
{ port :: Warp.Port { port :: Warp.Port
@ -19,7 +19,8 @@ data Options = Options
opt :: Opt.Parser Options opt :: Opt.Parser Options
opt = opt =
Options Options
<$> Opt.option Opt.auto <$> Opt.option
Opt.auto
( Opt.long "port" ( Opt.long "port"
<> Opt.short 'p' <> Opt.short 'p'
<> Opt.metavar "PORT" <> Opt.metavar "PORT"

View file

@ -1,19 +1,14 @@
{-# LANGUAGE TemplateHaskell #-} {- | Module : Scripts
Maintainer : emi@haskell.fyi
Description: Export scripts given configuration.
{- | Export scripts given configuration.
Module : Scripts
Maintainer : emi@haskell.fyi
Description: Export scripts given configuration.
Export scripts given configuration.
-} -}
module Main (main) where module Scripts (main) where
import Options (parseOptions)
import API (runServer) import API (runServer)
import Options (parseOptions)
main :: IO () main :: IO ()
main = do main =
options <- parseOptions parseOptions >>= runServer
runServer options

42
agora-scripts/Types.hs Normal file
View file

@ -0,0 +1,42 @@
{- |
Module : Types
Maintainer : emi@haskell.fyi
Description: Param and script types for generation.
Param and script types for generation.
-}
module Types (ScriptParams (..), AgoraScripts (..)) where
import Agora.SafeMoney (GTTag)
import Agora.ScriptInfo (PolicyInfo, ValidatorInfo)
import Data.Aeson qualified as Aeson
import Data.Tagged (Tagged)
import GHC.Generics qualified as GHC
import PlutusLedgerApi.V1 (TxOutRef)
import PlutusLedgerApi.V1.Value (AssetClass)
-- | Params required for creating script export.
data ScriptParams where
ScriptParams ::
{ governorInitialSpend :: TxOutRef
, gtClassRef :: Tagged GTTag AssetClass
, maximumCosigners :: Integer
} ->
ScriptParams
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
deriving stock (Show, Eq, GHC.Generic, Ord)
-- | Scripts that get exported.
data AgoraScripts = AgoraScripts
{ gitRev :: String
, governorPolicyInfo :: PolicyInfo
, governorValidatorInfo :: ValidatorInfo
, stakePolicyInfo :: PolicyInfo
, stakeValidatorInfo :: ValidatorInfo
, proposalPolicyInfo :: PolicyInfo
, proposalValidatorInfo :: ValidatorInfo
, treasuryValidatorInfo :: ValidatorInfo
, authorityTokenPolicyInfo :: PolicyInfo
}
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
deriving stock (Show, Eq, GHC.Generic)

View file

@ -235,18 +235,26 @@ executable agora-scripts
main-is: Scripts.hs main-is: Scripts.hs
hs-source-dirs: agora-scripts hs-source-dirs: agora-scripts
other-modules: other-modules:
Options
API API
Codec.Serialise.Orphans
Data.Cache.Cached
Options
Types
build-depends: build-depends:
, agora , agora
, cache
, clock
, gitrev , gitrev
, hashable
, http-types
, optparse-applicative , optparse-applicative
, prettyprinter
, servant , servant
, servant-server , servant-server
, warp
, wai , wai
, http-types , wai-cors
, prettyprinter , warp
executable agora-purescript-bridge executable agora-purescript-bridge
import: lang, deps, exe-opts import: lang, deps, exe-opts

View file

@ -140,11 +140,15 @@
); );
applyDep = pkgs: o: applyDep = pkgs: o:
let h = myhackage pkgs.system o.compiler-nix-name; in let
(plutarch.applyPlutarchDep pkgs o) // { h = myhackage pkgs.system o.compiler-nix-name;
modules = haskellModules ++ [ h.module ] ++ (o.modules or [ ]); o' = (plutarch.applyPlutarchDep pkgs o);
extra-hackages = [ (import h.hackageNix) ] ++ (o.extra-hackages or [ ]); in
extra-hackage-tarballs = { _xNJUd_plutarch-hackage = h.hackageTarball; } // (o.extra-hackage-tarballs or { }); o' // rec {
modules = haskellModules ++ [ h.module ] ++ (o'.modules or [ ]);
extra-hackages = [ (import h.hackageNix) ] ++ (o'.extra-hackages or [ ]);
extra-hackage-tarballs = { _xNJUd_plutarch-hackage = h.hackageTarball; };
cabalProjectLocal = (o'.cabalProjectLocal or "") + " , cache >= 0.1.3.0";
}; };
projectForGhc = compiler-nix-name: system: projectForGhc = compiler-nix-name: system: