├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── Pipfile
├── Pipfile.lock
├── README.md
├── example
├── ConvertLib.sol
├── MetaCoin.sol
├── example.png
├── test_metacoin.py
├── test_owned_and_timelimited.py
└── test_token_ico.py
├── pytest-cobra.png
├── pytest.ini
├── pytest_cobra
└── __init__.py
├── pytest_eth
├── __init__.py
├── account.py
├── config.py
├── factory.py
├── handler.py
├── instance.py
├── interfaces.py
├── log.py
├── tester.py
└── utils.py
├── requirements.txt
├── setup.py
├── tests
├── accounts
│ └── test_account.py
├── conftest.py
└── tester
│ └── test_tester.py
└── tox.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /.tox
3 |
4 | # Setuptools stuff
5 | build/
6 | dist/
7 | pytest_cobra.egg-info/
8 |
9 | # Python stuff
10 | __pycache__/
11 |
12 | # py.test stuff
13 | .pytest_cache/
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | dist: xenial
3 | sudo: true
4 | install:
5 | - sudo add-apt-repository -y ppa:ethereum/ethereum
6 | - sudo add-apt-repository -y ppa:deadsnakes/ppa
7 | - sudo apt-get update
8 | - sudo apt-get install -y python$TRAVIS_PYTHON_VERSION-dev npm solc
9 | - npm -g install ganache-cli
10 | - pip install -r requirements.txt
11 | - pip install tox-travis coveralls
12 | matrix:
13 | include:
14 | - name: '3.6'
15 | python: 3.6
16 | - name: '3.7'
17 | python: 3.7
18 | script: tox
19 | after_success: coveralls;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Meheret Tesfaye
4 |
5 | PYTETS-COBRA
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.md
3 | include requirements.txt
4 | include example/*
5 |
6 | recursive-exclude * __pycache__
7 | recursive-exclude * *.py[co]
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | install: clean
2 | -pip freeze | xargs pip uninstall -y
3 | pip install -e .
4 |
5 | test: install
6 | pytest tests/
7 |
8 | # Verify our example project
9 | .PHONY: example
10 | example: install
11 | pytest --cobra example/MetaCoin.sol
12 |
13 | upload: test example
14 | pip install twine
15 | python setup.py sdist
16 | twine upload dist/*
17 |
18 | # Checks dry run, then prompts to execute
19 | clean:
20 | rm -rf build/
21 | rm -rf dist/
22 | rm -rf pytest_cobra.egg-info/
23 | rm -rf __pycache__/
24 | rm -rf .pytest_cache/
25 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 | pytest = ">=3.7.1,<4.0.0"
8 | eth-keyfile = "==0.5.1"
9 | eth-tester = "==0.1.0b33"
10 | py-evm = "==0.2.0a33"
11 | eth-abi = "==1.2.2"
12 | py-ecc = "==1.4.3"
13 | py-solc = ">=3.2.0,<4.0.0"
14 | web3 = ">=4.4.1,<5.0.0"
15 | PyYAML = ">=3.13,<6.0"
16 |
17 | [packages]
18 | pytest-cobra = {editable = true,path = "."}
19 |
20 | [requires]
21 | python_version = "3.6"
22 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "bd09636b9970b499716c1ffc59bb12e302bf379ae68a812ab9d604374ab8ee61"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "atomicwrites": {
20 | "hashes": [
21 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
22 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
23 | ],
24 | "version": "==1.3.0"
25 | },
26 | "attrdict": {
27 | "hashes": [
28 | "sha256:35c90698b55c683946091177177a9e9c0713a0860f0e049febd72649ccd77b70",
29 | "sha256:9432e3498c74ff7e1b20b3d93b45d766b71cbffa90923496f82c4ae38b92be34"
30 | ],
31 | "version": "==2.0.1"
32 | },
33 | "attrs": {
34 | "hashes": [
35 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
36 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
37 | ],
38 | "version": "==19.3.0"
39 | },
40 | "certifi": {
41 | "hashes": [
42 | "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
43 | "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
44 | ],
45 | "version": "==2019.9.11"
46 | },
47 | "cffi": {
48 | "hashes": [
49 | "sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa",
50 | "sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a",
51 | "sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400",
52 | "sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365",
53 | "sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98",
54 | "sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526",
55 | "sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14",
56 | "sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5",
57 | "sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e",
58 | "sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1",
59 | "sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434",
60 | "sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b",
61 | "sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730",
62 | "sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43",
63 | "sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4",
64 | "sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331",
65 | "sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36",
66 | "sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599",
67 | "sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8",
68 | "sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8",
69 | "sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa",
70 | "sha256:8a2bcae2258d00fcfc96a9bde4a6177bc4274fe033f79311c5dd3d3148c26518",
71 | "sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78",
72 | "sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc",
73 | "sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e",
74 | "sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2",
75 | "sha256:b8f09f21544b9899defb09afbdaeb200e6a87a2b8e604892940044cf94444644",
76 | "sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0",
77 | "sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71",
78 | "sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891",
79 | "sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05",
80 | "sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"
81 | ],
82 | "version": "==1.13.1"
83 | },
84 | "chardet": {
85 | "hashes": [
86 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
87 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
88 | ],
89 | "version": "==3.0.4"
90 | },
91 | "cryptography": {
92 | "hashes": [
93 | "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
94 | "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
95 | "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
96 | "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
97 | "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
98 | "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
99 | "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
100 | "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
101 | "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
102 | "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
103 | "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
104 | "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
105 | "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
106 | "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
107 | "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
108 | "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
109 | "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
110 | "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
111 | "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
112 | "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
113 | "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
114 | ],
115 | "version": "==2.8"
116 | },
117 | "cytoolz": {
118 | "hashes": [
119 | "sha256:ed9f6a07c2bac70d6c597df360d0666d11d2adc90141d54c5c2db08b380a4fac"
120 | ],
121 | "markers": "implementation_name == 'cpython'",
122 | "version": "==0.10.0"
123 | },
124 | "eth-abi": {
125 | "hashes": [
126 | "sha256:2d0a056861c038ae53f50fabf65d545155fc0baee35eba41bce907c430f96837",
127 | "sha256:b80934f31e4d8fe20cf98244da7ed19b505b24b7893d1d9e7a2c236c47d9cdc9"
128 | ],
129 | "version": "==1.2.2"
130 | },
131 | "eth-account": {
132 | "hashes": [
133 | "sha256:3b5b1735db5736c9bb59786256edb0e18ea912f0a3d835611abb0266aa71c0d1",
134 | "sha256:63d782e7d0db455d13b5d6f18df790895072fde49ed00f1c176ae11dfa87251b"
135 | ],
136 | "version": "==0.3.0"
137 | },
138 | "eth-bloom": {
139 | "hashes": [
140 | "sha256:7946722121f40d76aba2a148afe5edde714d119c7d698ddd0ef4d5a1197c3765",
141 | "sha256:89d415710af1480683226e95805519f7c79b7244a3ca8d5287684301c7cee3de"
142 | ],
143 | "version": "==1.0.3"
144 | },
145 | "eth-hash": {
146 | "extras": [
147 | "pycryptodome"
148 | ],
149 | "hashes": [
150 | "sha256:1b9cb34dd3cd99c85c2bd6a1420ceae39a2eee8bf080efd264bcda8be3edecc8",
151 | "sha256:499dc02d098f69856d1a6dd005529c16174157d4fb2a9fe20c41f69e39f8f176"
152 | ],
153 | "version": "==0.2.0"
154 | },
155 | "eth-keyfile": {
156 | "hashes": [
157 | "sha256:70d734af17efdf929a90bb95375f43522be4ed80c3b9e0a8bca575fb11cd1159",
158 | "sha256:939540efb503380bc30d926833e6a12b22c6750de80feef3720d79e5a79de47d"
159 | ],
160 | "version": "==0.5.1"
161 | },
162 | "eth-keys": {
163 | "hashes": [
164 | "sha256:d1cdcd6b2118edf5dcd112ba6efc4b187b028c5c7d6af6ca04d90b7af94a1c58",
165 | "sha256:e15a0140852552ec3eb07e9731e23d390aea4bae892022279af42ce32e9c2620"
166 | ],
167 | "version": "==0.2.4"
168 | },
169 | "eth-rlp": {
170 | "hashes": [
171 | "sha256:05d8456981d85e16a9afa57f2f2c3356af5d1c49499cc8512cfcdc034b90dde5",
172 | "sha256:a94744c207ea731a7266bd0894179dc6e51a6a8965316000c8e823b5d7e07694"
173 | ],
174 | "version": "==0.1.2"
175 | },
176 | "eth-tester": {
177 | "hashes": [
178 | "sha256:2f81d63e2cdef93071e4e53c9b51e69eb33159ba6d19d34c561579506b32c3a5",
179 | "sha256:dfb2eaa91c0ae3ef8838b1cc4b97eb0826dff3f7caa5bfade526902933e0d8c0"
180 | ],
181 | "version": "==0.1.0b33"
182 | },
183 | "eth-typing": {
184 | "hashes": [
185 | "sha256:3b4744c9026e44f3234aae48d3d18062760efc0f755f663f723a12214f127dfc",
186 | "sha256:77da8a1f2f91f248cc42493f3dea3245f23a48224a513c4fd05f48b778dafb1a"
187 | ],
188 | "version": "==1.3.0"
189 | },
190 | "eth-utils": {
191 | "hashes": [
192 | "sha256:8f01c563fef5400c91465f33d584558e5384306d3b72b5a4fa2fcb2f6d7154e8",
193 | "sha256:e874aa50ceeceb9e46d8a9cb566d2c010702b35eecde1c6b48c91c29e9d155d1"
194 | ],
195 | "version": "==1.7.0"
196 | },
197 | "hexbytes": {
198 | "hashes": [
199 | "sha256:438ba9a28dfcda2c2276954b4310f9af1604fb198bfe5ac44c6518feaf6d376a",
200 | "sha256:9e8b3e3dc4a7de23c0cf1bb3c3edfcc1f0df4b78927bad63816c27a027b8b7d1"
201 | ],
202 | "version": "==0.2.0"
203 | },
204 | "idna": {
205 | "hashes": [
206 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
207 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
208 | ],
209 | "version": "==2.8"
210 | },
211 | "importlib-metadata": {
212 | "hashes": [
213 | "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
214 | "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
215 | ],
216 | "markers": "python_version < '3.8'",
217 | "version": "==0.23"
218 | },
219 | "lru-dict": {
220 | "hashes": [
221 | "sha256:365457660e3d05b76f1aba3e0f7fedbfcd6528e97c5115a351ddd0db488354cc"
222 | ],
223 | "version": "==1.1.6"
224 | },
225 | "more-itertools": {
226 | "hashes": [
227 | "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
228 | "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
229 | ],
230 | "version": "==7.2.0"
231 | },
232 | "parsimonious": {
233 | "hashes": [
234 | "sha256:3add338892d580e0cb3b1a39e4a1b427ff9f687858fdd61097053742391a9f6b"
235 | ],
236 | "version": "==0.8.1"
237 | },
238 | "pluggy": {
239 | "hashes": [
240 | "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
241 | "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
242 | ],
243 | "version": "==0.13.0"
244 | },
245 | "py": {
246 | "hashes": [
247 | "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
248 | "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
249 | ],
250 | "version": "==1.8.0"
251 | },
252 | "py-ecc": {
253 | "hashes": [
254 | "sha256:fc787718acbaaa9a292e99e1373c6c30969314d8ab9b2d10f02cc38fc1ac3111"
255 | ],
256 | "version": "==1.4.3"
257 | },
258 | "py-evm": {
259 | "hashes": [
260 | "sha256:7609f2fb1e8f9d31d2400c69f638ae3ff5f47b9032d28cafe7c8024f3857f568",
261 | "sha256:d9f1021241a9202741a26bcd0823a8bef59a305565e2baf85965932b38a23105"
262 | ],
263 | "version": "==0.2.0a33"
264 | },
265 | "py-solc": {
266 | "hashes": [
267 | "sha256:82095bdac661072f48cf2daf8a96bdb625674330d92b225be26043e8d3ef8c9a",
268 | "sha256:9ec0bc36ef22a9b0f5642e7846999c4485fa2fa562a61897aeb0a4ca53d60153"
269 | ],
270 | "version": "==3.2.0"
271 | },
272 | "pycparser": {
273 | "hashes": [
274 | "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
275 | ],
276 | "version": "==2.19"
277 | },
278 | "pycryptodome": {
279 | "hashes": [
280 | "sha256:023c294367d7189ae224fb61bc8d49a2347704087c1c78dbd5ab114dd5b97761",
281 | "sha256:0f29e1238ad3b6b6e2acd7ea1d8e8b382978a56503f2c48b67d5dc144d143cb0",
282 | "sha256:18f376698e3ddcb1d3b312512ca78c9eed132e68ac6d0bf2e72452dfe213e96f",
283 | "sha256:1de815b847982f909dc2e5e2ca641b85cde80d95cc7e6a359c03d4b42cd21568",
284 | "sha256:1ff619b8e4050799ca5ca0ffdf8eb0dbccba6997997866755f37e6aa7dde23fe",
285 | "sha256:233a04bb7bdd4b07e14d61d5166150942d872802daa4f049d49a453fe0659e94",
286 | "sha256:33c07e1e36ec84524b49f99f11804d5e4d2188c643e84d914cb1e0a277ed3c79",
287 | "sha256:3701822a085dbebf678bfbdfbd6ebd92ffa80d5a544c9979984bf16a67c9790b",
288 | "sha256:3f8e6851c0a45429f9b86c1597d3b831b0cff140b3e170a891fce55ef8dac2bb",
289 | "sha256:4f6cdddf1fe72e7f173e9734aa19b94cbd046b61a8559d650ff222e36021d5c1",
290 | "sha256:52d20b22c5b1fc952b4c686b99a6c55c3b0b0a673bec30570f156a72198f66ff",
291 | "sha256:5452b534fecf8bf57cf9106d00877f5f4ab7264e7a5e1f5ea8d15b04517d1255",
292 | "sha256:5a7a9a4a7f8f0990fa97fee71c7f7e0c412925c515cfc6d4996961e92c9be8e5",
293 | "sha256:600bf9dd5fbed0feee83950e2a8baacaa1f38b56c237fff270d31e47f8da9e52",
294 | "sha256:6840c9881e528224ebf72b3f73b3d11baf399e265106c9f4d9bae4f09615a93a",
295 | "sha256:71b041d43fe13004abc36ca720ac64ea489ee8a3407a25116481d0faf9d62494",
296 | "sha256:7252498b427c421e306473ed344e58235eedd95c15fec2e1b33d333aefa1ea10",
297 | "sha256:8d2135c941d38f241e0e62dbdfc1ca5d9240527e61316126797f50b6f3e49825",
298 | "sha256:a0962aea03933b99cf391c3e10dfef32f77915d5553464264cfbc6711f31d254",
299 | "sha256:a117047a220b3911d425affcd1cbc97a1af7ea7eb5d985d9964d42b4f0558489",
300 | "sha256:a35a5c588248ba00eb976a8554211e584a55de286783bc69b12bdd7954052b4a",
301 | "sha256:c1a4f3f651471b9bf60b0d98fa8a994b8a73ff8ab4edc691e23243c853aaff9f",
302 | "sha256:c419943306756ddd1a1997120bb073733bc223365909c68185106d5521cbc0ef",
303 | "sha256:c453ad968b67d66448543420ec39770c30bd16d986058255f058ab87c4f6cc1f",
304 | "sha256:d2d78644655629c7d1b9bf28e479d29facc0949d9ff095103ca9c2314b329ee0",
305 | "sha256:d7be60dc2126ee350ac7191549f5ab05c2dd76a5d5a3022249f395a401c6ea37",
306 | "sha256:dbeb08ad850056747aa7d5f33273b7ce0b9a77910604a1be7b7a6f2ef076213f",
307 | "sha256:f02382dc1bf91fb7123f2a3851fb1b526c871fa9359f387f2bcc847efc74ae52"
308 | ],
309 | "version": "==3.9.0"
310 | },
311 | "pyethash": {
312 | "hashes": [
313 | "sha256:ff66319ce26b9d77df1f610942634dac9742e216f2c27b051c0a2c2dec9c2818"
314 | ],
315 | "version": "==0.1.27"
316 | },
317 | "pytest": {
318 | "hashes": [
319 | "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec",
320 | "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"
321 | ],
322 | "version": "==3.10.1"
323 | },
324 | "pytest-cobra": {
325 | "editable": true,
326 | "path": "."
327 | },
328 | "pyyaml": {
329 | "hashes": [
330 | "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
331 | "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
332 | "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
333 | "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
334 | "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
335 | "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
336 | "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
337 | "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
338 | "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
339 | "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
340 | "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
341 | ],
342 | "version": "==3.13"
343 | },
344 | "requests": {
345 | "hashes": [
346 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
347 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
348 | ],
349 | "version": "==2.22.0"
350 | },
351 | "rlp": {
352 | "hashes": [
353 | "sha256:0505fd53278cb4a3ea6baf1b658357ac209bdcdd1b316ac90050c40f669ceacc",
354 | "sha256:ebe80a03c50e3d6aac47f44ddd45048bb99e411203cd764f5da1330e6d83821c"
355 | ],
356 | "version": "==1.1.0"
357 | },
358 | "semantic-version": {
359 | "hashes": [
360 | "sha256:695d5a06a86439d2dd0e5eaf3e46c5e6090bb5e72ba88377680a0acb483a3b44",
361 | "sha256:71c716e99086c44d068262b86e4775aa6db7fabee0743e4e33b00fbf6f672585"
362 | ],
363 | "version": "==2.8.2"
364 | },
365 | "six": {
366 | "hashes": [
367 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
368 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
369 | ],
370 | "version": "==1.12.0"
371 | },
372 | "toolz": {
373 | "hashes": [
374 | "sha256:08fdd5ef7c96480ad11c12d472de21acd32359996f69a5259299b540feba4560"
375 | ],
376 | "version": "==0.10.0"
377 | },
378 | "trie": {
379 | "hashes": [
380 | "sha256:5b7dedfeedd03c0d6b486b1b21c8182242307daff1bb011fed150a6c8dc4e34b",
381 | "sha256:5c9501bc1af2c065502601370fc991c496c186c725ca408993d65a0792c2949b"
382 | ],
383 | "version": "==1.4.0"
384 | },
385 | "urllib3": {
386 | "hashes": [
387 | "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
388 | "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
389 | ],
390 | "version": "==1.25.6"
391 | },
392 | "web3": {
393 | "hashes": [
394 | "sha256:1800c42c3492a6ebc72f9ad4d6dadfad5ac952564a3f0e1ec05d308a3d52aac7",
395 | "sha256:27058c6e07f7a3153d1355f7e7406c46c8cc0d5c7b6fdcea84f6f3c9cf7c4364"
396 | ],
397 | "version": "==4.9.2"
398 | },
399 | "websockets": {
400 | "hashes": [
401 | "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
402 | "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
403 | "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
404 | "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
405 | "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
406 | "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
407 | "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
408 | "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
409 | "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
410 | "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
411 | "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
412 | "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
413 | "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
414 | "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
415 | "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
416 | "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
417 | "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
418 | "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
419 | "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
420 | "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
421 | "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
422 | ],
423 | "version": "==6.0"
424 | },
425 | "zipp": {
426 | "hashes": [
427 | "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
428 | "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
429 | ],
430 | "version": "==0.6.0"
431 | }
432 | },
433 | "develop": {
434 | "atomicwrites": {
435 | "hashes": [
436 | "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
437 | "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
438 | ],
439 | "version": "==1.3.0"
440 | },
441 | "attrdict": {
442 | "hashes": [
443 | "sha256:35c90698b55c683946091177177a9e9c0713a0860f0e049febd72649ccd77b70",
444 | "sha256:9432e3498c74ff7e1b20b3d93b45d766b71cbffa90923496f82c4ae38b92be34"
445 | ],
446 | "version": "==2.0.1"
447 | },
448 | "attrs": {
449 | "hashes": [
450 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
451 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
452 | ],
453 | "version": "==19.3.0"
454 | },
455 | "certifi": {
456 | "hashes": [
457 | "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
458 | "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
459 | ],
460 | "version": "==2019.9.11"
461 | },
462 | "cffi": {
463 | "hashes": [
464 | "sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa",
465 | "sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a",
466 | "sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400",
467 | "sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365",
468 | "sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98",
469 | "sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526",
470 | "sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14",
471 | "sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5",
472 | "sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e",
473 | "sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1",
474 | "sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434",
475 | "sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b",
476 | "sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730",
477 | "sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43",
478 | "sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4",
479 | "sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331",
480 | "sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36",
481 | "sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599",
482 | "sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8",
483 | "sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8",
484 | "sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa",
485 | "sha256:8a2bcae2258d00fcfc96a9bde4a6177bc4274fe033f79311c5dd3d3148c26518",
486 | "sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78",
487 | "sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc",
488 | "sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e",
489 | "sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2",
490 | "sha256:b8f09f21544b9899defb09afbdaeb200e6a87a2b8e604892940044cf94444644",
491 | "sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0",
492 | "sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71",
493 | "sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891",
494 | "sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05",
495 | "sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"
496 | ],
497 | "version": "==1.13.1"
498 | },
499 | "chardet": {
500 | "hashes": [
501 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
502 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
503 | ],
504 | "version": "==3.0.4"
505 | },
506 | "cryptography": {
507 | "hashes": [
508 | "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
509 | "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
510 | "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
511 | "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
512 | "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
513 | "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
514 | "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
515 | "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
516 | "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
517 | "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
518 | "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
519 | "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
520 | "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
521 | "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
522 | "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
523 | "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
524 | "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
525 | "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
526 | "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
527 | "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
528 | "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
529 | ],
530 | "version": "==2.8"
531 | },
532 | "cytoolz": {
533 | "hashes": [
534 | "sha256:ed9f6a07c2bac70d6c597df360d0666d11d2adc90141d54c5c2db08b380a4fac"
535 | ],
536 | "markers": "implementation_name == 'cpython'",
537 | "version": "==0.10.0"
538 | },
539 | "eth-abi": {
540 | "hashes": [
541 | "sha256:2d0a056861c038ae53f50fabf65d545155fc0baee35eba41bce907c430f96837",
542 | "sha256:b80934f31e4d8fe20cf98244da7ed19b505b24b7893d1d9e7a2c236c47d9cdc9"
543 | ],
544 | "version": "==1.2.2"
545 | },
546 | "eth-account": {
547 | "hashes": [
548 | "sha256:3b5b1735db5736c9bb59786256edb0e18ea912f0a3d835611abb0266aa71c0d1",
549 | "sha256:63d782e7d0db455d13b5d6f18df790895072fde49ed00f1c176ae11dfa87251b"
550 | ],
551 | "version": "==0.3.0"
552 | },
553 | "eth-bloom": {
554 | "hashes": [
555 | "sha256:7946722121f40d76aba2a148afe5edde714d119c7d698ddd0ef4d5a1197c3765",
556 | "sha256:89d415710af1480683226e95805519f7c79b7244a3ca8d5287684301c7cee3de"
557 | ],
558 | "version": "==1.0.3"
559 | },
560 | "eth-hash": {
561 | "extras": [
562 | "pycryptodome"
563 | ],
564 | "hashes": [
565 | "sha256:1b9cb34dd3cd99c85c2bd6a1420ceae39a2eee8bf080efd264bcda8be3edecc8",
566 | "sha256:499dc02d098f69856d1a6dd005529c16174157d4fb2a9fe20c41f69e39f8f176"
567 | ],
568 | "version": "==0.2.0"
569 | },
570 | "eth-keyfile": {
571 | "hashes": [
572 | "sha256:70d734af17efdf929a90bb95375f43522be4ed80c3b9e0a8bca575fb11cd1159",
573 | "sha256:939540efb503380bc30d926833e6a12b22c6750de80feef3720d79e5a79de47d"
574 | ],
575 | "version": "==0.5.1"
576 | },
577 | "eth-keys": {
578 | "hashes": [
579 | "sha256:d1cdcd6b2118edf5dcd112ba6efc4b187b028c5c7d6af6ca04d90b7af94a1c58",
580 | "sha256:e15a0140852552ec3eb07e9731e23d390aea4bae892022279af42ce32e9c2620"
581 | ],
582 | "version": "==0.2.4"
583 | },
584 | "eth-rlp": {
585 | "hashes": [
586 | "sha256:05d8456981d85e16a9afa57f2f2c3356af5d1c49499cc8512cfcdc034b90dde5",
587 | "sha256:a94744c207ea731a7266bd0894179dc6e51a6a8965316000c8e823b5d7e07694"
588 | ],
589 | "version": "==0.1.2"
590 | },
591 | "eth-tester": {
592 | "hashes": [
593 | "sha256:2f81d63e2cdef93071e4e53c9b51e69eb33159ba6d19d34c561579506b32c3a5",
594 | "sha256:dfb2eaa91c0ae3ef8838b1cc4b97eb0826dff3f7caa5bfade526902933e0d8c0"
595 | ],
596 | "version": "==0.1.0b33"
597 | },
598 | "eth-typing": {
599 | "hashes": [
600 | "sha256:3b4744c9026e44f3234aae48d3d18062760efc0f755f663f723a12214f127dfc",
601 | "sha256:77da8a1f2f91f248cc42493f3dea3245f23a48224a513c4fd05f48b778dafb1a"
602 | ],
603 | "version": "==1.3.0"
604 | },
605 | "eth-utils": {
606 | "hashes": [
607 | "sha256:8f01c563fef5400c91465f33d584558e5384306d3b72b5a4fa2fcb2f6d7154e8",
608 | "sha256:e874aa50ceeceb9e46d8a9cb566d2c010702b35eecde1c6b48c91c29e9d155d1"
609 | ],
610 | "version": "==1.7.0"
611 | },
612 | "hexbytes": {
613 | "hashes": [
614 | "sha256:438ba9a28dfcda2c2276954b4310f9af1604fb198bfe5ac44c6518feaf6d376a",
615 | "sha256:9e8b3e3dc4a7de23c0cf1bb3c3edfcc1f0df4b78927bad63816c27a027b8b7d1"
616 | ],
617 | "version": "==0.2.0"
618 | },
619 | "idna": {
620 | "hashes": [
621 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
622 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
623 | ],
624 | "version": "==2.8"
625 | },
626 | "importlib-metadata": {
627 | "hashes": [
628 | "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
629 | "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
630 | ],
631 | "markers": "python_version < '3.8'",
632 | "version": "==0.23"
633 | },
634 | "lru-dict": {
635 | "hashes": [
636 | "sha256:365457660e3d05b76f1aba3e0f7fedbfcd6528e97c5115a351ddd0db488354cc"
637 | ],
638 | "version": "==1.1.6"
639 | },
640 | "more-itertools": {
641 | "hashes": [
642 | "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
643 | "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
644 | ],
645 | "version": "==7.2.0"
646 | },
647 | "parsimonious": {
648 | "hashes": [
649 | "sha256:3add338892d580e0cb3b1a39e4a1b427ff9f687858fdd61097053742391a9f6b"
650 | ],
651 | "version": "==0.8.1"
652 | },
653 | "pluggy": {
654 | "hashes": [
655 | "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
656 | "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
657 | ],
658 | "version": "==0.13.0"
659 | },
660 | "py": {
661 | "hashes": [
662 | "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
663 | "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
664 | ],
665 | "version": "==1.8.0"
666 | },
667 | "py-ecc": {
668 | "hashes": [
669 | "sha256:fc787718acbaaa9a292e99e1373c6c30969314d8ab9b2d10f02cc38fc1ac3111"
670 | ],
671 | "version": "==1.4.3"
672 | },
673 | "py-evm": {
674 | "hashes": [
675 | "sha256:7609f2fb1e8f9d31d2400c69f638ae3ff5f47b9032d28cafe7c8024f3857f568",
676 | "sha256:d9f1021241a9202741a26bcd0823a8bef59a305565e2baf85965932b38a23105"
677 | ],
678 | "version": "==0.2.0a33"
679 | },
680 | "py-solc": {
681 | "hashes": [
682 | "sha256:82095bdac661072f48cf2daf8a96bdb625674330d92b225be26043e8d3ef8c9a",
683 | "sha256:9ec0bc36ef22a9b0f5642e7846999c4485fa2fa562a61897aeb0a4ca53d60153"
684 | ],
685 | "version": "==3.2.0"
686 | },
687 | "pycparser": {
688 | "hashes": [
689 | "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
690 | ],
691 | "version": "==2.19"
692 | },
693 | "pycryptodome": {
694 | "hashes": [
695 | "sha256:023c294367d7189ae224fb61bc8d49a2347704087c1c78dbd5ab114dd5b97761",
696 | "sha256:0f29e1238ad3b6b6e2acd7ea1d8e8b382978a56503f2c48b67d5dc144d143cb0",
697 | "sha256:18f376698e3ddcb1d3b312512ca78c9eed132e68ac6d0bf2e72452dfe213e96f",
698 | "sha256:1de815b847982f909dc2e5e2ca641b85cde80d95cc7e6a359c03d4b42cd21568",
699 | "sha256:1ff619b8e4050799ca5ca0ffdf8eb0dbccba6997997866755f37e6aa7dde23fe",
700 | "sha256:233a04bb7bdd4b07e14d61d5166150942d872802daa4f049d49a453fe0659e94",
701 | "sha256:33c07e1e36ec84524b49f99f11804d5e4d2188c643e84d914cb1e0a277ed3c79",
702 | "sha256:3701822a085dbebf678bfbdfbd6ebd92ffa80d5a544c9979984bf16a67c9790b",
703 | "sha256:3f8e6851c0a45429f9b86c1597d3b831b0cff140b3e170a891fce55ef8dac2bb",
704 | "sha256:4f6cdddf1fe72e7f173e9734aa19b94cbd046b61a8559d650ff222e36021d5c1",
705 | "sha256:52d20b22c5b1fc952b4c686b99a6c55c3b0b0a673bec30570f156a72198f66ff",
706 | "sha256:5452b534fecf8bf57cf9106d00877f5f4ab7264e7a5e1f5ea8d15b04517d1255",
707 | "sha256:5a7a9a4a7f8f0990fa97fee71c7f7e0c412925c515cfc6d4996961e92c9be8e5",
708 | "sha256:600bf9dd5fbed0feee83950e2a8baacaa1f38b56c237fff270d31e47f8da9e52",
709 | "sha256:6840c9881e528224ebf72b3f73b3d11baf399e265106c9f4d9bae4f09615a93a",
710 | "sha256:71b041d43fe13004abc36ca720ac64ea489ee8a3407a25116481d0faf9d62494",
711 | "sha256:7252498b427c421e306473ed344e58235eedd95c15fec2e1b33d333aefa1ea10",
712 | "sha256:8d2135c941d38f241e0e62dbdfc1ca5d9240527e61316126797f50b6f3e49825",
713 | "sha256:a0962aea03933b99cf391c3e10dfef32f77915d5553464264cfbc6711f31d254",
714 | "sha256:a117047a220b3911d425affcd1cbc97a1af7ea7eb5d985d9964d42b4f0558489",
715 | "sha256:a35a5c588248ba00eb976a8554211e584a55de286783bc69b12bdd7954052b4a",
716 | "sha256:c1a4f3f651471b9bf60b0d98fa8a994b8a73ff8ab4edc691e23243c853aaff9f",
717 | "sha256:c419943306756ddd1a1997120bb073733bc223365909c68185106d5521cbc0ef",
718 | "sha256:c453ad968b67d66448543420ec39770c30bd16d986058255f058ab87c4f6cc1f",
719 | "sha256:d2d78644655629c7d1b9bf28e479d29facc0949d9ff095103ca9c2314b329ee0",
720 | "sha256:d7be60dc2126ee350ac7191549f5ab05c2dd76a5d5a3022249f395a401c6ea37",
721 | "sha256:dbeb08ad850056747aa7d5f33273b7ce0b9a77910604a1be7b7a6f2ef076213f",
722 | "sha256:f02382dc1bf91fb7123f2a3851fb1b526c871fa9359f387f2bcc847efc74ae52"
723 | ],
724 | "version": "==3.9.0"
725 | },
726 | "pyethash": {
727 | "hashes": [
728 | "sha256:ff66319ce26b9d77df1f610942634dac9742e216f2c27b051c0a2c2dec9c2818"
729 | ],
730 | "version": "==0.1.27"
731 | },
732 | "pytest": {
733 | "hashes": [
734 | "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec",
735 | "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"
736 | ],
737 | "version": "==3.10.1"
738 | },
739 | "pyyaml": {
740 | "hashes": [
741 | "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
742 | "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
743 | "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
744 | "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
745 | "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
746 | "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
747 | "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
748 | "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
749 | "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
750 | "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
751 | "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
752 | ],
753 | "version": "==3.13"
754 | },
755 | "requests": {
756 | "hashes": [
757 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
758 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
759 | ],
760 | "version": "==2.22.0"
761 | },
762 | "rlp": {
763 | "hashes": [
764 | "sha256:0505fd53278cb4a3ea6baf1b658357ac209bdcdd1b316ac90050c40f669ceacc",
765 | "sha256:ebe80a03c50e3d6aac47f44ddd45048bb99e411203cd764f5da1330e6d83821c"
766 | ],
767 | "version": "==1.1.0"
768 | },
769 | "semantic-version": {
770 | "hashes": [
771 | "sha256:695d5a06a86439d2dd0e5eaf3e46c5e6090bb5e72ba88377680a0acb483a3b44",
772 | "sha256:71c716e99086c44d068262b86e4775aa6db7fabee0743e4e33b00fbf6f672585"
773 | ],
774 | "version": "==2.8.2"
775 | },
776 | "six": {
777 | "hashes": [
778 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
779 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
780 | ],
781 | "version": "==1.12.0"
782 | },
783 | "toolz": {
784 | "hashes": [
785 | "sha256:08fdd5ef7c96480ad11c12d472de21acd32359996f69a5259299b540feba4560"
786 | ],
787 | "version": "==0.10.0"
788 | },
789 | "trie": {
790 | "hashes": [
791 | "sha256:5b7dedfeedd03c0d6b486b1b21c8182242307daff1bb011fed150a6c8dc4e34b",
792 | "sha256:5c9501bc1af2c065502601370fc991c496c186c725ca408993d65a0792c2949b"
793 | ],
794 | "version": "==1.4.0"
795 | },
796 | "urllib3": {
797 | "hashes": [
798 | "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
799 | "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
800 | ],
801 | "version": "==1.25.6"
802 | },
803 | "web3": {
804 | "hashes": [
805 | "sha256:1800c42c3492a6ebc72f9ad4d6dadfad5ac952564a3f0e1ec05d308a3d52aac7",
806 | "sha256:27058c6e07f7a3153d1355f7e7406c46c8cc0d5c7b6fdcea84f6f3c9cf7c4364"
807 | ],
808 | "version": "==4.9.2"
809 | },
810 | "websockets": {
811 | "hashes": [
812 | "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
813 | "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
814 | "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
815 | "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
816 | "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
817 | "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
818 | "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
819 | "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
820 | "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
821 | "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
822 | "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
823 | "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
824 | "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
825 | "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
826 | "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
827 | "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
828 | "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
829 | "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
830 | "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
831 | "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
832 | "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
833 | ],
834 | "version": "==6.0"
835 | },
836 | "zipp": {
837 | "hashes": [
838 | "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
839 | "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
840 | ],
841 | "version": "==0.6.0"
842 | }
843 | }
844 | }
845 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # PyTest-Cobra 
6 |
7 | *PyTest plugin for testing Smart Contracts for Ethereum blockchain.*
8 |
9 | [](https://travis-ci.com/cobraframework/pytest-cobra)
10 | 
11 | 
12 | 
13 | 
14 | [](https://en.cryptobadges.io/donate/0xD32AAEDF28A848e21040B6F643861A9077F83106)
15 |
16 | ## Dependency
17 |
18 | This library requires the `solc` executable to be present.
19 |
20 | Only versions `>=0.4.2` are supported and tested though this library may work
21 | with other versions.
22 |
23 | [solc installation instructions](http://solidity.readthedocs.io/en/latest/installing-solidity.html)
24 |
25 | Install Solidity compiler (solc) using Node Package Manager(npm)
26 | ```
27 | npm install -g solc
28 | ```
29 | or for Ubuntu(Linux)
30 | ```
31 | sudo add-apt-repository ppa:ethereum/ethereum
32 | sudo apt-get update
33 | sudo apt-get install solc
34 | ```
35 |
36 | ## Quickstart
37 | Installation
38 | ```
39 | pip install pytest-cobra
40 | ```
41 |
42 | ## Development
43 | Clone the repository and then run
44 | ```
45 | pip install -e . -r requirements.txt
46 | ```
47 |
48 | ## Usage
49 |
50 | #### Execute your test suite
51 | Example MetaCoin
52 | [picture](https://github.com/cobraframework/pytest-cobra/blob/master/example/example.png)
53 |
54 | ```python
55 | # MetaCoin Testing
56 |
57 | # cobra is pytest fixture
58 | def test_metacoin(cobra):
59 |
60 | # Getting Contract Factory by name
61 | metacoin = cobra.contract('MetaCoin')
62 |
63 | # Getting Contract Instance of MetaCoin
64 | metacoin = metacoin.deploy()
65 |
66 | assert metacoin.getBalance(cobra.accounts[0]) == 10000
67 | ```
68 |
69 | ### Running test from Solidity file (.sol)
70 |
71 | ```
72 | pytest --cobra MetaCoin.sol
73 | ```
74 |
75 | #### Optional commands
76 |
77 | ##### Import path remappings
78 | `solc` provides path aliasing allow you to have more reusable project configurations.
79 | ```
80 | pytest --cobra MetaCoin.sol --import_remappings ["zeppeling=/my-zeppelin-checkout-folder"]
81 | ```
82 |
83 | ##### Allow paths
84 | ```
85 | pytest --cobra MetaCoin.sol --allow_paths "/home/meheret,/user,/"
86 | ```
87 |
88 | ### Running test from compiled Contracts Json file (.json)
89 |
90 | Compile your contracts into a package (soon to be ethPM-compliant)
91 | ```
92 | solc --combined-json abi,bin,bin-runtime contracts/ > MetaCoin.json
93 | ```
94 | Testing Contracts.json
95 | ```
96 | pytest --cobra MetaCoin.json
97 | ```
98 |
99 | ### Running test from Yaml file (.yaml)
100 | ```yaml
101 | test:
102 | artifact_path: "./build/contracts/"
103 | test_paths: ["./tests"]
104 | contracts: [
105 | contract: {
106 | artifact: "Contract.json",
107 | links: ["Contract.json"]
108 | }
109 | ]
110 | ```
111 |
112 | ## Further help
113 | ##### PyTest
114 | Go check out the [PyTest](http://pytest.org).
115 |
116 | ## Author ✒️
117 |
118 | * **Meheret Tesfaye** - *Initial work* - [Cobra](https://github.com/cobraframework)
119 |
120 | ## License
121 |
122 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
123 |
124 |
--------------------------------------------------------------------------------
/example/ConvertLib.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.4;
2 |
3 | library ConvertLib{
4 | function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
5 | {
6 | return amount * conversionRate;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/MetaCoin.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.18;
2 |
3 | //import "toxinidir/example/ConvertLib.sol";
4 | import "/home/travis/build/cobraframework/pytest-cobra/example/ConvertLib.sol";
5 |
6 | // This is just a simple example of a coin-like contract.
7 | // It is not standards compatible and cannot be expected to talk to other
8 | // coin/token contracts. If you want to create a standards-compliant
9 | // token, see: https://github.com/ConsenSys/Tokens. Cheers!
10 |
11 | contract MetaCoin {
12 | mapping (address => uint) balances;
13 |
14 | event Transfer(address indexed _from, address indexed _to, uint256 _value);
15 |
16 | constructor() public {
17 | balances[tx.origin] = 10000;
18 | }
19 |
20 | function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
21 | if (balances[msg.sender] < amount) return false;
22 | balances[msg.sender] -= amount;
23 | balances[receiver] += amount;
24 | emit Transfer(msg.sender, receiver, amount);
25 | return true;
26 | }
27 |
28 | function getBalanceInEth(address addr) public view returns(uint){
29 | return ConvertLib.convert(getBalance(addr),2);
30 | }
31 |
32 | function getBalance(address addr) public view returns(uint) {
33 | return balances[addr];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meherett/pytest-solidity/ea6106269eb38e851bc2b05e34bd40133b66f97f/example/example.png
--------------------------------------------------------------------------------
/example/test_metacoin.py:
--------------------------------------------------------------------------------
1 | # MetaCoin Testing
2 |
3 |
4 | # cobra is pytest fixture
5 | def test_metacoin(cobra):
6 | # Getting Contract Factory by name
7 | metacoin = cobra.contract('MetaCoin')
8 | # Getting Contract Instance of MetaCoin
9 | metacoin = metacoin.deploy()
10 |
11 | assert metacoin.getBalance(cobra.accounts[0]) == 10000
12 |
--------------------------------------------------------------------------------
/example/test_owned_and_timelimited.py:
--------------------------------------------------------------------------------
1 | artifact_owned = {
2 | "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"changeOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
3 | "bytecode": "6060604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550341561004f57600080fd5b6101ce8061005e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638da5cb5b14610051578063a6f9dae1146100a6575b600080fd5b341561005c57600080fd5b6100646100df565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100b157600080fd5b6100dd600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610104565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561015f57600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a72305820880a4056fab249ea33a384814995ed65839292e7edf737e2082462ab370b372a0029"
4 | }
5 |
6 | artifact_timelimited = {
7 | "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"duration\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"alive\",\"outputs\":[{\"name\":\"_stuff\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"destroy\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"setExpired\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"changeOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"finished\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"creationTime\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_duration\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]",
8 | "bytecode": "6060604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555042600155341561005357600080fd5b604051602080610493833981016040528080519060200190919050508080600281905550505061040b806100886000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630fb5a6b414610093578063753899e9146100bc57806383197ef0146100e95780638da5cb5b146100fe578063a56fa76c14610153578063a6f9dae114610168578063bef4876b146101a1578063d8270dce146101ce575b600080fd5b341561009e57600080fd5b6100a66101f7565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100cf6101fd565b604051808215151515815260200191505060405180910390f35b34156100f457600080fd5b6100fc610242565b005b341561010957600080fd5b610111610276565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b61016661029b565b005b341561017357600080fd5b61019f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610328565b005b34156101ac57600080fd5b6101b46103c6565b604051808215151515815260200191505060405180910390f35b34156101d957600080fd5b6101e16103d9565b6040518082815260200191505060405180910390f35b60025481565b6000600360009054906101000a900460ff1615151561021b57600080fd5b600254600154014210801561023d5750600360009054906101000a900460ff16155b905090565b600360009054906101000a900460ff16151561025d57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff16ff5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102f657600080fd5b60025460015401421015151561030b57600080fd5b6001600360006101000a81548160ff021916908315150217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561038357600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600360009054906101000a900460ff1681565b600154815600a165627a7a72305820cfa24f8534068ff9caf1fc94dda21bbaf586fa7b501b0712c37a18549b783ff90029"
9 | }
10 |
11 |
12 | # Just use the 'cobra' fixture in your pytest_ethereum-enabled tests
13 | def test_owned_and_timelimited(cobra):
14 |
15 | # Get contracts via name from your assets file (e.g. 'contracts.json')
16 | # NOTE: When no contract from file is selected,
17 | # uses file basename e.g. 'path/to/Owned.sol:Owned'
18 | owned = cobra.new(artifact_owned)
19 | # Deploying Owned
20 | owned = owned.deploy()
21 |
22 | # You can specify a specific contract from the file
23 | timelimited_factory = cobra.new(artifact_timelimited)
24 | # You must specify the deployment args if they exist
25 | # NOTE: must be supplied in abi order
26 | timelimited = timelimited_factory.deploy(10) # arg1: 10 blocks
27 |
28 | # Use normal assert syntax for testing
29 | # pure/view/constant functions call by default
30 | assert owned.owner() == cobra.accounts[0] # Doesn't mine a block!
31 |
32 | # non-'constant' functions transact by default
33 | # NOTE: Transactions auto-mine (see eth-cobra)
34 | owned.changeOwner(cobra.accounts[1]) # Transaction auto-mined into block
35 | assert owned.owner() == cobra.accounts[1] # No transaction here
36 |
37 | # Use this for asserting when a failed transaction should occur
38 | starting_balance = cobra.accounts[0].balance
39 | with cobra.tx_fails:
40 | owned.changeOwner(cobra.accounts[0]) # account 0 is no longer the owner!
41 | # We can do multiple failures in here...
42 | with cobra.tx_fails:
43 | owned.changeOwner(cobra.accounts[2]) # account 2 isn't either
44 |
45 | # No transactions were committed for these failures
46 | assert starting_balance == cobra.accounts[0].balance
47 |
48 | # You can supply optional transaction params
49 | owned.changeOwner(cobra.accounts[0],
50 | transact={
51 | 'from': cobra.accounts[1], # from a different sender
52 | # 'value': 100, # send 100 wei in this transaction
53 | # You can also do other things... see web3.py for more info!
54 | }
55 | )
56 | assert owned.owner() == cobra.accounts[0] # account[0] is the owner again!
57 |
58 | # You can mine an empty block if you want
59 | while timelimited.alive(): # This makes a call, so no transaction occurs
60 | cobra.mine_blocks() # mines an empty block
61 |
62 | # You can check the current timestamp
63 | assert cobra.now() >= timelimited.creationTime() + timelimited.duration()
64 | timelimited.setExpired()
65 | # You can check to see if a contract still has code
66 | # NOTE: Implicitly calls address.codesize != 0
67 | assert timelimited.hascode
68 | timelimited.destroy() # Calls self destruct opcode, removing code
69 | assert not timelimited.hascode
70 |
71 | # Get Ether balance of any address
72 | print("Account 0 has", cobra.accounts[0].balance, "Wei")
73 | print("Account 1 has", cobra.accounts[1].balance, "Wei")
74 | print("Contract 'timelimited' has", timelimited.balance, "Wei")
75 |
76 | # Send any address Ether
77 | print("Account 2 has", cobra.accounts[2].balance, "Wei")
78 | cobra.accounts[1].transfer(cobra.accounts[2], 100) # send 100 wei to address 2
79 | print("Account 2 now has", cobra.accounts[2].balance, "Wei")
80 |
--------------------------------------------------------------------------------
/example/test_token_ico.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import random
3 |
4 | artifact_token = {
5 | "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balances\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"changeOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_symbol\",\"type\":\"string\"},{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_decimals\",\"type\":\"uint8\"},{\"name\":\"initialSupply\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}]",
6 | "bytecode": "6060604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550341561004f57600080fd5b60405161101c38038061101c8339810160405280805182019190602001805182019190602001805190602001909190805190602001909190505083838360ff16600a0a830282600190805190602001906100aa929190610157565b5081600290805190602001906100c1929190610157565b5080600381905550600354600460008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050505081600660006101000a81548160ff021916908360ff160217905550505050506101fc565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019857805160ff19168380011785556101c6565b828001600101855582156101c6579182015b828111156101c55782518255916020019190600101906101aa565b5b5090506101d391906101d7565b5090565b6101f991905b808211156101f55760008160009055506001016101dd565b5090565b90565b610e118061020b6000396000f3006060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d057806327e235e314610249578063313ce5671461029657806370a08231146102c55780638da5cb5b1461031257806395d89b4114610367578063a6f9dae1146103f5578063a9059cbb1461042e578063dd62ed3e14610488575b600080fd5b34156100ca57600080fd5b6100d26104f4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610592565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba610684565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068e565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b610280600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610995565b6040518082815260200191505060405180910390f35b34156102a157600080fd5b6102a96109ad565b604051808260ff1660ff16815260200191505060405180910390f35b34156102d057600080fd5b6102fc600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506109c0565b6040518082815260200191505060405180910390f35b341561031d57600080fd5b610325610a09565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561037257600080fd5b61037a610a2e565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103ba57808201518184015260208101905061039f565b50505050905090810190601f1680156103e75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561040057600080fd5b61042c600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610acc565b005b341561043957600080fd5b61046e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610b6a565b604051808215151515815260200191505060405180910390f35b341561049357600080fd5b6104de600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d5e565b6040518082815260200191505060405180910390f35b60028054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561058a5780601f1061055f5761010080835404028352916020019161058a565b820191906000526020600020905b81548152906001019060200180831161056d57829003601f168201915b505050505081565b600081600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600354905090565b600081600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015801561075b575081600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410155b80156107675750600082115b80156107f25750600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401115b156109895781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905061098e565b600090505b9392505050565b60046020528060005260406000206000915090505481565b600660009054906101000a900460ff1681565b6000600460008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610ac45780601f10610a9957610100808354040283529160200191610ac4565b820191906000526020600020905b815481529060010190602001808311610aa757829003601f168201915b505050505081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610b2757600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410158015610bbb5750600082115b8015610c465750600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401115b15610d535781600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050610d58565b600090505b92915050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050929150505600a165627a7a7230582050b3bfaccbeb1c92ad4a7b5f5396a2650ec833b1988ebf78bd5a8e68d41330890029",
7 | }
8 |
9 | artifact_ico = {
10 | "abi": "[{\"constant\":true,\"inputs\":[],\"name\":\"sold\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"icoStarted\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"duration\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_token\",\"type\":\"address\"}],\"name\":\"setToken\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"softCapReached\",\"outputs\":[{\"name\":\"isReached\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"refundToken\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_tokenPrice\",\"type\":\"uint256\"}],\"name\":\"updateTokenPrice\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"icoFailed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"startICO\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"tokenPrice\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"softCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"hardCapReached\",\"outputs\":[{\"name\":\"isReached\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maintainer\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newMaintainer\",\"type\":\"address\"}],\"name\":\"changeMaintainer\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"failedICO\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"buyToken\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"changeOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"successfulICO\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"creationTime\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"declareFailure\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"hardCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_tokenPrice\",\"type\":\"uint256\"},{\"name\":\"_hardCap\",\"type\":\"uint256\"},{\"name\":\"_softCap\",\"type\":\"uint256\"},{\"name\":\"_duration\",\"type\":\"uint256\"},{\"name\":\"_token\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"valueSent\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"tokensBought\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"refund\",\"type\":\"uint256\"}],\"name\":\"TokenBuy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tokensReturned\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"refund\",\"type\":\"uint256\"}],\"name\":\"TokenRefund\",\"type\":\"event\"}]",
11 | "bytecode": "6060604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055504260065560006008556000600a60006101000a81548160ff0219169083151502179055506000600a60016101000a81548160ff02191690831515021790555034156100cf57600080fd5b60405160a080611448833981016040528080519060200190919080519060200190919080519060200190919080519060200190919080519060200190919050508080600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550508460038190555083600481905550826005819055508160078190555050505050506112c6806101826000396000f300606060405260043610610133576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806302c7e7af146101385780630b5e89f4146101615780630fb5a6b41461018e578063144fa6d7146101b75780632b9edee9146101f05780635cb732be1461021d578063676c0d77146102325780637f94bc31146102555780637fa8c158146102825780637ff9b596146102975780638da5cb5b146102c0578063906a26e0146103155780639762f8021461033e5780639850d32b1461036b578063a12ee7ba146103c0578063a1e5c3ed146103f9578063a48217191461040e578063a6f9dae114610418578063d07165c714610451578063d8270dce14610466578063fb42b01a1461048f578063fb86a404146104a4578063fc0c546a146104cd575b600080fd5b341561014357600080fd5b61014b610522565b6040518082815260200191505060405180910390f35b341561016c57600080fd5b610174610528565b604051808215151515815260200191505060405180910390f35b341561019957600080fd5b6101a161053b565b6040518082815260200191505060405180910390f35b34156101c257600080fd5b6101ee600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610541565b005b34156101fb57600080fd5b6102036105e1565b604051808215151515815260200191505060405180910390f35b341561022857600080fd5b6102306105f0565b005b341561023d57600080fd5b61025360048080359060200190919050506107a2565b005b341561026057600080fd5b61026861083b565b604051808215151515815260200191505060405180910390f35b341561028d57600080fd5b61029561084e565b005b34156102a257600080fd5b6102aa610901565b6040518082815260200191505060405180910390f35b34156102cb57600080fd5b6102d3610907565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561032057600080fd5b61032861092c565b6040518082815260200191505060405180910390f35b341561034957600080fd5b610351610932565b604051808215151515815260200191505060405180910390f35b341561037657600080fd5b61037e610941565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156103cb57600080fd5b6103f7600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610967565b005b341561040457600080fd5b61040c610a07565b005b610416610ac4565b005b341561042357600080fd5b61044f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610c8d565b005b341561045c57600080fd5b610464610d2b565b005b341561047157600080fd5b610479610e28565b6040518082815260200191505060405180910390f35b341561049a57600080fd5b6104a2610e2e565b005b34156104af57600080fd5b6104b7610f1f565b6040518082815260200191505060405180910390f35b34156104d857600080fd5b6104e0610f25565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60085481565b600a60009054906101000a900460ff1681565b60075481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561059d57600080fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60006005546008541015905090565b600080600a60019054906101000a900460ff16151561060e57600080fd5b61061733610f4b565b915060008211151561062857600080fd5b610654336000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1684611034565b81600860008282540392505081905550600960003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000600960003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f47bc8b296a721ead4262b15df1a3123f2c4eed6afa70823038a3c34d60d478f2338383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561079e57600080fd5b5050565b600a60009054906101000a900460ff1680156107cb5750600a60019054906101000a900460ff16155b15156107d657600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561083157600080fd5b8060038190555050565b600a60019054906101000a900460ff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156108a957600080fd5b6004546108d76000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff163061117c565b101515156108e457600080fd5b6001600a60006101000a81548160ff021916908315150217905550565b60035481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055481565b60006004546008541015905090565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156109c357600080fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600a60019054906101000a900460ff161515610a2257600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610a7d57600080fd5b60006008541415610ac2576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b565b600080600a60009054906101000a900460ff168015610af05750600a60019054906101000a900460ff16155b1515610afb57600080fd5b610b03610932565b151515610b0f57600080fd5b60035434811515610b1c57fe5b049150600082111515610b2b57fe5b60035482023403905060035481101515610b4157fe5b816008600082825401925050819055506003548202600960003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550610bce6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff163384611034565b7f97b1cd3307803802ff5e5152505de2a9e850e59c5e8d05b69c592abf2aa8ae8c33348484604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200184815260200183815260200182815260200194505050505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610c8957600080fd5b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610ce857600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600a60009054906101000a900460ff168015610d545750600a60019054906101000a900460ff16155b1515610d5f57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610dba57600080fd5b610dc2610932565b80610de35750610dd06105e1565b8015610de25750600754600654014210155b5b1515610dee57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60065481565b600a60009054906101000a900460ff168015610e575750600a60019054906101000a900460ff16155b1515610e6257600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610ebd57600080fd5b600a60019054906101000a900460ff16151515610ed957600080fd5b610ee16105e1565b151515610eed57600080fd5b600754600654014210151515610f0257600080fd5b6001600a60016101000a81548160ff021916908315150217905550565b60045481565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231836000604051602001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b151561101257600080fd5b6102c65a03f1151561102357600080fd5b505050604051805190509050919050565b8061103f843061117c565b1015151561104957fe5b600081111561117757600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd8484846000604051602001526040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b151561115357600080fd5b6102c65a03f1151561116457600080fd5b50505060405180519050151561117657fe5b5b505050565b6000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84846000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200192505050602060405180830381600087803b151561127757600080fd5b6102c65a03f1151561128857600080fd5b505050604051805190509050929150505600a165627a7a7230582031d7c9b9225ce274184c008ec434a43930693c3f34633f89e067d7c2a54cd2760029",
12 | }
13 |
14 | # Constants for Token
15 | SYMBOL = 'TEST'
16 | NAME = 'Test Token'
17 | DECIMALS = 0
18 | INITIAL_SUPPLY = 100
19 |
20 |
21 | # You can also create your own fixtures!
22 | @pytest.fixture
23 | def Token(cobra):
24 | # cobra is an alias for cobra
25 | args = [SYMBOL, NAME, DECIMALS, INITIAL_SUPPLY] # for convienence
26 | # cobra.contract is an alias for cobra.contracts
27 | return cobra.new(artifact_token).deploy(*args)
28 |
29 |
30 | def test_token(cobra, Token):
31 | # You can do all of these with the your own fixtures too!
32 |
33 | # Test Token.transfer()
34 | # cobra.accounts is an alias for cobra.accounts
35 | assert Token.balanceOf(cobra.accounts[0]) == INITIAL_SUPPLY
36 | assert Token.balanceOf(cobra.accounts[1]) == 0
37 | Token.transfer(cobra.accounts[1], 10) # Creates a log
38 | assert Token.balanceOf(cobra.accounts[0]) == INITIAL_SUPPLY - 10
39 | assert Token.balanceOf(cobra.accounts[1]) == 10
40 |
41 | # Create a Transfer log to check against
42 | expected_log = Token.Transfer(
43 | # Below is all the members of the event
44 | {'_from': cobra.accounts[0], '_to': cobra.accounts[1], '_value': 10}
45 | )
46 | # Test transfer's event against the expected value
47 | assert Token.logs[-1] == expected_log
48 |
49 | # You can also check individual fields
50 | Token.approve(cobra.accounts[2], 10)
51 | approval = Token.logs[-1]
52 | assert approval['_owner'] == cobra.accounts[0]
53 | assert approval['_spender'] == cobra.accounts[2]
54 | assert approval['_value'] == 10
55 |
56 |
57 | # Constants for ICO
58 | TOKEN_PRICE = 1000 # 100 wei/token
59 | HARDCAP = INITIAL_SUPPLY # Max tokens for sale
60 | SOFTCAP = 10 # Min tokens for successful sale
61 | DURATION = 100 # active blocks
62 |
63 |
64 | @pytest.fixture
65 | def ICO(cobra, Token):
66 | # If you need to link fixtures together, you can!
67 | args = [TOKEN_PRICE, HARDCAP, SOFTCAP, DURATION, Token.address]
68 | return cobra.new(artifact_ico).deploy(*args)
69 |
70 |
71 | def test_ico(cobra, Token, ICO):
72 | # NOTE: Token is not the same deployment as the one in test_token!
73 | assert Token.balanceOf(cobra.accounts[0]) == INITIAL_SUPPLY
74 |
75 | Token.approve(ICO.address, ICO.hardCap())
76 | ICO.startICO() # Let's get this party started!
77 |
78 | # You can create very powerful tests with this library
79 | tp = ICO.tokenPrice()
80 | sold = 0 # use this later
81 | while not ICO.hardCapReached():
82 | # Every round, buyer buys an amount of tokens between [1, softCap)
83 | buyer = random.choice(cobra.accounts[1:])
84 | amount = tp * min(random.randrange(1, ICO.softCap()), ICO.hardCap() - sold)
85 | ICO.buyToken(transact={'from': buyer, 'value': amount})
86 | print(buyer, "bought", ICO.sold() - sold, "tokens this round!")
87 | sold = ICO.sold() # Update for next round
88 |
--------------------------------------------------------------------------------
/pytest-cobra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meherett/pytest-solidity/ea6106269eb38e851bc2b05e34bd40133b66f97f/pytest-cobra.png
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | testpaths = tests/
3 | python_files = test_*.py
4 | addopts = -v --cov=pytest_cobra/
--------------------------------------------------------------------------------
/pytest_cobra/__init__.py:
--------------------------------------------------------------------------------
1 | from web3.providers.eth_tester import EthereumTesterProvider
2 | from eth_tester.exceptions import TransactionFailed
3 | from eth_utils import event_abi_to_log_topic
4 | from web3.utils.events import get_event_data
5 | from functools import partial as partial_fn
6 | from web3.contract import ImplicitContract
7 | from solc import link_code, compile_source
8 | from eth_tester import EthereumTester
9 | from collections import Mapping
10 | from os.path import basename
11 | from os.path import join
12 | from json import loads
13 | from web3 import Web3
14 | import pytest
15 | import json
16 | import yaml
17 | import os
18 |
19 |
20 | class CobraTester:
21 |
22 | def __init__(self, _web3: Web3, _ethereum_tester: EthereumTester, compiled_interfaces=None):
23 | if compiled_interfaces is None:
24 | compiled_interfaces = dict()
25 | self.ethereum_tester = _ethereum_tester
26 | self.web3 = _web3
27 |
28 | self.compiled_interfaces = compiled_interfaces
29 |
30 | def contract(self, name):
31 | for compiled_interface in self.compiled_interfaces.keys():
32 | contract = compiled_interface.split(":")
33 | if contract[0] == name:
34 | interface = self.compiled_interfaces.get(compiled_interface)
35 | return self.new(interface)
36 | else:
37 | continue
38 |
39 | def new(self, interface):
40 | if isinstance(interface['abi'], str):
41 | interface['abi'] = loads(interface['abi'])
42 | return CobraFactory(self.web3, interface)
43 |
44 | @property
45 | def accounts(self):
46 | return [CobraAccount(self.web3, a) for a in self.ethereum_tester.get_accounts()]
47 |
48 | @property
49 | def eth(self):
50 | # Return the w3 eth API
51 | return self.web3.eth
52 |
53 | @property
54 | def tx_fails(self):
55 | return CobraFailureHandler(self.ethereum_tester)
56 |
57 | def now(self):
58 | # Get this from the Ethereum block timestamp
59 | return self.web3.eth.getBlock('pending')['timestamp']
60 |
61 | def mine_blocks(self, number=1):
62 | self.ethereum_tester.mine_blocks(number)
63 |
64 |
65 | class CobraLog(Mapping):
66 | def __new__(cls, event, args):
67 | obj = super().__new__(cls)
68 | obj._event = event
69 | obj._args = args
70 | return obj
71 |
72 | def __eq__(self, other):
73 | if not isinstance(other, CobraLog):
74 | return False
75 | if self._event != other._event:
76 | return False
77 | return self._args == other._args
78 |
79 | def __iter__(self):
80 | return iter(self._args)
81 |
82 | def __len__(self):
83 | return len(self._args)
84 |
85 | def __getitem__(self, key):
86 | return self._args[key]
87 |
88 |
89 | class CobraInstance:
90 | """Deployed instance of a contract"""
91 |
92 | def __init__(self, _web3: Web3, address, interface):
93 | self.web3 = _web3
94 | self.__address = address
95 | self.__instance = ImplicitContract(self.web3.eth.contract(self.__address, **interface))
96 | # Register new filter to watch for logs from this instance's address
97 | self.__filter = self.web3.eth.filter({
98 | # Include events from the deployment stage
99 | 'fromBlock': self.web3.eth.blockNumber - 1,
100 | 'address': self.__address
101 | })
102 | self.__event_signatures = self.get_event_signatures(interface['abi'])
103 | self.__event_processors = self.get_event_processors(interface['abi'])
104 |
105 | def __getattr__(self, name):
106 | """Delegates to either specialized methods or instance ABI"""
107 | if name in dir(self):
108 | # Specialized testing methods
109 | return getattr(self, name)
110 | elif name in self._events:
111 | return self._gen_log(name)
112 | else:
113 | # Method call of contract instance
114 | return getattr(self.__instance, name)
115 |
116 | @property
117 | def _events(self):
118 | return self.__event_signatures.keys()
119 |
120 | def _gen_log(self, name):
121 | return lambda v: CobraLog(name, v)
122 |
123 | @property
124 | def address(self):
125 | """This contract's address"""
126 | return self.__address
127 |
128 | @property
129 | def balance(self):
130 | """Ether balance of this contract (in wei)"""
131 | return self.web3.eth.getBalance(self.__address)
132 |
133 | @property
134 | def codesize(self):
135 | """Codesize of this contract (in bytes)"""
136 | return len(self.web3.eth.getCode(self.__address)[2:]) / 2
137 |
138 | @property
139 | def hascode(self):
140 | """Check if this contract currently has code (usually indicating suicide)"""
141 | return self.codesize != 0
142 |
143 | def process_logs(self, logs):
144 | processed_logs = []
145 | for log in logs:
146 | log_signature = log['topics'][0]
147 | if log_signature in self.__event_processors.keys():
148 | p_log = self.__event_processors[log_signature](log)
149 | processed_logs.append(CobraLog(p_log['event'], p_log['args']))
150 | return processed_logs
151 |
152 | @property
153 | def logs(self):
154 | """Returns all the event logs ever added for this contract"""
155 | return self.process_logs(self.__filter.get_all_entries())
156 |
157 | def get_event_signatures(self, abi_list):
158 | signatures = dict()
159 | for abi in abi_list:
160 | if abi['type'] == 'event':
161 | signatures[abi['name']] = event_abi_to_log_topic(abi)
162 | return signatures
163 |
164 | def get_event_processors(self, abi_list):
165 | processors = dict()
166 | for abi in abi_list:
167 | if abi['type'] == 'event':
168 | processors[event_abi_to_log_topic(abi)] = partial_fn(get_event_data, abi)
169 | return processors
170 |
171 |
172 | class CobraFactory:
173 | """Factory (prototype) of a contract"""
174 |
175 | def __init__(self, _web3: Web3, interface):
176 | self.web3 = _web3
177 | self.interface = interface
178 | self.contract_factory = self.web3.eth.contract(**self.interface)
179 |
180 | def deploy(self, *args, **kwargs):
181 | """Deploy a new instance of this contract"""
182 | kwargs = self.clean_modifiers(kwargs)
183 | if 'transact' in kwargs.keys():
184 | kwargs['transaction'] = kwargs['transact']
185 | del kwargs['transact']
186 |
187 | tx_hash = self.contract_factory.constructor(*args).transact(**kwargs)
188 | address = self.web3.eth.getTransactionReceipt(tx_hash)['contractAddress']
189 | return CobraInstance(self.web3, address, self.interface)
190 |
191 | def __getattr__(self, name):
192 | return getattr(self.contract_factory, name)
193 |
194 | def clean_modifiers(self, modifiers):
195 | cleaned_modifiers = modifiers.copy()
196 | for name, modifier in modifiers.items():
197 | for key, value in modifier.items():
198 | if not isinstance(value, str) or not isinstance(value, int):
199 | cleaned_modifiers[name][key] = str(value)
200 | return cleaned_modifiers
201 |
202 |
203 | class CobraAccount(str):
204 | def __new__(cls, w3: Web3, address):
205 | obj = super().__new__(cls, address)
206 | obj._w3 = w3
207 | obj._address = address
208 | return obj
209 |
210 | # Send Ether
211 | def transfer(self, address, amount):
212 | self._w3.eth.sendTransaction({'to': address, 'from': self._address, 'value': amount})
213 |
214 | @property
215 | def balance(self):
216 | return self._w3.eth.getBalance(self._address)
217 |
218 |
219 | class CobraFailureHandler:
220 | def __init__(self, eth_tester):
221 | self.eth_tester = eth_tester
222 |
223 | def __enter__(self):
224 | self.snapshot_id = self.eth_tester.take_snapshot()
225 | return self.snapshot_id
226 |
227 | def __exit__(self, *args):
228 | assert len(args) > 0 and \
229 | args[0] is TransactionFailed, "Didn't revert transaction."
230 | self.eth_tester.revert_to_snapshot(self.snapshot_id)
231 | return True
232 |
233 |
234 | # Interfaces
235 | UNDERSCORE = "_"
236 | LINK_LENGTH = 36
237 |
238 |
239 | class CobraConfiguration:
240 |
241 | def __init__(self):
242 | pass
243 |
244 | # File reader
245 | def file_reader(self, file):
246 | try:
247 | with open(file, 'r') as read_file:
248 | return_file = read_file.read()
249 | read_file.close()
250 | return return_file
251 | except FileNotFoundError:
252 | with pytest.raises(FileNotFoundError, message="[Cobra] FileNotFound: %s" % file):
253 | pass
254 |
255 | # YAML file loader
256 | def yaml_loader(self, yaml_file):
257 | try:
258 | load_compile = yaml.load(yaml_file)
259 | return load_compile
260 | except yaml.scanner.ScannerError as scannerError:
261 | with pytest.raises(yaml.scanner.ScannerError, message="[Cobra] YAMLScannerError: %s" % scannerError):
262 | pass
263 |
264 | # JSON file loader
265 | def json_loader(self, json_file):
266 | try:
267 | loaded_json = json.loads(json_file)
268 | return loaded_json
269 | except json.decoder.JSONDecodeError as jsonDecodeError:
270 | with pytest.raises(json.decoder.JSONDecodeError, message="[Cobra] JSONDecodeError: %s" % jsonDecodeError):
271 | pass
272 |
273 | def config_test_yaml(self, test_yaml):
274 | yaml_test = []
275 | try:
276 | if test_yaml['artifact_path'] and test_yaml['contracts']:
277 | for contract in test_yaml['contracts']:
278 | try:
279 | if contract['contract']['artifact']:
280 | try:
281 | if contract['contract']['links']:
282 | yaml_test.append(dict(
283 | artifact_path=test_yaml['artifact_path'],
284 | artifact=contract['contract']['artifact'],
285 | links=contract['contract']['links']
286 | ))
287 | elif not contract['contract']['links']:
288 | yaml_test.append(dict(
289 | artifact_path=test_yaml['artifact_path'],
290 | artifact=contract['contract']['artifact'],
291 | links=None
292 | ))
293 | continue
294 | except KeyError:
295 | yaml_test.append(dict(
296 | artifact_path=test_yaml['artifact_path'],
297 | artifact=contract['contract']['artifact'],
298 | links=None
299 | ))
300 | except TypeError:
301 | with pytest.raises(FileNotFoundError,
302 | message="[Cobra] There is no artifact in contract. [.yaml]"):
303 | pass
304 | except TypeError:
305 | with pytest.raises(FileNotFoundError,
306 | message="[Cobra] Can't find artifact_path or contracts in test [.yaml]"):
307 | pass
308 | return yaml_test
309 |
310 | def config_test_json(self, test_json):
311 | json_test = []
312 | for key in test_json.keys():
313 | contract = dict()
314 | artifact = test_json.get(key)
315 | contract_names = key.split(":")
316 | contract.setdefault("contractName", contract_names[0])
317 | contract.setdefault("abi", artifact["abi"])
318 | contract.setdefault("bin", artifact["bin"])
319 | contract.setdefault("bin-runtime", artifact["bin-runtime"])
320 | contract_names.remove(contract_names[0])
321 | if not contract_names:
322 | contract_names = None
323 | contract.setdefault("links", contract_names)
324 | json_test.insert(0, contract)
325 | else:
326 | contract.setdefault("links", contract_names)
327 | json_test.append(contract)
328 | return json_test
329 |
330 | # This cobra_converter function works to increase more read able compiled contracts
331 | def cobra_converter(self, compiled_contracts):
332 | contracts = dict()
333 | for compiled_contract in compiled_contracts.keys():
334 | contract_interface = compiled_contracts.get(compiled_contract)
335 |
336 | contract_link = self.links_absolute_path(contract_interface)
337 | if contract_link:
338 | contract_and_link = compiled_contract.split(":")[1] + ":" + str(":".join(
339 | basename(link)[:-4] for link in contract_link))
340 | else:
341 | contract_and_link = compiled_contract.split(":")[1]
342 |
343 | links_from_file = self.links_from_file(contract_interface)
344 | if links_from_file is None:
345 | links_from_absolutes_file = self.links_from_absolutes_file(contract_interface)
346 | contracts[contract_and_link] = links_from_absolutes_file
347 | else:
348 | contracts[contract_and_link] = links_from_file
349 | return contracts
350 |
351 | # Links file path from in compiled contract interface
352 | def links_file_path(self, contract_interface):
353 | files = []
354 | children = contract_interface['ast']['children']
355 | for attributes in children:
356 | try:
357 | files.append(attributes['attributes']['file'])
358 | except KeyError:
359 | continue
360 | return files
361 |
362 | # Links absolute path from in compiled contract interface
363 | def links_absolute_path(self, interface):
364 | absolutes = []
365 | children = interface['ast']['children']
366 | for attributes in children:
367 | try:
368 | absolutes.append(attributes['attributes']['absolutePath'])
369 | except KeyError:
370 | continue
371 | return absolutes
372 |
373 | def links_from_file(self, contract_interface):
374 | links_file = self.links_file_path(contract_interface)
375 | for link_file in links_file:
376 |
377 | contract_name = basename(link_file)[:-4]
378 | contract_name_len = len(contract_name)
379 | link_file = link_file + ":" + contract_name
380 | link_file_path = link_file[:36]
381 |
382 | bytecode = contract_interface['bin']
383 | bytecode_runtime = contract_interface['bin-runtime']
384 | link_file_path_check = "__" + link_file_path + "__"
385 | link_file_path_check_two = "__" + link_file_path[2:] + "__"
386 |
387 | if link_file_path_check in contract_interface['bin'] and \
388 | link_file_path_check in contract_interface['bin-runtime']:
389 | underscore_in_links = LINK_LENGTH - len(link_file_path)
390 | link_file_name = contract_name + \
391 | (((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
392 |
393 | contract_interface['bin'] = bytecode.replace(link_file_path, link_file_name, 1)
394 | contract_interface['bin-runtime'] = bytecode_runtime.replace(link_file_path, link_file_name, 1)
395 | elif link_file_path_check_two in contract_interface['bin'] and \
396 | link_file_path_check_two in contract_interface['bin-runtime']:
397 | link_file_path = link_file_path[2:]
398 | underscore_in_links = LINK_LENGTH - len(link_file_path)
399 | link_file_name = contract_name + \
400 | (((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
401 |
402 | contract_interface['bin'] = bytecode.replace(link_file_path, link_file_name, 1)
403 | contract_interface['bin-runtime'] = bytecode_runtime.replace(link_file_path, link_file_name, 1)
404 | else:
405 | return None
406 | return contract_interface
407 |
408 | def links_from_absolutes_file(self, contract_interface):
409 | links_absolutes = self.links_absolute_path(contract_interface)
410 | contract_interface_bin = contract_interface['bin']
411 | contract_interface_bin_runtime = contract_interface['bin-runtime']
412 | for links_absolute in links_absolutes:
413 | contract_name = basename(links_absolute)[:-4]
414 | contract_name_len = len(contract_name)
415 | links_absolute = links_absolute + ":" + contract_name
416 | links_absolute_path = links_absolute[:36]
417 |
418 | split_bytecode = contract_interface_bin.split(links_absolute_path, 1)
419 | split_bytecode_runtime = contract_interface_bin_runtime.split(links_absolute_path, 1)
420 | contract_bytecode = []
421 | contract_bytecode_runtime = []
422 |
423 | for index, contract in enumerate(split_bytecode):
424 | if len(contract) > LINK_LENGTH and (index % 2) != 0:
425 | underscore_in_links = LINK_LENGTH - len(links_absolute_path)
426 | link_absolute_name = contract_name + (
427 | ((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
428 | contract_bytecode.append(link_absolute_name)
429 | contract_bytecode.append(contract)
430 | contract_interface_bin = "".join(contract_bytecode)
431 |
432 | for index, contract in enumerate(split_bytecode_runtime):
433 | if len(contract) > LINK_LENGTH and (index % 2) != 0:
434 | underscore_in_links = LINK_LENGTH - len(links_absolute_path)
435 | link_absolute_name = contract_name + (
436 | ((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
437 | contract_bytecode_runtime.append(link_absolute_name)
438 | contract_bytecode_runtime.append(contract)
439 | contract_interface_bin_runtime = "".join(contract_bytecode_runtime)
440 |
441 | contract_interface['bin'] = contract_interface_bin
442 | contract_interface['bin-runtime'] = contract_interface_bin_runtime
443 | return contract_interface
444 |
445 |
446 | class CobraInterfaces(CobraConfiguration):
447 |
448 | def __init__(self, _web3: Web3):
449 | super().__init__()
450 | self.web3 = _web3
451 | self.contracts = dict()
452 |
453 | def cobra_file(self, file_name, import_remappings=None, allow_paths=None):
454 | # Import remapping None to empty array
455 | if import_remappings is None:
456 | import_remappings = []
457 |
458 | # Allow path None to current working directory path
459 | if allow_paths is None:
460 | allow_paths = str()
461 |
462 | # Fetching solidity file extension
463 | if file_name.endswith(".sol"):
464 | _import_remappings = ["-"]
465 | _import_remappings.extend(import_remappings)
466 | compiled_json = compile_source(
467 | self.file_reader(file_name),
468 | import_remappings=_import_remappings,
469 | allow_paths=allow_paths)
470 | convert_json = self.cobra_converter(compiled_json)
471 | self.cobra_test_json(convert_json)
472 | # Fetching compiled json file extension
473 | elif file_name.endswith(".json"):
474 | read_json = self.file_reader(file_name)
475 | load_json = self.json_loader(read_json)
476 | convert_json = self.cobra_converter(load_json)
477 | self.cobra_test_json(convert_json)
478 | # Fetching yaml from cobra framework file extension
479 | elif file_name.endswith(".yaml"):
480 | read_yaml = self.file_reader(file_name)
481 | load_yaml = self.yaml_loader(read_yaml)
482 | self.cobra_test_yaml(load_yaml)
483 | else:
484 | with pytest.raises(FileNotFoundError,
485 | message="[ERROR] Can't find this type of extension ['.sol', '.json' or '.yaml']"):
486 | pass
487 | return self.contracts
488 |
489 | def cobra_test_json(self, load_json):
490 | configurations_json = self.config_test_json(load_json)
491 | for configuration_json in configurations_json:
492 | if configuration_json['links'] is None:
493 | self.test_with_out_link(configuration_json)
494 | else:
495 | self.test_with_link(configuration_json, configuration_json['links'])
496 | return self.contracts
497 |
498 | def cobra_test_yaml(self, load_yaml):
499 | try:
500 | load_yaml_test = load_yaml['test']
501 | configurations_yaml = self.config_test_yaml(load_yaml_test)
502 | for configuration_yaml in configurations_yaml:
503 | file_path_json = join(configuration_yaml['artifact_path'], configuration_yaml['artifact'])
504 | read_json = self.file_reader(file_path_json)
505 | load_json = self.yaml_loader(read_json)
506 | if configuration_yaml['links'] is None:
507 | self.test_with_out_link(load_json)
508 | else:
509 | self.test_with_link(load_json, configuration_yaml['links'])
510 | return self.contracts
511 | except TypeError:
512 | with pytest.raises(FileNotFoundError, message="[Cobra] Can't find test in cobra.yaml"):
513 | pass
514 |
515 | def get_links_address(self, links):
516 | contract_name_and_address = dict()
517 | for link in links:
518 | for contract in self.contracts.keys():
519 | contract = contract.split(":")
520 | if contract[0] == link[:-5]:
521 | contract_name_and_address.setdefault(link[:-5], contract[1])
522 | elif contract[0] == link:
523 | contract_name_and_address.setdefault(link, contract[1])
524 | else:
525 | continue
526 | return contract_name_and_address
527 |
528 | def test_with_link(self, artifact, links):
529 | unlinked_bytecode = artifact['bin']
530 | get_link_address = self.get_links_address(links)
531 | linked_bytecode = link_code(unlinked_bytecode, get_link_address)
532 |
533 | contract_factory = self.web3.eth.contract(abi=artifact['abi'], bytecode=linked_bytecode)
534 | tx_hash = contract_factory.constructor().transact()
535 |
536 | address = self.web3.eth.getTransactionReceipt(tx_hash)['contractAddress']
537 | contract = {"abi": artifact['abi'], "bytecode": linked_bytecode}
538 | contract_name_and_address = artifact['contractName'] + ":" + str(address)
539 | self.contracts.setdefault(contract_name_and_address, contract)
540 |
541 | def test_with_out_link(self, artifact):
542 | contract_factory = self.web3.eth.contract(abi=artifact['abi'], bytecode=artifact['bin'])
543 | tx_hash = contract_factory.constructor().transact()
544 |
545 | address = self.web3.eth.getTransactionReceipt(tx_hash)['contractAddress']
546 | contract = {"abi": artifact['abi'], "bytecode": artifact['bin']}
547 | contract_name_and_address = artifact['contractName'] + ":" + str(address)
548 | self.contracts.setdefault(contract_name_and_address, contract)
549 |
550 |
551 | # Set Provider
552 | ethereum_tester = EthereumTester()
553 | web3 = Web3(EthereumTesterProvider(ethereum_tester))
554 |
555 |
556 | # Return zero gas price
557 | def zero_gas_price_strategy(web3, transaction_params=None):
558 | return 0
559 |
560 |
561 | # Set Gas Price to 0
562 | web3.eth.setGasPriceStrategy(zero_gas_price_strategy)
563 |
564 |
565 | def pytest_addoption(parser):
566 | group = parser.getgroup('Cobra', 'Ethereum Smart-Contract testing support')
567 | group.addoption('--cobra', action='store', default=None, metavar='path',
568 | help='pytest --cobra Contract.json')
569 | group.addoption('--import_remappings', action='store', default=None, metavar='path',
570 | help='pytest --cobra Contract.sol --import_remappings ["=", "-", "=/home"]')
571 | group.addoption('--allow_paths', action='store', default=None, metavar='path',
572 | help='pytest --cobra Contract.sol --allow_paths ["/home"]')
573 |
574 |
575 | @pytest.fixture(scope='session')
576 | def cobra_file(pytestconfig):
577 | cobra_file = dict()
578 | if pytestconfig.option.cobra:
579 | cobra_interface = CobraInterfaces(web3)
580 | cobra_file = cobra_interface.cobra_file(pytestconfig.option.cobra,
581 | pytestconfig.option.import_remappings,
582 | pytestconfig.option.allow_paths)
583 | return cobra_file
584 |
585 |
586 | @pytest.fixture
587 | def cobra(cobra_file):
588 | return CobraTester(web3, ethereum_tester, cobra_file)
589 |
--------------------------------------------------------------------------------
/pytest_eth/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 | from web3.types import Wei
5 | from web3.providers.eth_tester import EthereumTesterProvider
6 | from eth_tester import EthereumTester, MockBackend
7 |
8 | import pytest
9 |
10 | from .interfaces import Interfaces
11 | from .tester import Tester
12 |
13 |
14 | # Init Ethereum tester
15 | ethereum_tester = EthereumTester(
16 | backend=MockBackend()
17 | )
18 |
19 | # Set Provider
20 | web3 = Web3(
21 | provider=EthereumTesterProvider(
22 | ethereum_tester=ethereum_tester
23 | )
24 | )
25 |
26 |
27 | # Return zero gas price
28 | def zero_gas_price_strategy(_: Web3, transaction_params=None) -> Wei:
29 | return Wei(0)
30 |
31 |
32 | # Set gas price to 0
33 | web3.eth.setGasPriceStrategy(
34 | gas_price_strategy=zero_gas_price_strategy
35 | )
36 |
37 |
38 | def pytest_addoption(parser):
39 | group = parser.getgroup("eth", "Ethereum Smart-Contract testing support")
40 | group.addoption("--eth", action="store", default=None, metavar="path",
41 | help="pytest --eth Contract.json")
42 | group.addoption("--import_remappings", action="store", default=None, metavar="path",
43 | help="pytest --eth Contract.sol --import_remappings ['=', '-', '=/home']")
44 | group.addoption("--allow_paths", action="store", default=None, metavar="path",
45 | help="pytest --eth Contract.sol --allow_paths ['/home']")
46 |
47 |
48 | @pytest.fixture(scope="session")
49 | def eth_file(pytestconfig):
50 | eth_file = dict()
51 | if pytestconfig.option.eth:
52 | interface = Interfaces(web3)
53 | eth_file = interface.eth_file(
54 | pytestconfig.option.eth,
55 | pytestconfig.option.import_remappings,
56 | pytestconfig.option.allow_paths
57 | )
58 | return eth_file
59 |
60 |
61 | @pytest.fixture
62 | def eth(eth_file):
63 | return Tester(web3, ethereum_tester, eth_file)
64 |
--------------------------------------------------------------------------------
/pytest_eth/account.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 | from eth_typing import Address
5 |
6 |
7 | class Account(str):
8 |
9 | def __new__(cls, web3: Web3, address: Address):
10 | obj = super().__new__(cls, address)
11 | obj.web3 = web3
12 | obj.address = address
13 | return obj
14 |
15 | def transfer(self, address: Address, amount: int):
16 | self.web3.eth.sendTransaction(transaction={
17 | "to": address, "from": self.address, "value": amount
18 | })
19 |
20 | @property
21 | def balance(self):
22 | return self.web3.eth.getBalance(account=self.address)
23 |
--------------------------------------------------------------------------------
/pytest_eth/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from os.path import basename
4 | import pytest
5 | import json
6 | import yaml
7 |
8 | # Interfaces
9 | UNDERSCORE = "_"
10 | LINK_LENGTH = 36
11 |
12 |
13 | class Configuration:
14 |
15 | def __init__(self):
16 | pass
17 |
18 | # File reader
19 | @staticmethod
20 | def file_reader(file):
21 | try:
22 | with open(file, 'r') as read_file:
23 | return_file = read_file.read()
24 | read_file.close()
25 | return return_file
26 | except FileNotFoundError:
27 | with pytest.raises(FileNotFoundError, message="[Cobra] FileNotFound: %s" % file):
28 | pass
29 |
30 | # YAML file loader
31 | @staticmethod
32 | def yaml_loader(yaml_file):
33 | try:
34 | load_compile = yaml.load(yaml_file)
35 | return load_compile
36 | except yaml.scanner.ScannerError as scannerError:
37 | with pytest.raises(yaml.scanner.ScannerError, message="[Cobra] YAMLScannerError: %s" % scannerError):
38 | pass
39 |
40 | # JSON file loader
41 | @staticmethod
42 | def json_loader(json_file):
43 | try:
44 | loaded_json = json.loads(json_file)
45 | return loaded_json
46 | except json.decoder.JSONDecodeError as jsonDecodeError:
47 | with pytest.raises(json.decoder.JSONDecodeError, message="[Cobra] JSONDecodeError: %s" % jsonDecodeError):
48 | pass
49 |
50 | @staticmethod
51 | def config_test_yaml(test_yaml):
52 | yaml_test = []
53 | try:
54 | if test_yaml['artifact_path'] and test_yaml['contracts']:
55 | for contract in test_yaml['contracts']:
56 | try:
57 | if contract['contract']['artifact']:
58 | try:
59 | if contract['contract']['links']:
60 | yaml_test.append(dict(
61 | artifact_path=test_yaml['artifact_path'],
62 | artifact=contract['contract']['artifact'],
63 | links=contract['contract']['links']
64 | ))
65 | elif not contract['contract']['links']:
66 | yaml_test.append(dict(
67 | artifact_path=test_yaml['artifact_path'],
68 | artifact=contract['contract']['artifact'],
69 | links=None
70 | ))
71 | continue
72 | except KeyError:
73 | yaml_test.append(dict(
74 | artifact_path=test_yaml['artifact_path'],
75 | artifact=contract['contract']['artifact'],
76 | links=None
77 | ))
78 | except TypeError:
79 | with pytest.raises(FileNotFoundError,
80 | message="[Cobra] There is no artifact in contract. [.yaml]"):
81 | pass
82 | except TypeError:
83 | with pytest.raises(FileNotFoundError,
84 | message="[Cobra] Can't find artifact_path or contracts in test [.yaml]"):
85 | pass
86 | return yaml_test
87 |
88 | @staticmethod
89 | def config_test_json(test_json):
90 | json_test = []
91 | for key in test_json.keys():
92 | contract = dict()
93 | artifact = test_json.get(key)
94 | contract_names = key.split(":")
95 | contract.setdefault("contractName", contract_names[0])
96 | contract.setdefault("abi", artifact["abi"])
97 | contract.setdefault("bin", artifact["bin"])
98 | contract.setdefault("bin-runtime", artifact["bin-runtime"])
99 | contract_names.remove(contract_names[0])
100 | if not contract_names:
101 | contract_names = None
102 | contract.setdefault("links", contract_names)
103 | json_test.insert(0, contract)
104 | else:
105 | contract.setdefault("links", contract_names)
106 | json_test.append(contract)
107 | return json_test
108 |
109 | # This cobra_converter function works to increase more read able compiled contracts
110 | def cobra_converter(self, compiled_contracts):
111 | contracts = dict()
112 | for compiled_contract in compiled_contracts.keys():
113 | contract_interface = compiled_contracts.get(compiled_contract)
114 |
115 | contract_link = self.links_absolute_path(contract_interface)
116 | if contract_link:
117 | contract_and_link = compiled_contract.split(":")[1] + ":" + str(":".join(
118 | basename(link)[:-4] for link in contract_link))
119 | else:
120 | contract_and_link = compiled_contract.split(":")[1]
121 |
122 | links_from_file = self.links_from_file(contract_interface)
123 | if links_from_file is None:
124 | links_from_absolutes_file = self.links_from_absolutes_file(contract_interface)
125 | contracts[contract_and_link] = links_from_absolutes_file
126 | else:
127 | contracts[contract_and_link] = links_from_file
128 | return contracts
129 |
130 | # Links file path from in compiled contract interface
131 | @staticmethod
132 | def links_file_path(contract_interface):
133 | files = []
134 | children = contract_interface['ast']['children']
135 | for attributes in children:
136 | try:
137 | files.append(attributes['attributes']['file'])
138 | except KeyError:
139 | continue
140 | return files
141 |
142 | # Links absolute path from in compiled contract interface
143 | @staticmethod
144 | def links_absolute_path(interface):
145 | absolutes = []
146 | children = interface['ast']['children']
147 | for attributes in children:
148 | try:
149 | absolutes.append(attributes['attributes']['absolutePath'])
150 | except KeyError:
151 | continue
152 | return absolutes
153 |
154 | def links_from_file(self, contract_interface):
155 | links_file = self.links_file_path(contract_interface)
156 | for link_file in links_file:
157 |
158 | contract_name = basename(link_file)[:-4]
159 | contract_name_len = len(contract_name)
160 | link_file = link_file + ":" + contract_name
161 | link_file_path = link_file[:36]
162 |
163 | bytecode = contract_interface['bin']
164 | bytecode_runtime = contract_interface['bin-runtime']
165 | link_file_path_check = "__" + link_file_path + "__"
166 | link_file_path_check_two = "__" + link_file_path[2:] + "__"
167 |
168 | if link_file_path_check in contract_interface['bin'] and \
169 | link_file_path_check in contract_interface['bin-runtime']:
170 | underscore_in_links = LINK_LENGTH - len(link_file_path)
171 | link_file_name = contract_name + \
172 | (((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
173 |
174 | contract_interface['bin'] = bytecode.replace(link_file_path, link_file_name, 1)
175 | contract_interface['bin-runtime'] = bytecode_runtime.replace(link_file_path, link_file_name, 1)
176 | elif link_file_path_check_two in contract_interface['bin'] and \
177 | link_file_path_check_two in contract_interface['bin-runtime']:
178 | link_file_path = link_file_path[2:]
179 | underscore_in_links = LINK_LENGTH - len(link_file_path)
180 | link_file_name = contract_name + \
181 | (((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
182 |
183 | contract_interface['bin'] = bytecode.replace(link_file_path, link_file_name, 1)
184 | contract_interface['bin-runtime'] = bytecode_runtime.replace(link_file_path, link_file_name, 1)
185 | else:
186 | return None
187 | return contract_interface
188 |
189 | def links_from_absolutes_file(self, contract_interface):
190 | links_absolutes = self.links_absolute_path(contract_interface)
191 | contract_interface_bin = contract_interface['bin']
192 | contract_interface_bin_runtime = contract_interface['bin-runtime']
193 | for links_absolute in links_absolutes:
194 | contract_name = basename(links_absolute)[:-4]
195 | contract_name_len = len(contract_name)
196 | links_absolute = links_absolute + ":" + contract_name
197 | links_absolute_path = links_absolute[:36]
198 |
199 | split_bytecode = contract_interface_bin.split(links_absolute_path, 1)
200 | split_bytecode_runtime = contract_interface_bin_runtime.split(links_absolute_path, 1)
201 | contract_bytecode = []
202 | contract_bytecode_runtime = []
203 |
204 | for index, contract in enumerate(split_bytecode):
205 | if len(contract) > LINK_LENGTH and (index % 2) != 0:
206 | underscore_in_links = LINK_LENGTH - len(links_absolute_path)
207 | link_absolute_name = contract_name + (
208 | ((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
209 | contract_bytecode.append(link_absolute_name)
210 | contract_bytecode.append(contract)
211 | contract_interface_bin = "".join(contract_bytecode)
212 |
213 | for index, contract in enumerate(split_bytecode_runtime):
214 | if len(contract) > LINK_LENGTH and (index % 2) != 0:
215 | underscore_in_links = LINK_LENGTH - len(links_absolute_path)
216 | link_absolute_name = contract_name + (
217 | ((LINK_LENGTH - contract_name_len) - underscore_in_links) * UNDERSCORE)
218 | contract_bytecode_runtime.append(link_absolute_name)
219 | contract_bytecode_runtime.append(contract)
220 | contract_interface_bin_runtime = "".join(contract_bytecode_runtime)
221 |
222 | contract_interface['bin'] = contract_interface_bin
223 | contract_interface['bin-runtime'] = contract_interface_bin_runtime
224 | return contract_interface
225 |
--------------------------------------------------------------------------------
/pytest_eth/factory.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 |
5 | from .instance import Instance
6 |
7 |
8 | class Factory:
9 | """Factory (prototype) of a contract"""
10 |
11 | def __init__(self, _web3: Web3, interface):
12 | self.web3 = _web3
13 | self.interface = interface
14 | self.contract_factory = self.web3.eth.contract(**self.interface)
15 |
16 | def deploy(self, *args, **kwargs):
17 | """Deploy a new instance of this contract"""
18 | kwargs = self.clean_modifiers(kwargs)
19 | if "transact" in kwargs.keys():
20 | kwargs["transaction"] = kwargs["transact"]
21 | del kwargs["transact"]
22 |
23 | tx_hash = self.contract_factory.constructor(*args).transact(**kwargs)
24 | address = self.web3.eth.getTransactionReceipt(tx_hash)["contractAddress"]
25 | return Instance(self.web3, address, self.interface)
26 |
27 | def __getattr__(self, name):
28 | return getattr(self.contract_factory, name)
29 |
30 | @staticmethod
31 | def clean_modifiers(modifiers):
32 | cleaned_modifiers = modifiers.copy()
33 | for name, modifier in modifiers.items():
34 | for key, value in modifier.items():
35 | if not isinstance(value, str) or not isinstance(value, int):
36 | cleaned_modifiers[name][key] = str(value)
37 | return cleaned_modifiers
38 |
--------------------------------------------------------------------------------
/pytest_eth/handler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from eth_tester import EthereumTester
4 | from eth_tester.exceptions import TransactionFailed
5 |
6 |
7 | class FailureHandler:
8 |
9 | def __init__(self, ethereum_tester: EthereumTester):
10 | self.ethereum_tester = ethereum_tester
11 |
12 | def __enter__(self):
13 | self.snapshot = self.ethereum_tester.take_snapshot()
14 | return self.snapshot
15 |
16 | def __exit__(self, *args):
17 | assert len(args) > 0 and \
18 | args[0] is TransactionFailed, "Didn't revert transaction."
19 | self.ethereum_tester.revert_to_snapshot(self.snapshot)
20 | return True
21 |
--------------------------------------------------------------------------------
/pytest_eth/instance.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 | from web3._utils.events import get_event_data
5 | from web3.contract import ImplicitContract
6 | from eth_utils import event_abi_to_log_topic
7 | from functools import partial as partial_fn
8 | from eth_typing import Address
9 |
10 | from .log import Log
11 |
12 |
13 | class Instance:
14 | """Deployed instance of a contract"""
15 |
16 | def __init__(self, _web3: Web3, address: Address, interface):
17 | self.web3 = _web3
18 | self.address = address
19 | self.interface = ImplicitContract(
20 | classic_contract=self.web3.eth.contract(self.address, **interface)
21 | )
22 | # Register new filter to watch for logs from this instance"s address
23 | self.__filter = self.web3.eth.filter({
24 | # Include events from the deployment stage
25 | "fromBlock": self.web3.eth.blockNumber - 1,
26 | "address": self.address
27 | })
28 | self.event_signatures = self.get_event_signatures(interface["abi"])
29 | self.event_processors = self.get_event_processors(interface["abi"])
30 |
31 | def __getattr__(self, name):
32 | """Delegates to either specialized methods or instance ABI"""
33 | if name in dir(self):
34 | # Specialized testing methods
35 | return getattr(self, name)
36 | elif name in self.event_signatures.keys():
37 | return lambda args: Log(name, args)
38 | else:
39 | # Method call of contract instance
40 | return getattr(self.interface, name)
41 |
42 | @property
43 | def balance(self):
44 | """Ether balance of this contract (in wei)"""
45 | return self.web3.eth.getBalance(self.address)
46 |
47 | @property
48 | def code_size(self):
49 | """Code size of this contract (in bytes)"""
50 | return len(self.web3.eth.getCode(self.address)[2:]) / 2
51 |
52 | @property
53 | def has_code(self):
54 | """Check if this contract currently has code (usually indicating suicide)"""
55 | return self.code_size != 0
56 |
57 | def process_logs(self, logs):
58 | processed_logs = []
59 | for log in logs:
60 | log_signature = log["topics"][0]
61 | if log_signature in self.event_processors.keys():
62 | p_log = self.event_processors[log_signature](log)
63 | processed_logs.append(Log(p_log["event"], p_log["args"]))
64 | return processed_logs
65 |
66 | @property
67 | def logs(self):
68 | """Returns all the event logs ever added for this contract"""
69 | return self.process_logs(self.__filter.get_all_entries())
70 |
71 | @staticmethod
72 | def get_event_signatures(abi_list):
73 | signatures = dict()
74 | for abi in abi_list:
75 | if abi["type"] == "event":
76 | signatures[abi["name"]] = event_abi_to_log_topic(abi)
77 | return signatures
78 |
79 | @staticmethod
80 | def get_event_processors(abi_list):
81 | processors = dict()
82 | for abi in abi_list:
83 | if abi["type"] == "event":
84 | processors[event_abi_to_log_topic(abi)] = partial_fn(get_event_data, abi)
85 | return processors
86 |
--------------------------------------------------------------------------------
/pytest_eth/interfaces.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 | from solcx import link_code, compile_source
5 | from os.path import join
6 | import pytest
7 |
8 | from .config import Configuration
9 |
10 |
11 | class Interfaces(Configuration):
12 |
13 | def __init__(self, _web3: Web3):
14 | super().__init__()
15 | self.web3 = _web3
16 | self.contracts = dict()
17 |
18 | def eth_file(self, file_name, import_remappings=None, allow_paths=None):
19 | # Import remapping None to empty array
20 | if import_remappings is None:
21 | import_remappings = []
22 |
23 | # Allow path None to current working directory path
24 | if allow_paths is None:
25 | allow_paths = str()
26 |
27 | # Fetching solidity file extension
28 | if file_name.endswith(".sol"):
29 | _import_remappings = ["-"]
30 | _import_remappings.extend(import_remappings)
31 | compiled_json = compile_source(
32 | self.file_reader(file_name),
33 | import_remappings=_import_remappings,
34 | allow_paths=allow_paths
35 | )
36 | convert_json = self.cobra_converter(compiled_json)
37 | self.cobra_test_json(convert_json)
38 | # Fetching compiled json file extension
39 | elif file_name.endswith(".json"):
40 | read_json = self.file_reader(file_name)
41 | load_json = self.json_loader(read_json)
42 | convert_json = self.cobra_converter(load_json)
43 | self.cobra_test_json(convert_json)
44 | # Fetching yaml from cobra framework file extension
45 | elif file_name.endswith(".yaml"):
46 | read_yaml = self.file_reader(file_name)
47 | load_yaml = self.yaml_loader(read_yaml)
48 | self.cobra_test_yaml(load_yaml)
49 | else:
50 | with pytest.raises(FileNotFoundError,
51 | message="[ERROR] Can't find this type of extension ['.sol', '.json' or '.yaml']"):
52 | pass
53 | return self.contracts
54 |
55 | def cobra_test_json(self, load_json):
56 | configurations_json = self.config_test_json(load_json)
57 | for configuration_json in configurations_json:
58 | if configuration_json['links'] is None:
59 | self.test_with_out_link(configuration_json)
60 | else:
61 | self.test_with_link(configuration_json, configuration_json['links'])
62 | return self.contracts
63 |
64 | def cobra_test_yaml(self, load_yaml):
65 | try:
66 | load_yaml_test = load_yaml['test']
67 | configurations_yaml = self.config_test_yaml(load_yaml_test)
68 | for configuration_yaml in configurations_yaml:
69 | file_path_json = join(configuration_yaml['artifact_path'], configuration_yaml['artifact'])
70 | read_json = self.file_reader(file_path_json)
71 | load_json = self.yaml_loader(read_json)
72 | if configuration_yaml['links'] is None:
73 | self.test_with_out_link(load_json)
74 | else:
75 | self.test_with_link(load_json, configuration_yaml['links'])
76 | return self.contracts
77 | except TypeError:
78 | with pytest.raises(FileNotFoundError, message="[Cobra] Can't find test in cobra.yaml"):
79 | pass
80 |
81 | def get_links_address(self, links):
82 | contract_name_and_address = dict()
83 | for link in links:
84 | for contract in self.contracts.keys():
85 | contract = contract.split(":")
86 | if contract[0] == link[:-5]:
87 | contract_name_and_address.setdefault(link[:-5], contract[1])
88 | elif contract[0] == link:
89 | contract_name_and_address.setdefault(link, contract[1])
90 | else:
91 | continue
92 | return contract_name_and_address
93 |
94 | def test_with_link(self, artifact, links):
95 | unlinked_bytecode = artifact['bin']
96 | get_link_address = self.get_links_address(links)
97 | linked_bytecode = link_code(unlinked_bytecode, get_link_address)
98 |
99 | contract_factory = self.web3.eth.contract(abi=artifact['abi'], bytecode=linked_bytecode)
100 | # tx_hash = contract_factory.constructor().transact()
101 | tx_hash = contract_factory.constructor().transact(transaction={
102 | "gas": 12_500_000, "gasPrice": self.web3.eth.gasPrice
103 | })
104 |
105 | address = self.web3.eth.getTransactionReceipt(tx_hash)['contractAddress']
106 | contract = {"abi": artifact['abi'], "bytecode": linked_bytecode}
107 | contract_name_and_address = artifact['contractName'] + ":" + str(address)
108 | self.contracts.setdefault(contract_name_and_address, contract)
109 |
110 | def test_with_out_link(self, artifact):
111 | contract_factory = self.web3.eth.contract(abi=artifact['abi'], bytecode=artifact['bin'])
112 | # tx_hash = contract_factory.constructor().transact()
113 | tx_hash = contract_factory.constructor().transact(transaction={
114 | "gas": 12_500_000, "gasPrice": self.web3.eth.gasPrice
115 | })
116 |
117 | address = self.web3.eth.getTransactionReceipt(tx_hash)['contractAddress']
118 | print(address)
119 | contract = {"abi": artifact['abi'], "bytecode": artifact['bin']}
120 | contract_name_and_address = artifact['contractName'] + ":" + str(address)
121 | self.contracts.setdefault(contract_name_and_address, contract)
122 |
--------------------------------------------------------------------------------
/pytest_eth/log.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from collections import Mapping
4 |
5 |
6 | class Log(Mapping):
7 |
8 | def __new__(cls, event, args):
9 | obj = super().__new__(cls)
10 | obj.event = event
11 | obj.args = args
12 | return obj
13 |
14 | def __eq__(self, other):
15 | if not isinstance(other, Log):
16 | return False
17 | if self.event != other.event:
18 | return False
19 | return self.args == other.args
20 |
21 | def __iter__(self):
22 | return iter(self.args)
23 |
24 | def __len__(self):
25 | return len(self.args)
26 |
27 | def __getitem__(self, key):
28 | return self.args[key]
29 |
--------------------------------------------------------------------------------
/pytest_eth/tester.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from web3 import Web3
4 | from eth_tester import EthereumTester
5 | from json import loads
6 |
7 | from .account import Account
8 | from .handler import FailureHandler
9 | from .factory import Factory
10 |
11 |
12 | class Tester:
13 |
14 | def __init__(self, _web3: Web3, _ethereum_tester: EthereumTester, compiled_interfaces=None):
15 | if compiled_interfaces is None:
16 | compiled_interfaces = dict()
17 | self.ethereum_tester = _ethereum_tester
18 | self.web3 = _web3
19 |
20 | self.compiled_interfaces = compiled_interfaces
21 |
22 | def contract(self, name):
23 | for compiled_interface in self.compiled_interfaces.keys():
24 | contract = compiled_interface.split(":")
25 | if contract[0] == name:
26 | interface = self.compiled_interfaces.get(compiled_interface)
27 | return self.new(interface)
28 | else:
29 | continue
30 |
31 | def new(self, interface):
32 | if isinstance(interface["abi"], str):
33 | interface["abi"] = loads(interface["abi"])
34 | return Factory(self.web3, interface)
35 |
36 | @property
37 | def accounts(self):
38 | return [Account(self.web3, account)
39 | for account in self.ethereum_tester.get_accounts()]
40 |
41 | @property
42 | def eth(self):
43 | # Return the web3 eth API
44 | return self.web3.eth
45 |
46 | @property
47 | def tx_fails(self):
48 | return FailureHandler(self.ethereum_tester)
49 |
50 | def now(self):
51 | # Get this from the Ethereum block timestamp
52 | return self.web3.eth.getBlock("pending")["timestamp"]
53 |
54 | def mine_blocks(self, number=1):
55 | self.ethereum_tester.mine_blocks(number)
56 |
--------------------------------------------------------------------------------
/pytest_eth/utils.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meherett/pytest-solidity/ea6106269eb38e851bc2b05e34bd40133b66f97f/pytest_eth/utils.py
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pytest>=3.7.1,<4.0.0
2 | eth-keyfile==0.5.1
3 | eth-tester==0.1.0b33
4 | py-evm==0.2.0a33
5 | eth-abi==1.2.2
6 | py-ecc==1.4.3
7 | py-solc>=3.2.0,<4.0.0
8 | web3>=4.4.1,<5.0.0
9 | pytest-cov==2.7.1
10 | PyYAML>=3.13,<4.0
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | import os
3 |
4 | base_dir = os.path.dirname(__file__)
5 |
6 | with open(os.path.join(base_dir, "README.md"), "r") as readme:
7 | long_description = readme.read()
8 |
9 | setup(
10 | name="pytest-cobra",
11 | version='1.0.4',
12 | description='PyTest plugin for testing Smart Contracts for Ethereum blockchain.',
13 | long_description=long_description,
14 | long_description_content_type="text/markdown",
15 | license='MIT',
16 | author='Meheret Tesfaye',
17 | author_email='meherett@zoho.com',
18 | url='https://github.com/cobraframework/pytest-cobra',
19 | python_requires='>=3.6,<3.8',
20 | packages=['pytest_cobra'],
21 | install_requires=[
22 | 'pytest>=3.7.1,<4.0.0',
23 | 'eth-keyfile==0.5.1',
24 | 'eth-tester==0.1.0b33',
25 | 'py-evm==0.2.0a33',
26 | 'eth-abi==1.2.2',
27 | 'py-ecc==1.4.3',
28 | 'py-solc>=3.2.0,<4.0.0',
29 | 'web3>=4.4.1,<5.0.0',
30 | 'PyYAML>=3.13,<6.0'
31 | ],
32 | entry_points={
33 | 'pytest11': [
34 | 'name_of_plugin = pytest_cobra',
35 | ]
36 | },
37 | classifiers=[
38 | "Development Status :: 3 - Alpha",
39 | "License :: OSI Approved :: MIT License",
40 | 'Programming Language :: Python :: 3.6',
41 | 'Programming Language :: Python :: 3.7',
42 | "Framework :: Pytest",
43 | ],
44 | )
45 |
--------------------------------------------------------------------------------
/tests/accounts/test_account.py:
--------------------------------------------------------------------------------
1 | def test_init(cobra):
2 | for acct in cobra.accounts:
3 | assert acct.balance > 0
4 | assert acct not in filter(lambda accounts: accounts != acct, cobra.accounts)
5 |
6 |
7 | def test_transfer(cobra):
8 | # Ether transfers work
9 | starting_balance = cobra.accounts[0].balance
10 | assert cobra.accounts[1].balance == starting_balance
11 |
12 | cobra.accounts[0].transfer(cobra.accounts[1], 100)
13 | assert cobra.accounts[0].balance == starting_balance - 100
14 | assert cobra.accounts[1].balance == starting_balance + 100
15 |
16 | cobra.accounts[1].transfer(cobra.accounts[0], 100)
17 | assert cobra.accounts[0].balance == cobra.accounts[1].balance == starting_balance
18 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from web3.providers.eth_tester import EthereumTesterProvider
2 | from pytest_cobra import CobraTester
3 | from eth_tester import EthereumTester
4 | from web3 import Web3
5 | import pytest
6 |
7 |
8 | # Set Provider
9 | ethereum_tester = EthereumTester()
10 | web3 = Web3(EthereumTesterProvider(ethereum_tester))
11 |
12 |
13 | # Return zero gas price
14 | def zero_gas_price_strategy(web3, transaction_params=None):
15 | return 0
16 |
17 |
18 | # Set Gas Price to 0
19 | web3.eth.setGasPriceStrategy(zero_gas_price_strategy)
20 |
21 |
22 | @pytest.fixture
23 | def cobra():
24 | return CobraTester(web3, ethereum_tester)
25 |
--------------------------------------------------------------------------------
/tests/tester/test_tester.py:
--------------------------------------------------------------------------------
1 | from eth_tester.exceptions import TransactionFailed
2 |
3 |
4 | def test_mining(cobra):
5 | """
6 | Mining blocks works
7 | """
8 | starting_block = cobra.eth.blockNumber
9 | # Mining a block mines exactly one block
10 | cobra.mine_blocks()
11 | assert cobra.eth.blockNumber == starting_block + 1
12 | # Mining N blocks mines exactly N blocks
13 | cobra.mine_blocks(10)
14 | assert cobra.eth.blockNumber == starting_block + 11
15 |
16 |
17 | def test_time(cobra):
18 | """
19 | cobra.now() gets the current time
20 | This should match the mined block time
21 | when the block is mined
22 | """
23 | # Check pending block time is returned
24 | # NOTE: pending block time is at creation
25 | start_time = cobra.now()
26 | start_block = cobra.eth.getBlock('pending')['number']
27 | assert start_time == cobra.now() # Returns the same number if no mining
28 | # Mine a block, timestamps should match
29 | cobra.mine_blocks()
30 | assert cobra.eth.getBlock('latest')['timestamp'] == start_time
31 | assert cobra.now() > start_time
32 | # Mine another block, timestamps should still match
33 | cobra.mine_blocks()
34 | assert cobra.eth.getBlock(start_block)['timestamp'] == start_time
35 |
36 |
37 | def test_exception(cobra):
38 | # Can call as many transaction failures in a row as you need
39 | failures = 0
40 | with cobra.tx_fails:
41 | raise TransactionFailed
42 | failures += 1
43 | with cobra.tx_fails:
44 | raise TransactionFailed
45 | failures += 1
46 | with cobra.tx_fails:
47 | raise TransactionFailed
48 | failures += 1
49 | assert failures == 3
50 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py36,py37
3 |
4 | [travis]
5 | python =
6 | 3.6: py36
7 | 3.7: py37
8 |
9 | [testenv:py36]
10 | deps = -r{toxinidir}/requirements.txt
11 | commands = python -m pytest
12 |
13 | [testenv:py37]
14 | deps = -r{toxinidir}/requirements.txt
15 | commands = python -m pytest
16 |
--------------------------------------------------------------------------------