eth-prototype 0.7.5__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eth-prototype
3
- Version: 0.7.5
3
+ Version: 1.0.1
4
4
  Summary: Prototype Ethereum Smart Contracts in Python
5
5
  Home-page: https://github.com/gnarvaja/eth-prototype
6
6
  Author: Guillermo M. Narvaja
@@ -11,15 +11,14 @@ Platform: any
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
13
  Requires-Python: >=3.7
14
- Description-Content-Type: text/x-rst; charset=UTF-8
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
15
  License-File: LICENSE.txt
16
16
  License-File: AUTHORS.rst
17
17
  Requires-Dist: m9g
18
18
  Requires-Dist: environs
19
19
  Requires-Dist: requests
20
+ Requires-Dist: hexbytes
20
21
  Requires-Dist: importlib-metadata ; python_version < "3.8"
21
- Provides-Extra: brownie
22
- Requires-Dist: eth-brownie ; extra == 'brownie'
23
22
  Provides-Extra: defender
24
23
  Requires-Dist: boto3 ; extra == 'defender'
25
24
  Provides-Extra: gmpy2
@@ -29,74 +28,46 @@ Requires-Dist: setuptools ; extra == 'testing'
29
28
  Requires-Dist: pytest ; extra == 'testing'
30
29
  Requires-Dist: gmpy2 ; extra == 'testing'
31
30
  Requires-Dist: pytest-cov ; extra == 'testing'
32
- Provides-Extra: testing-br
33
- Requires-Dist: eth-brownie ; extra == 'testing-br'
34
- Requires-Dist: setuptools ; extra == 'testing-br'
35
- Requires-Dist: pytest ; extra == 'testing-br'
36
- Requires-Dist: pytest-cov ; extra == 'testing-br'
37
31
  Provides-Extra: testing-w3
38
- Requires-Dist: web3[tester] ; extra == 'testing-w3'
32
+ Requires-Dist: web3[tester] >=6 ; extra == 'testing-w3'
39
33
  Requires-Dist: setuptools ; extra == 'testing-w3'
40
34
  Requires-Dist: pytest ; extra == 'testing-w3'
41
- Requires-Dist: eth-event ; extra == 'testing-w3'
42
35
  Requires-Dist: pytest-cov ; extra == 'testing-w3'
43
36
  Requires-Dist: boto3 ; extra == 'testing-w3'
44
37
  Provides-Extra: web3
45
- Requires-Dist: web3 ; extra == 'web3'
46
- Requires-Dist: eth-event ; extra == 'web3'
38
+ Requires-Dist: web3 >=6 ; extra == 'web3'
47
39
 
48
- =============
49
- eth-prototype
50
- =============
40
+ # eth-prototype
51
41
 
52
42
 
53
43
  Prototype Ethereum Smart Contracts in Python
54
44
 
55
45
 
56
- Description
57
- ===========
46
+ ## Description
58
47
 
59
48
  Library with base classes to prototype Ethereum Smart Contracts in Python. This includes:
60
49
 
61
50
  - wadray: classes for fixed number of decimals math implemented with integers.
62
51
  - contracts: classes to simulate contracts in Python with features like *rollback* on exception, external
63
52
  methods, views. Also classes for common contracts like ERC20 (tokens), ERC721 (NFTs) and AccessControl.
64
- - brwrappers: classes to wrap ethereum contracts called thru [brownie](https://github.com/eth-brownie/brownie/) but with a pythonic interface
65
53
  - w3wrappers: classes to wrap ethereum contracts called thru [web3py](https://web3py.readthedocs.io/) but with a pythonic interface
66
54
 
55
+ To use the `defender_relay` module you need to have the `warrant` package from this repo: https://github.com/gnarvaja/warrant. Add it to your requirements.txt as:
67
56
 
68
- Tox Tests
69
- =========
57
+ ```
58
+ warrant @ git+https://github.com/gnarvaja/warrant.git#egg=warrant
59
+ ```
70
60
 
71
- The tox tests run in three variants:
61
+ Note that using the `warrant` package from pypi will not work because of incompatibilities with newer python versions.
72
62
 
73
- - `default`: only uses and tests the prototype libraries, no blockchain.
74
- - `default-w3`: users and tests two variants: prototype and w3wrappers (wrappers using web3py).
75
- - `default-br`: users and tests two variants: prototype and brwrappers (wrappers using brownie).
76
-
77
- It's not possible for now running all the tests together because of incompatibilities between brownie and web3[tester].
78
-
79
-
80
- To run the tox `default-br` tests, you need an environment with Brownie, SOLC and other requirements.
63
+ ## Tox Tests
81
64
 
82
- You can do it using a Docker image an a few commands
65
+ The tox tests run in two variants:
83
66
 
84
- .. code-block:: bash
85
-
86
- docker run -it -v $PWD:/code -w /code gnarvaja/eth-dev:1.0.0 bash
87
- gnarvaja/eth-dev:eth-proto-brownie
88
- pip install tox
89
- brownie pm install OpenZeppelin/openzeppelin-contracts@4.3.2
90
- brownie pm install OpenZeppelin/openzeppelin-contracts-upgradeable@4.3.2
91
- tox -e py39-br
92
-
93
- docker run -it -v $PWD:/code -w /code gnarvaja/eth-dev:eth-proto-brownie bash
94
- tox -e py39-br
95
-
96
- .. _pyscaffold-notes:
67
+ - `default`: only uses and tests the prototype libraries, no blockchain.
68
+ - `default-w3`: users and tests two variants: prototype and w3wrappers (wrappers using web3py).
97
69
 
98
- Note
99
- ====
70
+ # Note
100
71
 
101
72
  This project has been set up using PyScaffold 4.0.2. For details and usage
102
73
  information on PyScaffold see https://pyscaffold.org/.
@@ -0,0 +1,13 @@
1
+ ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
+ ethproto/build_artifacts.py,sha256=FzgUO6ierhCVBKHRxTkgrRx4AaAL6G5ql5Yzx_oTTmg,5578
3
+ ethproto/contracts.py,sha256=rNVbCK1hURy7lWKhzSdXgVWo3wx9O_Ghk-6PfgOsRNk,18662
4
+ ethproto/defender_relay.py,sha256=05A8TfRZwiBhCpo924Pf9CjfKSir2Wvgg1p_asFxJbw,1777
5
+ ethproto/w3wrappers.py,sha256=MrRf7GLFgDhKYKiTctjNONZHQNTo6nVzqPzprv0BU2w,20607
6
+ ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
7
+ ethproto/wrappers.py,sha256=9qDwRDOXw3wquzvGfIsub-VPWm98GBWP7dHLFOUPWzg,17307
8
+ eth_prototype-1.0.1.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
9
+ eth_prototype-1.0.1.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
10
+ eth_prototype-1.0.1.dist-info/METADATA,sha256=TE-gm2NZKjLg7mgbihmuddG0XwORT7DrAM8UqtLPMtg,2669
11
+ eth_prototype-1.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
+ eth_prototype-1.0.1.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
13
+ eth_prototype-1.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,152 @@
1
+ """Helper classes to use hardhat build artifacts from python"""
2
+
3
+
4
+ import json
5
+ import os
6
+ import os.path
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Union, Tuple
11
+
12
+ LIBRARY_PLACEHOLDER_MATCHER = re.compile(r"__\$[0-9a-f]{34}\$__")
13
+
14
+
15
+ @dataclass
16
+ class Artifact:
17
+ contract_name: str
18
+ abi: list
19
+ bytecode: str
20
+ deployed_bytecode: str
21
+ link_references: dict
22
+ deployed_link_references: dict
23
+
24
+ def __init__(self, **kwargs):
25
+ self.contract_name = kwargs["contractName"]
26
+ self.abi = kwargs["abi"]
27
+ self.bytecode = kwargs["bytecode"]
28
+ self.deployed_bytecode = kwargs["deployedBytecode"]
29
+ self.link_references = kwargs["linkReferences"]
30
+ self.deployed_link_references = kwargs["deployedLinkReferences"]
31
+
32
+ def link(self, libraries: dict) -> "Artifact":
33
+ """Returns a new artifact with the external libraries linked
34
+
35
+ Libraries is a dictionary of the form {library_name: address}
36
+ """
37
+ bytecode = self._replace_link_references(
38
+ self.bytecode, self.link_references, libraries
39
+ )
40
+ deployed_bytecode = self._replace_link_references(
41
+ self.deployed_bytecode, self.deployed_link_references, libraries
42
+ )
43
+ return Artifact(
44
+ contractName=self.contract_name,
45
+ abi=self.abi,
46
+ bytecode=bytecode,
47
+ deployedBytecode=deployed_bytecode,
48
+ linkReferences=self.link_references,
49
+ deployedLinkReferences=self.deployed_link_references,
50
+ )
51
+
52
+ def libraries(self) -> Tuple[str, str]:
53
+ """Generates a tuple of (library, source) for each library reference in the artifact"""
54
+ for source, libs in self.link_references.items():
55
+ for lib in libs.keys():
56
+ yield lib, source
57
+
58
+ def _replace_link_references(
59
+ self, bytecode: str, link_references: dict, libraries: dict
60
+ ) -> str:
61
+ # remove 0x prefix if present
62
+ bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode
63
+
64
+ for libs in link_references.values():
65
+ for lib, lib_refs in libs.items():
66
+ try:
67
+ address = libraries[lib]
68
+ except KeyError:
69
+ raise ValueError(f"Missing library address for {lib}")
70
+ address = address[2:] if address.startswith("0x") else address
71
+
72
+ assert len(address) == 40 # Sanity check
73
+
74
+ for ref in lib_refs:
75
+ # 2 nibbles -> 1 byte
76
+ start = ref["start"] * 2
77
+ length = ref["length"] * 2
78
+
79
+ # Sanity check
80
+ assert LIBRARY_PLACEHOLDER_MATCHER.match(
81
+ bytecode[start : start + length]
82
+ ), f"Unexpected placeholder at position {start}: {bytecode[start:start + length]}"
83
+
84
+ # Replace the placeholder with the actual address
85
+ bytecode = bytecode[:start] + address + bytecode[start + length :]
86
+
87
+ # Return value always has 0x prefix
88
+ return "0x" + bytecode
89
+
90
+ def __str__(self):
91
+ return f"Artifact({self.contract_name})"
92
+
93
+ def __repr__(self):
94
+ return f"Artifact({self.contract_name})"
95
+
96
+
97
+ class ArtifactLibrary:
98
+ def __init__(self, *paths: Tuple[Union[str, Path]]):
99
+ self.lookup_paths = [Path(p).absolute() for p in paths]
100
+ self._fullpath_cache = {}
101
+ self._name_cache = {}
102
+
103
+ def get_artifact(self, contract: str) -> Artifact:
104
+ """Returns a build artifact by full contract path
105
+
106
+ This method is compatible with hardhat's artifact structure.
107
+
108
+ Examples:
109
+
110
+ >>> library = ArtifactLibrary("./artifacts")
111
+ >>> counter_build = library.get_artifact("contracts/Counter.sol")
112
+ >>> proxy_build = library.get_artifact("@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol")
113
+ """
114
+ contract = Path(os.path.normpath(contract))
115
+
116
+ if contract not in self._fullpath_cache:
117
+ for path in self.lookup_paths:
118
+ build_artifact_path = (
119
+ path / contract / contract.with_suffix(".json").name
120
+ )
121
+ if build_artifact_path.exists():
122
+ with open(build_artifact_path) as f:
123
+ self._fullpath_cache[contract] = Artifact(**json.load(f))
124
+
125
+ if contract not in self._fullpath_cache:
126
+ raise FileNotFoundError(
127
+ f"Could not find artifact for {contract} on {self.lookup_paths}"
128
+ )
129
+
130
+ return self._fullpath_cache[contract]
131
+
132
+ def get_artifact_by_name(self, contract_name: str) -> Artifact:
133
+ """Returns a build artifact by looking for a matching contract name
134
+
135
+ Example:
136
+
137
+ >>> library = ArtifactLibrary("./artifacts")
138
+ >>> counter_build = library.get_artifact_by_name("Counter")
139
+ """
140
+ if contract_name not in self._name_cache:
141
+ for path in self.lookup_paths:
142
+ for dirpath, _, filenames in os.walk(path):
143
+ if f"{contract_name}.json" in filenames:
144
+ with open(Path(dirpath) / f"{contract_name}.json") as f:
145
+ self._name_cache[contract_name] = Artifact(**json.load(f))
146
+
147
+ if contract_name not in self._name_cache:
148
+ raise FileNotFoundError(
149
+ f"Could not find artifact for {contract_name} on {self.lookup_paths}"
150
+ )
151
+
152
+ return self._name_cache[contract_name]
ethproto/contracts.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import os
2
2
  import time
3
+ from contextlib import contextmanager
3
4
  from decimal import Decimal
4
5
  from functools import wraps
5
- from contextlib import contextmanager
6
+
6
7
  from m9g import Model
7
- from m9g.fields import IntField, DictField, StringField, TupleField, ListField
8
- from .wadray import Wad, Ray
8
+ from m9g.fields import DictField, IntField, ListField, StringField, TupleField
9
+
10
+ from .wadray import Ray, Wad
9
11
 
10
12
  __author__ = "Guillermo M. Narvaja"
11
13
  __copyright__ = "Guillermo M. Narvaja"
@@ -501,7 +503,7 @@ class ERC721Token(AccessControlContract): # NFT
501
503
  @view
502
504
  def owner_of(self, token_id):
503
505
  if token_id not in self.owners:
504
- raise RevertError("ERC721: owner query for nonexistent token")
506
+ raise RevertError("ERC721: invalid token ID")
505
507
  return self.owners[token_id]
506
508
 
507
509
  # def token_uri
@@ -536,7 +538,7 @@ class ERC721Token(AccessControlContract): # NFT
536
538
  owner = self.owners[token_id]
537
539
  if sender != owner and self.token_approvals.get(token_id, None) != sender and \
538
540
  sender not in self.operator_approvals.get(owner, []):
539
- raise RevertError("ERC721: transfer caller is not owner nor approved")
541
+ raise RevertError("ERC721: caller is not token owner or approved")
540
542
  return self._transfer(from_, to, token_id)
541
543
 
542
544
  @external
@@ -544,7 +546,7 @@ class ERC721Token(AccessControlContract): # NFT
544
546
  owner = self.owners[token_id]
545
547
  if sender != owner and self.token_approvals.get(token_id, None) != sender and \
546
548
  sender not in self.operator_approvals.get(owner, []):
547
- raise RevertError("ERC721: transfer caller is not owner nor approved")
549
+ raise RevertError("ERC721: caller is not token owner or approved")
548
550
  # TODO: if `to` is contract, call onERC721Received
549
551
  return self._transfer(from_, to, token_id)
550
552
 
ethproto/w3wrappers.py CHANGED
@@ -1,15 +1,27 @@
1
1
  import os
2
- import json
3
- from .contracts import RevertError
4
- from .wrappers import (
5
- ETHCall, AddressBook, MAXUINT256, ETHWrapper, SKIP_PROXY, register_provider, BaseProvider
6
- )
2
+ from collections import defaultdict
3
+ from typing import Iterator, List, Union
4
+
7
5
  from environs import Env
8
6
  from eth_account.account import Account, LocalAccount
9
7
  from eth_account.signers.base import BaseAccount
10
- from web3.exceptions import ExtraDataLengthError
8
+ from eth_utils.abi import event_abi_to_log_topic
9
+ from hexbytes import HexBytes
10
+ from web3.contract import Contract
11
+ from web3.exceptions import ContractLogicError, ExtraDataLengthError
11
12
  from web3.middleware import geth_poa_middleware
12
- from eth_event import get_topic_map, decode_logs
13
+
14
+ from .build_artifacts import ArtifactLibrary
15
+ from .contracts import RevertError
16
+ from .wrappers import (
17
+ MAXUINT256,
18
+ SKIP_PROXY,
19
+ AddressBook,
20
+ BaseProvider,
21
+ ETHCall,
22
+ ETHWrapper,
23
+ register_provider,
24
+ )
13
25
 
14
26
  env = Env()
15
27
 
@@ -20,17 +32,21 @@ W3_ADDRESS_BOOK_CREATE_UNKNOWN = env.str("W3_ADDRESS_BOOK_CREATE_UNKNOWN", "")
20
32
  W3_POA = env.str("W3_POA", "auto")
21
33
 
22
34
 
35
+ # TODO: This should probably be part of the AddressBook
36
+ _contract_map = {} # Address -> Contract
37
+
38
+
23
39
  class W3TimeControl:
24
40
  def __init__(self, w3):
25
41
  self.w3 = w3
26
42
 
27
43
  def fast_forward(self, secs):
28
- self.w3.provider.make_request("evm_increaseTime", [secs])
29
- # Not tested!
44
+ """Mines a new block whose timestamp is secs after the latest block's timestamp."""
45
+ self.w3.provider.make_request("evm_mine", [self.now + secs])
30
46
 
31
47
  @property
32
48
  def now(self):
33
- return self.w3.get_block("latest").timestamp
49
+ return self.w3.eth.get_block("latest").timestamp
34
50
 
35
51
 
36
52
  def register_w3_provider(provider_key="w3", tester=None, provider_kwargs={}):
@@ -42,6 +58,7 @@ def register_w3_provider(provider_key="w3", tester=None, provider_kwargs={}):
42
58
 
43
59
  if tester:
44
60
  from web3 import Web3
61
+
45
62
  w3 = Web3(Web3.EthereumTesterProvider())
46
63
  else:
47
64
  from web3.auto import w3
@@ -80,15 +97,19 @@ def transact(provider, function, tx_kwargs):
80
97
  else: # it's a string, I try to get the PK from the environment
81
98
  from_ = provider.address_book.get_signer_account(from_)
82
99
  tx_kwargs["from"] = from_.address
83
- tx = function.buildTransaction(
84
- {**tx_kwargs, **{"nonce": provider.w3.eth.get_transaction_count(from_.address)}}
100
+ tx = function.build_transaction(
101
+ {
102
+ **tx_kwargs,
103
+ **{"nonce": provider.w3.eth.get_transaction_count(from_.address)},
104
+ }
85
105
  )
86
106
  signed_tx = from_.sign_transaction(tx)
87
107
  tx_hash = provider.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
88
108
  elif W3_TRANSACT_MODE == "defender-async":
89
109
  from .defender_relay import send_transaction
110
+
90
111
  tx_kwargs = {**provider.tx_kwargs, **tx_kwargs}
91
- tx = function.buildTransaction(tx_kwargs)
112
+ tx = function.build_transaction(tx_kwargs)
92
113
  return send_transaction(tx)
93
114
 
94
115
  return provider.w3.eth.wait_for_transaction_receipt(tx_hash)
@@ -108,11 +129,11 @@ class W3AddressBook(AddressBook):
108
129
  return self._eth_accounts
109
130
 
110
131
  def get_account(self, name):
111
- if isinstance(name, (Account, LocalAccount)):
132
+ if isinstance(name, BaseAccount):
112
133
  return name
113
134
  if name is None:
114
135
  return self.ZERO
115
- if type(name) == str and name.startswith("0x"):
136
+ if isinstance(name, str) and name.startswith("0x"):
116
137
  return name
117
138
  if name not in self.name_to_address:
118
139
  self.last_account_used += 1
@@ -120,10 +141,15 @@ class W3AddressBook(AddressBook):
120
141
  self.name_to_address[name] = self.eth_accounts[self.last_account_used]
121
142
  except IndexError:
122
143
  self.name_to_address[name] = self.w3.eth.account.create().address
144
+ # TODO: This branch only generates a random account and discards the private key. Unlike the accounts in
145
+ # self.eth_accounts this one is not unlocked on the node. Something like this is necessary for this to
146
+ # be useful (and consistent with other branches):
147
+ # self.provider.register_account(address, private_key)
148
+
123
149
  return self.name_to_address[name]
124
150
 
125
151
  def get_name(self, account_or_address):
126
- if isinstance(account_or_address, (LocalAccount, )):
152
+ if isinstance(account_or_address, (LocalAccount,)):
127
153
  account_or_address = account_or_address.address
128
154
 
129
155
  for name, addr in self.name_to_address.items():
@@ -151,7 +177,7 @@ class W3EnvAddressBook(AddressBook):
151
177
  continue
152
178
  if k.endswith("_ADDR"):
153
179
  continue # Addresses linked to names
154
- addr = k[len(env_prefix):]
180
+ addr = k[len(env_prefix) :]
155
181
  if addr.startswith("0x"):
156
182
  account = w3.account.from_key(value)
157
183
  assert account.address == addr
@@ -183,7 +209,7 @@ class W3EnvAddressBook(AddressBook):
183
209
  raise RuntimeError(f"No account found for name {name}")
184
210
 
185
211
  def get_name(self, account_or_address):
186
- if isinstance(account_or_address, (LocalAccount, )):
212
+ if isinstance(account_or_address, (LocalAccount,)):
187
213
  account_or_address = account_or_address.address
188
214
 
189
215
  for name, addr in self.name_to_address.items():
@@ -205,25 +231,105 @@ class ReceiptWrapper:
205
231
  @property
206
232
  def events(self):
207
233
  if not hasattr(self, "_events"):
208
- topic_map = get_topic_map(self._contract.abi)
209
- logs = decode_logs(self._receipt.logs, topic_map, allow_undecoded=True)
210
- evts = {}
211
- for evt in logs:
212
- evt_name = evt["name"]
213
- evt_params = dict((d["name"], d["value"]) for d in evt["data"])
214
- if evt_name not in evts:
215
- evts[evt_name] = evt_params
216
- elif type(evts[evt_name]) == dict:
217
- evts[evt_name] = [evts[evt_name], evt_params] # start a list
218
- else: # it's already a list
219
- evts[evt_name].append(evt_params)
220
- self._events = evts
234
+ # Lookup the events in all known contracts
235
+ addresses = [log.address for log in self._receipt.logs]
236
+ contracts = {addr: _contract_map[addr] for addr in addresses if addr in _contract_map}
237
+ topic_map = {
238
+ HexBytes(event_abi_to_log_topic(event().abi)): event()
239
+ for contract in contracts.values()
240
+ for event in contract.events
241
+ }
242
+
243
+ parsed_logs = []
244
+ for log in self._receipt.logs:
245
+ for topic in log.topics:
246
+ if topic in topic_map:
247
+ parsed_logs += topic_map[topic].process_receipt(self._receipt)
248
+
249
+ evts = defaultdict(list)
250
+ for evt in parsed_logs:
251
+ evt_name = evt.event
252
+ evt_params = evt.args
253
+ evts[evt_name].append(evt_params)
254
+ self._events = {k: EventItem(k, v) for k, v in evts.items()}
221
255
  return self._events
222
256
 
223
257
  def __getattr__(self, attr_name):
224
258
  return getattr(self._receipt, attr_name)
225
259
 
226
260
 
261
+ class EventItem:
262
+ """
263
+ Dict/list hybrid container, represents one or more events with the same name
264
+ that were fired in a transaction.
265
+
266
+ Inspired on eth-brownies brownie.network.event._EventItem class
267
+ """
268
+
269
+ def __init__(self, name: str, events: List) -> None:
270
+ self.name = name
271
+ self._events = events
272
+
273
+ def __getitem__(self, key: Union[int, str]) -> List:
274
+ """if key is int: returns the n'th event that was fired with this name
275
+ if key is str: returns the value of data field 'key' from the 1st event
276
+ within the container"""
277
+ if not isinstance(key, (int, str)):
278
+ raise TypeError(f"Invalid key type '{type(key)}' - can only use strings or integers")
279
+ if isinstance(key, int):
280
+ try:
281
+ return self._events[key]
282
+ except IndexError:
283
+ raise IndexError(
284
+ f"Index {key} out of range - only {len(self._ordered)} '{self.name}' events fired"
285
+ )
286
+ if key in self._events[0]:
287
+ return self._events[0][key]
288
+ if f"{key} (indexed)" in self._events[0]:
289
+ return self._events[0][f"{key} (indexed)"]
290
+ valid_keys = ", ".join(self.keys())
291
+ raise KeyError(f"Unknown key '{key}' - the '{self.name}' event includes these keys: {valid_keys}")
292
+
293
+ def __contains__(self, name: str) -> bool:
294
+ """returns True if this event contains a value with the given name."""
295
+ return name in self._events[0]
296
+
297
+ def __len__(self) -> int:
298
+ """returns the number of events held in this container."""
299
+ return len(self._events)
300
+
301
+ def __repr__(self) -> str:
302
+ return str(self)
303
+
304
+ def __str__(self) -> str:
305
+ if len(self._events) == 1:
306
+ return str(self._events[0])
307
+ return str([i[0] for i in self._events])
308
+
309
+ def __iter__(self) -> Iterator:
310
+ return iter(self._events)
311
+
312
+ def __eq__(self, other: object) -> bool:
313
+ if len(self._events) == 1:
314
+ if isinstance(other, (tuple, list)):
315
+ # sequences compare directly against the event values
316
+ return self._events[0].values() == other
317
+ return other == self._events[0]
318
+ return other == self._events
319
+
320
+ def items(self) -> List:
321
+ """EventItem.items() -> a list object providing a view on EventItem[0]'s items"""
322
+ return [(i, self[i]) for i in self.keys()]
323
+
324
+ def keys(self) -> List:
325
+ """EventItem.keys() -> a list object providing a view on EventItem[0]'s keys"""
326
+ return [i.replace(" (indexed)", "") for i in self._events[0].keys()]
327
+
328
+ def values(self) -> List:
329
+ """EventItem.values() -> a list object providing a view on EventItem[0]'s values"""
330
+ return self._events[0].values()
331
+
332
+
227
333
  class W3ETHCall(ETHCall):
228
334
  @classmethod
229
335
  def find_function_abi(cls, contract, eth_method, eth_variant):
@@ -238,11 +344,14 @@ class W3ETHCall(ETHCall):
238
344
  function = getattr(wrapper.contract.functions, eth_method) # TODO: eth_variant
239
345
  function_abi = cls.find_function_abi(wrapper.contract, eth_method, eth_variant)
240
346
  if function_abi["stateMutability"] in ("pure", "view"):
347
+
241
348
  def eth_function(*args):
242
349
  if args and type(args[-1]) == dict:
243
350
  args = args[:-1] # remove dict with {from: ...}
244
351
  return function(*args).call()
352
+
245
353
  else: # Mutable function, need to send and wait transaction
354
+
246
355
  def eth_function(*args):
247
356
  if args and type(args[-1]) == dict:
248
357
  transact_args = args[-1]
@@ -255,12 +364,16 @@ class W3ETHCall(ETHCall):
255
364
 
256
365
  def normalize_receipt(self, wrapper, receipt):
257
366
  if W3_TRANSACT_MODE == "defender-async":
258
- return receipt # Don't do anything because the receipt it's just a dict of not-yet-mined tx
367
+ return receipt # Don't do anything because the receipt is just a dict of not-yet-mined tx
259
368
  return ReceiptWrapper(receipt, wrapper.contract)
260
369
 
261
370
  def _handle_exception(self, err):
262
- if str(err).startswith("execution reverted: "):
263
- raise RevertError(str(err)[len("execution reverted: "):])
371
+ if isinstance(err, ContractLogicError):
372
+ raise RevertError(
373
+ err.message[len("execution reverted: ") :]
374
+ if err.message and err.message.startswith("execution reverted: ")
375
+ else err.message
376
+ )
264
377
  super()._handle_exception(err)
265
378
 
266
379
  @classmethod
@@ -268,16 +381,14 @@ class W3ETHCall(ETHCall):
268
381
  if value_type.startswith("(") and value_type.endswith(")"):
269
382
  # It's a tuple / struct
270
383
  value_types = [t.strip() for t in value_type.split(",")]
271
- return tuple(
272
- cls.parse(wrapper, vt, value[i]) for i, vt in enumerate(value_types)
273
- )
384
+ return tuple(cls.parse(wrapper, vt, value[i]) for i, vt in enumerate(value_types))
274
385
  if value_type == "address":
275
- if isinstance(value, (LocalAccount, Account)):
276
- return value
277
- # elif isinstance(value, (Contract, ProjectContract)):
278
- # return value.address
386
+ if isinstance(value, BaseAccount):
387
+ return value.address
279
388
  elif isinstance(value, ETHWrapper):
280
389
  return value.contract.address
390
+ elif isinstance(value, Contract):
391
+ return value.address
281
392
  elif isinstance(value, str) and value.startswith("0x"):
282
393
  return value
283
394
  return wrapper.provider.address_book.get_account(value)
@@ -299,30 +410,19 @@ class W3Provider(BaseProvider):
299
410
 
300
411
  def __init__(self, w3, address_book=None, contracts_path=None, tx_kwargs=None):
301
412
  self.w3 = w3
302
- self.contracts_path = contracts_path or CONTRACT_JSON_PATH
303
- self.contract_def_cache = {}
413
+ self.artifact_library = ArtifactLibrary(
414
+ *(contracts_path if contracts_path is not None else CONTRACT_JSON_PATH)
415
+ )
304
416
  self.address_book = address_book or W3AddressBook(w3)
305
417
  self.time_control = W3TimeControl(w3)
306
418
  self.tx_kwargs = tx_kwargs or {}
307
419
 
308
420
  def get_contract_def(self, eth_contract):
309
- if eth_contract not in self.contract_def_cache:
310
- json_file = None
311
- for contract_path in self.contracts_path:
312
- for sub_path, _, files in os.walk(contract_path):
313
- if f"{eth_contract}.json" in files:
314
- json_file = os.path.join(sub_path, f"{eth_contract}.json")
315
- break
316
- if json_file is not None:
317
- break
318
- else:
319
- raise RuntimeError(f"{eth_contract} JSON definition not found in {self.contracts_path}")
320
- self.contract_def_cache[eth_contract] = json.load(open(json_file))
321
- return self.contract_def_cache[eth_contract]
421
+ return self.artifact_library.get_artifact_by_name(eth_contract)
322
422
 
323
423
  def get_contract_factory(self, eth_contract):
324
424
  contract_def = self.get_contract_def(eth_contract)
325
- return self.w3.eth.contract(abi=contract_def["abi"], bytecode=contract_def.get("bytecode", None))
425
+ return self.w3.eth.contract(abi=contract_def.abi, bytecode=contract_def.bytecode)
326
426
 
327
427
  def deploy(self, eth_contract, init_params, from_, **kwargs):
328
428
  factory = self.get_contract_factory(eth_contract)
@@ -358,49 +458,58 @@ class W3Provider(BaseProvider):
358
458
 
359
459
  def init_eth_wrapper(self, eth_wrapper, owner, init_params, kwargs):
360
460
  eth_wrapper.owner = self.address_book.get_account(owner)
361
- assert not eth_wrapper.libraries_required, "Not supported"
362
461
 
363
- eth_contract = self.get_contract_factory(eth_wrapper.eth_contract)
462
+ contract_def = self.get_contract_def(eth_wrapper.eth_contract)
463
+ libraries = {}
464
+ for lib, _ in contract_def.libraries():
465
+ if lib not in libraries:
466
+ library_def = self.get_contract_factory(lib)
467
+ library = self.construct(library_def)
468
+ libraries[lib] = library.address
469
+
470
+ if libraries:
471
+ contract_def = contract_def.link(libraries)
472
+
473
+ eth_contract = self.w3.eth.contract(abi=contract_def.abi, bytecode=contract_def.bytecode)
474
+
364
475
  if eth_wrapper.proxy_kind is None:
365
476
  eth_wrapper.contract = self.construct(eth_contract, init_params, {"from": eth_wrapper.owner})
366
477
  elif eth_wrapper.proxy_kind == "uups" and not SKIP_PROXY:
367
478
  constructor_params, init_params = init_params
368
479
  real_contract = self.construct(eth_contract, constructor_params, {"from": eth_wrapper.owner})
369
480
  ERC1967Proxy = self.get_contract_factory("ERC1967Proxy")
370
- init_data = real_contract.functions.initialize(
371
- *init_params
372
- ).build_transaction({**self.tx_kwargs, **{"from": eth_wrapper.owner}})["data"]
481
+ init_data = eth_contract.encodeABI(fn_name="initialize", args=init_params)
373
482
  proxy_contract = self.construct(
374
483
  ERC1967Proxy,
375
484
  (real_contract.address, init_data),
376
- {**self.tx_kwargs, **{"from": eth_wrapper.owner}}
377
- )
378
- eth_wrapper.contract = self.w3.eth.contract(
379
- abi=eth_contract.abi,
380
- address=proxy_contract.address
485
+ {**self.tx_kwargs, **{"from": eth_wrapper.owner}},
381
486
  )
487
+ eth_wrapper.contract = self.w3.eth.contract(abi=eth_contract.abi, address=proxy_contract.address)
382
488
  elif eth_wrapper.proxy_kind == "uups" and SKIP_PROXY:
383
489
  constructor_params, init_params = init_params
384
- eth_wrapper.contract = self.construct(eth_contract, constructor_params,
385
- {"from": eth_wrapper.owner})
490
+ eth_wrapper.contract = self.construct(
491
+ eth_contract, constructor_params, {"from": eth_wrapper.owner}
492
+ )
386
493
  transact(
387
494
  self,
388
495
  eth_wrapper.contract.functions.initialize(*init_params),
389
- {"from": eth_wrapper.owner}
496
+ {"from": eth_wrapper.owner},
390
497
  )
391
498
 
499
+ _contract_map[eth_wrapper.contract.address] = eth_wrapper.contract
500
+
392
501
  def construct(self, contract_factory, constructor_args=(), transact_kwargs={}):
393
502
  try:
394
- receipt = transact(
395
- self,
396
- contract_factory.constructor(*constructor_args),
397
- transact_kwargs
398
- )
503
+ receipt = transact(self, contract_factory.constructor(*constructor_args), transact_kwargs)
399
504
  except Exception as err:
400
505
  if str(err).startswith("execution reverted: "):
401
- raise RevertError(str(err)[len("execution reverted: "):])
506
+ raise RevertError(str(err)[len("execution reverted: ") :])
402
507
  raise
403
508
  return self.w3.eth.contract(abi=contract_factory.abi, address=receipt.contractAddress)
404
509
 
405
510
  def build_contract(self, contract_address, contract_factory, contract_name=None):
406
511
  return self.w3.eth.contract(abi=contract_factory.abi, address=contract_address)
512
+
513
+ def unlock_account(self, address):
514
+ """Unlocks an account on the node. Assumes hardhat network API."""
515
+ self.w3.provider.make_request("hardhat_impersonateAccount", [address])
ethproto/wrappers.py CHANGED
@@ -1,10 +1,14 @@
1
1
  """Base module for wrappers"""
2
+
2
3
  from abc import ABC, abstractmethod
3
4
  from contextlib import contextmanager
4
5
  from functools import partial
5
- from .wadray import Wad, Ray, make_integer_float
6
+
6
7
  import requests
7
8
  from environs import Env
9
+ from hexbytes import HexBytes
10
+
11
+ from .wadray import Ray, Wad, make_integer_float
8
12
 
9
13
  env = Env()
10
14
 
@@ -43,10 +47,8 @@ def get_provider(provider_key=None):
43
47
  def auto_register_provider(provider_key):
44
48
  if provider_key == "w3":
45
49
  from .w3wrappers import register_w3_provider
50
+
46
51
  register_w3_provider()
47
- elif provider_key == "brownie":
48
- from .brwrappers import BrownieProvider
49
- register_provider("brownie", BrownieProvider())
50
52
  else:
51
53
  raise RuntimeError(f"Unknown provider {provider_key}")
52
54
 
@@ -57,8 +59,16 @@ def register_provider(provider_key, provider):
57
59
 
58
60
 
59
61
  class MethodAdapter:
60
- def __init__(self, args=(), return_type="", eth_method=None, adapt_args=None, is_property=False,
61
- set_eth_method=None, eth_variant=None):
62
+ def __init__(
63
+ self,
64
+ args=(),
65
+ return_type="",
66
+ eth_method=None,
67
+ adapt_args=None,
68
+ is_property=False,
69
+ set_eth_method=None,
70
+ eth_variant=None,
71
+ ):
62
72
  self.eth_method = eth_method
63
73
  self.set_eth_method = set_eth_method
64
74
  self.return_type = return_type
@@ -76,8 +86,8 @@ class MethodAdapter:
76
86
 
77
87
  @staticmethod
78
88
  def snake_to_camel(name):
79
- components = name.split('_')
80
- return components[0] + ''.join(x.title() for x in components[1:])
89
+ components = name.split("_")
90
+ return components[0] + "".join(x.title() for x in components[1:])
81
91
 
82
92
  @property
83
93
  def method_name(self):
@@ -85,8 +95,7 @@ class MethodAdapter:
85
95
 
86
96
  def __get__(self, instance, owner=None):
87
97
  eth_call = instance.eth_call(
88
- self.eth_method, self.args, self.return_type, self.adapt_args,
89
- eth_variant=self.eth_variant
98
+ self.eth_method, self.args, self.return_type, self.adapt_args, eth_variant=self.eth_variant
90
99
  )
91
100
  if self.is_property:
92
101
  return eth_call(instance)
@@ -95,7 +104,7 @@ class MethodAdapter:
95
104
  def __set__(self, instance, value):
96
105
  if not self.is_property:
97
106
  raise NotImplementedError()
98
- eth_call = instance.eth_call(self.set_eth_method, (("new_value", self.return_type), ))
107
+ eth_call = instance.eth_call(self.set_eth_method, (("new_value", self.return_type),))
99
108
  return eth_call(instance, value)
100
109
 
101
110
 
@@ -147,7 +156,7 @@ class ETHCall(ABC):
147
156
 
148
157
  def __call__(self, wrapper, *args, **kwargs):
149
158
  call_args = []
150
- msg_args = {}
159
+ msg_args = wrapper.provider.tx_kwargs.copy()
151
160
 
152
161
  if self.adapt_args:
153
162
  args, kwargs = self.adapt_args(args, kwargs)
@@ -203,22 +212,26 @@ class ETHCall(ABC):
203
212
  return call_args, msg_args
204
213
 
205
214
  @classmethod
206
- def _parse_keccak256(cls, value):
207
- from Crypto.Hash import keccak # To avoid import of wrappers breaks if keccak not installed
208
- if value.startswith("0x"):
215
+ def _parse_keccak256(cls, value) -> HexBytes:
216
+ from Crypto.Hash import (
217
+ keccak, # To avoid import of wrappers breaks if keccak not installed
218
+ )
219
+
220
+ if isinstance(value, HexBytes):
209
221
  return value
222
+
223
+ if value.startswith("0x"):
224
+ return HexBytes(value)
210
225
  k = keccak.new(digest_bits=256)
211
226
  k.update(value.encode("utf-8"))
212
- return k.hexdigest()
227
+ return HexBytes(k.hexdigest())
213
228
 
214
229
  @classmethod
215
230
  def unparse(cls, wrapper, value_type, value):
216
231
  if value_type.startswith("(") and value_type.endswith(")"):
217
232
  # It's a tuple / struct
218
233
  value_types = [t.strip() for t in value_type.strip("()").split(",")]
219
- return tuple(
220
- cls.unparse(wrapper, vt, value[i]) for i, vt in enumerate(value_types)
221
- )
234
+ return tuple(cls.unparse(wrapper, vt, value[i]) for i, vt in enumerate(value_types))
222
235
  if value_type == "amount":
223
236
  return AMOUNT_CLASS(value)
224
237
  if value_type == "ray":
@@ -240,6 +253,8 @@ class ETHCall(ABC):
240
253
 
241
254
 
242
255
  class BaseProvider(ABC):
256
+ tx_kwargs = {}
257
+
243
258
  @abstractmethod
244
259
  def get_contract_factory(self, eth_contract):
245
260
  raise NotImplementedError()
@@ -259,8 +274,9 @@ class BaseProvider(ABC):
259
274
  return 0
260
275
  address = self.get_contract_address(eth_wrapper)
261
276
  url = (
262
- etherscan_url + f"&module=account&action=txlist&address={address}&startblock=0&" +
263
- "endblock=99999999&page=1&offset=10&sort=asc"
277
+ etherscan_url
278
+ + f"&module=account&action=txlist&address={address}&startblock=0&"
279
+ + "endblock=99999999&page=1&offset=10&sort=asc"
264
280
  )
265
281
  resp = requests.get(url)
266
282
  resp.raise_for_status()
@@ -305,9 +321,9 @@ class ETHWrapper:
305
321
  else:
306
322
  if self.constructor_args:
307
323
  constructor_params, transaction_kwargs = self.eth_call.parse_args(
308
- self, self.constructor_args, *init_params[:len(self.constructor_args)], **kwargs
324
+ self, self.constructor_args, *init_params[: len(self.constructor_args)], **kwargs
309
325
  )
310
- init_params = init_params[len(self.constructor_args):]
326
+ init_params = init_params[len(self.constructor_args) :]
311
327
  if transaction_kwargs:
312
328
  kwargs.update(transaction_kwargs)
313
329
  else:
@@ -357,6 +373,31 @@ class ETHWrapper:
357
373
  obj._auto_from = obj.owner
358
374
  return obj
359
375
 
376
+ @classmethod
377
+ def build_from_def(cls, contract_def, extra_properties=None):
378
+ extra_properties = extra_properties or {}
379
+ attributes = {}
380
+ for item in contract_def.abi:
381
+ if item["type"] == "constructor":
382
+ attributes["constructor_args"] = tuple((arg["name"], arg["type"]) for arg in item["inputs"])
383
+ elif item["type"] == "function":
384
+ attributes[item["name"]] = MethodAdapter(
385
+ args=tuple((arg["name"], arg["type"]) for arg in item["inputs"]),
386
+ return_type=item["outputs"][0]["type"] if item["outputs"] else "",
387
+ eth_method=item["name"],
388
+ eth_variant=item["inputs"],
389
+ )
390
+ wrapper_class = type(
391
+ contract_def.contract_name,
392
+ (cls,),
393
+ {
394
+ "eth_contract": contract_def.contract_name,
395
+ **attributes,
396
+ **extra_properties,
397
+ },
398
+ )
399
+ return wrapper_class
400
+
360
401
  @property
361
402
  def contract_id(self):
362
403
  return self.contract.address
@@ -371,8 +412,8 @@ class ETHWrapper:
371
412
  revoke_role = MethodAdapter((("role", "keccak256"), ("user", "address")))
372
413
  renounce_role = MethodAdapter((("role", "keccak256"), ("user", "address")))
373
414
  has_role = MethodAdapter((("role", "keccak256"), ("user", "address")), "bool")
374
- get_role_admin = MethodAdapter((("role", "keccak256"), ), "address")
375
- get_role_admin = MethodAdapter((("role", "keccak256"), ), "bytes32")
415
+ get_role_admin = MethodAdapter((("role", "keccak256"),), "address")
416
+ get_role_admin = MethodAdapter((("role", "keccak256"),), "bytes32")
376
417
 
377
418
  @contextmanager
378
419
  def as_(self, user):
@@ -397,14 +438,15 @@ class IERC20(ETHWrapper):
397
438
  symbol = MethodAdapter((), "string", is_property=True)
398
439
  decimals = MethodAdapter((), "int", is_property=True)
399
440
  total_supply = MethodAdapter((), "amount")
400
- balance_of = MethodAdapter((("account", "address"), ), "amount")
401
- transfer = MethodAdapter((
402
- ("sender", "msg.sender"), ("recipient", "address"), ("amount", "amount")
403
- ), "receipt")
441
+ balance_of = MethodAdapter((("account", "address"),), "amount")
442
+ transfer = MethodAdapter(
443
+ (("sender", "msg.sender"), ("recipient", "address"), ("amount", "amount")), "receipt"
444
+ )
404
445
 
405
446
  allowance = MethodAdapter((("owner", "address"), ("spender", "address")), "amount")
406
- approve = MethodAdapter((("owner", "msg.sender"), ("spender", "address"), ("amount", "amount")),
407
- "receipt")
447
+ approve = MethodAdapter(
448
+ (("owner", "msg.sender"), ("spender", "address"), ("amount", "amount")), "receipt"
449
+ )
408
450
  increase_allowance = MethodAdapter(
409
451
  (("owner", "msg.sender"), ("spender", "address"), ("amount", "amount"))
410
452
  )
@@ -412,9 +454,10 @@ class IERC20(ETHWrapper):
412
454
  (("owner", "msg.sender"), ("spender", "address"), ("amount", "amount"))
413
455
  )
414
456
 
415
- transfer_from = MethodAdapter((
416
- ("spender", "msg.sender"), ("sender", "address"), ("recipient", "address"), ("amount", "amount")
417
- ), "receipt")
457
+ transfer_from = MethodAdapter(
458
+ (("spender", "msg.sender"), ("sender", "address"), ("recipient", "address"), ("amount", "amount")),
459
+ "receipt",
460
+ )
418
461
 
419
462
 
420
463
  class IERC721(ETHWrapper):
@@ -423,19 +466,21 @@ class IERC721(ETHWrapper):
423
466
  name = MethodAdapter((), "string", is_property=True)
424
467
  symbol = MethodAdapter((), "string", is_property=True)
425
468
  total_supply = MethodAdapter((), "int")
426
- balance_of = MethodAdapter((("account", "address"), ), "int")
427
- owner_of = MethodAdapter((("token_id", "int"), ), "address")
428
- approve = MethodAdapter((
429
- ("sender", "msg.sender"), ("spender", "address"), ("token_id", "int")
430
- ), "receipt")
431
- get_approved = MethodAdapter((("token_id", "int"), ), "address")
432
- set_approval_for_all = MethodAdapter((
433
- ("sender", "msg.sender"), ("operator", "address"), ("approved", "bool")
434
- ))
469
+ balance_of = MethodAdapter((("account", "address"),), "int")
470
+ owner_of = MethodAdapter((("token_id", "int"),), "address")
471
+ approve = MethodAdapter(
472
+ (("sender", "msg.sender"), ("spender", "address"), ("token_id", "int")), "receipt"
473
+ )
474
+ get_approved = MethodAdapter((("token_id", "int"),), "address")
475
+ set_approval_for_all = MethodAdapter(
476
+ (("sender", "msg.sender"), ("operator", "address"), ("approved", "bool"))
477
+ )
435
478
  is_approved_for_all = MethodAdapter((("owner", "address"), ("operator", "address")), "bool")
436
- transfer_from = MethodAdapter((
437
- ("spender", "msg.sender"), ("from", "address"), ("to", "address"), ("token_id", "int")
438
- ), "receipt")
439
- safe_transfer_from = MethodAdapter((
440
- ("spender", "msg.sender"), ("from", "address"), ("to", "address"), ("token_id", "int")
441
- ), "receipt", eth_variant="address, address, uint256")
479
+ transfer_from = MethodAdapter(
480
+ (("spender", "msg.sender"), ("from", "address"), ("to", "address"), ("token_id", "int")), "receipt"
481
+ )
482
+ safe_transfer_from = MethodAdapter(
483
+ (("spender", "msg.sender"), ("from", "address"), ("to", "address"), ("token_id", "int")),
484
+ "receipt",
485
+ eth_variant="address, address, uint256",
486
+ )
@@ -1,13 +0,0 @@
1
- ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
- ethproto/brwrappers.py,sha256=sC6N7kzmly5M2s-ewvambbbA-8NbYJZP_MO_UC7o_Z4,9173
3
- ethproto/contracts.py,sha256=44oEIyCyX2mH-PftG1ZpQ7aXRPJZy95t1tgL29UsqP4,18685
4
- ethproto/defender_relay.py,sha256=05A8TfRZwiBhCpo924Pf9CjfKSir2Wvgg1p_asFxJbw,1777
5
- ethproto/w3wrappers.py,sha256=D8NJPvacZb60c5ukFtJyysmQi9H5y72VBnwMKli5DwA,16810
6
- ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
7
- ethproto/wrappers.py,sha256=hlqmmJOzNo2V4BUpMJ4hVzdLYtIb0IqLMU3Up1o-RPU,16185
8
- eth_prototype-0.7.5.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
9
- eth_prototype-0.7.5.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
10
- eth_prototype-0.7.5.dist-info/METADATA,sha256=wLQ8c-2UPhMC0H87QoW-nfWQC3MEFf6aBp8UFFA68hk,3646
11
- eth_prototype-0.7.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
12
- eth_prototype-0.7.5.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
13
- eth_prototype-0.7.5.dist-info/RECORD,,
ethproto/brwrappers.py DELETED
@@ -1,221 +0,0 @@
1
- import re
2
- from .contracts import RevertError
3
- from .wrappers import ETHCall, AddressBook, MAXUINT256, SKIP_PROXY, ETHWrapper, BaseProvider
4
- import eth_utils
5
- from brownie import accounts
6
- import brownie
7
- from brownie.network.account import Account, LocalAccount
8
- from brownie.exceptions import VirtualMachineError
9
- from brownie.network.state import Chain
10
- from brownie.network.contract import Contract, ProjectContract
11
-
12
-
13
- class BrownieTimeControl:
14
- def __init__(self, chain=None):
15
- self.chain = chain or Chain()
16
-
17
- def fast_forward(self, secs):
18
- self.chain.sleep(secs)
19
- self.chain.mine()
20
-
21
- @property
22
- def now(self):
23
- if len(self.chain) > 0:
24
- return self.chain[-1].timestamp
25
- return self.chain.time()
26
-
27
-
28
- class BrownieAddressBook(AddressBook):
29
-
30
- def __init__(self, eth_accounts):
31
- self.eth_accounts = eth_accounts # brownie.network.account.Accounts
32
- self.name_to_address = {}
33
- self.last_account_used = -1
34
-
35
- def get_account(self, name):
36
- if isinstance(name, (Account, LocalAccount)):
37
- return name
38
- if isinstance(name, (Contract, ProjectContract)):
39
- return name
40
- if name is None:
41
- return self.ZERO
42
- if name not in self.name_to_address:
43
- self.last_account_used += 1
44
- if (len(self.eth_accounts) - 1) > self.last_account_used:
45
- self.eth_accounts.add()
46
- self.name_to_address[name] = self.eth_accounts[self.last_account_used].address
47
- return self.eth_accounts.at(self.name_to_address[name])
48
-
49
- def get_name(self, account_or_address):
50
- if isinstance(account_or_address, Account):
51
- account_or_address = account_or_address.address
52
-
53
- for name, addr in self.name_to_address.items():
54
- if addr == account_or_address:
55
- return name
56
- return None
57
-
58
-
59
- AddressBook.set_instance(BrownieAddressBook(accounts))
60
-
61
-
62
- def encode_function_data(initializer=None, *args):
63
- """Encodes the function call so we can work with an initializer.
64
- Args:
65
- initializer ([brownie.network.contract.ContractTx], optional):
66
- The initializer function we want to call. Example: `box.store`.
67
- Defaults to None.
68
- args (Any, optional):
69
- The arguments to pass to the initializer function
70
- Returns:
71
- [bytes]: Return the encoded bytes.
72
- """
73
- if not initializer:
74
- return eth_utils.to_bytes(hexstr="0x")
75
- else:
76
- return initializer.encode_input(*args)
77
-
78
-
79
- class BrownieETHCall(ETHCall):
80
- error_match = [
81
- re.compile('VM Exception while processing transaction: revert with reason "([^"]+)"')
82
- ]
83
-
84
- def _handle_exception(self, err):
85
- if isinstance(err, VirtualMachineError) and err.revert_type == "revert":
86
- raise RevertError(err.revert_msg)
87
- # Tries to match error by regex
88
- for err_regex in self.error_match:
89
- m = err_regex.match(str(err))
90
- if m:
91
- raise RevertError(m.group(1))
92
- super()._handle_exception(err)
93
-
94
- @classmethod
95
- def get_eth_function(cls, wrapper, eth_method, eth_variant=None):
96
- if eth_variant:
97
- return getattr(wrapper.contract, eth_method)[eth_variant]
98
- else:
99
- return getattr(wrapper.contract, eth_method)
100
-
101
- @classmethod
102
- def get_eth_function_and_mutability(cls, wrapper, eth_method, eth_variant=None):
103
- function = cls.get_eth_function(wrapper, eth_method, eth_variant)
104
- return function, function.abi["stateMutability"]
105
-
106
- @classmethod
107
- def parse(cls, wrapper, value_type, value):
108
- if value_type.startswith("(") and value_type.endswith(")"):
109
- # It's a tuple / struct
110
- value_types = [t.strip() for t in value_type.strip("()").split(",")]
111
- return tuple(
112
- cls.parse(wrapper, vt, value[i]) for i, vt in enumerate(value_types)
113
- )
114
- if value_type == "address":
115
- if isinstance(value, (LocalAccount, Account)):
116
- return value
117
- elif isinstance(value, (Contract, ProjectContract)):
118
- return value.address
119
- elif isinstance(value, ETHWrapper):
120
- return value.contract.address
121
- elif isinstance(value, str) and value.startswith("0x"):
122
- return value
123
- return wrapper.provider.address_book.get_account(value)
124
- if value_type == "keccak256":
125
- return cls._parse_keccak256(value)
126
- if value_type == "contract":
127
- if isinstance(value, ETHWrapper):
128
- return value.contract.address
129
- elif value is None:
130
- return AddressBook.ZERO
131
- raise RuntimeError(f"Invalid contract: {value}")
132
- if value_type == "amount" and value is None:
133
- return MAXUINT256
134
- return value
135
-
136
-
137
- class BrownieProvider(BaseProvider):
138
- eth_call = BrownieETHCall
139
-
140
- def __init__(self, time_control=None, address_book=None):
141
- self.time_control = time_control or BrownieTimeControl()
142
- self.address_book = address_book or BrownieAddressBook(accounts)
143
-
144
- def get_contract_factory(self, eth_contract):
145
- ret = getattr(brownie, eth_contract, None)
146
- if ret is not None:
147
- return ret
148
- # Might be a manually loaded project or an interface
149
- project = brownie.project.get_loaded_projects()[0]
150
- ret = getattr(project, eth_contract, None)
151
- if ret is not None:
152
- return ret
153
- return getattr(project.interface, eth_contract)
154
-
155
- def get_events(self, eth_wrapper, event_name, filter_kwargs={}):
156
- """Returns a list of events given a filter, like this:
157
-
158
- >>> provider.get_events(currencywrapper, "Transfer", dict(fromBlock=0))
159
- [AttributeDict({
160
- 'args': AttributeDict(
161
- {'from': '0x0000000000000000000000000000000000000000',
162
- 'to': '0x56Cd397bAA08F2339F0ae470DEA99D944Ac064bB',
163
- 'value': 6000000000000000000000}),
164
- 'event': 'Transfer',
165
- 'logIndex': 0,
166
- 'transactionIndex': 0,
167
- 'transactionHash': HexBytes(
168
- '0x406b2cf8de2f12f4d0958e9f0568dc0919f337ed399f8d8d78ddbc648c01f806'
169
- ),
170
- 'address': '0xf8BedC7458fb8cAbD616B5e90F57c34c392e7168',
171
- 'blockHash': HexBytes('0x7b23c6ea49759bcee769b1a357dec7f63f03bdb1dd13f1ee19868925954134b3'),
172
- 'blockNumber': 23
173
- })]
174
- """
175
- w3 = brownie.network.web3
176
- w3_contract = w3.eth.contract(abi=eth_wrapper.contract.abi, address=eth_wrapper.contract.address)
177
- event = getattr(w3_contract.events, event_name)
178
- if "fromBlock" not in filter_kwargs:
179
- filter_kwargs["fromBlock"] = self.get_first_block(eth_wrapper)
180
- event_filter = event.create_filter(**filter_kwargs)
181
- return event_filter.get_all_entries()
182
-
183
- def deploy(self, eth_contract, init_params, from_, **kwargs):
184
- factory = self.get_contract_factory(eth_contract)
185
- kwargs["from"] = from_
186
- return factory.deploy(*init_params, kwargs)
187
-
188
- def init_eth_wrapper(self, eth_wrapper, owner, init_params, kwargs):
189
- eth_wrapper.owner = self.address_book.get_account(owner)
190
- for library in eth_wrapper.libraries_required:
191
- self.get_contract_factory(library).deploy({"from": eth_wrapper.owner})
192
- eth_contract = self.get_contract_factory(eth_wrapper.eth_contract)
193
- if eth_wrapper.proxy_kind is None:
194
- eth_wrapper.contract = eth_contract.deploy(*init_params, {"from": eth_wrapper.owner})
195
- elif eth_wrapper.proxy_kind == "uups" and not SKIP_PROXY:
196
- constructor_params, init_params = init_params
197
- real_contract = eth_contract.deploy(*constructor_params, {"from": eth_wrapper.owner})
198
- proxy_factory = self.get_contract_factory("ERC1967Proxy")
199
- try:
200
- proxy_contract = proxy_factory.deploy(
201
- real_contract,
202
- encode_function_data(getattr(real_contract, "initialize", None), *init_params),
203
- {"from": eth_wrapper.owner}
204
- )
205
- except VirtualMachineError as err:
206
- if err.revert_type == "revert":
207
- raise RevertError(err.revert_msg)
208
- raise
209
-
210
- # Replace the proxy contract with the implementation in brownie's state
211
- # This makes the gas reports work properly for uups contracts
212
- self.get_contract_factory("ERC1967Proxy").remove(proxy_contract)
213
- eth_wrapper.contract = eth_contract.at(proxy_contract.address)
214
-
215
- elif eth_wrapper.proxy_kind == "uups" and SKIP_PROXY:
216
- constructor_params, init_params = init_params
217
- eth_wrapper.contract = eth_contract.deploy(*constructor_params, {"from": eth_wrapper.owner})
218
- eth_wrapper.contract.initialize(*init_params, {"from": eth_wrapper.owner})
219
-
220
- def build_contract(self, contract_address, contract_factory, contract_name=None):
221
- return Contract.from_abi(contract_name or "Contract", contract_address, contract_factory.abi)