From 2bc54c693ca360d69ac579b63f402f14d0431feb Mon Sep 17 00:00:00 2001 From: Piper Merriam Date: Thu, 8 Sep 2016 13:07:34 -0600 Subject: [PATCH] document contracts --- docs/contracts.rst | 192 ++++++++++++++++++++++++++++++++++++++++++++- docs/filters.rst | 143 ++++++++++++++++++++++++++++++++- web3/contract.py | 32 ++++---- 3 files changed, 346 insertions(+), 21 deletions(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index ee5869e..c907617 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -21,12 +21,13 @@ Contract Factories Properties ---------- - Each Contract Factory exposes the following properties. +Each Contract Factory exposes the following properties. .. py:attribute:: Contract.address - The hexidecimal encoded 20 byte address of the contract. + The hexidecimal encoded 20 byte address of the contract. May be ``None`` + if not provided during factory creation. .. py:attribute:: Contract.abi @@ -36,9 +37,192 @@ Properties .. py:attribute:: Contract.code - The contract bytecode string. + The contract bytecode string. May be ``None`` if not provided during + factory creation. .. py:attribute:: Contract.code_runtime - The runtime part of the contract bytecode string. + The runtime part of the contract bytecode string. May be ``None`` if not + provided during factory creation. + + +.. py:attribute:: Contract.code_runtime + + The runtime part of the contract bytecode string. May be ``None`` if not + provided during factory creation. + + +Methods +------- + +Each Contract Factory exposes the following methods. + + +.. py:classmethod:: Contract.deploy(transaction=None, arguments=None) + + Construct and send a transaction to deploy the contract. + + If provided ``transaction`` should be a dictionary conforming to the + ``web3.eth.sendTransaction(transaction)`` method. This value may not + contain the keys ``data`` or ``to``. + + If the contract takes constructor arguments they should be provided as a + list via the ``arguments`` parameter. + + If a ``gas`` value is not provided, then the ``gas`` value for the + deployment transaction will be created using the ``web3.eth.estimateGas()`` + method. + + Returns the transaction hash for the deploy transaction. + +.. py:method:: Contract.transact(transaction).myMethod(*args, **kwargs) + + Execute the specified function by sending a new public transaction. + + This is executed in two steps. + + The first portion of this function call ``transact(transaction)`` takes a + single parameter which should be a python dictionary conforming to + the same format as the ``web3.eth.sendTransaction(transaction)`` method. + This dictionary may not contain the keys ``data`` or ``to``. + + The second portion of the function call ``myMethod(*args, **kwargs)`` + selects the appropriate contract function based on the name and provided + argument. Arguments can be provided as positional arguments, keyword + arguments, or a mix of the two. + + Returns the transaction hash. + + .. code-block:: python + + >>> token_contract.transact().transfer(web3.eth.accounts[1], 12345) + "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd" + + +.. py:method:: Contract.call(transaction).myMethod(*args) + + Call a contract function, executing the transaction locally using the + ``eth_call`` API. This will not create a new public transaction. + + This method behaves the same as the :py:method::`Contract.transact` method, + with transaction details being passed into the first portion of the + function call, and function arguments being passed into the second portion. + + Returns the return value of the executed function. + + .. code-block:: python + + >>> my_contract.call().multiply7(3) + 21 + >>> token_contract.call({'from': web3.eth.coinbase}).myBalance() + 12345 # the token balance for `web3.eth.coinbase` + >>> token_contract.call({'from': web3.eth.accounts[1]}).myBalance() + 54321 # the token balance for the account `web3.eth.accounts[1]` + + +.. py:method:: Contract.estimateGas(transaction).myMethod(*args) + + Call a contract function, executing the transaction locally using the + ``eth_call`` API. This will not create a new public transaction. + + This method behaves the same as the :py:method::`Contract.transact` method, + with transaction details being passed into the first portion of the + function call, and function arguments being passed into the second portion. + + Returns the amount of gas consumed which can be used as a gas estimate for + executing this transaction publicly. + + .. code-block:: python + + >>> my_contract.estimateGas().multiply7(3) + 42650 + + +Events +------ + +.. py:method:: +.. py:classmethod:: Contract.on(event_name, filter_params=None, *callbacks) + + Creates a new :py:class:`web3.utils.filters.LogFilter` instance. + + The ``event_name`` parameter should be the name of the contract event you + want to filter on. + + If provided, ``filter_params`` should be a dictionary specifying + additional filters for log entries. The following keys are supported. + + * ``filters``: ``dictionary`` - (optional) Dictionary keys should be + argument names for the Event arguments. Dictionary values should be the + value you want to filter on, or a list of values to be filtered on. + Lists of values will match log entries who's argument matches any value + in the list. + * ``fromBlock``: ``integer/tag`` - (optional, default: "latest") Integer + block number, or "latest" for the last mined block or "pending", + "earliest" for not yet mined transactions. + * ``toBlock``: ``integer/tag`` - (optional, default: "latest") Integer + block number, or "latest" for the last mined block or "pending", + "earliest" for not yet mined transactions. + * ``address``: ``string`` or list of ``strings``, each 20 Bytes - + (optional) Contract address or a list of addresses from which logs should + originate. + * ``topics``: list of 32 byte ``strings`` or ``null`` - (optional) Array of + topics that should be used for filtering. Topics are order-dependent. + This parameter can also be a list of topic lists in which case filtering + will match any of the provided topic arrays. + + The event topic for the event specified by ``event_name`` will be added to + the ``filter_params['topics']`` list. + + If the :py:attribute::`Contract.address` attribute for this contract is + non-null, the contract address will be added to the ``filter_params``. + + If provided, the ``*callbacks`` parameter should be callables which accept + a single Event Log object. When callbacks are provided, the filter will be + *started*. Otherwise the filter will be returned without starting it. + + The Event Log Object is a python dictionary with the following keys: + + * ``args``: Dictionary - The arguments coming from the event. + * ``event``: String - The event name. + * ``logIndex``: Number - integer of the log index position in the block. + * ``transactionIndex``: Number - integer of the transactions index position + log was created from. + * ``transactionHash``: String, 32 Bytes - hash of the transactions this log + was created from. + * ``address``: String, 32 Bytes - address from which this log originated. + * ``blockHash``: String, 32 Bytes - hash of the block where this log was + in. null when its pending. + * ``blockNumber``: Number - the block number where this log was in. null + when its pending. + + + .. code-block:: python + + >>> transfer_filter = my_token_contract.on('Transfer', {'filters': {'_from': '0xdc3a9db694bcdd55ebae4a89b22ac6d12b3f0c24'}}) + >>> transfer_filter.get() + [...] # array of Event Log Objects that match the filter. + >>> transfer_filter.watch(my_callback) + # now `my_callback` will be called each time a new matching event log + # is encountered. + + +.. py:method:: +.. py:classmethod:: Contract.pastEvents(event_name, filter_params=None, *callbacks) + + Creates a new :py:class:`web3.utils.filters.PastLogFilter` instance which + will match historical event logs. + + All parameters behave the same as the :py:method::`Contract.on` method with + the exception that: + + * ``filter_params`` parameter may not contain the keys ``toBlock`` or + ``fromBlock``. + + + .. code-block:: python + + >>> transfer_filter = my_token_contract.pastEvents('Transfer', {'filters': {'_from': '0xdc3a9db694bcdd55ebae4a89b22ac6d12b3f0c24'}}) + >>> transfer_filter.get() + [...] # array of Event Log Objects that match the filter for all historical events. diff --git a/docs/filters.rst b/docs/filters.rst index 91beb82..6ddd68b 100644 --- a/docs/filters.rst +++ b/docs/filters.rst @@ -1,4 +1,145 @@ Filtering ========= -TODO +.. py:module:: web3.utils.filters +.. py:currentmodule:: web3.utils.filters + + +The ``web3.eth.filter`` method can be used to setup filter for: + +* Pending Transactions +* New Blocks +* Event Logs + + +Filter API +---------- + +.. py:class:: Filter(web3, filter_id) + +The :py:class::`Filter` object is a subclass of the +:py:class::`gevent.Greenlet` object. It exposes these additional properties +and methods. + + +.. py:attribute:: Filter.filter_id + + The ``filter_id`` for this filter as returned by the ``eth_newFilter`` RPC + method when this filter was created. + + +.. py:attribute:: Filter.callbacks + + A list of callbacks that this filter will call with new entries. + + +.. py:attribute:: Filter.running + + Boolean as to whether this filter is currently polling. + + +.. py:attribute:: Filter.stopped + + Boolean as to whether this filter has been stopped. Will be set to + ``None`` if the filter has not yet been started. + + +.. py:method:: Filter.format_entry(entry) + + Hook for subclasses to modify the format of the log entries this filter + returns, or passes to it's callback functions. + + By default this returns the ``entry`` parameter umodified. + + +.. py:method:: Filter.is_valid_entry(entry) + + Hook for subclasses to add additional programatic filtering. The default + implementation always returns ``True``. + + +.. py:method:: Filter.watch(*callbacks) + + Registers the provided ``callbacks`` to be called with each new entry this + filter encounters and starts the filter polling for changes. + + Can only be called once on each filter. Cannot be called on a filter that + has already been started. + +.. py:method:: Filter.stop_watching(self, timeout=0) + + Stops the filter from polling and uninstalls the filter. Blocks until all + events that are currently being processed have been processed. + + +Block and Transaction Filters +----------------------------- + +.. py:class:: BlockFilter(...) + + You can setup a filter for new blocks using ``web3.eth.filter('latest')`` which + will return a new :py:class::`BlockFilter` object. + + .. code-block:: python + + >>> def new_block_callback(block_hash): + ... sys.stdout.write("New Block: {0}".format(block_hash)) + ... + >>> new_block_filter = web3.eth.filter('latest') + >>> new_block_filter.watch(new_block_filter) + # each time the client receieves a new block the `new_block_callback` + # function will be called with the block hash. + + +.. py:class:: TransactionFilter(...) + +You can setup a filter for new blocks using ``web3.eth.filter('pending')`` which +will return a new :py:class::`BlockFilter` object. + + .. code-block:: python + + >>> def new_transaction_callback(transaction_hash): + ... sys.stdout.write("New Block: {0}".format(transaction_hash)) + ... + >>> new_transaction_filter = web3.eth.filter('pending') + >>> new_transaction_filter.watch(new_transaction_filter) + # each time the client receieves a unmined transaction the + # `new_transaction_filter` function will be called with the transaction + # hash. + + +Event Log Filters +----------------- + +.. py:class:: LogFilter(web3, filter_id, log_entry_formatter=None, data_filter_set=None) + +The :py:class::`LogFilter` class is used for all filters pertaining to even +logs. It exposes the following additional methods. + + +.. py:method:: LogFilter.get(only_changes=True) + + Synchronously retrieve the event logs for this filter. + + If ``only_changes`` is ``True`` then logs will be retrieved using the + ``web3.eth.getFilterChanges`` which returns only new entries since the last + poll. + + If ``only_changes`` is ``False`` then the logs will be retrieved using the + ``web3.eth.getFilterLogs`` which returns all logs that match the given + filter. + + This method will raise a ``ValueError`` if called on a filter that is + currently polling. + + +The :py:class::`LogFilter` class is returned from the +:py:method::`web3.contract.Contract.on` and will be configured to extract the +event data from the event logs. + + +.. py:class:: PastLogFilter(...) + +The :py:class::`PastLogFilter` is a subclass of :py:class::`LogFilter` that is +configured specially to return historical event logs. It conforms to the same +API as the ``LogFilter`` class. diff --git a/web3/contract.py b/web3/contract.py index 04cf769..eac148a 100644 --- a/web3/contract.py +++ b/web3/contract.py @@ -365,31 +365,31 @@ class Contract(object): return deploy_data @combomethod - def on(self, event_name, default_filter_params=None, *callbacks): + def on(self, event_name, filter_params=None, *callbacks): """ register a callback to be triggered on the appropriate events. """ - if default_filter_params is None: - default_filter_params = {} + if filter_params is None: + filter_params = {} - argument_filters = default_filter_params.pop('filter', {}) + argument_filters = filter_params.pop('filter', {}) argument_filter_names = list(argument_filters.keys()) event_abi = self.find_matching_event_abi(event_name, argument_filter_names) - data_filter_set, filter_params = construct_event_filter_params( + data_filter_set, event_filter_params = construct_event_filter_params( event_abi, contract_address=self.address, argument_filters=argument_filters, - **default_filter_params + **filter_params ) log_data_extract_fn = functools.partial(get_event_data, event_abi) - log_filter = self.web3.eth.filter(filter_params) + log_filter = self.web3.eth.filter(event_filter_params) log_filter.set_data_filters(data_filter_set) log_filter.log_entry_formatter = log_data_extract_fn - log_filter.filter_params = filter_params + log_filter.filter_params = event_filter_params if callbacks: log_filter.watch(*callbacks) @@ -397,26 +397,26 @@ class Contract(object): return log_filter @combomethod - def pastEvents(self, event_name, default_filter_params=None, *callbacks): + def pastEvents(self, event_name, filter_params=None, *callbacks): """ register a callback to be triggered on all past events. """ - if default_filter_params is None: - default_filter_params = {} + if filter_params is None: + filter_params = {} - if 'fromBlock' in default_filter_params or 'toBlock' in default_filter_params: + if 'fromBlock' in filter_params or 'toBlock' in filter_params: raise ValueError("Cannot provide `fromBlock` or `toBlock` in `pastEvents` calls") - filter_params = {} - filter_params.update(default_filter_params) - filter_params.update({ + event_filter_params = {} + event_filter_params.update(filter_params) + event_filter_params.update({ 'fromBlock': "earliest", 'toBlock': self.web3.eth.blockNumber, }) log_filter = self.on( event_name, - default_filter_params=filter_params, + filter_params=event_filter_params, ) past_log_filter = PastLogFilter(