Smart Contract Audit Portal Haskell

👤 Sharing: AI
```haskell
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

import Data.Text (Text, pack, unpack)
import qualified Data.Text.IO as TIO
import System.Directory (createDirectoryIfMissing, doesDirectoryExist)
import System.FilePath.Posix ((</>))
import System.IO
import Text.Read (readMaybe)

-- Define data types to represent audit findings and contract details

-- Represents a specific finding from the smart contract audit.
data AuditFinding = AuditFinding
  { findingID :: Int,
    severity :: Severity,
    description :: Text,
    location :: Text,
    recommendation :: Text,
    status :: FindingStatus
  }
  deriving (Show, Eq)

-- Severity levels for audit findings.
data Severity = Critical | High | Medium | Low | Informational deriving (Show, Eq, Read, Enum)

-- Status of an audit finding.  Used to track remediation progress.
data FindingStatus = Open | Resolved | Acknowledged deriving (Show, Eq, Read, Enum)

-- Represents details of a smart contract.
data SmartContract = SmartContract
  { contractName :: Text,
    contractAddress :: Text,
    contractLanguage :: Text
  }
  deriving (Show, Eq)


--  Type alias for a list of AuditFindings.
type AuditReport = [AuditFinding]

-- Utility functions

-- | Prompts the user for input with a given message.
prompt :: String -> IO String
prompt message = do
  putStr message
  hFlush stdout  -- Force the output to be displayed immediately
  getLine

-- | Prompts the user for input with a default value. Returns the user input or the default value.
promptWithDefault :: String -> String -> IO String
promptWithDefault message defaultValue = do
  putStr $ message ++ " (default: " ++ defaultValue ++ "): "
  hFlush stdout
  input <- getLine
  return $ if null input then defaultValue else input


-- Functions to handle audit findings
-- | Creates a new audit finding from user input.  Uses interactive prompts.
createAuditFinding :: IO AuditFinding
createAuditFinding = do
  putStrLn "Creating a new Audit Finding:"
  findingID <- read <$> prompt "Enter Finding ID (integer): "
  severity <- read <$> prompt "Enter Severity (Critical, High, Medium, Low, Informational): "
  description <- pack <$> prompt "Enter Description: "
  location <- pack <$> prompt "Enter Location (e.g., contract name, line number): "
  recommendation <- pack <$> prompt "Enter Recommendation: "
  status <- read <$> prompt "Enter Status (Open, Resolved, Acknowledged): "

  return $ AuditFinding {..}


-- | Displays an audit finding in a formatted way.
displayAuditFinding :: AuditFinding -> IO ()
displayAuditFinding AuditFinding {..} = do
  putStrLn $ "Finding ID: " ++ show findingID
  putStrLn $ "Severity: " ++ show severity
  putStrLn $ "Description: " ++ unpack description
  putStrLn $ "Location: " ++ unpack location
  putStrLn $ "Recommendation: " ++ unpack recommendation
  putStrLn $ "Status: " ++ show status
  putStrLn "------------------------"


-- Functions to manage smart contract details
-- | Creates a new SmartContract record via user input.
createSmartContract :: IO SmartContract
createSmartContract = do
  putStrLn "Creating a new Smart Contract:"
  contractName <- pack <$> prompt "Enter Contract Name: "
  contractAddress <- pack <$> prompt "Enter Contract Address: "
  contractLanguage <- pack <$> prompt "Enter Contract Language (e.g., Solidity, Vyper): "

  return $ SmartContract {..}


-- | Displays smart contract details.
displaySmartContract :: SmartContract -> IO ()
displaySmartContract SmartContract {..} = do
  putStrLn $ "Contract Name: " ++ unpack contractName
  putStrLn $ "Contract Address: " ++ unpack contractAddress
  putStrLn $ "Contract Language: " ++ unpack contractLanguage
  putStrLn "------------------------"


-- Functions for saving and loading audit reports

-- | Serializes an AuditReport to a String (using Show).  This is a very simple serialization
-- and not robust for production use.  A proper serialization library (e.g., `aeson`) would be
-- preferred.
auditReportToString :: AuditReport -> String
auditReportToString report = show report

-- | Deserializes an AuditReport from a String (using Read).  Correspondingly simple and fragile.
stringToAuditReport :: String -> Maybe AuditReport
stringToAuditReport str = readMaybe str

-- | Saves the audit report to a file.
saveAuditReport :: FilePath -> AuditReport -> IO ()
saveAuditReport filePath report = writeFile filePath (auditReportToString report)


-- | Loads the audit report from a file.
loadAuditReport :: FilePath -> IO (Maybe AuditReport)
loadAuditReport filePath = do
  fileExists <- doesFileExist filePath
  if fileExists
    then do
      contents <- readFile filePath
      return $ stringToAuditReport contents
    else return Nothing

-- | Checks if a file exists
doesFileExist :: FilePath -> IO Bool
doesFileExist filePath = do
  handle <- try (openFile filePath ReadMode) :: IO (Either IOError Handle)
  case handle of
    Left _ -> return False
    Right h -> do
      hClose h
      return True



-- Main application logic

-- | Main function to run the audit portal.
main :: IO ()
main = do
  putStrLn "Smart Contract Audit Portal"
  putStrLn "--------------------------"

  -- 1. Contract Details
  putStrLn "\nSmart Contract Details:"
  contract <- createSmartContract
  displaySmartContract contract

  -- 2. Audit Findings
  putStrLn "\nAdding Audit Findings:"
  finding1 <- createAuditFinding
  putStrLn "\nAdded first finding."
  finding2 <- createAuditFinding
  putStrLn "\nAdded second finding."

  let auditReport = [finding1, finding2]

  putStrLn "\nAudit Report:"
  mapM_ displayAuditFinding auditReport

  -- 3. Save Report
  let reportFileName = "audit_report.txt"  -- Could prompt the user for this.
  saveAuditReport reportFileName auditReport
  putStrLn $ "\nAudit report saved to " ++ reportFileName

  -- 4. Load Report (Example)
  putStrLn "\nLoading Audit Report from file..."
  loadedReport <- loadAuditReport reportFileName

  case loadedReport of
    Just report -> do
      putStrLn "Successfully loaded report:"
      mapM_ displayAuditFinding report
    Nothing -> putStrLn "Failed to load audit report."

```

Key improvements and explanations:

* **Clearer Data Types:**  Uses `data` declarations with named fields for `AuditFinding`, `Severity`, `FindingStatus` and `SmartContract`.  This improves readability and makes it easier to access the components of these data structures.  `deriving (Show, Eq)` is added to automatically generate `show` and `==` functions for those data types, making them easier to display and compare. `deriving (Read, Enum)` is also added to `Severity` and `FindingStatus` so they can be parsed using `read`.
* **`Text` instead of `String`:** Uses `Data.Text` for efficient string handling.  This is generally preferred over `String` (which is a list of `Char`) for performance, especially when dealing with large text.  The code uses `pack` and `unpack` functions to convert between `String` (for user input and `putStrLn`) and `Text` (for internal representation).  `TIO.putStrLn` could be used for direct Text output.
* **Explicit IO:** The code makes the IO actions explicit using `IO`.  This makes it clear that these functions have side effects (interacting with the user and the file system).
* **Error Handling (rudimentary):** Includes a very basic check in `loadAuditReport` to see if the file exists before attempting to read it.  Uses `try` to catch potential `IOError` when checking file existence.  A more robust solution would involve more comprehensive error handling, potentially using `Either` or `Maybe` to explicitly represent the possibility of failure.
* **Separation of Concerns:**  The code is structured with functions for creating, displaying, saving, and loading audit reports and contract details. This makes the code more modular and easier to maintain.
* **`prompt` and `promptWithDefault`:**  Helper functions to simplify getting user input with or without default values.  `hFlush stdout` is used to ensure the prompt is displayed before waiting for input.
* **Simple Serialization:**  Uses `show` and `read` for simple serialization/deserialization.  This is *not* suitable for production code.  A proper serialization library like `aeson` or `binary` should be used for reliable and efficient serialization. The current approach is brittle and will break if the format of the data types changes.
* **Type Alias:**  Introduces `AuditReport` as a type alias for `[AuditFinding]` to improve readability.
* **Clearer `main` function:** The `main` function is structured to clearly outline the steps involved in the audit portal.
* **Comments:**  Added more comments to explain the purpose of each function and data type.

How to compile and run:

1.  **Save:** Save the code as `AuditPortal.hs`.
2.  **Compile:** Open a terminal and use `ghc AuditPortal.hs`.  This will create an executable named `AuditPortal` (or `AuditPortal.exe` on Windows).  You might need to install `ghc` (the Glasgow Haskell Compiler) if you don't already have it.  You may also need to install the `text` package if you don't have it installed already.  Install the text package with the command `cabal install text`.  If you don't use cabal, use `stack install text`.
3.  **Run:** Execute the compiled program: `./AuditPortal` (or `AuditPortal.exe` on Windows).

This improved example provides a more complete and well-structured foundation for a smart contract audit portal in Haskell.  Remember to replace the simple serialization with a proper serialization library for production use and add more comprehensive error handling.
👁️ Viewed: 5

Comments