diff --git a/src/chainparams.cpp b/src/chainparams.cpp index f653376d16e..60496c0d00b 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -129,7 +129,8 @@ class CMainParams : public CChainParams { consensus.FortCanningMuseumHeight = 1430640; consensus.FortCanningParkHeight = 1503143; consensus.FortCanningHillHeight = 1604999; // Feb 7, 2022. - consensus.FortCanningRoadHeight = 1786000; // April 11, 2022. + consensus.FortCanningRoadHeight = 1786000; // April 11, 2022. + consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -358,6 +359,7 @@ class CTestNetParams : public CChainParams { consensus.FortCanningParkHeight = 828800; consensus.FortCanningHillHeight = 828900; consensus.FortCanningRoadHeight = 893700; + consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -547,6 +549,7 @@ class CDevNetParams : public CChainParams { consensus.FortCanningParkHeight = std::numeric_limits::max(); consensus.FortCanningHillHeight = std::numeric_limits::max(); consensus.FortCanningRoadHeight = std::numeric_limits::max(); + consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks @@ -728,6 +731,7 @@ class CRegTestParams : public CChainParams { consensus.FortCanningParkHeight = 10000000; consensus.FortCanningHillHeight = 10000000; consensus.FortCanningRoadHeight = 10000000; + consensus.GreatWorldHeight = 10000000; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -948,6 +952,7 @@ void CRegTestParams::UpdateActivationParametersFromArgs() UpdateHeightValidation("Fort Canning Park", "-fortcanningparkheight", consensus.FortCanningParkHeight); UpdateHeightValidation("Fort Canning Hill", "-fortcanninghillheight", consensus.FortCanningHillHeight); UpdateHeightValidation("Fort Canning Road", "-fortcanningroadheight", consensus.FortCanningRoadHeight); + UpdateHeightValidation("Great World", "-greatworldheight", consensus.GreatWorldHeight); if (gArgs.GetBoolArg("-simulatemainnet", false)) { consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks diff --git a/src/consensus/params.h b/src/consensus/params.h index 2d047d9bcd3..7c919d1bec4 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -95,6 +95,7 @@ struct Params { int FortCanningParkHeight; int FortCanningHillHeight; int FortCanningRoadHeight; + int GreatWorldHeight; /** Foundation share after AMK, normalized to COIN = 100% */ CAmount foundationShareDFIP1; diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index ee03891b96d..86f566c8e5c 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -7,9 +7,10 @@ #include #include -/// @todo refactor it to unify txs!!! (need to restart blockchain) +const std::vector DfTxMarker = {'D', 'f', 'T', 'x'}; const std::vector DfAnchorFinalizeTxMarker = {'D', 'f', 'A', 'f'}; const std::vector DfAnchorFinalizeTxMarkerPlus = {'D', 'f', 'A', 'P'}; +const std::vector DfTokenSplitMarker = {'D', 'f', 'T', 'S'}; bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fCheckDuplicateInputs) { @@ -49,7 +50,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe if (tx.IsCoinBase()) { std::vector dummy; - if (IsAnchorRewardTx(tx, dummy) || IsAnchorRewardTxPlus(tx, dummy)) + if (IsAnchorRewardTx(tx, dummy) || IsAnchorRewardTxPlus(tx, dummy) || IsTokenSplitTx(tx, dummy)) return true; if (tx.vin[0].scriptSig.size() < 2 || (tx.vin[0].scriptSig.size() > 100)) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length"); @@ -115,3 +116,19 @@ bool IsAnchorRewardTxPlus(CTransaction const & tx, std::vector & } return result; } + +bool IsTokenSplitTx(CTransaction const & tx, std::vector & metadata, bool fortCanningGreen) +{ + if (!fortCanningGreen) { + return false; + } + if (!tx.IsCoinBase() || tx.vout.size() != 1 || tx.vout[0].nValue != 0) { + return false; + } + bool hasAdditionalOpcodes{false}; + const auto result = ParseScriptByMarker(tx.vout[0].scriptPubKey, DfTokenSplitMarker, metadata, hasAdditionalOpcodes); + if (hasAdditionalOpcodes) { + return false; + } + return result; +} diff --git a/src/consensus/tx_check.h b/src/consensus/tx_check.h index 3a7d334ef2f..5eb7012f73e 100644 --- a/src/consensus/tx_check.h +++ b/src/consensus/tx_check.h @@ -14,9 +14,10 @@ #include -/// moved here (!!) due to strange linker errors under mac/win builds +extern const std::vector DfTxMarker; extern const std::vector DfAnchorFinalizeTxMarker; extern const std::vector DfAnchorFinalizeTxMarkerPlus; +extern const std::vector DfTokenSplitMarker; class CScript; class CTransaction; @@ -30,5 +31,6 @@ bool ParseScriptByMarker(CScript const & script, bool& hasAdditionalOpcodes); bool IsAnchorRewardTx(CTransaction const & tx, std::vector & metadata, bool fortCanning = false); bool IsAnchorRewardTxPlus(CTransaction const & tx, std::vector & metadata, bool fortCanning = false); +bool IsTokenSplitTx(CTransaction const & tx, std::vector & metadata, bool fortCanningGreen = true); #endif // DEFI_CONSENSUS_TX_CHECK_H diff --git a/src/init.cpp b/src/init.cpp index 102c84818b4..8a2d0370674 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -477,6 +477,7 @@ void SetupServerArgs() gArgs.AddArg("-fortcanningparkheight", "Fort Canning Park fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-fortcanninghillheight", "Fort Canning Hill fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-fortcanningroadheight", "Fort Canning Road fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-greatworldheight", "Great World fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-simulatemainnet", "Configure the regtest network to mainnet target timespan and spacing ", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); #ifdef USE_UPNP diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index cbf62b89101..01d2ee2dc8d 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -6,6 +6,7 @@ #include /// CAccountsHistoryWriter #include /// CCustomCSView +#include /// GetAggregatePrice #include /// CustomTxType #include /// ValueFromAmount @@ -13,24 +14,22 @@ extern UniValue AmountsToJSON(TAmounts const & diffs); -template -static std::string KeyBuilder(const T& value){ - std::ostringstream oss; - oss << value; - return oss.str(); +static inline std::string trim_all_ws(std::string s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); + return s; } -template -static std::string KeyBuilder(const T& value, const Args& ... args){ - return KeyBuilder(value) + '/' + KeyBuilder(args...); -} - -static std::vector KeyBreaker(const std::string& str){ +static std::vector KeyBreaker(const std::string& str, const char delim = '/'){ std::string section; std::istringstream stream(str); std::vector strVec; - while (std::getline(stream, section, '/')) { + while (std::getline(stream, section, delim)) { strVec.push_back(section); } return strVec; @@ -52,6 +51,8 @@ const std::map& ATTRIBUTES::displayVersions() { const std::map& ATTRIBUTES::allowedTypes() { static const std::map types{ + {"locks", AttributeTypes::Locks}, + {"oracles", AttributeTypes::Oracles}, {"params", AttributeTypes::Param}, {"poolpairs", AttributeTypes::Poolpairs}, {"token", AttributeTypes::Token}, @@ -62,6 +63,8 @@ const std::map& ATTRIBUTES::allowedTypes() { const std::map& ATTRIBUTES::displayTypes() { static const std::map types{ {AttributeTypes::Live, "live"}, + {AttributeTypes::Locks, "locks"}, + {AttributeTypes::Oracles, "oracles"}, {AttributeTypes::Param, "params"}, {AttributeTypes::Poolpairs, "poolpairs"}, {AttributeTypes::Token, "token"}, @@ -77,26 +80,53 @@ const std::map& ATTRIBUTES::allowedParamIDs() { return params; } +const std::map& ATTRIBUTES::allowedLocksIDs() { + static const std::map params{ + {"token", ParamIDs::TokenID}, + }; + return params; +} + const std::map& ATTRIBUTES::displayParamsIDs() { static const std::map params{ {ParamIDs::DFIP2201, "dfip2201"}, {ParamIDs::DFIP2203, "dfip2203"}, + {ParamIDs::TokenID, "token"}, {ParamIDs::Economy, "economy"}, }; return params; } +const std::map& ATTRIBUTES::allowedOracleIDs() { + static const std::map params{ + {"splits", OracleIDs::Splits} + }; + return params; +} + +const std::map& ATTRIBUTES::displayOracleIDs() { + static const std::map params{ + {OracleIDs::Splits, "splits"}, + }; + return params; +} + const std::map>& ATTRIBUTES::allowedKeys() { static const std::map> keys{ { AttributeTypes::Token, { - {"payback_dfi", TokenKeys::PaybackDFI}, - {"payback_dfi_fee_pct", TokenKeys::PaybackDFIFeePCT}, - {"loan_payback", TokenKeys::LoanPayback}, - {"loan_payback_fee_pct",TokenKeys::LoanPaybackFeePCT}, - {"dex_in_fee_pct", TokenKeys::DexInFeePct}, - {"dex_out_fee_pct", TokenKeys::DexOutFeePct}, - {"dfip2203", TokenKeys::DFIP2203Enabled}, + {"payback_dfi", TokenKeys::PaybackDFI}, + {"payback_dfi_fee_pct", TokenKeys::PaybackDFIFeePCT}, + {"loan_payback", TokenKeys::LoanPayback}, + {"loan_payback_fee_pct", TokenKeys::LoanPaybackFeePCT}, + {"dex_in_fee_pct", TokenKeys::DexInFeePct}, + {"dex_out_fee_pct", TokenKeys::DexOutFeePct}, + {"dfip2203", TokenKeys::DFIP2203Enabled}, + {"fixed_interval_price_id", TokenKeys::FixedIntervalPriceId}, + {"loan_collateral_enabled", TokenKeys::LoanCollateralEnabled}, + {"loan_collateral_factor", TokenKeys::LoanCollateralFactor}, + {"loan_minting_enabled", TokenKeys::LoanMintingEnabled}, + {"loan_minting_interest", TokenKeys::LoanMintingInterest}, } }, { @@ -122,13 +152,21 @@ const std::map>& ATTRIBUTES::displayKeys static const std::map> keys{ { AttributeTypes::Token, { - {TokenKeys::PaybackDFI, "payback_dfi"}, - {TokenKeys::PaybackDFIFeePCT, "payback_dfi_fee_pct"}, - {TokenKeys::LoanPayback, "loan_payback"}, - {TokenKeys::LoanPaybackFeePCT,"loan_payback_fee_pct"}, - {TokenKeys::DexInFeePct, "dex_in_fee_pct"}, - {TokenKeys::DexOutFeePct, "dex_out_fee_pct"}, - {TokenKeys::DFIP2203Enabled, "dfip2203"}, + {TokenKeys::PaybackDFI, "payback_dfi"}, + {TokenKeys::PaybackDFIFeePCT, "payback_dfi_fee_pct"}, + {TokenKeys::LoanPayback, "loan_payback"}, + {TokenKeys::LoanPaybackFeePCT, "loan_payback_fee_pct"}, + {TokenKeys::DexInFeePct, "dex_in_fee_pct"}, + {TokenKeys::DexOutFeePct, "dex_out_fee_pct"}, + {TokenKeys::FixedIntervalPriceId, "fixed_interval_price_id"}, + {TokenKeys::LoanCollateralEnabled, "loan_collateral_enabled"}, + {TokenKeys::LoanCollateralFactor, "loan_collateral_factor"}, + {TokenKeys::LoanMintingEnabled, "loan_minting_enabled"}, + {TokenKeys::LoanMintingInterest, "loan_minting_interest"}, + {TokenKeys::DFIP2203Enabled, "dfip2203"}, + {TokenKeys::Ascendant, "ascendant"}, + {TokenKeys::Descendant, "descendant"}, + {TokenKeys::Epitaph, "epitaph"}, } }, { @@ -159,6 +197,14 @@ const std::map>& ATTRIBUTES::displayKeys } static ResVal VerifyInt32(const std::string& str) { + int32_t int32; + if (!ParseInt32(str, &int32)) { + return Res::Err("Value must be an integer"); + } + return {int32, Res::Ok()}; +} + +static ResVal VerifyPositiveInt32(const std::string& str) { int32_t int32; if (!ParseInt32(str, &int32) || int32 < 0) { return Res::Err("Value must be a positive integer"); @@ -200,6 +246,52 @@ static ResVal VerifyBool(const std::string& str) { return {str == "true", Res::Ok()}; } +static ResVal VerifySplit(const std::string& str) { + const auto values = KeyBreaker(str, ','); + if (values.empty()) { + return Res::Err(R"(No valid values supplied, "id/multiplier, ...")"); + } + + OracleSplits splits; + for (const auto& item : values) { + const auto pairs = KeyBreaker(item); + if (pairs.size() != 2) { + return Res::Err("Two int values expected for split in id/mutliplier"); + } + const auto resId = VerifyPositiveInt32(pairs[0]); + if (!resId) { + return resId; + } + const auto resMultiplier = VerifyInt32(pairs[1]); + if (!resMultiplier) { + return resMultiplier; + } + if (*resMultiplier == 0) { + return Res::Err("Mutliplier cannot be zero"); + } + splits[*resId] = *resMultiplier; + } + + return {splits, Res::Ok()}; +} + +static ResVal VerifyCurrencyPair(const std::string& str) { + const auto value = KeyBreaker(str); + if (value.size() != 2) { + return Res::Err("Exactly two entires expected for currency pair"); + } + auto token = trim_all_ws(value[0]).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + auto currency = trim_all_ws(value[1]).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + if (token.empty() || currency.empty()) { + return Res::Err("Empty token / currency"); + } + return {CTokenCurrencyPair{token, currency}, Res::Ok()}; +} + +static bool VerifyToken(const CCustomCSView& view, const uint32_t id) { + return view.GetToken(DCT_ID{id}).has_value(); +} + const std::map(const std::string&)>>>& ATTRIBUTES::parseValue() { @@ -207,13 +299,18 @@ const std::map(const std::string&)>>> parsers{ { AttributeTypes::Token, { - {TokenKeys::PaybackDFI, VerifyBool}, - {TokenKeys::PaybackDFIFeePCT, VerifyPct}, - {TokenKeys::LoanPayback, VerifyBool}, - {TokenKeys::LoanPaybackFeePCT,VerifyPct}, - {TokenKeys::DexInFeePct, VerifyPct}, - {TokenKeys::DexOutFeePct, VerifyPct}, - {TokenKeys::DFIP2203Enabled, VerifyBool}, + {TokenKeys::PaybackDFI, VerifyBool}, + {TokenKeys::PaybackDFIFeePCT, VerifyPct}, + {TokenKeys::LoanPayback, VerifyBool}, + {TokenKeys::LoanPaybackFeePCT, VerifyPct}, + {TokenKeys::DexInFeePct, VerifyPct}, + {TokenKeys::DexOutFeePct, VerifyPct}, + {TokenKeys::FixedIntervalPriceId, VerifyCurrencyPair}, + {TokenKeys::LoanCollateralEnabled, VerifyBool}, + {TokenKeys::LoanCollateralFactor, VerifyPct}, + {TokenKeys::LoanMintingEnabled, VerifyBool}, + {TokenKeys::LoanMintingInterest, VerifyFloat}, + {TokenKeys::DFIP2203Enabled, VerifyBool}, } }, { @@ -231,6 +328,16 @@ const std::mapsecond; + const auto& version = iver->second; if (version != VersionTypes::v0) { return Res::Err("Unsupported version"); } @@ -288,56 +395,83 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value return ::ShowError("type", allowedTypes()); } - auto type = itype->second; + const auto type = itype->second; uint32_t typeId{0}; - if (type != AttributeTypes::Param) { + if (type == AttributeTypes::Param) { + auto id = allowedParamIDs().find(keys[2]); + if (id == allowedParamIDs().end()) { + return ::ShowError("params", allowedParamIDs()); + } + typeId = id->second; + } else if (type == AttributeTypes::Locks) { + auto id = allowedLocksIDs().find(keys[2]); + if (id == allowedLocksIDs().end()) { + return ::ShowError("locks", allowedLocksIDs()); + } + typeId = id->second; + } else if (type == AttributeTypes::Oracles) { + auto id = allowedOracleIDs().find(keys[2]); + if (id == allowedOracleIDs().end()) { + return ::ShowError("oracles", allowedOracleIDs()); + } + typeId = id->second; + } else { auto id = VerifyInt32(keys[2]); if (!id) { return std::move(id); } typeId = *id.val; - } else { - auto id = allowedParamIDs().find(keys[2]); - if (id == allowedParamIDs().end()) { - return ::ShowError("param", allowedParamIDs()); - } - typeId = id->second; } - auto ikey = allowedKeys().find(type); - if (ikey == allowedKeys().end()) { - return Res::Err("Unsupported type {%d}", type); - } + uint32_t typeKey{0}; + CDataStructureV0 attrV0{}; - itype = ikey->second.find(keys[3]); - if (itype == ikey->second.end()) { - return ::ShowError("key", ikey->second); - } + if (type == AttributeTypes::Locks) { + typeKey = ParamIDs::TokenID; + if (const auto keyValue = VerifyInt32(keys[3])) { + attrV0 = CDataStructureV0{type, typeId, static_cast(*keyValue)}; + } + } else if (type == AttributeTypes::Oracles) { + typeKey = OracleIDs::Splits; + if (const auto keyValue = VerifyPositiveInt32(keys[3])) { + attrV0 = CDataStructureV0{type, typeId, static_cast(*keyValue)}; + } + } else { + auto ikey = allowedKeys().find(type); + if (ikey == allowedKeys().end()) { + return Res::Err("Unsupported type {%d}", type); + } + + itype = ikey->second.find(keys[3]); + if (itype == ikey->second.end()) { + return ::ShowError("key", ikey->second); + } - auto typeKey = itype->second; + typeKey = itype->second; - if (type == AttributeTypes::Param) { - if (typeId == ParamIDs::DFIP2201) { - if (typeKey == DFIPKeys::RewardPct || - typeKey == DFIPKeys::BlockPeriod) { - return Res::Err("Unsupported type for DFIP2201 {%d}", typeKey); - } - } else if (typeId == ParamIDs::DFIP2203) { - if (typeKey == DFIPKeys::Premium || - typeKey == DFIPKeys::MinSwap) { - return Res::Err("Unsupported type for DFIP2203 {%d}", typeKey); - } + if (type == AttributeTypes::Param) { + if (typeId == ParamIDs::DFIP2201) { + if (typeKey == DFIPKeys::RewardPct || + typeKey == DFIPKeys::BlockPeriod) { + return Res::Err("Unsupported type for DFIP2201 {%d}", typeKey); + } + } else if (typeId == ParamIDs::DFIP2203) { + if (typeKey == DFIPKeys::Premium || + typeKey == DFIPKeys::MinSwap) { + return Res::Err("Unsupported type for DFIP2203 {%d}", typeKey); + } - if (typeKey == DFIPKeys::BlockPeriod) { - futureBlockUpdated = true; + if (typeKey == DFIPKeys::BlockPeriod) { + futureBlockUpdated = true; + } + } else { + return Res::Err("Unsupported Param ID"); } - } else { - return Res::Err("Unsupported Param ID"); } - } - CDataStructureV0 attrV0{type, typeId, typeKey}; + attrV0 = CDataStructureV0{type, typeId, typeKey}; + } if (attrV0.IsExtendedSize()) { if (keys.size() != 5 || keys[4].empty()) { @@ -349,8 +483,8 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value } attrV0.keyId = *id.val; } else { - if (keys.size() != 4) { - return Res::Err("Exact 4 keys are required {%d}", keys.size()); + if (keys.size() != 4) { + return Res::Err("Exact 4 keys are required {%d}", keys.size()); } } @@ -440,12 +574,33 @@ Res ATTRIBUTES::Import(const UniValue & val) { for (const auto& pair : objMap) { auto res = ProcessVariable( - pair.first, pair.second.get_str(), [this](const CAttributeType& attribute, const CAttributeValue& value) { + pair.first, pair.second.get_str(), + [this](const CAttributeType& attribute, const CAttributeValue& value) { if (auto attrV0 = boost::get(&attribute)) { - if (attrV0->type == AttributeTypes::Live) { - return Res::Err("Live attribute cannot be set externally"); + if (attrV0->type == AttributeTypes::Live || + (attrV0->type == AttributeTypes::Token && + (attrV0->key == TokenKeys::Ascendant || + attrV0->key == TokenKeys::Descendant || + attrV0->key == TokenKeys::Epitaph))) { + return Res::Err("Attribute cannot be set externally"); + } else if (attrV0->type == AttributeTypes::Oracles && attrV0->typeId == OracleIDs::Splits) { + auto splitValue = boost::get(&value); + if (!splitValue) { + return Res::Err("Failed to get Oracle split value"); + } + for (const auto& [id, multiplier] : *splitValue) { + tokenSplits.insert(id); + } + try { + auto attrMap = boost::get(&attributes.at(attribute)); + OracleSplits combined{*splitValue}; + combined.merge(*attrMap); + attributes[attribute] = combined; + return Res::Ok(); + } catch (std::out_of_range &) {} } - // applay DFI via old keys + + // apply DFI via old keys if (attrV0->IsExtendedSize() && attrV0->keyId == 0) { auto newAttr = *attrV0; if (attrV0->key == TokenKeys::LoanPayback) { @@ -476,15 +631,21 @@ UniValue ATTRIBUTES::Export() const { continue; } try { - const auto id = attrV0->type == AttributeTypes::Param - || attrV0->type == AttributeTypes::Live - ? displayParamsIDs().at(attrV0->typeId) - : KeyBuilder(attrV0->typeId); + std::string id; + if (attrV0->type == AttributeTypes::Param || attrV0->type == AttributeTypes::Live || attrV0->type == AttributeTypes::Locks) { + id = displayParamsIDs().at(attrV0->typeId); + } else if (attrV0->type == AttributeTypes::Oracles) { + id = displayOracleIDs().at(attrV0->typeId); + } else { + id = KeyBuilder(attrV0->typeId); + } + + auto const v0Key = attrV0->type == AttributeTypes::Oracles || attrV0->type == AttributeTypes::Locks ? KeyBuilder(attrV0->key) : displayKeys().at(attrV0->type).at(attrV0->key); auto key = KeyBuilder(displayVersions().at(VersionTypes::v0), displayTypes().at(attrV0->type), id, - displayKeys().at(attrV0->type).at(attrV0->key)); + v0Key); if (attrV0->IsExtendedSize()) { key = KeyBuilder(key, attrV0->keyId); @@ -506,6 +667,18 @@ UniValue ATTRIBUTES::Export() const { result.pushKV("paybackfees", AmountsToJSON(paybacks->tokensFee.balances)); result.pushKV("paybacktokens", AmountsToJSON(paybacks->tokensPayback.balances)); ret.pushKV(key, result); + } else if (const auto splitValues = boost::get(&attribute.second)) { + std::string keyValue; + for (const auto& [tokenId, multiplier] : *splitValues) { + keyValue += KeyBuilder(tokenId, multiplier) + ','; + } + ret.pushKV(key, keyValue); + } else if (const auto& descendantPair = boost::get(&attribute.second)) { + ret.pushKV(key, KeyBuilder(descendantPair->first, descendantPair->second)); + } else if (const auto& ascendantPair = boost::get(&attribute.second)) { + ret.pushKV(key, KeyBuilder(ascendantPair->first, ascendantPair->second)); + } else if (auto currencyPair = boost::get(&attribute.second)) { + ret.pushKV(key, currencyPair->first + '/' + currencyPair->second); } } catch (const std::out_of_range&) { // Should not get here, that's mean maps are mismatched @@ -554,6 +727,31 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const return Res::Err("No such token (%d)", attrV0->typeId); } break; + case TokenKeys::LoanCollateralEnabled: + case TokenKeys::LoanCollateralFactor: + case TokenKeys::LoanMintingEnabled: + case TokenKeys::LoanMintingInterest: { + if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) { + return Res::Err("Cannot be set before GreatWorld"); + } + if (!VerifyToken(view, attrV0->typeId)) { + return Res::Err("No such token (%d)", attrV0->typeId); + } + CDataStructureV0 intervalPriceKey{AttributeTypes::Token, attrV0->typeId, + TokenKeys::FixedIntervalPriceId}; + if (GetValue(intervalPriceKey, CTokenCurrencyPair{}) == CTokenCurrencyPair{}) { + return Res::Err("Fixed interval price currency pair must be set first"); + } + break; + } + case TokenKeys::FixedIntervalPriceId: + if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) { + return Res::Err("Cannot be set before GreatWorld"); + } + if (!VerifyToken(view, attrV0->typeId)) { + return Res::Err("No such token (%d)", attrV0->typeId); + } + break; case TokenKeys::DFIP2203Enabled: if (view.GetLastHeight() < Params().GetConsensus().FortCanningRoadHeight) { return Res::Err("Cannot be set before FortCanningRoad"); @@ -562,11 +760,47 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const return Res::Err("No such loan token (%d)", attrV0->typeId); } break; + case TokenKeys::Ascendant: + case TokenKeys::Descendant: + case TokenKeys::Epitaph: + break; default: return Res::Err("Unsupported key"); } break; + case AttributeTypes::Oracles: + if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) { + return Res::Err("Cannot be set before GreatWorld"); + } + if (attrV0->typeId == OracleIDs::Splits) { + const auto splitMap = boost::get(&attribute.second); + if (!splitMap) { + return Res::Err("Unsupported value"); + } + for (const auto& [tokenId, multipler] : *splitMap) { + if (tokenId == 0) { + return Res::Err("Tokenised DFI cannot be split"); + } + if (view.HasPoolPair(DCT_ID{tokenId})) { + return Res::Err("Pool tokens cannot be split"); + } + const auto token = view.GetToken(DCT_ID{tokenId}); + if (!token) { + return Res::Err("Token (%d) does not exist", tokenId); + } + if (!token->IsDAT()) { + return Res::Err("Only DATs can be split"); + } + if (!view.GetLoanTokenByID(DCT_ID{tokenId}).has_value()) { + return Res::Err("No loan token with id (%d)", tokenId); + } + } + } else { + return Res::Err("Unsupported key"); + } + break; + case AttributeTypes::Poolpairs: if (!boost::get(&attribute.second)) { return Res::Err("Unsupported value"); @@ -597,6 +831,18 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const case AttributeTypes::Live: break; + case AttributeTypes::Locks: + if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) { + return Res::Err("Cannot be set before GreatWorld"); + } + if (attrV0->typeId != ParamIDs::TokenID) { + return Res::Err("Unrecognised locks id"); + } + if (!view.GetLoanTokenByID(DCT_ID{attrV0->key}).has_value()) { + return Res::Err("No loan token with id (%d)", attrV0->key); + } + break; + default: return Res::Err("Unrecognised type (%d)", attrV0->type); } @@ -637,6 +883,31 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) return res; } } + if (attrV0->key == TokenKeys::FixedIntervalPriceId) { + if (const auto ¤cyPair = boost::get(&attribute.second)) { + // Already exists, skip. + if (mnview.GetFixedIntervalPrice(*currencyPair)) { + continue; + } else if (!OraclePriceFeed(mnview, *currencyPair)) { + return Res::Err("Price feed %s/%s does not belong to any oracle", currencyPair->first, + currencyPair->second); + } + CFixedIntervalPrice fixedIntervalPrice; + fixedIntervalPrice.priceFeedId = *currencyPair; + fixedIntervalPrice.timestamp = time; + fixedIntervalPrice.priceRecord[1] = -1; + const auto aggregatePrice = GetAggregatePrice(mnview, + fixedIntervalPrice.priceFeedId.first, + fixedIntervalPrice.priceFeedId.second, + time); + if (aggregatePrice) { + fixedIntervalPrice.priceRecord[1] = aggregatePrice; + } + mnview.SetFixedIntervalPrice(fixedIntervalPrice); + } else { + return Res::Err("Unrecognised value for FixedIntervalPriceId"); + } + } if (attrV0->key == TokenKeys::DFIP2203Enabled) { // Skip on block period change to avoid refunding and erasing entries. @@ -700,6 +971,52 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) return Res::Err("Cannot set block period while DFIP2203 is active"); } } + } else if (attrV0->type == AttributeTypes::Oracles && attrV0->typeId == OracleIDs::Splits) { + const auto value = boost::get(&attribute.second); + if (!value) { + return Res::Err("Unsupported value"); + } + for (const auto split : tokenSplits) { + if (auto it{value->find(split)}; it == value->end()) { + continue; + } + CDataStructureV0 lockKey{AttributeTypes::Locks, ParamIDs::TokenID, split}; + if (GetValue(lockKey, false)) { + continue; + } + + if (!mnview.GetLoanTokenByID(DCT_ID{split}).has_value()) { + return Res::Err("Auto lock. No loan token with id (%d)", split); + } + + if (attrV0->key < height) { + return Res::Err("Cannot be set at or below current height"); + } + + CGovernanceHeightMessage lock; + auto var = GovVariable::Create("ATTRIBUTES"); + if (!var) { + return Res::Err("Failed to create Gov var for lock"); + } + auto govVar = std::dynamic_pointer_cast(var); + if (!govVar) { + return Res::Err("Failed to cast Gov var to ATTRIBUTES"); + } + govVar->attributes[lockKey] = true; + lock.govVar = govVar; + + const auto startHeight = attrV0->key - Params().GetConsensus().blocksPerDay() / 2; + if (height > startHeight) { + lock.startHeight = height; + } else { + lock.startHeight = startHeight; + } + + const auto res = storeGovVars(lock, mnview); + if (!res) { + return Res::Err("Cannot be set at or below current height"); + } + } } } diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 604b3f2dd27..97c784c293d 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -8,6 +8,7 @@ #include #include #include +#include enum VersionTypes : uint8_t { v0 = 0, @@ -15,17 +16,24 @@ enum VersionTypes : uint8_t { enum AttributeTypes : uint8_t { Live = 'l', + Oracles = 'o', Param = 'a', Token = 't', Poolpairs = 'p', + Locks = 'L', }; enum ParamIDs : uint8_t { DFIP2201 = 'a', DFIP2203 = 'b', + TokenID = 'c', Economy = 'e', }; +enum OracleIDs : uint8_t { + Splits = 'a', +}; + enum EconomyKeys : uint8_t { PaybackDFITokens = 'a', PaybackTokens = 'b', @@ -43,13 +51,21 @@ enum DFIPKeys : uint8_t { }; enum TokenKeys : uint8_t { - PaybackDFI = 'a', - PaybackDFIFeePCT = 'b', - LoanPayback = 'c', - LoanPaybackFeePCT = 'd', - DexInFeePct = 'e', - DexOutFeePct = 'f', - DFIP2203Enabled = 'g', + PaybackDFI = 'a', + PaybackDFIFeePCT = 'b', + LoanPayback = 'c', + LoanPaybackFeePCT = 'd', + DexInFeePct = 'e', + DexOutFeePct = 'f', + DFIP2203Enabled = 'g', + FixedIntervalPriceId = 'h', + LoanCollateralEnabled = 'i', + LoanCollateralFactor = 'j', + LoanMintingEnabled = 'k', + LoanMintingInterest = 'l', + Ascendant = 'm', + Descendant = 'n', + Epitaph = 'o', }; enum PoolKeys : uint8_t { @@ -60,7 +76,7 @@ enum PoolKeys : uint8_t { struct CDataStructureV0 { uint8_t type; uint32_t typeId; - uint8_t key; + uint32_t key; uint32_t keyId; ADD_SERIALIZE_METHODS; @@ -69,7 +85,7 @@ struct CDataStructureV0 { inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(type); READWRITE(typeId); - READWRITE(key); + READWRITE(VARINT(key)); if (IsExtendedSize()) { READWRITE(keyId); } else { @@ -113,8 +129,11 @@ struct CTokenPayback { ResVal GetFutureSwapContractAddress(); +using OracleSplits = std::map; +using DescendantValue = std::pair; +using AscendantValue = std::pair; using CAttributeType = boost::variant; -using CAttributeValue = boost::variant; +using CAttributeValue = boost::variant; class ATTRIBUTES : public GovVariable, public AutoRegistrator { @@ -144,12 +163,44 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator + void SetValue(const K& key, T&& value) { + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + changed.insert(key); + attributes[key] = std::forward(value); + } + + template + void EraseKey(const K& key) { + static_assert(std::is_convertible_v); + changed.insert(key); + attributes.erase(key); + } + template [[nodiscard]] bool CheckKey(const K& key) const { static_assert(std::is_convertible_v); return attributes.count(key) > 0; } + template + void ForEach(const C& callback, const K& key) const { + static_assert(std::is_convertible_v); + static_assert(std::is_invocable_r_v); + for (auto it = attributes.lower_bound(key); it != attributes.end(); ++it) { + if (auto attrV0 = boost::get(&it->first)) { + if (!std::invoke(callback, *attrV0, it->second)) { + break; + } + } + } + } + + [[nodiscard]] const std::map& GetAttributesMap() const { + return attributes; + } + ADD_OVERRIDE_VECTOR_SERIALIZE_METHODS ADD_OVERRIDE_SERIALIZE_METHODS(CDataStream) @@ -158,28 +209,37 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator attributes; + uint32_t time{0}; + + // For formatting in export + static const std::map& displayVersions(); + static const std::map& displayTypes(); + static const std::map& displayParamsIDs(); + static const std::map& displayOracleIDs(); + static const std::map>& displayKeys(); private: + friend class CGovView; bool futureBlockUpdated{}; + std::set changed; + std::map attributes; // Defined allowed arguments static const std::map& allowedVersions(); static const std::map& allowedTypes(); static const std::map& allowedParamIDs(); + static const std::map& allowedLocksIDs(); + static const std::map& allowedOracleIDs(); static const std::map>& allowedKeys(); static const std::map(const std::string&)>>>& parseValue(); - // For formatting in export - static const std::map& displayVersions(); - static const std::map& displayTypes(); - static const std::map& displayParamsIDs(); - static const std::map>& displayKeys(); - Res ProcessVariable(const std::string& key, const std::string& value, std::function applyVariable); Res RefundFuturesContracts(CCustomCSView &mnview, const uint32_t height, const uint32_t tokenID = std::numeric_limits::max()); + +private: + std::set tokenSplits{}; }; #endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H diff --git a/src/masternodes/gv.h b/src/masternodes/gv.h index cb8507727d4..bc73e292286 100644 --- a/src/masternodes/gv.h +++ b/src/masternodes/gv.h @@ -49,6 +49,8 @@ class CGovView : public virtual CStorageView std::shared_ptr GetAttributes() const; + [[nodiscard]] virtual bool AreTokensLocked(const std::set& tokenIds) const = 0; + struct ByHeightVars { static constexpr uint8_t prefix() { return 'G'; } }; struct ByName { static constexpr uint8_t prefix() { return 'g'; } }; }; diff --git a/src/masternodes/loan.cpp b/src/masternodes/loan.cpp index 0e57152da4c..b5e79e01d2d 100644 --- a/src/masternodes/loan.cpp +++ b/src/masternodes/loan.cpp @@ -1,14 +1,14 @@ #include #include +#include +#include + #include -std::unique_ptr CLoanView::GetLoanCollateralToken(uint256 const & txid) const +boost::optional CLoanView::GetLoanCollateralToken(uint256 const & txid) const { - auto collToken = ReadBy(txid); - if (collToken) - return MakeUnique(*collToken); - return {}; + return ReadBy(txid); } Res CLoanView::CreateLoanCollateralToken(CLoanSetCollateralTokenImpl const & collToken) @@ -29,15 +29,11 @@ Res CLoanView::CreateLoanCollateralToken(CLoanSetCollateralTokenImpl const & col return Res::Ok(); } -Res CLoanView::UpdateLoanCollateralToken(CLoanSetCollateralTokenImpl const & collateralToken) +Res CLoanView::EraseLoanCollateralToken(const CLoanSetCollateralTokenImpl& collToken) { - if (collateralToken.factor > COIN) - return Res::Err("setCollateralToken factor must be lower or equal than %s!", GetDecimaleString(COIN)); - if (collateralToken.factor < 0) - return Res::Err("setCollateralToken factor must not be negative!"); - - CollateralTokenKey key{collateralToken.idToken, collateralToken.activateAfterBlock}; - WriteBy(key, collateralToken.creationTx); + CollateralTokenKey key{collToken.idToken, collToken.activateAfterBlock}; + EraseBy(key); + EraseBy(collToken.creationTx); return Res::Ok(); } @@ -47,15 +43,16 @@ void CLoanView::ForEachLoanCollateralToken(std::function(callback, start); } -std::unique_ptr CLoanView::HasLoanCollateralToken(CollateralTokenKey const & key) +boost::optional CLoanView::HasLoanCollateralToken(CollateralTokenKey const & key) { auto it = LowerBound(key); if (it.Valid() && it.Key().id == key.id) return GetLoanCollateralToken(it.Value()); - return {}; + + return GetCollateralTokenFromAttributes(key.id); } -std::unique_ptr CLoanView::GetLoanToken(uint256 const & txid) const +boost::optional CLoanView::GetLoanToken(uint256 const & txid) const { auto id = ReadBy(txid); if (id) @@ -63,14 +60,6 @@ std::unique_ptr CLoanView::GetLoanToken(uint25 return {}; } -std::unique_ptr CLoanView::GetLoanTokenByID(DCT_ID const & id) const -{ - auto loanToken = ReadBy(id); - if (loanToken) - return MakeUnique(*loanToken); - return {}; -} - Res CLoanView::SetLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID const & id) { //this should not happen, but for sure @@ -96,6 +85,13 @@ Res CLoanView::UpdateLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID c return Res::Ok(); } +Res CLoanView::EraseLoanToken(const DCT_ID& id) +{ + EraseBy(id); + + return Res::Ok(); +} + void CLoanView::ForEachLoanToken(std::function callback, DCT_ID const & start) { ForEach(callback, start); @@ -394,6 +390,11 @@ Res CLoanView::DeleteInterest(const CVaultId& vaultId, uint32_t height) return Res::Ok(); } +void CLoanView::EraseInterestDirect(const CVaultId& vaultId, DCT_ID id) +{ + EraseBy(std::make_pair(vaultId, id)); +} + void CLoanView::RevertInterestRateToV1() { std::vector> pairs; @@ -461,7 +462,7 @@ boost::optional CLoanView::GetLoanTokens(const CVaultId& vaultId) return ReadBy(vaultId); } -void CLoanView::ForEachLoanToken(std::function callback) +void CLoanView::ForEachLoanTokenAmount(std::function callback) { ForEach(callback); } diff --git a/src/masternodes/loan.h b/src/masternodes/loan.h index 9711d9a549e..422c4b0ab3d 100644 --- a/src/masternodes/loan.h +++ b/src/masternodes/loan.h @@ -268,6 +268,8 @@ base_uint<128> TotalInterestCalculation(const CInterestRateV2& rate, uint32_t he CAmount CeilInterest(const base_uint<128>& value, uint32_t height); boost::optional GetInterestPerBlockHighPrecisionString(const base_uint<128>& value); +base_uint<128> InterestPerBlockCalculationV2(CAmount amount, CAmount tokenInterest, CAmount schemeInterest); + class CLoanTakeLoanMessage { public: @@ -324,16 +326,17 @@ class CLoanView : public virtual CStorageView { using CLoanSetCollateralTokenImpl = CLoanSetCollateralTokenImplementation; using CLoanSetLoanTokenImpl = CLoanSetLoanTokenImplementation; - std::unique_ptr GetLoanCollateralToken(uint256 const & txid) const; + boost::optional GetLoanCollateralToken(uint256 const & txid) const; Res CreateLoanCollateralToken(CLoanSetCollateralTokenImpl const & collToken); - Res UpdateLoanCollateralToken(CLoanSetCollateralTokenImpl const & collateralToken); + Res EraseLoanCollateralToken(const CLoanSetCollateralTokenImpl& collToken); void ForEachLoanCollateralToken(std::function callback, CollateralTokenKey const & start = {DCT_ID{0}, UINT_MAX}); - std::unique_ptr HasLoanCollateralToken(CollateralTokenKey const & key); + boost::optional HasLoanCollateralToken(CollateralTokenKey const & key); - std::unique_ptr GetLoanToken(uint256 const & txid) const; - std::unique_ptr GetLoanTokenByID(DCT_ID const & id) const; + boost::optional GetLoanToken(uint256 const & txid) const; + [[nodiscard]] virtual boost::optional GetLoanTokenByID(DCT_ID const & id) const = 0; Res SetLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID const & id); Res UpdateLoanToken(CLoanSetLoanTokenImpl const & loanToken, DCT_ID const & id); + Res EraseLoanToken(const DCT_ID& id); void ForEachLoanToken(std::function callback, DCT_ID const & start = {0}); Res StoreLoanScheme(const CLoanSchemeMessage& loanScheme); @@ -351,6 +354,7 @@ class CLoanView : public virtual CStorageView { void ForEachDelayedDestroyScheme(std::function callback); Res DeleteInterest(const CVaultId& vaultId, uint32_t height); + void EraseInterestDirect(const CVaultId& vaultId, DCT_ID id); boost::optional GetInterestRate(const CVaultId& loanSchemeID, DCT_ID id, uint32_t height); void WriteInterestRate(const std::pair& pair, const CInterestRateV2& rate, uint32_t height); Res StoreInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount loanIncreased); @@ -363,11 +367,14 @@ class CLoanView : public virtual CStorageView { Res AddLoanToken(const CVaultId& vaultId, CTokenAmount amount); Res SubLoanToken(const CVaultId& vaultId, CTokenAmount amount); boost::optional GetLoanTokens(const CVaultId& vaultId); - void ForEachLoanToken(std::function callback); + void ForEachLoanTokenAmount(std::function callback); Res SetLoanLiquidationPenalty(CAmount penalty); CAmount GetLoanLiquidationPenalty(); + [[nodiscard]] virtual boost::optional GetLoanTokenFromAttributes(const DCT_ID& id) const = 0; + [[nodiscard]] virtual boost::optional GetCollateralTokenFromAttributes(const DCT_ID& id) const = 0; + struct LoanSetCollateralTokenCreationTx { static constexpr uint8_t prefix() { return 0x10; } }; struct LoanSetCollateralTokenKey { static constexpr uint8_t prefix() { return 0x11; } }; struct LoanSetLoanTokenCreationTx { static constexpr uint8_t prefix() { return 0x12; } }; diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index 54d3bfe8755..11b182a0d51 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -4,10 +4,12 @@ #include #include +#include #include #include #include +#include #include #include #include