├── .gitignore ├── Dapper Wallet - Component Diagram.svg ├── LICENSE.txt ├── README.md ├── contracts ├── ECDSA.sol ├── ERC1271 │ └── ERC1271.sol ├── ERC165 │ ├── ERC165.sol │ ├── ERC165Checker.sol │ └── ERC165Query.sol ├── ERC223 │ └── ERC223Receiver.sol ├── ERC721 │ ├── ERC721Receivable.sol │ ├── ERC721ReceiverDraft.sol │ └── ERC721ReceiverFinal.sol ├── Migrations.sol ├── Ownership │ ├── HasNoEther.sol │ └── Ownable.sol ├── Test │ ├── Debuggable.sol │ ├── Delegate │ │ ├── CompositeInterface.sol │ │ ├── Delegate.sol │ │ └── SimpleInterface.sol │ ├── ERC721TokenMock.sol │ ├── Selector.sol │ ├── SimpleWallet.sol │ ├── StandardTokenMock.sol │ └── ThrowOnPayable.sol ├── Wallet │ ├── CloneableWallet.sol │ ├── CoreWallet.sol │ ├── FullWallet.sol │ └── QueryableWallet.sol └── WalletFactory │ ├── CloneFactory.sol │ ├── FullWalletByteCode.sol │ └── WalletFactory.sol ├── migrations ├── 1_initial_migration.js └── 2_main_contracts.js ├── out ├── transaction │ └── Dapper Wallet - Multi-Sig Transaction Structure.svg └── wallet │ └── Dapper Wallet - Component Diagram.svg ├── package.json ├── scripts └── generate-wallet-bytecode-contract.js ├── test ├── .gitkeep ├── utils.js ├── wallet-utils.js └── wallet.test.js ├── transaction.wsd ├── truffle.js ├── wallet.wsd └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,vim,solidity,sublimetext,visualstudiocode 3 | 4 | ### OSX ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Solidity ### 32 | # Logs 33 | logs 34 | *.log 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | 39 | # Runtime data 40 | pids 41 | *.pid 42 | *.seed 43 | *.pid.lock 44 | 45 | # Directory for instrumented libs generated by jscoverage/JSCover 46 | lib-cov 47 | 48 | # Coverage directory used by tools like istanbul 49 | coverage 50 | 51 | # nyc test coverage 52 | .nyc_output 53 | 54 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 55 | .grunt 56 | 57 | # Bower dependency directory (https://bower.io/) 58 | bower_components 59 | 60 | # node-waf configuration 61 | .lock-wscript 62 | 63 | # Compiled binary addons (http://nodejs.org/api/addons.html) 64 | build/Release 65 | 66 | # Dependency directories 67 | node_modules/ 68 | jspm_packages/ 69 | 70 | # Typescript v1 declaration files 71 | typings/ 72 | 73 | # Optional npm cache directory 74 | .npm 75 | 76 | # Optional eslint cache 77 | .eslintcache 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # dotenv environment variables file 89 | .env 90 | 91 | ### SublimeText ### 92 | # cache files for sublime text 93 | *.tmlanguage.cache 94 | *.tmPreferences.cache 95 | *.stTheme.cache 96 | 97 | # workspace files are user-specific 98 | *.sublime-workspace 99 | 100 | # project files should be checked into the repository, unless a significant 101 | # proportion of contributors will probably not be using SublimeText 102 | # *.sublime-project 103 | 104 | # sftp configuration file 105 | sftp-config.json 106 | 107 | # Package control specific files 108 | Package Control.last-run 109 | Package Control.ca-list 110 | Package Control.ca-bundle 111 | Package Control.system-ca-bundle 112 | Package Control.cache/ 113 | Package Control.ca-certs/ 114 | Package Control.merged-ca-bundle 115 | Package Control.user-ca-bundle 116 | oscrypto-ca-bundle.crt 117 | bh_unicode_properties.cache 118 | 119 | # Sublime-github package stores a github token in this file 120 | # https://packagecontrol.io/packages/sublime-github 121 | GitHub.sublime-settings 122 | 123 | ### Vim ### 124 | # swap 125 | .sw[a-p] 126 | .*.sw[a-p] 127 | # session 128 | Session.vim 129 | # temporary 130 | .netrwhist 131 | *~ 132 | # auto-generated tag files 133 | tags 134 | 135 | ### VisualStudioCode ### 136 | .vscode/* 137 | #!.vscode/settings.json 138 | #!.vscode/tasks.json 139 | #!.vscode/launch.json 140 | #!.vscode/extensions.json 141 | .history 142 | 143 | 144 | # End of https://www.gitignore.io/api/osx,vim,solidity,sublimetext,visualstudiocode 145 | 146 | ### Manticore ### 147 | contracts/mcore_*/ 148 | 149 | ### Truffle ### 150 | build/ 151 | -------------------------------------------------------------------------------- /Dapper Wallet - Component Diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Dapper Wallet - Component Diagram 13 | 14 | 15 | Smart Contract Wallet 16 | 17 | 18 | 19 | 20 | Device Keys 21 | 22 | 23 | 24 | 25 | Recovery 26 | 27 | 28 | User Cold Storage 29 | 30 | 31 | Cosigning Contract 1 32 | 33 | 34 | Recovery Transaction 35 | 36 | 37 | 38 | 39 | DK1 40 | 41 | 42 | 43 | 44 | DK2 45 | 46 | 47 | 48 | 49 | DK3 50 | 51 | 52 | 53 | 54 | Recovery Key 55 | 56 | 57 | 58 | 59 | Backup Key 60 | 61 | 62 | 63 | -handleTransaction() 64 | 65 | 66 | 67 | - Sets 68 | Backup Key 69 | as sole 70 | Device Key 71 | - Signed by 72 | Recovery Key 73 | 74 | 75 | 76 | 77 | Cosiging Key 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright © 2007 Free Software Foundation, Inc. 5 | 6 | Preamble 7 | The GNU General Public License is a free, copyleft license for software and other kinds of works. 8 | 9 | The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. 10 | 11 | When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. 12 | 13 | To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. 14 | 15 | For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 16 | 17 | Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. 18 | 19 | For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. 20 | 21 | Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. 22 | 23 | Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. 24 | 25 | The precise terms and conditions for copying, distribution and modification follow. 26 | 27 | TERMS AND CONDITIONS 28 | 0. Definitions. 29 | “This License” refers to version 3 of the GNU General Public License. 30 | 31 | “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. 32 | 33 | “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. 34 | 35 | To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. 36 | 37 | A “covered work” means either the unmodified Program or a work based on the Program. 38 | 39 | To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. 40 | 41 | To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. 42 | 43 | An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 44 | 45 | 1. Source Code. 46 | The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. 47 | 48 | A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. 49 | 50 | The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. 51 | 52 | The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. 53 | 54 | The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. 55 | 56 | The Corresponding Source for a work in source code form is that same work. 57 | 58 | 2. Basic Permissions. 59 | All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. 60 | 61 | You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. 62 | 63 | Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 64 | 65 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 66 | No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. 67 | 68 | When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 69 | 70 | 4. Conveying Verbatim Copies. 71 | You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. 72 | 73 | You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 74 | 75 | 5. Conveying Modified Source Versions. 76 | You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: 77 | 78 | a) The work must carry prominent notices stating that you modified it, and giving a relevant date. 79 | b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. 80 | c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. 81 | d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. 82 | A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 83 | 84 | 6. Conveying Non-Source Forms. 85 | You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: 86 | 87 | a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. 88 | b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. 89 | c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. 90 | d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. 91 | e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. 92 | A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. 93 | 94 | A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. 95 | 96 | “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. 97 | 98 | If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). 99 | 100 | The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. 101 | 102 | Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 103 | 104 | 7. Additional Terms. 105 | “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. 106 | 107 | When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. 108 | 109 | Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: 110 | 111 | a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or 112 | b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or 113 | c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or 114 | d) Limiting the use for publicity purposes of names of licensors or authors of the material; or 115 | e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or 116 | f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. 117 | All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. 118 | 119 | If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. 120 | 121 | Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 122 | 123 | 8. Termination. 124 | You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). 125 | 126 | However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. 127 | 128 | Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. 129 | 130 | Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 131 | 132 | 9. Acceptance Not Required for Having Copies. 133 | You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 134 | 135 | 10. Automatic Licensing of Downstream Recipients. 136 | Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. 137 | 138 | An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. 139 | 140 | You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 141 | 142 | 11. Patents. 143 | A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. 144 | 145 | A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. 146 | 147 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. 148 | 149 | In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. 150 | 151 | If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. 152 | 153 | If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. 154 | 155 | A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. 156 | 157 | Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 158 | 159 | 12. No Surrender of Others' Freedom. 160 | If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 161 | 162 | 13. Use with the GNU Affero General Public License. 163 | Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 164 | 165 | 14. Revised Versions of this License. 166 | The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 167 | 168 | Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. 169 | 170 | If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. 171 | 172 | Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 173 | 174 | 15. Disclaimer of Warranty. 175 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 176 | 177 | 16. Limitation of Liability. 178 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 179 | 180 | 17. Interpretation of Sections 15 and 16. 181 | If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 182 | 183 | END OF TERMS AND CONDITIONS 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dapper-Contracts 2 | 3 | ## Overview 4 | 5 | This repository implements the [Dapper](https://www.meetdapper.com/?utm_source=github) Ethereum smart contract wallet. The wallet has recovery and multi-signature capabilities (via [`cosigner address`](#cosigner-address)) as well as allowing for users to have full sovereignty over all features. 6 | 7 | ## Audit of Dapper 8 | To view the audit of Dapper's smart contracts please see Sigma Prime's audit [here](https://github.com/sigp/public-audits/blob/master/dapper-wallet/review.pdf) and blog post describing the audit [here](https://blog.sigmaprime.io/dapper-wallet-review.html). 9 | 10 | ## Dev Setup 11 | 12 | #### Requirements 13 | 14 | Node 9 15 | 16 | #### Run Tests 17 | 18 | - `npm install` 19 | - `npm run build` 20 | - due to contract bytecode being dependent upon the path at which it was compiled, copy the bytecode from `FullWallet.json` into `WalletFactory.sol` 21 | - `npm test` 22 | 23 | ### Definitions 24 | 25 | **key** - Unless otherwise noted, a `key` is defined as an Ethereum compatible public/private key pair, used with [Ethereum Accounts](https://github.com/ethereum/wiki/wiki/White-Paper#ethereum-accounts). When in the context of smart contracts, key refers to an [Ethereum address](http://gavwood.com/paper.pdf). 26 | 27 | **transaction** - Unless otherwise noted, a `transaction` is defined as an [Ethereum transaction](https://github.com/ethereum/wiki/wiki/White-Paper#messages-and-transactions). 28 | 29 | **value transfer** - the transferring of value by way of ETH, ERC20, ERC721, or any 3rd party contract call. 30 | 31 | ## Wallet Design and Features 32 | 33 | 34 | 35 | In contrast to the standard [HD wallet](https://en.bitcoin.it/wiki/Deterministic_wallet) implementation, rather than an [externally owned account](http://ethdocs.org/en/latest/contracts-and-transactions/account-types-gas-and-transactions.html), all assets (ETH, ERC20, ERC721, etc.) for the user are associated with the wallet's public address (contract account) for which by construction there exists no private key; hence **value transfer** actions from the wallet to another address require function calls to be performed on the wallet itself. For *importing* assets, one simply sends them to the user wallet contract address. 36 | 37 | ### Recovery Key 38 | 39 | The `recovery key` is an ephemeral key that exists only in memory on the clients device for a brief moment. The key is generated and used to sign the `recovery transaction` and then erased from memory. The wallet contract keeps track of the public address of the `recovery key` that signed the `recovery transaction`. 40 | 41 | ### Recovery Transaction 42 | 43 | The `recovery transaction` is a transaction signed by the `recovery key`. This transaction authorizes the **RECOVERY** operation. The **RECOVERY** operation is defined as the one time atomic removal of all existing `device keys` and the assignment of the `backup key` as the sole `device key`. 44 | 45 | It should be noted that the `recovery transaction` in and of itself is of no use to anyone without the corresponding `backup key`. 46 | 47 | ### Backup Key 48 | 49 | The `backup key` is a key that is used in conjunction with the `recovery transaction` in order to perform a **RECOVERY**. 50 | 51 | It should be noted that the `backup key` in and of itself is of no use to anyone without the corresponding `recovery transaction`. 52 | 53 | In the Dapper client interface, the `backup key` is stored in the users "Recovery Kit" in [mini private key format](https://en.bitcoin.it/wiki/Mini_private_key_format). 54 | 55 | ### Device Keys 56 | 57 | Administration and use of the wallet is controlled by what we refer to as `device keys`. More than one `device key` can exist. A `device key` needn't be the "creator" or "owner" of the wallet contract. 58 | 59 | It is of note that a `device key` can also be a smart contract, which provides a way to provide additional functionality; for example: a dead man switch or enforcement of a daily ETH limit. 60 | 61 | *It is recommended that the number of device keys is kept to a minimum to increase security and reduce attack surface* 62 | 63 | *As these keys allow unrestrained value transfer from the wallet, it is highly recommended that `device keys` have a `cosigner` set to improve security and reduce risk and a multiple single points of failure scenario* 64 | 65 | #### Device Key Capabilities 66 | 67 | - Perform **value transfer** transactions 68 | - Add another `device key` 69 | - Remove another `device key` 70 | - Adjust the `cosigner` on a `device key` 71 | - Rotate the `backup key` and `recovery transaction` 72 | 73 | #### Device Key Inabilities 74 | 75 | - Perform a RECOVERY operation; this is restricted to the `recovery key` only. 76 | 77 | #### Device Key Properties 78 | 79 | An `device key`'s only property is an optional [`cosigner address`](#cosigner-address) 80 | 81 | ### Cosigner Address 82 | 83 | Any `device key` can sign the outer transaction that allows you to perform value transactions, etc. However, a `cosigner` address can be set per `device key` such that a second signature must be provided in addition to the one provided by the `device key`. 84 | 85 | This design allows for on-chain (smart contract) or off-chain checks (ie. fraud detection) to be performed by a cosigning service. 86 | 87 | The cosigning key can also be replaced or removed, allowing for full flexibility and fine grained control of permissions and authorization. 88 | 89 | ### Multi-Signature Implementation 90 | 91 | The wallet achieves multi-signature capabilities by way of the `invoke()` method and its variants. Performing all value transfers, administration of `device keys` and rotating the `backup key` and `recovery transaction` are required to be called through the `invoke()` method, thus allowing for a cosigning check on all aforementioned operations. The `invoke()` methods variants are capable of receiving up to two signatures, in addition to the "free" signature provided by `msg.sender`. 92 | 93 | ### Recovery Operation 94 | 95 | A RECOVERY operation is performed by submitting the `recovery transaction` to the blockchain network. The effects of this transaction are as follows: 96 | 97 | - Remove all existing device keys (scorched earth policy) 98 | - Add the backup key as the only device key 99 | 100 | This operation is intended to be executed in the scenario where all the users device keys are LOST, STOLEN or COMPROMISED. The operation essentially “activates” the `backup key`, and as such the `backup key` is no longer considered a `backup key` but rather a full `device key`. 101 | 102 | The user can then rotate the `backup key` and `recovery transaction` via their new `device key`. 103 | 104 | For questions, inquiries or more please email support@meetdapper.com 105 | -------------------------------------------------------------------------------- /contracts/ECDSA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title ECDSA is a library that contains useful methods for working with ECDSA signatures 5 | library ECDSA { 6 | 7 | /// @notice Extracts the r, s, and v components from the `sigData` field starting from the `offset` 8 | /// @dev Note: does not do any bounds checking on the arguments! 9 | /// @param sigData the signature data; could be 1 or more packed signatures. 10 | /// @param offset the offset in sigData from which to start unpacking the signature components. 11 | function extractSignature(bytes memory sigData, uint256 offset) internal pure returns (bytes32 r, bytes32 s, uint8 v) { 12 | // Divide the signature in r, s and v variables 13 | // ecrecover takes the signature parameters, and the only way to get them 14 | // currently is to use assembly. 15 | // solium-disable-next-line security/no-inline-assembly 16 | assembly { 17 | let dataPointer := add(sigData, offset) 18 | r := mload(add(dataPointer, 0x20)) 19 | s := mload(add(dataPointer, 0x40)) 20 | v := byte(0, mload(add(dataPointer, 0x60))) 21 | } 22 | 23 | return (r, s, v); 24 | } 25 | } -------------------------------------------------------------------------------- /contracts/ERC1271/ERC1271.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | contract ERC1271 { 4 | 5 | /// @dev bytes4(keccak256("isValidSignature(bytes32,bytes)") 6 | bytes4 internal constant ERC1271_VALIDSIGNATURE = 0x1626ba7e; 7 | 8 | /// @dev Should return whether the signature provided is valid for the provided data 9 | /// @param hash 32-byte hash of the data that is signed 10 | /// @param _signature Signature byte array associated with _data 11 | /// MUST return the bytes4 magic value 0x1626ba7e when function passes. 12 | /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) 13 | /// MUST allow external calls 14 | function isValidSignature( 15 | bytes32 hash, 16 | bytes calldata _signature) 17 | external 18 | view 19 | returns (bytes4); 20 | } -------------------------------------------------------------------------------- /contracts/ERC165/ERC165.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | interface ERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceID The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 12 | } -------------------------------------------------------------------------------- /contracts/ERC165/ERC165Checker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./ERC165Query.sol"; 4 | 5 | /// @title ERC165Checker 6 | /// This contract makes sure we say we adhere to the right interfaces 7 | contract ERC165Checker is ERC165Query { 8 | 9 | function checkInterfaces(address wallet, bytes4[] calldata interfaces) external view returns (bool) { 10 | for (uint i = 0; i < interfaces.length; i++) { 11 | if (!doesContractImplementInterface(wallet, interfaces[i])) { 12 | return false; 13 | } 14 | } 15 | return true; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /contracts/ERC165/ERC165Query.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title ERC165Query example 5 | /// @notice see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md 6 | contract ERC165Query { 7 | bytes4 public constant _INTERFACE_ID_INVALID = 0xffffffff; 8 | bytes4 public constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; 9 | 10 | function doesContractImplementInterface( 11 | address _contract, 12 | bytes4 _interfaceId 13 | ) 14 | public 15 | view 16 | returns (bool) 17 | { 18 | uint256 success; 19 | uint256 result; 20 | 21 | (success, result) = noThrowCall(_contract, _INTERFACE_ID_ERC165); 22 | if ((success == 0) || (result == 0)) { 23 | return false; 24 | } 25 | 26 | (success, result) = noThrowCall(_contract, _INTERFACE_ID_INVALID); 27 | if ((success == 0) || (result != 0)) { 28 | return false; 29 | } 30 | 31 | (success, result) = noThrowCall(_contract, _interfaceId); 32 | if ((success == 1) && (result == 1)) { 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | function noThrowCall( 39 | address _contract, 40 | bytes4 _interfaceId 41 | ) 42 | internal 43 | view 44 | returns ( 45 | uint256 success, 46 | uint256 result 47 | ) 48 | { 49 | bytes memory encodedParams = abi.encodeWithSelector(_INTERFACE_ID_ERC165, _interfaceId); 50 | 51 | // solhint-disable-next-line no-inline-assembly 52 | assembly { 53 | let encodedParams_data := add(0x20, encodedParams) 54 | let encodedParams_size := mload(encodedParams) 55 | 56 | let output := mload(0x40) // Find empty storage location using "free memory pointer" 57 | mstore(output, 0x0) 58 | 59 | success := staticcall( 60 | 30000, // 30k gas 61 | _contract, // To addr 62 | encodedParams_data, 63 | encodedParams_size, 64 | output, 65 | 0x20 // Outputs are 32 bytes long 66 | ) 67 | 68 | result := mload(output) // Load the result 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /contracts/ERC223/ERC223Receiver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title ERC223Receiver ensures we are ERC223 compatible 5 | /// @author Christopher Scott 6 | contract ERC223Receiver { 7 | 8 | bytes4 public constant ERC223_ID = 0xc0ee0b8a; 9 | 10 | struct TKN { 11 | address sender; 12 | uint value; 13 | bytes data; 14 | bytes4 sig; 15 | } 16 | 17 | /// @notice tokenFallback is called from an ERC223 compatible contract 18 | /// @param _from the address from which the token was sent 19 | /// @param _value the amount of tokens sent 20 | /// @param _data the data sent with the transaction 21 | function tokenFallback(address _from, uint _value, bytes memory _data) public pure { 22 | _from; 23 | _value; 24 | _data; 25 | // TKN memory tkn; 26 | // tkn.sender = _from; 27 | // tkn.value = _value; 28 | // tkn.data = _data; 29 | // uint32 u = uint32(_data[3]) + (uint32(_data[2]) << 8) + (uint32(_data[1]) << 16) + (uint32(_data[0]) << 24); 30 | // tkn.sig = bytes4(u); 31 | 32 | /* tkn variable is analogue of msg variable of Ether transaction 33 | * tkn.sender is person who initiated this token transaction (analogue of msg.sender) 34 | * tkn.value the number of tokens that were sent (analogue of msg.value) 35 | * tkn.data is data of token transaction (analogue of msg.data) 36 | * tkn.sig is 4 bytes signature of function 37 | * if data of token transaction is a function execution 38 | */ 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /contracts/ERC721/ERC721Receivable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./ERC721ReceiverDraft.sol"; 4 | import "./ERC721ReceiverFinal.sol"; 5 | 6 | /// @title ERC721Receivable handles the reception of ERC721 tokens 7 | /// See ERC721 specification 8 | /// @author Christopher Scott 9 | /// @dev These functions are public, and could be called by anyone, even in the case 10 | /// where no NFTs have been transferred. Since it's not a reliable source of 11 | /// truth about ERC721 tokens being transferred, we save the gas and don't 12 | /// bother emitting a (potentially spurious) event as found in 13 | /// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/5471fc808a17342d738853d7bf3e9e5ef3108074/contracts/mocks/ERC721ReceiverMock.sol 14 | contract ERC721Receivable is ERC721ReceiverDraft, ERC721ReceiverFinal { 15 | 16 | /// @notice Handle the receipt of an NFT 17 | /// @dev The ERC721 smart contract calls this function on the recipient 18 | /// after a `transfer`. This function MAY throw to revert and reject the 19 | /// transfer. This function MUST use 50,000 gas or less. Return of other 20 | /// than the magic value MUST result in the transaction being reverted. 21 | /// Note: the contract address is always the message sender. 22 | /// @param _from The sending address 23 | /// @param _tokenId The NFT identifier which is being transfered 24 | /// @param data Additional data with no specified format 25 | /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` 26 | /// unless throwing 27 | function onERC721Received(address _from, uint256 _tokenId, bytes calldata data) external returns(bytes4) { 28 | _from; 29 | _tokenId; 30 | data; 31 | 32 | // emit ERC721Received(_operator, _from, _tokenId, _data, gasleft()); 33 | 34 | return ERC721_RECEIVED_DRAFT; 35 | } 36 | 37 | /// @notice Handle the receipt of an NFT 38 | /// @dev The ERC721 smart contract calls this function on the recipient 39 | /// after a `safetransfer`. This function MAY throw to revert and reject the 40 | /// transfer. Return of other than the magic value MUST result in the 41 | /// transaction being reverted. 42 | /// Note: the contract address is always the message sender. 43 | /// @param _operator The address which called `safeTransferFrom` function 44 | /// @param _from The address which previously owned the token 45 | /// @param _tokenId The NFT identifier which is being transferred 46 | /// @param _data Additional data with no specified format 47 | /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 48 | function onERC721Received( 49 | address _operator, 50 | address _from, 51 | uint256 _tokenId, 52 | bytes memory _data 53 | ) 54 | public 55 | returns(bytes4) 56 | { 57 | _operator; 58 | _from; 59 | _tokenId; 60 | _data; 61 | 62 | // emit ERC721Received(_operator, _from, _tokenId, _data, gasleft()); 63 | 64 | return ERC721_RECEIVED_FINAL; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /contracts/ERC721/ERC721ReceiverDraft.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title ERC721ReceiverDraft 5 | /// @dev Interface for any contract that wants to support safeTransfers from 6 | /// ERC721 asset contracts. 7 | /// @dev Note: this is the interface defined from 8 | /// https://github.com/ethereum/EIPs/commit/2bddd126def7c046e1e62408dc2b51bdd9e57f0f 9 | /// to https://github.com/ethereum/EIPs/commit/27788131d5975daacbab607076f2ee04624f9dbb 10 | /// and is not the final interface. 11 | /// Due to the extended period of time this revision was specified in the draft, 12 | /// we are supporting both this and the newer (final) interface in order to be 13 | /// compatible with any ERC721 implementations that may have used this interface. 14 | contract ERC721ReceiverDraft { 15 | 16 | /// @dev Magic value to be returned upon successful reception of an NFT 17 | /// Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`, 18 | /// which can be also obtained as `ERC721ReceiverDraft(0).onERC721Received.selector` 19 | /// @dev see https://github.com/ethereum/EIPs/commit/2bddd126def7c046e1e62408dc2b51bdd9e57f0f 20 | bytes4 internal constant ERC721_RECEIVED_DRAFT = 0xf0b9e5ba; 21 | 22 | /// @notice Handle the receipt of an NFT 23 | /// @dev The ERC721 smart contract calls this function on the recipient 24 | /// after a `transfer`. This function MAY throw to revert and reject the 25 | /// transfer. This function MUST use 50,000 gas or less. Return of other 26 | /// than the magic value MUST result in the transaction being reverted. 27 | /// Note: the contract address is always the message sender. 28 | /// @param _from The sending address 29 | /// @param _tokenId The NFT identifier which is being transfered 30 | /// @param data Additional data with no specified format 31 | /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` 32 | /// unless throwing 33 | function onERC721Received(address _from, uint256 _tokenId, bytes calldata data) external returns(bytes4); 34 | } -------------------------------------------------------------------------------- /contracts/ERC721/ERC721ReceiverFinal.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title ERC721ReceiverFinal 5 | /// @notice Interface for any contract that wants to support safeTransfers from 6 | /// ERC721 asset contracts. 7 | /// @dev Note: this is the final interface as defined at http://erc721.org 8 | contract ERC721ReceiverFinal { 9 | 10 | /// @dev Magic value to be returned upon successful reception of an NFT 11 | /// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`, 12 | /// which can be also obtained as `ERC721ReceiverFinal(0).onERC721Received.selector` 13 | /// @dev see https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.12.0/contracts/token/ERC721/ERC721Receiver.sol 14 | bytes4 internal constant ERC721_RECEIVED_FINAL = 0x150b7a02; 15 | 16 | /// @notice Handle the receipt of an NFT 17 | /// @dev The ERC721 smart contract calls this function on the recipient 18 | /// after a `safetransfer`. This function MAY throw to revert and reject the 19 | /// transfer. Return of other than the magic value MUST result in the 20 | /// transaction being reverted. 21 | /// Note: the contract address is always the message sender. 22 | /// @param _operator The address which called `safeTransferFrom` function 23 | /// @param _from The address which previously owned the token 24 | /// @param _tokenId The NFT identifier which is being transferred 25 | /// @param _data Additional data with no specified format 26 | /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 27 | function onERC721Received( 28 | address _operator, 29 | address _from, 30 | uint256 _tokenId, 31 | bytes memory _data 32 | ) 33 | public 34 | returns (bytes4); 35 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | // @dev Generated and used by the Truffle framework 5 | contract Migrations { 6 | address public owner; 7 | uint public lastCompletedMigration; 8 | 9 | constructor() public { 10 | owner = msg.sender; 11 | } 12 | 13 | modifier restricted() { 14 | if (msg.sender == owner) _; 15 | } 16 | 17 | function setCompleted(uint completed) public restricted { 18 | lastCompletedMigration = completed; 19 | } 20 | 21 | function upgrade(address newAddress) public restricted { 22 | Migrations upgraded = Migrations(newAddress); 23 | upgraded.setCompleted(lastCompletedMigration); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/Ownership/HasNoEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./Ownable.sol"; 4 | 5 | 6 | /// @title HasNoEther is for contracts that should not own Ether 7 | contract HasNoEther is Ownable { 8 | 9 | /// @dev This contructor rejects incoming Ether 10 | constructor() public payable { 11 | require(msg.value == 0, "must not send Ether"); 12 | } 13 | 14 | /// @dev Disallows direct send by default function not being `payable` 15 | function() external {} 16 | 17 | /// @dev Transfers all Ether held by this contract to the owner. 18 | function reclaimEther() external onlyOwner { 19 | msg.sender.transfer(address(this).balance); 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/Ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title Ownable is for contracts that can be owned. 5 | /// @dev The Ownable contract keeps track of an owner address, 6 | /// and provides basic authorization functions. 7 | contract Ownable { 8 | 9 | /// @dev the owner of the contract 10 | address public owner; 11 | 12 | /// @dev Fired when the owner to renounce ownership, leaving no one 13 | /// as the owner. 14 | /// @param previousOwner The previous `owner` of this contract 15 | event OwnershipRenounced(address indexed previousOwner); 16 | 17 | /// @dev Fired when the owner to changes ownership 18 | /// @param previousOwner The previous `owner` 19 | /// @param newOwner The new `owner` 20 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 21 | 22 | /// @dev sets the `owner` to `msg.sender` 23 | constructor() public { 24 | owner = msg.sender; 25 | } 26 | 27 | /// @dev Throws if the `msg.sender` is not the current `owner` 28 | modifier onlyOwner() { 29 | require(msg.sender == owner, "must be owner"); 30 | _; 31 | } 32 | 33 | /// @dev Allows the current `owner` to renounce ownership 34 | function renounceOwnership() external onlyOwner { 35 | emit OwnershipRenounced(owner); 36 | owner = address(0); 37 | } 38 | 39 | /// @dev Allows the current `owner` to transfer ownership 40 | /// @param _newOwner The new `owner` 41 | function transferOwnership(address _newOwner) external onlyOwner { 42 | _transferOwnership(_newOwner); 43 | } 44 | 45 | /// @dev Internal version of `transferOwnership` 46 | /// @param _newOwner The new `owner` 47 | function _transferOwnership(address _newOwner) internal { 48 | require(_newOwner != address(0), "cannot renounce ownership"); 49 | emit OwnershipTransferred(owner, _newOwner); 50 | owner = _newOwner; 51 | } 52 | } -------------------------------------------------------------------------------- /contracts/Test/Debuggable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title aid development, should not be released into final version 5 | contract Debuggable { 6 | event LogUint256(string label, uint256 value); 7 | event LogUint64(string label, uint64 b); 8 | event LogUint32(string label, uint32 b); 9 | event LogUint8(string label, uint8 b); 10 | event LogInt256(string label, int256 value); 11 | event LogAddress(string label, address addr); 12 | event LogBytes32(string label, bytes32 b); 13 | event LogBytes4(string label, bytes4 b); 14 | event LogBytes1(string label, bytes1 b); 15 | event LogBytes(string label, bytes b); 16 | event LogBool(string label, bool b); 17 | 18 | function timeNow() public view returns (uint256) { 19 | return now; 20 | } 21 | 22 | function logBytes(string memory s, bytes memory b) internal { 23 | emit LogBytes(s, b); 24 | } 25 | 26 | function logUint64(string memory s, uint64 x) internal { 27 | emit LogUint64(s, x); 28 | } 29 | 30 | function logBytes1(string memory s, bytes1 x) internal { 31 | emit LogBytes1(s, x); 32 | } 33 | 34 | function logUint32(string memory s, uint32 x) internal { 35 | emit LogUint32(s, x); 36 | } 37 | 38 | function logAddress(string memory s, address x) internal { 39 | emit LogAddress(s, x); 40 | } 41 | 42 | function logUint256(string memory s, uint256 x) internal { 43 | emit LogUint256(s, x); 44 | } 45 | 46 | function logInt256(string memory s, int256 x) internal { 47 | emit LogInt256(s, x); 48 | } 49 | 50 | function logBytes32(string memory s, bytes32 b) internal { 51 | emit LogBytes32(s, b); 52 | } 53 | 54 | function logBool(string memory s, bool b) internal { 55 | emit LogBool(s, b); 56 | } 57 | 58 | function logUint8(string memory s, uint8 i) internal { 59 | emit LogUint8(s, i); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/Test/Delegate/CompositeInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | interface CompositeInterface { 4 | function doSomething() external returns (uint256); 5 | function doSomethingElse(uint256 _paramter) external returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/Test/Delegate/Delegate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./SimpleInterface.sol"; 4 | import "./CompositeInterface.sol"; 5 | 6 | /// @title An example Delegate contract for testing delegate functionality. 7 | contract Delegate is SimpleInterface, CompositeInterface { 8 | uint256 storedValue = 0; 9 | 10 | function doSomething() external returns (uint256) { 11 | return 42; 12 | } 13 | 14 | function doSomethingElse(uint256 _parameter) external returns (uint256) { 15 | return _parameter; 16 | } 17 | 18 | function doSomethingThatReverts() pure external { 19 | revert(); 20 | } 21 | 22 | function doSomethingThatWritesToStorage() external { 23 | storedValue = storedValue + 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/Test/Delegate/SimpleInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | interface SimpleInterface { 4 | function doSomething() external returns (uint256); 5 | } 6 | -------------------------------------------------------------------------------- /contracts/Test/ERC721TokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol"; 4 | 5 | /** 6 | * @title ERC721TokenMock 7 | * This mock just provides a public mint and burn functions for testing purposes, 8 | * and a public setter for metadata URI 9 | */ 10 | contract ERC721TokenMock is ERC721Full { 11 | constructor(string memory name, string memory symbol) public 12 | ERC721Full(name, symbol) 13 | { } 14 | 15 | function mint(address _to, uint256 _tokenId) public { 16 | super._mint(_to, _tokenId); 17 | } 18 | 19 | function burn(uint256 _tokenId) public { 20 | super._burn(ownerOf(_tokenId), _tokenId); 21 | } 22 | 23 | function setTokenURI(uint256 _tokenId, string memory _uri) public { 24 | super._setTokenURI(_tokenId, _uri); 25 | } 26 | } -------------------------------------------------------------------------------- /contracts/Test/Selector.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | // apparently events are not '.selector'able 4 | import "../Wallet/FullWallet.sol"; 5 | 6 | contract Selector { 7 | 8 | // Events 9 | 10 | function invocationSuccessSelector() public pure returns (bytes32) { 11 | return keccak256("InvocationSuccess(bytes32,uint256,uint256)"); 12 | } 13 | 14 | function authorizedSelector() public pure returns (bytes32) { 15 | return keccak256("Authorized(address,address)"); 16 | } 17 | 18 | function emergencyRecoverySelector() public pure returns (bytes32) { 19 | return keccak256("EmergencyRecovery(address,address)"); 20 | } 21 | 22 | function recoveryAddressChangedSelector() public pure returns (bytes32) { 23 | return keccak256("RecoveryAddressChanged(address,address)"); 24 | } 25 | 26 | // Invoke methods 27 | 28 | function invoke0Selector() public pure returns (bytes4) { 29 | FullWallet w; 30 | return w.invoke0.selector; 31 | } 32 | 33 | function invoke1CosignerSendsSelector() public pure returns (bytes4) { 34 | FullWallet w; 35 | return w.invoke1CosignerSends.selector; 36 | } 37 | 38 | function invoke2Selector() public pure returns (bytes4) { 39 | FullWallet w; 40 | return w.invoke2.selector; 41 | } 42 | 43 | // ERC1271 44 | function isValidSignatureSelector() public pure returns (bytes4) { 45 | FullWallet w; 46 | return w.isValidSignature.selector; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /contracts/Test/SimpleWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | //import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "../Ownership/Ownable.sol"; 5 | 6 | 7 | /// @notice this wallet does not throw on receipt of ETH 8 | contract SimpleWallet is Ownable { 9 | 10 | /// @dev emit this event when someone sends you ETH 11 | /// @param from the address which sent you ether 12 | /// @param value the amount of ether sent 13 | event Deposited(address indexed from, uint value); 14 | 15 | /// @notice Gets called when a transaction is received without calling a method 16 | function() external payable { 17 | // length cannot be > 0 as we won't have enough gas 18 | require(msg.data.length == 0); 19 | // k + unindexedBytes * a + indexedTopics * b 20 | // k = 375, a = 8, b = 375 21 | // because we only index the first one, we should be under 2300 gas stipend 22 | // 375 + (32)*8 + (1)*375 = 1006 23 | // so we can afford this check 24 | if (msg.value > 0) { 25 | // Fire deposited event if we are receiving funds 26 | emit Deposited(msg.sender, msg.value); 27 | } 28 | } 29 | 30 | function transferOut(address payable target) external payable onlyOwner { 31 | target.transfer(msg.value); 32 | } 33 | 34 | function sendOut(address payable target) external payable onlyOwner { 35 | if (!target.send(msg.value)) { 36 | revert("send failed"); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /contracts/Test/StandardTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | //import "./MyStandardToken.sol"; 5 | 6 | // mock class using StandardToken 7 | contract StandardTokenMock is ERC20 { 8 | 9 | constructor(address initialAccount, uint256 initialBalance) public { 10 | _mint(initialAccount, initialBalance); 11 | } 12 | } -------------------------------------------------------------------------------- /contracts/Test/ThrowOnPayable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /// @notice this throws on its default (and only) payable function 4 | contract ThrowOnPayable { 5 | //uint256 balance; 6 | 7 | function() external payable { 8 | 9 | // these three ways will all cause a revert 10 | 11 | // 1. just revert 12 | revert("revert"); 13 | 14 | // 2.a. infinite loop 15 | /* `i` will have max a max value of 255 (initialized as uint8), 16 | * causing an infinite loop. 17 | */ 18 | // for (var i = 0; i < 1000; i++) { 19 | // balance++; 20 | // } 21 | 22 | // 2.b. infinite loop 23 | // while(true) { 24 | // balance++; 25 | // } 26 | } 27 | } -------------------------------------------------------------------------------- /contracts/Wallet/CloneableWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./CoreWallet.sol"; 4 | 5 | 6 | /// @title Cloneable Wallet 7 | /// @notice This contract represents a complete but non working wallet. 8 | /// It is meant to be deployed and serve as the contract that you clone 9 | /// in an EIP 1167 clone setup. 10 | /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md 11 | /// @dev Currently, we are seeing approximatley 933 gas overhead for using 12 | /// the clone wallet; use `FullWallet` if you think users will overtake 13 | /// the transaction threshold over the lifetime of the wallet. 14 | contract CloneableWallet is CoreWallet { 15 | 16 | /// @dev An empty constructor that deploys a NON-FUNCTIONAL version 17 | /// of `CoreWallet` 18 | constructor () public { 19 | initialized = true; 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/Wallet/CoreWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "../ERC721/ERC721Receivable.sol"; 4 | import "../ERC223/ERC223Receiver.sol"; 5 | import "../ERC1271/ERC1271.sol"; 6 | import "../ECDSA.sol"; 7 | 8 | 9 | /// @title Core Wallet 10 | /// @notice A basic smart contract wallet with cosigner functionality. The notion of "cosigner" is 11 | /// the simplest possible multisig solution, a two-of-two signature scheme. It devolves nicely 12 | /// to "one-of-one" (i.e. singlesig) by simply having the cosigner set to the same value as 13 | /// the main signer. 14 | /// 15 | /// Most "advanced" functionality (deadman's switch, multiday recovery flows, blacklisting, etc) 16 | /// can be implemented externally to this smart contract, either as an additional smart contract 17 | /// (which can be tracked as a signer without cosigner, or as a cosigner) or as an off-chain flow 18 | /// using a public/private key pair as cosigner. Of course, the basic cosigning functionality could 19 | /// also be implemented in this way, but (A) the complexity and gas cost of two-of-two multisig (as 20 | /// implemented here) is negligable even if you don't need the cosigner functionality, and 21 | /// (B) two-of-two multisig (as implemented here) handles a lot of really common use cases, most 22 | /// notably third-party gas payment and off-chain blacklisting and fraud detection. 23 | contract CoreWallet is ERC721Receivable, ERC223Receiver, ERC1271 { 24 | 25 | using ECDSA for bytes; 26 | 27 | /// @notice We require that presigned transactions use the EIP-191 signing format. 28 | /// See that EIP for more info: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-191.md 29 | byte public constant EIP191_VERSION_DATA = byte(0); 30 | byte public constant EIP191_PREFIX = byte(0x19); 31 | 32 | /// @notice This is the version of the contract. 33 | string public constant VERSION = "1.1.0"; 34 | 35 | /// @notice This is a sentinel value used to determine when a delegate is set to expose 36 | /// support for an interface containing more than a single function. See `delegates` and 37 | /// `setDelegate` for more information. 38 | address public constant COMPOSITE_PLACEHOLDER = address(1); 39 | 40 | /// @notice A pre-shifted "1", used to increment the authVersion, so we can "prepend" 41 | /// the authVersion to an address (for lookups in the authorizations mapping) 42 | /// by using the '+' operator (which is cheaper than a shift and a mask). See the 43 | /// comment on the `authorizations` variable for how this is used. 44 | uint256 public constant AUTH_VERSION_INCREMENTOR = (1 << 160); 45 | 46 | /// @notice The pre-shifted authVersion (to get the current authVersion as an integer, 47 | /// shift this value right by 160 bits). Starts as `1 << 160` (`AUTH_VERSION_INCREMENTOR`) 48 | /// See the comment on the `authorizations` variable for how this is used. 49 | uint256 public authVersion; 50 | 51 | /// @notice A mapping containing all of the addresses that are currently authorized to manage 52 | /// the assets owned by this wallet. 53 | /// 54 | /// The keys in this mapping are authorized addresses with a version number prepended, 55 | /// like so: (authVersion,96)(address,160). The current authVersion MUST BE included 56 | /// for each look-up; this allows us to effectively clear the entire mapping of its 57 | /// contents merely by incrementing the authVersion variable. (This is important for 58 | /// the emergencyRecovery() method.) Inspired by https://ethereum.stackexchange.com/a/42540 59 | /// 60 | /// The values in this mapping are 256bit words, whose lower 20 bytes constitute "cosigners" 61 | /// for each address. If an address maps to itself, then that address is said to have no cosigner. 62 | /// 63 | /// The upper 12 bytes are reserved for future meta-data purposes. The meta-data could refer 64 | /// to the key (authorized address) or the value (cosigner) of the mapping. 65 | /// 66 | /// Addresses that map to a non-zero cosigner in the current authVersion are called 67 | /// "authorized addresses". 68 | mapping(uint256 => uint256) public authorizations; 69 | 70 | /// @notice A per-key nonce value, incremented each time a transaction is processed with that key. 71 | /// Used for replay prevention. The nonce value in the transaction must exactly equal the current 72 | /// nonce value in the wallet for that key. (This mirrors the way Ethereum's transaction nonce works.) 73 | mapping(address => uint256) public nonces; 74 | 75 | /// @notice A mapping tracking dynamically supported interfaces and their corresponding 76 | /// implementation contracts. Keys are interface IDs and values are addresses of 77 | /// contracts that are responsible for implementing the function corresponding to the 78 | /// interface. 79 | /// 80 | /// Delegates are added (or removed) via the `setDelegate` method after the contract is 81 | /// deployed, allowing support for new interfaces to be dynamically added after deployment. 82 | /// When a delegate is added, its interface ID is considered "supported" under EIP165. 83 | /// 84 | /// For cases where an interface composed of more than a single function must be 85 | /// supported, it is necessary to manually add the composite interface ID with 86 | /// `setDelegate(interfaceId, COMPOSITE_PLACEHOLDER)`. Interface IDs added with the 87 | /// COMPOSITE_PLACEHOLDER address are ignored when called and are only used to specify 88 | /// supported interfaces. 89 | mapping(bytes4 => address) public delegates; 90 | 91 | /// @notice A special address that is authorized to call `emergencyRecovery()`. That function 92 | /// resets ALL authorization for this wallet, and must therefore be treated with utmost security. 93 | /// Reasonable choices for recoveryAddress include: 94 | /// - the address of a private key in cold storage 95 | /// - a physically secured hardware wallet 96 | /// - a multisig smart contract, possibly with a time-delayed challenge period 97 | /// - the zero address, if you like performing without a safety net ;-) 98 | address public recoveryAddress; 99 | 100 | /// @notice Used to track whether or not this contract instance has been initialized. This 101 | /// is necessary since it is common for this wallet smart contract to be used as the "library 102 | /// code" for an clone contract. See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md 103 | /// for more information about clone contracts. 104 | bool public initialized; 105 | 106 | /// @notice Used to decorate methods that can only be called directly by the recovery address. 107 | modifier onlyRecoveryAddress() { 108 | require(msg.sender == recoveryAddress, "sender must be recovery address"); 109 | _; 110 | } 111 | 112 | /// @notice Used to decorate the `init` function so this can only be called one time. Necessary 113 | /// since this contract will often be used as a "clone". (See above.) 114 | modifier onlyOnce() { 115 | require(!initialized, "must not already be initialized"); 116 | initialized = true; 117 | _; 118 | } 119 | 120 | /// @notice Used to decorate methods that can only be called indirectly via an `invoke()` method. 121 | /// In practice, it means that those methods can only be called by a signer/cosigner 122 | /// pair that is currently authorized. Theoretically, we could factor out the 123 | /// signer/cosigner verification code and use it explicitly in this modifier, but that 124 | /// would either result in duplicated code, or additional overhead in the invoke() 125 | /// calls (due to the stack manipulation for calling into the shared verification function). 126 | /// Doing it this way makes calling the administration functions more expensive (since they 127 | /// go through a explicit call() instead of just branching within the contract), but it 128 | /// makes invoke() more efficient. We assume that invoke() will be used much, much more often 129 | /// than any of the administration functions. 130 | modifier onlyInvoked() { 131 | require(msg.sender == address(this), "must be called from `invoke()`"); 132 | _; 133 | } 134 | 135 | /// @notice Emitted when an authorized address is added, removed, or modified. When an 136 | /// authorized address is removed ("deauthorized"), cosigner will be address(0) in 137 | /// this event. 138 | /// 139 | /// NOTE: When emergencyRecovery() is called, all existing addresses are deauthorized 140 | /// WITHOUT Authorized(addr, 0) being emitted. If you are keeping an off-chain mirror of 141 | /// authorized addresses, you must also watch for EmergencyRecovery events. 142 | /// @dev hash is 0xf5a7f4fb8a92356e8c8c4ae7ac3589908381450500a7e2fd08c95600021ee889 143 | /// @param authorizedAddress the address to authorize or unauthorize 144 | /// @param cosigner the 2-of-2 signatory (optional). 145 | event Authorized(address authorizedAddress, uint256 cosigner); 146 | 147 | /// @notice Emitted when an emergency recovery has been performed. If this event is fired, 148 | /// ALL previously authorized addresses have been deauthorized and the only authorized 149 | /// address is the authorizedAddress indicated in this event. 150 | /// @dev hash is 0xe12d0bbeb1d06d7a728031056557140afac35616f594ef4be227b5b172a604b5 151 | /// @param authorizedAddress the new authorized address 152 | /// @param cosigner the cosigning address for `authorizedAddress` 153 | event EmergencyRecovery(address authorizedAddress, uint256 cosigner); 154 | 155 | /// @notice Emitted when the recovery address changes. Either (but not both) of the 156 | /// parameters may be zero. 157 | /// @dev hash is 0x568ab3dedd6121f0385e007e641e74e1f49d0fa69cab2957b0b07c4c7de5abb6 158 | /// @param previousRecoveryAddress the previous recovery address 159 | /// @param newRecoveryAddress the new recovery address 160 | event RecoveryAddressChanged(address previousRecoveryAddress, address newRecoveryAddress); 161 | 162 | /// @dev Emitted when this contract receives a non-zero amount ether via the fallback function 163 | /// (i.e. This event is not fired if the contract receives ether as part of a method invocation) 164 | /// @param from the address which sent you ether 165 | /// @param value the amount of ether sent 166 | event Received(address from, uint value); 167 | 168 | /// @notice Emitted whenever a transaction is processed successfully from this wallet. Includes 169 | /// both simple send ether transactions, as well as other smart contract invocations. 170 | /// @dev hash is 0x101214446435ebbb29893f3348e3aae5ea070b63037a3df346d09d3396a34aee 171 | /// @param hash The hash of the entire operation set. 0 is returned when emitted from `invoke0()`. 172 | /// @param result A bitfield of the results of the operations. A bit of 0 means success, and 1 means failure. 173 | /// @param numOperations A count of the number of operations processed 174 | event InvocationSuccess( 175 | bytes32 hash, 176 | uint256 result, 177 | uint256 numOperations 178 | ); 179 | 180 | /// @notice Emitted when a delegate is added or removed. 181 | /// @param interfaceId The interface ID as specified by EIP165 182 | /// @param delegate The address of the contract implementing the given function. If this is 183 | /// COMPOSITE_PLACEHOLDER, we are indicating support for a composite interface. 184 | event DelegateUpdated(bytes4 interfaceId, address delegate); 185 | 186 | /// @notice The shared initialization code used to setup the contract state regardless of whether or 187 | /// not the clone pattern is being used. 188 | /// @param _authorizedAddress the initial authorized address, must not be zero! 189 | /// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress` 190 | /// @param _recoveryAddress the initial recovery address for the wallet, can be address(0) 191 | function init(address _authorizedAddress, uint256 _cosigner, address _recoveryAddress) public onlyOnce { 192 | require(_authorizedAddress != _recoveryAddress, "Do not use the recovery address as an authorized address."); 193 | require(address(_cosigner) != _recoveryAddress, "Do not use the recovery address as a cosigner."); 194 | require(_authorizedAddress != address(0), "Authorized addresses must not be zero."); 195 | require(address(_cosigner) != address(0), "Initial cosigner must not be zero."); 196 | 197 | recoveryAddress = _recoveryAddress; 198 | // set initial authorization value 199 | authVersion = AUTH_VERSION_INCREMENTOR; 200 | // add initial authorized address 201 | authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner; 202 | 203 | emit Authorized(_authorizedAddress, _cosigner); 204 | } 205 | 206 | /// @notice The fallback function, invoked whenever we receive a transaction that doesn't call any of our 207 | /// named functions. In particular, this method is called when we are the target of a simple send 208 | /// transaction, when someone calls a method we have dynamically added a delegate for, or when someone 209 | /// tries to call a function we don't implement, either statically or dynamically. 210 | /// 211 | /// A correct invocation of this method occurs in two cases: 212 | /// - someone transfers ETH to this wallet (`msg.data.length` is 0) 213 | /// - someone calls a delegated function (`msg.data.length` is greater than 0 and 214 | /// `delegates[msg.sig]` is set) 215 | /// In all other cases, this function will revert. 216 | /// 217 | /// NOTE: Some smart contracts send 0 eth as part of a more complex operation 218 | /// (-cough- CryptoKitties -cough-); ideally, we'd `require(msg.value > 0)` here when 219 | /// `msg.data.length == 0`, but to work with those kinds of smart contracts, we accept zero sends 220 | /// and just skip logging in that case. 221 | function() external payable { 222 | if (msg.value > 0) { 223 | emit Received(msg.sender, msg.value); 224 | } 225 | if (msg.data.length > 0) { 226 | address delegate = delegates[msg.sig]; 227 | require(delegate > COMPOSITE_PLACEHOLDER, "Invalid transaction"); 228 | 229 | // We have found a delegate contract that is responsible for the method signature of 230 | // this call. Now, pass along the calldata of this CALL to the delegate contract. 231 | assembly { 232 | calldatacopy(0, 0, calldatasize()) 233 | let result := staticcall(gas, delegate, 0, calldatasize(), 0, 0) 234 | returndatacopy(0, 0, returndatasize()) 235 | 236 | // If the delegate reverts, we revert. If the delegate does not revert, we return the data 237 | // returned by the delegate to the original caller. 238 | switch result 239 | case 0 { 240 | revert(0, returndatasize()) 241 | } 242 | default { 243 | return(0, returndatasize()) 244 | } 245 | } 246 | } 247 | } 248 | 249 | /// @notice Adds or removes dynamic support for an interface. Can be used in 3 ways: 250 | /// - Add a contract "delegate" that implements a single function 251 | /// - Remove delegate for a function 252 | /// - Specify that an interface ID is "supported", without adding a delegate. This is 253 | /// used for composite interfaces when the interface ID is not a single method ID. 254 | /// @dev Must be called through `invoke` 255 | /// @param _interfaceId The ID of the interface we are adding support for 256 | /// @param _delegate Either: 257 | /// - the address of a contract that implements the function specified by `_interfaceId` 258 | /// for adding an implementation for a single function 259 | /// - 0 for removing an existing delegate 260 | /// - COMPOSITE_PLACEHOLDER for specifying support for a composite interface 261 | function setDelegate(bytes4 _interfaceId, address _delegate) external onlyInvoked { 262 | delegates[_interfaceId] = _delegate; 263 | emit DelegateUpdated(_interfaceId, _delegate); 264 | } 265 | 266 | /// @notice Configures an authorizable address. Can be used in four ways: 267 | /// - Add a new signer/cosigner pair (cosigner must be non-zero) 268 | /// - Set or change the cosigner for an existing signer (if authorizedAddress != cosigner) 269 | /// - Remove the cosigning requirement for a signer (if authorizedAddress == cosigner) 270 | /// - Remove a signer (if cosigner == address(0)) 271 | /// @dev Must be called through `invoke()` 272 | /// @param _authorizedAddress the address to configure authorization 273 | /// @param _cosigner the corresponding cosigning address 274 | function setAuthorized(address _authorizedAddress, uint256 _cosigner) external onlyInvoked { 275 | // TODO: Allowing a signer to remove itself is actually pretty terrible; it could result in the user 276 | // removing their only available authorized key. Unfortunately, due to how the invocation forwarding 277 | // works, we don't actually _know_ which signer was used to call this method, so there's no easy way 278 | // to prevent this. 279 | 280 | // TODO: Allowing the backup key to be set as an authorized address bypasses the recovery mechanisms. 281 | // Dapper can prevent this with offchain logic and the cosigner, but it would be nice to have 282 | // this enforced by the smart contract logic itself. 283 | 284 | require(_authorizedAddress != address(0), "Authorized addresses must not be zero."); 285 | require(_authorizedAddress != recoveryAddress, "Do not use the recovery address as an authorized address."); 286 | require(address(_cosigner) == address(0) || address(_cosigner) != recoveryAddress, "Do not use the recovery address as a cosigner."); 287 | 288 | authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner; 289 | emit Authorized(_authorizedAddress, _cosigner); 290 | } 291 | 292 | /// @notice Performs an emergency recovery operation, removing all existing authorizations and setting 293 | /// a sole new authorized address with optional cosigner. THIS IS A SCORCHED EARTH SOLUTION, and great 294 | /// care should be taken to ensure that this method is never called unless it is a last resort. See the 295 | /// comments above about the proper kinds of addresses to use as the recoveryAddress to ensure this method 296 | /// is not trivially abused. 297 | /// @param _authorizedAddress the new and sole authorized address 298 | /// @param _cosigner the corresponding cosigner address, can be equal to _authorizedAddress 299 | function emergencyRecovery(address _authorizedAddress, uint256 _cosigner) external onlyRecoveryAddress { 300 | require(_authorizedAddress != address(0), "Authorized addresses must not be zero."); 301 | require(_authorizedAddress != recoveryAddress, "Do not use the recovery address as an authorized address."); 302 | require(address(_cosigner) != address(0), "The cosigner must not be zero."); 303 | 304 | // Incrementing the authVersion number effectively erases the authorizations mapping. See the comments 305 | // on the authorizations variable (above) for more information. 306 | authVersion += AUTH_VERSION_INCREMENTOR; 307 | 308 | // Store the new signer/cosigner pair as the only remaining authorized address 309 | authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner; 310 | emit EmergencyRecovery(_authorizedAddress, _cosigner); 311 | } 312 | 313 | /// @notice Sets the recovery address, which can be zero (indicating that no recovery is possible) 314 | /// Can be updated by any authorized address. This address should be set with GREAT CARE. See the 315 | /// comments above about the proper kinds of addresses to use as the recoveryAddress to ensure this 316 | /// mechanism is not trivially abused. 317 | /// @dev Must be called through `invoke()` 318 | /// @param _recoveryAddress the new recovery address 319 | function setRecoveryAddress(address _recoveryAddress) external onlyInvoked { 320 | require( 321 | address(authorizations[authVersion + uint256(_recoveryAddress)]) == address(0), 322 | "Do not use an authorized address as the recovery address." 323 | ); 324 | 325 | address previous = recoveryAddress; 326 | recoveryAddress = _recoveryAddress; 327 | 328 | emit RecoveryAddressChanged(previous, recoveryAddress); 329 | } 330 | 331 | /// @notice Allows ANY caller to recover gas by way of deleting old authorization keys after 332 | /// a recovery operation. Anyone can call this method to delete the old unused storage and 333 | /// get themselves a bit of gas refund in the bargin. 334 | /// @dev keys must be known to caller or else nothing is refunded 335 | /// @param _version the version of the mapping which you want to delete (unshifted) 336 | /// @param _keys the authorization keys to delete 337 | function recoverGas(uint256 _version, address[] calldata _keys) external { 338 | // TODO: should this be 0xffffffffffffffffffffffff ? 339 | require(_version > 0 && _version < 0xffffffff, "Invalid version number."); 340 | 341 | uint256 shiftedVersion = _version << 160; 342 | 343 | require(shiftedVersion < authVersion, "You can only recover gas from expired authVersions."); 344 | 345 | for (uint256 i = 0; i < _keys.length; ++i) { 346 | delete(authorizations[shiftedVersion + uint256(_keys[i])]); 347 | } 348 | } 349 | 350 | /// @notice Should return whether the signature provided is valid for the provided data 351 | /// See https://github.com/ethereum/EIPs/issues/1271 352 | /// @dev This function meets the following conditions as per the EIP: 353 | /// MUST return the bytes4 magic value `0x1626ba7e` when function passes. 354 | /// MUST NOT modify state (using `STATICCALL` for solc < 0.5, `view` modifier for solc > 0.5) 355 | /// MUST allow external calls 356 | /// @param hash A 32 byte hash of the signed data. The actual hash that is hashed however is the 357 | /// the following tightly packed arguments: `0x19,0x0,wallet_address,hash` 358 | /// @param _signature Signature byte array associated with `_data` 359 | /// @return Magic value `0x1626ba7e` upon success, 0 otherwise. 360 | function isValidSignature(bytes32 hash, bytes calldata _signature) external view returns (bytes4) { 361 | 362 | // We 'hash the hash' for the following reasons: 363 | // 1. `hash` is not the hash of an Ethereum transaction 364 | // 2. signature must target this wallet to avoid replaying the signature for another wallet 365 | // with the same key 366 | // 3. Gnosis does something similar: 367 | // https://github.com/gnosis/safe-contracts/blob/102e632d051650b7c4b0a822123f449beaf95aed/contracts/GnosisSafe.sol 368 | bytes32 operationHash = keccak256( 369 | abi.encodePacked( 370 | EIP191_PREFIX, 371 | EIP191_VERSION_DATA, 372 | this, 373 | hash)); 374 | 375 | bytes32[2] memory r; 376 | bytes32[2] memory s; 377 | uint8[2] memory v; 378 | address signer; 379 | address cosigner; 380 | 381 | // extract 1 or 2 signatures depending on length 382 | if (_signature.length == 65) { 383 | (r[0], s[0], v[0]) = _signature.extractSignature(0); 384 | signer = ecrecover(operationHash, v[0], r[0], s[0]); 385 | cosigner = signer; 386 | } else if (_signature.length == 130) { 387 | (r[0], s[0], v[0]) = _signature.extractSignature(0); 388 | (r[1], s[1], v[1]) = _signature.extractSignature(65); 389 | signer = ecrecover(operationHash, v[0], r[0], s[0]); 390 | cosigner = ecrecover(operationHash, v[1], r[1], s[1]); 391 | } else { 392 | return 0; 393 | } 394 | 395 | // check for valid signature 396 | if (signer == address(0)) { 397 | return 0; 398 | } 399 | 400 | // check for valid signature 401 | if (cosigner == address(0)) { 402 | return 0; 403 | } 404 | 405 | // check to see if this is an authorized key 406 | if (address(authorizations[authVersion + uint256(signer)]) != cosigner) { 407 | return 0; 408 | } 409 | 410 | return ERC1271_VALIDSIGNATURE; 411 | } 412 | 413 | /// @notice Query if this contract implements an interface. This function takes into account 414 | /// interfaces we implement dynamically through delegates. For interfaces that are just a 415 | /// single method, using `setDelegate` will result in that method's ID returning true from 416 | /// `supportsInterface`. For composite interfaces that are composed of multiple functions, it is 417 | /// necessary to add the interface ID manually with `setDelegate(interfaceID, 418 | /// COMPOSITE_PLACEHOLDER)` 419 | /// IN ADDITION to adding each function of the interface as usual. 420 | /// @param interfaceID The interface identifier, as specified in ERC-165 421 | /// @dev Interface identification is specified in ERC-165. This function 422 | /// uses less than 30,000 gas. 423 | /// @return `true` if the contract implements `interfaceID` and 424 | /// `interfaceID` is not 0xffffffff, `false` otherwise 425 | function supportsInterface(bytes4 interfaceID) external view returns (bool) { 426 | // First check if the ID matches one of the interfaces we support statically. 427 | if ( 428 | interfaceID == this.supportsInterface.selector || // ERC165 429 | interfaceID == ERC721_RECEIVED_FINAL || // ERC721 Final 430 | interfaceID == ERC721_RECEIVED_DRAFT || // ERC721 Draft 431 | interfaceID == ERC223_ID || // ERC223 432 | interfaceID == ERC1271_VALIDSIGNATURE // ERC1271 433 | ) { 434 | return true; 435 | } 436 | // If we don't support the interface statically, check whether we have added 437 | // dynamic support for it. 438 | return uint256(delegates[interfaceID]) > 0; 439 | } 440 | 441 | /// @notice A version of `invoke()` that has no explicit signatures, and uses msg.sender 442 | /// as both the signer and cosigner. Will only succeed if `msg.sender` is an authorized 443 | /// signer for this wallet, with no cosigner, saving transaction size and gas in that case. 444 | /// @param data The data containing the transactions to be invoked; see internalInvoke for details. 445 | function invoke0(bytes calldata data) external { 446 | // The nonce doesn't need to be incremented for transactions that don't include explicit signatures; 447 | // the built-in nonce of the native ethereum transaction will protect against replay attacks, and we 448 | // can save the gas that would be spent updating the nonce variable 449 | 450 | // The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) 451 | require(address(authorizations[authVersion + uint256(msg.sender)]) == msg.sender, "Invalid authorization."); 452 | 453 | internalInvoke(0, data); 454 | } 455 | 456 | /// @notice A version of `invoke()` that has one explicit signature which is used to derive the authorized 457 | /// address. Uses `msg.sender` as the cosigner. 458 | /// @param v the v value for the signature; see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md 459 | /// @param r the r value for the signature 460 | /// @param s the s value for the signature 461 | /// @param nonce the nonce value for the signature 462 | /// @param authorizedAddress the address of the authorization key; this is used here so that cosigner signatures are interchangeable 463 | /// between this function and `invoke2()` 464 | /// @param data The data containing the transactions to be invoked; see internalInvoke for details. 465 | function invoke1CosignerSends(uint8 v, bytes32 r, bytes32 s, uint256 nonce, address authorizedAddress, bytes calldata data) external { 466 | // check signature version 467 | require(v == 27 || v == 28, "Invalid signature version."); 468 | 469 | // calculate hash 470 | bytes32 operationHash = keccak256( 471 | abi.encodePacked( 472 | EIP191_PREFIX, 473 | EIP191_VERSION_DATA, 474 | this, 475 | nonce, 476 | authorizedAddress, 477 | data)); 478 | 479 | // recover signer 480 | address signer = ecrecover(operationHash, v, r, s); 481 | 482 | // check for valid signature 483 | require(signer != address(0), "Invalid signature."); 484 | 485 | // check nonce 486 | require(nonce == nonces[signer], "must use correct nonce"); 487 | 488 | // check signer 489 | require(signer == authorizedAddress, "authorized addresses must be equal"); 490 | 491 | // Get cosigner 492 | address requiredCosigner = address(authorizations[authVersion + uint256(signer)]); 493 | 494 | // The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or 495 | // if the actual cosigner matches the required cosigner. 496 | require(requiredCosigner == signer || requiredCosigner == msg.sender, "Invalid authorization."); 497 | 498 | // increment nonce to prevent replay attacks 499 | nonces[signer] = nonce + 1; 500 | 501 | // call internal function 502 | internalInvoke(operationHash, data); 503 | } 504 | 505 | /// @notice A version of `invoke()` that has one explicit signature which is used to derive the cosigning 506 | /// address. Uses `msg.sender` as the authorized address. 507 | /// @param v the v value for the signature; see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md 508 | /// @param r the r value for the signature 509 | /// @param s the s value for the signature 510 | /// @param data The data containing the transactions to be invoked; see internalInvoke for details. 511 | function invoke1SignerSends(uint8 v, bytes32 r, bytes32 s, bytes calldata data) external { 512 | // check signature version 513 | // `ecrecover` will in fact return 0 if given invalid 514 | // so perhaps this check is redundant 515 | require(v == 27 || v == 28, "Invalid signature version."); 516 | 517 | uint256 nonce = nonces[msg.sender]; 518 | 519 | // calculate hash 520 | bytes32 operationHash = keccak256( 521 | abi.encodePacked( 522 | EIP191_PREFIX, 523 | EIP191_VERSION_DATA, 524 | this, 525 | nonce, 526 | msg.sender, 527 | data)); 528 | 529 | // recover cosigner 530 | address cosigner = ecrecover(operationHash, v, r, s); 531 | 532 | // check for valid signature 533 | require(cosigner != address(0), "Invalid signature."); 534 | 535 | // Get required cosigner 536 | address requiredCosigner = address(authorizations[authVersion + uint256(msg.sender)]); 537 | 538 | // The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or 539 | // if the actual cosigner matches the required cosigner. 540 | require(requiredCosigner == cosigner || requiredCosigner == msg.sender, "Invalid authorization."); 541 | 542 | // increment nonce to prevent replay attacks 543 | nonces[msg.sender] = nonce + 1; 544 | 545 | internalInvoke(operationHash, data); 546 | } 547 | 548 | /// @notice A version of `invoke()` that has two explicit signatures, the first is used to derive the authorized 549 | /// address, the second to derive the cosigner. The value of `msg.sender` is ignored. 550 | /// @param v the v values for the signatures 551 | /// @param r the r values for the signatures 552 | /// @param s the s values for the signatures 553 | /// @param nonce the nonce value for the signature 554 | /// @param authorizedAddress the address of the signer; forces the signature to be unique and tied to the signers nonce 555 | /// @param data The data containing the transactions to be invoked; see internalInvoke for details. 556 | function invoke2(uint8[2] calldata v, bytes32[2] calldata r, bytes32[2] calldata s, uint256 nonce, address authorizedAddress, bytes calldata data) external { 557 | // check signature versions 558 | // `ecrecover` will infact return 0 if given invalid 559 | // so perhaps these checks are redundant 560 | require(v[0] == 27 || v[0] == 28, "invalid signature version v[0]"); 561 | require(v[1] == 27 || v[1] == 28, "invalid signature version v[1]"); 562 | 563 | bytes32 operationHash = keccak256( 564 | abi.encodePacked( 565 | EIP191_PREFIX, 566 | EIP191_VERSION_DATA, 567 | this, 568 | nonce, 569 | authorizedAddress, 570 | data)); 571 | 572 | // recover signer and cosigner 573 | address signer = ecrecover(operationHash, v[0], r[0], s[0]); 574 | address cosigner = ecrecover(operationHash, v[1], r[1], s[1]); 575 | 576 | // check for valid signatures 577 | require(signer != address(0), "Invalid signature for signer."); 578 | require(cosigner != address(0), "Invalid signature for cosigner."); 579 | 580 | // check signer address 581 | require(signer == authorizedAddress, "authorized addresses must be equal"); 582 | 583 | // check nonces 584 | require(nonce == nonces[signer], "must use correct nonce for signer"); 585 | 586 | // Get Mapping 587 | address requiredCosigner = address(authorizations[authVersion + uint256(signer)]); 588 | 589 | // The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or 590 | // if the actual cosigner matches the required cosigner. 591 | require(requiredCosigner == signer || requiredCosigner == cosigner, "Invalid authorization."); 592 | 593 | // increment nonce to prevent replay attacks 594 | nonces[signer]++; 595 | 596 | internalInvoke(operationHash, data); 597 | } 598 | 599 | /// @dev Internal invoke call, 600 | /// @param operationHash The hash of the operation 601 | /// @param data The data to send to the `call()` operation 602 | /// The data is prefixed with a global 1 byte revert flag 603 | /// If revert is 1, then any revert from a `call()` operation is rethrown. 604 | /// Otherwise, the error is recorded in the `result` field of the `InvocationSuccess` event. 605 | /// Immediately following the revert byte (no padding), the data format is then is a series 606 | /// of 1 or more tightly packed tuples: 607 | /// `` 608 | /// If `datalength == 0`, the data field must be omitted 609 | function internalInvoke(bytes32 operationHash, bytes memory data) internal { 610 | // keep track of the number of operations processed 611 | uint256 numOps; 612 | // keep track of the result of each operation as a bit 613 | uint256 result; 614 | 615 | // We need to store a reference to this string as a variable so we can use it as an argument to 616 | // the revert call from assembly. 617 | string memory invalidLengthMessage = "Data field too short"; 618 | string memory callFailed = "Call failed"; 619 | 620 | // At an absolute minimum, the data field must be at least 85 bytes 621 | // 622 | require(data.length >= 85, invalidLengthMessage); 623 | 624 | // Forward the call onto its actual target. Note that the target address can be `self` here, which is 625 | // actually the required flow for modifying the configuration of the authorized keys and recovery address. 626 | // 627 | // The assembly code below loads data directly from memory, so the enclosing function must be marked `internal` 628 | assembly { 629 | // A cursor pointing to the revert flag, starts after the length field of the data object 630 | let memPtr := add(data, 32) 631 | 632 | // The revert flag is the leftmost byte from memPtr 633 | let revertFlag := byte(0, mload(memPtr)) 634 | 635 | // A pointer to the end of the data object 636 | let endPtr := add(memPtr, mload(data)) 637 | 638 | // Now, memPtr is a cursor pointing to the beginning of the current sub-operation 639 | memPtr := add(memPtr, 1) 640 | 641 | // Loop through data, parsing out the various sub-operations 642 | for { } lt(memPtr, endPtr) { } { 643 | // Load the length of the call data of the current operation 644 | // 52 = to(20) + value(32) 645 | let len := mload(add(memPtr, 52)) 646 | 647 | // Compute a pointer to the end of the current operation 648 | // 84 = to(20) + value(32) + size(32) 649 | let opEnd := add(len, add(memPtr, 84)) 650 | 651 | // Bail if the current operation's data overruns the end of the enclosing data buffer 652 | // NOTE: Comment out this bit of code and uncomment the next section if you want 653 | // the solidity-coverage tool to work. 654 | // See https://github.com/sc-forks/solidity-coverage/issues/287 655 | if gt(opEnd, endPtr) { 656 | // The computed end of this operation goes past the end of the data buffer. Not good! 657 | revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage)) 658 | } 659 | // NOTE: Code that is compatible with solidity-coverage 660 | // switch gt(opEnd, endPtr) 661 | // case 1 { 662 | // revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage)) 663 | // } 664 | 665 | // This line of code packs in a lot of functionality! 666 | // - load the target address from memPtr, the address is only 20-bytes but mload always grabs 32-bytes, 667 | // so we have to shr by 12 bytes. 668 | // - load the value field, stored at memPtr+20 669 | // - pass a pointer to the call data, stored at memPtr+84 670 | // - use the previously loaded len field as the size of the call data 671 | // - make the call (passing all remaining gas to the child call) 672 | // - check the result (0 == reverted) 673 | if eq(0, call(gas, shr(96, mload(memPtr)), mload(add(memPtr, 20)), add(memPtr, 84), len, 0, 0)) { 674 | switch revertFlag 675 | case 1 { 676 | revert(add(callFailed, 32), mload(callFailed)) 677 | } 678 | default { 679 | // mark this operation as failed 680 | // create the appropriate bit, 'or' with previous 681 | result := or(result, exp(2, numOps)) 682 | } 683 | } 684 | 685 | // increment our counter 686 | numOps := add(numOps, 1) 687 | 688 | // Update mem pointer to point to the next sub-operation 689 | memPtr := opEnd 690 | } 691 | } 692 | 693 | // emit single event upon success 694 | emit InvocationSuccess(operationHash, result, numOps); 695 | } 696 | } 697 | -------------------------------------------------------------------------------- /contracts/Wallet/FullWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./CoreWallet.sol"; 4 | 5 | 6 | /// @title Full Wallet 7 | /// @notice This contract represents a working contract, used if you want to deploy 8 | /// the full code and not a clone shim 9 | contract FullWallet is CoreWallet { 10 | 11 | /// @notice A regular constructor that can be used if you wish to deploy a standalone instance of this 12 | /// smart contract wallet. Useful if you anticipate that the lifetime gas savings of being able to call 13 | /// this contract directly will outweigh the cost of deploying a complete copy of this contract. 14 | /// Comment out this constructor and use the one above if you wish to save gas deployment costs by 15 | /// using a clonable instance. 16 | /// @param _authorized the initial authorized address 17 | /// @param _cosigner the initial cosiging address for the `_authorized` address 18 | /// @param _recoveryAddress the initial recovery address for the wallet 19 | constructor (address _authorized, uint256 _cosigner, address _recoveryAddress) public { 20 | init(_authorized, _cosigner, _recoveryAddress); 21 | } 22 | } -------------------------------------------------------------------------------- /contracts/Wallet/QueryableWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "./CoreWallet.sol"; 4 | import "../ERC165/ERC165Query.sol"; 5 | 6 | 7 | /// @title Queryable Wallet 8 | /// @notice This contract represents an ERC165 queryable version of the full wallet 9 | /// @dev NOTE: This contract is for testing purposes only!! 10 | contract QueryableWallet is CoreWallet, ERC165Query { 11 | 12 | /// @notice A regular constructor that can be used if you wish to deploy a standalone instance of this 13 | /// smart contract wallet. Useful if you anticipate that the lifetime gas savings of being able to call 14 | /// this contract directly will outweigh the cost of deploying a complete copy of this contract. 15 | /// Comment out this constructor and use the one above if you wish to save gas deployment costs by 16 | /// using a clonable instance. 17 | /// @param _authorized the initial authorized address 18 | /// @param _cosigner the initial cosiging address for the `_authorized` address 19 | /// @param _recoveryAddress the initial recovery address for the wallet 20 | constructor (address _authorized, uint256 _cosigner, address _recoveryAddress) public { 21 | init(_authorized, _cosigner, _recoveryAddress); 22 | } 23 | } -------------------------------------------------------------------------------- /contracts/WalletFactory/CloneFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | 4 | /// @title CloneFactory - a contract that creates clones 5 | /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md 6 | /// @dev See https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol 7 | contract CloneFactory { 8 | event CloneCreated(address indexed target, address clone); 9 | 10 | function createClone(address target) internal returns (address payable result) { 11 | bytes20 targetBytes = bytes20(target); 12 | assembly { 13 | let clone := mload(0x40) 14 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 15 | mstore(add(clone, 0x14), targetBytes) 16 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 17 | result := create(0, clone, 0x37) 18 | } 19 | } 20 | 21 | function createClone2(address target, bytes32 salt) internal returns (address payable result) { 22 | bytes20 targetBytes = bytes20(target); 23 | assembly { 24 | let clone := mload(0x40) 25 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 26 | mstore(add(clone, 0x14), targetBytes) 27 | mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) 28 | result := create2(0, clone, 0x37, salt) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /contracts/WalletFactory/FullWalletByteCode.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /// @title FullWalletByteCode 4 | /// @dev A contract containing the FullWallet bytecode, for use in deployment. 5 | contract FullWalletByteCode { 6 | /// @notice This is the raw bytecode of the full wallet. It is encoded here as a raw byte 7 | /// array to support deployment with CREATE2, as Solidity's 'new' constructor system does 8 | /// not support CREATE2 yet. 9 | /// 10 | /// NOTE: Be sure to update this whenever the wallet bytecode changes! 11 | /// Simply run `npm run build` and then copy the `"bytecode"` 12 | /// portion from the `build/contracts/FullWallet.json` file to here, 13 | /// then append 64x3 0's. 14 | bytes constant fullWalletBytecode = hex'60806040523480156200001157600080fd5b5060405162002b5b38038062002b5b833981810160405260608110156200003757600080fd5b50805160208201516040909201519091906200005e8383836001600160e01b036200006716565b5050506200033b565b60045474010000000000000000000000000000000000000000900460ff1615620000f257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f6d757374206e6f7420616c726561647920626520696e697469616c697a656400604482015290519081900360640190fd5b6004805460ff60a01b1916740100000000000000000000000000000000000000001790556001600160a01b0383811690821614156200017d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603981526020018062002af46039913960400191505060405180910390fd5b806001600160a01b0316826001600160a01b03161415620001ea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e81526020018062002b2d602e913960400191505060405180910390fd5b6001600160a01b0383166200024b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018062002aac6026913960400191505060405180910390fd5b6001600160a01b038216620002ac576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018062002ad26022913960400191505060405180910390fd5b600480546001600160a01b0319166001600160a01b03838116919091179091557401000000000000000000000000000000000000000060008181559185169081018252600160209081526040928390208590558251918252810184905281517fb39b5f240c7440b58c1c6cfd328b09ff9aa18b3c8ef4b829774e4f5bad039416929181900390910190a1505050565b612761806200034b6000396000f3fe60806040526004361061019c5760003560e01c806375857eba116100ec578063a3c89c4f1161008a578063ce2d4f9611610064578063ce2d4f96146109e1578063ef009e42146109f6578063f0b9e5ba14610a78578063ffa1ad7414610b085761019c565b8063a3c89c4f14610867578063bf4fb0c0146108e2578063c0ee0b8a1461091b5761019c565b80638bf78874116100c65780638bf78874146107635780639105d9c41461077857806391aeeedc1461078d578063a0a2daf0146108335761019c565b806375857eba146106d85780637ecebe00146106ed57806388fb06e7146107205761019c565b8063210d66f81161015957806349efe5ae1161013357806349efe5ae146105aa57806357e61e29146105dd578063710eb26c1461066e578063727b7acf1461069f5761019c565b8063210d66f81461048b5780632698c20c146104c757806343fc00b8146105675761019c565b806301ffc9a71461027757806308405166146102bf578063150b7a02146102f1578063158ef93e146103c25780631626ba7e146103d75780631cd61bad14610459575b34156101dd576040805133815234602082015281517f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f88525874929181900390910190a15b361561027557600080356001600160e01b0319168152600360205260409020546001600160a01b031660018111610251576040805162461bcd60e51b815260206004820152601360248201527224b73b30b634b2103a3930b739b0b1ba34b7b760691b604482015290519081900360640190fd5b3660008037600080366000845afa3d6000803e808015610270573d6000f35b3d6000fd5b005b34801561028357600080fd5b506102ab6004803603602081101561029a57600080fd5b50356001600160e01b031916610b92565b604080519115158252519081900360200190f35b3480156102cb57600080fd5b506102d4610c4d565b604080516001600160e01b03199092168252519081900360200190f35b3480156102fd57600080fd5b506102d46004803603608081101561031457600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b81111561034e57600080fd5b82018360208201111561036057600080fd5b803590602001918460018302840111600160201b8311171561038157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610c58945050505050565b3480156103ce57600080fd5b506102ab610c68565b3480156103e357600080fd5b506102d4600480360360408110156103fa57600080fd5b81359190810190604081016020820135600160201b81111561041b57600080fd5b82018360208201111561042d57600080fd5b803590602001918460018302840111600160201b8311171561044e57600080fd5b509092509050610c78565b34801561046557600080fd5b5061046e610fe5565b604080516001600160f81b03199092168252519081900360200190f35b34801561049757600080fd5b506104b5600480360360208110156104ae57600080fd5b5035610fea565b60408051918252519081900360200190f35b3480156104d357600080fd5b5061027560048036036101208110156104eb57600080fd5b6040820190608083019060c0840135906001600160a01b0360e086013516908501856101208101610100820135600160201b81111561052957600080fd5b82018360208201111561053b57600080fd5b803590602001918460018302840111600160201b8311171561055c57600080fd5b509092509050610ffc565b34801561057357600080fd5b506102756004803603606081101561058a57600080fd5b506001600160a01b03813581169160208101359160409091013516611499565b3480156105b657600080fd5b50610275600480360360208110156105cd57600080fd5b50356001600160a01b03166116af565b3480156105e957600080fd5b506102756004803603608081101561060057600080fd5b60ff8235169160208101359160408201359190810190608081016060820135600160201b81111561063057600080fd5b82018360208201111561064257600080fd5b803590602001918460018302840111600160201b8311171561066357600080fd5b5090925090506117c1565b34801561067a57600080fd5b50610683611a3e565b604080516001600160a01b039092168252519081900360200190f35b3480156106ab57600080fd5b50610275600480360360408110156106c257600080fd5b506001600160a01b038135169060200135611a4d565b3480156106e457600080fd5b506104b5611c02565b3480156106f957600080fd5b506104b56004803603602081101561071057600080fd5b50356001600160a01b0316611c0a565b34801561072c57600080fd5b506102756004803603604081101561074357600080fd5b5080356001600160e01b03191690602001356001600160a01b0316611c1c565b34801561076f57600080fd5b506104b5611ce2565b34801561078457600080fd5b50610683611ce8565b34801561079957600080fd5b50610275600480360360c08110156107b057600080fd5b60ff823516916020810135916040820135916060810135916001600160a01b03608083013516919081019060c0810160a0820135600160201b8111156107f557600080fd5b82018360208201111561080757600080fd5b803590602001918460018302840111600160201b8311171561082857600080fd5b509092509050611ced565b34801561083f57600080fd5b506106836004803603602081101561085657600080fd5b50356001600160e01b03191661202c565b34801561087357600080fd5b506102756004803603602081101561088a57600080fd5b810190602081018135600160201b8111156108a457600080fd5b8201836020820111156108b657600080fd5b803590602001918460018302840111600160201b831117156108d757600080fd5b509092509050612047565b3480156108ee57600080fd5b506102756004803603604081101561090557600080fd5b506001600160a01b0381351690602001356120f7565b34801561092757600080fd5b506102756004803603606081101561093e57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b81111561096d57600080fd5b82018360208201111561097f57600080fd5b803590602001918460018302840111600160201b831117156109a057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061229a945050505050565b3480156109ed57600080fd5b5061046e61229f565b348015610a0257600080fd5b5061027560048036036040811015610a1957600080fd5b81359190810190604081016020820135600160201b811115610a3a57600080fd5b820183602082011115610a4c57600080fd5b803590602001918460208302840111600160201b83111715610a6d57600080fd5b5090925090506122a7565b348015610a8457600080fd5b506102d460048036036060811015610a9b57600080fd5b6001600160a01b0382351691602081013591810190606081016040820135600160201b811115610aca57600080fd5b820183602082011115610adc57600080fd5b803590602001918460018302840111600160201b83111715610afd57600080fd5b5090925090506123ab565b348015610b1457600080fd5b50610b1d6123bb565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610b57578181015183820152602001610b3f565b50505050905090810190601f168015610b845780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60006001600160e01b031982166301ffc9a760e01b1480610bc357506001600160e01b03198216630a85bd0160e11b145b80610bde57506001600160e01b0319821663785cf2dd60e11b145b80610bf957506001600160e01b0319821663607705c560e11b145b80610c1457506001600160e01b03198216630b135d3f60e11b145b15610c2157506001610c48565b506001600160e01b031981166000908152600360205260409020546001600160a01b031615155b919050565b63607705c560e11b81565b630a85bd0160e11b949350505050565b600454600160a01b900460ff1681565b60408051601960f81b6020808301919091526000602183018190523060601b602284015260368084018890528451808503909101815260569093019093528151910120610cc36125b0565b610ccb6125b0565b610cd36125b0565b6000806041881415610da457610d2960008a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929392505063ffffffff6123dc169050565b60ff908116865290865281875284518651604080516000815260208181018084528d9052939094168482015260608401949094526080830152915160019260a0808401939192601f1981019281900390910190855afa158015610d90573d6000803e3d6000fd5b505050602060405103519150819050610f48565b6082881415610f3857610df760008a8a8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929392505063ffffffff6123dc169050565b60ff16855285528552604080516020601f8b01819004810282018101909252898152610e4a91604191908c908c9081908401838280828437600092019190915250929392505063ffffffff6123dc169050565b60ff908116602087810191909152878101929092528188019290925284518751875160408051600081528086018083528d9052939095168386015260608301919091526080820152915160019260a08082019392601f1981019281900390910190855afa158015610ebf573d6000803e3d6000fd5b505060408051601f19808201516020808901518b8201518b830151600087528387018089528f905260ff909216868801526060860152608085015293519096506001945060a08084019493928201928290030190855afa158015610f27573d6000803e3d6000fd5b505050602060405103519050610f48565b5060009550610fde945050505050565b6001600160a01b038216610f66575060009550610fde945050505050565b6001600160a01b038116610f84575060009550610fde945050505050565b806001600160a01b031660016000846001600160a01b0316600054018152602001908152602001600020546001600160a01b031614610fcd575060009550610fde945050505050565b50630b135d3f60e11b955050505050505b9392505050565b600081565b60016020526000908152604090205481565b601b60ff88351614806110135750601c60ff883516145b611064576040805162461bcd60e51b815260206004820152601e60248201527f696e76616c6964207369676e61747572652076657273696f6e20765b305d0000604482015290519081900360640190fd5b601b60ff60208901351614806110815750601c60ff602089013516145b6110d2576040805162461bcd60e51b815260206004820152601e60248201527f696e76616c6964207369676e61747572652076657273696f6e20765b315d0000604482015290519081900360640190fd5b604051601960f81b6020820181815260006021840181905230606081811b6022870152603686018a905288901b6bffffffffffffffffffffffff1916605686015290938492899189918991899190606a018383808284378083019250505097505050505050505060405160208183030381529060405280519060200120905060006001828a60006002811061116357fe5b602002013560ff168a60006002811061117857fe5b604080516000815260208181018084529690965260ff90941684820152908402919091013560608301528a3560808301525160a08083019392601f198301929081900390910190855afa1580156111d3573d6000803e3d6000fd5b505060408051601f1980820151600080845260208085018087528990528f81013560ff16858701528e81013560608601528d81013560808601529451919650945060019360a0808501949193830192918290030190855afa15801561123c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0382166112a4576040805162461bcd60e51b815260206004820152601d60248201527f496e76616c6964207369676e617475726520666f72207369676e65722e000000604482015290519081900360640190fd5b6001600160a01b0381166112ff576040805162461bcd60e51b815260206004820152601f60248201527f496e76616c6964207369676e617475726520666f7220636f7369676e65722e00604482015290519081900360640190fd5b856001600160a01b0316826001600160a01b03161461134f5760405162461bcd60e51b815260040180806020018281038252602281526020018061270b6022913960400191505060405180910390fd5b6001600160a01b03821660009081526002602052604090205487146113a55760405162461bcd60e51b81526004018080602001828103825260218152602001806126bc6021913960400191505060405180910390fd5b600080546001600160a01b038085169182018352600160205260409092205491821614806113e45750816001600160a01b0316816001600160a01b0316145b61142e576040805162461bcd60e51b815260206004820152601660248201527524b73b30b634b21030baba3437b934bd30ba34b7b71760511b604482015290519081900360640190fd5b6001600160a01b038316600090815260026020908152604091829020805460010190558151601f880182900482028101820190925286825261148c91869189908990819084018382808284376000920191909152506123f892505050565b5050505050505050505050565b600454600160a01b900460ff16156114f8576040805162461bcd60e51b815260206004820152601f60248201527f6d757374206e6f7420616c726561647920626520696e697469616c697a656400604482015290519081900360640190fd5b6004805460ff60a01b1916600160a01b1790556001600160a01b0383811690821614156115565760405162461bcd60e51b81526004018080602001828103825260398152602001806126836039913960400191505060405180910390fd5b806001600160a01b0316826001600160a01b031614156115a75760405162461bcd60e51b815260040180806020018281038252602e8152602001806126dd602e913960400191505060405180910390fd5b6001600160a01b0383166115ec5760405162461bcd60e51b815260040180806020018281038252602681526020018061263b6026913960400191505060405180910390fd5b6001600160a01b0382166116315760405162461bcd60e51b81526004018080602001828103825260228152602001806126616022913960400191505060405180910390fd5b600480546001600160a01b0319166001600160a01b0383811691909117909155600160a01b60008181559185169081018252600160209081526040928390208590558251918252810184905281517fb39b5f240c7440b58c1c6cfd328b09ff9aa18b3c8ef4b829774e4f5bad039416929181900390910190a1505050565b333014611703576040805162461bcd60e51b815260206004820152601e60248201527f6d7573742062652063616c6c65642066726f6d2060696e766f6b652829600000604482015290519081900360640190fd5b600080546001600160a01b0383811690910182526001602052604090912054161561175f5760405162461bcd60e51b81526004018080602001828103825260398152602001806125cf6039913960400191505060405180910390fd5b600480546001600160a01b038381166001600160a01b0319831617928390556040805192821680845293909116602083015280517f568ab3dedd6121f0385e007e641e74e1f49d0fa69cab2957b0b07c4c7de5abb69281900390910190a15050565b8460ff16601b14806117d657508460ff16601c145b611827576040805162461bcd60e51b815260206004820152601a60248201527f496e76616c6964207369676e61747572652076657273696f6e2e000000000000604482015290519081900360640190fd5b336000818152600260209081526040808320549051601960f81b9281018381526021820185905230606081811b60228501526036840185905287901b6056840152929585939287928a918a9190606a0183838082843780830192505050975050505050505050604051602081830303815290604052805190602001209050600060018289898960405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611904573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116611961576040805162461bcd60e51b815260206004820152601260248201527124b73b30b634b21039b4b3b730ba3ab9329760711b604482015290519081900360640190fd5b6000805433018152600160205260409020546001600160a01b03818116908316148061199557506001600160a01b03811633145b6119df576040805162461bcd60e51b815260206004820152601660248201527524b73b30b634b21030baba3437b934bd30ba34b7b71760511b604482015290519081900360640190fd5b336000908152600260209081526040918290206001870190558151601f8801829004820281018201909252868252611a3391859189908990819084018382808284376000920191909152506123f892505050565b505050505050505050565b6004546001600160a01b031681565b6004546001600160a01b03163314611aac576040805162461bcd60e51b815260206004820152601f60248201527f73656e646572206d757374206265207265636f76657279206164647265737300604482015290519081900360640190fd5b6001600160a01b038216611af15760405162461bcd60e51b815260040180806020018281038252602681526020018061263b6026913960400191505060405180910390fd5b6004546001600160a01b0383811691161415611b3e5760405162461bcd60e51b81526004018080602001828103825260398152602001806126836039913960400191505060405180910390fd5b6001600160a01b038116611b99576040805162461bcd60e51b815260206004820152601e60248201527f54686520636f7369676e6572206d757374206e6f74206265207a65726f2e0000604482015290519081900360640190fd5b60008054600160a01b81810183556001600160a01b038516918201018252600160209081526040928390208490558251918252810183905281517fa9364fb2836862098c2b593d2d3f46759b4c6d5b054300f96172b0394430008a929181900390910190a15050565b600160a01b81565b60026020526000908152604090205481565b333014611c70576040805162461bcd60e51b815260206004820152601e60248201527f6d7573742062652063616c6c65642066726f6d2060696e766f6b652829600000604482015290519081900360640190fd5b6001600160e01b0319821660008181526003602090815260409182902080546001600160a01b0319166001600160a01b03861690811790915582519384529083015280517fd09b01a1a877e1a97b048725e0697d9be07bb94320c536e72b976c81016891fb9281900390910190a15050565b60005481565b600181565b8660ff16601b1480611d0257508660ff16601c145b611d53576040805162461bcd60e51b815260206004820152601a60248201527f496e76616c6964207369676e61747572652076657273696f6e2e000000000000604482015290519081900360640190fd5b604051601960f81b6020820181815260006021840181905230606081811b6022870152603686018a905288901b6bffffffffffffffffffffffff1916605686015290938492899189918991899190606a018383808284378083019250505097505050505050505060405160208183030381529060405280519060200120905060006001828a8a8a60405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611e31573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116611e8e576040805162461bcd60e51b815260206004820152601260248201527124b73b30b634b21039b4b3b730ba3ab9329760711b604482015290519081900360640190fd5b6001600160a01b0381166000908152600260205260409020548614611ef3576040805162461bcd60e51b81526020600482015260166024820152756d7573742075736520636f7272656374206e6f6e636560501b604482015290519081900360640190fd5b846001600160a01b0316816001600160a01b031614611f435760405162461bcd60e51b815260040180806020018281038252602281526020018061270b6022913960400191505060405180910390fd5b600080546001600160a01b03808416918201835260016020526040909220549182161480611f7957506001600160a01b03811633145b611fc3576040805162461bcd60e51b815260206004820152601660248201527524b73b30b634b21030baba3437b934bd30ba34b7b71760511b604482015290519081900360640190fd5b6001600160a01b03821660009081526002602090815260409182902060018a0190558151601f870182900482028101820190925285825261202091859188908890819084018382808284376000920191909152506123f892505050565b50505050505050505050565b6003602052600090815260409020546001600160a01b031681565b6000805433908101825260016020526040909120546001600160a01b0316146120b0576040805162461bcd60e51b815260206004820152601660248201527524b73b30b634b21030baba3437b934bd30ba34b7b71760511b604482015290519081900360640190fd5b6120f36000801b83838080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506123f892505050565b5050565b33301461214b576040805162461bcd60e51b815260206004820152601e60248201527f6d7573742062652063616c6c65642066726f6d2060696e766f6b652829600000604482015290519081900360640190fd5b6001600160a01b0382166121905760405162461bcd60e51b815260040180806020018281038252602681526020018061263b6026913960400191505060405180910390fd5b6004546001600160a01b03838116911614156121dd5760405162461bcd60e51b81526004018080602001828103825260398152602001806126836039913960400191505060405180910390fd5b6001600160a01b038116158061220157506004546001600160a01b03828116911614155b61223c5760405162461bcd60e51b815260040180806020018281038252602e8152602001806126dd602e913960400191505060405180910390fd5b600080546001600160a01b0384169081018252600160209081526040928390208490558251918252810183905281517fb39b5f240c7440b58c1c6cfd328b09ff9aa18b3c8ef4b829774e4f5bad039416929181900390910190a15050565b505050565b601960f81b81565b6000831180156122ba575063ffffffff83105b61230b576040805162461bcd60e51b815260206004820152601760248201527f496e76616c69642076657273696f6e206e756d6265722e000000000000000000604482015290519081900360640190fd5b60005460a084901b9081106123515760405162461bcd60e51b81526004018080602001828103825260338152602001806126086033913960400191505060405180910390fd5b60005b828110156123a4576001600085858481811061236c57fe5b905060200201356001600160a01b03166001600160a01b03168401815260200190815260200160002060009055806001019050612354565b5050505050565b63785cf2dd60e11b949350505050565b604051806040016040528060058152602001640312e312e360dc1b81525081565b0160208101516040820151606090920151909260009190911a90565b60008060606040518060400160405280601481526020017311185d1848199a595b19081d1bdbc81cda1bdc9d60621b815250905060606040518060400160405280600b81526020016a10d85b1b0819985a5b195960aa1b815250905060558551101582906124e45760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156124a9578181015183820152602001612491565b50505050905090810190601f1680156124d65780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060208501805160001a865182016001830192505b808310156125645760348301516054840181018281111561251c57865160208801fd5b60008083605488016014890151895160601c5af161255457836001811461254a578960020a89179850612552565b865160208801fd5b505b60018901985080945050506124f9565b5050604080518881526020810186905280820187905290517f101214446435ebbb29893f3348e3aae5ea070b63037a3df346d09d3396a34aee92509081900360600190a1505050505050565b6040518060400160405280600290602082028038833950919291505056fe446f206e6f742075736520616e20617574686f72697a6564206164647265737320617320746865207265636f7665727920616464726573732e596f752063616e206f6e6c79207265636f766572206761732066726f6d2065787069726564206175746856657273696f6e732e417574686f72697a656420616464726573736573206d757374206e6f74206265207a65726f2e496e697469616c20636f7369676e6572206d757374206e6f74206265207a65726f2e446f206e6f742075736520746865207265636f76657279206164647265737320617320616e20617574686f72697a656420616464726573732e6d7573742075736520636f7272656374206e6f6e636520666f72207369676e6572446f206e6f742075736520746865207265636f766572792061646472657373206173206120636f7369676e65722e617574686f72697a656420616464726573736573206d75737420626520657175616ca265627a7a723058203e27aaf42e5acdb44185fc5c91f25ca2c3ac6ab9c16f3e90a8832e6a750e865164736f6c634300050a0032417574686f72697a656420616464726573736573206d757374206e6f74206265207a65726f2e496e697469616c20636f7369676e6572206d757374206e6f74206265207a65726f2e446f206e6f742075736520746865207265636f76657279206164647265737320617320616e20617574686f72697a656420616464726573732e446f206e6f742075736520746865207265636f766572792061646472657373206173206120636f7369676e65722e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /contracts/WalletFactory/WalletFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | import "../Wallet/CloneableWallet.sol"; 4 | import "../Ownership/HasNoEther.sol"; 5 | import "./CloneFactory.sol"; 6 | import "./FullWalletByteCode.sol"; 7 | 8 | 9 | /// @title WalletFactory 10 | /// @dev A contract for creating wallets. 11 | contract WalletFactory is FullWalletByteCode, HasNoEther, CloneFactory { 12 | 13 | /// @dev Pointer to a pre-deployed instance of the Wallet contract. This 14 | /// deployment contains all the Wallet code. 15 | address public cloneWalletAddress; 16 | 17 | /// @notice Emitted whenever a wallet is created 18 | /// @param wallet The address of the wallet created 19 | /// @param authorizedAddress The initial authorized address of the wallet 20 | /// @param full `true` if the deployed wallet was a full, self 21 | /// contained wallet; `false` if the wallet is a clone wallet 22 | event WalletCreated(address wallet, address authorizedAddress, bool full); 23 | 24 | constructor(address _cloneWalletAddress) public { 25 | cloneWalletAddress = _cloneWalletAddress; 26 | } 27 | 28 | /// @notice Used to deploy a wallet clone 29 | /// @dev Reasonably cheap to run (~100K gas) 30 | /// @param _recoveryAddress the initial recovery address for the wallet 31 | /// @param _authorizedAddress an initial authorized address for the wallet 32 | /// @param _cosigner the cosigning address for the initial `_authorizedAddress` 33 | function deployCloneWallet( 34 | address _recoveryAddress, 35 | address _authorizedAddress, 36 | uint256 _cosigner 37 | ) 38 | public 39 | { 40 | // create the clone 41 | address payable clone = createClone(cloneWalletAddress); 42 | // init the clone 43 | CloneableWallet(clone).init(_authorizedAddress, _cosigner, _recoveryAddress); 44 | // emit event 45 | emit WalletCreated(clone, _authorizedAddress, false); 46 | } 47 | 48 | /// @notice Used to deploy a wallet clone 49 | /// @dev Reasonably cheap to run (~100K gas) 50 | /// @dev The clone does not require `onlyOwner` as we avoid front-running 51 | /// attacks by hashing the salt combined with the call arguments and using 52 | /// that as the salt we provide to `create2`. Given this constraint, a 53 | /// front-runner would need to use the same `_recoveryAddress`, `_authorizedAddress`, 54 | /// and `_cosigner` parameters as the original deployer, so the original deployer 55 | /// would have control of the wallet even if the transaction was front-run. 56 | /// @param _recoveryAddress the initial recovery address for the wallet 57 | /// @param _authorizedAddress an initial authorized address for the wallet 58 | /// @param _cosigner the cosigning address for the initial `_authorizedAddress` 59 | /// @param _salt the salt for the `create2` instruction 60 | function deployCloneWallet2( 61 | address _recoveryAddress, 62 | address _authorizedAddress, 63 | uint256 _cosigner, 64 | bytes32 _salt 65 | ) 66 | public 67 | { 68 | // calculate our own salt based off of args 69 | bytes32 salt = keccak256(abi.encodePacked(_salt, _authorizedAddress, _cosigner, _recoveryAddress)); 70 | // create the clone counterfactually 71 | address payable clone = createClone2(cloneWalletAddress, salt); 72 | // ensure we get an address 73 | require(clone != address(0), "wallet must have address"); 74 | 75 | // check size 76 | uint256 size; 77 | // note this takes an additional 700 gas 78 | assembly { 79 | size := extcodesize(clone) 80 | } 81 | 82 | require(size > 0, "wallet must have code"); 83 | 84 | // init the clone 85 | CloneableWallet(clone).init(_authorizedAddress, _cosigner, _recoveryAddress); 86 | // emit event 87 | emit WalletCreated(clone, _authorizedAddress, false); 88 | } 89 | 90 | /// @notice Used to deploy a full wallet 91 | /// @dev This is potentially very gas intensive! 92 | /// @param _recoveryAddress The initial recovery address for the wallet 93 | /// @param _authorizedAddress An initial authorized address for the wallet 94 | /// @param _cosigner The cosigning address for the initial `_authorizedAddress` 95 | function deployFullWallet( 96 | address _recoveryAddress, 97 | address _authorizedAddress, 98 | uint256 _cosigner 99 | ) 100 | public 101 | { 102 | // Copy the bytecode of the full wallet to memory. 103 | bytes memory fullWallet = fullWalletBytecode; 104 | 105 | address full; 106 | assembly { 107 | // get start of wallet buffer 108 | let startPtr := add(fullWallet, 0x20) 109 | // get start of arguments 110 | let endPtr := sub(add(startPtr, mload(fullWallet)), 0x60) 111 | // copy constructor parameters to memory 112 | mstore(endPtr, _authorizedAddress) 113 | mstore(add(endPtr, 0x20), _cosigner) 114 | mstore(add(endPtr, 0x40), _recoveryAddress) 115 | // create the contract 116 | full := create(0, startPtr, mload(fullWallet)) 117 | } 118 | 119 | // check address 120 | require(full != address(0), "wallet must have address"); 121 | 122 | // check size 123 | uint256 size; 124 | // note this takes an additional 700 gas, 125 | // which is a relatively small amount in this case 126 | assembly { 127 | size := extcodesize(full) 128 | } 129 | 130 | require(size > 0, "wallet must have code"); 131 | 132 | emit WalletCreated(full, _authorizedAddress, true); 133 | } 134 | 135 | /// @notice Used to deploy a full wallet counterfactually 136 | /// @dev This is potentially very gas intensive! 137 | /// @dev As the arguments are appended to the end of the bytecode and 138 | /// then included in the `create2` call, we are safe from front running 139 | /// attacks and do not need to restrict the caller of this function. 140 | /// @param _recoveryAddress The initial recovery address for the wallet 141 | /// @param _authorizedAddress An initial authorized address for the wallet 142 | /// @param _cosigner The cosigning address for the initial `_authorizedAddress` 143 | /// @param _salt The salt for the `create2` instruction 144 | function deployFullWallet2( 145 | address _recoveryAddress, 146 | address _authorizedAddress, 147 | uint256 _cosigner, 148 | bytes32 _salt 149 | ) 150 | public 151 | { 152 | // Note: Be sure to update this whenever the wallet bytecode changes! 153 | // Simply run `yarn run build` and then copy the `"bytecode"` 154 | // portion from the `build/contracts/FullWallet.json` file to here, 155 | // then append 64x3 0's. 156 | // 157 | // Note: By not passing in the code as an argument, we save 600,000 gas. 158 | // An alternative would be to use `extcodecopy`, but again we save 159 | // gas by not having to call `extcodecopy`. 160 | bytes memory fullWallet = fullWalletBytecode; 161 | 162 | address full; 163 | assembly { 164 | // get start of wallet buffer 165 | let startPtr := add(fullWallet, 0x20) 166 | // get start of arguments 167 | let endPtr := sub(add(startPtr, mload(fullWallet)), 0x60) 168 | // copy constructor parameters to memory 169 | mstore(endPtr, _authorizedAddress) 170 | mstore(add(endPtr, 0x20), _cosigner) 171 | mstore(add(endPtr, 0x40), _recoveryAddress) 172 | // create the contract using create2 173 | full := create2(0, startPtr, mload(fullWallet), _salt) 174 | } 175 | 176 | // check address 177 | require(full != address(0), "wallet must have address"); 178 | 179 | // check size 180 | uint256 size; 181 | // note this takes an additional 700 gas, 182 | // which is a relatively small amount in this case 183 | assembly { 184 | size := extcodesize(full) 185 | } 186 | 187 | require(size > 0, "wallet must have code"); 188 | 189 | emit WalletCreated(full, _authorizedAddress, true); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_main_contracts.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * These are not strictly needed for tests; however since the tests 3 | // * run deployments anyways it is nice to know that the contracts deploy, as 4 | // * it seems there are issues you can unearth when attempting to deploy 5 | // */ 6 | 7 | var WalletFactory = artifacts.require("./WalletFactory/WalletFactory.sol"); 8 | var CloneableWallet = artifacts.require('./Wallet/CloneableWallet.sol'); 9 | 10 | 11 | module.exports = function (deployer) { 12 | 13 | deployer.deploy(CloneableWallet).then(() => { 14 | 15 | return deployer.deploy(WalletFactory, CloneableWallet.address); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /out/transaction/Dapper Wallet - Multi-Sig Transaction Structure.svg: -------------------------------------------------------------------------------- 1 | Dapper Wallet - Multi-Sig Transaction StructureEthereum Transaction- nonce- gasprice- startgas- to- value- signatureData- to- value- wallet_address- device_address- inner_nonce- signatureInner DataA standardEthereum transactionAsk wallet contractfor correct inner nonceData sent to inner "to" addressincluding function, args, etc;Is "0x" for just sending ETH -------------------------------------------------------------------------------- /out/wallet/Dapper Wallet - Component Diagram.svg: -------------------------------------------------------------------------------- 1 | Dapper Wallet - Component DiagramSmart Contract WalletDevice KeysRecoveryUser Cold StorageCosigning Contract 1Recovery TransactionDK1DK2DK3Recovery KeyBackup Key-handleTransaction()- SetsBackup Keyas soleDevice Key- Signed byRecovery KeyCosiging Key -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapper-sc-wallet", 3 | "version": "1.0.0", 4 | "description": "Where the dapper smart contract wallet lives", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "bluebird": "^3.5.1", 11 | "bn": "^1.0.3", 12 | "chai": "^4.1.2", 13 | "chai-as-promised": "^7.1.1", 14 | "chai-bignumber": "^2.0.2", 15 | "console.table": "^0.10.0", 16 | "dotenv": "^6.1.0", 17 | "ethereumjs-abi": "^0.6.6", 18 | "ethereumjs-util": "^6.1.0", 19 | "lodash": "^4.17.10", 20 | "openzeppelin-solidity": "^2.1.3", 21 | "should": "^13.2.1", 22 | "solc": "^0.5.9", 23 | "truffle": "^5.0.8", 24 | "truffle-hdwallet-provider": "github:dapperlabs/truffle-hdwallet-provider#v1.0.1", 25 | "web3": "1.0.0-beta.55" 26 | }, 27 | "devDependencies": { 28 | "solidity-coverage": "^0.5.11" 29 | }, 30 | "scripts": { 31 | "test": "./node_modules/.bin/truffle test", 32 | "truffle": "truffle", 33 | "build": "./node_modules/.bin/truffle compile && node ./scripts/generate-wallet-bytecode-contract.js > ./contracts/WalletFactory/FullWalletByteCode.sol", 34 | "deploy": "./node_modules/.bin/truffle migrate -f 2 --network rinkeby_local", 35 | "coverage": "./node_modules/.bin/solidity-coverage" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/dapperlabs/dapper-sc-wallet.git" 40 | }, 41 | "keywords": [], 42 | "author": "Christopher Scott", 43 | "license": "ISC", 44 | "bugs": { 45 | "url": "https://github.com/dapperlabs/dapper-sc-wallet/issues" 46 | }, 47 | "homepage": "https://github.com/dapperlabs/dapper-sc-wallet#readme" 48 | } 49 | -------------------------------------------------------------------------------- /scripts/generate-wallet-bytecode-contract.js: -------------------------------------------------------------------------------- 1 | const { bytecode } = require("../build/contracts/FullWallet.json"); 2 | 3 | const contract = `pragma solidity ^0.5.10; 4 | 5 | /// @title FullWalletByteCode 6 | /// @dev A contract containing the FullWallet bytecode, for use in deployment. 7 | contract FullWalletByteCode { 8 | /// @notice This is the raw bytecode of the full wallet. It is encoded here as a raw byte 9 | /// array to support deployment with CREATE2, as Solidity's 'new' constructor system does 10 | /// not support CREATE2 yet. 11 | /// 12 | /// NOTE: Be sure to update this whenever the wallet bytecode changes! 13 | /// Simply run \`npm run build\` and then copy the \`"bytecode"\` 14 | /// portion from the \`build/contracts/FullWallet.json\` file to here, 15 | /// then append 64x3 0's. 16 | bytes constant fullWalletBytecode = hex'${bytecode.slice(2)}${"0".repeat( 17 | 192 18 | )}'; 19 | } 20 | `; 21 | 22 | // Print the contract source to STDOUT, can pipe this to a file. 23 | console.log(contract); 24 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapperlabs/dapper-contracts/0321efcc80c413dc1d3a3cd30ae02929b3ddadfe/test/.gitkeep -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const abi = require("ethereumjs-abi"); 2 | const BN = require("bn.js"); 3 | const Promise = require("bluebird"); 4 | const crypto = require("crypto"); 5 | const ethUtils = require("ethereumjs-util"); 6 | 7 | const getBalanceWei = async address => { 8 | return web3.utils.toBN(await web3.eth.getBalance(address)); 9 | }; 10 | 11 | // Polls an array for changes 12 | const waitForEvents = (eventsArray, numEvents) => { 13 | if (numEvents === 0) { 14 | return Promise.delay(1000); // Wait a reasonable amount so the caller can know no events fired 15 | } 16 | numEvents = numEvents || 1; 17 | const oldLength = eventsArray.length; 18 | let numTries = 0; 19 | const pollForEvents = function () { 20 | numTries++; 21 | if (eventsArray.length >= oldLength + numEvents) { 22 | return; 23 | } 24 | if (numTries >= 100) { 25 | if (eventsArray.length == 0) { 26 | console.log("Timed out waiting for events!"); 27 | } 28 | return; 29 | } 30 | return Promise.delay(50).then(pollForEvents); 31 | }; 32 | return pollForEvents(); 33 | }; 34 | 35 | const expectThrow = async promise => { 36 | try { 37 | await promise; 38 | } catch (error) { 39 | // TODO: Check jump destination to distinguish between a throw 40 | // and an actual invalid jump. 41 | const invalidOpcode = error.message.search("invalid opcode") >= 0; 42 | // TODO: When we contract A calls contract B, and B throws, instead 43 | // of an 'invalid jump', we get an 'out of gas' error. How do 44 | // we distinguish this from an actual out of gas event? (The 45 | // ganache log actually show an 'invalid jump' event.) 46 | const outOfGas = error.message.search("out of gas") >= 0; 47 | const revert = error.message.search("revert") >= 0; 48 | assert( 49 | invalidOpcode || outOfGas || revert, 50 | "Expected throw, got '" + error + "' instead" 51 | ); 52 | return; 53 | } 54 | assert.fail("Expected throw not received"); 55 | }; 56 | 57 | /** 58 | * Helper to get sha3 for solidity tightly-packed arguments 59 | * Actually signing `EIP191_PREFIX, EIP191_VERSION_DATA, walletAddress(this), nonce, authorizedAddress, data` 60 | * @param {string} walletAddr address of wallet 61 | * @param {number} nonce the nonce 62 | * @param {string} authorizedAddress the authorization key address 63 | * @param {string} data data as a hex string 64 | */ 65 | const getSha3ForConfirmationTx = (walletAddr, nonce, authorizedAddress, data) => { 66 | return abi.soliditySHA3( 67 | ['int8', 'int8', 'address', 'uint256', 'address', 'string'], 68 | [0x19, 0x0, new BN(walletAddr.replace('0x', ''), 16), nonce, new BN(authorizedAddress.replace('0x', ''), 16), data] 69 | ); 70 | }; 71 | 72 | const funcHash = signature => { 73 | return abi.soliditySHA3(["string"], [signature]).slice(0, 4); 74 | }; 75 | 76 | /** 77 | * Helper to get sha3 for solidity tightly-packed arguments 78 | * Actually signing `EIP191_PREFIX, EIP191_VERSION_DATA, walletAddress(this), data` 79 | * @param {string} walletAddr address of wallet 80 | * @param {Buffer} hash hashed data 81 | */ 82 | const getSha3ForERC1271 = (walletAddr, hash) => { 83 | return abi.soliditySHA3( 84 | ['int8', 'int8', 'address', 'bytes32'], 85 | [0x19, 0x0, new BN(walletAddr.replace('0x', ''), 16), hash] 86 | ); 87 | }; 88 | 89 | /** 90 | * Converts a number to a 32 byte padded hex encoded buffer 91 | * @param {number | string} num 92 | * @returns {Buffer} buffer 93 | */ 94 | const numToBuffer = num => { 95 | return numToBufferWithN(num, 64); 96 | }; 97 | 98 | const numToBufferWithN = (num, amt) => { 99 | return Buffer.from( 100 | new BN(web3.utils.toHex(num).replace("0x", ""), 16).toString(16, amt), 101 | "hex" 102 | ); // number 103 | }; 104 | 105 | /** 106 | * Pads a bytes4, encoded as a hex string, to 32 bytes. 107 | * The output is a hex-encoded string. 108 | */ 109 | const padBytes4 = b => `${b}${'0'.repeat(64-4*2)}`; 110 | 111 | /** 112 | * 113 | * @param {number | string | BigNumber} num 114 | */ 115 | const asAddressString = num => { 116 | //0x0000000000000000deadbeefdeadbeefdeadbeef2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e 117 | let str = new BN(web3.utils.toHex(num).replace("0x", ""), 16).toString( 118 | 16, 119 | 40 120 | ); 121 | if (str.length > 40) { 122 | str = str.slice(-40); 123 | } 124 | return web3.utils.toChecksumAddress("0x" + str); 125 | }; 126 | 127 | // Serialize signature into format understood by our recoverAddress function 128 | const serializeSignature = ({ r, s, v }) => 129 | "0x" + Buffer.concat([r, s, Buffer.from([v])]).toString("hex"); 130 | 131 | const serializeSignatures = (sig1, sig2) => 132 | "0x" + 133 | Buffer.concat([ 134 | sig1.r, 135 | sig1.s, 136 | Buffer.from([sig1.v]), 137 | sig2.r, 138 | sig2.s, 139 | Buffer.from([sig2.v]) 140 | ]).toString("hex"); 141 | 142 | // splits the signatures into their three parts 143 | const splitSignatures = (sig1, sig2) => { 144 | if (sig2) { 145 | return [ 146 | ["0x" + sig1.r.toString("hex"), "0x" + sig2.r.toString("hex")], 147 | ["0x" + sig1.s.toString("hex"), "0x" + sig2.s.toString("hex")], 148 | [ 149 | "0x" + Buffer.from([sig1.v]).toString("hex"), 150 | "0x" + Buffer.from([sig2.v]).toString("hex") 151 | ] 152 | ]; 153 | } else { 154 | return [ 155 | ["0x" + sig1.r.toString("hex"), "0x00000000"], 156 | ["0x" + sig1.s.toString("hex"), "0x00000000"], 157 | ["0x" + Buffer.from([sig1.v]).toString("hex"), "0x0"] 158 | ]; 159 | } 160 | }; 161 | 162 | const printLogs = result => { 163 | for (log of result.logs) { 164 | if (log.args.value) { 165 | console.log( 166 | 'log: {"label": ' + 167 | log.args.label + 168 | ', "value": 0x' + 169 | log.args.value.toString(16) + 170 | "}" 171 | ); 172 | } else { 173 | console.log("log: " + JSON.stringify(log.args)); 174 | } 175 | } 176 | }; 177 | 178 | /** 179 | * @returns {object} object with 'private' (`Buffer`) and 'address' (0x prefixed hex `string` address) components 180 | */ 181 | const newKeyPair = () => { 182 | const private = crypto.randomBytes(32); 183 | const address = "0x" + ethUtils.privateToAddress(private).toString("hex"); 184 | return { 185 | address: web3.utils.toChecksumAddress(address), 186 | private: private 187 | }; 188 | }; 189 | 190 | // deterministically computes the smart contract address given 191 | // the account the will deploy the contract (factory contract) 192 | // the salt as uint256 and the contract bytecode 193 | // As seen here: https://github.com/miguelmota/solidity-create2-example 194 | const buildCreate2Address = function (creatorAddress, saltHex, byteCode) { 195 | return `0x${web3.utils.sha3(`0x${[ 196 | 'ff', 197 | creatorAddress, 198 | saltHex, 199 | web3.utils.sha3(byteCode) 200 | ].map(x => x.replace(/0x/, '')) 201 | .join('')}`).slice(-40)}`.toLowerCase() 202 | } 203 | 204 | module.exports = { 205 | waitForEvents, 206 | expectThrow, 207 | getSha3ForConfirmationTx, 208 | getSha3ForERC1271, 209 | //getSha3ForConfirmationTxCallData, 210 | serializeSignature, 211 | serializeSignatures, 212 | splitSignatures, 213 | funcHash, 214 | // addrToBuffer, 215 | numToBuffer, 216 | numToBufferWithN, 217 | printLogs, 218 | newKeyPair, 219 | asAddressString, 220 | getBalanceWei, 221 | buildCreate2Address, 222 | padBytes4 223 | }; 224 | -------------------------------------------------------------------------------- /test/wallet-utils.js: -------------------------------------------------------------------------------- 1 | const utils = require("./utils"); 2 | const ethUtils = require("ethereumjs-util"); 3 | 4 | const WalletFactory = artifacts.require("./WalletFactory/WalletFactory.sol"); 5 | const CloneableWallet = artifacts.require("./Wallet/CloneableWallet.sol"); 6 | const FullWallet = artifacts.require("./Wallet/FullWallet.sol"); 7 | 8 | require("chai").should(); 9 | 10 | // wallet type 11 | const WALLET_REGULAR = 0; 12 | const WALLET_CLONE = 1; 13 | const WALLET_REGULAR_2 = 2; 14 | const WALLET_CLONE_2 = 3; 15 | 16 | 17 | // chain id 18 | const CHAIN_ID = 0; 19 | 20 | /** 21 | * @returns {[WalletFactory, root]} a new WalletFactory instance and the clone address 22 | */ 23 | const createCloneFactory = async function () { 24 | const walletRoot = await CloneableWallet.new(); 25 | const walletFactory = await WalletFactory.new(walletRoot.address); 26 | return [walletFactory, walletRoot.address]; 27 | }; 28 | 29 | /** 30 | * 31 | * @param {*} wtype WALLET_CLONE or WALLET_REGULAR 32 | * @param {string} _master master key address - string in hex format 33 | * @param {string} _admin admin key address - string in hex format 34 | * @param {string} _cosigner cosigner address - string in hex format 35 | * @param {*} _walletFactory a WalletFactory 36 | * @param {string} _salt the salt to use to deploy the wallet 37 | * @returns {FullWallet} a fresh wallet 38 | */ 39 | const createWallet = async function ( 40 | wtype, 41 | _master, 42 | _admin, 43 | _cosigner, 44 | _walletFactory, 45 | _salt 46 | ) { 47 | let _wallet; 48 | let code; 49 | switch (wtype) { 50 | case WALLET_CLONE: 51 | { 52 | const result = await _walletFactory.deployCloneWallet( 53 | _master, 54 | _admin, 55 | _cosigner 56 | ); 57 | result.logs 58 | .filter(log => log.event === "WalletCreated") 59 | .forEach(log => { 60 | _wallet = CloneableWallet.at(log.args.wallet); 61 | }); 62 | console.log("clone wallet gas used: " + result.receipt.gasUsed); 63 | } 64 | break; 65 | case WALLET_REGULAR: 66 | { 67 | const result = await _walletFactory.deployFullWallet( 68 | _master, 69 | _admin, 70 | _cosigner 71 | ); 72 | result.logs 73 | .filter(log => log.event === "WalletCreated") 74 | .forEach(log => { 75 | _wallet = FullWallet.at(log.args.wallet); 76 | }); 77 | console.log("full wallet gas used: " + result.receipt.gasUsed); 78 | } 79 | break; 80 | case WALLET_CLONE_2: 81 | { 82 | const result = await _walletFactory.deployCloneWallet2( 83 | _master, 84 | _admin, 85 | _cosigner, 86 | _salt 87 | ); 88 | result.logs 89 | .filter(log => log.event === "WalletCreated") 90 | .forEach(log => { 91 | _wallet = CloneableWallet.at(log.args.wallet); 92 | }); 93 | console.log("clone2 wallet gas used: " + result.receipt.gasUsed); 94 | } 95 | break; 96 | case WALLET_REGULAR_2: 97 | { 98 | const result = await _walletFactory.deployFullWallet2( 99 | _master, 100 | _admin, 101 | _cosigner, 102 | _salt 103 | ); 104 | 105 | result.logs 106 | .filter(log => log.event === "WalletCreated") 107 | .forEach(log => { 108 | _wallet = FullWallet.at(log.args.wallet); 109 | }); 110 | console.log("full wallet gas used: " + result.receipt.gasUsed); 111 | } 112 | break; 113 | } 114 | 115 | return _wallet; 116 | }; 117 | 118 | /** 119 | * 120 | * @param {number | string} _funder the funding address 121 | * @param {number | string} _to the receiver of the funds 122 | * @param {string} _amount the amount in wei 123 | */ 124 | const fundAddress = async function (_funder, _to, _amount) { 125 | await web3.eth.sendTransaction({ 126 | from: _funder, 127 | to: _to, 128 | value: _amount 129 | }); 130 | const toBalAfter = await web3.eth.getBalance(_to); 131 | toBalAfter.should.eql(_amount); 132 | }; 133 | 134 | /** 135 | * 136 | * @param {FullWallet} _wallet the wallet object 137 | * @param {number | string} _key the public address to get the nonce for 138 | * @returns {number} the nonce 139 | */ 140 | const getNonce = async function (_wallet, _key) { 141 | // Run before each test. Sets the sequence ID up to be used in the tests 142 | return (await _wallet.nonces.call(_key)).toNumber(); 143 | }; 144 | 145 | /** 146 | * Calls a wallet with an arbitrary method ID 147 | * @param {FullWallet} _wallet the wallet to call 148 | * @param {string} _methodID The method ID of the method we're calling 149 | * @param {Buffer} _data The data to pass to the method, encoding the args 150 | */ 151 | const callDynamic = async function (_wallet, _methodID, _data) { 152 | let args = ""; 153 | if (_data) { 154 | args = _data.toString("hex"); 155 | }; 156 | return (await web3.eth.call({ 157 | to: _wallet.address, 158 | data: "0x" + _methodID + args, 159 | })); 160 | } 161 | 162 | /** 163 | * Sends a transaction to a wallet with an arbitrary method ID 164 | * @param {FullWallet} _wallet The wallet to call 165 | * @param {string} _from The sender of the transaction 166 | * @param {string} _methodID The method ID of the method we're calling 167 | * @param {Buffer} _data The data to pass to the method, encoding the args 168 | */ 169 | const transactDynamic = async function (_wallet, _from, _methodID, _data) { 170 | let args = ""; 171 | if (_data) { 172 | args = _data.toString("hex"); 173 | }; 174 | return (await web3.eth.sendTransaction({ 175 | to: _wallet.address, 176 | from: _from, 177 | data: "0x" + _methodID + args, 178 | })); 179 | } 180 | 181 | 182 | /** 183 | * 184 | * @param {Buffer} _data 185 | * @param {FullWallet} _wallet 186 | * @param {string} _sender string in hex format (must be address from accounts array) 187 | */ 188 | const transact0 = async function (_data, _wallet, _sender) { 189 | // call invoke0 190 | const result = await _wallet.invoke0("0x" + _data.toString("hex"), { 191 | from: _sender 192 | }); 193 | 194 | //utils.printLogs(result); 195 | 196 | //console.log("sender: " + _sender); 197 | 198 | return result.receipt.gasUsed; 199 | }; 200 | 201 | /** 202 | * 203 | * @param {Buffer} _data 204 | * @param {Wallet} _wallet 205 | * @param {string} _sender string in hex format (must be address from accounts array) 206 | */ 207 | const transact0Twice = async function (_data, _wallet, _sender) { 208 | await transact0(_data, _wallet, _sender); 209 | const gas = await transact0(_data, _wallet, _sender); 210 | return gas; 211 | }; 212 | 213 | /** 214 | * 215 | * @param {Buffer} _data 216 | * @param {FullWallet} _wallet 217 | * @param {number} _nonce 218 | * @param {*} param3 - private/public key pair (the signer) 219 | * @param {string} _sender - string in hex format (must be address from accounts array) (the cosigner) 220 | */ 221 | const transact1 = async function ( 222 | _data, 223 | _wallet, 224 | _nonce, 225 | { address1, private1 }, 226 | _sender 227 | ) { 228 | // get hash 229 | const operationHash = utils.getSha3ForConfirmationTx( 230 | _wallet.address, 231 | _nonce, 232 | address1, 233 | _data 234 | ); 235 | 236 | //console.log("operationHash: 0x" + operationHash.toString('hex')); 237 | 238 | const sig1 = ethUtils.ecsign(operationHash, private1, CHAIN_ID); 239 | const r = "0x" + sig1.r.toString("hex"); 240 | const s = "0x" + sig1.s.toString("hex"); 241 | const v = "0x" + Buffer.from([sig1.v]).toString("hex"); 242 | 243 | // console.log("r: " + r); 244 | // console.log("s: " + s); 245 | // console.log("v: " + v); 246 | 247 | // call invoke1CosignerSends 248 | const result = await _wallet.invoke1CosignerSends( 249 | v, 250 | r, 251 | s, 252 | _nonce, 253 | address1, 254 | '0x' + _data.toString('hex'), 255 | { from: _sender } 256 | ); 257 | 258 | //utils.printLogs(result); 259 | 260 | //console.log("child key 1: " + address1); 261 | return result.receipt.gasUsed; 262 | }; 263 | 264 | /** 265 | * 266 | * @param {Buffer} _data 267 | * @param {Wallet} _wallet 268 | * @param {*} _nonce 269 | * @param {*} param3 270 | * @param {string} _sender 271 | */ 272 | const transact1Twice = async function ( 273 | _data, 274 | _wallet, 275 | _nonce, 276 | { address1, private1 }, 277 | _sender 278 | ) { 279 | let nonce = _nonce; 280 | await transact1(_data, _wallet, nonce, { address1, private1 }, _sender); 281 | nonce += 1; 282 | let gas = await transact1( 283 | _data, 284 | _wallet, 285 | nonce, 286 | { address1, private1 }, 287 | _sender 288 | ); 289 | return gas; 290 | }; 291 | 292 | /** 293 | * 294 | * @param {Buffer} _data 295 | * @param {Wallet} _wallet 296 | * @param {*} _nonce 297 | * @param {*} param3 - public/private key pair of the cosigner 298 | * @param {string} _sender - treated as the signer in this cae 299 | */ 300 | const transact11 = async function ( 301 | _data, 302 | _wallet, 303 | _nonce, 304 | { address1, private1 }, 305 | _sender 306 | ) { 307 | // get hash 308 | const operationHash = utils.getSha3ForConfirmationTx( 309 | _wallet.address, 310 | _nonce, 311 | _sender, 312 | _data 313 | ); 314 | 315 | //console.log("operationHash: 0x" + operationHash.toString('hex')); 316 | 317 | const sig1 = ethUtils.ecsign(operationHash, private1, CHAIN_ID); 318 | const r = "0x" + sig1.r.toString("hex"); 319 | const s = "0x" + sig1.s.toString("hex"); 320 | const v = "0x" + Buffer.from([sig1.v]).toString("hex"); 321 | 322 | // console.log("r: " + r); 323 | // console.log("s: " + s); 324 | // console.log("v: " + v); 325 | 326 | // call invoke1SignerSends (invoke11) 327 | const result = await _wallet.invoke1SignerSends( 328 | v, 329 | r, 330 | s, 331 | "0x" + _data.toString("hex"), 332 | { from: _sender } 333 | ); 334 | 335 | //utils.printLogs(result); 336 | 337 | //console.log("child key 1: " + address1); 338 | return result.receipt.gasUsed; 339 | }; 340 | 341 | /** 342 | * 343 | * @param {Buffer} _data 344 | * @param {Wallet} _wallet 345 | * @param {*} _nonce 346 | * @param {*} param3 347 | * @param {string} _sender 348 | */ 349 | const transact11Twice = async function ( 350 | _data, 351 | _wallet, 352 | _nonce, 353 | { address1, private1 }, 354 | _sender 355 | ) { 356 | let nonce = _nonce; 357 | await transact11(_data, _wallet, nonce, { address1, private1 }, _sender); 358 | nonce += 1; 359 | let gas = await transact11( 360 | _data, 361 | _wallet, 362 | nonce, 363 | { address1, private1 }, 364 | _sender 365 | ); 366 | return gas; 367 | }; 368 | 369 | /** 370 | * 371 | * @param {Buffer} _data 372 | * @param {Wallet} _wallet 373 | * @param {*} _nonce 374 | * @param {*} param3 375 | * @param {string} _sender 376 | */ 377 | const transact2 = async function ( 378 | _data, 379 | _wallet, 380 | _nonce, 381 | { address1, private1, address2, private2 }, 382 | _sender 383 | ) { 384 | // get hash 385 | const operationHash = utils.getSha3ForConfirmationTx( 386 | _wallet.address, 387 | _nonce, 388 | address1, 389 | _data 390 | ); 391 | 392 | //console.log("operationHash: 0x" + operationHash.toString('hex')); 393 | 394 | let r, s, v; 395 | const sig1 = ethUtils.ecsign(operationHash, private1, CHAIN_ID); 396 | const sig2 = ethUtils.ecsign(operationHash, private2, CHAIN_ID); 397 | [r, s, v] = utils.splitSignatures(sig1, sig2); 398 | 399 | const result = await _wallet.invoke2( 400 | v, 401 | r, 402 | s, 403 | _nonce, 404 | address1, 405 | '0x' + _data.toString('hex'), 406 | { from: _sender } 407 | ); 408 | 409 | utils.printLogs(result); 410 | 411 | //console.log("child key 1: " + address1); 412 | //console.log("child key 2: " + address2); 413 | 414 | return result.receipt.gasUsed; 415 | }; 416 | 417 | /** 418 | * 419 | * @param {Buffer} _data - a data buffer 420 | * @param {Wallet} _wallet - the wallet contract instance 421 | * @param {*} _nonce - the initial wallet nonce 422 | * @param {*} param3 - the public and private key pairs 423 | * @param {string} _sender - signer of the outer transaction 424 | */ 425 | const transact2Twice = async function ( 426 | _data, 427 | _wallet, 428 | _nonce, 429 | { address1, private1, address2, private2 }, 430 | _sender 431 | ) { 432 | let nonce = _nonce; 433 | await transact2( 434 | _data, 435 | _wallet, 436 | nonce, 437 | { address1, private1, address2, private2 }, 438 | _sender 439 | ); 440 | // send again because the gas cost will change a bit 441 | nonce += 1; 442 | const gas = await transact2( 443 | _data, 444 | _wallet, 445 | nonce, 446 | { address1, private1, address2, private2 }, 447 | _sender 448 | ); 449 | return gas; 450 | }; 451 | 452 | const erc20Transfer = (amount, erc20Recipient) => { 453 | let erc20DataArr = []; 454 | // function signature 455 | erc20DataArr.push(utils.funcHash("transfer(address,uint256)")); 456 | // arg: to address 457 | erc20DataArr.push(utils.numToBuffer(erc20Recipient)); 458 | // arg: amount (256) 459 | erc20DataArr.push(utils.numToBuffer(amount)); 460 | return Buffer.concat(erc20DataArr); 461 | }; 462 | 463 | const erc721Transfer = (tokenID, walletAddress, recipient) => { 464 | let dataArr = []; 465 | // function signature 466 | dataArr.push(utils.funcHash("transferFrom(address,address,uint256)")); 467 | // arg: from address 468 | dataArr.push(utils.numToBuffer(walletAddress)); 469 | // arg: to address 470 | dataArr.push(utils.numToBuffer(recipient)); 471 | // arg: NFT index 472 | dataArr.push(utils.numToBuffer(tokenID)); 473 | return Buffer.concat(dataArr); 474 | }; 475 | /** 476 | * 477 | * @param {number} revert : 1 (revert) or 0 (no revert) 478 | * @param {number | string} to 479 | * @param {number | string} amount 480 | * @param {Buffer} dataBuff 481 | */ 482 | const txData = (revert, to, amount, dataBuff) => { 483 | // revert_flag (1), to (20), value (32), data length (32), data 484 | let dataArr = []; 485 | let revertBuff = Buffer.alloc(1); 486 | // don't revert for now 487 | revertBuff.writeUInt8(revert); 488 | dataArr.push(revertBuff); 489 | // 'to' is not padded (20 bytes) 490 | dataArr.push(Buffer.from(to.replace("0x", ""), "hex")); // address as string 491 | // value (32 bytes) 492 | dataArr.push(utils.numToBuffer(amount)); 493 | // data length (0) 494 | dataArr.push(utils.numToBuffer(dataBuff.length)); 495 | if (dataBuff.length > 0) { 496 | dataArr.push(dataBuff); 497 | } 498 | return Buffer.concat(dataArr); 499 | }; 500 | 501 | module.exports = { 502 | WALLET_REGULAR, 503 | WALLET_CLONE, 504 | WALLET_CLONE_2, 505 | WALLET_REGULAR_2, 506 | CHAIN_ID, 507 | createCloneFactory, 508 | createWallet, 509 | fundAddress, 510 | getNonce, 511 | transact0, 512 | transact0Twice, 513 | transact1, 514 | transact1Twice, 515 | transact11, 516 | transact11Twice, 517 | transact2, 518 | transact2Twice, 519 | erc20Transfer, 520 | erc721Transfer, 521 | txData, 522 | callDynamic, 523 | transactDynamic 524 | }; 525 | -------------------------------------------------------------------------------- /transaction.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Dapper Wallet - Multi-Sig Transaction Structure 4 | 5 | 6 | ' object Outer { 7 | ' nonce = unqiue per tx per user 8 | ' gasprice = price of gas for this tx 9 | ' startgas = upper limit of gas to use 10 | ' to = destination of the tx 11 | ' value = amount of ETH sent 12 | ' singature = signature of this Tx 13 | ' ' object Data as data { 14 | ' ' name = "hello" 15 | ' ' } 16 | ' } 17 | 18 | rectangle "Ethereum Transaction\n\n - nonce \n - gasprice\n - startgas\n - to\n - value\n - signature" as outer { 19 | rectangle "Data\n\n - to\n - value\n - wallet_address\n - device_address\n - inner_nonce\n - signature" as data { 20 | rectangle "Inner Data" as inner { 21 | } 22 | } 23 | } 24 | 25 | note left of outer: A standard\nEthereum transaction 26 | note bottom of data: Ask wallet contract\nfor correct inner nonce 27 | note bottom of inner: Data sent to inner "to" address\nincluding function, args, etc;\n\nIs "0x" for just sending ETH 28 | 29 | @enduml 30 | 31 | @enduml -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | require("dotenv").config(); 16 | 17 | const web3 = require("web3"); 18 | const HDWalletProvider = require("truffle-hdwallet-provider"); 19 | 20 | const { 21 | ETH_NODE_ADDRESS, 22 | ETH_NODE_USER, 23 | ETH_NODE_PASSWORD, 24 | ETH_FROM_ADDRESS, 25 | HD_MNEMONIC, 26 | INFURA_API_KEY 27 | } = process.env; 28 | 29 | function getProvider() { 30 | return new web3.providers.HttpProvider( 31 | ETH_NODE_ADDRESS, 32 | 10000, 33 | ETH_NODE_USER, 34 | ETH_NODE_PASSWORD 35 | ); 36 | } 37 | 38 | function getHDProvider() { 39 | return new HDWalletProvider( 40 | HD_MNEMONIC, 41 | "https://rinkeby.infura.io/v3/" + INFURA_API_KEY 42 | ); 43 | } 44 | 45 | module.exports = { 46 | // See 47 | // to customize your Truffle configuration! 48 | networks: { 49 | // development: { 50 | // host: "127.0.0.1", 51 | // port: 8545, 52 | // gas: 4500000, // Gas limit used for deploys 53 | // gasPrice: 10000000000, 54 | // network_id: "*" // Match any network id 55 | // }, 56 | // development: { 57 | // host: "127.0.0.1", 58 | // port: 9545, 59 | // gas: 45000000, // Gas limit used for deploys 60 | // gasPrice: 10000000000, 61 | // network_id: "*" // Match any network id 62 | // }, 63 | 64 | // this is rinkeby for our geth node 65 | rinkeby_geth: { 66 | provider: getProvider, 67 | network_id: 4, 68 | from: ETH_FROM_ADDRESS, 69 | gas: 4500000, // 2M gas limit used for deploy 70 | gasPrice: 10000000000 // 10gwei 71 | }, 72 | rinkeby_local: { 73 | provider: getHDProvider, 74 | network_id: 4, 75 | gas: 4500000, // 2M gas limit used for deploy 76 | gasPrice: 10000000000 // 10gwei 77 | }, 78 | live: { 79 | provider: getProvider, 80 | network_id: 1, 81 | from: ETH_FROM_ADDRESS, 82 | gas: 1000000, // 1M 83 | gasPrice: 5000000000 // 5 gwei 84 | } 85 | }, 86 | solc: { 87 | optimizer: { 88 | enabled: true, 89 | runs: 200 90 | } 91 | }, 92 | // https://truffle.readthedocs.io/en/beta/advanced/configuration/ 93 | mocha: { 94 | bail: true 95 | }, 96 | compilers: { 97 | solc: { 98 | version: "0.5.10", 99 | settings: { 100 | evmVersion: "constantinople", 101 | optimizer: { 102 | enabled: true, 103 | runs: 200 104 | } 105 | } 106 | } 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /wallet.wsd: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | title Dapper Wallet - Component Diagram 4 | 5 | sprite $key [16x16/16] { 6 | FFFFFFFFFFFFFFFF 7 | FFFFFFFFFFFFFFFF 8 | FFFFFFFFFFFFFFFF 9 | FFFFFFFFFFFFFFFF 10 | FFFFFFFFFFFFFFFF 11 | FFF00FFFFFFFFFFF 12 | FF00000000000FFF 13 | FF000000000000FF 14 | FFF00FFFFF0F0FFF 15 | FFFFFFFFFFFFFFFF 16 | FFFFFFFFFFFFFFFF 17 | FFFFFFFFFFFFFFFF 18 | FFFFFFFFFFFFFFFF 19 | FFFFFFFFFFFFFFFF 20 | FFFFFFFFFFFFFFFF 21 | FFFFFFFFFFFFFFFF 22 | } 23 | 24 | package "Smart Contract Wallet" as SCW { 25 | 26 | node "Device Keys" { 27 | ' interface Ak1 <<$key>> as AK1 28 | [DK1] <<$key>> as AK1 29 | [DK2] <<$key>> as AK2 30 | [DK3] <<$key>> as AK3 31 | } 32 | 33 | node "Recovery" { 34 | [Recovery Key] <<$key>> as MK 35 | } 36 | } 37 | 38 | package "User Cold Storage" as UCS { 39 | [Backup Key] <<$key>> as BU 40 | } 41 | 42 | package "Cosigning Contract 1" as CS1 { 43 | [-handleTransaction()] as V1 44 | } 45 | 46 | package "Recovery Transaction" { 47 | [- Sets **Backup Key** as sole **Device Key**\n- Signed by **Recovery Key**] as P1 48 | } 49 | 50 | [Cosiging Key] <<$key>> as CS4 51 | 52 | AK1 -down-> CS4 53 | SCW <-down- V1 54 | P1 -down-> MK 55 | P1 -down-> BU 56 | AK3 -down->CS1 57 | 58 | @enduml --------------------------------------------------------------------------------