├── .gitignore ├── LICENSE.md ├── README.md ├── actors ├── .gitignore ├── package.json ├── src │ ├── CrapDapp.abi.json │ ├── attack-dapp.js │ └── env.js └── yarn.lock ├── contracts ├── .gitignore ├── build │ ├── .gitkeep │ ├── CrapDapp.abi │ ├── IEIP1271Validator.abi │ ├── IERC20.abi │ ├── LibSafeMath.abi │ ├── Siphon.abi │ ├── TestBalanceChanger.abi │ ├── TokenBalanceCheckCallWrapper.abi │ └── TronToken.abi ├── deployments.json ├── lib │ └── migrate.js ├── package.json ├── src │ ├── CrapDapp.sol │ ├── IEIP1271Validator.sol │ ├── IERC20.sol │ ├── LibSafeMath.sol │ ├── Siphon.sol │ ├── TestBalanceChanger.sol │ ├── TokenBalanceCheckCallWrapper.sol │ └── TronToken.sol ├── test │ ├── live_wrapper.js │ └── siphon.js └── yarn.lock ├── out-front-browser ├── .gitignore ├── .npmrc ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public │ ├── CNAME │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.less │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── AdminPage.tsx │ │ ├── AttemptForm.tsx │ │ ├── InputPage.tsx │ │ ├── LandingPage.tsx │ │ ├── RouterContainer.tsx │ │ └── WhitelistForm.tsx │ ├── data │ │ ├── AIO.json │ │ ├── AIO.ts │ │ ├── IERC20.json │ │ └── deployments.json │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── theme.less │ └── types.d.ts └── tsconfig.json └── out-front-node ├── .npmrc ├── README.md ├── package-lock.json ├── package.json ├── src ├── IERC20.abi.json ├── Siphon.abi.json ├── TokenBalanceCheckCallWrapper.bin-runtime ├── env.js ├── erc20.js ├── index.js ├── readpipe.js ├── rescue.js ├── tx-validator.js ├── util.js ├── web3.js └── writepipe.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/*.log 3 | **/.env 4 | /secrets.json 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 It's Too Loud In Here 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # out-front 2 | 3 | A service that monitors pending transactions for unauthorized transfers of ERC20 tokens from wallets and fires off a competing transaction at a higher gas price to divert funds into a rescue wallet. The system is non-custodial and uses a signed (EIP712) permission token to execute recovery 4 | 5 | ## origin 6 | 7 | This was built during the [EthDenver](https://ethdenver.com) 2020 hackathon. 8 | 9 | ## demo video 10 | 11 | [Hackathon finals pitch](https://ytcropper.com/cropped/hF5e4a07f022c85) 12 | 13 | [Full hackathon presentation](https://youtu.be/hFr8ly7EFJs) 14 | 15 | ## current status 16 | 17 | The project is currently a working POC. 18 | 19 | Running the project requires API keys for [Blocknative](https://blocknative.com) and/or [Radar Deploy](https://deploy.radar.tech). But it can be set up to use a personal geth node. 20 | 21 | ## goals 22 | 23 | We plan to add documentation on how this works and how to run the code yourself. 24 | 25 | # usage 26 | 27 | Coming soon... 28 | 29 | 30 | ### team members/contact: 31 | - Jaden McConkey : [@J2R5M3](https://twitter.com/J2R5M3) 32 | - Jordan Cason : [CryptRillionair](https://twitter.com/CryptRillionair) 33 | - Lawrence Forman : [@merkeljerk](https://twitter.com/merklejerk) 34 | - Justin Schuldt : [@justinschuldt](https://twitter.com/justinschuldt) 35 | 36 | -------------------------------------------------------------------------------- /actors/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinschuldt/out-front/58551ee97888ba2bdd7daa198138e47837b3f41c/actors/.gitignore -------------------------------------------------------------------------------- /actors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actors", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "Apache-2.0", 6 | "private": true, 7 | "scripts": { 8 | "attack": "node ./src/attack-dapp.js" 9 | }, 10 | "dependencies": { 11 | "bignumber.js": "^9.0.0", 12 | "colors": "^1.4.0", 13 | "dotenv": "^8.2.0", 14 | "flex-contract": "^2.1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /actors/src/CrapDapp.abi.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"victim","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exploit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"victim","type":"address"}],"name":"getVictimBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /actors/src/attack-dapp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('colors'); 3 | const process = require('process'); 4 | const BigNumber = require('bignumber.js'); 5 | const FlexContract = require('flex-contract'); 6 | const DAPP_ABI = require('./CrapDapp.abi.json'); 7 | 8 | const env = require('./env'); 9 | 10 | const DRAIN_PCT = 0.005; 11 | 12 | (async () => { 13 | const dapp = new FlexContract(DAPP_ABI, { address: env.dapp, providerURI: env.providerURI }); 14 | const balance = await dapp.getVictimBalance(env.token, env.victim).call({ from: env.attacker }); 15 | const amount = new BigNumber(balance).times(DRAIN_PCT).integerValue(); 16 | const tx = dapp.exploit(env.token, env.victim, amount).send({ key: env.attackerPrivateKey }); 17 | const txId = await tx.txId; 18 | console.log(`Sent attack TX ${txId.bold}!`); 19 | const receipt = await tx; 20 | console.log(`Transaction mined.`); 21 | process.exit(0); 22 | })(); 23 | -------------------------------------------------------------------------------- /actors/src/env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path'); 3 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 4 | const process = require('process'); 5 | const DEPLOYMENTS = require('../../contracts/deployments.json'); 6 | 7 | const NETWORK = process.env.NETWORK || 'ropsten'; 8 | module.exports = { 9 | network: NETWORK, 10 | victim: process.env.USER_ADDRESS.toLowerCase(), 11 | attacker: process.env.ATTACKER_ADDRESS.toLowerCase(), 12 | attackerPrivateKey: process.env.ATTACKER_PRIVATE_KEY, 13 | dapp: DEPLOYMENTS[NETWORK].CrapDapp, 14 | token: NETWORK !== 'main' 15 | ? DEPLOYMENTS[NETWORK].TronToken 16 | : process.env.MAINNET_USER_TOKEN, 17 | gasBonus: 1.25, 18 | providerURI: NETWORK !== 'main' 19 | ? process.env.TESTNET_PROVIDER 20 | : process.env.MAINNET_PROVIDER, 21 | }; 22 | -------------------------------------------------------------------------------- /actors/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.4.4": 6 | version "7.8.4" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" 8 | integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== 9 | dependencies: 10 | regenerator-runtime "^0.13.2" 11 | 12 | "@types/bn.js@^4.11.3": 13 | version "4.11.6" 14 | resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" 15 | integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== 16 | dependencies: 17 | "@types/node" "*" 18 | 19 | "@types/node@*": 20 | version "13.7.1" 21 | resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d" 22 | integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA== 23 | 24 | "@types/node@^10.3.2": 25 | version "10.17.15" 26 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.15.tgz#bfff4e23e9e70be6eec450419d51e18de1daf8e7" 27 | integrity sha512-daFGV9GSs6USfPgxceDA8nlSe48XrVCJfDeYm7eokxq/ye7iuOH87hKXgMtEAVLFapkczbZsx868PMDT1Y0a6A== 28 | 29 | aes-js@3.0.0: 30 | version "3.0.0" 31 | resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" 32 | integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0= 33 | 34 | bignumber.js@^9.0.0: 35 | version "9.0.0" 36 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" 37 | integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== 38 | 39 | bindings@^1.5.0: 40 | version "1.5.0" 41 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 42 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 43 | dependencies: 44 | file-uri-to-path "1.0.0" 45 | 46 | bip66@^1.1.5: 47 | version "1.1.5" 48 | resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" 49 | integrity sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI= 50 | dependencies: 51 | safe-buffer "^5.0.1" 52 | 53 | bn.js@4.11.6: 54 | version "4.11.6" 55 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" 56 | integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= 57 | 58 | bn.js@4.11.8, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.4.0: 59 | version "4.11.8" 60 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" 61 | integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== 62 | 63 | brorand@^1.0.1: 64 | version "1.1.0" 65 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" 66 | integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= 67 | 68 | browserify-aes@^1.0.6: 69 | version "1.2.0" 70 | resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" 71 | integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== 72 | dependencies: 73 | buffer-xor "^1.0.3" 74 | cipher-base "^1.0.0" 75 | create-hash "^1.1.0" 76 | evp_bytestokey "^1.0.3" 77 | inherits "^2.0.1" 78 | safe-buffer "^5.0.1" 79 | 80 | buffer-to-arraybuffer@^0.0.5: 81 | version "0.0.5" 82 | resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" 83 | integrity sha1-YGSkD6dutDxyOrqe+PbhIW0QURo= 84 | 85 | buffer-xor@^1.0.3: 86 | version "1.0.3" 87 | resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 88 | integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= 89 | 90 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: 91 | version "1.0.4" 92 | resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" 93 | integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== 94 | dependencies: 95 | inherits "^2.0.1" 96 | safe-buffer "^5.0.1" 97 | 98 | colors@^1.4.0: 99 | version "1.4.0" 100 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" 101 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== 102 | 103 | cookiejar@^2.1.1: 104 | version "2.1.2" 105 | resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" 106 | integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== 107 | 108 | create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: 109 | version "1.2.0" 110 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" 111 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== 112 | dependencies: 113 | cipher-base "^1.0.1" 114 | inherits "^2.0.1" 115 | md5.js "^1.3.4" 116 | ripemd160 "^2.0.1" 117 | sha.js "^2.4.0" 118 | 119 | create-hmac@^1.1.4: 120 | version "1.1.7" 121 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" 122 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== 123 | dependencies: 124 | cipher-base "^1.0.3" 125 | create-hash "^1.1.0" 126 | inherits "^2.0.1" 127 | ripemd160 "^2.0.0" 128 | safe-buffer "^5.0.1" 129 | sha.js "^2.4.8" 130 | 131 | create-web3-provider@^1.5.4: 132 | version "1.5.4" 133 | resolved "https://registry.yarnpkg.com/create-web3-provider/-/create-web3-provider-1.5.4.tgz#02c81330a083408475df1c0c98c3b2c393bd0fca" 134 | integrity sha512-Pr0zG4UylrDHmfZgVGftXbwn+hDWdkxzRBlUB8GLhI/H16HmrXcImA/XPqGgPUI2LmYAivCRjrTva3LNOdVHyg== 135 | dependencies: 136 | "@babel/runtime" "^7.4.4" 137 | ethereumjs-util "^6.1.0" 138 | eventemitter3 "^3.1.2" 139 | lodash "^4.17.14" 140 | url-parse "^1.4.7" 141 | websocket "^1.0.28" 142 | xhr2-cookies "^1.1.0" 143 | 144 | d@1, d@^1.0.1: 145 | version "1.0.1" 146 | resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" 147 | integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== 148 | dependencies: 149 | es5-ext "^0.10.50" 150 | type "^1.0.1" 151 | 152 | debug@^2.2.0: 153 | version "2.6.9" 154 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 155 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 156 | dependencies: 157 | ms "2.0.0" 158 | 159 | decode-uri-component@^0.2.0: 160 | version "0.2.0" 161 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" 162 | integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= 163 | 164 | decompress-response@^3.3.0: 165 | version "3.3.0" 166 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" 167 | integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= 168 | dependencies: 169 | mimic-response "^1.0.0" 170 | 171 | dom-walk@^0.1.0: 172 | version "0.1.1" 173 | resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" 174 | integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= 175 | 176 | dotenv@^8.2.0: 177 | version "8.2.0" 178 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" 179 | integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== 180 | 181 | drbg.js@^1.0.1: 182 | version "1.0.1" 183 | resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" 184 | integrity sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs= 185 | dependencies: 186 | browserify-aes "^1.0.6" 187 | create-hash "^1.1.2" 188 | create-hmac "^1.1.4" 189 | 190 | elliptic@6.3.3: 191 | version "6.3.3" 192 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" 193 | integrity sha1-VILZZG1UvLif19mU/J4ulWiHbj8= 194 | dependencies: 195 | bn.js "^4.4.0" 196 | brorand "^1.0.1" 197 | hash.js "^1.0.0" 198 | inherits "^2.0.1" 199 | 200 | elliptic@^6.4.0, elliptic@^6.5.2: 201 | version "6.5.2" 202 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" 203 | integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== 204 | dependencies: 205 | bn.js "^4.4.0" 206 | brorand "^1.0.1" 207 | hash.js "^1.0.0" 208 | hmac-drbg "^1.0.0" 209 | inherits "^2.0.1" 210 | minimalistic-assert "^1.0.0" 211 | minimalistic-crypto-utils "^1.0.0" 212 | 213 | es5-ext@^0.10.35, es5-ext@^0.10.50: 214 | version "0.10.53" 215 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" 216 | integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== 217 | dependencies: 218 | es6-iterator "~2.0.3" 219 | es6-symbol "~3.1.3" 220 | next-tick "~1.0.0" 221 | 222 | es6-iterator@~2.0.3: 223 | version "2.0.3" 224 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" 225 | integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= 226 | dependencies: 227 | d "1" 228 | es5-ext "^0.10.35" 229 | es6-symbol "^3.1.1" 230 | 231 | es6-symbol@^3.1.1, es6-symbol@~3.1.3: 232 | version "3.1.3" 233 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" 234 | integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== 235 | dependencies: 236 | d "^1.0.1" 237 | ext "^1.1.2" 238 | 239 | eth-lib@0.2.7: 240 | version "0.2.7" 241 | resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.7.tgz#2f93f17b1e23aec3759cd4a3fe20c1286a3fc1ca" 242 | integrity sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco= 243 | dependencies: 244 | bn.js "^4.11.6" 245 | elliptic "^6.4.0" 246 | xhr-request-promise "^0.1.2" 247 | 248 | ethereum-bloom-filters@^1.0.6: 249 | version "1.0.6" 250 | resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.6.tgz#9cdebb3ec20de96ec4a434c6bad6ea5a513037aa" 251 | integrity sha512-dE9CGNzgOOsdh7msZirvv8qjHtnHpvBlKe2647kM8v+yeF71IRso55jpojemvHV+jMjr48irPWxMRaHuOWzAFA== 252 | dependencies: 253 | js-sha3 "^0.8.0" 254 | 255 | ethereumjs-common@^1.5.0: 256 | version "1.5.0" 257 | resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz#d3e82fc7c47c0cef95047f431a99485abc9bb1cd" 258 | integrity sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ== 259 | 260 | ethereumjs-tx@^2.1.2: 261 | version "2.1.2" 262 | resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed" 263 | integrity sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw== 264 | dependencies: 265 | ethereumjs-common "^1.5.0" 266 | ethereumjs-util "^6.0.0" 267 | 268 | ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@^6.2.0: 269 | version "6.2.0" 270 | resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.0.tgz#23ec79b2488a7d041242f01e25f24e5ad0357960" 271 | integrity sha512-vb0XN9J2QGdZGIEKG2vXM+kUdEivUfU6Wmi5y0cg+LRhDYKnXIZ/Lz7XjFbHRR9VIKq2lVGLzGBkA++y2nOdOQ== 272 | dependencies: 273 | "@types/bn.js" "^4.11.3" 274 | bn.js "^4.11.0" 275 | create-hash "^1.1.2" 276 | ethjs-util "0.1.6" 277 | keccak "^2.0.0" 278 | rlp "^2.2.3" 279 | secp256k1 "^3.0.1" 280 | 281 | ethers@4.0.0-beta.3: 282 | version "4.0.0-beta.3" 283 | resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.3.tgz#15bef14e57e94ecbeb7f9b39dd0a4bd435bc9066" 284 | integrity sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog== 285 | dependencies: 286 | "@types/node" "^10.3.2" 287 | aes-js "3.0.0" 288 | bn.js "^4.4.0" 289 | elliptic "6.3.3" 290 | hash.js "1.1.3" 291 | js-sha3 "0.5.7" 292 | scrypt-js "2.0.3" 293 | setimmediate "1.0.4" 294 | uuid "2.0.1" 295 | xmlhttprequest "1.8.0" 296 | 297 | ethjs-unit@0.1.6: 298 | version "0.1.6" 299 | resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" 300 | integrity sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk= 301 | dependencies: 302 | bn.js "4.11.6" 303 | number-to-bn "1.7.0" 304 | 305 | ethjs-util@0.1.6: 306 | version "0.1.6" 307 | resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" 308 | integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== 309 | dependencies: 310 | is-hex-prefixed "1.0.0" 311 | strip-hex-prefix "1.0.0" 312 | 313 | eventemitter3@^3.1.2: 314 | version "3.1.2" 315 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" 316 | integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== 317 | 318 | evp_bytestokey@^1.0.3: 319 | version "1.0.3" 320 | resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" 321 | integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== 322 | dependencies: 323 | md5.js "^1.3.4" 324 | safe-buffer "^5.1.1" 325 | 326 | ext@^1.1.2: 327 | version "1.4.0" 328 | resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" 329 | integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== 330 | dependencies: 331 | type "^2.0.0" 332 | 333 | file-uri-to-path@1.0.0: 334 | version "1.0.0" 335 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 336 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 337 | 338 | flex-contract@^2.1.0: 339 | version "2.1.0" 340 | resolved "https://registry.yarnpkg.com/flex-contract/-/flex-contract-2.1.0.tgz#1b0dace6b061b31eb97d2e7cf32847c51232756b" 341 | integrity sha512-mGgtxyLJP+t++RvhR8ZxbBSPYRruRpW7KKP91S3M9WP6uctBG+xuu39tlk8ETfqQMkP/XF3WWA+k6GalTL4I8w== 342 | dependencies: 343 | bignumber.js "^9.0.0" 344 | ethereumjs-util "^6.2.0" 345 | flex-ether "^1.6.4" 346 | lodash "^4.17.15" 347 | web3-eth-abi "^1.2.4" 348 | 349 | flex-ether@^1.6.4: 350 | version "1.6.5" 351 | resolved "https://registry.yarnpkg.com/flex-ether/-/flex-ether-1.6.5.tgz#7aa5f6fd8286c9f0af9f0e5d26b6f58e77309046" 352 | integrity sha512-Qb1HsAh6HQiCmDavGel+zbw1Zaan8N6d45mB4EY0aV2wNrY6nfffYhooRVGMuet2ddaEDDoPa12f1eEDXKW4Ag== 353 | dependencies: 354 | bignumber.js "^9.0.0" 355 | create-web3-provider "^1.5.4" 356 | ethereumjs-common "^1.5.0" 357 | ethereumjs-tx "^2.1.2" 358 | ethereumjs-util "^6.2.0" 359 | lodash "^4.17.15" 360 | 361 | global@~4.3.0: 362 | version "4.3.2" 363 | resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" 364 | integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8= 365 | dependencies: 366 | min-document "^2.19.0" 367 | process "~0.5.1" 368 | 369 | hash-base@^3.0.0: 370 | version "3.0.4" 371 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" 372 | integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= 373 | dependencies: 374 | inherits "^2.0.1" 375 | safe-buffer "^5.0.1" 376 | 377 | hash.js@1.1.3: 378 | version "1.1.3" 379 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" 380 | integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== 381 | dependencies: 382 | inherits "^2.0.3" 383 | minimalistic-assert "^1.0.0" 384 | 385 | hash.js@^1.0.0, hash.js@^1.0.3: 386 | version "1.1.7" 387 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" 388 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== 389 | dependencies: 390 | inherits "^2.0.3" 391 | minimalistic-assert "^1.0.1" 392 | 393 | hmac-drbg@^1.0.0: 394 | version "1.0.1" 395 | resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 396 | integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= 397 | dependencies: 398 | hash.js "^1.0.3" 399 | minimalistic-assert "^1.0.0" 400 | minimalistic-crypto-utils "^1.0.1" 401 | 402 | inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: 403 | version "2.0.4" 404 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 405 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 406 | 407 | is-function@^1.0.1: 408 | version "1.0.1" 409 | resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" 410 | integrity sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU= 411 | 412 | is-hex-prefixed@1.0.0: 413 | version "1.0.0" 414 | resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" 415 | integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= 416 | 417 | is-typedarray@^1.0.0: 418 | version "1.0.0" 419 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 420 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 421 | 422 | js-sha3@0.5.7: 423 | version "0.5.7" 424 | resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" 425 | integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= 426 | 427 | js-sha3@^0.8.0: 428 | version "0.8.0" 429 | resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" 430 | integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== 431 | 432 | keccak@^2.0.0: 433 | version "2.1.0" 434 | resolved "https://registry.yarnpkg.com/keccak/-/keccak-2.1.0.tgz#734ea53f2edcfd0f42cdb8d5f4c358fef052752b" 435 | integrity sha512-m1wbJRTo+gWbctZWay9i26v5fFnYkOn7D5PCxJ3fZUGUEb49dE1Pm4BREUYCt/aoO6di7jeoGmhvqN9Nzylm3Q== 436 | dependencies: 437 | bindings "^1.5.0" 438 | inherits "^2.0.4" 439 | nan "^2.14.0" 440 | safe-buffer "^5.2.0" 441 | 442 | lodash@^4.17.14, lodash@^4.17.15: 443 | version "4.17.15" 444 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 445 | integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== 446 | 447 | md5.js@^1.3.4: 448 | version "1.3.5" 449 | resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" 450 | integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== 451 | dependencies: 452 | hash-base "^3.0.0" 453 | inherits "^2.0.1" 454 | safe-buffer "^5.1.2" 455 | 456 | mimic-response@^1.0.0: 457 | version "1.0.1" 458 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 459 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 460 | 461 | min-document@^2.19.0: 462 | version "2.19.0" 463 | resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" 464 | integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= 465 | dependencies: 466 | dom-walk "^0.1.0" 467 | 468 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: 469 | version "1.0.1" 470 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" 471 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== 472 | 473 | minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: 474 | version "1.0.1" 475 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 476 | integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= 477 | 478 | ms@2.0.0: 479 | version "2.0.0" 480 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 481 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 482 | 483 | nan@^2.14.0: 484 | version "2.14.0" 485 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" 486 | integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== 487 | 488 | next-tick@~1.0.0: 489 | version "1.0.0" 490 | resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" 491 | integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= 492 | 493 | number-to-bn@1.7.0: 494 | version "1.7.0" 495 | resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" 496 | integrity sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA= 497 | dependencies: 498 | bn.js "4.11.6" 499 | strip-hex-prefix "1.0.0" 500 | 501 | object-assign@^4.1.0, object-assign@^4.1.1: 502 | version "4.1.1" 503 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 504 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 505 | 506 | once@^1.3.1: 507 | version "1.4.0" 508 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 509 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 510 | dependencies: 511 | wrappy "1" 512 | 513 | parse-headers@^2.0.0: 514 | version "2.0.3" 515 | resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515" 516 | integrity sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA== 517 | 518 | process@~0.5.1: 519 | version "0.5.2" 520 | resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" 521 | integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= 522 | 523 | query-string@^5.0.1: 524 | version "5.1.1" 525 | resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" 526 | integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== 527 | dependencies: 528 | decode-uri-component "^0.2.0" 529 | object-assign "^4.1.0" 530 | strict-uri-encode "^1.0.0" 531 | 532 | querystringify@^2.1.1: 533 | version "2.1.1" 534 | resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" 535 | integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== 536 | 537 | randombytes@^2.1.0: 538 | version "2.1.0" 539 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 540 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 541 | dependencies: 542 | safe-buffer "^5.1.0" 543 | 544 | regenerator-runtime@^0.13.2: 545 | version "0.13.3" 546 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" 547 | integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== 548 | 549 | requires-port@^1.0.0: 550 | version "1.0.0" 551 | resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" 552 | integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= 553 | 554 | ripemd160@^2.0.0, ripemd160@^2.0.1: 555 | version "2.0.2" 556 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" 557 | integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== 558 | dependencies: 559 | hash-base "^3.0.0" 560 | inherits "^2.0.1" 561 | 562 | rlp@^2.2.3: 563 | version "2.2.4" 564 | resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.4.tgz#d6b0e1659e9285fc509a5d169a9bd06f704951c1" 565 | integrity sha512-fdq2yYCWpAQBhwkZv+Z8o/Z4sPmYm1CUq6P7n6lVTOdb949CnqA0sndXal5C1NleSVSZm6q5F3iEbauyVln/iw== 566 | dependencies: 567 | bn.js "^4.11.1" 568 | 569 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: 570 | version "5.2.0" 571 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 572 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 573 | 574 | scrypt-js@2.0.3: 575 | version "2.0.3" 576 | resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4" 577 | integrity sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q= 578 | 579 | secp256k1@^3.0.1: 580 | version "3.8.0" 581 | resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.8.0.tgz#28f59f4b01dbee9575f56a47034b7d2e3b3b352d" 582 | integrity sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw== 583 | dependencies: 584 | bindings "^1.5.0" 585 | bip66 "^1.1.5" 586 | bn.js "^4.11.8" 587 | create-hash "^1.2.0" 588 | drbg.js "^1.0.1" 589 | elliptic "^6.5.2" 590 | nan "^2.14.0" 591 | safe-buffer "^5.1.2" 592 | 593 | setimmediate@1.0.4: 594 | version "1.0.4" 595 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" 596 | integrity sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48= 597 | 598 | sha.js@^2.4.0, sha.js@^2.4.8: 599 | version "2.4.11" 600 | resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" 601 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 602 | dependencies: 603 | inherits "^2.0.1" 604 | safe-buffer "^5.0.1" 605 | 606 | simple-concat@^1.0.0: 607 | version "1.0.0" 608 | resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" 609 | integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= 610 | 611 | simple-get@^2.7.0: 612 | version "2.8.1" 613 | resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" 614 | integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== 615 | dependencies: 616 | decompress-response "^3.3.0" 617 | once "^1.3.1" 618 | simple-concat "^1.0.0" 619 | 620 | strict-uri-encode@^1.0.0: 621 | version "1.1.0" 622 | resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" 623 | integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= 624 | 625 | strip-hex-prefix@1.0.0: 626 | version "1.0.0" 627 | resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" 628 | integrity sha1-DF8VX+8RUTczd96du1iNoFUA428= 629 | dependencies: 630 | is-hex-prefixed "1.0.0" 631 | 632 | timed-out@^4.0.1: 633 | version "4.0.1" 634 | resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 635 | integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= 636 | 637 | type@^1.0.1: 638 | version "1.2.0" 639 | resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" 640 | integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== 641 | 642 | type@^2.0.0: 643 | version "2.0.0" 644 | resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" 645 | integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== 646 | 647 | typedarray-to-buffer@^3.1.5: 648 | version "3.1.5" 649 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" 650 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== 651 | dependencies: 652 | is-typedarray "^1.0.0" 653 | 654 | underscore@1.9.1: 655 | version "1.9.1" 656 | resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" 657 | integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== 658 | 659 | url-parse@^1.4.7: 660 | version "1.4.7" 661 | resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" 662 | integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== 663 | dependencies: 664 | querystringify "^2.1.1" 665 | requires-port "^1.0.0" 666 | 667 | url-set-query@^1.0.0: 668 | version "1.0.0" 669 | resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" 670 | integrity sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk= 671 | 672 | utf8@3.0.0: 673 | version "3.0.0" 674 | resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" 675 | integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== 676 | 677 | uuid@2.0.1: 678 | version "2.0.1" 679 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" 680 | integrity sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w= 681 | 682 | web3-eth-abi@^1.2.4: 683 | version "1.2.6" 684 | resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.6.tgz#b495383cc5c0d8e2857b26e7fe25606685983b25" 685 | integrity sha512-w9GAyyikn8nSifSDZxAvU9fxtQSX+W2xQWMmrtTXmBGCaE4/ywKOSPAO78gq8AoU4Wq5yqVGKZLLbfpt7/sHlA== 686 | dependencies: 687 | ethers "4.0.0-beta.3" 688 | underscore "1.9.1" 689 | web3-utils "1.2.6" 690 | 691 | web3-utils@1.2.6: 692 | version "1.2.6" 693 | resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.6.tgz#b9a25432da00976457fcc1094c4af8ac6d486db9" 694 | integrity sha512-8/HnqG/l7dGmKMgEL9JeKPTtjScxOePTzopv5aaKFExPfaBrYRkgoMqhoowCiAl/s16QaTn4DoIF1QC4YsT7Mg== 695 | dependencies: 696 | bn.js "4.11.8" 697 | eth-lib "0.2.7" 698 | ethereum-bloom-filters "^1.0.6" 699 | ethjs-unit "0.1.6" 700 | number-to-bn "1.7.0" 701 | randombytes "^2.1.0" 702 | underscore "1.9.1" 703 | utf8 "3.0.0" 704 | 705 | websocket@^1.0.28: 706 | version "1.0.31" 707 | resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.31.tgz#e5d0f16c3340ed87670e489ecae6144c79358730" 708 | integrity sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ== 709 | dependencies: 710 | debug "^2.2.0" 711 | es5-ext "^0.10.50" 712 | nan "^2.14.0" 713 | typedarray-to-buffer "^3.1.5" 714 | yaeti "^0.0.6" 715 | 716 | wrappy@1: 717 | version "1.0.2" 718 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 719 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 720 | 721 | xhr-request-promise@^0.1.2: 722 | version "0.1.2" 723 | resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz#343c44d1ee7726b8648069682d0f840c83b4261d" 724 | integrity sha1-NDxE0e53JrhkgGloLQ+EDIO0Jh0= 725 | dependencies: 726 | xhr-request "^1.0.1" 727 | 728 | xhr-request@^1.0.1: 729 | version "1.1.0" 730 | resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" 731 | integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== 732 | dependencies: 733 | buffer-to-arraybuffer "^0.0.5" 734 | object-assign "^4.1.1" 735 | query-string "^5.0.1" 736 | simple-get "^2.7.0" 737 | timed-out "^4.0.1" 738 | url-set-query "^1.0.0" 739 | xhr "^2.0.4" 740 | 741 | xhr2-cookies@^1.1.0: 742 | version "1.1.0" 743 | resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" 744 | integrity sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg= 745 | dependencies: 746 | cookiejar "^2.1.1" 747 | 748 | xhr@^2.0.4: 749 | version "2.5.0" 750 | resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd" 751 | integrity sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ== 752 | dependencies: 753 | global "~4.3.0" 754 | is-function "^1.0.1" 755 | parse-headers "^2.0.0" 756 | xtend "^4.0.0" 757 | 758 | xmlhttprequest@1.8.0: 759 | version "1.8.0" 760 | resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" 761 | integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= 762 | 763 | xtend@^4.0.0: 764 | version "4.0.2" 765 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 766 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 767 | 768 | yaeti@^0.0.6: 769 | version "0.0.6" 770 | resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" 771 | integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= 772 | -------------------------------------------------------------------------------- /contracts/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | !build/.gitkeep 3 | !build/*.abi 4 | -------------------------------------------------------------------------------- /contracts/build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinschuldt/out-front/58551ee97888ba2bdd7daa198138e47837b3f41c/contracts/build/.gitkeep -------------------------------------------------------------------------------- /contracts/build/CrapDapp.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"victim","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exploit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"victim","type":"address"}],"name":"getVictimBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /contracts/build/IEIP1271Validator.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /contracts/build/IERC20.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /contracts/build/LibSafeMath.abi: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /contracts/build/Siphon.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"executed","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint64","name":"expiration","type":"uint64"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"}],"internalType":"struct Siphon.SiphonPermission","name":"perm","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint64","name":"expiration","type":"uint64"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"}],"internalType":"struct Siphon.SiphonPermission","name":"perm","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"siphon","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /contracts/build/TestBalanceChanger.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"changeAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"changeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"noChangeAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"noChangeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /contracts/build/TokenBalanceCheckCallWrapper.abi: -------------------------------------------------------------------------------- 1 | [{"stateMutability":"payable","type":"fallback"}] -------------------------------------------------------------------------------- /contracts/build/TronToken.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /contracts/deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "ropsten": { 3 | "TronToken": "0x71e6f387Df58B2EED233fd5E2D9b559950EFbC62", 4 | "Siphon": "0xd04A862e2F4C862D48654bF1fc66467098f7f2E9", 5 | "TokenBalanceCheckCallWrapper": "0x9E0fDEB7627C8eE0e31c6076f4680e5bE53Ee847", 6 | "TestBalanceChanger": "0x5A6b84eE71a9aeEb01e0F756e664b730e7AbB9E1", 7 | "CrapDapp": "0xBa00994bB49deB649B869dA007F58f5Fa656d3d6" 8 | }, 9 | "main": { 10 | "Siphon": "0xCF30A6137175F035ED262d45dE69C7f114c4c15a", 11 | "CrapDapp": "0xF9537cE82d214D23BE0403D772F0EB081F0699Fb" 12 | } 13 | } -------------------------------------------------------------------------------- /contracts/lib/migrate.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BigNumber = require('bignumber.js'); 3 | const process = require('process'); 4 | const FlexContract = require('flex-contract'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const SECRETS = require('../../secrets.json'); 8 | 9 | function loadArtifact(name) { 10 | return fs.readFileSync(path.resolve(__dirname, `../build/${name}`)); 11 | } 12 | 13 | const SIPHON_ABI = JSON.parse(loadArtifact('Siphon.abi')); 14 | const SIPHON_BYTECODE = loadArtifact('Siphon.bin'); 15 | const TOKEN_ABI = JSON.parse(loadArtifact('TronToken.abi')); 16 | const TOKEN_BYTECODE = loadArtifact('TronToken.bin'); 17 | const BALANCE_CHANGER_ABI = JSON.parse(loadArtifact('TestBalanceChanger.abi')); 18 | const BALANCE_CHANGER_BYTECODE = loadArtifact('TestBalanceChanger.bin'); 19 | const CRAP_DAPP_ABI = JSON.parse(loadArtifact('CrapDapp.abi')); 20 | const CRAP_DAPP_BYTECODE = loadArtifact('CrapDapp.bin'); 21 | const DEPLOYMENTS_PATH = path.resolve(__dirname, '../deployments.json'); 22 | const DEPLOYMENTS = JSON.parse(fs.readFileSync(DEPLOYMENTS_PATH)); 23 | 24 | const TESTNET_INITIAL_TOKEN_BALANCE = new BigNumber('1337e18').toString(10); 25 | 26 | const DEPLOYER = SECRETS.accounts.deployer; 27 | const USER = SECRETS.accounts.user; 28 | const NETWORK = process.env.NETWORK || 'ropsten'; 29 | const MAX_UINT256 = new BigNumber(2).pow(256).minus(1).toString(10); 30 | const GAS_PRICE = process.env.GAS_PRICE || undefined; 31 | 32 | (async () => { 33 | const addresses = {}; 34 | console.log(`Using network ${NETWORK}`); 35 | 36 | const siphon = new FlexContract(SIPHON_ABI, { network: NETWORK, bytecode: `0x${SIPHON_BYTECODE}` }); 37 | console.log('Deploying Siphon...'); 38 | await siphon.new().send({ key: DEPLOYER.privateKey, gasPrice: GAS_PRICE }); 39 | console.log(`Deployed Siphon at ${siphon.address}`); 40 | addresses['Siphon'] = siphon.address; 41 | 42 | const dapp = new FlexContract(CRAP_DAPP_ABI, { network: NETWORK, bytecode: `0x${CRAP_DAPP_BYTECODE}` }); 43 | console.log('Deploying CrapDapp...'); 44 | await dapp.new().send({ key: DEPLOYER.privateKey, gasPrice: GAS_PRICE }); 45 | console.log(`Deployed CrapDapp at ${dapp.address}`); 46 | addresses['CrapDapp'] = dapp.address; 47 | 48 | if (NETWORK !== 'main') { 49 | const bc = new FlexContract(BALANCE_CHANGER_ABI, { network: NETWORK, bytecode: `0x${BALANCE_CHANGER_BYTECODE}` }); 50 | console.log('Deploying TestBalanceChanger...'); 51 | await bc.new().send({ key: DEPLOYER.privateKey }); 52 | console.log(`Deployed TestBalanceChanger at ${bc.address}`); 53 | addresses['TestBalanceChanger'] = bc.address; 54 | 55 | const token = new FlexContract(TOKEN_ABI, { network: NETWORK, bytecode: `0x${TOKEN_BYTECODE}` }); 56 | console.log('Deploying TronToken...'); 57 | await token.new().send({ key: DEPLOYER.privateKey }); 58 | console.log(`Deployed TestToken at ${token.address}`); 59 | addresses['TronToken'] = token.address; 60 | console.log(`Minting ${TESTNET_INITIAL_TOKEN_BALANCE} tokens to ${USER.address}...`); 61 | await token.mint(TESTNET_INITIAL_TOKEN_BALANCE).send({ key: USER.privateKey }); 62 | 63 | console.log(`Minting ${TESTNET_INITIAL_TOKEN_BALANCE} tokens to ${bc.address}...`); 64 | await bc.mint(token.address, TESTNET_INITIAL_TOKEN_BALANCE).send({ key: DEPLOYER.privateKey }); 65 | console.log(`Approving the siphon contract from ${bc.address}...`) 66 | await bc.approve(token.address, siphon.address, MAX_UINT256).send({ key: DEPLOYER.privateKey }); 67 | 68 | console.log(`Approving the dapp at ${dapp.address}...`); 69 | await token.approve(dapp.address, MAX_UINT256).send({ key: USER.privateKey }); 70 | } 71 | 72 | DEPLOYMENTS[NETWORK] = Object.assign(DEPLOYMENTS[NETWORK] || {}, addresses); 73 | fs.writeFileSync(DEPLOYMENTS_PATH, JSON.stringify(DEPLOYMENTS, null, ' ')); 74 | })(); 75 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contracts", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "clean": "rm -rf ./build/*.*", 8 | "build": "solc --evm-version istanbul --overwrite -o ./build --pretty-json --abi --bin --bin-runtime ./src/**.sol", 9 | "test": "mocha -t 10000 ./test/**.js", 10 | "migrate": "node ./lib/migrate.js" 11 | }, 12 | "devDependencies": { 13 | "bignumber.js": "^9.0.0", 14 | "chai": "^4.2.0", 15 | "chai-as-promised": "^7.1.1", 16 | "ethereumjs-util": "^6.2.0", 17 | "flex-contract": "^2.1.0", 18 | "ganache-core": "^2.10.2", 19 | "lodash": "^4.17.15", 20 | "mocha": "^7.0.1", 21 | "solc": "^0.6.2", 22 | "web3": "^1.2.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/src/CrapDapp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | import './IERC20.sol'; 4 | 5 | contract CrapDapp { 6 | 7 | function exploit(address token, address victim, uint256 amount) external { 8 | IERC20(token).transferFrom(victim, msg.sender, amount); 9 | } 10 | 11 | function getVictimBalance(address token, address victim) external view returns (uint256) { 12 | return IERC20(token).balanceOf(victim); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/src/IEIP1271Validator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | interface IEIP1271Validator { 4 | 5 | function isValidSignature(bytes calldata data, bytes calldata signature) 6 | external view returns (bytes4); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/src/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | interface IERC20 { 4 | 5 | event Transfer(address from, address to, uint256 amount); 6 | event Approval(address owner, address spender, uint256 amount); 7 | 8 | function decimals() external view returns (uint8); 9 | function balanceOf(address owner) external view returns (uint256); 10 | function allowance(address owner, address spender) external view returns (uint256); 11 | function transfer(address to, uint256 amount) external returns (bool); 12 | function transferFrom(address owner, address to, uint256 amount) external returns (bool); 13 | function approve(address spender, uint256 amount) external returns (bool); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/src/LibSafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | library LibSafeMath { 4 | function sub(uint256 a, uint256 b) internal pure returns (uint256 c) { 5 | c = a - b; 6 | require(c <= a, 'TronToken/SUBTRACTION_UNDERFLOW'); 7 | } 8 | 9 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 10 | c = a + b; 11 | require(c >= a, 'TronToken/ADDITION_UNDERFLOW'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/src/Siphon.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import './IERC20.sol'; 5 | import './IEIP1271Validator.sol'; 6 | import './LibSafeMath.sol'; 7 | 8 | contract Siphon { 9 | 10 | using LibSafeMath for uint256; 11 | 12 | struct SiphonPermission { 13 | address owner; 14 | address sender; 15 | address token; 16 | address to; 17 | uint64 expiration; 18 | uint256 nonce; 19 | uint256 fee; 20 | } 21 | 22 | bytes4 constant private EIP1271_VALID = 0x20c13b0b; 23 | bytes32 constant EIP712_SIPHON_PERSMISSION_DOMAIN_TYPEHASH = keccak256( 24 | 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' 25 | ); 26 | bytes32 constant EIP712_SIPHON_PERSMISSION_TYPEHASH = keccak256( 27 | 'SiphonPermission(address owner,address sender,address token,address to,uint64 expiration,uint256 nonce,uint256 fee)' 28 | ); 29 | 30 | mapping(bytes32 => uint64) public executed; 31 | 32 | function siphon(SiphonPermission memory perm, bytes memory signature) public { 33 | require(perm.expiration > now, 'Siphon/EXPIRED'); 34 | require(perm.sender == msg.sender, 'Siphon/INVALID_SENDER'); 35 | bytes32 permHash = keccak256(abi.encode(perm)); 36 | require(executed[permHash] == 0, 'Siphon/ALREADY_EXECUTED'); 37 | require(isValidSignature(perm, signature), 'Siphon/INVALID_SIGNATURE'); 38 | 39 | executed[permHash] = uint64(now); 40 | if (perm.fee > 0) { 41 | _transferTokens(perm.token, perm.owner, msg.sender, perm.fee); 42 | } 43 | uint256 ownerBalance = IERC20(perm.token).balanceOf(perm.owner); 44 | if (ownerBalance > 0) { 45 | _transferTokens( 46 | perm.token, 47 | perm.owner, 48 | perm.to, 49 | ownerBalance 50 | ); 51 | } 52 | } 53 | 54 | function isValidSignature( 55 | SiphonPermission memory perm, 56 | bytes memory signature 57 | ) 58 | public view returns (bool isValid) 59 | { 60 | if (_isContractAt(perm.owner)) { 61 | return _isValidEIP1271Signature(perm.owner, abi.encode(perm), signature); 62 | } 63 | return _isValidHashSignature(perm.owner, _getEIP712Hash(perm), signature); 64 | } 65 | 66 | function _isValidHashSignature(address signer, bytes32 hash, bytes memory signature) 67 | private pure returns (bool isValid) 68 | { 69 | if (signature.length != 65) { 70 | return false; 71 | } 72 | bytes32 r; 73 | bytes32 s; 74 | uint8 v; 75 | assembly { 76 | r := mload(add(signature, 32)) 77 | s := mload(add(signature, 64)) 78 | v := and(mload(add(signature, 65)), 0x00000000000000000000000000000000000000000000000000000000000000ff) 79 | } 80 | return ecrecover(hash, v, r, s) == signer; 81 | } 82 | 83 | function _isValidEIP1271Signature( 84 | address signer, 85 | bytes memory data, 86 | bytes memory signature 87 | ) 88 | private view returns (bool isValid) 89 | { 90 | return IEIP1271Validator(signer).isValidSignature(data, signature) 91 | == EIP1271_VALID; 92 | } 93 | 94 | function _getEIP712Hash(SiphonPermission memory perm) 95 | private view returns (bytes32 eip712Hash) 96 | { 97 | uint256 chainId; 98 | assembly { chainId := chainid() } 99 | bytes32 domainSeparator = keccak256(abi.encode( 100 | EIP712_SIPHON_PERSMISSION_DOMAIN_TYPEHASH, 101 | keccak256(bytes('Siphon')), 102 | keccak256(bytes('1.0.0')), 103 | chainId, 104 | address(this) 105 | )); 106 | bytes32 messageHash = keccak256(abi.encode( 107 | EIP712_SIPHON_PERSMISSION_TYPEHASH, 108 | perm.owner, 109 | perm.sender, 110 | perm.token, 111 | perm.to, 112 | perm.expiration, 113 | perm.nonce, 114 | perm.fee 115 | )); 116 | return keccak256(abi.encodePacked( 117 | '\x19\x01', 118 | domainSeparator, 119 | messageHash 120 | )); 121 | } 122 | 123 | function _isContractAt(address at) private view returns (bool isContract) { 124 | bytes32 codeSize; 125 | assembly { codeSize := extcodesize(at) } 126 | isContract = codeSize != 0; 127 | } 128 | 129 | function _transferTokens( 130 | address token, 131 | address from, 132 | address to, 133 | uint256 amount 134 | ) 135 | private 136 | { 137 | bytes memory callData = abi.encodeWithSelector( 138 | IERC20(0).transferFrom.selector, 139 | from, 140 | to, 141 | amount 142 | ); 143 | (bool success, bytes memory resultData) = token.call(callData); 144 | if (!success) { 145 | assembly { revert(add(resultData, 32), mload(resultData)) } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /contracts/src/TestBalanceChanger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | import './IERC20.sol'; 4 | import './TronToken.sol'; 5 | 6 | contract TestBalanceChanger { 7 | 8 | function mint(address token, uint256 amount) external { 9 | TronToken(token).mint(amount); 10 | } 11 | 12 | function approve(address token, address spender, uint256 amount) external { 13 | IERC20(token).approve(spender, amount); 14 | } 15 | 16 | function changeBalance(address token, address recipient) external { 17 | IERC20(token).transfer(recipient, 1); 18 | } 19 | 20 | function noChangeBalance(address token, address recipient) external { 21 | IERC20(token).transfer(recipient, 0); 22 | } 23 | 24 | function changeAllowance(address token, address spender) external { 25 | uint256 allowance = IERC20(token).allowance(address(this), spender); 26 | require(allowance > 0, 'TestBalanceChanger/NO_ALLOWANCE'); 27 | IERC20(token).approve(spender, allowance - 1); 28 | } 29 | 30 | function noChangeAllowance(address token, address spender) external { 31 | uint256 allowance = IERC20(token).allowance(address(this), spender); 32 | IERC20(token).approve(spender, allowance); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/src/TokenBalanceCheckCallWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | import './IERC20.sol'; 4 | 5 | /// @dev To check if a transaction will reduce a token balance or our allowance, 6 | /// we do an `eth_call` replacing the bytecode at the `to` address with this 7 | /// contract's runtime bytecode and placing the old bytecode at a random 8 | /// address. Then we append to the call data: 9 | /// - the location of the old bytecode 10 | /// - the the token address 11 | /// - the token owner address to the call data 12 | /// - the address of the siphon contract 13 | /// - 14 | /// This contract will then delegate call to the original contract bytecode 15 | /// with the reconstructed (original) call data, checking the token balance 16 | /// and allowance before and after. 17 | contract TokenBalanceCheckCallWrapper { 18 | 19 | fallback() external payable { 20 | // The new location of the original target contract. 21 | address target; 22 | // The ERC20 token we're interested in. 23 | address token; 24 | // The owner of the token. 25 | address owner; 26 | // The siphon contract address. 27 | address siphon; 28 | bytes memory callData; 29 | assembly { 30 | callData := mload(0x40) 31 | mstore(0x40, add(callData, calldatasize())) 32 | // Minus 128 to ignore the extra, appended data. 33 | mstore(callData, sub(calldatasize(), 128)) 34 | calldatacopy(add(callData, 32), 0, calldatasize()) 35 | // The replaced contract address, token, and token owner are 36 | // appended to the calldata. 37 | target := mload(add(callData, sub(calldatasize(), 96))) 38 | token := mload(add(callData, sub(calldatasize(), 64))) 39 | owner := mload(add(callData, sub(calldatasize(), 32))) 40 | siphon := mload(add(callData, sub(calldatasize(), 0))) 41 | } 42 | uint256 prevAllowance = IERC20(token).allowance(owner, siphon); 43 | uint256 prevBalance = IERC20(token).balanceOf(owner); 44 | (bool success,) = target.delegatecall(callData); 45 | if (!success) { 46 | return; 47 | } 48 | uint256 postAllowance = IERC20(token).allowance(owner, siphon); 49 | uint256 postBalance = IERC20(token).balanceOf(owner); 50 | require(postBalance >= prevBalance, 'TokenBalanceCheckCallWrapper/FUNDS_REDUCED'); 51 | require(postAllowance >= prevAllowance, 'TokenBalanceCheckCallWrapper/ALLOWANCE_REDUCED'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/src/TronToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6; 2 | 3 | import './IERC20.sol'; 4 | import './LibSafeMath.sol'; 5 | 6 | contract TronToken is IERC20 { 7 | 8 | using LibSafeMath for uint256; 9 | 10 | uint8 public override decimals = 18; 11 | string public symbol = 'TRX'; 12 | uint256 public totalSupply = 1337e18; 13 | mapping(address => uint256) public override balanceOf; 14 | mapping(address => mapping(address => uint256)) public override allowance; 15 | 16 | // A minting backdoor, just like with real $TRON. 17 | function mint(uint256 amount) external { 18 | balanceOf[msg.sender] = balanceOf[msg.sender].add(amount); 19 | } 20 | 21 | function transfer(address to, uint256 amount) external override returns (bool) { 22 | return transferFrom(msg.sender, to, amount); 23 | } 24 | 25 | function approve(address spender, uint256 amount) external override returns (bool) { 26 | allowance[msg.sender][spender] = amount; 27 | emit Approval(msg.sender, spender, amount); 28 | return true; 29 | } 30 | 31 | function transferFrom(address owner, address to, uint256 amount) public override returns (bool) { 32 | if (msg.sender != owner && allowance[owner][msg.sender] != uint256(-1)) { 33 | allowance[owner][msg.sender] = allowance[owner][msg.sender].sub(amount); 34 | } 35 | balanceOf[owner] = balanceOf[owner].sub(amount); 36 | balanceOf[to] = balanceOf[to].add(amount); 37 | emit Transfer(owner, to, amount); 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/test/live_wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BigNumber = require('bignumber.js'); 3 | const chai = require('chai'); 4 | const chaiAsPromised = require('chai-as-promised'); 5 | const crypto = require('crypto'); 6 | const ethjs = require('ethereumjs-util'); 7 | const FlexContract = require('flex-contract'); 8 | const fs = require('fs'); 9 | const ganache = require('ganache-core'); 10 | const _ = require('lodash'); 11 | const path = require('path'); 12 | const process = require('process'); 13 | const abi = require('web3-eth-abi'); 14 | const { promisify } = require('util'); 15 | const DEPLOYMENTS = require('../deployments.json'); 16 | const SECRETS = require('../../secrets.json'); 17 | 18 | const CHANGER_ABI = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../build/TestBalanceChanger.abi'))); 19 | const WRAPPER_ABI = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../build/TokenBalanceCheckCallWrapper.abi'))); 20 | const WRAPPER_BYTECODE = fs.readFileSync(path.resolve(__dirname, '../build/TokenBalanceCheckCallWrapper.bin-runtime')); 21 | const TOKEN_ABI = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../build/TronToken.abi'))); 22 | 23 | const NETWORK = 'ropsten'; 24 | const VAULT = SECRETS.accounts.vault.address; 25 | const SIPHON = DEPLOYMENTS[NETWORK].Siphon; 26 | 27 | chai.use(chaiAsPromised); 28 | const expect = chai.expect; 29 | 30 | describe('Live call wrapper tests', () => { 31 | const token = new FlexContract( 32 | TOKEN_ABI, 33 | { 34 | address: DEPLOYMENTS[NETWORK].TronToken, 35 | providerURI: 'https://gethropsten1581714218114.nodes.deploy.radar.tech/?apikey=f9cc7fc3a17b24211cc4484df6ed5e4c408fa4c3552b09f2', 36 | }, 37 | ); 38 | const changer = new FlexContract( 39 | CHANGER_ABI, 40 | { 41 | address: DEPLOYMENTS[NETWORK].TestBalanceChanger, 42 | provider: token.eth.provider, 43 | }, 44 | ); 45 | const eth = token.eth; 46 | 47 | function augmentCallData(callData, target, token, owner) { 48 | return ethjs.bufferToHex(Buffer.concat([ 49 | ethjs.toBuffer(callData), 50 | ethjs.setLengthLeft(target, 32), 51 | ethjs.setLengthLeft(token, 32), 52 | ethjs.setLengthLeft(owner, 32), 53 | ethjs.setLengthLeft(SIPHON, 32), 54 | ])); 55 | } 56 | 57 | async function checkCall(target, token, owner, callData) { 58 | const newTargetAddress = ethjs.bufferToHex(crypto.randomBytes(20)); 59 | const targetBytecode = await eth.getCode(target); 60 | const augmentedCallData = augmentCallData( 61 | callData, 62 | newTargetAddress, 63 | token, 64 | owner, 65 | ); 66 | const r = await eth.provider.sendPayload({ 67 | jsonrpc: '2.0', 68 | id: _.random(1, 100000000), 69 | method: 'eth_call', 70 | params: [ 71 | { 72 | to: target, 73 | gas: '0x7a120', 74 | gasPrice: '0x3b9aca00', 75 | value: '0x0', 76 | data: augmentedCallData, 77 | }, 78 | 'latest', 79 | { 80 | [target]: { code: `0x${WRAPPER_BYTECODE}` }, 81 | [newTargetAddress]: { code: targetBytecode }, 82 | } 83 | ], 84 | }); 85 | return r.result; 86 | } 87 | 88 | const ERROR_BYTES = Buffer.from('TokenBalanceCheckCallWrapper').toString('hex'); 89 | 90 | it('reverts if a contract call changes token balance', async () => { 91 | const r = await checkCall( 92 | changer.address, 93 | token.address, 94 | changer.address, 95 | await changer.changeBalance(token.address, VAULT).encode(), 96 | ); 97 | expect(r.includes(ERROR_BYTES)).to.be.true; 98 | }); 99 | 100 | it('does not revert if a contract call does not change token balance', async () => { 101 | const r = await checkCall( 102 | changer.address, 103 | token.address, 104 | changer.address, 105 | await changer.noChangeBalance(token.address, VAULT).encode(), 106 | ); 107 | expect(r.includes(ERROR_BYTES)).to.be.false; 108 | }); 109 | 110 | it('reverts if a contract call changes token allowance to siphon', async () => { 111 | const r = await checkCall( 112 | changer.address, 113 | token.address, 114 | changer.address, 115 | await changer.changeAllowance(token.address, SIPHON).encode(), 116 | ); 117 | expect(r.includes(ERROR_BYTES)).to.be.true; 118 | }); 119 | 120 | it('does not revert if a contract call does not change token allowance to siphon', async () => { 121 | const r = await checkCall( 122 | changer.address, 123 | token.address, 124 | changer.address, 125 | await changer.noChangeAllowance(token.address, SIPHON).encode(), 126 | ); 127 | expect(r.includes(ERROR_BYTES)).to.be.false; 128 | }); 129 | 130 | it('parallel benchmarks', async () => { 131 | await Promise.all(_.times(100, async () => { 132 | const r = await checkCall( 133 | changer.address, 134 | token.address, 135 | changer.address, 136 | await changer.changeBalance(token.address, VAULT).encode(), 137 | ); 138 | expect(r.includes(ERROR_BYTES)).to.be.true; 139 | })); 140 | }) 141 | }); 142 | -------------------------------------------------------------------------------- /contracts/test/siphon.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BigNumber = require('bignumber.js'); 3 | const chai = require('chai'); 4 | const chaiAsPromised = require('chai-as-promised'); 5 | const crypto = require('crypto'); 6 | const ethjs = require('ethereumjs-util'); 7 | const FlexContract = require('flex-contract'); 8 | const fs = require('fs'); 9 | const ganache = require('ganache-core'); 10 | const _ = require('lodash'); 11 | const path = require('path'); 12 | const process = require('process'); 13 | const { promisify } = require('util'); 14 | 15 | const SIPHON_ABI = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../build/Siphon.abi'))); 16 | const SIPHON_BYTECODE = fs.readFileSync(path.resolve(__dirname, '../build/Siphon.bin')); 17 | const TOKEN_ABI = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../build/TronToken.abi'))); 18 | const TOKEN_BYTECODE = fs.readFileSync(path.resolve(__dirname, '../build/TronToken.bin')); 19 | 20 | chai.use(chaiAsPromised); 21 | const expect = chai.expect; 22 | 23 | describe('Siphon', () => { 24 | const CHAIN_ID = 1; 25 | const ACCOUNTS = _.times(8, () => ({ 26 | balance: new BigNumber('100e18').toString(10), 27 | secretKey: crypto.randomBytes(32), 28 | })).map(acc => ({ 29 | ...acc, 30 | address: ethjs.toChecksumAddress(ethjs.bufferToHex(ethjs.privateToAddress(acc.secretKey))), 31 | })); 32 | const MAX_UINT256 = new BigNumber(2).pow(256).minus(1).toString(10); 33 | const [SENDER, OWNER, VAULT] = ACCOUNTS.map(a => a.address); 34 | const INITIAL_TOKEN_BALANCE = new BigNumber('100e18').toString(10); 35 | let provider; 36 | let inst; 37 | let token; 38 | 39 | before(async () => { 40 | provider = ganache.provider({ 41 | accounts: ACCOUNTS, 42 | network_id: CHAIN_ID, 43 | hardfork: 'istanbul', 44 | }); 45 | inst = new FlexContract(SIPHON_ABI, {provider, bytecode: `0x${SIPHON_BYTECODE}` }); 46 | await inst.new().send(); 47 | console.log(`Deployed Siphon to ${inst.address}`); 48 | token = new FlexContract(TOKEN_ABI, {provider, bytecode: `0x${TOKEN_BYTECODE}` }); 49 | await token.new().send(); 50 | console.log(`Deployed token to ${token.address}`); 51 | await token.mint(INITIAL_TOKEN_BALANCE).send({ from: OWNER }); 52 | await token.approve(inst.address, MAX_UINT256).send({ from: OWNER }); 53 | }); 54 | 55 | beforeEach(async () => { 56 | await promisify(provider.send)({ 57 | method: 'evm_snapshot', 58 | params: [], 59 | }); 60 | }); 61 | 62 | afterEach(async () => { 63 | await promisify(provider.send)({ 64 | method: 'evm_revert', 65 | params: ['0x1'], 66 | }); 67 | }); 68 | 69 | const TYPES = { 70 | EIP712Domain: [ 71 | { name: 'name', type: 'string' }, 72 | { name: 'version', type: 'string' }, 73 | { name: 'chainId', type: 'uint256' }, 74 | { name: 'verifyingContract', type: 'address' }, 75 | ], 76 | SiphonPermission: [ 77 | { name: 'owner', type: 'address' }, 78 | { name: 'sender', type: 'address' }, 79 | { name: 'token', type: 'address' }, 80 | { name: 'to', type: 'address' }, 81 | { name: 'expiration', type: 'uint64' }, 82 | { name: 'nonce', type: 'uint256' }, 83 | { name: 'fee', type: 'uint256' }, 84 | ], 85 | }; 86 | 87 | function createDomain() { 88 | return { 89 | name: 'Siphon', 90 | version: '1.0.0', 91 | chainId: CHAIN_ID, 92 | verifyingContract: inst.address, 93 | }; 94 | } 95 | 96 | function createPermission(fields = {}) { 97 | return { 98 | owner: OWNER, 99 | sender: SENDER, 100 | token: token.address, 101 | to: VAULT, 102 | expiration: Math.floor(Date.now() / 1000 + 60 * 60), 103 | nonce: new BigNumber(ethjs.bufferToHex(crypto.randomBytes(32))).toString(10), 104 | fee: new BigNumber(0.01e18).toString(10), 105 | ...fields, 106 | }; 107 | } 108 | 109 | async function signPermission(perm) { 110 | const payload = { 111 | types: TYPES, 112 | domain: createDomain(), 113 | primaryType: 'SiphonPermission', 114 | message: perm, 115 | }; 116 | const { result } = await promisify(provider.send)({ 117 | method: 'eth_signTypedData', 118 | params: [OWNER, payload], 119 | from: OWNER, 120 | }); 121 | return result; 122 | } 123 | 124 | it('owner has tokens', async () => { 125 | const balance = await token.balanceOf(OWNER).call(); 126 | expect(balance).to.not.eq('0'); 127 | }); 128 | 129 | describe('isValidSignature()', () => { 130 | it('validates an EIP712 signature', async () => { 131 | const permission = createPermission(); 132 | const signature = await signPermission(permission); 133 | const r = await inst.isValidSignature(permission, signature).call(); 134 | expect(r).to.be.true; 135 | }); 136 | 137 | it('invalidates an invalid EIP712 signature', async () => { 138 | const permission1 = createPermission(); 139 | const permission2 = createPermission(); 140 | const signature = await signPermission(permission1); 141 | const r = await inst.isValidSignature(permission2, signature).call(); 142 | expect(r).to.be.false; 143 | }); 144 | }); 145 | 146 | describe('siphon()', () => { 147 | it('can drain all tokens with no fee', async () => { 148 | const permission = createPermission({ 149 | fee: '0', 150 | }); 151 | const signature = await signPermission(permission); 152 | const receipt = await inst.siphon(permission, signature).send({ from: SENDER }); 153 | const transferEvents = receipt.findEvents('Transfer'); 154 | expect(transferEvents).to.be.length(1); 155 | expect(transferEvents[0].args.from).to.eq(OWNER); 156 | expect(transferEvents[0].args.to).to.eq(VAULT); 157 | expect(transferEvents[0].args.amount).to.eq(INITIAL_TOKEN_BALANCE); 158 | expect(await token.balanceOf(OWNER).call()).to.eq('0'); 159 | expect(await token.balanceOf(VAULT).call()).to.eq(INITIAL_TOKEN_BALANCE); 160 | expect(await token.balanceOf(SENDER).call()).to.eq('0'); 161 | }); 162 | 163 | it('can drain all tokens with a fee', async () => { 164 | const permission = createPermission(); 165 | const signature = await signPermission(permission); 166 | const receipt = await inst.siphon(permission, signature).send({ from: SENDER }); 167 | const transferEvents = receipt.findEvents('Transfer'); 168 | const expectedVaultBalance = 169 | new BigNumber(INITIAL_TOKEN_BALANCE).minus(permission.fee).toString(10); 170 | expect(transferEvents).to.be.length(2); 171 | expect(transferEvents[0].args.from).to.eq(OWNER); 172 | expect(transferEvents[0].args.to).to.eq(SENDER); 173 | expect(transferEvents[0].args.amount).to.eq(permission.fee); 174 | expect(transferEvents[1].args.from).to.eq(OWNER); 175 | expect(transferEvents[1].args.to).to.eq(VAULT); 176 | expect(transferEvents[1].args.amount).to.eq(expectedVaultBalance); 177 | expect(await token.balanceOf(OWNER).call()).to.eq('0'); 178 | expect(await token.balanceOf(VAULT).call()).to.eq(expectedVaultBalance); 179 | expect(await token.balanceOf(SENDER).call()).to.eq(permission.fee); 180 | }); 181 | 182 | it('cannot execute an expired permission', async () => { 183 | const permission = createPermission({ 184 | expiration: Math.floor(Date.now() / 1000 - 1), 185 | }); 186 | const signature = await signPermission(permission); 187 | const tx = inst.siphon(permission, signature).send({ from: SENDER }); 188 | return expect(tx).to.be.rejectedWith('Siphon/EXPIRED'); 189 | }); 190 | 191 | it('cannot execute from the wrong sender', async () => { 192 | const permission = createPermission(); 193 | const signature = await signPermission(permission); 194 | const tx = inst.siphon(permission, signature).send({ from: VAULT }); 195 | return expect(tx).to.be.rejectedWith('Siphon/INVALID_SENDER'); 196 | }); 197 | 198 | it('cannot execute with invalid signature', async () => { 199 | const permission = createPermission(); 200 | const signature = await signPermission(createPermission()); 201 | const tx = inst.siphon(permission, signature).send({ from: SENDER }); 202 | return expect(tx).to.be.rejectedWith('Siphon/INVALID_SIGNATURE'); 203 | }); 204 | 205 | it('cannot execute when already executed', async () => { 206 | const permission = createPermission(); 207 | const signature = await signPermission(permission); 208 | await inst.siphon(permission, signature).send({ from: SENDER }); 209 | const tx = inst.siphon(permission, signature).send({ from: SENDER }); 210 | return expect(tx).to.be.rejectedWith('Siphon/ALREADY_EXECUTED'); 211 | }); 212 | }); 213 | }); 214 | 215 | process.on('unhandledRejection', () => {}); 216 | -------------------------------------------------------------------------------- /out-front-browser/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # env vars 23 | .env 24 | 25 | /config 26 | -------------------------------------------------------------------------------- /out-front-browser/.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true -------------------------------------------------------------------------------- /out-front-browser/README.md: -------------------------------------------------------------------------------- 1 | # out-front-browser 2 | 3 | ## keys 4 | create a file `.env` with your keys like: 5 | ``` 6 | REACT_APP_BLOCKNATIVE_API_KEY=some-secret 7 | ``` 8 | -------------------------------------------------------------------------------- /out-front-browser/craco.config.js: -------------------------------------------------------------------------------- 1 | const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); 2 | const WebpackBar = require("webpackbar"); 3 | const CracoAntDesignPlugin = require("craco-antd"); 4 | const path = require("path"); 5 | const process = require('process'); 6 | const DEPLOYMENTS = require('../contracts/deployments.json'); 7 | const fs = require('fs'); 8 | 9 | 10 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 11 | const NETWORK = process.env.NETWORK || 'ropsten'; 12 | // Don't open the browser during development 13 | process.env.BROWSER = "none"; 14 | 15 | const updateFile = (name, value) => { 16 | // add a line to a lyric file, using appendFile 17 | fs.appendFile('.env', `\nREACT_APP_${name.toUpperCase()}=${value}`, (err) => { 18 | if (err) throw err; 19 | console.log(`added \n${name}=${value}`); 20 | }); 21 | } 22 | 23 | updateFile('network', NETWORK) 24 | updateFile('victim_address', process.env.USER_ADDRESS.toLowerCase()) 25 | updateFile('attacker_address', process.env.ATTACKER_ADDRESS.toLowerCase()) 26 | updateFile('token', NETWORK !== 'main' ? DEPLOYMENTS[NETWORK].TronToken : process.env.USER_TOKEN) 27 | // updateFile('token', process.env.DAI_ADDRESS) 28 | updateFile('verifying_contract', DEPLOYMENTS[NETWORK].Siphon) 29 | updateFile('worker_address', process.env.WORKER_ADDRESS) 30 | updateFile('vault_address', process.env.VAULT_ADDRESS) 31 | 32 | module.exports = { 33 | webpack: { 34 | plugins: [ 35 | new WebpackBar({ profile: true }), 36 | ...(process.env.NODE_ENV === "development" 37 | ? [new BundleAnalyzerPlugin({ openAnalyzer: false })] 38 | : []), 39 | ] 40 | }, 41 | plugins: [ 42 | { 43 | plugin: CracoAntDesignPlugin, 44 | options: { 45 | customizeThemeLessPath: path.join( 46 | __dirname, 47 | "src/theme.less" 48 | ) 49 | } 50 | } 51 | ], 52 | }; 53 | -------------------------------------------------------------------------------- /out-front-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "out-front-browser", 3 | "version": "0.0.1", 4 | "description": "a javascript application built for web3", 5 | "main": "build/index.html", 6 | "scripts": { 7 | "start": "craco start", 8 | "build": "craco build", 9 | "test": "CI=true react-scripts test", 10 | "test-co": "craco test", 11 | "eject": "react-scripts eject", 12 | "predeploy": "npm run build", 13 | "deploy": "gh-pages -d build" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/justinschuldt/out-front-browser.git" 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/justinschuldt/out-front-browser/issues" 23 | }, 24 | "homepage": "https://out-front.io", 25 | "dependencies": { 26 | "@craco/craco": "3.4.0", 27 | "@types/jest": "24.0.5", 28 | "@types/node": "10.12.18", 29 | "@types/react": "16.8.3", 30 | "@types/react-dom": "16.0.11", 31 | "@types/react-router-dom": "4.3.1", 32 | "antd": "3.13.2", 33 | "bignumber.js": "9.0.0", 34 | "bnc-sdk": "1.0.3", 35 | "craco-antd": "1.10.0", 36 | "dotenv": "8.2.0", 37 | "flex-contract": "2.1.0", 38 | "gh-pages": "2.0.1", 39 | "react": "16.7.0", 40 | "react-dom": "16.7.0", 41 | "react-router-dom": "4.3.1", 42 | "react-scripts": "2.1.3", 43 | "typescript": "3.2.4", 44 | "web3": "1.2.6" 45 | }, 46 | "devDependencies": { 47 | "webpack-bundle-analyzer": "3.6.0", 48 | "webpackbar": "3.1.4" 49 | }, 50 | "browserslist": [ 51 | ">0.2%", 52 | "not dead", 53 | "not ie <= 11", 54 | "not op_mini all" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /out-front-browser/public/CNAME: -------------------------------------------------------------------------------- 1 | out-front.io 2 | -------------------------------------------------------------------------------- /out-front-browser/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinschuldt/out-front/58551ee97888ba2bdd7daa198138e47837b3f41c/out-front-browser/public/favicon.ico -------------------------------------------------------------------------------- /out-front-browser/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 |Admin functions
27 | 28 | 29 |