diff --git a/src/gridcoin/contract/contract.cpp b/src/gridcoin/contract/contract.cpp index bb6b2a4b36..b68685068c 100644 --- a/src/gridcoin/contract/contract.cpp +++ b/src/gridcoin/contract/contract.cpp @@ -16,6 +16,7 @@ #include "gridcoin/project.h" #include "gridcoin/researcher.h" #include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "gridcoin/support/block_finder.h" #include "gridcoin/support/xml.h" #include "gridcoin/tx_message.h" @@ -677,12 +678,13 @@ bool Contract::RequiresMasterKey() const // beacons by signing them with the original private key: return m_version == 1 && m_action == ContractAction::REMOVE; - case ContractType::POLL: return m_action == ContractAction::REMOVE; - case ContractType::PROJECT: return true; - case ContractType::PROTOCOL: return true; - case ContractType::SCRAPER: return true; - case ContractType::VOTE: return m_action == ContractAction::REMOVE; - default: return false; + case ContractType::POLL: return m_action == ContractAction::REMOVE; + case ContractType::PROJECT: return true; + case ContractType::PROTOCOL: return true; + case ContractType::SCRAPER: return true; + case ContractType::VOTE: return m_action == ContractAction::REMOVE; + case ContractType::SIDESTAKE: return true; + default: return false; } } @@ -761,6 +763,7 @@ Contract::Type Contract::Type::Parse(std::string input) if (input == "scraper") return ContractType::SCRAPER; if (input == "protocol") return ContractType::PROTOCOL; if (input == "message") return ContractType::MESSAGE; + if (input == "sidestake") return ContractType::SIDESTAKE; return ContractType::UNKNOWN; } @@ -777,6 +780,7 @@ std::string Contract::Type::ToString() const case ContractType::PROTOCOL: return "protocol"; case ContractType::SCRAPER: return "scraper"; case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; default: return ""; } } @@ -793,6 +797,7 @@ std::string Contract::Type::ToString(ContractType contract_type) case ContractType::PROTOCOL: return "protocol"; case ContractType::SCRAPER: return "scraper"; case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; default: return ""; } } @@ -809,6 +814,7 @@ std::string Contract::Type::ToTranslatedString(ContractType contract_type) case ContractType::PROTOCOL: return _("protocol"); case ContractType::SCRAPER: return _("scraper"); case ContractType::VOTE: return _("vote"); + case ContractType::SIDESTAKE: return _("sidestake"); default: return ""; } } @@ -905,6 +911,9 @@ ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type, uint3 case ContractType::VOTE: return ContractPayload::Make( LegacyVote::Parse(legacy.m_key, legacy.m_value)); + case ContractType::SIDESTAKE: + // Sidestakes have no legacy representation as a contract. + assert(false && "Attempted to convert non-existent legacy sidestake contract."); case ContractType::OUT_OF_BOUND: assert(false); } @@ -961,6 +970,9 @@ void Contract::Body::ResetType(const ContractType type) case ContractType::VOTE: m_payload.Reset(new Vote()); break; + case ContractType::SIDESTAKE: + m_payload.Reset(new SideStakePayload()); + break; case ContractType::OUT_OF_BOUND: assert(false); } diff --git a/src/gridcoin/contract/registry.cpp b/src/gridcoin/contract/registry.cpp index cb7e85e5b5..aa618ef50e 100644 --- a/src/gridcoin/contract/registry.cpp +++ b/src/gridcoin/contract/registry.cpp @@ -11,6 +11,7 @@ const std::vector RegistryBookmarks::CONTRACT_TYPES_WITH_REG_ ContractType::PROJECT, ContractType::PROTOCOL, ContractType::SCRAPER, + ContractType::SIDESTAKE }; const std::vector RegistryBookmarks::CONTRACT_TYPES_SUPPORTING_REVERT = { @@ -20,6 +21,7 @@ const std::vector RegistryBookmarks::CONTRACT_TYPES_SUPPORTIN ContractType::PROTOCOL, ContractType::SCRAPER, ContractType::VOTE, + ContractType::SIDESTAKE }; } // namespace GRC diff --git a/src/gridcoin/contract/registry.h b/src/gridcoin/contract/registry.h index b92b840a5f..4132f465eb 100644 --- a/src/gridcoin/contract/registry.h +++ b/src/gridcoin/contract/registry.h @@ -9,6 +9,7 @@ #include "gridcoin/beacon.h" #include "gridcoin/project.h" #include "gridcoin/protocol.h" +#include "gridcoin/sidestake.h" #include "gridcoin/scraper/scraper_registry.h" #include "gridcoin/voting/registry.h" @@ -50,6 +51,7 @@ class RegistryBookmarks case ContractType::PROJECT: return GetWhitelist(); case ContractType::PROTOCOL: return GetProtocolRegistry(); case ContractType::SCRAPER: return GetScraperRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); case ContractType::UNKNOWN: [[fallthrough]]; case ContractType::CLAIM: @@ -78,6 +80,7 @@ class RegistryBookmarks case ContractType::PROTOCOL: return GetProtocolRegistry(); case ContractType::SCRAPER: return GetScraperRegistry(); case ContractType::VOTE: return GetPollRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); [[fallthrough]]; case ContractType::UNKNOWN: [[fallthrough]]; diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index c06ad5e234..b9beec8389 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -182,16 +182,30 @@ const SideStakeRegistry::SideStakeMap& SideStakeRegistry::SideStakeEntries() con const std::vector SideStakeRegistry::ActiveSideStakeEntries() { std::vector sidestakes; + double allocation_sum = 0.0; // For right now refresh sidestakes from config file. This is about the same overhead as the original // function in the miner. Perhaps replace with a signal to only refresh when r-w config file is // actually changed. LoadLocalSideStakesFromConfig(); + // The loops below prevent sidestakes from being added that cause a total allocation above 1.0 (100%). + + // Do mandatory sidestakes first. for (const auto& entry : m_sidestake_entries) { - if (entry.second->m_status == SideStakeStatus::ACTIVE || entry.second->m_status == SideStakeStatus::MANDATORY) { + if (entry.second->m_status == SideStakeStatus::MANDATORY && allocation_sum + entry.second->m_allocation <= 1.0) { sidestakes.push_back(entry.second); + allocation_sum += entry.second->m_allocation; + } + } + + // Followed by local active sidestakes + for (const auto& entry : m_sidestake_entries) + { + if (entry.second->m_status == SideStakeStatus::ACTIVE && allocation_sum + entry.second->m_allocation <= 1.0) { + sidestakes.push_back(entry.second); + allocation_sum += entry.second->m_allocation; } } @@ -519,9 +533,17 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() } } + // First, determine allocation already taken by mandatory sidestakes, because they must be allocated first. + for (const auto& entry : SideStakeEntries()) { + if (entry.second->m_status == SideStakeStatus::MANDATORY) { + dSumAllocation += entry.second->m_allocation; + } + } + for (const auto& entry : raw_vSideStakeAlloc) { std::string sAddress; + double dAllocation = 0.0; sAddress = entry.first; diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index f0fc8b4687..92f28255f4 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -372,6 +372,8 @@ class SideStakeRegistry : public IContractHandler //! \brief Get the collection of active sidestake entries. This is presented as a vector of //! smart pointers to the relevant sidestake entries in the database. The entries included have //! the status of active (for local sidestakes) and/or mandatory (for contract sidestakes). + //! Mandatory sidestakes come before local ones, and the method ensures that the sidestakes + //! returned do not total an allocation greater than 1.0. //! //! \return A vector of smart pointers to sidestake entries. //! diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 538ce8c954..9bf2acbc4c 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -7,6 +7,7 @@ #include "blockchain.h" #include "gridcoin/protocol.h" #include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "node/blockstorage.h" #include #include "gridcoin/mrc.h" @@ -2304,7 +2305,8 @@ UniValue addkey(const UniValue& params, bool fHelp) if (!(type == GRC::ContractType::PROJECT || type == GRC::ContractType::SCRAPER - || type == GRC::ContractType::PROTOCOL)) { + || type == GRC::ContractType::PROTOCOL + || type == GRC::ContractType::SIDESTAKE)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid contract type for addkey."); } @@ -2433,6 +2435,34 @@ UniValue addkey(const UniValue& params, bool fHelp) params[2].get_str(), // key params[3].get_str()); // value break; + case GRC::ContractType::SIDESTAKE: + { + CBitcoinAddress sidestake_address; + if (!sidestake_address.SetString(params[2].get_str())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address specified for the sidestake is invalid."); + } + + double allocation = 0.0; + if (!ParseDouble(params[3].get_str(), &allocation)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid allocation specified."); + } + + allocation /= 100.0; + + if (allocation > 1.0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Allocation specified is greater than 100.0%."); + } + + contract = GRC::MakeContract( + contract_version, + action, + uint32_t {1}, // Contract payload version number + sidestake_address, // Sidestake address + allocation, // Sidestake allocation + GRC::SideStakeStatus::ACTIVE + ); + break; + } case GRC::ContractType::BEACON: [[fallthrough]]; case GRC::ContractType::CLAIM: