Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
contracts.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright (c) 2015-2022 Clearmatics Technologies Ltd
4 #
5 # SPDX-License-Identifier: LGPL-3.0+
6 
7 from __future__ import annotations
8 from zeth.core.utils import EtherValue
9 from zeth.core.constants import SOL_COMPILER_VERSION
10 from web3._utils.contracts import find_matching_event_abi
11 from eth_utils import event_abi_to_log_topic
12 import solcx
13 from typing import Dict, List, Iterator, Optional, Union, Iterable, Any, cast
14 
15 # Avoid trying to read too much data into memory
16 SYNC_BLOCKS_PER_BATCH = 1000
17 
18 Interface = Dict[str, Any]
19 
20 
22  """
23  Minimal data required to instantiate the in-memory interface to a contract.
24  """
25  def __init__(self, address: str, abi: Dict[str, Any]):
26  self.address = address
27  self.abi = abi
28 
29  def to_json_dict(self) -> Dict[str, Any]:
30  return {
31  "address": self.address,
32  "abi": self.abi
33  }
34 
35  @staticmethod
36  def from_json_dict(desc_json: Dict[str, Any]) -> InstanceDescription:
37  return InstanceDescription(desc_json["address"], desc_json["abi"])
38 
39  @staticmethod
40  def deploy(
41  web3: Any,
42  source_file: str,
43  contract_name: str,
44  deployer_eth_address: str,
45  deployer_eth_private_key: Optional[bytes],
46  deployment_gas: Optional[int],
47  compiler_flags: Dict[str, Any] = None,
48  args: Iterable[Any] = None) -> InstanceDescription:
49  """
50  Compile and deploy a contract, returning the live instance and an instance
51  description (which the caller should save in order to access the
52  instance in the future).
53  """
54  compiled = InstanceDescription.compile(
55  source_file, contract_name, compiler_flags)
56  assert compiled
57  instance_desc = InstanceDescription.deploy_from_compiled(
58  web3,
59  deployer_eth_address,
60  deployer_eth_private_key,
61  deployment_gas,
62  compiled,
63  *(args or []))
64  print(
65  f"deploy: contract: {contract_name} "
66  f"to address: {instance_desc.address}")
67  return instance_desc
68 
69  @staticmethod
71  web3: Any,
72  deployer_eth_address: str,
73  deployer_eth_private_key: Optional[bytes],
74  deployment_gas: Optional[int],
75  compiled: Any,
76  *args: Any) -> InstanceDescription:
77  contract = web3.eth.contract(
78  abi=compiled['abi'], bytecode=compiled['bin'])
79  construct_call = contract.constructor(*args)
80  tx_hash = send_contract_call(
81  web3,
82  construct_call,
83  deployer_eth_address,
84  deployer_eth_private_key,
85  None,
86  deployment_gas)
87 
88  tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000)
89  contract_address = tx_receipt['contractAddress']
90  print(
91  f"deploy: tx_hash={tx_hash[0:8].hex()}, " +
92  f" gasUsed={tx_receipt.gasUsed}, status={tx_receipt.status}")
93  return InstanceDescription(contract_address, compiled['abi'])
94 
95  @staticmethod
96  def compile(
97  source_file: str,
98  contract_name: str,
99  compiler_flags: Dict[str, Any] = None) \
100  -> Any:
101  compiled_all = compile_files([source_file], **(compiler_flags or {}))
102  assert compiled_all
103  compiled = compiled_all[f"{source_file}:{contract_name}"]
104  assert compiled
105  return compiled
106 
107  def instantiate(self, web3: Any) -> Any:
108  """
109  Return the instantiated contract
110  """
111  return web3.eth.contract(address=self.address, abi=self.abi)
112 
113 
114 def get_block_number(web3: Any) -> int:
115  return web3.eth.blockNumber
116 
117 
118 def install_sol() -> None:
119  solcx.install_solc(SOL_COMPILER_VERSION)
120 
121 
122 def compile_files(files: List[str], **kwargs: Any) -> Any:
123  """
124  Wrapper around solcx which ensures the required version of the compiler is
125  used.
126  """
127  solcx.set_solc_version(SOL_COMPILER_VERSION)
128  return solcx.compile_files(files, optimize=True, **kwargs)
129 
130 
132  web3: Any,
133  call: Any,
134  sender_eth_addr: str,
135  sender_eth_private_key: Optional[bytes] = None,
136  value: Optional[EtherValue] = None,
137  gas: Optional[int] = None) -> bytes:
138  """
139  Broadcast a transaction for a contract call, handling the difference
140  between hosted keys (sender_eth_private_key is None) and local keys
141  (sender_eth_private_key is not None). Returns the hash of the broadcast
142  transaction.
143  """
144  tx_desc: Dict[str, Union[str, int]] = {'from': sender_eth_addr}
145  if value:
146  tx_desc["value"] = value.wei
147  if gas:
148  tx_desc["gas"] = gas
149  if sender_eth_private_key:
150  tx_desc["gasPrice"] = web3.eth.gasPrice
151  tx_desc["nonce"] = web3.eth.getTransactionCount(sender_eth_addr)
152  transaction = call.buildTransaction(tx_desc)
153  signed_tx = web3.eth.account.signTransaction(
154  transaction, sender_eth_private_key)
155  print(f"send_contract_call: size={len(signed_tx.rawTransaction)}")
156  return web3.eth.sendRawTransaction(signed_tx.rawTransaction)
157 
158  # Hosted path
159  return call.transact(tx_desc)
160 
161 
163  call: Any,
164  sender_eth_addr: str,
165  value: Optional[EtherValue] = None,
166  gas: Optional[int] = None) -> Any:
167  """
168  Make a contract call locally on the RPC host and return the result. Does
169  not create a transaction.
170  """
171  tx_desc: Dict[str, Union[str, int]] = {'from': sender_eth_addr}
172  if value:
173  tx_desc["value"] = value.wei
174  if gas:
175  tx_desc["gas"] = gas
176  return call.call(tx_desc)
177 
178 
180  web3: Any,
181  instance: Any,
182  event_name: str,
183  start_block: int,
184  end_block: int,
185  batch_size: Optional[int]) -> Iterator[Any]:
186  """
187  Query the attached node for all events emitted by the given contract
188  instance, with the given name. Yields an iterator of event-specific objects
189  to be decoded by the caller.
190  """
191 
192  # It is possible to achieve this via the contract interface, with code of
193  # the form:
194  #
195  # event = instance.events[event_name]
196  # filter = event.createFilter(fromBlock=start_block, toBlock=to_block)
197  # logs = web3.eth.getFilterLogs(filter)
198  #
199  # However, this creates filters on the host node, which may not be
200  # permitted in all configurations. Hence, the code here iterates manually,
201  # skpping events with other topics, from the same contract.
202 
203  contract_address = instance.address
204  contract_event = instance.events[event_name]()
205 
206  event_abi = find_matching_event_abi(instance.abi, event_name=event_name)
207  log_topic = event_abi_to_log_topic(cast(Dict[str, Any], event_abi))
208 
209  batch_size = batch_size or SYNC_BLOCKS_PER_BATCH
210  while start_block <= end_block:
211  # Filters are *inclusive* wrt "toBlock", hence the -1 here, and +1 to
212  # set start_block before iterating.
213  to_block = min(start_block + batch_size - 1, end_block)
214  filter_params = {
215  'fromBlock': start_block,
216  'toBlock': to_block,
217  'address': contract_address,
218  }
219  logs = web3.eth.getLogs(filter_params)
220  for log in logs:
221  if log_topic == log['topics'][0]:
222  yield contract_event.processLog(log)
223  start_block = to_block + 1
224 
225 
227  instance: Any,
228  event_name: str,
229  tx_receipt: Any) -> Iterator[Any]:
230  """
231  Query a transaction receipt for all events emitted by the given contract
232  instance with a given event name. Yields an iterator of event-specific
233  objects to be decoded by the caller. This function intentionally avoids
234  connecting to a node, or creating host-side filters.
235  """
236  contract_address = instance.address
237  contract_event = instance.events[event_name]()
238 
239  event_abi = find_matching_event_abi(instance.abi, event_name=event_name)
240  log_topic = event_abi_to_log_topic(cast(Dict[str, Any], event_abi))
241  for log in tx_receipt.logs:
242  if log.address == contract_address and log_topic == log['topics'][0]:
243  yield contract_event.processLog(log)
zeth.core.contracts.InstanceDescription.instantiate
Any instantiate(self, Any web3)
Definition: contracts.py:107
zeth.core.contracts.InstanceDescription
Definition: contracts.py:21
zeth.core.contracts.InstanceDescription.compile
Any compile(str source_file, str contract_name, Dict[str, Any] compiler_flags=None)
Definition: contracts.py:96
zeth.core.contracts.install_sol
None install_sol()
Definition: contracts.py:118
zeth.core.constants
Definition: constants.py:1
zeth.core.contracts.InstanceDescription.to_json_dict
Dict[str, Any] to_json_dict(self)
Definition: contracts.py:29
zeth.core.contracts.InstanceDescription.deploy_from_compiled
InstanceDescription deploy_from_compiled(Any web3, str deployer_eth_address, Optional[bytes] deployer_eth_private_key, Optional[int] deployment_gas, Any compiled, *Any args)
Definition: contracts.py:70
zeth.core.contracts.compile_files
Any compile_files(List[str] files, **Any kwargs)
Definition: contracts.py:122
zeth.core.utils
Definition: utils.py:1
zeth.core.contracts.InstanceDescription.deploy
InstanceDescription deploy(Any web3, str source_file, str contract_name, str deployer_eth_address, Optional[bytes] deployer_eth_private_key, Optional[int] deployment_gas, Dict[str, Any] compiler_flags=None, Iterable[Any] args=None)
Definition: contracts.py:40
zeth.core.contracts.InstanceDescription.from_json_dict
InstanceDescription from_json_dict(Dict[str, Any] desc_json)
Definition: contracts.py:36
zeth.core.contracts.get_block_number
int get_block_number(Any web3)
Definition: contracts.py:114
zeth.core.contracts.InstanceDescription.abi
abi
Definition: contracts.py:27
zeth.core.contracts.get_event_logs_from_tx_receipt
Iterator[Any] get_event_logs_from_tx_receipt(Any instance, str event_name, Any tx_receipt)
Definition: contracts.py:226
zeth.core.contracts.get_event_logs
Iterator[Any] get_event_logs(Any web3, Any instance, str event_name, int start_block, int end_block, Optional[int] batch_size)
Definition: contracts.py:179
zeth.core.contracts.InstanceDescription.address
address
Definition: contracts.py:26
zeth.core.contracts.send_contract_call
bytes send_contract_call(Any web3, Any call, str sender_eth_addr, Optional[bytes] sender_eth_private_key=None, Optional[EtherValue] value=None, Optional[int] gas=None)
Definition: contracts.py:131
zeth.core.contracts.local_contract_call
Any local_contract_call(Any call, str sender_eth_addr, Optional[EtherValue] value=None, Optional[int] gas=None)
Definition: contracts.py:162
zeth.core.contracts.InstanceDescription.__init__
def __init__(self, str address, Dict[str, Any] abi)
Definition: contracts.py:25