diff --git a/.gitignore b/.gitignore
index 69504a8c2..59839feff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,4 @@ Dockerfile
/rocketpool/rocketpool-daemon-darwin-arm64
/rocketpool/rocketpool-daemon-linux-arm64
.vscode-ctags
-build/
\ No newline at end of file
+build/
diff --git a/bindings/LICENSE b/bindings/LICENSE
new file mode 100644
index 000000000..94a9ed024
--- /dev/null
+++ b/bindings/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "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.
+
+ 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.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 1. Source Code.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ 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.
+
+ 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.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ 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.
+
+ 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.
+
+ 4. Conveying Verbatim Copies.
+
+ 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.
+
+ 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.
+
+ 5. Conveying Modified Source Versions.
+
+ 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:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ 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".
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 6. Conveying Non-Source Forms.
+
+ 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:
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ "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.
+
+ 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).
+
+ 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.
+
+ 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.
+
+ 7. Additional Terms.
+
+ "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.
+
+ 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.
+
+ 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:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ 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
+
+ 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
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 8. Termination.
+
+ 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).
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ 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.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 11. Patents.
+
+ 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".
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 12. No Surrender of Others' Freedom.
+
+ 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.
+
+ 13. Use with the GNU Affero General Public License.
+
+ 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.
+
+ 14. Revised Versions of this License.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ 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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ 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.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/bindings/README.md b/bindings/README.md
new file mode 100644
index 000000000..8a595cc3c
--- /dev/null
+++ b/bindings/README.md
@@ -0,0 +1,2 @@
+# rocketpool-go
+A Golang library for interacting with the Rocket Pool network.
diff --git a/bindings/auction/auction.go b/bindings/auction/auction.go
new file mode 100644
index 000000000..821aae6dc
--- /dev/null
+++ b/bindings/auction/auction.go
@@ -0,0 +1,606 @@
+package auction
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Settings
+const LotDetailsBatchSize = 10
+
+// Lot details
+type LotDetails struct {
+ Index uint64 `json:"index"`
+ Exists bool `json:"exists"`
+ StartBlock uint64 `json:"startBlock"`
+ EndBlock uint64 `json:"endBlock"`
+ StartPrice *big.Int `json:"startPrice"`
+ ReservePrice *big.Int `json:"reservePrice"`
+ PriceAtCurrentBlock *big.Int `json:"priceAtCurrentBlock"`
+ PriceByTotalBids *big.Int `json:"priceByTotalBids"`
+ CurrentPrice *big.Int `json:"currentPrice"`
+ TotalRPLAmount *big.Int `json:"totalRplAmount"`
+ ClaimedRPLAmount *big.Int `json:"claimedRplAmount"`
+ RemainingRPLAmount *big.Int `json:"remainingRplAmount"`
+ TotalBidAmount *big.Int `json:"totalBidAmount"`
+ AddressBidAmount *big.Int `json:"addressBidAmount"`
+ Cleared bool `json:"cleared"`
+ RPLRecovered bool `json:"rplRecovered"`
+}
+
+// Get all lot details
+func GetLots(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]LotDetails, error) {
+
+ // Get lot count
+ lotCount, err := GetLotCount(rp, opts)
+ if err != nil {
+ return []LotDetails{}, err
+ }
+
+ // Load lot details in batches
+ details := make([]LotDetails, lotCount)
+ for bsi := uint64(0); bsi < lotCount; bsi += LotDetailsBatchSize {
+
+ // Get batch start & end index
+ lsi := bsi
+ lei := bsi + LotDetailsBatchSize
+ if lei > lotCount {
+ lei = lotCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for li := lsi; li < lei; li++ {
+ li := li
+ wg.Go(func() error {
+ lotDetails, err := GetLotDetails(rp, li, opts)
+ if err == nil {
+ details[li] = lotDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []LotDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all lot details with bids from an address
+func GetLotsWithBids(rp *rocketpool.RocketPool, bidder common.Address, opts *bind.CallOpts) ([]LotDetails, error) {
+
+ // Get lot count
+ lotCount, err := GetLotCount(rp, opts)
+ if err != nil {
+ return []LotDetails{}, err
+ }
+
+ // Load lot details in batches
+ details := make([]LotDetails, lotCount)
+ for bsi := uint64(0); bsi < lotCount; bsi += LotDetailsBatchSize {
+
+ // Get batch start & end index
+ lsi := bsi
+ lei := bsi + LotDetailsBatchSize
+ if lei > lotCount {
+ lei = lotCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for li := lsi; li < lei; li++ {
+ li := li
+ wg.Go(func() error {
+ lotDetails, err := GetLotDetailsWithBids(rp, li, bidder, opts)
+ if err == nil {
+ details[li] = lotDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []LotDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get a lot's details
+func GetLotDetails(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (LotDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var startBlock uint64
+ var endBlock uint64
+ var startPrice *big.Int
+ var reservePrice *big.Int
+ var priceAtCurrentBlock *big.Int
+ var priceByTotalBids *big.Int
+ var currentPrice *big.Int
+ var totalRplAmount *big.Int
+ var claimedRplAmount *big.Int
+ var remainingRplAmount *big.Int
+ var totalBidAmount *big.Int
+ var cleared bool
+ var rplRecovered bool
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetLotExists(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ startBlock, err = GetLotStartBlock(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ endBlock, err = GetLotEndBlock(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ startPrice, err = GetLotStartPrice(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ reservePrice, err = GetLotReservePrice(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ priceAtCurrentBlock, err = GetLotPriceAtCurrentBlock(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ priceByTotalBids, err = GetLotPriceByTotalBids(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ currentPrice, err = GetLotCurrentPrice(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ totalRplAmount, err = GetLotTotalRPLAmount(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ claimedRplAmount, err = GetLotClaimedRPLAmount(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ remainingRplAmount, err = GetLotRemainingRPLAmount(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ totalBidAmount, err = GetLotTotalBidAmount(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ cleared, err = GetLotIsCleared(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ rplRecovered, err = GetLotRPLRecovered(rp, lotIndex, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return LotDetails{}, err
+ }
+
+ // Return
+ return LotDetails{
+ Index: lotIndex,
+ Exists: exists,
+ StartBlock: startBlock,
+ EndBlock: endBlock,
+ StartPrice: startPrice,
+ ReservePrice: reservePrice,
+ PriceAtCurrentBlock: priceAtCurrentBlock,
+ PriceByTotalBids: priceByTotalBids,
+ CurrentPrice: currentPrice,
+ TotalRPLAmount: totalRplAmount,
+ ClaimedRPLAmount: claimedRplAmount,
+ RemainingRPLAmount: remainingRplAmount,
+ TotalBidAmount: totalBidAmount,
+ Cleared: cleared,
+ RPLRecovered: rplRecovered,
+ }, nil
+
+}
+
+// Get a lot's details with address bid amounts
+func GetLotDetailsWithBids(rp *rocketpool.RocketPool, lotIndex uint64, bidder common.Address, opts *bind.CallOpts) (LotDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var details LotDetails
+ var addressBidAmount *big.Int
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ details, err = GetLotDetails(rp, lotIndex, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ addressBidAmount, err = GetLotAddressBidAmount(rp, lotIndex, bidder, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return LotDetails{}, err
+ }
+
+ // Return
+ details.AddressBidAmount = addressBidAmount
+ return details, nil
+
+}
+
+// Get the total RPL balance of the auction contract
+func GetTotalRPLBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalRplBalance := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, totalRplBalance, "getTotalRPLBalance"); err != nil {
+ return nil, fmt.Errorf("error getting auction contract total RPL balance: %w", err)
+ }
+ return *totalRplBalance, nil
+}
+
+// Get the allotted RPL balance of the auction contract
+func GetAllottedRPLBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ allottedRplBalance := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, allottedRplBalance, "getAllottedRPLBalance"); err != nil {
+ return nil, fmt.Errorf("error getting auction contract allotted RPL balance: %w", err)
+ }
+ return *allottedRplBalance, nil
+}
+
+// Get the remaining RPL balance of the auction contract
+func GetRemainingRPLBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ remainingRplBalance := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, remainingRplBalance, "getRemainingRPLBalance"); err != nil {
+ return nil, fmt.Errorf("error getting auction contract remaining RPL balance: %w", err)
+ }
+ return *remainingRplBalance, nil
+}
+
+// Get the number of lots for auction
+func GetLotCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ lotCount := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotCount, "getLotCount"); err != nil {
+ return 0, fmt.Errorf("error getting lot count: %w", err)
+ }
+ return (*lotCount).Uint64(), nil
+}
+
+// Lot details
+func GetLotExists(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (bool, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ lotExists := new(bool)
+ if err := rocketAuctionManager.Call(opts, lotExists, "getLotExists", big.NewInt(int64(lotIndex))); err != nil {
+ return false, fmt.Errorf("error getting lot %d exists status: %w", lotIndex, err)
+ }
+ return *lotExists, nil
+}
+func GetLotStartBlock(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ lotStartBlock := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotStartBlock, "getLotStartBlock", big.NewInt(int64(lotIndex))); err != nil {
+ return 0, fmt.Errorf("error getting lot %d start block: %w", lotIndex, err)
+ }
+ return (*lotStartBlock).Uint64(), nil
+}
+func GetLotEndBlock(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ lotEndBlock := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotEndBlock, "getLotEndBlock", big.NewInt(int64(lotIndex))); err != nil {
+ return 0, fmt.Errorf("error getting lot %d end block: %w", lotIndex, err)
+ }
+ return (*lotEndBlock).Uint64(), nil
+}
+func GetLotStartPrice(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotStartPrice := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotStartPrice, "getLotStartPrice", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d start price: %w", lotIndex, err)
+ }
+ return *lotStartPrice, nil
+}
+func GetLotReservePrice(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotReservePrice := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotReservePrice, "getLotReservePrice", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d reserve price: %w", lotIndex, err)
+ }
+ return *lotReservePrice, nil
+}
+func GetLotTotalRPLAmount(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotTotalRplAmount := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotTotalRplAmount, "getLotTotalRPLAmount", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d total RPL amount: %w", lotIndex, err)
+ }
+ return *lotTotalRplAmount, nil
+}
+func GetLotTotalBidAmount(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotTotalBidAmount := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotTotalBidAmount, "getLotTotalBidAmount", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d total ETH bid amount: %w", lotIndex, err)
+ }
+ return *lotTotalBidAmount, nil
+}
+func GetLotRPLRecovered(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (bool, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ lotRplRecovered := new(bool)
+ if err := rocketAuctionManager.Call(opts, lotRplRecovered, "getLotRPLRecovered", big.NewInt(int64(lotIndex))); err != nil {
+ return false, fmt.Errorf("error getting lot %d RPL recovered status: %w", lotIndex, err)
+ }
+ return *lotRplRecovered, nil
+}
+func GetLotPriceAtCurrentBlock(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotPriceAtCurrentBlock := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotPriceAtCurrentBlock, "getLotPriceAtCurrentBlock", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d price by current block: %w", lotIndex, err)
+ }
+ return *lotPriceAtCurrentBlock, nil
+}
+func GetLotPriceByTotalBids(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotPriceByTotalBids := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotPriceByTotalBids, "getLotPriceByTotalBids", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d price by total bids: %w", lotIndex, err)
+ }
+ return *lotPriceByTotalBids, nil
+}
+func GetLotCurrentPrice(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotCurrentPrice := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotCurrentPrice, "getLotCurrentPrice", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d current price: %w", lotIndex, err)
+ }
+ return *lotCurrentPrice, nil
+}
+func GetLotClaimedRPLAmount(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotClaimedRplAmount := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotClaimedRplAmount, "getLotClaimedRPLAmount", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d claimed RPL amount: %w", lotIndex, err)
+ }
+ return *lotClaimedRplAmount, nil
+}
+func GetLotRemainingRPLAmount(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotRemainingRplAmount := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotRemainingRplAmount, "getLotRemainingRPLAmount", big.NewInt(int64(lotIndex))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d remaining RPL amount: %w", lotIndex, err)
+ }
+ return *lotRemainingRplAmount, nil
+}
+func GetLotIsCleared(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.CallOpts) (bool, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ lotIsCleared := new(bool)
+ if err := rocketAuctionManager.Call(opts, lotIsCleared, "getLotIsCleared", big.NewInt(int64(lotIndex))); err != nil {
+ return false, fmt.Errorf("error getting lot %d cleared status: %w", lotIndex, err)
+ }
+ return *lotIsCleared, nil
+}
+
+// Get the price of a lot at a specific block
+func GetLotPriceAtBlock(rp *rocketpool.RocketPool, lotIndex, blockNumber uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lotPriceAtBlock := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lotPriceAtBlock, "getLotPriceAtBlock", big.NewInt(int64(lotIndex)), big.NewInt(int64(blockNumber))); err != nil {
+ return nil, fmt.Errorf("error getting lot %d price at block: %w", lotIndex, err)
+ }
+ return *lotPriceAtBlock, nil
+}
+
+// Get the ETH amount bid on a lot by an address
+func GetLotAddressBidAmount(rp *rocketpool.RocketPool, lotIndex uint64, bidder common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ lot := new(*big.Int)
+ if err := rocketAuctionManager.Call(opts, lot, "getLotAddressBidAmount", big.NewInt(int64(lotIndex)), bidder); err != nil {
+ return nil, fmt.Errorf("error getting lot %d address ETH bid amount: %w", lotIndex, err)
+ }
+ return *lot, nil
+}
+
+// Estimate the gas of CreateLot
+func EstimateCreateLotGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketAuctionManager.GetTransactionGasInfo(opts, "createLot")
+}
+
+// Create a new lot
+func CreateLot(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ lotCount, err := GetLotCount(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ tx, err := rocketAuctionManager.Transact(opts, "createLot")
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error creating lot: %w", err)
+ }
+ return lotCount, tx.Hash(), nil
+}
+
+// Estimate the gas of PlaceBid
+func EstimatePlaceBidGas(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketAuctionManager.GetTransactionGasInfo(opts, "placeBid", big.NewInt(int64(lotIndex)))
+}
+
+// Place a bid on a lot
+func PlaceBid(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketAuctionManager.Transact(opts, "placeBid", big.NewInt(int64(lotIndex)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error placing bid on lot %d: %w", lotIndex, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ClaimBid
+func EstimateClaimBidGas(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketAuctionManager.GetTransactionGasInfo(opts, "claimBid", big.NewInt(int64(lotIndex)))
+}
+
+// Claim RPL from a lot that was bid on
+func ClaimBid(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketAuctionManager.Transact(opts, "claimBid", big.NewInt(int64(lotIndex)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error claiming bid from lot %d: %w", lotIndex, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of RecoverUnclaimedRPL
+func EstimateRecoverUnclaimedRPLGas(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketAuctionManager.GetTransactionGasInfo(opts, "recoverUnclaimedRPL", big.NewInt(int64(lotIndex)))
+}
+
+// Recover unclaimed RPL from a lot
+func RecoverUnclaimedRPL(rp *rocketpool.RocketPool, lotIndex uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketAuctionManager, err := getRocketAuctionManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketAuctionManager.Transact(opts, "recoverUnclaimedRPL", big.NewInt(int64(lotIndex)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error recovering unclaimed RPL from lot %d: %w", lotIndex, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketAuctionManagerLock sync.Mutex
+
+func getRocketAuctionManager(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketAuctionManagerLock.Lock()
+ defer rocketAuctionManagerLock.Unlock()
+ return rp.GetContract("rocketAuctionManager", opts)
+}
diff --git a/bindings/azure-pipelines.yml b/bindings/azure-pipelines.yml
new file mode 100644
index 000000000..a1338bf3a
--- /dev/null
+++ b/bindings/azure-pipelines.yml
@@ -0,0 +1,36 @@
+# Go
+# Build your Go project.
+# Add steps that test, save build artifacts, deploy, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/go
+
+trigger:
+ branches:
+ include:
+ - '*'
+ - refs/tags/*
+
+pool:
+ vmImage: ubuntu-latest
+
+steps:
+- task: DownloadSecureFile@1
+ name: githubPEM
+ displayName: 'Download Github PEM'
+ inputs:
+ secureFile: 'rp-azure-pipeline-github.pem'
+- bash: |
+ eval $(ruby -e "require 'openssl'; require 'jwt'; private_pem = File.read(ENV['GITHUB_PEM_PATH']); private_key = OpenSSL::PKey::RSA.new(private_pem); payload = { iat: Time.now.to_i - 60, exp: Time.now.to_i + (10 * 60), iss: ENV['GITHUB_APP_ID'] }; jwt = JWT.encode(payload, private_key, 'RS256'); puts 'PUSH_JWT='+jwt;")
+ TOKEN=$(curl -s -X POST \
+ -H "Authorization: Bearer $PUSH_JWT" \
+ -H "Accept: application/vnd.github.v3+json" \
+ https://api.github.com/app/installations/$GITHUB_APP_INSTALLATION_ID/access_tokens \
+ | jq -r '.token')
+ git remote add github https://x-access-token:$TOKEN@github.com/rocket-pool/$REPO_NAME
+ git fetch github
+ git push github HEAD:$(Build.SourceBranch) -f --verbose
+ git push github HEAD:$(Build.SourceBranch) --tags --verbose
+ displayName: 'Push to Github'
+ env:
+ GITHUB_PEM_PATH: $(githubPEM.secureFilePath)
+ GITHUB_APP_ID: $(GITHUB_APP_ID)
+ GITHUB_APP_INSTALLATION_ID: $(GITHUB_APP_INSTALLATION_ID)
diff --git a/bindings/contracts/rocket-storage.go b/bindings/contracts/rocket-storage.go
new file mode 100644
index 000000000..174c7a8a5
--- /dev/null
+++ b/bindings/contracts/rocket-storage.go
@@ -0,0 +1,683 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package contracts
+
+import (
+ "math/big"
+ "strings"
+
+ ethereum "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var (
+ _ = big.NewInt
+ _ = strings.NewReader
+ _ = ethereum.NotFound
+ _ = bind.Bind
+ _ = common.Big1
+ _ = types.BloomLookup
+ _ = event.NewSubscription
+)
+
+// RocketStorageABI is the input ABI used to generate the binding from.
+const RocketStorageABI = "[{\"inputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": false,\"internalType\": \"address\",\"name\": \"oldGuardian\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"address\",\"name\": \"newGuardian\",\"type\": \"address\"}],\"name\": \"GuardianChanged\",\"type\": \"event\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"node\",\"type\": \"address\"},{\"indexed\": true,\"internalType\": \"address\",\"name\": \"withdrawalAddress\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"time\",\"type\": \"uint256\"}],\"name\": \"NodeWithdrawalAddressSet\",\"type\": \"event\"},{\"inputs\": [],\"name\": \"getGuardian\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_newAddress\",\"type\": \"address\"}],\"name\": \"setGuardian\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"confirmGuardian\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"getDeployedStatus\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [],\"name\": \"setDeployedStatus\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_nodeAddress\",\"type\": \"address\"}],\"name\": \"getNodeWithdrawalAddress\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_nodeAddress\",\"type\": \"address\"}],\"name\": \"getNodePendingWithdrawalAddress\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_nodeAddress\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"_newWithdrawalAddress\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"_confirm\",\"type\": \"bool\"}],\"name\": \"setWithdrawalAddress\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_nodeAddress\",\"type\": \"address\"}],\"name\": \"confirmWithdrawalAddress\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getAddress\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"r\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getUint\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"r\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getString\",\"outputs\": [{\"internalType\": \"string\",\"name\": \"\",\"type\": \"string\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getBytes\",\"outputs\": [{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getBool\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"r\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getInt\",\"outputs\": [{\"internalType\": \"int256\",\"name\": \"r\",\"type\": \"int256\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"getBytes32\",\"outputs\": [{\"internalType\": \"bytes32\",\"name\": \"r\",\"type\": \"bytes32\"}],\"stateMutability\": \"view\",\"type\": \"function\",\"constant\": true},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"address\",\"name\": \"_value\",\"type\": \"address\"}],\"name\": \"setAddress\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"uint256\",\"name\": \"_value\",\"type\": \"uint256\"}],\"name\": \"setUint\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"string\",\"name\": \"_value\",\"type\": \"string\"}],\"name\": \"setString\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"bytes\",\"name\": \"_value\",\"type\": \"bytes\"}],\"name\": \"setBytes\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"bool\",\"name\": \"_value\",\"type\": \"bool\"}],\"name\": \"setBool\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"int256\",\"name\": \"_value\",\"type\": \"int256\"}],\"name\": \"setInt\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"bytes32\",\"name\": \"_value\",\"type\": \"bytes32\"}],\"name\": \"setBytes32\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteAddress\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteUint\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteString\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteBytes\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteBool\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteInt\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"}],\"name\": \"deleteBytes32\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"uint256\",\"name\": \"_amount\",\"type\": \"uint256\"}],\"name\": \"addUint\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"_key\",\"type\": \"bytes32\"},{\"internalType\": \"uint256\",\"name\": \"_amount\",\"type\": \"uint256\"}],\"name\": \"subUint\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"}]\r\n"
+
+// RocketStorage is an auto generated Go binding around an Ethereum contract.
+type RocketStorage struct {
+ RocketStorageCaller // Read-only binding to the contract
+ RocketStorageTransactor // Write-only binding to the contract
+ RocketStorageFilterer // Log filterer for contract events
+}
+
+// RocketStorageCaller is an auto generated read-only Go binding around an Ethereum contract.
+type RocketStorageCaller struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// RocketStorageTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type RocketStorageTransactor struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// RocketStorageFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type RocketStorageFilterer struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// RocketStorageSession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type RocketStorageSession struct {
+ Contract *RocketStorage // Generic contract binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// RocketStorageCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type RocketStorageCallerSession struct {
+ Contract *RocketStorageCaller // Generic contract caller binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+}
+
+// RocketStorageTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type RocketStorageTransactorSession struct {
+ Contract *RocketStorageTransactor // Generic contract transactor binding to set the session for
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// RocketStorageRaw is an auto generated low-level Go binding around an Ethereum contract.
+type RocketStorageRaw struct {
+ Contract *RocketStorage // Generic contract binding to access the raw methods on
+}
+
+// RocketStorageCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type RocketStorageCallerRaw struct {
+ Contract *RocketStorageCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// RocketStorageTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type RocketStorageTransactorRaw struct {
+ Contract *RocketStorageTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewRocketStorage creates a new instance of RocketStorage, bound to a specific deployed contract.
+func NewRocketStorage(address common.Address, backend bind.ContractBackend) (*RocketStorage, error) {
+ contract, err := bindRocketStorage(address, backend, backend, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &RocketStorage{RocketStorageCaller: RocketStorageCaller{contract: contract}, RocketStorageTransactor: RocketStorageTransactor{contract: contract}, RocketStorageFilterer: RocketStorageFilterer{contract: contract}}, nil
+}
+
+// NewRocketStorageCaller creates a new read-only instance of RocketStorage, bound to a specific deployed contract.
+func NewRocketStorageCaller(address common.Address, caller bind.ContractCaller) (*RocketStorageCaller, error) {
+ contract, err := bindRocketStorage(address, caller, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &RocketStorageCaller{contract: contract}, nil
+}
+
+// NewRocketStorageTransactor creates a new write-only instance of RocketStorage, bound to a specific deployed contract.
+func NewRocketStorageTransactor(address common.Address, transactor bind.ContractTransactor) (*RocketStorageTransactor, error) {
+ contract, err := bindRocketStorage(address, nil, transactor, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &RocketStorageTransactor{contract: contract}, nil
+}
+
+// NewRocketStorageFilterer creates a new log filterer instance of RocketStorage, bound to a specific deployed contract.
+func NewRocketStorageFilterer(address common.Address, filterer bind.ContractFilterer) (*RocketStorageFilterer, error) {
+ contract, err := bindRocketStorage(address, nil, nil, filterer)
+ if err != nil {
+ return nil, err
+ }
+ return &RocketStorageFilterer{contract: contract}, nil
+}
+
+// bindRocketStorage binds a generic wrapper to an already deployed contract.
+func bindRocketStorage(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+ parsed, err := abi.JSON(strings.NewReader(RocketStorageABI))
+ if err != nil {
+ return nil, err
+ }
+ return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_RocketStorage *RocketStorageRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _RocketStorage.Contract.RocketStorageCaller.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_RocketStorage *RocketStorageRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _RocketStorage.Contract.RocketStorageTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_RocketStorage *RocketStorageRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _RocketStorage.Contract.RocketStorageTransactor.contract.Transact(opts, method, params...)
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_RocketStorage *RocketStorageCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
+ return _RocketStorage.Contract.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_RocketStorage *RocketStorageTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _RocketStorage.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_RocketStorage *RocketStorageTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _RocketStorage.Contract.contract.Transact(opts, method, params...)
+}
+
+// GetAddress is a free data retrieval call binding the contract method 0x21f8a721.
+//
+// Solidity: function getAddress(bytes32 _key) view returns(address)
+func (_RocketStorage *RocketStorageCaller) GetAddress(opts *bind.CallOpts, _key [32]byte) (common.Address, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getAddress", _key)
+
+ if err != nil {
+ return *new(common.Address), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
+
+ return out0, err
+
+}
+
+// GetAddress is a free data retrieval call binding the contract method 0x21f8a721.
+//
+// Solidity: function getAddress(bytes32 _key) view returns(address)
+func (_RocketStorage *RocketStorageSession) GetAddress(_key [32]byte) (common.Address, error) {
+ return _RocketStorage.Contract.GetAddress(&_RocketStorage.CallOpts, _key)
+}
+
+// GetAddress is a free data retrieval call binding the contract method 0x21f8a721.
+//
+// Solidity: function getAddress(bytes32 _key) view returns(address)
+func (_RocketStorage *RocketStorageCallerSession) GetAddress(_key [32]byte) (common.Address, error) {
+ return _RocketStorage.Contract.GetAddress(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBool is a free data retrieval call binding the contract method 0x7ae1cfca.
+//
+// Solidity: function getBool(bytes32 _key) view returns(bool)
+func (_RocketStorage *RocketStorageCaller) GetBool(opts *bind.CallOpts, _key [32]byte) (bool, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getBool", _key)
+
+ if err != nil {
+ return *new(bool), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
+
+ return out0, err
+
+}
+
+// GetBool is a free data retrieval call binding the contract method 0x7ae1cfca.
+//
+// Solidity: function getBool(bytes32 _key) view returns(bool)
+func (_RocketStorage *RocketStorageSession) GetBool(_key [32]byte) (bool, error) {
+ return _RocketStorage.Contract.GetBool(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBool is a free data retrieval call binding the contract method 0x7ae1cfca.
+//
+// Solidity: function getBool(bytes32 _key) view returns(bool)
+func (_RocketStorage *RocketStorageCallerSession) GetBool(_key [32]byte) (bool, error) {
+ return _RocketStorage.Contract.GetBool(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBytes is a free data retrieval call binding the contract method 0xc031a180.
+//
+// Solidity: function getBytes(bytes32 _key) view returns(bytes)
+func (_RocketStorage *RocketStorageCaller) GetBytes(opts *bind.CallOpts, _key [32]byte) ([]byte, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getBytes", _key)
+
+ if err != nil {
+ return *new([]byte), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte)
+
+ return out0, err
+
+}
+
+// GetBytes is a free data retrieval call binding the contract method 0xc031a180.
+//
+// Solidity: function getBytes(bytes32 _key) view returns(bytes)
+func (_RocketStorage *RocketStorageSession) GetBytes(_key [32]byte) ([]byte, error) {
+ return _RocketStorage.Contract.GetBytes(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBytes is a free data retrieval call binding the contract method 0xc031a180.
+//
+// Solidity: function getBytes(bytes32 _key) view returns(bytes)
+func (_RocketStorage *RocketStorageCallerSession) GetBytes(_key [32]byte) ([]byte, error) {
+ return _RocketStorage.Contract.GetBytes(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBytes32 is a free data retrieval call binding the contract method 0xa6ed563e.
+//
+// Solidity: function getBytes32(bytes32 _key) view returns(bytes32)
+func (_RocketStorage *RocketStorageCaller) GetBytes32(opts *bind.CallOpts, _key [32]byte) ([32]byte, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getBytes32", _key)
+
+ if err != nil {
+ return *new([32]byte), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte)
+
+ return out0, err
+
+}
+
+// GetBytes32 is a free data retrieval call binding the contract method 0xa6ed563e.
+//
+// Solidity: function getBytes32(bytes32 _key) view returns(bytes32)
+func (_RocketStorage *RocketStorageSession) GetBytes32(_key [32]byte) ([32]byte, error) {
+ return _RocketStorage.Contract.GetBytes32(&_RocketStorage.CallOpts, _key)
+}
+
+// GetBytes32 is a free data retrieval call binding the contract method 0xa6ed563e.
+//
+// Solidity: function getBytes32(bytes32 _key) view returns(bytes32)
+func (_RocketStorage *RocketStorageCallerSession) GetBytes32(_key [32]byte) ([32]byte, error) {
+ return _RocketStorage.Contract.GetBytes32(&_RocketStorage.CallOpts, _key)
+}
+
+// GetInt is a free data retrieval call binding the contract method 0xdc97d962.
+//
+// Solidity: function getInt(bytes32 _key) view returns(int256)
+func (_RocketStorage *RocketStorageCaller) GetInt(opts *bind.CallOpts, _key [32]byte) (*big.Int, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getInt", _key)
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// GetInt is a free data retrieval call binding the contract method 0xdc97d962.
+//
+// Solidity: function getInt(bytes32 _key) view returns(int256)
+func (_RocketStorage *RocketStorageSession) GetInt(_key [32]byte) (*big.Int, error) {
+ return _RocketStorage.Contract.GetInt(&_RocketStorage.CallOpts, _key)
+}
+
+// GetInt is a free data retrieval call binding the contract method 0xdc97d962.
+//
+// Solidity: function getInt(bytes32 _key) view returns(int256)
+func (_RocketStorage *RocketStorageCallerSession) GetInt(_key [32]byte) (*big.Int, error) {
+ return _RocketStorage.Contract.GetInt(&_RocketStorage.CallOpts, _key)
+}
+
+// GetString is a free data retrieval call binding the contract method 0x986e791a.
+//
+// Solidity: function getString(bytes32 _key) view returns(string)
+func (_RocketStorage *RocketStorageCaller) GetString(opts *bind.CallOpts, _key [32]byte) (string, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getString", _key)
+
+ if err != nil {
+ return *new(string), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(string)).(*string)
+
+ return out0, err
+
+}
+
+// GetString is a free data retrieval call binding the contract method 0x986e791a.
+//
+// Solidity: function getString(bytes32 _key) view returns(string)
+func (_RocketStorage *RocketStorageSession) GetString(_key [32]byte) (string, error) {
+ return _RocketStorage.Contract.GetString(&_RocketStorage.CallOpts, _key)
+}
+
+// GetString is a free data retrieval call binding the contract method 0x986e791a.
+//
+// Solidity: function getString(bytes32 _key) view returns(string)
+func (_RocketStorage *RocketStorageCallerSession) GetString(_key [32]byte) (string, error) {
+ return _RocketStorage.Contract.GetString(&_RocketStorage.CallOpts, _key)
+}
+
+// GetUint is a free data retrieval call binding the contract method 0xbd02d0f5.
+//
+// Solidity: function getUint(bytes32 _key) view returns(uint256)
+func (_RocketStorage *RocketStorageCaller) GetUint(opts *bind.CallOpts, _key [32]byte) (*big.Int, error) {
+ var out []interface{}
+ err := _RocketStorage.contract.Call(opts, &out, "getUint", _key)
+
+ if err != nil {
+ return *new(*big.Int), err
+ }
+
+ out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
+
+ return out0, err
+
+}
+
+// GetUint is a free data retrieval call binding the contract method 0xbd02d0f5.
+//
+// Solidity: function getUint(bytes32 _key) view returns(uint256)
+func (_RocketStorage *RocketStorageSession) GetUint(_key [32]byte) (*big.Int, error) {
+ return _RocketStorage.Contract.GetUint(&_RocketStorage.CallOpts, _key)
+}
+
+// GetUint is a free data retrieval call binding the contract method 0xbd02d0f5.
+//
+// Solidity: function getUint(bytes32 _key) view returns(uint256)
+func (_RocketStorage *RocketStorageCallerSession) GetUint(_key [32]byte) (*big.Int, error) {
+ return _RocketStorage.Contract.GetUint(&_RocketStorage.CallOpts, _key)
+}
+
+// DeleteAddress is a paid mutator transaction binding the contract method 0x0e14a376.
+//
+// Solidity: function deleteAddress(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteAddress(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteAddress", _key)
+}
+
+// DeleteAddress is a paid mutator transaction binding the contract method 0x0e14a376.
+//
+// Solidity: function deleteAddress(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteAddress(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteAddress(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteAddress is a paid mutator transaction binding the contract method 0x0e14a376.
+//
+// Solidity: function deleteAddress(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteAddress(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteAddress(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBool is a paid mutator transaction binding the contract method 0x2c62ff2d.
+//
+// Solidity: function deleteBool(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteBool(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteBool", _key)
+}
+
+// DeleteBool is a paid mutator transaction binding the contract method 0x2c62ff2d.
+//
+// Solidity: function deleteBool(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteBool(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBool(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBool is a paid mutator transaction binding the contract method 0x2c62ff2d.
+//
+// Solidity: function deleteBool(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteBool(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBool(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBytes is a paid mutator transaction binding the contract method 0x616b59f6.
+//
+// Solidity: function deleteBytes(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteBytes(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteBytes", _key)
+}
+
+// DeleteBytes is a paid mutator transaction binding the contract method 0x616b59f6.
+//
+// Solidity: function deleteBytes(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteBytes(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBytes(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBytes is a paid mutator transaction binding the contract method 0x616b59f6.
+//
+// Solidity: function deleteBytes(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteBytes(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBytes(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBytes32 is a paid mutator transaction binding the contract method 0x0b9adc57.
+//
+// Solidity: function deleteBytes32(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteBytes32(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteBytes32", _key)
+}
+
+// DeleteBytes32 is a paid mutator transaction binding the contract method 0x0b9adc57.
+//
+// Solidity: function deleteBytes32(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteBytes32(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBytes32(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteBytes32 is a paid mutator transaction binding the contract method 0x0b9adc57.
+//
+// Solidity: function deleteBytes32(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteBytes32(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteBytes32(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteInt is a paid mutator transaction binding the contract method 0x8c160095.
+//
+// Solidity: function deleteInt(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteInt(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteInt", _key)
+}
+
+// DeleteInt is a paid mutator transaction binding the contract method 0x8c160095.
+//
+// Solidity: function deleteInt(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteInt(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteInt(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteInt is a paid mutator transaction binding the contract method 0x8c160095.
+//
+// Solidity: function deleteInt(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteInt(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteInt(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteString is a paid mutator transaction binding the contract method 0xf6bb3cc4.
+//
+// Solidity: function deleteString(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteString(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteString", _key)
+}
+
+// DeleteString is a paid mutator transaction binding the contract method 0xf6bb3cc4.
+//
+// Solidity: function deleteString(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteString(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteString(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteString is a paid mutator transaction binding the contract method 0xf6bb3cc4.
+//
+// Solidity: function deleteString(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteString(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteString(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteUint is a paid mutator transaction binding the contract method 0xe2b202bf.
+//
+// Solidity: function deleteUint(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactor) DeleteUint(opts *bind.TransactOpts, _key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "deleteUint", _key)
+}
+
+// DeleteUint is a paid mutator transaction binding the contract method 0xe2b202bf.
+//
+// Solidity: function deleteUint(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageSession) DeleteUint(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteUint(&_RocketStorage.TransactOpts, _key)
+}
+
+// DeleteUint is a paid mutator transaction binding the contract method 0xe2b202bf.
+//
+// Solidity: function deleteUint(bytes32 _key) returns()
+func (_RocketStorage *RocketStorageTransactorSession) DeleteUint(_key [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.DeleteUint(&_RocketStorage.TransactOpts, _key)
+}
+
+// SetAddress is a paid mutator transaction binding the contract method 0xca446dd9.
+//
+// Solidity: function setAddress(bytes32 _key, address _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetAddress(opts *bind.TransactOpts, _key [32]byte, _value common.Address) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setAddress", _key, _value)
+}
+
+// SetAddress is a paid mutator transaction binding the contract method 0xca446dd9.
+//
+// Solidity: function setAddress(bytes32 _key, address _value) returns()
+func (_RocketStorage *RocketStorageSession) SetAddress(_key [32]byte, _value common.Address) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetAddress(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetAddress is a paid mutator transaction binding the contract method 0xca446dd9.
+//
+// Solidity: function setAddress(bytes32 _key, address _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetAddress(_key [32]byte, _value common.Address) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetAddress(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBool is a paid mutator transaction binding the contract method 0xabfdcced.
+//
+// Solidity: function setBool(bytes32 _key, bool _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetBool(opts *bind.TransactOpts, _key [32]byte, _value bool) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setBool", _key, _value)
+}
+
+// SetBool is a paid mutator transaction binding the contract method 0xabfdcced.
+//
+// Solidity: function setBool(bytes32 _key, bool _value) returns()
+func (_RocketStorage *RocketStorageSession) SetBool(_key [32]byte, _value bool) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBool(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBool is a paid mutator transaction binding the contract method 0xabfdcced.
+//
+// Solidity: function setBool(bytes32 _key, bool _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetBool(_key [32]byte, _value bool) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBool(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBytes is a paid mutator transaction binding the contract method 0x2e28d084.
+//
+// Solidity: function setBytes(bytes32 _key, bytes _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetBytes(opts *bind.TransactOpts, _key [32]byte, _value []byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setBytes", _key, _value)
+}
+
+// SetBytes is a paid mutator transaction binding the contract method 0x2e28d084.
+//
+// Solidity: function setBytes(bytes32 _key, bytes _value) returns()
+func (_RocketStorage *RocketStorageSession) SetBytes(_key [32]byte, _value []byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBytes(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBytes is a paid mutator transaction binding the contract method 0x2e28d084.
+//
+// Solidity: function setBytes(bytes32 _key, bytes _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetBytes(_key [32]byte, _value []byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBytes(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBytes32 is a paid mutator transaction binding the contract method 0x4e91db08.
+//
+// Solidity: function setBytes32(bytes32 _key, bytes32 _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetBytes32(opts *bind.TransactOpts, _key [32]byte, _value [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setBytes32", _key, _value)
+}
+
+// SetBytes32 is a paid mutator transaction binding the contract method 0x4e91db08.
+//
+// Solidity: function setBytes32(bytes32 _key, bytes32 _value) returns()
+func (_RocketStorage *RocketStorageSession) SetBytes32(_key [32]byte, _value [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBytes32(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetBytes32 is a paid mutator transaction binding the contract method 0x4e91db08.
+//
+// Solidity: function setBytes32(bytes32 _key, bytes32 _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetBytes32(_key [32]byte, _value [32]byte) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetBytes32(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetInt is a paid mutator transaction binding the contract method 0x3e49bed0.
+//
+// Solidity: function setInt(bytes32 _key, int256 _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetInt(opts *bind.TransactOpts, _key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setInt", _key, _value)
+}
+
+// SetInt is a paid mutator transaction binding the contract method 0x3e49bed0.
+//
+// Solidity: function setInt(bytes32 _key, int256 _value) returns()
+func (_RocketStorage *RocketStorageSession) SetInt(_key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetInt(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetInt is a paid mutator transaction binding the contract method 0x3e49bed0.
+//
+// Solidity: function setInt(bytes32 _key, int256 _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetInt(_key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetInt(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetString is a paid mutator transaction binding the contract method 0x6e899550.
+//
+// Solidity: function setString(bytes32 _key, string _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetString(opts *bind.TransactOpts, _key [32]byte, _value string) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setString", _key, _value)
+}
+
+// SetString is a paid mutator transaction binding the contract method 0x6e899550.
+//
+// Solidity: function setString(bytes32 _key, string _value) returns()
+func (_RocketStorage *RocketStorageSession) SetString(_key [32]byte, _value string) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetString(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetString is a paid mutator transaction binding the contract method 0x6e899550.
+//
+// Solidity: function setString(bytes32 _key, string _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetString(_key [32]byte, _value string) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetString(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetUint is a paid mutator transaction binding the contract method 0xe2a4853a.
+//
+// Solidity: function setUint(bytes32 _key, uint256 _value) returns()
+func (_RocketStorage *RocketStorageTransactor) SetUint(opts *bind.TransactOpts, _key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.contract.Transact(opts, "setUint", _key, _value)
+}
+
+// SetUint is a paid mutator transaction binding the contract method 0xe2a4853a.
+//
+// Solidity: function setUint(bytes32 _key, uint256 _value) returns()
+func (_RocketStorage *RocketStorageSession) SetUint(_key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetUint(&_RocketStorage.TransactOpts, _key, _value)
+}
+
+// SetUint is a paid mutator transaction binding the contract method 0xe2a4853a.
+//
+// Solidity: function setUint(bytes32 _key, uint256 _value) returns()
+func (_RocketStorage *RocketStorageTransactorSession) SetUint(_key [32]byte, _value *big.Int) (*types.Transaction, error) {
+ return _RocketStorage.Contract.SetUint(&_RocketStorage.TransactOpts, _key, _value)
+}
diff --git a/bindings/dao/claim.go b/bindings/dao/claim.go
new file mode 100644
index 000000000..0fd704e97
--- /dev/null
+++ b/bindings/dao/claim.go
@@ -0,0 +1,30 @@
+package dao
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+func GetContractExists(rp *rocketpool.RocketPool, contractName string, opts *bind.CallOpts) (bool, error) {
+ rocketClaimDAO, err := getRocketClaimDAO(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ result := new(bool)
+ if err := rocketClaimDAO.Call(opts, result, "getContractExists", contractName); err != nil {
+ return false, fmt.Errorf("error checking if contract %s exists: %w", contractName, err)
+ }
+ return *result, nil
+}
+
+// Get contracts
+var rocketClaimDAOLock sync.Mutex
+
+func getRocketClaimDAO(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketClaimDAOLock.Lock()
+ defer rocketClaimDAOLock.Unlock()
+ return rp.GetContract("rocketClaimDAO", opts)
+}
diff --git a/bindings/dao/proposal-payload.go b/bindings/dao/proposal-payload.go
new file mode 100644
index 000000000..aeb201e73
--- /dev/null
+++ b/bindings/dao/proposal-payload.go
@@ -0,0 +1,64 @@
+package dao
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ strutils "github.com/rocket-pool/rocketpool-go/utils/strings"
+)
+
+// Get the string representation of a proposal payload
+var getProposalPayloadStringLock sync.Mutex
+
+func GetProposalPayloadString(rp *rocketpool.RocketPool, daoName string, payload []byte, opts *bind.CallOpts) (string, error) {
+
+ // Lock while getting proposal payload string
+ getProposalPayloadStringLock.Lock()
+ defer getProposalPayloadStringLock.Unlock()
+
+ // Get proposal DAO contract ABI
+ daoContractAbi, err := rp.GetABI(daoName, opts)
+ if err != nil {
+ return "", fmt.Errorf("error getting '%s' DAO contract ABI: %w", daoName, err)
+ }
+
+ // Get proposal payload method
+ method, err := daoContractAbi.MethodById(payload)
+ if err != nil {
+ return "", fmt.Errorf("error getting proposal payload method: %w", err)
+ }
+
+ // Get proposal payload argument values
+ args, err := method.Inputs.UnpackValues(payload[4:])
+ if err != nil {
+ return "", fmt.Errorf("error getting proposal payload arguments: %w", err)
+ }
+
+ // Format argument values as strings
+ argStrs := []string{}
+ for ai, arg := range args {
+ switch method.Inputs[ai].Type.T {
+ case abi.AddressTy:
+ argStrs = append(argStrs, arg.(common.Address).Hex())
+ case abi.HashTy:
+ argStrs = append(argStrs, arg.(common.Hash).Hex())
+ case abi.FixedBytesTy:
+ fallthrough
+ case abi.BytesTy:
+ argStrs = append(argStrs, hex.EncodeToString(arg.([]byte)))
+ default:
+ argStrs = append(argStrs, fmt.Sprintf("%v", arg))
+ }
+ }
+
+ // Build & return payload string
+ return strutils.Sanitize(fmt.Sprintf("%s(%s)", method.RawName, strings.Join(argStrs, ","))), nil
+
+}
diff --git a/bindings/dao/proposals.go b/bindings/dao/proposals.go
new file mode 100644
index 000000000..3245beb09
--- /dev/null
+++ b/bindings/dao/proposals.go
@@ -0,0 +1,647 @@
+package dao
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+ "github.com/rocket-pool/rocketpool-go/utils/strings"
+)
+
+// Settings
+const (
+ ProposalDAONamesBatchSize = 50
+ ProposalDetailsBatchSize = 10
+)
+
+// Proposal details
+type ProposalDetails struct {
+ ID uint64 `json:"id"`
+ DAO string `json:"dao"`
+ ProposerAddress common.Address `json:"proposerAddress"`
+ Message string `json:"message"`
+ CreatedTime uint64 `json:"createdTime"`
+ StartTime uint64 `json:"startTime"`
+ EndTime uint64 `json:"endTime"`
+ ExpiryTime uint64 `json:"expiryTime"`
+ VotesRequired float64 `json:"votesRequired"`
+ VotesFor float64 `json:"votesFor"`
+ VotesAgainst float64 `json:"votesAgainst"`
+ MemberVoted bool `json:"memberVoted"`
+ MemberSupported bool `json:"memberSupported"`
+ IsCancelled bool `json:"isCancelled"`
+ IsExecuted bool `json:"isExecuted"`
+ Payload []byte `json:"payload"`
+ PayloadStr string `json:"payloadStr"`
+ State rptypes.ProposalState `json:"state"`
+}
+
+// Get all proposal details
+func GetProposals(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]ProposalDetails, error) {
+
+ // Get proposal count
+ proposalCount, err := GetProposalCount(rp, opts)
+ if err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ // Load proposal details in batches
+ details := make([]ProposalDetails, proposalCount)
+ for bsi := uint64(0); bsi < proposalCount; bsi += ProposalDetailsBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDetailsBatchSize
+ if pei > proposalCount {
+ pei = proposalCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDetails, err := GetProposalDetails(rp, pi+1, opts) // Proposals are 1-indexed
+ if err == nil {
+ details[pi] = proposalDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all proposal details with member data
+func GetProposalsWithMember(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) ([]ProposalDetails, error) {
+
+ // Get proposal count
+ proposalCount, err := GetProposalCount(rp, opts)
+ if err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ // Load proposal details in batches
+ details := make([]ProposalDetails, proposalCount)
+ for bsi := uint64(0); bsi < proposalCount; bsi += ProposalDetailsBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDetailsBatchSize
+ if pei > proposalCount {
+ pei = proposalCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDetails, err := GetProposalDetailsWithMember(rp, pi+1, memberAddress, opts) // Proposals are 1-indexed
+ if err == nil {
+ details[pi] = proposalDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get DAO proposal details
+func GetDAOProposals(rp *rocketpool.RocketPool, daoName string, opts *bind.CallOpts) ([]ProposalDetails, error) {
+
+ // Get DAO proposal IDs
+ proposalIds, err := GetDAOProposalIDs(rp, daoName, opts)
+ if err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ // Load proposal details in batches
+ details := make([]ProposalDetails, len(proposalIds))
+ for bsi := 0; bsi < len(proposalIds); bsi += ProposalDetailsBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDetailsBatchSize
+ if pei > len(proposalIds) {
+ pei = len(proposalIds)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDetails, err := GetProposalDetails(rp, proposalIds[pi], opts)
+ if err == nil {
+ details[pi] = proposalDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get DAO proposal details with member data
+func GetDAOProposalsWithMember(rp *rocketpool.RocketPool, daoName string, memberAddress common.Address, opts *bind.CallOpts) ([]ProposalDetails, error) {
+
+ // Get DAO proposal IDs
+ proposalIds, err := GetDAOProposalIDs(rp, daoName, opts)
+ if err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ // Load proposal details in batches
+ details := make([]ProposalDetails, len(proposalIds))
+ for bsi := 0; bsi < len(proposalIds); bsi += ProposalDetailsBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDetailsBatchSize
+ if pei > len(proposalIds) {
+ pei = len(proposalIds)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDetails, err := GetProposalDetailsWithMember(rp, proposalIds[pi], memberAddress, opts)
+ if err == nil {
+ details[pi] = proposalDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []ProposalDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get the IDs of proposals filtered by a DAO
+func GetDAOProposalIDs(rp *rocketpool.RocketPool, daoName string, opts *bind.CallOpts) ([]uint64, error) {
+
+ // Get proposal count
+ proposalCount, err := GetProposalCount(rp, opts)
+ if err != nil {
+ return []uint64{}, err
+ }
+
+ // Load proposal DAO names in batches
+ proposalDaoNames := make([]string, proposalCount)
+ for bsi := uint64(0); bsi < proposalCount; bsi += ProposalDAONamesBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDAONamesBatchSize
+ if pei > proposalCount {
+ pei = proposalCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDaoName, err := GetProposalDAO(rp, pi+1, opts) // Proposals are 1-indexed
+ if err == nil {
+ proposalDaoNames[pi] = proposalDaoName
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []uint64{}, err
+ }
+
+ }
+
+ // Get & return IDs for DAO proposals
+ ids := []uint64{}
+ for pi, proposalDaoName := range proposalDaoNames {
+ if proposalDaoName == daoName {
+ ids = append(ids, uint64(pi+1)) // Proposals are 1-indexed
+ }
+ }
+ return ids, nil
+
+}
+
+// Get a proposal's details
+func GetProposalDetails(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (ProposalDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var dao string
+ var proposerAddress common.Address
+ var message string
+ var createdTime uint64
+ var startTime uint64
+ var endTime uint64
+ var expiryTime uint64
+ var votesRequired float64
+ var votesFor float64
+ var votesAgainst float64
+ var isCancelled bool
+ var isExecuted bool
+ var payload []byte
+ var state rptypes.ProposalState
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ dao, err = GetProposalDAO(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ proposerAddress, err = GetProposalProposerAddress(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ message, err = GetProposalMessage(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ createdTime, err = GetProposalCreatedTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ startTime, err = GetProposalStartTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ endTime, err = GetProposalEndTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ expiryTime, err = GetProposalExpiryTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ votesRequired, err = GetProposalVotesRequired(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ votesFor, err = GetProposalVotesFor(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ votesAgainst, err = GetProposalVotesAgainst(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ isCancelled, err = GetProposalIsCancelled(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ isExecuted, err = GetProposalIsExecuted(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ payload, err = GetProposalPayload(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ state, err = GetProposalState(rp, proposalId, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return ProposalDetails{}, err
+ }
+
+ // Get proposal payload string
+ payloadStr, err := GetProposalPayloadString(rp, dao, payload, opts)
+ if err != nil {
+ payloadStr = "(unknown)"
+ }
+
+ // Return
+ return ProposalDetails{
+ ID: proposalId,
+ DAO: dao,
+ ProposerAddress: proposerAddress,
+ Message: message,
+ CreatedTime: createdTime,
+ StartTime: startTime,
+ EndTime: endTime,
+ ExpiryTime: expiryTime,
+ VotesRequired: votesRequired,
+ VotesFor: votesFor,
+ VotesAgainst: votesAgainst,
+ IsCancelled: isCancelled,
+ IsExecuted: isExecuted,
+ Payload: payload,
+ PayloadStr: payloadStr,
+ State: state,
+ }, nil
+
+}
+
+// Get a proposal's details with member data
+func GetProposalDetailsWithMember(rp *rocketpool.RocketPool, proposalId uint64, memberAddress common.Address, opts *bind.CallOpts) (ProposalDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var details ProposalDetails
+ var memberVoted bool
+ var memberSupported bool
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ details, err = GetProposalDetails(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ memberVoted, err = GetProposalMemberVoted(rp, proposalId, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ memberSupported, err = GetProposalMemberSupported(rp, proposalId, memberAddress, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return ProposalDetails{}, err
+ }
+
+ // Return
+ details.MemberVoted = memberVoted
+ details.MemberSupported = memberSupported
+ return details, nil
+
+}
+
+// Get the proposal count
+func GetProposalCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ proposalCount := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, proposalCount, "getTotal"); err != nil {
+ return 0, fmt.Errorf("error getting proposal count: %w", err)
+ }
+ return (*proposalCount).Uint64(), nil
+}
+
+// Proposal details
+func GetProposalDAO(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (string, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ daoName := new(string)
+ if err := rocketDAOProposal.Call(opts, daoName, "getDAO", big.NewInt(int64(proposalId))); err != nil {
+ return "", fmt.Errorf("error getting proposal %d DAO: %w", proposalId, err)
+ }
+ return strings.Sanitize(*daoName), nil
+}
+func GetProposalProposerAddress(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ proposerAddress := new(common.Address)
+ if err := rocketDAOProposal.Call(opts, proposerAddress, "getProposer", big.NewInt(int64(proposalId))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting proposal %d proposer address: %w", proposalId, err)
+ }
+ return *proposerAddress, nil
+}
+func GetProposalMessage(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (string, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ message := new(string)
+ if err := rocketDAOProposal.Call(opts, message, "getMessage", big.NewInt(int64(proposalId))); err != nil {
+ return "", fmt.Errorf("error getting proposal %d message: %w", proposalId, err)
+ }
+ return strings.Sanitize(*message), nil
+}
+func GetProposalCreatedTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ createdTime := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, createdTime, "getCreated", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d created time: %w", proposalId, err)
+ }
+ return (*createdTime).Uint64(), nil
+}
+func GetProposalStartTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ startTime := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, startTime, "getStart", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d start time: %w", proposalId, err)
+ }
+ return (*startTime).Uint64(), nil
+}
+func GetProposalEndTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ endTime := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, endTime, "getEnd", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d end time: %w", proposalId, err)
+ }
+ return (*endTime).Uint64(), nil
+}
+func GetProposalExpiryTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ expiryTime := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, expiryTime, "getExpires", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d expiry time: %w", proposalId, err)
+ }
+ return (*expiryTime).Uint64(), nil
+}
+func GetProposalVotesRequired(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (float64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ votesRequired := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, votesRequired, "getVotesRequired", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d votes required: %w", proposalId, err)
+ }
+ return eth.WeiToEth(*votesRequired), nil
+}
+func GetProposalVotesFor(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (float64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ votesFor := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, votesFor, "getVotesFor", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d votes for: %w", proposalId, err)
+ }
+ return eth.WeiToEth(*votesFor), nil
+}
+func GetProposalVotesAgainst(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (float64, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ votesAgainst := new(*big.Int)
+ if err := rocketDAOProposal.Call(opts, votesAgainst, "getVotesAgainst", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d votes against: %w", proposalId, err)
+ }
+ return eth.WeiToEth(*votesAgainst), nil
+}
+func GetProposalIsCancelled(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ cancelled := new(bool)
+ if err := rocketDAOProposal.Call(opts, cancelled, "getCancelled", big.NewInt(int64(proposalId))); err != nil {
+ return false, fmt.Errorf("error getting proposal %d cancelled status: %w", proposalId, err)
+ }
+ return *cancelled, nil
+}
+func GetProposalIsExecuted(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ executed := new(bool)
+ if err := rocketDAOProposal.Call(opts, executed, "getExecuted", big.NewInt(int64(proposalId))); err != nil {
+ return false, fmt.Errorf("error getting proposal %d executed status: %w", proposalId, err)
+ }
+ return *executed, nil
+}
+func GetProposalPayload(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) ([]byte, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return []byte{}, err
+ }
+ payload := new([]byte)
+ if err := rocketDAOProposal.Call(opts, payload, "getPayload", big.NewInt(int64(proposalId))); err != nil {
+ return []byte{}, fmt.Errorf("error getting proposal %d payload: %w", proposalId, err)
+ }
+ return *payload, nil
+}
+func GetProposalPayloadStr(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (string, error) {
+ dao, err := GetProposalDAO(rp, proposalId, opts)
+ if err != nil {
+ return "", err
+ }
+ payload, err := GetProposalPayload(rp, proposalId, opts)
+ if err != nil {
+ return "", err
+ }
+ payloadStr, err := GetProposalPayloadString(rp, dao, payload, opts)
+ if err != nil {
+ payloadStr = "(unknown)"
+ }
+ return payloadStr, nil
+}
+func GetProposalState(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (rptypes.ProposalState, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ state := new(uint8)
+ if err := rocketDAOProposal.Call(opts, state, "getState", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d state: %w", proposalId, err)
+ }
+ return rptypes.ProposalState(*state), nil
+}
+
+// Get whether a member has voted on a proposal
+func GetProposalMemberVoted(rp *rocketpool.RocketPool, proposalId uint64, memberAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ voted := new(bool)
+ if err := rocketDAOProposal.Call(opts, voted, "getReceiptHasVoted", big.NewInt(int64(proposalId)), memberAddress); err != nil {
+ return false, fmt.Errorf("error getting proposal %d member %s voted status: %w", proposalId, memberAddress.Hex(), err)
+ }
+ return *voted, nil
+}
+
+// Get whether a member has voted in support of a proposal
+func GetProposalMemberSupported(rp *rocketpool.RocketPool, proposalId uint64, memberAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProposal, err := getRocketDAOProposal(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ supported := new(bool)
+ if err := rocketDAOProposal.Call(opts, supported, "getReceiptSupported", big.NewInt(int64(proposalId)), memberAddress); err != nil {
+ return false, fmt.Errorf("error getting proposal %d member %s supported status: %w", proposalId, memberAddress.Hex(), err)
+ }
+ return *supported, nil
+}
+
+// Get contracts
+var rocketDAOProposalLock sync.Mutex
+
+func getRocketDAOProposal(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOProposalLock.Lock()
+ defer rocketDAOProposalLock.Unlock()
+ return rp.GetContract("rocketDAOProposal", opts)
+}
diff --git a/bindings/dao/protocol/dao.go b/bindings/dao/protocol/dao.go
new file mode 100644
index 000000000..541bccb66
--- /dev/null
+++ b/bindings/dao/protocol/dao.go
@@ -0,0 +1,18 @@
+package protocol
+
+import (
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get contracts
+var rocketDAOProtocolLock sync.Mutex
+
+func getRocketDAOProtocol(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOProtocolLock.Lock()
+ defer rocketDAOProtocolLock.Unlock()
+ return rp.GetContract("rocketDAOProtocol", opts)
+}
diff --git a/bindings/dao/protocol/proposal.go b/bindings/dao/protocol/proposal.go
new file mode 100644
index 000000000..bc2cffaa3
--- /dev/null
+++ b/bindings/dao/protocol/proposal.go
@@ -0,0 +1,727 @@
+package protocol
+
+import (
+ "context"
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ strutils "github.com/rocket-pool/rocketpool-go/utils/strings"
+ "golang.org/x/sync/errgroup"
+)
+
+// Settings
+const (
+ ProposalDAONamesBatchSize = 50
+ ProposalDetailsBatchSize = 10
+)
+
+// =====================
+// === Proposal Info ===
+// =====================
+
+// Proposal details
+type ProtocolDaoProposalDetails struct {
+ ID uint64 `json:"id"`
+ DAO string `json:"dao"`
+ ProposerAddress common.Address `json:"proposerAddress"`
+ TargetBlock uint32 `json:"targetBlock"`
+ Message string `json:"message"`
+ CreatedTime time.Time `json:"createdTime"`
+ ChallengeWindow time.Duration `json:"challengeWindow"`
+ VotingStartTime time.Time `json:"startTime"`
+ Phase1EndTime time.Time `json:"phase1EndTime"`
+ Phase2EndTime time.Time `json:"phase2EndTime"`
+ ExpiryTime time.Time `json:"expiryTime"`
+ VotingPowerRequired *big.Int `json:"votingPowerRequired"`
+ VotingPowerFor *big.Int `json:"votingPowerFor"`
+ VotingPowerAgainst *big.Int `json:"votingPowerAgainst"`
+ VotingPowerAbstained *big.Int `json:"votingPowerAbstained"`
+ VotingPowerToVeto *big.Int `json:"votingPowerVeto"`
+ IsDestroyed bool `json:"isDestroyed"`
+ IsFinalized bool `json:"isFinalized"`
+ IsExecuted bool `json:"isExecuted"`
+ IsVetoed bool `json:"isVetoed"`
+ VetoQuorum *big.Int `json:"vetoQuorum"`
+ Payload []byte `json:"payload"`
+ PayloadStr string `json:"payloadStr"`
+ State types.ProtocolDaoProposalState `json:"state"`
+ ProposalBond *big.Int `json:"proposalBond"`
+ ChallengeBond *big.Int `json:"challengeBond"`
+ DefeatIndex uint64 `json:"defeatIndex"`
+}
+
+// Get all proposal details
+func GetProposals(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]ProtocolDaoProposalDetails, error) {
+ // Get proposal count
+ proposalCount, err := GetTotalProposalCount(rp, opts)
+ if err != nil {
+ return []ProtocolDaoProposalDetails{}, err
+ }
+
+ // Load proposal details in batches
+ details := make([]ProtocolDaoProposalDetails, proposalCount)
+ for bsi := uint64(0); bsi < proposalCount; bsi += ProposalDetailsBatchSize {
+
+ // Get batch start & end index
+ psi := bsi
+ pei := bsi + ProposalDetailsBatchSize
+ if pei > proposalCount {
+ pei = proposalCount
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for pi := psi; pi < pei; pi++ {
+ pi := pi
+ wg.Go(func() error {
+ proposalDetails, err := GetProposalDetails(rp, pi+1, opts) // Proposals are 1-indexed
+ if err == nil {
+ details[pi] = proposalDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []ProtocolDaoProposalDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+}
+
+// Get a proposal's details
+func GetProposalDetails(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (ProtocolDaoProposalDetails, error) {
+ var wg errgroup.Group
+ var prop ProtocolDaoProposalDetails
+ prop.ID = proposalId
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ prop.ProposerAddress, err = GetProposalProposer(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.TargetBlock, err = GetProposalBlock(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.Message, err = GetProposalMessage(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingStartTime, err = GetProposalVotingStartTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.Phase1EndTime, err = GetProposalPhase1EndTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.Phase2EndTime, err = GetProposalPhase2EndTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.ExpiryTime, err = GetProposalExpiryTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.CreatedTime, err = GetProposalCreationTime(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingPowerRequired, err = GetProposalVotingPowerRequired(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingPowerFor, err = GetProposalVotingPowerFor(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingPowerAgainst, err = GetProposalVotingPowerAgainst(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingPowerAbstained, err = GetProposalVotingPowerAbstained(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VotingPowerToVeto, err = GetProposalVotingPowerVetoed(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.IsDestroyed, err = GetProposalIsDestroyed(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.IsFinalized, err = GetProposalIsFinalized(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.IsExecuted, err = GetProposalIsExecuted(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.IsVetoed, err = GetProposalIsVetoed(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.VetoQuorum, err = GetProposalVetoQuorum(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.Payload, err = GetProposalPayload(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.State, err = GetProposalState(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.DefeatIndex, err = GetDefeatIndex(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.ProposalBond, err = GetProposalBond(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.ChallengeBond, err = GetChallengeBond(rp, proposalId, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ prop.ChallengeWindow, err = GetChallengePeriod(rp, proposalId, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return ProtocolDaoProposalDetails{}, err
+ }
+
+ // Get proposal payload string
+ payloadStr, err := GetProposalPayloadString(rp, prop.Payload, opts)
+ if err != nil {
+ payloadStr = "(unknown)"
+ }
+ prop.PayloadStr = payloadStr
+ return prop, nil
+}
+
+// Get the block that was used for voting power calculation in a proposal
+func GetProposalBlock(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint32, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getProposalBlock", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return 0, fmt.Errorf("error getting proposal block for proposal %d: %w", proposalId, err)
+ }
+ return uint32((*value).Uint64()), nil
+}
+
+// Get the veto quorum required to veto a proposal
+func GetProposalVetoQuorum(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getProposalVetoQuorum", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting proposal veto quorum for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// The total number of Protocol DAO proposals
+func GetTotalProposalCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getTotal"); err != nil {
+ return 0, fmt.Errorf("error getting total proposal count: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+
+// Get the address of the proposer
+func GetProposalProposer(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return common.Address{}, err
+ }
+ value := new(common.Address)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getProposer", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return common.Address{}, fmt.Errorf("error getting proposer for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the proposal's message
+func GetProposalMessage(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (string, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return "", err
+ }
+ value := new(string)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getMessage", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return "", fmt.Errorf("error getting message for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the start time of this proposal, when voting begins
+func GetProposalVotingStartTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Time, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getStart", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return time.Time{}, fmt.Errorf("error getting start block for proposal %d: %w", proposalId, err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+// Get the phase 1 end time of this proposal
+func GetProposalPhase1EndTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Time, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getPhase1End", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return time.Time{}, fmt.Errorf("error getting phase 1 end time for proposal %d: %w", proposalId, err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+// Get the phase 2 end time of this proposal
+func GetProposalPhase2EndTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Time, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getPhase2End", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return time.Time{}, fmt.Errorf("error getting phase 2 end time for proposal %d: %w", proposalId, err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+// Get the time where the proposal expires and can no longer be executed if it is successful
+func GetProposalExpiryTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Time, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getExpires", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return time.Time{}, fmt.Errorf("error getting expiry time for proposal %d: %w", proposalId, err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+// Get the time the proposal was created
+func GetProposalCreationTime(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Time, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getCreated", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return time.Time{}, fmt.Errorf("error getting creation time for proposal %d: %w", proposalId, err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+// Get the cumulative amount of voting power voting in favor of this proposal
+func GetProposalVotingPowerFor(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVotingPowerFor", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting total 'for' voting power for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the cumulative amount of voting power voting against this proposal
+func GetProposalVotingPowerAgainst(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVotingPowerAgainst", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting total 'against' voting power for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the cumulative amount of voting power that vetoed this proposal
+func GetProposalVotingPowerVetoed(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVotingPowerVeto", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting total 'veto' voting power for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the cumulative amount of voting power that abstained from this proposal
+func GetProposalVotingPowerAbstained(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVotingPowerAbstained", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting total 'abstained' voting power for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the cumulative amount of voting power that must vote on this proposal for it to be eligible for execution if it succeeds
+func GetProposalVotingPowerRequired(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVotingPowerRequired", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting required voting power for proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get whether or not the proposal has been destroyed
+func GetProposalIsDestroyed(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getDestroyed", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return false, fmt.Errorf("error getting destroyed status of proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get whether or not the proposal has been finalized
+func GetProposalIsFinalized(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getFinalised", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return false, fmt.Errorf("error getting finalized status of proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get whether or not the proposal has been executed
+func GetProposalIsExecuted(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getExecuted", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return false, fmt.Errorf("error getting executed status of proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get whether or not the proposal's veto quorum has been met and it has been vetoed
+func GetProposalIsVetoed(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (bool, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getVetoed", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return false, fmt.Errorf("error getting veto status of proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the proposal's payload
+func GetProposalPayload(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) ([]byte, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new([]byte)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getPayload", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return nil, fmt.Errorf("error getting payload of proposal %d: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get a proposal's payload as a human-readable string
+func GetProposalPayloadString(rp *rocketpool.RocketPool, payload []byte, opts *bind.CallOpts) (string, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return "", err
+ }
+
+ // Get proposal DAO contract ABI
+ daoContractAbi := rocketDAOProtocolProposals.ABI
+
+ // Get proposal payload method
+ method, err := daoContractAbi.MethodById(payload)
+ if err != nil {
+ return "", fmt.Errorf("error getting proposal payload method: %w", err)
+ }
+
+ // Get proposal payload argument values
+ args, err := method.Inputs.UnpackValues(payload[4:])
+ if err != nil {
+ return "", fmt.Errorf("error getting proposal payload arguments: %w", err)
+ }
+
+ // Format argument values as strings
+ argStrs := []string{}
+ for ai, arg := range args {
+ switch method.Inputs[ai].Type.T {
+ case abi.AddressTy:
+ argStrs = append(argStrs, arg.(common.Address).Hex())
+ case abi.HashTy:
+ argStrs = append(argStrs, arg.(common.Hash).Hex())
+ case abi.FixedBytesTy:
+ fallthrough
+ case abi.BytesTy:
+ argStrs = append(argStrs, hex.EncodeToString(arg.([]byte)))
+ default:
+ argStrs = append(argStrs, fmt.Sprintf("%v", arg))
+ }
+ }
+
+ // Build & return payload string
+ return strutils.Sanitize(fmt.Sprintf("%s(%s)", method.RawName, strings.Join(argStrs, ","))), nil
+}
+
+// Get the proposal's state
+func GetProposalState(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (types.ProtocolDaoProposalState, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return types.ProtocolDaoProposalState_Pending, err
+ }
+ value := new(uint8)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getState", big.NewInt(0).SetUint64(proposalId)); err != nil {
+ return types.ProtocolDaoProposalState_Pending, fmt.Errorf("error getting state of proposal %d: %w", proposalId, err)
+ }
+ return types.ProtocolDaoProposalState(*value), nil
+}
+
+// Get the option that the address voted on for the proposal, and whether or not it's voted yet
+func GetAddressVoteDirection(rp *rocketpool.RocketPool, proposalId uint64, address common.Address, opts *bind.CallOpts) (types.VoteDirection, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return types.VoteDirection_NoVote, err
+ }
+ value := new(uint8)
+ if err := rocketDAOProtocolProposal.Call(opts, value, "getReceiptDirection", big.NewInt(0).SetUint64(proposalId), address); err != nil {
+ return types.VoteDirection_NoVote, fmt.Errorf("error getting voting status of proposal %d by address %s: %w", proposalId, address.Hex(), err)
+ }
+ return types.VoteDirection(*value), nil
+}
+
+// ====================
+// === Transactions ===
+// ====================
+
+// Estimate the gas of a proposal submission
+func estimateProposalGas(rp *rocketpool.RocketPool, message string, payload []byte, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ err = simulateProposalExecution(rp, payload)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error simulating proposal execution: %w", err)
+ }
+ return rocketDAOProtocolProposal.GetTransactionGasInfo(opts, "propose", message, payload, blockNumber, treeNodes)
+}
+
+// Submit a trusted node DAO proposal
+// Returns the ID of the new proposal
+func submitProposal(rp *rocketpool.RocketPool, message string, payload []byte, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ proposalCount, err := dao.GetProposalCount(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolProposal.Transact(opts, "propose", message, payload, blockNumber, treeNodes)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error submitting Protocol DAO proposal: %w", err)
+ }
+ return proposalCount + 1, tx.Hash(), nil
+}
+
+// Estimate the gas of VoteOnProposal
+func EstimateVoteOnProposalGas(rp *rocketpool.RocketPool, proposalId uint64, voteDirection types.VoteDirection, votingPower *big.Int, nodeIndex uint64, witness []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolProposal.GetTransactionGasInfo(opts, "vote", big.NewInt(int64(proposalId)), voteDirection, votingPower, big.NewInt(int64(nodeIndex)), witness)
+}
+
+// Vote on a submitted proposal
+func VoteOnProposal(rp *rocketpool.RocketPool, proposalId uint64, voteDirection types.VoteDirection, votingPower *big.Int, nodeIndex uint64, witness []types.VotingTreeNode, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolProposal.Transact(opts, "vote", big.NewInt(int64(proposalId)), voteDirection, votingPower, big.NewInt(int64(nodeIndex)), witness)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting on Protocol DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of OverrideVote
+func EstimateOverrideVoteGas(rp *rocketpool.RocketPool, proposalId uint64, voteDirection types.VoteDirection, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolProposal.GetTransactionGasInfo(opts, "overrideVote", big.NewInt(int64(proposalId)), voteDirection)
+}
+
+// Override a delegate's vote during pDAO voting phase 2
+func OverrideVote(rp *rocketpool.RocketPool, proposalId uint64, voteDirection types.VoteDirection, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolProposal.Transact(opts, "overrideVote", big.NewInt(int64(proposalId)), voteDirection)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error overriding vote on Protocol DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Finalize
+func EstimateFinalizeGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolProposal.GetTransactionGasInfo(opts, "finalise", big.NewInt(int64(proposalId)))
+}
+
+// Finalizes a vetoed proposal by burning the proposer's bond
+func Finalize(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolProposal.Transact(opts, "finalise", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error finalizing Protocol DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ExecuteProposal
+func EstimateExecuteProposalGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolProposal.GetTransactionGasInfo(opts, "execute", big.NewInt(int64(proposalId)))
+}
+
+// Execute a submitted proposal
+func ExecuteProposal(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolProposal.Transact(opts, "execute", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error executing Protocol DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Simulate a proposal's execution to verify it won't revert
+func simulateProposalExecution(rp *rocketpool.RocketPool, payload []byte) error {
+ rocketDAOProtocolProposal, err := getRocketDAOProtocolProposal(rp, nil)
+ if err != nil {
+ return err
+ }
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return err
+ }
+
+ _, err = rp.Client.EstimateGas(context.Background(), ethereum.CallMsg{
+ From: *rocketDAOProtocolProposal.Address,
+ To: rocketDAOProtocolProposals.Address,
+ GasPrice: big.NewInt(0),
+ Value: nil,
+ Data: payload,
+ })
+ return err
+}
+
+// Get contracts
+var rocketDAOProtocolProposalLock sync.Mutex
+
+func getRocketDAOProtocolProposal(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOProtocolProposalLock.Lock()
+ defer rocketDAOProtocolProposalLock.Unlock()
+ return rp.GetContract("rocketDAOProtocolProposal", opts)
+}
diff --git a/bindings/dao/protocol/proposals.go b/bindings/dao/protocol/proposals.go
new file mode 100644
index 000000000..38b564c3c
--- /dev/null
+++ b/bindings/dao/protocol/proposals.go
@@ -0,0 +1,393 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Estimate the gas of ProposeSetMulti
+func EstimateProposeSetMultiGas(rp *rocketpool.RocketPool, message string, contractNames []string, settingPaths []string, settingTypes []types.ProposalSettingType, values []any, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ encodedValues, err := abiEncodeMultiValues(settingTypes, values)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error ABI encoding values: %w", err)
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingMulti", contractNames, settingPaths, settingTypes, encodedValues)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error setting multi-set proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update multiple Protocol DAO settings at once
+func ProposeSetMulti(rp *rocketpool.RocketPool, message string, contractNames []string, settingPaths []string, settingTypes []types.ProposalSettingType, values []any, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ encodedValues, err := abiEncodeMultiValues(settingTypes, values)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error ABI encoding values: %w", err)
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingMulti", contractNames, settingPaths, settingTypes, encodedValues)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error setting multi-set proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeSetBool
+func EstimateProposeSetBoolGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error setting bool setting proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update a bool Protocol DAO setting
+func ProposeSetBool(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error setting bool setting proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeSetUint
+func EstimateProposeSetUintGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update a uint Protocol DAO setting
+func ProposeSetUint(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeSetAddress
+func EstimateProposeSetAddressGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingAddress", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set address setting proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update an address Protocol DAO setting
+func ProposeSetAddress(rp *rocketpool.RocketPool, message, contractName, settingPath string, value common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingAddress", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set address setting proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeSetRewardsPercentage
+func EstimateProposeSetRewardsPercentageGas(rp *rocketpool.RocketPool, message string, odaoPercentage *big.Int, pdaoPercentage *big.Int, nodePercentage *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingRewardsClaimers", odaoPercentage, pdaoPercentage, nodePercentage)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set rewards-claimers percent proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update the allocations of RPL rewards
+func ProposeSetRewardsPercentage(rp *rocketpool.RocketPool, message string, odaoPercentage *big.Int, pdaoPercentage *big.Int, nodePercentage *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSettingRewardsClaimers", odaoPercentage, pdaoPercentage, nodePercentage)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set rewards-claimers percent proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeOneTimeTreasurySpend
+func EstimateProposeOneTimeTreasurySpendGas(rp *rocketpool.RocketPool, message, invoiceID string, recipient common.Address, amount *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryOneTimeSpend", invoiceID, recipient, amount)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set spend-treasury percent proposal payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to spend a portion of the Rocket Pool treasury one time
+func ProposeOneTimeTreasurySpend(rp *rocketpool.RocketPool, message, invoiceID string, recipient common.Address, amount *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryOneTimeSpend", invoiceID, recipient, amount)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set spend-treasury percent proposal payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeRecurringTreasurySpend
+func EstimateProposeRecurringTreasurySpendGas(rp *rocketpool.RocketPool, message string, contractName string, recipient common.Address, amountPerPeriod *big.Int, periodLength time.Duration, startTime time.Time, numberOfPeriods uint64, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryNewContract", contractName, recipient, amountPerPeriod, big.NewInt(int64(periodLength.Seconds())), big.NewInt(startTime.Unix()), big.NewInt(int64(numberOfPeriods)))
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalTreasuryNewContract payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to spend a portion of the Rocket Pool treasury in a recurring manner
+func ProposeRecurringTreasurySpend(rp *rocketpool.RocketPool, message string, contractName string, recipient common.Address, amountPerPeriod *big.Int, periodLength time.Duration, startTime time.Time, numberOfPeriods uint64, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryNewContract", contractName, recipient, amountPerPeriod, big.NewInt(int64(periodLength.Seconds())), big.NewInt(startTime.Unix()), big.NewInt(int64(numberOfPeriods)))
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalTreasuryNewContract payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeRecurringTreasurySpendUpdate
+func EstimateProposeRecurringTreasurySpendUpdateGas(rp *rocketpool.RocketPool, message string, contractName string, recipient common.Address, amountPerPeriod *big.Int, periodLength time.Duration, numberOfPeriods uint64, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryUpdateContract", contractName, recipient, amountPerPeriod, big.NewInt(int64(periodLength.Seconds())), big.NewInt(int64(numberOfPeriods)))
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalTreasuryUpdateContract payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to update a recurrint Rocket Pool treasury spending plan
+func ProposeRecurringTreasurySpendUpdate(rp *rocketpool.RocketPool, message string, contractName string, recipient common.Address, amountPerPeriod *big.Int, periodLength time.Duration, numberOfPeriods uint64, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalTreasuryUpdateContract", contractName, recipient, amountPerPeriod, big.NewInt(int64(periodLength.Seconds())), big.NewInt(int64(numberOfPeriods)))
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalTreasuryUpdateContract payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeInviteToSecurityCouncil
+func EstimateProposeInviteToSecurityCouncilGas(rp *rocketpool.RocketPool, message string, id string, address common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityInvite", id, address)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalSecurityInvite payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to invite a member to the security council
+func ProposeInviteToSecurityCouncil(rp *rocketpool.RocketPool, message string, id string, address common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityInvite", id, address)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalSecurityInvite payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeKickFromSecurityCouncil
+func EstimateProposeKickFromSecurityCouncilGas(rp *rocketpool.RocketPool, message string, address common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityKick", address)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalSecurityKick payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to kick a member from the security council
+func ProposeKickFromSecurityCouncil(rp *rocketpool.RocketPool, message string, address common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityKick", address)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalSecurityKick payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeKickMultiFromSecurityCouncil
+func EstimateProposeKickMultiFromSecurityCouncilGas(rp *rocketpool.RocketPool, message string, addresses []common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityKickMulti", addresses)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalSecurityKickMulti payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to kick multiple members from the security council
+func ProposeKickMultiFromSecurityCouncil(rp *rocketpool.RocketPool, message string, addresses []common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityKickMulti", addresses)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalSecurityKickMulti payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Estimate the gas of ProposeReplaceSecurityCouncilMember
+func EstimateProposeReplaceSecurityCouncilMemberGas(rp *rocketpool.RocketPool, message string, existingMemberAddress common.Address, newMemberID string, newMemberAddress common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityReplace", existingMemberAddress, newMemberID, newMemberAddress)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding proposalSecurityReplace payload: %w", err)
+ }
+ return estimateProposalGas(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Submit a proposal to replace a member of the security council with another one in a single TX
+func ProposeReplaceSecurityCouncilMember(rp *rocketpool.RocketPool, message string, existingMemberAddress common.Address, newMemberID string, newMemberAddress common.Address, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOProtocolProposals, err := getRocketDAOProtocolProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOProtocolProposals.ABI.Pack("proposalSecurityReplace", existingMemberAddress, newMemberID, newMemberAddress)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding proposalSecurityReplace payload: %w", err)
+ }
+ return submitProposal(rp, message, payload, blockNumber, treeNodes, opts)
+}
+
+// Get the ABI encoding of multiple values for a ProposeSettingMulti call
+func abiEncodeMultiValues(settingTypes []types.ProposalSettingType, values []any) ([][]byte, error) {
+ // Sanity check the lengths
+ settingCount := len(settingTypes)
+ if settingCount != len(values) {
+ return nil, fmt.Errorf("settingTypes and values must be the same length")
+ }
+ if settingCount == 0 {
+ return [][]byte{}, nil
+ }
+
+ // ABI encode each value
+ results := make([][]byte, settingCount)
+ for i, settingType := range settingTypes {
+ var encodedArg []byte
+ switch settingType {
+ case types.ProposalSettingType_Uint256:
+ arg, success := values[i].(*big.Int)
+ if !success {
+ return nil, fmt.Errorf("value %d is not a *big.Int, but the setting type is Uint256", i)
+ }
+ encodedArg = math.U256Bytes(big.NewInt(0).Set(arg))
+
+ case types.ProposalSettingType_Bool:
+ arg, success := values[i].(bool)
+ if !success {
+ return nil, fmt.Errorf("value %d is not a bool, but the setting type is Bool", i)
+ }
+ if arg {
+ encodedArg = math.PaddedBigBytes(common.Big1, 32)
+ } else {
+ encodedArg = math.PaddedBigBytes(common.Big0, 32)
+ }
+
+ case types.ProposalSettingType_Address:
+ arg, success := values[i].(common.Address)
+ if !success {
+ return nil, fmt.Errorf("value %d is not an address, but the setting type is Address", i)
+ }
+ encodedArg = common.LeftPadBytes(arg.Bytes(), 32)
+
+ default:
+ return nil, fmt.Errorf("unknown proposal setting type [%v]", settingType)
+ }
+ results[i] = encodedArg
+ }
+
+ return results, nil
+}
+
+// Get contracts
+var rocketDAOProtocolProposalsLock sync.Mutex
+
+func getRocketDAOProtocolProposals(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOProtocolProposalsLock.Lock()
+ defer rocketDAOProtocolProposalsLock.Unlock()
+ return rp.GetContract("rocketDAOProtocolProposals", opts)
+}
diff --git a/bindings/dao/protocol/verify.go b/bindings/dao/protocol/verify.go
new file mode 100755
index 000000000..a00962e75
--- /dev/null
+++ b/bindings/dao/protocol/verify.go
@@ -0,0 +1,535 @@
+package protocol
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ challengeStateBatchSize uint64 = 500
+)
+
+// Structure of the RootSubmitted event
+type RootSubmitted struct {
+ ProposalID *big.Int `json:"proposalId"`
+ Proposer common.Address `json:"proposer"`
+ BlockNumber uint32 `json:"blockNumber"`
+ Index *big.Int `json:"index"`
+ Root types.VotingTreeNode `json:"root"`
+ TreeNodes []types.VotingTreeNode `json:"treeNodes"`
+ Timestamp time.Time `json:"timestamp"`
+}
+
+// Internal struct - returned by the RootSubmitted event
+type rootSubmittedRaw struct {
+ BlockNumber uint32 `json:"blockNumber"`
+ Index *big.Int `json:"index"`
+ Root types.VotingTreeNode `json:"root"`
+ TreeNodes []types.VotingTreeNode `json:"treeNodes"`
+ Timestamp *big.Int `json:"timestamp"`
+}
+
+// Structure of the ChallengeSubmitted event
+type ChallengeSubmitted struct {
+ ProposalID *big.Int `json:"proposalId"`
+ Challenger common.Address `json:"challenger"`
+ Index *big.Int `json:"index"`
+ Timestamp time.Time `json:"timestamp"`
+}
+
+// Internal struct - returned by the ChallengeSubmitted event
+type challengeSubmittedRaw struct {
+ Index *big.Int `json:"index"`
+ Timestamp *big.Int `json:"timestamp"`
+}
+
+// Get the depth-per-round for voting trees
+func GetDepthPerRound(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolVerifier.Call(opts, value, "getDepthPerRound"); err != nil {
+ return 0, fmt.Errorf("error getting depth per round: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+
+// Get the node of a proposal at the given index
+func GetNode(rp *rocketpool.RocketPool, proposalId uint64, index uint64, opts *bind.CallOpts) (types.VotingTreeNode, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return types.VotingTreeNode{}, err
+ }
+ // define a struct to unmarshall the VotingTreeNode data from the smart contract call
+ res := new(struct {
+ Sum *big.Int `json:"sum"`
+ Hash [32]byte `json:"hash"`
+ })
+ err = rocketDAOProtocolVerifier.Call(opts, &res, "getNode", big.NewInt(int64(proposalId)), big.NewInt(int64(index)))
+ if err != nil {
+ return types.VotingTreeNode{}, fmt.Errorf("error getting proposal %d / index %d node: %w", proposalId, index, err)
+ }
+ // convert the [32]byte field into a common.Hash
+ node := types.VotingTreeNode{
+ Sum: res.Sum,
+ Hash: common.BytesToHash(res.Hash[:]),
+ }
+
+ return node, nil
+}
+
+// Estimate the gas of CreateChallenge
+func EstimateCreateChallengeGas(rp *rocketpool.RocketPool, proposalId uint64, index uint64, node types.VotingTreeNode, witness []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolVerifier.GetTransactionGasInfo(opts, "createChallenge", big.NewInt(int64(proposalId)), big.NewInt((int64(index))), node, witness)
+}
+
+// Challenge a proposal at a specific tree node index, providing a Merkle proof of the node as well
+func CreateChallenge(rp *rocketpool.RocketPool, proposalId uint64, index uint64, node types.VotingTreeNode, witness []types.VotingTreeNode, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolVerifier.Transact(opts, "createChallenge", big.NewInt(int64(proposalId)), big.NewInt((int64(index))), node, witness)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error creating challenge: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of SubmitRoot
+func EstimateSubmitRootGas(rp *rocketpool.RocketPool, proposalId uint64, index uint64, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolVerifier.GetTransactionGasInfo(opts, "submitRoot", big.NewInt(int64(proposalId)), big.NewInt((int64(index))), treeNodes)
+}
+
+// Submit the Merkle root for a proposal at the specific index in response to a challenge
+func SubmitRoot(rp *rocketpool.RocketPool, proposalId uint64, index uint64, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolVerifier.Transact(opts, "submitRoot", big.NewInt(int64(proposalId)), big.NewInt((int64(index))), treeNodes)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting proposal root: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the state of a challenge on a proposal and tree node index
+func GetChallengeState(rp *rocketpool.RocketPool, proposalId uint64, index uint64, opts *bind.CallOpts) (types.ChallengeState, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return types.ChallengeState_Unchallenged, err
+ }
+ state := new(uint8)
+ if err := rocketDAOProtocolVerifier.Call(opts, state, "getChallengeState", big.NewInt(int64(proposalId)), big.NewInt(int64(index))); err != nil {
+ return types.ChallengeState_Unchallenged, fmt.Errorf("error getting proposal %d / index %d challenge state: %w", proposalId, index, err)
+ }
+ challengeState := types.ChallengeState(*state)
+ return challengeState, nil
+}
+
+// Get the defeat index for a proposal
+func GetDefeatIndex(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolVerifier.Call(opts, value, "getDefeatIndex", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d defeat index: %w", proposalId, err)
+ }
+ return (*value).Uint64(), nil
+}
+
+// Get the proposal bond for a proposal
+func GetProposalBond(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolVerifier.Call(opts, value, "getProposalBond", big.NewInt(int64(proposalId))); err != nil {
+ return nil, fmt.Errorf("error getting proposal %d proposal bond: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the challenge bond for a proposal
+func GetChallengeBond(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolVerifier.Call(opts, value, "getChallengeBond", big.NewInt(int64(proposalId))); err != nil {
+ return nil, fmt.Errorf("error getting proposal %d challenge bond: %w", proposalId, err)
+ }
+ return *value, nil
+}
+
+// Get the challenge period for a proposal
+func GetChallengePeriod(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.CallOpts) (time.Duration, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOProtocolVerifier.Call(opts, value, "getChallengePeriod", big.NewInt(int64(proposalId))); err != nil {
+ return 0, fmt.Errorf("error getting proposal %d challenge period: %w", proposalId, err)
+ }
+ return time.Second * time.Duration((*value).Uint64()), nil
+}
+
+// Get the states of multiple challenges using multicall
+// NOTE: wen v2...
+func GetMultiChallengeStatesFast(rp *rocketpool.RocketPool, multicallAddress common.Address, proposalIds []uint64, challengedIndices []uint64, opts *bind.CallOpts) ([]types.ChallengeState, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ if opts == nil {
+ // Get the latest block
+ blockNum, err := rp.Client.BlockNumber(context.Background())
+ if err != nil {
+ return nil, fmt.Errorf("error getting latest block number: %w", err)
+ }
+ opts = &bind.CallOpts{
+ BlockNumber: big.NewInt(int64(blockNum)),
+ }
+ }
+
+ count := uint64(len(proposalIds))
+ if count != uint64(len(challengedIndices)) {
+ return nil, fmt.Errorf("have %d proposal IDs but %d challenge indices", count, len(challengedIndices))
+ }
+
+ // Sync
+ var wg errgroup.Group
+
+ // Run the getters in batches
+ rawStates := make([]uint8, count)
+ for i := uint64(0); i < count; i += challengeStateBatchSize {
+ i := i
+ max := i + challengeStateBatchSize
+ if max > count {
+ max = count
+ }
+
+ // Load details
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, multicallAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ propID := big.NewInt(int64(proposalIds[j]))
+ challengedIndex := big.NewInt(int64(challengedIndices[j]))
+ mc.AddCall(rocketDAOProtocolVerifier, &rawStates[j], "getChallengeState", propID, challengedIndex)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Cast the results
+ states := make([]types.ChallengeState, count)
+ for i, state := range rawStates {
+ states[i] = types.ChallengeState(state)
+ }
+ return states, nil
+}
+
+// Get RootSubmitted event info
+func GetRootSubmittedEvents(rp *rocketpool.RocketPool, proposalIDs []uint64, intervalSize *big.Int, startBlock *big.Int, endBlock *big.Int, verifierAddresses []common.Address, opts *bind.CallOpts) ([]RootSubmitted, error) {
+ // Get the contract
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Construct a filter query for relevant logs
+ idBuffers := make([]common.Hash, len(proposalIDs))
+ for i, id := range proposalIDs {
+ proposalIdBig := big.NewInt(0).SetUint64(id)
+ proposalIdBig.FillBytes(idBuffers[i][:])
+ }
+
+ // Create the list of addresses to check
+ currentAddress := *rocketDAOProtocolVerifier.Address
+ if verifierAddresses == nil {
+ verifierAddresses = []common.Address{currentAddress}
+ } else {
+ found := false
+ for _, address := range verifierAddresses {
+ if address == currentAddress {
+ found = true
+ break
+ }
+ }
+ if !found {
+ verifierAddresses = append(verifierAddresses, currentAddress)
+ }
+ }
+
+ rootSubmittedEvent := rocketDAOProtocolVerifier.ABI.Events["RootSubmitted"]
+ addressFilter := verifierAddresses
+ topicFilter := [][]common.Hash{{rootSubmittedEvent.ID}, idBuffers}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, endBlock, nil)
+ if err != nil {
+ return nil, err
+ }
+ if len(logs) == 0 {
+ return []RootSubmitted{}, nil
+ }
+
+ events := make([]RootSubmitted, 0, len(logs))
+ for _, log := range logs {
+ // Get the log info values
+ values, err := rootSubmittedEvent.Inputs.Unpack(log.Data)
+ if err != nil {
+ return nil, fmt.Errorf("error unpacking RootSubmitted event data: %w", err)
+ }
+
+ // Get the topic values
+ if len(log.Topics) < 3 {
+ return nil, fmt.Errorf("event had %d topics but at least 3 are required", len(log.Topics))
+ }
+ idHash := log.Topics[1]
+ proposerHash := log.Topics[2]
+ propID := big.NewInt(0).SetBytes(idHash.Bytes())
+ proposer := common.BytesToAddress(proposerHash.Bytes())
+
+ // Convert to a native struct
+ var raw rootSubmittedRaw
+ err = rootSubmittedEvent.Inputs.Copy(&raw, values)
+ if err != nil {
+ return nil, fmt.Errorf("error converting RootSubmitted event data to struct: %w", err)
+ }
+
+ // Get the decoded data
+ events = append(events, RootSubmitted{
+ ProposalID: propID,
+ Proposer: proposer,
+ BlockNumber: raw.BlockNumber,
+ Index: raw.Index,
+ Root: raw.Root,
+ TreeNodes: raw.TreeNodes,
+ Timestamp: time.Unix(raw.Timestamp.Int64(), 0),
+ })
+ }
+
+ return events, nil
+}
+
+// Get ChallengeSubmitted event info
+func GetChallengeSubmittedEvents(rp *rocketpool.RocketPool, proposalIDs []uint64, intervalSize *big.Int, startBlock *big.Int, endBlock *big.Int, verifierAddresses []common.Address, opts *bind.CallOpts) ([]ChallengeSubmitted, error) {
+ // Get the contract
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Construct a filter query for relevant logs
+ idBuffers := make([]common.Hash, len(proposalIDs))
+ for i, id := range proposalIDs {
+ proposalIdBig := big.NewInt(0).SetUint64(id)
+ proposalIdBig.FillBytes(idBuffers[i][:])
+ }
+
+ // Create the list of addresses to check
+ currentAddress := *rocketDAOProtocolVerifier.Address
+ if verifierAddresses == nil {
+ verifierAddresses = []common.Address{currentAddress}
+ } else {
+ found := false
+ for _, address := range verifierAddresses {
+ if address == currentAddress {
+ found = true
+ break
+ }
+ }
+ if !found {
+ verifierAddresses = append(verifierAddresses, currentAddress)
+ }
+ }
+
+ challengeSubmittedEvent := rocketDAOProtocolVerifier.ABI.Events["ChallengeSubmitted"]
+ addressFilter := verifierAddresses
+ topicFilter := [][]common.Hash{{challengeSubmittedEvent.ID}, idBuffers}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, endBlock, nil)
+ if err != nil {
+ return nil, err
+ }
+ if len(logs) == 0 {
+ return []ChallengeSubmitted{}, nil
+ }
+
+ events := make([]ChallengeSubmitted, 0, len(logs))
+ for _, log := range logs {
+ // Get the log info values
+ values, err := challengeSubmittedEvent.Inputs.Unpack(log.Data)
+ if err != nil {
+ return nil, fmt.Errorf("error unpacking ChallengeSubmitted event data: %w", err)
+ }
+
+ // Get the topic values
+ if len(log.Topics) < 3 {
+ return nil, fmt.Errorf("event had %d topics but at least 3 are required", len(log.Topics))
+ }
+ idHash := log.Topics[1]
+ challengerHash := log.Topics[2]
+ propID := big.NewInt(0).SetBytes(idHash.Bytes())
+ challenger := common.BytesToAddress(challengerHash.Bytes())
+
+ // Convert to a native struct
+ var raw challengeSubmittedRaw
+ err = challengeSubmittedEvent.Inputs.Copy(&raw, values)
+ if err != nil {
+ return nil, fmt.Errorf("error converting ChallengeSubmitted event data to struct: %w", err)
+ }
+
+ // Get the decoded data
+ events = append(events, ChallengeSubmitted{
+ ProposalID: propID,
+ Challenger: challenger,
+ Index: raw.Index,
+ Timestamp: time.Unix(raw.Timestamp.Int64(), 0),
+ })
+ }
+
+ return events, nil
+}
+
+// Estimate the gas of ClaimBondChallenger
+func EstimateClaimBondChallengerGas(rp *rocketpool.RocketPool, proposalID uint64, indices []uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ // Make the args
+ proposalIDBig := big.NewInt(int64(proposalID))
+ indicesBig := make([]*big.Int, len(indices))
+ for i, index := range indices {
+ indicesBig[i] = big.NewInt(int64(index))
+ }
+ return rocketDAOProtocolVerifier.GetTransactionGasInfo(opts, "claimBondChallenger", proposalIDBig, indicesBig)
+}
+
+// Claim any RPL bond refunds or rewards for a proposal, as a challenger
+func ClaimBondChallenger(rp *rocketpool.RocketPool, proposalID uint64, indices []uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ // Make the args
+ proposalIDBig := big.NewInt(int64(proposalID))
+ indicesBig := make([]*big.Int, len(indices))
+ for i, index := range indices {
+ indicesBig[i] = big.NewInt(int64(index))
+ }
+ tx, err := rocketDAOProtocolVerifier.Transact(opts, "claimBondChallenger", proposalIDBig, indicesBig)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ClaimBondProposer
+func EstimateClaimBondProposerGas(rp *rocketpool.RocketPool, proposalID uint64, indices []uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ // Make the args
+ proposalIDBig := big.NewInt(int64(proposalID))
+ indicesBig := make([]*big.Int, len(indices))
+ for i, index := range indices {
+ indicesBig[i] = big.NewInt(int64(index))
+ }
+ return rocketDAOProtocolVerifier.GetTransactionGasInfo(opts, "claimBondProposer", proposalIDBig, indicesBig)
+}
+
+// Claim any RPL bond refunds or rewards for a proposal, as the proposer
+func ClaimBondProposer(rp *rocketpool.RocketPool, proposalID uint64, indices []uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ // Make the args
+ proposalIDBig := big.NewInt(int64(proposalID))
+ indicesBig := make([]*big.Int, len(indices))
+ for i, index := range indices {
+ indicesBig[i] = big.NewInt(int64(index))
+ }
+ tx, err := rocketDAOProtocolVerifier.Transact(opts, "claimBondProposer", proposalIDBig, indicesBig)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DefeatProposal
+func EstimateDefeatProposalGas(rp *rocketpool.RocketPool, proposalId uint64, index uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOProtocolVerifier.GetTransactionGasInfo(opts, "defeatProposal", big.NewInt(int64(proposalId)), big.NewInt(int64(index)))
+}
+
+// Defeat a proposal if it fails to respond to a challenge within the challenge window, providing the node index that wasn't responded to
+func DefeatProposal(rp *rocketpool.RocketPool, proposalId uint64, index uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOProtocolVerifier, err := getRocketDAOProtocolVerifier(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOProtocolVerifier.Transact(opts, "defeatProposal", big.NewInt(int64(proposalId)), big.NewInt(int64(index)))
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDAOProtocolVerifierLock sync.Mutex
+
+func getRocketDAOProtocolVerifier(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOProtocolVerifierLock.Lock()
+ defer rocketDAOProtocolVerifierLock.Unlock()
+ return rp.GetContract("rocketDAOProtocolVerifier", opts)
+}
diff --git a/bindings/dao/security/actions.go b/bindings/dao/security/actions.go
new file mode 100644
index 000000000..e49f86b74
--- /dev/null
+++ b/bindings/dao/security/actions.go
@@ -0,0 +1,130 @@
+package security
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas of Join
+func EstimateJoinGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityActions.GetTransactionGasInfo(opts, "actionJoin")
+}
+
+// Join the security DAO
+// Requires an executed invite proposal
+func Join(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityActions.Transact(opts, "actionJoin")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error joining the security DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Kick
+func EstimateKickGas(rp *rocketpool.RocketPool, address common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityActions.GetTransactionGasInfo(opts, "actionKick", address)
+}
+
+// Removes a member from the security DAO
+func Kick(rp *rocketpool.RocketPool, address common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityActions.Transact(opts, "actionKick", address)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error kicking %s from the security DAO: %w", address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of KickMulti
+func EstimateKickMultiGas(rp *rocketpool.RocketPool, addresses []common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityActions.GetTransactionGasInfo(opts, "actionKickMulti", addresses)
+}
+
+// Removes multiple members from the security DAO
+func KickMulti(rp *rocketpool.RocketPool, addresses []common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityActions.Transact(opts, "actionKickMulti", addresses)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error kicking members from the security DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of RequestLeave
+func EstimateRequestLeaveGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityActions.GetTransactionGasInfo(opts, "actionRequestLeave")
+}
+
+// A member who wishes to leave the security council can call this method to initiate the process
+func RequestLeave(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityActions.Transact(opts, "actionRequestLeave")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error requesting to leave the security DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Leave
+func EstimateLeaveGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityActions.GetTransactionGasInfo(opts, "actionLeave")
+}
+
+// A member who has asked to leave and waited the required time can call this method to formally leave the security council
+func Leave(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityActions, err := getRocketDAOSecurityActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityActions.Transact(opts, "actionLeave")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error leaving the security DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDAOSecurityActionsLock sync.Mutex
+
+func getRocketDAOSecurityActions(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOSecurityActionsLock.Lock()
+ defer rocketDAOSecurityActionsLock.Unlock()
+ return rp.GetContract("rocketDAOSecurityActions", opts)
+}
diff --git a/bindings/dao/security/proposals.go b/bindings/dao/security/proposals.go
new file mode 100644
index 000000000..a9087eee9
--- /dev/null
+++ b/bindings/dao/security/proposals.go
@@ -0,0 +1,166 @@
+package security
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas of ProposeSetUint
+func EstimateProposeSetUintGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOSecurityProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to update a uint trusted node DAO setting
+func ProposeSetUint(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOSecurityProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeSetBool
+func EstimateProposeSetBoolGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAOSecurityProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set bool setting proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to update a bool trusted node DAO setting
+func ProposeSetBool(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAOSecurityProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set bool setting proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of a proposal submission
+func EstimateProposalGas(rp *rocketpool.RocketPool, message string, payload []byte, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityProposals.GetTransactionGasInfo(opts, "propose", message, payload)
+}
+
+// Submit a security DAO proposal
+// Returns the ID of the new proposal
+func SubmitProposal(rp *rocketpool.RocketPool, message string, payload []byte, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ proposalCount, err := dao.GetProposalCount(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityProposals.Transact(opts, "propose", message, payload)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error submitting security DAO proposal: %w", err)
+ }
+ return proposalCount + 1, tx.Hash(), nil
+}
+
+// Estimate the gas of VoteOnProposal
+func EstimateVoteOnProposalGas(rp *rocketpool.RocketPool, proposalId uint64, support bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityProposals.GetTransactionGasInfo(opts, "vote", big.NewInt(int64(proposalId)), support)
+}
+
+// Vote on a submitted proposal
+func VoteOnProposal(rp *rocketpool.RocketPool, proposalId uint64, support bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityProposals.Transact(opts, "vote", big.NewInt(int64(proposalId)), support)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting on security DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of CancelProposal
+func EstimateCancelProposalGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityProposals.GetTransactionGasInfo(opts, "cancel", big.NewInt(int64(proposalId)))
+}
+
+// Cancel a submitted proposal
+func CancelProposal(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityProposals.Transact(opts, "cancel", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error cancelling security DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ExecuteProposal
+func EstimateExecuteProposalGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAOSecurityProposals.GetTransactionGasInfo(opts, "execute", big.NewInt(int64(proposalId)))
+}
+
+// Execute a submitted proposal
+func ExecuteProposal(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAOSecurityProposals, err := getRocketDAOSecurityProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAOSecurityProposals.Transact(opts, "execute", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error executing security DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDAOSecurityProposalsLock sync.Mutex
+
+func getRocketDAOSecurityProposals(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOSecurityProposalsLock.Lock()
+ defer rocketDAOSecurityProposalsLock.Unlock()
+ return rp.GetContract("rocketDAOSecurityProposals", opts)
+}
diff --git a/bindings/dao/security/security.go b/bindings/dao/security/security.go
new file mode 100644
index 000000000..056fdf479
--- /dev/null
+++ b/bindings/dao/security/security.go
@@ -0,0 +1,249 @@
+package security
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/strings"
+ "golang.org/x/sync/errgroup"
+)
+
+// Settings
+const (
+ MemberAddressBatchSize = 50
+ MemberDetailsBatchSize = 20
+)
+
+// Member details
+type SecurityDAOMemberDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ ID string `json:"id"`
+ JoinedTime uint64 `json:"joinedTime"`
+}
+
+// Get all member details
+func GetMembers(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]SecurityDAOMemberDetails, error) {
+ // Get member addresses
+ memberAddresses, err := GetMemberAddresses(rp, opts)
+ if err != nil {
+ return []SecurityDAOMemberDetails{}, err
+ }
+
+ // Load member details in batches
+ details := make([]SecurityDAOMemberDetails, len(memberAddresses))
+ for bsi := 0; bsi < len(memberAddresses); bsi += MemberDetailsBatchSize {
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MemberDetailsBatchSize
+ if mei > len(memberAddresses) {
+ mei = len(memberAddresses)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ memberAddress := memberAddresses[mi]
+ memberDetails, err := GetMemberDetails(rp, memberAddress, opts)
+ if err == nil {
+ details[mi] = memberDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []SecurityDAOMemberDetails{}, err
+ }
+ }
+
+ // Return
+ return details, nil
+}
+
+// Get all member addresses
+func GetMemberAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get member count
+ memberCount, err := GetMemberCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load member addresses in batches
+ addresses := make([]common.Address, memberCount)
+ for bsi := uint64(0); bsi < memberCount; bsi += MemberAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MemberAddressBatchSize
+ if mei > memberCount {
+ mei = memberCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetMemberAt(rp, mi, opts)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+}
+
+// Get a member's details
+func GetMemberDetails(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (SecurityDAOMemberDetails, error) {
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var id string
+ var joinedTime uint64
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetMemberExists(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ id, err = GetMemberID(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ joinedTime, err = GetMemberJoinedTime(rp, memberAddress, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return SecurityDAOMemberDetails{}, err
+ }
+
+ // Return
+ return SecurityDAOMemberDetails{
+ Address: memberAddress,
+ Exists: exists,
+ ID: id,
+ JoinedTime: joinedTime,
+ }, nil
+}
+
+// Get the amount of member votes need for a proposal to pass (as a fraction of 1e18)
+func GetMemberQuorumVotesRequired(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketDAOSecurity.Call(opts, value, "getMemberQuorumVotesRequired"); err != nil {
+ return nil, fmt.Errorf("error getting security DAO quorum votes required: %w", err)
+ }
+ return *value, nil
+}
+
+// Get the member count
+func GetMemberCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ memberCount := new(*big.Int)
+ if err := rocketDAOSecurity.Call(opts, memberCount, "getMemberCount"); err != nil {
+ return 0, fmt.Errorf("error getting security DAO member count: %w", err)
+ }
+ return (*memberCount).Uint64(), nil
+}
+
+// Get a member address by index
+func GetMemberAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ memberAddress := new(common.Address)
+ if err := rocketDAOSecurity.Call(opts, memberAddress, "getMemberAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting security DAO member %d address: %w", index, err)
+ }
+ return *memberAddress, nil
+}
+
+// Member details
+func GetMemberExists(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ exists := new(bool)
+ if err := rocketDAOSecurity.Call(opts, exists, "getMemberIsValid", memberAddress); err != nil {
+ return false, fmt.Errorf("error getting security DAO member %s exists status: %w", memberAddress.Hex(), err)
+ }
+ return *exists, nil
+}
+func GetMemberID(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (string, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ id := new(string)
+ if err := rocketDAOSecurity.Call(opts, id, "getMemberID", memberAddress); err != nil {
+ return "", fmt.Errorf("error getting security DAO member %s ID: %w", memberAddress.Hex(), err)
+ }
+ return strings.Sanitize(*id), nil
+}
+func GetMemberJoinedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ joinedTime := new(*big.Int)
+ if err := rocketDAOSecurity.Call(opts, joinedTime, "getMemberJoinedTime", memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting security DAO member %s joined time: %w", memberAddress.Hex(), err)
+ }
+ return (*joinedTime).Uint64(), nil
+}
+
+// Get the time that a proposal for a member was executed at
+func GetMemberInviteProposalExecutedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ return GetMemberProposalExecutedTime(rp, "invited", memberAddress, opts)
+}
+func GetMemberLeaveProposalExecutedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ return GetMemberProposalExecutedTime(rp, "leave", memberAddress, opts)
+}
+func GetMemberProposalExecutedTime(rp *rocketpool.RocketPool, proposalType string, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAOSecurity, err := getRocketDAOSecurity(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ proposalExecutedTime := new(*big.Int)
+ if err := rocketDAOSecurity.Call(opts, proposalExecutedTime, "getMemberProposalExecutedTime", proposalType, memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting security DAO %s proposal executed time for member %s: %w", proposalType, memberAddress.Hex(), err)
+ }
+ return (*proposalExecutedTime).Uint64(), nil
+}
+
+// Get contracts
+var rocketDAOSecurityLock sync.Mutex
+
+func getRocketDAOSecurity(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAOSecurityLock.Lock()
+ defer rocketDAOSecurityLock.Unlock()
+ return rp.GetContract("rocketDAOSecurity", opts)
+}
diff --git a/bindings/dao/trustednode/actions.go b/bindings/dao/trustednode/actions.go
new file mode 100644
index 000000000..90924c266
--- /dev/null
+++ b/bindings/dao/trustednode/actions.go
@@ -0,0 +1,147 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Estimate the gas of Join
+func EstimateJoinGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedActions.GetTransactionGasInfo(opts, "actionJoin")
+}
+
+// Join the trusted node DAO
+// Requires an executed invite proposal
+func Join(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedActions.Transact(opts, "actionJoin")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error joining the trusted node DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Leave
+func EstimateLeaveGas(rp *rocketpool.RocketPool, rplBondRefundAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedActions.GetTransactionGasInfo(opts, "actionLeave", rplBondRefundAddress)
+}
+
+// Leave the trusted node DAO
+// Requires an executed leave proposal
+func Leave(rp *rocketpool.RocketPool, rplBondRefundAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedActions.Transact(opts, "actionLeave", rplBondRefundAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error leaving the trusted node DAO: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of MakeChallenge
+func EstimateMakeChallengeGas(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedActions.GetTransactionGasInfo(opts, "actionChallengeMake", memberAddress)
+}
+
+// Make a challenge against a node
+func MakeChallenge(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedActions.Transact(opts, "actionChallengeMake", memberAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error challenging trusted node DAO member %s: %w", memberAddress.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DecideChallenge
+func EstimateDecideChallengeGas(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedActions.GetTransactionGasInfo(opts, "actionChallengeDecide", memberAddress)
+}
+
+// Decide a challenge against a node
+func DecideChallenge(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedActions.Transact(opts, "actionChallengeDecide", memberAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error deciding the challenge against trusted node DAO member %s: %w", memberAddress.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Returns the most recent block number that the number of trusted nodes changed since fromBlock
+func GetLatestMemberCountChangedBlock(rp *rocketpool.RocketPool, fromBlock uint64, intervalSize *big.Int, opts *bind.CallOpts) (uint64, error) {
+ // Get contracts
+ rocketDaoNodeTrustedActions, err := getRocketDAONodeTrustedActions(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketDaoNodeTrustedActions.Address}
+ topicFilter := [][]common.Hash{{rocketDaoNodeTrustedActions.ABI.Events["ActionJoined"].ID, rocketDaoNodeTrustedActions.ABI.Events["ActionLeave"].ID, rocketDaoNodeTrustedActions.ABI.Events["ActionKick"].ID, rocketDaoNodeTrustedActions.ABI.Events["ActionChallengeDecided"].ID}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, big.NewInt(int64(fromBlock)), nil, nil)
+ if err != nil {
+ return 0, err
+ }
+
+ for i := range logs {
+ log := logs[len(logs)-i-1]
+ if log.Topics[0] == rocketDaoNodeTrustedActions.ABI.Events["ActionChallengeDecided"].ID {
+ values := make(map[string]interface{})
+ // Decode the event
+ if rocketDaoNodeTrustedActions.ABI.Events["ActionChallengeDecided"].Inputs.UnpackIntoMap(values, log.Data) != nil {
+ return 0, err
+ }
+ if values["success"].(bool) {
+ return log.BlockNumber, nil
+ }
+ } else {
+ return log.BlockNumber, nil
+ }
+ }
+ return fromBlock, nil
+}
+
+// Get contracts
+var rocketDAONodeTrustedActionsLock sync.Mutex
+
+func getRocketDAONodeTrustedActions(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAONodeTrustedActionsLock.Lock()
+ defer rocketDAONodeTrustedActionsLock.Unlock()
+ return rp.GetContract("rocketDAONodeTrustedActions", opts)
+}
diff --git a/bindings/dao/trustednode/dao.go b/bindings/dao/trustednode/dao.go
new file mode 100644
index 000000000..194d9db63
--- /dev/null
+++ b/bindings/dao/trustednode/dao.go
@@ -0,0 +1,363 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/strings"
+)
+
+// Settings
+const (
+ MemberAddressBatchSize = 50
+ MemberDetailsBatchSize = 20
+)
+
+// Member details
+type MemberDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ ID string `json:"id"`
+ Url string `json:"url"`
+ JoinedTime uint64 `json:"joinedTime"`
+ LastProposalTime uint64 `json:"lastProposalTime"`
+ RPLBondAmount *big.Int `json:"rplBondAmount"`
+ UnbondedValidatorCount uint64 `json:"unbondedValidatorCount"`
+}
+
+// Get all member details
+func GetMembers(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]MemberDetails, error) {
+
+ // Get member addresses
+ memberAddresses, err := GetMemberAddresses(rp, opts)
+ if err != nil {
+ return []MemberDetails{}, err
+ }
+
+ // Load member details in batches
+ details := make([]MemberDetails, len(memberAddresses))
+ for bsi := 0; bsi < len(memberAddresses); bsi += MemberDetailsBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MemberDetailsBatchSize
+ if mei > len(memberAddresses) {
+ mei = len(memberAddresses)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ memberAddress := memberAddresses[mi]
+ memberDetails, err := GetMemberDetails(rp, memberAddress, opts)
+ if err == nil {
+ details[mi] = memberDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []MemberDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all member addresses
+func GetMemberAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]common.Address, error) {
+
+ // Get member count
+ memberCount, err := GetMemberCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load member addresses in batches
+ addresses := make([]common.Address, memberCount)
+ for bsi := uint64(0); bsi < memberCount; bsi += MemberAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MemberAddressBatchSize
+ if mei > memberCount {
+ mei = memberCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetMemberAt(rp, mi, opts)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get a member's details
+func GetMemberDetails(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (MemberDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var id string
+ var url string
+ var joinedTime uint64
+ var lastProposalTime uint64
+ var rplBondAmount *big.Int
+ var unbondedValidatorCount uint64
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetMemberExists(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ id, err = GetMemberID(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ url, err = GetMemberUrl(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ joinedTime, err = GetMemberJoinedTime(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ lastProposalTime, err = GetMemberLastProposalTime(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ rplBondAmount, err = GetMemberRPLBondAmount(rp, memberAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ unbondedValidatorCount, err = GetMemberUnbondedValidatorCount(rp, memberAddress, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return MemberDetails{}, err
+ }
+
+ // Return
+ return MemberDetails{
+ Address: memberAddress,
+ Exists: exists,
+ ID: id,
+ Url: url,
+ JoinedTime: joinedTime,
+ LastProposalTime: lastProposalTime,
+ RPLBondAmount: rplBondAmount,
+ UnbondedValidatorCount: unbondedValidatorCount,
+ }, nil
+
+}
+
+// Get the minimum member count
+func GetMinimumMemberCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minMemberCount := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, minMemberCount, "getMemberMinRequired"); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO minimum member count: %w", err)
+ }
+ return (*minMemberCount).Uint64(), nil
+}
+
+// Get the member count
+func GetMemberCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ memberCount := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, memberCount, "getMemberCount"); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO member count: %w", err)
+ }
+ return (*memberCount).Uint64(), nil
+}
+
+// Get a member address by index
+func GetMemberAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ memberAddress := new(common.Address)
+ if err := rocketDAONodeTrusted.Call(opts, memberAddress, "getMemberAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting trusted node DAO member %d address: %w", index, err)
+ }
+ return *memberAddress, nil
+}
+
+// Member details
+func GetMemberExists(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ exists := new(bool)
+ if err := rocketDAONodeTrusted.Call(opts, exists, "getMemberIsValid", memberAddress); err != nil {
+ return false, fmt.Errorf("error getting trusted node DAO member %s exists status: %w", memberAddress.Hex(), err)
+ }
+ return *exists, nil
+}
+func GetMemberID(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (string, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ id := new(string)
+ if err := rocketDAONodeTrusted.Call(opts, id, "getMemberID", memberAddress); err != nil {
+ return "", fmt.Errorf("error getting trusted node DAO member %s ID: %w", memberAddress.Hex(), err)
+ }
+ return strings.Sanitize(*id), nil
+}
+func GetMemberUrl(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (string, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ url := new(string)
+ if err := rocketDAONodeTrusted.Call(opts, url, "getMemberUrl", memberAddress); err != nil {
+ return "", fmt.Errorf("error getting trusted node DAO member %s URL: %w", memberAddress.Hex(), err)
+ }
+ return strings.Sanitize(*url), nil
+}
+func GetMemberJoinedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ joinedTime := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, joinedTime, "getMemberJoinedTime", memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO member %s joined time: %w", memberAddress.Hex(), err)
+ }
+ return (*joinedTime).Uint64(), nil
+}
+func GetMemberLastProposalTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ lastProposalTime := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, lastProposalTime, "getMemberLastProposalTime", memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO member %s last proposal time: %w", memberAddress.Hex(), err)
+ }
+ return (*lastProposalTime).Uint64(), nil
+}
+func GetMemberRPLBondAmount(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rplBondAmount := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, rplBondAmount, "getMemberRPLBondAmount", memberAddress); err != nil {
+ return nil, fmt.Errorf("error getting trusted node DAO member %s RPL bond amount: %w", memberAddress.Hex(), err)
+ }
+ return *rplBondAmount, nil
+}
+func GetMemberUnbondedValidatorCount(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ unbondedValidatorCount := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, unbondedValidatorCount, "getMemberUnbondedValidatorCount", memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO member %s unbonded validator count: %w", memberAddress.Hex(), err)
+ }
+ return (*unbondedValidatorCount).Uint64(), nil
+}
+
+// Get the time that a proposal for a member was executed at
+func GetMemberInviteProposalExecutedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ return GetMemberProposalExecutedTime(rp, "invited", memberAddress, opts)
+}
+func GetMemberLeaveProposalExecutedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ return GetMemberProposalExecutedTime(rp, "leave", memberAddress, opts)
+}
+func GetMemberReplaceProposalExecutedTime(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ return GetMemberProposalExecutedTime(rp, "replace", memberAddress, opts)
+}
+func GetMemberProposalExecutedTime(rp *rocketpool.RocketPool, proposalType string, memberAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ proposalExecutedTime := new(*big.Int)
+ if err := rocketDAONodeTrusted.Call(opts, proposalExecutedTime, "getMemberProposalExecutedTime", proposalType, memberAddress); err != nil {
+ return 0, fmt.Errorf("error getting trusted node DAO %s proposal executed time for member %s: %w", proposalType, memberAddress.Hex(), err)
+ }
+ return (*proposalExecutedTime).Uint64(), nil
+}
+
+// Get a member's replacement address if being replaced
+func GetMemberReplacementAddress(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ replacementAddress := new(common.Address)
+ if err := rocketDAONodeTrusted.Call(opts, replacementAddress, "getMemberReplacedAddress", "new", memberAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting trusted node DAO member %s replacement address: %w", memberAddress.Hex(), err)
+ }
+ return *replacementAddress, nil
+}
+
+// Get whether a member has an active challenge against them
+func GetMemberIsChallenged(rp *rocketpool.RocketPool, memberAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDAONodeTrusted, err := getRocketDAONodeTrusted(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ isChallenged := new(bool)
+ if err := rocketDAONodeTrusted.Call(opts, isChallenged, "getMemberIsChallenged", memberAddress); err != nil {
+ return false, fmt.Errorf("error getting trusted node DAO member %s is challenged status: %w", memberAddress.Hex(), err)
+ }
+ return *isChallenged, nil
+}
+
+// Get contracts
+var rocketDAONodeTrustedLock sync.Mutex
+
+func getRocketDAONodeTrusted(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAONodeTrustedLock.Lock()
+ defer rocketDAONodeTrustedLock.Unlock()
+ return rp.GetContract("rocketDAONodeTrusted", opts)
+}
diff --git a/bindings/dao/trustednode/proposals.go b/bindings/dao/trustednode/proposals.go
new file mode 100644
index 000000000..53d3e0904
--- /dev/null
+++ b/bindings/dao/trustednode/proposals.go
@@ -0,0 +1,310 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/strings"
+)
+
+// Estimate the gas of ProposeInviteMember
+func EstimateProposeInviteMemberGas(rp *rocketpool.RocketPool, message string, newMemberAddress common.Address, newMemberId, newMemberUrl string, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ newMemberUrl = strings.Sanitize(newMemberUrl)
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalInvite", newMemberId, newMemberUrl, newMemberAddress)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding invite member proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to invite a new member to the trusted node DAO
+func ProposeInviteMember(rp *rocketpool.RocketPool, message string, newMemberAddress common.Address, newMemberId, newMemberUrl string, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ newMemberUrl = strings.Sanitize(newMemberUrl)
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalInvite", newMemberId, newMemberUrl, newMemberAddress)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding invite member proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeMemberLeave
+func EstimateProposeMemberLeaveGas(rp *rocketpool.RocketPool, message string, memberAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalLeave", memberAddress)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding member leave proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal for a member to leave the trusted node DAO
+func ProposeMemberLeave(rp *rocketpool.RocketPool, message string, memberAddress common.Address, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalLeave", memberAddress)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding member leave proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeReplaceMember
+func EstimateProposeReplaceMemberGas(rp *rocketpool.RocketPool, message string, memberAddress, newMemberAddress common.Address, newMemberId, newMemberUrl string, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ newMemberUrl = strings.Sanitize(newMemberUrl)
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalReplace", memberAddress, newMemberId, newMemberUrl, newMemberAddress)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding replace member proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to replace a member in the trusted node DAO
+func ProposeReplaceMember(rp *rocketpool.RocketPool, message string, memberAddress, newMemberAddress common.Address, newMemberId, newMemberUrl string, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ newMemberUrl = strings.Sanitize(newMemberUrl)
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalReplace", memberAddress, newMemberId, newMemberUrl, newMemberAddress)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding replace member proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeKickMember
+func EstimateProposeKickMemberGas(rp *rocketpool.RocketPool, message string, memberAddress common.Address, rplFineAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalKick", memberAddress, rplFineAmount)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding kick member proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to kick a member from the trusted node DAO
+func ProposeKickMember(rp *rocketpool.RocketPool, message string, memberAddress common.Address, rplFineAmount *big.Int, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalKick", memberAddress, rplFineAmount)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding kick member proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeSetBool
+func EstimateProposeSetBoolGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set bool setting proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to update a bool trusted node DAO setting
+func ProposeSetBool(rp *rocketpool.RocketPool, message, contractName, settingPath string, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalSettingBool", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set bool setting proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeSetUint
+func EstimateProposeSetUintGas(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to update a uint trusted node DAO setting
+func ProposeSetUint(rp *rocketpool.RocketPool, message, contractName, settingPath string, value *big.Int, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalSettingUint", contractName, settingPath, value)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding set uint setting proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of ProposeUpgradeContract
+func EstimateProposeUpgradeContractGas(rp *rocketpool.RocketPool, message, upgradeType, contractName, contractAbi string, contractAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ compressedAbi, err := rocketpool.EncodeAbiStr(contractAbi)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalUpgrade", upgradeType, contractName, compressedAbi, contractAddress)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error encoding upgrade contract proposal payload: %w", err)
+ }
+ return EstimateProposalGas(rp, message, payload, opts)
+}
+
+// Submit a proposal to upgrade a contract
+func ProposeUpgradeContract(rp *rocketpool.RocketPool, message, upgradeType, contractName, contractAbi string, contractAddress common.Address, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ compressedAbi, err := rocketpool.EncodeAbiStr(contractAbi)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ payload, err := rocketDAONodeTrustedProposals.ABI.Pack("proposalUpgrade", upgradeType, contractName, compressedAbi, contractAddress)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error encoding upgrade contract proposal payload: %w", err)
+ }
+ return SubmitProposal(rp, message, payload, opts)
+}
+
+// Estimate the gas of a proposal submission
+func EstimateProposalGas(rp *rocketpool.RocketPool, message string, payload []byte, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedProposals.GetTransactionGasInfo(opts, "propose", message, payload)
+}
+
+// Submit a trusted node DAO proposal
+// Returns the ID of the new proposal
+func SubmitProposal(rp *rocketpool.RocketPool, message string, payload []byte, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ proposalCount, err := dao.GetProposalCount(rp, nil)
+ if err != nil {
+ return 0, common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedProposals.Transact(opts, "propose", message, payload)
+ if err != nil {
+ return 0, common.Hash{}, fmt.Errorf("error submitting trusted node DAO proposal: %w", err)
+ }
+ return proposalCount + 1, tx.Hash(), nil
+}
+
+// Estimate the gas of CancelProposal
+func EstimateCancelProposalGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedProposals.GetTransactionGasInfo(opts, "cancel", big.NewInt(int64(proposalId)))
+}
+
+// Cancel a submitted proposal
+func CancelProposal(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedProposals.Transact(opts, "cancel", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error cancelling trusted node DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of VoteOnProposal
+func EstimateVoteOnProposalGas(rp *rocketpool.RocketPool, proposalId uint64, support bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedProposals.GetTransactionGasInfo(opts, "vote", big.NewInt(int64(proposalId)), support)
+}
+
+// Vote on a submitted proposal
+func VoteOnProposal(rp *rocketpool.RocketPool, proposalId uint64, support bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedProposals.Transact(opts, "vote", big.NewInt(int64(proposalId)), support)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting on trusted node DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ExecuteProposal
+func EstimateExecuteProposalGas(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDAONodeTrustedProposals.GetTransactionGasInfo(opts, "execute", big.NewInt(int64(proposalId)))
+}
+
+// Execute a submitted proposal
+func ExecuteProposal(rp *rocketpool.RocketPool, proposalId uint64, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDAONodeTrustedProposals, err := getRocketDAONodeTrustedProposals(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDAONodeTrustedProposals.Transact(opts, "execute", big.NewInt(int64(proposalId)))
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error executing trusted node DAO proposal %d: %w", proposalId, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDAONodeTrustedProposalsLock sync.Mutex
+
+func getRocketDAONodeTrustedProposals(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAONodeTrustedProposalsLock.Lock()
+ defer rocketDAONodeTrustedProposalsLock.Unlock()
+ return rp.GetContract("rocketDAONodeTrustedProposals", opts)
+}
diff --git a/bindings/deposit/deposit.go b/bindings/deposit/deposit.go
new file mode 100644
index 000000000..83e88467c
--- /dev/null
+++ b/bindings/deposit/deposit.go
@@ -0,0 +1,104 @@
+package deposit
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the deposit pool balance
+func GetBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ balance := new(*big.Int)
+ if err := rocketDepositPool.Call(opts, balance, "getBalance"); err != nil {
+ return nil, fmt.Errorf("error getting deposit pool balance: %w", err)
+ }
+ return *balance, nil
+}
+
+// Get the deposit pool balance
+func GetUserBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ balance := new(*big.Int)
+ if err := rocketDepositPool.Call(opts, balance, "getUserBalance"); err != nil {
+ return nil, fmt.Errorf("error getting deposit pool user balance: %w", err)
+ }
+ return *balance, nil
+}
+
+// Get the excess deposit pool balance
+func GetExcessBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ excessBalance := new(*big.Int)
+ if err := rocketDepositPool.Call(opts, excessBalance, "getExcessBalance"); err != nil {
+ return nil, fmt.Errorf("error getting deposit pool excess balance: %w", err)
+ }
+ return *excessBalance, nil
+}
+
+// Estimate the gas of Deposit
+func EstimateDepositGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDepositPool.GetTransactionGasInfo(opts, "deposit")
+}
+
+// Make a deposit
+func Deposit(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDepositPool.Transact(opts, "deposit")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error depositing: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of AssignDeposits
+func EstimateAssignDepositsGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDepositPool.GetTransactionGasInfo(opts, "assignDeposits")
+}
+
+// Assign deposits
+func AssignDeposits(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDepositPool, err := getRocketDepositPool(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDepositPool.Transact(opts, "assignDeposits")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error assigning deposits: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDepositPoolLock sync.Mutex
+
+func getRocketDepositPool(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDepositPoolLock.Lock()
+ defer rocketDepositPoolLock.Unlock()
+ return rp.GetContract("rocketDepositPool", opts)
+}
diff --git a/bindings/docs/docgen.go b/bindings/docs/docgen.go
new file mode 100644
index 000000000..06f77312e
--- /dev/null
+++ b/bindings/docs/docgen.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "os"
+
+ "github.com/princjef/gomarkdoc"
+ "github.com/princjef/gomarkdoc/lang"
+ "github.com/princjef/gomarkdoc/logger"
+)
+
+const repo string = "https://github.com/rocket-pool/rocketpool-go"
+const branch string = "release"
+
+func main() {
+
+ // gomarkdoc's logger
+ log := logger.New(logger.DebugLevel)
+
+ // Make a new doc renderer
+ renderer, err := gomarkdoc.NewRenderer()
+ if err != nil {
+ fmt.Printf("Error creating renderer: %s\n", err.Error())
+ os.Exit(1)
+ }
+
+ // Get the working directory
+ wd, err := os.Getwd()
+ if err != nil {
+ fmt.Printf("Error getting working directory: %s\n", err.Error())
+ os.Exit(1)
+ }
+
+ // These are all of the packages to generate the source for
+ packages := map[string]string{
+ "auction": "%s/../auction",
+ "contracts": "%s/../contracts",
+ "dao": "%s/../dao",
+ "dao-protocol": "%s/../dao/protocol",
+ "dao-trustednode": "%s/../dao/trustednode",
+ "deposit": "%s/../deposit",
+ "minipool": "%s/../minipool",
+ "network": "%s/../network",
+ "node": "%s/../node",
+ "rewards": "%s/../rewards",
+ "rocketpool": "%s/../rocketpool",
+ "settings-protocol": "%s/../settings/protocol",
+ "settings-trustednode": "%s/../settings/trustednode",
+ "storage": "%s/../storage",
+ "tokens": "%s/../tokens",
+ "types": "%s/../types",
+ "utils": "%s/../utils",
+ "utils-eth": "%s/../utils/eth",
+ "utils-strings": "%s/../utils/strings",
+ }
+
+ // Build the documentation file for each package
+ for filename, path := range packages {
+
+ // Load the source dir
+ builder, err := build.ImportDir(fmt.Sprintf(path, wd), build.ImportComment)
+ if err != nil {
+ fmt.Printf("Error loading package builder for %s: %s\n", filename, err.Error())
+ os.Exit(1)
+ }
+
+ // Create a package from the source
+ pkg, err := lang.NewPackageFromBuild(log, builder, lang.PackageWithRepositoryOverrides(&lang.Repo{
+ Remote: repo,
+ DefaultBranch: branch,
+ }))
+ if err != nil {
+ fmt.Printf("Error creating package %s: %s\n", filename, err.Error())
+ os.Exit(1)
+ }
+
+ // Render the documentation for the package
+ packageContents, err := renderer.Package(pkg)
+ if err != nil {
+ fmt.Printf("Error exporting package %s: %s\n", filename, err.Error())
+ os.Exit(1)
+ }
+
+ // Write the docs out to the appropriate file
+ err = os.WriteFile(fmt.Sprintf("%s/%s.md", wd, filename), []byte(packageContents), 0644)
+ if err != nil {
+ fmt.Printf("Error writing file for package %s: %s\n", filename, err.Error())
+ os.Exit(1)
+ }
+ }
+
+}
diff --git a/bindings/go.mod b/bindings/go.mod
new file mode 100644
index 000000000..b5c67df2f
--- /dev/null
+++ b/bindings/go.mod
@@ -0,0 +1,72 @@
+module github.com/rocket-pool/rocketpool-go
+
+go 1.21
+
+require (
+ github.com/ethereum/go-ethereum v1.10.26
+ github.com/hashicorp/go-version v1.6.0
+ github.com/princjef/gomarkdoc v0.4.1
+ github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
+ golang.org/x/sync v0.1.0
+)
+
+require (
+ github.com/Microsoft/go-winio v0.5.0 // indirect
+ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
+ github.com/VividCortex/ewma v1.2.0 // indirect
+ github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cheggaaa/pb/v3 v3.0.8 // indirect
+ github.com/deckarep/golang-set v1.8.0 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+ github.com/dgraph-io/ristretto v0.1.0 // indirect
+ github.com/dustin/go-humanize v1.0.0 // indirect
+ github.com/emirpasic/gods v1.12.0 // indirect
+ github.com/fatih/color v1.11.0 // indirect
+ github.com/ferranbt/fastssz v0.1.2 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-git/gcfg v1.5.0 // indirect
+ github.com/go-git/go-billy/v5 v5.3.1 // indirect
+ github.com/go-git/go-git/v5 v5.3.0 // indirect
+ github.com/go-ole/go-ole v1.2.1 // indirect
+ github.com/go-stack/stack v1.8.1 // indirect
+ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
+ github.com/google/go-cmp v0.5.7 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/gorilla/websocket v1.4.2 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/kevinburke/ssh_config v1.1.0 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/mattn/go-runewidth v0.0.12 // indirect
+ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
+ github.com/minio/highwayhash v1.0.2 // indirect
+ github.com/minio/sha256-simd v1.0.0 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.4.1 // indirect
+ github.com/nxadm/tail v1.4.8 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/princjef/mageutil v1.0.0 // indirect
+ github.com/protolambda/zssz v0.1.5 // indirect
+ github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect
+ github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/rjeczalik/notify v0.9.1 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/sergi/go-diff v1.2.0 // indirect
+ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
+ github.com/sirupsen/logrus v1.8.1 // indirect
+ github.com/tklauser/go-sysconf v0.3.5 // indirect
+ github.com/tklauser/numcpus v0.2.2 // indirect
+ github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
+ github.com/xanzy/ssh-agent v0.3.0 // indirect
+ golang.org/x/crypto v0.1.0 // indirect
+ golang.org/x/net v0.4.0 // indirect
+ golang.org/x/sys v0.5.0 // indirect
+ golang.org/x/term v0.3.0 // indirect
+ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ mvdan.cc/xurls/v2 v2.2.0 // indirect
+)
diff --git a/bindings/go.sum b/bindings/go.sum
new file mode 100644
index 000000000..6758191ab
--- /dev/null
+++ b/bindings/go.sum
@@ -0,0 +1,304 @@
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
+github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
+github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
+github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
+github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
+github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
+github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=
+github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
+github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
+github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
+github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
+github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s=
+github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fatih/color v1.11.0 h1:l4iX0RqNnx/pU7rY2DB/I+znuYY0K3x6Ywac6EIr0PA=
+github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
+github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
+github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
+github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc=
+github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
+github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
+github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
+github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
+github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
+github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
+github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
+github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
+github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
+github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
+github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
+github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
+github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
+github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
+github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
+github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
+github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
+github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/princjef/gomarkdoc v0.4.1 h1:Ubt5OiHYi2PdxrDkWMeWM4ROrbvAGkIXBz3PquxglBM=
+github.com/princjef/gomarkdoc v0.4.1/go.mod h1:+o04FW4GNL2vPr/35yxMV/8eXjhsdNBBPMVVDOOTLec=
+github.com/princjef/mageutil v1.0.0 h1:1OfZcJUMsooPqieOz2ooLjI+uHUo618pdaJsbCXcFjQ=
+github.com/princjef/mageutil v1.0.0/go.mod h1:mkShhaUomCYfAoVvTKRcbAs8YSVPdtezI5j6K+VXhrs=
+github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
+github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
+github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw=
+github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4=
+github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388 h1:4bD+ujqGfY4zoDUF3q9MhdmpPXzdp03DYUIlXeQ72kk=
+github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc=
+github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
+github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
+github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
+github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
+github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
+github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
+github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
+github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
+github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
+github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
+github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
+github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
+golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v2 v2.0.7/go.mod h1:0CiZ1p8pvtxBlQpLXkHuUTpdJ1shm3OqCF1QugkjHL4=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fatih/color.v1 v1.7.0/go.mod h1:P7yosIhqIl/sX8J8UypY5M+dDpD2KmyfP5IRs5v/fo0=
+gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY=
+gopkg.in/mattn/go-isatty.v0 v0.0.4/go.mod h1:wt691ab7g0X4ilKZNmMII3egK0bTxl37fEn/Fwbd8gc=
+gopkg.in/mattn/go-runewidth.v0 v0.0.4/go.mod h1:BmXejnxvhwdaATwiJbB1vZ2dtXkQKZGu9yLFCZb4msQ=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
+mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
diff --git a/bindings/legacy/v1.0.0/minipool/minipool.go b/bindings/legacy/v1.0.0/minipool/minipool.go
new file mode 100644
index 000000000..cb7acc3bb
--- /dev/null
+++ b/bindings/legacy/v1.0.0/minipool/minipool.go
@@ -0,0 +1,552 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Settings
+const (
+ MinipoolPrelaunchBatchSize = 250
+ MinipoolAddressBatchSize = 50
+ MinipoolDetailsBatchSize = 20
+)
+
+// Minipool details
+type MinipoolDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ Pubkey rptypes.ValidatorPubkey `json:"pubkey"`
+}
+
+// The counts of minipools per status
+type MinipoolCountsPerStatus struct {
+ Initialized *big.Int `abi:"initialisedCount"`
+ Prelaunch *big.Int `abi:"prelaunchCount"`
+ Staking *big.Int `abi:"stakingCount"`
+ Withdrawable *big.Int `abi:"withdrawableCount"`
+ Dissolved *big.Int `abi:"dissolvedCount"`
+}
+
+// Get all minipool details
+func GetMinipools(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]MinipoolDetails, error) {
+ minipoolAddresses, err := GetMinipoolAddresses(rp, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []MinipoolDetails{}, err
+ }
+ return loadMinipoolDetails(rp, minipoolAddresses, opts, legacyRocketMinipoolManagerAddress)
+}
+
+// Get a node's minipool details
+func GetNodeMinipools(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]MinipoolDetails, error) {
+ minipoolAddresses, err := GetNodeMinipoolAddresses(rp, nodeAddress, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []MinipoolDetails{}, err
+ }
+ return loadMinipoolDetails(rp, minipoolAddresses, opts, legacyRocketMinipoolManagerAddress)
+}
+
+// Load minipool details
+func loadMinipoolDetails(rp *rocketpool.RocketPool, minipoolAddresses []common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]MinipoolDetails, error) {
+
+ // Load minipool details in batches
+ details := make([]MinipoolDetails, len(minipoolAddresses))
+ for bsi := 0; bsi < len(minipoolAddresses); bsi += MinipoolDetailsBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolDetailsBatchSize
+ if mei > len(minipoolAddresses) {
+ mei = len(minipoolAddresses)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ minipoolAddress := minipoolAddresses[mi]
+ minipoolDetails, err := GetMinipoolDetails(rp, minipoolAddress, opts, legacyRocketMinipoolManagerAddress)
+ if err == nil {
+ details[mi] = minipoolDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []MinipoolDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all minipool addresses
+func GetMinipoolAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]common.Address, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetMinipoolCount(rp, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load minipool addresses in batches
+ addresses := make([]common.Address, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetMinipoolAt(rp, mi, opts, legacyRocketMinipoolManagerAddress)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get the addresses of all minipools in prelaunch status
+func GetPrelaunchMinipoolAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]common.Address, error) {
+
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Get the total number of minipools
+ totalMinipoolsUint, err := GetMinipoolCount(rp, nil, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ totalMinipools := int64(totalMinipoolsUint)
+ addresses := []common.Address{}
+ limit := big.NewInt(MinipoolPrelaunchBatchSize)
+ for i := int64(0); i < totalMinipools; i += MinipoolPrelaunchBatchSize {
+ // Get a batch of addresses
+ offset := big.NewInt(i)
+ newAddresses := new([]common.Address)
+ if err := rocketMinipoolManager.Call(opts, newAddresses, "getPrelaunchMinipools", offset, limit); err != nil {
+ return []common.Address{}, fmt.Errorf("error getting prelaunch minipool addresses: %w", err)
+ }
+ addresses = append(addresses, *newAddresses...)
+ }
+
+ return addresses, nil
+}
+
+// Get a node's minipool addresses
+func GetNodeMinipoolAddresses(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]common.Address, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetNodeMinipoolCount(rp, nodeAddress, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load minipool addresses in batches
+ addresses := make([]common.Address, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetNodeMinipoolAt(rp, nodeAddress, mi, opts, legacyRocketMinipoolManagerAddress)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get a node's validating minipool pubkeys
+func GetNodeValidatingMinipoolPubkeys(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]rptypes.ValidatorPubkey, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetNodeValidatingMinipoolCount(rp, nodeAddress, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return []rptypes.ValidatorPubkey{}, err
+ }
+
+ // Load pubkeys in batches
+ var lock = sync.RWMutex{}
+ pubkeys := make([]rptypes.ValidatorPubkey, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load pubkeys
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ minipoolAddress, err := GetNodeValidatingMinipoolAt(rp, nodeAddress, mi, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return err
+ }
+ pubkey, err := GetMinipoolPubkey(rp, minipoolAddress, opts, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return err
+ }
+ lock.Lock()
+ pubkeys[mi] = pubkey
+ lock.Unlock()
+ return nil
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []rptypes.ValidatorPubkey{}, err
+ }
+
+ }
+
+ // Return
+ return pubkeys, nil
+
+}
+
+// Get a minipool's details
+func GetMinipoolDetails(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (MinipoolDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var pubkey rptypes.ValidatorPubkey
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetMinipoolExists(rp, minipoolAddress, opts, legacyRocketMinipoolManagerAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ pubkey, err = GetMinipoolPubkey(rp, minipoolAddress, opts, legacyRocketMinipoolManagerAddress)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return MinipoolDetails{}, err
+ }
+
+ // Return
+ return MinipoolDetails{
+ Address: minipoolAddress,
+ Exists: exists,
+ Pubkey: pubkey,
+ }, nil
+
+}
+
+// Get the minipool count
+func GetMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, nil, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of finalised minipools in the network
+func GetFinalisedMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, nil, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getFinalisedMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting finalised minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of active minipools in the network
+func GetActiveMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, nil, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getActiveMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting finalised minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the minipool count by status
+func GetMinipoolCountPerStatus(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (MinipoolCountsPerStatus, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return MinipoolCountsPerStatus{}, err
+ }
+
+ // Get the total number of minipools
+ totalMinipoolsUint, err := GetMinipoolCount(rp, nil, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return MinipoolCountsPerStatus{}, err
+ }
+
+ totalMinipools := int64(totalMinipoolsUint)
+ minipoolCounts := MinipoolCountsPerStatus{
+ Initialized: big.NewInt(0),
+ Prelaunch: big.NewInt(0),
+ Staking: big.NewInt(0),
+ Dissolved: big.NewInt(0),
+ Withdrawable: big.NewInt(0),
+ }
+ limit := big.NewInt(MinipoolPrelaunchBatchSize)
+ for i := int64(0); i < totalMinipools; i += MinipoolPrelaunchBatchSize {
+ // Get a batch of counts
+ offset := big.NewInt(i)
+ newMinipoolCounts := new(MinipoolCountsPerStatus)
+ if err := rocketMinipoolManager.Call(opts, newMinipoolCounts, "getMinipoolCountPerStatus", offset, limit); err != nil {
+ return MinipoolCountsPerStatus{}, fmt.Errorf("error getting minipool counts: %w", err)
+ }
+ if newMinipoolCounts != nil {
+ if newMinipoolCounts.Initialized != nil {
+ minipoolCounts.Initialized.Add(minipoolCounts.Initialized, newMinipoolCounts.Initialized)
+ }
+ if newMinipoolCounts.Prelaunch != nil {
+ minipoolCounts.Prelaunch.Add(minipoolCounts.Prelaunch, newMinipoolCounts.Prelaunch)
+ }
+ if newMinipoolCounts.Staking != nil {
+ minipoolCounts.Staking.Add(minipoolCounts.Staking, newMinipoolCounts.Staking)
+ }
+ if newMinipoolCounts.Dissolved != nil {
+ minipoolCounts.Dissolved.Add(minipoolCounts.Dissolved, newMinipoolCounts.Dissolved)
+ }
+ if newMinipoolCounts.Withdrawable != nil {
+ minipoolCounts.Withdrawable.Add(minipoolCounts.Withdrawable, newMinipoolCounts.Withdrawable)
+ }
+ }
+ }
+ return minipoolCounts, nil
+}
+
+// Get a minipool address by index
+func GetMinipoolAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getMinipoolAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool %d address: %w", index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a node's minipool count
+func GetNodeMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of minipools owned by a node that are not finalised
+func GetNodeActiveMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeActiveMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of minipools owned by a node that are finalised
+func GetNodeFinalisedMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeFinalisedMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get a node's minipool address by index
+func GetNodeMinipoolAt(rp *rocketpool.RocketPool, nodeAddress common.Address, index uint64, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getNodeMinipoolAt", nodeAddress, big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s minipool %d address: %w", nodeAddress.Hex(), index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a node's validating minipool count
+func GetNodeValidatingMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeValidatingMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s validating minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get a node's validating minipool address by index
+func GetNodeValidatingMinipoolAt(rp *rocketpool.RocketPool, nodeAddress common.Address, index uint64, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getNodeValidatingMinipoolAt", nodeAddress, big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s validating minipool %d address: %w", nodeAddress.Hex(), index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a minipool address by validator pubkey
+func GetMinipoolByPubkey(rp *rocketpool.RocketPool, pubkey rptypes.ValidatorPubkey, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getMinipoolByPubkey", pubkey[:]); err != nil {
+ return common.Address{}, fmt.Errorf("error getting validator %s minipool address: %w", pubkey.Hex(), err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Check whether a minipool exists
+func GetMinipoolExists(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (bool, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ exists := new(bool)
+ if err := rocketMinipoolManager.Call(opts, exists, "getMinipoolExists", minipoolAddress); err != nil {
+ return false, fmt.Errorf("error getting minipool %s exists status: %w", minipoolAddress.Hex(), err)
+ }
+ return *exists, nil
+}
+
+// Get a minipool's validator pubkey
+func GetMinipoolPubkey(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (rptypes.ValidatorPubkey, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return rptypes.ValidatorPubkey{}, err
+ }
+ pubkey := new(rptypes.ValidatorPubkey)
+ if err := rocketMinipoolManager.Call(opts, pubkey, "getMinipoolPubkey", minipoolAddress); err != nil {
+ return rptypes.ValidatorPubkey{}, fmt.Errorf("error getting minipool %s pubkey: %w", minipoolAddress.Hex(), err)
+ }
+ return *pubkey, nil
+}
+
+// Get the CreationCode binary for the RocketMinipool contract that will be created by node deposits
+func GetMinipoolBytecode(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) ([]byte, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return []byte{}, err
+ }
+ bytecode := new([]byte)
+ if err := rocketMinipoolManager.Call(opts, bytecode, "getMinipoolBytecode"); err != nil {
+ return []byte{}, fmt.Errorf("error getting minipool contract bytecode: %w", err)
+ }
+ return *bytecode, nil
+}
+
+// Get the 0x01-based Beacon Chain withdrawal credentials for a given minipool
+func GetMinipoolWithdrawalCredentials(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts, legacyRocketMinipoolManagerAddress *common.Address) (common.Hash, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ withdrawalCredentials := new(common.Hash)
+ if err := rocketMinipoolManager.Call(opts, withdrawalCredentials, "getMinipoolWithdrawalCredentials", minipoolAddress); err != nil {
+ return common.Hash{}, fmt.Errorf("error getting minipool withdrawal credentials: %w", err)
+ }
+ return *withdrawalCredentials, nil
+}
+
+// Get contracts
+var rocketMinipoolManagerLock sync.Mutex
+
+func getRocketMinipoolManager(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolManagerLock.Lock()
+ defer rocketMinipoolManagerLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_0_0.GetContract("rocketMinipoolManager", opts)
+ } else {
+ return rp.VersionManager.V1_0_0.GetContractWithAddress("rocketMinipoolManager", *address)
+ }
+}
diff --git a/bindings/legacy/v1.0.0/rewards/node.go b/bindings/legacy/v1.0.0/rewards/node.go
new file mode 100644
index 000000000..6ec782c65
--- /dev/null
+++ b/bindings/legacy/v1.0.0/rewards/node.go
@@ -0,0 +1,132 @@
+package rewards
+
+import (
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Get whether node reward claims are enabled
+func GetNodeClaimsEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketClaimNodeAddress *common.Address) (bool, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ return getEnabled(rocketClaimNode, "node", opts)
+}
+
+// Get whether a node rewards claimer can claim
+func GetNodeClaimPossible(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimNodeAddress *common.Address) (bool, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ return getClaimPossible(rocketClaimNode, "node", claimerAddress, opts)
+}
+
+// Get the percentage of rewards available for a node rewards claimer
+func GetNodeClaimRewardsPerc(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimNodeAddress *common.Address) (float64, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ return getClaimRewardsPerc(rocketClaimNode, "node", claimerAddress, opts)
+}
+
+// Get the total amount of rewards available for a node rewards claimer
+func GetNodeClaimRewardsAmount(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimNodeAddress *common.Address) (*big.Int, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ return getClaimRewardsAmount(rocketClaimNode, "node", claimerAddress, opts)
+}
+
+// Estimate the gas of ClaimNodeRewards
+func EstimateClaimNodeRewardsGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts, legacyRocketClaimNodeAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateClaimGas(rocketClaimNode, opts)
+}
+
+// Claim node rewards
+func ClaimNodeRewards(rp *rocketpool.RocketPool, opts *bind.TransactOpts, legacyRocketClaimNodeAddress *common.Address) (common.Hash, error) {
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return claim(rocketClaimNode, "node", opts)
+}
+
+// Filters through token claim events and sums the total amount claimed by claimerAddress
+func CalculateLifetimeNodeRewards(rp *rocketpool.RocketPool, claimerAddress common.Address, intervalSize *big.Int, startBlock *big.Int, legacyRocketRewardsPoolAddress *common.Address, legacyRocketClaimNodeAddress *common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ // Get contracts
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rocketClaimNode, err := getRocketClaimNode(rp, legacyRocketClaimNodeAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketRewardsPool.Address}
+ // RPLTokensClaimed(address clamingContract, address claimingAddress, uint256 amount, uint256 time)
+ topicFilter := [][]common.Hash{
+ {rocketRewardsPool.ABI.Events["RPLTokensClaimed"].ID},
+ {common.BytesToHash(rocketClaimNode.Address.Bytes())},
+ {common.BytesToHash(claimerAddress.Bytes())},
+ }
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Iterate over the logs and sum the amount
+ sum := big.NewInt(0)
+ for _, log := range logs {
+ values := make(map[string]interface{})
+ // Decode the event
+ if rocketRewardsPool.ABI.Events["RPLTokensClaimed"].Inputs.UnpackIntoMap(values, log.Data) != nil {
+ return nil, err
+ }
+ // Add the amount argument to our sum
+ amount := values["amount"].(*big.Int)
+ sum.Add(sum, amount)
+ }
+ // Return the result
+ return sum, nil
+}
+
+// Get the time that the user registered as a claimer
+func GetNodeRegistrationTime(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Time, error) {
+ return getClaimingContractUserRegisteredTime(rp, "rocketClaimNode", claimerAddress, opts, legacyRocketRewardsPoolAddress)
+}
+
+// Get the total rewards claimed for this claiming contract this interval
+func GetNodeTotalClaimed(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ return getClaimingContractTotalClaimed(rp, "rocketClaimNode", opts, legacyRocketRewardsPoolAddress)
+}
+
+// Get contracts
+var rocketClaimNodeLock sync.Mutex
+
+func getRocketClaimNode(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketClaimNodeLock.Lock()
+ defer rocketClaimNodeLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_0_0.GetContract("rocketClaimNode", opts)
+ } else {
+ return rp.VersionManager.V1_0_0.GetContractWithAddress("rocketClaimNode", *address)
+ }
+}
diff --git a/bindings/legacy/v1.0.0/rewards/rewards.go b/bindings/legacy/v1.0.0/rewards/rewards.go
new file mode 100644
index 000000000..f25d1d5b0
--- /dev/null
+++ b/bindings/legacy/v1.0.0/rewards/rewards.go
@@ -0,0 +1,157 @@
+package rewards
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Get whether a claims contract is enabled
+func getEnabled(claimsContract *rocketpool.Contract, claimsName string, opts *bind.CallOpts) (bool, error) {
+ enabled := new(bool)
+ if err := claimsContract.Call(opts, enabled, "getEnabled"); err != nil {
+ return false, fmt.Errorf("error getting %s claims contract enabled status: %w", claimsName, err)
+ }
+ return *enabled, nil
+}
+
+// Get whether a claimer can make a claim
+// Use to check whether a claimer is able to make claims at all
+func getClaimPossible(claimsContract *rocketpool.Contract, claimsName string, claimerAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ claimPossible := new(bool)
+ if err := claimsContract.Call(opts, claimPossible, "getClaimPossible", claimerAddress); err != nil {
+ return false, fmt.Errorf("error getting %s claim possible status for %s: %w", claimsName, claimerAddress.Hex(), err)
+ }
+ return *claimPossible, nil
+}
+
+// Get the percentage of rewards available to a claimer
+func getClaimRewardsPerc(claimsContract *rocketpool.Contract, claimsName string, claimerAddress common.Address, opts *bind.CallOpts) (float64, error) {
+ claimRewardsPerc := new(*big.Int)
+ if err := claimsContract.Call(opts, claimRewardsPerc, "getClaimRewardsPerc", claimerAddress); err != nil {
+ return 0, fmt.Errorf("error getting %s claim rewards percent for %s: %w", claimsName, claimerAddress.Hex(), err)
+ }
+ return eth.WeiToEth(*claimRewardsPerc), nil
+}
+
+// Get the total amount of rewards available to a claimer
+// Use to check whether a claimer is able to make a claim for the current interval (returns zero if unable)
+func getClaimRewardsAmount(claimsContract *rocketpool.Contract, claimsName string, claimerAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ claimRewardsAmount := new(*big.Int)
+ if err := claimsContract.Call(opts, claimRewardsAmount, "getClaimRewardsAmount", claimerAddress); err != nil {
+ return nil, fmt.Errorf("error getting %s claim rewards amount for %s: %w", claimsName, claimerAddress.Hex(), err)
+ }
+ return *claimRewardsAmount, nil
+}
+
+// Get the time that the user registered as a claimer
+func getClaimingContractUserRegisteredTime(rp *rocketpool.RocketPool, claimsContract string, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Time, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ claimTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, claimTime, "getClaimingContractUserRegisteredTime", claimsContract, claimerAddress); err != nil {
+ return time.Time{}, fmt.Errorf("error getting claims registration time on contract %s for %s: %w", claimsContract, claimerAddress.Hex(), err)
+ }
+ return time.Unix((*claimTime).Int64(), 0), nil
+}
+
+// Get the total amount claimed in the current interval by the given claiming contract
+func getClaimingContractTotalClaimed(rp *rocketpool.RocketPool, claimsContract string, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalClaimed := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, totalClaimed, "getClaimingContractTotalClaimed", claimsContract); err != nil {
+ return nil, fmt.Errorf("error getting total claimed for %s: %w", claimsContract, err)
+ }
+ return *totalClaimed, nil
+}
+
+// Estimate the gas of claim
+func estimateClaimGas(claimsContract *rocketpool.Contract, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return claimsContract.GetTransactionGasInfo(opts, "claim")
+}
+
+// Claim rewards
+func claim(claimsContract *rocketpool.Contract, claimsName string, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := claimsContract.Transact(opts, "claim")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error claiming %s rewards: %w", claimsName, err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the timestamp that the current rewards interval started
+func GetClaimIntervalTimeStart(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Time, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTimeStart"); err != nil {
+ return time.Time{}, fmt.Errorf("error getting claim interval time start: %w", err)
+ }
+ return time.Unix((*unixTime).Int64(), 0), nil
+}
+
+// Get the number of seconds in a claim interval
+func GetClaimIntervalTime(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Duration, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTime"); err != nil {
+ return 0, fmt.Errorf("error getting claim interval time: %w", err)
+ }
+ return time.Duration((*unixTime).Int64()) * time.Second, nil
+}
+
+// Get the percent of checkpoint rewards that goes to node operators
+func GetNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (float64, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimNode"); err != nil {
+ return 0, fmt.Errorf("error getting node operator rewards percent: %w", err)
+ }
+ return eth.WeiToEth(*perc), nil
+}
+
+// Get the percent of checkpoint rewards that goes to ODAO members
+func GetTrustedNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (float64, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimTrustedNode"); err != nil {
+ return 0, fmt.Errorf("error getting trusted node operator rewards percent: %w", err)
+ }
+ return eth.WeiToEth(*perc), nil
+}
+
+// Get contracts
+var rocketRewardsPoolLock sync.Mutex
+
+func getRocketRewardsPool(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketRewardsPoolLock.Lock()
+ defer rocketRewardsPoolLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_0_0.GetContract("rocketRewardsPool", opts)
+ } else {
+ return rp.VersionManager.V1_0_0.GetContractWithAddress("rocketRewardsPool", *address)
+ }
+}
diff --git a/bindings/legacy/v1.0.0/rewards/trusted-node.go b/bindings/legacy/v1.0.0/rewards/trusted-node.go
new file mode 100644
index 000000000..9bb1144d2
--- /dev/null
+++ b/bindings/legacy/v1.0.0/rewards/trusted-node.go
@@ -0,0 +1,132 @@
+package rewards
+
+import (
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Get whether trusted node reward claims are enabled
+func GetTrustedNodeClaimsEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (bool, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ return getEnabled(rocketClaimTrustedNode, "trusted node", opts)
+}
+
+// Get whether a trusted node rewards claimer can claim
+func GetTrustedNodeClaimPossible(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (bool, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ return getClaimPossible(rocketClaimTrustedNode, "trusted node", claimerAddress, opts)
+}
+
+// Get the percentage of rewards available for a trusted node rewards claimer
+func GetTrustedNodeClaimRewardsPerc(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (float64, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ return getClaimRewardsPerc(rocketClaimTrustedNode, "trusted node", claimerAddress, opts)
+}
+
+// Get the total amount of rewards available for a trusted node rewards claimer
+func GetTrustedNodeClaimRewardsAmount(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (*big.Int, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ return getClaimRewardsAmount(rocketClaimTrustedNode, "trusted node", claimerAddress, opts)
+}
+
+// Estimate the gas of ClaimTrustedNodeRewards
+func EstimateClaimTrustedNodeRewardsGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateClaimGas(rocketClaimTrustedNode, opts)
+}
+
+// Claim trusted node rewards
+func ClaimTrustedNodeRewards(rp *rocketpool.RocketPool, opts *bind.TransactOpts, legacyRocketClaimTrustedNodeAddress *common.Address) (common.Hash, error) {
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return claim(rocketClaimTrustedNode, "trusted node", opts)
+}
+
+// Filters through token claim events and sums the total amount claimed by claimerAddress
+func CalculateLifetimeTrustedNodeRewards(rp *rocketpool.RocketPool, claimerAddress common.Address, intervalSize *big.Int, startBlock *big.Int, legacyRocketRewardsPoolAddress *common.Address, legacyRocketClaimTrustedNodeAddress *common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ // Get contracts
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rocketClaimTrustedNode, err := getRocketClaimTrustedNode(rp, legacyRocketClaimTrustedNodeAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketRewardsPool.Address}
+ // RPLTokensClaimed(address clamingContract, address clainingAddress, uint256 amount, uint256 time)
+ topicFilter := [][]common.Hash{
+ {rocketRewardsPool.ABI.Events["RPLTokensClaimed"].ID},
+ {common.BytesToHash(rocketClaimTrustedNode.Address.Bytes())},
+ {common.BytesToHash(claimerAddress.Bytes())},
+ }
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Iterate over the logs and sum the amount
+ sum := big.NewInt(0)
+ for _, log := range logs {
+ values := make(map[string]interface{})
+ // Decode the event
+ if rocketRewardsPool.ABI.Events["RPLTokensClaimed"].Inputs.UnpackIntoMap(values, log.Data) != nil {
+ return nil, err
+ }
+ // Add the amount argument to our sum
+ amount := values["amount"].(*big.Int)
+ sum.Add(sum, amount)
+ }
+ // Return the result
+ return sum, nil
+}
+
+// Get the time that the user registered as a claimer
+func GetTrustedNodeRegistrationTime(rp *rocketpool.RocketPool, claimerAddress common.Address, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Time, error) {
+ return getClaimingContractUserRegisteredTime(rp, "rocketClaimTrustedNode", claimerAddress, opts, legacyRocketRewardsPoolAddress)
+}
+
+// Get the total rewards claimed for this claiming contract this interval
+func GetTrustedNodeTotalClaimed(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ return getClaimingContractTotalClaimed(rp, "rocketClaimTrustedNode", opts, legacyRocketRewardsPoolAddress)
+}
+
+// Get contracts
+var rocketClaimTrustedNodeLock sync.Mutex
+
+func getRocketClaimTrustedNode(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketClaimTrustedNodeLock.Lock()
+ defer rocketClaimTrustedNodeLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_0_0.GetContract("rocketClaimTrustedNode", opts)
+ } else {
+ return rp.VersionManager.V1_0_0.GetContractWithAddress("rocketClaimTrustedNode", *address)
+ }
+}
diff --git a/bindings/legacy/v1.0.0/utils/address_generation.go b/bindings/legacy/v1.0.0/utils/address_generation.go
new file mode 100644
index 000000000..f738113c9
--- /dev/null
+++ b/bindings/legacy/v1.0.0/utils/address_generation.go
@@ -0,0 +1,75 @@
+package utils
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/rocket-pool/rocketpool-go/legacy/v1.0.0/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Combine a node's address and a salt to retreive a new salt compatible with depositing
+func GetNodeSalt(nodeAddress common.Address, salt *big.Int) common.Hash {
+ // Create a new salt by hashing the original and the node address
+ saltBytes := [32]byte{}
+ salt.FillBytes(saltBytes[:])
+ saltHash := crypto.Keccak256Hash(nodeAddress.Bytes(), saltBytes[:])
+ return saltHash
+}
+
+// Precompute the address of a minipool based on the node wallet, deposit type, and unique salt
+// If you set minipoolBytecode to nil, this will retrieve it from the contracts using minipool.GetMinipoolBytecode().
+func GenerateAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, depositType rptypes.MinipoolDeposit, salt *big.Int, minipoolBytecode []byte, legacyRocketMinipoolManagerAddress *common.Address, opts *bind.CallOpts) (common.Address, error) {
+
+ // Get dependencies
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, legacyRocketMinipoolManagerAddress, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAbi, err := rp.GetABI("rocketMinipool", nil)
+ if err != nil {
+ return common.Address{}, err
+ }
+
+ if len(minipoolBytecode) == 0 {
+ minipoolBytecode, err = minipool.GetMinipoolBytecode(rp, nil, legacyRocketMinipoolManagerAddress)
+ if err != nil {
+ return common.Address{}, fmt.Errorf("Error getting minipool bytecode: %w", err)
+ }
+ }
+
+ // Create the hash of the minipool constructor call
+ depositTypeBytes := [32]byte{}
+ depositTypeBytes[0] = byte(depositType)
+ packedConstructorArgs, err := minipoolAbi.Pack("", rp.RocketStorageContract.Address, nodeAddress, depositType)
+ if err != nil {
+ return common.Address{}, fmt.Errorf("Error creating minipool constructor args: %w", err)
+ }
+
+ // Get the node salt and initialization data
+ nodeSalt := GetNodeSalt(nodeAddress, salt)
+ initData := append(minipoolBytecode, packedConstructorArgs...)
+ initHash := crypto.Keccak256(initData)
+
+ address := crypto.CreateAddress2(*rocketMinipoolManager.Address, nodeSalt, initHash)
+ return address, nil
+
+}
+
+// Get contracts
+var rocketMinipoolManagerLock sync.Mutex
+
+func getRocketMinipoolManager(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolManagerLock.Lock()
+ defer rocketMinipoolManagerLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_0_0.GetContract("rocketMinipoolManager", opts)
+ } else {
+ return rp.VersionManager.V1_0_0.GetContractWithAddress("rocketMinipoolManager", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0-rc1/rewards/rewards.go b/bindings/legacy/v1.1.0-rc1/rewards/rewards.go
new file mode 100644
index 000000000..9f1f9b993
--- /dev/null
+++ b/bindings/legacy/v1.1.0-rc1/rewards/rewards.go
@@ -0,0 +1,306 @@
+package rewards
+
+import (
+ "fmt"
+ "math/big"
+ "reflect"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Info for a rewards snapshot event
+type RewardsEvent struct {
+ Index *big.Int
+ ExecutionBlock *big.Int
+ ConsensusBlock *big.Int
+ MerkleRoot common.Hash
+ MerkleTreeCID string
+ IntervalsPassed *big.Int
+ TreasuryRPL *big.Int
+ TrustedNodeRPL []*big.Int
+ NodeRPL []*big.Int
+ NodeETH []*big.Int
+ IntervalStartTime time.Time
+ IntervalEndTime time.Time
+ SubmissionTime time.Time
+}
+
+// Struct for submitting the rewards for a checkpoint
+type RewardSubmission struct {
+ RewardIndex *big.Int `json:"rewardIndex"`
+ ExecutionBlock *big.Int `json:"executionBlock"`
+ ConsensusBlock *big.Int `json:"consensusBlock"`
+ MerkleRoot [32]byte `json:"merkleRoot"`
+ MerkleTreeCID string `json:"merkleTreeCID"`
+ IntervalsPassed *big.Int `json:"intervalsPassed"`
+ TreasuryRPL *big.Int `json:"treasuryRPL"`
+ TrustedNodeRPL []*big.Int `json:"trustedNodeRPL"`
+ NodeRPL []*big.Int `json:"nodeRPL"`
+ NodeETH []*big.Int `json:"nodeETH"`
+}
+
+// Get the index of the active rewards period
+func GetRewardIndex(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ index := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, index, "getRewardIndex"); err != nil {
+ return nil, fmt.Errorf("error getting current reward index: %w", err)
+ }
+ return *index, nil
+}
+
+// Get the timestamp that the current rewards interval started
+func GetClaimIntervalTimeStart(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Time, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTimeStart"); err != nil {
+ return time.Time{}, fmt.Errorf("error getting claim interval time start: %w", err)
+ }
+ return time.Unix((*unixTime).Int64(), 0), nil
+}
+
+// Get the number of seconds in a claim interval
+func GetClaimIntervalTime(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (time.Duration, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTime"); err != nil {
+ return 0, fmt.Errorf("error getting claim interval time: %w", err)
+ }
+ return time.Duration((*unixTime).Int64()) * time.Second, nil
+}
+
+// Get the percent of checkpoint rewards that goes to node operators
+func GetNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimNode"); err != nil {
+ return nil, fmt.Errorf("error getting node operator rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the percent of checkpoint rewards that goes to ODAO members
+func GetTrustedNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimTrustedNode"); err != nil {
+ return nil, fmt.Errorf("error getting trusted node operator rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the percent of checkpoint rewards that goes to the PDAO
+func GetProtocolDaoRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimDAO"); err != nil {
+ return nil, fmt.Errorf("error getting protocol DAO rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the amount of RPL rewards that will be provided to node operators
+func GetPendingRPLRewards(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rewards := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, rewards, "getPendingRPLRewards"); err != nil {
+ return nil, fmt.Errorf("error getting pending RPL rewards: %w", err)
+ }
+ return *rewards, nil
+}
+
+// Get the amount of ETH rewards that will be provided to node operators
+func GetPendingETHRewards(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketRewardsPoolAddress *common.Address) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rewards := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, rewards, "getPendingETHRewards"); err != nil {
+ return nil, fmt.Errorf("error getting pending ETH rewards: %w", err)
+ }
+ return *rewards, nil
+}
+
+// Estimate the gas for submiting a Merkle Tree-based snapshot for a rewards interval
+func EstimateSubmitRewardSnapshotGas(rp *rocketpool.RocketPool, submission RewardSubmission, opts *bind.TransactOpts, legacyRocketRewardsPoolAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketRewardsPool.GetTransactionGasInfo(opts, "submitRewardSnapshot", submission)
+}
+
+// Submit a Merkle Tree-based snapshot for a rewards interval
+func SubmitRewardSnapshot(rp *rocketpool.RocketPool, submission RewardSubmission, opts *bind.TransactOpts, legacyRocketRewardsPoolAddress *common.Address) (common.Hash, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketRewardsPool.Transact(opts, "submitRewardSnapshot", submission)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting rewards snapshot: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the event info for a rewards snapshot
+func GetRewardSnapshotEvent(rp *rocketpool.RocketPool, index uint64, intervalSize *big.Int, startBlock *big.Int, endBlock *big.Int, legacyRocketRewardsPoolAddress *common.Address, opts *bind.CallOpts) (RewardsEvent, error) {
+ // Get contracts
+ rocketRewardsPool, err := getRocketRewardsPool(rp, legacyRocketRewardsPoolAddress, opts)
+ if err != nil {
+ return RewardsEvent{}, err
+ }
+
+ // Construct a filter query for relevant logs
+ indexBig := big.NewInt(0).SetUint64(index)
+ indexBytes := [32]byte{}
+ indexBig.FillBytes(indexBytes[:])
+ addressFilter := []common.Address{*rocketRewardsPool.Address}
+ topicFilter := [][]common.Hash{{rocketRewardsPool.ABI.Events["RewardSnapshot"].ID}, {indexBytes}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, endBlock, nil)
+ if err != nil {
+ return RewardsEvent{}, err
+ }
+
+ // Get the log info
+ values := make(map[string]interface{})
+ if len(logs) == 0 {
+ return RewardsEvent{}, fmt.Errorf("reward snapshot for interval %d not found", index)
+ }
+ err = rocketRewardsPool.ABI.Events["RewardSnapshot"].Inputs.UnpackIntoMap(values, logs[0].Data)
+ if err != nil {
+ return RewardsEvent{}, err
+ }
+
+ // Get the decoded data
+ submissionPrototype := RewardSubmission{}
+ submissionType := reflect.TypeOf(submissionPrototype)
+ submission := reflect.ValueOf(values["submission"]).Convert(submissionType).Interface().(RewardSubmission)
+ eventIntervalStartTime := values["intervalStartTime"].(*big.Int)
+ eventIntervalEndTime := values["intervalEndTime"].(*big.Int)
+ submissionTime := values["time"].(*big.Int)
+ eventData := RewardsEvent{
+ Index: indexBig,
+ ExecutionBlock: submission.ExecutionBlock,
+ ConsensusBlock: submission.ConsensusBlock,
+ IntervalsPassed: submission.IntervalsPassed,
+ TreasuryRPL: submission.TreasuryRPL,
+ TrustedNodeRPL: submission.TrustedNodeRPL,
+ NodeRPL: submission.NodeRPL,
+ NodeETH: submission.NodeETH,
+ MerkleRoot: common.BytesToHash(submission.MerkleRoot[:]),
+ MerkleTreeCID: submission.MerkleTreeCID,
+ IntervalStartTime: time.Unix(eventIntervalStartTime.Int64(), 0),
+ IntervalEndTime: time.Unix(eventIntervalEndTime.Int64(), 0),
+ SubmissionTime: time.Unix(submissionTime.Int64(), 0),
+ }
+
+ return eventData, nil
+
+}
+
+// Get the event info for a rewards snapshot
+func GetRewardSnapshotEventWithUpgrades(rp *rocketpool.RocketPool, index uint64, intervalSize *big.Int, startBlock *big.Int, endBlock *big.Int, rocketRewardsPoolAddresses []common.Address, opts *bind.CallOpts) (bool, RewardsEvent, error) {
+
+ if len(rocketRewardsPoolAddresses) == 0 {
+ return false, RewardsEvent{}, fmt.Errorf("rocketRewardsPoolAddresses must have at least one element.")
+ }
+
+ // Get contracts
+ rocketRewardsPool, err := getRocketRewardsPool(rp, &rocketRewardsPoolAddresses[0], opts)
+ if err != nil {
+ return false, RewardsEvent{}, err
+ }
+
+ // Construct a filter query for relevant logs
+ indexBig := big.NewInt(0).SetUint64(index)
+ indexBytes := [32]byte{}
+ indexBig.FillBytes(indexBytes[:])
+ addressFilter := rocketRewardsPoolAddresses
+ topicFilter := [][]common.Hash{{rocketRewardsPool.ABI.Events["RewardSnapshot"].ID}, {indexBytes}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, endBlock, nil)
+ if err != nil {
+ return false, RewardsEvent{}, err
+ }
+
+ // Get the log info
+ values := make(map[string]interface{})
+ if len(logs) == 0 {
+ return false, RewardsEvent{}, nil
+ }
+ err = rocketRewardsPool.ABI.Events["RewardSnapshot"].Inputs.UnpackIntoMap(values, logs[0].Data)
+ if err != nil {
+ return false, RewardsEvent{}, err
+ }
+
+ // Get the decoded data
+ submissionPrototype := RewardSubmission{}
+ submissionType := reflect.TypeOf(submissionPrototype)
+ submission := reflect.ValueOf(values["submission"]).Convert(submissionType).Interface().(RewardSubmission)
+ eventIntervalStartTime := values["intervalStartTime"].(*big.Int)
+ eventIntervalEndTime := values["intervalEndTime"].(*big.Int)
+ submissionTime := values["time"].(*big.Int)
+ eventData := RewardsEvent{
+ Index: indexBig,
+ ExecutionBlock: submission.ExecutionBlock,
+ ConsensusBlock: submission.ConsensusBlock,
+ IntervalsPassed: submission.IntervalsPassed,
+ TreasuryRPL: submission.TreasuryRPL,
+ TrustedNodeRPL: submission.TrustedNodeRPL,
+ NodeRPL: submission.NodeRPL,
+ NodeETH: submission.NodeETH,
+ MerkleRoot: common.BytesToHash(submission.MerkleRoot[:]),
+ MerkleTreeCID: submission.MerkleTreeCID,
+ IntervalStartTime: time.Unix(eventIntervalStartTime.Int64(), 0),
+ IntervalEndTime: time.Unix(eventIntervalEndTime.Int64(), 0),
+ SubmissionTime: time.Unix(submissionTime.Int64(), 0),
+ }
+
+ return true, eventData, nil
+
+}
+
+// Get contracts
+var rocketRewardsPoolLock sync.Mutex
+
+func getRocketRewardsPool(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketRewardsPoolLock.Lock()
+ defer rocketRewardsPoolLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0_RC1.GetContract("rocketRewardsPool", opts)
+ } else {
+ return rp.VersionManager.V1_1_0_RC1.GetContractWithAddress("rocketRewardsPool", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/minipool/factory.go b/bindings/legacy/v1.1.0/minipool/factory.go
new file mode 100644
index 000000000..32c31fb7f
--- /dev/null
+++ b/bindings/legacy/v1.1.0/minipool/factory.go
@@ -0,0 +1,37 @@
+package minipool
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the CreationCode binary for the RocketMinipool contract that will be created by node deposits
+func GetMinipoolBytecode(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolFactoryAddress *common.Address) ([]byte, error) {
+ rocketMinipoolFactory, err := getRocketMinipoolFactory(rp, legacyRocketMinipoolFactoryAddress, opts)
+ if err != nil {
+ return []byte{}, err
+ }
+ bytecode := new([]byte)
+ if err := rocketMinipoolFactory.Call(opts, bytecode, "getMinipoolBytecode"); err != nil {
+ return []byte{}, fmt.Errorf("error getting minipool contract bytecode: %w", err)
+ }
+ return *bytecode, nil
+}
+
+// Get contracts
+var rocketMinipoolFactoryLock sync.Mutex
+
+func getRocketMinipoolFactory(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolFactoryLock.Lock()
+ defer rocketMinipoolFactoryLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0.GetContract("rocketMinipoolFactory", opts)
+ } else {
+ return rp.VersionManager.V1_1_0.GetContractWithAddress("rocketMinipoolFactory", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/minipool/queue.go b/bindings/legacy/v1.1.0/minipool/queue.go
new file mode 100644
index 000000000..3ba0605df
--- /dev/null
+++ b/bindings/legacy/v1.1.0/minipool/queue.go
@@ -0,0 +1,302 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/storage"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Minipool queue lengths
+type QueueLengths struct {
+ Total uint64
+ FullDeposit uint64
+ HalfDeposit uint64
+ EmptyDeposit uint64
+}
+
+// Minipool queue capacity
+type QueueCapacity struct {
+ Total *big.Int
+ Effective *big.Int
+ NextMinipool *big.Int
+}
+
+// Minipools queue status details
+type QueueDetails struct {
+ Position uint64
+}
+
+// Get minipool queue lengths
+func GetQueueLengths(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (QueueLengths, error) {
+
+ // Data
+ var wg errgroup.Group
+ var total uint64
+ var fullDeposit uint64
+ var halfDeposit uint64
+ var emptyDeposit uint64
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ total, err = GetQueueTotalLength(rp, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ fullDeposit, err = GetQueueLength(rp, rptypes.Full, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ halfDeposit, err = GetQueueLength(rp, rptypes.Half, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ emptyDeposit, err = GetQueueLength(rp, rptypes.Empty, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return QueueLengths{}, err
+ }
+
+ // Return
+ return QueueLengths{
+ Total: total,
+ FullDeposit: fullDeposit,
+ HalfDeposit: halfDeposit,
+ EmptyDeposit: emptyDeposit,
+ }, nil
+
+}
+
+// Get minipool queue capacity
+func GetQueueCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (QueueCapacity, error) {
+
+ // Data
+ var wg errgroup.Group
+ var total *big.Int
+ var effective *big.Int
+ var nextMinipool *big.Int
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ total, err = GetQueueTotalCapacity(rp, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ effective, err = GetQueueEffectiveCapacity(rp, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ nextMinipool, err = GetQueueNextCapacity(rp, opts, legacyRocketMinipoolQueueAddress)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return QueueCapacity{}, err
+ }
+
+ // Return
+ return QueueCapacity{
+ Total: total,
+ Effective: effective,
+ NextMinipool: nextMinipool,
+ }, nil
+
+}
+
+// Get the total length of the minipool queue
+func GetQueueTotalLength(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (uint64, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, legacyRocketMinipoolQueueAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ length := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, length, "getTotalLength"); err != nil {
+ return 0, fmt.Errorf("error getting minipool queue total length: %w", err)
+ }
+ return (*length).Uint64(), nil
+}
+
+// Get the length of a single minipool queue
+func GetQueueLength(rp *rocketpool.RocketPool, depositType rptypes.MinipoolDeposit, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (uint64, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, legacyRocketMinipoolQueueAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ length := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, length, "getLength", depositType); err != nil {
+ return 0, fmt.Errorf("error getting minipool queue length for deposit type %d: %w", depositType, err)
+ }
+ return (*length).Uint64(), nil
+}
+
+// Get the total capacity of the minipool queue
+func GetQueueTotalCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (*big.Int, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, legacyRocketMinipoolQueueAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ capacity := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, capacity, "getTotalCapacity"); err != nil {
+ return nil, fmt.Errorf("error getting minipool queue total capacity: %w", err)
+ }
+ return *capacity, nil
+}
+
+// Get the total effective capacity of the minipool queue (used in node demand calculation)
+func GetQueueEffectiveCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (*big.Int, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, legacyRocketMinipoolQueueAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ capacity := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, capacity, "getEffectiveCapacity"); err != nil {
+ return nil, fmt.Errorf("error getting minipool queue effective capacity: %w", err)
+ }
+ return *capacity, nil
+}
+
+// Get the capacity of the next minipool in the queue
+func GetQueueNextCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (*big.Int, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, legacyRocketMinipoolQueueAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ capacity := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, capacity, "getNextCapacity"); err != nil {
+ return nil, fmt.Errorf("error getting minipool queue next item capacity: %w", err)
+ }
+ return *capacity, nil
+}
+
+// Get Queue position details of a minipool
+func GetQueueDetails(rp *rocketpool.RocketPool, mp minipool.Minipool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (QueueDetails, error) {
+ position, err := GetQueuePositionOfMinipool(rp, mp, opts, legacyRocketMinipoolQueueAddress)
+ if err != nil {
+ return QueueDetails{}, err
+ }
+
+ // Return
+ return QueueDetails{
+ Position: position,
+ }, nil
+}
+
+// Get a minipools position in queue (1-indexed). 0 means it is currently not queued.
+func GetQueuePositionOfMinipool(rp *rocketpool.RocketPool, mp minipool.Minipool, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (uint64, error) {
+ depositType, err := mp.GetDepositType(opts)
+ if err != nil {
+ return 0, fmt.Errorf("error getting deposit type: %w", err)
+ }
+ if depositType == rptypes.None {
+ return 0, fmt.Errorf("Minipool address %s has no deposit type", mp.GetAddress())
+ }
+
+ queryIndex := func(key string) (uint64, error) {
+ index, err := storage.GetAddressQueueIndexOf(rp, opts, crypto.Keccak256Hash([]byte(key)), mp.GetAddress())
+ if err != nil {
+ return 0, fmt.Errorf("error getting queue index for address %s: %w", mp.GetAddress(), err)
+ }
+ return uint64(index + 1), nil
+ }
+
+ position := uint64(0)
+
+ // half cleared first
+ if depositType != rptypes.Half {
+ position, err = GetQueueLength(rp, rptypes.Half, opts, legacyRocketMinipoolQueueAddress)
+ if err != nil {
+ return 0, fmt.Errorf("error getting queue length of type %s: %w", rptypes.MinipoolDepositTypes[rptypes.Empty], err)
+ }
+ } else {
+ return queryIndex("minipools.available.half")
+ }
+
+ // full deposits next
+ if depositType != rptypes.Full {
+ length, err := GetQueueLength(rp, rptypes.Full, opts, legacyRocketMinipoolQueueAddress)
+ if err != nil {
+ return 0, fmt.Errorf("error getting queue length of type %s: %w", rptypes.MinipoolDepositTypes[rptypes.Empty], err)
+ }
+ position += length
+ } else {
+ index, err := queryIndex("minipools.available.full")
+ if err != nil || index == 0 {
+ return 0, err
+ }
+ return position + index, nil
+ }
+
+ // must be empty type now
+ index, err := queryIndex("minipools.available.empty")
+ if err != nil || index == 0 {
+ return 0, err
+ }
+ return position + index, nil
+}
+
+// Get the minipool at the specified position in queue (0-indexed).
+func GetQueueMinipoolAtPosition(rp *rocketpool.RocketPool, position uint64, opts *bind.CallOpts, legacyRocketMinipoolQueueAddress *common.Address) (minipool.Minipool, error) {
+ totalLength, err := GetQueueTotalLength(rp, opts, legacyRocketMinipoolQueueAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error getting total queue length: %w", err)
+ }
+ if position >= totalLength {
+ return nil, fmt.Errorf("error getting index %d beyond queue length %d", position, totalLength)
+ }
+ lengths, err := GetQueueLengths(rp, opts, legacyRocketMinipoolQueueAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error getting queue lengths: %w", err)
+ }
+
+ getMinipool := func(key string) (minipool.Minipool, error) {
+ pos := big.NewInt(int64(position))
+ address, err := storage.GetAddressQueueItem(rp, opts, crypto.Keccak256Hash([]byte(key)), pos)
+ if err != nil {
+ return nil, fmt.Errorf("error getting address in queue at position %d: %w", position, err)
+ }
+ return minipool.NewMinipool(rp, address, opts)
+ }
+
+ if position < lengths.HalfDeposit {
+ return getMinipool("minipools.available.half")
+ }
+ position -= lengths.HalfDeposit
+ if position < lengths.FullDeposit {
+ return getMinipool("minipools.available.full")
+ }
+ position -= lengths.FullDeposit
+ return getMinipool("minipools.available.empty")
+}
+
+// Get contracts
+var rocketMinipoolQueueLock sync.Mutex
+
+func getRocketMinipoolQueue(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolQueueLock.Lock()
+ defer rocketMinipoolQueueLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0.GetContract("rocketMinipoolQueue", opts)
+ } else {
+ return rp.VersionManager.V1_1_0.GetContractWithAddress("rocketMinipoolQueue", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/network/prices.go b/bindings/legacy/v1.1.0/network/prices.go
new file mode 100644
index 000000000..3a947e2e2
--- /dev/null
+++ b/bindings/legacy/v1.1.0/network/prices.go
@@ -0,0 +1,99 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the block number which network prices are current for
+func GetPricesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (uint64, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ pricesBlock := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, pricesBlock, "getPricesBlock"); err != nil {
+ return 0, fmt.Errorf("error getting network prices block: %w", err)
+ }
+ return (*pricesBlock).Uint64(), nil
+}
+
+// Get the current network RPL price in ETH
+func GetRPLPrice(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rplPrice := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, rplPrice, "getRPLPrice"); err != nil {
+ return nil, fmt.Errorf("error getting network RPL price: %w", err)
+ }
+ return *rplPrice, nil
+}
+
+// Estimate the gas of SubmitPrices
+func EstimateSubmitPricesGas(rp *rocketpool.RocketPool, block uint64, rplPrice *big.Int, effectiveRplStake *big.Int, opts *bind.TransactOpts, legacyRocketNetworkPricesAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkPrices.GetTransactionGasInfo(opts, "submitPrices", big.NewInt(int64(block)), rplPrice, effectiveRplStake)
+}
+
+// Submit network prices and total effective RPL stake for an epoch
+func SubmitPrices(rp *rocketpool.RocketPool, block uint64, rplPrice, effectiveRplStake *big.Int, opts *bind.TransactOpts, legacyRocketNetworkPricesAddress *common.Address) (common.Hash, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkPrices.Transact(opts, "submitPrices", big.NewInt(int64(block)), rplPrice, effectiveRplStake)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting network prices: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Check if the network is currently in consensus about the RPL price, or if it is still reaching consensus
+func InConsensus(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (bool, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return false, err
+ }
+ isInConsensus := new(bool)
+ if err := rocketNetworkPrices.Call(opts, isInConsensus, "inConsensus"); err != nil {
+ return false, fmt.Errorf("error getting consensus status: %w", err)
+ }
+ return *isInConsensus, nil
+}
+
+// Returns the latest block number that oracles should be reporting prices for
+func GetLatestReportablePricesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ latestReportableBlock := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, latestReportableBlock, "getLatestReportableBlock"); err != nil {
+ return nil, fmt.Errorf("error getting latest reportable block: %w", err)
+ }
+ return *latestReportableBlock, nil
+}
+
+// Get contracts
+var rocketNetworkPricesLock sync.Mutex
+
+func getRocketNetworkPrices(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkPricesLock.Lock()
+ defer rocketNetworkPricesLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0.GetContract("rocketNetworkPrices", opts)
+ } else {
+ return rp.VersionManager.V1_1_0.GetContractWithAddress("rocketNetworkPrices", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/node/deposit.go b/bindings/legacy/v1.1.0/node/deposit.go
new file mode 100644
index 000000000..f5a171eb1
--- /dev/null
+++ b/bindings/legacy/v1.1.0/node/deposit.go
@@ -0,0 +1,64 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Estimate the gas of Deposit
+func EstimateDepositGas(rp *rocketpool.RocketPool, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts, legacyRocketNodeDepositAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, legacyRocketNodeDepositAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeDeposit.GetTransactionGasInfo(opts, "deposit", eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+}
+
+// Make a node deposit
+func Deposit(rp *rocketpool.RocketPool, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts, legacyRocketNodeDepositAddress *common.Address) (*types.Transaction, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, legacyRocketNodeDepositAddress, nil)
+ if err != nil {
+ return nil, err
+ }
+ tx, err := rocketNodeDeposit.Transact(opts, "deposit", eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error making node deposit: %w", err)
+ }
+ return tx, nil
+}
+
+// Get the type of a deposit based on the amount
+func GetDepositType(rp *rocketpool.RocketPool, amount *big.Int, opts *bind.CallOpts, legacyRocketNodeDepositAddress *common.Address) (rptypes.MinipoolDeposit, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, legacyRocketNodeDepositAddress, opts)
+ if err != nil {
+ return rptypes.Empty, err
+ }
+
+ depositType := new(uint8)
+ if err := rocketNodeDeposit.Call(opts, depositType, "getDepositType", amount); err != nil {
+ return rptypes.Empty, fmt.Errorf("error getting deposit type: %w", err)
+ }
+ return rptypes.MinipoolDeposit(*depositType), nil
+}
+
+// Get contracts
+var rocketNodeDepositLock sync.Mutex
+
+func getRocketNodeDeposit(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeDepositLock.Lock()
+ defer rocketNodeDepositLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0.GetContract("rocketNodeDeposit", opts)
+ } else {
+ return rp.VersionManager.V1_1_0.GetContractWithAddress("rocketNodeDeposit", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/node/staking.go b/bindings/legacy/v1.1.0/node/staking.go
new file mode 100644
index 000000000..29dc307b6
--- /dev/null
+++ b/bindings/legacy/v1.1.0/node/staking.go
@@ -0,0 +1,211 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the version of the Node Staking contract
+func GetNodeStakingVersion(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (uint8, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ version := new(uint8)
+ if err := rocketNodeStaking.Call(opts, version, "version"); err != nil {
+ return 0, fmt.Errorf("error getting node staking version: %w", err)
+ }
+ return *version, nil
+}
+
+// Get the total RPL staked in the network
+func GetTotalRPLStake(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, totalRplStake, "getTotalRPLStake"); err != nil {
+ return nil, fmt.Errorf("error getting total network RPL stake: %w", err)
+ }
+ return *totalRplStake, nil
+}
+
+// Get the effective RPL staked in the network
+func GetTotalEffectiveRPLStake(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalEffectiveRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, totalEffectiveRplStake, "getTotalEffectiveRPLStake"); err != nil {
+ return nil, fmt.Errorf("error getting effective network RPL stake: %w", err)
+ }
+ return *totalEffectiveRplStake, nil
+}
+
+// Get a node's RPL stake
+func GetNodeRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeRplStake, "getNodeRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting total node RPL stake: %w", err)
+ }
+ return *nodeRplStake, nil
+}
+
+// Get a node's effective RPL stake
+func GetNodeEffectiveRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeEffectiveRplStakeWrapper := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeEffectiveRplStakeWrapper, "getNodeEffectiveRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting effective node RPL stake: %w", err)
+ }
+
+ minimumStake, err := GetNodeMinimumRPLStake(rp, nodeAddress, opts, legacyRocketNodeStakingAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minimum node RPL stake to verify effective stake: %w", err)
+ }
+
+ nodeEffectiveRplStake := *nodeEffectiveRplStakeWrapper
+ if nodeEffectiveRplStake.Cmp(minimumStake) == -1 {
+ // Effective stake should be zero if it's less than the minimum RPL stake
+ return big.NewInt(0), nil
+ }
+
+ return nodeEffectiveRplStake, nil
+}
+
+// Get a node's minimum RPL stake to collateralize their minipools
+func GetNodeMinimumRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeMinimumRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeMinimumRplStake, "getNodeMinimumRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting minimum node RPL stake: %w", err)
+ }
+ return *nodeMinimumRplStake, nil
+}
+
+// Get a node's maximum RPL stake to collateralize their minipools
+func GetNodeMaximumRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeMaximumRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeMaximumRplStake, "getNodeMaximumRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting maximum node RPL stake: %w", err)
+ }
+ return *nodeMaximumRplStake, nil
+}
+
+// Get the time a node last staked RPL
+func GetNodeRPLStakedTime(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (uint64, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ nodeRplStakedTime := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeRplStakedTime, "getNodeRPLStakedTime", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node RPL staked time: %w", err)
+ }
+ return (*nodeRplStakedTime).Uint64(), nil
+}
+
+// Get a node's minipool limit based on RPL stake
+func GetNodeMinipoolLimit(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (uint64, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolLimit := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, minipoolLimit, "getNodeMinipoolLimit", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node minipool limit: %w", err)
+ }
+ return (*minipoolLimit).Uint64(), nil
+}
+
+// Estimate the gas of Stake
+func EstimateStakeGas(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts, legacyRocketNodeStakingAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "stakeRPL", rplAmount)
+}
+
+// Stake RPL
+func StakeRPL(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts, legacyRocketNodeStakingAddress *common.Address) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "stakeRPL", rplAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error staking RPL: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of WithdrawRPL
+func EstimateWithdrawRPLGas(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts, legacyRocketNodeStakingAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "withdrawRPL", rplAmount)
+}
+
+// Withdraw staked RPL
+func WithdrawRPL(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts, legacyRocketNodeStakingAddress *common.Address) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "withdrawRPL", rplAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error withdrawing staked RPL: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Calculate total effective RPL stake
+func CalculateTotalEffectiveRPLStake(rp *rocketpool.RocketPool, offset, limit, rplPrice *big.Int, opts *bind.CallOpts, legacyRocketNodeStakingAddress *common.Address) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, legacyRocketNodeStakingAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalEffectiveRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, totalEffectiveRplStake, "calculateTotalEffectiveRPLStake", offset, limit, rplPrice); err != nil {
+ return nil, fmt.Errorf("error getting total effective RPL stake: %w", err)
+ }
+ return *totalEffectiveRplStake, nil
+}
+
+// Get contracts
+var rocketNodeStakingLock sync.Mutex
+
+func getRocketNodeStaking(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeStakingLock.Lock()
+ defer rocketNodeStakingLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_1_0.GetContract("rocketNodeStaking", opts)
+ } else {
+ return rp.VersionManager.V1_1_0.GetContractWithAddress("rocketNodeStaking", *address)
+ }
+}
diff --git a/bindings/legacy/v1.1.0/utils/address_generation.go b/bindings/legacy/v1.1.0/utils/address_generation.go
new file mode 100644
index 000000000..1faa9a472
--- /dev/null
+++ b/bindings/legacy/v1.1.0/utils/address_generation.go
@@ -0,0 +1,71 @@
+package utils
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ v110_minipool "github.com/rocket-pool/rocketpool-go/legacy/v1.1.0/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Combine a node's address and a salt to retreive a new salt compatible with depositing
+func GetNodeSalt(nodeAddress common.Address, salt *big.Int) common.Hash {
+ // Create a new salt by hashing the original and the node address
+ saltBytes := [32]byte{}
+ salt.FillBytes(saltBytes[:])
+ saltHash := crypto.Keccak256Hash(nodeAddress.Bytes(), saltBytes[:])
+ return saltHash
+}
+
+// Precompute the address of a minipool based on the node wallet, deposit type, and unique salt
+// If you set minipoolBytecode to nil, this will retrieve it from the contracts using minipool.GetMinipoolBytecode().
+func GenerateAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, depositType rptypes.MinipoolDeposit, salt *big.Int, minipoolBytecode []byte, opts *bind.CallOpts, legacyRocketMinipoolFactoryAddress *common.Address) (common.Address, error) {
+
+ // Get dependencies
+ rocketMinipoolFactory, err := getRocketMinipoolFactory(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAbi, err := rp.GetABI("rocketMinipool", opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+
+ if len(minipoolBytecode) == 0 {
+ minipoolBytecode, err = v110_minipool.GetMinipoolBytecode(rp, nil, legacyRocketMinipoolFactoryAddress)
+ if err != nil {
+ return common.Address{}, fmt.Errorf("Error getting minipool bytecode: %w", err)
+ }
+ }
+
+ // Create the hash of the minipool constructor call
+ depositTypeBytes := [32]byte{}
+ depositTypeBytes[0] = byte(depositType)
+ packedConstructorArgs, err := minipoolAbi.Pack("", rp.RocketStorageContract.Address, nodeAddress, depositType)
+ if err != nil {
+ return common.Address{}, fmt.Errorf("Error creating minipool constructor args: %w", err)
+ }
+
+ // Get the node salt and initialization data
+ nodeSalt := GetNodeSalt(nodeAddress, salt)
+ initData := append(minipoolBytecode, packedConstructorArgs...)
+ initHash := crypto.Keccak256(initData)
+
+ address := crypto.CreateAddress2(*rocketMinipoolFactory.Address, nodeSalt, initHash)
+ return address, nil
+
+}
+
+// Get contracts
+var rocketMinipoolFactoryLock sync.Mutex
+
+func getRocketMinipoolFactory(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolFactoryLock.Lock()
+ defer rocketMinipoolFactoryLock.Unlock()
+ return rp.GetContract("rocketMinipoolFactory", opts)
+}
diff --git a/bindings/legacy/v1.2.0/network/balances.go b/bindings/legacy/v1.2.0/network/balances.go
new file mode 100644
index 000000000..9c3b044e1
--- /dev/null
+++ b/bindings/legacy/v1.2.0/network/balances.go
@@ -0,0 +1,138 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Get the block number which network balances are current for
+func GetBalancesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (uint64, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ balancesBlock := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, balancesBlock, "getBalancesBlock"); err != nil {
+ return 0, fmt.Errorf("Could not get network balances block: %w", err)
+ }
+ return (*balancesBlock).Uint64(), nil
+}
+
+// Get the block number which network balances are current for
+func GetBalancesBlockRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ balancesBlock := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, balancesBlock, "getBalancesBlock"); err != nil {
+ return nil, fmt.Errorf("Could not get network balances block: %w", err)
+ }
+ return *balancesBlock, nil
+}
+
+// Get the current network total ETH balance
+func GetTotalETHBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalEthBalance := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, totalEthBalance, "getTotalETHBalance"); err != nil {
+ return nil, fmt.Errorf("Could not get network total ETH balance: %w", err)
+ }
+ return *totalEthBalance, nil
+}
+
+// Get the current network staking ETH balance
+func GetStakingETHBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ stakingEthBalance := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, stakingEthBalance, "getStakingETHBalance"); err != nil {
+ return nil, fmt.Errorf("Could not get network staking ETH balance: %w", err)
+ }
+ return *stakingEthBalance, nil
+}
+
+// Get the current network total rETH supply
+func GetTotalRETHSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalRethSupply := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, totalRethSupply, "getTotalRETHSupply"); err != nil {
+ return nil, fmt.Errorf("Could not get network total rETH supply: %w", err)
+ }
+ return *totalRethSupply, nil
+}
+
+// Get the current network ETH utilization rate
+func GetETHUtilizationRate(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (float64, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ ethUtilizationRate := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, ethUtilizationRate, "getETHUtilizationRate"); err != nil {
+ return 0, fmt.Errorf("Could not get network ETH utilization rate: %w", err)
+ }
+ return eth.WeiToEth(*ethUtilizationRate), nil
+}
+
+// Estimate the gas of SubmitBalances
+func EstimateSubmitBalancesGas(rp *rocketpool.RocketPool, block uint64, totalEth, stakingEth, rethSupply *big.Int, opts *bind.TransactOpts, legacyRocketNetworkBalancesAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkBalances.GetTransactionGasInfo(opts, "submitBalances", big.NewInt(int64(block)), totalEth, stakingEth, rethSupply)
+}
+
+// Submit network balances for an epoch
+func SubmitBalances(rp *rocketpool.RocketPool, block uint64, totalEth, stakingEth, rethSupply *big.Int, opts *bind.TransactOpts, legacyRocketNetworkBalancesAddress *common.Address) (common.Hash, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkBalances.Transact(opts, "submitBalances", big.NewInt(int64(block)), totalEth, stakingEth, rethSupply)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("Could not submit network balances: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Returns the latest block number that oracles should be reporting balances for
+func GetLatestReportableBalancesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkBalancesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, legacyRocketNetworkBalancesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ latestReportableBlock := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, latestReportableBlock, "getLatestReportableBlock"); err != nil {
+ return nil, fmt.Errorf("Could not get latest reportable block: %w", err)
+ }
+ return *latestReportableBlock, nil
+}
+
+// Get contracts
+var rocketNetworkBalancesLock sync.Mutex
+
+func getRocketNetworkBalances(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkBalancesLock.Lock()
+ defer rocketNetworkBalancesLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_2_0.GetContract("rocketNetworkBalances", opts)
+ }
+ return rp.VersionManager.V1_2_0.GetContractWithAddress("rocketNetworkBalances", *address)
+}
diff --git a/bindings/legacy/v1.2.0/network/prices.go b/bindings/legacy/v1.2.0/network/prices.go
new file mode 100644
index 000000000..287658b22
--- /dev/null
+++ b/bindings/legacy/v1.2.0/network/prices.go
@@ -0,0 +1,85 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the block number which network prices are current for
+func GetPricesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (uint64, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return 0, err
+ }
+ pricesBlock := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, pricesBlock, "getPricesBlock"); err != nil {
+ return 0, fmt.Errorf("Could not get network prices block: %w", err)
+ }
+ return (*pricesBlock).Uint64(), nil
+}
+
+// Get the current network RPL price in ETH
+func GetRPLPrice(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ rplPrice := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, rplPrice, "getRPLPrice"); err != nil {
+ return nil, fmt.Errorf("Could not get network RPL price: %w", err)
+ }
+ return *rplPrice, nil
+}
+
+// Estimate the gas of SubmitPrices
+func EstimateSubmitPricesGas(rp *rocketpool.RocketPool, block uint64, rplPrice *big.Int, opts *bind.TransactOpts, legacyRocketNetworkPricesAddress *common.Address) (rocketpool.GasInfo, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkPrices.GetTransactionGasInfo(opts, "submitPrices", big.NewInt(int64(block)), rplPrice)
+}
+
+// Submit network prices and total effective RPL stake for an epoch
+func SubmitPrices(rp *rocketpool.RocketPool, block uint64, rplPrice *big.Int, opts *bind.TransactOpts, legacyRocketNetworkPricesAddress *common.Address) (common.Hash, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkPrices.Transact(opts, "submitPrices", big.NewInt(int64(block)), rplPrice)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("Could not submit network prices: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Returns the latest block number that oracles should be reporting prices for
+func GetLatestReportablePricesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts, legacyRocketNetworkPricesAddress *common.Address) (*big.Int, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, legacyRocketNetworkPricesAddress, opts)
+ if err != nil {
+ return nil, err
+ }
+ latestReportableBlock := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, latestReportableBlock, "getLatestReportableBlock"); err != nil {
+ return nil, fmt.Errorf("Could not get latest reportable block: %w", err)
+ }
+ return *latestReportableBlock, nil
+}
+
+// Get contracts
+var rocketNetworkPricesLock sync.Mutex
+
+func getRocketNetworkPrices(rp *rocketpool.RocketPool, address *common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkPricesLock.Lock()
+ defer rocketNetworkPricesLock.Unlock()
+ if address == nil {
+ return rp.VersionManager.V1_2_0.GetContract("rocketNetworkPrices", opts)
+ }
+ return rp.VersionManager.V1_2_0.GetContractWithAddress("rocketNetworkPrices", *address)
+}
diff --git a/bindings/minipool/bond-reducer.go b/bindings/minipool/bond-reducer.go
new file mode 100644
index 000000000..db4e186c4
--- /dev/null
+++ b/bindings/minipool/bond-reducer.go
@@ -0,0 +1,143 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas required to vote to cancel a minipool's bond reduction
+func EstimateVoteCancelReductionGas(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketMinipoolBondReducer.GetTransactionGasInfo(opts, "voteCancelReduction", minipoolAddress)
+}
+
+// Vote to cancel a minipool's bond reduction
+func VoteCancelReduction(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketMinipoolBondReducer.Transact(opts, "voteCancelReduction", minipoolAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting to cancel bond reduction for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Gets whether or not the bond reduction process for this minipool has already been cancelled
+func GetReduceBondCancelled(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ isCancelled := new(bool)
+ if err := rocketMinipoolBondReducer.Call(opts, isCancelled, "getReduceBondCancelled", minipoolAddress); err != nil {
+ return false, fmt.Errorf("error getting reduce bond cancelled status for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return *isCancelled, nil
+}
+
+// Gets the time at which the MP owner started the bond reduction process
+func GetReduceBondTime(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (time.Time, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ reduceBondTime := new(*big.Int)
+ if err := rocketMinipoolBondReducer.Call(opts, reduceBondTime, "getReduceBondTime", minipoolAddress); err != nil {
+ return time.Time{}, fmt.Errorf("error getting reduce bond time for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return time.Unix((*reduceBondTime).Int64(), 0), nil
+}
+
+// Gets the amount of ETH a minipool is reducing its bond to
+func GetReduceBondValue(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ reduceBondValue := new(*big.Int)
+ if err := rocketMinipoolBondReducer.Call(opts, reduceBondValue, "getReduceBondValue", minipoolAddress); err != nil {
+ return nil, fmt.Errorf("error getting reduce bond value for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return *reduceBondValue, nil
+}
+
+// Gets the timestamp at which the bond was last reduced
+func GetLastBondReductionTime(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (time.Time, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return time.Time{}, err
+ }
+ lastBondReductionTime := new(*big.Int)
+ if err := rocketMinipoolBondReducer.Call(opts, lastBondReductionTime, "getLastBondReductionTime", minipoolAddress); err != nil {
+ return time.Time{}, fmt.Errorf("error getting last bond reduction time for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return time.Unix((*lastBondReductionTime).Int64(), 0), nil
+}
+
+// Gets the previous bond amount of the minipool prior to its last reduction
+func GetLastBondReductionPrevValue(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ lastBondReductionPrevValue := new(*big.Int)
+ if err := rocketMinipoolBondReducer.Call(opts, lastBondReductionPrevValue, "getLastBondReductionPrevValue", minipoolAddress); err != nil {
+ return nil, fmt.Errorf("error getting last bond reduction previous value for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return *lastBondReductionPrevValue, nil
+}
+
+// Gets the previous node fee (commission) of the minipool prior to its last reduction
+func GetLastBondReductionPrevNodeFee(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ lastBondReductionPrevNodeFee := new(*big.Int)
+ if err := rocketMinipoolBondReducer.Call(opts, lastBondReductionPrevNodeFee, "getLastBondReductionPrevNodeFee", minipoolAddress); err != nil {
+ return nil, fmt.Errorf("error getting last bond reduction previous node fee for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return *lastBondReductionPrevNodeFee, nil
+}
+
+// Estimate the gas required to begin a minipool bond reduction
+func EstimateBeginReduceBondAmountGas(rp *rocketpool.RocketPool, minipoolAddress common.Address, newBondAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketMinipoolBondReducer.GetTransactionGasInfo(opts, "beginReduceBondAmount", minipoolAddress, newBondAmount)
+}
+
+// Begin a minipool bond reduction
+func BeginReduceBondAmount(rp *rocketpool.RocketPool, minipoolAddress common.Address, newBondAmount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketMinipoolBondReducer, err := getRocketMinipoolBondReducer(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketMinipoolBondReducer.Transact(opts, "beginReduceBondAmount", minipoolAddress, newBondAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error beginning bond reduction for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketMinipoolBondReducerLock sync.Mutex
+
+func getRocketMinipoolBondReducer(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolBondReducerLock.Lock()
+ defer rocketMinipoolBondReducerLock.Unlock()
+ return rp.GetContract("rocketMinipoolBondReducer", opts)
+}
diff --git a/bindings/minipool/factory.go b/bindings/minipool/factory.go
new file mode 100644
index 000000000..3cd1800c3
--- /dev/null
+++ b/bindings/minipool/factory.go
@@ -0,0 +1,34 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the address of a minipool based on the node address and a salt
+func GetExpectedAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, salt *big.Int, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolFactory, err := getRocketMinipoolFactory(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ address := new(common.Address)
+ if err := rocketMinipoolFactory.Call(opts, address, "getExpectedAddress", nodeAddress, salt); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool expected address: %w", err)
+ }
+ return *address, nil
+}
+
+// Get contracts
+var rocketMinipoolFactoryLock sync.Mutex
+
+func getRocketMinipoolFactory(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolFactoryLock.Lock()
+ defer rocketMinipoolFactoryLock.Unlock()
+ return rp.GetContract("rocketMinipoolFactory", opts)
+}
diff --git a/bindings/minipool/minipool-constructor.go b/bindings/minipool/minipool-constructor.go
new file mode 100644
index 000000000..b5c7f4afd
--- /dev/null
+++ b/bindings/minipool/minipool-constructor.go
@@ -0,0 +1,88 @@
+package minipool
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Create a minipool binding
+func NewMinipool(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (Minipool, error) {
+
+ // Get the contract version
+ version, err := rocketpool.GetContractVersion(rp, address, opts)
+ if err != nil {
+ errMsg := err.Error()
+ errMsg = strings.ToLower(errMsg)
+ if strings.Contains(errMsg, "execution reverted") ||
+ strings.Contains(errMsg, "vm execution error") {
+ // Reversions happen for minipool v1 on Prater which didn't have version() yet
+ version = 1
+ } else {
+ return nil, fmt.Errorf("error getting minipool contract version: %w", err)
+ }
+ }
+
+ switch version {
+ case 1, 2:
+ return newMinipool_v2(rp, address)
+ case 3:
+ return newMinipool_v3(rp, address, opts)
+ default:
+ return nil, fmt.Errorf("unexpected minipool contract version [%d]", version)
+ }
+}
+
+// Create a minipool binding from an explicit version number
+func NewMinipoolFromVersion(rp *rocketpool.RocketPool, address common.Address, version uint8, opts *bind.CallOpts) (Minipool, error) {
+ switch version {
+ case 1, 2:
+ return newMinipool_v2(rp, address)
+ case 3:
+ return newMinipool_v3(rp, address, opts)
+ default:
+ return nil, fmt.Errorf("unexpected minipool contract version [%d]", version)
+ }
+}
+
+// Create a minipool contract directly from its ABI, encoded in string form
+func createMinipoolContractFromEncodedAbi(rp *rocketpool.RocketPool, address common.Address, encodedAbi string) (*rocketpool.Contract, error) {
+ // Decode ABI
+ abi, err := rocketpool.DecodeAbi(encodedAbi)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding minipool %s ABI: %w", address, err)
+ }
+
+ // Create and return
+ return &rocketpool.Contract{
+ Contract: bind.NewBoundContract(address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: abi,
+ Client: rp.Client,
+ }, nil
+}
+
+// Create a minipool contract directly from its ABI
+func createMinipoolContractFromAbi(rp *rocketpool.RocketPool, address common.Address, abi *abi.ABI) (*rocketpool.Contract, error) {
+ // Create and return
+ return &rocketpool.Contract{
+ Contract: bind.NewBoundContract(address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: abi,
+ Client: rp.Client,
+ }, nil
+}
+
+// Get a minipool contract
+var rocketMinipoolLock sync.Mutex
+
+func getMinipoolContract(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolLock.Lock()
+ defer rocketMinipoolLock.Unlock()
+ return rp.MakeContract("rocketMinipool", minipoolAddress, opts)
+}
diff --git a/bindings/minipool/minipool-contract-v2.go b/bindings/minipool/minipool-contract-v2.go
new file mode 100644
index 000000000..f050dac3b
--- /dev/null
+++ b/bindings/minipool/minipool-contract-v2.go
@@ -0,0 +1,610 @@
+package minipool
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/storage"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+const (
+ minipoolV2EncodedAbi string = "eJzdWd1v2jAQ/1cqnvvUaVPVt3ZdpUnrVEG7PVQVcpIDLIyN7HMYqva/7xwgHyRAKHGT7qkNXO5+97vzfZjn1x6TSi5nypre1YgJA+c9LucW6fH5lf6N4A9EvSvUNvkGQUsmHpdz6F31WBRpMKZ33pNs5j4YaTWjJyx+/fc8pyi1UdBk6fni85dMEyNEEjNdG4G36EJOf8qaXlKBbzgBfQtzZTiS3lQUYiAMzmSTJJFsaAt2TiFKqgiuGyTLGtBN6kOFTNwwwWRYFQRv4fzNcRJptmDiQauQ2PUfWFQfNfc3ZMm3U1SNJ1gi5BiKmeARQ6UfbDCFZWZtJVfDwV0KB3wsGVoNb9X56SLTGq1KwS1D1lcKt1SS5DtHdcvpRZraXzVEFCZOb73B7+OT5Z5LPleKjhQYZNNTjlTTkAahtkHg/5DPYBaAbuagH3QuceqXar4pOVuXGRAKJlpThHLpyaXE1NOcTm21V6kP2TsxaMOVK07KYs7DfS6VnCF1zk24t8gCLjgunWYOi0xyZGWIztAOHGPAwYapPUhA2tlZmpebF/ziuuNkn6+a3B5oASGqwpJ83ihFN0KF08MRKyRPdeI0BulxlZttISpZK9WW4c7iUvQmXxVaDvZ6ak4s1j8U67e8n4qfbjhOWd6DrhSK6hg0BOkO2szDEpx1NLIhvTPI+kCCUQeBrSm7Nobmzi6cwyeTbrAdoyuHrJN0bUC13B0K8B7d0pyW+QO1q+2mJQtFtnIsPqITDKNCRyl1hbUYve7WHpp4CuRU+klj8pwtWSDgaG+3Nq9hrQW2noYDG+v+DXV4eEXNuJJZwTpMVk2mMu02O0oetOukAzQZ40y3EcxM/KgercdxP9pDJgdu/W6ljnYxw02JjeaSBDC9Sly96MFIxA1qHliEdfO+ltGd1xQqWfRaRkstahjsvBHOp7kIrSAYbuIaTJju1PZ2ok9uAmnbp0I6GCViX/VKKF95HNN8lAxKXvM3VBI1C/Gsr8Kpu05Qmo3huxMasSTimxzQeYFjpqK256p6fBERVDdsSP6dfNdb8liJ6BYEjAnILoePUyhhcZLC4283N+b6SgigxTW5Amv0hvx/Zu1pPtYs8n+H/5F/pu5DCDyu5qjOvM2ECFxa1pTXK3M7+0Yxcp5mldyhCtjWrXLjG19hah7S+Idcjium52w+pFb+gxAYzB0bDzSMD1lq6QK4DpL3u1990BBzKhNdw/VtNAKSiaElYC//AGQZTdM="
+)
+
+type MinipoolV2 interface {
+ Minipool
+ EstimateDistributeBalanceAndFinaliseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ DistributeBalanceAndFinalise(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateDistributeBalanceGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ DistributeBalance(opts *bind.TransactOpts) (common.Hash, error)
+}
+
+// Minipool contract
+type minipool_v2 struct {
+ Address common.Address
+ Version uint8
+ Contract *rocketpool.Contract
+ RocketPool *rocketpool.RocketPool
+}
+
+// The decoded ABI for v2 minipools
+var minipoolV2Abi *abi.ABI
+
+// Create new minipool contract
+func newMinipool_v2(rp *rocketpool.RocketPool, address common.Address) (Minipool, error) {
+
+ var contract *rocketpool.Contract
+ var err error
+ if minipoolV2Abi == nil {
+ // Get contract
+ contract, err = createMinipoolContractFromEncodedAbi(rp, address, minipoolV2EncodedAbi)
+ } else {
+ contract, err = createMinipoolContractFromAbi(rp, address, minipoolV2Abi)
+ }
+ if err != nil {
+ return nil, err
+ } else if minipoolV2Abi == nil {
+ minipoolV2Abi = contract.ABI
+ }
+
+ // Create and return
+ return &minipool_v2{
+ Address: address,
+ Version: 2,
+ Contract: contract,
+ RocketPool: rp,
+ }, nil
+}
+
+// Get the minipool as a v2 minipool if it implements the required methods
+func GetMinipoolAsV2(mp Minipool) (MinipoolV2, bool) {
+ castedMp, ok := mp.(MinipoolV2)
+ if ok {
+ return castedMp, true
+ }
+ return nil, false
+}
+
+// Get the contract
+func (mp *minipool_v2) GetContract() *rocketpool.Contract {
+ return mp.Contract
+}
+
+// Get the contract address
+func (mp *minipool_v2) GetAddress() common.Address {
+ return mp.Address
+}
+
+// Get the contract version
+func (mp *minipool_v2) GetVersion() uint8 {
+ return mp.Version
+}
+
+// Get status details
+func (mp *minipool_v2) GetStatusDetails(opts *bind.CallOpts) (StatusDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var status rptypes.MinipoolStatus
+ var statusBlock uint64
+ var statusTime time.Time
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ status, err = mp.GetStatus(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ statusBlock, err = mp.GetStatusBlock(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ statusTime, err = mp.GetStatusTime(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return StatusDetails{}, err
+ }
+
+ // Return
+ return StatusDetails{
+ Status: status,
+ StatusBlock: statusBlock,
+ StatusTime: statusTime,
+ }, nil
+
+}
+func (mp *minipool_v2) GetStatus(opts *bind.CallOpts) (rptypes.MinipoolStatus, error) {
+ status := new(uint8)
+ if err := mp.Contract.Call(opts, status, "getStatus"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s status: %w", mp.Address.Hex(), err)
+ }
+ return rptypes.MinipoolStatus(*status), nil
+}
+func (mp *minipool_v2) GetStatusBlock(opts *bind.CallOpts) (uint64, error) {
+ statusBlock := new(*big.Int)
+ if err := mp.Contract.Call(opts, statusBlock, "getStatusBlock"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s status changed block: %w", mp.Address.Hex(), err)
+ }
+ return (*statusBlock).Uint64(), nil
+}
+func (mp *minipool_v2) GetStatusTime(opts *bind.CallOpts) (time.Time, error) {
+ statusTime := new(*big.Int)
+ if err := mp.Contract.Call(opts, statusTime, "getStatusTime"); err != nil {
+ return time.Unix(0, 0), fmt.Errorf("error getting minipool %s status changed time: %w", mp.Address.Hex(), err)
+ }
+ return time.Unix((*statusTime).Int64(), 0), nil
+}
+func (mp *minipool_v2) GetFinalised(opts *bind.CallOpts) (bool, error) {
+ finalised := new(bool)
+ if err := mp.Contract.Call(opts, finalised, "getFinalised"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s finalised: %w", mp.Address.Hex(), err)
+ }
+ return *finalised, nil
+}
+
+// Get deposit type
+func (mp *minipool_v2) GetDepositType(opts *bind.CallOpts) (rptypes.MinipoolDeposit, error) {
+ depositType := new(uint8)
+ if err := mp.Contract.Call(opts, depositType, "getDepositType"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s deposit type: %w", mp.Address.Hex(), err)
+ }
+ return rptypes.MinipoolDeposit(*depositType), nil
+}
+
+// Get node details
+func (mp *minipool_v2) GetNodeDetails(opts *bind.CallOpts) (NodeDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var address common.Address
+ var fee float64
+ var depositBalance *big.Int
+ var refundBalance *big.Int
+ var depositAssigned bool
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ address, err = mp.GetNodeAddress(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ fee, err = mp.GetNodeFee(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositBalance, err = mp.GetNodeDepositBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ refundBalance, err = mp.GetNodeRefundBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssigned, err = mp.GetNodeDepositAssigned(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return NodeDetails{}, err
+ }
+
+ // Return
+ return NodeDetails{
+ Address: address,
+ Fee: fee,
+ DepositBalance: depositBalance,
+ RefundBalance: refundBalance,
+ DepositAssigned: depositAssigned,
+ }, nil
+
+}
+func (mp *minipool_v2) GetNodeAddress(opts *bind.CallOpts) (common.Address, error) {
+ nodeAddress := new(common.Address)
+ if err := mp.Contract.Call(opts, nodeAddress, "getNodeAddress"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool %s node address: %w", mp.Address.Hex(), err)
+ }
+ return *nodeAddress, nil
+}
+func (mp *minipool_v2) GetNodeFee(opts *bind.CallOpts) (float64, error) {
+ nodeFee := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeFee, "getNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s node fee: %w", mp.Address.Hex(), err)
+ }
+ return eth.WeiToEth(*nodeFee), nil
+}
+func (mp *minipool_v2) GetNodeFeeRaw(opts *bind.CallOpts) (*big.Int, error) {
+ nodeFee := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeFee, "getNodeFee"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node fee: %w", mp.Address.Hex(), err)
+ }
+ return *nodeFee, nil
+}
+func (mp *minipool_v2) GetNodeDepositBalance(opts *bind.CallOpts) (*big.Int, error) {
+ nodeDepositBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeDepositBalance, "getNodeDepositBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node deposit balance: %w", mp.Address.Hex(), err)
+ }
+ return *nodeDepositBalance, nil
+}
+func (mp *minipool_v2) GetNodeRefundBalance(opts *bind.CallOpts) (*big.Int, error) {
+ nodeRefundBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeRefundBalance, "getNodeRefundBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node refund balance: %w", mp.Address.Hex(), err)
+ }
+ return *nodeRefundBalance, nil
+}
+func (mp *minipool_v2) GetNodeDepositAssigned(opts *bind.CallOpts) (bool, error) {
+ nodeDepositAssigned := new(bool)
+ if err := mp.Contract.Call(opts, nodeDepositAssigned, "getNodeDepositAssigned"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s node deposit assigned status: %w", mp.Address.Hex(), err)
+ }
+ return *nodeDepositAssigned, nil
+}
+
+// Get user deposit details
+func (mp *minipool_v2) GetUserDetails(opts *bind.CallOpts) (UserDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var depositBalance *big.Int
+ var depositAssigned bool
+ var depositAssignedTime time.Time
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ depositBalance, err = mp.GetUserDepositBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssigned, err = mp.GetUserDepositAssigned(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssignedTime, err = mp.GetUserDepositAssignedTime(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return UserDetails{}, err
+ }
+
+ // Return
+ return UserDetails{
+ DepositBalance: depositBalance,
+ DepositAssigned: depositAssigned,
+ DepositAssignedTime: depositAssignedTime,
+ }, nil
+
+}
+func (mp *minipool_v2) GetUserDepositBalance(opts *bind.CallOpts) (*big.Int, error) {
+ userDepositBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, userDepositBalance, "getUserDepositBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s user deposit balance: %w", mp.Address.Hex(), err)
+ }
+ return *userDepositBalance, nil
+}
+func (mp *minipool_v2) GetUserDepositAssigned(opts *bind.CallOpts) (bool, error) {
+ userDepositAssigned := new(bool)
+ if err := mp.Contract.Call(opts, userDepositAssigned, "getUserDepositAssigned"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s user deposit assigned status: %w", mp.Address.Hex(), err)
+ }
+ return *userDepositAssigned, nil
+}
+func (mp *minipool_v2) GetUserDepositAssignedTime(opts *bind.CallOpts) (time.Time, error) {
+ depositAssignedTime := new(*big.Int)
+ if err := mp.Contract.Call(opts, depositAssignedTime, "getUserDepositAssignedTime"); err != nil {
+ return time.Unix(0, 0), fmt.Errorf("error getting minipool %s user deposit assigned time: %w", mp.Address.Hex(), err)
+ }
+ return time.Unix((*depositAssignedTime).Int64(), 0), nil
+}
+
+// Estimate the gas of Refund
+func (mp *minipool_v2) EstimateRefundGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "refund")
+}
+
+// Refund node ETH from the minipool
+func (mp *minipool_v2) Refund(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "refund")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error refunding from minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DistributeBalance
+func (mp *minipool_v2) EstimateDistributeBalanceGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "distributeBalance")
+}
+
+// Distribute the minipool's ETH balance to the node operator and rETH staking pool.
+// !!! WARNING !!!
+// DO NOT CALL THIS until the minipool's validator has exited from the Beacon Chain
+// and the balance has been deposited into the minipool!
+func (mp *minipool_v2) DistributeBalance(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "distributeBalance")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error processing withdrawal for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DistributeBalanceAndFinalise
+func (mp *minipool_v2) EstimateDistributeBalanceAndFinaliseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "distributeBalanceAndFinalise")
+}
+
+// Distribute the minipool's ETH balance to the node operator and rETH staking pool,
+// then finalises the minipool
+// !!! WARNING !!!
+// DO NOT CALL THIS until the minipool's validator has exited from the Beacon Chain
+// and the balance has been deposited into the minipool!
+func (mp *minipool_v2) DistributeBalanceAndFinalise(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "distributeBalanceAndFinalise")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error processing withdrawal for and finalise minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Stake
+func (mp *minipool_v2) EstimateStakeGas(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "stake", validatorSignature[:], depositDataRoot)
+}
+
+// Progress the prelaunch minipool to staking
+func (mp *minipool_v2) Stake(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "stake", validatorSignature[:], depositDataRoot)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error staking minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Dissolve
+func (mp *minipool_v2) EstimateDissolveGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "dissolve")
+}
+
+// Dissolve the initialized or prelaunch minipool
+func (mp *minipool_v2) Dissolve(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "dissolve")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error dissolving minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Close
+func (mp *minipool_v2) EstimateCloseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "close")
+}
+
+// Withdraw node balances from the dissolved minipool and close it
+func (mp *minipool_v2) Close(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "close")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error closing minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Finalise
+func (mp *minipool_v2) EstimateFinaliseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "finalise")
+}
+
+// Finalise a minipool to get the RPL stake back
+func (mp *minipool_v2) Finalise(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "finalise")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error finalizing minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DelegateUpgrade
+func (mp *minipool_v2) EstimateDelegateUpgradeGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "delegateUpgrade")
+}
+
+// Upgrade this minipool to the latest network delegate contract
+func (mp *minipool_v2) DelegateUpgrade(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "delegateUpgrade")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error upgrading delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DelegateRollback
+func (mp *minipool_v2) EstimateDelegateRollbackGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "delegateRollback")
+}
+
+// Rollback to previous delegate contract
+func (mp *minipool_v2) DelegateRollback(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "delegateRollback")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error rolling back delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of SetUseLatestDelegate
+func (mp *minipool_v2) EstimateSetUseLatestDelegateGas(setting bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "setUseLatestDelegate", setting)
+}
+
+// If set to true, will automatically use the latest delegate contract
+func (mp *minipool_v2) SetUseLatestDelegate(setting bool, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "setUseLatestDelegate", setting)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting use latest delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Getter for useLatestDelegate setting
+func (mp *minipool_v2) GetUseLatestDelegate(opts *bind.CallOpts) (bool, error) {
+ setting := new(bool)
+ if err := mp.Contract.Call(opts, setting, "getUseLatestDelegate"); err != nil {
+ return false, fmt.Errorf("error getting use latest delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *setting, nil
+}
+
+// Returns the address of the minipool's stored delegate
+func (mp *minipool_v2) GetDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Returns the address of the minipool's previous delegate (or address(0) if not set)
+func (mp *minipool_v2) GetPreviousDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getPreviousDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting previous delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Returns the delegate which will be used when calling this minipool taking into account useLatestDelegate setting
+func (mp *minipool_v2) GetEffectiveDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getEffectiveDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting effective delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Given a validator balance, calculates how much belongs to the node taking into consideration rewards and penalties
+func (mp *minipool_v2) CalculateNodeShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ nodeAmount := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeAmount, "calculateNodeShare", balance); err != nil {
+ return nil, fmt.Errorf("error getting minipool node portion: %w", err)
+ }
+ return *nodeAmount, nil
+}
+
+// Given a validator balance, calculates how much belongs to rETH users taking into consideration rewards and penalties
+func (mp *minipool_v2) CalculateUserShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ userAmount := new(*big.Int)
+ if err := mp.Contract.Call(opts, userAmount, "calculateUserShare", balance); err != nil {
+ return nil, fmt.Errorf("error getting minipool user portion: %w", err)
+ }
+ return *userAmount, nil
+}
+
+// Estimate the gas requiired to vote to scrub a minipool
+func (mp *minipool_v2) EstimateVoteScrubGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "voteScrub")
+}
+
+// Vote to scrub a minipool
+func (mp *minipool_v2) VoteScrub(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "voteScrub")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting to scrub minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the data from this minipool's MinipoolPrestaked event
+func (mp *minipool_v2) GetPrestakeEvent(intervalSize *big.Int, opts *bind.CallOpts) (PrestakeData, error) {
+
+ addressFilter := []common.Address{mp.Address}
+ topicFilter := [][]common.Hash{{mp.Contract.ABI.Events["MinipoolPrestaked"].ID}}
+
+ // Grab the latest block number
+ currentBlock, err := mp.RocketPool.Client.BlockNumber(context.Background())
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting current block %s: %w", mp.Address.Hex(), err)
+ }
+
+ // Grab the lowest block number worth querying from (should never have to go back this far in practice)
+ fromBlockBig, err := storage.GetDeployBlock(mp.RocketPool)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting deploy block %s: %w", mp.Address.Hex(), err)
+ }
+
+ fromBlock := fromBlockBig.Uint64()
+ var log types.Log
+ found := false
+
+ // Backwards scan through blocks to find the event
+ for i := currentBlock; i >= fromBlock; i -= EventScanInterval {
+ from := i - EventScanInterval + 1
+ if from < fromBlock {
+ from = fromBlock
+ }
+
+ fromBig := big.NewInt(0).SetUint64(from)
+ toBig := big.NewInt(0).SetUint64(i)
+
+ logs, err := eth.GetLogs(mp.RocketPool, addressFilter, topicFilter, intervalSize, fromBig, toBig, nil)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting prestake logs for minipool %s: %w", mp.Address.Hex(), err)
+ }
+
+ if len(logs) > 0 {
+ log = logs[0]
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ // This should never happen
+ return PrestakeData{}, fmt.Errorf("Error finding prestake log for minipool %s", mp.Address.Hex())
+ }
+
+ // Decode the event
+ prestakeEvent := new(MinipoolPrestakeEvent)
+ mp.Contract.Contract.UnpackLog(prestakeEvent, "MinipoolPrestaked", log)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error unpacking prestake data: %w", err)
+ }
+
+ // Convert the event to a more useable struct
+ prestakeData := PrestakeData{
+ Pubkey: rptypes.BytesToValidatorPubkey(prestakeEvent.Pubkey),
+ WithdrawalCredentials: common.BytesToHash(prestakeEvent.WithdrawalCredentials),
+ Amount: prestakeEvent.Amount,
+ Signature: rptypes.BytesToValidatorSignature(prestakeEvent.Signature),
+ DepositDataRoot: prestakeEvent.DepositDataRoot,
+ Time: time.Unix(prestakeEvent.Time.Int64(), 0),
+ }
+ return prestakeData, nil
+}
diff --git a/bindings/minipool/minipool-contract-v3.go b/bindings/minipool/minipool-contract-v3.go
new file mode 100644
index 000000000..e4f900e84
--- /dev/null
+++ b/bindings/minipool/minipool-contract-v3.go
@@ -0,0 +1,651 @@
+package minipool
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/storage"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+const (
+ minipoolV3EncodedAbi string = "eJztWltv2koQ/isVz3lq1SrqW3Jon5qeCJKch6pCY+8Aqyy71l7MQdH57x0bY2Mw2MAau0d9SsDjmW8uO7flx9sApJKrhXJm8HkKwuDNgMvIWfr4443+Zfgvsq1HFrUE8bSKcPB54Ojz+4+fBjcDCYvki0hjzInXvZLsjphKS89smfi/m9P5Slz6Zmk5/dnn9DMnSASOkLmQmOZ0GCMBSOQ1s5vVbh8LMKbRmALLVKtFIWPz+Byt4JoW+mLnqIcYKcNt+0Yi2tCV5FxiKKkY+gwnZ1B7DU9lQdyDABlWOaE1d/7D7ZxpWIJ41Cok67bvWKt+19jfGEueb6JqPMHK4paFYhCcgVX60QWvuCqkrekaKHiI4ZjPJFin8VyeH94XXNk6FQzBwkgpu8OSKK/s1R2ll3lo/6WRkZs4vXWG3qcHywOXPFKKjhQaC6+XHCn/kNRCXZS9fSMah9oFwRUQBZ47itBpTUCvm7Q3VnuBkKSuKMIi0O3n7AUuAtR+8natjmlEvFwWpZVqJbJuCyB0Nq0zZSi3LamUinqOKAlXa5XrULwT4IzLZ+ozhtxYzQNqhuhN5WxBmWiAD85CwAW3q7TPkRGsIBBbeKZOhpYrWRb0VqvVJDgY2gXKEEToBMH4Th3WeA66DLJeSjX3fc1ijstr65RYv2udtmHJLH0fhxNQfqjCkn7vCcg4qWtdwxDKeD0SP3dbm3rmp3PmxigRt3uUy6afaFyCZuZvKVYVbiiAZVmmqGgtWHbKCSZvy28ztNmAmlriaHyidIt3m3o6zB1eeYRvvQQsofuaqc86PjsEJUnYd3mhPwJmrxuo6AP8Qco8cWcMzSp9sVMGqupkdFUNMmRfsVdwRkiErId2elLRc/QCwvUGFfXu34DI5n1D9cBnGhKy7t1Yn4smB0eTklqlmaLjhDLezB1Ni1P+Qru1aS3mXqjwtS/RuIb0tB6veoHoKVnU5tFU48XrwUrnxL5VzgpQfXLlFrzuM90+srwf74En03WT7QRHgxIgS53s4TpA6TRZAnuePposHZRkmypfuSU8tseeNLoOaMahZv9/fN8/qV/4F7aONA49D9ENDX18x1vDoHapW9Iw2b1mm9h25tmoYt/jjblOb7tLF+2tSEkmgnZ4GwFm3u4mpfuTZPZWbd7s54oK2MKqy5Wq2J0Qatl5LYtRm+RxbaG/rYLhr92O6VSnXaRnxzZ7PVSS3OJCiuOL79n2yrESbIgCZ2C3JJ5wYbTHUOLyIoanX9dsxI2UEMjuIR2IMnI/t5P/Z6s9RzMN7M/PqA6YKf0pyQhD5HHju0C2FZAB7MznHm89Sg5sR0i6os/jshdr5y/TKRJNjH0D9pj9vrJvuGg+/UZMTENPdjcZahW+JlskpWF2MA3+XtPlzt2eQWu5nFUYddM+1rnrPIBN+kPIklVDer3OiQT+F40AQik="
+)
+
+type MinipoolV3 interface {
+ Minipool
+ EstimateReduceBondAmountGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ ReduceBondAmount(opts *bind.TransactOpts) (common.Hash, error)
+ EstimatePromoteGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Promote(opts *bind.TransactOpts) (common.Hash, error)
+ GetPreMigrationBalance(opts *bind.CallOpts) (*big.Int, error)
+ GetUserDistributed(opts *bind.CallOpts) (bool, error)
+ EstimateDistributeBalanceGas(rewardsOnly bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ DistributeBalance(rewardsOnly bool, opts *bind.TransactOpts) (common.Hash, error)
+}
+
+// Minipool contract
+type minipool_v3 struct {
+ Address common.Address
+ Version uint8
+ Contract *rocketpool.Contract
+ RocketPool *rocketpool.RocketPool
+}
+
+// The decoded ABI for v2 minipools
+var minipoolV3Abi *abi.ABI
+
+// Create new minipool contract
+func newMinipool_v3(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (Minipool, error) {
+
+ var contract *rocketpool.Contract
+ var err error
+ if minipoolV3Abi == nil {
+ // Get contract
+ contract, err = createMinipoolContractFromEncodedAbi(rp, address, minipoolV3EncodedAbi)
+ } else {
+ contract, err = createMinipoolContractFromAbi(rp, address, minipoolV3Abi)
+ }
+ if err != nil {
+ return nil, err
+ } else if minipoolV3Abi == nil {
+ minipoolV3Abi = contract.ABI
+ }
+
+ // Create and return
+ return &minipool_v3{
+ Address: address,
+ Version: 3,
+ Contract: contract,
+ RocketPool: rp,
+ }, nil
+}
+
+// Get the minipool as a v3 minipool if it implements the required methods
+func GetMinipoolAsV3(mp Minipool) (MinipoolV3, bool) {
+ castedMp, ok := mp.(MinipoolV3)
+ if ok {
+ return castedMp, true
+ }
+ return nil, false
+}
+
+// Get the contract
+func (mp *minipool_v3) GetContract() *rocketpool.Contract {
+ return mp.Contract
+}
+
+// Get the contract address
+func (mp *minipool_v3) GetAddress() common.Address {
+ return mp.Address
+}
+
+// Get the contract version
+func (mp *minipool_v3) GetVersion() uint8 {
+ return mp.Version
+}
+
+// Get status details
+func (mp *minipool_v3) GetStatusDetails(opts *bind.CallOpts) (StatusDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var status rptypes.MinipoolStatus
+ var statusBlock uint64
+ var statusTime time.Time
+ var isVacant bool
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ status, err = mp.GetStatus(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ statusBlock, err = mp.GetStatusBlock(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ statusTime, err = mp.GetStatusTime(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ isVacant, err = mp.GetVacant(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return StatusDetails{}, err
+ }
+
+ // Return
+ return StatusDetails{
+ Status: status,
+ StatusBlock: statusBlock,
+ StatusTime: statusTime,
+ IsVacant: isVacant,
+ }, nil
+
+}
+func (mp *minipool_v3) GetStatus(opts *bind.CallOpts) (rptypes.MinipoolStatus, error) {
+ status := new(uint8)
+ if err := mp.Contract.Call(opts, status, "getStatus"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s status: %w", mp.Address.Hex(), err)
+ }
+ return rptypes.MinipoolStatus(*status), nil
+}
+func (mp *minipool_v3) GetStatusBlock(opts *bind.CallOpts) (uint64, error) {
+ statusBlock := new(*big.Int)
+ if err := mp.Contract.Call(opts, statusBlock, "getStatusBlock"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s status changed block: %w", mp.Address.Hex(), err)
+ }
+ return (*statusBlock).Uint64(), nil
+}
+func (mp *minipool_v3) GetStatusTime(opts *bind.CallOpts) (time.Time, error) {
+ statusTime := new(*big.Int)
+ if err := mp.Contract.Call(opts, statusTime, "getStatusTime"); err != nil {
+ return time.Unix(0, 0), fmt.Errorf("error getting minipool %s status changed time: %w", mp.Address.Hex(), err)
+ }
+ return time.Unix((*statusTime).Int64(), 0), nil
+}
+func (mp *minipool_v3) GetFinalised(opts *bind.CallOpts) (bool, error) {
+ finalised := new(bool)
+ if err := mp.Contract.Call(opts, finalised, "getFinalised"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s finalised: %w", mp.Address.Hex(), err)
+ }
+ return *finalised, nil
+}
+
+// Get deposit type
+func (mp *minipool_v3) GetDepositType(opts *bind.CallOpts) (rptypes.MinipoolDeposit, error) {
+ depositType := new(uint8)
+ if err := mp.Contract.Call(opts, depositType, "getDepositType"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s deposit type: %w", mp.Address.Hex(), err)
+ }
+ return rptypes.MinipoolDeposit(*depositType), nil
+}
+
+// Get node details
+func (mp *minipool_v3) GetNodeDetails(opts *bind.CallOpts) (NodeDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var address common.Address
+ var fee float64
+ var depositBalance *big.Int
+ var refundBalance *big.Int
+ var depositAssigned bool
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ address, err = mp.GetNodeAddress(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ fee, err = mp.GetNodeFee(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositBalance, err = mp.GetNodeDepositBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ refundBalance, err = mp.GetNodeRefundBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssigned, err = mp.GetNodeDepositAssigned(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return NodeDetails{}, err
+ }
+
+ // Return
+ return NodeDetails{
+ Address: address,
+ Fee: fee,
+ DepositBalance: depositBalance,
+ RefundBalance: refundBalance,
+ DepositAssigned: depositAssigned,
+ }, nil
+
+}
+func (mp *minipool_v3) GetNodeAddress(opts *bind.CallOpts) (common.Address, error) {
+ nodeAddress := new(common.Address)
+ if err := mp.Contract.Call(opts, nodeAddress, "getNodeAddress"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool %s node address: %w", mp.Address.Hex(), err)
+ }
+ return *nodeAddress, nil
+}
+func (mp *minipool_v3) GetNodeFee(opts *bind.CallOpts) (float64, error) {
+ nodeFee := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeFee, "getNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting minipool %s node fee: %w", mp.Address.Hex(), err)
+ }
+ return eth.WeiToEth(*nodeFee), nil
+}
+func (mp *minipool_v3) GetNodeFeeRaw(opts *bind.CallOpts) (*big.Int, error) {
+ nodeFee := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeFee, "getNodeFee"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node fee: %w", mp.Address.Hex(), err)
+ }
+ return *nodeFee, nil
+}
+func (mp *minipool_v3) GetNodeDepositBalance(opts *bind.CallOpts) (*big.Int, error) {
+ nodeDepositBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeDepositBalance, "getNodeDepositBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node deposit balance: %w", mp.Address.Hex(), err)
+ }
+ return *nodeDepositBalance, nil
+}
+func (mp *minipool_v3) GetNodeRefundBalance(opts *bind.CallOpts) (*big.Int, error) {
+ nodeRefundBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeRefundBalance, "getNodeRefundBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s node refund balance: %w", mp.Address.Hex(), err)
+ }
+ return *nodeRefundBalance, nil
+}
+func (mp *minipool_v3) GetNodeDepositAssigned(opts *bind.CallOpts) (bool, error) {
+ nodeDepositAssigned := new(bool)
+ if err := mp.Contract.Call(opts, nodeDepositAssigned, "getNodeDepositAssigned"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s node deposit assigned status: %w", mp.Address.Hex(), err)
+ }
+ return *nodeDepositAssigned, nil
+}
+func (mp *minipool_v3) GetVacant(opts *bind.CallOpts) (bool, error) {
+ isVacant := new(bool)
+ if err := mp.Contract.Call(opts, isVacant, "getVacant"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s vacant status: %w", mp.Address.Hex(), err)
+ }
+ return *isVacant, nil
+}
+func (mp *minipool_v3) GetPreMigrationBalance(opts *bind.CallOpts) (*big.Int, error) {
+ preMigrationBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, preMigrationBalance, "getPreMigrationBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s pre-migration balance: %w", mp.Address.Hex(), err)
+ }
+ return *preMigrationBalance, nil
+}
+
+// Get user deposit details
+func (mp *minipool_v3) GetUserDetails(opts *bind.CallOpts) (UserDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var depositBalance *big.Int
+ var depositAssigned bool
+ var depositAssignedTime time.Time
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ depositBalance, err = mp.GetUserDepositBalance(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssigned, err = mp.GetUserDepositAssigned(opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ depositAssignedTime, err = mp.GetUserDepositAssignedTime(opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return UserDetails{}, err
+ }
+
+ // Return
+ return UserDetails{
+ DepositBalance: depositBalance,
+ DepositAssigned: depositAssigned,
+ DepositAssignedTime: depositAssignedTime,
+ }, nil
+
+}
+func (mp *minipool_v3) GetUserDepositBalance(opts *bind.CallOpts) (*big.Int, error) {
+ userDepositBalance := new(*big.Int)
+ if err := mp.Contract.Call(opts, userDepositBalance, "getUserDepositBalance"); err != nil {
+ return nil, fmt.Errorf("error getting minipool %s user deposit balance: %w", mp.Address.Hex(), err)
+ }
+ return *userDepositBalance, nil
+}
+func (mp *minipool_v3) GetUserDepositAssigned(opts *bind.CallOpts) (bool, error) {
+ userDepositAssigned := new(bool)
+ if err := mp.Contract.Call(opts, userDepositAssigned, "getUserDepositAssigned"); err != nil {
+ return false, fmt.Errorf("error getting minipool %s user deposit assigned status: %w", mp.Address.Hex(), err)
+ }
+ return *userDepositAssigned, nil
+}
+func (mp *minipool_v3) GetUserDepositAssignedTime(opts *bind.CallOpts) (time.Time, error) {
+ depositAssignedTime := new(*big.Int)
+ if err := mp.Contract.Call(opts, depositAssignedTime, "getUserDepositAssignedTime"); err != nil {
+ return time.Unix(0, 0), fmt.Errorf("error getting minipool %s user deposit assigned time: %w", mp.Address.Hex(), err)
+ }
+ return time.Unix((*depositAssignedTime).Int64(), 0), nil
+}
+
+// Estimate the gas of Refund
+func (mp *minipool_v3) EstimateRefundGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "refund")
+}
+
+// Refund node ETH from the minipool
+func (mp *minipool_v3) Refund(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "refund")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error refunding from minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Check if the minipool's balance has already been distributed
+func (mp *minipool_v3) GetUserDistributed(opts *bind.CallOpts) (bool, error) {
+ distributed := new(bool)
+ if err := mp.Contract.Call(opts, distributed, "getUserDistributed"); err != nil {
+ return false, fmt.Errorf("error getting user distributed status for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *distributed, nil
+}
+
+// Estimate the gas of DistributeBalance
+func (mp *minipool_v3) EstimateDistributeBalanceGas(rewardsOnly bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "distributeBalance", rewardsOnly)
+}
+
+// Distribute the minipool's ETH balance to the node operator and rETH staking pool.
+func (mp *minipool_v3) DistributeBalance(rewardsOnly bool, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "distributeBalance", rewardsOnly)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error processing withdrawal for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Stake
+func (mp *minipool_v3) EstimateStakeGas(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "stake", validatorSignature[:], depositDataRoot)
+}
+
+// Progress the prelaunch minipool to staking
+func (mp *minipool_v3) Stake(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "stake", validatorSignature[:], depositDataRoot)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error staking minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Dissolve
+func (mp *minipool_v3) EstimateDissolveGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "dissolve")
+}
+
+// Dissolve the initialized or prelaunch minipool
+func (mp *minipool_v3) Dissolve(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "dissolve")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error dissolving minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Close
+func (mp *minipool_v3) EstimateCloseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "close")
+}
+
+// Withdraw node balances from the dissolved minipool and close it
+func (mp *minipool_v3) Close(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "close")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error closing minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of Finalise
+func (mp *minipool_v3) EstimateFinaliseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "finalise")
+}
+
+// Finalise a minipool to get the RPL stake back
+func (mp *minipool_v3) Finalise(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "finalise")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error finalizing minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DelegateUpgrade
+func (mp *minipool_v3) EstimateDelegateUpgradeGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "delegateUpgrade")
+}
+
+// Upgrade this minipool to the latest network delegate contract
+func (mp *minipool_v3) DelegateUpgrade(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "delegateUpgrade")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error upgrading delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of DelegateRollback
+func (mp *minipool_v3) EstimateDelegateRollbackGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "delegateRollback")
+}
+
+// Rollback to previous delegate contract
+func (mp *minipool_v3) DelegateRollback(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "delegateRollback")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error rolling back delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of SetUseLatestDelegate
+func (mp *minipool_v3) EstimateSetUseLatestDelegateGas(setting bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "setUseLatestDelegate", setting)
+}
+
+// If set to true, will automatically use the latest delegate contract
+func (mp *minipool_v3) SetUseLatestDelegate(setting bool, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "setUseLatestDelegate", setting)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting use latest delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Getter for useLatestDelegate setting
+func (mp *minipool_v3) GetUseLatestDelegate(opts *bind.CallOpts) (bool, error) {
+ setting := new(bool)
+ if err := mp.Contract.Call(opts, setting, "getUseLatestDelegate"); err != nil {
+ return false, fmt.Errorf("error getting use latest delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *setting, nil
+}
+
+// Returns the address of the minipool's stored delegate
+func (mp *minipool_v3) GetDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Returns the address of the minipool's previous delegate (or address(0) if not set)
+func (mp *minipool_v3) GetPreviousDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getPreviousDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting previous delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Returns the delegate which will be used when calling this minipool taking into account useLatestDelegate setting
+func (mp *minipool_v3) GetEffectiveDelegate(opts *bind.CallOpts) (common.Address, error) {
+ address := new(common.Address)
+ if err := mp.Contract.Call(opts, address, "getEffectiveDelegate"); err != nil {
+ return common.Address{}, fmt.Errorf("error getting effective delegate for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return *address, nil
+}
+
+// Estimate the gas required to reduce a minipool's bond
+func (mp *minipool_v3) EstimateReduceBondAmountGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "reduceBondAmount")
+}
+
+// Reduce a minipool's bond
+func (mp *minipool_v3) ReduceBondAmount(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "reduceBondAmount")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error reducing bond for minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Given a validator balance, calculates how much belongs to the node taking into consideration rewards and penalties
+func (mp *minipool_v3) CalculateNodeShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ nodeAmount := new(*big.Int)
+ if err := mp.Contract.Call(opts, nodeAmount, "calculateNodeShare", balance); err != nil {
+ return nil, fmt.Errorf("error getting minipool node portion: %w", err)
+ }
+ return *nodeAmount, nil
+}
+
+// Given a validator balance, calculates how much belongs to rETH users taking into consideration rewards and penalties
+func (mp *minipool_v3) CalculateUserShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ userAmount := new(*big.Int)
+ if err := mp.Contract.Call(opts, userAmount, "calculateUserShare", balance); err != nil {
+ return nil, fmt.Errorf("error getting minipool user portion: %w", err)
+ }
+ return *userAmount, nil
+}
+
+// Estimate the gas required to vote to scrub a minipool
+func (mp *minipool_v3) EstimateVoteScrubGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "voteScrub")
+}
+
+// Vote to scrub a minipool
+func (mp *minipool_v3) VoteScrub(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "voteScrub")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error voting to scrub minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas required to promote a vacant minipool
+func (mp *minipool_v3) EstimatePromoteGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return mp.Contract.GetTransactionGasInfo(opts, "promote")
+}
+
+// Promote a vacant minipool
+func (mp *minipool_v3) Promote(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := mp.Contract.Transact(opts, "promote")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error promoting minipool %s: %w", mp.Address.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the data from this minipool's MinipoolPrestaked event
+func (mp *minipool_v3) GetPrestakeEvent(intervalSize *big.Int, opts *bind.CallOpts) (PrestakeData, error) {
+
+ addressFilter := []common.Address{mp.Address}
+ topicFilter := [][]common.Hash{{mp.Contract.ABI.Events["MinipoolPrestaked"].ID}}
+
+ // Grab the latest block number
+ currentBlock, err := mp.RocketPool.Client.BlockNumber(context.Background())
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting current block %s: %w", mp.Address.Hex(), err)
+ }
+
+ // Grab the lowest block number worth querying from (should never have to go back this far in practice)
+ fromBlockBig, err := storage.GetDeployBlock(mp.RocketPool)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting deploy block %s: %w", mp.Address.Hex(), err)
+ }
+
+ fromBlock := fromBlockBig.Uint64()
+ var log types.Log
+ found := false
+
+ // Backwards scan through blocks to find the event
+ for i := currentBlock; i >= fromBlock; i -= EventScanInterval {
+ from := i - EventScanInterval + 1
+ if from < fromBlock {
+ from = fromBlock
+ }
+
+ fromBig := big.NewInt(0).SetUint64(from)
+ toBig := big.NewInt(0).SetUint64(i)
+
+ logs, err := eth.GetLogs(mp.RocketPool, addressFilter, topicFilter, intervalSize, fromBig, toBig, nil)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error getting prestake logs for minipool %s: %w", mp.Address.Hex(), err)
+ }
+
+ if len(logs) > 0 {
+ log = logs[0]
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ // This should never happen
+ return PrestakeData{}, fmt.Errorf("Error finding prestake log for minipool %s", mp.Address.Hex())
+ }
+
+ // Decode the event
+ prestakeEvent := new(MinipoolPrestakeEvent)
+ mp.Contract.Contract.UnpackLog(prestakeEvent, "MinipoolPrestaked", log)
+ if err != nil {
+ return PrestakeData{}, fmt.Errorf("Error unpacking prestake data: %w", err)
+ }
+
+ // Convert the event to a more useable struct
+ prestakeData := PrestakeData{
+ Pubkey: rptypes.BytesToValidatorPubkey(prestakeEvent.Pubkey),
+ WithdrawalCredentials: common.BytesToHash(prestakeEvent.WithdrawalCredentials),
+ Amount: prestakeEvent.Amount,
+ Signature: rptypes.BytesToValidatorSignature(prestakeEvent.Signature),
+ DepositDataRoot: prestakeEvent.DepositDataRoot,
+ Time: time.Unix(prestakeEvent.Time.Int64(), 0),
+ }
+ return prestakeData, nil
+}
diff --git a/bindings/minipool/minipool-interface.go b/bindings/minipool/minipool-interface.go
new file mode 100644
index 000000000..d87722fbc
--- /dev/null
+++ b/bindings/minipool/minipool-interface.go
@@ -0,0 +1,102 @@
+package minipool
+
+import (
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// The number of blocks to look for events in at once when scanning
+const EventScanInterval = 10000
+
+// Minipool detail types
+type StatusDetails struct {
+ Status rptypes.MinipoolStatus `json:"status"`
+ StatusBlock uint64 `json:"statusBlock"`
+ StatusTime time.Time `json:"statusTime"`
+ IsVacant bool `json:"isVacant"`
+}
+type NodeDetails struct {
+ Address common.Address `json:"address"`
+ Fee float64 `json:"fee"`
+ DepositBalance *big.Int `json:"depositBalance"`
+ RefundBalance *big.Int `json:"refundBalance"`
+ DepositAssigned bool `json:"depositAssigned"`
+}
+type UserDetails struct {
+ DepositBalance *big.Int `json:"depositBalance"`
+ DepositAssigned bool `json:"depositAssigned"`
+ DepositAssignedTime time.Time `json:"depositAssignedTime"`
+}
+
+// The data from a minipool's MinipoolPrestaked event
+type MinipoolPrestakeEvent struct {
+ Pubkey []byte `abi:"validatorPubkey"`
+ Signature []byte `abi:"validatorSignature"`
+ DepositDataRoot [32]byte `abi:"depositDataRoot"`
+ Amount *big.Int `abi:"amount"`
+ WithdrawalCredentials []byte `abi:"withdrawalCredentials"`
+ Time *big.Int `abi:"time"`
+}
+
+// Formatted MinipoolPrestaked event data
+type PrestakeData struct {
+ Pubkey rptypes.ValidatorPubkey `json:"pubkey"`
+ WithdrawalCredentials common.Hash `json:"withdrawalCredentials"`
+ Amount *big.Int `json:"amount"`
+ Signature rptypes.ValidatorSignature `json:"signature"`
+ DepositDataRoot common.Hash `json:"depositDataRoot"`
+ Time time.Time `json:"time"`
+}
+
+type Minipool interface {
+ GetContract() *rocketpool.Contract
+ GetAddress() common.Address
+ GetVersion() uint8
+ GetStatusDetails(opts *bind.CallOpts) (StatusDetails, error)
+ GetStatus(opts *bind.CallOpts) (rptypes.MinipoolStatus, error)
+ GetStatusBlock(opts *bind.CallOpts) (uint64, error)
+ GetStatusTime(opts *bind.CallOpts) (time.Time, error)
+ GetFinalised(opts *bind.CallOpts) (bool, error)
+ GetDepositType(opts *bind.CallOpts) (rptypes.MinipoolDeposit, error)
+ GetNodeDetails(opts *bind.CallOpts) (NodeDetails, error)
+ GetNodeAddress(opts *bind.CallOpts) (common.Address, error)
+ GetNodeFee(opts *bind.CallOpts) (float64, error)
+ GetNodeFeeRaw(opts *bind.CallOpts) (*big.Int, error)
+ GetNodeDepositBalance(opts *bind.CallOpts) (*big.Int, error)
+ GetNodeRefundBalance(opts *bind.CallOpts) (*big.Int, error)
+ GetNodeDepositAssigned(opts *bind.CallOpts) (bool, error)
+ GetUserDetails(opts *bind.CallOpts) (UserDetails, error)
+ GetUserDepositBalance(opts *bind.CallOpts) (*big.Int, error)
+ GetUserDepositAssigned(opts *bind.CallOpts) (bool, error)
+ GetUserDepositAssignedTime(opts *bind.CallOpts) (time.Time, error)
+ EstimateRefundGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Refund(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateStakeGas(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Stake(validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, opts *bind.TransactOpts) (common.Hash, error)
+ EstimateDissolveGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Dissolve(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateCloseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Close(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateFinaliseGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ Finalise(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateDelegateUpgradeGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ DelegateUpgrade(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateDelegateRollbackGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ DelegateRollback(opts *bind.TransactOpts) (common.Hash, error)
+ EstimateSetUseLatestDelegateGas(setting bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ SetUseLatestDelegate(setting bool, opts *bind.TransactOpts) (common.Hash, error)
+ GetUseLatestDelegate(opts *bind.CallOpts) (bool, error)
+ GetDelegate(opts *bind.CallOpts) (common.Address, error)
+ GetPreviousDelegate(opts *bind.CallOpts) (common.Address, error)
+ GetEffectiveDelegate(opts *bind.CallOpts) (common.Address, error)
+ CalculateNodeShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error)
+ CalculateUserShare(balance *big.Int, opts *bind.CallOpts) (*big.Int, error)
+ EstimateVoteScrubGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error)
+ VoteScrub(opts *bind.TransactOpts) (common.Hash, error)
+ GetPrestakeEvent(intervalSize *big.Int, opts *bind.CallOpts) (PrestakeData, error)
+}
diff --git a/bindings/minipool/minipool.go b/bindings/minipool/minipool.go
new file mode 100644
index 000000000..9cc191009
--- /dev/null
+++ b/bindings/minipool/minipool.go
@@ -0,0 +1,626 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Settings
+const (
+ MinipoolPrelaunchBatchSize = 250
+ MinipoolAddressBatchSize = 50
+ MinipoolDetailsBatchSize = 20
+ NativeMinipoolDetailsBatchSize = 1000
+)
+
+// Minipool details
+type MinipoolDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ Pubkey rptypes.ValidatorPubkey `json:"pubkey"`
+}
+
+// The counts of minipools per status
+type MinipoolCountsPerStatus struct {
+ Initialized *big.Int `abi:"initialisedCount"`
+ Prelaunch *big.Int `abi:"prelaunchCount"`
+ Staking *big.Int `abi:"stakingCount"`
+ Withdrawable *big.Int `abi:"withdrawableCount"`
+ Dissolved *big.Int `abi:"dissolvedCount"`
+}
+
+// Get all minipool details
+func GetMinipools(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]MinipoolDetails, error) {
+ minipoolAddresses, err := GetMinipoolAddresses(rp, opts)
+ if err != nil {
+ return []MinipoolDetails{}, err
+ }
+ return loadMinipoolDetails(rp, minipoolAddresses, opts)
+}
+
+// Get a node's minipool details
+func GetNodeMinipools(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) ([]MinipoolDetails, error) {
+ minipoolAddresses, err := GetNodeMinipoolAddresses(rp, nodeAddress, opts)
+ if err != nil {
+ return []MinipoolDetails{}, err
+ }
+ return loadMinipoolDetails(rp, minipoolAddresses, opts)
+}
+
+// Load minipool details
+func loadMinipoolDetails(rp *rocketpool.RocketPool, minipoolAddresses []common.Address, opts *bind.CallOpts) ([]MinipoolDetails, error) {
+
+ // Load minipool details in batches
+ details := make([]MinipoolDetails, len(minipoolAddresses))
+ for bsi := 0; bsi < len(minipoolAddresses); bsi += MinipoolDetailsBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolDetailsBatchSize
+ if mei > len(minipoolAddresses) {
+ mei = len(minipoolAddresses)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ minipoolAddress := minipoolAddresses[mi]
+ minipoolDetails, err := GetMinipoolDetails(rp, minipoolAddress, opts)
+ if err == nil {
+ details[mi] = minipoolDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []MinipoolDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all minipool addresses
+func GetMinipoolAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]common.Address, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetMinipoolCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load minipool addresses in batches
+ addresses := make([]common.Address, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetMinipoolAt(rp, mi, opts)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get the addresses of all minipools in prelaunch status
+func GetPrelaunchMinipoolAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]common.Address, error) {
+
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Get the total number of minipools
+ totalMinipoolsUint, err := GetMinipoolCount(rp, nil)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ totalMinipools := int64(totalMinipoolsUint)
+ addresses := []common.Address{}
+ limit := big.NewInt(MinipoolPrelaunchBatchSize)
+ for i := int64(0); i < totalMinipools; i += MinipoolPrelaunchBatchSize {
+ // Get a batch of addresses
+ offset := big.NewInt(i)
+ newAddresses := new([]common.Address)
+ if err := rocketMinipoolManager.Call(opts, newAddresses, "getPrelaunchMinipools", offset, limit); err != nil {
+ return []common.Address{}, fmt.Errorf("error getting prelaunch minipool addresses: %w", err)
+ }
+ addresses = append(addresses, *newAddresses...)
+ }
+
+ return addresses, nil
+}
+
+// Get a node's minipool addresses
+func GetNodeMinipoolAddresses(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) ([]common.Address, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetNodeMinipoolCount(rp, nodeAddress, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load minipool addresses in batches
+ addresses := make([]common.Address, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ address, err := GetNodeMinipoolAt(rp, nodeAddress, mi, opts)
+ if err == nil {
+ addresses[mi] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get a node's validating minipool pubkeys
+func GetNodeValidatingMinipoolPubkeys(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) ([]rptypes.ValidatorPubkey, error) {
+
+ // Get minipool count
+ minipoolCount, err := GetNodeValidatingMinipoolCount(rp, nodeAddress, opts)
+ if err != nil {
+ return []rptypes.ValidatorPubkey{}, err
+ }
+
+ // Load pubkeys in batches
+ var lock = sync.RWMutex{}
+ pubkeys := make([]rptypes.ValidatorPubkey, minipoolCount)
+ for bsi := uint64(0); bsi < minipoolCount; bsi += MinipoolAddressBatchSize {
+
+ // Get batch start & end index
+ msi := bsi
+ mei := bsi + MinipoolAddressBatchSize
+ if mei > minipoolCount {
+ mei = minipoolCount
+ }
+
+ // Load pubkeys
+ var wg errgroup.Group
+ for mi := msi; mi < mei; mi++ {
+ mi := mi
+ wg.Go(func() error {
+ minipoolAddress, err := GetNodeValidatingMinipoolAt(rp, nodeAddress, mi, opts)
+ if err != nil {
+ return err
+ }
+ pubkey, err := GetMinipoolPubkey(rp, minipoolAddress, opts)
+ if err != nil {
+ return err
+ }
+ lock.Lock()
+ pubkeys[mi] = pubkey
+ lock.Unlock()
+ return nil
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []rptypes.ValidatorPubkey{}, err
+ }
+
+ }
+
+ // Return
+ return pubkeys, nil
+
+}
+
+// Get a minipool's details
+func GetMinipoolDetails(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (MinipoolDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var pubkey rptypes.ValidatorPubkey
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetMinipoolExists(rp, minipoolAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ pubkey, err = GetMinipoolPubkey(rp, minipoolAddress, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return MinipoolDetails{}, err
+ }
+
+ // Return
+ return MinipoolDetails{
+ Address: minipoolAddress,
+ Exists: exists,
+ Pubkey: pubkey,
+ }, nil
+
+}
+
+// Get the minipool count
+func GetMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of staking minipools in the network
+func GetStakingMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getStakingMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting staking minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of finalised minipools in the network
+func GetFinalisedMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getFinalisedMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting finalised minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of active minipools in the network
+func GetActiveMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getActiveMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting finalised minipool count: %w", err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the minipool count by status
+func GetMinipoolCountPerStatus(rp *rocketpool.RocketPool, opts *bind.CallOpts) (MinipoolCountsPerStatus, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return MinipoolCountsPerStatus{}, err
+ }
+
+ // Get the total number of minipools
+ totalMinipoolsUint, err := GetMinipoolCount(rp, nil)
+ if err != nil {
+ return MinipoolCountsPerStatus{}, err
+ }
+
+ totalMinipools := int64(totalMinipoolsUint)
+ minipoolCounts := MinipoolCountsPerStatus{
+ Initialized: big.NewInt(0),
+ Prelaunch: big.NewInt(0),
+ Staking: big.NewInt(0),
+ Dissolved: big.NewInt(0),
+ Withdrawable: big.NewInt(0),
+ }
+ limit := big.NewInt(MinipoolPrelaunchBatchSize)
+ for i := int64(0); i < totalMinipools; i += MinipoolPrelaunchBatchSize {
+ // Get a batch of counts
+ offset := big.NewInt(i)
+ newMinipoolCounts := new(MinipoolCountsPerStatus)
+ if err := rocketMinipoolManager.Call(opts, newMinipoolCounts, "getMinipoolCountPerStatus", offset, limit); err != nil {
+ return MinipoolCountsPerStatus{}, fmt.Errorf("error getting minipool counts: %w", err)
+ }
+ if newMinipoolCounts != nil {
+ if newMinipoolCounts.Initialized != nil {
+ minipoolCounts.Initialized.Add(minipoolCounts.Initialized, newMinipoolCounts.Initialized)
+ }
+ if newMinipoolCounts.Prelaunch != nil {
+ minipoolCounts.Prelaunch.Add(minipoolCounts.Prelaunch, newMinipoolCounts.Prelaunch)
+ }
+ if newMinipoolCounts.Staking != nil {
+ minipoolCounts.Staking.Add(minipoolCounts.Staking, newMinipoolCounts.Staking)
+ }
+ if newMinipoolCounts.Dissolved != nil {
+ minipoolCounts.Dissolved.Add(minipoolCounts.Dissolved, newMinipoolCounts.Dissolved)
+ }
+ if newMinipoolCounts.Withdrawable != nil {
+ minipoolCounts.Withdrawable.Add(minipoolCounts.Withdrawable, newMinipoolCounts.Withdrawable)
+ }
+ }
+ }
+ return minipoolCounts, nil
+}
+
+// Get a minipool address by index
+func GetMinipoolAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getMinipoolAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool %d address: %w", index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a node's minipool count
+func GetNodeMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get a node's minipool count
+func GetNodeMinipoolCountRaw(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeMinipoolCount", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return *minipoolCount, nil
+}
+
+// Get the number of minipools owned by a node that are not finalised
+func GetNodeActiveMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeActiveMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get the number of minipools owned by a node that are finalised
+func GetNodeFinalisedMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeFinalisedMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get a node's minipool address by index
+func GetNodeMinipoolAt(rp *rocketpool.RocketPool, nodeAddress common.Address, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getNodeMinipoolAt", nodeAddress, big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s minipool %d address: %w", nodeAddress.Hex(), index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a node's validating minipool count
+func GetNodeValidatingMinipoolCount(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ minipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, minipoolCount, "getNodeValidatingMinipoolCount", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s validating minipool count: %w", nodeAddress.Hex(), err)
+ }
+ return (*minipoolCount).Uint64(), nil
+}
+
+// Get a node's validating minipool address by index
+func GetNodeValidatingMinipoolAt(rp *rocketpool.RocketPool, nodeAddress common.Address, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getNodeValidatingMinipoolAt", nodeAddress, big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s validating minipool %d address: %w", nodeAddress.Hex(), index, err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Get a minipool address by validator pubkey
+func GetMinipoolByPubkey(rp *rocketpool.RocketPool, pubkey rptypes.ValidatorPubkey, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ minipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, minipoolAddress, "getMinipoolByPubkey", pubkey[:]); err != nil {
+ return common.Address{}, fmt.Errorf("error getting validator %s minipool address: %w", pubkey.Hex(), err)
+ }
+ return *minipoolAddress, nil
+}
+
+// Check whether a minipool exists
+func GetMinipoolExists(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ exists := new(bool)
+ if err := rocketMinipoolManager.Call(opts, exists, "getMinipoolExists", minipoolAddress); err != nil {
+ return false, fmt.Errorf("error getting minipool %s exists status: %w", minipoolAddress.Hex(), err)
+ }
+ return *exists, nil
+}
+
+// Get a minipool's validator pubkey
+func GetMinipoolPubkey(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (rptypes.ValidatorPubkey, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return rptypes.ValidatorPubkey{}, err
+ }
+ pubkey := new(rptypes.ValidatorPubkey)
+ if err := rocketMinipoolManager.Call(opts, pubkey, "getMinipoolPubkey", minipoolAddress); err != nil {
+ return rptypes.ValidatorPubkey{}, fmt.Errorf("error getting minipool %s pubkey: %w", minipoolAddress.Hex(), err)
+ }
+ return *pubkey, nil
+}
+
+// Get the 0x01-based Beacon Chain withdrawal credentials for a given minipool
+func GetMinipoolWithdrawalCredentials(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (common.Hash, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ withdrawalCredentials := new(common.Hash)
+ if err := rocketMinipoolManager.Call(opts, withdrawalCredentials, "getMinipoolWithdrawalCredentials", minipoolAddress); err != nil {
+ return common.Hash{}, fmt.Errorf("error getting minipool withdrawal credentials: %w", err)
+ }
+ return *withdrawalCredentials, nil
+}
+
+// Get the number of penalties applied to a minipool
+func GetMinipoolPenaltyCount(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ key := crypto.Keccak256Hash([]byte("network.penalties.penalty"), minipoolAddress.Bytes())
+ penalties, err := rp.RocketStorage.GetUint(opts, key)
+ if err != nil {
+ return 0, err
+ }
+ return penalties.Uint64(), nil
+}
+
+// Get the vacant minipool count
+func GetVacantMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ vacantMinipoolCount := new(*big.Int)
+ if err := rocketMinipoolManager.Call(opts, vacantMinipoolCount, "getVacantMinipoolCount"); err != nil {
+ return 0, fmt.Errorf("error getting vacant minipool count: %w", err)
+ }
+ return (*vacantMinipoolCount).Uint64(), nil
+}
+
+// Get a vacant minipool address by index
+func GetVacantMinipoolAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ vacantMinipoolAddress := new(common.Address)
+ if err := rocketMinipoolManager.Call(opts, vacantMinipoolAddress, "getVacantMinipoolAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting vacant minipool %d address: %w", index, err)
+ }
+ return *vacantMinipoolAddress, nil
+}
+
+// Get a minipool's RPL slashing status
+func GetMinipoolRPLSlashed(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketMinipoolManager.Call(opts, value, "getMinipoolRPLSlashed", minipoolAddress); err != nil {
+ return false, fmt.Errorf("error getting minipool %s slashed status: %w", minipoolAddress.Hex(), err)
+ }
+ return *value, nil
+}
+
+// Get a minipool's deposit type invariant of its delegate version
+func GetMinipoolDepositType(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (types.MinipoolDeposit, error) {
+ rocketMinipoolManager, err := getRocketMinipoolManager(rp, opts)
+ if err != nil {
+ return types.None, err
+ }
+ value := new(uint8)
+ if err := rocketMinipoolManager.Call(opts, value, "getMinipoolDepositType", minipoolAddress); err != nil {
+ return types.None, fmt.Errorf("error getting minipool %s slashed status: %w", minipoolAddress.Hex(), err)
+ }
+ return types.MinipoolDeposit(*value), nil
+}
+
+// Get contracts
+var rocketMinipoolManagerLock sync.Mutex
+
+func getRocketMinipoolManager(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolManagerLock.Lock()
+ defer rocketMinipoolManagerLock.Unlock()
+ return rp.GetContract("rocketMinipoolManager", opts)
+}
diff --git a/bindings/minipool/queue.go b/bindings/minipool/queue.go
new file mode 100644
index 000000000..4b83ddae7
--- /dev/null
+++ b/bindings/minipool/queue.go
@@ -0,0 +1,144 @@
+package minipool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Minipool queue capacity
+type QueueCapacity struct {
+ Total *big.Int
+ Effective *big.Int
+}
+
+// Minipools queue status details
+type QueueDetails struct {
+ Position int64
+}
+
+// Get minipool queue capacity
+func GetQueueCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts) (QueueCapacity, error) {
+
+ // Data
+ var wg errgroup.Group
+ var total *big.Int
+ var effective *big.Int
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ total, err = GetQueueTotalCapacity(rp, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ effective, err = GetQueueEffectiveCapacity(rp, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return QueueCapacity{}, err
+ }
+
+ // Return
+ return QueueCapacity{
+ Total: total,
+ Effective: effective,
+ }, nil
+
+}
+
+// Get the total length of the minipool queue
+func GetQueueTotalLength(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ length := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, length, "getTotalLength"); err != nil {
+ return 0, fmt.Errorf("error getting total minipool queue length: %w", err)
+ }
+ return (*length).Uint64(), nil
+}
+
+// Get the total capacity of the minipool queue
+func GetQueueTotalCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ capacity := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, capacity, "getTotalCapacity"); err != nil {
+ return nil, fmt.Errorf("error getting minipool queue total capacity: %w", err)
+ }
+ return *capacity, nil
+}
+
+// Get the total effective capacity of the minipool queue (used in node demand calculation)
+func GetQueueEffectiveCapacity(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ capacity := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, capacity, "getEffectiveCapacity"); err != nil {
+ return nil, fmt.Errorf("error getting minipool queue effective capacity: %w", err)
+ }
+ return *capacity, nil
+}
+
+// Get Queue position details of a minipool
+func GetQueueDetails(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (QueueDetails, error) {
+ position, err := GetQueuePositionOfMinipool(rp, minipoolAddress, opts)
+ if err != nil {
+ return QueueDetails{}, err
+ }
+
+ // Return
+ return QueueDetails{
+ Position: position,
+ }, nil
+}
+
+// Get a minipools position in queue (1-indexed). 0 means it is currently not queued.
+func GetQueuePositionOfMinipool(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.CallOpts) (int64, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ position := new(*big.Int)
+ if err := rocketMinipoolQueue.Call(opts, position, "getMinipoolPosition", minipoolAddress); err != nil {
+ return 0, fmt.Errorf("error getting queue position for minipool %s: %w", minipoolAddress.Hex(), err)
+ }
+ return (*position).Int64() + 1, nil
+}
+
+// Get the minipool at the specified position in queue (0-indexed).
+func GetQueueMinipoolAtPosition(rp *rocketpool.RocketPool, position uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketMinipoolQueue, err := getRocketMinipoolQueue(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ address := new(common.Address)
+ if err := rocketMinipoolQueue.Call(opts, address, "getMinipoolAt", big.NewInt(int64(position))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting minipool at queue position %d: %w", position, err)
+ }
+ return *address, nil
+}
+
+// Get contracts
+var rocketMinipoolQueueLock sync.Mutex
+
+func getRocketMinipoolQueue(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolQueueLock.Lock()
+ defer rocketMinipoolQueueLock.Unlock()
+ return rp.GetContract("rocketMinipoolQueue", opts)
+}
diff --git a/bindings/minipool/status.go b/bindings/minipool/status.go
new file mode 100644
index 000000000..44eb2ab99
--- /dev/null
+++ b/bindings/minipool/status.go
@@ -0,0 +1,42 @@
+package minipool
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas of SubmitMinipoolWithdrawable
+func EstimateSubmitMinipoolWithdrawableGas(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketMinipoolStatus, err := getRocketMinipoolStatus(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketMinipoolStatus.GetTransactionGasInfo(opts, "submitMinipoolWithdrawable", minipoolAddress)
+}
+
+// Submit a minipool withdrawable event
+func SubmitMinipoolWithdrawable(rp *rocketpool.RocketPool, minipoolAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketMinipoolStatus, err := getRocketMinipoolStatus(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketMinipoolStatus.Transact(opts, "submitMinipoolWithdrawable", minipoolAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting minipool withdrawable event: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketMinipoolStatusLock sync.Mutex
+
+func getRocketMinipoolStatus(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketMinipoolStatusLock.Lock()
+ defer rocketMinipoolStatusLock.Unlock()
+ return rp.GetContract("rocketMinipoolStatus", opts)
+}
diff --git a/bindings/network/balances.go b/bindings/network/balances.go
new file mode 100644
index 000000000..681438fc8
--- /dev/null
+++ b/bindings/network/balances.go
@@ -0,0 +1,229 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Info for a balances updated event
+type BalancesUpdatedEvent struct {
+ BlockNumber *big.Int `json:"blockNumber"`
+ SlotTimestamp *big.Int `json:"slotTimestamp"`
+ TotalEth *big.Int `json:"totalEth"`
+ StakingEth *big.Int `json:"stakingEth"`
+ RethSupply *big.Int `json:"rethSupply"`
+ BlockTimestamp *big.Int `json:"blockTimestamp"`
+}
+
+// Get the block number which network balances are current for
+func GetBalancesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ balancesBlock := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, balancesBlock, "getBalancesBlock"); err != nil {
+ return 0, fmt.Errorf("error getting network balances block: %w", err)
+ }
+ return (*balancesBlock).Uint64(), nil
+}
+
+// Get the block number which network balances are current for
+func GetBalancesBlockRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ balancesBlock := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, balancesBlock, "getBalancesBlock"); err != nil {
+ return nil, fmt.Errorf("error getting network balances block: %w", err)
+ }
+ return *balancesBlock, nil
+}
+
+// Get the current network total ETH balance
+func GetTotalETHBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalEthBalance := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, totalEthBalance, "getTotalETHBalance"); err != nil {
+ return nil, fmt.Errorf("error getting network total ETH balance: %w", err)
+ }
+ return *totalEthBalance, nil
+}
+
+// Get the current network staking ETH balance
+func GetStakingETHBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ stakingEthBalance := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, stakingEthBalance, "getStakingETHBalance"); err != nil {
+ return nil, fmt.Errorf("error getting network staking ETH balance: %w", err)
+ }
+ return *stakingEthBalance, nil
+}
+
+// Get the current network total rETH supply
+func GetTotalRETHSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalRethSupply := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, totalRethSupply, "getTotalRETHSupply"); err != nil {
+ return nil, fmt.Errorf("error getting network total rETH supply: %w", err)
+ }
+ return *totalRethSupply, nil
+}
+
+// Get the current network ETH utilization rate
+func GetETHUtilizationRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ ethUtilizationRate := new(*big.Int)
+ if err := rocketNetworkBalances.Call(opts, ethUtilizationRate, "getETHUtilizationRate"); err != nil {
+ return 0, fmt.Errorf("error getting network ETH utilization rate: %w", err)
+ }
+ return eth.WeiToEth(*ethUtilizationRate), nil
+}
+
+// Estimate the gas of SubmitBalances
+func EstimateSubmitBalancesGas(rp *rocketpool.RocketPool, block uint64, slotTimestamp uint64, totalEth, stakingEth, rethSupply *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkBalances.GetTransactionGasInfo(opts, "submitBalances", big.NewInt(int64(block)), big.NewInt(int64(slotTimestamp)), totalEth, stakingEth, rethSupply)
+}
+
+// Submit network balances for an epoch
+func SubmitBalances(rp *rocketpool.RocketPool, block uint64, slotTimestamp uint64, totalEth, stakingEth, rethSupply *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkBalances.Transact(opts, "submitBalances", big.NewInt(int64(block)), big.NewInt(int64(slotTimestamp)), totalEth, stakingEth, rethSupply)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting network balances: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Returns an array of block numbers for balances submissions the given trusted node has submitted since fromBlock
+func GetBalancesSubmissions(rp *rocketpool.RocketPool, nodeAddress common.Address, fromBlock uint64, intervalSize *big.Int, opts *bind.CallOpts) (*[]uint64, error) {
+ // Get contracts
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketNetworkBalances.Address}
+ topicFilter := [][]common.Hash{{rocketNetworkBalances.ABI.Events["BalancesSubmitted"].ID}, {common.BytesToHash(nodeAddress.Bytes())}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, big.NewInt(int64(fromBlock)), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ timestamps := make([]uint64, len(logs))
+ for i, log := range logs {
+ values := make(map[string]interface{})
+ // Decode the event
+ if rocketNetworkBalances.ABI.Events["BalancesSubmitted"].Inputs.UnpackIntoMap(values, log.Data) != nil {
+ return nil, err
+ }
+ timestamps[i] = values["block"].(*big.Int).Uint64()
+ }
+ return ×tamps, nil
+}
+
+// Returns an array of members who submitted a balance since fromBlock
+func GetLatestBalancesSubmissions(rp *rocketpool.RocketPool, fromBlock uint64, intervalSize *big.Int, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get contracts
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketNetworkBalances.Address}
+ topicFilter := [][]common.Hash{{rocketNetworkBalances.ABI.Events["BalancesSubmitted"].ID}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, big.NewInt(int64(fromBlock)), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ results := make([]common.Address, len(logs))
+ for i, log := range logs {
+ // Topic 0 is the event, topic 1 is the "from" address
+ address := common.BytesToAddress(log.Topics[1].Bytes())
+ results[i] = address
+ }
+ return results, nil
+}
+
+func GetBalancesUpdatedEvent(rp *rocketpool.RocketPool, blockNumber uint64, opts *bind.CallOpts) (bool, BalancesUpdatedEvent, error) {
+ // Get contracts
+ rocketNetworkBalances, err := getRocketNetworkBalances(rp, opts)
+ if err != nil {
+ return false, BalancesUpdatedEvent{}, err
+ }
+
+ // Create the list of addresses to check
+ currentAddress := *rocketNetworkBalances.Address
+ rocketNetworkBalancesAddress := []common.Address{currentAddress}
+
+ // Construct a filter query for relevant logs
+ balancesUpdatedEvent := rocketNetworkBalances.ABI.Events["BalancesUpdated"]
+ indexBytes := [32]byte{}
+ addressFilter := rocketNetworkBalancesAddress
+ topicFilter := [][]common.Hash{{balancesUpdatedEvent.ID}, {indexBytes}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, big.NewInt(1), big.NewInt(int64(blockNumber)), big.NewInt(int64(blockNumber)), nil)
+ if err != nil {
+ return false, BalancesUpdatedEvent{}, err
+ }
+ if len(logs) == 0 {
+ return false, BalancesUpdatedEvent{}, nil
+ }
+
+ // Get the log info values
+ values, err := balancesUpdatedEvent.Inputs.Unpack(logs[0].Data)
+ if err != nil {
+ return false, BalancesUpdatedEvent{}, fmt.Errorf("error unpacking price updated event data: %w", err)
+ }
+
+ // Convert to a native struct
+ var eventData BalancesUpdatedEvent
+ err = balancesUpdatedEvent.Inputs.Copy(&eventData, values)
+ if err != nil {
+ return false, BalancesUpdatedEvent{}, fmt.Errorf("error converting price updated event data to struct: %w", err)
+ }
+
+ return true, eventData, nil
+}
+
+// Get contracts
+var rocketNetworkBalancesLock sync.Mutex
+
+func getRocketNetworkBalances(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkBalancesLock.Lock()
+ defer rocketNetworkBalancesLock.Unlock()
+ return rp.GetContract("rocketNetworkBalances", opts)
+}
diff --git a/bindings/network/fees.go b/bindings/network/fees.go
new file mode 100644
index 000000000..a2eded958
--- /dev/null
+++ b/bindings/network/fees.go
@@ -0,0 +1,60 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Get the current network node demand in ETH
+func GetNodeDemand(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkFees, err := getRocketNetworkFees(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeDemand := new(*big.Int)
+ if err := rocketNetworkFees.Call(opts, nodeDemand, "getNodeDemand"); err != nil {
+ return nil, fmt.Errorf("error getting network node demand: %w", err)
+ }
+ return *nodeDemand, nil
+}
+
+// Get the current network node commission rate
+func GetNodeFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ rocketNetworkFees, err := getRocketNetworkFees(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ nodeFee := new(*big.Int)
+ if err := rocketNetworkFees.Call(opts, nodeFee, "getNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting network node fee: %w", err)
+ }
+ return eth.WeiToEth(*nodeFee), nil
+}
+
+// Get the network node fee for a node demand value
+func GetNodeFeeByDemand(rp *rocketpool.RocketPool, nodeDemand *big.Int, opts *bind.CallOpts) (float64, error) {
+ rocketNetworkFees, err := getRocketNetworkFees(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ nodeFee := new(*big.Int)
+ if err := rocketNetworkFees.Call(opts, nodeFee, "getNodeFeeByDemand", nodeDemand); err != nil {
+ return 0, fmt.Errorf("error getting node fee by node demand: %w", err)
+ }
+ return eth.WeiToEth(*nodeFee), nil
+}
+
+// Get contracts
+var rocketNetworkFeesLock sync.Mutex
+
+func getRocketNetworkFees(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkFeesLock.Lock()
+ defer rocketNetworkFeesLock.Unlock()
+ return rp.GetContract("rocketNetworkFees", opts)
+}
diff --git a/bindings/network/penalties.go b/bindings/network/penalties.go
new file mode 100644
index 000000000..3cfa539f1
--- /dev/null
+++ b/bindings/network/penalties.go
@@ -0,0 +1,43 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas of SubmitPenalty
+func EstimateSubmitPenaltyGas(rp *rocketpool.RocketPool, minipoolAddress common.Address, block *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkPenalties, err := getRocketNetworkPenalties(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkPenalties.GetTransactionGasInfo(opts, "submitPenalty", minipoolAddress, block)
+}
+
+// Submit penalty for given minipool
+func SubmitPenalty(rp *rocketpool.RocketPool, minipoolAddress common.Address, block *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkPrices, err := getRocketNetworkPenalties(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkPrices.Transact(opts, "submitPenalty", minipoolAddress, block)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting penalty: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketNetworkPenaltiesLock sync.Mutex
+
+func getRocketNetworkPenalties(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkPenaltiesLock.Lock()
+ defer rocketNetworkPenaltiesLock.Unlock()
+ return rp.GetContract("rocketNetworkPenalties", opts)
+}
diff --git a/bindings/network/prices.go b/bindings/network/prices.go
new file mode 100644
index 000000000..4ba071bbd
--- /dev/null
+++ b/bindings/network/prices.go
@@ -0,0 +1,178 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Info for a price updated event
+type PriceUpdatedEvent struct {
+ BlockNumber *big.Int `json:"blockNumber"`
+ SlotTimestamp *big.Int `json:"slotTimestamp"`
+ RplPrice *big.Int `json:"rplPrice"`
+ Time *big.Int `json:"time"`
+}
+
+// Get the block number which network prices are current for
+func GetPricesBlock(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ pricesBlock := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, pricesBlock, "getPricesBlock"); err != nil {
+ return 0, fmt.Errorf("error getting network prices block: %w", err)
+ }
+ return (*pricesBlock).Uint64(), nil
+}
+
+// Get the current network RPL price in ETH
+func GetRPLPrice(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rplPrice := new(*big.Int)
+ if err := rocketNetworkPrices.Call(opts, rplPrice, "getRPLPrice"); err != nil {
+ return nil, fmt.Errorf("error getting network RPL price: %w", err)
+ }
+ return *rplPrice, nil
+}
+
+// Estimate the gas of SubmitPrices
+func EstimateSubmitPricesGas(rp *rocketpool.RocketPool, block uint64, slotTimestamp uint64, rplPrice *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkPrices.GetTransactionGasInfo(opts, "submitPrices", big.NewInt(int64(block)), big.NewInt(int64(slotTimestamp)), rplPrice)
+}
+
+// Submit network prices and total effective RPL stake for an epoch
+func SubmitPrices(rp *rocketpool.RocketPool, block uint64, slotTimestamp uint64, rplPrice *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkPrices.Transact(opts, "submitPrices", big.NewInt(int64(block)), big.NewInt(int64(slotTimestamp)), rplPrice)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting network prices: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Returns an array of block numbers for prices submissions the given trusted node has submitted since fromBlock
+func GetPricesSubmissions(rp *rocketpool.RocketPool, nodeAddress common.Address, fromBlock uint64, intervalSize *big.Int, opts *bind.CallOpts) (*[]uint64, error) {
+ // Get contracts
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketNetworkPrices.Address}
+ topicFilter := [][]common.Hash{{rocketNetworkPrices.ABI.Events["PricesSubmitted"].ID}, {common.BytesToHash(nodeAddress.Bytes())}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, big.NewInt(int64(fromBlock)), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ timestamps := make([]uint64, len(logs))
+ for i, log := range logs {
+ values := make(map[string]interface{})
+ // Decode the event
+ if rocketNetworkPrices.ABI.Events["PricesSubmitted"].Inputs.UnpackIntoMap(values, log.Data) != nil {
+ return nil, err
+ }
+ timestamps[i] = values["block"].(*big.Int).Uint64()
+ }
+ return ×tamps, nil
+}
+
+// Returns an array of members who submitted prices since fromBlock
+func GetLatestPricesSubmissions(rp *rocketpool.RocketPool, fromBlock uint64, intervalSize *big.Int, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get contracts
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Construct a filter query for relevant logs
+ addressFilter := []common.Address{*rocketNetworkPrices.Address}
+ topicFilter := [][]common.Hash{{rocketNetworkPrices.ABI.Events["PricesSubmitted"].ID}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, big.NewInt(int64(fromBlock)), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ results := make([]common.Address, len(logs))
+ for i, log := range logs {
+ // Topic 0 is the event, topic 1 is the "from" address
+ address := common.BytesToAddress(log.Topics[1].Bytes())
+ results[i] = address
+ }
+ return results, nil
+}
+
+// Get the event info for a price update
+func GetPriceUpdatedEvent(rp *rocketpool.RocketPool, blockNumber uint64, opts *bind.CallOpts) (bool, PriceUpdatedEvent, error) {
+ // Get contracts
+ rocketNetworkPrices, err := getRocketNetworkPrices(rp, opts)
+ if err != nil {
+ return false, PriceUpdatedEvent{}, err
+ }
+
+ indexBig := big.NewInt(0).SetUint64(blockNumber)
+
+ // Create the list of addresses to check
+ currentAddress := *rocketNetworkPrices.Address
+ rocketNetworkPricesAddress := []common.Address{currentAddress}
+
+ // Construct a filter query for relevant logs
+ pricesUpdatedEvent := rocketNetworkPrices.ABI.Events["PricesUpdated"]
+ indexBytes := [32]byte{}
+ indexBig.FillBytes(indexBytes[:])
+ addressFilter := rocketNetworkPricesAddress
+ topicFilter := [][]common.Hash{{pricesUpdatedEvent.ID}, {indexBytes}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, big.NewInt(100), big.NewInt(int64(blockNumber)), big.NewInt(int64(blockNumber+1000)), nil)
+ if err != nil {
+ return false, PriceUpdatedEvent{}, err
+ }
+ if len(logs) == 0 {
+ return false, PriceUpdatedEvent{}, nil
+ }
+
+ // Get the log info values
+ values, err := pricesUpdatedEvent.Inputs.Unpack(logs[0].Data)
+ if err != nil {
+ return false, PriceUpdatedEvent{}, fmt.Errorf("error unpacking price updated event data: %w", err)
+ }
+
+ // Convert to a native struct
+ var eventData PriceUpdatedEvent
+ err = pricesUpdatedEvent.Inputs.Copy(&eventData, values)
+ if err != nil {
+ return false, PriceUpdatedEvent{}, fmt.Errorf("error converting price updated event data to struct: %w", err)
+ }
+
+ return true, eventData, nil
+}
+
+// Get contracts
+var rocketNetworkPricesLock sync.Mutex
+
+func getRocketNetworkPrices(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkPricesLock.Lock()
+ defer rocketNetworkPricesLock.Unlock()
+ return rp.GetContract("rocketNetworkPrices", opts)
+}
diff --git a/bindings/network/voting.go b/bindings/network/voting.go
new file mode 100644
index 000000000..bfd8c3a11
--- /dev/null
+++ b/bindings/network/voting.go
@@ -0,0 +1,229 @@
+package network
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ nodeVotingDetailsBatchSize uint64 = 250
+)
+
+// Get the version of the Rocket Network Voting Contract
+func GetRocketNetworkVotingVersion(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint8, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ return rocketpool.GetContractVersion(rp, *rocketNetworkVoting.Address, opts)
+}
+
+// Gets the voting power and delegation info for every node at the specified block using multicall
+func GetNodeInfoSnapshotFast(rp *rocketpool.RocketPool, blockNumber uint32, multicallAddress common.Address, opts *bind.CallOpts) ([]types.NodeVotingInfo, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get the number of voting nodes
+ nodeCountBig, err := GetVotingNodeCount(rp, blockNumber, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting voting node count: %w", err)
+ }
+ nodeCount := nodeCountBig.Uint64()
+
+ // Get the node addresses
+ nodeAddresses, err := node.GetNodeAddressesFast(rp, multicallAddress, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting node addresses: %w", err)
+ }
+
+ // Sync
+ var wg errgroup.Group
+
+ // Run the getters in batches
+ votingInfos := make([]types.NodeVotingInfo, nodeCount)
+ for i := uint64(0); i < nodeCount; i += nodeVotingDetailsBatchSize {
+ i := i
+ max := i + nodeVotingDetailsBatchSize
+ if max > nodeCount {
+ max = nodeCount
+ }
+
+ // Load details
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, multicallAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ nodeAddress := nodeAddresses[j]
+ votingInfos[j].NodeAddress = nodeAddress
+ mc.AddCall(rocketNetworkVoting, &votingInfos[j].VotingPower, "getVotingPower", nodeAddress, blockNumber)
+ mc.AddCall(rocketNetworkVoting, &votingInfos[j].Delegate, "getDelegate", nodeAddress, blockNumber)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ return votingInfos, nil
+}
+
+// Check whether or not on-chain voting has been initialized for the given node
+func GetVotingInitialized(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketNetworkVoting.Call(opts, value, "getVotingInitialised", address); err != nil {
+ return false, fmt.Errorf("error getting voting initialized status: %w", err)
+ }
+ return *value, nil
+}
+
+// Estimate the gas of InitializeVoting
+func EstimateInitializeVotingGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkVoting.GetTransactionGasInfo(opts, "initialiseVoting")
+}
+
+// Initialize on-chain voting for the node
+func InitializeVoting(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkVoting.Transact(opts, "initialiseVoting")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error initializing voting: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of InitializeVotingWithDelegate
+func EstimateInitializeVotingWithDelegateGas(rp *rocketpool.RocketPool, delegateAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkVoting.GetTransactionGasInfo(opts, "initialiseVotingWithDelegate", delegateAddress)
+}
+
+// Initialize on-chain voting for the node and delegate voting power at the same transaction
+func InitializeVotingWithDelegate(rp *rocketpool.RocketPool, delegateAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkVoting.Transact(opts, "initialiseVotingWithDelegate", delegateAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error initializing voting with delegate: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the number of nodes that were present in the network at the provided block
+func GetVotingNodeCount(rp *rocketpool.RocketPool, blockNumber uint32, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketNetworkVoting.Call(opts, value, "getNodeCount", blockNumber); err != nil {
+ return nil, fmt.Errorf("error getting node count for block %d: %w", blockNumber, err)
+ }
+ return *value, nil
+}
+
+// Get the voting power of the given node on the provided block
+func GetVotingPower(rp *rocketpool.RocketPool, address common.Address, blockNumber uint32, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketNetworkVoting.Call(opts, value, "getVotingPower", address, blockNumber); err != nil {
+ return nil, fmt.Errorf("error getting voting power for node %s on block %d: %w", address.Hex(), blockNumber, err)
+ }
+ return *value, nil
+}
+
+// Get the address that the provided node has delegated voting power to on the given block
+func GetVotingDelegate(rp *rocketpool.RocketPool, address common.Address, blockNumber uint32, opts *bind.CallOpts) (common.Address, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return common.Address{}, err
+ }
+ value := new(common.Address)
+ if err := rocketNetworkVoting.Call(opts, value, "getDelegate", address, blockNumber); err != nil {
+ return common.Address{}, fmt.Errorf("error getting delegate for node %s on block %d: %w", address.Hex(), blockNumber, err)
+ }
+ return *value, nil
+}
+
+// Get the address that the provided node has currently delegated voting power to
+func GetCurrentVotingDelegate(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (common.Address, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return common.Address{}, err
+ }
+ value := new(common.Address)
+ if err := rocketNetworkVoting.Call(opts, value, "getCurrentDelegate", address); err != nil {
+ return common.Address{}, fmt.Errorf("error getting current delegate for node %s: %w", address.Hex(), err)
+ }
+ return *value, nil
+}
+
+// Estimate the gas of SetVotingDelegate
+func EstimateSetVotingDelegateGas(rp *rocketpool.RocketPool, newDelegate common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNetworkVoting.GetTransactionGasInfo(opts, "setDelegate", newDelegate)
+}
+
+// Set the voting delegate for the node
+func SetVotingDelegate(rp *rocketpool.RocketPool, newDelegate common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNetworkVoting, err := getRocketNetworkVoting(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNetworkVoting.Transact(opts, "setDelegate", newDelegate)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting voting delegate: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketNetworkVotingLock sync.Mutex
+
+func getRocketNetworkVoting(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkVotingLock.Lock()
+ defer rocketNetworkVotingLock.Unlock()
+ return rp.GetContract("rocketNetworkVoting", opts)
+}
diff --git a/bindings/node/deposit.go b/bindings/node/deposit.go
new file mode 100644
index 000000000..57bd2f875
--- /dev/null
+++ b/bindings/node/deposit.go
@@ -0,0 +1,182 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Estimate the gas of Deposit
+func EstimateDepositGas(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeDeposit.GetTransactionGasInfo(opts, "deposit", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+}
+
+// Make a node deposit
+func Deposit(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ tx, err := rocketNodeDeposit.Transact(opts, "deposit", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error making node deposit: %w", err)
+ }
+ return tx, nil
+}
+
+// Estimate the gas to WithdrawETH
+func EstimateWithdrawEthGas(rp *rocketpool.RocketPool, nodeAccount common.Address, ethAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeDeposit.GetTransactionGasInfo(opts, "withdrawEth", nodeAccount, ethAmount)
+}
+
+// Withdraw unused Ether that was staked on behalf of the node
+func WithdrawEth(rp *rocketpool.RocketPool, nodeAccount common.Address, ethAmount *big.Int, opts *bind.TransactOpts) (*types.Transaction, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ tx, err := rocketNodeDeposit.Transact(opts, "withdrawEth", nodeAccount, ethAmount)
+ if err != nil {
+ return nil, fmt.Errorf("error trying to withdraw ETH: %w", err)
+ }
+ return tx, nil
+}
+
+// Estimate the gas of DepositWithCredit
+func EstimateDepositWithCreditGas(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeDeposit.GetTransactionGasInfo(opts, "depositWithCredit", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+}
+
+// Make a node deposit by using the credit balance
+func DepositWithCredit(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, validatorSignature rptypes.ValidatorSignature, depositDataRoot common.Hash, salt *big.Int, expectedMinipoolAddress common.Address, opts *bind.TransactOpts) (*types.Transaction, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ tx, err := rocketNodeDeposit.Transact(opts, "depositWithCredit", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], validatorSignature[:], depositDataRoot, salt, expectedMinipoolAddress)
+ if err != nil {
+ return nil, fmt.Errorf("error making node deposit with credit: %w", err)
+ }
+ return tx, nil
+}
+
+// Estimate the gas of CreateVacantMinipool
+func EstimateCreateVacantMinipoolGas(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, salt *big.Int, expectedMinipoolAddress common.Address, currentBalance *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeDeposit.GetTransactionGasInfo(opts, "createVacantMinipool", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], salt, expectedMinipoolAddress, currentBalance)
+}
+
+// Make a vacant minipool for solo staker migration
+func CreateVacantMinipool(rp *rocketpool.RocketPool, bondAmount *big.Int, minimumNodeFee float64, validatorPubkey rptypes.ValidatorPubkey, salt *big.Int, expectedMinipoolAddress common.Address, currentBalance *big.Int, opts *bind.TransactOpts) (*types.Transaction, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ tx, err := rocketNodeDeposit.Transact(opts, "createVacantMinipool", bondAmount, eth.EthToWei(minimumNodeFee), validatorPubkey[:], salt, expectedMinipoolAddress, currentBalance)
+ if err != nil {
+ return nil, fmt.Errorf("error creating vacant minipool: %w", err)
+ }
+ return tx, nil
+}
+
+// Get the amount of ETH in the node's deposit credit bank
+func GetNodeDepositCredit(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ creditBalance := new(*big.Int)
+ if err := rocketNodeDeposit.Call(opts, creditBalance, "getNodeDepositCredit", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node deposit credit: %w", err)
+ }
+ return *creditBalance, nil
+}
+
+// Get the current ETH balance for the given node operator
+func GetNodeEthBalance(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ creditBalance := new(*big.Int)
+ if err := rocketNodeDeposit.Call(opts, creditBalance, "getNodeEthBalance", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node ETH balance: %w", err)
+ }
+ return *creditBalance, nil
+}
+
+// Get the sum of the credit balance of a given node operator and their ETH balance
+func GetNodeCreditAndBalance(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ creditAndBalance := new(*big.Int)
+ if err := rocketNodeDeposit.Call(opts, creditAndBalance, "getNodeCreditAndBalance", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node credit and ETH balance: %w", err)
+ }
+ return *creditAndBalance, nil
+}
+
+// Get the sum of the amount of ETH credit currently usable by a given node operator and their balance
+func GetNodeUsableCreditAndBalance(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ usableCreditBalance := new(*big.Int)
+ if err := rocketNodeDeposit.Call(opts, usableCreditBalance, "getNodeUsableCreditAndBalance", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node usable credit and ETH balance: %w", err)
+ }
+ return *usableCreditBalance, nil
+}
+
+// Get the amount of ETH credit currently usable by a given node operator
+func GetNodeUsableCredit(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeDeposit, err := getRocketNodeDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ usableCredit := new(*big.Int)
+ if err := rocketNodeDeposit.Call(opts, usableCredit, "getNodeUsableCredit", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node usable credit: %w", err)
+ }
+ return *usableCredit, nil
+}
+
+// Get contracts
+var rocketNodeDepositLock sync.Mutex
+
+func getRocketNodeDeposit(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeDepositLock.Lock()
+ defer rocketNodeDepositLock.Unlock()
+ return rp.GetContract("rocketNodeDeposit", opts)
+}
diff --git a/bindings/node/distributor.go b/bindings/node/distributor.go
new file mode 100644
index 000000000..e914aba5e
--- /dev/null
+++ b/bindings/node/distributor.go
@@ -0,0 +1,99 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Distributor contract
+type Distributor struct {
+ Address common.Address
+ Contract *rocketpool.Contract
+ RocketPool *rocketpool.RocketPool
+}
+
+// Create new distributor contract
+func NewDistributor(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (*Distributor, error) {
+
+ // Get contract
+ contract, err := getDistributorContract(rp, address, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create and return
+ return &Distributor{
+ Address: address,
+ Contract: contract,
+ RocketPool: rp,
+ }, nil
+}
+
+// Gets the deterministic address for a node's reward distributor contract
+func GetDistributorAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ rocketNodeDistributorFactory, err := getRocketNodeDistributorFactory(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ var address common.Address
+ if err := rocketNodeDistributorFactory.Call(opts, &address, "getProxyAddress", nodeAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting distributor address: %w", err)
+ }
+ return address, nil
+}
+
+// Estimate the gas of a distribute
+func (d *Distributor) EstimateDistributeGas(opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return d.Contract.GetTransactionGasInfo(opts, "distribute")
+}
+
+// Distribute the contract's balance to the rETH contract and the user
+func (d *Distributor) Distribute(opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := d.Contract.Transact(opts, "distribute")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error distributing fee distributor balance: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Gets the node share of the distributor's current balance
+func (d *Distributor) GetNodeShare(opts *bind.CallOpts) (*big.Int, error) {
+ nodeShare := new(*big.Int)
+ if err := d.Contract.Call(opts, nodeShare, "getNodeShare"); err != nil {
+ return nil, fmt.Errorf("error getting distributor %s node share: %w", d.Address.Hex(), err)
+ }
+ return *nodeShare, nil
+}
+
+// Gets the user share of the distributor's current balance
+func (d *Distributor) GetUserShare(opts *bind.CallOpts) (*big.Int, error) {
+ userShare := new(*big.Int)
+ if err := d.Contract.Call(opts, userShare, "getUserShare"); err != nil {
+ return nil, fmt.Errorf("error getting distributor %s user share: %w", d.Address.Hex(), err)
+ }
+ return *userShare, nil
+}
+
+// Get contracts
+var rocketNodeDistributorFactoryLock sync.Mutex
+
+func getRocketNodeDistributorFactory(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeDistributorFactoryLock.Lock()
+ defer rocketNodeDistributorFactoryLock.Unlock()
+ return rp.GetContract("rocketNodeDistributorFactory", opts)
+}
+
+// Get a distributor contract
+var rocketDistributorLock sync.Mutex
+
+func getDistributorContract(rp *rocketpool.RocketPool, distributorAddress common.Address, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDistributorLock.Lock()
+ defer rocketDistributorLock.Unlock()
+ return rp.MakeContract("rocketNodeDistributorDelegate", distributorAddress, opts)
+}
diff --git a/bindings/node/node.go b/bindings/node/node.go
new file mode 100644
index 000000000..494f30a2a
--- /dev/null
+++ b/bindings/node/node.go
@@ -0,0 +1,741 @@
+package node
+
+import (
+ "fmt"
+ "math"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/storage"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "github.com/rocket-pool/rocketpool-go/utils/strings"
+)
+
+// Settings
+const (
+ nodeAddressFastBatchSize int = 1000
+ NodeAddressBatchSize = 50
+ NodeDetailsBatchSize = 20
+ SmoothingPoolCountBatchSize uint64 = 2000
+ NativeNodeDetailsBatchSize = 10000
+)
+
+// Node details
+type NodeDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ PrimaryWithdrawalAddress common.Address `json:"primaryWithdrawalAddress"`
+ PendingPrimaryWithdrawalAddress common.Address `json:"pendingPrimaryWithdrawalAddress"`
+ IsRPLWithdrawalAddressSet bool `json:"isRPLWithdrawalAddressSet"`
+ RPLWithdrawalAddress common.Address `json:"rplWithdrawalAddress"`
+ PendingRPLWithdrawalAddress common.Address `json:"pendingRPLWithdrawalAddress"`
+ TimezoneLocation string `json:"timezoneLocation"`
+}
+
+// Count of nodes belonging to a timezone
+type TimezoneCount struct {
+ Timezone string `abi:"timezone"`
+ Count *big.Int `abi:"count"`
+}
+
+// The results of the trusted node participation calculation
+type TrustedNodeParticipation struct {
+ StartBlock uint64
+ UpdateFrequency uint64
+ UpdateCount uint64
+ Probability float64
+ ExpectedSubmissions float64
+ ActualSubmissions map[common.Address]float64
+ Participation map[common.Address][]bool
+}
+
+// Get the version of the Node Manager contract
+func GetNodeManagerVersion(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint8, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ return rocketpool.GetContractVersion(rp, *rocketNodeManager.Address, opts)
+}
+
+// Get all node details
+// The 'includeRplWithdrawalAddress' flag is used for backwards compatibility with Atlas, - set it to `false` if Houston hasn't been deployed yet
+func GetNodes(rp *rocketpool.RocketPool, includeRplWithdrawalAddress bool, opts *bind.CallOpts) ([]NodeDetails, error) {
+
+ // Get node addresses
+ nodeAddresses, err := GetNodeAddresses(rp, opts)
+ if err != nil {
+ return []NodeDetails{}, err
+ }
+
+ // Load node details in batches
+ details := make([]NodeDetails, len(nodeAddresses))
+ for bsi := 0; bsi < len(nodeAddresses); bsi += NodeDetailsBatchSize {
+
+ // Get batch start & end index
+ nsi := bsi
+ nei := bsi + NodeDetailsBatchSize
+ if nei > len(nodeAddresses) {
+ nei = len(nodeAddresses)
+ }
+
+ // Load details
+ var wg errgroup.Group
+ for ni := nsi; ni < nei; ni++ {
+ ni := ni
+ wg.Go(func() error {
+ nodeAddress := nodeAddresses[ni]
+ nodeDetails, err := GetNodeDetails(rp, nodeAddress, includeRplWithdrawalAddress, opts)
+ if err == nil {
+ details[ni] = nodeDetails
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []NodeDetails{}, err
+ }
+
+ }
+
+ // Return
+ return details, nil
+
+}
+
+// Get all node addresses
+func GetNodeAddresses(rp *rocketpool.RocketPool, opts *bind.CallOpts) ([]common.Address, error) {
+
+ // Get node count
+ nodeCount, err := GetNodeCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Load node addresses in batches
+ addresses := make([]common.Address, nodeCount)
+ for bsi := uint64(0); bsi < nodeCount; bsi += NodeAddressBatchSize {
+
+ // Get batch start & end index
+ nsi := bsi
+ nei := bsi + NodeAddressBatchSize
+ if nei > nodeCount {
+ nei = nodeCount
+ }
+
+ // Load addresses
+ var wg errgroup.Group
+ for ni := nsi; ni < nei; ni++ {
+ ni := ni
+ wg.Go(func() error {
+ address, err := GetNodeAt(rp, ni, opts)
+ if err == nil {
+ addresses[ni] = address
+ }
+ return err
+ })
+ }
+ if err := wg.Wait(); err != nil {
+ return []common.Address{}, err
+ }
+
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Get all node addresses using a multicaller
+func GetNodeAddressesFast(rp *rocketpool.RocketPool, multicallAddress common.Address, opts *bind.CallOpts) ([]common.Address, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get minipool count
+ nodeCount, err := GetNodeCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Sync
+ var wg errgroup.Group
+ addresses := make([]common.Address, nodeCount)
+
+ // Run the getters in batches
+ count := int(nodeCount)
+ for i := 0; i < count; i += nodeAddressFastBatchSize {
+ i := i
+ max := i + nodeAddressFastBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, multicallAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ mc.AddCall(rocketNodeManager, &addresses[j], "getNodeAt", big.NewInt(int64(j)))
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting node addresses: %w", err)
+ }
+
+ return addresses, nil
+}
+
+// Get a node's details
+// The 'includeRplWithdrawalAddress' flag is used for backwards compatibility with Atlas, - set it to `false` if Houston hasn't been deployed yet
+func GetNodeDetails(rp *rocketpool.RocketPool, nodeAddress common.Address, includeRplWithdrawalAddress bool, opts *bind.CallOpts) (NodeDetails, error) {
+
+ // Data
+ var wg errgroup.Group
+ var exists bool
+ var primaryWithdrawalAddress common.Address
+ var pendingPrimaryWithdrawalAddress common.Address
+ var isRPLWithdrawalAddressSet bool
+ var rplWithdrawalAddress common.Address
+ var pendingRPLWithdrawalAddress common.Address
+ var timezoneLocation string
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ exists, err = GetNodeExists(rp, nodeAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ primaryWithdrawalAddress, err = storage.GetNodeWithdrawalAddress(rp, nodeAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ pendingPrimaryWithdrawalAddress, err = storage.GetNodePendingWithdrawalAddress(rp, nodeAddress, opts)
+ return err
+ })
+ if includeRplWithdrawalAddress {
+ wg.Go(func() error {
+ var err error
+ isRPLWithdrawalAddressSet, err = GetNodeRPLWithdrawalAddressIsSet(rp, nodeAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ rplWithdrawalAddress, err = GetNodeRPLWithdrawalAddress(rp, nodeAddress, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ pendingRPLWithdrawalAddress, err = GetNodePendingRPLWithdrawalAddress(rp, nodeAddress, opts)
+ return err
+ })
+ }
+ wg.Go(func() error {
+ var err error
+ timezoneLocation, err = GetNodeTimezoneLocation(rp, nodeAddress, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return NodeDetails{}, err
+ }
+
+ // Return
+ return NodeDetails{
+ Address: nodeAddress,
+ Exists: exists,
+ PrimaryWithdrawalAddress: primaryWithdrawalAddress,
+ PendingPrimaryWithdrawalAddress: pendingPrimaryWithdrawalAddress,
+ IsRPLWithdrawalAddressSet: isRPLWithdrawalAddressSet,
+ RPLWithdrawalAddress: rplWithdrawalAddress,
+ PendingRPLWithdrawalAddress: pendingRPLWithdrawalAddress,
+ TimezoneLocation: timezoneLocation,
+ }, nil
+
+}
+
+// Get the number of nodes in the network
+func GetNodeCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ nodeCount := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, nodeCount, "getNodeCount"); err != nil {
+ return 0, fmt.Errorf("error getting node count: %w", err)
+ }
+ return (*nodeCount).Uint64(), nil
+}
+
+// Get a breakdown of the number of nodes per timezone
+func GetNodeCountPerTimezone(rp *rocketpool.RocketPool, offset, limit *big.Int, opts *bind.CallOpts) ([]TimezoneCount, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return []TimezoneCount{}, err
+ }
+ timezoneCounts := new([]TimezoneCount)
+ if err := rocketNodeManager.Call(opts, timezoneCounts, "getNodeCountPerTimezone", offset, limit); err != nil {
+ return []TimezoneCount{}, fmt.Errorf("error getting node count: %w", err)
+ }
+ return *timezoneCounts, nil
+}
+
+// Get a node address by index
+func GetNodeAt(rp *rocketpool.RocketPool, index uint64, opts *bind.CallOpts) (common.Address, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ nodeAddress := new(common.Address)
+ if err := rocketNodeManager.Call(opts, nodeAddress, "getNodeAt", big.NewInt(int64(index))); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %d address: %w", index, err)
+ }
+ return *nodeAddress, nil
+}
+
+// Check whether a node exists
+func GetNodeExists(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ exists := new(bool)
+ if err := rocketNodeManager.Call(opts, exists, "getNodeExists", nodeAddress); err != nil {
+ return false, fmt.Errorf("error getting node %s exists status: %w", nodeAddress.Hex(), err)
+ }
+ return *exists, nil
+}
+
+// Get a node's timezone location
+func GetNodeTimezoneLocation(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (string, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return "", err
+ }
+ timezoneLocation := new(string)
+ if err := rocketNodeManager.Call(opts, timezoneLocation, "getNodeTimezoneLocation", nodeAddress); err != nil {
+ return "", fmt.Errorf("error getting node %s timezone location: %w", nodeAddress.Hex(), err)
+ }
+ return strings.Sanitize(*timezoneLocation), nil
+}
+
+// Estimate the gas of RegisterNode
+func EstimateRegisterNodeGas(rp *rocketpool.RocketPool, timezoneLocation string, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ _, err = time.LoadLocation(timezoneLocation)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error verifying timezone [%s]: %w", timezoneLocation, err)
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "registerNode", timezoneLocation)
+}
+
+// Register a node
+func RegisterNode(rp *rocketpool.RocketPool, timezoneLocation string, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ _, err = time.LoadLocation(timezoneLocation)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error verifying timezone [%s]: %w", timezoneLocation, err)
+ }
+ tx, err := rocketNodeManager.Transact(opts, "registerNode", timezoneLocation)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error registering node: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of SetTimezoneLocation
+func EstimateSetTimezoneLocationGas(rp *rocketpool.RocketPool, timezoneLocation string, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ _, err = time.LoadLocation(timezoneLocation)
+ if err != nil {
+ return rocketpool.GasInfo{}, fmt.Errorf("error verifying timezone [%s]: %w", timezoneLocation, err)
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "setTimezoneLocation", timezoneLocation)
+}
+
+// Set a node's timezone location
+func SetTimezoneLocation(rp *rocketpool.RocketPool, timezoneLocation string, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ _, err = time.LoadLocation(timezoneLocation)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error verifying timezone [%s]: %w", timezoneLocation, err)
+ }
+ tx, err := rocketNodeManager.Transact(opts, "setTimezoneLocation", timezoneLocation)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting node timezone location: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the network ID for a node's rewards
+func GetRewardNetwork(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ rewardNetwork := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, rewardNetwork, "getRewardNetwork", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s reward network: %w", nodeAddress.Hex(), err)
+ }
+ return (*rewardNetwork).Uint64(), nil
+}
+
+// Get the network ID for a node's rewards
+func GetRewardNetworkRaw(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rewardNetwork := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, rewardNetwork, "getRewardNetwork", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node %s reward network: %w", nodeAddress.Hex(), err)
+ }
+ return *rewardNetwork, nil
+}
+
+// Check if a node's fee distributor has been initialized yet
+func GetFeeDistributorInitialized(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ isInitialized := new(bool)
+ if err := rocketNodeManager.Call(opts, isInitialized, "getFeeDistributorInitialised", nodeAddress); err != nil {
+ return false, fmt.Errorf("error checking if node %s's fee distributor is initialized: %w", nodeAddress.Hex(), err)
+ }
+ return *isInitialized, nil
+}
+
+// Estimate the gas for creating the fee distributor contract for a node
+func EstimateInitializeFeeDistributorGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "initialiseFeeDistributor")
+}
+
+// Create the fee distributor contract for a node
+func InitializeFeeDistributor(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeManager.Transact(opts, "initialiseFeeDistributor")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error initializing fee distributor: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get a node's average minipool fee
+func GetNodeAverageFee(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (float64, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ avgFee := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, avgFee, "getAverageNodeFee", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node %s average fee: %w", nodeAddress.Hex(), err)
+ }
+ return eth.WeiToEth(*avgFee), nil
+}
+
+// Get a node's average minipool fee
+func GetNodeAverageFeeRaw(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ avgFee := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, avgFee, "getAverageNodeFee", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node %s average fee: %w", nodeAddress.Hex(), err)
+ }
+ return *avgFee, nil
+}
+
+// Get the time that the user registered as a claimer
+func GetNodeRegistrationTime(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (time.Time, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ registrationTime := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, registrationTime, "getNodeRegistrationTime", address); err != nil {
+ return time.Time{}, fmt.Errorf("error getting registration time for %s: %w", address.Hex(), err)
+ }
+ return time.Unix((*registrationTime).Int64(), 0), nil
+}
+
+// Get the time that the user registered as a claimer
+func GetNodeRegistrationTimeRaw(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ registrationTime := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, registrationTime, "getNodeRegistrationTime", address); err != nil {
+ return nil, fmt.Errorf("error getting registration time for %s: %w", address.Hex(), err)
+ }
+ return *registrationTime, nil
+}
+
+// Get the smoothing pool opt-in status of a node
+func GetSmoothingPoolRegistrationState(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ state := new(bool)
+ if err := rocketNodeManager.Call(opts, state, "getSmoothingPoolRegistrationState", nodeAddress); err != nil {
+ return false, fmt.Errorf("error getting node %s smoothing pool registration status: %w", nodeAddress.Hex(), err)
+ }
+ return *state, nil
+}
+
+// Get the time of the previous smoothing pool opt-in / opt-out
+func GetSmoothingPoolRegistrationChanged(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (time.Time, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ timestamp := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, timestamp, "getSmoothingPoolRegistrationChanged", nodeAddress); err != nil {
+ return time.Time{}, fmt.Errorf("error getting node %s's last smoothing pool registration change time: %w", nodeAddress.Hex(), err)
+ }
+ return time.Unix((*timestamp).Int64(), 0), nil
+}
+
+// Get the time of the previous smoothing pool opt-in / opt-out
+func GetSmoothingPoolRegistrationChangedRaw(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ timestamp := new(*big.Int)
+ if err := rocketNodeManager.Call(opts, timestamp, "getSmoothingPoolRegistrationChanged", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node %s's last smoothing pool registration change time: %w", nodeAddress.Hex(), err)
+ }
+ return *timestamp, nil
+}
+
+// Estimate the gas for opting into / out of the smoothing pool
+func EstimateSetSmoothingPoolRegistrationStateGas(rp *rocketpool.RocketPool, optIn bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "setSmoothingPoolRegistrationState", optIn)
+}
+
+// Opt into / out of the smoothing pool
+func SetSmoothingPoolRegistrationState(rp *rocketpool.RocketPool, optIn bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeManager.Transact(opts, "setSmoothingPoolRegistrationState", optIn)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting smoothing pool registration state: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the number of nodes in the Smoothing Pool
+func GetSmoothingPoolRegisteredNodeCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+
+ // Get the number of nodes
+ nodeCount, err := GetNodeCount(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+
+ iterations := uint64(math.Ceil(float64(nodeCount) / float64(SmoothingPoolCountBatchSize)))
+ iterationCounts := make([]*big.Int, iterations)
+
+ // Load addresses
+ var wg errgroup.Group
+ for i := uint64(0); i < iterations; i++ {
+ i := i
+ offset := i * SmoothingPoolCountBatchSize
+ limit := SmoothingPoolCountBatchSize
+ if nodeCount-offset < SmoothingPoolCountBatchSize {
+ limit = nodeCount - offset
+ }
+ wg.Go(func() error {
+ count := new(*big.Int)
+ err := rocketNodeManager.Call(opts, count, "getSmoothingPoolRegisteredNodeCount", big.NewInt(int64(offset)), big.NewInt(int64(limit)))
+ if err != nil {
+ return fmt.Errorf("error getting smoothing pool opt-in count for batch starting at %d: %w", offset, err)
+ }
+
+ iterationCounts[i] = *count
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return 0, err
+ }
+
+ total := uint64(0)
+ for _, count := range iterationCounts {
+ total += count.Uint64()
+ }
+
+ return total, nil
+
+}
+
+// Check if the RPL-specific withdrawal address has been set
+func GetNodeRPLWithdrawalAddressIsSet(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketNodeManager.Call(opts, value, "getNodeRPLWithdrawalAddressIsSet", nodeAddress); err != nil {
+ return false, fmt.Errorf("error getting node %s's RPL withdrawal address status: %w", nodeAddress.Hex(), err)
+ }
+ return *value, nil
+}
+
+// Get the RPL-specific withdrawal address
+func GetNodeRPLWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ value := new(common.Address)
+ if err := rocketNodeManager.Call(opts, value, "getNodeRPLWithdrawalAddress", nodeAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s's RPL withdrawal address: %w", nodeAddress.Hex(), err)
+ }
+ return *value, nil
+}
+
+// Get the pending RPL-specific withdrawal address
+func GetNodePendingRPLWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ value := new(common.Address)
+ if err := rocketNodeManager.Call(opts, value, "getNodePendingRPLWithdrawalAddress", nodeAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s's pending RPL withdrawal address: %w", nodeAddress.Hex(), err)
+ }
+ return *value, nil
+}
+
+// Estimate the gas for setting the RPL-specific withdrawal address
+func EstimateSetRPLWithdrawalAddressGas(rp *rocketpool.RocketPool, nodeAddress common.Address, withdrawalAddress common.Address, confirm bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "setRPLWithdrawalAddress", nodeAddress, withdrawalAddress, confirm)
+}
+
+// Set the RPL-specific withdrawal address
+func SetRPLWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, withdrawalAddress common.Address, confirm bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeManager.Transact(opts, "setRPLWithdrawalAddress", nodeAddress, withdrawalAddress, confirm)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting RPL withdrawal address: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas for confirming the RPL-specific withdrawal address
+func EstimateConfirmRPLWithdrawalAddressGas(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeManager.GetTransactionGasInfo(opts, "confirmRPLWithdrawalAddress", nodeAddress)
+}
+
+// Confirm the RPL-specific withdrawal address
+func ConfirmRPLWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeManager, err := getRocketNodeManager(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeManager.Transact(opts, "confirmRPLWithdrawalAddress", nodeAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error confirming RPL withdrawal address: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketNodeManagerLock sync.Mutex
+
+func getRocketNodeManager(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeManagerLock.Lock()
+ defer rocketNodeManagerLock.Unlock()
+ return rp.GetContract("rocketNodeManager", opts)
+}
+
+var rocketNetworkPricesLock sync.Mutex
+
+func getRocketNetworkPrices(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkPricesLock.Lock()
+ defer rocketNetworkPricesLock.Unlock()
+ return rp.GetContract("rocketNetworkPrices", opts)
+}
+
+var rocketNetworkBalancesLock sync.Mutex
+
+func getRocketNetworkBalances(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNetworkBalancesLock.Lock()
+ defer rocketNetworkBalancesLock.Unlock()
+ return rp.GetContract("rocketNetworkBalances", opts)
+}
+
+var rocketDAONodeTrustedActionsLock sync.Mutex
+
+func getRocketDAONodeTrustedActions(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDAONodeTrustedActionsLock.Lock()
+ defer rocketDAONodeTrustedActionsLock.Unlock()
+ return rp.GetContract("rocketDAONodeTrustedActions", opts)
+}
diff --git a/bindings/node/staking.go b/bindings/node/staking.go
new file mode 100644
index 000000000..a1cfa1e13
--- /dev/null
+++ b/bindings/node/staking.go
@@ -0,0 +1,273 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get the version of the Node Staking contract
+func GetNodeStakingVersion(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint8, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ return rocketpool.GetContractVersion(rp, *rocketNodeStaking.Address, opts)
+}
+
+// Get the total RPL staked in the network
+func GetTotalRPLStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, totalRplStake, "getTotalRPLStake"); err != nil {
+ return nil, fmt.Errorf("error getting total network RPL stake: %w", err)
+ }
+ return *totalRplStake, nil
+}
+
+// Get a node's RPL stake
+func GetNodeRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeRplStake, "getNodeRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting total node RPL stake: %w", err)
+ }
+ return *nodeRplStake, nil
+}
+
+// Get a node's effective RPL stake
+func GetNodeEffectiveRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeEffectiveRplStakeWrapper := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeEffectiveRplStakeWrapper, "getNodeEffectiveRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting effective node RPL stake: %w", err)
+ }
+
+ minimumStake, err := GetNodeMinimumRPLStake(rp, nodeAddress, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minimum node RPL stake to verify effective stake: %w", err)
+ }
+
+ nodeEffectiveRplStake := *nodeEffectiveRplStakeWrapper
+ if nodeEffectiveRplStake.Cmp(minimumStake) == -1 {
+ // Effective stake should be zero if it's less than the minimum RPL stake
+ return big.NewInt(0), nil
+ }
+
+ return nodeEffectiveRplStake, nil
+}
+
+// Get a node's minimum RPL stake to collateralize their minipools
+func GetNodeMinimumRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeMinimumRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeMinimumRplStake, "getNodeMinimumRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting minimum node RPL stake: %w", err)
+ }
+ return *nodeMinimumRplStake, nil
+}
+
+// Get a node's maximum RPL stake to collateralize their minipools
+func GetNodeMaximumRPLStake(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeMaximumRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeMaximumRplStake, "getNodeMaximumRPLStake", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting maximum node RPL stake: %w", err)
+ }
+ return *nodeMaximumRplStake, nil
+}
+
+// Get the time a node last staked RPL
+func GetNodeRPLStakedTime(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (uint64, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ nodeRplStakedTime := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeRplStakedTime, "getNodeRPLStakedTime", nodeAddress); err != nil {
+ return 0, fmt.Errorf("error getting node RPL staked time: %w", err)
+ }
+ return (*nodeRplStakedTime).Uint64(), nil
+}
+
+// Get the amount of ETH the node has borrowed from the deposit pool to create its minipools
+func GetNodeEthMatched(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeEthMatched := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeEthMatched, "getNodeETHMatched", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node ETH matched: %w", err)
+ }
+ return *nodeEthMatched, nil
+}
+
+// Get the amount of ETH the node can borrow from the deposit pool to create its minipools
+func GetNodeEthMatchedLimit(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ nodeEthMatchedLimit := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, nodeEthMatchedLimit, "getNodeETHMatchedLimit", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node ETH matched limit: %w", err)
+ }
+ return *nodeEthMatchedLimit, nil
+}
+
+// Estimate the gas of Stake
+func EstimateStakeGas(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "stakeRPL", rplAmount)
+}
+
+// Stake RPL
+func StakeRPL(rp *rocketpool.RocketPool, rplAmount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "stakeRPL", rplAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error staking RPL: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of set RPL locking allowed
+func EstimateSetRPLLockingAllowedGas(rp *rocketpool.RocketPool, caller common.Address, allowed bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "setRPLLockingAllowed", caller, allowed)
+}
+
+// Set RPL locking allowed
+func SetRPLLockingAllowed(rp *rocketpool.RocketPool, caller common.Address, allowed bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "setRPLLockingAllowed", caller, allowed)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting RPL locking allowed: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get RPL locking allowed state for a node
+func GetRPLLockedAllowed(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rocketNodeStaking.Call(opts, value, "getRPLLockingAllowed", nodeAddress); err != nil {
+ return false, fmt.Errorf("error getting node RPL locked: %w", err)
+ }
+ return *value, nil
+}
+
+// Estimate the gas of set stake RPL for allowed
+func EstimateSetStakeRPLForAllowedGas(rp *rocketpool.RocketPool, caller common.Address, allowed bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "setStakeRPLForAllowed", caller, allowed)
+}
+
+// Set stake RPL for allowed
+func SetStakeRPLForAllowed(rp *rocketpool.RocketPool, caller common.Address, allowed bool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "setStakeRPLForAllowed", caller, allowed)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting stake RPL for allowed: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of WithdrawRPL
+func EstimateWithdrawRPLGas(rp *rocketpool.RocketPool, nodeAddress common.Address, rplAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketNodeStaking.GetTransactionGasInfo(opts, "withdrawRPL", nodeAddress, rplAmount)
+}
+
+// Withdraw staked RPL
+func WithdrawRPL(rp *rocketpool.RocketPool, nodeAddress common.Address, rplAmount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketNodeStaking.Transact(opts, "withdrawRPL", nodeAddress, rplAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error withdrawing staked RPL: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Calculate total effective RPL stake
+func CalculateTotalEffectiveRPLStake(rp *rocketpool.RocketPool, offset, limit, rplPrice *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalEffectiveRplStake := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, totalEffectiveRplStake, "calculateTotalEffectiveRPLStake", offset, limit, rplPrice); err != nil {
+ return nil, fmt.Errorf("error getting total effective RPL stake: %w", err)
+ }
+ return *totalEffectiveRplStake, nil
+}
+
+// Get the amount of RPL locked as part of active PDAO proposals or challenges
+func GetNodeRPLLocked(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketNodeStaking, err := getRocketNodeStaking(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rocketNodeStaking.Call(opts, value, "getNodeRPLLocked", nodeAddress); err != nil {
+ return nil, fmt.Errorf("error getting node RPL locked: %w", err)
+ }
+ return *value, nil
+}
+
+// Get contracts
+var rocketNodeStakingLock sync.Mutex
+
+func getRocketNodeStaking(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketNodeStakingLock.Lock()
+ defer rocketNodeStakingLock.Unlock()
+ return rp.GetContract("rocketNodeStaking", opts)
+}
diff --git a/bindings/rewards/distributor-mainnet.go b/bindings/rewards/distributor-mainnet.go
new file mode 100644
index 000000000..a4395c7ea
--- /dev/null
+++ b/bindings/rewards/distributor-mainnet.go
@@ -0,0 +1,90 @@
+package rewards
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Check if the given node has already claimed rewards for the given interval
+func IsClaimed(rp *rocketpool.RocketPool, index *big.Int, claimerAddress common.Address, opts *bind.CallOpts) (bool, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ isClaimed := new(bool)
+ if err := rocketDistributorMainnet.Call(opts, isClaimed, "isClaimed", index, claimerAddress); err != nil {
+ return false, fmt.Errorf("error getting rewards claim status for interval %s, node %s: %w", index.String(), claimerAddress.Hex(), err)
+ }
+ return *isClaimed, nil
+}
+
+// Get the Merkle root for an interval
+func MerkleRoots(rp *rocketpool.RocketPool, interval *big.Int, opts *bind.CallOpts) ([]byte, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ bytes := new([32]byte)
+ if err := rocketDistributorMainnet.Call(opts, bytes, "merkleRoots", interval); err != nil {
+ return nil, fmt.Errorf("error getting Merkle root for interval %s: %w", interval.String(), err)
+ }
+ return (*bytes)[:], nil
+}
+
+// Estimate claim rewards gas
+func EstimateClaimGas(rp *rocketpool.RocketPool, address common.Address, indices []*big.Int, amountRPL []*big.Int, amountETH []*big.Int, merkleProofs [][]common.Hash, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDistributorMainnet.GetTransactionGasInfo(opts, "claim", address, indices, amountRPL, amountETH, merkleProofs)
+}
+
+// Claim rewards
+func Claim(rp *rocketpool.RocketPool, address common.Address, indices []*big.Int, amountRPL []*big.Int, amountETH []*big.Int, merkleProofs [][]common.Hash, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDistributorMainnet.Transact(opts, "claim", address, indices, amountRPL, amountETH, merkleProofs)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error claiming rewards: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate claim and restake rewards gas
+func EstimateClaimAndStakeGas(rp *rocketpool.RocketPool, address common.Address, indices []*big.Int, amountRPL []*big.Int, amountETH []*big.Int, merkleProofs [][]common.Hash, stakeAmount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketDistributorMainnet.GetTransactionGasInfo(opts, "claimAndStake", address, indices, amountRPL, amountETH, merkleProofs, stakeAmount)
+}
+
+// Claim and restake rewards
+func ClaimAndStake(rp *rocketpool.RocketPool, address common.Address, indices []*big.Int, amountRPL []*big.Int, amountETH []*big.Int, merkleProofs [][]common.Hash, stakeAmount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketDistributorMainnet, err := getRocketDistributorMainnet(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketDistributorMainnet.Transact(opts, "claimAndStake", address, indices, amountRPL, amountETH, merkleProofs, stakeAmount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error claiming rewards: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get contracts
+var rocketDistributorMainnetLock sync.Mutex
+
+func getRocketDistributorMainnet(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketDistributorMainnetLock.Lock()
+ defer rocketDistributorMainnetLock.Unlock()
+ return rp.GetContract("rocketMerkleDistributorMainnet", opts)
+}
diff --git a/bindings/rewards/rewards.go b/bindings/rewards/rewards.go
new file mode 100644
index 000000000..352ad108f
--- /dev/null
+++ b/bindings/rewards/rewards.go
@@ -0,0 +1,327 @@
+package rewards
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+const (
+ rewardsSnapshotSubmittedNodeKey string = "rewards.snapshot.submitted.node.key"
+)
+
+// Info for a rewards snapshot event
+type RewardsEvent struct {
+ Index *big.Int
+ ExecutionBlock *big.Int
+ ConsensusBlock *big.Int
+ MerkleRoot common.Hash
+ MerkleTreeCID string
+ IntervalsPassed *big.Int
+ TreasuryRPL *big.Int
+ TrustedNodeRPL []*big.Int
+ NodeRPL []*big.Int
+ NodeETH []*big.Int
+ UserETH *big.Int
+ IntervalStartTime time.Time
+ IntervalEndTime time.Time
+ SubmissionTime time.Time
+}
+
+// Struct for submitting the rewards for a checkpoint
+type RewardSubmission struct {
+ RewardIndex *big.Int `json:"rewardIndex"`
+ ExecutionBlock *big.Int `json:"executionBlock"`
+ ConsensusBlock *big.Int `json:"consensusBlock"`
+ MerkleRoot [32]byte `json:"merkleRoot"`
+ MerkleTreeCID string `json:"merkleTreeCID"`
+ IntervalsPassed *big.Int `json:"intervalsPassed"`
+ TreasuryRPL *big.Int `json:"treasuryRPL"`
+ TrustedNodeRPL []*big.Int `json:"trustedNodeRPL"`
+ NodeRPL []*big.Int `json:"nodeRPL"`
+ NodeETH []*big.Int `json:"nodeETH"`
+ UserETH *big.Int `json:"userETH"`
+}
+
+// Internal struct - this is the structure of what gets returned by the RewardSnapshot event
+type rewardSnapshot struct {
+ RewardIndex *big.Int `json:"rewardIndex"`
+ Submission RewardSubmission `json:"submission"`
+ IntervalStartTime *big.Int `json:"intervalStartTime"`
+ IntervalEndTime *big.Int `json:"intervalEndTime"`
+ Time *big.Int `json:"time"`
+}
+
+// Get the timestamp that the current rewards interval started
+func GetClaimIntervalTimeStart(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Time, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTimeStart"); err != nil {
+ return time.Time{}, fmt.Errorf("error getting claim interval time start: %w", err)
+ }
+ return time.Unix((*unixTime).Int64(), 0), nil
+}
+
+// Get the number of seconds in a claim interval
+func GetClaimIntervalTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ unixTime := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, unixTime, "getClaimIntervalTime"); err != nil {
+ return 0, fmt.Errorf("error getting claim interval time: %w", err)
+ }
+ return time.Duration((*unixTime).Int64()) * time.Second, nil
+}
+
+// Get the percent of checkpoint rewards that goes to node operators
+func GetNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimNode"); err != nil {
+ return nil, fmt.Errorf("error getting node operator rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the percent of checkpoint rewards that goes to ODAO members
+func GetTrustedNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimTrustedNode"); err != nil {
+ return nil, fmt.Errorf("error getting trusted node operator rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the percent of checkpoint rewards that goes to the PDAO
+func GetProtocolDaoRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ perc := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, perc, "getClaimingContractPerc", "rocketClaimDAO"); err != nil {
+ return nil, fmt.Errorf("error getting protocol DAO rewards percent: %w", err)
+ }
+ return *perc, nil
+}
+
+// Get the amount of RPL rewards that will be provided to node operators
+func GetPendingRPLRewards(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rewards := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, rewards, "getPendingRPLRewards"); err != nil {
+ return nil, fmt.Errorf("error getting pending RPL rewards: %w", err)
+ }
+ return *rewards, nil
+}
+
+// Get the amount of ETH rewards that will be provided to node operators
+func GetPendingETHRewards(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rewards := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, rewards, "getPendingETHRewards"); err != nil {
+ return nil, fmt.Errorf("error getting pending ETH rewards: %w", err)
+ }
+ return *rewards, nil
+}
+
+// Check whether or not the given address has submitted for the given rewards interval
+func GetTrustedNodeSubmitted(rp *rocketpool.RocketPool, nodeAddress common.Address, rewardsIndex uint64, opts *bind.CallOpts) (bool, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return false, err
+ }
+
+ indexBig := big.NewInt(0).SetUint64(rewardsIndex)
+ hasSubmitted := new(bool)
+ if err := rocketRewardsPool.Call(opts, hasSubmitted, "getTrustedNodeSubmitted", nodeAddress, indexBig); err != nil {
+ return false, fmt.Errorf("error getting trusted node submission status: %w", err)
+ }
+ return *hasSubmitted, nil
+}
+
+// Check whether or not the given address has submitted specific rewards info
+func GetTrustedNodeSubmittedSpecificRewards(rp *rocketpool.RocketPool, nodeAddress common.Address, submission RewardSubmission, opts *bind.CallOpts) (bool, error) {
+ // NOTE: this doesn't have a view yet so we have to construct it manually, and RLP
+ stringTy, _ := abi.NewType("string", "string", nil)
+ addressTy, _ := abi.NewType("address", "address", nil)
+
+ submissionTy, _ := abi.NewType("tuple", "struct RewardSubmission", []abi.ArgumentMarshaling{
+ {Name: "rewardIndex", Type: "uint256"},
+ {Name: "executionBlock", Type: "uint256"},
+ {Name: "consensusBlock", Type: "uint256"},
+ {Name: "merkleRoot", Type: "bytes32"},
+ {Name: "merkleTreeCID", Type: "string"},
+ {Name: "intervalsPassed", Type: "uint256"},
+ {Name: "treasuryRPL", Type: "uint256"},
+ {Name: "trustedNodeRPL", Type: "uint256[]"},
+ {Name: "nodeRPL", Type: "uint256[]"},
+ {Name: "nodeETH", Type: "uint256[]"},
+ {Name: "userETH", Type: "uint256"},
+ })
+
+ args := abi.Arguments{
+ {Type: stringTy, Name: "key"},
+ {Type: addressTy, Name: "trustedNodeAddress"},
+ {Type: submissionTy, Name: "submission"},
+ }
+
+ bytes, err := args.Pack(rewardsSnapshotSubmittedNodeKey, nodeAddress, &submission)
+ if err != nil {
+ return false, fmt.Errorf("error encoding submission data into ABI format: %w", err)
+ }
+
+ key := crypto.Keccak256Hash(bytes)
+ result, err := rp.RocketStorage.GetBool(opts, key)
+ if err != nil {
+ return false, fmt.Errorf("error checking if trusted node submitted specific rewards: %w", err)
+ }
+ return result, nil
+}
+
+// Estimate the gas for submiting a Merkle Tree-based snapshot for a rewards interval
+func EstimateSubmitRewardSnapshotGas(rp *rocketpool.RocketPool, submission RewardSubmission, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketRewardsPool.GetTransactionGasInfo(opts, "submitRewardSnapshot", submission)
+}
+
+// Submit a Merkle Tree-based snapshot for a rewards interval
+func SubmitRewardSnapshot(rp *rocketpool.RocketPool, submission RewardSubmission, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketRewardsPool.Transact(opts, "submitRewardSnapshot", submission)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error submitting rewards snapshot: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the event info for a rewards snapshot using the Atlas getter
+func GetRewardsEvent(rp *rocketpool.RocketPool, index uint64, rocketRewardsPoolAddresses []common.Address, opts *bind.CallOpts) (bool, RewardsEvent, error) {
+ // Get contracts
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return false, RewardsEvent{}, err
+ }
+
+ // Get the block that the event was emitted on
+ indexBig := big.NewInt(0).SetUint64(index)
+ blockWrapper := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, blockWrapper, "getClaimIntervalExecutionBlock", indexBig); err != nil {
+ return false, RewardsEvent{}, fmt.Errorf("error getting the event block for interval %d: %w", index, err)
+ }
+ block := *blockWrapper
+
+ // Create the list of addresses to check
+ currentAddress := *rocketRewardsPool.Address
+ if rocketRewardsPoolAddresses == nil {
+ rocketRewardsPoolAddresses = []common.Address{currentAddress}
+ } else {
+ found := false
+ for _, address := range rocketRewardsPoolAddresses {
+ if address == currentAddress {
+ found = true
+ break
+ }
+ }
+ if !found {
+ rocketRewardsPoolAddresses = append(rocketRewardsPoolAddresses, currentAddress)
+ }
+ }
+
+ // Construct a filter query for relevant logs
+ rewardsSnapshotEvent := rocketRewardsPool.ABI.Events["RewardSnapshot"]
+ indexBytes := [32]byte{}
+ indexBig.FillBytes(indexBytes[:])
+ addressFilter := rocketRewardsPoolAddresses
+ topicFilter := [][]common.Hash{{rewardsSnapshotEvent.ID}, {indexBytes}}
+
+ // Get the event logs
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, big.NewInt(1), block, block, nil)
+ if err != nil {
+ return false, RewardsEvent{}, err
+ }
+ if len(logs) == 0 {
+ return false, RewardsEvent{}, nil
+ }
+
+ // Get the log info values
+ values, err := rewardsSnapshotEvent.Inputs.Unpack(logs[0].Data)
+ if err != nil {
+ return false, RewardsEvent{}, fmt.Errorf("error unpacking rewards snapshot event data: %w", err)
+ }
+
+ // Convert to a native struct
+ var snapshot rewardSnapshot
+ err = rewardsSnapshotEvent.Inputs.Copy(&snapshot, values)
+ if err != nil {
+ return false, RewardsEvent{}, fmt.Errorf("error converting rewards snapshot event data to struct: %w", err)
+ }
+
+ // Get the decoded data
+ submission := snapshot.Submission
+ eventData := RewardsEvent{
+ Index: indexBig,
+ ExecutionBlock: submission.ExecutionBlock,
+ ConsensusBlock: submission.ConsensusBlock,
+ IntervalsPassed: submission.IntervalsPassed,
+ TreasuryRPL: submission.TreasuryRPL,
+ TrustedNodeRPL: submission.TrustedNodeRPL,
+ NodeRPL: submission.NodeRPL,
+ NodeETH: submission.NodeETH,
+ UserETH: submission.UserETH,
+ MerkleRoot: submission.MerkleRoot,
+ MerkleTreeCID: submission.MerkleTreeCID,
+ IntervalStartTime: time.Unix(snapshot.IntervalStartTime.Int64(), 0),
+ IntervalEndTime: time.Unix(snapshot.IntervalEndTime.Int64(), 0),
+ SubmissionTime: time.Unix(snapshot.Time.Int64(), 0),
+ }
+
+ // Convert v1.1.0-rc1 events to modern ones
+ if eventData.UserETH == nil {
+ eventData.UserETH = big.NewInt(0)
+ }
+
+ return true, eventData, nil
+}
+
+// Get contracts
+var rocketRewardsPoolLock sync.Mutex
+
+func getRocketRewardsPool(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketRewardsPoolLock.Lock()
+ defer rocketRewardsPoolLock.Unlock()
+ return rp.GetContract("rocketRewardsPool", opts)
+}
diff --git a/bindings/rocketpool/abi.go b/bindings/rocketpool/abi.go
new file mode 100644
index 000000000..46e17a014
--- /dev/null
+++ b/bindings/rocketpool/abi.go
@@ -0,0 +1,70 @@
+package rocketpool
+
+import (
+ "bytes"
+ "compress/zlib"
+ "encoding/base64"
+ "fmt"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+)
+
+var decoderCache sync.Map
+
+// Decode, decompress and parse a zlib-compressed, base64-encoded ABI
+func DecodeAbi(abiEncoded string) (*abi.ABI, error) {
+
+ if cached, ok := decoderCache.Load(abiEncoded); ok {
+ return cached.(*abi.ABI), nil
+ }
+
+ // base64 decode
+ abiCompressed, err := base64.StdEncoding.DecodeString(abiEncoded)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding base64 data: %w", err)
+ }
+
+ // zlib decompress
+ byteReader := bytes.NewReader(abiCompressed)
+ zlibReader, err := zlib.NewReader(byteReader)
+ if err != nil {
+ return nil, fmt.Errorf("error decompressing zlib data: %w", err)
+ }
+ defer func() {
+ _ = zlibReader.Close()
+ }()
+
+ // Parse ABI
+ abiParsed, err := abi.JSON(zlibReader)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing JSON: %w", err)
+ }
+
+ decoderCache.Store(abiEncoded, &abiParsed)
+
+ // Return
+ return &abiParsed, nil
+
+}
+
+// zlib-compress and base64-encode an ABI JSON string
+func EncodeAbiStr(abiStr string) (string, error) {
+
+ // zlib compress
+ var abiCompressed bytes.Buffer
+ zlibWriter := zlib.NewWriter(&abiCompressed)
+ if _, err := zlibWriter.Write([]byte(abiStr)); err != nil {
+ return "", fmt.Errorf("error zlib-compressing ABI string: %w", err)
+ }
+ if err := zlibWriter.Flush(); err != nil {
+ return "", fmt.Errorf("error zlib-compressing ABI string: %w", err)
+ }
+ if err := zlibWriter.Close(); err != nil {
+ return "", fmt.Errorf("error zlib-compressing ABI string: %w", err)
+ }
+
+ // base64 encode & return
+ return base64.StdEncoding.EncodeToString(abiCompressed.Bytes()), nil
+
+}
diff --git a/bindings/rocketpool/contract.go b/bindings/rocketpool/contract.go
new file mode 100644
index 000000000..598ed4140
--- /dev/null
+++ b/bindings/rocketpool/contract.go
@@ -0,0 +1,252 @@
+package rocketpool
+
+import (
+ "bytes"
+ "context"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "math/big"
+ "reflect"
+ "regexp"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// Transaction settings
+const (
+ GasLimitMultiplier float64 = 1.5
+ MaxGasLimit uint64 = 30000000
+ NethermindRevertRegex string = "Reverted 0x(?P[0-9a-fA-F]+).*"
+)
+
+// Contract type wraps go-ethereum bound contract
+type Contract struct {
+ Contract *bind.BoundContract
+ Address *common.Address
+ ABI *abi.ABI
+ Client ExecutionClient
+}
+
+// Response for gas limits from network and from user request
+type GasInfo struct {
+ EstGasLimit uint64 `json:"estGasLimit"`
+ SafeGasLimit uint64 `json:"safeGasLimit"`
+}
+
+// Call a contract method
+func (c *Contract) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+ results := make([]interface{}, 1)
+ results[0] = result
+ return c.Contract.Call(opts, &results, method, params...)
+}
+
+// Get Gas Limit for transaction
+func (c *Contract) GetTransactionGasInfo(opts *bind.TransactOpts, method string, params ...interface{}) (GasInfo, error) {
+
+ response := GasInfo{}
+
+ // Pack transaction Info
+ input, err := c.ABI.Pack(method, params...)
+ if err != nil {
+ return response, fmt.Errorf("Error getting transaction gas info: Could not encode input data: %w", err)
+ }
+
+ // Estimate gas limit
+ estGasLimit, safeGasLimit, err := c.estimateGasLimit(opts, input)
+
+ if err != nil {
+ return response, fmt.Errorf("Error getting transaction gas info: could not estimate gas limit: %w", err)
+ }
+ response.EstGasLimit = estGasLimit
+ response.SafeGasLimit = safeGasLimit
+
+ return response, err
+}
+
+// Transact on a contract method and wait for a receipt
+func (c *Contract) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+
+ // Estimate gas limit
+ if opts.GasLimit == 0 {
+ input, err := c.ABI.Pack(method, params...)
+ if err != nil {
+ return nil, fmt.Errorf("error encoding input data: %w", err)
+ }
+ _, safeGasLimit, err := c.estimateGasLimit(opts, input)
+ if err != nil {
+ return nil, err
+ }
+ opts.GasLimit = safeGasLimit
+ }
+
+ // Send transaction
+ tx, err := c.Contract.Transact(opts, method, params...)
+ if err != nil {
+ return nil, c.normalizeErrorMessage(err)
+ }
+
+ return tx, nil
+
+}
+
+// Get gas limit for a transfer call
+func (c *Contract) GetTransferGasInfo(opts *bind.TransactOpts) (GasInfo, error) {
+
+ response := GasInfo{}
+
+ // Estimate gas limit
+ estGasLimit, safeGasLimit, err := c.estimateGasLimit(opts, []byte{})
+ if err != nil {
+ return response, fmt.Errorf("Error getting transfer gas info: could not estimate gas limit: %w", err)
+ }
+ response.EstGasLimit = estGasLimit
+ response.SafeGasLimit = safeGasLimit
+
+ return response, nil
+}
+
+// Transfer ETH to a contract and wait for a receipt
+func (c *Contract) Transfer(opts *bind.TransactOpts) (common.Hash, error) {
+
+ // Estimate gas limit
+ if opts.GasLimit == 0 {
+ _, safeGasLimit, err := c.estimateGasLimit(opts, []byte{})
+ if err != nil {
+ return common.Hash{}, err
+ }
+ opts.GasLimit = safeGasLimit
+ }
+
+ // Send transaction
+ tx, err := c.Contract.Transfer(opts)
+ if err != nil {
+ return common.Hash{}, c.normalizeErrorMessage(err)
+ }
+
+ return tx.Hash(), nil
+
+}
+
+// Estimate the expected and safe gas limits for a contract transaction
+func (c *Contract) estimateGasLimit(opts *bind.TransactOpts, input []byte) (uint64, uint64, error) {
+
+ // Estimate gas limit
+ gasLimit, err := c.Client.EstimateGas(context.Background(), ethereum.CallMsg{
+ From: opts.From,
+ To: c.Address,
+ GasPrice: big.NewInt(0), // use 0 gwei for simulation
+ Value: opts.Value,
+ Data: input,
+ })
+
+ if err != nil {
+ return 0, 0, fmt.Errorf("error estimating gas needed: %w", c.normalizeErrorMessage(err))
+ }
+
+ // Pad and return gas limit
+ safeGasLimit := uint64(float64(gasLimit) * GasLimitMultiplier)
+ if gasLimit > MaxGasLimit {
+ return 0, 0, fmt.Errorf("estimated gas of %d is greater than the max gas limit of %d", gasLimit, MaxGasLimit)
+ }
+ if safeGasLimit > MaxGasLimit {
+ safeGasLimit = MaxGasLimit
+ }
+ return gasLimit, safeGasLimit, nil
+
+}
+
+// Wait for a transaction to be mined and get a tx receipt
+func (c *Contract) getTransactionReceipt(tx *types.Transaction) (*types.Receipt, error) {
+
+ // Wait for transaction to be mined
+ txReceipt, err := bind.WaitMined(context.Background(), c.Client, tx)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check transaction status
+ if txReceipt.Status == 0 {
+ return txReceipt, errors.New("Transaction failed with status 0")
+ }
+
+ // Return
+ return txReceipt, nil
+
+}
+
+// Get contract events from a transaction
+// eventPrototype must be an event struct type
+// Returns a slice of untyped values; assert returned events to event struct type
+func (c *Contract) GetTransactionEvents(txReceipt *types.Receipt, eventName string, eventPrototype interface{}) ([]interface{}, error) {
+
+ // Get event type
+ eventType := reflect.TypeOf(eventPrototype)
+ if eventType.Kind() != reflect.Struct {
+ return nil, errors.New("Invalid event type")
+ }
+
+ // Get ABI event
+ abiEvent, ok := c.ABI.Events[eventName]
+ if !ok {
+ return nil, fmt.Errorf("Event '%s' does not exist on contract", eventName)
+ }
+
+ // Process transaction receipt logs
+ events := make([]interface{}, 0)
+ for _, log := range txReceipt.Logs {
+
+ // Check log address matches contract address
+ if !bytes.Equal(log.Address.Bytes(), c.Address.Bytes()) {
+ continue
+ }
+
+ // Check log first topic matches event ID
+ if len(log.Topics) == 0 || !bytes.Equal(log.Topics[0].Bytes(), abiEvent.ID.Bytes()) {
+ continue
+ }
+
+ // Unpack event
+ event := reflect.New(eventType)
+ if err := c.Contract.UnpackLog(event.Interface(), eventName, *log); err != nil {
+ return nil, fmt.Errorf("error unpacking event data: %w", err)
+ }
+ events = append(events, reflect.Indirect(event).Interface())
+
+ }
+
+ // Return events
+ return events, nil
+
+}
+
+// Normalize error messages so they're all in ASCII format
+func (c *Contract) normalizeErrorMessage(err error) error {
+ if err == nil {
+ return err
+ }
+
+ // Get the message in hex format, if it exists
+ reg := regexp.MustCompile(NethermindRevertRegex)
+ matches := reg.FindStringSubmatch(err.Error())
+ if matches == nil {
+ return err
+ }
+ messageIndex := reg.SubexpIndex("message")
+ if messageIndex == -1 {
+ return err
+ }
+ message := matches[messageIndex]
+
+ // Convert the hex message to ASCII
+ bytes, err2 := hex.DecodeString(message)
+ if err2 != nil {
+ return err // Return the original error if decoding failed somehow
+ }
+
+ return fmt.Errorf("Reverted: %s", string(bytes))
+}
diff --git a/bindings/rocketpool/ec-interface.go b/bindings/rocketpool/ec-interface.go
new file mode 100644
index 000000000..5e44a2377
--- /dev/null
+++ b/bindings/rocketpool/ec-interface.go
@@ -0,0 +1,105 @@
+package rocketpool
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// This is the common interface for execution clients.
+type ExecutionClient interface {
+
+ /// ========================
+ /// ContractCaller Functions
+ /// ========================
+
+ // CodeAt returns the code of the given account. This is needed to differentiate
+ // between contract internal errors and the local chain being out of sync.
+ CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
+
+ // CallContract executes an Ethereum contract call with the specified data as the
+ // input.
+ CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
+
+ /// ============================
+ /// ContractTransactor Functions
+ /// ============================
+
+ // HeaderByHash returns the block header with the given hash.
+ HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
+
+ // HeaderByNumber returns a block header from the current canonical chain. If number is
+ // nil, the latest known header is returned.
+ HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
+
+ // PendingCodeAt returns the code of the given account in the pending state.
+ PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
+
+ // PendingNonceAt retrieves the current pending nonce associated with an account.
+ PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
+
+ // SuggestGasPrice retrieves the currently suggested gas price to allow a timely
+ // execution of a transaction.
+ SuggestGasPrice(ctx context.Context) (*big.Int, error)
+
+ // SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
+ // a timely execution of a transaction.
+ SuggestGasTipCap(ctx context.Context) (*big.Int, error)
+
+ // EstimateGas tries to estimate the gas needed to execute a specific
+ // transaction based on the current pending state of the backend blockchain.
+ // There is no guarantee that this is the true gas limit requirement as other
+ // transactions may be added or removed by miners, but it should provide a basis
+ // for setting a reasonable default.
+ EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
+
+ // SendTransaction injects the transaction into the pending pool for execution.
+ SendTransaction(ctx context.Context, tx *types.Transaction) error
+
+ /// ==========================
+ /// ContractFilterer Functions
+ /// ==========================
+
+ // FilterLogs executes a log filter operation, blocking during execution and
+ // returning all the results in one batch.
+ //
+ // TODO(karalabe): Deprecate when the subscription one can return past data too.
+ FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
+
+ // SubscribeFilterLogs creates a background log filtering operation, returning
+ // a subscription immediately, which can be used to stream the found events.
+ SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
+
+ /// =======================
+ /// DeployBackend Functions
+ /// =======================
+
+ // TransactionReceipt returns the receipt of a transaction by transaction hash.
+ // Note that the receipt is not available for pending transactions.
+ TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
+
+ /// ================
+ /// Client functions
+ /// ================
+
+ // BlockNumber returns the most recent block number
+ BlockNumber(ctx context.Context) (uint64, error)
+
+ // BalanceAt returns the wei balance of the given account.
+ // The block number can be nil, in which case the balance is taken from the latest known block.
+ BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
+
+ // TransactionByHash returns the transaction with the given hash.
+ TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
+
+ // NonceAt returns the account nonce of the given account.
+ // The block number can be nil, in which case the nonce is taken from the latest known block.
+ NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
+
+ // SyncProgress retrieves the current progress of the sync algorithm. If there's
+ // no sync currently running, it returns nil.
+ SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)
+}
diff --git a/bindings/rocketpool/rewards.go b/bindings/rocketpool/rewards.go
new file mode 100644
index 000000000..85204074f
--- /dev/null
+++ b/bindings/rocketpool/rewards.go
@@ -0,0 +1,35 @@
+package rocketpool
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+)
+
+// rocketpool-go's dependencies are all inverted: the subfolders have dependencies back to
+// the main package. This is less than ideal, but hard to fix- instead, I will be migrating content
+// out of the subpackages into the main package to fulfill interfaces as needed.
+
+// Get the index of the active rewards period
+func (rp *RocketPool) GetRewardIndex(opts *bind.CallOpts) (*big.Int, error) {
+ rocketRewardsPool, err := getRocketRewardsPool(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ index := new(*big.Int)
+ if err := rocketRewardsPool.Call(opts, index, "getRewardIndex"); err != nil {
+ return nil, fmt.Errorf("error getting current reward index: %w", err)
+ }
+ return *index, nil
+}
+
+// Get contracts
+var rocketRewardsPoolLock sync.Mutex
+
+func getRocketRewardsPool(rp *RocketPool, opts *bind.CallOpts) (*Contract, error) {
+ rocketRewardsPoolLock.Lock()
+ defer rocketRewardsPoolLock.Unlock()
+ return rp.GetContract("rocketRewardsPool", opts)
+}
diff --git a/bindings/rocketpool/rocketpool.go b/bindings/rocketpool/rocketpool.go
new file mode 100644
index 000000000..59e6c72d1
--- /dev/null
+++ b/bindings/rocketpool/rocketpool.go
@@ -0,0 +1,365 @@
+package rocketpool
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/contracts"
+)
+
+// Cache settings
+const CacheTTL = 300 // 5 minutes
+
+// Cached data types
+type cachedAddress struct {
+ address *common.Address
+ time int64
+}
+type cachedABI struct {
+ abi *abi.ABI
+ time int64
+}
+type cachedContract struct {
+ contract *Contract
+ time int64
+}
+
+// Rocket Pool contract manager
+type RocketPool struct {
+ Client ExecutionClient
+ RocketStorage *contracts.RocketStorage
+ RocketStorageContract *Contract
+ VersionManager *VersionManager
+ addresses map[string]cachedAddress
+ abis map[string]cachedABI
+ contracts map[string]cachedContract
+ addressesLock sync.RWMutex
+ abisLock sync.RWMutex
+ contractsLock sync.RWMutex
+}
+
+// Create new contract manager
+func NewRocketPool(client ExecutionClient, rocketStorageAddress common.Address) (*RocketPool, error) {
+
+ // Initialize RocketStorage contract
+ rocketStorage, err := contracts.NewRocketStorage(rocketStorageAddress, client)
+ if err != nil {
+ return nil, fmt.Errorf("error initializing Rocket Pool storage contract: %w", err)
+ }
+
+ // Create a Contract for it
+ rsAbi, err := abi.JSON(strings.NewReader(contracts.RocketStorageABI))
+ if err != nil {
+ return nil, err
+ }
+ contract := &Contract{
+ Contract: bind.NewBoundContract(rocketStorageAddress, rsAbi, client, client, client),
+ Address: &rocketStorageAddress,
+ ABI: &rsAbi,
+ Client: client,
+ }
+
+ // Create and return
+ rp := &RocketPool{
+ Client: client,
+ RocketStorage: rocketStorage,
+ RocketStorageContract: contract,
+ addresses: make(map[string]cachedAddress),
+ abis: make(map[string]cachedABI),
+ contracts: make(map[string]cachedContract),
+ }
+ rp.VersionManager = NewVersionManager(rp)
+
+ return rp, nil
+
+}
+
+// Load Rocket Pool contract addresses
+func (rp *RocketPool) GetAddress(contractName string, opts *bind.CallOpts) (*common.Address, error) {
+
+ // Check for cached address
+ if opts == nil {
+ if cached, ok := rp.getCachedAddress(contractName); ok {
+ if time.Now().Unix()-cached.time <= CacheTTL {
+ return cached.address, nil
+ } else {
+ rp.deleteCachedAddress(contractName)
+ }
+ }
+ }
+
+ // Get address
+ address, err := rp.RocketStorage.GetAddress(opts, crypto.Keccak256Hash([]byte("contract.address"), []byte(contractName)))
+ if err != nil {
+ return nil, fmt.Errorf("error loading contract %s address: %w", contractName, err)
+ }
+
+ // Cache address
+ if opts == nil {
+ rp.setCachedAddress(contractName, cachedAddress{
+ address: &address,
+ time: time.Now().Unix(),
+ })
+ }
+
+ // Return
+ return &address, nil
+
+}
+
+func (rp *RocketPool) GetAddresses(opts *bind.CallOpts, contractNames ...string) ([]*common.Address, error) {
+
+ // Data
+ var wg errgroup.Group
+ addresses := make([]*common.Address, len(contractNames))
+
+ // Load addresses
+ for ci, contractName := range contractNames {
+ ci, contractName := ci, contractName
+ wg.Go(func() error {
+ address, err := rp.GetAddress(contractName, opts)
+ if err == nil {
+ addresses[ci] = address
+ }
+ return err
+ })
+ }
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Return
+ return addresses, nil
+
+}
+
+// Load Rocket Pool contract ABIs
+func (rp *RocketPool) GetABI(contractName string, opts *bind.CallOpts) (*abi.ABI, error) {
+
+ // Check for cached ABI
+ if opts == nil {
+ if cached, ok := rp.getCachedABI(contractName); ok {
+ if time.Now().Unix()-cached.time <= CacheTTL {
+ return cached.abi, nil
+ } else {
+ rp.deleteCachedABI(contractName)
+ }
+ }
+ }
+
+ // Get ABI
+ abiEncoded, err := rp.RocketStorage.GetString(opts, crypto.Keccak256Hash([]byte("contract.abi"), []byte(contractName)))
+ if err != nil {
+ return nil, fmt.Errorf("error loading contract %s ABI: %w", contractName, err)
+ }
+
+ // Decode ABI
+ abi, err := DecodeAbi(abiEncoded)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding contract %s ABI: %w", contractName, err)
+ }
+
+ // Cache ABI
+ if opts == nil {
+ rp.setCachedABI(contractName, cachedABI{
+ abi: abi,
+ time: time.Now().Unix(),
+ })
+ }
+
+ // Return
+ return abi, nil
+
+}
+func (rp *RocketPool) GetABIs(opts *bind.CallOpts, contractNames ...string) ([]*abi.ABI, error) {
+
+ // Data
+ var wg errgroup.Group
+ abis := make([]*abi.ABI, len(contractNames))
+
+ // Load ABIs
+ for ci, contractName := range contractNames {
+ ci, contractName := ci, contractName
+ wg.Go(func() error {
+ abi, err := rp.GetABI(contractName, opts)
+ if err == nil {
+ abis[ci] = abi
+ }
+ return err
+ })
+ }
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Return
+ return abis, nil
+
+}
+
+// Load Rocket Pool contracts
+func (rp *RocketPool) GetContract(contractName string, opts *bind.CallOpts) (*Contract, error) {
+
+ // Check for cached contract
+ if opts == nil {
+ if cached, ok := rp.getCachedContract(contractName); ok {
+ if time.Now().Unix()-cached.time <= CacheTTL {
+ return cached.contract, nil
+ } else {
+ rp.deleteCachedContract(contractName)
+ }
+ }
+ }
+
+ // Data
+ var wg errgroup.Group
+ var address *common.Address
+ var abi *abi.ABI
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ address, err = rp.GetAddress(contractName, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ abi, err = rp.GetABI(contractName, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Create contract
+ contract := &Contract{
+ Contract: bind.NewBoundContract(*address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: address,
+ ABI: abi,
+ Client: rp.Client,
+ }
+
+ // Cache contract
+ rp.setCachedContract(contractName, cachedContract{
+ contract: contract,
+ time: time.Now().Unix(),
+ })
+
+ // Return
+ return contract, nil
+
+}
+func (rp *RocketPool) GetContracts(opts *bind.CallOpts, contractNames ...string) ([]*Contract, error) {
+
+ // Data
+ var wg errgroup.Group
+ contracts := make([]*Contract, len(contractNames))
+
+ // Load contracts
+ for ci, contractName := range contractNames {
+ ci, contractName := ci, contractName
+ wg.Go(func() error {
+ contract, err := rp.GetContract(contractName, opts)
+ if err == nil {
+ contracts[ci] = contract
+ }
+ return err
+ })
+ }
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Return
+ return contracts, nil
+
+}
+
+// Create a Rocket Pool contract instance
+func (rp *RocketPool) MakeContract(contractName string, address common.Address, opts *bind.CallOpts) (*Contract, error) {
+
+ // Load ABI
+ abi, err := rp.GetABI(contractName, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create and return
+ return &Contract{
+ Contract: bind.NewBoundContract(address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: abi,
+ Client: rp.Client,
+ }, nil
+
+}
+
+// Address cache control
+func (rp *RocketPool) getCachedAddress(contractName string) (cachedAddress, bool) {
+ rp.addressesLock.RLock()
+ defer rp.addressesLock.RUnlock()
+ value, ok := rp.addresses[contractName]
+ return value, ok
+}
+func (rp *RocketPool) setCachedAddress(contractName string, value cachedAddress) {
+ rp.addressesLock.Lock()
+ defer rp.addressesLock.Unlock()
+ rp.addresses[contractName] = value
+}
+func (rp *RocketPool) deleteCachedAddress(contractName string) {
+ rp.addressesLock.Lock()
+ defer rp.addressesLock.Unlock()
+ delete(rp.addresses, contractName)
+}
+
+// ABI cache control
+func (rp *RocketPool) getCachedABI(contractName string) (cachedABI, bool) {
+ rp.abisLock.RLock()
+ defer rp.abisLock.RUnlock()
+ value, ok := rp.abis[contractName]
+ return value, ok
+}
+func (rp *RocketPool) setCachedABI(contractName string, value cachedABI) {
+ rp.abisLock.Lock()
+ defer rp.abisLock.Unlock()
+ rp.abis[contractName] = value
+}
+func (rp *RocketPool) deleteCachedABI(contractName string) {
+ rp.abisLock.Lock()
+ defer rp.abisLock.Unlock()
+ delete(rp.abis, contractName)
+}
+
+// Contract cache control
+func (rp *RocketPool) getCachedContract(contractName string) (cachedContract, bool) {
+ rp.contractsLock.RLock()
+ defer rp.contractsLock.RUnlock()
+ value, ok := rp.contracts[contractName]
+ return value, ok
+}
+func (rp *RocketPool) setCachedContract(contractName string, value cachedContract) {
+ rp.contractsLock.Lock()
+ defer rp.contractsLock.Unlock()
+ rp.contracts[contractName] = value
+}
+func (rp *RocketPool) deleteCachedContract(contractName string) {
+ rp.contractsLock.Lock()
+ defer rp.contractsLock.Unlock()
+ delete(rp.contracts, contractName)
+}
diff --git a/bindings/rocketpool/v1.0.0-manager.go b/bindings/rocketpool/v1.0.0-manager.go
new file mode 100644
index 000000000..a8f7084f4
--- /dev/null
+++ b/bindings/rocketpool/v1.0.0-manager.go
@@ -0,0 +1,60 @@
+package rocketpool
+
+import (
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/hashicorp/go-version"
+)
+
+// A wrapper that holds the updated contract information for this version
+type LegacyVersionWrapper_v1_0_0 struct {
+ rp *RocketPool
+ rpVersion *version.Version
+ contractNameMap map[string]string
+ abiMap map[string]string
+}
+
+// Creates a new wrapper for this version
+func newLegacyVersionWrapper_v1_0_0(rp *RocketPool) *LegacyVersionWrapper_v1_0_0 {
+ rpVersion, _ := version.NewSemver("1.0.0")
+ return &LegacyVersionWrapper_v1_0_0{
+ rp: rp,
+ rpVersion: rpVersion,
+ contractNameMap: map[string]string{
+ "rocketRewardsPool": "rocketRewardsPool.v1",
+ "rocketClaimNode": "rocketClaimNode.v1",
+ "rocketClaimTrustedNode": "rocketClaimTrustedNode.v1",
+ "rocketMinipoolManager": "rocketMinipoolManager.v1"},
+ abiMap: map[string]string{
+ "rocketRewardsPool": "eJzVWN9vmzAQ/lcmnvPADxtD37po0ia1VZVmT1U1HfYRoRKobJMmqvq/zxCSQBOatMsYe0vs43zfd2ffZ9+/WEn2VGhlXdyXPzXKDNLp6gmtC4vnmZbA9ZdJzh9R3+lcwgx/lEYxcLRGVgbz0vCXbBpcCiFRKTOt136gHnh9GFlKg8brQkOUpIlemdksz55gBVGKuy/MykrLghuHZlAlswx0Id/OvI5eLDCfr+Z5YQDEkCoctfEIXKKwLswX1UwLHmzjrGHwFJJ5ks3GNe4DCEafdtrNStPnFkPLaWH+u9TfOQWDOGvEtzH4jC+dzPGAp4etweT2apo/YqbGJRTjeWuMC6yiaCbIXlKMHYfboU89QpkTeg4NYrQjwimNInBsLlzhskAg4bHHfRFyZCSmFG2fUofXKOos7uJYoFRJnpn18kJ31WwZf7AD1wYWdJTgIsHnnWVcZFyvF6qqDQzGOtlvgJJYEEHtroBnqA13XyGFrNotR8JuZeVwRs4WuuuEAfpuJ9cm9Crb1WZfmDBN4u80SD0sGBF4hEck+jCMcT43xlUtDwmOZ3uIGJwKR92CUkMDYRMvYEy8tyv2cjIsBNQGN2ZIjyEoI78Cpa9BDAwBD23PYduG0BWW6aSmMzX6+Kbj30CzJ9Rmr/sMNJrlt2WitHqfhijP00McVONnJYARQhwRiD4JyEoNc2Qz9seAj8INKCUfZqBTBJ3EwjTXkO6EwoD2BBo54vgOPULIXlhrRjanVdU9pocl0+gc7O472ZOUay8o3xXa3Sn6qVB+B3VSkvqrV4f5IYt85/z12g+jE5yZAxAliuE1NJdEkTBa/H/ldgxZNTyUWvVC4hIW9322llSsz9dCyvXNa0BFZkgJGMW/UGSnkXKDy4ExAuZm66Af9czILUpeytJhkWGj6zJOnVOvBRN8BilUldqBIRGUUebCP0jrsIiIffBiH+OeibhM0/x5eE8qNoS+Z3N3oE1230mH0jVOqtfFut663wU3qbncPEYOKBk+w4Cw4FiHPguPbd3xC7f3wLf6o7aQtVJcC3DZIu7kF/IGE23kJLLRE+6xa85ZkO870XmfFcj3ROGf8xeGPo/AL5+KfwPvAY9J",
+ "rocketClaimNode": "eJzNlMFuwjAMhl9l6rmHUtoOuKFphx0mIbYbQshJ3CpaSVDiwtDEuy+ljNKNwg7dxK2Nnfj77V+efXhSrQqy3mhWfhIaBfnrdoXeyONakQFOd1PN35BeSBvI8KlMSoGj53sKlmXiwpwmjIUwaK0LU/UOHA52c9+zBITPBQGTuaStiyqtVrAFlmN9w1W2ZAruHnSHVmYKqDDfIzv/hH5+pFmjsVIrd1EX1KatcP+DWkFdugqcR11L3NSZaaE4VYX2VKDIGzk2bBIH73GUikjEQRtwhvSoygaIy8xM6/wc8v68U+LeQGAAqWgS/wCC46S/jKC0uDz/WvJDDnI50dbKavQ3ITyMwoClSfLnwqe4ASPsBA2/btQwTtqsWoa6NWu/z1nEgv/qwHipC8dyUz3oB3EUJ9C1/f0rxl6gaq7Bo78PCQYzad0DjW79eqmeyG/KBRb0WJQM2/YTL8fVcc0I73tiGLoWzz8BB105fQ==",
+ "rocketClaimTrustedNode": "eJzNlMFuwjAMhl9l6rmHUtoOuKFphx02IbYbQshN3CpaSVDiwNDEuy+ljNKNwg4McWtsx/7s/PXk0xNyYcl4g0n5SaglFG/rBXoDjylJGhjdjRV7R3olpSHHpzIoA4ae70mYl4EzfRgw5FyjMc5NVR7YGTZT3zMEhM+WIBWFoLXzSiUXsIa0wPqGq2xIW+YSOqMRuQSy+qdn4x/QT/c0S9RGKOkuKkttvVl37tUd1KUrx3HUpcBVHZlZyagqtKUCSd7AsWGTOPiIo4xHPA7agHOkR1kOgJ9mTpUqjiFv7Rcl7vQ4BpDxJvEvINi/9LcQXDJDyF8UPy2DuvOHAsR8pIwRlQJuov8wCoM0S5Jr9T/GFWhuRqjZedmGcdIm3NJ1Wel2uyyN0uDKgxjOlXVINzWKbhBHcQL/9E/4Z9Q+Q9lckXvR7wI05sKV0Y2h/XnhHkyh2TWkQSeNkn7b7mLlq124ZoT3Hd4P3aSnX+6rRSk=",
+ "rocketMinipoolManager": "eJztWU1v2zgQ/SuFzj5QEimSuWU/CvSQRbBdbA9BEAzJYSrElgyJcmIU/e+lbCuyHduSY7tQgd5scTic9zjDGQ7vvgVpNq1cGVzd1T8dFhmM/5tPMbgKdJ65ArT78G+un9B9dnkBj/ipFrKgMRgFGUxqwYdiXeDamALL0g+7pR5Yffh+PwpKBw5vKgcqHadu7kezPJvCHNQY2xl+5dIVlfYKg++jbwF4ofkkr7yZFsYljjatNviCJrjyMxYjGyDg1ZqVsZM0S6d5Pt5h3+hoZVlusEPRq8Ebmir/P2JJq8mlkzVNzXBN2UrgZmX3nwV6Ck0rizPMnP9bpo8ZuKqov5EXIhRVMlRgwUoZhZRpZgxHIoWx2qg4soJLHlIdMqoTFuqIMwGUag4ISH/zvsX7X+hdMp93Mx8TybUilsWJNkKEIVMipDJkhpOYmMiiZELSOGZcm0iFIDE0CgQkPEadyBWMFc+tITMsyjTP/Hp55fbFbA1AtOg2kYk9IThL8bmVtFWm3XKhRRyCx7jalk2gjFpDDSP7DH5E9+q0ebVgqsPyjZ3ZvStns977uCHKb+d+6z87eEqzxwGDSLjSEMXsAIiPqTcvLdEMGIYJERKNhzzp2mua4YAxaJSKS9jyp26zcmtLdDuMG3XPHaeTdNfU/TF4i4X3aleVR5Lndbh06UcN9++wd1rgGDyhX0/QUS6D8gQNz6n7agp4rmuOE9SYtCzz8Ww/Ief0rlgx1GhhcN5122xp42YdnrXK1Xf3uwKzHTxvojIWrUzYseQ9LIqJvvF13XEgvSli+lbI70SN1vI4lLYD9RuzHuri6mAR32L/x4sO+EwO/aGMOo4vTcHgk1MoKEVNwksT8SsUG0oIENLyS3Mx/OqRcUXRJurMTPRILf1O1vXTZWinq1AxISSSl/ai/308GXDDdiQrSZRwTYfsSG+JHJpLSQOEE9KVsNXc4TqH00o94bxdbzm+u0b5Y37bSA8IuLYJUJ4cn6abnlLPeGpY+PslLV1HnaqWrao3BCy+n/cOTxKlkgh/Fvr1RtYgCADOuBS8625zNgL6hMBWkO2KrvPd7VCxmOHxqeSd+L8099+6l2wwqy/2XdHwHjqmNcDj6Yi04omOL1efpZkucOJx96rSej9arAHcOtcl4ZEQ+mKADP5cQJwpJjW/3M1yY4d63ClOh6QIRSEScfEKBrNq8qE9iqd5ueixNOrM8stCeLt336ccKmF8uGWjF89HN+1jzIGo33oAbCbteAHsWyActScsFMhJrPd1hc0yke3EcgYnV4oSgLNXY+XBRHS63ZEPzIQaeqCX3laDDvXyJW0gZ78VTCWS+krw/gfIvWxo",
+ },
+ }
+}
+
+// Get the version for this manager
+func (m *LegacyVersionWrapper_v1_0_0) GetVersion() *version.Version {
+ return m.rpVersion
+}
+
+// Get the versioned name of the contract if it was upgraded as part of this deployment
+func (m *LegacyVersionWrapper_v1_0_0) GetVersionedContractName(contractName string) (string, bool) {
+ legacyName, exists := m.contractNameMap[contractName]
+ return legacyName, exists
+}
+
+// Get the ABI for the provided contract
+func (m *LegacyVersionWrapper_v1_0_0) GetEncodedABI(contractName string) string {
+ return m.abiMap[contractName]
+}
+
+// Get the contract with the provided name for this version of Rocket Pool
+func (m *LegacyVersionWrapper_v1_0_0) GetContract(contractName string, opts *bind.CallOpts) (*Contract, error) {
+ return getLegacyContract(m.rp, contractName, m, opts)
+}
+
+func (m *LegacyVersionWrapper_v1_0_0) GetContractWithAddress(contractName string, address common.Address) (*Contract, error) {
+ return getLegacyContractWithAddress(m.rp, contractName, address, m)
+}
diff --git a/bindings/rocketpool/v1.1.0-manager.go b/bindings/rocketpool/v1.1.0-manager.go
new file mode 100644
index 000000000..162484d8d
--- /dev/null
+++ b/bindings/rocketpool/v1.1.0-manager.go
@@ -0,0 +1,67 @@
+package rocketpool
+
+import (
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/hashicorp/go-version"
+)
+
+// A wrapper that holds the updated contract information for this version
+type LegacyVersionWrapper_v1_1_0 struct {
+ rp *RocketPool
+ rpVersion *version.Version
+ contractNameMap map[string]string
+ abiMap map[string]string
+}
+
+// Creates a new wrapper for this version
+func newLegacyVersionWrapper_v1_1_0(rp *RocketPool) *LegacyVersionWrapper_v1_1_0 {
+ rpVersion, _ := version.NewSemver("1.1.0")
+ return &LegacyVersionWrapper_v1_1_0{
+ rp: rp,
+ rpVersion: rpVersion,
+ contractNameMap: map[string]string{
+ "rocketNetworkPrices": "rocketNetworkPrices.v1",
+ "rocketNodeStaking": "rocketNodeStaking.v2",
+ "rocketNodeDeposit": "rocketNodeDeposit.v2",
+ "rocketMinipoolQueue": "rocketMinipoolQueue.v1",
+ "rocketMinipoolFactory": "rocketMinipoolFactory.v1",
+ },
+ abiMap: map[string]string{
+ "rocketNetworkPrices": "eJztVslu2zAQ/ZVCZx+0kKKYW1v0UMAFDKc9BUEwJIeBEJkSSMqNEeTfSyuKFSVektYpjKIHAzI5y3sz5ONc3EWlaVrvorOL9adHa6D6vmowOotkbbwF6T/Ma3mD/tzXFq7x69pIg8RoEhlYrA2v7FODj0pZdC5s+4c40C/cX04i58Hjt9aDKKvSr8KuqU0DKxAVDh4hs/O2lSFgWHTltQHf2uc795O7CIL7alG3gYCGyuFkzEfhLaroLHh0OyN6sMHZ09C2XmxBPXkSaJNjFKkN/1OaD5FEFQoyhHrc/51QtqlmtpR4nGioNUpfLnHeVOcebo4U1peLbZEuNwYdBXfeikXpfQi8scUlGv+sx/FtDimNmS6IykETolLgMqMUmE4wkyTniUhIHtNEIqdxIkjKVUEIE8BplsjsDUfjf0f/pKM/GgWv6SfqVOsiyVBLHn4sZzzN8lwSkJDmjAkWukshyUBrSYniwSOjShbIhWBZT6Hv3gBiidaVtQn56tbvkrE1+GJgNmZV7FClZYk/B0vdmlDjLlEnQBA49qIyJkqJVkTReBfga/QPhfvUn6cDuEc92d6Po2GPNZJYSLUH+3w2fTy6JwScpUQRiPke4F82t2Q2fbwlJ8SASlGknONbGPRX7wQPEtJcJIoVYzaHYV1BUOtOPXbLTmmkRXC4v6GvHjSecBpzELRQBVfZe3BQ+Hc4xDQNrSDxmznsfewO+B563Q64v+Y5GyrpuoHiQVGPfQAgTYjM5L9bvDAPyNb3OvIuNdSMKgYi3yVrpfkchASNa8eZX1AVdV1tk69u/ajalQW8NI7JHiWehlTOz7GprV8X4gQVGMLUlORhLru//AWSfqX7",
+
+ "rocketNodeStaking": "eJzdV8FOAjEQ/RXT855MNIabB01M0BglejCEDN1Zaey2m3YKEuO/OwXcdbMsIkJEbix9ffPem+5k+/QmlCkCedF5ij8JnQHdmxYoOkJaQw4kHd1Z+YJ0T9bBM15FUAYSRSIM5BE4cF8B52nq0HtepjkPLP547yfCExBeB4Kh0oqmvGqsKWAKQ43VDq7syQXJhOI9eRPAoGluA8vMQHtM6qpTfMVUdHjHbKVmAko1C7HGprhEW/KFqKxRYwr8fHxyWjEBKzJUcX0CNuFCGj2ADrgdNlL5MqZ+Cbi77d5r8CPmLGE4Rnaz7bgzZ/P9i3utgAhedp8P2f+ZzqOiUepgYpYFVMZRbRmj88pGtA3UNnFiqbNKR13DWcsAGSucVMgsGEmxUIuOZ6SeJdCfDf5eUC2a5bFsJqpRrXE4BnFYrZynlasbhh60qbQ3P5Z/5ax5hi6yDBk43svcG9VslnlsnRCr92qVqw23ukLfOiVXDxQJWgbNvvc/11+f58N2d62MykN+mN7g9XC9cd8Ka3V38abvjbNGtUHrp05lysf+cJ9qRta+fuwo8zWG5U+8XfLlaJf2NhI3WXwW/sfs+Q42uxSdr9GDiNuCx/4HCbNAvw==",
+
+ "rocketNodeDeposit": "eJytk11rwjAUhv/KyHWvHBvi3UAGu3AM3Z1IOU1PJdgkJTnpLOJ/X2pr084qm+yuad7znPd8ZH1gQhWOLJut609CoyD/rApkM8a1IgOcHpaa75BWpA1s8a0WZcCRRUyBrIWx6Qte0tSgtf6aGg60P46biFkCwoUjSEQuqPK3SqsCKkhyDBE+syXjuAeyY3Rg4EWV1M7bzCC3GA1dp7jHlM18xOlmUAR0blqzmdFyxFvUA3U5BiTnz5On50AC70hRYJ0F97BISBwhbTrBHAttBS2Royg9uNNiid5Ek7PtSYgq0VihlVdrR9fmXGebBitDG9MrYysFfgVl5hSnOtHAx2imQdmxFEpIJ991iq841oHoApJUhL15xiXkIgW/Kh8u2WEVGI3wL4SV2CogZ/C3kMdJD5M2I5oDwVJr+sHw0hHKZUcs5Fd36vZmx7gvkBOmC9/UQuv85ktsg1rTgw0ZHfjFI7175lffTXC1RWoX/kS5ub6onHw4lzzvyvmXbd58Aw32wtE=",
+
+ "rocketMinipoolQueue": "eJztV0tv2zAM/iuDzzlIsiTLve3RQ4F1h663oigoicyMJXZmy1mDov99yqN1nGcPbZdDb45Ikd/3USKVm4ekKCdtaJKzm/lnwLqE0fVsgslZ4qoy1ODCp6vK/cbwM1Q1DPFi7kTgMBkkJYznjnf1usNn72tsmmgOyziwWni8HSRNgICXbQBbjIowi9ayKicwAzvCbkfM3IS6dTFgXGyKYQmhrTctj4OHBOL22bhqIwGCUYODPh+P9+iTs7hjYenRg2ecKxrjoiwmVTXagXxwLJidBWxS0QX702KLF76L9eTRi/WMuResjb+F0l2wUIzX1Hkyz/VcOVyuoH/DRd61tDjFMmyIyO5d5nOPDNB545URuc+1J2+FcEJbqYXyEL8Vy41TKkNjWSpBGcYVcZPDh/bb2p+XL9PeWsaMQnJpxo3NgIRgkjwnTh4MUu5TsgCSkSGXkbRMAubM61ygJPeh/Q7tr3BcTV8gvfZakjAkuXUamdaWyEqpSFvHyIs0t4Kj45Rl3KFUnMALneo0syitXJFYCd3BmGLdFFUZ81Vt2NdO5/BNx63Py+zpjtMC/3ae1JYuLBMtGiFEjqu69ImqeKCkV2wf4CGG6yrA6DuWw/DrOO5eVXZX5NWwi9TmiG4D+xYsLNvxp67vTaqmCGsjyS9XFs47lO50OEUJcsyy2AzoWPm+wgTcPNNJoZdSOG4oPYD+nAhjsCmeJgPUzEpmD+n/A+/DaYJXXHORMnUEfHdlDmA/fMm2Ltbgv5PXYISMo/Ptmsc2x62BendgonZ1wOV74bLz7Qrx4pfymjAbo06gIszte3RRjz0mX2YvOlt73yGv8AdirywmdlbDxN6bvUHlBBl4w5E5Z96jsPXiWfVGJ9Qjt96ZWIrbf2ct65Y=",
+
+ "rocketMinipoolFactory": "eNqlkU9LAzEQxb9KyXlPgiK9+efioRf1VkqZzU5LMJ0JyaQlSL+7ibtttxgWxVvIe/Pml5flpzLkogQ1X5ajoCew78mhmivNJB60zF5Zf6C8CXvY4ksxbUCjahTBrhjXfmx46DqPIWRZ+hwYLo6rRgUBwUUUaI01krJKTA4StBYvE3lzEB91DlTHZooRzstOLMTdBELzIwEp7mYLQ8Yx22d0HIyM4rr+5tt8jos5474WVoSb27vRfAAr14NFL1UMjrzAcnoaus5WjvLr1/6n5E0kLYbpuuEL2BblVMtjEtS512m6NruqbL1QJ3PR/4Fpjz4UeZKj/50Kx/BtVY69wUONY/UF4uEWyg==",
+ },
+ }
+}
+
+// Get the version for this manager
+func (m *LegacyVersionWrapper_v1_1_0) GetVersion() *version.Version {
+ return m.rpVersion
+}
+
+// Get the versioned name of the contract if it was upgraded as part of this deployment
+func (m *LegacyVersionWrapper_v1_1_0) GetVersionedContractName(contractName string) (string, bool) {
+ legacyName, exists := m.contractNameMap[contractName]
+ return legacyName, exists
+}
+
+// Get the ABI for the provided contract
+func (m *LegacyVersionWrapper_v1_1_0) GetEncodedABI(contractName string) string {
+ return m.abiMap[contractName]
+}
+
+// Get the contract with the provided name for this version of Rocket Pool
+func (m *LegacyVersionWrapper_v1_1_0) GetContract(contractName string, opts *bind.CallOpts) (*Contract, error) {
+ return getLegacyContract(m.rp, contractName, m, opts)
+}
+
+func (m *LegacyVersionWrapper_v1_1_0) GetContractWithAddress(contractName string, address common.Address) (*Contract, error) {
+ return getLegacyContractWithAddress(m.rp, contractName, address, m)
+}
diff --git a/bindings/rocketpool/v1.1.0-rc1-manager.go b/bindings/rocketpool/v1.1.0-rc1-manager.go
new file mode 100644
index 000000000..43f6dd540
--- /dev/null
+++ b/bindings/rocketpool/v1.1.0-rc1-manager.go
@@ -0,0 +1,55 @@
+package rocketpool
+
+import (
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/hashicorp/go-version"
+)
+
+// A wrapper that holds the updated contract information for this version
+type LegacyVersionWrapper_v1_1_0_rc1 struct {
+ rp *RocketPool
+ rpVersion *version.Version
+ contractNameMap map[string]string
+ abiMap map[string]string
+}
+
+// Creates a new wrapper for this version
+func newLegacyVersionWrapper_v1_1_0_rc1(rp *RocketPool) *LegacyVersionWrapper_v1_1_0_rc1 {
+ rpVersion, _ := version.NewSemver("1.1.0-rc1")
+ return &LegacyVersionWrapper_v1_1_0_rc1{
+ rp: rp,
+ rpVersion: rpVersion,
+ contractNameMap: map[string]string{
+ "rocketRewardsPool": "rocketRewardsPool.v2",
+ },
+ abiMap: map[string]string{
+ "rocketRewardsPool": "eJztWdtu2zgQ/ZWFnv2gC6lL3rbZAhugWxiJ3wLDGJHDVKhMGSSV1ij676UUNZJs+ZZ1ARXVk21yeDjnjGY4lB+/OZnclEY7N4/VV4NKQr7YbtC5cVghjQJm/rov2Gc0D6ZQ8IR3lZEAhs7MkbCuDFeqa/A35wq1ttPmBQeage/LmaMNGPyvNJBmeWa2dlYWcgNbSHNsV9idtVEls4B2UGdPEkypdme+z745YJdv10VpCQjINc76fDh+Re7c2BX1TI9eaX/7NGxpKPwCit9Vi1pXflpVm7FivSkkymG5Lsc7BWCdZ6XJCvkutwK/DaMSDKUu9fkY6dagDvwWY43qc473RWHa9T+NBtbbAGXyaXf5QiHe3v3TIjRm55Co559tfOegtQ3om5QwCkGXans//3AJwOOyC1Fqg/xjwXEIxZqehyOvAfB+8e8gwHLWPvivObEboLJK6/r5fCjTdaa1fcpaeN0da3Yw5cam6It7R9EPRu/BgDKLbI2H5H8b7HvJrwdqhpGWrwaNaBI2+lM3H/DZ1oWdYuV+5SkPOKaEhC53eYiYMkIJJiKlHjKG3Is540lkPykK4XHqh24EFHjIYzdM/m+Ng9da3LgvVLEeqMyzqVhOxXIqliMolhcWoNohY7phHq5EiUiCIGEh8xM3JIS5AefCVibBiE8x4BEVGIacB7EgFKkPwL3IgwTQDVLfTxoyTd1p3XlG1VAvSnOok6xoxC3HPr/4QGP4nOGX1lKUkpmXjeoeECzHplT1iVIiOOHUPeTwE5r7Xq054XcvOsORuZrvwveYR112zPf5h3eQg6z77xG57ntJjKF/zPU5Sm6LmGXwEgA9LgaRTQThYXSagS0mo2SQxl7KgXpHGNzmkK3vmhOiapvqpmxkNCAgLCXpJTTGxYC64IsI6bkM2qN6RCRcEsRRxHcK6ckGZsUqZnbotnl9sN/E7AvRMZ+jYuMSQoQQiBDFWUJ0G489KfSuFk0LclgNfbYc3Y0PtTpXk4Rxe8iix05Isnf9WHWawsPvic7oT1fH7ws9TRftnt126YikaVHkQ2rW49c9Nl2CPriwK+R0e5puT7/J7emy29LqyHWpl7Xt4tuilCNrUWgCjARs70iY0nZK2z84bWsjs/eKtE3cs/8B6mRiP/MCFgL47pR5U+ZNmbfceRzxl6YeCUgc8foo/QHwlCkA",
+ },
+ }
+}
+
+// Get the version for this manager
+func (m *LegacyVersionWrapper_v1_1_0_rc1) GetVersion() *version.Version {
+ return m.rpVersion
+}
+
+// Get the versioned name of the contract if it was upgraded as part of this deployment
+func (m *LegacyVersionWrapper_v1_1_0_rc1) GetVersionedContractName(contractName string) (string, bool) {
+ legacyName, exists := m.contractNameMap[contractName]
+ return legacyName, exists
+}
+
+// Get the ABI for the provided contract
+func (m *LegacyVersionWrapper_v1_1_0_rc1) GetEncodedABI(contractName string) string {
+ return m.abiMap[contractName]
+}
+
+// Get the contract with the provided name for this version of Rocket Pool
+func (m *LegacyVersionWrapper_v1_1_0_rc1) GetContract(contractName string, opts *bind.CallOpts) (*Contract, error) {
+ return getLegacyContract(m.rp, contractName, m, opts)
+}
+
+func (m *LegacyVersionWrapper_v1_1_0_rc1) GetContractWithAddress(contractName string, address common.Address) (*Contract, error) {
+ return getLegacyContractWithAddress(m.rp, contractName, address, m)
+}
diff --git a/bindings/rocketpool/v1.2.0-manager.go b/bindings/rocketpool/v1.2.0-manager.go
new file mode 100644
index 000000000..6291dc899
--- /dev/null
+++ b/bindings/rocketpool/v1.2.0-manager.go
@@ -0,0 +1,57 @@
+package rocketpool
+
+import (
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/hashicorp/go-version"
+)
+
+// A wrapper that holds the updated contract information for this version
+type LegacyVersionWrapper_v1_2_0 struct {
+ rp *RocketPool
+ rpVersion *version.Version
+ contractNameMap map[string]string
+ abiMap map[string]string
+}
+
+// Creates a new wrapper for this version
+func newLegacyVersionWrapper_v1_2_0(rp *RocketPool) *LegacyVersionWrapper_v1_2_0 {
+ rpVersion, _ := version.NewSemver("1.2.0")
+ return &LegacyVersionWrapper_v1_2_0{
+ rp: rp,
+ rpVersion: rpVersion,
+ contractNameMap: map[string]string{
+ "rocketNetworkPrices": "rocketNetworkPrices.v2",
+ "rocketNetworkBalances": "rocketNetworkBalances.v2",
+ },
+ abiMap: map[string]string{
+ "rocketNetworkPrices": "eJzlVE1v2zAM/SuDzznIimRLuW23AR1QpNupKApKogNjjm1IdNag6H+fkrhx890NHRBgN1skH997lHj/nJR121FIJverT0JfQ/V92WIySWxTkwdLn6aN/Yl0R42HGX5dJRVgMRklNcxXiY/+bcJn5zyGEMO0wYH+4OVhlAQCwm8dgSmrkpYxWjd1C0swFQ4VsXMg39kIGA9DOauBOr8feRk9JxDLl/OmiwIKqAKOdvU4fEKXTGLFOrIjD7Y8exmFb+ZHWI/eAG177CB18Z/LbEAyVTRkgHqN/w2Ub6tbX1r8GDQq58eQHrYJ617hrjPzkigCb3NxgTXtDYM98VyqtDBGaa0BlRijhkwxlaHBnGVKKJSFVpKhcxpAa25S7kCIXI9jZ/sHM/xPrP/ROniH8ZAZNWYmFyi5y12aCoZ5xiUvOJfO5gYdV7niMtVKyIwxY9w4VXwsUmfTVPYSTj39A/aPZ329UHvGyEF+NNN2hBv9Gy9iftNRz/Hdy6PoaktlUx94pguTiULrXe0DgxnSTWwQaIpt42mF+qVXPdC4LPe4ykPuixJ/HWW93nEQh97vrb3Ja2PSLN6Q0yo27l0hd1agYMae4z69vXm9K1dEPOfCCWD6+p5NWK/qf/JeuEanGYdT01qgD5u6C5NSp+akPnZKUhROOMki6m+WhvGX",
+ "rocketNetworkBalances": "eJztVk1vm0AQ/SsVZx/4XMC3RoqUSu3Fdk6RFc3uzjooGBAMbtwo/71jm5g4tiGpXIlDfMK7M2/e2zfscPdsJVlRU2WN7zaPhGUG6WxdoDW2VJ5RCYq+TXL1iDSlvIQF/tgEGVBojawMlpvA+/JtwHetS6wq3qYdDjQLL/ORVREQ/qoJZJImtObdLM8KWINMsc3gyhWVtWJAXqySRQZUl+93XkbPFnD6epnXLMBAWuHoUI/GJ9TWmDO2OwfyYM+zkWHKfHmC9egN0L7GAVLN/91AtEgy5QNpoV73/wWKcoL0mh4ug8bH/5hki4vhlUgP07oo0vWF1CZLPIE03wdcQQqZwmpay2VCxND7aFxhRu/axX5CEYQgtIiD0PgGpPRCGQnta153MHK1sYUjtasj8FBjoJ04lLaMIwiA180nuuyrOQbSHLeFhg+0Rij553ih0aB9z9PCEZF0VBio0PFV6MoIJEbaDn1hG1vZkoNs4WEojWO82G9EnLs+j/jfdzrfk9tndU96v7c9AJ1mthawoaom3Dnw6gdn5DU15/ThIWDqTFGSZ0e+BcIYT7jB4fm3HBZIr5WvmhNvy/dLPa3vmPMqwd8n2W5nFHDDNXPnkH3s2LZyPN3B/np2c0tc5g9sECdceGAStAQhwHRI+MmlKppgkZe0sXWARkDMb7/ALiOmzVszu2kaalgKjIMahOd3KJhtr42B8o+Fr23fVX38Jyxgf/cMiL/yFY8H0F+D4HgQVNsPtP80ATxXy8AP7XONs8Ky2uX1dEt0rleiy3ZK4Bv+5AyY8PwvSEB9Dw==",
+ },
+ }
+}
+
+// Get the version for this manager
+func (m *LegacyVersionWrapper_v1_2_0) GetVersion() *version.Version {
+ return m.rpVersion
+}
+
+// Get the versioned name of the contract if it was upgraded as part of this deployment
+func (m *LegacyVersionWrapper_v1_2_0) GetVersionedContractName(contractName string) (string, bool) {
+ legacyName, exists := m.contractNameMap[contractName]
+ return legacyName, exists
+}
+
+// Get the ABI for the provided contract
+func (m *LegacyVersionWrapper_v1_2_0) GetEncodedABI(contractName string) string {
+ return m.abiMap[contractName]
+}
+
+// Get the contract with the provided name for this version of Rocket Pool
+func (m *LegacyVersionWrapper_v1_2_0) GetContract(contractName string, opts *bind.CallOpts) (*Contract, error) {
+ return getLegacyContract(m.rp, contractName, m, opts)
+}
+
+func (m *LegacyVersionWrapper_v1_2_0) GetContractWithAddress(contractName string, address common.Address) (*Contract, error) {
+ return getLegacyContractWithAddress(m.rp, contractName, address, m)
+}
diff --git a/bindings/rocketpool/version-interface.go b/bindings/rocketpool/version-interface.go
new file mode 100644
index 000000000..c78f27d52
--- /dev/null
+++ b/bindings/rocketpool/version-interface.go
@@ -0,0 +1,77 @@
+package rocketpool
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const (
+ rocketVersionInterfaceAbiString string = `[
+ {
+ "inputs": [],
+ "name": "version",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ }
+ ]`
+)
+
+var versionAbi *abi.ABI
+
+// Get the version of the given contract
+func GetContractVersion(rp *RocketPool, contractAddress common.Address, opts *bind.CallOpts) (uint8, error) {
+ if versionAbi == nil {
+ // Parse ABI using the hardcoded string until the contract is deployed
+ abiParsed, err := abi.JSON(strings.NewReader(rocketVersionInterfaceAbiString))
+ if err != nil {
+ return 0, fmt.Errorf("error parsing version interface JSON: %w", err)
+ }
+ versionAbi = &abiParsed
+ }
+
+ // Create contract
+ contract := &Contract{
+ Contract: bind.NewBoundContract(contractAddress, *versionAbi, rp.Client, rp.Client, rp.Client),
+ Address: &contractAddress,
+ ABI: versionAbi,
+ Client: rp.Client,
+ }
+
+ // Get the contract version
+ version := new(uint8)
+ if err := contract.Call(opts, version, "version"); err != nil {
+ return 0, fmt.Errorf("error getting contract version: %w", err)
+ }
+
+ return *version, nil
+}
+
+// Get the rocketVersion contract binding at the given address
+func GetRocketVersionContractForAddress(rp *RocketPool, address common.Address) (*Contract, error) {
+ if versionAbi == nil {
+ // Parse ABI using the hardcoded string until the contract is deployed
+ abiParsed, err := abi.JSON(strings.NewReader(rocketVersionInterfaceAbiString))
+ if err != nil {
+ return nil, fmt.Errorf("error parsing version interface JSON: %w", err)
+ }
+ versionAbi = &abiParsed
+ }
+
+ return &Contract{
+ Contract: bind.NewBoundContract(address, *versionAbi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: versionAbi,
+ Client: rp.Client,
+ }, nil
+}
diff --git a/bindings/rocketpool/version-manager.go b/bindings/rocketpool/version-manager.go
new file mode 100644
index 000000000..d4f52e097
--- /dev/null
+++ b/bindings/rocketpool/version-manager.go
@@ -0,0 +1,107 @@
+package rocketpool
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/hashicorp/go-version"
+)
+
+// Wrapper for legacy contract versions
+type LegacyVersionWrapper interface {
+ GetVersion() *version.Version
+ GetVersionedContractName(contractName string) (string, bool)
+ GetEncodedABI(contractName string) string
+ GetContract(contractName string, opts *bind.CallOpts) (*Contract, error)
+ GetContractWithAddress(contractName string, address common.Address) (*Contract, error)
+}
+
+type VersionManager struct {
+ V1_0_0 LegacyVersionWrapper
+ V1_1_0_RC1 LegacyVersionWrapper
+ V1_1_0 LegacyVersionWrapper
+ V1_2_0 LegacyVersionWrapper
+
+ rp *RocketPool
+}
+
+func NewVersionManager(rp *RocketPool) *VersionManager {
+ return &VersionManager{
+ V1_0_0: newLegacyVersionWrapper_v1_0_0(rp),
+ V1_1_0_RC1: newLegacyVersionWrapper_v1_1_0_rc1(rp),
+ V1_1_0: newLegacyVersionWrapper_v1_1_0(rp),
+ V1_2_0: newLegacyVersionWrapper_v1_2_0(rp),
+ rp: rp,
+ }
+}
+
+// Get the contract with the provided name and version wrapper
+func getLegacyContract(rp *RocketPool, contractName string, m LegacyVersionWrapper, opts *bind.CallOpts) (*Contract, error) {
+
+ legacyName, exists := m.GetVersionedContractName(contractName)
+ if !exists {
+ // This wasn't upgraded in previous versions
+ return rp.GetContract(contractName, opts)
+ }
+
+ // Check for cached contract
+ if cached, ok := rp.getCachedContract(legacyName); ok {
+ if time.Now().Unix()-cached.time <= CacheTTL {
+ return cached.contract, nil
+ } else {
+ rp.deleteCachedContract(legacyName)
+ }
+ }
+
+ // Try to get the legacy address from RocketStorage first
+ emptyAddress := common.Address{}
+ address, err := rp.RocketStorage.GetAddress(nil, crypto.Keccak256Hash([]byte("contract.address"), []byte(legacyName)))
+ if err != nil {
+ return nil, fmt.Errorf("error loading v%s contract %s address: %w", m.GetVersion().String(), contractName, err)
+ }
+
+ if address == emptyAddress {
+ // Not found, so the legacy contract is still on the network - try loading the original contract name instead
+ return rp.GetContract(contractName, opts)
+ }
+
+ // If we're here, we have a legacy contract
+ abiEncoded := m.GetEncodedABI(contractName)
+ abi, err := DecodeAbi(abiEncoded)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding contract %s ABI: %w", contractName, err)
+ }
+
+ contract := &Contract{
+ Contract: bind.NewBoundContract(address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: abi,
+ Client: rp.Client,
+ }
+
+ return contract, nil
+
+}
+
+// Get the contract with the provided name, address, and version wrapper
+func getLegacyContractWithAddress(rp *RocketPool, contractName string, address common.Address, m LegacyVersionWrapper) (*Contract, error) {
+
+ abiEncoded := m.GetEncodedABI(contractName)
+ abi, err := DecodeAbi(abiEncoded)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding contract %s ABI: %w", contractName, err)
+ }
+
+ contract := &Contract{
+ Contract: bind.NewBoundContract(address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &address,
+ ABI: abi,
+ Client: rp.Client,
+ }
+
+ return contract, nil
+
+}
diff --git a/bindings/settings/protocol/auction.go b/bindings/settings/protocol/auction.go
new file mode 100644
index 000000000..6ba9d3334
--- /dev/null
+++ b/bindings/settings/protocol/auction.go
@@ -0,0 +1,196 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ AuctionSettingsContractName string = "rocketDAOProtocolSettingsAuction"
+ CreateLotEnabledSettingPath string = "auction.lot.create.enabled"
+ BidOnLotEnabledSettingPath string = "auction.lot.bidding.enabled"
+ LotMinimumEthValueSettingPath string = "auction.lot.value.minimum"
+ LotMaximumEthValueSettingPath string = "auction.lot.value.maximum"
+ LotDurationSettingPath string = "auction.lot.duration"
+ LotStartingPriceRatioSettingPath string = "auction.price.start"
+ LotReservePriceRatioSettingPath string = "auction.price.reserve"
+)
+
+// Lot creation currently enabled
+func GetCreateLotEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := auctionSettingsContract.Call(opts, value, "getCreateLotEnabled"); err != nil {
+ return false, fmt.Errorf("error getting lot creation enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeCreateLotEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", CreateLotEnabledSettingPath), AuctionSettingsContractName, CreateLotEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeCreateLotEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", CreateLotEnabledSettingPath), AuctionSettingsContractName, CreateLotEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Lot bidding currently enabled
+func GetBidOnLotEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := auctionSettingsContract.Call(opts, value, "getBidOnLotEnabled"); err != nil {
+ return false, fmt.Errorf("error getting lot bidding enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeBidOnLotEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", BidOnLotEnabledSettingPath), AuctionSettingsContractName, BidOnLotEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeBidOnLotEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", BidOnLotEnabledSettingPath), AuctionSettingsContractName, BidOnLotEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The minimum lot size in ETH value
+func GetLotMinimumEthValue(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getLotMinimumEthValue"); err != nil {
+ return nil, fmt.Errorf("error getting lot minimum ETH value: %w", err)
+ }
+ return *value, nil
+}
+func ProposeLotMinimumEthValue(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", LotMinimumEthValueSettingPath), AuctionSettingsContractName, LotMinimumEthValueSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeLotMinimumEthValueGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", LotMinimumEthValueSettingPath), AuctionSettingsContractName, LotMinimumEthValueSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The maximum lot size in ETH value
+func GetLotMaximumEthValue(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getLotMaximumEthValue"); err != nil {
+ return nil, fmt.Errorf("error getting lot maximum ETH value: %w", err)
+ }
+ return *value, nil
+}
+func ProposeLotMaximumEthValue(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", LotMaximumEthValueSettingPath), AuctionSettingsContractName, LotMaximumEthValueSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeLotMaximumEthValueGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", LotMaximumEthValueSettingPath), AuctionSettingsContractName, LotMaximumEthValueSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// // The lot duration
+func GetLotDuration(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getLotDuration"); err != nil {
+ return 0, fmt.Errorf("error getting lot duration: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeLotDuration(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", LotDurationSettingPath), AuctionSettingsContractName, LotDurationSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeLotDurationGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", LotDurationSettingPath), AuctionSettingsContractName, LotDurationSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The starting price relative to current ETH price, as a fraction
+func GetLotStartingPriceRatio(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getStartingPriceRatio"); err != nil {
+ return 0, fmt.Errorf("error getting lot starting price ratio: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The starting price relative to current ETH price, as a fraction
+func GetLotStartingPriceRatioRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getStartingPriceRatio"); err != nil {
+ return nil, fmt.Errorf("error getting lot starting price ratio: %w", err)
+ }
+ return *value, nil
+}
+func ProposeLotStartingPriceRatio(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", LotStartingPriceRatioSettingPath), AuctionSettingsContractName, LotStartingPriceRatioSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeLotStartingPriceRatioGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", LotStartingPriceRatioSettingPath), AuctionSettingsContractName, LotStartingPriceRatioSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The reserve price relative to current ETH price, as a fraction
+func GetLotReservePriceRatio(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getReservePriceRatio"); err != nil {
+ return 0, fmt.Errorf("error getting lot reserve price ratio: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The reserve price relative to current ETH price, as a fraction
+func GetLotReservePriceRatioRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ auctionSettingsContract, err := getAuctionSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := auctionSettingsContract.Call(opts, value, "getReservePriceRatio"); err != nil {
+ return nil, fmt.Errorf("error getting lot reserve price ratio: %w", err)
+ }
+ return *value, nil
+}
+func ProposeLotReservePriceRatio(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", LotReservePriceRatioSettingPath), AuctionSettingsContractName, LotReservePriceRatioSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeLotReservePriceRatioGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", LotReservePriceRatioSettingPath), AuctionSettingsContractName, LotReservePriceRatioSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var auctionSettingsContractLock sync.Mutex
+
+func getAuctionSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ auctionSettingsContractLock.Lock()
+ defer auctionSettingsContractLock.Unlock()
+ return rp.GetContract(AuctionSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/deposit.go b/bindings/settings/protocol/deposit.go
new file mode 100644
index 000000000..9d816c8b1
--- /dev/null
+++ b/bindings/settings/protocol/deposit.go
@@ -0,0 +1,168 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Config
+const (
+ DepositSettingsContractName string = "rocketDAOProtocolSettingsDeposit"
+ DepositEnabledSettingPath string = "deposit.enabled"
+ AssignDepositsEnabledSettingPath string = "deposit.assign.enabled"
+ MinimumDepositSettingPath string = "deposit.minimum"
+ MaximumDepositPoolSizeSettingPath string = "deposit.pool.maximum"
+ MaximumDepositAssignmentsSettingPath string = "deposit.assign.maximum"
+ MaximumSocializedDepositAssignmentsSettingPath string = "deposit.assign.socialised.maximum"
+ DepositFeeSettingPath string = "deposit.fee"
+)
+
+// Deposits currently enabled
+func GetDepositEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := depositSettingsContract.Call(opts, value, "getDepositEnabled"); err != nil {
+ return false, fmt.Errorf("error getting deposits enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeDepositEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", DepositEnabledSettingPath), DepositSettingsContractName, DepositEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeDepositEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", DepositEnabledSettingPath), DepositSettingsContractName, DepositEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Deposit assignments currently enabled
+func GetAssignDepositsEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := depositSettingsContract.Call(opts, value, "getAssignDepositsEnabled"); err != nil {
+ return false, fmt.Errorf("error getting deposit assignments enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeAssignDepositsEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", AssignDepositsEnabledSettingPath), DepositSettingsContractName, AssignDepositsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeAssignDepositsEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", AssignDepositsEnabledSettingPath), DepositSettingsContractName, AssignDepositsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Minimum deposit amount
+func GetMinimumDeposit(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := depositSettingsContract.Call(opts, value, "getMinimumDeposit"); err != nil {
+ return nil, fmt.Errorf("error getting minimum deposit amount: %w", err)
+ }
+ return *value, nil
+}
+func ProposeMinimumDeposit(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumDepositSettingPath), DepositSettingsContractName, MinimumDepositSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinimumDepositGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumDepositSettingPath), DepositSettingsContractName, MinimumDepositSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Maximum deposit pool size
+func GetMaximumDepositPoolSize(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := depositSettingsContract.Call(opts, value, "getMaximumDepositPoolSize"); err != nil {
+ return nil, fmt.Errorf("error getting maximum deposit pool size: %w", err)
+ }
+ return *value, nil
+}
+func ProposeMaximumDepositPoolSize(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumDepositPoolSizeSettingPath), DepositSettingsContractName, MaximumDepositPoolSizeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumDepositPoolSizeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumDepositPoolSizeSettingPath), DepositSettingsContractName, MaximumDepositPoolSizeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Maximum deposit assignments per transaction
+func GetMaximumDepositAssignments(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := depositSettingsContract.Call(opts, value, "getMaximumDepositAssignments"); err != nil {
+ return 0, fmt.Errorf("error getting maximum deposit assignments: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeMaximumDepositAssignments(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumDepositAssignmentsSettingPath), DepositSettingsContractName, MaximumDepositAssignmentsSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumDepositAssignmentsGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumDepositAssignmentsSettingPath), DepositSettingsContractName, MaximumDepositAssignmentsSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Maximum socialized deposit assignments per transaction
+func GetMaximumSocializedDepositAssignments(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := depositSettingsContract.Call(opts, value, "getMaximumDepositSocialisedAssignments"); err != nil {
+ return 0, fmt.Errorf("error getting maximum socialized deposit assignments: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeMaximumSocializedDepositAssignments(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumSocializedDepositAssignmentsSettingPath), DepositSettingsContractName, MaximumSocializedDepositAssignmentsSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumSocializedDepositAssignmentsGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumSocializedDepositAssignmentsSettingPath), DepositSettingsContractName, MaximumSocializedDepositAssignmentsSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Current fee taken from user deposits
+func GetDepositFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ depositSettingsContract, err := getDepositSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := depositSettingsContract.Call(opts, value, "getDepositFee"); err != nil {
+ return nil, fmt.Errorf("error getting deposit fee: %w", err)
+ }
+ return *value, nil
+}
+func ProposeDepositFee(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", DepositFeeSettingPath), DepositSettingsContractName, DepositFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeDepositFeeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", DepositFeeSettingPath), DepositSettingsContractName, DepositFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var depositSettingsContractLock sync.Mutex
+
+func getDepositSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ depositSettingsContractLock.Lock()
+ defer depositSettingsContractLock.Unlock()
+ return rp.GetContract(DepositSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/inflation.go b/bindings/settings/protocol/inflation.go
new file mode 100644
index 000000000..eb4dfb5ac
--- /dev/null
+++ b/bindings/settings/protocol/inflation.go
@@ -0,0 +1,65 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ InflationSettingsContractName string = "rocketDAOProtocolSettingsInflation"
+)
+
+// RPL inflation rate per interval
+func GetInflationIntervalRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ inflationSettingsContract, err := getInflationSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := inflationSettingsContract.Call(opts, value, "getInflationIntervalRate"); err != nil {
+ return 0, fmt.Errorf("error getting inflation rate: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// RPL inflation rate per interval
+func GetInflationIntervalRateRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ inflationSettingsContract, err := getInflationSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := inflationSettingsContract.Call(opts, value, "getInflationIntervalRate"); err != nil {
+ return nil, fmt.Errorf("error getting inflation rate: %w", err)
+ }
+ return *value, nil
+}
+
+// RPL inflation start time
+func GetInflationStartTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ inflationSettingsContract, err := getInflationSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := inflationSettingsContract.Call(opts, value, "getInflationIntervalStartTime"); err != nil {
+ return 0, fmt.Errorf("error getting inflation start time: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+
+// Get contracts
+var inflationSettingsContractLock sync.Mutex
+
+func getInflationSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ inflationSettingsContractLock.Lock()
+ defer inflationSettingsContractLock.Unlock()
+ return rp.GetContract(InflationSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/minipool.go b/bindings/settings/protocol/minipool.go
new file mode 100644
index 000000000..7aa4e523c
--- /dev/null
+++ b/bindings/settings/protocol/minipool.go
@@ -0,0 +1,163 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Config
+const (
+ MinipoolSettingsContractName string = "rocketDAOProtocolSettingsMinipool"
+ MinipoolSubmitWithdrawableEnabledSettingPath string = "minipool.submit.withdrawable.enabled"
+ MinipoolLaunchTimeoutSettingPath string = "minipool.launch.timeout"
+ BondReductionEnabledSettingPath string = "minipool.bond.reduction.enabled"
+ MaximumMinipoolCountSettingPath string = "minipool.maximum.count"
+ MinipoolUserDistributeWindowStartSettingPath string = "minipool.user.distribute.window.start"
+ MinipoolUserDistributeWindowLengthSettingPath string = "minipool.user.distribute.window.length"
+)
+
+// Minipool withdrawable event submissions currently enabled
+func GetMinipoolSubmitWithdrawableEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := minipoolSettingsContract.Call(opts, value, "getSubmitWithdrawableEnabled"); err != nil {
+ return false, fmt.Errorf("error getting minipool withdrawable submissions enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeMinipoolSubmitWithdrawableEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", MinipoolSubmitWithdrawableEnabledSettingPath), MinipoolSettingsContractName, MinipoolSubmitWithdrawableEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinipoolSubmitWithdrawableEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", MinipoolSubmitWithdrawableEnabledSettingPath), MinipoolSettingsContractName, MinipoolSubmitWithdrawableEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Timeout period in seconds for prelaunch minipools to launch
+func GetMinipoolLaunchTimeout(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getLaunchTimeout"); err != nil {
+ return 0, fmt.Errorf("error getting minipool launch timeout: %w", err)
+ }
+ seconds := time.Duration((*value).Int64()) * time.Second
+ return seconds, nil
+}
+func ProposeMinipoolLaunchTimeout(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinipoolLaunchTimeoutSettingPath), MinipoolSettingsContractName, MinipoolLaunchTimeoutSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinipoolLaunchTimeoutGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinipoolLaunchTimeoutSettingPath), MinipoolSettingsContractName, MinipoolLaunchTimeoutSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Timeout period in seconds for prelaunch minipools to launch
+func GetMinipoolLaunchTimeoutRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getLaunchTimeout"); err != nil {
+ return nil, fmt.Errorf("error getting minipool launch timeout: %w", err)
+ }
+ return *value, nil
+}
+
+// Minipool bond reductions currently enabled
+func GetBondReductionEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := minipoolSettingsContract.Call(opts, value, "getBondReductionEnabled"); err != nil {
+ return false, fmt.Errorf("error getting bond reduction enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeBondReductionEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", BondReductionEnabledSettingPath), MinipoolSettingsContractName, BondReductionEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeBondReductionEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", BondReductionEnabledSettingPath), MinipoolSettingsContractName, BondReductionEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The maximum number of minipools allowed
+func GetMaximumMinipoolCount(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getMaximumCount"); err != nil {
+ return 0, fmt.Errorf("error getting maximum minipool count: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeMaximumMinipoolCount(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumMinipoolCountSettingPath), MinipoolSettingsContractName, MaximumMinipoolCountSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumMinipoolCountGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumMinipoolCountSettingPath), MinipoolSettingsContractName, MaximumMinipoolCountSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The time a user must wait before being able to distribute a minipool
+func GetMinipoolUserDistributeWindowStart(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getUserDistributeWindowStart"); err != nil {
+ return 0, fmt.Errorf("error getting user distribute window start: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeMinipoolUserDistributeWindowStart(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinipoolUserDistributeWindowStartSettingPath), MinipoolSettingsContractName, MinipoolUserDistributeWindowStartSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinipoolUserDistributeWindowStartGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinipoolUserDistributeWindowStartSettingPath), MinipoolSettingsContractName, MinipoolUserDistributeWindowStartSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The time a user has to distribute a minipool after waiting the start length
+func GetMinipoolUserDistributeWindowLength(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getUserDistributeWindowLength"); err != nil {
+ return 0, fmt.Errorf("error getting user distribute window length: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeMinipoolUserDistributeWindowLength(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinipoolUserDistributeWindowLengthSettingPath), MinipoolSettingsContractName, MinipoolUserDistributeWindowLengthSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinipoolUserDistributeWindowLengthGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinipoolUserDistributeWindowLengthSettingPath), MinipoolSettingsContractName, MinipoolUserDistributeWindowLengthSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var minipoolSettingsContractLock sync.Mutex
+
+func getMinipoolSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ minipoolSettingsContractLock.Lock()
+ defer minipoolSettingsContractLock.Unlock()
+ return rp.GetContract(MinipoolSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/network.go b/bindings/settings/protocol/network.go
new file mode 100644
index 000000000..1baebf614
--- /dev/null
+++ b/bindings/settings/protocol/network.go
@@ -0,0 +1,381 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ NetworkSettingsContractName string = "rocketDAOProtocolSettingsNetwork"
+ NodeConsensusThresholdSettingPath string = "network.consensus.threshold"
+ SubmitBalancesEnabledSettingPath string = "network.submit.balances.enabled"
+ SubmitBalancesFrequencySettingPath string = "network.submit.balances.frequency"
+ SubmitPricesEnabledSettingPath string = "network.submit.prices.enabled"
+ SubmitPricesFrequencySettingPath string = "network.submit.prices.frequency"
+ MinimumNodeFeeSettingPath string = "network.node.fee.minimum"
+ TargetNodeFeeSettingPath string = "network.node.fee.target"
+ MaximumNodeFeeSettingPath string = "network.node.fee.maximum"
+ NodeFeeDemandRangeSettingPath string = "network.node.fee.demand.range"
+ TargetRethCollateralRateSettingPath string = "network.reth.collateral.target"
+ NetworkPenaltyThresholdSettingPath string = "network.penalty.threshold"
+ NetworkPenaltyPerRateSettingPath string = "network.penalty.per.rate"
+ SubmitRewardsEnabledSettingPath string = "network.submit.rewards.enabled"
+)
+
+// The threshold of trusted nodes that must reach consensus on oracle data to commit it
+func GetNodeConsensusThreshold(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getNodeConsensusThreshold"); err != nil {
+ return 0, fmt.Errorf("error getting trusted node consensus threshold: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The threshold of trusted nodes that must reach consensus on oracle data to commit it
+func GetNodeConsensusThresholdRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getNodeConsensusThreshold"); err != nil {
+ return nil, fmt.Errorf("error getting trusted node consensus threshold: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNodeConsensusThreshold(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", NodeConsensusThresholdSettingPath), NetworkSettingsContractName, NodeConsensusThresholdSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNodeConsensusThresholdGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", NodeConsensusThresholdSettingPath), NetworkSettingsContractName, NodeConsensusThresholdSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Network balance submissions currently enabled
+func GetSubmitBalancesEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := networkSettingsContract.Call(opts, value, "getSubmitBalancesEnabled"); err != nil {
+ return false, fmt.Errorf("error getting network balance submissions enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeSubmitBalancesEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", SubmitBalancesEnabledSettingPath), NetworkSettingsContractName, SubmitBalancesEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSubmitBalancesEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", SubmitBalancesEnabledSettingPath), NetworkSettingsContractName, SubmitBalancesEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The frequency in seconds at which network balances should be submitted by trusted nodes
+func GetSubmitBalancesFrequency(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getSubmitBalancesFrequency"); err != nil {
+ return 0, fmt.Errorf("error getting network balance submission frequency: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeSubmitBalancesFrequency(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SubmitBalancesFrequencySettingPath), NetworkSettingsContractName, SubmitBalancesFrequencySettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSubmitBalancesFrequencyGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SubmitBalancesFrequencySettingPath), NetworkSettingsContractName, SubmitBalancesFrequencySettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Network price submissions currently enabled
+func GetSubmitPricesEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := networkSettingsContract.Call(opts, value, "getSubmitPricesEnabled"); err != nil {
+ return false, fmt.Errorf("error getting network price submissions enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeSubmitPricesEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", SubmitPricesEnabledSettingPath), NetworkSettingsContractName, SubmitPricesEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSubmitPricesEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", SubmitPricesEnabledSettingPath), NetworkSettingsContractName, SubmitPricesEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The frequency in seconds at which network prices should be submitted by trusted nodes
+func GetSubmitPricesFrequency(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getSubmitPricesFrequency"); err != nil {
+ return 0, fmt.Errorf("error getting network price submission frequency: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeSubmitPricesFrequency(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SubmitPricesFrequencySettingPath), NetworkSettingsContractName, SubmitPricesFrequencySettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSubmitPricesFrequencyGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SubmitPricesFrequencySettingPath), NetworkSettingsContractName, SubmitPricesFrequencySettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Minimum node commission rate
+func GetMinimumNodeFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getMinimumNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting minimum node fee: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// Minimum node commission rate
+func GetMinimumNodeFeeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getMinimumNodeFee"); err != nil {
+ return nil, fmt.Errorf("error getting minimum node fee: %w", err)
+ }
+ return *value, nil
+}
+func ProposeMinimumNodeFee(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumNodeFeeSettingPath), NetworkSettingsContractName, MinimumNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinimumNodeFeeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumNodeFeeSettingPath), NetworkSettingsContractName, MinimumNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Target node commission rate
+func GetTargetNodeFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getTargetNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting target node fee: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// Target node commission rate
+func GetTargetNodeFeeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getTargetNodeFee"); err != nil {
+ return nil, fmt.Errorf("error getting target node fee: %w", err)
+ }
+ return *value, nil
+}
+func ProposeTargetNodeFee(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", TargetNodeFeeSettingPath), NetworkSettingsContractName, TargetNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeTargetNodeFeeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", TargetNodeFeeSettingPath), NetworkSettingsContractName, TargetNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Maximum node commission rate
+func GetMaximumNodeFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getMaximumNodeFee"); err != nil {
+ return 0, fmt.Errorf("error getting maximum node fee: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// Maximum node commission rate
+func GetMaximumNodeFeeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getMaximumNodeFee"); err != nil {
+ return nil, fmt.Errorf("error getting maximum node fee: %w", err)
+ }
+ return *value, nil
+}
+func ProposeMaximumNodeFee(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumNodeFeeSettingPath), NetworkSettingsContractName, MaximumNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumNodeFeeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumNodeFeeSettingPath), NetworkSettingsContractName, MaximumNodeFeeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The range of node demand values to base fee calculations on
+func GetNodeFeeDemandRange(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getNodeFeeDemandRange"); err != nil {
+ return nil, fmt.Errorf("error getting node fee demand range: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNodeFeeDemandRange(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", NodeFeeDemandRangeSettingPath), NetworkSettingsContractName, NodeFeeDemandRangeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNodeFeeDemandRangeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", NodeFeeDemandRangeSettingPath), NetworkSettingsContractName, NodeFeeDemandRangeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The target collateralization rate for the rETH contract as a fraction
+func GetTargetRethCollateralRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getTargetRethCollateralRate"); err != nil {
+ return 0, fmt.Errorf("error getting target rETH contract collateralization rate: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The target collateralization rate for the rETH contract as a fraction
+func GetTargetRethCollateralRateRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getTargetRethCollateralRate"); err != nil {
+ return nil, fmt.Errorf("error getting target rETH contract collateralization rate: %w", err)
+ }
+ return *value, nil
+}
+func ProposeTargetRethCollateralRate(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", TargetRethCollateralRateSettingPath), NetworkSettingsContractName, TargetRethCollateralRateSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeTargetRethCollateralRateGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", TargetRethCollateralRateSettingPath), NetworkSettingsContractName, TargetRethCollateralRateSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The number of oDAO members that have to vote for a penalty expressed as a percentage
+func GetNetworkPenaltyThreshold(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getNodePenaltyThreshold"); err != nil {
+ return 0, fmt.Errorf("error getting network penalty threshold: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The number of oDAO members that have to vote for a penalty expressed as a percentage
+func GetNetworkPenaltyThresholdRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getNodePenaltyThreshold"); err != nil {
+ return nil, fmt.Errorf("error getting network penalty threshold: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNetworkPenaltyThreshold(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", NetworkPenaltyThresholdSettingPath), NetworkSettingsContractName, NetworkPenaltyThresholdSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNetworkPenaltyThresholdGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", NetworkPenaltyThresholdSettingPath), NetworkSettingsContractName, NetworkPenaltyThresholdSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The amount a node operator is penalised for each penalty as a percentage
+func GetNetworkPenaltyPerRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getPerPenaltyRate"); err != nil {
+ return 0, fmt.Errorf("error getting network penalty per rate: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The amount a node operator is penalised for each penalty as a percentage
+func GetNetworkPenaltyPerRateRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := networkSettingsContract.Call(opts, value, "getPerPenaltyRate"); err != nil {
+ return nil, fmt.Errorf("error getting network penalty per rate: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNetworkPenaltyPerRate(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", NetworkPenaltyPerRateSettingPath), NetworkSettingsContractName, NetworkPenaltyPerRateSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNetworkPenaltyPerRateGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", NetworkPenaltyPerRateSettingPath), NetworkSettingsContractName, NetworkPenaltyPerRateSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Rewards submissions currently enabled
+func GetSubmitRewardsEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ networkSettingsContract, err := getNetworkSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := networkSettingsContract.Call(opts, value, "getSubmitRewardsEnabled"); err != nil {
+ return false, fmt.Errorf("error getting rewards submissions enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeSubmitRewardsEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", SubmitRewardsEnabledSettingPath), NetworkSettingsContractName, SubmitRewardsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSubmitRewardsEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", SubmitRewardsEnabledSettingPath), NetworkSettingsContractName, SubmitRewardsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var networkSettingsContractLock sync.Mutex
+
+func getNetworkSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ networkSettingsContractLock.Lock()
+ defer networkSettingsContractLock.Unlock()
+ return rp.GetContract(NetworkSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/node.go b/bindings/settings/protocol/node.go
new file mode 100644
index 000000000..992e36760
--- /dev/null
+++ b/bindings/settings/protocol/node.go
@@ -0,0 +1,175 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ NodeSettingsContractName string = "rocketDAOProtocolSettingsNode"
+ NodeRegistrationEnabledSettingPath string = "node.registration.enabled"
+ SmoothingPoolRegistrationEnabledSettingPath string = "node.smoothing.pool.registration.enabled"
+ NodeDepositEnabledSettingPath string = "node.deposit.enabled"
+ VacantMinipoolsEnabledSettingPath string = "node.vacant.minipools.enabled"
+ MinimumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.minimum"
+ MaximumPerMinipoolStakeSettingPath string = "node.per.minipool.stake.maximum"
+)
+
+// Node registrations currently enabled
+func GetNodeRegistrationEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := nodeSettingsContract.Call(opts, value, "getRegistrationEnabled"); err != nil {
+ return false, fmt.Errorf("error getting node registrations enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNodeRegistrationEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", NodeRegistrationEnabledSettingPath), NodeSettingsContractName, NodeRegistrationEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNodeRegistrationEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", NodeRegistrationEnabledSettingPath), NodeSettingsContractName, NodeRegistrationEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Smoothing pool joining currently enabled
+func GetSmoothingPoolRegistrationEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := nodeSettingsContract.Call(opts, value, "getSmoothingPoolRegistrationEnabled"); err != nil {
+ return false, fmt.Errorf("error getting smoothing pool registrations enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeSmoothingPoolRegistrationEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", SmoothingPoolRegistrationEnabledSettingPath), NodeSettingsContractName, SmoothingPoolRegistrationEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSmoothingPoolRegistrationEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", SmoothingPoolRegistrationEnabledSettingPath), NodeSettingsContractName, SmoothingPoolRegistrationEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Node deposits currently enabled
+func GetNodeDepositEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := nodeSettingsContract.Call(opts, value, "getDepositEnabled"); err != nil {
+ return false, fmt.Errorf("error getting node deposits enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeNodeDepositEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", NodeDepositEnabledSettingPath), NodeSettingsContractName, NodeDepositEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeNodeDepositEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", NodeDepositEnabledSettingPath), NodeSettingsContractName, NodeDepositEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Vacant minipools currently enabled
+func GetVacantMinipoolsEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := nodeSettingsContract.Call(opts, value, "getVacantMinipoolsEnabled"); err != nil {
+ return false, fmt.Errorf("error getting vacant minipools enabled status: %w", err)
+ }
+ return *value, nil
+}
+func ProposeVacantMinipoolsEnabled(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetBool(rp, fmt.Sprintf("set %s", VacantMinipoolsEnabledSettingPath), NodeSettingsContractName, VacantMinipoolsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeVacantMinipoolsEnabledGas(rp *rocketpool.RocketPool, value bool, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", VacantMinipoolsEnabledSettingPath), NodeSettingsContractName, VacantMinipoolsEnabledSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The minimum RPL stake per minipool as a fraction of assigned user ETH
+func GetMinimumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil {
+ return 0, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+func ProposeMinimumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMinimumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinimumPerMinipoolStakeSettingPath), NodeSettingsContractName, MinimumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The minimum RPL stake per minipool as a fraction of assigned user ETH
+func GetMinimumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := nodeSettingsContract.Call(opts, value, "getMinimumPerMinipoolStake"); err != nil {
+ return nil, fmt.Errorf("error getting minimum RPL stake per minipool: %w", err)
+ }
+ return *value, nil
+}
+
+// The maximum RPL stake per minipool as a fraction of assigned user ETH
+func GetMaximumPerMinipoolStake(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil {
+ return 0, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+func ProposeMaximumPerMinipoolStake(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeMaximumPerMinipoolStakeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MaximumPerMinipoolStakeSettingPath), NodeSettingsContractName, MaximumPerMinipoolStakeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The maximum RPL stake per minipool as a fraction of assigned user ETH
+func GetMaximumPerMinipoolStakeRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ nodeSettingsContract, err := getNodeSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := nodeSettingsContract.Call(opts, value, "getMaximumPerMinipoolStake"); err != nil {
+ return nil, fmt.Errorf("error getting maximum RPL stake per minipool: %w", err)
+ }
+ return *value, nil
+}
+
+// Get contracts
+var nodeSettingsContractLock sync.Mutex
+
+func getNodeSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ nodeSettingsContractLock.Lock()
+ defer nodeSettingsContractLock.Unlock()
+ return rp.GetContract(NodeSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/proposals.go b/bindings/settings/protocol/proposals.go
new file mode 100644
index 000000000..3b3eaa135
--- /dev/null
+++ b/bindings/settings/protocol/proposals.go
@@ -0,0 +1,255 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ ProposalsSettingsContractName string = "rocketDAOProtocolSettingsProposals"
+ VotePhase1TimeSettingPath string = "proposal.vote.phase1.time"
+ VotePhase2TimeSettingPath string = "proposal.vote.phase2.time"
+ VoteDelayTimeSettingPath string = "proposal.vote.delay.time"
+ ExecuteTimeSettingPath string = "proposal.execute.time"
+ ProposalBondSettingPath string = "proposal.bond"
+ ChallengeBondSettingPath string = "proposal.challenge.bond"
+ ChallengePeriodSettingPath string = "proposal.challenge.period"
+ ProposalQuorumSettingPath string = "proposal.quorum"
+ ProposalVetoQuorumSettingPath string = "proposal.veto.quorum"
+ ProposalMaxBlockAgeSettingPath string = "proposal.max.block.age"
+)
+
+// How long a proposal can be voted on phase 1
+func GetVotePhase1Time(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getVotePhase1Time"); err != nil {
+ return 0, fmt.Errorf("error getting vote time: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeVotePhase1Time(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", VotePhase1TimeSettingPath), ProposalsSettingsContractName, VotePhase1TimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeVotePhase1TimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", VotePhase1TimeSettingPath), ProposalsSettingsContractName, VotePhase1TimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long a proposal can be voted on phase 2
+func GetVotePhase2Time(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getVotePhase2Time"); err != nil {
+ return 0, fmt.Errorf("error getting vote time: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeVotePhase2Time(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", VotePhase2TimeSettingPath), ProposalsSettingsContractName, VotePhase2TimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeVotePhase2TimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", VotePhase2TimeSettingPath), ProposalsSettingsContractName, VotePhase2TimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long before a proposal can be voted on after its created
+func GetVoteDelayTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getVoteDelayTime"); err != nil {
+ return 0, fmt.Errorf("error getting vote delay time: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeVoteDelayTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", VoteDelayTimeSettingPath), ProposalsSettingsContractName, VoteDelayTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeVoteDelayTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", VoteDelayTimeSettingPath), ProposalsSettingsContractName, VoteDelayTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long after a succesful proposal can it be executed before it expires
+func GetExecuteTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getExecuteTime"); err != nil {
+ return 0, fmt.Errorf("error getting execute time: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeExecuteTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ExecuteTimeSettingPath), ProposalsSettingsContractName, ExecuteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeExecuteTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ExecuteTimeSettingPath), ProposalsSettingsContractName, ExecuteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How much RPL is locked when creating a proposal
+func GetProposalBond(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalBond"); err != nil {
+ return nil, fmt.Errorf("error getting proposal bond: %w", err)
+ }
+ return *value, nil
+}
+func ProposeProposalBond(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ProposalBondSettingPath), ProposalsSettingsContractName, ProposalBondSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeProposalBondGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ProposalBondSettingPath), ProposalsSettingsContractName, ProposalBondSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How much RPL is locked when challenging a proposal
+func GetChallengeBond(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getChallengeBond"); err != nil {
+ return nil, fmt.Errorf("error getting challenge bond: %w", err)
+ }
+ return *value, nil
+}
+func ProposeChallengeBond(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ChallengeBondSettingPath), ProposalsSettingsContractName, ChallengeBondSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeChallengeBondGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ChallengeBondSettingPath), ProposalsSettingsContractName, ChallengeBondSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long a proposer has to respond to a challenge before the proposal is defeated
+func GetChallengePeriod(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getChallengePeriod"); err != nil {
+ return 0, fmt.Errorf("error getting challenge period: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeChallengePeriod(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ChallengePeriodSettingPath), ProposalsSettingsContractName, ChallengePeriodSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeChallengePeriodGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ChallengePeriodSettingPath), ProposalsSettingsContractName, ChallengePeriodSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The minimum amount of voting power a proposal needs to succeed
+func GetProposalQuorum(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalQuorum"); err != nil {
+ return 0, fmt.Errorf("error getting proposal quorum: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The minimum amount of voting power a proposal needs to succeed
+func GetProposalQuorumRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalQuorum"); err != nil {
+ return nil, fmt.Errorf("error getting proposal quorum: %w", err)
+ }
+ return *value, nil
+}
+func ProposeProposalQuorum(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ProposalQuorumSettingPath), ProposalsSettingsContractName, ProposalQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeProposalQuorumGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ProposalQuorumSettingPath), ProposalsSettingsContractName, ProposalQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The amount of voting power vetoing a proposal require to veto it
+func GetProposalVetoQuorum(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalVetoQuorum"); err != nil {
+ return 0, fmt.Errorf("error getting proposal veto quorum: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// The amount of voting power vetoing a proposal require to veto it
+func GetProposalVetoQuorumRaw(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalVetoQuorum"); err != nil {
+ return nil, fmt.Errorf("error getting proposal veto quorum: %w", err)
+ }
+ return *value, nil
+}
+func ProposeProposalVetoQuorum(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ProposalVetoQuorumSettingPath), ProposalsSettingsContractName, ProposalVetoQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeProposalVetoQuorumGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ProposalVetoQuorumSettingPath), ProposalsSettingsContractName, ProposalVetoQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// The maximum number of blocks old a proposal can be submitted for
+func GetProposalMaxBlockAge(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getProposalMaxBlockAge"); err != nil {
+ return 0, fmt.Errorf("error getting proposal max block age: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalMaxBlockAge(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", ProposalMaxBlockAgeSettingPath), ProposalsSettingsContractName, ProposalMaxBlockAgeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeProposalMaxBlockAgeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ProposalMaxBlockAgeSettingPath), ProposalsSettingsContractName, ProposalMaxBlockAgeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var proposalsSettingsContractLock sync.Mutex
+
+func getProposalsSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ proposalsSettingsContractLock.Lock()
+ defer proposalsSettingsContractLock.Unlock()
+ return rp.GetContract(ProposalsSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/rewards.go b/bindings/settings/protocol/rewards.go
new file mode 100644
index 000000000..4d62c2279
--- /dev/null
+++ b/bindings/settings/protocol/rewards.go
@@ -0,0 +1,135 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ RewardsSettingsContractName string = "rocketDAOProtocolSettingsRewards"
+ RewardsClaimIntervalPeriodsSettingPath string = "rewards.claimsperiods"
+)
+
+// Rewards claimer percents
+type RplRewardsPercentages struct {
+ OdaoPercentage *big.Int `abi:"trustedNodePerc"`
+ PdaoPercentage *big.Int `abi:"protocolPerc"`
+ NodePercentage *big.Int `abi:"nodePerc"`
+}
+
+// The RPL rewards percentages for the Oracle DAO, Protocol DAO, and node operators
+func GetRewardsPercentages(rp *rocketpool.RocketPool, opts *bind.CallOpts) (RplRewardsPercentages, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return RplRewardsPercentages{}, err
+ }
+ value := new(RplRewardsPercentages)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersPerc"); err != nil {
+ return RplRewardsPercentages{}, fmt.Errorf("error getting rewards percentages: %w", err)
+ }
+ return *value, nil
+}
+
+// The total RPL rewards percentage for node operator collateral
+func GetNodeOperatorRewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersNodePerc"); err != nil {
+ return nil, fmt.Errorf("error getting node operator rewards percent: %w", err)
+ }
+ return *value, nil
+}
+
+// The total RPL rewards percentage for Oracle DAO members
+func GetOracleDAORewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersTrustedNodePerc"); err != nil {
+ return nil, fmt.Errorf("error getting oracle DAO rewards percent: %w", err)
+ }
+ return *value, nil
+}
+
+// The total RPL rewards percentage for the Protocol DAO treasury
+func GetProtocolDAORewardsPercent(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersProtocolPerc"); err != nil {
+ return nil, fmt.Errorf("error getting protocol DAO rewards percent: %w", err)
+ }
+ return *value, nil
+}
+
+// The time that the RPL rewards percentages were last updated
+func GetRewardsClaimerPercTimeUpdated(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersTimeUpdated"); err != nil {
+ return 0, fmt.Errorf("error getting rewards claimer updated time: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+
+// The total claim amount for all claimers as a fraction
+func GetRewardsClaimersPercTotal(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimersPercTotal"); err != nil {
+ return 0, fmt.Errorf("error getting rewards claimers total percent: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+
+// Rewards claim interval time
+func GetRewardsClaimIntervalTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := rewardsSettingsContract.Call(opts, value, "getRewardsClaimIntervalTime"); err != nil {
+ return 0, fmt.Errorf("error getting rewards claim interval: %w", err)
+ }
+ return time.Duration((*value).Uint64()) * time.Second, nil
+}
+func ProposeRewardsClaimIntervalTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", RewardsClaimIntervalPeriodsSettingPath), RewardsSettingsContractName, RewardsClaimIntervalPeriodsSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeRewardsClaimIntervalTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", RewardsClaimIntervalPeriodsSettingPath), RewardsSettingsContractName, RewardsClaimIntervalPeriodsSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var rewardsSettingsContractLock sync.Mutex
+
+func getRewardsSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rewardsSettingsContractLock.Lock()
+ defer rewardsSettingsContractLock.Unlock()
+ return rp.GetContract(RewardsSettingsContractName, opts)
+}
diff --git a/bindings/settings/protocol/security.go b/bindings/settings/protocol/security.go
new file mode 100644
index 000000000..16c277d12
--- /dev/null
+++ b/bindings/settings/protocol/security.go
@@ -0,0 +1,128 @@
+package protocol
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+)
+
+// Config
+const (
+ SecuritySettingsContractName string = "rocketDAOProtocolSettingsSecurity"
+ SecurityMembersQuorumSettingPath string = "members.quorum"
+ SecurityMembersLeaveTimeSettingPath string = "members.leave.time"
+ SecurityProposalVoteTimeSettingPath string = "proposal.vote.time"
+ SecurityProposalExecuteTimeSettingPath string = "proposal.execute.time"
+ SecurityProposalActionTimeSettingPath string = "proposal.action.time"
+)
+
+// Security council member quorum threshold that must be met for proposals to pass
+func GetSecurityMembersQuorum(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ securitySettingsContract, err := getSecuritySettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := securitySettingsContract.Call(opts, value, "getQuorum"); err != nil {
+ return nil, fmt.Errorf("error getting security members quorum: %w", err)
+ }
+ return *value, nil
+}
+func ProposeSecurityMembersQuorum(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SecurityMembersQuorumSettingPath), SecuritySettingsContractName, SecurityMembersQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSecurityMembersQuorumGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SecurityMembersQuorumSettingPath), SecuritySettingsContractName, SecurityMembersQuorumSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long a member must give notice for before manually leaving the security council
+func GetSecurityMembersLeaveTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ securitySettingsContract, err := getSecuritySettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := securitySettingsContract.Call(opts, value, "getLeaveTime"); err != nil {
+ return 0, fmt.Errorf("error getting security members leave time: %w", err)
+ }
+ return time.Second * time.Duration((*value).Uint64()), nil
+}
+func ProposeSecurityMembersLeaveTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SecurityMembersLeaveTimeSettingPath), SecuritySettingsContractName, SecurityMembersLeaveTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSecurityMembersLeaveTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SecurityMembersLeaveTimeSettingPath), SecuritySettingsContractName, SecurityMembersLeaveTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long a security council proposal can be voted on (phase2)
+func GetSecurityProposalVoteTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ securitySettingsContract, err := getSecuritySettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := securitySettingsContract.Call(opts, value, "getVoteTime"); err != nil {
+ return 0, fmt.Errorf("error getting security proposal vote time: %w", err)
+ }
+ return time.Second * time.Duration((*value).Uint64()), nil
+}
+func ProposeSecurityProposalVoteTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SecurityProposalVoteTimeSettingPath), SecuritySettingsContractName, SecurityProposalVoteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSecurityProposalVoteTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SecurityProposalVoteTimeSettingPath), SecuritySettingsContractName, SecurityProposalVoteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// How long a security council proposal can be executed after its voting period is finished
+func GetSecurityProposalExecuteTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ securitySettingsContract, err := getSecuritySettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := securitySettingsContract.Call(opts, value, "getExecuteTime"); err != nil {
+ return 0, fmt.Errorf("error getting security proposal execute time: %w", err)
+ }
+ return time.Second * time.Duration((*value).Uint64()), nil
+}
+func ProposeSecurityProposalExecuteTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SecurityProposalExecuteTimeSettingPath), SecuritySettingsContractName, SecurityProposalExecuteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSecurityProposalExecuteTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SecurityProposalExecuteTimeSettingPath), SecuritySettingsContractName, SecurityProposalExecuteTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Certain security council proposals require a secondary action to be run after the proposal is successful (joining, leaving etc). This is how long until that action expires.
+func GetSecurityProposalActionTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) {
+ securitySettingsContract, err := getSecuritySettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := securitySettingsContract.Call(opts, value, "getActionTime"); err != nil {
+ return 0, fmt.Errorf("error getting security proposal action time: %w", err)
+ }
+ return time.Second * time.Duration((*value).Uint64()), nil
+}
+func ProposeSecurityProposalActionTime(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return protocol.ProposeSetUint(rp, fmt.Sprintf("set %s", SecurityProposalActionTimeSettingPath), SecuritySettingsContractName, SecurityProposalActionTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+func EstimateProposeSecurityProposalActionTimeGas(rp *rocketpool.RocketPool, value *big.Int, blockNumber uint32, treeNodes []types.VotingTreeNode, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return protocol.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", SecurityProposalActionTimeSettingPath), SecuritySettingsContractName, SecurityProposalActionTimeSettingPath, value, blockNumber, treeNodes, opts)
+}
+
+// Get contracts
+var securitySettingsContractLock sync.Mutex
+
+func getSecuritySettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ securitySettingsContractLock.Lock()
+ defer securitySettingsContractLock.Unlock()
+ return rp.GetContract(SecuritySettingsContractName, opts)
+}
diff --git a/bindings/settings/security/auction.go b/bindings/settings/security/auction.go
new file mode 100644
index 000000000..57771ee0f
--- /dev/null
+++ b/bindings/settings/security/auction.go
@@ -0,0 +1,32 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/security"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ psettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+)
+
+const (
+ auctionNamespace string = "auction"
+)
+
+// Lot creation currently enabled
+func ProposeCreateLotEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.CreateLotEnabledSettingPath), auctionNamespace, psettings.CreateLotEnabledSettingPath, value, opts)
+}
+func EstimateProposeCreateLotEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.CreateLotEnabledSettingPath), auctionNamespace, psettings.CreateLotEnabledSettingPath, value, opts)
+}
+
+// Lot bidding currently enabled
+func ProposeBidOnLotEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.BidOnLotEnabledSettingPath), auctionNamespace, psettings.BidOnLotEnabledSettingPath, value, opts)
+}
+func EstimateProposeBidOnLotEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.BidOnLotEnabledSettingPath), auctionNamespace, psettings.BidOnLotEnabledSettingPath, value, opts)
+}
diff --git a/bindings/settings/security/deposit.go b/bindings/settings/security/deposit.go
new file mode 100644
index 000000000..8ce4b2540
--- /dev/null
+++ b/bindings/settings/security/deposit.go
@@ -0,0 +1,32 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/security"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ psettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+)
+
+const (
+ depositNamespace string = "deposit"
+)
+
+// Deposits currently enabled
+func ProposeDepositEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.DepositEnabledSettingPath), depositNamespace, psettings.DepositEnabledSettingPath, value, opts)
+}
+func EstimateProposeDepositEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.DepositEnabledSettingPath), depositNamespace, psettings.DepositEnabledSettingPath, value, opts)
+}
+
+// Deposit assignments currently enabled
+func ProposeAssignDepositsEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.AssignDepositsEnabledSettingPath), depositNamespace, psettings.AssignDepositsEnabledSettingPath, value, opts)
+}
+func EstimateProposeAssignDepositsEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.AssignDepositsEnabledSettingPath), depositNamespace, psettings.AssignDepositsEnabledSettingPath, value, opts)
+}
diff --git a/bindings/settings/security/minipool.go b/bindings/settings/security/minipool.go
new file mode 100644
index 000000000..a5e9fe076
--- /dev/null
+++ b/bindings/settings/security/minipool.go
@@ -0,0 +1,32 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/security"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ psettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+)
+
+const (
+ minipoolNamespace string = "minipool"
+)
+
+// Minipool withdrawable event submissions currently enabled
+func ProposeMinipoolSubmitWithdrawableEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.MinipoolSubmitWithdrawableEnabledSettingPath), minipoolNamespace, psettings.MinipoolSubmitWithdrawableEnabledSettingPath, value, opts)
+}
+func EstimateProposeMinipoolSubmitWithdrawableEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.MinipoolSubmitWithdrawableEnabledSettingPath), minipoolNamespace, psettings.MinipoolSubmitWithdrawableEnabledSettingPath, value, opts)
+}
+
+// Minipool bond reductions currently enabled
+func ProposeBondReductionEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.BondReductionEnabledSettingPath), minipoolNamespace, psettings.BondReductionEnabledSettingPath, value, opts)
+}
+func EstimateProposeBondReductionEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.BondReductionEnabledSettingPath), minipoolNamespace, psettings.BondReductionEnabledSettingPath, value, opts)
+}
diff --git a/bindings/settings/security/network.go b/bindings/settings/security/network.go
new file mode 100644
index 000000000..8bbcc161e
--- /dev/null
+++ b/bindings/settings/security/network.go
@@ -0,0 +1,32 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/security"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ psettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+)
+
+const (
+ networkNamespace string = "network"
+)
+
+// Network balance submissions currently enabled
+func ProposeSubmitBalancesEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.SubmitBalancesEnabledSettingPath), networkNamespace, psettings.SubmitBalancesEnabledSettingPath, value, opts)
+}
+func EstimateProposeSubmitBalancesEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.SubmitBalancesEnabledSettingPath), networkNamespace, psettings.SubmitBalancesEnabledSettingPath, value, opts)
+}
+
+// Rewards submissions currently enabled
+func ProposeSubmitRewardsEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.SubmitRewardsEnabledSettingPath), networkNamespace, psettings.SubmitRewardsEnabledSettingPath, value, opts)
+}
+func EstimateProposeSubmitRewardsEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.SubmitRewardsEnabledSettingPath), networkNamespace, psettings.SubmitRewardsEnabledSettingPath, value, opts)
+}
diff --git a/bindings/settings/security/node.go b/bindings/settings/security/node.go
new file mode 100644
index 000000000..07036c917
--- /dev/null
+++ b/bindings/settings/security/node.go
@@ -0,0 +1,48 @@
+package security
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao/security"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ psettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+)
+
+const (
+ nodeNamespace string = "node"
+)
+
+// Node registrations currently enabled
+func ProposeNodeRegistrationEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.NodeRegistrationEnabledSettingPath), nodeNamespace, psettings.NodeRegistrationEnabledSettingPath, value, opts)
+}
+func EstimateProposeNodeRegistrationEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.NodeRegistrationEnabledSettingPath), nodeNamespace, psettings.NodeRegistrationEnabledSettingPath, value, opts)
+}
+
+// Smoothing pool joining currently enabled
+func ProposeSmoothingPoolRegistrationEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.SmoothingPoolRegistrationEnabledSettingPath), nodeNamespace, psettings.SmoothingPoolRegistrationEnabledSettingPath, value, opts)
+}
+func EstimateProposeSmoothingPoolRegistrationEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.SmoothingPoolRegistrationEnabledSettingPath), nodeNamespace, psettings.SmoothingPoolRegistrationEnabledSettingPath, value, opts)
+}
+
+// Node deposits currently enabled
+func ProposeNodeDepositEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.NodeDepositEnabledSettingPath), nodeNamespace, psettings.NodeDepositEnabledSettingPath, value, opts)
+}
+func EstimateProposeNodeDepositEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.NodeDepositEnabledSettingPath), nodeNamespace, psettings.NodeDepositEnabledSettingPath, value, opts)
+}
+
+// Vacant minipools currently enabled
+func ProposeVacantMinipoolsEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return security.ProposeSetBool(rp, fmt.Sprintf("set %s", psettings.VacantMinipoolsEnabledSettingPath), nodeNamespace, psettings.VacantMinipoolsEnabledSettingPath, value, opts)
+}
+func EstimateProposeVacantMinipoolsEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return security.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", psettings.VacantMinipoolsEnabledSettingPath), nodeNamespace, psettings.VacantMinipoolsEnabledSettingPath, value, opts)
+}
diff --git a/bindings/settings/trustednode/members.go b/bindings/settings/trustednode/members.go
new file mode 100644
index 000000000..043ffbe6f
--- /dev/null
+++ b/bindings/settings/trustednode/members.go
@@ -0,0 +1,168 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Config
+const (
+ MembersSettingsContractName = "rocketDAONodeTrustedSettingsMembers"
+ QuorumSettingPath = "members.quorum"
+ RPLBondSettingPath = "members.rplbond"
+ MinipoolUnbondedMaxSettingPath = "members.minipool.unbonded.max"
+ MinipoolUnbondedMinFeeSettingPath = "members.minipool.unbonded.min.fee"
+ ChallengeCooldownSettingPath = "members.challenge.cooldown"
+ ChallengeWindowSettingPath = "members.challenge.window"
+ ChallengeCostSettingPath = "members.challenge.cost"
+)
+
+// Member proposal quorum threshold
+func GetQuorum(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getQuorum"); err != nil {
+ return 0, fmt.Errorf("error getting member quorum threshold: %w", err)
+ }
+ return eth.WeiToEth(*value), nil
+}
+func ProposeQuorum(rp *rocketpool.RocketPool, value float64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", QuorumSettingPath), MembersSettingsContractName, QuorumSettingPath, eth.EthToWei(value), opts)
+}
+func EstimateProposeQuorumGas(rp *rocketpool.RocketPool, value float64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", QuorumSettingPath), MembersSettingsContractName, QuorumSettingPath, eth.EthToWei(value), opts)
+}
+
+// RPL bond required for a member
+func GetRPLBond(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getRPLBond"); err != nil {
+ return nil, fmt.Errorf("error getting member RPL bond amount: %w", err)
+ }
+ return *value, nil
+}
+func ProposeRPLBond(rp *rocketpool.RocketPool, value *big.Int, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", RPLBondSettingPath), MembersSettingsContractName, RPLBondSettingPath, value, opts)
+}
+func EstimateProposeRPLBondGas(rp *rocketpool.RocketPool, value *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", RPLBondSettingPath), MembersSettingsContractName, RPLBondSettingPath, value, opts)
+}
+
+// The maximum number of unbonded minipools a member can run
+func GetMinipoolUnbondedMax(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getMinipoolUnbondedMax"); err != nil {
+ return 0, fmt.Errorf("error getting member unbonded minipool limit: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeMinipoolUnbondedMax(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", MinipoolUnbondedMaxSettingPath), MembersSettingsContractName, MinipoolUnbondedMaxSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeMinipoolUnbondedMaxGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinipoolUnbondedMaxSettingPath), MembersSettingsContractName, MinipoolUnbondedMaxSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The minimum commission rate before unbonded minipools are allowed
+func GetMinipoolUnbondedMinFee(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getMinipoolUnbondedMinFee"); err != nil {
+ return 0, fmt.Errorf("error getting member unbonded minipool minimum fee: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeMinipoolUnbondedMinFee(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", MinipoolUnbondedMinFeeSettingPath), MembersSettingsContractName, MinipoolUnbondedMinFeeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeMinipoolUnbondedMinFeeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", MinipoolUnbondedMinFeeSettingPath), MembersSettingsContractName, MinipoolUnbondedMinFeeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The period a member must wait for before submitting another challenge, in blocks
+func GetChallengeCooldown(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getChallengeCooldown"); err != nil {
+ return 0, fmt.Errorf("error getting member challenge cooldown period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeChallengeCooldown(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ChallengeCooldownSettingPath), MembersSettingsContractName, ChallengeCooldownSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeChallengeCooldownGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ChallengeCooldownSettingPath), MembersSettingsContractName, ChallengeCooldownSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The period during which a member can respond to a challenge, in blocks
+func GetChallengeWindow(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getChallengeWindow"); err != nil {
+ return 0, fmt.Errorf("error getting member challenge window period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeChallengeWindow(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ChallengeWindowSettingPath), MembersSettingsContractName, ChallengeWindowSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeChallengeWindowGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ChallengeWindowSettingPath), MembersSettingsContractName, ChallengeWindowSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The fee for a non-member to challenge a member, in wei
+func GetChallengeCost(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ membersSettingsContract, err := getMembersSettingsContract(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ value := new(*big.Int)
+ if err := membersSettingsContract.Call(opts, value, "getChallengeCost"); err != nil {
+ return nil, fmt.Errorf("error getting member challenge cost: %w", err)
+ }
+ return *value, nil
+}
+func ProposeChallengeCost(rp *rocketpool.RocketPool, value *big.Int, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ChallengeCostSettingPath), MembersSettingsContractName, ChallengeCostSettingPath, value, opts)
+}
+func EstimateProposeChallengeCostGas(rp *rocketpool.RocketPool, value *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ChallengeCostSettingPath), MembersSettingsContractName, ChallengeCostSettingPath, value, opts)
+}
+
+// Get contracts
+var membersSettingsContractLock sync.Mutex
+
+func getMembersSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ membersSettingsContractLock.Lock()
+ defer membersSettingsContractLock.Unlock()
+ return rp.GetContract(MembersSettingsContractName, opts)
+}
diff --git a/bindings/settings/trustednode/minipool.go b/bindings/settings/trustednode/minipool.go
new file mode 100644
index 000000000..6e98e806a
--- /dev/null
+++ b/bindings/settings/trustednode/minipool.go
@@ -0,0 +1,127 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Config
+const (
+ MinipoolSettingsContractName = "rocketDAONodeTrustedSettingsMinipool"
+ ScrubPeriodPath = "minipool.scrub.period"
+ PromotionScrubPeriodPath = "minipool.promotion.scrub.period"
+ ScrubPenaltyEnabledPath = "minipool.scrub.penalty.enabled"
+ BondReductionWindowStartPath = "minipool.bond.reduction.window.start"
+ BondReductionWindowLengthPath = "minipool.bond.reduction.window.length"
+)
+
+// The amount of time, in seconds, the scrub check lasts before a minipool can move from prelaunch to staking
+func GetScrubPeriod(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getScrubPeriod"); err != nil {
+ return 0, fmt.Errorf("error getting scrub period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeScrubPeriod(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ScrubPeriodPath), MinipoolSettingsContractName, ScrubPeriodPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeScrubPeriodGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ScrubPeriodPath), MinipoolSettingsContractName, ScrubPeriodPath, big.NewInt(int64(value)), opts)
+}
+
+// The amount of time, in seconds, the promotion scrub check lasts before a vacant minipool can be promoted
+func GetPromotionScrubPeriod(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getPromotionScrubPeriod"); err != nil {
+ return 0, fmt.Errorf("error getting promotion scrub period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposePromotionScrubPeriod(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", PromotionScrubPeriodPath), MinipoolSettingsContractName, PromotionScrubPeriodPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposePromotionScrubPeriodGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", PromotionScrubPeriodPath), MinipoolSettingsContractName, PromotionScrubPeriodPath, big.NewInt(int64(value)), opts)
+}
+
+// Whether or not the RPL slashing penalty is applied to scrubbed minipools
+func GetScrubPenaltyEnabled(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := minipoolSettingsContract.Call(opts, value, "getScrubPenaltyEnabled"); err != nil {
+ return false, fmt.Errorf("error getting scrub penalty setting: %w", err)
+ }
+ return (*value), nil
+}
+func ProposeScrubPenaltyEnabled(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetBool(rp, fmt.Sprintf("set %s", ScrubPenaltyEnabledPath), MinipoolSettingsContractName, ScrubPenaltyEnabledPath, value, opts)
+}
+func EstimateProposeScrubPenaltyEnabledGas(rp *rocketpool.RocketPool, value bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetBoolGas(rp, fmt.Sprintf("set %s", ScrubPenaltyEnabledPath), MinipoolSettingsContractName, ScrubPenaltyEnabledPath, value, opts)
+}
+
+// The amount of time, in seconds, a minipool must wait after beginning a bond reduction before it can apply the bond reduction (how long the Oracle DAO has to cancel the reduction if required)
+func GetBondReductionWindowStart(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getBondReductionWindowStart"); err != nil {
+ return 0, fmt.Errorf("error getting bond reduction window start: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeBondReductionWindowStart(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", BondReductionWindowStartPath), MinipoolSettingsContractName, BondReductionWindowStartPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeBondReductionWindowStartGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", BondReductionWindowStartPath), MinipoolSettingsContractName, BondReductionWindowStartPath, big.NewInt(int64(value)), opts)
+}
+
+// The amount of time, in seconds, a minipool has to reduce its bond once it has passed the check window
+func GetBondReductionWindowLength(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ minipoolSettingsContract, err := getMinipoolSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := minipoolSettingsContract.Call(opts, value, "getBondReductionWindowLength"); err != nil {
+ return 0, fmt.Errorf("error getting bond reduction window length: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeBondReductionWindowLength(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", BondReductionWindowLengthPath), MinipoolSettingsContractName, BondReductionWindowLengthPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeBondReductionWindowLengthGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", BondReductionWindowLengthPath), MinipoolSettingsContractName, BondReductionWindowLengthPath, big.NewInt(int64(value)), opts)
+}
+
+// Get contracts
+var minipoolSettingsContractLock sync.Mutex
+
+func getMinipoolSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ minipoolSettingsContractLock.Lock()
+ defer minipoolSettingsContractLock.Unlock()
+ return rp.GetContract(MinipoolSettingsContractName, opts)
+}
diff --git a/bindings/settings/trustednode/proposals.go b/bindings/settings/trustednode/proposals.go
new file mode 100644
index 000000000..e3fb61fd8
--- /dev/null
+++ b/bindings/settings/trustednode/proposals.go
@@ -0,0 +1,127 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Config
+const (
+ ProposalsSettingsContractName = "rocketDAONodeTrustedSettingsProposals"
+ CooldownTimeSettingPath = "proposal.cooldown.time"
+ VoteTimeSettingPath = "proposal.vote.time"
+ VoteDelayTimeSettingPath = "proposal.vote.delay.time"
+ ExecuteTimeSettingPath = "proposal.execute.time"
+ ActionTimeSettingPath = "proposal.action.time"
+)
+
+// The cooldown period a member must wait after making a proposal before making another in seconds
+func GetProposalCooldownTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getCooldownTime"); err != nil {
+ return 0, fmt.Errorf("error getting proposal cooldown period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalCooldownTime(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", CooldownTimeSettingPath), ProposalsSettingsContractName, CooldownTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeProposalCooldownTimeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", CooldownTimeSettingPath), ProposalsSettingsContractName, CooldownTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The period a proposal can be voted on for in seconds
+func GetProposalVoteTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getVoteTime"); err != nil {
+ return 0, fmt.Errorf("error getting proposal voting period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalVoteTime(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", VoteTimeSettingPath), ProposalsSettingsContractName, VoteTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeProposalVoteTimeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", VoteTimeSettingPath), ProposalsSettingsContractName, VoteTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The delay after creation before a proposal can be voted on in seconds
+func GetProposalVoteDelayTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getVoteDelayTime"); err != nil {
+ return 0, fmt.Errorf("error getting proposal voting delay: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalVoteDelayTime(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", VoteDelayTimeSettingPath), ProposalsSettingsContractName, VoteDelayTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeProposalVoteDelayTimeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", VoteDelayTimeSettingPath), ProposalsSettingsContractName, VoteDelayTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The period during which a passed proposal can be executed in time
+func GetProposalExecuteTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getExecuteTime"); err != nil {
+ return 0, fmt.Errorf("error getting proposal execution period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalExecuteTime(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ExecuteTimeSettingPath), ProposalsSettingsContractName, ExecuteTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeProposalExecuteTimeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ExecuteTimeSettingPath), ProposalsSettingsContractName, ExecuteTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// The period during which an action can be performed on an executed proposal in seconds
+func GetProposalActionTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (uint64, error) {
+ proposalsSettingsContract, err := getProposalsSettingsContract(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ value := new(*big.Int)
+ if err := proposalsSettingsContract.Call(opts, value, "getActionTime"); err != nil {
+ return 0, fmt.Errorf("error getting proposal action period: %w", err)
+ }
+ return (*value).Uint64(), nil
+}
+func ProposeProposalActionTime(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (uint64, common.Hash, error) {
+ return trustednodedao.ProposeSetUint(rp, fmt.Sprintf("set %s", ActionTimeSettingPath), ProposalsSettingsContractName, ActionTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+func EstimateProposeProposalActionTimeGas(rp *rocketpool.RocketPool, value uint64, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return trustednodedao.EstimateProposeSetUintGas(rp, fmt.Sprintf("set %s", ActionTimeSettingPath), ProposalsSettingsContractName, ActionTimeSettingPath, big.NewInt(int64(value)), opts)
+}
+
+// Get contracts
+var proposalsSettingsContractLock sync.Mutex
+
+func getProposalsSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ proposalsSettingsContractLock.Lock()
+ defer proposalsSettingsContractLock.Unlock()
+ return rp.GetContract(ProposalsSettingsContractName, opts)
+}
diff --git a/bindings/settings/trustednode/rewards.go b/bindings/settings/trustednode/rewards.go
new file mode 100644
index 000000000..1381aa910
--- /dev/null
+++ b/bindings/settings/trustednode/rewards.go
@@ -0,0 +1,39 @@
+package trustednode
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Config
+const (
+ RewardsSettingsContractName string = "rocketDAONodeTrustedSettingsRewards"
+ NetworkEnabledPath string = "rewards.network.enabled"
+)
+
+// Get whether or not the provided rewards network is enabled
+func GetNetworkEnabled(rp *rocketpool.RocketPool, network *big.Int, opts *bind.CallOpts) (bool, error) {
+ rewardsSettingsContract, err := getRewardsSettingsContract(rp, opts)
+ if err != nil {
+ return false, err
+ }
+ value := new(bool)
+ if err := rewardsSettingsContract.Call(opts, value, "getNetworkEnabled", network); err != nil {
+ return false, fmt.Errorf("error checking if network %s is enabled: %w", network.String(), err)
+ }
+ return (*value), nil
+}
+
+// Get contracts
+var rewardsSettingsContractLock sync.Mutex
+
+func getRewardsSettingsContract(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rewardsSettingsContractLock.Lock()
+ defer rewardsSettingsContractLock.Unlock()
+ return rp.GetContract(RewardsSettingsContractName, opts)
+}
diff --git a/bindings/storage/address-queue-storage.go b/bindings/storage/address-queue-storage.go
new file mode 100644
index 000000000..506e17226
--- /dev/null
+++ b/bindings/storage/address-queue-storage.go
@@ -0,0 +1,61 @@
+package storage
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// low-level address queue storage interface. Currently only used for the minipool queue.
+
+// Return the length of all addresses matching the given key in the queue
+func GetAddressQueueLength(rp *rocketpool.RocketPool, opts *bind.CallOpts, key [32]byte) (uint64, error) {
+ addressQueueStorage, err := getAddressQueueStorage(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ length := new(*big.Int)
+ if err := addressQueueStorage.Call(opts, length, "getIndexOf", key); err != nil {
+ return 0, fmt.Errorf("error getting address queue length for key: %w", key, err)
+ }
+ return (*length).Uint64(), nil
+}
+
+// Return address item at index for the given key
+func GetAddressQueueItem(rp *rocketpool.RocketPool, opts *bind.CallOpts, key [32]byte, index *big.Int) (common.Address, error) {
+ addressQueueStorage, err := getAddressQueueStorage(rp, opts)
+ if err != nil {
+ return common.Address{}, err
+ }
+ address := new(common.Address)
+ if err := addressQueueStorage.Call(opts, address, "getItem", key, index); err != nil {
+ return common.Address{}, fmt.Errorf("error getting address item at index %d: %w", index, key, err)
+ }
+ return *address, nil
+}
+
+// Return index of the input address for the given key. -1 if not present.
+func GetAddressQueueIndexOf(rp *rocketpool.RocketPool, opts *bind.CallOpts, key [32]byte, address common.Address) (int64, error) {
+ addressQueueStorage, err := getAddressQueueStorage(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ index := new(*big.Int)
+ if err := addressQueueStorage.Call(opts, index, "getIndexOf", key, address); err != nil {
+ return 0, fmt.Errorf("error getting index for address %s: %w", address.String(), err)
+ }
+ return (*index).Int64(), nil
+}
+
+// Get contracts
+var AddressQueueStorageLock sync.Mutex
+
+func getAddressQueueStorage(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ AddressQueueStorageLock.Lock()
+ defer AddressQueueStorageLock.Unlock()
+ return rp.GetContract("addressQueueStorage", opts)
+}
diff --git a/bindings/storage/rocket-storage.go b/bindings/storage/rocket-storage.go
new file mode 100644
index 000000000..02566a540
--- /dev/null
+++ b/bindings/storage/rocket-storage.go
@@ -0,0 +1,68 @@
+package storage
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Get a node's withdrawal address
+func GetNodeWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ withdrawalAddress := new(common.Address)
+ if err := rp.RocketStorageContract.Call(opts, withdrawalAddress, "getNodeWithdrawalAddress", nodeAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s withdrawal address: %w", nodeAddress.Hex(), err)
+ }
+ return *withdrawalAddress, nil
+}
+
+// Get a node's pending withdrawal address
+func GetNodePendingWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.CallOpts) (common.Address, error) {
+ withdrawalAddress := new(common.Address)
+ if err := rp.RocketStorageContract.Call(opts, withdrawalAddress, "getNodePendingWithdrawalAddress", nodeAddress); err != nil {
+ return common.Address{}, fmt.Errorf("error getting node %s pending withdrawal address: %w", nodeAddress.Hex(), err)
+ }
+ return *withdrawalAddress, nil
+}
+
+// Estimate the gas of SetWithdrawalAddress
+func EstimateSetWithdrawalAddressGas(rp *rocketpool.RocketPool, nodeAddress common.Address, withdrawalAddress common.Address, confirm bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return rp.RocketStorageContract.GetTransactionGasInfo(opts, "setWithdrawalAddress", nodeAddress, withdrawalAddress, confirm)
+}
+
+// Set a node's withdrawal address
+func SetWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, withdrawalAddress common.Address, confirm bool, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := rp.RocketStorageContract.Transact(opts, "setWithdrawalAddress", nodeAddress, withdrawalAddress, confirm)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error setting node withdrawal address: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of ConfirmWithdrawalAddress
+func EstimateConfirmWithdrawalAddressGas(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return rp.RocketStorageContract.GetTransactionGasInfo(opts, "confirmWithdrawalAddress", nodeAddress)
+}
+
+// Set a node's withdrawal address
+func ConfirmWithdrawalAddress(rp *rocketpool.RocketPool, nodeAddress common.Address, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := rp.RocketStorageContract.Transact(opts, "confirmWithdrawalAddress", nodeAddress)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error confirming node withdrawal address: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the number of the block that Rocket Pool was deployed on
+func GetDeployBlock(rp *rocketpool.RocketPool) (*big.Int, error) {
+ deployBlockHash := crypto.Keccak256Hash([]byte("deploy.block"))
+ deployBlock, err := rp.RocketStorage.GetUint(nil, deployBlockHash)
+ if err != nil {
+ return nil, fmt.Errorf("error getting Rocket Pool deployment block: %w", err)
+ }
+
+ return deployBlock, nil
+}
diff --git a/bindings/test.sh b/bindings/test.sh
new file mode 100755
index 000000000..8dbf2e3de
--- /dev/null
+++ b/bindings/test.sh
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+
+# Exit if a command fails
+set -o errexit
+
+# Check commands
+if ! command -v git &> /dev/null; then
+ echo "git command required"; exit
+fi
+if ! command -v npm &> /dev/null; then
+ echo "npm command required"; exit
+fi
+if ! command -v go &> /dev/null; then
+ echo "go command required"; exit
+fi
+
+
+##
+# Config
+##
+
+
+# Rocket Pool settings
+rp_repo_url="git@ssh.dev.azure.com:v3/rocket-pool/RocketPool/rocketpool"
+rp_repo_branch="v1.1"
+
+# Dependencies
+rp_dependencies=(
+ "@openzeppelin/contracts@3.3.0"
+ "babel-polyfill@6.26.0"
+ "babel-register@6.26.0"
+ "ganache-cli@6.12.2"
+ "pako@1.0.11"
+ "truffle@5.1.66"
+ "truffle-contract@4.0.31"
+ "@truffle/hdwallet-provider@^1.2.3"
+ "web3@1.2.8"
+)
+
+# Ganache settings
+ganache_eth_balance="1000000"
+ganache_gas_limit="12450000"
+ganache_mnemonic="jungle neck govern chief unaware rubber frequent tissue service license alcohol velvet"
+ganache_port="8545"
+
+
+##
+# Helpers
+##
+
+
+# Clean up
+cleanup() {
+
+ # Remove RP repo
+ if [ -d "$rp_tmp_path" ]; then
+ rm -rf "$rp_tmp_path"
+ fi
+
+ # Kill ganache instance
+ if [ -n "$ganache_pid" ] && ps -p "$ganache_pid" > /dev/null; then
+ kill -9 "$ganache_pid"
+ fi
+
+}
+
+# Clone Rocket Pool repo
+clone_rp() {
+ rp_tmp_path="$(mktemp -d)"
+ rp_path="$rp_tmp_path/rocketpool"
+ git clone "$rp_repo_url" -b "$rp_repo_branch" "$rp_path"
+}
+
+# Install Rocket Pool dependencies
+install_rp_deps() {
+ cd "$rp_path"
+ rm package.json package-lock.json
+ npm install "${rp_dependencies[@]}"
+ cd - > /dev/null
+}
+
+# Start ganache-cli instance
+start_ganache() {
+ cd "$rp_path"
+ node_modules/.bin/ganache-cli -e "$ganache_eth_balance" -l "$ganache_gas_limit" -m "$ganache_mnemonic" -p "$ganache_port" > /dev/null &
+ ganache_pid=$!
+ cd - > /dev/null
+}
+
+# Migrate Rocket Pool contracts
+migrate_rp() {
+ cd "$rp_path"
+ node_modules/.bin/truffle migrate --network localhost
+ cd - > /dev/null
+}
+
+# Run tests
+run_tests() {
+ go clean -testcache
+ go test -p 1 ./...
+}
+
+
+##
+# Run
+##
+
+
+# Clean up before exiting
+trap cleanup EXIT
+
+# Clone RP repo
+echo ""
+echo "Cloning main Rocket Pool repository..."
+echo ""
+clone_rp
+
+# Install RP deps
+echo ""
+echo "Installing Rocket Pool dependencies..."
+echo ""
+install_rp_deps
+
+# Start ganache
+echo ""
+echo "Starting ganache-cli process..."
+echo ""
+start_ganache
+
+# Migrate RP contracts
+echo ""
+echo "Migrating Rocket Pool contracts..."
+echo ""
+migrate_rp
+
+# Run tests
+echo ""
+echo "Running tests..."
+echo ""
+run_tests
+
diff --git a/bindings/tests/auction/auction_test.go b/bindings/tests/auction/auction_test.go
new file mode 100644
index 000000000..9f8940d85
--- /dev/null
+++ b/bindings/tests/auction/auction_test.go
@@ -0,0 +1,333 @@
+package auction
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+
+ "github.com/rocket-pool/rocketpool-go/auction"
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ auctionutils "github.com/rocket-pool/rocketpool-go/tests/testutils/auction"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestAuctionDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Disable min commission rate for unbonded pools
+ if _, err := trustednode.BootstrapMinipoolUnbondedMinFee(rp, uint64(0), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial RPL balances
+ totalBalance1, err := auction.GetTotalRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ allottedBalance1, err := auction.GetAllottedRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ remainingBalance1, err := auction.GetRemainingRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if totalBalance1.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial auction contract total RPL balance %s", totalBalance1.String())
+ }
+ if allottedBalance1.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial auction contract allotted RPL balance %s", allottedBalance1.String())
+ }
+ if remainingBalance1.Cmp(totalBalance1) != 0 {
+ t.Errorf("Incorrect initial auction contract remaining RPL balance %s", remainingBalance1.String())
+ }
+
+ // Mint slashed RPL to auction contract
+ if err := auctionutils.CreateSlashedRPL(t, rp, ownerAccount, trustedNodeAccount1, trustedNodeAccount2, userAccount1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated RPL balances
+ totalBalance2, err := auction.GetTotalRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ allottedBalance2, err := auction.GetAllottedRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ remainingBalance2, err := auction.GetRemainingRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if totalBalance2.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated auction contract total RPL balance 1 %s", totalBalance2.String())
+ }
+ if allottedBalance2.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated auction contract allotted RPL balance 1 %s", allottedBalance2.String())
+ }
+ if remainingBalance2.Cmp(totalBalance2) != 0 {
+ t.Errorf("Incorrect updated auction contract remaining RPL balance 1 %s", remainingBalance2.String())
+ }
+
+ // Create a new lot
+ if _, _, err := auction.CreateLot(rp, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated RPL balances
+ totalBalance3, err := auction.GetTotalRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ allottedBalance3, err := auction.GetAllottedRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ remainingBalance3, err := auction.GetRemainingRPLBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var expectedRemainingBalance big.Int
+ expectedRemainingBalance.Sub(totalBalance3, allottedBalance3)
+ if allottedBalance3.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated auction contract allotted RPL balance 2 %s", allottedBalance3.String())
+ }
+ if remainingBalance3.Cmp(&expectedRemainingBalance) != 0 {
+ t.Errorf("Incorrect updated auction contract remaining RPL balance 2 %s", remainingBalance3.String())
+ }
+
+}
+
+func TestLotDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Disable min commission rate for unbonded pools
+ if _, err := trustednode.BootstrapMinipoolUnbondedMinFee(rp, uint64(0), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set network parameters
+ if _, err := network.SubmitPrices(rp, 1, eth.EthToWei(1), eth.EthToWei(24), trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := network.SubmitPrices(rp, 1, eth.EthToWei(1), eth.EthToWei(24), trustedNodeAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := protocol.BootstrapLotStartingPriceRatio(rp, 1.0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := protocol.BootstrapLotReservePriceRatio(rp, 0.5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := protocol.BootstrapLotMaximumEthValue(rp, eth.EthToWei(10), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := protocol.BootstrapLotDuration(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint slashed RPL to auction contract
+ if err := auctionutils.CreateSlashedRPL(t, rp, ownerAccount, trustedNodeAccount1, trustedNodeAccount2, userAccount1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial lot details
+ if lots, err := auction.GetLots(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(lots) != 0 {
+ t.Error("Incorrect initial lot count")
+ }
+ if lots, err := auction.GetLotsWithBids(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(lots) != 0 {
+ t.Error("Incorrect initial lot count")
+ }
+
+ // Create lots
+ lot1Index, _, err := auction.CreateLot(rp, userAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ lot2Index, _, err := auction.CreateLot(rp, userAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Place bid on lot 1
+ bidAmount := eth.EthToWei(1)
+ bid1Opts := userAccount1.GetTransactor()
+ bid1Opts.Value = bidAmount
+ if _, err := auction.PlaceBid(rp, lot1Index, bid1Opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Place another bid on lot 1 to clear it
+ bid2Opts := userAccount2.GetTransactor()
+ bid2Opts.Value = eth.EthToWei(1000)
+ if _, err := auction.PlaceBid(rp, lot1Index, bid2Opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mine blocks until lot 2 hits reserve price & recover unclaimed RPL from it
+ if err := evm.MineBlocks(5); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := auction.RecoverUnclaimedRPL(rp, lot2Index, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated lot details
+ if lots, err := auction.GetLots(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(lots) != 2 {
+ t.Error("Incorrect updated lot count")
+ } else if lots[0].Index != lot1Index || lots[1].Index != lot2Index {
+ t.Error("Incorrect lot indexes")
+ }
+ if lots, err := auction.GetLotsWithBids(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(lots) != 2 {
+ t.Error("Incorrect updated lot count")
+ } else {
+ lot1 := lots[0]
+ lot2 := lots[1]
+
+ // Lot 1
+ if lot1.Index != lot1Index {
+ t.Errorf("Incorrect lot index %d", lot1.Index)
+ }
+ if !lot1.Exists {
+ t.Error("Incorrect lot exists status")
+ }
+ if lot1.StartBlock == 0 {
+ t.Errorf("Incorrect lot start block %d", lot1.StartBlock)
+ }
+ if lot1.EndBlock <= lot1.StartBlock {
+ t.Errorf("Incorrect lot end block %d", lot1.EndBlock)
+ }
+ if lot1.StartPrice.Cmp(eth.EthToWei(1)) != 0 {
+ t.Errorf("Incorrect lot start price %s", lot1.StartPrice.String())
+ }
+ if lot1.ReservePrice.Cmp(eth.EthToWei(0.5)) != 0 {
+ t.Errorf("Incorrect lot reserve price %s", lot1.ReservePrice.String())
+ }
+ if lot1.PriceAtCurrentBlock.Cmp(lot1.StartPrice) == 1 || lot1.PriceAtCurrentBlock.Cmp(lot1.ReservePrice) == -1 {
+ t.Errorf("Incorrect lot price at current block %s", lot1.PriceAtCurrentBlock.String())
+ }
+ if lot1.PriceByTotalBids.Cmp(lot1.StartPrice) == 1 || lot1.PriceByTotalBids.Cmp(lot1.ReservePrice) == -1 {
+ t.Errorf("Incorrect lot price at current block %s", lot1.PriceByTotalBids.String())
+ }
+ if lot1.CurrentPrice.Cmp(lot1.StartPrice) == 1 || lot1.CurrentPrice.Cmp(lot1.ReservePrice) == -1 {
+ t.Errorf("Incorrect lot price at current block %s", lot1.CurrentPrice.String())
+ }
+ if lot1.TotalRPLAmount.Cmp(eth.EthToWei(10)) != 0 {
+ t.Errorf("Incorrect lot total RPL amount %s", lot1.TotalRPLAmount.String())
+ }
+ if lot1.ClaimedRPLAmount.Cmp(eth.EthToWei(10)) != 0 {
+ t.Errorf("Incorrect lot claimed RPL amount %s", lot1.ClaimedRPLAmount.String())
+ }
+ if lot1.RemainingRPLAmount.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect lot remaining RPL amount %s", lot1.RemainingRPLAmount.String())
+ }
+ if lot1.TotalBidAmount.Cmp(bidAmount) != 1 {
+ t.Errorf("Incorrect lot total bid amount %s", lot1.TotalBidAmount.String())
+ }
+ if lot1.AddressBidAmount.Cmp(bidAmount) != 0 {
+ t.Errorf("Incorrect lot address bid amount %s", lot1.AddressBidAmount.String())
+ }
+ if !lot1.Cleared {
+ t.Error("Incorrect lot cleared status")
+ }
+ if lot1.RPLRecovered {
+ t.Error("Incorrect lot RPL recovered status")
+ }
+
+ // Lot 1 prices at blocks
+ if priceAtBlock, err := auction.GetLotPriceAtBlock(rp, lot1Index, 0, nil); err != nil {
+ t.Error(err)
+ } else if priceAtBlock.Cmp(lot1.StartPrice) != 0 {
+ t.Errorf("Incorrect lot price at block 1 %s", priceAtBlock.String())
+ }
+ if priceAtBlock, err := auction.GetLotPriceAtBlock(rp, lot1Index, 1000000, nil); err != nil {
+ t.Error(err)
+ } else if priceAtBlock.Cmp(lot1.ReservePrice) != 0 {
+ t.Errorf("Incorrect lot price at block 2 %s", priceAtBlock.String())
+ }
+
+ // Lot 2
+ if lot2.Index != lot2Index {
+ t.Errorf("Incorrect lot index %d", lot2.Index)
+ }
+ if !lot2.RPLRecovered {
+ t.Error("Incorrect lot RPL recovered status")
+ }
+
+ }
+
+ // Get & check initial bidder RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial bidder RPL balance %s", rplBalance.String())
+ }
+
+ // Claim bid on lot 1
+ if _, err := auction.ClaimBid(rp, lot1Index, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated bidder RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated bidder RPL balance %s", rplBalance.String())
+ }
+
+}
diff --git a/bindings/tests/auction/main_test.go b/bindings/tests/auction/main_test.go
new file mode 100644
index 000000000..4b0e624b5
--- /dev/null
+++ b/bindings/tests/auction/main_test.go
@@ -0,0 +1,77 @@
+package auction
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/utils"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount1 *accounts.Account
+ trustedNodeAccount2 *accounts.Account
+ trustedNodeAccount3 *accounts.Account
+ userAccount1 *accounts.Account
+ userAccount2 *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount1, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount2, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount3, err = accounts.GetAccount(3)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount1, err = accounts.GetAccount(8)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount2, err = accounts.GetAccount(9)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Do the bootstrap settings
+ utils.Stage4Bootstrap(rp, ownerAccount)
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/config.go b/bindings/tests/config.go
new file mode 100644
index 000000000..38b17b950
--- /dev/null
+++ b/bindings/tests/config.go
@@ -0,0 +1,31 @@
+package tests
+
+// Contract addresses and account private keys are based on the following mnemonic:
+// jungle neck govern chief unaware rubber frequent tissue service license alcohol velvet
+
+const (
+ Eth1ProviderAddress = "http://127.0.0.1:8545"
+ RocketStorageAddress = "0x70a5F2eB9e4C003B105399b471DAeDbC8d00B1c5"
+)
+
+const (
+ ValidatorPubkey = "968bcf4081af4a10d054c1cde1dadfd6e85a120a397174173ca869f66bdc72835f9918ea251930778e5ba67a7907e30e"
+ ValidatorPubkey2 = "968bcf4081af4a10d054c1cde1dadfd6e85a120a397174173ca869f66bdc72835f9918ea251930778e5ba67a7907e30d"
+ ValidatorPubkey3 = "968bcf4081af4a10d054c1cde1dadfd6e85a120a397174173ca869f66bdc72835f9918ea251930778e5ba67a7907e30c"
+ ValidatorSignature = "83757098b3b118c67d993218afb69e80a13eb3b174cd3da9958971f05e6b30b9ff5a55677d644f972b31c24e0544604703e8cf18b109fde1e0d3cde0446147bf2f38f02fefce604e4119a605348dfc8a99935dbd65a64eb773c77508f9150e33"
+ ValidatorSignature2 = "83757098b3b118c67d993218afb69e80a13eb3b174cd3da9958971f05e6b30b9ff5a55677d644f972b31c24e0544604703e8cf18b109fde1e0d3cde0446147bf2f38f02fefce604e4119a605348dfc8a99935dbd65a64eb773c77508f9150e34"
+ ValidatorSignature3 = "83757098b3b118c67d993218afb69e80a13eb3b174cd3da9958971f05e6b30b9ff5a55677d644f972b31c24e0544604703e8cf18b109fde1e0d3cde0446147bf2f38f02fefce604e4119a605348dfc8a99935dbd65a64eb773c77508f9150e35"
+)
+
+var AccountPrivateKeys = []string{
+ "c6d2ac9b00bd599c4ce9d3a69c91e496eb9e79781d9dc84c79bafa7618f45f37",
+ "025515b79bbe5edf008112d19a14457e6bea72dc4660667eeb2c3225c8285618",
+ "02984e048155b5a3b80162a2041e096c3f99b9b4324bc7ff3e56e96d37f1500b",
+ "5894075a2b08d7585fd4b354914326da5c9b05f92a737b8789f127ba7a21f939",
+ "5a18d98ff88545ab82044b31ace49ad252056b89445913dc6a5653eca58c438a",
+ "ea8a7f5637ca1ae8ee6783850af1c0c57cdc5e66d1dcb92fd636908ad9b4cc04",
+ "836915de8841cd4e3a24b80c9c33e59be8db8ab3daf32d5edce56597b905bbf0",
+ "759b3437ff0fd1af70a5a367ac281c73f6dca2e17a4650a7f939fb50ad15f6cd",
+ "dde1c7fcfe3fa4c5e824e2e0cf5d8cef98692cde611b070d054045c2826aecb4",
+ "418bb76e4af529837d39f4812201c6e4b9b3d5d521f66047b6f34a6d7bc0c811",
+}
diff --git a/bindings/tests/dao/main_test.go b/bindings/tests/dao/main_test.go
new file mode 100644
index 000000000..4b8ec2495
--- /dev/null
+++ b/bindings/tests/dao/main_test.go
@@ -0,0 +1,72 @@
+package dao
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/utils"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount1 *accounts.Account
+ trustedNodeAccount2 *accounts.Account
+ trustedNodeAccount3 *accounts.Account
+ nodeAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount1, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount2, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount3, err = accounts.GetAccount(3)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(4)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Do the bootstrap settings
+ utils.Stage4Bootstrap(rp, ownerAccount)
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/dao/proposals_test.go b/bindings/tests/dao/proposals_test.go
new file mode 100644
index 000000000..31fdbe601
--- /dev/null
+++ b/bindings/tests/dao/proposals_test.go
@@ -0,0 +1,209 @@
+package dao
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/dao"
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/node"
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestProposalDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // The DAO to check for proposals under
+ proposalDaoName := "rocketDAONodeTrustedProposals"
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodesettings.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial proposal details
+ if proposals, err := dao.GetProposals(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(proposals) != 0 {
+ t.Error("Incorrect initial proposal count")
+ }
+ if proposals, err := dao.GetProposalsWithMember(rp, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(proposals) != 0 {
+ t.Error("Incorrect initial proposal count")
+ }
+ if daoProposals, err := dao.GetDAOProposals(rp, proposalDaoName, nil); err != nil {
+ t.Error(err)
+ } else if len(daoProposals) != 0 {
+ t.Error("Incorrect initial DAO proposal count")
+ }
+ if daoProposals, err := dao.GetDAOProposalsWithMember(rp, proposalDaoName, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(daoProposals) != 0 {
+ t.Error("Incorrect initial DAO proposal count")
+ }
+
+ // Submit invite member proposal
+ proposalMessage := "invite coolguy"
+ proposalMemberAddress := nodeAccount.Address
+ proposalMemberId := "coolguy"
+ proposalMemberEmail := "coolguy@rocketpool.net"
+ proposalId, _, err := trustednodedao.ProposeInviteMember(rp, proposalMessage, proposalMemberAddress, proposalMemberId, proposalMemberEmail, trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Increase time until proposal voting delay has passed
+ voteDelayTime, err := trustednodesettings.GetProposalVoteDelayTime(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := evm.IncreaseTime(int(voteDelayTime)); err != nil {
+ t.Fatal(err)
+ }
+
+ // Vote on & execute proposal
+ if _, err := trustednodedao.VoteOnProposal(rp, proposalId, true, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.VoteOnProposal(rp, proposalId, true, trustedNodeAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.ExecuteProposal(rp, proposalId, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit invite member proposal & cancel it
+ cancelledProposalId, _, err := trustednodedao.ProposeInviteMember(rp, "cancel this", nodeAccount.Address, "cancel", "cancel@rocketpool.net", trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.CancelProposal(rp, cancelledProposalId, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated proposal details
+ if proposals, err := dao.GetProposals(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(proposals) != 2 {
+ t.Error("Incorrect updated proposal count")
+ } else if proposals[0].ID != proposalId || proposals[1].ID != cancelledProposalId {
+ t.Error("Incorrect proposal indexes")
+ }
+ if proposals, err := dao.GetProposalsWithMember(rp, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(proposals) != 2 {
+ t.Error("Incorrect updated proposal count")
+ } else {
+
+ // Passed proposal
+ proposal := proposals[0]
+ if proposal.ID != proposalId {
+ t.Errorf("Incorrect proposal ID %d", proposal.ID)
+ }
+ if proposal.DAO != proposalDaoName {
+ t.Errorf("Incorrect proposal DAO %s", proposal.DAO)
+ }
+ if !bytes.Equal(proposal.ProposerAddress.Bytes(), trustedNodeAccount1.Address.Bytes()) {
+ t.Errorf("Incorrect proposal proposer address %s", proposal.ProposerAddress.Hex())
+ }
+ if proposal.Message != proposalMessage {
+ t.Errorf("Incorrect proposal message %s", proposal.Message)
+ }
+ if proposal.CreatedTime == 0 {
+ t.Errorf("Incorrect proposal created time %d", proposal.CreatedTime)
+ }
+ if proposal.StartTime <= proposal.CreatedTime {
+ t.Errorf("Incorrect proposal start time %d", proposal.StartTime)
+ }
+ if proposal.EndTime <= proposal.StartTime {
+ t.Errorf("Incorrect proposal end time %d", proposal.EndTime)
+ }
+ if proposal.ExpiryTime <= proposal.EndTime {
+ t.Errorf("Incorrect proposal expiry time %d", proposal.ExpiryTime)
+ }
+ if proposal.VotesRequired == 0.0 {
+ t.Errorf("Incorrect proposal required votes %f", proposal.VotesRequired)
+ }
+ if proposal.VotesFor != 2.0 {
+ t.Errorf("Incorrect proposal votes for %f", proposal.VotesFor)
+ }
+ if proposal.VotesAgainst != 0.0 {
+ t.Errorf("Incorrect proposal votes against %f", proposal.VotesAgainst)
+ }
+ if !proposal.MemberVoted {
+ t.Error("Incorrect proposal member voted status")
+ }
+ if !proposal.MemberSupported {
+ t.Error("Incorrect proposal member supported status")
+ }
+ if proposal.IsCancelled {
+ t.Error("Incorrect proposal cancelled status")
+ }
+ if !proposal.IsExecuted {
+ t.Error("Incorrect proposal executed status")
+ }
+ if proposal.PayloadStr != fmt.Sprintf("proposalInvite(%s,%s,%s)", proposalMemberId, proposalMemberEmail, proposalMemberAddress.Hex()) {
+ t.Errorf("Incorrect proposal payload string %s", proposal.PayloadStr)
+ }
+ if proposal.State != rptypes.Executed {
+ t.Errorf("Incorrect proposal state %s", proposal.State.String())
+ }
+
+ // Cancelled proposal
+ cancelledProposal := proposals[1]
+ if cancelledProposal.ID != cancelledProposalId {
+ t.Errorf("Incorrect cancelled proposal ID %d", cancelledProposal.ID)
+ }
+ if !cancelledProposal.IsCancelled {
+ t.Error("Incorrect cancelled proposal cancelled status")
+ }
+
+ }
+ if daoProposals, err := dao.GetDAOProposals(rp, proposalDaoName, nil); err != nil {
+ t.Error(err)
+ } else if len(daoProposals) != 2 {
+ t.Error("Incorrect updated DAO proposal count")
+ } else if daoProposals[0].ID != proposalId || daoProposals[1].ID != cancelledProposalId {
+ t.Error("Incorrect DAO proposal indexes")
+ }
+ if daoProposals, err := dao.GetDAOProposalsWithMember(rp, proposalDaoName, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(daoProposals) != 2 {
+ t.Error("Incorrect updated DAO proposal count")
+ } else if daoProposals[0].ID != proposalId || daoProposals[1].ID != cancelledProposalId {
+ t.Error("Incorrect DAO proposal indexes")
+ }
+
+}
diff --git a/bindings/tests/dao/trustednode/dao_test.go b/bindings/tests/dao/trustednode/dao_test.go
new file mode 100644
index 000000000..752914252
--- /dev/null
+++ b/bindings/tests/dao/trustednode/dao_test.go
@@ -0,0 +1,187 @@
+package trustednode
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/node"
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestMemberDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Disable min commission rate for unbonded pools
+ if _, err := trustednodesettings.BootstrapMinipoolUnbondedMinFee(rp, uint64(0), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check minimum member count
+ if minMemberCount, err := trustednodedao.GetMinimumMemberCount(rp, nil); err != nil {
+ t.Error(err)
+ } else if minMemberCount == 0 {
+ t.Error("Incorrect trusted node DAO minimum member count")
+ }
+
+ // Get & check initial member details
+ if members, err := trustednodedao.GetMembers(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(members) != 0 {
+ t.Error("Incorrect initial trusted node DAO member count")
+ }
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", trustedNodeAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", trustedNodeAccount3.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Bootstrap trusted node DAO member
+ memberId := "coolguy"
+ memberEmail := "coolguy@rocketpool.net"
+ if _, err := trustednodedao.BootstrapMember(rp, memberId, memberEmail, trustedNodeAccount1.Address, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.BootstrapMember(rp, memberId, memberEmail, trustedNodeAccount2.Address, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.BootstrapMember(rp, memberId, memberEmail, trustedNodeAccount3.Address, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get RPL bond amount
+ rplBondAmount, err := trustednodesettings.GetRPLBond(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint trusted node RPL bond & join trusted node DAO
+ if err := nodeutils.MintTrustedNodeBond(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.MintTrustedNodeBond(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.MintTrustedNodeBond(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.Join(rp, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.Join(rp, trustedNodeAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.Join(rp, trustedNodeAccount3.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit a proposal
+ if _, _, err := trustednodedao.ProposeMemberLeave(rp, "bye", trustedNodeAccount1.Address, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create an unbonded minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, trustedNodeAccount1, eth.EthToWei(16), 1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated member details
+ if members, err := trustednodedao.GetMembers(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(members) != 3 {
+ t.Error("Incorrect updated trusted node DAO member count")
+ } else {
+ member := members[0]
+ if !bytes.Equal(member.Address.Bytes(), trustedNodeAccount1.Address.Bytes()) {
+ t.Errorf("Incorrect member address %s", member.Address.Hex())
+ }
+ if !member.Exists {
+ t.Error("Incorrect member exists status")
+ }
+ if member.ID != memberId {
+ t.Errorf("Incorrect member ID %s", member.ID)
+ }
+ if member.Url != memberEmail {
+ t.Errorf("Incorrect member email %s", member.Url)
+ }
+ if member.JoinedTime == 0 {
+ t.Errorf("Incorrect member joined time %d", member.JoinedTime)
+ }
+ if member.LastProposalTime == 0 {
+ t.Errorf("Incorrect member last proposal time %d", member.LastProposalTime)
+ }
+ if member.RPLBondAmount.Cmp(rplBondAmount) != 0 {
+ t.Errorf("Incorrect member RPL bond amount %s", member.RPLBondAmount.String())
+ }
+ /* TEMPORARILY DISABLED
+ if member.UnbondedValidatorCount != 1 {
+ t.Errorf("Incorrect member unbonded validator count %d", member.UnbondedValidatorCount)
+ }
+ */
+ }
+
+}
+
+func TestUpgradeContract(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Upgrade contract
+ contractName := "rocketDepositPool"
+ contractNewAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ contractNewAbi := "[{\"name\":\"foo\",\"type\":\"function\",\"inputs\":[],\"outputs\":[]}]"
+ if _, err := trustednodedao.BootstrapUpgrade(rp, "upgradeContract", contractName, contractNewAbi, contractNewAddress, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated contract details
+ if contractAddress, err := rp.GetAddress(contractName); err != nil {
+ t.Error(err)
+ } else if !bytes.Equal(contractAddress.Bytes(), contractNewAddress.Bytes()) {
+ t.Errorf("Incorrect updated contract address %s", contractAddress.Hex())
+ }
+ if contractAbi, err := rp.GetABI(contractName); err != nil {
+ t.Error(err)
+ } else if _, ok := contractAbi.Methods["foo"]; !ok {
+ t.Errorf("Incorrect updated contract ABI")
+ }
+
+}
diff --git a/bindings/tests/dao/trustednode/main_test.go b/bindings/tests/dao/trustednode/main_test.go
new file mode 100644
index 000000000..724154754
--- /dev/null
+++ b/bindings/tests/dao/trustednode/main_test.go
@@ -0,0 +1,73 @@
+package trustednode
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount1 *accounts.Account
+ trustedNodeAccount2 *accounts.Account
+ trustedNodeAccount3 *accounts.Account
+ trustedNodeAccount4 *accounts.Account
+ nodeAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount1, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount2, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount3, err = accounts.GetAccount(3)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount4, err = accounts.GetAccount(4)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(5)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/dao/trustednode/proposals_test.go b/bindings/tests/dao/trustednode/proposals_test.go
new file mode 100644
index 000000000..29da3e732
--- /dev/null
+++ b/bindings/tests/dao/trustednode/proposals_test.go
@@ -0,0 +1,321 @@
+package trustednode
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/dao"
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ daoutils "github.com/rocket-pool/rocketpool-go/tests/testutils/dao"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestProposeInviteMember(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodesettings.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit, pass & execute invite member proposal
+ proposalMemberAddress := nodeAccount.Address
+ proposalMemberId := "coolguy"
+ proposalMemberEmail := "coolguy@rocketpool.net"
+ proposalId, _, err := trustednodedao.ProposeInviteMember(rp, "invite coolguy", proposalMemberAddress, proposalMemberId, proposalMemberEmail, trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if exists {
+ t.Error("Incorrect initial member exists status")
+ }
+
+ // Mint trusted node RPL bond & join trusted node DAO
+ if err := nodeutils.MintTrustedNodeBond(rp, ownerAccount, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodedao.Join(rp, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Incorrect updated member exists status")
+ }
+
+ // Get & check proposal payload string
+ if payloadStr, err := dao.GetProposalPayloadStr(rp, proposalId, nil); err != nil {
+ t.Error(err)
+ } else if payloadStr != fmt.Sprintf("proposalInvite(%s,%s,%s)", proposalMemberId, proposalMemberEmail, proposalMemberAddress.Hex()) {
+ t.Errorf("Incorrect proposal payload string %s", payloadStr)
+ }
+
+ // Get & check member invite executed block
+ if inviteExecutedTime, err := trustednodedao.GetMemberInviteProposalExecutedTime(rp, proposalMemberAddress, nil); err != nil {
+ t.Error(err)
+ } else if inviteExecutedTime == 0 {
+ t.Errorf("Incorrect member invite proposal executed time %d", inviteExecutedTime)
+ }
+
+}
+
+func TestProposeMemberLeave(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodesettings.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register nodes
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount4); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit, pass & execute member leave proposal
+ proposalMemberAddress := trustedNodeAccount1.Address
+ proposalId, _, err := trustednodedao.ProposeMemberLeave(rp, "node 1 leave", proposalMemberAddress, trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{
+ trustedNodeAccount1,
+ trustedNodeAccount2,
+ trustedNodeAccount3,
+ trustedNodeAccount4,
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check member leave executed time
+ if leaveExecutedTime, err := trustednodedao.GetMemberLeaveProposalExecutedTime(rp, proposalMemberAddress, nil); err != nil {
+ t.Error(err)
+ } else if leaveExecutedTime == 0 {
+ t.Errorf("Incorrect member leave proposal executed time %d", leaveExecutedTime)
+ }
+
+ // Get & check initial member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Incorrect initial member exists status")
+ }
+
+ // Leave trusted node DAO
+ if _, err := trustednodedao.Leave(rp, trustedNodeAccount1.Address, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, trustedNodeAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if exists {
+ t.Error("Incorrect updated member exists status")
+ }
+
+ // Get & check proposal payload string
+ if payloadStr, err := dao.GetProposalPayloadStr(rp, proposalId, nil); err != nil {
+ t.Error(err)
+ } else if payloadStr != fmt.Sprintf("proposalLeave(%s)", proposalMemberAddress.Hex()) {
+ t.Errorf("Incorrect proposal payload string %s", payloadStr)
+ }
+
+}
+
+func TestProposeKickMember(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodesettings.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register nodes
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, trustedNodeAccount2.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Incorrect initial member exists status")
+ }
+
+ // Submit, pass & execute kick member proposal
+ proposalMemberAddress := trustedNodeAccount2.Address
+ proposalFineAmount := eth.EthToWei(1000)
+ proposalId, _, err := trustednodedao.ProposeKickMember(rp, "kick node 2", proposalMemberAddress, proposalFineAmount, trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated member exists status
+ if exists, err := trustednodedao.GetMemberExists(rp, trustedNodeAccount2.Address, nil); err != nil {
+ t.Error(err)
+ } else if exists {
+ t.Error("Incorrect updated member exists status")
+ }
+
+ // Get & check proposal payload string
+ if payloadStr, err := dao.GetProposalPayloadStr(rp, proposalId, nil); err != nil {
+ t.Error(err)
+ } else if payloadStr != fmt.Sprintf("proposalKick(%s,%s)", proposalMemberAddress.Hex(), proposalFineAmount.String()) {
+ t.Errorf("Incorrect proposal payload string %s", payloadStr)
+ }
+
+}
+
+func TestProposeUpgradeContract(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednodesettings.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednodesettings.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit, pass & execute upgrade contract proposal
+ proposalUpgradeType := "upgradeContract"
+ proposalContractName := "rocketDepositPool"
+ proposalContractAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ proposalContractAbi := "[{\"name\":\"foo\",\"type\":\"function\",\"inputs\":[],\"outputs\":[]}]"
+ proposalId, _, err := trustednodedao.ProposeUpgradeContract(rp, "upgrade rocketDepositPool", proposalUpgradeType, proposalContractName, proposalContractAbi, proposalContractAddress, trustedNodeAccount1.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated contract details
+ if contractAddress, err := rp.GetAddress(proposalContractName); err != nil {
+ t.Error(err)
+ } else if !bytes.Equal(contractAddress.Bytes(), proposalContractAddress.Bytes()) {
+ t.Errorf("Incorrect updated contract address %s", contractAddress.Hex())
+ }
+ if contractAbi, err := rp.GetABI(proposalContractName); err != nil {
+ t.Error(err)
+ } else if _, ok := contractAbi.Methods["foo"]; !ok {
+ t.Errorf("Incorrect updated contract ABI")
+ }
+
+ // Get & check proposal payload string
+ if payloadStr, err := dao.GetProposalPayloadStr(rp, proposalId, nil); err != nil {
+ t.Error(err)
+ } else if encodedAbi, err := rocketpool.EncodeAbiStr(proposalContractAbi); err != nil {
+ t.Error(err)
+ } else if payloadStr != fmt.Sprintf("proposalUpgrade(%s,%s,%s,%s)", proposalUpgradeType, proposalContractName, encodedAbi, proposalContractAddress.Hex()) {
+ t.Errorf("Incorrect proposal payload string %s", payloadStr)
+ }
+
+}
diff --git a/bindings/tests/deposit/deposit_test.go b/bindings/tests/deposit/deposit_test.go
new file mode 100644
index 000000000..ad2362ebb
--- /dev/null
+++ b/bindings/tests/deposit/deposit_test.go
@@ -0,0 +1,106 @@
+package deposit
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+)
+
+func TestDeposit(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Make deposit
+ opts := userAccount.GetTransactor()
+ opts.Value = eth.EthToWei(10)
+ if _, err := deposit.Deposit(rp, opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check deposit pool balance
+ if balance, err := deposit.GetBalance(rp, nil); err != nil {
+ t.Error(err)
+ } else if balance.Cmp(opts.Value) != 0 {
+ t.Error("Incorrect deposit pool balance")
+ }
+
+ // Get & check deposit pool excess balance
+ if excessBalance, err := deposit.GetExcessBalance(rp, nil); err != nil {
+ t.Error(err)
+ } else if excessBalance.Cmp(opts.Value) != 0 {
+ t.Error("Incorrect deposit pool excess balance")
+ }
+
+}
+
+func TestAssignDeposits(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Disable deposit assignments
+ if _, err := protocol.BootstrapAssignDepositsEnabled(rp, false, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ userDepositOpts := userAccount.GetTransactor()
+ userDepositOpts.Value = eth.EthToWei(32)
+ if _, err := deposit.Deposit(rp, userDepositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register node & create minipool
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Re-enable deposit assignments
+ if _, err := protocol.BootstrapAssignDepositsEnabled(rp, true, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial deposit pool balance
+ balance1, err := deposit.GetBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Assign deposits
+ if _, err := deposit.AssignDeposits(rp, userAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated deposit pool balance
+ balance2, err := deposit.GetBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ } else if balance2.Cmp(balance1) != -1 {
+ t.Error("Deposit pool balance did not decrease after assigning deposits")
+ }
+
+}
diff --git a/bindings/tests/deposit/main_test.go b/bindings/tests/deposit/main_test.go
new file mode 100644
index 000000000..20dd37c8d
--- /dev/null
+++ b/bindings/tests/deposit/main_test.go
@@ -0,0 +1,58 @@
+package deposit
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ nodeAccount *accounts.Account
+ userAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount, err = accounts.GetAccount(9)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/minipool/contract_test.go b/bindings/tests/minipool/contract_test.go
new file mode 100644
index 000000000..679354d7e
--- /dev/null
+++ b/bindings/tests/minipool/contract_test.go
@@ -0,0 +1,757 @@
+package minipool
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/utils"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/validator"
+)
+
+func TestDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get current network node fee
+ networkNodeFee, err := network.GetNodeFee(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ depositOpts := userAccount.GetTransactor()
+ depositOpts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, depositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set minipool withdrawable status
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check minipool details
+ if status, err := mp.GetStatusDetails(nil); err != nil {
+ t.Error(err)
+ } else {
+ if status.Status != rptypes.Withdrawable {
+ t.Errorf("Incorrect minipool status %s", status.Status.String())
+ }
+ if status.StatusBlock == 0 {
+ t.Errorf("Incorrect minipool status block %d", status.StatusBlock)
+ }
+ if status.StatusTime.Unix() == 0 {
+ t.Errorf("Incorrect minipool status time %v", status.StatusTime)
+ }
+ }
+ if depositType, err := mp.GetDepositType(nil); err != nil {
+ t.Error(err)
+ } else if depositType != rptypes.Full {
+ t.Errorf("Incorrect minipool deposit type %s", depositType.String())
+ }
+ if node, err := mp.GetNodeDetails(nil); err != nil {
+ t.Error(err)
+ } else {
+ if !bytes.Equal(node.Address.Bytes(), nodeAccount.Address.Bytes()) {
+ t.Errorf("Incorrect minipool node address %s", node.Address.Hex())
+ }
+ if node.Fee != networkNodeFee {
+ t.Errorf("Incorrect minipool node fee %f", node.Fee)
+ }
+ if node.DepositBalance.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect minipool node deposit balance %s", node.DepositBalance.String())
+ }
+ if node.RefundBalance.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect minipool node refund balance %s", node.RefundBalance.String())
+ }
+ if !node.DepositAssigned {
+ t.Error("Incorrect minipool node deposit assigned status")
+ }
+ }
+ if user, err := mp.GetUserDetails(nil); err != nil {
+ t.Error(err)
+ } else {
+ if user.DepositBalance.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect minipool user deposit balance %s", user.DepositBalance.String())
+ }
+ if !user.DepositAssigned {
+ t.Error("Incorrect minipool user deposit assigned status")
+ }
+ if user.DepositAssignedTime.Unix() == 0 {
+ t.Errorf("Incorrect minipool user deposit assigned time %v", user.DepositAssignedTime)
+ }
+ }
+ if withdrawalCredentials, err := minipool.GetMinipoolWithdrawalCredentials(rp, mp.Address, nil); err != nil {
+ t.Error(err)
+ } else {
+ withdrawalPrefix := byte(1)
+ padding := make([]byte, 11)
+ expectedWithdrawalCredentials := bytes.Join([][]byte{{withdrawalPrefix}, padding, mp.Address.Bytes()}, []byte{})
+ if !bytes.Equal(withdrawalCredentials.Bytes(), expectedWithdrawalCredentials) {
+ t.Errorf("Incorrect minipool withdrawal credentials %s", hex.EncodeToString(withdrawalCredentials.Bytes()))
+ }
+ }
+
+}
+
+func TestRefund(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ depositOpts := userAccount.GetTransactor()
+ depositOpts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, depositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial node refund balance
+ nodeRefundBalance1, err := mp.GetNodeRefundBalance(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Refund
+ if _, err := mp.Refund(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node refund balance
+ nodeRefundBalance2, err := mp.GetNodeRefundBalance(nil)
+ if err != nil {
+ t.Fatal(err)
+ } else if nodeRefundBalance2.Cmp(nodeRefundBalance1) != -1 {
+ t.Error("Node refund balance did not decrease after refunding from minipool")
+ }
+
+}
+
+func TestStake(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get validator & deposit data
+ validatorPubkey, err := validator.GetValidatorPubkey(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ withdrawalCredentials, err := minipool.GetMinipoolWithdrawalCredentials(rp, mp.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ validatorSignature, err := validator.GetValidatorSignature(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ depositDataRoot, err := validator.GetDepositDataRoot(validatorPubkey, withdrawalCredentials, validatorSignature)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial minipool status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status != rptypes.Prelaunch {
+ t.Errorf("Incorrect initial minipool status %s", status.String())
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if _, err := mp.Stake(validatorSignature, depositDataRoot, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated minipool status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status != rptypes.Staking {
+ t.Errorf("Incorrect updated minipool status %s", status.String())
+ }
+
+}
+
+func TestDissolve(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial minipool status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status != rptypes.Initialized {
+ t.Errorf("Incorrect initial minipool status %s", status.String())
+ }
+
+ // Dissolve minipool
+ if _, err := mp.Dissolve(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated minipool status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status != rptypes.Dissolved {
+ t.Errorf("Incorrect updated minipool status %s", status.String())
+ }
+
+}
+
+func TestClose(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Dissolve minipool
+ if _, err := mp.Dissolve(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial minipool exists status
+ if exists, err := minipool.GetMinipoolExists(rp, mp.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Incorrect initial minipool exists status")
+ }
+
+ // Simulate a post-merge withdrawal by sending 16 ETH to the minipool
+ opts := nodeAccount.GetTransactor()
+ opts.Value = eth.EthToWei(16)
+ hash, err := eth.SendTransaction(rp.Client, mp.Address, big.NewInt(1337), opts) // Ganache's default chain ID is 1337
+ if err != nil {
+ t.Errorf("Error sending ETH to minipool: %s", err.Error())
+ }
+ utils.WaitForTransaction(rp.Client, hash)
+
+ // Close minipool
+ if _, err := mp.Close(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated minipool exists status
+ if exists, err := minipool.GetMinipoolExists(rp, mp.Address, nil); err != nil {
+ t.Error(err)
+ } else if exists {
+ t.Error("Incorrect updated minipool exists status")
+ }
+
+}
+
+func TestFinalise(t *testing.T) {
+
+ // TODO
+
+}
+
+func TestWithdrawValidatorBalance(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ userDepositAmount := eth.EthToWei(16)
+ userDepositOpts := userAccount.GetTransactor()
+ userDepositOpts.Value = userDepositAmount
+ if _, err := deposit.Deposit(rp, userDepositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set minipool withdrawable status
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial token contract ETH balances
+ rethContractBalance1, err := tokens.GetRETHContractETHBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Withdraw minipool validator balance
+ opts := swcAccount.GetTransactor()
+ opts.Value = eth.EthToWei(32)
+ if _, err := mp.Contract.Transfer(opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get node balances before withdrawal
+ nodeBalance1, err := tokens.GetBalances(rp, nodeAccount.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Call ProcessWithdrawal method
+ if _, err := mp.DistributeBalance(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Call refund method to withdraw node's balance
+ if _, err := mp.Refund(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node ETH balances
+ if nodeBalance2, err := tokens.GetBalances(rp, nodeAccount.Address, nil); err != nil {
+ t.Fatal(err)
+ } else if nodeBalance2.ETH.Cmp(nodeBalance1.ETH) != 1 {
+ t.Error("node ETH balance did not increase after processing withdrawal")
+ }
+
+ // Get & check updated token contract ETH balances
+ if rethContractBalance2, err := tokens.GetRETHContractETHBalance(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethContractBalance2.Cmp(rethContractBalance1) != 1 {
+ t.Error("rETH contract ETH balance did not increase after processing withdrawal")
+ }
+
+ // Get & check rETH collateral amount & rate
+ if rethTotalCollateral, err := tokens.GetRETHTotalCollateral(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethTotalCollateral.Cmp(userDepositAmount) != 0 {
+ t.Errorf("Incorrect rETH total collateral amount %s", rethTotalCollateral.String())
+ }
+ if rethCollateralRate, err := tokens.GetRETHCollateralRate(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethCollateralRate != 1 {
+ t.Errorf("Incorrect rETH collateral rate %f", rethCollateralRate)
+ }
+
+ // Confirm the minipool still exists
+ if exists, err := minipool.GetMinipoolExists(rp, mp.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Minipool no longer exists but it should")
+ }
+
+}
+
+func TestWithdrawValidatorBalanceAndFinalise(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ userDepositAmount := eth.EthToWei(16)
+ userDepositOpts := userAccount.GetTransactor()
+ userDepositOpts.Value = userDepositAmount
+ if _, err := deposit.Deposit(rp, userDepositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set minipool withdrawable status
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial token contract ETH balances
+ rethContractBalance1, err := tokens.GetRETHContractETHBalance(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Withdraw minipool validator balance
+ opts := swcAccount.GetTransactor()
+ opts.Value = eth.EthToWei(32)
+ if _, err := mp.Contract.Transfer(opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get node balances before withdrawal
+ nodeBalance1, err := tokens.GetBalances(rp, nodeAccount.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Call DistributeBalanceAndFinalise method
+ if _, err := mp.DistributeBalanceAndFinalise(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node ETH balances
+ if nodeBalance2, err := tokens.GetBalances(rp, nodeAccount.Address, nil); err != nil {
+ t.Fatal(err)
+ } else if nodeBalance2.ETH.Cmp(nodeBalance1.ETH) != 1 {
+ t.Error("node ETH balance did not increase after processing withdrawal")
+ }
+
+ // Get & check updated token contract ETH balances
+ if rethContractBalance2, err := tokens.GetRETHContractETHBalance(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethContractBalance2.Cmp(rethContractBalance1) != 1 {
+ t.Error("rETH contract ETH balance did not increase after processing withdrawal")
+ }
+
+ // Get & check rETH collateral amount & rate
+ if rethTotalCollateral, err := tokens.GetRETHTotalCollateral(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethTotalCollateral.Cmp(userDepositAmount) != 0 {
+ t.Errorf("Incorrect rETH total collateral amount %s", rethTotalCollateral.String())
+ }
+ if rethCollateralRate, err := tokens.GetRETHCollateralRate(rp, nil); err != nil {
+ t.Fatal(err)
+ } else if rethCollateralRate != 0.1 {
+ t.Errorf("Incorrect rETH collateral rate %f", rethCollateralRate)
+ }
+
+ // Confirm the minipool still exists
+ if exists, err := minipool.GetMinipoolExists(rp, mp.Address, nil); err != nil {
+ t.Error(err)
+ } else if !exists {
+ t.Error("Minipool doesn't exist but it should")
+ }
+
+}
+
+func TestDelegateUpgradeAndRollback(t *testing.T) {
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get original delegate contract
+ originalDelegate, err := mp.GetEffectiveDelegate(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newDelegate := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ newAbi := "[{\"name\":\"foo\",\"type\":\"function\",\"inputs\":[],\"outputs\":[]}]"
+
+ // Upgrade the network delegate contract
+ _, err = trustednodedao.BootstrapUpgrade(rp, "upgradeContract", "rocketMinipoolDelegate", newAbi, newDelegate, ownerAccount.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get new effective delegate
+ effectiveDelegate, err := mp.GetEffectiveDelegate(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check
+ if effectiveDelegate != originalDelegate {
+ t.Errorf("Effective delegate %s did not match original delegate %s", effectiveDelegate.Hex(), originalDelegate.Hex())
+ }
+
+ // Call upgrade
+ if _, err := mp.DelegateUpgrade(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check effective delegate
+ if effectiveDelegate, err = mp.GetEffectiveDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if effectiveDelegate != newDelegate {
+ t.Errorf("Effective delegate %s did not match new delegate %s", effectiveDelegate.Hex(), newDelegate.Hex())
+ }
+
+ // Check previous delegate
+ if previousDelegate, err := mp.GetPreviousDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if previousDelegate != originalDelegate {
+ t.Errorf("Previous delegate %s did not match original delegate %s", previousDelegate.Hex(), originalDelegate.Hex())
+ }
+
+ // Check current delegate
+ if currentDelegate, err := mp.GetDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if currentDelegate != newDelegate {
+ t.Errorf("Current delegate %s did not match new delegate %s", currentDelegate.Hex(), newDelegate.Hex())
+ }
+
+ // Rollback
+ if _, err := mp.DelegateRollback(nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get new effective delegate
+ if effectiveDelegate, err = mp.GetEffectiveDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if effectiveDelegate != originalDelegate {
+ t.Errorf("Effective delegate %s did not match original delegate %s", effectiveDelegate.Hex(), newDelegate.Hex())
+ }
+}
+
+func TestUseLatestDelegate(t *testing.T) {
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // New delegate params
+ newDelegate := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ newAbi := "[{\"name\":\"foo\",\"type\":\"function\",\"inputs\":[],\"outputs\":[]}]"
+
+ // Upgrade the network delegate contract
+ _, err = trustednodedao.BootstrapUpgrade(rp, "upgradeContract", "rocketMinipoolDelegate", newAbi, newDelegate, ownerAccount.GetTransactor())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Set use latest delegate
+ if _, err = mp.SetUseLatestDelegate(true, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get use latest delegate
+ if useLatest, err := mp.GetUseLatestDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if !useLatest {
+ t.Error("GetUseLatestDelegate returned false after being set")
+ }
+
+ // Check effective delegate
+ if effectiveDelegate, err := mp.GetEffectiveDelegate(nil); err != nil {
+ t.Fatal(err)
+ } else if effectiveDelegate != newDelegate {
+ t.Errorf("Effective delegate %s did not match new delegate %s", effectiveDelegate.Hex(), newDelegate.Hex())
+ }
+}
diff --git a/bindings/tests/minipool/main_test.go b/bindings/tests/minipool/main_test.go
new file mode 100644
index 000000000..038ea510e
--- /dev/null
+++ b/bindings/tests/minipool/main_test.go
@@ -0,0 +1,68 @@
+package minipool
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount *accounts.Account
+ nodeAccount *accounts.Account
+ userAccount *accounts.Account
+ swcAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount, err = accounts.GetAccount(8)
+ if err != nil {
+ log.Fatal(err)
+ }
+ swcAccount, err = accounts.GetAccount(9)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/minipool/minipool_test.go b/bindings/tests/minipool/minipool_test.go
new file mode 100644
index 000000000..82b48b9a9
--- /dev/null
+++ b/bindings/tests/minipool/minipool_test.go
@@ -0,0 +1,139 @@
+package minipool
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/types"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/validator"
+)
+
+func TestMinipoolDetails(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial minipool details
+ if minipools, err := minipool.GetMinipools(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(minipools) != 0 {
+ t.Error("Incorrect initial minipool count")
+ }
+ if nodeMinipools, err := minipool.GetNodeMinipools(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(nodeMinipools) != 0 {
+ t.Error("Incorrect initial node minipool count")
+ }
+ if nodeMinipoolPubkeys, err := minipool.GetNodeValidatingMinipoolPubkeys(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(nodeMinipoolPubkeys) != 0 {
+ t.Error("Incorrect initial node minipool pubkeys count")
+ }
+
+ // Minipool deposit/withdrawal amounts
+ minipoolDepositAmount := eth.EthToWei(32)
+
+ // Create & stake minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, minipoolDepositAmount, 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mark minipool as withdrawable
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get minipool validator pubkey
+ validatorPubkey, err := validator.GetValidatorPubkey(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated minipool details
+ if minipools, err := minipool.GetMinipools(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(minipools) != 1 {
+ t.Error("Incorrect updated minipool count")
+ } else {
+ mpDetails := minipools[0]
+ if !bytes.Equal(mpDetails.Address.Bytes(), mp.Address.Bytes()) {
+ t.Errorf("Incorrect minipool address %s", mpDetails.Address.Hex())
+ }
+ if !mpDetails.Exists {
+ t.Error("Incorrect minipool exists status")
+ }
+ if !bytes.Equal(mpDetails.Pubkey.Bytes(), validatorPubkey.Bytes()) {
+ t.Errorf("Incorrect minipool validator pubkey %s", mpDetails.Pubkey.Hex())
+ }
+ }
+ // Check status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else {
+ if status != types.Withdrawable {
+ t.Error("Incorrect minipool withdrawable status")
+ }
+ }
+ if nodeMinipools, err := minipool.GetNodeMinipools(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(nodeMinipools) != 1 {
+ t.Error("Incorrect updated node minipool count")
+ } else if !bytes.Equal(nodeMinipools[0].Address.Bytes(), mp.Address.Bytes()) {
+ t.Errorf("Incorrect node minipool address %s", nodeMinipools[0].Address.Hex())
+ }
+ if nodeMinipoolPubkeys, err := minipool.GetNodeValidatingMinipoolPubkeys(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if len(nodeMinipoolPubkeys) != 1 {
+ t.Error("Incorrect updated node minipool pubkeys count")
+ } else if !bytes.Equal(nodeMinipoolPubkeys[0].Bytes(), validatorPubkey.Bytes()) {
+ t.Errorf("Incorrect node minipool pubkey %s", nodeMinipoolPubkeys[0].Hex())
+ }
+
+ // Get & check minipool address by pubkey
+ if minipoolAddress, err := minipool.GetMinipoolByPubkey(rp, validatorPubkey, nil); err != nil {
+ t.Error(err)
+ } else if !bytes.Equal(minipoolAddress.Bytes(), mp.Address.Bytes()) {
+ t.Errorf("Incorrect minipool address %s for pubkey %s", minipoolAddress.Hex(), validatorPubkey.Hex())
+ }
+
+}
diff --git a/bindings/tests/minipool/queue_test.go b/bindings/tests/minipool/queue_test.go
new file mode 100644
index 000000000..b94f58875
--- /dev/null
+++ b/bindings/tests/minipool/queue_test.go
@@ -0,0 +1,229 @@
+package minipool
+
+import (
+ "testing"
+
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestQueueLengths(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Disable min commission rate for unbonded pools
+ if _, err := trustednodesettings.BootstrapMinipoolUnbondedMinFee(rp, uint64(0), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue lengths
+ if queueLengths, err := minipool.GetQueueLengths(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueLengths.Total != 0 {
+ t.Errorf("Incorrect total queue length 1 %d", queueLengths.Total)
+ }
+ if queueLengths.FullDeposit != 0 {
+ t.Errorf("Incorrect full deposit queue length 1 %d", queueLengths.FullDeposit)
+ }
+ if queueLengths.HalfDeposit != 0 {
+ t.Errorf("Incorrect half deposit queue length 1 %d", queueLengths.HalfDeposit)
+ }
+ if queueLengths.EmptyDeposit != 0 {
+ t.Errorf("Incorrect empty deposit queue length 1 %d", queueLengths.EmptyDeposit)
+ }
+ }
+
+ // Create full deposit minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue lengths
+ if queueLengths, err := minipool.GetQueueLengths(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueLengths.Total != 1 {
+ t.Errorf("Incorrect total queue length 2 %d", queueLengths.Total)
+ }
+ if queueLengths.FullDeposit != 1 {
+ t.Errorf("Incorrect full deposit queue length 2 %d", queueLengths.FullDeposit)
+ }
+ if queueLengths.HalfDeposit != 0 {
+ t.Errorf("Incorrect half deposit queue length 2 %d", queueLengths.HalfDeposit)
+ }
+ if queueLengths.EmptyDeposit != 0 {
+ t.Errorf("Incorrect empty deposit queue length 2 %d", queueLengths.EmptyDeposit)
+ }
+ }
+
+ // Create half deposit minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 2); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue lengths
+ if queueLengths, err := minipool.GetQueueLengths(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueLengths.Total != 2 {
+ t.Errorf("Incorrect total queue length 3 %d", queueLengths.Total)
+ }
+ if queueLengths.FullDeposit != 1 {
+ t.Errorf("Incorrect full deposit queue length 3 %d", queueLengths.FullDeposit)
+ }
+ if queueLengths.HalfDeposit != 1 {
+ t.Errorf("Incorrect half deposit queue length 3 %d", queueLengths.HalfDeposit)
+ }
+ if queueLengths.EmptyDeposit != 0 {
+ t.Errorf("Incorrect empty deposit queue length 3 %d", queueLengths.EmptyDeposit)
+ }
+ }
+
+ // Create empty deposit minipool
+ //if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, trustedNodeAccount, eth.EthToWei(0), 3); err != nil { t.Fatal(err) }
+
+ // Get & check queue lengths
+ if queueLengths, err := minipool.GetQueueLengths(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueLengths.Total != 2 {
+ t.Errorf("Incorrect total queue length 4 %d", queueLengths.Total)
+ }
+ if queueLengths.FullDeposit != 1 {
+ t.Errorf("Incorrect full deposit queue length 4 %d", queueLengths.FullDeposit)
+ }
+ if queueLengths.HalfDeposit != 1 {
+ t.Errorf("Incorrect half deposit queue length 4 %d", queueLengths.HalfDeposit)
+ }
+ //if queueLengths.EmptyDeposit != 1 {
+ // t.Errorf("Incorrect empty deposit queue length 4 %d", queueLengths.EmptyDeposit)
+ //}
+ }
+
+}
+
+func TestQueueCapacity(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Disable min commission rate for unbonded pools
+ if _, err := trustednodesettings.BootstrapMinipoolUnbondedMinFee(rp, uint64(0), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue capacity
+ if queueCapacity, err := minipool.GetQueueCapacity(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueCapacity.Total.Cmp(eth.EthToWei(0)) != 0 {
+ t.Errorf("Incorrect queue total capacity 1 %s", queueCapacity.Total.String())
+ }
+ if queueCapacity.Effective.Cmp(eth.EthToWei(0)) != 0 {
+ t.Errorf("Incorrect queue effective capacity 1 %s", queueCapacity.Effective.String())
+ }
+ if queueCapacity.NextMinipool.Cmp(eth.EthToWei(0)) != 0 {
+ t.Errorf("Incorrect queue next minipool capacity 1 %s", queueCapacity.NextMinipool.String())
+ }
+ }
+
+ /* TODO: Unbonded minipools are temporarily disabled
+ // Create empty deposit minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, trustedNodeAccount, eth.EthToWei(0)); err != nil { t.Fatal(err) }
+
+ // Get & check queue capacity
+ if queueCapacity, err := minipool.GetQueueCapacity(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueCapacity.Total.Cmp(eth.EthToWei(32)) != 0 {
+ t.Errorf("Incorrect queue total capacity 2 %s", queueCapacity.Total.String())
+ }
+ if queueCapacity.Effective.Cmp(eth.EthToWei(0)) != 0 {
+ t.Errorf("Incorrect queue effective capacity 2 %s", queueCapacity.Effective.String())
+ }
+ if queueCapacity.NextMinipool.Cmp(eth.EthToWei(32)) != 0 {
+ t.Errorf("Incorrect queue next minipool capacity 2 %s", queueCapacity.NextMinipool.String())
+ }
+ }
+ */
+
+ // Create half deposit minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue capacity
+ if queueCapacity, err := minipool.GetQueueCapacity(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueCapacity.Total.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect queue total capacity 3 %s", queueCapacity.Total.String())
+ }
+ if queueCapacity.Effective.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect queue effective capacity 3 %s", queueCapacity.Effective.String())
+ }
+ if queueCapacity.NextMinipool.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect queue next minipool capacity 3 %s", queueCapacity.NextMinipool.String())
+ }
+ }
+
+ // Create full deposit minipool
+ if _, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 2); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check queue capacity
+ if queueCapacity, err := minipool.GetQueueCapacity(rp, nil); err != nil {
+ t.Error(err)
+ } else {
+ if queueCapacity.Total.Cmp(eth.EthToWei(32)) != 0 {
+ t.Errorf("Incorrect queue total capacity 4 %s", queueCapacity.Total.String())
+ }
+ if queueCapacity.Effective.Cmp(eth.EthToWei(32)) != 0 {
+ t.Errorf("Incorrect queue effective capacity 4 %s", queueCapacity.Effective.String())
+ }
+ if queueCapacity.NextMinipool.Cmp(eth.EthToWei(16)) != 0 {
+ t.Errorf("Incorrect queue next minipool capacity 4 %s", queueCapacity.NextMinipool.String())
+ }
+ }
+
+}
diff --git a/bindings/tests/minipool/status_test.go b/bindings/tests/minipool/status_test.go
new file mode 100644
index 000000000..c22a4c880
--- /dev/null
+++ b/bindings/tests/minipool/status_test.go
@@ -0,0 +1,78 @@
+package minipool
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/types"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestSubmitMinipoolWithdrawable(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register nodes
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create & stake minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(32), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial minipool withdrawable status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status == types.Withdrawable {
+ t.Error("Incorrect initial minipool withdrawable status")
+ }
+
+ // Submit minipool withdrawable status
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated minipool withdrawable status
+ if status, err := mp.GetStatus(nil); err != nil {
+ t.Error(err)
+ } else if status != types.Withdrawable {
+ t.Error("Incorrect updated minipool withdrawable status")
+ }
+
+}
diff --git a/bindings/tests/network/balances_test.go b/bindings/tests/network/balances_test.go
new file mode 100644
index 000000000..7fbe6d000
--- /dev/null
+++ b/bindings/tests/network/balances_test.go
@@ -0,0 +1,75 @@
+package network
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestSubmitBalances(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register trusted node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit balances
+ var balancesBlock uint64 = 100
+ var slotTimestamp uint64 = 16000000
+ totalEth := eth.EthToWei(100)
+ stakingEth := eth.EthToWei(80)
+ rethSupply := eth.EthToWei(70)
+ if _, err := network.SubmitBalances(rp, balancesBlock, slotTimestamp, totalEth, stakingEth, rethSupply, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check network balances block
+ if networkBalancesBlock, err := network.GetBalancesBlock(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkBalancesBlock != balancesBlock {
+ t.Errorf("Incorrect network balances block %d", networkBalancesBlock)
+ }
+
+ // Get & check network total ETH
+ if networkTotalEth, err := network.GetTotalETHBalance(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkTotalEth.Cmp(totalEth) != 0 {
+ t.Errorf("Incorrect network total ETH balance %s", networkTotalEth.String())
+ }
+
+ // Get & check network staking ETH
+ if networkStakingEth, err := network.GetStakingETHBalance(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkStakingEth.Cmp(stakingEth) != 0 {
+ t.Errorf("Incorrect network staking ETH balance %s", networkStakingEth.String())
+ }
+
+ // Get & check network rETH supply
+ if networkRethSupply, err := network.GetTotalRETHSupply(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkRethSupply.Cmp(rethSupply) != 0 {
+ t.Errorf("Incorrect network total rETH supply %s", networkRethSupply.String())
+ }
+
+ // Get & check ETH utilization rate
+ if ethUtilizationRate, err := network.GetETHUtilizationRate(rp, nil); err != nil {
+ t.Error(err)
+ } else if ethUtilizationRate != eth.WeiToEth(stakingEth)/eth.WeiToEth(totalEth) {
+ t.Errorf("Incorrect network ETH utilization rate %f", ethUtilizationRate)
+ }
+
+}
diff --git a/bindings/tests/network/fees_test.go b/bindings/tests/network/fees_test.go
new file mode 100644
index 000000000..d879188d6
--- /dev/null
+++ b/bindings/tests/network/fees_test.go
@@ -0,0 +1,98 @@
+package network
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestNodeFee(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Get settings
+ targetNodeFee, err := protocol.GetTargetNodeFee(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ minNodeFee, err := protocol.GetMinimumNodeFee(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ maxNodeFee, err := protocol.GetMaximumNodeFee(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ demandRange, err := protocol.GetNodeFeeDemandRange(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check initial node demand
+ if nodeDemand, err := network.GetNodeDemand(rp, nil); err != nil {
+ t.Error(err)
+ } else if nodeDemand.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node demand value %s", nodeDemand.String())
+ }
+
+ // Get & check initial node fee
+ if nodeFee, err := network.GetNodeFee(rp, nil); err != nil {
+ t.Error(err)
+ } else if nodeFee != targetNodeFee {
+ t.Errorf("Incorrect initial node fee %f", nodeFee)
+ }
+
+ // Make user deposit
+ opts := userAccount.GetTransactor()
+ opts.Value = demandRange
+ if _, err := deposit.Deposit(rp, opts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node demand
+ if nodeDemand, err := network.GetNodeDemand(rp, nil); err != nil {
+ t.Error(err)
+ } else if nodeDemand.Cmp(opts.Value) != 0 {
+ t.Errorf("Incorrect updated node demand value %s", nodeDemand.String())
+ }
+
+ // Get & check updated node fee
+ if nodeFee, err := network.GetNodeFee(rp, nil); err != nil {
+ t.Error(err)
+ } else if nodeFee != maxNodeFee {
+ t.Errorf("Incorrect updated node fee %f", nodeFee)
+ }
+
+ // Get & check node fees by demand values
+ negDemandRange := new(big.Int)
+ negDemandRange.Neg(demandRange)
+ if nodeFee, err := network.GetNodeFeeByDemand(rp, big.NewInt(0), nil); err != nil {
+ t.Error(err)
+ } else if nodeFee != targetNodeFee {
+ t.Errorf("Incorrect node fee for zero demand %f", nodeFee)
+ }
+ if nodeFee, err := network.GetNodeFeeByDemand(rp, negDemandRange, nil); err != nil {
+ t.Error(err)
+ } else if nodeFee != minNodeFee {
+ t.Errorf("Incorrect node fee for negative demand %f", nodeFee)
+ }
+ if nodeFee, err := network.GetNodeFeeByDemand(rp, demandRange, nil); err != nil {
+ t.Error(err)
+ } else if nodeFee != maxNodeFee {
+ t.Errorf("Incorrect node fee for positive demand %f", nodeFee)
+ }
+
+}
diff --git a/bindings/tests/network/main_test.go b/bindings/tests/network/main_test.go
new file mode 100644
index 000000000..854951708
--- /dev/null
+++ b/bindings/tests/network/main_test.go
@@ -0,0 +1,63 @@
+package network
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount *accounts.Account
+ nodeAccount *accounts.Account
+ userAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount, err = accounts.GetAccount(9)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/network/prices_test.go b/bindings/tests/network/prices_test.go
new file mode 100644
index 000000000..7b676709c
--- /dev/null
+++ b/bindings/tests/network/prices_test.go
@@ -0,0 +1,53 @@
+package network
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestSubmitPrices(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register trusted node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit prices
+ var pricesBlock uint64 = 100
+ var slotTimestamp uint64 = 16000000
+ rplPrice := eth.EthToWei(1000)
+ effectiveRplStake := eth.EthToWei(24000)
+ if _, err := network.SubmitPrices(rp, pricesBlock, slotTimestamp, rplPrice, effectiveRplStake, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check network prices block
+ if networkPricesBlock, err := network.GetPricesBlock(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkPricesBlock != pricesBlock {
+ t.Errorf("Incorrect network prices block %d", networkPricesBlock)
+ }
+
+ // Get & check network RPL price
+ if networkRplPrice, err := network.GetRPLPrice(rp, nil); err != nil {
+ t.Error(err)
+ } else if networkRplPrice.Cmp(rplPrice) != 0 {
+ t.Errorf("Incorrect network RPL price %s", networkRplPrice.String())
+ }
+
+}
diff --git a/bindings/tests/node/deposit_test.go b/bindings/tests/node/deposit_test.go
new file mode 100644
index 000000000..ad6077a6a
--- /dev/null
+++ b/bindings/tests/node/deposit_test.go
@@ -0,0 +1,60 @@
+package node
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestDeposit(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial node minipool count
+ minipoolCount1, err := minipool.GetNodeMinipoolCount(rp, nodeAccount.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint & stake RPL required for mininpool
+ rplRequired, err := minipoolutils.GetMinipoolRPLRequired(rp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.StakeRPL(rp, ownerAccount, nodeAccount, rplRequired); err != nil {
+ t.Fatal(err)
+ }
+
+ // Deposit
+ if _, _, err := nodeutils.Deposit(t, rp, nodeAccount, eth.EthToWei(16), 1); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node minipool count
+ minipoolCount2, err := minipool.GetNodeMinipoolCount(rp, nodeAccount.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ } else if minipoolCount2 != minipoolCount1+1 {
+ t.Error("Incorrect node minipool count")
+ }
+
+}
diff --git a/bindings/tests/node/distributor_test.go b/bindings/tests/node/distributor_test.go
new file mode 100644
index 000000000..426f1c0d6
--- /dev/null
+++ b/bindings/tests/node/distributor_test.go
@@ -0,0 +1,31 @@
+package node
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/node"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestNodeDistributor(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ distributorAddress, err := node.GetDistributorAddress(rp, nodeAccount.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if distributorAddress.Hex() == "0x0000000000000000000000000000000000000000" {
+ t.Errorf("Invalid distributor address")
+ }
+}
diff --git a/bindings/tests/node/main_test.go b/bindings/tests/node/main_test.go
new file mode 100644
index 000000000..d12f5f1a7
--- /dev/null
+++ b/bindings/tests/node/main_test.go
@@ -0,0 +1,57 @@
+package node
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ nodeAccount *accounts.Account
+ withdrawalAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ withdrawalAccount, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/node/node_test.go b/bindings/tests/node/node_test.go
new file mode 100644
index 000000000..b822b8122
--- /dev/null
+++ b/bindings/tests/node/node_test.go
@@ -0,0 +1,169 @@
+package node
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/storage"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestRegisterNode(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Get & check initial node exists status
+ if exists, err := node.GetNodeExists(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if exists {
+ t.Error("Node already existed before registration")
+ }
+
+ // Get & check initial node details
+ if details, err := node.GetNodes(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(details) != 0 {
+ t.Error("Incorrect initial node count")
+ }
+
+ // Register node
+ timezoneLocation := "Australia/Brisbane"
+ if _, err := node.RegisterNode(rp, timezoneLocation, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node details
+ if details, err := node.GetNodes(rp, nil); err != nil {
+ t.Error(err)
+ } else if len(details) != 1 {
+ t.Error("Incorrect updated node count")
+ } else {
+ nodeDetails := details[0]
+ if !bytes.Equal(nodeDetails.Address.Bytes(), nodeAccount.Address.Bytes()) {
+ t.Errorf("Incorrect node address %s", nodeDetails.Address.Hex())
+ }
+ if !nodeDetails.Exists {
+ t.Error("Incorrect node exists status")
+ }
+ if !bytes.Equal(nodeDetails.PrimaryWithdrawalAddress.Bytes(), nodeAccount.Address.Bytes()) {
+ t.Errorf("Incorrect node withdrawal address '%s'", nodeDetails.PrimaryWithdrawalAddress.Hex())
+ }
+ if nodeDetails.TimezoneLocation != timezoneLocation {
+ t.Errorf("Incorrect node timezone location '%s'", nodeDetails.TimezoneLocation)
+ }
+ }
+
+}
+
+func TestSetWithdrawalAddress(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set withdrawal address
+ withdrawalAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ if _, err := storage.SetWithdrawalAddress(rp, nodeAccount.Address, withdrawalAddress, true, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check node withdrawal address
+ if nodeWithdrawalAddress, err := storage.GetNodeWithdrawalAddress(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if !bytes.Equal(nodeWithdrawalAddress.Bytes(), withdrawalAddress.Bytes()) {
+ t.Errorf("Incorrect node withdrawal address '%s'", nodeWithdrawalAddress.Hex())
+ }
+
+}
+
+func TestSetWithdrawalAddressConfirmation(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set withdrawal address
+ withdrawalAddress := withdrawalAccount.Address
+ if _, err := storage.SetWithdrawalAddress(rp, nodeAccount.Address, withdrawalAddress, false, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Confirm withdrawal address
+ if _, err := storage.ConfirmWithdrawalAddress(rp, nodeAccount.Address, withdrawalAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check node withdrawal address
+ if nodeWithdrawalAddress, err := storage.GetNodeWithdrawalAddress(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if !bytes.Equal(nodeWithdrawalAddress.Bytes(), withdrawalAddress.Bytes()) {
+ t.Errorf("Incorrect node withdrawal address '%s'", nodeWithdrawalAddress.Hex())
+ }
+
+}
+
+func TestSetTimezoneLocation(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set timezone
+ timezoneLocation := "Australia/Sydney"
+ if _, err := node.SetTimezoneLocation(rp, timezoneLocation, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check node timezone location
+ if nodeTimezoneLocation, err := node.GetNodeTimezoneLocation(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeTimezoneLocation != timezoneLocation {
+ t.Errorf("Incorrect node timezone location '%s'", nodeTimezoneLocation)
+ }
+
+}
diff --git a/bindings/tests/node/staking_test.go b/bindings/tests/node/staking_test.go
new file mode 100644
index 000000000..00aa3b85c
--- /dev/null
+++ b/bindings/tests/node/staking_test.go
@@ -0,0 +1,267 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+func TestStakeRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get RPL amount required for 2 minipools
+ minipoolRplRequired, err := minipoolutils.GetMinipoolRPLRequired(rp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rplAmount := new(big.Int)
+ rplAmount.Mul(minipoolRplRequired, big.NewInt(2))
+
+ // Mint RPL
+ if err := rplutils.MintRPL(rp, ownerAccount, nodeAccount, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Approve RPL transfer for staking
+ rocketNodeStakingAddress, err := rp.GetAddress("rocketNodeStaking")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := tokens.ApproveRPL(rp, *rocketNodeStakingAddress, rplAmount, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check initial staking details
+ if totalRplStake, err := node.GetTotalRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial total RPL stake %s", totalRplStake.String())
+ }
+ if totalEffectiveRplStake, err := node.GetTotalEffectiveRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalEffectiveRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial total effective RPL stake %s", totalEffectiveRplStake.String())
+ }
+ if nodeRplStake, err := node.GetNodeRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node RPL stake %s", nodeRplStake.String())
+ }
+ if nodeEffectiveRplStake, err := node.GetNodeEffectiveRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeEffectiveRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node effective RPL stake %s", nodeEffectiveRplStake.String())
+ }
+ if nodeMinimumRplStake, err := node.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeMinimumRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node minimum RPL stake %s", nodeMinimumRplStake.String())
+ }
+ if nodeRplStakedTime, err := node.GetNodeRPLStakedTime(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStakedTime != 0 {
+ t.Errorf("Incorrect initial node RPL staked time %d", nodeRplStakedTime)
+ }
+ if nodeMinipoolLimit, err := node.GetNodeMinipoolLimit(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeMinipoolLimit != 0 {
+ t.Errorf("Incorrect initial node minipool limit %d", nodeMinipoolLimit)
+ }
+
+ // Stake RPL
+ if _, err := node.StakeRPL(rp, rplAmount, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check updated staking details
+ if totalRplStake, err := node.GetTotalRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect updated total RPL stake 1 %s", totalRplStake.String())
+ }
+ if totalEffectiveRplStake, err := node.GetTotalEffectiveRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalEffectiveRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated total effective RPL stake 1 %s", totalEffectiveRplStake.String())
+ }
+ if nodeRplStake, err := node.GetNodeRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect updated node RPL stake 1 %s", nodeRplStake.String())
+ }
+ if nodeEffectiveRplStake, err := node.GetNodeEffectiveRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeEffectiveRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated node effective RPL stake 1 %s", nodeEffectiveRplStake.String())
+ }
+ if nodeMinimumRplStake, err := node.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeMinimumRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated node minimum RPL stake 1 %s", nodeMinimumRplStake.String())
+ }
+ if nodeRplStakedTime, err := node.GetNodeRPLStakedTime(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStakedTime == 0 {
+ t.Errorf("Incorrect updated node RPL staked time 1 %d", nodeRplStakedTime)
+ }
+ if nodeMinipoolLimit, err := node.GetNodeMinipoolLimit(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeMinipoolLimit != 2 {
+ t.Errorf("Incorrect updated node minipool limit 1 %d", nodeMinipoolLimit)
+ }
+
+ // Make node deposit to create minipool
+ minipoolAddress, _, err := nodeutils.Deposit(t, rp, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ mp, err := minipool.NewMinipool(rp, minipoolAddress)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Make user deposit
+ depositOpts := nodeAccount.GetTransactor()
+ depositOpts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, depositOpts); err != nil {
+ t.Fatal(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check updated staking details
+ if totalEffectiveRplStake, err := node.GetTotalEffectiveRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalEffectiveRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect updated total effective RPL stake 2 %s", totalEffectiveRplStake.String())
+ }
+ if nodeEffectiveRplStake, err := node.GetNodeEffectiveRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeEffectiveRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect updated node effective RPL stake 2 %s", nodeEffectiveRplStake.String())
+ }
+ if nodeMinimumRplStake, err := node.GetNodeMinimumRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeMinimumRplStake.Cmp(minipoolRplRequired) != 0 {
+ t.Errorf("Incorrect updated node minimum RPL stake 2 %s", nodeMinimumRplStake.String())
+ }
+
+}
+
+func TestWithdrawRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint & stake RPL
+ rplAmount := eth.EthToWei(1000)
+ if err := nodeutils.StakeRPL(rp, ownerAccount, nodeAccount, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & set rewards claim interval
+ rewardsClaimIntervalTime, err := protocol.GetRewardsClaimIntervalTime(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := protocol.BootstrapRewardsClaimIntervalTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check initial staking details
+ if totalRplStake, err := node.GetTotalRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect initial total RPL stake %s", totalRplStake.String())
+ }
+ if nodeRplStake, err := node.GetNodeRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStake.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect initial node RPL stake %s", nodeRplStake.String())
+ }
+
+ // Withdraw RPL
+ if _, err := node.WithdrawRPL(rp, rplAmount, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Check updated staking details
+ if totalRplStake, err := node.GetTotalRPLStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if totalRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated total RPL stake %s", totalRplStake.String())
+ }
+ if nodeRplStake, err := node.GetNodeRPLStake(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeRplStake.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect updated node RPL stake %s", nodeRplStake.String())
+ }
+
+ // Reset rewards claim interval
+ if _, err := protocol.BootstrapRewardsClaimIntervalTime(rp, rewardsClaimIntervalTime, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+}
diff --git a/bindings/tests/rewards/main_test.go b/bindings/tests/rewards/main_test.go
new file mode 100644
index 000000000..920876ef1
--- /dev/null
+++ b/bindings/tests/rewards/main_test.go
@@ -0,0 +1,58 @@
+package rewards
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount *accounts.Account
+ nodeAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ nodeAccount, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/rewards/node_test.go b/bindings/tests/rewards/node_test.go
new file mode 100644
index 000000000..0c1d5c45c
--- /dev/null
+++ b/bindings/tests/rewards/node_test.go
@@ -0,0 +1,173 @@
+package rewards
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rewards"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+func TestNodeRewards(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Constants
+ oneDay := 24 * 60 * 60
+ rewardInterval := oneDay
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set network parameters
+ if _, err := protocol.BootstrapRewardsClaimIntervalTime(rp, uint64(rewardInterval), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check node claims enabled status
+ if claimsEnabled, err := rewards.GetNodeClaimsEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if !claimsEnabled {
+ t.Error("Incorrect node claims enabled status")
+ }
+
+ // Get & check initial node claim possible status
+ if nodeClaimPossible, err := rewards.GetNodeClaimPossible(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeClaimPossible {
+ t.Error("Incorrect initial node claim possible status")
+ }
+
+ // Increase time until node claims are possible
+ if err := evm.IncreaseTime(rewardInterval); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node claim possible status
+ if nodeClaimPossible, err := rewards.GetNodeClaimPossible(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if !nodeClaimPossible {
+ t.Error("Incorrect updated node claim possible status")
+ }
+
+ // Get & check initial node claim rewards percent
+ if rewardsPerc, err := rewards.GetNodeClaimRewardsPerc(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsPerc != 0 {
+ t.Errorf("Incorrect initial node claim rewards perc %f", rewardsPerc)
+ }
+
+ // Stake RPL & create a minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, nodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Deposit user ETH to minipool
+ opts := nodeAccount.GetTransactor()
+ opts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, opts); err != nil {
+ t.Error(err)
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ t.Fatal(fmt.Errorf("error increasing time: %w", err))
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, nodeAccount); err != nil {
+ t.Error(err)
+ }
+
+ // Get & check updated node claim rewards percent
+ if rewardsPerc, err := rewards.GetNodeClaimRewardsPerc(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsPerc != 1 {
+ t.Errorf("Incorrect updated node claim rewards perc %f", rewardsPerc)
+ }
+
+ // Get & check initial node claim rewards amount
+ if rewardsAmount, err := rewards.GetNodeClaimRewardsAmount(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsAmount.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node claim rewards amount %s", rewardsAmount.String())
+ }
+
+ // Get & check initial RPL rewards amount
+ if pendingRewards, err := rewards.GetPendingRewards(rp, nil); err != nil {
+ t.Error(err)
+ } else if pendingRewards != 0 {
+ t.Errorf("Incorrect initial pending rewards amount %f", pendingRewards)
+ }
+
+ // Start RPL inflation
+ if header, err := rp.Client.HeaderByNumber(context.Background(), nil); err != nil {
+ t.Fatal(err)
+ } else if _, err := protocol.BootstrapInflationStartTime(rp, header.Time+uint64(oneDay), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Increase time until rewards are available
+ if err := evm.IncreaseTime(oneDay + oneDay); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node claim rewards amount
+ if rewardsAmount, err := rewards.GetNodeClaimRewardsAmount(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsAmount.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated node claim rewards amount %s", rewardsAmount.String())
+ }
+
+ // Get & check updated RPL rewards amount
+ if pendingRewards, err := rewards.GetPendingRewards(rp, nil); err != nil {
+ t.Error(err)
+ } else if pendingRewards <= 0 {
+ t.Errorf("Incorrect updated pending rewards amount %f", pendingRewards)
+ }
+
+ // Get & check initial node RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node RPL balance %s", rplBalance.String())
+ }
+
+ // Claim node rewards
+ if _, err := rewards.ClaimNodeRewards(rp, nodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, nodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated node RPL balance %s", rplBalance.String())
+ }
+
+}
diff --git a/bindings/tests/rewards/trusted_node_test.go b/bindings/tests/rewards/trusted_node_test.go
new file mode 100644
index 000000000..4d6d341f9
--- /dev/null
+++ b/bindings/tests/rewards/trusted_node_test.go
@@ -0,0 +1,120 @@
+package rewards
+
+import (
+ "context"
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/rewards"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestTrustedNodeRewards(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Constants
+ oneDay := 24 * 60 * 60
+ rewardInterval := oneDay
+
+ // Register node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set network parameters
+ if _, err := protocol.BootstrapRewardsClaimIntervalTime(rp, uint64(rewardInterval), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check trusted node claims enabled status
+ if claimsEnabled, err := rewards.GetTrustedNodeClaimsEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if !claimsEnabled {
+ t.Error("Incorrect trusted node claims enabled status")
+ }
+
+ // Get & check initial trusted node claim possible status
+ if nodeClaimPossible, err := rewards.GetTrustedNodeClaimPossible(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if nodeClaimPossible {
+ t.Error("Incorrect initial trusted node claim possible status")
+ }
+
+ // Increase time until node claims are possible
+ if err := evm.IncreaseTime(rewardInterval); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated trusted node claim possible status
+ if nodeClaimPossible, err := rewards.GetTrustedNodeClaimPossible(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if !nodeClaimPossible {
+ t.Error("Incorrect updated trusted node claim possible status")
+ }
+
+ // Get & check trusted node claim rewards percent
+ if rewardsPerc, err := rewards.GetTrustedNodeClaimRewardsPerc(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsPerc != 1 {
+ t.Errorf("Incorrect trusted node claim rewards perc %f", rewardsPerc)
+ }
+
+ // Get & check initial trusted node claim rewards amount
+ if rewardsAmount, err := rewards.GetTrustedNodeClaimRewardsAmount(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsAmount.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial trusted node claim rewards amount %s", rewardsAmount.String())
+ }
+
+ // Start RPL inflation
+ if header, err := rp.Client.HeaderByNumber(context.Background(), nil); err != nil {
+ t.Fatal(err)
+ } else if _, err := protocol.BootstrapInflationStartTime(rp, header.Time+uint64(oneDay), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Increase time until rewards are available
+ if err := evm.IncreaseTime(oneDay + oneDay); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated trusted node claim rewards amount
+ if rewardsAmount, err := rewards.GetTrustedNodeClaimRewardsAmount(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rewardsAmount.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated trusted node claim rewards amount %s", rewardsAmount.String())
+ }
+
+ // Get & check initial node RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 0 {
+ t.Errorf("Incorrect initial node RPL balance %s", rplBalance.String())
+ }
+
+ // Claim node rewards
+ if _, err := rewards.ClaimTrustedNodeRewards(rp, trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated node RPL balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, trustedNodeAccount.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect updated node RPL balance %s", rplBalance.String())
+ }
+
+}
diff --git a/bindings/tests/rocketpool/main_test.go b/bindings/tests/rocketpool/main_test.go
new file mode 100644
index 000000000..0c4da11e8
--- /dev/null
+++ b/bindings/tests/rocketpool/main_test.go
@@ -0,0 +1,39 @@
+package rocketpool
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/rocketpool/rocketpool_test.go b/bindings/tests/rocketpool/rocketpool_test.go
new file mode 100644
index 000000000..4c23c15be
--- /dev/null
+++ b/bindings/tests/rocketpool/rocketpool_test.go
@@ -0,0 +1,157 @@
+package rocketpool
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func TestGetAddress(t *testing.T) {
+
+ // Get contract address
+ address1, err := rp.GetAddress("rocketDepositPool")
+ if err != nil {
+ t.Fatalf("error getting contract address: %s", err)
+ } else if bytes.Equal(address1.Bytes(), common.Address{}.Bytes()) {
+ t.Error("Contract address was not found")
+ }
+
+ // Get cached contract address
+ address2, err := rp.GetAddress("rocketDepositPool")
+ if err != nil {
+ t.Fatalf("error getting cached contract address: %s", err)
+ } else if !bytes.Equal(address2.Bytes(), address1.Bytes()) {
+ t.Error("Cached contract address did not match original contract address")
+ }
+
+}
+
+func TestGetAddresses(t *testing.T) {
+
+ // Get contract addresses
+ addresses1, err := rp.GetAddresses("rocketNodeManager", "rocketNodeDeposit")
+ if err != nil {
+ t.Fatalf("error getting contract addresses: %s", err)
+ } else {
+ for ai, address := range addresses1 {
+ if bytes.Equal(address.Bytes(), common.Address{}.Bytes()) {
+ t.Errorf("Contract address %d was not found", ai)
+ }
+ }
+ }
+
+ // Get cached contract addresses
+ addresses2, err := rp.GetAddresses("rocketNodeManager", "rocketNodeDeposit")
+ if err != nil {
+ t.Fatalf("error getting cached contract addresses: %s", err)
+ } else {
+ for ai := 0; ai < len(addresses2); ai++ {
+ if !bytes.Equal(addresses2[ai].Bytes(), addresses1[ai].Bytes()) {
+ t.Errorf("Cached contract address %d did not match original contract address", ai)
+ }
+ }
+ }
+
+}
+
+func TestGetABI(t *testing.T) {
+
+ // Get ABI
+ abi1, err := rp.GetABI("rocketDepositPool")
+ if err != nil {
+ t.Fatalf("error getting contract ABI: %s", err)
+ }
+
+ // Get cached ABI
+ abi2, err := rp.GetABI("rocketDepositPool")
+ if err != nil {
+ t.Fatalf("error getting cached contract ABI: %s", err)
+ } else {
+ abi2Json, err := json.Marshal(abi2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ abi1Json, err := json.Marshal(abi1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(abi2Json, abi1Json) {
+ t.Error("Cached contract ABI did not match original contract ABI")
+ }
+ }
+
+}
+
+func TestGetABIs(t *testing.T) {
+
+ // Get ABIs
+ abis1, err := rp.GetABIs("rocketNodeManager", "rocketNodeDeposit")
+ if err != nil {
+ t.Fatalf("error getting contract ABIs: %s", err)
+ }
+
+ // Get cached ABIs
+ abis2, err := rp.GetABIs("rocketNodeManager", "rocketNodeDeposit")
+ if err != nil {
+ t.Fatalf("error getting cached contract ABIs: %s", err)
+ } else {
+ for ai := 0; ai < len(abis2); ai++ {
+ abi2Json, err := json.Marshal(abis2[ai])
+ if err != nil {
+ t.Fatal(err)
+ }
+ abi1Json, err := json.Marshal(abis1[ai])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(abi2Json, abi1Json) {
+ t.Errorf("Cached contract ABI %d did not match original contract ABI", ai)
+ }
+ }
+ }
+
+}
+
+func TestGetContract(t *testing.T) {
+
+ // Get contract
+ if _, err := rp.GetContract("rocketDepositPool"); err != nil {
+ t.Fatalf("error getting contract: %s", err)
+ }
+
+ // Get cached contract
+ if _, err := rp.GetContract("rocketDepositPool"); err != nil {
+ t.Fatalf("error getting cached contract: %s", err)
+ }
+
+}
+
+func TestGetContracts(t *testing.T) {
+
+ // Get contracts
+ if _, err := rp.GetContracts("rocketNodeManager", "rocketNodeDeposit"); err != nil {
+ t.Fatalf("error getting contracts: %s", err)
+ }
+
+ // Get cached contracts
+ if _, err := rp.GetContracts("rocketNodeManager", "rocketNodeDeposit"); err != nil {
+ t.Fatalf("error getting cached contracts: %s", err)
+ }
+
+}
+
+func TestMakeContract(t *testing.T) {
+
+ // Make contract
+ if _, err := rp.MakeContract("rocketMinipool", common.HexToAddress("0x1111111111111111111111111111111111111111")); err != nil {
+ t.Fatalf("error making contract: %s", err)
+ }
+
+ // Make contract with cached ABI
+ if _, err := rp.MakeContract("rocketMinipool", common.HexToAddress("0x2222222222222222222222222222222222222222")); err != nil {
+ t.Fatalf("error making contract with cached ABI: %s", err)
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/auction_test.go b/bindings/tests/settings/protocol/auction_test.go
new file mode 100644
index 000000000..8295925d4
--- /dev/null
+++ b/bindings/tests/settings/protocol/auction_test.go
@@ -0,0 +1,94 @@
+package protocol
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestAuctionSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get creat lots enabled
+ createLotEnabled := false
+ if _, err := protocol.BootstrapCreateLotEnabled(rp, createLotEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetCreateLotEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != createLotEnabled {
+ t.Error("Incorrect creat lots enabled value")
+ }
+
+ // Set & get bid on lot enabled
+ bidOnLotEnabled := false
+ if _, err := protocol.BootstrapBidOnLotEnabled(rp, bidOnLotEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetBidOnLotEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != bidOnLotEnabled {
+ t.Error("Incorrect bid on lot enabled value")
+ }
+
+ // Set & get lot minimum ETH value
+ lotMinimumEthValue := eth.EthToWei(1000)
+ if _, err := protocol.BootstrapLotMinimumEthValue(rp, lotMinimumEthValue, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetLotMinimumEthValue(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(lotMinimumEthValue) != 0 {
+ t.Error("Incorrect lot minimum ETH value value")
+ }
+
+ // Set & get lot maximum ETH value
+ lotMaximumEthValue := eth.EthToWei(0.01)
+ if _, err := protocol.BootstrapLotMaximumEthValue(rp, lotMaximumEthValue, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetLotMaximumEthValue(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(lotMaximumEthValue) != 0 {
+ t.Error("Incorrect lot maximum ETH value value")
+ }
+
+ // Set & get lot duration
+ var lotDuration uint64 = 1
+ if _, err := protocol.BootstrapLotDuration(rp, lotDuration, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetLotDuration(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != lotDuration {
+ t.Error("Incorrect lot duration value")
+ }
+
+ // Set & get lot starting price ratio
+ lotStartingPriceRatio := 2.0
+ if _, err := protocol.BootstrapLotStartingPriceRatio(rp, lotStartingPriceRatio, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetLotStartingPriceRatio(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != lotStartingPriceRatio {
+ t.Error("Incorrect lot starting price ratio value")
+ }
+
+ // Set & get lot reserve price ratio
+ lotReservePriceRatio := 1.9
+ if _, err := protocol.BootstrapLotReservePriceRatio(rp, lotReservePriceRatio, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetLotReservePriceRatio(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != lotReservePriceRatio {
+ t.Error("Incorrect lot reserve price ratio value")
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/deposit_test.go b/bindings/tests/settings/protocol/deposit_test.go
new file mode 100644
index 000000000..0077b106e
--- /dev/null
+++ b/bindings/tests/settings/protocol/deposit_test.go
@@ -0,0 +1,74 @@
+package protocol
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestDepositSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get deposits enabled
+ depositEnabled := false
+ if _, err := protocol.BootstrapDepositEnabled(rp, depositEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetDepositEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != depositEnabled {
+ t.Error("Incorrect deposit enabled value")
+ }
+
+ // Set & get deposit assignments enabled
+ assignDepositsEnabled := false
+ if _, err := protocol.BootstrapAssignDepositsEnabled(rp, assignDepositsEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetAssignDepositsEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != assignDepositsEnabled {
+ t.Error("Incorrect assign deposits enabled value")
+ }
+
+ // Set & get minimum deposit amount
+ minimumDeposit := eth.EthToWei(1000)
+ if _, err := protocol.BootstrapMinimumDeposit(rp, minimumDeposit, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMinimumDeposit(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(minimumDeposit) != 0 {
+ t.Error("Incorrect minimum deposit value")
+ }
+
+ // Set & get maximum deposit pool size
+ maximumDepositPoolSize := eth.EthToWei(1)
+ if _, err := protocol.BootstrapMaximumDepositPoolSize(rp, maximumDepositPoolSize, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMaximumDepositPoolSize(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(maximumDepositPoolSize) != 0 {
+ t.Error("Incorrect maximum deposit pool size value")
+ }
+
+ // Set & get maximum deposit assignments
+ var maximumDepositAssignments uint64 = 50
+ if _, err := protocol.BootstrapMaximumDepositAssignments(rp, maximumDepositAssignments, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMaximumDepositAssignments(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != maximumDepositAssignments {
+ t.Error("Incorrect maximum deposit assignments value")
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/inflation_test.go b/bindings/tests/settings/protocol/inflation_test.go
new file mode 100644
index 000000000..2b3d3e995
--- /dev/null
+++ b/bindings/tests/settings/protocol/inflation_test.go
@@ -0,0 +1,44 @@
+package protocol
+
+import (
+ "testing"
+ "time"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestInflationSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get inflation interval rate
+ inflationIntervalRate := 0.5
+ if _, err := protocol.BootstrapInflationIntervalRate(rp, inflationIntervalRate, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetInflationIntervalRate(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != inflationIntervalRate {
+ t.Error("Incorrect inflation interval rate value")
+ }
+
+ // Set & get inflation start block
+ inflationStartTime := uint64(time.Now().Unix()) + 3600
+ if _, err := protocol.BootstrapInflationStartTime(rp, inflationStartTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetInflationStartTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != inflationStartTime {
+ t.Error("Incorrect inflation start time value")
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/main_test.go b/bindings/tests/settings/protocol/main_test.go
new file mode 100644
index 000000000..8d2ba8039
--- /dev/null
+++ b/bindings/tests/settings/protocol/main_test.go
@@ -0,0 +1,48 @@
+package protocol
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/settings/protocol/minipool_test.go b/bindings/tests/settings/protocol/minipool_test.go
new file mode 100644
index 000000000..8e0d11976
--- /dev/null
+++ b/bindings/tests/settings/protocol/minipool_test.go
@@ -0,0 +1,84 @@
+package protocol
+
+import (
+ "testing"
+ "time"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestMinipoolSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Get & check launch balance and deposit amounts
+ fullMinipoolBalance := eth.EthToWei(32)
+ halfMinipoolBalance := eth.EthToWei(16)
+ emptyMinipoolBalance := eth.EthToWei(0)
+ if value, err := protocol.GetMinipoolLaunchBalance(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(fullMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool launch balance")
+ }
+ if value, err := protocol.GetMinipoolFullDepositNodeAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(fullMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool full deposit node amount")
+ }
+ if value, err := protocol.GetMinipoolHalfDepositNodeAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(halfMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool half deposit node amount")
+ }
+ if value, err := protocol.GetMinipoolEmptyDepositNodeAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(emptyMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool empty deposit node amount")
+ }
+ if value, err := protocol.GetMinipoolFullDepositUserAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(halfMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool full deposit user amount")
+ }
+ if value, err := protocol.GetMinipoolHalfDepositUserAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(halfMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool half deposit user amount")
+ }
+ if value, err := protocol.GetMinipoolEmptyDepositUserAmount(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(fullMinipoolBalance) != 0 {
+ t.Error("Incorrect minipool empty deposit user amount")
+ }
+
+ // Set & get submit withdrawable enabled
+ submitWithdrawableEnabled := false
+ if _, err := protocol.BootstrapMinipoolSubmitWithdrawableEnabled(rp, submitWithdrawableEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMinipoolSubmitWithdrawableEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != submitWithdrawableEnabled {
+ t.Error("Incorrect minipool withdrawable submissions enabled value")
+ }
+
+ // Set & get minipool launch timeout
+ var minipoolLaunchTimeout time.Duration = 5 * time.Second
+ if _, err := protocol.BootstrapMinipoolLaunchTimeout(rp, minipoolLaunchTimeout, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMinipoolLaunchTimeout(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != minipoolLaunchTimeout {
+ t.Error("Incorrect minipool launch timeout value")
+ }
+}
diff --git a/bindings/tests/settings/protocol/network_test.go b/bindings/tests/settings/protocol/network_test.go
new file mode 100644
index 000000000..abffda729
--- /dev/null
+++ b/bindings/tests/settings/protocol/network_test.go
@@ -0,0 +1,124 @@
+package protocol
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestNetworkSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get node consensus threshold
+ nodeConsensusThreshold := 0.1
+ if _, err := protocol.BootstrapNodeConsensusThreshold(rp, nodeConsensusThreshold, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetNodeConsensusThreshold(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != nodeConsensusThreshold {
+ t.Error("Incorrect node consensus threshold value")
+ }
+
+ // Set & get network balance submissions enabled
+ submitBalancesEnabled := false
+ if _, err := protocol.BootstrapSubmitBalancesEnabled(rp, submitBalancesEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetSubmitBalancesEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != submitBalancesEnabled {
+ t.Error("Incorrect network balance submissions enabled value")
+ }
+
+ // Set & get network balance submission frequency
+ var submitBalancesFrequency uint64 = 10
+ if _, err := protocol.BootstrapSubmitBalancesFrequency(rp, submitBalancesFrequency, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetSubmitBalancesFrequency(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != submitBalancesFrequency {
+ t.Error("Incorrect network balance submission frequency value")
+ }
+
+ // Set & get network price submissions enabled
+ submitPricesEnabled := false
+ if _, err := protocol.BootstrapSubmitPricesEnabled(rp, submitPricesEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetSubmitPricesEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != submitPricesEnabled {
+ t.Error("Incorrect network price submissions enabled value")
+ }
+
+ // Set & get network price submission frequency
+ var submitPricesFrequency uint64 = 10
+ if _, err := protocol.BootstrapSubmitPricesFrequency(rp, submitPricesFrequency, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetSubmitPricesFrequency(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != submitPricesFrequency {
+ t.Error("Incorrect network price submission frequency value")
+ }
+
+ // Set & get minimum node fee
+ minimumNodeFee := 0.80
+ if _, err := protocol.BootstrapMinimumNodeFee(rp, minimumNodeFee, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMinimumNodeFee(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != minimumNodeFee {
+ t.Error("Incorrect minimum node fee value")
+ }
+
+ // Set & get target node fee
+ targetNodeFee := 0.85
+ if _, err := protocol.BootstrapTargetNodeFee(rp, targetNodeFee, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetTargetNodeFee(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != targetNodeFee {
+ t.Error("Incorrect target node fee value")
+ }
+
+ // Set & get maximum node fee
+ maximumNodeFee := 0.90
+ if _, err := protocol.BootstrapMaximumNodeFee(rp, maximumNodeFee, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMaximumNodeFee(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != maximumNodeFee {
+ t.Error("Incorrect maximum node fee value")
+ }
+
+ // Set & get node fee demand range
+ nodeFeeDemandRange := eth.EthToWei(10)
+ if _, err := protocol.BootstrapNodeFeeDemandRange(rp, nodeFeeDemandRange, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetNodeFeeDemandRange(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(nodeFeeDemandRange) != 0 {
+ t.Error("Incorrect node fee demand range value")
+ }
+
+ // Set & get target rETH collateral rate
+ targetRethCollateralRate := 0.95
+ if _, err := protocol.BootstrapTargetRethCollateralRate(rp, targetRethCollateralRate, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetTargetRethCollateralRate(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != targetRethCollateralRate {
+ t.Error("Incorrect target rETH collateral rate value")
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/node_test.go b/bindings/tests/settings/protocol/node_test.go
new file mode 100644
index 000000000..ac4dd7961
--- /dev/null
+++ b/bindings/tests/settings/protocol/node_test.go
@@ -0,0 +1,63 @@
+package protocol
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestNodeSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get node registrations enabled
+ nodeRegistrationsEnabled := false
+ if _, err := protocol.BootstrapNodeRegistrationEnabled(rp, nodeRegistrationsEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetNodeRegistrationEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != nodeRegistrationsEnabled {
+ t.Error("Incorrect node registrations enabled value")
+ }
+
+ // Set & get node deposits enabled
+ nodeDepositsEnabled := false
+ if _, err := protocol.BootstrapNodeDepositEnabled(rp, nodeDepositsEnabled, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetNodeDepositEnabled(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != nodeDepositsEnabled {
+ t.Error("Incorrect node deposits enabled value")
+ }
+
+ // Set & get minimum per minipool RPL stake
+ minimumPerMinipoolStake := 1.0
+ if _, err := protocol.BootstrapMinimumPerMinipoolStake(rp, minimumPerMinipoolStake, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMinimumPerMinipoolStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != minimumPerMinipoolStake {
+ t.Error("Incorrect minimum per minipool stake value")
+ }
+
+ // Set & get maximum per minipool RPL stake
+ maximumPerMinipoolStake := 10.0
+ if _, err := protocol.BootstrapMaximumPerMinipoolStake(rp, maximumPerMinipoolStake, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocol.GetMaximumPerMinipoolStake(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != maximumPerMinipoolStake {
+ t.Error("Incorrect maximum per minipool stake value")
+ }
+
+}
diff --git a/bindings/tests/settings/protocol/rewards_test.go b/bindings/tests/settings/protocol/rewards_test.go
new file mode 100644
index 000000000..ce567ca3b
--- /dev/null
+++ b/bindings/tests/settings/protocol/rewards_test.go
@@ -0,0 +1,56 @@
+package protocol
+
+import (
+ "testing"
+
+ protocoldao "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ protocolsettings "github.com/rocket-pool/rocketpool-go/settings/protocol"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+func TestRewardsSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Bootstrap a claimer & get claimer settings
+ claimerPerc := 0.1
+ if _, err := protocoldao.BootstrapClaimer(rp, "rocketClaimNode", claimerPerc, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else {
+ if value, err := protocolsettings.GetRewardsClaimerPerc(rp, "rocketClaimNode", nil); err != nil {
+ t.Error(err)
+ } else if value != claimerPerc {
+ t.Errorf("Incorrect rewards claimer percent %f", value)
+ }
+ if value, err := protocolsettings.GetRewardsClaimerPercTimeUpdated(rp, "rocketClaimNode", nil); err != nil {
+ t.Error(err)
+ } else if value == 0 {
+ t.Errorf("Incorrect rewards claimer percent time updated %d", value)
+ }
+ if value, err := protocolsettings.GetRewardsClaimersPercTotal(rp, nil); err != nil {
+ t.Error(err)
+ } else if value == 0 {
+ t.Errorf("Incorrect rewards claimers total percent %f", value)
+ }
+ }
+
+ // Set & get rewards claim interval time
+ var rewardsClaimIntervalTime uint64 = 1
+ if _, err := protocolsettings.BootstrapRewardsClaimIntervalTime(rp, rewardsClaimIntervalTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := protocolsettings.GetRewardsClaimIntervalTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != rewardsClaimIntervalTime {
+ t.Error("Incorrect rewards claim interval time value")
+ }
+
+}
diff --git a/bindings/tests/settings/trustednode/main_test.go b/bindings/tests/settings/trustednode/main_test.go
new file mode 100644
index 000000000..1727002a9
--- /dev/null
+++ b/bindings/tests/settings/trustednode/main_test.go
@@ -0,0 +1,63 @@
+package trustednode
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount1 *accounts.Account
+ trustedNodeAccount2 *accounts.Account
+ trustedNodeAccount3 *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount1, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount2, err = accounts.GetAccount(2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount3, err = accounts.GetAccount(3)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/settings/trustednode/members_test.go b/bindings/tests/settings/trustednode/members_test.go
new file mode 100644
index 000000000..a881706a3
--- /dev/null
+++ b/bindings/tests/settings/trustednode/members_test.go
@@ -0,0 +1,162 @@
+package trustednode
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ daoutils "github.com/rocket-pool/rocketpool-go/tests/testutils/dao"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestBootstrapMembersSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get quorum
+ quorum := 0.1
+ if _, err := trustednode.BootstrapQuorum(rp, quorum, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetQuorum(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != quorum {
+ t.Error("Incorrect quorum value")
+ }
+
+ // Set & get rpl bond
+ rplBond := eth.EthToWei(1)
+ if _, err := trustednode.BootstrapRPLBond(rp, rplBond, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetRPLBond(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(rplBond) != 0 {
+ t.Error("Incorrect rpl bond value")
+ }
+
+ // Set & get maximum unbonded minipools
+ var minipoolUnbondedMax uint64 = 1
+ if _, err := trustednode.BootstrapMinipoolUnbondedMax(rp, minipoolUnbondedMax, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetMinipoolUnbondedMax(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != minipoolUnbondedMax {
+ t.Error("Incorrect maximum unbonded minipools value")
+ }
+
+}
+
+func TestProposeMembersSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednode.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednode.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register trusted node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set & get quorum
+ quorum := 0.1
+ if proposalId, _, err := trustednode.ProposeQuorum(rp, quorum, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetQuorum(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != quorum {
+ t.Error("Incorrect quorum value")
+ }
+
+ // Set & get rpl bond
+ rplBond := eth.EthToWei(1)
+ if proposalId, _, err := trustednode.ProposeRPLBond(rp, rplBond, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetRPLBond(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(rplBond) != 0 {
+ t.Error("Incorrect rpl bond value")
+ }
+
+ // Set & get maximum unbonded minipools
+ var minipoolUnbondedMax uint64 = 1
+ if proposalId, _, err := trustednode.ProposeMinipoolUnbondedMax(rp, minipoolUnbondedMax, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetMinipoolUnbondedMax(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != minipoolUnbondedMax {
+ t.Error("Incorrect maximum unbonded minipools value")
+ }
+
+ // Set & get member challenge cooldown period
+ var memberChallengeCooldown uint64 = 1
+ if proposalId, _, err := trustednode.ProposeChallengeCooldown(rp, memberChallengeCooldown, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetChallengeCooldown(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != memberChallengeCooldown {
+ t.Error("Incorrect member challenge cooldown value")
+ }
+
+ // Set & get member challenge window period
+ var memberChallengeWindow uint64 = 1
+ if proposalId, _, err := trustednode.ProposeChallengeWindow(rp, memberChallengeWindow, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetChallengeWindow(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != memberChallengeWindow {
+ t.Error("Incorrect member challenge window value")
+ }
+
+ // Set & get member challenge cost amount
+ challengeCost := eth.EthToWei(1)
+ if proposalId, _, err := trustednode.ProposeChallengeCost(rp, challengeCost, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetChallengeCost(rp, nil); err != nil {
+ t.Error(err)
+ } else if value.Cmp(challengeCost) != 0 {
+ t.Error("Incorrect member challenge cost value")
+ }
+
+}
diff --git a/bindings/tests/settings/trustednode/proposals_test.go b/bindings/tests/settings/trustednode/proposals_test.go
new file mode 100644
index 000000000..d391ab32b
--- /dev/null
+++ b/bindings/tests/settings/trustednode/proposals_test.go
@@ -0,0 +1,169 @@
+package trustednode
+
+import (
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ daoutils "github.com/rocket-pool/rocketpool-go/tests/testutils/dao"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+func TestBootstrapProposalsSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set & get cooldown
+ var cooldown uint64 = 1
+ if _, err := trustednode.BootstrapProposalCooldownTime(rp, cooldown, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalCooldownTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != cooldown {
+ t.Error("Incorrect cooldown value")
+ }
+
+ // Set & get vote time
+ var voteTime uint64 = 10
+ if _, err := trustednode.BootstrapProposalVoteTime(rp, voteTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalVoteTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != voteTime {
+ t.Error("Incorrect vote time value")
+ }
+
+ // Set & get execute time
+ var executeTime uint64 = 10
+ if _, err := trustednode.BootstrapProposalExecuteTime(rp, executeTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalExecuteTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != executeTime {
+ t.Error("Incorrect execute time value")
+ }
+
+ // Set & get action time
+ var actionTime uint64 = 10
+ if _, err := trustednode.BootstrapProposalActionTime(rp, actionTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalActionTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != actionTime {
+ t.Error("Incorrect action time value")
+ }
+
+ // Set & get vote delay time
+ var voteDelayTime uint64 = 1000
+ if _, err := trustednode.BootstrapProposalVoteDelayTime(rp, voteDelayTime, ownerAccount.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalVoteDelayTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != voteDelayTime {
+ t.Error("Incorrect vote delay time value")
+ }
+
+}
+
+func TestProposeProposalsSettings(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Set proposal cooldown
+ if _, err := trustednode.BootstrapProposalCooldownTime(rp, 0, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := trustednode.BootstrapProposalVoteDelayTime(rp, 5, ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Register trusted node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount1); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount2); err != nil {
+ t.Fatal(err)
+ }
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount3); err != nil {
+ t.Fatal(err)
+ }
+
+ // Set & get cooldown
+ var cooldown uint64 = 1
+ if proposalId, _, err := trustednode.ProposeProposalCooldownTime(rp, cooldown, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalCooldownTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != cooldown {
+ t.Error("Incorrect cooldown value")
+ }
+
+ // Set & get vote time
+ var voteTime uint64 = 10
+ if proposalId, _, err := trustednode.ProposeProposalVoteTime(rp, voteTime, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalVoteTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != voteTime {
+ t.Error("Incorrect vote time value")
+ }
+
+ // Set & get execute time
+ var executeTime uint64 = 10
+ if proposalId, _, err := trustednode.ProposeProposalExecuteTime(rp, executeTime, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalExecuteTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != executeTime {
+ t.Error("Incorrect execute time value")
+ }
+
+ // Set & get action time
+ var actionTime uint64 = 10
+ if proposalId, _, err := trustednode.ProposeProposalActionTime(rp, actionTime, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalActionTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != actionTime {
+ t.Error("Incorrect action time value")
+ }
+
+ // Set & get vote delay time
+ var voteDelayTime uint64 = 1000
+ if proposalId, _, err := trustednode.ProposeProposalVoteDelayTime(rp, voteDelayTime, trustedNodeAccount1.GetTransactor()); err != nil {
+ t.Error(err)
+ } else if err := daoutils.PassAndExecuteProposal(rp, proposalId, []*accounts.Account{trustedNodeAccount1, trustedNodeAccount2}); err != nil {
+ t.Error(err)
+ } else if value, err := trustednode.GetProposalVoteDelayTime(rp, nil); err != nil {
+ t.Error(err)
+ } else if value != voteDelayTime {
+ t.Error("Incorrect vote delay time value")
+ }
+
+}
diff --git a/bindings/tests/testutils/accounts/accounts.go b/bindings/tests/testutils/accounts/accounts.go
new file mode 100644
index 000000000..b1c8f9c17
--- /dev/null
+++ b/bindings/tests/testutils/accounts/accounts.go
@@ -0,0 +1,49 @@
+package accounts
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "encoding/hex"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+)
+
+// An account containing a keypair and address
+type Account struct {
+ PrivateKey *ecdsa.PrivateKey
+ Address common.Address
+}
+
+// Get an account by index
+func GetAccount(index uint8) (*Account, error) {
+
+ // Get private key data
+ privateKeyBytes, err := hex.DecodeString(tests.AccountPrivateKeys[index])
+ if err != nil {
+ return nil, err
+ }
+
+ // Get private key
+ privateKey, err := crypto.ToECDSA(privateKeyBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ // Return account
+ return &Account{
+ PrivateKey: privateKey,
+ Address: crypto.PubkeyToAddress(privateKey.PublicKey),
+ }, nil
+
+}
+
+// Get a transactor for an account
+func (a *Account) GetTransactor() *bind.TransactOpts {
+ opts := bind.NewKeyedTransactor(a.PrivateKey)
+ opts.Context = context.Background()
+ return opts
+}
diff --git a/bindings/tests/testutils/auction/auction.go b/bindings/tests/testutils/auction/auction.go
new file mode 100644
index 000000000..4f9427a54
--- /dev/null
+++ b/bindings/tests/testutils/auction/auction.go
@@ -0,0 +1,78 @@
+package auction
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ minipoolutils "github.com/rocket-pool/rocketpool-go/tests/testutils/minipool"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+)
+
+// Create an amount of slashed RPL in the auction contract
+func CreateSlashedRPL(t *testing.T, rp *rocketpool.RocketPool, ownerAccount *accounts.Account, trustedNodeAccount, trustedNodeAccount2 *accounts.Account, userAccount *accounts.Account) error {
+
+ // Stake a large amount of RPL against the node
+ if err := nodeutils.StakeRPL(rp, ownerAccount, trustedNodeAccount, eth.EthToWei(1000000)); err != nil {
+ return err
+ }
+
+ // Make user deposit
+ depositOpts := userAccount.GetTransactor()
+ depositOpts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, depositOpts); err != nil {
+ return err
+ }
+
+ // Create unbonded minipool
+ mp, err := minipoolutils.CreateMinipool(t, rp, ownerAccount, trustedNodeAccount, eth.EthToWei(16), 1)
+ if err != nil {
+ return err
+ }
+
+ // Deposit user ETH to minipool
+ opts := userAccount.GetTransactor()
+ opts.Value = eth.EthToWei(16)
+ if _, err := deposit.Deposit(rp, opts); err != nil {
+ return err
+ }
+
+ // Delay for the time between depositing and staking
+ scrubPeriod, err := trustednode.GetScrubPeriod(rp, nil)
+ if err != nil {
+ return err
+ }
+ err = evm.IncreaseTime(int(scrubPeriod + 1))
+ if err != nil {
+ return fmt.Errorf("error increasing time: %w", err)
+ }
+
+ // Stake minipool
+ if err := minipoolutils.StakeMinipool(rp, mp, trustedNodeAccount); err != nil {
+ return err
+ }
+
+ // Mark minipool as withdrawable with zero end balance
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+ if _, err := minipool.SubmitMinipoolWithdrawable(rp, mp.Address, trustedNodeAccount2.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Distribute balance and finalise pool to send slashed RPL to auction contract
+ if _, err := mp.DistributeBalanceAndFinalise(trustedNodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
diff --git a/bindings/tests/testutils/dao/proposals.go b/bindings/tests/testutils/dao/proposals.go
new file mode 100644
index 000000000..b687a2166
--- /dev/null
+++ b/bindings/tests/testutils/dao/proposals.go
@@ -0,0 +1,48 @@
+package dao
+
+import (
+ "github.com/rocket-pool/rocketpool-go/dao"
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+)
+
+// Pass and execute a proposal
+func PassAndExecuteProposal(rp *rocketpool.RocketPool, proposalId uint64, trustedNodeAccounts []*accounts.Account) error {
+
+ // Get proposal voting delay
+ voteDelayTime, err := trustednodesettings.GetProposalVoteDelayTime(rp, nil)
+ if err != nil {
+ return err
+ }
+
+ // Increase time until proposal voting delay has passed
+ if err := evm.IncreaseTime(int(voteDelayTime)); err != nil {
+ return err
+ }
+
+ // Vote on proposal until passed
+ for _, account := range trustedNodeAccounts {
+ if state, err := dao.GetProposalState(rp, proposalId, nil); err != nil {
+ return err
+ } else if state == rptypes.Succeeded {
+ break
+ }
+ if _, err := trustednodedao.VoteOnProposal(rp, proposalId, true, account.GetTransactor()); err != nil {
+ return err
+ }
+ }
+
+ // Execute proposal
+ if _, err := trustednodedao.ExecuteProposal(rp, proposalId, trustedNodeAccounts[0].GetTransactor()); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
diff --git a/bindings/tests/testutils/evm/mining.go b/bindings/tests/testutils/evm/mining.go
new file mode 100644
index 000000000..3d80324f3
--- /dev/null
+++ b/bindings/tests/testutils/evm/mining.go
@@ -0,0 +1,50 @@
+package evm
+
+import (
+ "github.com/ethereum/go-ethereum/rpc"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+)
+
+// Mine a number of blocks
+func MineBlocks(numBlocks int) error {
+
+ // Initialize RPC client
+ client, err := rpc.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ return err
+ }
+
+ // Make RPC calls
+ for bi := 0; bi < numBlocks; bi++ {
+ if err := client.Call(nil, "evm_mine"); err != nil {
+ return err
+ }
+ }
+
+ // Return
+ return nil
+
+}
+
+// Fast forward to some number of seconds
+func IncreaseTime(time int) error {
+
+ // Initialize RPC client
+ client, err := rpc.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ return err
+ }
+
+ // Make RPC calls
+ if err := client.Call(nil, "evm_increaseTime", time); err != nil {
+ return err
+ }
+ if err := MineBlocks(1); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
diff --git a/bindings/tests/testutils/evm/snapshots.go b/bindings/tests/testutils/evm/snapshots.go
new file mode 100644
index 000000000..a5eba8090
--- /dev/null
+++ b/bindings/tests/testutils/evm/snapshots.go
@@ -0,0 +1,45 @@
+package evm
+
+import (
+ "github.com/ethereum/go-ethereum/rpc"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+)
+
+// The ID of the current snapshot of the EVM state
+var snapshotId string
+
+// Take a snapshot of the EVM state
+func TakeSnapshot() error {
+
+ // Initialize RPC client
+ client, err := rpc.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ return err
+ }
+
+ // Make RPC call
+ var response string
+ if err := client.Call(&response, "evm_snapshot"); err != nil {
+ return err
+ }
+
+ // Set snapshot ID & return
+ snapshotId = response
+ return nil
+
+}
+
+// Restore a snapshot of the EVM state
+func RevertSnapshot() error {
+
+ // Initialize RPC client
+ client, err := rpc.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ return err
+ }
+
+ // Make RPC call & return
+ return client.Call(nil, "evm_revert", snapshotId)
+
+}
diff --git a/bindings/tests/testutils/minipool/minipool.go b/bindings/tests/testutils/minipool/minipool.go
new file mode 100644
index 000000000..30680106e
--- /dev/null
+++ b/bindings/tests/testutils/minipool/minipool.go
@@ -0,0 +1,121 @@
+package minipool
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/validator"
+)
+
+// Minipool created event
+type minipoolCreated struct {
+ Minipool common.Address
+ Node common.Address
+ Time *big.Int
+}
+
+// Create a minipool
+func CreateMinipool(t *testing.T, rp *rocketpool.RocketPool, ownerAccount, nodeAccount *accounts.Account, depositAmount *big.Int, pubkey int) (*minipool.Minipool, error) {
+
+ // Mint & stake RPL required for mininpool
+ rplRequired, err := GetMinipoolRPLRequired(rp)
+ if err != nil {
+ return nil, err
+ }
+ if err := nodeutils.StakeRPL(rp, ownerAccount, nodeAccount, rplRequired); err != nil {
+ return nil, err
+ }
+
+ // Do the node deposit to generate the minipool
+ expectedMinipoolAddress, txReceipt, err := nodeutils.Deposit(t, rp, nodeAccount, depositAmount, pubkey)
+ if err != nil {
+ return nil, fmt.Errorf("error doing node deposit: %w", err)
+ }
+
+ // Get minipool manager contract
+ rocketMinipoolManager, err := rp.GetContract("rocketMinipoolManager")
+ if err != nil {
+ return nil, err
+ }
+
+ // Get created minipool address
+ minipoolCreatedEvents, err := rocketMinipoolManager.GetTransactionEvents(txReceipt, "MinipoolCreated", minipoolCreated{})
+ if err != nil || len(minipoolCreatedEvents) == 0 {
+ return nil, errors.New("error getting minipool created event")
+ }
+ minipoolAddress := minipoolCreatedEvents[0].(minipoolCreated).Minipool
+
+ // Sanity check to verify the created minipool is at the expected address
+ if expectedMinipoolAddress != minipoolAddress {
+ return nil, errors.New(fmt.Sprintf("Expected minipool address %s but got %s", expectedMinipoolAddress.Hex(), minipoolAddress.Hex()))
+ }
+
+ // Return minipool instance
+ return minipool.NewMinipool(rp, minipoolAddress)
+
+}
+
+// Stake a minipool
+func StakeMinipool(rp *rocketpool.RocketPool, mp *minipool.Minipool, nodeAccount *accounts.Account) error {
+
+ // Get validator & deposit data
+ validatorPubkey, err := validator.GetValidatorPubkey(1)
+ if err != nil {
+ return err
+ }
+ withdrawalCredentials, err := minipool.GetMinipoolWithdrawalCredentials(rp, mp.Address, nil)
+ if err != nil {
+ return err
+ }
+ validatorSignature, err := validator.GetValidatorSignature(1)
+ if err != nil {
+ return err
+ }
+ depositDataRoot, err := validator.GetDepositDataRoot(validatorPubkey, withdrawalCredentials, validatorSignature)
+ if err != nil {
+ return err
+ }
+
+ // Stake minipool & return
+ _, err = mp.Stake(validatorSignature, depositDataRoot, nodeAccount.GetTransactor())
+ return err
+
+}
+
+// Get the RPL required per minipool
+func GetMinipoolRPLRequired(rp *rocketpool.RocketPool) (*big.Int, error) {
+
+ // Get data
+ depositUserAmount, err := protocol.GetMinipoolHalfDepositUserAmount(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ minimumPerMinipoolStake, err := protocol.GetMinimumPerMinipoolStake(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+ rplPrice, err := network.GetRPLPrice(rp, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Calculate and return RPL required
+ var tmp big.Int
+ var rplRequired big.Int
+ tmp.Mul(depositUserAmount, eth.EthToWei(minimumPerMinipoolStake))
+ rplRequired.Quo(&tmp, rplPrice)
+ return &rplRequired, nil
+
+}
diff --git a/bindings/tests/testutils/node/deposit.go b/bindings/tests/testutils/node/deposit.go
new file mode 100644
index 000000000..a6bd05aa7
--- /dev/null
+++ b/bindings/tests/testutils/node/deposit.go
@@ -0,0 +1,77 @@
+package node
+
+import (
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/validator"
+)
+
+var salt int64 = 0
+
+// Returns a unique salt for minipool address generation
+func GetSalt() *big.Int {
+ salt += 1
+ return big.NewInt(salt)
+}
+
+// Call deposit on the node using the validator test values
+func Deposit(t *testing.T, rp *rocketpool.RocketPool, nodeAccount *accounts.Account, depositAmount *big.Int, pubkey int) (common.Address, *types.Receipt, error) {
+
+ // Get the next salt
+ salt := GetSalt()
+
+ // Get validator & deposit data
+ depositType, err := node.GetDepositType(rp, depositAmount, nil)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error getting deposit type: %w", err)
+ }
+ validatorPubkey, err := validator.GetValidatorPubkey(pubkey)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error getting validator pubkey: %w", err)
+ }
+ expectedMinipoolAddress, err := utils.GenerateAddress(rp, nodeAccount.Address, depositType, salt, nil)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error generating minipool address: %w", err)
+ }
+ withdrawalCredentials, err := minipool.GetMinipoolWithdrawalCredentials(rp, expectedMinipoolAddress, nil)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error getting minipool withdrawal credentials: %w", err)
+ }
+ validatorSignature, err := validator.GetValidatorSignature(pubkey)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error getting validator signature: %w", err)
+ }
+ depositDataRoot, err := validator.GetDepositDataRoot(validatorPubkey, withdrawalCredentials, validatorSignature)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error getting deposit data root: %w", err)
+ }
+
+ // Make node deposit
+ opts := nodeAccount.GetTransactor()
+ opts.Value = depositAmount
+
+ minNodeFee := 0.0
+ //t.Logf("Deposit:\n\tMin Node Fee: %f\n\tValidator Pubkey: %s\n\tValidator Signature: %s\n\tDeposit Data Root: %s\n\tNode Address: %s\n\tSalt: %s\n\tExpected Minipool: %s\n",
+ // minNodeFee, validatorPubkey.Hex(), validatorSignature.Hex(), depositDataRoot.Hex(), nodeAccount.Address.Hex(), GetDefaultSalt().String(), expectedMinipoolAddress.Hex())
+ tx, err := node.Deposit(rp, minNodeFee, validatorPubkey, validatorSignature, depositDataRoot, salt, expectedMinipoolAddress, opts)
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error executing deposit: %w", err)
+ }
+ txReceipt, err := utils.WaitForTransaction(rp.Client, tx.Hash())
+ if err != nil {
+ return common.Address{}, nil, fmt.Errorf("Error waiting for deposit transaction: %w", err)
+ }
+
+ return expectedMinipoolAddress, txReceipt, nil
+}
diff --git a/bindings/tests/testutils/node/node.go b/bindings/tests/testutils/node/node.go
new file mode 100644
index 000000000..dcaf52ab2
--- /dev/null
+++ b/bindings/tests/testutils/node/node.go
@@ -0,0 +1,74 @@
+package node
+
+import (
+ "fmt"
+
+ trustednodedao "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ trustednodesettings "github.com/rocket-pool/rocketpool-go/settings/trustednode"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+// Trusted node counter
+var trustedNodeIndex = 0
+
+// Register a trusted node
+func RegisterTrustedNode(rp *rocketpool.RocketPool, ownerAccount *accounts.Account, trustedNodeAccount *accounts.Account) error {
+
+ // Register node
+ if _, err := node.RegisterNode(rp, "Australia/Brisbane", trustedNodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Bootstrap trusted node DAO member
+ if _, err := trustednodedao.BootstrapMember(rp, fmt.Sprintf("tn%d", trustedNodeIndex), fmt.Sprintf("tn%d@rocketpool.net", trustedNodeIndex), trustedNodeAccount.Address, ownerAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Mint trusted node RPL bond
+ if err := MintTrustedNodeBond(rp, ownerAccount, trustedNodeAccount); err != nil {
+ return err
+ }
+
+ // Join trusted node DAO
+ if _, err := trustednodedao.Join(rp, trustedNodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Increment trusted node counter & return
+ trustedNodeIndex++
+ return nil
+
+}
+
+// Mint trusted node DAO RPL bond to a node account and approve it for spending
+func MintTrustedNodeBond(rp *rocketpool.RocketPool, ownerAccount *accounts.Account, trustedNodeAccount *accounts.Account) error {
+
+ // Get RPL bond amount
+ rplBondAmount, err := trustednodesettings.GetRPLBond(rp, nil)
+ if err != nil {
+ return err
+ }
+
+ // Get RocketDAONodeTrustedActions contract address
+ rocketDAONodeTrustedActionsAddress, err := rp.GetAddress("rocketDAONodeTrustedActions")
+ if err != nil {
+ return err
+ }
+
+ // Mint RPL to node & allow trusted node DAO contract to spend it
+ if err := rplutils.MintRPL(rp, ownerAccount, trustedNodeAccount, rplBondAmount); err != nil {
+ return err
+ }
+ if _, err := tokens.ApproveRPL(rp, *rocketDAONodeTrustedActionsAddress, rplBondAmount, trustedNodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
diff --git a/bindings/tests/testutils/node/staking.go b/bindings/tests/testutils/node/staking.go
new file mode 100644
index 000000000..70ef91dae
--- /dev/null
+++ b/bindings/tests/testutils/node/staking.go
@@ -0,0 +1,37 @@
+package node
+
+import (
+ "math/big"
+
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+// Mint & stake an amount of RPL against a node
+func StakeRPL(rp *rocketpool.RocketPool, ownerAccount, nodeAccount *accounts.Account, amount *big.Int) error {
+
+ // Get RocketNodeStaking contract address
+ rocketNodeStakingAddress, err := rp.GetAddress("rocketNodeStaking")
+ if err != nil {
+ return err
+ }
+
+ // Mint, approve & stake RPL
+ if err := rplutils.MintRPL(rp, ownerAccount, nodeAccount, amount); err != nil {
+ return err
+ }
+ if _, err := tokens.ApproveRPL(rp, *rocketNodeStakingAddress, amount, nodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+ if _, err := node.StakeRPL(rp, amount, nodeAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
diff --git a/bindings/tests/testutils/tokens/reth/reth.go b/bindings/tests/testutils/tokens/reth/reth.go
new file mode 100644
index 000000000..2d73b5170
--- /dev/null
+++ b/bindings/tests/testutils/tokens/reth/reth.go
@@ -0,0 +1,28 @@
+package tokens
+
+import (
+ "math/big"
+
+ "github.com/rocket-pool/rocketpool-go/deposit"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+// Mint an amount of rETH to an account
+func MintRETH(rp *rocketpool.RocketPool, toAccount *accounts.Account, amount *big.Int) error {
+
+ // Get ETH value of amount
+ ethValue, err := tokens.GetETHValueOfRETH(rp, amount, nil)
+ if err != nil {
+ return err
+ }
+
+ // Deposit from account to mint rETH
+ opts := toAccount.GetTransactor()
+ opts.Value = ethValue
+ _, err = deposit.Deposit(rp, opts)
+ return err
+
+}
diff --git a/bindings/tests/testutils/tokens/rpl/rpl.go b/bindings/tests/testutils/tokens/rpl/rpl.go
new file mode 100644
index 000000000..443d20a76
--- /dev/null
+++ b/bindings/tests/testutils/tokens/rpl/rpl.go
@@ -0,0 +1,48 @@
+package rpl
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+// Mint an amount of RPL to an account
+func MintRPL(rp *rocketpool.RocketPool, ownerAccount *accounts.Account, toAccount *accounts.Account, amount *big.Int) error {
+
+ // Get RPL token contract address
+ rocketTokenRPLAddress, err := rp.GetAddress("rocketTokenRPL")
+ if err != nil {
+ return err
+ }
+
+ // Mint, approve & swap fixed-supply RPL
+ if err := MintFixedSupplyRPL(rp, ownerAccount, toAccount, amount); err != nil {
+ return err
+ }
+ if _, err := tokens.ApproveFixedSupplyRPL(rp, *rocketTokenRPLAddress, amount, toAccount.GetTransactor()); err != nil {
+ return err
+ }
+ if _, err := tokens.SwapFixedSupplyRPLForRPL(rp, amount, toAccount.GetTransactor()); err != nil {
+ return err
+ }
+
+ // Return
+ return nil
+
+}
+
+// Mint an amount of fixed-supply RPL to an account
+func MintFixedSupplyRPL(rp *rocketpool.RocketPool, ownerAccount *accounts.Account, toAccount *accounts.Account, amount *big.Int) error {
+ rocketTokenFixedSupplyRPL, err := rp.GetContract("rocketTokenRPLFixedSupply")
+ if err != nil {
+ return err
+ }
+ if _, err := rocketTokenFixedSupplyRPL.Transact(ownerAccount.GetTransactor(), "mint", toAccount.Address, amount); err != nil {
+ return fmt.Errorf("error minting fixed-supply RPL tokens to %s: %w", toAccount.Address.Hex(), err)
+ }
+ return nil
+}
diff --git a/bindings/tests/testutils/validator/deposit-data.go b/bindings/tests/testutils/validator/deposit-data.go
new file mode 100644
index 000000000..ddd1faa0d
--- /dev/null
+++ b/bindings/tests/testutils/validator/deposit-data.go
@@ -0,0 +1,59 @@
+package validator
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/prysmaticlabs/go-ssz"
+
+ "github.com/rocket-pool/rocketpool-go/types"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+)
+
+// Deposit settings
+const depositAmount = 16000000000 // gwei
+
+// Deposit data
+type depositData struct {
+ PublicKey []byte `ssz-size:"48"`
+ WithdrawalCredentials []byte `ssz-size:"32"`
+ Amount uint64
+ Signature []byte `ssz-size:"96"`
+}
+
+// Get the validator pubkey
+func GetValidatorPubkey(pubkey int) (types.ValidatorPubkey, error) {
+ if pubkey == 1 {
+ return types.HexToValidatorPubkey(tests.ValidatorPubkey)
+ } else if pubkey == 2 {
+ return types.HexToValidatorPubkey(tests.ValidatorPubkey2)
+ } else if pubkey == 3 {
+ return types.HexToValidatorPubkey(tests.ValidatorPubkey3)
+ } else {
+ return types.ValidatorPubkey{}, fmt.Errorf("Invalid pubkey index %d", pubkey)
+ }
+}
+
+// Get the validator deposit signature
+func GetValidatorSignature(pubkey int) (types.ValidatorSignature, error) {
+ if pubkey == 1 {
+ return types.HexToValidatorSignature(tests.ValidatorSignature)
+ } else if pubkey == 2 {
+ return types.HexToValidatorSignature(tests.ValidatorSignature2)
+ } else if pubkey == 3 {
+ return types.HexToValidatorSignature(tests.ValidatorSignature3)
+ } else {
+ return types.ValidatorSignature{}, fmt.Errorf("Invalid pubkey index %d", pubkey)
+ }
+}
+
+// Get the validator deposit depositDataRoot
+func GetDepositDataRoot(validatorPubkey types.ValidatorPubkey, withdrawalCredentials common.Hash, validatorSignature types.ValidatorSignature) (common.Hash, error) {
+ return ssz.HashTreeRoot(depositData{
+ PublicKey: validatorPubkey.Bytes(),
+ WithdrawalCredentials: withdrawalCredentials[:],
+ Amount: depositAmount,
+ Signature: validatorSignature.Bytes(),
+ })
+}
diff --git a/bindings/tests/tokens/main_test.go b/bindings/tests/tokens/main_test.go
new file mode 100644
index 000000000..54e00526d
--- /dev/null
+++ b/bindings/tests/tokens/main_test.go
@@ -0,0 +1,68 @@
+package tokens
+
+import (
+ "log"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+)
+
+var (
+ client *ethclient.Client
+ rp *rocketpool.RocketPool
+
+ ownerAccount *accounts.Account
+ trustedNodeAccount *accounts.Account
+ userAccount1 *accounts.Account
+ userAccount2 *accounts.Account
+ swcAccount *accounts.Account
+)
+
+func TestMain(m *testing.M) {
+ var err error
+
+ // Initialize eth client
+ client, err = ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize contract manager
+ rp, err = rocketpool.NewRocketPool(client, common.HexToAddress(tests.RocketStorageAddress))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize accounts
+ ownerAccount, err = accounts.GetAccount(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ trustedNodeAccount, err = accounts.GetAccount(1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount1, err = accounts.GetAccount(7)
+ if err != nil {
+ log.Fatal(err)
+ }
+ userAccount2, err = accounts.GetAccount(8)
+ if err != nil {
+ log.Fatal(err)
+ }
+ swcAccount, err = accounts.GetAccount(9)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Run tests
+ os.Exit(m.Run())
+
+}
diff --git a/bindings/tests/tokens/reth_test.go b/bindings/tests/tokens/reth_test.go
new file mode 100644
index 000000000..915b86878
--- /dev/null
+++ b/bindings/tests/tokens/reth_test.go
@@ -0,0 +1,240 @@
+package tokens
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ nodeutils "github.com/rocket-pool/rocketpool-go/tests/testutils/node"
+ rethutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/reth"
+)
+
+// GetRETHContractETHBalance test under minipool.TestWithdrawValidatorBalance
+// GetRETHTotalCollateral test under minipool.TestWithdrawValidatorBalance
+// GetRETHCollateralRate test under minipool.TestWithdrawValidatorBalance
+
+func TestRETHBalances(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint rETH
+ rethAmount := eth.EthToWei(100)
+ if err := rethutils.MintRETH(rp, userAccount1, rethAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check rETH total supply
+ if rethTotalSupply, err := tokens.GetRETHTotalSupply(rp, nil); err != nil {
+ t.Error(err)
+ } else if rethTotalSupply.Cmp(rethAmount) != 0 {
+ t.Errorf("Incorrect rETH total supply %s", rethTotalSupply.String())
+ }
+
+ // Get & check rETH account balance
+ if rethBalance, err := tokens.GetRETHBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if rethBalance.Cmp(rethAmount) != 0 {
+ t.Errorf("Incorrect rETH account balance %s", rethBalance.String())
+ }
+
+}
+
+func TestTransferRETH(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint rETH
+ rethAmount := eth.EthToWei(100)
+ if err := rethutils.MintRETH(rp, userAccount1, rethAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mine pre-requisite 5760 blocks before being able to transfer
+ if err := evm.MineBlocks(5760); err != nil {
+ t.Fatal(err)
+ }
+
+ // Transfer rETH
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.TransferRETH(rp, toAddress, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check rETH account balance
+ if rethBalance, err := tokens.GetRETHBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if rethBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect rETH account balance %s", rethBalance.String())
+ }
+
+}
+
+func TestTransferFromRETH(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint rETH
+ rethAmount := eth.EthToWei(100)
+ if err := rethutils.MintRETH(rp, userAccount1, rethAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Approve rETH spender
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.ApproveRETH(rp, userAccount2.Address, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check spender allowance
+ if allowance, err := tokens.GetRETHAllowance(rp, userAccount1.Address, userAccount2.Address, nil); err != nil {
+ t.Error(err)
+ } else if allowance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect rETH spender allowance %s", allowance.String())
+ }
+
+ // Mine pre-requisite 5760 blocks before being able to transfer
+ if err := evm.MineBlocks(5760); err != nil {
+ t.Fatal(err)
+ }
+
+ // Transfer rETH from account
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ if _, err := tokens.TransferFromRETH(rp, userAccount1.Address, toAddress, sendAmount, userAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check rETH account balance
+ if rethBalance, err := tokens.GetRETHBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if rethBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect rETH account balance %s", rethBalance.String())
+ }
+
+}
+
+func TestRETHExchangeRate(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Register trusted node
+ if err := nodeutils.RegisterTrustedNode(rp, ownerAccount, trustedNodeAccount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Submit network balances
+ if _, err := network.SubmitBalances(rp, 1, eth.EthToWei(100), eth.EthToWei(100), eth.EthToWei(50), trustedNodeAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check ETH value of rETH amount
+ rethAmount := eth.EthToWei(1)
+ if ethValue, err := tokens.GetETHValueOfRETH(rp, rethAmount, nil); err != nil {
+ t.Error(err)
+ } else if ethValue.Cmp(eth.EthToWei(2)) != 0 {
+ t.Errorf("Incorrect ETH value %s of rETH amount %s", ethValue.String(), rethAmount.String())
+ }
+
+ // Get & check rETH value of ETH amount
+ ethAmount := eth.EthToWei(2)
+ if rethValue, err := tokens.GetRETHValueOfETH(rp, ethAmount, nil); err != nil {
+ t.Error(err)
+ } else if rethValue.Cmp(eth.EthToWei(1)) != 0 {
+ t.Errorf("Incorrect rETH value %s of ETH amount %s", rethValue.String(), ethAmount.String())
+ }
+
+ // Get & check ETH : rETH exchange rate
+ if exchangeRate, err := tokens.GetRETHExchangeRate(rp, nil); err != nil {
+ t.Error(err)
+ } else if exchangeRate != 2 {
+ t.Errorf("Incorrect ETH : rETH exchange rate %f : 1", exchangeRate)
+ }
+
+}
+
+func TestBurnRETH(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint rETH
+ rethAmount := eth.EthToWei(100)
+ if err := rethutils.MintRETH(rp, userAccount1, rethAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial balances
+ balances1, err := tokens.GetBalances(rp, userAccount1.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Mine pre-requisite 5760 blocks before being able to burn
+ if err := evm.MineBlocks(5760); err != nil {
+ t.Fatal(err)
+ }
+
+ // Burn rETH
+ burnAmount := eth.EthToWei(50)
+ if _, err := tokens.BurnRETH(rp, burnAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated balances
+ balances2, err := tokens.GetBalances(rp, userAccount1.Address, nil)
+ if err != nil {
+ t.Fatal(err)
+ } else {
+ if balances2.RETH.Cmp(balances1.RETH) != -1 {
+ t.Error("rETH balance did not decrease after burning rETH")
+ }
+ if balances2.ETH.Cmp(balances1.ETH) != 1 {
+ t.Error("ETH balance did not increase after burning rETH")
+ }
+ }
+
+}
diff --git a/bindings/tests/tokens/rpl_fixed_test.go b/bindings/tests/tokens/rpl_fixed_test.go
new file mode 100644
index 000000000..3a7f1bbdc
--- /dev/null
+++ b/bindings/tests/tokens/rpl_fixed_test.go
@@ -0,0 +1,127 @@
+package tokens
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+func TestFixedSupplyRPLBalances(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint fixed-supply RPL
+ fixedRplAmount := eth.EthToWei(100)
+ if err := rplutils.MintFixedSupplyRPL(rp, ownerAccount, userAccount1, fixedRplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check fixed-supply RPL total supply
+ if fixedRplTotalSupply, err := tokens.GetFixedSupplyRPLTotalSupply(rp, nil); err != nil {
+ t.Error(err)
+ } else if fixedRplTotalSupply.Cmp(fixedRplAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL total supply %s", fixedRplTotalSupply.String())
+ }
+
+ // Get & check fixed-supply RPL account balance
+ if fixedRplBalance, err := tokens.GetFixedSupplyRPLBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if fixedRplBalance.Cmp(fixedRplAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL account balance %s", fixedRplBalance.String())
+ }
+
+}
+
+func TestTransferFixedSupplyRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint fixed-supply RPL
+ fixedRplAmount := eth.EthToWei(100)
+ if err := rplutils.MintFixedSupplyRPL(rp, ownerAccount, userAccount1, fixedRplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Transfer fixed-supply RPL
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.TransferFixedSupplyRPL(rp, toAddress, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check fixed-supply RPL account balance
+ if fixedRplBalance, err := tokens.GetFixedSupplyRPLBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if fixedRplBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL account balance %s", fixedRplBalance.String())
+ }
+
+}
+
+func TestTransferFromFixedSupplyRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint fixed-supply RPL
+ fixedRplAmount := eth.EthToWei(100)
+ if err := rplutils.MintFixedSupplyRPL(rp, ownerAccount, userAccount1, fixedRplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Approve fixed-supply RPL spender
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.ApproveFixedSupplyRPL(rp, userAccount2.Address, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check spender allowance
+ if allowance, err := tokens.GetFixedSupplyRPLAllowance(rp, userAccount1.Address, userAccount2.Address, nil); err != nil {
+ t.Error(err)
+ } else if allowance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL spender allowance %s", allowance.String())
+ }
+
+ // Transfer fixed-supply RPL from account
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ if _, err := tokens.TransferFromFixedSupplyRPL(rp, userAccount1.Address, toAddress, sendAmount, userAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check fixed-supply RPL account balance
+ if fixedRplBalance, err := tokens.GetFixedSupplyRPLBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if fixedRplBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL account balance %s", fixedRplBalance.String())
+ }
+
+}
diff --git a/bindings/tests/tokens/rpl_test.go b/bindings/tests/tokens/rpl_test.go
new file mode 100644
index 000000000..4c785a85a
--- /dev/null
+++ b/bindings/tests/tokens/rpl_test.go
@@ -0,0 +1,218 @@
+package tokens
+
+import (
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+func TestRPLBalances(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint RPL
+ rplAmount := eth.EthToWei(100)
+ if err := rplutils.MintRPL(rp, ownerAccount, userAccount1, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check RPL account balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect RPL account balance %s", rplBalance.String())
+ }
+
+ // Get & check RPL total supply
+ initialTotalSupply := eth.EthToWei(18000000)
+ if rplTotalSupply, err := tokens.GetRPLTotalSupply(rp, nil); err != nil {
+ t.Error(err)
+ } else if rplTotalSupply.Cmp(initialTotalSupply) != 0 {
+ t.Errorf("Incorrect RPL total supply %s", rplTotalSupply.String())
+ }
+
+}
+
+func TestTransferRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint RPL
+ rplAmount := eth.EthToWei(100)
+ if err := rplutils.MintRPL(rp, ownerAccount, userAccount1, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Transfer RPL
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.TransferRPL(rp, toAddress, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check RPL account balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect RPL account balance %s", rplBalance.String())
+ }
+
+}
+
+func TestTransferFromRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint RPL
+ rplAmount := eth.EthToWei(100)
+ if err := rplutils.MintRPL(rp, ownerAccount, userAccount1, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Approve RPL spender
+ sendAmount := eth.EthToWei(50)
+ if _, err := tokens.ApproveRPL(rp, userAccount2.Address, sendAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check spender allowance
+ if allowance, err := tokens.GetRPLAllowance(rp, userAccount1.Address, userAccount2.Address, nil); err != nil {
+ t.Error(err)
+ } else if allowance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect RPL spender allowance %s", allowance.String())
+ }
+
+ // Transfer RPL from account
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ if _, err := tokens.TransferFromRPL(rp, userAccount1.Address, toAddress, sendAmount, userAccount2.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check RPL account balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, toAddress, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect RPL account balance %s", rplBalance.String())
+ }
+
+}
+
+func TestMintInflationRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Constants
+ oneDay := 24 * 60 * 60
+
+ // Start RPL inflation
+ if _, err := protocol.BootstrapInflationStartTime(rp, uint64(time.Now().Unix()+3600), ownerAccount.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Increase time until rewards are available
+ if err := evm.IncreaseTime(3600 + oneDay); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get initial total supply
+ rplTotalSupply1, err := tokens.GetRPLTotalSupply(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint RPL from inflation
+ if _, err := tokens.MintInflationRPL(rp, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check updated total supply
+ rplTotalSupply2, err := tokens.GetRPLTotalSupply(rp, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if rplTotalSupply2.Cmp(rplTotalSupply1) != 1 {
+ t.Errorf("Incorrect updated RPL total supply %s", rplTotalSupply2.String())
+ }
+
+}
+
+func TestSwapFixedSupplyRPLForRPL(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint fixed-supply RPL
+ rplAmount := eth.EthToWei(100)
+ if err := rplutils.MintFixedSupplyRPL(rp, ownerAccount, userAccount1, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Approve fixed-supply RPL spend
+ rocketTokenRPLAddress, err := rp.GetAddress("rocketTokenRPL")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := tokens.ApproveFixedSupplyRPL(rp, *rocketTokenRPLAddress, rplAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Swap fixed-supply RP for RPL
+ if _, err := tokens.SwapFixedSupplyRPLForRPL(rp, rplAmount, userAccount1.GetTransactor()); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check RPL account balance
+ if rplBalance, err := tokens.GetRPLBalance(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else if rplBalance.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect RPL account balance %s", rplBalance.String())
+ }
+
+}
diff --git a/bindings/tests/tokens/tokens_test.go b/bindings/tests/tokens/tokens_test.go
new file mode 100644
index 000000000..2d6d3c1f6
--- /dev/null
+++ b/bindings/tests/tokens/tokens_test.go
@@ -0,0 +1,63 @@
+package tokens
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/tokens"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ rethutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/reth"
+ rplutils "github.com/rocket-pool/rocketpool-go/tests/testutils/tokens/rpl"
+)
+
+func TestTokenBalances(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Mint rETH
+ rethAmount := eth.EthToWei(102)
+ if err := rethutils.MintRETH(rp, userAccount1, rethAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint RPL
+ rplAmount := eth.EthToWei(103)
+ if err := rplutils.MintRPL(rp, ownerAccount, userAccount1, rplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Mint fixed-supply RPL
+ fixedRplAmount := eth.EthToWei(104)
+ if err := rplutils.MintFixedSupplyRPL(rp, ownerAccount, userAccount1, fixedRplAmount); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check token balances
+ if balances, err := tokens.GetBalances(rp, userAccount1.Address, nil); err != nil {
+ t.Error(err)
+ } else {
+ if balances.ETH.Cmp(big.NewInt(0)) != 1 {
+ t.Errorf("Incorrect ETH balance %s", balances.ETH.String())
+ }
+ if balances.RETH.Cmp(rethAmount) != 0 {
+ t.Errorf("Incorrect rETH balance %s", balances.RETH.String())
+ }
+ if balances.RPL.Cmp(rplAmount) != 0 {
+ t.Errorf("Incorrect RPL balance %s", balances.RPL.String())
+ }
+ if balances.FixedSupplyRPL.Cmp(fixedRplAmount) != 0 {
+ t.Errorf("Incorrect fixed-supply RPL balance %s", balances.FixedSupplyRPL.String())
+ }
+ }
+
+}
diff --git a/bindings/tests/utils/eth/transactions_test.go b/bindings/tests/utils/eth/transactions_test.go
new file mode 100644
index 000000000..1c99e6248
--- /dev/null
+++ b/bindings/tests/utils/eth/transactions_test.go
@@ -0,0 +1,65 @@
+package eth
+
+import (
+ "context"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethclient"
+
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+
+ "github.com/rocket-pool/rocketpool-go/tests"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/evm"
+ "github.com/rocket-pool/rocketpool-go/utils"
+)
+
+func TestSendTransaction(t *testing.T) {
+
+ // State snapshotting
+ if err := evm.TakeSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := evm.RevertSnapshot(); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ // Initialize eth client
+ client, err := ethclient.Dial(tests.Eth1ProviderAddress)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Initialize accounts
+ userAccount, err := accounts.GetAccount(9)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Transaction parameters
+ toAddress := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ sendAmount := eth.EthToWei(50)
+
+ // Send transaction
+ opts := userAccount.GetTransactor()
+ opts.Value = sendAmount
+ hash, err := eth.SendTransaction(client, toAddress, big.NewInt(1337), opts) // Ganache's default chain ID is 1337
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := utils.WaitForTransaction(client, hash); err != nil {
+ t.Fatal(err)
+ }
+
+ // Get & check to address balance
+ if balance, err := client.BalanceAt(context.Background(), toAddress, nil); err != nil {
+ t.Error(err)
+ } else if balance.Cmp(sendAmount) != 0 {
+ t.Errorf("Incorrect to address balance %s", balance.String())
+ }
+
+}
diff --git a/bindings/tests/utils/eth/units_test.go b/bindings/tests/utils/eth/units_test.go
new file mode 100644
index 000000000..afea60810
--- /dev/null
+++ b/bindings/tests/utils/eth/units_test.go
@@ -0,0 +1,38 @@
+package eth
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+func TestConversion(t *testing.T) {
+
+ // Equivalent unit amounts
+ weiAmount := new(big.Int)
+ weiAmount.SetString("999999999999999000000", 0)
+ var gweiAmount float64 = 999999999999.999000000
+ var ethAmount float64 = 999.999999999999000000
+
+ // Convert wei to eth
+ if toEthAmount := eth.WeiToEth(weiAmount); toEthAmount != ethAmount {
+ t.Errorf("Incorrect eth amount %f", toEthAmount)
+ }
+
+ // Convert eth to wei
+ if toWeiAmount := eth.EthToWei(ethAmount); toWeiAmount.Cmp(weiAmount) != 0 {
+ t.Errorf("Incorrect wei amount %s", toWeiAmount.String())
+ }
+
+ // Convert wei to gigawei
+ if toGweiAmount := eth.WeiToGwei(weiAmount); toGweiAmount != gweiAmount {
+ t.Errorf("Incorrect gwei amount %f", toGweiAmount)
+ }
+
+ // Convert eth to gwei
+ if toWeiAmount := eth.GweiToWei(gweiAmount); toWeiAmount.Cmp(weiAmount) != 0 {
+ t.Errorf("Incorrect wei amount %s", toWeiAmount.String())
+ }
+
+}
diff --git a/bindings/tests/utils/stage4_bootstrap.go b/bindings/tests/utils/stage4_bootstrap.go
new file mode 100644
index 000000000..3fc7d7ecf
--- /dev/null
+++ b/bindings/tests/utils/stage4_bootstrap.go
@@ -0,0 +1,30 @@
+package utils
+
+import (
+ "time"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/settings/protocol"
+ "github.com/rocket-pool/rocketpool-go/tests/testutils/accounts"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// Bootstrap all of the parameters to mimic Stage 4 so the unit tests work correctly
+func Stage4Bootstrap(rp *rocketpool.RocketPool, ownerAccount *accounts.Account) {
+
+ opts := ownerAccount.GetTransactor()
+
+ protocol.BootstrapDepositEnabled(rp, true, opts)
+ protocol.BootstrapAssignDepositsEnabled(rp, true, opts)
+ protocol.BootstrapMaximumDepositPoolSize(rp, eth.EthToWei(1000), opts)
+ protocol.BootstrapNodeRegistrationEnabled(rp, true, opts)
+ protocol.BootstrapNodeDepositEnabled(rp, true, opts)
+ protocol.BootstrapMinipoolSubmitWithdrawableEnabled(rp, true, opts)
+ protocol.BootstrapMinimumNodeFee(rp, 0.05, opts)
+ protocol.BootstrapTargetNodeFee(rp, 0.1, opts)
+ protocol.BootstrapMaximumNodeFee(rp, 0.2, opts)
+ protocol.BootstrapNodeFeeDemandRange(rp, eth.EthToWei(1000), opts)
+ protocol.BootstrapInflationStartTime(rp,
+ uint64(time.Now().Unix()+(60*60*24*14)), opts)
+
+}
diff --git a/bindings/tokens/reth.go b/bindings/tokens/reth.go
new file mode 100644
index 000000000..a65a57b4b
--- /dev/null
+++ b/bindings/tokens/reth.go
@@ -0,0 +1,211 @@
+package tokens
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+//
+// Core ERC-20 functions
+//
+
+// Get rETH total supply
+func GetRETHTotalSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return totalSupply(rocketTokenRETH, "rETH", opts)
+}
+
+// Get rETH balance
+func GetRETHBalance(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return balanceOf(rocketTokenRETH, "rETH", address, opts)
+}
+
+// Get rETH allowance
+func GetRETHAllowance(rp *rocketpool.RocketPool, owner, spender common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return allowance(rocketTokenRETH, "rETH", owner, spender, opts)
+}
+
+// Estimate the gas of TransferRETH
+func EstimateTransferRETHGas(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferGas(rocketTokenRETH, "rETH", to, amount, opts)
+}
+
+// Transfer rETH
+func TransferRETH(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transfer(rocketTokenRETH, "rETH", to, amount, opts)
+}
+
+// Estimate the gas of ApproveRETH
+func EstimateApproveRETHGas(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateApproveGas(rocketTokenRETH, "rETH", spender, amount, opts)
+}
+
+// Approve a rETH spender
+func ApproveRETH(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return approve(rocketTokenRETH, "rETH", spender, amount, opts)
+}
+
+// Estimate the gas of TransferFromRETH
+func EstimateTransferFromRETHGas(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferFromGas(rocketTokenRETH, "rETH", from, to, amount, opts)
+}
+
+// Transfer rETH from a sender
+func TransferFromRETH(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transferFrom(rocketTokenRETH, "rETH", from, to, amount, opts)
+}
+
+//
+// rETH functions
+//
+
+// Get the rETH contract ETH balance
+func GetRETHContractETHBalance(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return contractETHBalance(rp, rocketTokenRETH, opts)
+}
+
+// Get the ETH value of an amount of rETH
+func GetETHValueOfRETH(rp *rocketpool.RocketPool, rethAmount *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ ethValue := new(*big.Int)
+ if err := rocketTokenRETH.Call(opts, ethValue, "getEthValue", rethAmount); err != nil {
+ return nil, fmt.Errorf("error getting ETH value of rETH amount: %w", err)
+ }
+ return *ethValue, nil
+}
+
+// Get the rETH value of an amount of ETH
+func GetRETHValueOfETH(rp *rocketpool.RocketPool, ethAmount *big.Int, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rethValue := new(*big.Int)
+ if err := rocketTokenRETH.Call(opts, rethValue, "getRethValue", ethAmount); err != nil {
+ return nil, fmt.Errorf("error getting rETH value of ETH amount: %w", err)
+ }
+ return *rethValue, nil
+}
+
+// Get the current ETH : rETH exchange rate
+func GetRETHExchangeRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ exchangeRate := new(*big.Int)
+ if err := rocketTokenRETH.Call(opts, exchangeRate, "getExchangeRate"); err != nil {
+ return 0, fmt.Errorf("error getting rETH exchange rate: %w", err)
+ }
+ return eth.WeiToEth(*exchangeRate), nil
+}
+
+// Get the total amount of ETH collateral available for rETH trades
+func GetRETHTotalCollateral(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ totalCollateral := new(*big.Int)
+ if err := rocketTokenRETH.Call(opts, totalCollateral, "getTotalCollateral"); err != nil {
+ return nil, fmt.Errorf("error getting rETH total collateral: %w", err)
+ }
+ return *totalCollateral, nil
+}
+
+// Get the rETH collateralization rate
+func GetRETHCollateralRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (float64, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, opts)
+ if err != nil {
+ return 0, err
+ }
+ collateralRate := new(*big.Int)
+ if err := rocketTokenRETH.Call(opts, collateralRate, "getCollateralRate"); err != nil {
+ return 0, fmt.Errorf("error getting rETH collateral rate: %w", err)
+ }
+ return eth.WeiToEth(*collateralRate), nil
+}
+
+// Estimate the gas of BurnRETH
+func EstimateBurnRETHGas(rp *rocketpool.RocketPool, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketTokenRETH.GetTransactionGasInfo(opts, "burn", amount)
+}
+
+// Burn rETH for ETH
+func BurnRETH(rp *rocketpool.RocketPool, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRETH, err := getRocketTokenRETH(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketTokenRETH.Transact(opts, "burn", amount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error burning rETH: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+//
+// Contracts
+//
+
+// Get contracts
+var rocketTokenRETHLock sync.Mutex
+
+func getRocketTokenRETH(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketTokenRETHLock.Lock()
+ defer rocketTokenRETHLock.Unlock()
+ return rp.GetContract("rocketTokenRETH", opts)
+}
diff --git a/bindings/tokens/rpl-fixed.go b/bindings/tokens/rpl-fixed.go
new file mode 100644
index 000000000..bebbad1e3
--- /dev/null
+++ b/bindings/tokens/rpl-fixed.go
@@ -0,0 +1,109 @@
+package tokens
+
+import (
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+//
+// Core ERC-20 functions
+//
+
+// Get fixed-supply RPL total supply
+func GetFixedSupplyRPLTotalSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return totalSupply(rocketTokenFixedSupplyRPL, "fixed-supply RPL", opts)
+}
+
+// Get fixed-supply RPL balance
+func GetFixedSupplyRPLBalance(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return balanceOf(rocketTokenFixedSupplyRPL, "fixed-supply RPL", address, opts)
+}
+
+// Get fixed-supply RPL allowance
+func GetFixedSupplyRPLAllowance(rp *rocketpool.RocketPool, owner, spender common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return allowance(rocketTokenFixedSupplyRPL, "fixed-supply RPL", owner, spender, opts)
+}
+
+// Estimate the gas of TransferFixedSupplyRPL
+func EstimateTransferFixedSupplyRPLGas(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferGas(rocketTokenFixedSupplyRPL, "fixed-supply RPL", to, amount, opts)
+}
+
+// Transfer fixed-supply RPL
+func TransferFixedSupplyRPL(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transfer(rocketTokenFixedSupplyRPL, "fixed-supply RPL", to, amount, opts)
+}
+
+// Estimate the gas of ApproveFixedSupplyRPL
+func EstimateApproveFixedSupplyRPLGas(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateApproveGas(rocketTokenFixedSupplyRPL, "fixed-supply RPL", spender, amount, opts)
+}
+
+// Approve an fixed-supply RPL spender
+func ApproveFixedSupplyRPL(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return approve(rocketTokenFixedSupplyRPL, "fixed-supply RPL", spender, amount, opts)
+}
+
+// Estimate the gas of TransferFromFixedSupplyRPL
+func EstimateTransferFromFixedSupplyRPLGas(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferFromGas(rocketTokenFixedSupplyRPL, "fixed-supply RPL", from, to, amount, opts)
+}
+
+// Transfer fixed-supply RPL from a sender
+func TransferFromFixedSupplyRPL(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenFixedSupplyRPL, err := getRocketTokenRPLFixedSupply(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transferFrom(rocketTokenFixedSupplyRPL, "fixed-supply RPL", from, to, amount, opts)
+}
+
+//
+// Contracts
+//
+
+// Get contracts
+var rocketTokenFixedSupplyRPLLock sync.Mutex
+
+func getRocketTokenRPLFixedSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketTokenFixedSupplyRPLLock.Lock()
+ defer rocketTokenFixedSupplyRPLLock.Unlock()
+ return rp.GetContract("rocketTokenRPLFixedSupply", opts)
+}
diff --git a/bindings/tokens/rpl.go b/bindings/tokens/rpl.go
new file mode 100644
index 000000000..80314e2a4
--- /dev/null
+++ b/bindings/tokens/rpl.go
@@ -0,0 +1,185 @@
+package tokens
+
+import (
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+//
+// Core ERC-20 functions
+//
+
+// Get RPL total supply
+func GetRPLTotalSupply(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return totalSupply(rocketTokenRPL, "RPL", opts)
+}
+
+// Get RPL balance
+func GetRPLBalance(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return balanceOf(rocketTokenRPL, "RPL", address, opts)
+}
+
+// Get RPL allowance
+func GetRPLAllowance(rp *rocketpool.RocketPool, owner, spender common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ return allowance(rocketTokenRPL, "RPL", owner, spender, opts)
+}
+
+// Estimate the gas of TransferRPL
+func EstimateTransferRPLGas(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferGas(rocketTokenRPL, "RPL", to, amount, opts)
+}
+
+// Transfer RPL
+func TransferRPL(rp *rocketpool.RocketPool, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transfer(rocketTokenRPL, "RPL", to, amount, opts)
+}
+
+// Estimate the gas of ApproveRPL
+func EstimateApproveRPLGas(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateApproveGas(rocketTokenRPL, "RPL", spender, amount, opts)
+}
+
+// Approve an RPL spender
+func ApproveRPL(rp *rocketpool.RocketPool, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return approve(rocketTokenRPL, "RPL", spender, amount, opts)
+}
+
+// Estimate the gas of TransferFromRPL
+func EstimateTransferFromRPLGas(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return estimateTransferFromGas(rocketTokenRPL, "RPL", from, to, amount, opts)
+}
+
+// Transfer RPL from a sender
+func TransferFromRPL(rp *rocketpool.RocketPool, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return transferFrom(rocketTokenRPL, "RPL", from, to, amount, opts)
+}
+
+//
+// RPL functions
+//
+
+// Estimate the gas of MintInflationRPL
+func EstimateMintInflationRPLGas(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketTokenRPL.GetTransactionGasInfo(opts, "inflationMintTokens")
+}
+
+// Mint new RPL tokens from inflation
+func MintInflationRPL(rp *rocketpool.RocketPool, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketTokenRPL.Transact(opts, "inflationMintTokens")
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error minting RPL tokens from inflation: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of SwapFixedSupplyRPLForRPL
+func EstimateSwapFixedSupplyRPLForRPLGas(rp *rocketpool.RocketPool, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ return rocketTokenRPL.GetTransactionGasInfo(opts, "swapTokens", amount)
+}
+
+// Swap fixed-supply RPL for new RPL tokens
+func SwapFixedSupplyRPLForRPL(rp *rocketpool.RocketPool, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, nil)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ tx, err := rocketTokenRPL.Transact(opts, "swapTokens", amount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error swapping fixed-supply RPL for new RPL: %w", err)
+ }
+ return tx.Hash(), nil
+}
+
+// Get the RPL inflation interval rate
+func GetRPLInflationIntervalRate(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+ rate := new(*big.Int)
+ if err := rocketTokenRPL.Call(opts, rate, "getInflationIntervalRate"); err != nil {
+ return nil, fmt.Errorf("error getting RPL inflation interval rate: %w", err)
+ }
+ return *rate, nil
+}
+
+// Get the time that inflation started for this interval
+func GetRPLInflationIntervalStartTime(rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Time, error) {
+ rocketTokenRPL, err := getRocketTokenRPL(rp, opts)
+ if err != nil {
+ return time.Time{}, err
+ }
+ value := new(*big.Int)
+ if err := rocketTokenRPL.Call(opts, value, "getInflationIntervalStartTime"); err != nil {
+ return time.Time{}, fmt.Errorf("Could not get RPL inflation interval start time: %w", err)
+ }
+ return time.Unix((*value).Int64(), 0), nil
+}
+
+//
+// Contracts
+//
+
+// Get contracts
+var rocketTokenRPLLock sync.Mutex
+
+func getRocketTokenRPL(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ rocketTokenRPLLock.Lock()
+ defer rocketTokenRPLLock.Unlock()
+ return rp.GetContract("rocketTokenRPL", opts)
+}
diff --git a/bindings/tokens/tokens.go b/bindings/tokens/tokens.go
new file mode 100644
index 000000000..340b76c95
--- /dev/null
+++ b/bindings/tokens/tokens.go
@@ -0,0 +1,152 @@
+package tokens
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "golang.org/x/sync/errgroup"
+
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Token balances
+type Balances struct {
+ ETH *big.Int `json:"eth"`
+ RETH *big.Int `json:"reth"`
+ RPL *big.Int `json:"rpl"`
+ FixedSupplyRPL *big.Int `json:"fixedSupplyRpl"`
+}
+
+// Get token balances of an address
+func GetBalances(rp *rocketpool.RocketPool, address common.Address, opts *bind.CallOpts) (Balances, error) {
+
+ // Get call options block number
+ var blockNumber *big.Int
+ if opts != nil {
+ blockNumber = opts.BlockNumber
+ }
+
+ // Data
+ var wg errgroup.Group
+ var ethBalance *big.Int
+ var rethBalance *big.Int
+ var rplBalance *big.Int
+ var fixedSupplyRplBalance *big.Int
+
+ // Load data
+ wg.Go(func() error {
+ var err error
+ ethBalance, err = rp.Client.BalanceAt(context.Background(), address, blockNumber)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ rethBalance, err = GetRETHBalance(rp, address, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ rplBalance, err = GetRPLBalance(rp, address, opts)
+ return err
+ })
+ wg.Go(func() error {
+ var err error
+ fixedSupplyRplBalance, err = GetFixedSupplyRPLBalance(rp, address, opts)
+ return err
+ })
+
+ // Wait for data
+ if err := wg.Wait(); err != nil {
+ return Balances{}, err
+ }
+
+ // Return
+ return Balances{
+ ETH: ethBalance,
+ RETH: rethBalance,
+ RPL: rplBalance,
+ FixedSupplyRPL: fixedSupplyRplBalance,
+ }, nil
+
+}
+
+// Get a token contract's ETH balance
+func contractETHBalance(rp *rocketpool.RocketPool, tokenContract *rocketpool.Contract, opts *bind.CallOpts) (*big.Int, error) {
+ var blockNumber *big.Int
+ if opts != nil {
+ blockNumber = opts.BlockNumber
+ }
+ return rp.Client.BalanceAt(context.Background(), *(tokenContract.Address), blockNumber)
+}
+
+// Get a token's total supply
+func totalSupply(tokenContract *rocketpool.Contract, tokenName string, opts *bind.CallOpts) (*big.Int, error) {
+ totalSupply := new(*big.Int)
+ if err := tokenContract.Call(opts, totalSupply, "totalSupply"); err != nil {
+ return nil, fmt.Errorf("error getting %s total supply: %w", tokenName, err)
+ }
+ return *totalSupply, nil
+}
+
+// Get a token balance
+func balanceOf(tokenContract *rocketpool.Contract, tokenName string, address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ balance := new(*big.Int)
+ if err := tokenContract.Call(opts, balance, "balanceOf", address); err != nil {
+ return nil, fmt.Errorf("error getting %s balance of %s: %w", tokenName, address.Hex(), err)
+ }
+ return *balance, nil
+}
+
+// Get a spender's allowance for an address
+func allowance(tokenContract *rocketpool.Contract, tokenName string, owner, spender common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ allowance := new(*big.Int)
+ if err := tokenContract.Call(opts, allowance, "allowance", owner, spender); err != nil {
+ return nil, fmt.Errorf("error getting %s allowance of %s for %s: %w", tokenName, spender.Hex(), owner.Hex(), err)
+ }
+ return *allowance, nil
+}
+
+// Estimate the gas of transfer
+func estimateTransferGas(tokenContract *rocketpool.Contract, tokenName string, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return tokenContract.GetTransactionGasInfo(opts, "transfer", to, amount)
+}
+
+// Transfer tokens to an address
+func transfer(tokenContract *rocketpool.Contract, tokenName string, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := tokenContract.Transact(opts, "transfer", to, amount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error transferring %s to %s: %w", tokenName, to.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of approve
+func estimateApproveGas(tokenContract *rocketpool.Contract, tokenName string, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return tokenContract.GetTransactionGasInfo(opts, "approve", spender, amount)
+}
+
+// Approve a token allowance for a spender
+func approve(tokenContract *rocketpool.Contract, tokenName string, spender common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := tokenContract.Transact(opts, "approve", spender, amount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error approving %s allowance for %s: %w", tokenName, spender.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
+
+// Estimate the gas of transferFrom
+func estimateTransferFromGas(tokenContract *rocketpool.Contract, tokenName string, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return tokenContract.GetTransactionGasInfo(opts, "transferFrom", from, to, amount)
+}
+
+// Transfer tokens from a sender to an address
+func transferFrom(tokenContract *rocketpool.Contract, tokenName string, from, to common.Address, amount *big.Int, opts *bind.TransactOpts) (common.Hash, error) {
+ tx, err := tokenContract.Transact(opts, "transferFrom", from, to, amount)
+ if err != nil {
+ return common.Hash{}, fmt.Errorf("error transferring %s from %s to %s: %w", tokenName, from.Hex(), to.Hex(), err)
+ }
+ return tx.Hash(), nil
+}
diff --git a/bindings/types/beacon.go b/bindings/types/beacon.go
new file mode 100644
index 000000000..8a476a88a
--- /dev/null
+++ b/bindings/types/beacon.go
@@ -0,0 +1,105 @@
+package types
+
+import (
+ "fmt"
+
+ "encoding/hex"
+
+ "github.com/rocket-pool/rocketpool-go/utils/json"
+)
+
+// Validator pubkey
+const ValidatorPubkeyLength = 48 // bytes
+type ValidatorPubkey [ValidatorPubkeyLength]byte
+
+// Bytes conversion
+func (v ValidatorPubkey) Bytes() []byte {
+ return v[:]
+}
+func BytesToValidatorPubkey(value []byte) ValidatorPubkey {
+ var pubkey ValidatorPubkey
+ copy(pubkey[:], value)
+ return pubkey
+}
+
+// String conversion
+func (v ValidatorPubkey) Hex() string {
+ return hex.EncodeToString(v.Bytes())
+}
+func (v ValidatorPubkey) String() string {
+ return v.Hex()
+}
+func HexToValidatorPubkey(value string) (ValidatorPubkey, error) {
+ pubkey := make([]byte, ValidatorPubkeyLength)
+ if len(value) != hex.EncodedLen(ValidatorPubkeyLength) {
+ return ValidatorPubkey{}, fmt.Errorf("Invalid validator public key hex string %s: invalid length %d", value, len(value))
+ }
+ if _, err := hex.Decode(pubkey, []byte(value)); err != nil {
+ return ValidatorPubkey{}, err
+ }
+ return BytesToValidatorPubkey(pubkey), nil
+}
+
+// JSON encoding
+func (v ValidatorPubkey) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.Hex())
+}
+func (v *ValidatorPubkey) UnmarshalJSON(data []byte) error {
+ var dataStr string
+ if err := json.Unmarshal(data, &dataStr); err != nil {
+ return err
+ }
+ pubkey, err := HexToValidatorPubkey(dataStr)
+ if err == nil {
+ *v = pubkey
+ }
+ return err
+}
+
+// Validator signature
+const ValidatorSignatureLength = 96 // bytes
+type ValidatorSignature [ValidatorSignatureLength]byte
+
+// Bytes conversion
+func (v ValidatorSignature) Bytes() []byte {
+ return v[:]
+}
+func BytesToValidatorSignature(value []byte) ValidatorSignature {
+ var signature ValidatorSignature
+ copy(signature[:], value)
+ return signature
+}
+
+// String conversion
+func (v ValidatorSignature) Hex() string {
+ return hex.EncodeToString(v.Bytes())
+}
+func (v ValidatorSignature) String() string {
+ return v.Hex()
+}
+func HexToValidatorSignature(value string) (ValidatorSignature, error) {
+ signature := make([]byte, ValidatorSignatureLength)
+ if len(value) != hex.EncodedLen(ValidatorSignatureLength) {
+ return ValidatorSignature{}, fmt.Errorf("Invalid validator signature hex string %s: invalid length %d", value, len(value))
+ }
+ if _, err := hex.Decode(signature, []byte(value)); err != nil {
+ return ValidatorSignature{}, err
+ }
+ return BytesToValidatorSignature(signature), nil
+}
+
+// JSON encoding
+func (v ValidatorSignature) MarshalJSON() ([]byte, error) {
+ return json.Marshal(v.Hex())
+}
+func (v *ValidatorSignature) UnmarshalJSON(data []byte) error {
+ var dataStr string
+ if err := json.Unmarshal(data, &dataStr); err != nil {
+ return err
+ }
+ signature, err := HexToValidatorSignature(dataStr)
+ if err == nil {
+ *v = signature
+ }
+ return err
+}
diff --git a/bindings/types/dao.go b/bindings/types/dao.go
new file mode 100644
index 000000000..0d387ff6b
--- /dev/null
+++ b/bindings/types/dao.go
@@ -0,0 +1,123 @@
+package types
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/utils/json"
+)
+
+// DAO proposal states
+type ProposalState uint8
+
+const (
+ Pending ProposalState = iota
+ Active
+ Cancelled
+ Defeated
+ Succeeded
+ Expired
+ Executed
+)
+
+var ProposalStates = []string{"Pending", "Active", "Cancelled", "Defeated", "Succeeded", "Expired", "Executed"}
+
+// pDAO proposal states
+type ProtocolDaoProposalState uint8
+
+const (
+ ProtocolDaoProposalState_Pending ProtocolDaoProposalState = iota
+ ProtocolDaoProposalState_ActivePhase1
+ ProtocolDaoProposalState_ActivePhase2
+ ProtocolDaoProposalState_Destroyed
+ ProtocolDaoProposalState_Vetoed
+ ProtocolDaoProposalState_QuorumNotMet
+ ProtocolDaoProposalState_Defeated
+ ProtocolDaoProposalState_Succeeded
+ ProtocolDaoProposalState_Expired
+ ProtocolDaoProposalState_Executed
+)
+
+var ProtocolDaoProposalStates = []string{"Pending", "Active (Phase 1)", "Active (Phase 2)", "Destroyed", "Vetoed", "Quorum not Met", "Defeated", "Succeeded", "Expired", "Executed"}
+
+// pDAO voting direction
+type VoteDirection uint8
+
+const (
+ VoteDirection_NoVote VoteDirection = iota
+ VoteDirection_Abstain
+ VoteDirection_For
+ VoteDirection_Against
+ VoteDirection_AgainstWithVeto
+)
+
+var VoteDirections = []string{"Not Voted", "Abstain", "In Favor", "Against", "Against with Veto"}
+
+// DAO setting types
+type ProposalSettingType uint8
+
+const (
+ ProposalSettingType_Uint256 ProposalSettingType = iota
+ ProposalSettingType_Bool
+ ProposalSettingType_Address
+)
+
+// Challenge states
+type ChallengeState uint8
+
+const (
+ ChallengeState_Unchallenged ChallengeState = iota
+ ChallengeState_Challenged
+ ChallengeState_Responded
+ ChallengeState_Paid
+)
+
+// Info about a node's voting power
+type NodeVotingInfo struct {
+ NodeAddress common.Address `json:"nodeAddress"`
+ VotingPower *big.Int `json:"votingPower"`
+ Delegate common.Address `json:"delegate"`
+}
+
+// A node of the voting Merkle Tree (not a Rocket Pool node)
+type VotingTreeNode struct {
+ Sum *big.Int `json:"sum"`
+ Hash common.Hash `json:"hash"`
+}
+
+// String conversion
+func (s ProposalState) String() string {
+ if int(s) >= len(ProposalStates) {
+ return ""
+ }
+ return ProposalStates[s]
+}
+func StringToProposalState(value string) (ProposalState, error) {
+ for state, str := range ProposalStates {
+ if value == str {
+ return ProposalState(state), nil
+ }
+ }
+ return 0, fmt.Errorf("Invalid proposal state '%s'", value)
+}
+
+// JSON encoding
+func (s ProposalState) MarshalJSON() ([]byte, error) {
+ str := s.String()
+ if str == "" {
+ return []byte{}, fmt.Errorf("Invalid proposal state '%d'", s)
+ }
+ return json.Marshal(str)
+}
+func (s *ProposalState) UnmarshalJSON(data []byte) error {
+ var dataStr string
+ if err := json.Unmarshal(data, &dataStr); err != nil {
+ return err
+ }
+ state, err := StringToProposalState(dataStr)
+ if err == nil {
+ *s = state
+ }
+ return err
+}
diff --git a/bindings/types/minipool.go b/bindings/types/minipool.go
new file mode 100644
index 000000000..03a7bd17b
--- /dev/null
+++ b/bindings/types/minipool.go
@@ -0,0 +1,105 @@
+package types
+
+import (
+ "fmt"
+
+ "github.com/rocket-pool/rocketpool-go/utils/json"
+)
+
+// Minipool statuses
+type MinipoolStatus uint8
+
+const (
+ Initialized MinipoolStatus = iota
+ Prelaunch
+ Staking
+ Withdrawable
+ Dissolved
+)
+
+var MinipoolStatuses = []string{"Initialized", "Prelaunch", "Staking", "Withdrawable", "Dissolved"}
+
+// String conversion
+func (s MinipoolStatus) String() string {
+ if int(s) >= len(MinipoolStatuses) {
+ return ""
+ }
+ return MinipoolStatuses[s]
+}
+func StringToMinipoolStatus(value string) (MinipoolStatus, error) {
+ for status, str := range MinipoolStatuses {
+ if value == str {
+ return MinipoolStatus(status), nil
+ }
+ }
+ return 0, fmt.Errorf("Invalid minipool status '%s'", value)
+}
+
+// JSON encoding
+func (s MinipoolStatus) MarshalJSON() ([]byte, error) {
+ str := s.String()
+ if str == "" {
+ return []byte{}, fmt.Errorf("Invalid minipool status '%d'", s)
+ }
+ return json.Marshal(str)
+}
+func (s *MinipoolStatus) UnmarshalJSON(data []byte) error {
+ var dataStr string
+ if err := json.Unmarshal(data, &dataStr); err != nil {
+ return err
+ }
+ status, err := StringToMinipoolStatus(dataStr)
+ if err == nil {
+ *s = status
+ }
+ return err
+}
+
+// Minipool deposit types
+type MinipoolDeposit uint8
+
+const (
+ None MinipoolDeposit = iota
+ Full
+ Half
+ Empty
+ Variable
+)
+
+var MinipoolDepositTypes = []string{"None", "Full", "Half", "Empty", "Variable"}
+
+// String conversion
+func (d MinipoolDeposit) String() string {
+ if int(d) >= len(MinipoolDepositTypes) {
+ return ""
+ }
+ return MinipoolDepositTypes[d]
+}
+func StringToMinipoolDeposit(value string) (MinipoolDeposit, error) {
+ for depositType, str := range MinipoolDepositTypes {
+ if value == str {
+ return MinipoolDeposit(depositType), nil
+ }
+ }
+ return 0, fmt.Errorf("Invalid minipool deposit type '%s'", value)
+}
+
+// JSON encoding
+func (d MinipoolDeposit) MarshalJSON() ([]byte, error) {
+ str := d.String()
+ if str == "" {
+ return []byte{}, fmt.Errorf("Invalid minipool deposit type '%d'", d)
+ }
+ return json.Marshal(str)
+}
+func (d *MinipoolDeposit) UnmarshalJSON(data []byte) error {
+ var dataStr string
+ if err := json.Unmarshal(data, &dataStr); err != nil {
+ return err
+ }
+ depositType, err := StringToMinipoolDeposit(dataStr)
+ if err == nil {
+ *d = depositType
+ }
+ return err
+}
diff --git a/bindings/utils/address_generation.go b/bindings/utils/address_generation.go
new file mode 100644
index 000000000..5720b7880
--- /dev/null
+++ b/bindings/utils/address_generation.go
@@ -0,0 +1,17 @@
+package utils
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+// Combine a node's address and a salt to retreive a new salt compatible with depositing
+func GetNodeSalt(nodeAddress common.Address, salt *big.Int) common.Hash {
+ // Create a new salt by hashing the original and the node address
+ saltBytes := [32]byte{}
+ salt.FillBytes(saltBytes[:])
+ saltHash := crypto.Keccak256Hash(nodeAddress.Bytes(), saltBytes[:])
+ return saltHash
+}
diff --git a/bindings/utils/deposit_retrieval.go b/bindings/utils/deposit_retrieval.go
new file mode 100644
index 000000000..d23f7d472
--- /dev/null
+++ b/bindings/utils/deposit_retrieval.go
@@ -0,0 +1,123 @@
+package utils
+
+import (
+ "bytes"
+ "encoding/binary"
+ "math/big"
+ "sort"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ rptypes "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+)
+
+// BeaconDepositEvent represents a DepositEvent event raised by the BeaconDeposit contract.
+type BeaconDepositEvent struct {
+ Pubkey []byte `abi:"pubkey"`
+ WithdrawalCredentials []byte `abi:"withdrawal_credentials"`
+ Amount []byte `abi:"amount"`
+ Signature []byte `abi:"signature"`
+ Index []byte `abi:"index"`
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// Formatted Beacon deposit event data
+type DepositData struct {
+ Pubkey rptypes.ValidatorPubkey `json:"pubkey"`
+ WithdrawalCredentials common.Hash `json:"withdrawalCredentials"`
+ Amount uint64 `json:"amount"`
+ Signature rptypes.ValidatorSignature `json:"signature"`
+ TxHash common.Hash `json:"txHash"`
+ BlockNumber uint64 `json:"blockNumber"`
+ TxIndex uint `json:"txIndex"`
+}
+
+// Gets all of the deposit contract's deposit events for the provided pubkeys
+func GetDeposits(rp *rocketpool.RocketPool, pubkeys map[rptypes.ValidatorPubkey]bool, startBlock *big.Int, intervalSize *big.Int, opts *bind.CallOpts) (map[rptypes.ValidatorPubkey][]DepositData, error) {
+
+ // Get the deposit contract wrapper
+ casperDeposit, err := getCasperDeposit(rp, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the initial map and pubkey lookup
+ depositMap := make(map[rptypes.ValidatorPubkey][]DepositData, len(pubkeys))
+
+ // Get the deposit events
+ addressFilter := []common.Address{*casperDeposit.Address}
+ topicFilter := [][]common.Hash{{casperDeposit.ABI.Events["DepositEvent"].ID}}
+ logs, err := eth.GetLogs(rp, addressFilter, topicFilter, intervalSize, startBlock, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Process each event
+ for _, log := range logs {
+ depositEvent := new(BeaconDepositEvent)
+ err = casperDeposit.Contract.UnpackLog(depositEvent, "DepositEvent", log)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check if this is a deposit for one of the pubkeys we're looking for
+ pubkey := rptypes.BytesToValidatorPubkey(depositEvent.Pubkey)
+ _, exists := pubkeys[pubkey]
+ if exists {
+ // Convert the deposit amount from little-endian binary to a uint64
+ var amount uint64
+ buf := bytes.NewReader(depositEvent.Amount)
+ err = binary.Read(buf, binary.LittleEndian, &amount)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the deposit data wrapper and add it to this pubkey's collection
+ depositData := DepositData{
+ Pubkey: pubkey,
+ WithdrawalCredentials: common.BytesToHash(depositEvent.WithdrawalCredentials),
+ Amount: amount,
+ Signature: rptypes.BytesToValidatorSignature(depositEvent.Signature),
+ TxHash: log.TxHash,
+ BlockNumber: log.BlockNumber,
+ TxIndex: log.TxIndex,
+ }
+ depositMap[pubkey] = append(depositMap[pubkey], depositData)
+ }
+ }
+
+ // Sort deposits by time
+ for _, deposits := range depositMap {
+ if len(deposits) > 1 {
+ sortDepositData(deposits)
+ }
+ }
+
+ return depositMap, nil
+}
+
+// Sorts a slice of deposit data entries - lower blocks come first, and if multiple transactions occur
+// in the same block, lower transaction indices come first
+func sortDepositData(data []DepositData) {
+ sort.Slice(data, func(i int, j int) bool {
+ first := data[i]
+ second := data[j]
+ if first.BlockNumber == second.BlockNumber {
+ return first.TxIndex < second.TxIndex
+ }
+ return first.BlockNumber < second.BlockNumber
+ })
+}
+
+// Get contracts
+var casperDepositLock sync.Mutex
+
+func getCasperDeposit(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) {
+ casperDepositLock.Lock()
+ defer casperDepositLock.Unlock()
+ return rp.GetContract("casperDeposit", opts)
+}
diff --git a/bindings/utils/eth/erc20.go b/bindings/utils/eth/erc20.go
new file mode 100644
index 000000000..fb85ac69c
--- /dev/null
+++ b/bindings/utils/eth/erc20.go
@@ -0,0 +1,205 @@
+package eth
+
+import (
+ "fmt"
+ "math/big"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+const (
+ Erc20AbiString string = `[
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "name": "balance",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "name": "success",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ }
+ ]`
+)
+
+// Global container for the parsed ABI above
+var erc20Abi *abi.ABI
+
+type Erc20Contract struct {
+ Name string
+ Symbol string
+ Decimals uint8
+ contract *rocketpool.Contract
+}
+
+// Creates a contract wrapper for the ERC20 at the given address
+func NewErc20Contract(address common.Address, client rocketpool.ExecutionClient, opts *bind.CallOpts) (*Erc20Contract, error) {
+ // Parse the ABI
+ if erc20Abi == nil {
+ abiParsed, err := abi.JSON(strings.NewReader(Erc20AbiString))
+ if err != nil {
+ return nil, fmt.Errorf("error parsing ERC20 ABI: %w", err)
+ }
+ erc20Abi = &abiParsed
+ }
+
+ // Create contract
+ contract := &rocketpool.Contract{
+ Contract: bind.NewBoundContract(address, *erc20Abi, client, client, client),
+ Address: &address,
+ ABI: erc20Abi,
+ Client: client,
+ }
+
+ // Create the wrapper
+ wrapper := &Erc20Contract{
+ contract: contract,
+ }
+
+ // Get the details
+ name, err := wrapper.GetName(opts)
+ if err != nil {
+ return nil, err
+ }
+ wrapper.Name = name
+ symbol, err := wrapper.GetSymbol(opts)
+ if err != nil {
+ return nil, err
+ }
+ wrapper.Symbol = symbol
+ decimals, err := wrapper.GetDecimals(opts)
+ if err != nil {
+ return nil, err
+ }
+ wrapper.Decimals = decimals
+
+ return wrapper, nil
+}
+
+// Get the token name
+func (c *Erc20Contract) GetName(opts *bind.CallOpts) (string, error) {
+ name := new(string)
+ err := c.contract.Call(opts, name, "name")
+ if err != nil {
+ return "", fmt.Errorf("could not get ERC20 name: %w", err)
+ }
+ return *name, nil
+}
+
+// Get the token symbol
+func (c *Erc20Contract) GetSymbol(opts *bind.CallOpts) (string, error) {
+ symbol := new(string)
+ err := c.contract.Call(opts, symbol, "symbol")
+ if err != nil {
+ return "", fmt.Errorf("could not get ERC20 symbol: %w", err)
+ }
+ return *symbol, nil
+}
+
+// Get the token decimals
+func (c *Erc20Contract) GetDecimals(opts *bind.CallOpts) (uint8, error) {
+ decimals := new(uint8)
+ err := c.contract.Call(opts, decimals, "decimals")
+ if err != nil {
+ return 0, fmt.Errorf("could not get ERC20 decimals: %w", err)
+ }
+ return *decimals, nil
+}
+
+// Get the token balance for an address
+func (c *Erc20Contract) BalanceOf(address common.Address, opts *bind.CallOpts) (*big.Int, error) {
+ balance := new(*big.Int)
+ err := c.contract.Call(opts, balance, "balanceOf", address)
+ if err != nil {
+ return nil, fmt.Errorf("could not get ERC20 balance for address %s: %w", address.Hex(), err)
+ }
+ return *balance, nil
+}
+
+// Estimate the gas for transferring an ERC20 to another address
+func (c *Erc20Contract) EstimateTransferGas(to common.Address, amount *big.Int, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+ return c.contract.GetTransactionGasInfo(opts, "transfer", to, amount)
+}
+
+// Transfer an ERC20 to another address
+func (c *Erc20Contract) Transfer(to common.Address, amount *big.Int, opts *bind.TransactOpts) (*types.Transaction, error) {
+ tx, err := c.contract.Transact(opts, "transfer", to, amount)
+ if err != nil {
+ return nil, fmt.Errorf("could not transfer ERC20 to %s: %w", to.Hex(), err)
+ }
+ return tx, nil
+}
diff --git a/bindings/utils/eth/logs.go b/bindings/utils/eth/logs.go
new file mode 100644
index 000000000..54c7c95f4
--- /dev/null
+++ b/bindings/utils/eth/logs.go
@@ -0,0 +1,124 @@
+package eth
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/storage"
+)
+
+type FilterQuery struct {
+ BlockHash *common.Hash
+ FromBlock *big.Int
+ ToBlock *big.Int
+ Topics [][]common.Hash
+}
+
+func FilterContractLogs(rp *rocketpool.RocketPool, contractName string, q FilterQuery, intervalSize *big.Int, opts *bind.CallOpts) ([]types.Log, error) {
+ rocketDaoNodeTrustedUpgrade, err := rp.GetContract("rocketDAONodeTrustedUpgrade", opts)
+ if err != nil {
+ return nil, err
+ }
+ // Get all the addresses this contract has ever been deployed at
+ addresses := make([]common.Address, 0)
+ // Construct a filter to query ContractUpgraded event
+ addressFilter := []common.Address{*rocketDaoNodeTrustedUpgrade.Address}
+ topicFilter := [][]common.Hash{{rocketDaoNodeTrustedUpgrade.ABI.Events["ContractUpgraded"].ID}, {crypto.Keccak256Hash([]byte(contractName))}}
+ logs, err := GetLogs(rp, addressFilter, topicFilter, intervalSize, nil, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ // Iterate the logs and store every past contract address
+ for _, log := range logs {
+ addresses = append(addresses, common.HexToAddress(log.Topics[2].Hex()))
+ }
+ // Append current address
+ currentAddress, err := rp.GetAddress(contractName, opts)
+ if err != nil {
+ return nil, err
+ }
+ addresses = append(addresses, *currentAddress)
+ // Perform the desired getLogs call and return results
+ return GetLogs(rp, addresses, q.Topics, intervalSize, q.FromBlock, q.ToBlock, q.BlockHash)
+}
+
+// Gets the logs for a particular log request, breaking the calls into batches if necessary
+func GetLogs(rp *rocketpool.RocketPool, addressFilter []common.Address, topicFilter [][]common.Hash, intervalSize, fromBlock, toBlock *big.Int, blockHash *common.Hash) ([]types.Log, error) {
+ var logs []types.Log
+
+ // Get the block that Rocket Pool was deployed on as the lower bound if one wasn't specified
+ if fromBlock == nil {
+ var err error
+ fromBlock, err = storage.GetDeployBlock(rp)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if intervalSize == nil {
+ // Handle unlimited intervals with a single call
+ logs, err := rp.Client.FilterLogs(context.Background(), ethereum.FilterQuery{
+ Addresses: addressFilter,
+ Topics: topicFilter,
+ FromBlock: fromBlock,
+ ToBlock: toBlock,
+ BlockHash: blockHash,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return logs, nil
+ } else {
+ // Get the latest block
+ if toBlock == nil {
+ latestBlock, err := rp.Client.BlockNumber(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ toBlock = big.NewInt(0)
+ toBlock.SetUint64(latestBlock)
+ }
+
+ // Set the start and end, clamping on the latest block
+ intervalSize := big.NewInt(0).Sub(intervalSize, big.NewInt(1))
+ start := big.NewInt(0).Set(fromBlock)
+ end := big.NewInt(0).Add(start, intervalSize)
+ if end.Cmp(toBlock) == 1 {
+ end.Set(toBlock)
+ }
+ for {
+ // Get the logs using the current interval
+ newLogs, err := rp.Client.FilterLogs(context.Background(), ethereum.FilterQuery{
+ Addresses: addressFilter,
+ Topics: topicFilter,
+ FromBlock: start,
+ ToBlock: end,
+ BlockHash: blockHash,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Append the logs to the total list
+ logs = append(logs, newLogs...)
+
+ // Return once we've finished iterating
+ if end.Cmp(toBlock) == 0 {
+ return logs, nil
+ }
+
+ // Update to the next interval (end+1 : that + interval - 1)
+ start.Add(end, big.NewInt(1))
+ end.Add(start, intervalSize)
+ if end.Cmp(toBlock) == 1 {
+ end.Set(toBlock)
+ }
+ }
+ }
+}
diff --git a/bindings/utils/eth/transactions.go b/bindings/utils/eth/transactions.go
new file mode 100644
index 000000000..ad6fc53fb
--- /dev/null
+++ b/bindings/utils/eth/transactions.go
@@ -0,0 +1,125 @@
+package eth
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Estimate the gas of SendTransaction
+func EstimateSendTransactionGas(client rocketpool.ExecutionClient, toAddress common.Address, data []byte, useSafeGasLimit bool, opts *bind.TransactOpts) (rocketpool.GasInfo, error) {
+
+ // User-defined settings
+ response := rocketpool.GasInfo{}
+
+ // Set default value
+ value := opts.Value
+ if value == nil {
+ value = big.NewInt(0)
+ }
+
+ // Set default data
+ if data == nil {
+ data = []byte{}
+ }
+
+ // Estimate gas limit
+ gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
+ From: opts.From,
+ To: &toAddress,
+ GasPrice: big.NewInt(0), // set to 0 for simulation
+ Data: data,
+ Value: value,
+ })
+ if err != nil {
+ return rocketpool.GasInfo{}, err
+ }
+ response.EstGasLimit = gasLimit
+
+ if useSafeGasLimit {
+ response.SafeGasLimit = uint64(float64(gasLimit) * rocketpool.GasLimitMultiplier)
+ } else {
+ response.SafeGasLimit = gasLimit
+ }
+
+ return response, err
+}
+
+// Send a transaction to an address
+// useSafeGasLimit will amplify the estimated gas limit to by 50% for safety (no effect if the gas limit in opts is already set).
+func SendTransaction(client rocketpool.ExecutionClient, toAddress common.Address, chainID *big.Int, data []byte, useSafeGasLimit bool, opts *bind.TransactOpts) (common.Hash, error) {
+ var err error
+
+ // Get from address nonce
+ var nonce uint64
+ if opts.Nonce == nil {
+ nonce, err = client.PendingNonceAt(context.Background(), opts.From)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ } else {
+ nonce = opts.Nonce.Uint64()
+ }
+
+ // Set default value
+ value := opts.Value
+ if value == nil {
+ value = big.NewInt(0)
+ }
+
+ // Set default data
+ if data == nil {
+ data = []byte{}
+ }
+
+ // Estimate gas limit
+ gasLimit := opts.GasLimit
+ if gasLimit == 0 {
+ gasLimit, err = client.EstimateGas(context.Background(), ethereum.CallMsg{
+ From: opts.From,
+ To: &toAddress,
+ GasPrice: big.NewInt(0), // use 0 gwei for simulation
+ Data: data,
+ Value: value,
+ })
+ if err != nil {
+ return common.Hash{}, err
+ }
+
+ if useSafeGasLimit {
+ gasLimit = uint64(float64(gasLimit) * rocketpool.GasLimitMultiplier)
+ }
+ }
+
+ // Initialize transaction
+ tx := types.NewTx(&types.DynamicFeeTx{
+ ChainID: chainID,
+ Nonce: nonce,
+ GasTipCap: opts.GasTipCap,
+ GasFeeCap: opts.GasFeeCap,
+ Gas: gasLimit,
+ To: &toAddress,
+ Value: value,
+ Data: data,
+ AccessList: []types.AccessTuple{},
+ })
+
+ // Sign transaction
+ signedTx, err := opts.Signer(opts.From, tx)
+ if err != nil {
+ return common.Hash{}, err
+ }
+
+ // Send transaction
+ if err = client.SendTransaction(context.Background(), signedTx); err != nil {
+ return common.Hash{}, err
+ }
+
+ return signedTx.Hash(), nil
+
+}
diff --git a/bindings/utils/eth/units.go b/bindings/utils/eth/units.go
new file mode 100644
index 000000000..1278f83ee
--- /dev/null
+++ b/bindings/utils/eth/units.go
@@ -0,0 +1,82 @@
+package eth
+
+import (
+ "math"
+ "math/big"
+ "strconv"
+)
+
+// Conversion factors
+const (
+ WeiPerEth float64 = 1e18
+ WeiPerGwei float64 = 1e9
+)
+
+// Convert wei to eth
+func WeiToEth(wei *big.Int) float64 {
+ if wei == nil {
+ return 0
+ }
+ var weiFloat big.Float
+ var eth big.Float
+ weiFloat.SetInt(wei)
+ eth.Quo(&weiFloat, big.NewFloat(WeiPerEth))
+ eth64, _ := eth.Float64()
+ return eth64
+}
+
+// Convert eth to wei
+func EthToWei(eth float64) *big.Int {
+ var ethFloat big.Float
+ var weiFloat big.Float
+ var wei big.Int
+ ethFloat.SetString(strconv.FormatFloat(eth, 'f', -1, 64))
+ weiFloat.Mul(ðFloat, big.NewFloat(WeiPerEth))
+ weiFloat.Int(&wei)
+ return &wei
+}
+
+// Convert wei to gigawei
+func WeiToGwei(wei *big.Int) float64 {
+ var weiFloat big.Float
+ var gwei big.Float
+ weiFloat.SetInt(wei)
+ gwei.Quo(&weiFloat, big.NewFloat(WeiPerGwei))
+ gwei64, _ := gwei.Float64()
+ return gwei64
+}
+
+// Convert gigawei to wei
+func GweiToWei(gwei float64) *big.Int {
+ var gweiFloat big.Float
+ var weiFloat big.Float
+ var wei big.Int
+ gweiFloat.SetString(strconv.FormatFloat(gwei, 'f', -1, 64))
+ weiFloat.Mul(&gweiFloat, big.NewFloat(WeiPerGwei))
+ weiFloat.Int(&wei)
+ return &wei
+}
+
+// Converts float amount to big.Int considering a token's decimals
+func EthToWeiWithDecimals(amountRaw float64, decimals uint8) *big.Int {
+ var ethFloat big.Float
+ var weiFloat big.Float
+ var wei big.Int
+ ethFloat.SetString(strconv.FormatFloat(amountRaw, 'f', -1, 64))
+ weiFloat.Mul(ðFloat, big.NewFloat(math.Pow(10, float64(decimals))))
+ weiFloat.Int(&wei)
+ return &wei
+}
+
+// Converts big.Int to float64 considering a token's decimals
+func WeiToEthWithDecimals(amount *big.Int, decimals uint8) float64 {
+ if amount == nil {
+ return 0
+ }
+ var weiFloat big.Float
+ var eth big.Float
+ weiFloat.SetInt(amount)
+ eth.Quo(&weiFloat, big.NewFloat(math.Pow(10, float64(decimals))))
+ eth64, _ := eth.Float64()
+ return eth64
+}
diff --git a/bindings/utils/json/json.go b/bindings/utils/json/json.go
new file mode 100644
index 000000000..ec15a007b
--- /dev/null
+++ b/bindings/utils/json/json.go
@@ -0,0 +1,19 @@
+package json
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+func Marshal(v interface{}) ([]byte, error) {
+ return json.Marshal(v)
+}
+
+func Unmarshal(data []byte, v interface{}) error {
+ err := json.Unmarshal(data, v)
+ if err != nil {
+ return fmt.Errorf("%w\nUnable to Unmarshal JSON string %s", err, string(data))
+ }
+
+ return nil
+}
diff --git a/bindings/utils/multicall/abi.go b/bindings/utils/multicall/abi.go
new file mode 100644
index 000000000..5bbaf033d
--- /dev/null
+++ b/bindings/utils/multicall/abi.go
@@ -0,0 +1,21 @@
+/*
+This code was derived from the following sources:
+
+- https://github.com/depocket/multicall-go
+- https://github.com/wbobeirne/eth-balance-checker
+*/
+
+package multicall
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type MultiCall struct {
+ Target common.Address
+ CallData []byte
+}
+
+var MulticallABI string = "[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"aggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"blockAndAggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBlockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockCoinbase\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockDifficulty\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getEthBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"tryAggregate\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"tryBlockAndAggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
+
+var BalancesABI string = "[{\"constant\":true,\"inputs\":[{\"name\":\"user\",\"type\":\"address\"},{\"name\":\"token\",\"type\":\"address\"}],\"name\":\"tokenBalance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"users\",\"type\":\"address[]\"},{\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"balances\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"}]"
diff --git a/bindings/utils/multicall/balances.go b/bindings/utils/multicall/balances.go
new file mode 100644
index 000000000..81fb7e26d
--- /dev/null
+++ b/bindings/utils/multicall/balances.go
@@ -0,0 +1,97 @@
+package multicall
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "strings"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ balanceBatchSize int = 1000
+ threadLimit int = 6
+)
+
+type BalanceBatcher struct {
+ Client rocketpool.ExecutionClient
+ ABI abi.ABI
+ ContractAddress common.Address
+}
+
+func NewBalanceBatcher(client rocketpool.ExecutionClient, address common.Address) (*BalanceBatcher, error) {
+ abi, err := abi.JSON(strings.NewReader(BalancesABI))
+ if err != nil {
+ return nil, err
+ }
+
+ return &BalanceBatcher{
+ Client: client,
+ ContractAddress: address,
+ ABI: abi,
+ }, nil
+}
+
+func (b *BalanceBatcher) GetEthBalances(addresses []common.Address, opts *bind.CallOpts) ([]*big.Int, error) {
+
+ // Sync
+ count := len(addresses)
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ balances := make([]*big.Int, count)
+
+ // Run the getters in batches
+ for i := 0; i < count; i += balanceBatchSize {
+ i := i
+ max := i + balanceBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ subAddresses := addresses[i:max]
+ tokens := []common.Address{
+ {}, // Empty token for ETH balance
+ }
+ callData, err := b.ABI.Pack("balances", subAddresses, tokens)
+ if err != nil {
+ return fmt.Errorf("error creating calldata for balances: %w", err)
+ }
+
+ response, err := b.Client.CallContract(context.Background(), ethereum.CallMsg{To: &b.ContractAddress, Data: callData}, opts.BlockNumber)
+ if err != nil {
+ return fmt.Errorf("error calling balances: %w", err)
+ }
+
+ var subBalances []*big.Int
+ err = b.ABI.UnpackIntoInterface(&subBalances, "balances", response)
+ if err != nil {
+ return fmt.Errorf("error unpacking balances response: %w", err)
+ }
+
+ if len(subBalances) != len(subAddresses) {
+ return fmt.Errorf("received %d balances which mismatches query batch size %d", len(subBalances), len(subAddresses))
+ }
+ for j, balance := range subBalances {
+ if balance == nil {
+ return fmt.Errorf("received nil balance for address %s", subAddresses[j].String())
+ }
+ balances[i+j] = balance
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting balances: %w", err)
+ }
+
+ return balances, nil
+}
diff --git a/bindings/utils/multicall/multicaller.go b/bindings/utils/multicall/multicaller.go
new file mode 100644
index 000000000..ec03c5e27
--- /dev/null
+++ b/bindings/utils/multicall/multicaller.go
@@ -0,0 +1,133 @@
+/*
+* This code was derived from https://github.com/depocket/multicall-go
+ */
+
+package multicall
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+type Call struct {
+ Method string `json:"method"`
+ Target common.Address `json:"target"`
+ CallData []byte `json:"call_data"`
+ Contract *rocketpool.Contract
+ output interface{}
+}
+
+type CallResponse struct {
+ Method string
+ Status bool
+ ReturnDataRaw []byte `json:"returnData"`
+}
+
+type Result struct {
+ Success bool `json:"success"`
+ Output interface{}
+}
+
+func (call Call) GetMultiCall() MultiCall {
+ return MultiCall{Target: call.Target, CallData: call.CallData}
+}
+
+type MultiCaller struct {
+ Client rocketpool.ExecutionClient
+ ABI abi.ABI
+ ContractAddress common.Address
+ calls []Call
+}
+
+func NewMultiCaller(client rocketpool.ExecutionClient, multicallerAddress common.Address) (*MultiCaller, error) {
+ mcAbi, err := abi.JSON(strings.NewReader(MulticallABI))
+ if err != nil {
+ return nil, err
+ }
+
+ return &MultiCaller{
+ Client: client,
+ ABI: mcAbi,
+ ContractAddress: multicallerAddress,
+ calls: []Call{},
+ }, nil
+}
+
+func (caller *MultiCaller) AddCall(contract *rocketpool.Contract, output interface{}, method string, args ...interface{}) error {
+ callData, err := contract.ABI.Pack(method, args...)
+ if err != nil {
+ return fmt.Errorf("error adding call [%s]: %w", method, err)
+ }
+ call := Call{
+ Method: method,
+ Target: *contract.Address,
+ CallData: callData,
+ Contract: contract,
+ output: output,
+ }
+ caller.calls = append(caller.calls, call)
+ return nil
+}
+
+func (caller *MultiCaller) Execute(requireSuccess bool, opts *bind.CallOpts) ([]CallResponse, error) {
+ var multiCalls = make([]MultiCall, 0, len(caller.calls))
+ for _, call := range caller.calls {
+ multiCalls = append(multiCalls, call.GetMultiCall())
+ }
+ callData, err := caller.ABI.Pack("tryAggregate", requireSuccess, multiCalls)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := caller.Client.CallContract(context.Background(), ethereum.CallMsg{To: &caller.ContractAddress, Data: callData}, opts.BlockNumber)
+ if err != nil {
+ return nil, err
+ }
+
+ responses, err := caller.ABI.Unpack("tryAggregate", resp)
+
+ if err != nil {
+ return nil, err
+ }
+
+ results := make([]CallResponse, len(caller.calls))
+ for i, response := range responses[0].([]struct {
+ Success bool `json:"success"`
+ ReturnData []byte `json:"returnData"`
+ }) {
+ results[i].Method = caller.calls[i].Method
+ results[i].ReturnDataRaw = response.ReturnData
+ results[i].Status = response.Success
+ }
+ return results, nil
+}
+
+func (caller *MultiCaller) FlexibleCall(requireSuccess bool, opts *bind.CallOpts) ([]Result, error) {
+ res := make([]Result, len(caller.calls))
+ results, err := caller.Execute(requireSuccess, opts)
+ if err != nil {
+ caller.calls = []Call{}
+ return nil, err
+ }
+ for i, call := range caller.calls {
+ callSuccess := results[i].Status
+ if callSuccess {
+ err := call.Contract.ABI.UnpackIntoInterface(call.output, call.Method, results[i].ReturnDataRaw)
+ if err != nil {
+ caller.calls = []Call{}
+ return nil, err
+ }
+ }
+ res[i].Success = callSuccess
+ res[i].Output = call.output
+ }
+ caller.calls = []Call{}
+ return res, err
+}
diff --git a/bindings/utils/state/common.go b/bindings/utils/state/common.go
new file mode 100644
index 000000000..5c4f83501
--- /dev/null
+++ b/bindings/utils/state/common.go
@@ -0,0 +1,23 @@
+package state
+
+import (
+ "math/big"
+ "time"
+)
+
+const (
+ threadLimit int = 10
+)
+
+// Global constants
+var zero = big.NewInt(0)
+
+// Converts a time on the chain (as Unix time in seconds) to a time.Time struct
+func convertToTime(value *big.Int) time.Time {
+ return time.Unix(value.Int64(), 0)
+}
+
+// Converts a duration on the chain (as a number of seconds) to a time.Duration struct
+func convertToDuration(value *big.Int) time.Duration {
+ return time.Duration(value.Uint64()) * time.Second
+}
diff --git a/bindings/utils/state/contracts.go b/bindings/utils/state/contracts.go
new file mode 100644
index 000000000..616774c1f
--- /dev/null
+++ b/bindings/utils/state/contracts.go
@@ -0,0 +1,248 @@
+package state
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/hashicorp/go-version"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+)
+
+// Container for network contracts
+type NetworkContracts struct {
+ // Non-RP Utility
+ BalanceBatcher *multicall.BalanceBatcher
+ Multicaller *multicall.MultiCaller
+ ElBlockNumber *big.Int
+
+ // Network version
+ Version *version.Version
+
+ // Redstone
+ RocketDAONodeTrusted *rocketpool.Contract
+ RocketDAONodeTrustedSettingsMinipool *rocketpool.Contract
+ RocketDAOProtocolSettingsMinipool *rocketpool.Contract
+ RocketDAOProtocolSettingsNetwork *rocketpool.Contract
+ RocketDAOProtocolSettingsNode *rocketpool.Contract
+ RocketDepositPool *rocketpool.Contract
+ RocketMinipoolManager *rocketpool.Contract
+ RocketMinipoolQueue *rocketpool.Contract
+ RocketNetworkBalances *rocketpool.Contract
+ RocketNetworkFees *rocketpool.Contract
+ RocketNetworkPrices *rocketpool.Contract
+ RocketNodeDeposit *rocketpool.Contract
+ RocketNodeDistributorFactory *rocketpool.Contract
+ RocketNodeManager *rocketpool.Contract
+ RocketNodeStaking *rocketpool.Contract
+ RocketRewardsPool *rocketpool.Contract
+ RocketSmoothingPool *rocketpool.Contract
+ RocketStorage *rocketpool.Contract
+ RocketTokenRETH *rocketpool.Contract
+ RocketTokenRPL *rocketpool.Contract
+ RocketTokenRPLFixedSupply *rocketpool.Contract
+
+ // Atlas
+ RocketMinipoolBondReducer *rocketpool.Contract
+
+ // Houston
+ RocketDAOProtocolProposal *rocketpool.Contract
+ RocketDAOProtocolVerifier *rocketpool.Contract
+}
+
+type contractArtifacts struct {
+ name string
+ address common.Address
+ abiEncoded string
+ contract **rocketpool.Contract
+}
+
+// Get a new network contracts container
+func NewNetworkContracts(rp *rocketpool.RocketPool, multicallerAddress common.Address, balanceBatcherAddress common.Address, opts *bind.CallOpts) (*NetworkContracts, error) {
+ // Get the latest block number if it's not provided
+ if opts == nil {
+ latestElBlock, err := rp.Client.BlockNumber(context.Background())
+ if err != nil {
+ return nil, fmt.Errorf("error getting latest block number: %w", err)
+ }
+ opts = &bind.CallOpts{
+ BlockNumber: big.NewInt(0).SetUint64(latestElBlock),
+ }
+ }
+
+ // Create the contract binding
+ contracts := &NetworkContracts{
+ RocketStorage: rp.RocketStorageContract,
+ ElBlockNumber: opts.BlockNumber,
+ }
+
+ // Create the multicaller
+ var err error
+ contracts.Multicaller, err = multicall.NewMultiCaller(rp.Client, multicallerAddress)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the balance batcher
+ contracts.BalanceBatcher, err = multicall.NewBalanceBatcher(rp.Client, balanceBatcherAddress)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the contract wrappers for Redstone
+ wrappers := []contractArtifacts{
+ {
+ name: "rocketDAONodeTrusted",
+ contract: &contracts.RocketDAONodeTrusted,
+ }, {
+ name: "rocketDAONodeTrustedSettingsMinipool",
+ contract: &contracts.RocketDAONodeTrustedSettingsMinipool,
+ }, {
+ name: "rocketDAOProtocolSettingsMinipool",
+ contract: &contracts.RocketDAOProtocolSettingsMinipool,
+ }, {
+ name: "rocketDAOProtocolSettingsNetwork",
+ contract: &contracts.RocketDAOProtocolSettingsNetwork,
+ }, {
+ name: "rocketDAOProtocolSettingsNode",
+ contract: &contracts.RocketDAOProtocolSettingsNode,
+ }, {
+ name: "rocketDepositPool",
+ contract: &contracts.RocketDepositPool,
+ }, {
+ name: "rocketMinipoolManager",
+ contract: &contracts.RocketMinipoolManager,
+ }, {
+ name: "rocketMinipoolQueue",
+ contract: &contracts.RocketMinipoolQueue,
+ }, {
+ name: "rocketNetworkBalances",
+ contract: &contracts.RocketNetworkBalances,
+ }, {
+ name: "rocketNetworkFees",
+ contract: &contracts.RocketNetworkFees,
+ }, {
+ name: "rocketNetworkPrices",
+ contract: &contracts.RocketNetworkPrices,
+ }, {
+ name: "rocketNodeDeposit",
+ contract: &contracts.RocketNodeDeposit,
+ }, {
+ name: "rocketNodeDistributorFactory",
+ contract: &contracts.RocketNodeDistributorFactory,
+ }, {
+ name: "rocketNodeManager",
+ contract: &contracts.RocketNodeManager,
+ }, {
+ name: "rocketNodeStaking",
+ contract: &contracts.RocketNodeStaking,
+ }, {
+ name: "rocketRewardsPool",
+ contract: &contracts.RocketRewardsPool,
+ }, {
+ name: "rocketSmoothingPool",
+ contract: &contracts.RocketSmoothingPool,
+ }, {
+ name: "rocketTokenRETH",
+ contract: &contracts.RocketTokenRETH,
+ }, {
+ name: "rocketTokenRPL",
+ contract: &contracts.RocketTokenRPL,
+ }, {
+ name: "rocketTokenRPLFixedSupply",
+ contract: &contracts.RocketTokenRPLFixedSupply,
+ },
+ }
+
+ // Atlas wrappers
+ wrappers = append(wrappers, contractArtifacts{
+ name: "rocketMinipoolBondReducer",
+ contract: &contracts.RocketMinipoolBondReducer,
+ })
+
+ // Houston wrappers
+ wrappers = append(wrappers, contractArtifacts{
+ name: "rocketDAOProtocolProposal",
+ contract: &contracts.RocketDAOProtocolProposal,
+ }, contractArtifacts{
+ name: "rocketDAOProtocolVerifier",
+ contract: &contracts.RocketDAOProtocolVerifier,
+ })
+
+ // Add the address and ABI getters to multicall
+ for i, wrapper := range wrappers {
+ // Add the address getter
+ contracts.Multicaller.AddCall(contracts.RocketStorage, &wrappers[i].address, "getAddress", [32]byte(crypto.Keccak256Hash([]byte("contract.address"), []byte(wrapper.name))))
+
+ // Add the ABI getter
+ contracts.Multicaller.AddCall(contracts.RocketStorage, &wrappers[i].abiEncoded, "getString", [32]byte(crypto.Keccak256Hash([]byte("contract.abi"), []byte(wrapper.name))))
+ }
+
+ // Run the multi-getter
+ _, err = contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error executing multicall for contract retrieval: %w", err)
+ }
+
+ // Postprocess the contracts
+ for i, wrapper := range wrappers {
+ // Decode the ABI
+ abi, err := rocketpool.DecodeAbi(wrapper.abiEncoded)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding ABI for %s: %w", wrapper.name, err)
+ }
+
+ // Create the contract binding
+ contract := &rocketpool.Contract{
+ Contract: bind.NewBoundContract(wrapper.address, *abi, rp.Client, rp.Client, rp.Client),
+ Address: &wrappers[i].address,
+ ABI: abi,
+ Client: rp.Client,
+ }
+
+ // Set the contract in the main wrapper object
+ *wrappers[i].contract = contract
+ }
+
+ err = contracts.getCurrentVersion(rp)
+ if err != nil {
+ return nil, fmt.Errorf("error getting network contract version: %w", err)
+ }
+
+ return contracts, nil
+}
+
+// Get the current version of the network
+func (c *NetworkContracts) getCurrentVersion(rp *rocketpool.RocketPool) error {
+ opts := &bind.CallOpts{
+ BlockNumber: c.ElBlockNumber,
+ }
+
+ // Check for v1.2
+ nodeStakingVersion, err := rocketpool.GetContractVersion(rp, *c.RocketNodeStaking.Address, opts)
+ if err != nil {
+ return fmt.Errorf("error checking node staking version: %w", err)
+ }
+ if nodeStakingVersion > 3 {
+ c.Version, err = version.NewSemver("1.2.0")
+ return err
+ }
+
+ // Check for v1.1
+ nodeMgrVersion, err := rocketpool.GetContractVersion(rp, *c.RocketNodeManager.Address, opts)
+ if err != nil {
+ return fmt.Errorf("error checking node manager version: %w", err)
+ }
+ if nodeMgrVersion > 1 {
+ c.Version, err = version.NewSemver("1.1.0")
+ return err
+ }
+
+ // v1.0
+ c.Version, err = version.NewSemver("1.0.0")
+ return err
+}
diff --git a/bindings/utils/state/minipool.go b/bindings/utils/state/minipool.go
new file mode 100644
index 000000000..813c8f596
--- /dev/null
+++ b/bindings/utils/state/minipool.go
@@ -0,0 +1,602 @@
+package state
+
+import (
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ minipoolBatchSize int = 100
+ minipoolCompleteShareBatchSize int = 500
+ minipoolAddressBatchSize int = 1000
+ minipoolVersionBatchSize int = 500
+)
+
+// Complete details for a minipool
+type NativeMinipoolDetails struct {
+ // Redstone
+ Exists bool `json:"exists"`
+ MinipoolAddress common.Address `json:"minipool_address"`
+ Pubkey types.ValidatorPubkey `json:"pubkey"`
+ StatusRaw uint8 `json:"status_raw"`
+ StatusBlock *big.Int `json:"status_block"`
+ StatusTime *big.Int `json:"status_time"`
+ Finalised bool `json:"finalised"`
+ DepositTypeRaw uint8 `json:"deposit_type_raw"`
+ NodeFee *big.Int `json:"node_fee"`
+ NodeDepositBalance *big.Int `json:"node_deposit_balance"`
+ NodeDepositAssigned bool `json:"node_deposit_assigned"`
+ UserDepositBalance *big.Int `json:"user_deposit_balance"`
+ UserDepositAssigned bool `json:"user_deposit_assigned"`
+ UserDepositAssignedTime *big.Int `json:"user_deposit_assigned_time"`
+ UseLatestDelegate bool `json:"use_latest_delegate"`
+ Delegate common.Address `json:"delegate"`
+ PreviousDelegate common.Address `json:"previous_delegate"`
+ EffectiveDelegate common.Address `json:"effective_delegate"`
+ PenaltyCount *big.Int `json:"penalty_count"`
+ PenaltyRate *big.Int `json:"penalty_rate"`
+ NodeAddress common.Address `json:"node_address"`
+ Version uint8 `json:"version"`
+ Balance *big.Int `json:"balance"`
+ DistributableBalance *big.Int `json:"distributable_balance"`
+ NodeShareOfBalance *big.Int `json:"node_share_of_balance"` // Result of calculateNodeShare(contract balance)
+ UserShareOfBalance *big.Int `json:"user_share_of_balance"` // Result of calculateUserShare(contract balance)
+ NodeRefundBalance *big.Int `json:"node_refund_balance"`
+ WithdrawalCredentials common.Hash `json:"withdrawal_credentials"`
+ Status types.MinipoolStatus `json:"status"`
+ DepositType types.MinipoolDeposit `json:"deposit_type"`
+
+ // Must call CalculateCompleteMinipoolShares to get these
+ NodeShareOfBalanceIncludingBeacon *big.Int `json:"node_share_of_balance_including_beacon"`
+ UserShareOfBalanceIncludingBeacon *big.Int `json:"user_share_of_balance_including_beacon"`
+ NodeShareOfBeaconBalance *big.Int `json:"node_share_of_beacon_balance"`
+ UserShareOfBeaconBalance *big.Int `json:"user_share_of_beacon_balance"`
+
+ // Atlas
+ UserDistributed bool
+ Slashed bool
+ IsVacant bool
+ LastBondReductionTime *big.Int
+ LastBondReductionPrevValue *big.Int
+ LastBondReductionPrevNodeFee *big.Int
+ ReduceBondTime *big.Int
+ ReduceBondCancelled bool
+ ReduceBondValue *big.Int
+ PreMigrationBalance *big.Int
+}
+
+var sixteenEth = big.NewInt(0).Mul(big.NewInt(16), oneEth)
+
+func (details *NativeMinipoolDetails) IsEligibleForBonuses(eligibleEnd time.Time) bool {
+ // A minipool is eligible for bonuses if it was active and had a bond of less than 16 ETH during the interval
+ if details.Status != types.Staking {
+ return false
+ }
+ if details.NodeDepositBalance.Cmp(sixteenEth) >= 0 {
+ return false
+ }
+
+ lastBondReductionTimestamp := details.LastBondReductionTime.Int64()
+ if lastBondReductionTimestamp == 0 {
+ // eligible if the bond was always under 16 eth
+ return true
+ }
+ lastBondReductionTime := time.Unix(lastBondReductionTimestamp, 0)
+ // eligible if the bond was reduced before or during the interval
+ return lastBondReductionTime.Before(eligibleEnd)
+}
+
+// Gets the details for a minipool using the efficient multicall contract
+func GetNativeMinipoolDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, minipoolAddress common.Address) (NativeMinipoolDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ details := NativeMinipoolDetails{}
+ details.MinipoolAddress = minipoolAddress
+
+ version, err := rocketpool.GetContractVersion(rp, minipoolAddress, opts)
+ if err != nil {
+ return NativeMinipoolDetails{}, fmt.Errorf("error getting minipool version: %w", err)
+ }
+ details.Version = version
+ addMinipoolDetailsCalls(rp, contracts, contracts.Multicaller, &details, opts)
+
+ _, err = contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return NativeMinipoolDetails{}, fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ fixupMinipoolDetails(&details)
+
+ return details, nil
+}
+
+// Gets the minpool details for a node using the efficient multicall contract
+func GetNodeNativeMinipoolDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, nodeAddress common.Address) ([]NativeMinipoolDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the list of minipool addresses for this node
+ addresses, err := getNodeMinipoolAddressesFast(rp, contracts, nodeAddress, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minipool addresses: %w", err)
+ }
+
+ // Get the list of minipool versions
+ versions, err := getMinipoolVersionsFast(rp, contracts, addresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minipool versions: %w", err)
+ }
+
+ // Get the minipool details
+ return getBulkMinipoolDetails(rp, contracts, addresses, versions, opts)
+}
+
+// Gets all minpool details using the efficient multicall contract
+func GetAllNativeMinipoolDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ([]NativeMinipoolDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the list of all minipool addresses
+ addresses, err := getAllMinipoolAddressesFast(rp, contracts, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minipool addresses: %w", err)
+ }
+
+ // Get the list of minipool versions
+ versions, err := getMinipoolVersionsFast(rp, contracts, addresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minipool versions: %w", err)
+ }
+
+ // Get the minipool details
+ return getBulkMinipoolDetails(rp, contracts, addresses, versions, opts)
+}
+
+// Calculate the node and user shares of the total minipool balance, including the portion on the Beacon chain
+func CalculateCompleteMinipoolShares(rp *rocketpool.RocketPool, contracts *NetworkContracts, minipoolDetails []*NativeMinipoolDetails, beaconBalances []*big.Int) error {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ count := len(minipoolDetails)
+ for i := 0; i < count; i += minipoolCompleteShareBatchSize {
+ i := i
+ max := i + minipoolCompleteShareBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+
+ // Make the minipool contract
+ details := minipoolDetails[j]
+ mp, err := minipool.NewMinipoolFromVersion(rp, details.MinipoolAddress, details.Version, opts)
+ if err != nil {
+ return err
+ }
+ mpContract := mp.GetContract()
+
+ // Calculate the Beacon shares
+ beaconBalance := big.NewInt(0).Set(beaconBalances[j])
+ if beaconBalance.Cmp(zero) > 0 {
+ mc.AddCall(mpContract, &details.NodeShareOfBeaconBalance, "calculateNodeShare", beaconBalance)
+ mc.AddCall(mpContract, &details.UserShareOfBeaconBalance, "calculateUserShare", beaconBalance)
+ } else {
+ details.NodeShareOfBeaconBalance = big.NewInt(0)
+ details.UserShareOfBeaconBalance = big.NewInt(0)
+ }
+
+ // Calculate the total balance
+ totalBalance := big.NewInt(0).Set(beaconBalances[j]) // Total balance = beacon balance
+ totalBalance.Add(totalBalance, details.Balance) // Add contract balance
+ totalBalance.Sub(totalBalance, details.NodeRefundBalance) // Remove node refund
+
+ // Calculate the node and user shares
+ if totalBalance.Cmp(zero) > 0 {
+ mc.AddCall(mpContract, &details.NodeShareOfBalanceIncludingBeacon, "calculateNodeShare", totalBalance)
+ mc.AddCall(mpContract, &details.UserShareOfBalanceIncludingBeacon, "calculateUserShare", totalBalance)
+ } else {
+ details.NodeShareOfBalanceIncludingBeacon = big.NewInt(0)
+ details.UserShareOfBalanceIncludingBeacon = big.NewInt(0)
+ }
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return fmt.Errorf("error calculating minipool shares: %w", err)
+ }
+
+ return nil
+}
+
+var oneEth = big.NewInt(1e18)
+
+// Get the bond and node fee of a minipool for the specified time
+func (details *NativeMinipoolDetails) GetMinipoolBondAndNodeFee(blockTime time.Time) (*big.Int, *big.Int) {
+ currentBond := details.NodeDepositBalance
+ currentFee := details.NodeFee
+ previousBond := details.LastBondReductionPrevValue
+ previousFee := details.LastBondReductionPrevNodeFee
+
+ var reductionTimeBig *big.Int = details.LastBondReductionTime
+ if reductionTimeBig.Cmp(common.Big0) == 0 {
+ // Never reduced
+ return currentBond, currentFee
+ }
+
+ reductionTime := time.Unix(reductionTimeBig.Int64(), 0)
+ if reductionTime.Sub(blockTime) > 0 {
+ // This block occurred before the reduction
+ if previousFee.Cmp(common.Big0) == 0 {
+ // Catch for minipools that were created before this call existed
+ return previousBond, currentFee
+ }
+ return previousBond, previousFee
+ }
+
+ return currentBond, currentFee
+}
+
+// Get all minipool addresses using the multicaller
+func getNodeMinipoolAddressesFast(rp *rocketpool.RocketPool, contracts *NetworkContracts, nodeAddress common.Address, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get minipool count
+ minipoolCount, err := minipool.GetNodeMinipoolCount(rp, nodeAddress, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ addresses := make([]common.Address, minipoolCount)
+
+ // Run the getters in batches
+ count := int(minipoolCount)
+ for i := 0; i < count; i += minipoolAddressBatchSize {
+ i := i
+ max := i + minipoolAddressBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ mc.AddCall(contracts.RocketMinipoolManager, &addresses[j], "getNodeMinipoolAt", nodeAddress, big.NewInt(int64(j)))
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting minipool addresses for node %s: %w", nodeAddress.Hex(), err)
+ }
+
+ return addresses, nil
+}
+
+// Get all minipool addresses using the multicaller
+func getAllMinipoolAddressesFast(rp *rocketpool.RocketPool, contracts *NetworkContracts, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get minipool count
+ minipoolCount, err := minipool.GetMinipoolCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ addresses := make([]common.Address, minipoolCount)
+
+ // Run the getters in batches
+ count := int(minipoolCount)
+ for i := 0; i < count; i += minipoolAddressBatchSize {
+ i := i
+ max := i + minipoolAddressBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ mc.AddCall(contracts.RocketMinipoolManager, &addresses[j], "getMinipoolAt", big.NewInt(int64(j)))
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting all minipool addresses: %w", err)
+ }
+
+ return addresses, nil
+}
+
+// Get minipool versions using the multicaller
+func getMinipoolVersionsFast(rp *rocketpool.RocketPool, contracts *NetworkContracts, addresses []common.Address, opts *bind.CallOpts) ([]uint8, error) {
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+
+ // Run the getters in batches
+ count := len(addresses)
+ versions := make([]uint8, count)
+ for i := 0; i < count; i += minipoolVersionBatchSize {
+ i := i
+ max := i + minipoolVersionBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ contract, err := rocketpool.GetRocketVersionContractForAddress(rp, addresses[j])
+ if err != nil {
+ return fmt.Errorf("error creating version contract for minipool %s: %w", addresses[j].Hex(), err)
+ }
+ mc.AddCall(contract, &versions[j], "version")
+ }
+ results, err := mc.FlexibleCall(false, opts) // Allow calls to fail - necessary for Prater
+ for j, result := range results {
+ if !result.Success {
+ versions[j+i] = 1 // Anything that failed the version check didn't have the method yet so it must be v1
+ }
+ }
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting minipool versions: %w", err)
+ }
+
+ return versions, nil
+}
+
+// Get multiple minipool details at once
+func getBulkMinipoolDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, addresses []common.Address, versions []uint8, opts *bind.CallOpts) ([]NativeMinipoolDetails, error) {
+ minipoolDetails := make([]NativeMinipoolDetails, len(addresses))
+
+ // Get the balances of the minipools
+ balances, err := contracts.BalanceBatcher.GetEthBalances(addresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting minipool balances: %w", err)
+ }
+ for i := range minipoolDetails {
+ minipoolDetails[i].Balance = balances[i]
+ }
+
+ // Round 1: most of the details
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ count := len(addresses)
+ for i := 0; i < count; i += minipoolBatchSize {
+ i := i
+ max := i + minipoolBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+
+ address := addresses[j]
+ details := &minipoolDetails[j]
+ details.MinipoolAddress = address
+ details.Version = versions[j]
+
+ addMinipoolDetailsCalls(rp, contracts, mc, details, opts)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting minipool details r1: %w", err)
+ }
+
+ // Round 2: NodeShare and UserShare once the refund amount has been populated
+ var wg2 errgroup.Group
+ wg2.SetLimit(threadLimit)
+ for i := 0; i < count; i += minipoolBatchSize {
+ i := i
+ max := i + minipoolBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg2.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ details := &minipoolDetails[j]
+ details.Version = versions[j]
+ addMinipoolShareCalls(rp, mc, details, opts)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg2.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting minipool details r2: %w", err)
+ }
+
+ // Postprocess the minipools
+ for i := range minipoolDetails {
+ fixupMinipoolDetails(&minipoolDetails[i])
+ }
+
+ return minipoolDetails, nil
+}
+
+// Add all of the calls for the minipool details to the multicaller
+func addMinipoolDetailsCalls(rp *rocketpool.RocketPool, contracts *NetworkContracts, mc *multicall.MultiCaller, details *NativeMinipoolDetails, opts *bind.CallOpts) error {
+ // Create the minipool contract binding
+ address := details.MinipoolAddress
+ mp, err := minipool.NewMinipoolFromVersion(rp, address, details.Version, opts)
+ if err != nil {
+ return err
+ }
+ mpContract := mp.GetContract()
+
+ details.Version = mp.GetVersion()
+ mc.AddCall(contracts.RocketMinipoolManager, &details.Exists, "getMinipoolExists", address)
+ mc.AddCall(contracts.RocketMinipoolManager, &details.Pubkey, "getMinipoolPubkey", address)
+ mc.AddCall(contracts.RocketMinipoolManager, &details.WithdrawalCredentials, "getMinipoolWithdrawalCredentials", address)
+ mc.AddCall(contracts.RocketMinipoolManager, &details.Slashed, "getMinipoolRPLSlashed", address)
+ mc.AddCall(mpContract, &details.StatusRaw, "getStatus")
+ mc.AddCall(mpContract, &details.StatusBlock, "getStatusBlock")
+ mc.AddCall(mpContract, &details.StatusTime, "getStatusTime")
+ mc.AddCall(mpContract, &details.Finalised, "getFinalised")
+ mc.AddCall(mpContract, &details.NodeFee, "getNodeFee")
+ mc.AddCall(mpContract, &details.NodeDepositBalance, "getNodeDepositBalance")
+ mc.AddCall(mpContract, &details.NodeDepositAssigned, "getNodeDepositAssigned")
+ mc.AddCall(mpContract, &details.UserDepositBalance, "getUserDepositBalance")
+ mc.AddCall(mpContract, &details.UserDepositAssigned, "getUserDepositAssigned")
+ mc.AddCall(mpContract, &details.UserDepositAssignedTime, "getUserDepositAssignedTime")
+ mc.AddCall(mpContract, &details.UseLatestDelegate, "getUseLatestDelegate")
+ mc.AddCall(mpContract, &details.Delegate, "getDelegate")
+ mc.AddCall(mpContract, &details.PreviousDelegate, "getPreviousDelegate")
+ mc.AddCall(mpContract, &details.EffectiveDelegate, "getEffectiveDelegate")
+ mc.AddCall(mpContract, &details.NodeAddress, "getNodeAddress")
+ mc.AddCall(mpContract, &details.NodeRefundBalance, "getNodeRefundBalance")
+
+ if details.Version < 3 {
+ // These fields are all v3+ only
+ details.UserDistributed = false
+ details.LastBondReductionTime = big.NewInt(0)
+ details.LastBondReductionPrevValue = big.NewInt(0)
+ details.LastBondReductionPrevNodeFee = big.NewInt(0)
+ details.IsVacant = false
+ details.ReduceBondTime = big.NewInt(0)
+ details.ReduceBondCancelled = false
+ details.ReduceBondValue = big.NewInt(0)
+ details.PreMigrationBalance = big.NewInt(0)
+ } else {
+ mc.AddCall(mpContract, &details.UserDistributed, "getUserDistributed")
+ mc.AddCall(mpContract, &details.IsVacant, "getVacant")
+ mc.AddCall(mpContract, &details.PreMigrationBalance, "getPreMigrationBalance")
+
+ // If minipool v3 exists, RocketMinipoolBondReducer exists so this is safe
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.ReduceBondTime, "getReduceBondTime", address)
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.ReduceBondCancelled, "getReduceBondCancelled", address)
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.LastBondReductionTime, "getLastBondReductionTime", address)
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.LastBondReductionPrevValue, "getLastBondReductionPrevValue", address)
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.LastBondReductionPrevNodeFee, "getLastBondReductionPrevNodeFee", address)
+ mc.AddCall(contracts.RocketMinipoolBondReducer, &details.ReduceBondValue, "getReduceBondValue", address)
+ }
+
+ penaltyCountKey := crypto.Keccak256Hash([]byte("network.penalties.penalty"), address.Bytes())
+ mc.AddCall(contracts.RocketStorage, &details.PenaltyCount, "getUint", penaltyCountKey)
+
+ penaltyRatekey := crypto.Keccak256Hash([]byte("minipool.penalty.rate"), address.Bytes())
+ mc.AddCall(contracts.RocketStorage, &details.PenaltyRate, "getUint", penaltyRatekey)
+
+ // Query the minipool manager using the delegate-invariant function
+ mc.AddCall(contracts.RocketMinipoolManager, &details.DepositTypeRaw, "getMinipoolDepositType", address)
+
+ return nil
+}
+
+// Add the calls for the minipool node and user share to the multicaller
+func addMinipoolShareCalls(rp *rocketpool.RocketPool, mc *multicall.MultiCaller, details *NativeMinipoolDetails, opts *bind.CallOpts) error {
+ // Create the minipool contract binding
+ address := details.MinipoolAddress
+ mp, err := minipool.NewMinipoolFromVersion(rp, address, details.Version, opts)
+ if err != nil {
+ return err
+ }
+ mpContract := mp.GetContract()
+
+ details.DistributableBalance = big.NewInt(0).Sub(details.Balance, details.NodeRefundBalance)
+ if details.DistributableBalance.Cmp(zero) >= 0 {
+ mc.AddCall(mpContract, &details.NodeShareOfBalance, "calculateNodeShare", details.DistributableBalance)
+ mc.AddCall(mpContract, &details.UserShareOfBalance, "calculateUserShare", details.DistributableBalance)
+ } else {
+ details.NodeShareOfBalance = big.NewInt(0)
+ details.UserShareOfBalance = big.NewInt(0)
+ }
+
+ return nil
+}
+
+// Fixes a minipool details struct with supplemental logic
+func fixupMinipoolDetails(details *NativeMinipoolDetails) error {
+
+ details.Status = types.MinipoolStatus(details.StatusRaw)
+ details.DepositType = types.MinipoolDeposit(details.DepositTypeRaw)
+
+ return nil
+}
diff --git a/bindings/utils/state/network.go b/bindings/utils/state/network.go
new file mode 100644
index 000000000..18ac814e5
--- /dev/null
+++ b/bindings/utils/state/network.go
@@ -0,0 +1,243 @@
+package state
+
+import (
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/minipool"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/eth"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ networkEffectiveStakeBatchSize int = 250
+)
+
+type NetworkDetails struct {
+ // Redstone
+ RplPrice *big.Int `json:"rpl_price"`
+ MinCollateralFraction *big.Int `json:"min_collateral_fraction"`
+ MaxCollateralFraction *big.Int `json:"max_collateral_fraction"`
+ IntervalDuration time.Duration `json:"interval_duration"`
+ IntervalStart time.Time `json:"interval_start"`
+ NodeOperatorRewardsPercent *big.Int `json:"node_operator_rewards_percent"`
+ TrustedNodeOperatorRewardsPercent *big.Int `json:"trusted_node_operator_rewards_percent"`
+ ProtocolDaoRewardsPercent *big.Int `json:"protocol_dao_rewards_percent"`
+ PendingRPLRewards *big.Int `json:"pending_rpl_rewards"`
+ RewardIndex uint64 `json:"reward_index"`
+ ScrubPeriod time.Duration `json:"scrub_period"`
+ SmoothingPoolAddress common.Address `json:"smoothing_pool_address"`
+ DepositPoolBalance *big.Int `json:"deposit_pool_balance"`
+ DepositPoolExcess *big.Int `json:"deposit_pool_excess"`
+ QueueCapacity minipool.QueueCapacity `json:"queue_capacity"`
+ QueueLength *big.Int `json:"queue_length"`
+ RPLInflationIntervalRate *big.Int `json:"rpl_inflation_interval_rate"`
+ RPLTotalSupply *big.Int `json:"rpl_total_supply"`
+ PricesBlock uint64 `json:"prices_block"`
+ LatestReportablePricesBlock uint64 `json:"latest_reportable_prices_block"`
+ ETHUtilizationRate float64 `json:"eth_utilization_rate"`
+ StakingETHBalance *big.Int `json:"staking_eth_balance"`
+ RETHExchangeRate float64 `json:"reth_exchange_rate"`
+ TotalETHBalance *big.Int `json:"total_eth_balance"`
+ RETHBalance *big.Int `json:"reth_balance"`
+ TotalRETHSupply *big.Int `json:"total_reth_supply"`
+ TotalRPLStake *big.Int `json:"total_rpl_stake"`
+ SmoothingPoolBalance *big.Int `json:"smoothing_pool_balance"`
+ NodeFee float64 `json:"node_fee"`
+ BalancesBlock uint64 `json:"balances_block"`
+ LatestReportableBalancesBlock uint64 `json:"latest_reportable_balances_block"`
+ SubmitBalancesEnabled bool `json:"submit_balances_enabled"`
+ SubmitPricesEnabled bool `json:"submit_prices_enabled"`
+ MinipoolLaunchTimeout *big.Int `json:"minipool_launch_timeout"`
+
+ // Atlas
+ PromotionScrubPeriod time.Duration `json:"promotion_scrub_period"`
+ BondReductionWindowStart time.Duration `json:"bond_reduction_window_start"`
+ BondReductionWindowLength time.Duration `json:"bond_reduction_window_length"`
+ DepositPoolUserBalance *big.Int `json:"deposit_pool_user_balance"`
+
+ // Houston
+ PricesSubmissionFrequency uint64 `json:"prices_submission_frequency"`
+ BalancesSubmissionFrequency uint64 `json:"balances_submission_frequency"`
+}
+
+// Create a snapshot of all of the network's details
+func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) (*NetworkDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ details := &NetworkDetails{}
+
+ // Local vars for things that need to be converted
+ var rewardIndex *big.Int
+ var intervalStart *big.Int
+ var intervalDuration *big.Int
+ var scrubPeriodSeconds *big.Int
+ var totalQueueCapacity *big.Int
+ var effectiveQueueCapacity *big.Int
+ var totalQueueLength *big.Int
+ var pricesBlock *big.Int
+ var pricesSubmissionFrequency *big.Int
+ var ethUtilizationRate *big.Int
+ var rETHExchangeRate *big.Int
+ var nodeFee *big.Int
+ var balancesBlock *big.Int
+ var balancesSubmissionFrequency *big.Int
+ var minipoolLaunchTimeout *big.Int
+ var promotionScrubPeriodSeconds *big.Int
+ var windowStartRaw *big.Int
+ var windowLengthRaw *big.Int
+
+ // Multicall getters
+ contracts.Multicaller.AddCall(contracts.RocketNetworkPrices, &details.RplPrice, "getRPLPrice")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MinCollateralFraction, "getMinimumPerMinipoolStake")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNode, &details.MaxCollateralFraction, "getMaximumPerMinipoolStake")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &rewardIndex, "getRewardIndex")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &intervalStart, "getClaimIntervalTimeStart")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &intervalDuration, "getClaimIntervalTime")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &details.NodeOperatorRewardsPercent, "getClaimingContractPerc", "rocketClaimNode")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &details.TrustedNodeOperatorRewardsPercent, "getClaimingContractPerc", "rocketClaimTrustedNode")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &details.ProtocolDaoRewardsPercent, "getClaimingContractPerc", "rocketClaimDAO")
+ contracts.Multicaller.AddCall(contracts.RocketRewardsPool, &details.PendingRPLRewards, "getPendingRPLRewards")
+ contracts.Multicaller.AddCall(contracts.RocketDAONodeTrustedSettingsMinipool, &scrubPeriodSeconds, "getScrubPeriod")
+ contracts.Multicaller.AddCall(contracts.RocketDepositPool, &details.DepositPoolBalance, "getBalance")
+ contracts.Multicaller.AddCall(contracts.RocketDepositPool, &details.DepositPoolExcess, "getExcessBalance")
+ contracts.Multicaller.AddCall(contracts.RocketMinipoolQueue, &totalQueueCapacity, "getTotalCapacity")
+ contracts.Multicaller.AddCall(contracts.RocketMinipoolQueue, &effectiveQueueCapacity, "getEffectiveCapacity")
+ contracts.Multicaller.AddCall(contracts.RocketMinipoolQueue, &totalQueueLength, "getTotalLength")
+ contracts.Multicaller.AddCall(contracts.RocketTokenRPL, &details.RPLInflationIntervalRate, "getInflationIntervalRate")
+ contracts.Multicaller.AddCall(contracts.RocketTokenRPL, &details.RPLTotalSupply, "totalSupply")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkPrices, &pricesBlock, "getPricesBlock")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkBalances, ðUtilizationRate, "getETHUtilizationRate")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkBalances, &details.StakingETHBalance, "getStakingETHBalance")
+ contracts.Multicaller.AddCall(contracts.RocketTokenRETH, &rETHExchangeRate, "getExchangeRate")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkBalances, &details.TotalETHBalance, "getTotalETHBalance")
+ contracts.Multicaller.AddCall(contracts.RocketTokenRETH, &details.TotalRETHSupply, "totalSupply")
+ contracts.Multicaller.AddCall(contracts.RocketNodeStaking, &details.TotalRPLStake, "getTotalRPLStake")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkFees, &nodeFee, "getNodeFee")
+ contracts.Multicaller.AddCall(contracts.RocketNetworkBalances, &balancesBlock, "getBalancesBlock")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.SubmitBalancesEnabled, "getSubmitBalancesEnabled")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &details.SubmitPricesEnabled, "getSubmitPricesEnabled")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsMinipool, &minipoolLaunchTimeout, "getLaunchTimeout")
+
+ // Atlas things
+ contracts.Multicaller.AddCall(contracts.RocketDAONodeTrustedSettingsMinipool, &promotionScrubPeriodSeconds, "getPromotionScrubPeriod")
+ contracts.Multicaller.AddCall(contracts.RocketDAONodeTrustedSettingsMinipool, &windowStartRaw, "getBondReductionWindowStart")
+ contracts.Multicaller.AddCall(contracts.RocketDAONodeTrustedSettingsMinipool, &windowLengthRaw, "getBondReductionWindowLength")
+ contracts.Multicaller.AddCall(contracts.RocketDepositPool, &details.DepositPoolUserBalance, "getUserBalance")
+
+ // Houston
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &pricesSubmissionFrequency, "getSubmitPricesFrequency")
+ contracts.Multicaller.AddCall(contracts.RocketDAOProtocolSettingsNetwork, &balancesSubmissionFrequency, "getSubmitBalancesFrequency")
+
+ _, err := contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ // Conversion for raw parameters
+ details.RewardIndex = rewardIndex.Uint64()
+ details.IntervalStart = convertToTime(intervalStart)
+ details.IntervalDuration = convertToDuration(intervalDuration)
+ details.ScrubPeriod = convertToDuration(scrubPeriodSeconds)
+ details.SmoothingPoolAddress = *contracts.RocketSmoothingPool.Address
+ details.QueueCapacity = minipool.QueueCapacity{
+ Total: totalQueueCapacity,
+ Effective: effectiveQueueCapacity,
+ }
+ details.QueueLength = totalQueueLength
+ details.PricesBlock = pricesBlock.Uint64()
+
+ details.PricesSubmissionFrequency = pricesSubmissionFrequency.Uint64()
+ details.BalancesSubmissionFrequency = balancesSubmissionFrequency.Uint64()
+ details.ETHUtilizationRate = eth.WeiToEth(ethUtilizationRate)
+ details.RETHExchangeRate = eth.WeiToEth(rETHExchangeRate)
+ details.NodeFee = eth.WeiToEth(nodeFee)
+ details.BalancesBlock = balancesBlock.Uint64()
+ details.MinipoolLaunchTimeout = minipoolLaunchTimeout
+ details.PromotionScrubPeriod = convertToDuration(promotionScrubPeriodSeconds)
+ details.BondReductionWindowStart = convertToDuration(windowStartRaw)
+ details.BondReductionWindowLength = convertToDuration(windowLengthRaw)
+
+ // Get various balances
+ addresses := []common.Address{
+ *contracts.RocketSmoothingPool.Address,
+ *contracts.RocketTokenRETH.Address,
+ }
+ balances, err := contracts.BalanceBatcher.GetEthBalances(addresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting contract balances: %w", err)
+ }
+ details.SmoothingPoolBalance = balances[0]
+ details.RETHBalance = balances[1]
+
+ return details, nil
+}
+
+// Gets the details for a node using the efficient multicall contract
+func GetTotalEffectiveRplStake(rp *rocketpool.RocketPool, contracts *NetworkContracts) (*big.Int, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the list of node addresses
+ addresses, err := getNodeAddressesFast(rp, contracts, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting node addresses: %w", err)
+ }
+ count := len(addresses)
+ minimumStakes := make([]*big.Int, count)
+ effectiveStakes := make([]*big.Int, count)
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+
+ // Run the getters in batches
+ for i := 0; i < count; i += networkEffectiveStakeBatchSize {
+ i := i
+ max := i + networkEffectiveStakeBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ address := addresses[j]
+ mc.AddCall(contracts.RocketNodeStaking, &minimumStakes[j], "getNodeMinimumRPLStake", address)
+ mc.AddCall(contracts.RocketNodeStaking, &effectiveStakes[j], "getNodeEffectiveRPLStake", address)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting effective stakes for all nodes: %w", err)
+ }
+
+ totalEffectiveStake := big.NewInt(0)
+ for i, effectiveStake := range effectiveStakes {
+ minimumStake := minimumStakes[i]
+ // Fix the effective stake
+ if effectiveStake.Cmp(minimumStake) >= 0 {
+ totalEffectiveStake.Add(totalEffectiveStake, effectiveStake)
+ }
+ }
+
+ return totalEffectiveStake, nil
+}
diff --git a/bindings/utils/state/node.go b/bindings/utils/state/node.go
new file mode 100644
index 000000000..b252039c7
--- /dev/null
+++ b/bindings/utils/state/node.go
@@ -0,0 +1,353 @@
+package state
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ legacyNodeBatchSize int = 100
+ nodeAddressBatchSize int = 1000
+)
+
+// Complete details for a node
+type NativeNodeDetails struct {
+ Exists bool `json:"exists"`
+ RegistrationTime *big.Int `json:"registration_time"`
+ TimezoneLocation string `json:"timezone_location"`
+ FeeDistributorInitialised bool `json:"fee_distributor_initialised"`
+ FeeDistributorAddress common.Address `json:"fee_distributor_address"`
+ RewardNetwork *big.Int `json:"reward_network"`
+ RplStake *big.Int `json:"rpl_stake"`
+ EffectiveRPLStake *big.Int `json:"effective_rpl_stake"`
+ MinimumRPLStake *big.Int `json:"minimum_rpl_stake"`
+ MaximumRPLStake *big.Int `json:"maximum_rpl_stake"`
+ EthMatched *big.Int `json:"eth_matched"`
+ EthMatchedLimit *big.Int `json:"eth_matched_limit"`
+ MinipoolCount *big.Int `json:"minipool_count"`
+ BalanceETH *big.Int `json:"balance_eth"`
+ BalanceRETH *big.Int `json:"balance_reth"`
+ BalanceRPL *big.Int `json:"balance_rpl"`
+ BalanceOldRPL *big.Int `json:"balance_old_rpl"`
+ DepositCreditBalance *big.Int `json:"deposit_credit_balance"`
+ DistributorBalanceUserETH *big.Int `json:"distributor_balance_user_eth"` // Must call CalculateAverageFeeAndDistributorShares to get this
+ DistributorBalanceNodeETH *big.Int `json:"distributor_balance_node_eth"` // Must call CalculateAverageFeeAndDistributorShares to get this
+ WithdrawalAddress common.Address `json:"withdrawal_address"`
+ PendingWithdrawalAddress common.Address `json:"pending_withdrawal_address"`
+ SmoothingPoolRegistrationState bool `json:"smoothing_pool_registration_state"`
+ SmoothingPoolRegistrationChanged *big.Int `json:"smoothing_pool_registration_changed"`
+ NodeAddress common.Address `json:"node_address"`
+ AverageNodeFee *big.Int `json:"average_node_fee"` // Must call CalculateAverageFeeAndDistributorShares to get this
+ CollateralisationRatio *big.Int `json:"collateralisation_ratio"`
+ DistributorBalance *big.Int `json:"distributor_balance"`
+}
+
+func timeMax(a, b time.Time) time.Time {
+ if a.After(b) {
+ return a
+ }
+ return b
+}
+
+func timeMin(a, b time.Time) time.Time {
+ if a.Before(b) {
+ return a
+ }
+ return b
+}
+
+// Returns whether the node is eligible for bonuses, and the start and end times of its eligibility
+func (nnd *NativeNodeDetails) IsEligibleForBonuses(eligibleStart time.Time, eligibleEnd time.Time) (bool, time.Time, time.Time) {
+ // Nodes are not eligible for bonuses if they never opted into the smoothing pool
+ registeredTime := time.Unix(nnd.SmoothingPoolRegistrationChanged.Int64(), 0)
+ if registeredTime.Unix() == 0 {
+ return false, time.Time{}, time.Time{}
+ }
+
+ // Nodes are eligible for bonuses if they were in the Smoothing Pool for a portion of the interval
+ if nnd.SmoothingPoolRegistrationState {
+ return registeredTime.Before(eligibleEnd), timeMax(registeredTime, eligibleStart), eligibleEnd
+ }
+
+ // Nodes that weren't opted in at the end of the interval are eligible if they opted out during the interval
+ return registeredTime.Before(eligibleEnd), timeMax(registeredTime, eligibleStart), timeMin(registeredTime, eligibleEnd)
+}
+
+// Gets the details for a node using the efficient multicall contract
+func GetNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, nodeAddress common.Address) (NativeNodeDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+ details := NativeNodeDetails{
+ NodeAddress: nodeAddress,
+ AverageNodeFee: big.NewInt(0),
+ CollateralisationRatio: big.NewInt(0),
+ DistributorBalanceUserETH: big.NewInt(0),
+ DistributorBalanceNodeETH: big.NewInt(0),
+ }
+
+ addNodeDetailsCalls(contracts, contracts.Multicaller, &details, nodeAddress)
+
+ _, err := contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return NativeNodeDetails{}, fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ // Get the node's ETH balance
+ details.BalanceETH, err = rp.Client.BalanceAt(context.Background(), nodeAddress, opts.BlockNumber)
+ if err != nil {
+ return NativeNodeDetails{}, err
+ }
+
+ // Get the distributor balance
+ distributorBalance, err := rp.Client.BalanceAt(context.Background(), details.FeeDistributorAddress, opts.BlockNumber)
+ if err != nil {
+ return NativeNodeDetails{}, err
+ }
+
+ // Do some postprocessing on the node data
+ details.DistributorBalance = distributorBalance
+
+ // Fix the effective stake
+ if details.EffectiveRPLStake.Cmp(details.MinimumRPLStake) == -1 {
+ details.EffectiveRPLStake.SetUint64(0)
+ }
+
+ return details, nil
+}
+
+// Gets the details for all nodes using the efficient multicall contract
+func GetAllNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ([]NativeNodeDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the list of node addresses
+ addresses, err := getNodeAddressesFast(rp, contracts, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting node addresses: %w", err)
+ }
+ count := len(addresses)
+ nodeDetails := make([]NativeNodeDetails, count)
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+
+ // Run the getters in batches
+ for i := 0; i < count; i += legacyNodeBatchSize {
+ i := i
+ max := i + legacyNodeBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ address := addresses[j]
+ details := &nodeDetails[j]
+ details.NodeAddress = address
+ details.AverageNodeFee = big.NewInt(0)
+ details.DistributorBalanceUserETH = big.NewInt(0)
+ details.DistributorBalanceNodeETH = big.NewInt(0)
+ details.CollateralisationRatio = big.NewInt(0)
+
+ addNodeDetailsCalls(contracts, mc, details, address)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting node details: %w", err)
+ }
+
+ // Get the balances of the nodes
+ distributorAddresses := make([]common.Address, count)
+ balances, err := contracts.BalanceBatcher.GetEthBalances(addresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting node balances: %w", err)
+ }
+ for i, details := range nodeDetails {
+ nodeDetails[i].BalanceETH = balances[i]
+ distributorAddresses[i] = details.FeeDistributorAddress
+ }
+
+ // Get the balances of the distributors
+ balances, err = contracts.BalanceBatcher.GetEthBalances(distributorAddresses, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting distributor balances: %w", err)
+ }
+
+ // Do some postprocessing on the node data
+ for i := range nodeDetails {
+ details := &nodeDetails[i]
+ details.DistributorBalance = balances[i]
+
+ // Fix the effective stake
+ if details.EffectiveRPLStake.Cmp(details.MinimumRPLStake) == -1 {
+ details.EffectiveRPLStake.SetUint64(0)
+ }
+ }
+
+ return nodeDetails, nil
+}
+
+func (node *NativeNodeDetails) WasOptedInAt(t time.Time) bool {
+ if node.SmoothingPoolRegistrationState {
+ // If a node is opted in, check if the check time is after the opt-in time
+ return t.After(time.Unix(node.SmoothingPoolRegistrationChanged.Int64(), 0))
+ }
+
+ // If the node isn't opted in and was never opted in, it's not opted in
+ if node.SmoothingPoolRegistrationChanged.Cmp(big.NewInt(0)) == 0 {
+ return false
+ }
+
+ // If a node is opted out, but was opted in, check if the check time is before the opt-out time
+ return t.Before(time.Unix(node.SmoothingPoolRegistrationChanged.Int64(), 0))
+}
+
+// Calculate the average node fee and user/node shares of the distributor's balance
+func (node *NativeNodeDetails) CalculateAverageFeeAndDistributorShares(minipoolDetails []*NativeMinipoolDetails) error {
+
+ // Calculate the total of all fees for staking minipools that aren't finalized
+ totalFee := big.NewInt(0)
+ eligibleMinipools := int64(0)
+ for _, mpd := range minipoolDetails {
+ if mpd.Status == types.Staking && !mpd.Finalised {
+ totalFee.Add(totalFee, mpd.NodeFee)
+ eligibleMinipools++
+ }
+ }
+
+ // Get the average fee (0 if there aren't any minipools)
+ if eligibleMinipools > 0 {
+ node.AverageNodeFee.Div(totalFee, big.NewInt(eligibleMinipools))
+ }
+
+ // Get the user and node portions of the distributor balance
+ distributorBalance := big.NewInt(0).Set(node.DistributorBalance)
+ if distributorBalance.Cmp(big.NewInt(0)) > 0 {
+ nodeBalance := big.NewInt(0)
+ nodeBalance.Mul(distributorBalance, big.NewInt(1e18))
+ nodeBalance.Div(nodeBalance, node.CollateralisationRatio)
+
+ userBalance := big.NewInt(0)
+ userBalance.Sub(distributorBalance, nodeBalance)
+
+ if eligibleMinipools == 0 {
+ // Split it based solely on the collateralisation ratio if there are no minipools (and hence no average fee)
+ node.DistributorBalanceNodeETH = big.NewInt(0).Set(nodeBalance)
+ node.DistributorBalanceUserETH = big.NewInt(0).Sub(distributorBalance, nodeBalance)
+ } else {
+ // Amount of ETH given to the NO as a commission
+ commissionEth := big.NewInt(0)
+ commissionEth.Mul(userBalance, node.AverageNodeFee)
+ commissionEth.Div(commissionEth, big.NewInt(1e18))
+
+ node.DistributorBalanceNodeETH.Add(nodeBalance, commissionEth) // Node gets their portion + commission on user portion
+ node.DistributorBalanceUserETH.Sub(distributorBalance, node.DistributorBalanceNodeETH) // User gets balance - node share
+ }
+
+ } else {
+ // No distributor balance
+ node.DistributorBalanceNodeETH = big.NewInt(0)
+ node.DistributorBalanceUserETH = big.NewInt(0)
+ }
+
+ return nil
+}
+
+// Get all node addresses using the multicaller
+func getNodeAddressesFast(rp *rocketpool.RocketPool, contracts *NetworkContracts, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get minipool count
+ nodeCount, err := node.GetNodeCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ addresses := make([]common.Address, nodeCount)
+
+ // Run the getters in batches
+ count := int(nodeCount)
+ for i := 0; i < count; i += nodeAddressBatchSize {
+ i := i
+ max := i + nodeAddressBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ mc.AddCall(contracts.RocketNodeManager, &addresses[j], "getNodeAt", big.NewInt(int64(j)))
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting node addresses: %w", err)
+ }
+
+ return addresses, nil
+}
+
+// Add all of the calls for the node details to the multicaller
+func addNodeDetailsCalls(contracts *NetworkContracts, mc *multicall.MultiCaller, details *NativeNodeDetails, address common.Address) {
+ mc.AddCall(contracts.RocketNodeManager, &details.Exists, "getNodeExists", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.RegistrationTime, "getNodeRegistrationTime", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.TimezoneLocation, "getNodeTimezoneLocation", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.FeeDistributorInitialised, "getFeeDistributorInitialised", address)
+ mc.AddCall(contracts.RocketNodeDistributorFactory, &details.FeeDistributorAddress, "getProxyAddress", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.RewardNetwork, "getRewardNetwork", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.RplStake, "getNodeRPLStake", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.EffectiveRPLStake, "getNodeEffectiveRPLStake", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.MinimumRPLStake, "getNodeMinimumRPLStake", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.MaximumRPLStake, "getNodeMaximumRPLStake", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.EthMatched, "getNodeETHMatched", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.EthMatchedLimit, "getNodeETHMatchedLimit", address)
+ mc.AddCall(contracts.RocketMinipoolManager, &details.MinipoolCount, "getNodeMinipoolCount", address)
+ mc.AddCall(contracts.RocketTokenRETH, &details.BalanceRETH, "balanceOf", address)
+ mc.AddCall(contracts.RocketTokenRPL, &details.BalanceRPL, "balanceOf", address)
+ mc.AddCall(contracts.RocketTokenRPLFixedSupply, &details.BalanceOldRPL, "balanceOf", address)
+ mc.AddCall(contracts.RocketStorage, &details.WithdrawalAddress, "getNodeWithdrawalAddress", address)
+ mc.AddCall(contracts.RocketStorage, &details.PendingWithdrawalAddress, "getNodePendingWithdrawalAddress", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.SmoothingPoolRegistrationState, "getSmoothingPoolRegistrationState", address)
+ mc.AddCall(contracts.RocketNodeManager, &details.SmoothingPoolRegistrationChanged, "getSmoothingPoolRegistrationChanged", address)
+
+ // Atlas
+ mc.AddCall(contracts.RocketNodeDeposit, &details.DepositCreditBalance, "getNodeDepositCredit", address)
+ mc.AddCall(contracts.RocketNodeStaking, &details.CollateralisationRatio, "getNodeETHCollateralisationRatio", address)
+}
diff --git a/bindings/utils/state/odao.go b/bindings/utils/state/odao.go
new file mode 100644
index 000000000..133f4e5f8
--- /dev/null
+++ b/bindings/utils/state/odao.go
@@ -0,0 +1,188 @@
+package state
+
+import (
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao/trustednode"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ oDaoAddressBatchSize int = 1000
+ oDaoDetailsBatchSize int = 50
+)
+
+type OracleDaoMemberDetails struct {
+ Address common.Address `json:"address"`
+ Exists bool `json:"exists"`
+ ID string `json:"id"`
+ Url string `json:"url"`
+ JoinedTime time.Time `json:"joinedTime"`
+ LastProposalTime time.Time `json:"lastProposalTime"`
+ RPLBondAmount *big.Int `json:"rplBondAmount"`
+ ReplacementAddress common.Address `json:"replacementAddress"`
+ IsChallenged bool `json:"isChallenged"`
+ joinedTimeRaw *big.Int `json:"-"`
+ lastProposalTimeRaw *big.Int `json:"-"`
+}
+
+// Gets the details for an Oracle DAO member using the efficient multicall contract
+func GetOracleDaoMemberDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, memberAddress common.Address) (OracleDaoMemberDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ details := OracleDaoMemberDetails{}
+ details.Address = memberAddress
+
+ addOracleDaoMemberDetailsCalls(contracts, contracts.Multicaller, &details)
+
+ _, err := contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return OracleDaoMemberDetails{}, fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ fixupOracleDaoMemberDetails(&details)
+
+ return details, nil
+}
+
+// Gets all Oracle DAO member details using the efficient multicall contract
+func GetAllOracleDaoMemberDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ([]OracleDaoMemberDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the list of all minipool addresses
+ addresses, err := getOdaoAddresses(rp, contracts, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting Oracle DAO addresses: %w", err)
+ }
+
+ // Get the minipool details
+ return getOracleDaoDetails(rp, contracts, addresses, opts)
+}
+
+// Get all Oracle DAO addresses
+func getOdaoAddresses(rp *rocketpool.RocketPool, contracts *NetworkContracts, opts *bind.CallOpts) ([]common.Address, error) {
+ // Get minipool count
+ memberCount, err := trustednode.GetMemberCount(rp, opts)
+ if err != nil {
+ return []common.Address{}, err
+ }
+
+ // Sync
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ addresses := make([]common.Address, memberCount)
+
+ // Run the getters in batches
+ count := int(memberCount)
+ for i := 0; i < count; i += minipoolAddressBatchSize {
+ i := i
+ max := i + oDaoAddressBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ mc.AddCall(contracts.RocketDAONodeTrusted, &addresses[j], "getMemberAt", big.NewInt(int64(j)))
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting Oracle DAO addresses: %w", err)
+ }
+
+ return addresses, nil
+}
+
+// Get the details of the Oracle DAO members
+func getOracleDaoDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, addresses []common.Address, opts *bind.CallOpts) ([]OracleDaoMemberDetails, error) {
+ memberDetails := make([]OracleDaoMemberDetails, len(addresses))
+
+ // Get the details in batches
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ count := len(addresses)
+ for i := 0; i < count; i += minipoolBatchSize {
+ i := i
+ max := i + minipoolBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+
+ address := addresses[j]
+ details := &memberDetails[j]
+ details.Address = address
+
+ addOracleDaoMemberDetailsCalls(contracts, mc, details)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting Oracle DAO details: %w", err)
+ }
+
+ // Postprocessing
+ for i := range memberDetails {
+ details := &memberDetails[i]
+ fixupOracleDaoMemberDetails(details)
+ }
+
+ return memberDetails, nil
+}
+
+// Add the Oracle DAO details getters to the multicaller
+func addOracleDaoMemberDetailsCalls(contracts *NetworkContracts, mc *multicall.MultiCaller, details *OracleDaoMemberDetails) error {
+ address := details.Address
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.Exists, "getMemberIsValid", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.ID, "getMemberID", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.Url, "getMemberUrl", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.joinedTimeRaw, "getMemberJoinedTime", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.lastProposalTimeRaw, "getMemberLastProposalTime", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.RPLBondAmount, "getMemberRPLBondAmount", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.ReplacementAddress, "getMemberReplacedAddress", address)
+ mc.AddCall(contracts.RocketDAONodeTrusted, &details.IsChallenged, "getMemberIsChallenged", address)
+ return nil
+}
+
+// Fixes a member details struct with supplemental logic
+func fixupOracleDaoMemberDetails(details *OracleDaoMemberDetails) error {
+ details.JoinedTime = convertToTime(details.joinedTimeRaw)
+ details.LastProposalTime = convertToTime(details.lastProposalTimeRaw)
+ return nil
+}
diff --git a/bindings/utils/state/pdao.go b/bindings/utils/state/pdao.go
new file mode 100644
index 000000000..0f2dd8208
--- /dev/null
+++ b/bindings/utils/state/pdao.go
@@ -0,0 +1,212 @@
+package state
+
+import (
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/rocket-pool/rocketpool-go/dao/protocol"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+ "github.com/rocket-pool/rocketpool-go/types"
+ "github.com/rocket-pool/rocketpool-go/utils/multicall"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ pDaoPropDetailsBatchSize int = 50
+)
+
+// Proposal details
+type protocolDaoProposalDetailsRaw struct {
+ ID uint64
+ DAO string
+ ProposerAddress common.Address
+ TargetBlock *big.Int
+ Message string
+ CreatedTime *big.Int
+ ChallengeWindow *big.Int
+ VotingStartTime *big.Int
+ Phase1EndTime *big.Int
+ Phase2EndTime *big.Int
+ ExpiryTime *big.Int
+ VotingPowerRequired *big.Int
+ VotingPowerFor *big.Int
+ VotingPowerAgainst *big.Int
+ VotingPowerAbstained *big.Int
+ VotingPowerToVeto *big.Int
+ IsDestroyed bool
+ IsFinalized bool
+ IsExecuted bool
+ IsVetoed bool
+ VetoQuorum *big.Int
+ Payload []byte
+ PayloadStr string
+ State uint8
+ ProposalBond *big.Int
+ ChallengeBond *big.Int
+ DefeatIndex *big.Int
+}
+
+// Gets a Protocol DAO proposal's details using the efficient multicall contract
+func GetProtocolDaoProposalDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, proposalID uint64) (protocol.ProtocolDaoProposalDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ details := protocol.ProtocolDaoProposalDetails{}
+ rawDetails := protocolDaoProposalDetailsRaw{}
+ details.ID = proposalID
+
+ addProposalCalls(contracts, contracts.Multicaller, &rawDetails)
+
+ _, err := contracts.Multicaller.FlexibleCall(true, opts)
+ if err != nil {
+ return details, fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ fixupPdaoProposalDetails(rp, &rawDetails, &details, opts)
+
+ return details, nil
+}
+
+// Gets all Protocol DAO proposal details using the efficient multicall contract
+func GetAllProtocolDaoProposalDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ([]protocol.ProtocolDaoProposalDetails, error) {
+ opts := &bind.CallOpts{
+ BlockNumber: contracts.ElBlockNumber,
+ }
+
+ // Get the number of proposals available
+ propCount, err := protocol.GetTotalProposalCount(rp, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error getting proposal count: %w", err)
+ }
+
+ // Make the proposal IDs (1-indexed) and return the details
+ ids := make([]uint64, propCount)
+ for i := range ids {
+ ids[i] = uint64(i + 1)
+ }
+ return getProposalDetails(rp, contracts, ids, opts)
+}
+
+// Get the details of all protocol DAO proposals
+func getProposalDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts, ids []uint64, opts *bind.CallOpts) ([]protocol.ProtocolDaoProposalDetails, error) {
+ propDetailsRaw := make([]protocolDaoProposalDetailsRaw, len(ids))
+
+ // Get the details in batches
+ var wg errgroup.Group
+ wg.SetLimit(threadLimit)
+ count := len(propDetailsRaw)
+ for i := 0; i < count; i += pDaoPropDetailsBatchSize {
+ i := i
+ max := i + pDaoPropDetailsBatchSize
+ if max > count {
+ max = count
+ }
+
+ wg.Go(func() error {
+ var err error
+ mc, err := multicall.NewMultiCaller(rp.Client, contracts.Multicaller.ContractAddress)
+ if err != nil {
+ return err
+ }
+ for j := i; j < max; j++ {
+ id := ids[j]
+ details := &propDetailsRaw[j]
+ details.ID = id
+
+ addProposalCalls(contracts, mc, details)
+ }
+ _, err = mc.FlexibleCall(true, opts)
+ if err != nil {
+ return fmt.Errorf("error executing multicall: %w", err)
+ }
+
+ return nil
+ })
+ }
+
+ if err := wg.Wait(); err != nil {
+ return nil, fmt.Errorf("error getting Protocol DAO proposal details: %w", err)
+ }
+
+ // Postprocessing
+ props := make([]protocol.ProtocolDaoProposalDetails, len(ids))
+ for i := range propDetailsRaw {
+ rawDetails := &propDetailsRaw[i]
+ details := &props[i]
+ fixupPdaoProposalDetails(rp, rawDetails, details, opts)
+ }
+
+ return props, nil
+}
+
+// Get the details of a proposal
+func addProposalCalls(contracts *NetworkContracts, mc *multicall.MultiCaller, details *protocolDaoProposalDetailsRaw) error {
+ id := big.NewInt(0).SetUint64(details.ID)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.ProposerAddress, "getProposer", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.DAO, "getDAO", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.TargetBlock, "getProposalBlock", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.Message, "getMessage", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingStartTime, "getStart", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.Phase1EndTime, "getPhase1End", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.Phase2EndTime, "getPhase2End", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.ExpiryTime, "getExpires", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.CreatedTime, "getCreated", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingPowerRequired, "getVotingPowerRequired", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingPowerFor, "getVotingPowerFor", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingPowerAgainst, "getVotingPowerAgainst", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingPowerAbstained, "getVotingPowerAbstained", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VotingPowerToVeto, "getVotingPowerVeto", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.IsDestroyed, "getDestroyed", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.IsFinalized, "getFinalised", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.IsExecuted, "getExecuted", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.IsVetoed, "getVetoed", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.VetoQuorum, "getProposalVetoQuorum", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.Payload, "getPayload", id)
+ mc.AddCall(contracts.RocketDAOProtocolProposal, &details.State, "getState", id)
+ mc.AddCall(contracts.RocketDAOProtocolVerifier, &details.DefeatIndex, "getDefeatIndex", id)
+ mc.AddCall(contracts.RocketDAOProtocolVerifier, &details.ProposalBond, "getProposalBond", id)
+ mc.AddCall(contracts.RocketDAOProtocolVerifier, &details.ChallengeBond, "getChallengeBond", id)
+ mc.AddCall(contracts.RocketDAOProtocolVerifier, &details.ChallengeWindow, "getChallengePeriod", id)
+ return nil
+}
+
+// Converts a raw proposal to a well-formatted one
+func fixupPdaoProposalDetails(rp *rocketpool.RocketPool, rawDetails *protocolDaoProposalDetailsRaw, details *protocol.ProtocolDaoProposalDetails, opts *bind.CallOpts) error {
+ details.ID = rawDetails.ID
+ details.DAO = rawDetails.DAO
+ details.ProposerAddress = rawDetails.ProposerAddress
+ details.TargetBlock = uint32(rawDetails.TargetBlock.Uint64())
+ details.Message = rawDetails.Message
+ details.VotingStartTime = time.Unix(rawDetails.VotingStartTime.Int64(), 0)
+ details.Phase1EndTime = time.Unix(rawDetails.Phase1EndTime.Int64(), 0)
+ details.Phase2EndTime = time.Unix(rawDetails.Phase2EndTime.Int64(), 0)
+ details.ExpiryTime = time.Unix(rawDetails.ExpiryTime.Int64(), 0)
+ details.CreatedTime = time.Unix(rawDetails.CreatedTime.Int64(), 0)
+ details.VotingPowerRequired = rawDetails.VotingPowerRequired
+ details.VotingPowerFor = rawDetails.VotingPowerFor
+ details.VotingPowerAgainst = rawDetails.VotingPowerAgainst
+ details.VotingPowerAbstained = rawDetails.VotingPowerAbstained
+ details.VotingPowerToVeto = rawDetails.VotingPowerToVeto
+ details.IsDestroyed = rawDetails.IsDestroyed
+ details.IsFinalized = rawDetails.IsFinalized
+ details.IsExecuted = rawDetails.IsExecuted
+ details.IsVetoed = rawDetails.IsVetoed
+ details.VetoQuorum = rawDetails.VetoQuorum
+ details.Payload = rawDetails.Payload
+ details.State = types.ProtocolDaoProposalState(rawDetails.State)
+ details.DefeatIndex = rawDetails.DefeatIndex.Uint64()
+ details.ProposalBond = rawDetails.ProposalBond
+ details.ChallengeBond = rawDetails.ChallengeBond
+ details.ChallengeWindow = time.Second * time.Duration(rawDetails.ChallengeWindow.Uint64())
+
+ var err error
+ details.PayloadStr, err = protocol.GetProposalPayloadString(rp, rawDetails.Payload, opts)
+ if err != nil {
+ details.PayloadStr = fmt.Sprintf("", err.Error())
+ }
+ return nil
+}
diff --git a/bindings/utils/strings/sanitize.go b/bindings/utils/strings/sanitize.go
new file mode 100644
index 000000000..ac8904f3a
--- /dev/null
+++ b/bindings/utils/strings/sanitize.go
@@ -0,0 +1,16 @@
+package strings
+
+import (
+ "strings"
+ "unicode"
+)
+
+// Remove non-printable characters from a string
+func Sanitize(str string) string {
+ return strings.Map(func(r rune) rune {
+ if unicode.IsPrint(r) {
+ return r
+ }
+ return -1
+ }, str)
+}
diff --git a/bindings/utils/version-checker.go b/bindings/utils/version-checker.go
new file mode 100644
index 000000000..efb319ea3
--- /dev/null
+++ b/bindings/utils/version-checker.go
@@ -0,0 +1,51 @@
+package utils
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/hashicorp/go-version"
+ "github.com/rocket-pool/rocketpool-go/network"
+ "github.com/rocket-pool/rocketpool-go/node"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+func GetCurrentVersion(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*version.Version, error) {
+
+ // Check for v1.3.1 (Houston Hotfix)
+ networkVotingVersion, err := network.GetRocketNetworkVotingVersion(rp, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error checking network voting version: %w", err)
+ }
+ if networkVotingVersion > 1 {
+ return version.NewSemver("1.3.1")
+ }
+
+ nodeMgrVersion, err := node.GetNodeManagerVersion(rp, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error checking node manager version: %w", err)
+ }
+
+ // Check for v1.3 (Houston)
+ if nodeMgrVersion > 3 {
+ return version.NewSemver("1.3.0")
+ }
+
+ // Check for v1.2 (Atlas)
+ nodeStakingVersion, err := node.GetNodeStakingVersion(rp, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error checking node staking version: %w", err)
+ }
+ if nodeStakingVersion > 3 {
+ return version.NewSemver("1.2.0")
+ }
+
+ // Check for v1.1 (Redstone)
+ if nodeMgrVersion > 1 {
+ return version.NewSemver("1.1.0")
+ }
+
+ // v1.0 (Classic)
+ return version.NewSemver("1.0.0")
+
+}
diff --git a/bindings/utils/wait.go b/bindings/utils/wait.go
new file mode 100644
index 000000000..2b32541eb
--- /dev/null
+++ b/bindings/utils/wait.go
@@ -0,0 +1,52 @@
+package utils
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/rocket-pool/rocketpool-go/rocketpool"
+)
+
+// Wait for a transaction to get mined
+func WaitForTransaction(client rocketpool.ExecutionClient, hash common.Hash) (*types.Receipt, error) {
+
+ var tx *types.Transaction
+ var err error
+
+ // Get the transaction from its hash, retrying for 30 sec if it wasn't found
+ for i := 0; i < 30; i++ {
+ if i == 29 {
+ return nil, fmt.Errorf("Transaction not found after 30 seconds.")
+ }
+
+ tx, _, err = client.TransactionByHash(context.Background(), hash)
+ if err != nil {
+ if err.Error() == "not found" {
+ time.Sleep(1 * time.Second)
+ continue
+ }
+ return nil, err
+ } else {
+ break
+ }
+ }
+
+ // Wait for transaction to be mined
+ txReceipt, err := bind.WaitMined(context.Background(), client, tx)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check transaction status
+ if txReceipt.Status == 0 {
+ return txReceipt, errors.New("Transaction failed with status 0")
+ }
+
+ // Return
+ return txReceipt, nil
+}