mirror of
https://github.com/airalab/hs-web3.git
synced 2024-10-04 04:07:20 +03:00
Merge branch 'master' into fix-setuphs-on-jenkins
This commit is contained in:
commit
2156cee9be
12
.travis.yml
12
.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:
|
||||
|
6
Setup.hs
6
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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,4 @@
|
||||
{-# LANGUAGE PolyKinds #-}
|
||||
|
||||
-- |
|
||||
-- Module : Network.Ethereum.Web3.Encoding
|
||||
-- Copyright : Alexander Krupenkin 2016
|
||||
|
@ -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
|
||||
|
@ -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 '[]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,6 +1,5 @@
|
||||
{-# LANGUAGE PolyKinds #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
-- |
|
||||
-- Module : Network.Ethereum.Web3.Encoding.Internal
|
||||
-- Copyright : Alexander Krupenkin 2016
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
-- |
|
||||
-- Module : Network.Ethereum.Web3.JsonAbi
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
- '.'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
module Network.Ethereum.Web3.Test.MethodDumpSpec where
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
|
52
web3.cabal
52
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
|
||||
|
Loading…
Reference in New Issue
Block a user