├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── LICENSE-APACHE2.md ├── LICENSE-MIT.md ├── Pipfile ├── Pipfile.lock ├── README.md ├── contracts ├── CallbackSwap.sol ├── CallbackSwaps.sol ├── CloneFactory.sol ├── Migrations.sol ├── NoFun.sol ├── StatelessSwap.sol ├── StatelessSwaps.sol └── test │ ├── DummyERC20.sol │ ├── DummyERC721.sol │ └── DummySwap.sol ├── migrations ├── 1_initial_migration.js ├── 2_deploy_callback.js ├── 3_deploy_stateless.js └── networkInfo.js ├── package-lock.json ├── package.json ├── scripts ├── __init__.py ├── eth_auction_setup.py ├── interface_wrapper.py ├── merkle.py ├── partial_tx.py ├── run_tests.sh ├── utils.py └── utxo_setup.py ├── test ├── ERC20Swap.test.js ├── ERC721Swap.test.js ├── EthSwap.test.js ├── StatelessSwap.test.js ├── constants.js └── utils.js └── truffle-config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | }, 17 | rules: { 18 | "comma-dangle": 'off' 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | scripts/nft_auction_setup.py 4 | scripts/eth_auction_constants.py 5 | __pycache__/ 6 | .mypy_cache/ 7 | 8 | # coverage 9 | scTopics 10 | coverage/ 11 | coverageEnv/ 12 | coverage.json 13 | .env 14 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copyPackages: ['eth-gas-reporter'], 3 | skipFiles: [ 4 | 'bitcoin-spv/', 5 | 'test/', 6 | 'interfaces/' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | contracts/test/DummyERC20.sol 4 | contracts/test/DummyERC721.sol 5 | contracts/test/DummySwap.sol 6 | contracts/interfaces/IERC721.sol 7 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /LICENSE-APACHE2.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Indefinite Integral Incorporated 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Indefinite Integral Incorporated 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [scripts] 7 | test = "./scripts/run_tests.sh" 8 | 9 | [dev-packages] 10 | mypy = "*" 11 | "flake8" = "*" 12 | 13 | [packages] 14 | riemann-tx = "==2.1.0" 15 | ecdsa = "*" 16 | pycryptodomex = "*" 17 | ipython = "*" 18 | connectrum = "*" 19 | riemann-ether = "==6.0.5" 20 | 21 | [requires] 22 | python_version = "3.7" 23 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f93e6e2fc5eec110ef8732982cdcea8d5cf28c11b3737a35c48a41c3f4c7933b" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "backcall": { 20 | "hashes": [ 21 | "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", 22 | "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" 23 | ], 24 | "version": "==0.1.0" 25 | }, 26 | "connectrum": { 27 | "hashes": [ 28 | "sha256:40a146bae45782ff6f5b52d3c5058bc62535d9b67b8445148b34e5676d35b9e2" 29 | ], 30 | "index": "pypi", 31 | "version": "==0.7.4" 32 | }, 33 | "cytoolz": { 34 | "hashes": [ 35 | "sha256:82f5bba81d73a5a6b06f2a3553ff9003d865952fcb32e1df192378dd944d8a5c" 36 | ], 37 | "markers": "implementation_name == 'cpython'", 38 | "version": "==0.10.1" 39 | }, 40 | "decorator": { 41 | "hashes": [ 42 | "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", 43 | "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" 44 | ], 45 | "version": "==4.4.2" 46 | }, 47 | "ecdsa": { 48 | "hashes": [ 49 | "sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061", 50 | "sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277" 51 | ], 52 | "index": "pypi", 53 | "version": "==0.15" 54 | }, 55 | "eth-hash": { 56 | "hashes": [ 57 | "sha256:1b9cb34dd3cd99c85c2bd6a1420ceae39a2eee8bf080efd264bcda8be3edecc8", 58 | "sha256:499dc02d098f69856d1a6dd005529c16174157d4fb2a9fe20c41f69e39f8f176" 59 | ], 60 | "version": "==0.2.0" 61 | }, 62 | "eth-keys": { 63 | "hashes": [ 64 | "sha256:6b788eec609982bcfdd0f9e38803627e88592474f592b3817740976e9e9ea15e", 65 | "sha256:7df4a4a57b7c4d09860a459452cab356bb5aa166d379479104a63bec0d35d072" 66 | ], 67 | "version": "==0.3.1" 68 | }, 69 | "eth-typing": { 70 | "hashes": [ 71 | "sha256:2f3e1f891226148898b219bd94674a9af06c2d75d8cdd8c6722227b472cbd4d4", 72 | "sha256:cf9e5e9fb62cfeb1027823328569315166851c65c5774604d801b6b926ff65bc" 73 | ], 74 | "version": "==2.2.1" 75 | }, 76 | "eth-utils": { 77 | "hashes": [ 78 | "sha256:8358318685e7a7666b148b07df3c4d409435b424dce18501e79920aa52bcaba7", 79 | "sha256:f398c649859cda5ef7c4ee2753468038d93be7d864de7631c06c3e73a7060649" 80 | ], 81 | "version": "==1.8.4" 82 | }, 83 | "ipython": { 84 | "hashes": [ 85 | "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a", 86 | "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333" 87 | ], 88 | "index": "pypi", 89 | "version": "==7.13.0" 90 | }, 91 | "ipython-genutils": { 92 | "hashes": [ 93 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 94 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 95 | ], 96 | "version": "==0.2.0" 97 | }, 98 | "jedi": { 99 | "hashes": [ 100 | "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2", 101 | "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5" 102 | ], 103 | "version": "==0.16.0" 104 | }, 105 | "parso": { 106 | "hashes": [ 107 | "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", 108 | "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" 109 | ], 110 | "version": "==0.6.2" 111 | }, 112 | "pexpect": { 113 | "hashes": [ 114 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 115 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 116 | ], 117 | "markers": "sys_platform != 'win32'", 118 | "version": "==4.8.0" 119 | }, 120 | "pickleshare": { 121 | "hashes": [ 122 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 123 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 124 | ], 125 | "version": "==0.7.5" 126 | }, 127 | "prompt-toolkit": { 128 | "hashes": [ 129 | "sha256:859e1b205b6cf6a51fa57fa34202e45365cf58f8338f0ee9f4e84a4165b37d5b", 130 | "sha256:ebe6b1b08c888b84c50d7f93dee21a09af39860144ff6130aadbd61ae8d29783" 131 | ], 132 | "version": "==3.0.4" 133 | }, 134 | "ptyprocess": { 135 | "hashes": [ 136 | "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", 137 | "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" 138 | ], 139 | "version": "==0.6.0" 140 | }, 141 | "pycryptodomex": { 142 | "hashes": [ 143 | "sha256:1537d2d15b604b303aef56e7f440895a1c81adbee786b91f1f06eddc34da5314", 144 | "sha256:1d20ab8369b7558168fc014a0745c678613f9f486dae468cca2d68145196b8a4", 145 | "sha256:1ecc9db7409db67765eb008e558879d298406642d33ade43a6488224d23e8081", 146 | "sha256:37033976f72af829fe15f7fe5fe1dbed308cc43a98d9dd9d2a0a76de8ca5ee78", 147 | "sha256:3c3dd9d4c9c1e279d3945ae422895c901f98987333acc132dc094faf52afec35", 148 | "sha256:3c9b3fba037ea52c626060c5a87ee6de7e86c99e8a7c6ee07302539985d2bd64", 149 | "sha256:45ee555fc5e28c119a46d44ce373f5237e54a35c61b750fb3a94446b09855dbc", 150 | "sha256:4c93038ac011b36512cb0bf2ee3e2aec774e8bc81021d015917c89fe02bb0ee5", 151 | "sha256:50163324834edd0c9ce3e4512ded3e221c969086e10fdd5d3fdcaadac5e24a78", 152 | "sha256:59b0ea9cda5490f924771456912a225d8d9e678891f9f986661af718534719b2", 153 | "sha256:5cf306a17cccc327a33cdc3845629fa13f4573a4ec620ed607c79cf6785f2e27", 154 | "sha256:5fff8da399af16a1855f58771223acbbdac720b9969cd03fc5013d2e9a7bd9a4", 155 | "sha256:68650ce5b9f7152b8283302a4617269f821695a612692640dd247bd12ab21c0b", 156 | "sha256:6b3a9a562688996f760b5077714c3ab8b62ca56061b6e9ab7906841e43e19f91", 157 | "sha256:7e938ed51a59e29431ea86fab60423ada2757728db0f78952329fa02a789bd31", 158 | "sha256:87aa70daad6f039e814790a06422a3189311198b674b62f13933a2bdcb6b1bcc", 159 | "sha256:99be3a1df2b2b9f731ebe1c264a2c07c465e71cee68e35e1640b645b5213a755", 160 | "sha256:a3f2908666e6f74b8c4893f86dd02e16170f50e4a78ae7f3468b6208d54bc205", 161 | "sha256:ae3d44a639fd11dbdeca47e35e94febb1ee8bc15daf26673331add37146e0b85", 162 | "sha256:afb4c2fa3c6f492fd9a8b38d76e13f32d429b8e5e1e00238309391b5591cde0d", 163 | "sha256:b1515ce3a8a2c3fa537d137c5ca5f8b7a902044d04e07d7c3aa26c3e026120fb", 164 | "sha256:bf391b377413a197000b43ef2b74359974d8927d329a897c9f5ba7b63dca7b9c", 165 | "sha256:c436919117c23355740c669f89720673578b9aa4569bbfe105f6c10101fc1966", 166 | "sha256:d2c3c280975638e2a2c2fd9cb36ab111980219757fa163a2755594b9448e4138", 167 | "sha256:e585d530764c459cbd5d460aed0288807bb881f376ca9a20e653645217895961", 168 | "sha256:e76e6638ead4a7d93262a24218f0ff3ff74de6b6c823b7e19dccb31b6a481978", 169 | "sha256:ebfc2f885cafda076c31ae30fa0dd81e7e919ec34059a88d3018ed66e83fcce3", 170 | "sha256:f5797a39933a3d41526da60856735e6684b2b71a8ca99d5f79555ca121be2f4b", 171 | "sha256:f7e5fc5e124200b19a14be33fb0099e956e6ebb5e25d287b0829ef0a78ed76c7", 172 | "sha256:fb350e31e55211fec8ddc89fc0256f3b9bc3b44b68a8bde1cf44b3b4e80c0e42" 173 | ], 174 | "index": "pypi", 175 | "version": "==3.9.7" 176 | }, 177 | "pygments": { 178 | "hashes": [ 179 | "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", 180 | "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" 181 | ], 182 | "version": "==2.6.1" 183 | }, 184 | "riemann-ether": { 185 | "hashes": [ 186 | "sha256:4b40264b3e53c88a66939e18f082845d4ce931860008d69e43939b30ebd7b07d", 187 | "sha256:bccf83a246bc000f41eccfcb4cfd4750196dafc8395275017440e00572ea0f2f" 188 | ], 189 | "index": "pypi", 190 | "version": "==6.0.5" 191 | }, 192 | "riemann-tx": { 193 | "hashes": [ 194 | "sha256:0974d25f94e10f56d67a8ca663c406e3062bb5938f14e8b396c10dca07cc0dac", 195 | "sha256:58de8f169a9b5a48b2a16f0216d783b02653ebf7b1c0e1f3ccc7f3a7f2be13cb" 196 | ], 197 | "index": "pypi", 198 | "version": "==2.1.0" 199 | }, 200 | "six": { 201 | "hashes": [ 202 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 203 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 204 | ], 205 | "version": "==1.14.0" 206 | }, 207 | "toolz": { 208 | "hashes": [ 209 | "sha256:08fdd5ef7c96480ad11c12d472de21acd32359996f69a5259299b540feba4560" 210 | ], 211 | "version": "==0.10.0" 212 | }, 213 | "traitlets": { 214 | "hashes": [ 215 | "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", 216 | "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7" 217 | ], 218 | "version": "==4.3.3" 219 | }, 220 | "wcwidth": { 221 | "hashes": [ 222 | "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", 223 | "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" 224 | ], 225 | "version": "==0.1.8" 226 | }, 227 | "websockets": { 228 | "hashes": [ 229 | "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", 230 | "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", 231 | "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", 232 | "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", 233 | "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", 234 | "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", 235 | "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", 236 | "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", 237 | "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", 238 | "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", 239 | "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", 240 | "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", 241 | "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", 242 | "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", 243 | "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", 244 | "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", 245 | "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", 246 | "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", 247 | "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", 248 | "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", 249 | "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", 250 | "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" 251 | ], 252 | "version": "==8.1" 253 | } 254 | }, 255 | "develop": { 256 | "entrypoints": { 257 | "hashes": [ 258 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 259 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 260 | ], 261 | "version": "==0.3" 262 | }, 263 | "flake8": { 264 | "hashes": [ 265 | "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", 266 | "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" 267 | ], 268 | "index": "pypi", 269 | "version": "==3.7.9" 270 | }, 271 | "mccabe": { 272 | "hashes": [ 273 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 274 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 275 | ], 276 | "version": "==0.6.1" 277 | }, 278 | "mypy": { 279 | "hashes": [ 280 | "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2", 281 | "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1", 282 | "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164", 283 | "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761", 284 | "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce", 285 | "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27", 286 | "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754", 287 | "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae", 288 | "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9", 289 | "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600", 290 | "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65", 291 | "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8", 292 | "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913", 293 | "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3" 294 | ], 295 | "index": "pypi", 296 | "version": "==0.770" 297 | }, 298 | "mypy-extensions": { 299 | "hashes": [ 300 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 301 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 302 | ], 303 | "version": "==0.4.3" 304 | }, 305 | "pycodestyle": { 306 | "hashes": [ 307 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 308 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 309 | ], 310 | "version": "==2.5.0" 311 | }, 312 | "pyflakes": { 313 | "hashes": [ 314 | "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", 315 | "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" 316 | ], 317 | "version": "==2.1.1" 318 | }, 319 | "typed-ast": { 320 | "hashes": [ 321 | "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", 322 | "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", 323 | "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", 324 | "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", 325 | "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", 326 | "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", 327 | "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", 328 | "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", 329 | "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", 330 | "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", 331 | "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", 332 | "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", 333 | "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", 334 | "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", 335 | "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", 336 | "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", 337 | "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", 338 | "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", 339 | "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", 340 | "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", 341 | "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" 342 | ], 343 | "version": "==1.4.1" 344 | }, 345 | "typing-extensions": { 346 | "hashes": [ 347 | "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", 348 | "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", 349 | "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" 350 | ], 351 | "version": "==3.7.4.1" 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Integral Swaps 2 | 3 | This repo contains examples of two SPV-based cross-chain trades, as well as 4 | some helpful Python utilites for interacting with them. **This is 5 | unaudited code** and we have not provided usage instructions. This code is 6 | presented without license. It is a complex cryptoeconomic system. If you're 7 | interested in using or deploying this code, please reach out to us directly via 8 | [our website](https://summa.one). 9 | 10 | **Stateless Swaps** use 11 | [Stateless SPV](https://medium.com/summa-technology/summa-auction-bitcoin-technical-7344096498f2) 12 | to establish Bitcoin payments. 13 | 14 | **Callback Swaps** use a full Bitcoin Relay with 15 | [SPV proof callbacks](https://github.com/summa-tx/relays/blob/master/solidity/contracts/OnDemandSPV.sol). 16 | These are the preferred method. 17 | 18 | Each have been abstracted to work with multiple asset types, including 19 | `NoFun`, a wrapper intended to manage the complexities of revocable 20 | centrally managed tokens. 21 | 22 | ### Building and testing contracts 23 | ``` 24 | npm i 25 | truffle compile 26 | truffle test 27 | ``` 28 | 29 | ### Linting 30 | 31 | ``` 32 | npm run lint 33 | ``` 34 | 35 | ### Python Setup 36 | 37 | - Install `pyenv`: [link](https://https://github.com/pyenv/pyenv-installer) 38 | - Install `pipenv`: 39 | [link](https://pipenv-fork.readthedocs.io/en/latest/install.html#installing-pipenv) 40 | 41 | Then run: the following to set up the virtual environment: 42 | ``` 43 | $ pyenv install 3.7.0 44 | $ pipenv install 45 | ``` 46 | 47 | Then interact with the scripts via interpreter 48 | 49 | ``` 50 | $ pipenv run ipython 51 | ``` 52 | -------------------------------------------------------------------------------- /contracts/CallbackSwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import {BytesLib} from "@summa-tx/bitcoin-spv-sol/contracts/BytesLib.sol"; 4 | import {BTCUtils} from "@summa-tx/bitcoin-spv-sol/contracts/BTCUtils.sol"; 5 | import {ISPVConsumer, ISPVRequestManager} from "@summa-tx/relay-sol/contracts/Interfaces.sol"; 6 | 7 | import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | 9 | 10 | interface ICallbackSwap { 11 | 12 | event ListingActive( 13 | bytes32 indexed _listingID, 14 | address indexed _seller, 15 | address indexed _asset, 16 | uint256 _value, 17 | bytes _partialTx 18 | ); 19 | 20 | event ListingClosed( 21 | bytes32 indexed _listingID, 22 | address _seller, 23 | address indexed _bidder, 24 | address indexed _asset, 25 | uint256 _value 26 | ); 27 | 28 | function open( 29 | bytes calldata _partialTx, 30 | address _asset, 31 | uint256 _value 32 | ) external payable returns (bytes32); 33 | } 34 | 35 | contract CallbackSwap is ICallbackSwap, ISPVConsumer { 36 | using BytesLib for bytes; 37 | using BTCUtils for bytes; 38 | using SafeMath for uint256; 39 | 40 | enum ListingStates { NONE, ACTIVE, CLOSED } 41 | 42 | struct Listing { 43 | ListingStates state; 44 | uint256 value; // Asset amount or 721 ID 45 | address asset; // Asset info 46 | address seller; // Seller address 47 | address wrapper; // The new NoFun (if applicable) 48 | 49 | // Filled Later 50 | address bidder; // Accepted bidder address 51 | bytes32 txid; // Accepted tx hash 52 | } 53 | 54 | address payable public developer; 55 | ISPVRequestManager internal proofProvider; 56 | mapping(bytes32 => Listing) public listings; 57 | 58 | constructor (address _developer, address _proofProvider) public { 59 | developer = address(uint160(_developer)); 60 | proofProvider = ISPVRequestManager(_proofProvider); 61 | } 62 | 63 | // IMPLEMENT FOR SPECIFIC ASSET TYPES 64 | function ensureFunding(Listing storage _listing) internal; 65 | function distribute(Listing storage _listing) internal; 66 | 67 | function open( 68 | bytes calldata _partialTx, 69 | address _asset, 70 | uint256 _value 71 | ) external payable returns (bytes32) { 72 | // Listing identifier is keccak256 of Seller's partial transaction outpoint 73 | bytes memory _outpoint = _partialTx.slice(7, 36); 74 | bytes32 _listingID = keccak256(_outpoint); 75 | 76 | // Require unique listing identifier 77 | require(listings[_listingID].state == ListingStates.NONE, "Listing exists."); 78 | 79 | // Add to listings mapping 80 | listings[_listingID].state = ListingStates.ACTIVE; 81 | listings[_listingID].value = _value; 82 | if (_asset != address(0)) { 83 | listings[_listingID].asset = _asset; 84 | } 85 | listings[_listingID].seller = msg.sender; 86 | 87 | ensureFunding(listings[_listingID]); 88 | 89 | // Register a new request for the outpoint 90 | proofProvider.request( 91 | _outpoint, // spends 92 | hex"", // pays 93 | 0, // paysValue 94 | address(this), // consumer 95 | 8, // numConfs 96 | 0 // notBefore 97 | ); 98 | 99 | // Emit ListingActive event 100 | emit ListingActive( 101 | _listingID, 102 | msg.sender, 103 | _asset, 104 | _value, 105 | _partialTx); 106 | 107 | return _listingID; 108 | } 109 | 110 | function spv( 111 | bytes32, 112 | bytes calldata _vin, 113 | bytes calldata _vout, 114 | uint256, 115 | uint8, 116 | uint8 117 | ) external { 118 | require(msg.sender == address(proofProvider), "CallbackSwap/spv - Not the SPV provider"); 119 | 120 | bytes32 _listingID = keccak256(_vin.slice(1, 36)); 121 | 122 | Listing storage _listing = listings[_listingID]; 123 | require(_listing.state == ListingStates.ACTIVE, "CallbackSwap/spv - Listing has closed or does not exist."); 124 | 125 | address _bidder = _extractBidder(_vout); 126 | if (_bidder == address(0)) { 127 | _bidder = _listing.seller; 128 | } 129 | 130 | // Update listing state 131 | _listing.bidder = _bidder; 132 | _listing.state = ListingStates.CLOSED; 133 | 134 | distribute(_listing); 135 | 136 | // Emit ListingClosed event 137 | emit ListingClosed( 138 | _listingID, 139 | _listing.seller, 140 | _listing.bidder, 141 | _listing.asset, 142 | _listing.value 143 | ); 144 | } 145 | 146 | /// @notice Extracts the bidder address from the bid tx 147 | /// @dev Returns 0 if the op_return is weird or there's only 1 output 148 | /// @param _vout The length-prefixed transaction output vector 149 | /// @return The 20 byte bidder address from the op_return, or address(0) 150 | function _extractBidder(bytes memory _vout) internal pure returns (address _bidder) { 151 | _bidder = address(0); 152 | if (uint8(_vout[0]) > 1) { 153 | bytes memory _data = _vout.extractOutputAtIndex(1).extractOpReturnData(); 154 | _bidder = _data.length >= 20 ? _data.toAddress(0) : address(0); 155 | } 156 | } 157 | 158 | /// @notice Calculates the developer's fee 159 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 160 | /// @param _value The amount of value to split between bidder and developer 161 | /// @return The fee share and the bidder's share 162 | function _allocate(uint256 _value) internal pure returns (uint256, uint256) { 163 | // developer share 164 | uint256 _feeShare = _value.div(400); 165 | // Bidder share 166 | uint256 _bidderShare = _value.sub(_feeShare); 167 | return (_feeShare, _bidderShare); 168 | } 169 | 170 | /// @notice Calculates the developer's fee 171 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 172 | /// @param _value The amount of value to split between bidder and developer 173 | /// @return The fee share and the bidder's share 174 | function allocate(uint256 _value) external pure returns (uint256, uint256) { 175 | return _allocate(_value); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /contracts/CallbackSwaps.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import {NoFun} from "./NoFun.sol"; 4 | import {Factory} from "./CloneFactory.sol"; 5 | import {CallbackSwap} from "./CallbackSwap.sol"; 6 | 7 | import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 8 | import {ERC721} from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; 9 | import {SafeERC20} from "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 10 | 11 | 12 | contract CallbackSwapEth is CallbackSwap { 13 | 14 | constructor ( 15 | address _developer, 16 | address _proofProvider 17 | ) public CallbackSwap(_developer, _proofProvider) {} 18 | 19 | /// @notice Ensures that ether is paid in 20 | /// @dev User must pass in address(0) and the amount of ether 21 | /// @param _listing The listing object to in msgthat we expect to be funded 22 | function ensureFunding (Listing storage _listing) internal { 23 | // Require Seller to fund tx 24 | require(msg.value > 0, "No asset received. Listing must be funded on initialization."); 25 | require(_listing.asset == address(0), "asset must be zero address for ether listings."); 26 | require(_listing.value == msg.value, "value must equal msg.value"); 27 | } 28 | 29 | /// @notice Transfers ETH to the bidder and developer 30 | /// @dev Calls allocate to calculate shares 31 | /// @param _listing A pointer to the listing 32 | function distribute(Listing storage _listing) internal { 33 | if (_listing.bidder == _listing.seller) { 34 | // No fee for cancellation 35 | address(uint160(_listing.seller)).transfer(_listing.value); 36 | } else { 37 | // Calculate fee and bidder shares 38 | uint256 _feeShare; 39 | uint256 _bidderShare; 40 | (_feeShare, _bidderShare) = _allocate(_listing.value); 41 | 42 | // Transfer fee 43 | address(developer).transfer(_feeShare); 44 | 45 | // Transfer eth to selected bidder 46 | address(uint160(_listing.bidder)).transfer(_bidderShare); 47 | } 48 | } 49 | } 50 | 51 | 52 | contract CallbackSwap20 is CallbackSwap { 53 | 54 | using SafeERC20 for ERC20; 55 | 56 | constructor ( 57 | address _developer, 58 | address _proofProvider 59 | ) public CallbackSwap(_developer, _proofProvider) {} 60 | 61 | /// @notice Ensures that the tokens are transferred 62 | /// @dev Calls transferFrom on the erc20 contract, and checks that no ether is being burnt 63 | /// @param _listing The listing that we expect to be funded 64 | function ensureFunding (Listing storage _listing) internal { 65 | require(msg.value == 0, "Do not burn ether here please"); 66 | require(_listing.value > 0, "_value must be greater than 0"); 67 | ERC20(_listing.asset).safeTransferFrom(msg.sender, address(this), _listing.value); 68 | } 69 | 70 | /// @notice Transfers tokens to the bidder and developer 71 | /// @dev Calls allocate to calculate shares 72 | /// @param _listing A pointer to the listing 73 | function distribute(Listing storage _listing) internal { 74 | if (_listing.bidder == _listing.seller) { 75 | // No fee for cancellation 76 | ERC20(_listing.asset).safeTransfer(_listing.seller, _listing.value); 77 | } else { 78 | // Calculate fee and bidder shares 79 | uint256 _feeShare; 80 | uint256 _bidderShare; 81 | (_feeShare, _bidderShare) = _allocate(_listing.value); 82 | 83 | // send developer fee 84 | ERC20(_listing.asset).safeTransfer(developer, _feeShare); 85 | // send bidder proceeds 86 | ERC20(_listing.asset).safeTransfer(_listing.bidder, _bidderShare); 87 | } 88 | } 89 | } 90 | 91 | 92 | contract CallbackSwap721 is CallbackSwap { 93 | 94 | constructor ( 95 | address _developer, 96 | address _proofProvider 97 | ) public CallbackSwap(_developer, _proofProvider) {} 98 | 99 | /// @notice Ensures that the NFT is transferred 100 | /// @dev Calls transferFrom on the erc721 contract, and checks that no ether is being burnt 101 | /// @param _listing The listing that we expect to be funded 102 | function ensureFunding (Listing storage _listing) internal { 103 | require(msg.value == 0, "Do not burn ether here please"); 104 | ERC721(_listing.asset).transferFrom(msg.sender, address(this), _listing.value); 105 | } 106 | 107 | /// @notice Transfers the NFT to the bidder 108 | /// @dev Calls transferFrom on the erc721 contract 109 | /// @param _listing A pointer to the listing 110 | function distribute(Listing storage _listing) internal { 111 | // Transfer tokens to bidder 112 | ERC721(_listing.asset).transferFrom( 113 | address(this), _listing.bidder, _listing.value); 114 | } 115 | } 116 | 117 | contract CallbackSwapNoFun is CallbackSwap, Factory { 118 | 119 | using SafeERC20 for ERC20; 120 | 121 | address noFun; // The NoFun implementation for cloning 122 | 123 | constructor ( 124 | address _developer, 125 | address _proofProvider, 126 | address _noFun 127 | ) public CallbackSwap(_developer, _proofProvider) { 128 | noFun = _noFun; 129 | } 130 | 131 | /// @notice Ensures that the Tokens are transferred to a new wrapper 132 | /// @dev Calls transferFrom on the erc20 contract, 133 | /// @param _listing The listing that we expect to be funded 134 | function ensureFunding (Listing storage _listing) internal { 135 | _listing.wrapper = createClone(noFun); 136 | NoFun(_listing.wrapper).init(developer, _listing.asset); 137 | ERC20(_listing.asset).safeTransferFrom(msg.sender, _listing.wrapper, _listing.value); 138 | } 139 | 140 | /// @notice Transfers the NoFun wrapper to the bidder 141 | /// @dev Calls transfer on the NoFun contract 142 | /// @param _listing A pointer to the listing 143 | function distribute(Listing storage _listing) internal { 144 | NoFun(_listing.wrapper).transfer(_listing.bidder); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /contracts/CloneFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2018 Murray Software, LLC. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | /* solium-disable */ 29 | 30 | contract CloneFactory { 31 | 32 | function createClone(address target) internal returns (address result) { 33 | bytes20 targetBytes = bytes20(target); 34 | assembly { 35 | let clone := mload(0x40) 36 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 37 | mstore(add(clone, 0x14), targetBytes) 38 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 39 | result := create(0, clone, 0x37) 40 | } 41 | } 42 | 43 | function isClone(address target, address query) internal view returns (bool result) { 44 | bytes20 targetBytes = bytes20(target); 45 | assembly { 46 | let clone := mload(0x40) 47 | mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000) 48 | mstore(add(clone, 0xa), targetBytes) 49 | mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 50 | 51 | let other := add(clone, 0x40) 52 | extcodecopy(query, other, 0, 0x2d) 53 | result := and( 54 | eq(mload(clone), mload(other)), 55 | eq(mload(add(clone, 0xd)), mload(add(other, 0xd))) 56 | ) 57 | } 58 | } 59 | } 60 | 61 | contract Factory is CloneFactory { 62 | function make(address target) external returns (address result) { 63 | return CloneFactory.createClone(target); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/NoFun.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | import {SafeERC20} from "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | 9 | contract NoFun { 10 | using SafeMath for uint256; 11 | using SafeERC20 for ERC20; 12 | 13 | address developer; 14 | address owner; 15 | uint256 value; 16 | ERC20 asset; 17 | 18 | function init(address _developer, address _asset) public { 19 | require(developer == address(0), "NoFun/init - already init"); 20 | require(_asset != address(0), "NoFun/init - no 0 asset"); 21 | developer = _developer; 22 | asset = ERC20(_asset); 23 | } 24 | 25 | function withdrawERC20(address _recipient, address _asset) public { 26 | require(msg.sender == owner, "NoFun/withdrawERC20 - only owner"); 27 | uint256 _feeShare; 28 | uint256 _ownerShare; 29 | 30 | uint256 _balance = ERC20(_asset).balanceOf(address(this)); 31 | (_feeShare, _ownerShare) = _allocate(_balance); 32 | 33 | ERC20(_asset).safeTransfer(_recipient, _ownerShare); 34 | ERC20(_asset).safeTransfer(developer, _feeShare); 35 | } 36 | 37 | function withdrawEth(address _recipient) public { 38 | require(msg.sender == owner, "NoFun/withdrawEth - only owner"); 39 | uint256 _feeShare; 40 | uint256 _ownerShare; 41 | 42 | uint256 _balance = address(this).balance; 43 | (_feeShare, _ownerShare) = _allocate(_balance); 44 | 45 | address(uint160(_recipient)).transfer(_ownerShare); 46 | address(uint160(developer)).transfer(_feeShare); 47 | } 48 | 49 | function shutdown() public { 50 | require(msg.sender == owner, "NoFun/shutdown - only owner"); 51 | uint256 _feeShare; 52 | uint256 _ownerShare; 53 | 54 | uint256 _balance = address(this).balance; 55 | (_feeShare, _ownerShare) = _allocate(_balance); 56 | 57 | address(uint160(developer)).transfer(_feeShare); 58 | selfdestruct(address(uint160(owner))); 59 | } 60 | 61 | function transfer(address _newOwner) public returns (bool) { 62 | require(msg.sender == owner, "NoFun/transfer - only owner"); 63 | owner = _newOwner; 64 | return true; 65 | } 66 | 67 | /// @notice Calculates the developer's fee 68 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 69 | /// @param _value The amount of value to split between bidder and developer 70 | /// @return The fee share and the bidder's share 71 | function _allocate(uint256 _value) internal pure returns (uint256, uint256) { 72 | // developer share 73 | uint256 _feeShare = _value.div(400); 74 | // Bidder share 75 | uint256 _bidderShare = _value.sub(_feeShare); 76 | return (_feeShare, _bidderShare); 77 | } 78 | 79 | /// @notice Calculates the developer's fee 80 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 81 | /// @param _value The amount of value to split between bidder and developer 82 | /// @return The fee share and the bidder's share 83 | function allocate(uint256 _value) external pure returns (uint256, uint256) { 84 | return _allocate(_value); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/StatelessSwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import {BytesLib} from "@summa-tx/bitcoin-spv-sol/contracts/BytesLib.sol"; 4 | import {BTCUtils} from "@summa-tx/bitcoin-spv-sol/contracts/BTCUtils.sol"; 5 | import {ValidateSPV} from "@summa-tx/bitcoin-spv-sol/contracts/ValidateSPV.sol"; 6 | 7 | import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | 9 | interface IStatelessSwap { 10 | 11 | event ListingActive( 12 | bytes32 indexed _listingID, 13 | address indexed _seller, 14 | address indexed _asset, 15 | uint256 _value, 16 | bytes _partialTx, 17 | uint256 _reqDiff 18 | ); 19 | 20 | event ListingClosed( 21 | bytes32 indexed _listingID, 22 | address _seller, 23 | address indexed _bidder, 24 | address indexed _asset, 25 | uint256 _value 26 | ); 27 | 28 | /// @notice Seller opens listing by committing ethereum 29 | /// @param _partialTx Seller's partial transaction 30 | /// @param _reqDiff Minimum acceptable block difficulty summation 31 | /// @param _asset The address of the asset contract. address(0) for ETH 32 | /// @param _value The amount of asset for sale, in smallest possible units 33 | /// @return true if Seller post is valid, false otherwise 34 | function open( 35 | bytes calldata _partialTx, 36 | uint256 _reqDiff, 37 | address _asset, 38 | uint256 _value 39 | ) external payable returns (bytes32); 40 | 41 | /// @notice Validate selected bid, bidder claims eth 42 | /// @param _version The 4-byte tx version 43 | /// @param _vin The length-prepended tx input vector 44 | /// @param _vout The length-prepended tx output vector 45 | /// @param _locktime The 4-byte tx locktime 46 | /// @param _proof The merkle proof of inclusion 47 | /// @param _index Merkle proof leaf index to aid verification 48 | /// @param _headers The raw bytes of all headers in order from earliest to latest 49 | /// @return true if bid is successfully accepted, error otherwise 50 | function claim( 51 | bytes calldata _proof, 52 | uint _index, 53 | bytes calldata _version, 54 | bytes calldata _vin, 55 | bytes calldata _vout, 56 | bytes calldata _locktime, 57 | bytes calldata _headers 58 | ) external returns (bool); 59 | 60 | /// @notice Validates the submitted bid transaction 61 | /// @dev Uses bitcoin parsing tools. This could be made more gas efficient 62 | /// @param _version The 4-byte tx version 63 | /// @param _vin The length-prepended tx input vector 64 | /// @param _vout The length-prepended tx output vector 65 | /// @param _locktime The 4-byte tx locktime 66 | /// @return The txid 67 | function checkTx( 68 | bytes calldata _version, 69 | bytes calldata _vin, 70 | bytes calldata _vout, 71 | bytes calldata _locktime 72 | ) external pure returns (bytes32 _txid); 73 | 74 | /// @notice Validates submitted header chain 75 | /// @dev Checks that all headers are linked, that each meets its target difficulty 76 | /// @param _headers The raw byte header chain 77 | /// @return The total difficulty of the header chain, and the first header's tx tree root 78 | function checkHeaders( 79 | bytes calldata _headers 80 | ) external view returns (uint256 _diff, bytes32 _merkleRoot); 81 | 82 | /// @notice Validates submitted merkle inclusion proof 83 | /// @dev Takes in the x 84 | /// @param _txid The 32 byte txid 85 | /// @param _merkleRoot The block header's merkle root 86 | /// @param _proof The inclusion proof 87 | /// @param _index The index of the txid in the leaf set 88 | /// @return true if _proof and _index show that _txid is in the _merkleRoot, else false 89 | function checkProof( 90 | bytes32 _txid, 91 | bytes32 _merkleRoot, 92 | bytes calldata _proof, 93 | uint256 _index 94 | ) external pure returns (bool); 95 | } 96 | 97 | contract StatelessSwap is IStatelessSwap { 98 | 99 | using BTCUtils for bytes; 100 | using BTCUtils for uint256; 101 | using BytesLib for bytes; 102 | using SafeMath for uint256; 103 | using ValidateSPV for bytes; 104 | using ValidateSPV for bytes32; 105 | 106 | enum ListingStates { NONE, ACTIVE, CLOSED } 107 | 108 | struct Listing { 109 | ListingStates state; 110 | uint256 value; // Asset amount or 721 ID 111 | uint256 reqDiff; // Required number of difficulty in confirmed blocks 112 | address asset; // Asset info 113 | address seller; // Seller address 114 | address wrapper; // The new NoFun (if applicable) 115 | 116 | // Filled Later 117 | address bidder; // Accepted bidder address 118 | bytes32 txid; // Accepted tx hash 119 | } 120 | 121 | address payable public developer; 122 | mapping(bytes32 => Listing) public listings; 123 | 124 | constructor (address _developer) public { 125 | developer = address(uint160(_developer)); 126 | } 127 | 128 | // IMPLEMENT FOR SPECIFIC ASSET TYPES 129 | function ensureFunding(Listing storage _listing) internal; 130 | function distribute(Listing storage _listing) internal; 131 | 132 | /// @notice Seller opens listing by committing ethereum 133 | /// @param _partialTx Seller's partial transaction 134 | /// @param _reqDiff Minimum acceptable block difficulty summation 135 | /// @param _asset The address of the asset contract. address(0) for ETH 136 | /// @param _value The amount of asset for sale, in smallest possible units 137 | /// @return true if Seller post is valid, false otherwise 138 | function open( 139 | bytes calldata _partialTx, 140 | uint256 _reqDiff, 141 | address _asset, 142 | uint256 _value 143 | ) external payable returns (bytes32) { 144 | // Listing identifier is keccak256 of Seller's partial transaction outpoint 145 | bytes32 _listingID = keccak256(_partialTx.slice(7, 36)); 146 | 147 | // Require unique listing identifier 148 | require(listings[_listingID].state == ListingStates.NONE, "Listing exists."); 149 | 150 | // Add to listings mapping 151 | listings[_listingID].state = ListingStates.ACTIVE; 152 | listings[_listingID].value = _value; 153 | if (_asset != address(0)) { 154 | listings[_listingID].asset = _asset; 155 | } 156 | listings[_listingID].seller = msg.sender; 157 | listings[_listingID].reqDiff = _reqDiff; 158 | 159 | ensureFunding(listings[_listingID]); 160 | 161 | // Emit ListingActive event 162 | emit ListingActive( 163 | _listingID, 164 | msg.sender, 165 | _asset, 166 | _value, 167 | _partialTx, 168 | _reqDiff); 169 | 170 | return _listingID; 171 | } 172 | 173 | /// @notice Validate selected bid, bidder claims eth 174 | /// @param _proof The merkle proof of inclusion 175 | /// @param _index Merkle proof leaf index to aid verification 176 | /// @param _version The 4-byte tx version 177 | /// @param _vin The length-prepended tx input vector 178 | /// @param _vout The length-prepended tx output vector 179 | /// @param _locktime The 4-byte tx locktime 180 | /// @param _headers The raw bytes of all headers in order from earliest to latest 181 | /// @return true if bid is successfully accepted, error otherwise 182 | function claim( 183 | bytes calldata _proof, 184 | uint _index, 185 | bytes calldata _version, 186 | bytes calldata _vin, 187 | bytes calldata _vout, 188 | bytes calldata _locktime, 189 | bytes calldata _headers 190 | ) external returns (bool) { 191 | uint256 _diff = _makeAllChecks( 192 | _proof, 193 | _index, 194 | _version, 195 | _vin, 196 | _vout, 197 | _locktime, 198 | _headers); 199 | 200 | bytes32 _listingID = keccak256(_vin.slice(1, 36)); 201 | Listing storage _listing = listings[_listingID]; 202 | 203 | // Require listing state to be ACTIVE and difficulty to be sufficient 204 | require(_diff >= _listing.reqDiff, "Not enough difficulty in header chain."); 205 | require(_listing.state == ListingStates.ACTIVE, "Listing has closed or does not exist."); 206 | address _bidder = _extractBidder(_vout); 207 | if (_bidder == address(0)) { 208 | _bidder = _listing.seller; 209 | } 210 | 211 | // Update listing state 212 | _listing.bidder = _bidder; 213 | _listing.state = ListingStates.CLOSED; 214 | 215 | distribute(_listing); 216 | 217 | // Emit ListingClosed event 218 | emit ListingClosed( 219 | _listingID, 220 | _listing.seller, 221 | _listing.bidder, 222 | _listing.asset, 223 | _listing.value 224 | ); 225 | 226 | return true; 227 | } 228 | 229 | /// @notice Extracts the bidder address from the bid tx 230 | /// @dev Returns 0 if the op_return is weird or there's only 1 output 231 | /// @param _vout The length-prefixed transaction output vector 232 | /// @return The 20 byte bidder address from the op_return, or address(0) 233 | function _extractBidder(bytes memory _vout) internal pure returns (address _bidder) { 234 | _bidder = address(0); 235 | if (uint8(_vout[0]) > 1) { 236 | bytes memory _data = _vout.extractOutputAtIndex(1).extractOpReturnData(); 237 | _bidder = _data.length >= 20 ? _data.toAddress(0) : address(0); 238 | } 239 | } 240 | 241 | /// @notice Extracts the bidder address from the bid tx 242 | /// @dev Returns 0 if the op_return is weird or there's only 1 output 243 | /// @param _vout The length-prefixed transaction output vector 244 | /// @return The 20 byte bidder address from the op_return, or address(0) 245 | function extractBidder(bytes calldata _vout) external pure returns (address _bidder) { 246 | return _extractBidder(_vout); 247 | } 248 | 249 | /// @notice Validate everything about an spv proof 250 | /// @param _proof The merkle proof of inclusion 251 | /// @param _index Merkle proof leaf index to aid verification 252 | /// @param _version The 4-byte tx version 253 | /// @param _vin The length-prepended tx input vector 254 | /// @param _vout The length-prepended tx output vector 255 | /// @param _locktime The 4-byte tx locktime 256 | /// @param _headers The raw bytes of all headers in order from earliest to latest 257 | /// @return The difficulty of the header chain, or error if there was an issue 258 | function _makeAllChecks( 259 | bytes memory _proof, 260 | uint _index, 261 | bytes memory _version, 262 | bytes memory _vin, 263 | bytes memory _vout, 264 | bytes memory _locktime, 265 | bytes memory _headers 266 | ) internal view returns (uint256 _diff) { 267 | bytes32 _merkleRoot; 268 | bytes32 _txid = _checkTx(_version, _vin, _vout, _locktime); 269 | (_diff, _merkleRoot) = _checkHeaders(_headers); 270 | _checkProof(_txid, _merkleRoot, _proof, _index); 271 | } 272 | 273 | /// @notice Validate everything about an spv proof 274 | /// @param _proof The merkle proof of inclusion 275 | /// @param _index Merkle proof leaf index to aid verification 276 | /// @param _version The 4-byte tx version 277 | /// @param _vin The length-prepended tx input vector 278 | /// @param _vout The length-prepended tx output vector 279 | /// @param _locktime The 4-byte tx locktime 280 | /// @param _headers The raw bytes of all headers in order from earliest to latest 281 | /// @return The difficulty of the header chain, or error if there was an issue 282 | function makeAllChecks( 283 | bytes calldata _proof, 284 | uint _index, 285 | bytes calldata _version, 286 | bytes calldata _vin, 287 | bytes calldata _vout, 288 | bytes calldata _locktime, 289 | bytes calldata _headers 290 | ) external view returns (uint256 _diff) { 291 | return _makeAllChecks(_proof, _index, _version, _vin, _vout, _locktime, _headers); 292 | } 293 | 294 | /// @notice Validates the submitted bid transaction 295 | /// @dev Uses bitcoin parsing tools. This could be made more gas efficient 296 | /// @param _version The 4-byte tx version 297 | /// @param _vin The length-prepended tx input vector 298 | /// @param _vout The length-prepended tx output vector 299 | /// @param _locktime The 4-byte tx locktime 300 | /// @return The txid 301 | function _checkTx( 302 | bytes memory _version, 303 | bytes memory _vin, 304 | bytes memory _vout, 305 | bytes memory _locktime 306 | ) internal pure returns (bytes32 _txid) { 307 | require(_vin.validateVin(), "vin is malformed"); 308 | require(_vout.validateVout(), "vout is malformed"); 309 | 310 | _txid = ValidateSPV.calculateTxId(_version, _vin, _vout, _locktime); 311 | } 312 | 313 | /// @notice Validates the submitted bid transaction 314 | /// @dev Uses bitcoin parsing tools. This could be made more gas efficient 315 | /// @param _version The 4-byte tx version 316 | /// @param _vin The length-prepended tx input vector 317 | /// @param _vout The length-prepended tx output vector 318 | /// @param _locktime The 4-byte tx locktime 319 | /// @return The txid, the bidder's ethereum address, and the value of the first output 320 | function checkTx( 321 | bytes calldata _version, 322 | bytes calldata _vin, 323 | bytes calldata _vout, 324 | bytes calldata _locktime 325 | ) external pure returns (bytes32 _txid) { 326 | return _checkTx(_version, _vin, _vout, _locktime); 327 | } 328 | 329 | /// @notice Validates submitted header chain 330 | /// @dev Checks that all headers are linked, that each meets its target difficulty 331 | /// @param _headers The raw byte header chain 332 | /// @return The total difficulty of the header chain, and the first header's tx tree root 333 | function _checkHeaders( 334 | bytes memory _headers 335 | ) internal view returns (uint256 _diff, bytes32 _merkleRoot) { 336 | _diff = _headers.validateHeaderChain(); 337 | require(_diff != ValidateSPV.getErrBadLength(), "Header bytes not multiple of 80."); 338 | require(_diff != ValidateSPV.getErrInvalidChain(), "Header bytes not a valid chain."); 339 | require(_diff != ValidateSPV.getErrLowWork(), "Header does not meet its own difficulty target."); 340 | _merkleRoot = _headers.extractMerkleRootLE().toBytes32(); 341 | } 342 | 343 | /// @notice Validates submitted header chain 344 | /// @dev Checks that all headers are linked, that each meets its target difficulty 345 | /// @param _headers The raw byte header chain 346 | /// @return The total difficulty of the header chain, and the first header's tx tree root 347 | function checkHeaders( 348 | bytes calldata _headers 349 | ) external view returns (uint256 _diff, bytes32 _merkleRoot) { 350 | return _checkHeaders(_headers); 351 | } 352 | 353 | /// @notice Validates submitted merkle inclusion proof 354 | /// @dev Takes in the x 355 | /// @param _txid The 32 byte txid 356 | /// @param _merkleRoot The block header's merkle root 357 | /// @param _proof The inclusion proof 358 | /// @param _index The index of the txid in the leaf set 359 | /// @return true if _proof and _index show that _txid is in the _merkleRoot, else false 360 | function _checkProof( 361 | bytes32 _txid, 362 | bytes32 _merkleRoot, 363 | bytes memory _proof, 364 | uint256 _index 365 | ) internal pure returns (bool) { 366 | require(_txid.prove(_merkleRoot, _proof, _index), "Bad inclusion proof"); 367 | return true; 368 | } 369 | 370 | /// @notice Validates submitted merkle inclusion proof 371 | /// @dev Takes in the x 372 | /// @param _txid The 32 byte txid 373 | /// @param _merkleRoot The block header's merkle root 374 | /// @param _proof The inclusion proof 375 | /// @param _index The index of the txid in the leaf set 376 | /// @return true if _proof and _index show that _txid is in the _merkleRoot, else false 377 | function checkProof( 378 | bytes32 _txid, 379 | bytes32 _merkleRoot, 380 | bytes calldata _proof, 381 | uint256 _index 382 | ) external pure returns (bool) { 383 | return _checkProof(_txid, _merkleRoot, _proof, _index); 384 | } 385 | 386 | /// @notice Calculates the developer's fee 387 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 388 | /// @param _value The amount of value to split between bidder and developer 389 | /// @return The fee share and the bidder's share 390 | function _allocate(uint256 _value) internal pure returns (uint256, uint256) { 391 | // developer share 392 | uint256 _feeShare = _value.div(400); 393 | // Bidder share 394 | uint256 _bidderShare = _value.sub(_feeShare); 395 | return (_feeShare, _bidderShare); 396 | } 397 | 398 | /// @notice Calculates the developer's fee 399 | /// @dev Looks up the listing and calculates a 25bps fee. Do not use for erc721. 400 | /// @param _value The amount of value to split between bidder and developer 401 | /// @return The fee share and the bidder's share 402 | function allocate(uint256 _value) external pure returns (uint256, uint256) { 403 | return _allocate(_value); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /contracts/StatelessSwaps.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import {NoFun} from "./NoFun.sol"; 4 | import {Factory} from "./CloneFactory.sol"; 5 | import {StatelessSwap} from "./StatelessSwap.sol"; 6 | 7 | import {ERC20} from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 8 | import {ERC721} from "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; 9 | import {SafeERC20} from "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 10 | 11 | 12 | contract StatelessSwapEth is StatelessSwap { 13 | 14 | /* solium-disable-next-line */ 15 | constructor (address _developer) public StatelessSwap(_developer) {} 16 | 17 | /// @notice Ensures that ether is paid in 18 | /// @dev User must pass in address(0) and the amount of ether 19 | /// @param _listing The listing object to in msgthat we expect to be funded 20 | function ensureFunding (Listing storage _listing) internal { 21 | // Require Seller to fund tx 22 | require(msg.value > 0, "No asset received. Listing must be funded on initialization."); 23 | require(_listing.asset == address(0), "asset must be zero address for ether listings."); 24 | require(_listing.value == msg.value, "value must equal msg.value"); 25 | } 26 | 27 | /// @notice Transfers ETH to the bidder and developer 28 | /// @dev Calls allocate to calculate shares 29 | /// @param _listing A pointer to the listing 30 | function distribute(Listing storage _listing) internal { 31 | if (_listing.bidder == _listing.seller) { 32 | // No fee for cancellation 33 | address(uint160(_listing.seller)).transfer(_listing.value); 34 | } else { 35 | // Calculate fee and bidder shares 36 | uint256 _feeShare; 37 | uint256 _bidderShare; 38 | (_feeShare, _bidderShare) = _allocate(_listing.value); 39 | 40 | // Transfer fee 41 | address(developer).transfer(_feeShare); 42 | 43 | // Transfer eth to selected bidder 44 | address(uint160(_listing.bidder)).transfer(_bidderShare); 45 | } 46 | } 47 | } 48 | 49 | contract StatelessSwap20 is StatelessSwap { 50 | 51 | using SafeERC20 for ERC20; 52 | 53 | /* solium-disable-next-line */ 54 | constructor (address _developer) public StatelessSwap(_developer) {} 55 | 56 | /// @notice Ensures that the tokens are transferred 57 | /// @dev Calls transferFrom on the erc20 contract, and checks that no ether is being burnt 58 | /// @param _listing The listing that we expect to be funded 59 | function ensureFunding (Listing storage _listing) internal { 60 | require(msg.value == 0, "Do not burn ether here please"); 61 | require(_listing.value > 0, "_value must be greater than 0"); 62 | ERC20(_listing.asset).safeTransferFrom(msg.sender, address(this), _listing.value); 63 | } 64 | 65 | 66 | /// @notice Transfers tokens to the bidder and developer 67 | /// @dev Calls allocate to calculate shares 68 | /// @param _listing A pointer to the listing 69 | function distribute(Listing storage _listing) internal { 70 | if (_listing.bidder == _listing.seller) { 71 | // No fee for cancellation 72 | ERC20(_listing.asset).safeTransfer(_listing.seller, _listing.value); 73 | } else { 74 | // Calculate fee and bidder shares 75 | uint256 _feeShare; 76 | uint256 _bidderShare; 77 | (_feeShare, _bidderShare) = _allocate(_listing.value); 78 | 79 | // send developer fee 80 | ERC20(_listing.asset).safeTransfer( 81 | developer, _feeShare); 82 | 83 | // send bidder proceeds 84 | ERC20(_listing.asset).safeTransfer(_listing.bidder, _bidderShare); 85 | } 86 | } 87 | } 88 | 89 | contract StatelessSwap721 is StatelessSwap { 90 | 91 | /* solium-disable-next-line */ 92 | constructor (address _developer) public StatelessSwap(_developer) {} 93 | 94 | /// @notice Ensures that the NFT is transferred 95 | /// @dev Calls transferFrom on the erc721 contract, and checks that no ether is being burnt 96 | /// @param _listing The listing that we expect to be funded 97 | function ensureFunding (Listing storage _listing) internal { 98 | require(msg.value == 0, "Do not burn ether here please"); 99 | ERC721(_listing.asset).transferFrom(msg.sender, address(this), _listing.value); 100 | } 101 | 102 | /// @notice Transfers the NFT to the bidder 103 | /// @dev Calls transferFrom on the erc721 contract 104 | /// @param _listing A pointer to the listing 105 | function distribute(Listing storage _listing) internal { 106 | // Transfer tokens to bidder 107 | ERC721(_listing.asset).transferFrom( 108 | address(this), _listing.bidder, _listing.value); 109 | } 110 | } 111 | 112 | contract StatelessSwapNoFun is StatelessSwap, Factory { 113 | 114 | using SafeERC20 for ERC20; 115 | 116 | address noFun; // The NoFun implementation for cloning 117 | 118 | constructor (address _developer, address _noFun) public StatelessSwap(_developer) { 119 | noFun = _noFun; 120 | } 121 | 122 | /// @notice Ensures that the Tokens are transferred to a new wrapper 123 | /// @dev Calls transferFrom on the erc20 contract, 124 | /// @param _listing The listing that we expect to be funded 125 | function ensureFunding(Listing storage _listing) internal { 126 | _listing.wrapper = createClone(noFun); 127 | NoFun(_listing.wrapper).init(developer, _listing.asset); 128 | ERC20(_listing.asset).safeTransferFrom(msg.sender, _listing.wrapper, _listing.value); 129 | } 130 | 131 | /// @notice Transfers the NoFun wrapper to the bidder 132 | /// @dev Calls transfer on the NoFun contract 133 | /// @param _listing A pointer to the listing 134 | function distribute(Listing storage _listing) internal { 135 | NoFun(_listing.wrapper).transfer(_listing.bidder); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /contracts/test/DummyERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | contract DummyERC20 { 4 | uint8 errorTimer; 5 | 6 | mapping (address => uint256) public balances; 7 | 8 | constructor() public { 9 | errorTimer = 0; 10 | } 11 | 12 | function totalSupply() external pure returns (uint256) {return 0;} 13 | 14 | function balanceOf(address who) external view returns (uint256) { 15 | return balances[who]; 16 | } 17 | 18 | function allowance(address owner, address) 19 | external pure returns (uint256) {return uint256(owner);} 20 | 21 | function approve(address, uint256) 22 | external pure returns (bool) {return true;} 23 | 24 | function transferFrom( 25 | address, 26 | address to, 27 | uint256 value 28 | ) external returns (bool) { 29 | balances[to] = value; 30 | return _do(); 31 | } 32 | 33 | function transfer( 34 | address to, 35 | uint256 value 36 | ) external returns (bool) { 37 | balances[to] = value; 38 | return _do(); 39 | } 40 | 41 | function setError(uint8 _timer) external { 42 | errorTimer = _timer; 43 | } 44 | 45 | function clearError() external { 46 | errorTimer = 0; 47 | } 48 | 49 | function _do() internal returns (bool) { 50 | if (errorTimer == 1) { 51 | errorTimer = 0; 52 | return false; 53 | } 54 | if (errorTimer > 1){ 55 | errorTimer -= 1; 56 | } 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/test/DummyERC721.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | contract DummyERC721 { 5 | uint8 errorTimer; 6 | 7 | constructor() public { 8 | errorTimer = 0; 9 | } 10 | 11 | function transferFrom( 12 | address, 13 | address, 14 | uint256 15 | ) public { 16 | _do(); 17 | } 18 | 19 | function setError(uint8 _timer) external { 20 | errorTimer = _timer; 21 | } 22 | 23 | function clearError() external { 24 | errorTimer = 0; 25 | } 26 | 27 | function _do() internal { 28 | if (errorTimer == 1) { 29 | require(false, 'Dummy revert'); 30 | } 31 | errorTimer -= 1; 32 | } 33 | 34 | function balanceOf(address) public pure returns (uint256) {return 0;} 35 | function ownerOf(uint256) public pure returns (address) {return address(0);} 36 | 37 | function approve(address, uint256) public pure {} 38 | function getApproved(uint256) 39 | public pure returns (address) {return address(0);} 40 | 41 | function setApprovalForAll(address, bool) public {} 42 | function isApprovedForAll(address, address) 43 | public pure returns (bool) {return true;} 44 | 45 | function safeTransferFrom(address, address, uint256) public pure {} 46 | 47 | function safeTransferFrom( 48 | address, 49 | address, 50 | uint256, 51 | bytes memory 52 | ) public pure {} 53 | } 54 | -------------------------------------------------------------------------------- /contracts/test/DummySwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import {StatelessSwap} from "../StatelessSwap.sol"; 4 | 5 | contract DummySwap is StatelessSwap { 6 | 7 | constructor (address _developer) public StatelessSwap(_developer) {} 8 | 9 | function ensureFunding(Listing storage _listing) internal { 10 | _listing; 11 | } 12 | 13 | function distribute(Listing storage _listing) internal { 14 | _listing; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | const Migrations = artifacts.require('Migrations'); 3 | 4 | module.exports = async (deployer) => { 5 | deployer.deploy(Migrations); 6 | }; 7 | -------------------------------------------------------------------------------- /migrations/2_deploy_callback.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const nets = require('./networkInfo'); 4 | 5 | const callbacks = [ 6 | 'CallbackSwapEth', 7 | 'CallbackSwap20', 8 | 'CallbackSwap721', 9 | ].map(s => artifacts.require(s)); 10 | 11 | function sleep(milliseconds) { 12 | return new Promise(resolve => setTimeout(resolve, milliseconds)); 13 | } 14 | 15 | module.exports = async (deployer, network) => { 16 | if (['test', 'development', 'coverage'].includes(network)) { 17 | // never run deployments on development. We deploy in tests 18 | return; 19 | } 20 | 21 | // dry runs are postfixed with '-fork' 22 | const strippedNetwork = network.split('-')[0]; 23 | const deployInfo = nets[strippedNetwork]; 24 | const { developer, relay } = deployInfo; 25 | 26 | /* eslint-disable */ 27 | console.log(''); 28 | console.log(`developer is ${developer}`); 29 | console.log(`relay is ${relay}`); 30 | console.log('Press Ctrl+C to cancel'); 31 | console.log(''); 32 | await sleep(7500); 33 | 34 | for (let i = 0; i < callbacks.length; i += 1) { 35 | await deployer.deploy(callbacks[i], developer, relay); 36 | } 37 | 38 | callbacks.forEach(c => console.log(`${c.contractName} deployed at ${c.address}`)); 39 | }; 40 | -------------------------------------------------------------------------------- /migrations/3_deploy_stateless.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const nets = require('./networkInfo'); 4 | 5 | const stateless = [ 6 | 'StatelessSwapEth', 7 | 'StatelessSwap20', 8 | 'StatelessSwap721' 9 | ].map(s => artifacts.require(s)); 10 | 11 | function sleep(milliseconds) { 12 | return new Promise(resolve => setTimeout(resolve, milliseconds)); 13 | } 14 | 15 | module.exports = async (deployer, network) => { 16 | if (['test', 'development', 'coverage'].includes(network)) { 17 | // never run deployments on development. We deploy in tests 18 | return; 19 | } 20 | 21 | // dry runs are postfixed with '-fork' 22 | const strippedNetwork = network.split('-')[0]; 23 | const deployInfo = nets[strippedNetwork]; 24 | const { developer } = deployInfo; 25 | 26 | /* eslint-disable */ 27 | console.log(''); 28 | console.log(`developer is ${developer}`); 29 | console.log('Press Ctrl+C to cancel'); 30 | console.log(''); 31 | await sleep(7500); 32 | 33 | for (let i = 0; i < stateless.length; i += 1) { 34 | await deployer.deploy(stateless[i], developer); 35 | } 36 | 37 | stateless.forEach(c => console.log(`${c.contractName} deployed at ${c.address}`)) 38 | }; 39 | -------------------------------------------------------------------------------- /migrations/networkInfo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ropsten: {}, 3 | ropsten_test: {}, 4 | kovan: {}, 5 | kovan_test: {}, 6 | alfajores: { 7 | developer: '0xdb894ea89b029a4169d54e6d80387b9032926cb8', 8 | relay: '0xc91ded938daeb449cf77118515911a1447731d0c' 9 | }, 10 | alfajores_test: {} 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stateless-swap", 3 | "version": "1.0.0", 4 | "description": "ETH <=> BTC trades", 5 | "scripts": { 6 | "compile": "truffle compile", 7 | "test:coverage": "npx solidity-coverage", 8 | "lint": "solium -d contracts/ && eslint ./test", 9 | "lint:fix": "solium --fix -d contracts/ && eslint --fix ./test", 10 | "test": "truffle test" 11 | }, 12 | "author": "James Prestwich", 13 | "license": "(MIT OR Apache-2.0)", 14 | "dependencies": { 15 | "@celo/contractkit": "^0.3.3", 16 | "@summa-tx/bitcoin-spv-sol": "^3.0.0", 17 | "@summa-tx/relay-sol": "^2.0.0", 18 | "bn.js": "^5.0.0", 19 | "dotenv": "^8.2.0", 20 | "openzeppelin-solidity": "^2.5.0", 21 | "solc": "^0.4.26" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^5.16.0", 25 | "eslint-config-airbnb-base": "^13.2.0", 26 | "eslint-plugin-import": "^2.18.0", 27 | "eth-gas-reporter": "^0.2.4", 28 | "solidity-coverage": "^0.6.3", 29 | "solium": "^1.2.5", 30 | "@truffle/hdwallet-provider": "^1.0.33", 31 | "truffle-plugin-verify": "^0.3.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-tx/swaps/d7752b827ffbb1359b5fe968df1025cc8d2ba4a9/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/eth_auction_setup.py: -------------------------------------------------------------------------------- 1 | import scripts.utils as utils 2 | import scripts.utxo_setup as us 3 | import scripts.partial_tx as pt 4 | import scripts.interface_wrapper as iw 5 | import scripts.merkle as merkle 6 | 7 | import asyncio 8 | 9 | import riemann 10 | import riemann.tx as tx 11 | import riemann.utils as rutils 12 | import riemann.simple as simple 13 | 14 | # from ether import transactions 15 | 16 | from typing import cast, List, Tuple 17 | 18 | GWEI = 1000000000 # 1 GWEI 19 | ETH_ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 20 | 21 | 22 | # 1. Prepare a bunch of UTXOs (split_tx) 23 | # 1. Send money to ADDRESS 24 | # 2. Get tx_id + index + value from block explorer, pass in 25 | # 2. Sign dutch auctions partial_txns 26 | # 1. Assume they're at index 0 - 9 27 | # 2. Assume all are 550 sat 28 | # 3. Define the auction format 29 | # 3. Make Ether data blobs 30 | 31 | 32 | def make_several_auctions( 33 | tx_id: str, 34 | index: int, 35 | control_addr: str, 36 | control_addr_keypair: Tuple[str, str], 37 | prevout_value: int, 38 | start_nonce: int, 39 | contract_address: str, 40 | reqDiff: int, 41 | eth_value: int, 42 | eth_privkey: str, 43 | num_auctions: int, 44 | recipient: str, 45 | form: List[Tuple[int, int]], 46 | network_id: int = 1) -> Tuple[tx.Tx, List[str], List[str]]: 47 | ''' 48 | Args: 49 | tx_id (str): A txid containing an output 50 | held by control_addr 51 | index (int): The index controlled 52 | control_addr (str): the addr controlling the 53 | outpoint described above 54 | control_addr_keypair (Tuple(str, str)): the keypair to the control addr 55 | prevout_value (int): The value of the prevout 56 | start_nonce (int): the ethereum account nonce to 57 | use in the first tx 58 | contract_address (str): the ether auction contract 59 | eth_value (int): the amount of ethereum in wei 60 | to sell in each auc 61 | eth_privkey (str): the privkey to an eth account 62 | num_auctions (int): the number of auctions to make 63 | recipient (str): the bitcoin address to send 64 | proceeds to 65 | form list(tuple): the price/timelock tuples for 66 | the auctions 67 | network_id (int): ether network id, 1 for main, 68 | 3 for ropsten 69 | Returns: 70 | tuple(riemann.tx.Tx, List(str), List(str)): 71 | the Bitcoin tx, 72 | the ether data blobs, 73 | and the signed Ethereum txns 74 | ''' 75 | if network_id != 1: 76 | riemann.select_network('bitcoin_test') 77 | split_tx = make_and_sign_split_tx( 78 | tx_id=tx_id, 79 | index=index, 80 | prevout_value=prevout_value, 81 | control_addr=control_addr, 82 | control_addr_keypair=control_addr_keypair, 83 | num_auctions=num_auctions, 84 | change_addr=recipient) 85 | 86 | split_tx_id = split_tx.tx_id.hex() 87 | prevout_tuples = [(split_tx_id, i, 550) for i in range(num_auctions)] 88 | 89 | partial_txns = pt.multidutch_as_hex( 90 | prevouts=prevout_tuples, 91 | recipient_addr=recipient, 92 | format_tuples=form, 93 | keypair=control_addr_keypair) 94 | 95 | ether_blobs = [iw.create_open_data( 96 | partial_tx=p[0], 97 | reservePrice=1000000, 98 | reqDiff=reqDiff, 99 | asset=ETH_ZERO_ADDRESS, 100 | value=eth_value 101 | ).hex() for p in zip(partial_txns, range(len(partial_txns)))] 102 | 103 | unsigned_ether = [iw.create_open_tx( 104 | partial_tx=p[0], 105 | reservePrice=1000000, 106 | reqDiff=reqDiff, 107 | asset=ETH_ZERO_ADDRESS, 108 | value=eth_value, 109 | nonce=start_nonce + p[1], 110 | gas_price=15 * GWEI, 111 | start_gas=500000, 112 | contract_address=contract_address, 113 | network_id=network_id 114 | ) for p in zip(partial_txns, range(len(partial_txns)))] 115 | 116 | secret_key = bytes.fromhex(eth_privkey) 117 | signed_ether_txns = [ 118 | tx.sign(secret_key).serialize().hex() for tx in unsigned_ether 119 | ] 120 | 121 | return split_tx, ether_blobs, signed_ether_txns 122 | 123 | 124 | def make_and_sign_split_tx( 125 | tx_id: str, 126 | index: int, 127 | prevout_value: int, 128 | control_addr: str, 129 | control_addr_keypair: Tuple[str, str], 130 | num_auctions: int, 131 | change_addr: str) -> tx.Tx: 132 | ''' 133 | Makes and signs a transaction with several small outputs 134 | Args: 135 | tx_id: the input prevout's txid 136 | index: the input prevout's index 137 | prevout_value: the input prevout's value 138 | control_addr: the input prevout's controlling address 139 | control_addr_keypair: the priv/pub keypair as a tuple of hex 140 | num_auctions: how many outputs to make 141 | change_addr: where to send leftover funds 142 | ''' 143 | # Split in 10, send SUMMA change 144 | split_tx = us.generate_small_utxos( 145 | tx_id=tx_id, 146 | index=index, 147 | prevout_value=prevout_value, 148 | recipient_addr=control_addr, 149 | num_outputs=num_auctions, 150 | change_addr=change_addr, 151 | fee=8000, 152 | size=550) 153 | 154 | pubkeyhash = rutils.hash160(bytes.fromhex(control_addr_keypair[1])) 155 | prevout_script = b'\x19\x76\xa9\x14' + pubkeyhash + b'\x88\xac' 156 | 157 | sighash_bytes = split_tx.sighash_single( 158 | index=0, 159 | script=prevout_script, 160 | prevout_value=rutils.i2le_padded(prevout_value, 8), 161 | anyone_can_pay=True) 162 | sig = utils.sign_hash(sighash_bytes, control_addr_keypair[0]) 163 | sig = '{}{}'.format(sig, '83') 164 | 165 | # Build the witness 166 | wit = tx.make_witness( 167 | [bytes.fromhex(sig), 168 | bytes.fromhex(control_addr_keypair[1])]) 169 | tx_witnesses = [wit] 170 | 171 | split_tx = split_tx.copy(tx_witnesses=tx_witnesses) 172 | 173 | return split_tx 174 | 175 | 176 | def undo_split( 177 | tx_id: str, 178 | num_auctions: int, 179 | change_addr: str, 180 | control_addr_keypair: Tuple[str, str]) -> tx.Tx: 181 | ''' 182 | undoes a split tx. NOT FOR SHUTTING DOWN AUCTIONS 183 | Args: 184 | tx_id: the tx_id of the split tx 185 | num_auctions: the number of non-change outputs of the split tx 186 | change_addr: the address to send leftovers to 187 | control_addr_keypair: the keypair of the controlling address 188 | ''' 189 | tx_ins = [simple.unsigned_input(simple.outpoint(tx_id, i)) 190 | for i in range(num_auctions)] 191 | tx_outs = [simple.output(600, change_addr)] 192 | unsplit_tx = simple.unsigned_witness_tx(tx_ins, tx_outs) 193 | 194 | pubkeyhash = rutils.hash160(bytes.fromhex(control_addr_keypair[1])) 195 | prevout_script = b'\x19\x76\xa9\x14' + pubkeyhash + b'\x88\xac' 196 | 197 | tx_witnesses = [] 198 | for i in range(num_auctions): 199 | sighash_bytes = unsplit_tx.sighash_all( 200 | index=i, 201 | script=prevout_script, 202 | prevout_value=rutils.i2le_padded(550, 8), 203 | anyone_can_pay=False) 204 | 205 | sig = utils.sign_hash(sighash_bytes, control_addr_keypair[0]) 206 | sig = '{}{}'.format(sig, '01') 207 | 208 | # Build the witness 209 | wit = tx.make_witness( 210 | [bytes.fromhex(sig), 211 | bytes.fromhex(control_addr_keypair[1])]) 212 | tx_witnesses.append(wit) 213 | 214 | return cast(tx.Tx, unsplit_tx.copy(tx_witnesses=tx_witnesses)) 215 | 216 | 217 | def make_btc_shutdown_txns( 218 | auction_tx_id: str, 219 | idxs: List[int], 220 | add_funds_tx_id: str, 221 | add_funds_idx: int, 222 | add_funds_value: int, 223 | control_addr: str, 224 | control_addr_keypair: Tuple[str, str], 225 | change_addr: str, 226 | eth_addr: str, 227 | fee: int = 7700) -> List[str]: 228 | ''' 229 | Shuts down an auction by winning them with the owner keypair 230 | Args: 231 | tx_id: the split tx for the auction set 232 | idxs: the unpurchased indexes 233 | add_funds_tx_id: a prevout tx id to fund these transactions 234 | add_funds_idx: the prevout index 235 | add_funds_value: the prevout value 236 | control_addr: the input prevout's controlling address 237 | control_addr_keypair: the priv/pub keypair as a tuple of hex 238 | change_addr: where to send leftover funds 239 | eth_addr: where to deliver auction proceeds 240 | fee: the tx fee to pay 241 | ''' 242 | prev = (add_funds_tx_id, add_funds_idx) 243 | val = add_funds_value 244 | shutdown_txns = [] 245 | 246 | pubkeyhash = rutils.hash160(bytes.fromhex(control_addr_keypair[1])) 247 | prevout_script = b'\x19\x76\xa9\x14' + pubkeyhash + b'\x88\xac' 248 | 249 | for i in range(len(idxs)): 250 | tx_ins = [ 251 | simple.unsigned_input(simple.outpoint(auction_tx_id, idxs[i])), 252 | simple.unsigned_input(simple.outpoint(*prev)) 253 | ] 254 | 255 | out_val = val + 550 - fee 256 | addr = control_addr if i < len(idxs) - 1 else change_addr 257 | tx_outs = [ 258 | simple.output(out_val, addr), 259 | tx.make_op_return_output(bytes.fromhex(eth_addr[2:])) 260 | ] 261 | 262 | shutdown_tx = simple.unsigned_witness_tx(tx_ins, tx_outs) 263 | 264 | tx_witnesses = [] 265 | 266 | sighash_bytes = shutdown_tx.sighash_all( 267 | index=0, 268 | script=prevout_script, 269 | prevout_value=rutils.i2le_padded(550, 8), 270 | anyone_can_pay=False) 271 | sig = utils.sign_hash(sighash_bytes, control_addr_keypair[0]) 272 | sig = '{}{}'.format(sig, '01') 273 | 274 | # Build the witness 275 | wit = tx.make_witness( 276 | [bytes.fromhex(sig), 277 | bytes.fromhex(control_addr_keypair[1])]) 278 | tx_witnesses.append(wit) 279 | 280 | sighash_bytes_2 = shutdown_tx.sighash_all( 281 | index=1, 282 | script=prevout_script, 283 | prevout_value=rutils.i2le_padded(val, 8), 284 | anyone_can_pay=False) 285 | sig_2 = utils.sign_hash(sighash_bytes_2, control_addr_keypair[0]) 286 | sig_2 = '{}{}'.format(sig_2, '01') 287 | 288 | # Build the witness 289 | wit_2 = tx.make_witness( 290 | [bytes.fromhex(sig_2), 291 | bytes.fromhex(control_addr_keypair[1])]) 292 | tx_witnesses.append(wit_2) 293 | 294 | prev = (shutdown_tx.tx_id.hex(), 0) 295 | val = out_val 296 | shutdown_txns.append(shutdown_tx.copy(tx_witnesses=tx_witnesses).hex()) 297 | 298 | return shutdown_txns 299 | 300 | 301 | def make_and_broadcast_btc_shutdown( 302 | auction_tx_id: str, 303 | idxs: List[int], 304 | control_addr: str, 305 | control_addr_keypair: Tuple[str, str], 306 | add_funds_tx_id: str, 307 | add_funds_idx: int, 308 | add_funds_value: int, 309 | change_addr: str, 310 | eth_addr: str, 311 | fee: int = 7700) -> List[str]: 312 | 313 | ''' 314 | Does make_btc_shutdown_txns and then broadcasts 315 | Args: 316 | auction_tx_id: the split tx for the auction set 317 | idxs: the unpurchased indexes 318 | add_funds_tx_id: a prevout tx id to fund these transactions 319 | add_funds_idx: the prevout index 320 | add_funds_value: the prevout value 321 | control_addr: the input prevout's controlling address 322 | control_addr_keypair: the priv/pub keypair as a tuple of hex 323 | change_addr: where to send leftover funds 324 | eth_addr: where to deliver auction proceeds 325 | fee: the tx fee to pay 326 | ''' 327 | 328 | shutdown_txns = make_btc_shutdown_txns( 329 | auction_tx_id=auction_tx_id, 330 | idxs=idxs, 331 | control_addr=control_addr, 332 | control_addr_keypair=control_addr_keypair, 333 | add_funds_tx_id=add_funds_tx_id, 334 | add_funds_idx=add_funds_idx, 335 | add_funds_value=add_funds_value, 336 | change_addr=change_addr, 337 | eth_addr=eth_addr, 338 | fee=fee) 339 | 340 | async def do() -> List[str]: 341 | ret = [await merkle.broadcast(t) for t in shutdown_txns] 342 | return ret 343 | 344 | task = asyncio.ensure_future(do()) 345 | return asyncio.get_event_loop().run_until_complete(task) 346 | 347 | # def make_ethereum_settlement_transactions( 348 | # bitcoin_txids: List[str], 349 | # start_nonce: int, 350 | # eth_privkey: str, 351 | # contract_address: str) -> List[str]: 352 | # coros = [ 353 | # merkle.get_that_tx( 354 | # bitcoin_txids[i], 8, contract_address, i + start_nonce) 355 | # for i in range(len(bitcoin_txids))] 356 | # task = asyncio.gather(*coros) 357 | # txns = asyncio.get_event_loop().run_until_complete(task) 358 | # signed = [iw.sign(txn, bytes.fromhex(eth_privkey)) for txn in txns] 359 | # return [transactions.serialize(txn) for txn in signed] 360 | -------------------------------------------------------------------------------- /scripts/interface_wrapper.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from ether import calldata, transactions 4 | 5 | from typing import Any 6 | 7 | 8 | # Loads the abi from the file 9 | # For some reason solc generates json stored as a string inside json 10 | # So we have to call .loads twice 11 | with open('build/IntegralAuction.json', 'r') as jsonfile: 12 | j = json.loads(jsonfile.read()) 13 | ABI = json.loads(j['interface']) 14 | 15 | 16 | def create_unsigned_tx( 17 | contract_address: str, 18 | value: int = 0, 19 | start_gas: int = 500000, 20 | gas_price: int = 20, 21 | nonce: int = 0, 22 | tx_data: bytes = b'', 23 | network_id: int = 1) -> transactions.UnsignedEthTx: 24 | '''Creates an unsigned contract call transaction. 25 | Args: 26 | contract_method (str): name of contract method to call 27 | contract_method_args (list): contract method arguments in 28 | value (int): amount in wei payable to contract 29 | method. is 0 if contract method is 30 | not payable 31 | contract_address (str): address of the contract to call 32 | value (int): amount of ether (in wei) to include 33 | nonce (int): number of transactions already sent by 34 | signing account 35 | gas_price (int): gas price 36 | start_gas (int): gas limit 37 | Returns: 38 | (Transaction instance): unsigned transaction 39 | ''' 40 | # Transaction instance, unsigned 41 | return transactions.UnsignedEthTx( 42 | to=contract_address, 43 | value=value, 44 | gas=start_gas, 45 | gasPrice=gas_price, 46 | nonce=nonce, 47 | data=tx_data, 48 | chainId=network_id) 49 | 50 | 51 | def create_open_data( 52 | partial_tx: str, 53 | reservePrice: int, 54 | reqDiff: int, 55 | asset: str, 56 | value: int) -> bytes: 57 | '''Makes an data blob for calling open 58 | 59 | Args: 60 | partial_tx (str): the partial transaction to submit 61 | reservePrice (int): the lowest acceptable price (not enforced) 62 | reqDiff (int): the amount of difficult required 63 | in the proof's header chain 64 | asset (str): asset address 65 | value (int): asset amount or 721 ID 66 | 67 | Returns: 68 | (bytes): the data blob 69 | ''' 70 | contract_method_args = [ 71 | bytes.fromhex(partial_tx), 72 | reservePrice, 73 | reqDiff, 74 | asset, 75 | value] 76 | return calldata.call('open', contract_method_args, ABI) 77 | 78 | 79 | def create_claim_data( 80 | tx: bytes, proof: bytes, index: int, headers: bytes) -> bytes: 81 | '''Makes an unsigned transaction calling claim 82 | 83 | Args: 84 | tx (bytes): the fully signed tx 85 | proof (bytes): the merkle inclusion proof 86 | index (int): the index of the tx for merkle verification 87 | headers (bytes): the header chain containing work 88 | 89 | Returns: 90 | (bytes): the data blob 91 | ''' 92 | contract_method_args = [ 93 | tx, 94 | proof, 95 | index, 96 | headers] 97 | return calldata.call('claim', contract_method_args, ABI) 98 | 99 | 100 | def create_open_tx( 101 | partial_tx: str, 102 | reservePrice: int, 103 | reqDiff: int, 104 | asset: str, 105 | value: int, 106 | **kwargs: Any) -> transactions.UnsignedEthTx: 107 | '''Makes an unsigned transaction calling open 108 | 109 | Args: 110 | partial_tx (str): the partial transaction to submit 111 | reservePrice (int): the lowest acceptable price (not enforced) 112 | reqDiff (int): the amount of difficult required 113 | in the proof's header chain 114 | asset (str): asset address 115 | value (int): asset amount or 721 ID 116 | **kwargs: 117 | contract_address (str): address of the contract to call 118 | nonce (int): number of transactions already sent by 119 | signing account 120 | gas_price (int): gas price 121 | start_gas (int): gas limit 122 | network_id (int): ethereum network id 123 | 124 | Returns: 125 | (ethereum.transactions.Transaction): the unsigned tx 126 | ''' 127 | tx_data = create_open_data( 128 | partial_tx, reservePrice, reqDiff, asset, value) 129 | return create_unsigned_tx( 130 | tx_data=tx_data, 131 | value=value, 132 | **kwargs) 133 | 134 | 135 | def create_claim_tx( 136 | tx: bytes, 137 | proof: bytes, 138 | index: int, 139 | headers: bytes, 140 | **kwargs: Any) -> transactions.UnsignedEthTx: 141 | '''Makes an unsigned transaction calling claim 142 | 143 | Args: 144 | tx (bytes): the fully signed tx 145 | proof (str): the merkle inclusion proof 146 | index (int): the index of the tx for merkle verification 147 | headers (str): the header chain containing work 148 | **kwargs: 149 | contract_address (str): address of the contract to call 150 | value (int): amount of ether (in wei) to include 151 | nonce (int): number of transactions already sent by 152 | signing account 153 | gas_price (int): gas price 154 | start_gas (int): gas limit 155 | network_id (int): ethereum network id 156 | 157 | Returns: 158 | (ethereum.transactions.Transaction): the unsigned tx 159 | ''' 160 | tx_data = create_claim_data(tx, proof, index, headers) 161 | return create_unsigned_tx( 162 | tx_data=tx_data, 163 | **kwargs) 164 | 165 | 166 | def sign( 167 | tx: transactions.UnsignedEthTx, 168 | key: bytes) -> transactions.SignedEthTx: 169 | ''' 170 | Sign this transaction with a private key. 171 | A potentially already existing signature would be overridden. 172 | ''' 173 | return tx.sign(key) 174 | -------------------------------------------------------------------------------- /scripts/merkle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import asyncio 4 | 5 | # from scripts import interface_wrapper as iw 6 | 7 | from connectrum.svr_info import ServerInfo 8 | from connectrum.client import StratumClient 9 | 10 | from riemann import tx 11 | from riemann import utils as rutils 12 | 13 | # from ether.transactions import UnsignedEthTx 14 | 15 | from typing import Any, cast, Tuple, Union 16 | 17 | with open('build/ValidateSPV.json', 'r') as jsonfile: 18 | j = json.loads(jsonfile.read()) 19 | ABI = json.loads(j['interface']) 20 | 21 | 22 | CLIENT: StratumClient 23 | 24 | 25 | async def _get_client() -> StratumClient: 26 | try: 27 | return CLIENT 28 | except NameError: 29 | global CLIENT 30 | CLIENT = await setup_client() 31 | return CLIENT 32 | 33 | 34 | async def setup_client() -> StratumClient: 35 | try: 36 | return CLIENT 37 | except NameError: 38 | pass 39 | 40 | server = ServerInfo({ 41 | "nickname": None, 42 | "hostname": "fortress.qtornado.com", 43 | "ip_addr": None, 44 | "ports": [ 45 | "s50002", 46 | "t50001" 47 | ], 48 | "version": "1.4", 49 | "pruning_limit": 0, 50 | "seen_at": 1533670768.8676858 51 | }) 52 | 53 | client = StratumClient() 54 | 55 | await asyncio.wait_for( 56 | client.connect( 57 | server_info=server, 58 | proto_code='s', 59 | use_tor=False, 60 | disable_cert_verify=True), 61 | timeout=5) 62 | # 63 | # await asyncio.wait_for( 64 | # client.RPC( 65 | # 'server.version', 66 | # 'bitcoin-spv-merkle', 67 | # '1.2'), 68 | # timeout=5) 69 | 70 | return client 71 | 72 | # # # # # # # # # # # # # # # 73 | # Use this script Sparingly # 74 | # # # # # # # # # # # # # # # 75 | 76 | 77 | async def broadcast(t: Union[tx.Tx, str]) -> Any: 78 | ''' 79 | broadcasts a bitcoin transaction. accepts an object or a string 80 | ''' 81 | if type(t) is str: 82 | txhex = cast(str, t) 83 | else: 84 | txhex = cast(tx.Tx, t).hex() 85 | 86 | client = await _get_client() 87 | res = await client.RPC('blockchain.transaction.broadcast', txhex) 88 | return res 89 | 90 | 91 | # def make_ether_txn( 92 | # t: tx.Tx, 93 | # proof: str, 94 | # index: int, 95 | # headers: str, 96 | # contract_address: str, 97 | # nonce: int) -> UnsignedEthTx: 98 | # 99 | # return iw.create_claim_tx( 100 | # tx=t.to_bytes(), 101 | # proof=bytes.fromhex(proof), 102 | # index=index, 103 | # headers=bytes.fromhex(headers), 104 | # nonce=nonce, 105 | # contract_address=contract_address, 106 | # gas_price=15 * 1000000000, # 15 GWEI 107 | # start_gas=1000000, 108 | # value=0) 109 | 110 | 111 | async def get_latest_blockheight() -> int: 112 | ''' 113 | gets the latest blockheight that a server is aware of 114 | ''' 115 | client = await _get_client() 116 | fut, _ = client.subscribe('blockchain.headers.subscribe') 117 | block_dict = await fut 118 | print(block_dict) 119 | return cast(int, block_dict['height']) 120 | 121 | 122 | async def get_block_merkle_root(height: int) -> bytes: 123 | ''' 124 | gets the merkle root of a block 125 | ''' 126 | client = await _get_client() 127 | 128 | header_dict = await client.RPC('blockchain.block.headers', height, 1) 129 | merkle_root = bytes.fromhex(header_dict['hex'])[36:68] 130 | 131 | return merkle_root 132 | 133 | 134 | async def get_tx_from_api(tx_id: str) -> Tuple[dict, tx.Tx]: 135 | ''' 136 | gets a transaction from electrum and returns it as a dict and an object 137 | ''' 138 | client = await _get_client() 139 | tx_dict = await client.RPC('blockchain.transaction.get', tx_id, True) 140 | t = tx.Tx.from_hex(tx_dict['hex']) 141 | 142 | latest_blockheight = await get_latest_blockheight() 143 | 144 | # NB: I'm not sure why this works. I feel like it should be -1 145 | tx_dict['block_height'] = latest_blockheight - tx_dict['confirmations'] + 1 146 | 147 | return tx_dict, t 148 | 149 | 150 | async def get_header_chain(start_height: int, count: int) -> str: 151 | ''' 152 | gets headers starting at a specified height 153 | ''' 154 | client = await _get_client() 155 | 156 | res = await client.RPC( 157 | 'blockchain.block.headers', start_height, count) 158 | 159 | return cast(str, res['hex']) 160 | 161 | 162 | async def get_merkle_proof_from_api(tx_id: str, hght: int) -> Tuple[str, int]: 163 | ''' 164 | gets a transaction inclusion proof from electrum 165 | puts it into the format we expect 166 | ''' 167 | client = await _get_client() 168 | 169 | res = await client.RPC('blockchain.transaction.get_merkle', tx_id, hght) 170 | 171 | pos = res['pos'] 172 | 173 | proof = bytearray() 174 | proof.extend(bytes.fromhex(tx_id)[::-1]) 175 | for tx_id in res['merkle']: 176 | proof.extend(bytes.fromhex(tx_id)[::-1]) 177 | 178 | block_root = await get_block_merkle_root(hght) 179 | 180 | proof.extend(block_root) 181 | 182 | # NB: add 1 because our proof uses 1-indexed position 183 | return proof.hex(), pos + 1 184 | 185 | 186 | def verify_proof(proof: bytes, index: int) -> bool: 187 | ''' 188 | verifies a merkle leaf occurs at a specified index given a merkle proof 189 | ''' 190 | index = index # This is 1 indexed 191 | # TODO: making creating and verifying indexes the same 192 | root = proof[-32:] 193 | current = proof[0:32] 194 | 195 | # For all hashes between first and last 196 | for i in range(1, len(proof) // 32 - 1): 197 | # If the current index is even, 198 | # The next hash goes before the current one 199 | if index % 2 == 0: 200 | current = rutils.hash256( 201 | proof[i * 32: (i + 1) * 32] 202 | + current 203 | ) 204 | # Halve and floor the index 205 | index = index // 2 206 | else: 207 | # The next hash goes after the current one 208 | current = rutils.hash256( 209 | current 210 | + proof[i * 32: (i + 1) * 32] 211 | ) 212 | # Halve and ceil the index 213 | index = index // 2 + 1 214 | print(current.hex()) 215 | # At the end we should have made the root 216 | if current != root: 217 | return False 218 | return True 219 | 220 | 221 | # async def get_that_tx( 222 | # tx_id: str, 223 | # num_headers: int, 224 | # contract_address: str, 225 | # nonce: int): 226 | # ''' 227 | # Makes an ethereum transaction containing a full SPV proof for a tx 228 | # ''' 229 | # (tx_json, t) = await get_tx_from_api(tx_id) 230 | # 231 | # proof, index = await get_merkle_proof_from_api( 232 | # t.tx_id.hex(), tx_json['block_height']) 233 | # 234 | # # Create a header chain 235 | # chain = await get_header_chain( 236 | # tx_json['block_height'], 237 | # num_headers + 1) 238 | # 239 | # txn = make_ether_txn(t, proof, index, chain, contract_address, nonce) 240 | # 241 | # return txn 242 | 243 | 244 | async def do_it_all(tx_id: str, num_headers: int) -> None: 245 | ''' 246 | Gets proof info and prints it 247 | ''' 248 | (tx_json, t) = await get_tx_from_api(tx_id) 249 | 250 | proof, index = await get_merkle_proof_from_api( 251 | t.tx_id.hex(), tx_json['block_height']) 252 | 253 | # Create a header chain 254 | chain = await get_header_chain( 255 | tx_json['block_height'], 256 | num_headers + 1) 257 | 258 | # Error if the proof isn't valid 259 | assert(verify_proof(bytes.fromhex(proof), index)) 260 | 261 | print() 262 | print() 263 | print('---- TX ----') 264 | print(t.hex()) 265 | print() 266 | print() 267 | print('--- PROOF ---') 268 | print(proof) 269 | print() 270 | print() 271 | print('--- INDEX ---') 272 | print(index) 273 | print() 274 | print() 275 | print('--- CHAIN ---') 276 | print(chain) 277 | 278 | 279 | def main() -> None: 280 | # Read tx_id from args, and then get it and its block from explorers 281 | tx_id = str(sys.argv[1]) 282 | num_headers = int(sys.argv[2]) if len(sys.argv) > 2 else 6 283 | 284 | asyncio.get_event_loop().run_until_complete(do_it_all(tx_id, num_headers)) 285 | 286 | 287 | if __name__ == '__main__': 288 | main() 289 | -------------------------------------------------------------------------------- /scripts/partial_tx.py: -------------------------------------------------------------------------------- 1 | import scripts.utils as utils 2 | from riemann import simple, tx 3 | from riemann import utils as rutils 4 | 5 | from riemann.tx import Outpoint, Tx 6 | from typing import cast, List, Tuple 7 | 8 | 9 | Format = List[Tuple[int, int]] # sale value, block height 10 | KeyPair = Tuple[str, str] 11 | Prevout = Tuple[str, int, int] # txid, index, value 12 | Auction = List[Tx] 13 | 14 | 15 | def make_partial_tx(outpoint: Outpoint, 16 | output_value: int, 17 | output_address: str, 18 | lock_time: int = 0) -> Tx: 19 | '''Creates an unsigned partial tx 20 | Args: 21 | outpoint (riemann.tx.Outpoint): the outpoint to spend 22 | sequence (int): tx nSequence 23 | output_value (int): the number of satoshi to receive 24 | output_address (str): the seller's address 25 | lock_time (int): the tx's lock time 26 | ''' 27 | tx_ins = [simple.unsigned_input(outpoint, sequence=0xFFFFFFFD)] 28 | tx_outs = [simple.output(output_value, output_address)] 29 | return cast(Tx, simple.unsigned_witness_tx( 30 | tx_ins=tx_ins, 31 | tx_outs=tx_outs, 32 | lock_time=lock_time)) 33 | 34 | 35 | def sign_partial_tx( 36 | partial_tx: Tx, 37 | keypair: KeyPair, 38 | prevout_script: bytes, 39 | prevout_value: bytes) -> Tx: 40 | ''' 41 | Signs a partial transaction. 42 | Args: 43 | partial_tx (riemann.tx.Tx): the partial_tx to sign 44 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 45 | prevout_script (bytes): the script code of the prevout 46 | prevout_value (bytes): the value of the prevout in LE uint64 47 | Returns: 48 | (riemann.tx.Tx): sighash_singleanyonecanpay signed partial_tx 49 | ''' 50 | sighash_bytes = partial_tx.sighash_single( 51 | index=0, 52 | script=prevout_script, 53 | prevout_value=prevout_value, 54 | anyone_can_pay=True) 55 | sig = utils.sign_hash(sighash_bytes, keypair[0]) 56 | sig = '{}{}'.format(sig, '83') 57 | 58 | # Build the witness 59 | wit = tx.make_witness( 60 | [bytes.fromhex(sig), 61 | bytes.fromhex(keypair[1])]) 62 | tx_witnesses = [wit] 63 | 64 | return partial_tx.copy(tx_witnesses=tx_witnesses) 65 | 66 | 67 | def partial( 68 | tx_id: str, 69 | index: int, 70 | prevout_value: int, 71 | recipient_addr: str, 72 | output_value: int, 73 | lock_time: int, 74 | keypair: KeyPair) -> Tx: 75 | ''' 76 | Makes a partial_tx from human readable information 77 | 78 | Args: 79 | tx_id (str): txid of parent tx 80 | index (int): index of input in parent tx 81 | prevout_value (int): value in satoshi of the input 82 | recipient_addr (str): address of the recipient 83 | output_value (int): value in satoshi of the output 84 | lock_time (int): desired lock_time in bitcoin format 85 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 86 | Returns: 87 | (riemann.tx.Tx): The signed transaction 88 | ''' 89 | outpoint = simple.outpoint(tx_id, index) 90 | pub = bytes.fromhex(keypair[1]) 91 | pkh = rutils.hash160(pub) 92 | output_script = b'\x19\x76\xa9\x14' + pkh + b'\x88\xac' # Assume PKH 93 | 94 | unsigned = make_partial_tx( 95 | outpoint=outpoint, 96 | output_value=output_value, 97 | output_address=recipient_addr, 98 | lock_time=lock_time) 99 | 100 | signed = sign_partial_tx( 101 | partial_tx=unsigned, 102 | keypair=keypair, 103 | prevout_script=output_script, 104 | prevout_value=rutils.i2le_padded(prevout_value, 8)) 105 | return signed 106 | 107 | 108 | def dutch( 109 | tx_id: str, 110 | index: int, 111 | prevout_value: int, 112 | recipient_addr: str, 113 | format_tuples: Format, 114 | keypair: KeyPair) -> Auction: 115 | ''' 116 | Makes a dutch auction given a list representing the format 117 | 118 | Args: 119 | tx_id (str): txid of parent tx 120 | index (int): index of input in parent tx 121 | prevout_value (int): value in satoshi of the input 122 | recipient_addr (str): address of the recipient 123 | format_tuples (list(tuple(int, int))): tuples of value and timelock 124 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 125 | Returns: 126 | list(riemann.tx.Tx): The signed transactions 127 | ''' 128 | ret = [] 129 | for t in format_tuples: 130 | txn = partial(tx_id, index, prevout_value, recipient_addr, 131 | t[0], t[1], keypair) 132 | ret.append(txn) 133 | return ret 134 | 135 | 136 | def dutch_as_hex( 137 | tx_id: str, 138 | index: int, 139 | prevout_value: int, 140 | recipient_addr: str, 141 | format_tuples: Format, 142 | keypair: KeyPair) -> str: 143 | ''' 144 | Makes a dutch auction given a list representing the format 145 | 146 | Args: 147 | tx_id (str): txid of parent tx 148 | index (int): index of input in parent tx 149 | prevout_value (int): value in satoshi of the input 150 | recipient_addr (str): address of the recipient 151 | format_tuples (list(tuple(int, int))): tuples of value and timelock 152 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 153 | Returns: 154 | str: The signed transactions as a hex blob 155 | ''' 156 | txns = dutch(tx_id, index, prevout_value, recipient_addr, 157 | format_tuples, keypair) 158 | b = bytearray() 159 | for t in txns: 160 | b.extend(t) 161 | return b.hex() 162 | 163 | 164 | def multidutch( 165 | prevouts: List[Prevout], 166 | recipient_addr: str, 167 | format_tuples: Format, 168 | keypair: KeyPair) -> List[Auction]: 169 | ''' 170 | Makes identical dutch auctions for each outpoint in a list of outpoints 171 | 172 | Args: 173 | prevouts (list(tuple(str, int, int))): tuple of txid, index, value 174 | recipient_addr (str): address of the recipient 175 | format_tuples (list(tuple(int, int))): tuples of value and timelock 176 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 177 | Returns: 178 | list(list(riemann.tx.Tx)): The signed transactions 179 | ''' 180 | ret = [] 181 | for p in prevouts: 182 | d = dutch(p[0], p[1], p[2], recipient_addr, 183 | format_tuples, keypair) 184 | ret.append(d) 185 | return ret 186 | 187 | 188 | def multidutch_as_hex( 189 | prevouts: List[Prevout], 190 | recipient_addr: str, 191 | format_tuples: Format, 192 | keypair: KeyPair) -> List[str]: 193 | ''' 194 | Makes identical dutch auctions for each outpoint in a list of outpoints 195 | 196 | Args: 197 | prevouts (list(tuple(str, str, int))): tuple of txid, index, value 198 | recipient_addr (str): address of the recipient 199 | format_tuples (list(tuple(int, int))): tuples of value and timelock 200 | keypair (tuple(str, str)): privkey as hex, pubkey as hex 201 | Returns: 202 | list(str): A list of dutch partial_tx blobs 203 | ''' 204 | dutches = multidutch(prevouts, recipient_addr, format_tuples, keypair) 205 | ret = [] 206 | for d in dutches: 207 | b = bytearray() 208 | for t in d: 209 | b.extend(t) 210 | ret.append(b.hex()) 211 | return ret 212 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | flake8 \ 4 | --ignore=W503,W504,F723 \ 5 | --exclude scripts/tests/ \ 6 | scripts && \ 7 | mypy \ 8 | scripts/ \ 9 | --disallow-untyped-defs \ 10 | --strict-equality \ 11 | --show-error-codes \ 12 | --warn-return-any \ 13 | --ignore-missing-imports 14 | -------------------------------------------------------------------------------- /scripts/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import ecdsa 5 | import signal 6 | import hashlib 7 | import logging 8 | 9 | from ecdsa.util import sigencode_der_canonize 10 | from ecdsa.ecdsa import int_to_string 11 | from Cryptodome.Cipher import AES 12 | from Cryptodome.Util.Padding import pad, unpad 13 | from riemann import utils as rutils 14 | 15 | from riemann.tx import Tx 16 | from typing import Any, Tuple 17 | 18 | # TODO: CHANGE FOR WINDOWS 19 | PATH = os.path.expanduser('~/.integral/bidder/') 20 | DB_PBKDF_SALT = b'integral-bidder-key-stretching' 21 | PBKDF_ITERATIONS = 100000 22 | SIGHASH_ALL = 0x01 23 | 24 | 25 | def get_value_and_lock_time(tx: Tx) -> Tuple[int, int]: 26 | '''Parses the time lock and first output's value from a txn 27 | 28 | Args: 29 | tx (riemann.tx.Tx): the transaction 30 | Returns: 31 | (int, int): the value and locktime 32 | ''' 33 | return (rutils.le2i(tx.tx_outs[0].value), 34 | rutils.le2i(tx.lock_time)) 35 | 36 | 37 | def sign_hash(hash_bytes: bytes, privkeydata): # type: ignore 38 | '''Signs a hash with a private key 39 | 40 | Args: 41 | hash_bytes (bytes): The 32-byte hash to sign 42 | privkeydata (*): The private key to sign with 43 | 44 | Returns: 45 | (bytes): The signature 46 | ''' 47 | signing_key = coerce_key(privkeydata) 48 | return signing_key.sign_digest( 49 | hash_bytes, 50 | sigencode=sigencode_der_canonize).hex() 51 | 52 | 53 | def coerce_key(data) -> ecdsa.SigningKey: # type: ignore 54 | ''' Coerces key data to an ECDSA signing_key object 55 | Args: 56 | data (*): A key in some supported format 57 | Returns: 58 | (ecdsa.SigningKey): The key object 59 | ''' 60 | try: 61 | data = bytes.fromhex(data) 62 | except: # noqa: E722 63 | pass # additional coercion attempts go here 64 | return ecdsa.SigningKey.from_string( 65 | data, 66 | curve=ecdsa.SECP256k1) 67 | 68 | 69 | def write_to_file(data: bytes, filename: str) -> bool: 70 | ''' 71 | writes bytes to a file with termination protection 72 | ''' 73 | with TerminateProtected(): 74 | with open(filename, 'wb') as outfile: 75 | outfile.write(data) 76 | return True 77 | 78 | 79 | def write_encrypted_json_file( 80 | data_dict: dict, 81 | filename: str, 82 | secret_phrase: str) -> None: 83 | msg = json.dumps(data_dict).encode('utf-8') 84 | msg = encode_aes(msg, secret_phrase) 85 | write_to_file(msg, filename) 86 | 87 | 88 | def read_encrypted_json_file(filename: str, secret_phrase: str) -> Any: 89 | with open(filename, 'rb') as datafile: 90 | content = datafile.read() 91 | content = decode_aes(content, secret_phrase).decode('utf-8') 92 | return json.loads(content) 93 | 94 | 95 | def pbkdf2_hmac( 96 | data: bytes, 97 | salt: bytes = b'', 98 | hash_name: str = 'sha512', 99 | iterations: int = 2048) -> bytes: 100 | ''' Key stretching function PBKDF2 using HMAC-SHA512 to implement BIP39. 101 | Args: 102 | data (bytes): data to stretch, mnemonic for BIP39 103 | salt (bytes): optional data for security, 'mnemonic' for BIP39 104 | hash_name (str): HMAC hash digest algorithm, SHA512 for BIP39 105 | iterations (int): number of HMAC-SHA512 hashing rounds, 2048 for BIP39 106 | Returns: 107 | (bytes): generated seed, 512-bit seed for BIP39 108 | ''' 109 | return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations) 110 | 111 | 112 | def _aes_encrypt_with_iv(key: bytes, iv: bytes, data_bytes: bytes) -> bytes: 113 | '''Encrypts a message with a key. 114 | Args: 115 | key (bytes): the AES key (32 bytes) 116 | iv (bytes): the AES initialization vector 117 | data_bytes (bytes): the message to encrypt 118 | Returns: 119 | (bytes): the encrypted message 120 | ''' 121 | data_bytes = pad(data_bytes, 16) 122 | e = AES.new(key, AES.MODE_CBC, iv).encrypt(data_bytes) 123 | return e 124 | 125 | 126 | def _aes_decrypt_with_iv(key: bytes, iv: bytes, data_bytes: bytes) -> bytes: 127 | '''Decrypts a message with a key. 128 | Args: 129 | key (bytes): the AES key (32 bytes) 130 | iv (bytes): the AES initialization vector 131 | data_bytes (bytes): the message to decrypt 132 | Returns: 133 | (bytes): the decrypted message 134 | ''' 135 | cipher = AES.new(key, AES.MODE_CBC, iv) 136 | data_bytes = cipher.decrypt(data_bytes) 137 | try: 138 | return unpad(data_bytes, 16) 139 | except ValueError as e: 140 | e.args += ('Invalid passphrase',) 141 | raise e 142 | 143 | 144 | def encode_aes(message_bytes: bytes, secret_phrase: str) -> bytes: 145 | '''Encrypts a message with a phrase 146 | Args: 147 | message_bytes (bytes): the bytes to encrypt 148 | secret_phrase (str): the user's db encryption phrase 149 | Returns: 150 | (bytes): the encrypted message, prepended with its iv and ephemeral key 151 | ''' 152 | secret = pbkdf2_hmac( 153 | data=secret_phrase.encode('utf-8'), 154 | salt=DB_PBKDF_SALT, 155 | hash_name='sha256', 156 | iterations=PBKDF_ITERATIONS) 157 | 158 | # NB: New iv and ephemeral key are created each time we encrypt 159 | iv = os.urandom(16) 160 | tmp_key = os.urandom(32) 161 | enc_tmp_key = _aes_encrypt_with_iv(secret, iv, tmp_key) 162 | 163 | ciphertext = _aes_encrypt_with_iv(tmp_key, iv, message_bytes) 164 | 165 | # NB: we prepend the ciphertext with its iv (16 bytes) 166 | # and the ephemeral key (48 bytes) 167 | encrypted_message_bytes = iv + enc_tmp_key + ciphertext 168 | return encrypted_message_bytes 169 | 170 | 171 | def decode_aes(encrypted_message_bytes, secret_phrase): # type: ignore 172 | '''Decrypts a message with a phrase 173 | Args: 174 | encrypted_message_bytes (bytes): the encrypted message, prepended with 175 | its iv and ephemeral key 176 | secret_phrase (str): the user's db encryption phrase 177 | Returns: 178 | (bytes): the decrypted message 179 | ''' 180 | secret = pbkdf2_hmac( 181 | data=secret_phrase.encode('utf-8'), 182 | salt=DB_PBKDF_SALT, 183 | hash_name='sha256', 184 | iterations=PBKDF_ITERATIONS) 185 | 186 | # NB: Extract the iv and encrypted key from the message 187 | iv = encrypted_message_bytes[:16] 188 | enc_tmp_key = encrypted_message_bytes[16:64] 189 | encrypted_message_bytes = encrypted_message_bytes[64:] 190 | 191 | # NB: Decrypt the key and use it to decrypt the message 192 | tmp_key = _aes_decrypt_with_iv(secret, iv, enc_tmp_key) 193 | message_bytes = _aes_decrypt_with_iv(tmp_key, iv, encrypted_message_bytes) 194 | 195 | return message_bytes 196 | 197 | 198 | class TerminateProtected: 199 | """ Protect a piece of code from being killed by SIGINT or SIGTERM. 200 | It can still be killed by a force kill. 201 | 202 | https://stackoverflow.com/questions/18499497/how-to-process-sigterm-signal-gracefully 203 | 204 | Example: 205 | with TerminateProtected(): 206 | run_func_1() 207 | run_func_2() 208 | 209 | Both functions will be executed even if sigterm or sigkill received 210 | """ 211 | killed = False 212 | 213 | def _handler(self, signum, frame): # type: ignore 214 | logging.error('Received SIGINT or SIGTERM!' 215 | 'Finishing this block, then exiting.') 216 | self.killed = True 217 | 218 | def __enter__(self): # type: ignore 219 | self.old_sigint = signal.signal(signal.SIGINT, self._handler) 220 | self.old_sigterm = signal.signal(signal.SIGTERM, self._handler) 221 | 222 | def __exit__(self, type, value, traceback): # type: ignore 223 | if self.killed: 224 | sys.exit(0) 225 | signal.signal(signal.SIGINT, self.old_sigint) 226 | signal.signal(signal.SIGTERM, self.old_sigterm) 227 | 228 | 229 | # --------------------------------------------------------- 230 | # 231 | # Copyright 2014 Corgan Labs 232 | # 233 | # The MIT License (MIT) 234 | # 235 | # Permission is hereby granted, free of charge, to any person obtaining a copy 236 | # of this software and associated documentation files (the "Software"), to deal 237 | # in the Software without restriction, including without limitation the rights 238 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 239 | # copies of the Software, and to permit persons to whom the Software is 240 | # furnished to do so, subject to the following conditions: 241 | # 242 | # The above copyright notice and this permission notice shall be included in 243 | # all copies or substantial portions of the Software. 244 | # 245 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 246 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 247 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 248 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 249 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 250 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 251 | # THE SOFTWARE. 252 | 253 | 254 | def to_pubkey(privkey_obj: ecdsa.SigningKey) -> bytes: 255 | """ 256 | Return compressed public key encoding 257 | Adapted from prusnak's bip32utils 258 | https://github.com/prusnak/bip32utils/ 259 | https://github.com/prusnak/bip32utils/blob/master/LICENSE 260 | 261 | ecdsa.SigningKey -> bytes 262 | """ 263 | ck = b'' 264 | pubkey_obj = privkey_obj.get_verifying_key() 265 | padx = (b'\0' * 32 + int_to_string(pubkey_obj.pubkey.point.x()))[-32:] 266 | if pubkey_obj.pubkey.point.y() & 1: 267 | ck = b'\3' + padx 268 | else: 269 | ck = b'\2' + padx 270 | return ck 271 | -------------------------------------------------------------------------------- /scripts/utxo_setup.py: -------------------------------------------------------------------------------- 1 | from riemann import simple 2 | 3 | from typing import cast 4 | from riemann.tx import Tx 5 | 6 | 7 | def generate_small_utxos( 8 | tx_id: str, 9 | index: int, 10 | prevout_value: int, 11 | recipient_addr: str, 12 | num_outputs: int, 13 | fee: int, 14 | change_addr: str, 15 | size: int = 550) -> Tx: 16 | ''' 17 | Makes new utxos. 18 | All utxos have the same address (i.e. the same keypair) 19 | 20 | Args: 21 | tx_id (str): txid of parent tx 22 | index (int): index of input in parent tx 23 | prevout_value (int): value in satoshi of the input 24 | recipient_addr (str): address of the recipient 25 | num_outputs (int): how many new small UTXOs to make 26 | fee (int): fee to pay in satoshi 27 | change_addr (str): address to send change to 28 | Returns: 29 | (rieman.tx.Tx): The unsigned tx making num_outputs new UTXOs 30 | ''' 31 | # Make the input 32 | outpoint = simple.outpoint(tx_id, index) 33 | tx_ins = [simple.unsigned_input(outpoint)] 34 | 35 | # make small outputs 36 | tx_outs = [simple.output(size, recipient_addr) for i in range(num_outputs)] 37 | 38 | # Make a change output 39 | change = prevout_value - (size * num_outputs) - fee 40 | tx_outs.append(simple.output(change, change_addr)) 41 | 42 | return cast(Tx, simple.unsigned_witness_tx(tx_ins, tx_outs)) 43 | -------------------------------------------------------------------------------- /test/ERC20Swap.test.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract before describe it assert */ 2 | 3 | const BN = require('bn.js'); 4 | const constants = require('./constants.js'); 5 | 6 | const StatelessSwap20 = artifacts.require('StatelessSwap20'); 7 | const DummyERC20 = artifacts.require('DummyERC20'); 8 | 9 | const ETHER = new BN('1000000000000000000', 10); 10 | const DIFF = new BN('7019199231177', 10); 11 | const developerShare = new BN(ETHER.divn(400)); 12 | const bidderShare = new BN(ETHER.sub(developerShare)); 13 | 14 | contract('StatelessSwap20', (accounts) => { 15 | const [developer, seller] = accounts; 16 | 17 | let iac; 18 | let erc20; 19 | let erc20address; 20 | 21 | before(async () => { 22 | iac = await StatelessSwap20.new(developer); 23 | erc20 = await DummyERC20.new(); 24 | erc20address = erc20.address; 25 | }); 26 | 27 | 28 | describe('stateful tests', async () => { 29 | describe('#open', async () => { 30 | it('fails with 0 value', async () => { 31 | try { 32 | await iac.open(constants.GOOD.PARTIAL_TX, DIFF, erc20address, 0, { from: seller }); 33 | assert(false); 34 | } catch (e) { 35 | assert.include(e.message, '_value must be greater than 0'); 36 | } 37 | }); 38 | 39 | it('fails if transferFrom fails', async () => { 40 | await erc20.setError(1); 41 | try { 42 | await iac.open(constants.GOOD.PARTIAL_TX, DIFF, erc20address, ETHER, { from: seller }); 43 | assert(false); 44 | } catch (e) { 45 | assert.include(e.message, 'SafeERC20: ERC20 operation did not succeed.'); 46 | } 47 | await erc20.clearError(); 48 | }); 49 | 50 | it('does not burn ether', async () => { 51 | try { 52 | await iac.open( 53 | constants.GOOD.PARTIAL_TX, 54 | DIFF, 55 | erc20address, 56 | ETHER, 57 | { from: seller, value: ETHER } 58 | ); 59 | assert(false); 60 | } catch (e) { 61 | assert.include(e.message, 'Do not burn ether here please'); 62 | } 63 | }); 64 | }); 65 | 66 | describe('#claim', async () => { 67 | before(async () => { 68 | await iac.open(constants.GOOD.PARTIAL_TX, DIFF, erc20address, ETHER, { from: seller }); 69 | }); 70 | 71 | it('fails if developer transfer fails', async () => { 72 | await erc20.setError(1); 73 | try { 74 | await iac.claim( 75 | constants.GOOD.PROOF, 76 | constants.GOOD.PROOF_INDEX, 77 | constants.GOOD.VERSION, 78 | constants.GOOD.VIN, 79 | constants.GOOD.VOUT, 80 | constants.GOOD.LOCKTIME, 81 | constants.GOOD.HEADER_CHAIN, 82 | { from: seller } 83 | ); 84 | assert(false); 85 | } catch (e) { 86 | assert.include(e.message, 'SafeERC20: ERC20 operation did not succeed.'); 87 | } 88 | await erc20.clearError(); 89 | }); 90 | 91 | it('fails if bidder transfer fails', async () => { 92 | await erc20.setError(2); 93 | try { 94 | await iac.claim( 95 | constants.GOOD.PROOF, 96 | constants.GOOD.PROOF_INDEX, 97 | constants.GOOD.VERSION, 98 | constants.GOOD.VIN, 99 | constants.GOOD.VOUT, 100 | constants.GOOD.LOCKTIME, 101 | constants.GOOD.HEADER_CHAIN, 102 | { from: seller } 103 | ); 104 | assert(false); 105 | } catch (e) { 106 | assert.include(e.message, 'SafeERC20: ERC20 operation did not succeed.'); 107 | } 108 | await erc20.clearError(); 109 | }); 110 | 111 | it('succeeds, and transfers shares to developer and bidder', async () => { 112 | await iac.claim( 113 | constants.GOOD.PROOF, 114 | constants.GOOD.PROOF_INDEX, 115 | constants.GOOD.VERSION, 116 | constants.GOOD.VIN, 117 | constants.GOOD.VOUT, 118 | constants.GOOD.LOCKTIME, 119 | constants.GOOD.HEADER_CHAIN, 120 | { from: seller } 121 | ); 122 | const developerBalance = new BN(await erc20.balanceOf.call(developer), 10); 123 | assert(developerBalance.eq(developerShare)); 124 | 125 | const bidderBalance = new BN(await erc20.balanceOf.call(constants.GOOD.BIDDER), 10); 126 | assert(bidderBalance.eq(bidderShare)); 127 | }); 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/ERC721Swap.test.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract before describe it assert */ 2 | const BN = require('bn.js'); 3 | const constants = require('./constants.js'); 4 | 5 | const StatelessSwap721 = artifacts.require('StatelessSwap721'); 6 | const DummyERC721 = artifacts.require('DummyERC721'); 7 | 8 | const ETHER = new BN('1000000000000000000', 10); 9 | const DIFF = new BN('7019199231177', 10); 10 | 11 | contract('StatelessSwap721', (accounts) => { 12 | const [developer, seller] = accounts; 13 | 14 | let iac; 15 | let erc721; 16 | let erc721address; 17 | 18 | before(async () => { 19 | iac = await StatelessSwap721.new(developer); 20 | erc721 = await DummyERC721.new(); 21 | erc721address = erc721.address; 22 | }); 23 | 24 | describe('stateful tests', async () => { 25 | describe('#open', async () => { 26 | it('fails if transferFrom fails', async () => { 27 | await erc721.setError(1); 28 | try { 29 | await iac.open( 30 | constants.GOOD.PARTIAL_TX, 31 | DIFF, 32 | erc721address, 33 | ETHER, 34 | { from: seller } 35 | ); 36 | assert(false); 37 | } catch (e) { 38 | assert.include(e.message, 'Dummy revert'); 39 | } 40 | erc721.clearError(); 41 | }); 42 | 43 | it('does not burn ether', async () => { 44 | try { 45 | await iac.open( 46 | constants.GOOD.PARTIAL_TX, 47 | DIFF, 48 | erc721address, 49 | ETHER, 50 | { from: seller, value: ETHER } 51 | ); 52 | assert(false); 53 | } catch (e) { 54 | assert.include(e.message, 'Do not burn ether here please'); 55 | } 56 | }); 57 | }); 58 | 59 | describe('#claim', async () => { 60 | before(async () => { 61 | await iac.open( 62 | constants.GOOD.PARTIAL_TX, 63 | DIFF, 64 | erc721address, 65 | ETHER, 66 | { from: seller } 67 | ); 68 | }); 69 | 70 | it('fails if bidder transfer fails', async () => { 71 | await erc721.setError(1); 72 | 73 | try { 74 | await iac.claim( 75 | constants.GOOD.PROOF, 76 | constants.GOOD.PROOF_INDEX, 77 | constants.GOOD.VERSION, 78 | constants.GOOD.VIN, 79 | constants.GOOD.VOUT, 80 | constants.GOOD.LOCKTIME, 81 | constants.GOOD.HEADER_CHAIN, 82 | { from: seller } 83 | ); 84 | assert(false); 85 | } catch (e) { 86 | assert.include(e.message, 'Dummy revert'); 87 | } 88 | await erc721.clearError(); 89 | }); 90 | 91 | it('works', async () => { 92 | await iac.claim( 93 | constants.GOOD.PROOF, 94 | constants.GOOD.PROOF_INDEX, 95 | constants.GOOD.VERSION, 96 | constants.GOOD.VIN, 97 | constants.GOOD.VOUT, 98 | constants.GOOD.LOCKTIME, 99 | constants.GOOD.HEADER_CHAIN, 100 | { from: seller } 101 | ); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/EthSwap.test.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract before describe it assert web3 */ 2 | const BN = require('bn.js'); 3 | const constants = require('./constants.js'); 4 | 5 | const StatelessSwapEth = artifacts.require('StatelessSwapEth'); 6 | 7 | const ETHER = new BN('1000000000000000000', 10); 8 | const DIFF = new BN('7019199231177', 10); 9 | const developerShare = new BN(ETHER.divn(400)); 10 | const bidderShare = new BN(ETHER.sub(developerShare)); 11 | 12 | 13 | contract('StatelessSwapEth', (accounts) => { 14 | const [developer, seller] = accounts; 15 | 16 | let iac; 17 | 18 | before(async () => { 19 | iac = await StatelessSwapEth.new(developer); 20 | }); 21 | 22 | describe('stateful tests', async () => { 23 | describe('#open', async () => { 24 | it('errors if listing was not funded', async () => { 25 | try { 26 | await iac.open( 27 | constants.GOOD.PARTIAL_TX, 28 | DIFF, 29 | constants.ADDR0, 30 | ETHER, 31 | { from: seller, value: 0 } 32 | ); 33 | assert(false); 34 | } catch (e) { 35 | assert.include(e.message, 'No asset received. Listing must be funded on initialization.'); 36 | } 37 | }); 38 | 39 | it('errors if asset is not address0', async () => { 40 | try { 41 | await iac.open( 42 | constants.GOOD.PARTIAL_TX, 43 | DIFF, 44 | developer, 45 | ETHER, 46 | { from: seller, value: 10 ** 18 } 47 | ); 48 | assert(false); 49 | } catch (e) { 50 | assert.include(e.message, 'asset must be zero address for ether listings.'); 51 | } 52 | }); 53 | 54 | it('errors if value is not equal to message.value', async () => { 55 | try { 56 | await iac.open( 57 | constants.GOOD.PARTIAL_TX, 58 | DIFF, 59 | constants.ADDR0, 60 | ETHER, 61 | { from: seller, value: 10 ** 11 } 62 | ); 63 | assert(false); 64 | } catch (e) { 65 | assert.include(e.message, 'value must equal msg.value'); 66 | } 67 | }); 68 | }); 69 | 70 | describe('#claim', async () => { 71 | let developerStartBalance; 72 | let bidderStartBalance; 73 | 74 | before(async () => { 75 | await iac.open( 76 | constants.GOOD.PARTIAL_TX, 77 | DIFF, 78 | constants.ADDR0, 79 | ETHER, 80 | { from: seller, value: ETHER } 81 | ); 82 | 83 | developerStartBalance = new BN(await web3.eth.getBalance(developer), 10); 84 | bidderStartBalance = new BN(await web3.eth.getBalance(constants.GOOD.BIDDER), 10); 85 | }); 86 | 87 | it('returns on success', async () => { 88 | await iac.claim( 89 | constants.GOOD.PROOF, 90 | constants.GOOD.PROOF_INDEX, 91 | constants.GOOD.VERSION, 92 | constants.GOOD.VIN, 93 | constants.GOOD.VOUT, 94 | constants.GOOD.LOCKTIME, 95 | constants.GOOD.HEADER_CHAIN, 96 | { from: seller } 97 | ); 98 | const developerBalance = new BN(await web3.eth.getBalance(developer), 10); 99 | assert(developerBalance.eq(developerStartBalance.add(developerShare))); 100 | const bidderBalance = new BN(await web3.eth.getBalance(constants.GOOD.BIDDER), 10); 101 | assert(bidderBalance.eq(bidderStartBalance.add(bidderShare))); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/StatelessSwap.test.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract before describe it assert web3 */ 2 | 3 | const BN = require('bn.js'); 4 | const constants = require('./constants.js'); 5 | 6 | const DummySwap = artifacts.require('DummySwap'); 7 | 8 | 9 | const ETHER = new BN('1000000000000000000', 10); 10 | const DIFF = new BN('7019199231177', 10); 11 | 12 | contract('StatelessSwap', (accounts) => { 13 | let iac; 14 | let developer; 15 | let seller; 16 | 17 | let aucId; 18 | 19 | before(async () => { 20 | [developer, seller] = accounts; 21 | iac = await DummySwap.new(developer); 22 | }); 23 | 24 | describe('#constructor', async () => { 25 | it('sets the developer address', async () => { 26 | assert.strictEqual(await iac.developer.call(), developer); 27 | }); 28 | }); 29 | 30 | describe('stateful tests', async () => { 31 | describe('#open', async () => { 32 | it('opens a new listing and emits an event', async () => { 33 | const blockNumber = await web3.eth.getBlock('latest').number; 34 | 35 | await iac.open( 36 | constants.GOOD.PARTIAL_TX, 37 | DIFF.addn(1), 38 | constants.ADDR0, 39 | ETHER, 40 | { from: seller, value: 10 ** 18 } 41 | ); 42 | 43 | const eventList = await iac.getPastEvents( 44 | 'ListingActive', 45 | { fromBlock: blockNumber, toBlock: 'latest' } 46 | ); 47 | /* eslint-disable no-underscore-dangle */ 48 | aucId = eventList[0].returnValues._listingID; 49 | assert.strictEqual(eventList[0].returnValues._seller, seller); 50 | /* eslint-enable no-underscore-dangle */ 51 | }); 52 | 53 | it('returns the txid on success', async () => { 54 | assert.strictEqual(aucId, '0x9ff0076d904f8a7125b063f44995fe0d94f05ba759c435fbeb0f0936fb876432'); 55 | }); 56 | 57 | it('adds a new listing to the listings mapping', async () => { 58 | const res = await iac.listings.call(aucId); 59 | assert(res[0].eqn(1)); // state 60 | assert(res[1].eq(ETHER)); // ethValue 61 | }); 62 | 63 | it('errors if listing already exists', async () => { 64 | try { 65 | await iac.open( 66 | constants.GOOD.PARTIAL_TX, 67 | 100, 68 | constants.ADDR0, 69 | ETHER, 70 | { from: seller, value: 10 ** 18 } 71 | ); 72 | assert(false); 73 | } catch (e) { 74 | assert.include(e.message, 'Listing exists.'); 75 | } 76 | }); 77 | }); 78 | 79 | describe('#claim', async () => { 80 | it('errors if total difficulty sum is too low', async () => { 81 | try { 82 | await iac.claim( 83 | constants.GOOD.PROOF, 84 | constants.GOOD.PROOF_INDEX, 85 | constants.GOOD.VERSION, 86 | constants.GOOD.VIN, 87 | constants.GOOD.VOUT, 88 | constants.GOOD.LOCKTIME, 89 | constants.GOOD.HEADER_CHAIN.substring(0, 162), 90 | { from: seller } 91 | ); 92 | assert(false); 93 | } catch (e) { 94 | assert.include(e.message, 'Not enough difficulty in header chain.'); 95 | } 96 | }); 97 | 98 | it('returns on success and emits ListingClosed', async () => { 99 | const blockNumber = await web3.eth.getBlock('latest').number; 100 | 101 | await iac.claim( 102 | constants.GOOD.PROOF, 103 | constants.GOOD.PROOF_INDEX, 104 | constants.GOOD.VERSION, 105 | constants.GOOD.VIN, 106 | constants.GOOD.VOUT, 107 | constants.GOOD.LOCKTIME, 108 | constants.GOOD.HEADER_CHAIN, 109 | { from: seller } 110 | ); 111 | 112 | const eventList = await iac.getPastEvents( 113 | 'ListingClosed', 114 | { fromBlock: blockNumber, toBlock: 'latest' } 115 | ); 116 | /* eslint-disable-next-line no-underscore-dangle */ 117 | assert.strictEqual(eventList[0].returnValues._listingID, aucId); 118 | }); 119 | 120 | it('updates listing state to CLOSED', async () => { 121 | const res = await iac.listings.call(aucId); 122 | assert(res[0].eqn(2)); 123 | }); 124 | 125 | it('errors if listing state is not ACTIVE', async () => { 126 | try { 127 | await iac.claim( 128 | constants.GOOD.PROOF, 129 | constants.GOOD.PROOF_INDEX, 130 | constants.GOOD.VERSION, 131 | constants.GOOD.VIN, 132 | constants.GOOD.VOUT, 133 | constants.GOOD.LOCKTIME, 134 | constants.GOOD.HEADER_CHAIN, 135 | { from: seller } 136 | ); 137 | assert(false); 138 | } catch (e) { 139 | assert.include(e.message, 'Listing has closed or does not exist.'); 140 | } 141 | }); 142 | 143 | it('defauls to the seller when bidder address parsing fails', async () => { 144 | const blockNumber = await web3.eth.getBlock('latest').number; 145 | await iac.open( 146 | constants.FEW_OUTPUTS.PARTIAL_TX, 147 | 0, 148 | constants.ADDR0, 149 | ETHER, 150 | { from: seller, value: 10 ** 18 } 151 | ); 152 | await iac.claim( 153 | constants.FEW_OUTPUTS.PROOF, 154 | constants.FEW_OUTPUTS.PROOF_INDEX, 155 | constants.FEW_OUTPUTS.VERSION, 156 | constants.FEW_OUTPUTS.VIN, 157 | constants.FEW_OUTPUTS.VOUT, 158 | constants.FEW_OUTPUTS.LOCKTIME, 159 | constants.FEW_OUTPUTS.HEADER_CHAIN, 160 | { from: seller } 161 | ); 162 | const eventList = await iac.getPastEvents( 163 | 'ListingClosed', 164 | { fromBlock: blockNumber, toBlock: 'latest' } 165 | ); 166 | /* eslint-disable-next-line no-underscore-dangle */ 167 | assert.strictEqual(eventList[0].returnValues._bidder, seller); 168 | }); 169 | }); 170 | }); 171 | 172 | describe('#allocate', async () => { 173 | it('returns allocated values', async () => { 174 | const res = await iac.allocate.call(ETHER); 175 | assert(res[0].eq(ETHER.divn(400))); 176 | assert(res[1].eq(ETHER.sub(res[0]))); 177 | }); 178 | }); 179 | 180 | describe('#extractBidder', async () => { 181 | it('extracts the bidder from the op_return in the 2nd output', async () => { 182 | const res = await iac.extractBidder(constants.GOOD.VOUT); 183 | assert.strictEqual(res, constants.GOOD.BIDDER); 184 | }); 185 | 186 | it('returns address(0) if there is only 1 output', async () => { 187 | const res = await iac.extractBidder(constants.FEW_OUTPUTS.VOUT); 188 | assert.strictEqual(res, constants.ADDR0); 189 | }); 190 | it('returns address(0) if the 2nd output is not an opreturn', async () => { 191 | const res = await iac.extractBidder(constants.OP_RETURN_WRONG.VOUT); 192 | assert.strictEqual(res, constants.ADDR0); 193 | }); 194 | }); 195 | 196 | describe('#makeAllChecks', async () => { 197 | it('returns the difficutly', async () => { 198 | const res = await iac.makeAllChecks( 199 | constants.GOOD.PROOF, 200 | constants.GOOD.PROOF_INDEX, 201 | constants.GOOD.VERSION, 202 | constants.GOOD.VIN, 203 | constants.GOOD.VOUT, 204 | constants.GOOD.LOCKTIME, 205 | constants.GOOD.HEADER_CHAIN 206 | ); 207 | assert(res.eq(DIFF.muln(7))); 208 | }); 209 | }); 210 | 211 | describe('#checkTx', async () => { 212 | it('validates the vin', async () => { 213 | try { 214 | await iac.checkTx( 215 | constants.GOOD.VERSION, 216 | '0xFF', 217 | constants.GOOD.VOUT, 218 | constants.GOOD.LOCKTIME 219 | ); 220 | assert(false); 221 | } catch (e) { 222 | assert.include(e.message, 'vin is malformed'); 223 | } 224 | }); 225 | 226 | it('validates the vin', async () => { 227 | try { 228 | await iac.checkTx( 229 | constants.GOOD.VERSION, 230 | constants.GOOD.VIN, 231 | '0xFF', 232 | constants.GOOD.LOCKTIME 233 | ); 234 | assert(false); 235 | } catch (e) { 236 | assert.include(e.message, 'vout is malformed'); 237 | } 238 | }); 239 | 240 | it('returns the txid', async () => { 241 | const res = await iac.checkTx( 242 | constants.GOOD.VERSION, 243 | constants.GOOD.VIN, 244 | constants.GOOD.VOUT, 245 | constants.GOOD.LOCKTIME 246 | ); 247 | assert.strictEqual(res, constants.GOOD.TX_ID_LE); 248 | }); 249 | }); 250 | 251 | describe('#checkHeaders', async () => { 252 | it('errors if the header array length is not a multiple of 80', async () => { 253 | try { 254 | await iac.checkHeaders('0xFF'); 255 | assert(false); 256 | } catch (e) { 257 | assert.include(e.message, 'Header bytes not multiple of 80.'); 258 | } 259 | }); 260 | it('errors if the headers are not a valid chain', async () => { 261 | try { 262 | const badChain = `${constants.GOOD.HEADER_CHAIN.substring(0, 162)}${'00'.repeat(160)}`; 263 | await iac.checkHeaders(badChain); 264 | assert(false); 265 | } catch (e) { 266 | assert.include(e.message, 'Header bytes not a valid chain.'); 267 | } 268 | }); 269 | it('errors if the headers do not meet the difficulty target', async () => { 270 | try { 271 | // Change the last byte to make its work low 272 | const badChain = `${constants.GOOD.HEADER_CHAIN.substring(0, 320)}${'ff'}`; 273 | await iac.checkHeaders(badChain); 274 | assert(false); 275 | } catch (e) { 276 | assert.include(e.message, 'Header does not meet its own difficulty target.'); 277 | } 278 | }); 279 | it('returns diff and merkle root', async () => { 280 | const res = await iac.checkHeaders(constants.GOOD.HEADER_CHAIN); 281 | assert(res[0].eq(DIFF.muln(7))); 282 | assert.strictEqual(res[1], constants.GOOD.MERKLE_ROOT); 283 | }); 284 | }); 285 | 286 | describe('#checkProof', async () => { 287 | it('errors on a bad proof', async () => { 288 | try { 289 | await iac.checkProof( 290 | constants.GOOD.TX_ID_LE, 291 | constants.GOOD.MERKLE_ROOT, 292 | constants.GOOD.PROOF, 293 | 3 294 | ); 295 | assert(false); 296 | } catch (e) { 297 | assert.include(e.message, 'Bad inclusion proof'); 298 | } 299 | }); 300 | 301 | it('validates proofs successfully', async () => { 302 | const res = await iac.checkProof( 303 | constants.GOOD.TX_ID_LE, 304 | constants.GOOD.MERKLE_ROOT, 305 | constants.GOOD.PROOF, 306 | constants.GOOD.PROOF_INDEX 307 | ); 308 | assert.isTrue(res); 309 | }); 310 | }); 311 | }); 312 | -------------------------------------------------------------------------------- /test/constants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | exports.ADDR0 = '0x0000000000000000000000000000000000000000'; 4 | 5 | exports.GOOD = { // This is a real on-chain tx and real headers 6 | PARTIAL_TX: '0x010000000001011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff', 7 | OP_RETURN_TX: '0x010000000001011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211024730440220276e0ec78028582054d86614c65bc4bf85ff5710b9d3a248ca28dd311eb2fa6802202ec950dd2a8c9435ff2d400cc45d7a4854ae085f49e05cc3f503834546d410de012103732783eef3af7e04d3af444430a629b16a9261e4025f52bf4d6d026299c37c7400000000', 8 | VERSION: '0x01000000', 9 | VIN: '0x011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff', 10 | VOUT: '0x024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211', 11 | LOCKTIME: '0x00000000', 12 | TX_ID: 'd60033c5cf5c199208a9c656a29967810c4e428c22efb492fdd816e6a0a1e548', 13 | TX_ID_LE: '0x48e5a1a0e616d8fd92b4ef228c424e0c816799a256c6a90892195ccfc53300d6', 14 | MERKLE_ROOT: '0x0296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c2', 15 | HEADER_CHAIN: '0x0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b00000020fe70e48339d6b17fbbf1340d245338f57336e97767cc240000000000000000005af53b865c27c6e9b5e5db4c3ea8e024f8329178a79ddb39f7727ea2fe6e6825d1349c5ba1192817e2d9515900000020baaea6746f4c16ccb7cd961655b636d39b5fe1519b8f15000000000000000000c63a8848a448a43c9e4402bd893f701cd11856e14cbbe026699e8fdc445b35a8d93c9c5ba1192817b945dc6c00000020f402c0b551b944665332466753f1eebb846a64ef24c71700000000000000000033fc68e070964e908d961cd11033896fa6c9b8b76f64a2db7ea928afa7e304257d3f9c5ba11928176164145d0000ff3f63d40efa46403afd71a254b54f2b495b7b0164991c2d22000000000000000000f046dc1b71560b7d0786cfbdb25ae320bd9644c98d5c7c77bf9df05cbe96212758419c5ba1192817a2bb2caa00000020e2d4f0edd5edd80bdcb880535443747c6b22b48fb6200d0000000000000000001d3799aa3eb8d18916f46bf2cf807cb89a9b1b4c56c3f2693711bf1064d9a32435429c5ba1192817752e49ae0000002022dba41dff28b337ee3463bf1ab1acf0e57443e0f7ab1d000000000000000000c3aadcc8def003ecbd1ba514592a18baddddcd3a287ccf74f584b04c5c10044e97479c5ba1192817c341f595', 16 | PROOF_INDEX: 281, 17 | PROOF: '0xe35a0d6de94b656694589964a252957e4673a9fb1d2f8b4a92e3f0a7bb654fddb94e5a1e6d7f7f499fd1be5dd30a73bf5584bf137da5fdd77cc21aeb95b9e35788894be019284bd4fbed6dd6118ac2cb6d26bc4be4e423f55a3a48f2874d8d02a65d9c87d07de21d4dfe7b0a9f4a23cc9a58373e9e6931fefdb5afade5df54c91104048df1ee999240617984e18b6f931e2373673d0195b8c6987d7ff7650d5ce53bcec46e13ab4f2da1146a7fc621ee672f62bc22742486392d75e55e67b09960c3386a0b49e75f1723d6ab28ac9a2028a0c72866e2111d79d4817b88e17c821937847768d92837bae3832bb8e5a4ab4434b97e00a6c10182f211f592409068d6f5652400d9a3d1cc150a7fb692e874cc42d76bdafc842f2fe0f835a7c24d2d60c109b187d64571efbaa8047be85821f8e67e0e85f2f5894bc63d00c2ed9d64', 18 | BIDDER: '0xeDB1b5C2f39AF0feC151732585b1049B07895211', 19 | }; 20 | 21 | exports.OP_RETURN_WRONG = { 22 | // Barney 23 | // unsigned_wit_tx with op_return in wrong position 24 | REQ_DIFF: 0, 25 | PARTIAL_TX: '0x010000000001029746474a9f9dc19f567ae0462fe7f03e18444ff08a544f1501743b81f49a8c9d0000000000feffffff', 26 | OP_RETURN_TX: '0x010000000001029746474a9f9dc19f567ae0462fe7f03e18444ff08a544f1501743b81f49a8c9d0000000000feffffffae84f5d593339f710b25b561dd1a20d2fcfbbcdbc879cf4b9cfb6b191cf99e8b0000000000feffffff030000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbbe8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b000000000000001600140bd9d9f93c30beb1ee38820f6d91d89831cafa3a000000000000', 27 | VERSION: '0x01000000', 28 | VIN: '0x029746474a9f9dc19f567ae0462fe7f03e18444ff08a544f1501743b81f49a8c9d0000000000feffffffae84f5d593339f710b25b561dd1a20d2fcfbbcdbc879cf4b9cfb6b191cf99e8b0000000000feffffff', 29 | VOUT: '0x030000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbbe8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b000000000000001600140bd9d9f93c30beb1ee38820f6d91d89831cafa3a', 30 | LOCKTIME: '0x00000000', 31 | TX_ID: 'c20823f9a119b7747d973f009e9e5893b4fd5cc8d5511aec27b4c6ecb96d2b4a', 32 | HEADER_CHAIN: '0xbbbbbbbb7777777777777777777777777777777777777777777777777777777777777777c20823f9a119b7747d973f009e9e5893b4fd5cc8d5511aec27b4c6ecb96d2b4accccccccffff001f87240000bbbbbbbbf3c2f5dcb6427a3311fa05827d42b068d0dfa00daa23302a56090c7022a300008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001fae5e0000bbbbbbbbee496b970cc0130866075d6c2f057a7b36b5a1d3798a31b0e1a60520bd9d00008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001f348e0000', 33 | PROOF_INDEX: 0, 34 | PROOF: '0x', 35 | // BIDDER: // it's broken 36 | }; 37 | 38 | exports.WORK_TOO_LOW = { 39 | PARTIAL_TX: '0x010000000001012746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff', 40 | OP_RETURN_TX: '0x010000000001012746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211024730440220276e0ec78028582054d86614c65bc4bf85ff5710b9d3a248ca28dd311eb2fa6802202ec950dd2a8c9435ff2d400cc45d7a4854ae085f49e05cc3f503834546d410de012103732783eef3af7e04d3af444430a629b16a9261e4025f52bf4d6d026299c37c7400000000', 41 | VERSION: '0x01000000', 42 | VIN: '0x012746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff', 43 | VOUT: '0x024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211', 44 | LOCKTIME: '0x00000000', 45 | TX_ID: '94ef67eb3406ed0ef0caa9984d3e252b79499d8afc93712239100efa804e53ab', 46 | HEADER_CHAIN: '0xbbbbbbbb7777777777777777777777777777777777777777777777777777777777777777e0e333d0fd648162d344c1a760a319f2184ab2dce1335353f36da2eea155f97fccccccccffff001fe85f0000bbbbbbbbcbee0f1f713bdfca4aa550474f7f252581268935ef8948f18d48ec0a2b4800008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001f01440000bbbbbbbbfe6c72f9b42e11c339a9cbe1185b2e16b74acce90c8316f4a5c8a6c0a10f00008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001f30340000', 47 | PROOF_INDEX: 0, 48 | PROOF: '0x', 49 | BIDDER: '0xedb1b5c2f39af0fec151732585b1049b07895211', 50 | }; 51 | 52 | exports.FEW_OUTPUTS = { 53 | PARTIAL_TX: '0x010000000001014e35f7fe339956b6c4f8e2d87d0b2df489098d8f9b19325829fd7c5553c43c0b0000000000feffffff01e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000', 54 | OP_RETURN_TX: '0x010000000001014e35f7fe339956b6c4f8e2d87d0b2df489098d8f9b19325829fd7c5553c43c0b0000000000feffffff01e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000', 55 | VERSION: '0x01000000', 56 | VIN: '0x014e35f7fe339956b6c4f8e2d87d0b2df489098d8f9b19325829fd7c5553c43c0b0000000000feffffff', 57 | VOUT: '0x01e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c', 58 | LOCKTIME: '0x00000000', 59 | TX_ID: 'e926eb39ff0d76ef45df6533631adcc95ff1fbbb5f300755ed34a4450417955a', 60 | TX_ID_LE: '0x5a95170445a434ed5507305fbbfbf15fc9dc1a633365df45ef760dff39eb26e9', 61 | HEADER_CHAIN: '0xbbbbbbbb77777777777777777777777777777777777777777777777777777777777777775a95170445a434ed5507305fbbfbf15fc9dc1a633365df45ef760dff39eb26e9ccccccccffff001f69830100bbbbbbbb889f11490cea4fd91feafa2068c6fadb41fcd2805983bdcf892293401ac900008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001fda060000bbbbbbbb19aee5389344753223bc7545d0af060dd201644e1c8cc9ce89c14739e6d100008888888888888888888888888888888888888888888888888888888888888888ccccccccffff001f6f730000', 62 | PROOF_INDEX: 0, 63 | PROOF: '0x', 64 | BIDDER: '0x7849e6bf5e4b1ba7235572d1b0cbc094f0213e6c', 65 | }; 66 | 67 | // unsigned_wit_tx, no bidder input (Mr. Wind-Up Bird) 68 | // 01000000000101b024559b3b6b43df1fd4f3f217861f443dd3c3f897453b2b7518564ac41763850000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbb7b00000000000000160014bb4de20c5c49cc739d17f2527d51e8f99c707b130000000000 69 | // 70 | // unsigned_wit_tx, 3 inputs (Owen) 71 | // 010000000001034b7900f26f7c401601f87ea1714b4455ff5f76f409018ec98e0b36e53f2228b20000000000feffffffa0414e265dedd1ad0a661598172ef4b8a1c21f8f0638ab0845be829e118485990000000000feffffff77a2e6ad4f4a169514160671516230650f39e1beb930f1a6e633cbbe58f3736f0000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbb7b0000000000000016001456a7fd481dc74897f63f77ea52b8f07f9e5c18da00000000000000 72 | // 73 | // unsigned_wit_tx, killed change output (Luke) 74 | // 010000000001027ab3b57672332e8e9c62e331d2494bfd0044a1f72932544c31895cd9894dc9160000000000feffffff6d5c177714032f20ef42c2c1fc89b45b1a0ba8f3c72f5d34e0afc1beb8e3282c0000000000feffffff02e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbb000000000000 75 | // signed_wit_tx, killed change output (Guido) 76 | // 010000000001027ab3b57672332e8e9c62e331d2494bfd0044a1f72932544c31895cd9894dc9160000000000feffffff6d5c177714032f20ef42c2c1fc89b45b1a0ba8f3c72f5d34e0afc1beb8e3282c0000000000feffffff02e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbb0247304402200f8fa7eda4155d8a4440f731a100e6e8bf411d7d1cc7c3eb6ce96eb35e4342ce02202405ab0dc9e847b8267fcfc62714b40e0dd483efc22945d85d0847acc6c987b7832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc2024730440220611e5b9a3948ad19c44b7661c531d8035137c8647483e4e5668a8256fba6b4dd022055ecca2b835392c543b5a6346e14798b0d20c7e6dd26be102e52ed2f4c00240c012102ebc42e14245d561bb79a4839d95aaec3b79b34ad31aafd709e9347f0d8a331eb00000000 77 | // 78 | // unsigned_wit_tx -- killed op_return_output (Emilio) 79 | // 01000000000102d7bf72dd33b69c882a1e09f1658cc2389b06ee943f07b52088b25ca0ee6171960000000000feffffffa01f566b5bc76c6354f685b69addba402a8a911ab588651fe26f32007735b4470000000000feffffff02e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b00000000000000160014e4392b75b5feaab1ad8a90e47da6e29aad8a3b62000000000000 80 | // signed_wit_tx -- killed op_return_output (Ted) 81 | // 01000000000102d7bf72dd33b69c882a1e09f1658cc2389b06ee943f07b52088b25ca0ee6171960000000000feffffffa01f566b5bc76c6354f685b69addba402a8a911ab588651fe26f32007735b4470000000000feffffff02e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b00000000000000160014e4392b75b5feaab1ad8a90e47da6e29aad8a3b620247304402207e025c40f1569aab243c327dac79b839cff740521435c55a68f3c59a8664baf0022014b5ba1cb2b1f5820a65b4b195af7f7591c85d725b40ce3bfcd9ba8aae82b306832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc202483045022100ec137eb1bcf483469e8004b04d82275ebbb87ebeb632475f0da3b6df762a274102206d4d252b9f92e12f127542099e3cfb03e0a04ce7374361544a464b3a03c4b6c6012103cd51750b586f6fb89104b7471a76e63b20659d22280beb19a16689b1bbfcdefd00000000 82 | // 83 | // unsigned_wit_tx with op_return in wrong position (Barney) 84 | // 010000000001029746474a9f9dc19f567ae0462fe7f03e18444ff08a544f1501743b81f49a8c9d0000000000feffffffae84f5d593339f710b25b561dd1a20d2fcfbbcdbc879cf4b9cfb6b191cf99e8b0000000000feffffff030000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbbe8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b000000000000001600140bd9d9f93c30beb1ee38820f6d91d89831cafa3a000000000000 85 | // signed_wit_tx with op_return in wrong position (Ross) 86 | // 010000000001029746474a9f9dc19f567ae0462fe7f03e18444ff08a544f1501743b81f49a8c9d0000000000feffffffae84f5d593339f710b25b561dd1a20d2fcfbbcdbc879cf4b9cfb6b191cf99e8b0000000000feffffff030000000000000000166a1423d81b160cb51f763e7bf9b373a34f5ddb75fcbbe8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c7b000000000000001600140bd9d9f93c30beb1ee38820f6d91d89831cafa3a0247304402202b6ca6baa0c65c54e528dee8bec26ca66432e04c6d925ffe0fa1a4746d610be70220054d67967008e6edd0cbfc43958ac30e061e12dbd948b667b363a83b444bb3b5832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc202483045022100bb6742a5159ecdca8a70d259caa11c5c5f7f0cc77b755e25e2487a2f9aeaba13022059ea38f07a0baac7ba74068ac18bffb64b4b6d5838b1f0e588b6e8e7405a862c012102447418612e8d57a8f3e0bf02297a9508e64a5a8e1a159b5a5c7c4dbdd8685b2f00000000 87 | // 88 | // unsigned_wit_tx with 3 outs but no OP_RETURN output (Rachel) 89 | // 01000000000102c93a8841546251348a7473d88c82cfc0bf4fd8644945269ce0f507aa548b71570000000000feffffff0c3f2c78bf39ad77197b031a82bcbc66a50c1da41b6952716209dbe6b0a12ec20000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6ce8cd9a3b000000001600142daa0f6d60faf6c49605504a567a8bca3b9bf5027b0000000000000016001461523346362da430c795042b4cf388699e9bf8fd000000000000 90 | // signed_wit_tx with 3 outs but no OP_RETURN output (Monica) 91 | // 01000000000102c93a8841546251348a7473d88c82cfc0bf4fd8644945269ce0f507aa548b71570000000000feffffff0c3f2c78bf39ad77197b031a82bcbc66a50c1da41b6952716209dbe6b0a12ec20000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6ce8cd9a3b000000001600142daa0f6d60faf6c49605504a567a8bca3b9bf5027b0000000000000016001461523346362da430c795042b4cf388699e9bf8fd0247304402202e7612a8249feb2500c5723494bdefed30ea3a6ae57207745db445f17b04415f02205b8dcc817d116029a18bf16483715bc9aaa765569892b51da233f0dee8cd873a832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc2024730440220491d5cf69c6bd0aebdb1850a7ddcad47f3f551497209c54f664b5eef1c73dfbf02204163478290c9360792361cfbf2daac569a554a6ced99c113377014ca3fe8a4f5012102ea2f2dc5d55b67bab212ccb9b16a4b4a6e74fea16b10cc15bb618e5e6507409b00000000 92 | // 93 | // unsigned_wit_tx with op_return to random hex address (fails checksum) (Joey) 94 | // 01000000000102bc7d691de7224bdce3d942bf653894577f1360c60a7ecc6f7343468f628f6d6a0000000000feffffff7a8f17bbae730451b0d305410197a45cc0ade4c8b10c4dc996044aefce36151f0000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a14985bbaa4ffa37bec575e5660d7370a493f44194a7b0000000000000016001499d060056059f089b54ada2c970bd0ddc1417f21000000000000 95 | // signed_wit_tx with op_return to random hex address (fails checksum) (Chandler) 96 | // 01000000000102bc7d691de7224bdce3d942bf653894577f1360c60a7ecc6f7343468f628f6d6a0000000000feffffff7a8f17bbae730451b0d305410197a45cc0ade4c8b10c4dc996044aefce36151f0000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a14985bbaa4ffa37bec575e5660d7370a493f44194a7b0000000000000016001499d060056059f089b54ada2c970bd0ddc1417f2102483045022100be1553a6b0a4ee2c380370a06f099f5e8d1d685b58fbade8c64e35cc19a001ac0220100f3f0b9f466518541b9bd36fc41392584f79a95ecaaa6c5f20b59d34f7ad1e832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc202483045022100a2c576cbcda4e42a87aacad5ca33b4fb18c0e6fa39a1a8bd0e9f3d3ea7ed7d8f022010f759b87d08b9e9e27a3c09b758f333b463df9b9feb15d217c64198ad744b8d0121025e6c72a58f2d3b6b2d87966631dead2134985af0e9c099718443e8ad5a74b5b400000000 97 | // 98 | // unsigned_wit_tx with op_return confirmed invalid address (Phoebe) 99 | // 010000000001026fa160dbd05ca476d786d73b319d22daa7ca17f72baa880117433059d1b659a60000000000feffffff61f1e7c36c1bcc50da5f3ba1e8a82c8f17d5d368f448fc51b51835db0f30af190000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a148af1c1357ceb552eabe834f93d27097d3103c2b47b00000000000000160014b12c6eb58f2d2aa2e8e1d3a5fd43248fa6a4e753000000000000 100 | // signed_wit_tx with op_return confirmed invalid address (Gunther) 101 | // 010000000001026fa160dbd05ca476d786d73b319d22daa7ca17f72baa880117433059d1b659a60000000000feffffff61f1e7c36c1bcc50da5f3ba1e8a82c8f17d5d368f448fc51b51835db0f30af190000000000feffffff03e8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c0000000000000000166a148af1c1357ceb552eabe834f93d27097d3103c2b47b00000000000000160014b12c6eb58f2d2aa2e8e1d3a5fd43248fa6a4e7530247304402206035a8526aa701d8819dcbae862d6ecf453580b271a5702b0d436dde26c689ae022054652c23280a4af86b171471628edcd87e7f5ba3654eac52c3b8dfcc8978f11e832102a004b949e4769ed341064829137b18992be884da5932c755e48f9465c1069dc202483045022100d2d38c43d57f44553fd6ea75c319cd9903241636175ee455537d9a563d582c9f02204c31a289578c39376d19d30804e854a7b0db2f9be25c8a21b6aebe0d1b21804a012102ed431feb193d77f61f52891426c90a9a78859e7d74a5600c587db4a70b69445500000000 102 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line camelcase 2 | async function deploySystem(deployList) { 3 | const deployed = {}; // name: contract object 4 | const linkable = {}; // name: linkable address 5 | 6 | for (let i = 0; i < deployList.length; i += 1) { 7 | let { args } = deployList[i]; 8 | if (!args) { 9 | args = []; 10 | } 11 | 12 | // eslint-disable-next-line no-await-in-loop 13 | await deployList[i].contract.link(linkable); 14 | 15 | // eslint-disable-next-line no-await-in-loop 16 | const contract = await deployList[i].contract.new(...args); 17 | linkable[deployList[i].name] = contract.address; 18 | deployed[deployList[i].name] = contract; 19 | } 20 | return deployed; 21 | } 22 | 23 | module.exports = { 24 | deploySystem 25 | }; 26 | 27 | 28 | // The MIT License (MIT) 29 | // 30 | // Copyright (c) 2016 Smart Contract Solutions, Inc. 31 | // 32 | // Permission is hereby granted, free of charge, to any person obtaining 33 | // a copy of this software and associated documentation files (the 34 | // "Software"), to deal in the Software without restriction, including 35 | // without limitation the rights to use, copy, modify, merge, publish, 36 | // distribute, sublicense, and/or sell copies of the Software, and to 37 | // permit persons to whom the Software is furnished to do so, subject to 38 | // the following conditions: 39 | // 40 | // The above copyright notice and this permission notice shall be included 41 | // in all copies or substantial portions of the Software. 42 | // 43 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 44 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 45 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 46 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 47 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 48 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 49 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | require('dotenv').config(); 4 | 5 | const Kit = require('@celo/contractkit') 6 | 7 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 8 | const infuraKey = process.env.SUMMA_RELAY_DEPLOY_INFURA_KEY; 9 | const mnemonic = process.env.MNEMONIC; 10 | 11 | const ropsten = { 12 | provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/${infuraKey}`), 13 | network_id: 3, 14 | gas: 5500000, 15 | confirmations: 2, 16 | timeoutBlocks: 200 17 | } 18 | 19 | const kovan = { 20 | provider: () => new HDWalletProvider(mnemonic, `https://kovan.infura.io/v3/${infuraKey}`), 21 | network_id: 42, 22 | gas: 5500000, 23 | confirmations: 2, 24 | timeoutBlocks: 200 25 | } 26 | 27 | const alfajores = { 28 | provider: () => { 29 | const provider = new HDWalletProvider(mnemonic, 'http://127.0.0.1:9999'); // sinkhole any requests 30 | // slip44 31 | const celoBIP44 = "m/44'/52752'/0'/0/0"; 32 | const hdkey = provider.hdwallet.derivePath(celoBIP44); 33 | // Get the privkey and hand it to the kit 34 | const privkey = hdkey._hdkey.privateKey.toString('hex'); 35 | const kit = Kit.newKit('https://alfajores-forno.celo-testnet.org'); 36 | kit.addAccount(privkey); 37 | return kit.web3.currentProvider; 38 | }, 39 | network_id: 44786, 40 | gas: 5500000, 41 | confirmations: 2, 42 | timeoutBlocks: 200 43 | } 44 | 45 | module.exports = { 46 | api_keys: { 47 | etherscan: process.env.ETHERSCAN_KEY 48 | }, 49 | plugins: [ 50 | 'truffle-plugin-verify' 51 | ], 52 | 53 | networks: { 54 | coverage: { 55 | host: "localhost", 56 | network_id: "*", 57 | port: 8555, 58 | gas: 0xfffffffffff, 59 | gasPrice: 0x01 60 | }, 61 | 62 | ropsten: ropsten, 63 | kovan: kovan, 64 | alfajores: alfajores, 65 | }, 66 | 67 | compilers: { 68 | solc: { 69 | version: "0.5.10", 70 | settings: { 71 | optimizer: { 72 | enabled: true, 73 | runs: 200 74 | } 75 | } 76 | } 77 | } 78 | }; 79 | --------------------------------------------------------------------------------