├── .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 ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-cobra.svg?style=for-the-badge) 6 | 7 | *PyTest plugin for testing Smart Contracts for Ethereum blockchain.* 8 | 9 | [![Build Status](https://travis-ci.com/cobraframework/pytest-cobra.svg?branch=master)](https://travis-ci.com/cobraframework/pytest-cobra) 10 | ![PyPI Version](https://img.shields.io/pypi/v/pytest-cobra.svg?color=blue) 11 | ![GitHub License](https://img.shields.io/github/license/cobraframework/pytest-cobra.svg) 12 | ![Github Date](https://img.shields.io/github/release-date/cobraframework/pytest-cobra.svg?color=black) 13 | ![PyPI Wheel](https://img.shields.io/pypi/wheel/pytest-cobra.svg?color=%2308490e) 14 | [![Donate with Ethereum](https://en.cryptobadges.io/badge/micro/0xD32AAEDF28A848e21040B6F643861A9077F83106)](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 | --------------------------------------------------------------------------------