Merge branch 'master' into fix-setuphs-on-jenkins

This commit is contained in:
Ilya Ostrovskiy 2018-01-17 14:59:48 -05:00 committed by GitHub
commit 2156cee9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 533 additions and 218 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,4 @@
{-# LANGUAGE PolyKinds #-}
-- |
-- Module : Network.Ethereum.Web3.Encoding
-- Copyright : Alexander Krupenkin 2016

View File

@ -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

View File

@ -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 '[]

View File

@ -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

View File

@ -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<M> and uint<M> support.
--
module Network.Ethereum.Web3.Encoding.Int where
import Control.Error (hush)

View File

@ -1,6 +1,5 @@
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Network.Ethereum.Web3.Encoding.Internal
-- Copyright : Alexander Krupenkin 2016

View File

@ -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)

View File

@ -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"

View File

@ -1,4 +1,4 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TemplateHaskell #-}
-- |
-- Module : Network.Ethereum.Web3.JsonAbi

View File

@ -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

View File

@ -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]

View File

@ -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.

View File

@ -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:
- '.'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -1,3 +1,4 @@
{-# LANGUAGE QuasiQuotes #-}
module Network.Ethereum.Web3.Test.MethodDumpSpec where
import Control.Monad.IO.Class (liftIO)

View File

@ -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