├── .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 |
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 |
--------------------------------------------------------------------------------
/out/wallet/Dapper Wallet - Component Diagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------