diff --git a/.travis.yml b/.travis.yml index 01d56f1..3fe8b42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ -sudo: false +sudo: required language: haskell +services: +- docker before_install: +- nvm install 8.9.4 +- nvm use 8.9.4 +- npm i -g truffle - if [ $STACK ]; then mkdir -p ~/.local/bin; export PATH=$HOME/.local/bin:$PATH; travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'; else export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH; @@ -26,8 +31,9 @@ install: $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/; fi; script: -- if [ $STACK ]; then stack -j2 --no-terminal --install-ghc test web3:unit; - else cabal update && cabal install --only-dependencies --enable-tests $CABALCONFOPTS && cabal configure --enable-tests $CABALCONFOPTS -v2 && cabal build web3 $CABALBUILDOPTS && cabal test unit; +- docker run --rm -d -p 8545:8545 foamspace/cliquebait:latest +- if [ $STACK ]; then stack -j2 --no-terminal --install-ghc test; + else cabal update && cabal install --only-dependencies --enable-tests $CABALCONFOPTS && cabal configure --enable-tests $CABALCONFOPTS -v2 && cabal build web3 $CABALBUILDOPTS && cabal test; fi matrix: diff --git a/Setup.hs b/Setup.hs index b116495..97efb7d 100644 --- a/Setup.hs +++ b/Setup.hs @@ -6,8 +6,10 @@ import Data.Maybe (fromMaybe) import Distribution.PackageDescription (HookedBuildInfo, PackageDescription (testSuites), TestSuite (..)) import Distribution.Simple -import Distribution.Simple.LocalBuildInfo (ComponentName (..), LocalBuildInfo (..)) -import Distribution.Simple.Setup (BuildFlags (..), fromFlag) +import Distribution.Simple.LocalBuildInfo (ComponentName (..), + LocalBuildInfo (..)) +import Distribution.Simple.Setup (BuildFlags (..), + fromFlag) import Distribution.Simple.Utils import Distribution.Verbosity (Verbosity) import System.Directory (makeAbsolute) diff --git a/src/Network/Ethereum/Web3.hs b/src/Network/Ethereum/Web3.hs index 21d4be9..cd8c8bd 100644 --- a/src/Network/Ethereum/Web3.hs +++ b/src/Network/Ethereum/Web3.hs @@ -1,4 +1,3 @@ --- -- Module : Network.Ethereum.Web3 -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 @@ -29,6 +28,8 @@ module Network.Ethereum.Web3 ( , EventAction(..) , Event(..) , event + , event' + , eventMany' , Method(..) , sendTx , call diff --git a/src/Network/Ethereum/Web3/Contract.hs b/src/Network/Ethereum/Web3/Contract.hs index 2b71178..7398e85 100644 --- a/src/Network/Ethereum/Web3/Contract.hs +++ b/src/Network/Ethereum/Web3/Contract.hs @@ -4,8 +4,8 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} - -- | -- Module : Network.Ethereum.Web3.Contract -- Copyright : Alexander Krupenkin 2016 @@ -39,6 +39,8 @@ module Network.Ethereum.Web3.Contract ( EventAction(..) , Event(..) , event + , event' + , eventMany' , Method(..) , call , sendTx @@ -46,11 +48,15 @@ module Network.Ethereum.Web3.Contract ( , nopay ) where -import Control.Concurrent (ThreadId, threadDelay) +import Control.Concurrent (threadDelay) +import Control.Concurrent.Async (Async) import Control.Exception (throwIO) -import Control.Monad (forM, when) +import Control.Monad (forM, void, when) import Control.Monad.IO.Class (liftIO) +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Maybe (MaybeT (..)) import Control.Monad.Trans.Reader (ReaderT (..)) +import Data.Machine import Data.Maybe (listToMaybe, mapMaybe) import Data.Monoid ((<>)) import Data.Proxy (Proxy (..)) @@ -69,6 +75,14 @@ import qualified Network.Ethereum.Web3.Eth as Eth import Network.Ethereum.Web3.Provider import Network.Ethereum.Web3.Types +import Data.Machine.Plan + + + +-------------------------------------------------------------------------------- +-- * Event Streaming +-------------------------------------------------------------------------------- + -- | Event callback control response data EventAction = ContinueEvent -- ^ Continue to listen events @@ -79,44 +93,191 @@ data EventAction = ContinueEvent -- | Contract event listener class Event e where -- | Event filter structure used by low-level subscription methods - eventFilter :: Proxy e -> Address -> Filter + eventFilter :: Address -> Filter e --- | 'event' spawns an asynchronous event filter to monitor the latest events --- | logged by the contract at the given address for a particular event type. All --- | events of type 'e' are composed of an indexed component 'i', and a --- | non-indexed component 'ni'. -event :: forall p e i ni. - ( Provider p - , Event e +-- | run 'event\'' one block at a time. +event :: forall p i ni e . + ( Provider p , DecodeEvent i ni e + , Event e ) - => Address - -> (e -> ReaderT Change (Web3 p) EventAction) - -> Web3 p ThreadId -event a f = do - fid <- Eth.newFilter (eventFilter (Proxy :: Proxy e) a) - forkWeb3 $ - let loop = do liftIO (threadDelay 1000000) - changes <- Eth.getFilterChanges fid - acts <- forM (mapMaybe pairChange changes) $ \(changeEvent, changeWithMeta) -> - runReaderT (f changeEvent) changeWithMeta - when (TerminateEvent `notElem` acts) loop - in do loop - Eth.uninstallFilter fid - return () - where - prepareTopics = fmap (T.drop 2) . drop 1 - pairChange :: DecodeEvent i ni e => Change -> Maybe (e, Change) - pairChange changeWithMeta = do - changeEvent <- decodeEvent changeWithMeta - return (changeEvent, changeWithMeta) + => Filter e + -> (e -> ReaderT Change (Web3 p) EventAction) + -> Web3 p (Async ()) +event fltr handler = forkWeb3 $ event' fltr handler +-- | same as event, but does not immediately spawn a new thread. +event' :: forall p i ni e . + ( Provider p + , DecodeEvent i ni e + , Event e + ) + => Filter e + -> (e -> ReaderT Change (Web3 p) EventAction) + -> Web3 p () +event' fltr handler = eventMany' fltr 0 handler + +-- | 'event\'' take s a filter, a window size, and a handler. It runs the handler +-- | over the results of 'eventLogs' results using 'reduceEventStream'. If no +-- | 'TerminateEvent' action is thrown and the toBlock is not yet reached, +-- | it then transitions to polling. +eventMany' :: forall p i ni e . + ( Provider p + , DecodeEvent i ni e + , Event e + ) + => Filter e + -> Integer + -> (e -> ReaderT Change (Web3 p) EventAction) + -> Web3 p () +eventMany' fltr window handler = do + start <- mkBlockNumber $ filterFromBlock fltr + let initState = FilterStreamState { fssCurrentBlock = start + , fssInitialFilter = fltr + , fssWindowSize = window + } + mLastProcessedFilterState <- reduceEventStream (playLogs initState) handler + case mLastProcessedFilterState of + Nothing -> startPolling fltr {filterFromBlock = BlockWithNumber start} + Just a@(act, lastBlock) -> do + end <- mkBlockNumber . filterToBlock $ fltr + when (act /= TerminateEvent && lastBlock < end) $ + let pollingFromBlock = lastBlock + 1 + in startPolling fltr {filterFromBlock = BlockWithNumber pollingFromBlock} + where + startPolling fltr = do + filterId <- Eth.newFilter fltr + let pollTo = filterToBlock fltr + void $ reduceEventStream (pollFilter filterId pollTo) handler + +-- | Effectively a mapM_ over the machine using the given handler. +reduceEventStream :: Monad m + => MachineT m k [FilterChange a] + -> (a -> ReaderT Change m EventAction) + -> m (Maybe (EventAction, BlockNumber)) +reduceEventStream filterChanges handler = fmap listToMaybe . runT $ + filterChanges + ~> autoM (processChanges handler) + ~> asParts + ~> runWhile (\(act, _) -> act /= TerminateEvent) + ~> final + where + runWhile p = repeatedly $ do + v <- await + if p v + then yield v + else yield v >> stop + processChanges :: Monad m + => (a -> ReaderT Change m EventAction) + -> [FilterChange a] + -> m [(EventAction, BlockNumber)] + processChanges handler changes = forM changes $ \FilterChange{..} -> do + act <- flip runReaderT filterChangeRawChange $ + handler filterChangeEvent + return (act, changeBlockNumber filterChangeRawChange) + +data FilterChange a = FilterChange { filterChangeRawChange :: Change + , filterChangeEvent :: a + } + +-- | 'playLogs' streams the 'filterStream' and calls eth_getLogs on these +-- | 'Filter' objects. +playLogs :: forall p k i ni e. + ( Provider p + , DecodeEvent i ni e + , Event e + ) + => FilterStreamState e + -> MachineT (Web3 p) k [FilterChange e] +playLogs s = filterStream s + ~> autoM Eth.getLogs + ~> mapping mkFilterChanges + +-- | polls a filter from the given filterId until the target toBlock is reached. +pollFilter :: forall p i ni e s k. + ( Provider p + , DecodeEvent i ni e + , Event e + ) + => FilterId + -> DefaultBlock + -> MachineT (Web3 p) k [FilterChange e] +pollFilter fid end = construct $ pollPlan fid end + where + pollPlan :: FilterId -> DefaultBlock -> PlanT k [FilterChange e] (Web3 p) () + pollPlan fid end = do + bn <- lift $ Eth.blockNumber + if BlockWithNumber bn > end + then do + lift $ Eth.uninstallFilter fid + stop + else do + liftIO $ threadDelay 1000000 + changes <- lift $ Eth.getFilterChanges fid + yield $ mkFilterChanges changes + pollPlan fid end + +mkFilterChanges :: forall i ni e. + ( Event e + , DecodeEvent i ni e + ) + => [Change] + -> [FilterChange e] +mkFilterChanges cs = + flip mapMaybe cs $ \c@Change{..} -> do + x <- decodeEvent c + return $ FilterChange c x + +data FilterStreamState e = + FilterStreamState { fssCurrentBlock :: BlockNumber + , fssInitialFilter :: Filter e + , fssWindowSize :: Integer + } + + +-- | `filterStream` is a machine which represents taking an initial filter +-- | over a range of blocks b1, ... bn (where bn is possibly `Latest` or `Pending`, +-- | but b1 is an actual `BlockNumber`), and making a stream of filter objects +-- | which cover this filter in intervals of size `windowSize`. The machine +-- | halts whenever the `fromBlock` of a spanning filter either (1) excedes the +-- | initial filter's `toBlock` or (2) is greater than the chain head's `BlockNumber`. +filterStream :: Provider p + => FilterStreamState e + -> MachineT (Web3 p) k (Filter e) +filterStream initialPlan = unfoldPlan initialPlan filterPlan + where + filterPlan :: Provider p => FilterStreamState e -> PlanT k (Filter e) (Web3 p) (FilterStreamState e) + filterPlan initialState@FilterStreamState{..} = do + end <- lift . mkBlockNumber $ filterToBlock fssInitialFilter + if fssCurrentBlock > end + then stop + else do + let to' = newTo end fssCurrentBlock fssWindowSize + filter' = fssInitialFilter { filterFromBlock = BlockWithNumber fssCurrentBlock + , filterToBlock = BlockWithNumber to' + } + yield filter' + filterPlan $ initialState { fssCurrentBlock = succ to' } + succ :: BlockNumber -> BlockNumber + succ (BlockNumber bn) = BlockNumber $ bn + 1 + newTo :: BlockNumber -> BlockNumber -> Integer -> BlockNumber + newTo upper (BlockNumber current) window = min upper . BlockNumber $ current + window + +-- | Coerce a 'DefaultBlock' into a numerical block number. +mkBlockNumber :: Provider p => DefaultBlock -> Web3 p BlockNumber +mkBlockNumber bm = case bm of + BlockWithNumber bn -> return bn + Earliest -> return 0 + _ -> Eth.blockNumber + +-------------------------------------------------------------------------------- +-- * Transactions and Calls +-------------------------------------------------------------------------------- class Method a where - -- | selector is used to compute the function selector for a given function type, defined as - -- | the hex string representation of the first 4 bytes of the hash of the signature. selector :: Proxy a -> T.Text +-- | 'sendTx' is used to submit a state changing transaction. sendTx :: ( Generic a , GenericABIEncode (Rep a) , Provider p @@ -131,6 +292,8 @@ sendTx call (dat :: a) = let sel = selector (Proxy :: Proxy a) in Eth.sendTransaction (call { callData = Just $ sel <> genericToData dat }) +-- | 'call' is used to call contract methods that have no state changing effects, +-- | or to call m call :: ( Generic a , GenericABIEncode (Rep a) , Generic b diff --git a/src/Network/Ethereum/Web3/Encoding.hs b/src/Network/Ethereum/Web3/Encoding.hs index c51898f..a709b97 100644 --- a/src/Network/Ethereum/Web3/Encoding.hs +++ b/src/Network/Ethereum/Web3/Encoding.hs @@ -1,5 +1,4 @@ {-# LANGUAGE PolyKinds #-} - -- | -- Module : Network.Ethereum.Web3.Encoding -- Copyright : Alexander Krupenkin 2016 diff --git a/src/Network/Ethereum/Web3/Encoding/Event.hs b/src/Network/Ethereum/Web3/Encoding/Event.hs index 3086514..a4180ee 100644 --- a/src/Network/Ethereum/Web3/Encoding/Event.hs +++ b/src/Network/Ethereum/Web3/Encoding/Event.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -7,8 +8,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} - - -- | -- Module : Network.Ethereum.Web3.Encoding.Event -- Copyright : Alexander Krupenkin 2016 @@ -22,7 +21,6 @@ -- to assist in event decoding. The user of this library should have no need to use -- this directly in application code. -- - module Network.Ethereum.Web3.Encoding.Event( DecodeEvent(..) , ArrayParser(..) @@ -30,17 +28,18 @@ module Network.Ethereum.Web3.Encoding.Event( , genericArrayParser ) where -import qualified Data.Text as T +import qualified Data.Text as T import Generics.SOP -import qualified GHC.Generics as GHC (Generic) +import qualified GHC.Generics as GHC (Generic) -import Network.Ethereum.Web3.Address (Address) -import Network.Ethereum.Web3.Encoding (ABIDecode, fromData) -import Network.Ethereum.Web3.Encoding.Generic (GenericABIDecode, - genericFromData) +import Network.Ethereum.Web3.Address (Address) +import Network.Ethereum.Web3.Encoding (ABIDecode, + fromData) import Network.Ethereum.Web3.Encoding.Event.Internal +import Network.Ethereum.Web3.Encoding.Generic (GenericABIDecode, + genericFromData) import Network.Ethereum.Web3.Encoding.Internal -import Network.Ethereum.Web3.Types (Change (..)) +import Network.Ethereum.Web3.Types (Change (..)) -- | Indexed event args come back in as a list of encoded values. 'ArrayParser' -- | is used to decode these values so that they can be used to reconstruct the @@ -87,7 +86,8 @@ parseChange :: ( Generic i , GenericABIDecode nirep ) => Change - -> Bool -- is anonymous event + -> Bool + -- ^ is anonymous event -> Maybe (Event i ni) parseChange change isAnonymous = do i <- genericArrayParser topics diff --git a/src/Network/Ethereum/Web3/Encoding/Event/Internal.hs b/src/Network/Ethereum/Web3/Encoding/Event/Internal.hs index bcdf6a4..c535b46 100644 --- a/src/Network/Ethereum/Web3/Encoding/Event/Internal.hs +++ b/src/Network/Ethereum/Web3/Encoding/Event/Internal.hs @@ -10,8 +10,6 @@ {-# LANGUAGE TypeInType #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} - - -- | -- Module : Network.Ethereum.Web3.Encoding.Event.Internal -- Copyright : Alexander Krupenkin 2016 @@ -25,14 +23,13 @@ -- to assist in event decoding. The user of this library should have no need to use -- this directly in application code. -- - module Network.Ethereum.Web3.Encoding.Event.Internal where import Data.Kind -import Data.Proxy (Proxy (..)) -import Data.Tagged (Tagged (..)) +import Data.Proxy (Proxy (..)) +import Data.Tagged (Tagged (..)) import Generics.SOP -import GHC.TypeLits (CmpNat) +import GHC.TypeLits (CmpNat) data HList :: [*] -> * where HNil :: HList '[] diff --git a/src/Network/Ethereum/Web3/Encoding/Generic.hs b/src/Network/Ethereum/Web3/Encoding/Generic.hs index dec869c..6872488 100644 --- a/src/Network/Ethereum/Web3/Encoding/Generic.hs +++ b/src/Network/Ethereum/Web3/Encoding/Generic.hs @@ -8,8 +8,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeInType #-} {-# LANGUAGE TypeOperators #-} - - -- | -- Module : Network.Ethereum.Web3.Encoding.Generic -- Copyright : Alexander Krupenkin 2016 @@ -23,7 +21,6 @@ -- to assist in encoding and decoding Solidity types for function calls and events. -- The user of this library should have no need to use this directly in application code. -- - module Network.Ethereum.Web3.Encoding.Generic ( GenericABIEncode , GenericABIDecode diff --git a/src/Network/Ethereum/Web3/Encoding/Int.hs b/src/Network/Ethereum/Web3/Encoding/Int.hs index 6d26076..a0ab843 100644 --- a/src/Network/Ethereum/Web3/Encoding/Int.hs +++ b/src/Network/Ethereum/Web3/Encoding/Int.hs @@ -3,8 +3,6 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} - - -- | -- Module : Network.Ethereum.Web3.Encoding.Int -- Copyright : Alexander Krupenkin 2016 @@ -16,7 +14,6 @@ -- -- The type int and uint support. -- - module Network.Ethereum.Web3.Encoding.Int where import Control.Error (hush) diff --git a/src/Network/Ethereum/Web3/Encoding/Internal.hs b/src/Network/Ethereum/Web3/Encoding/Internal.hs index 12103e6..8572f16 100644 --- a/src/Network/Ethereum/Web3/Encoding/Internal.hs +++ b/src/Network/Ethereum/Web3/Encoding/Internal.hs @@ -1,6 +1,5 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} - -- | -- Module : Network.Ethereum.Web3.Encoding.Internal -- Copyright : Alexander Krupenkin 2016 diff --git a/src/Network/Ethereum/Web3/Encoding/Vector.hs b/src/Network/Ethereum/Web3/Encoding/Vector.hs index a6b83ac..25689e0 100644 --- a/src/Network/Ethereum/Web3/Encoding/Vector.hs +++ b/src/Network/Ethereum/Web3/Encoding/Vector.hs @@ -5,7 +5,6 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} - -- | -- Module : Network.Ethereum.Web3.Encoding.Vector -- Copyright : Alexander Krupenkin 2016 @@ -15,9 +14,8 @@ -- Stability : experimental -- Portability : noportable -- --- statically sized vector support. +-- Statically sized vector support. -- - module Network.Ethereum.Web3.Encoding.Vector where import Control.Monad (replicateM) diff --git a/src/Network/Ethereum/Web3/Eth.hs b/src/Network/Ethereum/Web3/Eth.hs index 994f956..516309f 100644 --- a/src/Network/Ethereum/Web3/Eth.hs +++ b/src/Network/Ethereum/Web3/Eth.hs @@ -105,7 +105,7 @@ getBalance = remote "eth_getBalance" -- | Creates a filter object, based on filter options, to notify when the -- state changes (logs). To check if the state has changed, call -- 'getFilterChanges'. -newFilter :: Provider a => Filter -> Web3 a FilterId +newFilter :: Provider a => Filter e -> Web3 a FilterId {-# INLINE newFilter #-} newFilter = remote "eth_newFilter" @@ -122,7 +122,7 @@ uninstallFilter :: Provider a => FilterId -> Web3 a Bool uninstallFilter = remote "eth_uninstallFilter" -- | Returns an array of all logs matching a given filter object. -getLogs :: Provider a => Filter -> Web3 a [Change] +getLogs :: Provider a => Filter e -> Web3 a [Change] {-# INLINE getLogs #-} getLogs = remote "eth_getLogs" @@ -185,7 +185,7 @@ getBlockFilterChanges :: Provider a => Text -> Web3 a [Text] getBlockFilterChanges = remote "eth_getBlockFilterChanges" -- | Returns the number of most recent block. -blockNumber :: Provider a => Web3 a Text +blockNumber :: Provider a => Web3 a BlockNumber {-# INLINE blockNumber #-} blockNumber = remote "eth_blockNumber" diff --git a/src/Network/Ethereum/Web3/JsonAbi.hs b/src/Network/Ethereum/Web3/JsonAbi.hs index a764bdb..498098a 100644 --- a/src/Network/Ethereum/Web3/JsonAbi.hs +++ b/src/Network/Ethereum/Web3/JsonAbi.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Module : Network.Ethereum.Web3.JsonAbi diff --git a/src/Network/Ethereum/Web3/Provider.hs b/src/Network/Ethereum/Web3/Provider.hs index 64998dd..a545779 100644 --- a/src/Network/Ethereum/Web3/Provider.hs +++ b/src/Network/Ethereum/Web3/Provider.hs @@ -11,7 +11,7 @@ -- module Network.Ethereum.Web3.Provider where -import Control.Concurrent (ThreadId, forkIO) +import Control.Concurrent.Async (Async, async) import Control.Exception (try) import Control.Monad.IO.Class (MonadIO (..)) import Network.Ethereum.Web3.Types @@ -38,6 +38,6 @@ runWeb3 :: MonadIO m => Web3 DefaultProvider b -> m (Either Web3Error b) runWeb3 = runWeb3' -- | Fork 'Web3' with the same 'Provider' -forkWeb3 :: Web3 a () -> Web3 a ThreadId +forkWeb3 :: Web3 a b -> Web3 a (Async b) {-# INLINE forkWeb3 #-} -forkWeb3 = Web3 . forkIO . unWeb3 +forkWeb3 = Web3 . async . unWeb3 diff --git a/src/Network/Ethereum/Web3/TH.hs b/src/Network/Ethereum/Web3/TH.hs index e632943..2ecd4e0 100644 --- a/src/Network/Ethereum/Web3/TH.hs +++ b/src/Network/Ethereum/Web3/TH.hs @@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} - -- | -- Module : Network.Ethereum.Web3.TH -- Copyright : Alexander Krupenkin 2016 @@ -43,6 +42,7 @@ module Network.Ethereum.Web3.TH ( import Control.Monad ((<=<)) import Data.List (length, uncons) import Data.Tagged (Tagged) +import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as B @@ -54,9 +54,9 @@ import Network.Ethereum.Web3.Address (Address) import Network.Ethereum.Web3.Contract import Network.Ethereum.Web3.Encoding import Network.Ethereum.Web3.Encoding.Event +import Network.Ethereum.Web3.Encoding.Generic import Network.Ethereum.Web3.Encoding.Int import Network.Ethereum.Web3.Encoding.Vector -import Network.Ethereum.Web3.Encoding.Generic import Network.Ethereum.Web3.Internal import Network.Ethereum.Web3.JsonAbi import Network.Ethereum.Web3.Provider @@ -112,15 +112,15 @@ funD' name p f = funD name [clause p (normalB f) []] toHSType :: SolidityType -> TypeQ toHSType s = case s of - SolidityBool -> conT (mkName "Bool") - SolidityAddress -> conT (mkName "Address") - SolidityUint n -> appT (conT (mkName "UIntN")) (numLit n) - SolidityInt n -> appT (conT (mkName "IntN")) (numLit n) - SolidityString -> conT (mkName "Text") - SolidityBytesN n -> appT (conT (mkName "BytesN")) (numLit n) - SolidityBytesD -> conT (mkName "BytesD") + SolidityBool -> conT (mkName "Bool") + SolidityAddress -> conT (mkName "Address") + SolidityUint n -> appT (conT (mkName "UIntN")) (numLit n) + SolidityInt n -> appT (conT (mkName "IntN")) (numLit n) + SolidityString -> conT (mkName "Text") + SolidityBytesN n -> appT (conT (mkName "BytesN")) (numLit n) + SolidityBytesD -> conT (mkName "BytesD") SolidityVector ns a -> expandVector ns a - SolidityArray a -> appT listT $ toHSType a + SolidityArray a -> appT listT $ toHSType a where numLit n = litT (numTyLit $ toInteger n) expandVector :: [Int] -> SolidityType -> TypeQ @@ -133,7 +133,7 @@ toHSType s = case s of typeQ :: Text -> TypeQ typeQ t = case parseSolidityType t of - Left e -> error $ "Unable to parse solidity type: " ++ show e + Left e -> error $ "Unable to parse solidity type: " ++ show e Right ty -> toHSType ty -- | Event argument to TH type @@ -161,11 +161,11 @@ eventFilterD :: String -> Int -> [DecQ] eventFilterD topic0 n = let addr = mkName "a" indexedArgs = replicate n Nothing :: [Maybe String] - in [ funD' (mkName "eventFilter") [wildP, varP addr] + in [ funD' (mkName "eventFilter") [varP addr] [|Filter (Just $(varE addr)) (Just $ [Just topic0] <> indexedArgs) - Nothing - Nothing + Latest + Latest |] ] @@ -243,15 +243,15 @@ mkEvent ev@(DEvent name inputs anonymous) = sequence -- | arg_name -> evArg_name -- | _argName -> evArgName -- | "" -> evi , for example Transfer(address, address uint256) ~> Transfer {transfer1 :: address, transfer2 :: address, transfer3 :: Integer} -makeArgs :: T.Text -> [(T.Text, T.Text)] -> [(Name, T.Text)] +makeArgs :: Text -> [(Text, Text)] -> [(Name, Text)] makeArgs prefix ns = go 1 ns where prefixStr = toLowerFirst . T.unpack $ prefix - go :: Int -> [(T.Text, T.Text)] -> [(Name, T.Text)] + go :: Int -> [(Text, Text)] -> [(Name, Text)] go i [] = [] go i ((h, ty) : tail) = if T.null h then (mkName $ prefixStr ++ show i, ty) : go (i + 1) tail - else (mkName . (++) prefixStr . toUpperFirst . (\t -> if head t == '_' then drop 1 t else t) . T.unpack $ h, ty) : go (i + 1) tail + else (mkName . (++ "_") . (++) prefixStr . toUpperFirst . T.unpack $ h, ty) : go (i + 1) tail -- | Method delcarations maker mkFun :: Declaration -> Q [Dec] diff --git a/src/Network/Ethereum/Web3/Types.hs b/src/Network/Ethereum/Web3/Types.hs index e14d8fa..eeb51cc 100644 --- a/src/Network/Ethereum/Web3/Types.hs +++ b/src/Network/Ethereum/Web3/Types.hs @@ -21,6 +21,7 @@ import Data.Aeson import Data.Aeson.TH import Data.Default import Data.Monoid ((<>)) +import Data.Ord (Down (..)) import Data.Text (Text) import qualified Data.Text.Lazy.Builder as B import qualified Data.Text.Lazy.Builder.Int as B @@ -90,16 +91,19 @@ instance UnitSpec Quantity where divider = const 1 name = const "quantity" --- | Low-level event filter data structure -data Filter = Filter - { filterAddress :: !(Maybe Address) - , filterTopics :: !(Maybe [Maybe Text]) - , filterFromBlock :: !(Maybe Text) - , filterToBlock :: !(Maybe Text) - } deriving (Show, Generic) +newtype BlockNumber = BlockNumber Integer deriving (Eq, Show, Generic, Ord, Read, Num) -$(deriveJSON (defaultOptions - { fieldLabelModifier = toLowerFirst . drop 6 }) ''Filter) +instance FromJSON BlockNumber where + parseJSON (String v) = + case R.hexadecimal v of + Right (x, "") -> return (BlockNumber x) + _ -> fail "Unable to parse BlockNumber!" + parseJSON _ = fail "The string is required!" + +instance ToJSON BlockNumber where + toJSON (BlockNumber x) = + let hexValue = B.toLazyText (B.hexadecimal x) + in toJSON ("0x" <> hexValue) -- | Event filter identifier newtype FilterId = FilterId Integer @@ -124,7 +128,7 @@ data Change = Change , changeTransactionIndex :: !Text , changeTransactionHash :: !Text , changeBlockHash :: !Text - , changeBlockNumber :: !Text + , changeBlockNumber :: !BlockNumber , changeAddress :: !Address , changeData :: !Text , changeTopics :: ![Text] @@ -152,13 +156,41 @@ instance Default Call where -- | The contract call mode describe used state: latest or pending -data DefaultBlock = BlockNumberHex Text | Earliest | Latest | Pending +data DefaultBlock = BlockWithNumber BlockNumber | Earliest | Latest | Pending deriving (Show, Eq) instance ToJSON DefaultBlock where - toJSON (BlockNumberHex hex) = toJSON hex + toJSON (BlockWithNumber bn) = toJSON bn toJSON parameter = toJSON . toLowerFirst . show $ parameter +-- | Low-level event filter data structure +data Filter e = Filter + { filterAddress :: !(Maybe Address) + , filterTopics :: !(Maybe [Maybe Text]) + , filterFromBlock :: !DefaultBlock + , filterToBlock :: !DefaultBlock + } deriving (Show, Generic) + + +instance ToJSON (Filter e) where + toJSON f = object [ "address" .= filterAddress f + , "topics" .= filterTopics f + , "fromBlock" .= filterFromBlock f + , "toBlock" .= filterToBlock f + ] + +instance Ord DefaultBlock where + compare Pending Pending = EQ + compare Latest Latest = EQ + compare Earliest Earliest = EQ + compare (BlockWithNumber a) (BlockWithNumber b) = compare a b + compare _ Pending = LT + compare Pending Latest = GT + compare _ Latest = LT + compare Earliest _ = LT + compare a b = compare (Down b) (Down a) + + -- TODO: Wrap -- | Transaction hash text string type TxHash = Text @@ -171,7 +203,7 @@ data Transaction = Transaction -- ^ QUANTITY - the number of transactions made by the sender prior to this one. , txBlockHash :: !Text -- ^ DATA, 32 Bytes - hash of the block where this transaction was in. null when its pending. - , txBlockNumber :: !Text + , txBlockNumber :: !BlockNumber -- ^ QUANTITY - block number where this transaction was in. null when its pending. , txTransactionIndex :: !Text -- ^ QUANTITY - integer of the transactions index position in the block. null when its pending. @@ -194,7 +226,7 @@ $(deriveJSON (defaultOptions -- | Block information data Block = Block - { blockNumber :: !Text + { blockBlockNumber :: !BlockNumber -- ^ QUANTITY - the block number. null when its pending block. , blockHash :: !Text -- ^ DATA, 32 Bytes - hash of the block. null when its pending block. diff --git a/stack.yaml b/stack.yaml index e0e6143..7f0b7b6 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,5 +1,5 @@ # Resolver to choose a 'specific' stackage snapshot or a compiler version. -resolver: lts-10.2 +resolver: lts-10.3 # User packages to be built. packages: - '.' diff --git a/test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs b/test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs index 3565da5..7e76ada 100644 --- a/test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs +++ b/test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs @@ -1,7 +1,7 @@ -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Module : Network.Ethereum.Web3.Test.ComplexStorage @@ -16,9 +16,12 @@ -- several different types. The point of this test is to test the encoding -- of a complicated Solidity tuple, consisting of dynamically and statically -- sized components. +-- module Network.Ethereum.Web3.Test.ComplexStorageSpec where +import Control.Concurrent.Async (wait) +import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO) import Data.ByteArray (convert) import Data.ByteString (ByteString) @@ -58,9 +61,18 @@ complexStorageSpec = do sByte2sElem = (BytesN (convert ("\x12\x34" :: ByteString)) :: BytesN 2) sByte2sVec = sByte2sElem :< sByte2sElem :< sByte2sElem :< sByte2sElem :< NilL sByte2s = [sByte2sVec, sByte2sVec] - it "can set the values of a ComplexStorage" $ \primaryAccount -> do + + it "can set the values of a ComplexStorage and validate them with an event" $ \primaryAccount -> do contractAddress <- Prelude.fmap fromString . liftIO $ getEnv "COMPLEXSTORAGE_CONTRACT_ADDRESS" let theCall = callFromTo primaryAccount contractAddress + fltr = eventFilter contractAddress + -- kick off listening for the ValsSet event + vals <- newEmptyMVar + fiber <- runWeb3Configured' $ + event fltr $ \(vs :: ValsSet) -> do + liftIO $ putMVar vals vs + pure TerminateEvent + -- kick off tx ret <- runWeb3Configured $ setValues theCall sUint sInt @@ -71,14 +83,23 @@ complexStorageSpec = do sString sBytes16 sByte2s - True `shouldBe` True -- we need to et this far :) + -- wait for its ValsSet event + wait fiber + (ValsSet vsA vsB vsC vsD vsE vsF vsG vsH vsI) <- takeMVar vals + vsA `shouldBe` sUint + vsB `shouldBe` sInt + vsC `shouldBe` sBool + vsD `shouldBe` sInt224 + vsE `shouldBe` sBools + vsF `shouldBe` sInts + vsG `shouldBe` sString + vsH `shouldBe` sBytes16 + vsI `shouldBe` sByte2s it "can verify that it set the values correctly" $ \primaryAccount -> do contractAddress <- Prelude.fmap fromString . liftIO $ getEnv "COMPLEXSTORAGE_CONTRACT_ADDRESS" let theCall = callFromTo primaryAccount contractAddress runGetterCall f = runWeb3Configured (f theCall) - -- gotta sleep for the block to get confirmed! - sleepSeconds 5 -- there really has to be a better way to do this uintVal' <- runGetterCall uintVal intVal' <- runGetterCall intVal diff --git a/test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs b/test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs index bfeff9a..a5dc645 100644 --- a/test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs +++ b/test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs @@ -1,9 +1,8 @@ -{-# LANGUAGE KindSignatures #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} - +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} -- Module : Network.Ethereum.Web3.Test.SimpleStorage -- Copyright : Alexander Krupenkin 2016 @@ -20,32 +19,42 @@ module Network.Ethereum.Web3.Test.SimpleStorageSpec where import Control.Concurrent (threadDelay) +import Control.Concurrent.Async (wait) import Control.Concurrent.MVar import Control.Monad (void) import Control.Monad.IO.Class (liftIO) +import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Reader (ask) import Data.ByteString (ByteString) import Data.Default import Data.Either (isRight) +import Data.Foldable (forM_) import Data.List (sort) -import Data.Proxy +import Data.Maybe +import Data.Monoid import Data.String (fromString) import qualified Data.Text as T import Data.Traversable (for) import GHC.TypeLits + import Network.Ethereum.Web3 hiding (convert) import Network.Ethereum.Web3.Contract (Event (..)) import qualified Network.Ethereum.Web3.Eth as Eth -import Network.Ethereum.Web3.Test.Utils import Network.Ethereum.Web3.TH -import Network.Ethereum.Web3.Types (Call (..), Change (..), Filter (..)) +import Network.Ethereum.Web3.Types + import Numeric (showHex) import System.Environment (getEnv) import System.IO.Unsafe (unsafePerformIO) import Test.Hspec +import Network.Ethereum.Web3.Test.Utils + [abiFrom|test-support/build/contracts/abis/SimpleStorage.json|] +unCountSet :: CountSet -> UIntN 256 +unCountSet (CountSet n) = n + contractAddress :: Address contractAddress = fromString . unsafePerformIO $ getEnv "SIMPLESTORAGE_CONTRACT_ADDRESS" @@ -66,9 +75,8 @@ interactions = describe "can interact with a SimpleStorage contract" $ do it "can read the value back" $ \primaryAccount -> do let theCall = callFromTo primaryAccount contractAddress - now' <- runWeb3Configured Eth.blockNumber - let now = read (T.unpack now') - later = now + 3 + now <- runWeb3Configured Eth.blockNumber + let later = now + 3 awaitBlock later v <- runWeb3Configured (count theCall) v `shouldBe` theValue @@ -77,28 +85,118 @@ events :: SpecWith Address events = describe "can interact with a SimpleStorage contract across block intervals" $ do it "can stream events starting and ending in the future, unbounded" $ \primaryAccount -> do var <- newMVar [] - termination <- newEmptyMVar let theCall = callFromTo primaryAccount contractAddress - theSets = [8, 9, 10] - now' <- runWeb3Configured Eth.blockNumber - let now = read (T.unpack now') - later = now + 3 - liftIO . putStrLn $ "now is " ++ show now ++ " (" ++ show now' ++ ")" - void . runWeb3Configured $ event contractAddress $ \(CountSet cs) -> do - liftIO . print $ "Got count: " ++ show cs - v <- liftIO $ takeMVar var - let newV = cs : v - liftIO $ putMVar var newV - if length newV == 3 - then do - liftIO $ putMVar termination True - return TerminateEvent - else do - return ContinueEvent - awaitBlock later - void . for theSets $ \v -> runWeb3Configured (setCount theCall v) - takeMVarWithTimeout 20000000 termination >>= \case - Nothing -> error "timed out waiting for event thread!" - Just term -> return () + theSets = map (fromJust . uIntNFromInteger) [1, 2, 3] + print "Setting up the filter..." + fiber <- runWeb3Configured' $ do + let fltr = eventFilter contractAddress + forkWeb3 $ processUntil' var fltr ((3 ==) . length) + print "Setting the values..." + setValues theCall theSets + wait fiber + print "Filter caught 3 values..." vals <- takeMVar var - sort vals `shouldBe` sort theSets + sort (unCountSet <$> vals) `shouldBe` sort theSets + + it "can stream events starting and ending in the future, bounded" $ \primaryAccount -> do + runWeb3Configured Eth.blockNumber >>= \bn -> awaitBlock (bn + 1) + var <- newMVar [] + let theCall = callFromTo primaryAccount contractAddress + theSets = map (fromJust . uIntNFromInteger) [13, 14, 15] + start <- runWeb3Configured Eth.blockNumber + let later = BlockWithNumber (start + 3) + latest = BlockWithNumber (start + 8) + fltr = (eventFilter contractAddress :: Filter CountSet) { filterFromBlock = later + , filterToBlock = latest + } + print "Setting up the filter..." + fiber <- runWeb3Configured' $ + forkWeb3 $ processUntil' var fltr ((3 ==) . length) + awaitBlock (start + 3) + print "Setting the values..." + setValues theCall theSets + wait fiber + print "Filter caught 3 values..." + vals <- takeMVar var + sort (unCountSet <$> vals) `shouldBe` sort theSets + + it "can stream events starting in the past and ending in the future" $ \primaryAccount -> do + runWeb3Configured Eth.blockNumber >>= \bn -> awaitBlock (bn + 1) + var <- newMVar [] + blockNumberVar <- newEmptyMVar + let theCall = callFromTo primaryAccount contractAddress + theSets1 = map (fromJust . uIntNFromInteger) [7, 8, 9] + theSets2 = map (fromJust . uIntNFromInteger) [10, 11, 12] + start <- runWeb3Configured Eth.blockNumber + let fltr = eventFilter contractAddress :: Filter CountSet + fiber <- runWeb3Configured' $ do + forkWeb3 $ processUntil var fltr ((3 ==) . length) (liftIO . putMVar blockNumberVar . changeBlockNumber) + print "Running first transactions as past transactions..." + setValues theCall theSets1 + wait fiber + print "All past transactions succeeded... " + end <- takeMVar blockNumberVar + awaitBlock $ end + 1 -- make past transactions definitively in past + var' <- newMVar [] + fiber <- runWeb3Configured' $ do + let fltr = (eventFilter contractAddress :: Filter CountSet) {filterFromBlock = BlockWithNumber start} + forkWeb3 $ processUntil' var' fltr ((6 ==) . length) + print "Setting more values" + setValues theCall theSets2 + wait fiber + print "All new values have ben set" + vals <- takeMVar var' + sort (unCountSet <$> vals) `shouldBe` sort (theSets1 <> theSets2) + + it "can stream events starting and ending in the past, bounded" $ \primaryAccount -> do + runWeb3Configured Eth.blockNumber >>= \bn -> awaitBlock (bn + 1) + var <- newMVar [] + let theCall = callFromTo primaryAccount contractAddress + theSets = map (fromJust . uIntNFromInteger) [4, 5, 6] + start <- runWeb3Configured Eth.blockNumber + blockNumberVar <- newEmptyMVar + let fltr = eventFilter contractAddress + print "Setting up filter for past transactions..." + fiber <- runWeb3Configured' $ do + forkWeb3 $ processUntil var fltr ((3 ==) . length) (liftIO . putMVar blockNumberVar . changeBlockNumber) + print "Setting values" + setValues theCall theSets + wait fiber + print "All values have been set" + end <- takeMVar blockNumberVar + var' <- newMVar [] + let fltr' = fltr { filterFromBlock = BlockWithNumber start + , filterToBlock = BlockWithNumber end + } + awaitBlock $ end + 1 -- make it definitively in the past + runWeb3Configured $ processUntil' var' fltr' ((3 ==) . length) + vals <- takeMVar var' + sort (unCountSet <$> vals) `shouldBe` sort theSets + +processUntil :: (Provider provider) + => MVar [CountSet] + -> Filter CountSet + -> ([CountSet] -> Bool) -- TODO: make it work for any event + -> (Change -> Web3 provider ()) + -> Web3 provider () +processUntil var filter predicate action = do + event' filter $ \(ev :: CountSet) -> do + newV <- liftIO $ modifyMVar var $ \v -> return (ev:v, ev:v) + if predicate newV + then do + change <- ask + lift $ action change + return TerminateEvent + else return ContinueEvent + +processUntil' :: (Provider provider) + => MVar [CountSet] + -> Filter CountSet + -> ([CountSet] -> Bool) + -> Web3 provider () +processUntil' var filter predicate = processUntil var filter predicate (const $ return ()) + +setValues :: Call -> [UIntN 256] -> IO () +setValues theCall theSets = forM_ theSets $ \v -> do + runWeb3Configured (setCount theCall v) + threadDelay 1000000 diff --git a/test/Network/Ethereum/Web3/Test/Utils.hs b/test/Network/Ethereum/Web3/Test/Utils.hs index c360ac0..b85b34b 100644 --- a/test/Network/Ethereum/Web3/Test/Utils.hs +++ b/test/Network/Ethereum/Web3/Test/Utils.hs @@ -1,12 +1,13 @@ +{-# LANGUAGE LambdaCase #-} module Network.Ethereum.Web3.Test.Utils ( injectExportedEnvironmentVariables , runWeb3Configured + , runWeb3Configured' , withAccounts , withPrimaryEthereumAccount , callFromTo , sleepSeconds , microtime - , takeMVarWithTimeout , awaitBlock ) where @@ -21,9 +22,11 @@ import Data.String (IsString, fromString) import qualified Data.Text as T import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Traversable (for) -import Network.Ethereum.Web3 (Address, DefaultProvider, Provider (..), Web3, Web3Error, runWeb3') +import Network.Ethereum.Web3 (Address, DefaultProvider, + Provider (..), Web3, Web3Error, + runWeb3') import Network.Ethereum.Web3.Eth (accounts, blockNumber) -import Network.Ethereum.Web3.Types (Call (..)) +import Network.Ethereum.Web3.Types (BlockNumber, Call (..)) import System.Environment (lookupEnv, setEnv) import Test.Hspec.Expectations (shouldSatisfy) @@ -64,6 +67,11 @@ runWeb3Configured f = do v `shouldSatisfy` isRight let Right a = v in return a +runWeb3Configured' :: Web3 EnvironmentProvider a -> IO a +runWeb3Configured' f = do + Right v <- runWeb3' f + return v + withAccounts :: ([Address] -> IO a) -> IO a withAccounts f = runWeb3Configured accounts >>= f @@ -83,24 +91,10 @@ sleepSeconds = threadDelay . (* 1000000) microtime :: IO Integer microtime = numerator . toRational . (* 1000000) <$> getPOSIXTime -takeMVarWithTimeout :: Integer -> MVar a -> IO (Maybe a) -takeMVarWithTimeout timeout mv = do - startTime <- microtime - go (startTime + timeout) - - where go expires = tryTakeMVar mv >>= \case - Just x -> return (Just x) - Nothing -> do - now <- microtime - if now < expires - then threadDelay 1000000 >> go expires - else return Nothing - -awaitBlock :: Integer -> IO () +awaitBlock :: BlockNumber -> IO () awaitBlock bn = do bn' <- runWeb3Configured blockNumber - let bn'' = read (T.unpack bn') - putStrLn $ "awaiting block " ++ show bn ++ ", currently " ++ show bn'' - if bn'' >= bn + putStrLn $ "awaiting block " ++ show bn ++ ", currently " ++ show bn' + if bn' >= bn then return () else threadDelay 1000000 >> awaitBlock bn diff --git a/unit/Network/Ethereum/Web3/Test/EncodingSpec.hs b/unit/Network/Ethereum/Web3/Test/EncodingSpec.hs index 9c20cb0..ef9b4a9 100644 --- a/unit/Network/Ethereum/Web3/Test/EncodingSpec.hs +++ b/unit/Network/Ethereum/Web3/Test/EncodingSpec.hs @@ -1,24 +1,25 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} module Network.Ethereum.Web3.Test.EncodingSpec where -import qualified Data.ByteString.Base16 as BS16 -import Data.ByteString (ByteString) -import Data.ByteArray (Bytes, convert) -import qualified Data.Text as T -import qualified Data.Text.Lazy as TL -import qualified Data.Text.Encoding as T -import Data.Text.Lazy.Builder (toLazyText) -import Generics.SOP (Generic, Rep) -import Data.Maybe (fromJust) -import Network.Ethereum.Web3.Encoding -import Network.Ethereum.Web3.Encoding.Vector -import Network.Ethereum.Web3.Encoding.Int -import Network.Ethereum.Web3.Encoding.Generic -import Data.Monoid -import Data.Sized -import Network.Ethereum.Web3 hiding (convert) -import Test.Hspec +import Data.ByteArray (Bytes, convert) +import Data.ByteString (ByteString) +import qualified Data.ByteString.Base16 as BS16 +import Data.Maybe (fromJust) +import Data.Monoid +import Data.Sized +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import qualified Data.Text.Lazy as TL +import Data.Text.Lazy.Builder (toLazyText) +import Generics.SOP (Generic, Rep) +import Network.Ethereum.Web3 hiding (convert) +import Network.Ethereum.Web3.Encoding +import Network.Ethereum.Web3.Encoding.Generic +import Network.Ethereum.Web3.Encoding.Int +import Network.Ethereum.Web3.Encoding.Vector +import Test.Hspec spec :: Spec diff --git a/unit/Network/Ethereum/Web3/Test/EventSpec.hs b/unit/Network/Ethereum/Web3/Test/EventSpec.hs index 5d386ad..9fec51e 100644 --- a/unit/Network/Ethereum/Web3/Test/EventSpec.hs +++ b/unit/Network/Ethereum/Web3/Test/EventSpec.hs @@ -1,17 +1,18 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE DeriveGeneric #-} module Network.Ethereum.Web3.Test.EventSpec where -import qualified GHC.Generics as GHC -import Generics.SOP -import Data.Tagged -import Network.Ethereum.Web3 -import Network.Ethereum.Web3.Types -import Network.Ethereum.Web3.Encoding.Event -import Network.Ethereum.Web3.Encoding -import Network.Ethereum.Web3.Encoding.Generic -import Test.Hspec +import Data.Tagged +import Generics.SOP +import qualified GHC.Generics as GHC +import Network.Ethereum.Web3 +import Network.Ethereum.Web3.Encoding +import Network.Ethereum.Web3.Encoding.Event +import Network.Ethereum.Web3.Encoding.Generic +import Network.Ethereum.Web3.Types +import Test.Hspec spec :: Spec spec = eventTest @@ -26,7 +27,7 @@ eventTest = , changeTransactionIndex = "0x2" , changeTransactionHash = "0xe8cac6af0ceb3cecbcb2a5639361fc9811b1aa753672cf7c7e8b528df53e0e94" , changeBlockHash = "0x0c7e1701858232ac210e3bcc8ab3b33cc6b08025692b22abb39059dc41f6a76e" - , changeBlockNumber = "0x268" + , changeBlockNumber = 0 , changeAddress = "0x617e5941507aab5d2d8bcb56cb8c6ce2eeb16b21" , changeData = "0x000000000000000000000000000000000000000000000000000000000000000a" , changeTopics = ["0xa32bc18230dd172221ac5c4821a5f1f1a831f27b1396d244cdd891c58f132435"] @@ -38,7 +39,7 @@ eventTest = , changeTransactionIndex = "0x2" , changeTransactionHash = "0xe8cac6af0ceb3cecbcb2a5639361fc9811b1aa753672cf7c7e8b528df53e0e94" , changeBlockHash = "0x0c7e1701858232ac210e3bcc8ab3b33cc6b08025692b22abb39059dc41f6a76e" - , changeBlockNumber = "0x268" + , changeBlockNumber = 0 , changeAddress = "0x617e5941507aab5d2d8bcb56cb8c6ce2eeb16b21" , changeData = "0x000000000000000000000000000000000000000000000000000000000000000a" , changeTopics = [ "0xb32bc18230dd172221ac5c4821a5f1f1a831f27b1396d244cdd891c58f132435" diff --git a/unit/Network/Ethereum/Web3/Test/MethodDumpSpec.hs b/unit/Network/Ethereum/Web3/Test/MethodDumpSpec.hs index 2191ead..0ae073d 100644 --- a/unit/Network/Ethereum/Web3/Test/MethodDumpSpec.hs +++ b/unit/Network/Ethereum/Web3/Test/MethodDumpSpec.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE QuasiQuotes #-} module Network.Ethereum.Web3.Test.MethodDumpSpec where import Control.Monad.IO.Class (liftIO) diff --git a/web3.cabal b/web3.cabal index e4d1c17..ee5995a 100644 --- a/web3.cabal +++ b/web3.cabal @@ -37,45 +37,48 @@ library exposed-modules: Network.Ethereum.Web3 , Network.Ethereum.Unit , Network.Ethereum.Web3.TH - , Network.Ethereum.Web3.Web3 , Network.Ethereum.Web3.Eth , Network.Ethereum.Web3.Net + , Network.Ethereum.Web3.Web3 , Network.Ethereum.Web3.Types , Network.Ethereum.Web3.Address , Network.Ethereum.Web3.JsonAbi , Network.Ethereum.Web3.Provider , Network.Ethereum.Web3.Encoding , Network.Ethereum.Web3.Contract + , Network.Ethereum.Web3.Encoding.Int , Network.Ethereum.Web3.Encoding.Bytes , Network.Ethereum.Web3.Encoding.Event - , Network.Ethereum.Web3.Encoding.Event.Internal - , Network.Ethereum.Web3.Encoding.Generic - , Network.Ethereum.Web3.Encoding.Int , Network.Ethereum.Web3.Encoding.Vector + , Network.Ethereum.Web3.Encoding.Generic other-modules: Network.Ethereum.Web3.JsonRpc , Network.Ethereum.Web3.Internal , Network.Ethereum.Web3.Encoding.Internal + , Network.Ethereum.Web3.Encoding.Event.Internal build-depends: base >4.8 && <4.11 , aeson + , async , base16-bytestring , bytestring , cryptonite - , errors + , data-default , generics-sop , http-client-tls , http-client - , data-default - , memory + , machines , mtl + , errors + , memory , parsec , singletons , sized , tagged , template-haskell - , text , transformers + , text , vector default-extensions: OverloadedStrings + ghc-options: -Werror default-language: Haskell2010 test-suite unit @@ -85,25 +88,24 @@ test-suite unit other-modules: Network.Ethereum.Web3.Test.MethodDumpSpec Network.Ethereum.Web3.Test.EncodingSpec Network.Ethereum.Web3.Test.EventSpec - build-depends: base + build-depends: base >4.8 && <4.11 + , hspec-expectations + , hspec-discover + , hspec-contrib + , hspec , base16-bytestring - , bytestring , data-default , generics-sop - , hspec - , hspec-contrib - , hspec-discover - , hspec-expectations + , transformers + , bytestring , memory + , tagged , sized , split - , stm - , tagged , time , text - , transformers , web3 - ghc-options: -threaded -rtsopts -with-rtsopts=-N -ddump-splices + ghc-options: -Werror -threaded -rtsopts -with-rtsopts=-N -ddump-splices default-extensions: DataKinds LambdaCase DeriveGeneric QuasiQuotes TemplateHaskell OverloadedStrings ScopedTypeVariables TypeApplications default-language: Haskell2010 @@ -114,22 +116,28 @@ test-suite live other-modules: Network.Ethereum.Web3.Test.ComplexStorageSpec , Network.Ethereum.Web3.Test.SimpleStorageSpec , Network.Ethereum.Web3.Test.Utils - build-depends: base + build-depends: async + , base , bytestring , data-default , hspec , hspec-contrib , hspec-discover , hspec-expectations + , hspec-discover + , hspec-contrib + , hspec + , transformers + , data-default + , bytestring , memory - , text , sized , split , stm + , text , time , text - , transformers , web3 - ghc-options: -threaded -rtsopts -with-rtsopts=-N -ddump-splices + ghc-options: -Werror -threaded -rtsopts -with-rtsopts=-N -ddump-splices default-extensions: DataKinds LambdaCase DeriveGeneric QuasiQuotes TemplateHaskell OverloadedStrings ScopedTypeVariables TypeApplications default-language: Haskell2010