{-# Language ForeignFunctionInterface #-} module EVM.Precompiled (execute) where import Data.ByteString (ByteString) import qualified Data.ByteString 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 "ethjet_init" ethjet_init :: IO (Ptr EthjetContext) foreign import ccall "ðjet_free" ethjet_free :: FunPtr (Ptr EthjetContext -> IO ()) foreign import ccall "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