From d7eefe53ac59380d5d70480543cb9246145e41ff Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Wed, 21 May 2025 14:04:56 -0400 Subject: [PATCH] Merge remote-tracking branch 'rocketpool-go/jms/monorepo-master' into HEAD --- .gitignore | 2 +- bindings/LICENSE | 674 ++++++++++++++++ bindings/README.md | 2 + bindings/auction/auction.go | 606 ++++++++++++++ bindings/azure-pipelines.yml | 36 + bindings/contracts/rocket-storage.go | 683 ++++++++++++++++ bindings/dao/claim.go | 30 + bindings/dao/proposal-payload.go | 64 ++ bindings/dao/proposals.go | 647 +++++++++++++++ bindings/dao/protocol/dao.go | 18 + bindings/dao/protocol/proposal.go | 727 +++++++++++++++++ bindings/dao/protocol/proposals.go | 393 +++++++++ bindings/dao/protocol/verify.go | 535 +++++++++++++ bindings/dao/security/actions.go | 130 +++ bindings/dao/security/proposals.go | 166 ++++ bindings/dao/security/security.go | 249 ++++++ bindings/dao/trustednode/actions.go | 147 ++++ bindings/dao/trustednode/dao.go | 363 +++++++++ bindings/dao/trustednode/proposals.go | 310 +++++++ bindings/deposit/deposit.go | 104 +++ bindings/docs/docgen.go | 93 +++ bindings/go.mod | 72 ++ bindings/go.sum | 304 +++++++ bindings/legacy/v1.0.0/minipool/minipool.go | 552 +++++++++++++ bindings/legacy/v1.0.0/rewards/node.go | 132 +++ bindings/legacy/v1.0.0/rewards/rewards.go | 157 ++++ .../legacy/v1.0.0/rewards/trusted-node.go | 132 +++ .../legacy/v1.0.0/utils/address_generation.go | 75 ++ bindings/legacy/v1.1.0-rc1/rewards/rewards.go | 306 +++++++ bindings/legacy/v1.1.0/minipool/factory.go | 37 + bindings/legacy/v1.1.0/minipool/queue.go | 302 +++++++ bindings/legacy/v1.1.0/network/prices.go | 99 +++ bindings/legacy/v1.1.0/node/deposit.go | 64 ++ bindings/legacy/v1.1.0/node/staking.go | 211 +++++ .../legacy/v1.1.0/utils/address_generation.go | 71 ++ bindings/legacy/v1.2.0/network/balances.go | 138 ++++ bindings/legacy/v1.2.0/network/prices.go | 85 ++ bindings/minipool/bond-reducer.go | 143 ++++ bindings/minipool/factory.go | 34 + bindings/minipool/minipool-constructor.go | 88 ++ bindings/minipool/minipool-contract-v2.go | 610 ++++++++++++++ bindings/minipool/minipool-contract-v3.go | 651 +++++++++++++++ bindings/minipool/minipool-interface.go | 102 +++ bindings/minipool/minipool.go | 626 +++++++++++++++ bindings/minipool/queue.go | 144 ++++ bindings/minipool/status.go | 42 + bindings/network/balances.go | 229 ++++++ bindings/network/fees.go | 60 ++ bindings/network/penalties.go | 43 + bindings/network/prices.go | 178 ++++ bindings/network/voting.go | 229 ++++++ bindings/node/deposit.go | 182 +++++ bindings/node/distributor.go | 99 +++ bindings/node/node.go | 741 +++++++++++++++++ bindings/node/staking.go | 273 +++++++ bindings/rewards/distributor-mainnet.go | 90 +++ bindings/rewards/rewards.go | 327 ++++++++ bindings/rocketpool/abi.go | 70 ++ bindings/rocketpool/contract.go | 252 ++++++ bindings/rocketpool/ec-interface.go | 105 +++ bindings/rocketpool/rewards.go | 35 + bindings/rocketpool/rocketpool.go | 365 +++++++++ bindings/rocketpool/v1.0.0-manager.go | 60 ++ bindings/rocketpool/v1.1.0-manager.go | 67 ++ bindings/rocketpool/v1.1.0-rc1-manager.go | 55 ++ bindings/rocketpool/v1.2.0-manager.go | 57 ++ bindings/rocketpool/version-interface.go | 77 ++ bindings/rocketpool/version-manager.go | 107 +++ bindings/settings/protocol/auction.go | 196 +++++ bindings/settings/protocol/deposit.go | 168 ++++ bindings/settings/protocol/inflation.go | 65 ++ bindings/settings/protocol/minipool.go | 163 ++++ bindings/settings/protocol/network.go | 381 +++++++++ bindings/settings/protocol/node.go | 175 ++++ bindings/settings/protocol/proposals.go | 255 ++++++ bindings/settings/protocol/rewards.go | 135 ++++ bindings/settings/protocol/security.go | 128 +++ bindings/settings/security/auction.go | 32 + bindings/settings/security/deposit.go | 32 + bindings/settings/security/minipool.go | 32 + bindings/settings/security/network.go | 32 + bindings/settings/security/node.go | 48 ++ bindings/settings/trustednode/members.go | 168 ++++ bindings/settings/trustednode/minipool.go | 127 +++ bindings/settings/trustednode/proposals.go | 127 +++ bindings/settings/trustednode/rewards.go | 39 + bindings/storage/address-queue-storage.go | 61 ++ bindings/storage/rocket-storage.go | 68 ++ bindings/test.sh | 141 ++++ bindings/tests/auction/auction_test.go | 333 ++++++++ bindings/tests/auction/main_test.go | 77 ++ bindings/tests/config.go | 31 + bindings/tests/dao/main_test.go | 72 ++ bindings/tests/dao/proposals_test.go | 209 +++++ bindings/tests/dao/trustednode/dao_test.go | 187 +++++ bindings/tests/dao/trustednode/main_test.go | 73 ++ .../tests/dao/trustednode/proposals_test.go | 321 ++++++++ bindings/tests/deposit/deposit_test.go | 106 +++ bindings/tests/deposit/main_test.go | 58 ++ bindings/tests/minipool/contract_test.go | 757 ++++++++++++++++++ bindings/tests/minipool/main_test.go | 68 ++ bindings/tests/minipool/minipool_test.go | 139 ++++ bindings/tests/minipool/queue_test.go | 229 ++++++ bindings/tests/minipool/status_test.go | 78 ++ bindings/tests/network/balances_test.go | 75 ++ bindings/tests/network/fees_test.go | 98 +++ bindings/tests/network/main_test.go | 63 ++ bindings/tests/network/prices_test.go | 53 ++ bindings/tests/node/deposit_test.go | 60 ++ bindings/tests/node/distributor_test.go | 31 + bindings/tests/node/main_test.go | 57 ++ bindings/tests/node/node_test.go | 169 ++++ bindings/tests/node/staking_test.go | 267 ++++++ bindings/tests/rewards/main_test.go | 58 ++ bindings/tests/rewards/node_test.go | 173 ++++ bindings/tests/rewards/trusted_node_test.go | 120 +++ bindings/tests/rocketpool/main_test.go | 39 + bindings/tests/rocketpool/rocketpool_test.go | 157 ++++ .../tests/settings/protocol/auction_test.go | 94 +++ .../tests/settings/protocol/deposit_test.go | 74 ++ .../tests/settings/protocol/inflation_test.go | 44 + bindings/tests/settings/protocol/main_test.go | 48 ++ .../tests/settings/protocol/minipool_test.go | 84 ++ .../tests/settings/protocol/network_test.go | 124 +++ bindings/tests/settings/protocol/node_test.go | 63 ++ .../tests/settings/protocol/rewards_test.go | 56 ++ .../tests/settings/trustednode/main_test.go | 63 ++ .../settings/trustednode/members_test.go | 162 ++++ .../settings/trustednode/proposals_test.go | 169 ++++ bindings/tests/testutils/accounts/accounts.go | 49 ++ bindings/tests/testutils/auction/auction.go | 78 ++ bindings/tests/testutils/dao/proposals.go | 48 ++ bindings/tests/testutils/evm/mining.go | 50 ++ bindings/tests/testutils/evm/snapshots.go | 45 ++ bindings/tests/testutils/minipool/minipool.go | 121 +++ bindings/tests/testutils/node/deposit.go | 77 ++ bindings/tests/testutils/node/node.go | 74 ++ bindings/tests/testutils/node/staking.go | 37 + bindings/tests/testutils/tokens/reth/reth.go | 28 + bindings/tests/testutils/tokens/rpl/rpl.go | 48 ++ .../tests/testutils/validator/deposit-data.go | 59 ++ bindings/tests/tokens/main_test.go | 68 ++ bindings/tests/tokens/reth_test.go | 240 ++++++ bindings/tests/tokens/rpl_fixed_test.go | 127 +++ bindings/tests/tokens/rpl_test.go | 218 +++++ bindings/tests/tokens/tokens_test.go | 63 ++ bindings/tests/utils/eth/transactions_test.go | 65 ++ bindings/tests/utils/eth/units_test.go | 38 + bindings/tests/utils/stage4_bootstrap.go | 30 + bindings/tokens/reth.go | 211 +++++ bindings/tokens/rpl-fixed.go | 109 +++ bindings/tokens/rpl.go | 185 +++++ bindings/tokens/tokens.go | 152 ++++ bindings/types/beacon.go | 105 +++ bindings/types/dao.go | 123 +++ bindings/types/minipool.go | 105 +++ bindings/utils/address_generation.go | 17 + bindings/utils/deposit_retrieval.go | 123 +++ bindings/utils/eth/erc20.go | 205 +++++ bindings/utils/eth/logs.go | 124 +++ bindings/utils/eth/transactions.go | 125 +++ bindings/utils/eth/units.go | 82 ++ bindings/utils/json/json.go | 19 + bindings/utils/multicall/abi.go | 21 + bindings/utils/multicall/balances.go | 97 +++ bindings/utils/multicall/multicaller.go | 133 +++ bindings/utils/state/common.go | 23 + bindings/utils/state/contracts.go | 248 ++++++ bindings/utils/state/minipool.go | 602 ++++++++++++++ bindings/utils/state/network.go | 243 ++++++ bindings/utils/state/node.go | 353 ++++++++ bindings/utils/state/odao.go | 188 +++++ bindings/utils/state/pdao.go | 212 +++++ bindings/utils/strings/sanitize.go | 16 + bindings/utils/version-checker.go | 51 ++ bindings/utils/wait.go | 52 ++ 176 files changed, 28055 insertions(+), 1 deletion(-) create mode 100644 bindings/LICENSE create mode 100644 bindings/README.md create mode 100644 bindings/auction/auction.go create mode 100644 bindings/azure-pipelines.yml create mode 100644 bindings/contracts/rocket-storage.go create mode 100644 bindings/dao/claim.go create mode 100644 bindings/dao/proposal-payload.go create mode 100644 bindings/dao/proposals.go create mode 100644 bindings/dao/protocol/dao.go create mode 100644 bindings/dao/protocol/proposal.go create mode 100644 bindings/dao/protocol/proposals.go create mode 100755 bindings/dao/protocol/verify.go create mode 100644 bindings/dao/security/actions.go create mode 100644 bindings/dao/security/proposals.go create mode 100644 bindings/dao/security/security.go create mode 100644 bindings/dao/trustednode/actions.go create mode 100644 bindings/dao/trustednode/dao.go create mode 100644 bindings/dao/trustednode/proposals.go create mode 100644 bindings/deposit/deposit.go create mode 100644 bindings/docs/docgen.go create mode 100644 bindings/go.mod create mode 100644 bindings/go.sum create mode 100644 bindings/legacy/v1.0.0/minipool/minipool.go create mode 100644 bindings/legacy/v1.0.0/rewards/node.go create mode 100644 bindings/legacy/v1.0.0/rewards/rewards.go create mode 100644 bindings/legacy/v1.0.0/rewards/trusted-node.go create mode 100644 bindings/legacy/v1.0.0/utils/address_generation.go create mode 100644 bindings/legacy/v1.1.0-rc1/rewards/rewards.go create mode 100644 bindings/legacy/v1.1.0/minipool/factory.go create mode 100644 bindings/legacy/v1.1.0/minipool/queue.go create mode 100644 bindings/legacy/v1.1.0/network/prices.go create mode 100644 bindings/legacy/v1.1.0/node/deposit.go create mode 100644 bindings/legacy/v1.1.0/node/staking.go create mode 100644 bindings/legacy/v1.1.0/utils/address_generation.go create mode 100644 bindings/legacy/v1.2.0/network/balances.go create mode 100644 bindings/legacy/v1.2.0/network/prices.go create mode 100644 bindings/minipool/bond-reducer.go create mode 100644 bindings/minipool/factory.go create mode 100644 bindings/minipool/minipool-constructor.go create mode 100644 bindings/minipool/minipool-contract-v2.go create mode 100644 bindings/minipool/minipool-contract-v3.go create mode 100644 bindings/minipool/minipool-interface.go create mode 100644 bindings/minipool/minipool.go create mode 100644 bindings/minipool/queue.go create mode 100644 bindings/minipool/status.go create mode 100644 bindings/network/balances.go create mode 100644 bindings/network/fees.go create mode 100644 bindings/network/penalties.go create mode 100644 bindings/network/prices.go create mode 100644 bindings/network/voting.go create mode 100644 bindings/node/deposit.go create mode 100644 bindings/node/distributor.go create mode 100644 bindings/node/node.go create mode 100644 bindings/node/staking.go create mode 100644 bindings/rewards/distributor-mainnet.go create mode 100644 bindings/rewards/rewards.go create mode 100644 bindings/rocketpool/abi.go create mode 100644 bindings/rocketpool/contract.go create mode 100644 bindings/rocketpool/ec-interface.go create mode 100644 bindings/rocketpool/rewards.go create mode 100644 bindings/rocketpool/rocketpool.go create mode 100644 bindings/rocketpool/v1.0.0-manager.go create mode 100644 bindings/rocketpool/v1.1.0-manager.go create mode 100644 bindings/rocketpool/v1.1.0-rc1-manager.go create mode 100644 bindings/rocketpool/v1.2.0-manager.go create mode 100644 bindings/rocketpool/version-interface.go create mode 100644 bindings/rocketpool/version-manager.go create mode 100644 bindings/settings/protocol/auction.go create mode 100644 bindings/settings/protocol/deposit.go create mode 100644 bindings/settings/protocol/inflation.go create mode 100644 bindings/settings/protocol/minipool.go create mode 100644 bindings/settings/protocol/network.go create mode 100644 bindings/settings/protocol/node.go create mode 100644 bindings/settings/protocol/proposals.go create mode 100644 bindings/settings/protocol/rewards.go create mode 100644 bindings/settings/protocol/security.go create mode 100644 bindings/settings/security/auction.go create mode 100644 bindings/settings/security/deposit.go create mode 100644 bindings/settings/security/minipool.go create mode 100644 bindings/settings/security/network.go create mode 100644 bindings/settings/security/node.go create mode 100644 bindings/settings/trustednode/members.go create mode 100644 bindings/settings/trustednode/minipool.go create mode 100644 bindings/settings/trustednode/proposals.go create mode 100644 bindings/settings/trustednode/rewards.go create mode 100644 bindings/storage/address-queue-storage.go create mode 100644 bindings/storage/rocket-storage.go create mode 100755 bindings/test.sh create mode 100644 bindings/tests/auction/auction_test.go create mode 100644 bindings/tests/auction/main_test.go create mode 100644 bindings/tests/config.go create mode 100644 bindings/tests/dao/main_test.go create mode 100644 bindings/tests/dao/proposals_test.go create mode 100644 bindings/tests/dao/trustednode/dao_test.go create mode 100644 bindings/tests/dao/trustednode/main_test.go create mode 100644 bindings/tests/dao/trustednode/proposals_test.go create mode 100644 bindings/tests/deposit/deposit_test.go create mode 100644 bindings/tests/deposit/main_test.go create mode 100644 bindings/tests/minipool/contract_test.go create mode 100644 bindings/tests/minipool/main_test.go create mode 100644 bindings/tests/minipool/minipool_test.go create mode 100644 bindings/tests/minipool/queue_test.go create mode 100644 bindings/tests/minipool/status_test.go create mode 100644 bindings/tests/network/balances_test.go create mode 100644 bindings/tests/network/fees_test.go create mode 100644 bindings/tests/network/main_test.go create mode 100644 bindings/tests/network/prices_test.go create mode 100644 bindings/tests/node/deposit_test.go create mode 100644 bindings/tests/node/distributor_test.go create mode 100644 bindings/tests/node/main_test.go create mode 100644 bindings/tests/node/node_test.go create mode 100644 bindings/tests/node/staking_test.go create mode 100644 bindings/tests/rewards/main_test.go create mode 100644 bindings/tests/rewards/node_test.go create mode 100644 bindings/tests/rewards/trusted_node_test.go create mode 100644 bindings/tests/rocketpool/main_test.go create mode 100644 bindings/tests/rocketpool/rocketpool_test.go create mode 100644 bindings/tests/settings/protocol/auction_test.go create mode 100644 bindings/tests/settings/protocol/deposit_test.go create mode 100644 bindings/tests/settings/protocol/inflation_test.go create mode 100644 bindings/tests/settings/protocol/main_test.go create mode 100644 bindings/tests/settings/protocol/minipool_test.go create mode 100644 bindings/tests/settings/protocol/network_test.go create mode 100644 bindings/tests/settings/protocol/node_test.go create mode 100644 bindings/tests/settings/protocol/rewards_test.go create mode 100644 bindings/tests/settings/trustednode/main_test.go create mode 100644 bindings/tests/settings/trustednode/members_test.go create mode 100644 bindings/tests/settings/trustednode/proposals_test.go create mode 100644 bindings/tests/testutils/accounts/accounts.go create mode 100644 bindings/tests/testutils/auction/auction.go create mode 100644 bindings/tests/testutils/dao/proposals.go create mode 100644 bindings/tests/testutils/evm/mining.go create mode 100644 bindings/tests/testutils/evm/snapshots.go create mode 100644 bindings/tests/testutils/minipool/minipool.go create mode 100644 bindings/tests/testutils/node/deposit.go create mode 100644 bindings/tests/testutils/node/node.go create mode 100644 bindings/tests/testutils/node/staking.go create mode 100644 bindings/tests/testutils/tokens/reth/reth.go create mode 100644 bindings/tests/testutils/tokens/rpl/rpl.go create mode 100644 bindings/tests/testutils/validator/deposit-data.go create mode 100644 bindings/tests/tokens/main_test.go create mode 100644 bindings/tests/tokens/reth_test.go create mode 100644 bindings/tests/tokens/rpl_fixed_test.go create mode 100644 bindings/tests/tokens/rpl_test.go create mode 100644 bindings/tests/tokens/tokens_test.go create mode 100644 bindings/tests/utils/eth/transactions_test.go create mode 100644 bindings/tests/utils/eth/units_test.go create mode 100644 bindings/tests/utils/stage4_bootstrap.go create mode 100644 bindings/tokens/reth.go create mode 100644 bindings/tokens/rpl-fixed.go create mode 100644 bindings/tokens/rpl.go create mode 100644 bindings/tokens/tokens.go create mode 100644 bindings/types/beacon.go create mode 100644 bindings/types/dao.go create mode 100644 bindings/types/minipool.go create mode 100644 bindings/utils/address_generation.go create mode 100644 bindings/utils/deposit_retrieval.go create mode 100644 bindings/utils/eth/erc20.go create mode 100644 bindings/utils/eth/logs.go create mode 100644 bindings/utils/eth/transactions.go create mode 100644 bindings/utils/eth/units.go create mode 100644 bindings/utils/json/json.go create mode 100644 bindings/utils/multicall/abi.go create mode 100644 bindings/utils/multicall/balances.go create mode 100644 bindings/utils/multicall/multicaller.go create mode 100644 bindings/utils/state/common.go create mode 100644 bindings/utils/state/contracts.go create mode 100644 bindings/utils/state/minipool.go create mode 100644 bindings/utils/state/network.go create mode 100644 bindings/utils/state/node.go create mode 100644 bindings/utils/state/odao.go create mode 100644 bindings/utils/state/pdao.go create mode 100644 bindings/utils/strings/sanitize.go create mode 100644 bindings/utils/version-checker.go create mode 100644 bindings/utils/wait.go 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 +}