content: module EVM.Precompiled (execute) where
  
  import Data.ByteString (ByteString)
  import Data.ByteString qualified as BS
  
  import Foreign.C
  import Foreign.Ptr
  import Foreign.ForeignPtr
  
  import System.IO.Unsafe
  
  -- | Opaque representation of the C library's context struct.
  data EthjetContext
  
  foreign import ccall unsafe "ethjet_init"
    ethjet_init :: IO (Ptr EthjetContext)
  foreign import ccall unsafe "&ethjet_free"
    ethjet_free :: FunPtr (Ptr EthjetContext -> IO ())
  foreign import ccall unsafe "ethjet"
    ethjet
      :: Ptr EthjetContext     -- initialized context
      -> CInt                  -- operation
      -> Ptr CChar -> CInt     -- input
      -> Ptr CChar -> CInt     -- output
      -> IO CInt               -- 1 if good
  
  -- Lazy evaluation ensures this context is only initialized once,
  -- and `unsafePerformIO` in such situations is a common pattern.
  --
  -- We use a "foreign pointer" annotated with a finalizer.
  globalContext :: ForeignPtr EthjetContext
  {-# NOINLINE globalContext #-}
  globalContext =
    unsafePerformIO $
      ethjet_init >>= newForeignPtr ethjet_free
  
  -- | Run a given precompiled contract using the C library.
  execute
    :: Int                -- ^ The number of the precompiled contract
    -> ByteString         -- ^ The input buffer
    -> Int                -- ^ The desired output size
    -> Maybe ByteString   -- ^ Hopefully, the output buffer
  execute contract input outputSize =
  
    -- This code looks messy because of the pointer handling,
    -- but it's actually simple.
    --
    -- We use `unsafePerformIO` because the contracts are pure.
  
    unsafePerformIO . BS.useAsCStringLen input $
      \(inputPtr, inputSize) -> do
         outputForeignPtr <- mallocForeignPtrBytes outputSize
         withForeignPtr outputForeignPtr $ \outputPtr -> do
           status <-
             withForeignPtr globalContext $ \contextPtr ->
               -- Finally, we can invoke the C library.
               ethjet contextPtr (fromIntegral contract)
                 inputPtr (fromIntegral inputSize)
                 outputPtr (fromIntegral outputSize)
  
           case status of
             1 -> Just <$> BS.packCStringLen (outputPtr, outputSize)
             _ -> pure Nothing
  