diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ed2a4c1571..aa7f0361fd 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -118,6 +118,7 @@ class CMainParams : public CChainParams { consensus.BayfrontHeight = 405000; consensus.BayfrontMarinaHeight = 465150; consensus.BayfrontGardensHeight = 488300; + consensus.ClarkeQuayHeight = 595738; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -279,6 +280,7 @@ class CTestNetParams : public CChainParams { consensus.BayfrontHeight = 3000; consensus.BayfrontMarinaHeight = 90470; consensus.BayfrontGardensHeight = 101342; + consensus.ClarkeQuayHeight = 155000; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -415,8 +417,9 @@ class CDevNetParams : public CChainParams { consensus.BIP66Height = 0; consensus.AMKHeight = 150; consensus.BayfrontHeight = 250; - consensus.BayfrontMarinaHeight = 0; - consensus.BayfrontGardensHeight = 0; + consensus.BayfrontMarinaHeight = 300; + consensus.BayfrontGardensHeight = 300; + consensus.ClarkeQuayHeight = 500; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks @@ -550,6 +553,7 @@ class CRegTestParams : public CChainParams { consensus.BayfrontHeight = 10000000; consensus.BayfrontMarinaHeight = 10000000; consensus.BayfrontGardensHeight = 10000000; + consensus.ClarkeQuayHeight = 10000000; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.pos.nTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -726,6 +730,17 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) consensus.BayfrontGardensHeight = static_cast(height); } + if (gArgs.IsArgSet("-clarkequayheight")) { + int64_t height = gArgs.GetArg("-clarkequayheight", consensus.ClarkeQuayHeight); + if (height < -1 || height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Activation height %ld for ClarkeQuay is out of valid range. Use -1 to disable clarkequay features.", height)); + } else if (height == -1) { + LogPrintf("CQ disabled for testing\n"); + height = std::numeric_limits::max(); + } + consensus.ClarkeQuayHeight = static_cast(height); + } + if (!args.IsArgSet("-vbparams")) return; for (const std::string& strDeployment : args.GetArgs("-vbparams")) { diff --git a/src/consensus/params.h b/src/consensus/params.h index 1155371cc2..bbdee88da9 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -76,6 +76,8 @@ struct Params { int BayfrontHeight; int BayfrontMarinaHeight; int BayfrontGardensHeight; + /** Third major fork. */ + int ClarkeQuayHeight; /** Foundation share after AMK, normalized to COIN = 100% */ CAmount foundationShareDFIP1; diff --git a/src/init.cpp b/src/init.cpp index 9d10e8c0f3..62f89cc0a6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -468,6 +468,7 @@ void SetupServerArgs() gArgs.AddArg("-amkheight", "AMK fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-bayfrontheight", "Bayfront fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-bayfrontgardensheight", "Bayfront Gardens fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-clarkequayheight", "ClarkeQuay fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); #ifdef USE_UPNP #if USE_UPNP gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 8197da9f0d..c0e27d41fb 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1018,9 +1018,16 @@ Res ApplyCreatePoolPairTx(CCustomCSView &mnview, const CCoinsViewCache &coins, c CPoolPairMessage poolPairMsg; std::string pairSymbol; + CBalances rewards; CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); ss >> poolPairMsg; ss >> pairSymbol; + + // Read custom pool rewards + if (static_cast(height) >= consensusParams.ClarkeQuayHeight && !ss.empty()) { + ss >> rewards; + } + if (!ss.empty()) { return Res::Err("%s: deserialization failed: excess %d bytes", base, ss.size()); } @@ -1042,12 +1049,12 @@ Res ApplyCreatePoolPairTx(CCustomCSView &mnview, const CCoinsViewCache &coins, c auto tokenA = mnview.GetToken(poolPairMsg.idTokenA); if (!tokenA) { - return Res::Err("%s: token %s does not exist!", poolPairMsg.idTokenA.ToString()); + return Res::Err("%s: token %s does not exist!", base, poolPairMsg.idTokenA.ToString()); } auto tokenB = mnview.GetToken(poolPairMsg.idTokenB); if (!tokenB) { - return Res::Err("%s: token %s does not exist!", poolPairMsg.idTokenB.ToString()); + return Res::Err("%s: token %s does not exist!", base, poolPairMsg.idTokenB.ToString()); } if(pairSymbol.empty()) @@ -1079,6 +1086,20 @@ Res ApplyCreatePoolPairTx(CCustomCSView &mnview, const CCoinsViewCache &coins, c rpcInfo->pushKV("tradeable", token.IsTradeable()); rpcInfo->pushKV("finalized", token.IsFinalized()); + if (!rewards.balances.empty()) { + UniValue rewardArr(UniValue::VARR); + + for (const auto& reward : rewards.balances) { + if (reward.second > 0) { + rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); + } + } + + if (!rewardArr.empty()) { + rpcInfo->pushKV("customRewards", rewardArr); + } + } + return Res::Ok(base); } @@ -1098,6 +1119,29 @@ Res ApplyCreatePoolPairTx(CCustomCSView &mnview, const CCoinsViewCache &coins, c return Res::Err("%s %s: %s", base, pairSymbol, resPP.msg); } + if (!rewards.balances.empty()) { + // Check tokens exist and remove empty reward amounts + for (auto it = rewards.balances.cbegin(), next_it = it; it != rewards.balances.cend(); it = next_it) { + ++next_it; + + auto token = pcustomcsview->GetToken(it->first); + if (!token) { + return Res::Err("%s: reward token %d does not exist!", base, it->first.v); + } + + if (it->second == 0) { + rewards.balances.erase(it); + } + } + + auto resCR = mnview.SetPoolCustomReward(pairToken->first, rewards); + + // Will only fail if pool was not actually created in SetPoolPair + if (!resCR.ok) { + return Res::Err("%s %s: %s", base, pairSymbol, resCR.msg); + } + } + return Res::Ok(base); } @@ -1113,11 +1157,18 @@ Res ApplyUpdatePoolPairTx(CCustomCSView & mnview, CCoinsViewCache const & coins, bool status; CAmount commission; CScript ownerAddress; + CBalances rewards; CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); ss >> poolId; ss >> status; ss >> commission; ss >> ownerAddress; + + // Read custom pool rewards + if (static_cast(height) >= consensusParams.ClarkeQuayHeight && !ss.empty()) { + ss >> rewards; + } + if (!ss.empty()) { return Res::Err("Pool Update: deserialization failed: excess %d bytes", ss.size()); } @@ -1141,6 +1192,56 @@ Res ApplyUpdatePoolPairTx(CCustomCSView & mnview, CCoinsViewCache const & coins, rpcInfo->pushKV("commission", ValueFromAmount(commission)); rpcInfo->pushKV("status", status); rpcInfo->pushKV("ownerAddress", ownerAddress.GetHex()); + + // Add rewards here before processing them below to avoid adding current rewards + if (!rewards.balances.empty()) { + UniValue rewardArr(UniValue::VARR); + + // Check for special case to wipe rewards + if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} + && rewards.balances.cbegin()->second == std::numeric_limits::max()) { + rpcInfo->pushKV("customRewards", rewardArr); + } else { + for (const auto& reward : rewards.balances) { + if (reward.second > 0) { + rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); + } + } + + if (!rewardArr.empty()) { + rpcInfo->pushKV("customRewards", rewardArr); + } + } + } + } + + if (!rewards.balances.empty()) { + // Check for special case to wipe rewards + if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} + && rewards.balances.cbegin()->second == std::numeric_limits::max()) { + rewards.balances.clear(); + } + + // Check if tokens exist and remove empty reward amounts + for (auto it = rewards.balances.cbegin(), next_it = it; it != rewards.balances.cend(); it = next_it) { + ++next_it; + + auto token = pcustomcsview->GetToken(it->first); + if (!token) { + return Res::Err("%s: reward token %d does not exist!", base, it->first.v); + } + + if (it->second == 0) { + rewards.balances.erase(it); + } + } + + auto resCR = mnview.SetPoolCustomReward(poolId, rewards); + + // Will only fail if pool was not actually created in SetPoolPair + if (!resCR.ok) { + return Res::Err("%s %s: %s", base, poolId.ToString(), resCR.msg); + } } return Res::Ok(base); diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index 575fabb119..3163690cb9 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -1815,6 +1815,31 @@ UniValue poolToJSON(DCT_ID const& id, CPoolPair const& pool, CToken const& token poolObj.pushKV("rewardPct", ValueFromAmount(pool.rewardPct)); + auto rewards = pcustomcsview->GetPoolCustomReward(id); + if (rewards && !rewards->balances.empty()) { + for (auto it = rewards->balances.cbegin(), next_it = it; it != rewards->balances.cend(); it = next_it) { + ++next_it; + + // Get token balance + const auto balance = pcustomcsview->GetBalance(pool.ownerAddress, it->first).nValue; + + // Make there's enough to pay reward otherwise remove it + if (balance < it->second) { + rewards->balances.erase(it); + } + } + + if (!rewards->balances.empty()) { + UniValue rewardArr(UniValue::VARR); + + for (const auto& reward : rewards->balances) { + rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); + } + + poolObj.pushKV("customRewards", rewardArr); + } + } + poolObj.pushKV("creationTx", pool.creationTx.GetHex()); poolObj.pushKV("creationHeight", (uint64_t) pool.creationHeight); } @@ -2500,6 +2525,8 @@ UniValue createpoolpair(const JSONRPCRequest& request) { "Pool Status: True is Active, False is Restricted"}, {"ownerAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "Address of the pool owner."}, + {"customRewards", RPCArg::Type::STR, RPCArg::Optional::OMITTED, + "Token reward to be paid on each block, multiple can be specified."}, {"pairSymbol", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Pair symbol (unique), no longer than " + std::to_string(CToken::MAX_TOKEN_SYMBOL_LENGTH)}, @@ -2524,14 +2551,16 @@ UniValue createpoolpair(const JSONRPCRequest& request) { "\"tokenB\":\"MyToken2\"," "\"commission\":\"0.001\"," "\"status\":\"True\"," - "\"ownerAddress\":\"Address\"" + "\"ownerAddress\":\"Address\"," + "\"customRewards\":\"[\\\"1@tokena\\\",\\\"10@tokenb\\\"]\"" "}' '[{\"txid\":\"id\",\"vout\":0}]'") + HelpExampleRpc("createpoolpair", "'{\"tokenA\":\"MyToken1\"," "\"tokenB\":\"MyToken2\"," "\"commission\":\"0.001\"," "\"status\":\"True\"," - "\"ownerAddress\":\"Address\"" - "}' '[{\"txid\":\"id\",\"vout\":0}]'") + "\"ownerAddress\":\"Address\"," + "\"customRewards\":\"[\\\"1@tokena\\\",\\\"10@tokenb\\\"]\"" + "}' '[{\"txid\":\"id\",\"vout\":0}]'") }, }.Check(request); @@ -2546,6 +2575,7 @@ UniValue createpoolpair(const JSONRPCRequest& request) { std::string tokenA, tokenB, pairSymbol; CAmount commission = 0; // !!! CScript ownerAddress; + CBalances rewards; bool status = true; // default Active UniValue metadataObj = request.params[0].get_obj(); if (!metadataObj["tokenA"].isNull()) { @@ -2566,6 +2596,9 @@ UniValue createpoolpair(const JSONRPCRequest& request) { if (!metadataObj["pairSymbol"].isNull()) { pairSymbol = metadataObj["pairSymbol"].getValStr(); } + if (!metadataObj["customRewards"].isNull()) { + rewards = DecodeAmounts(pwallet->chain(), metadataObj["customRewards"], ""); + } int targetHeight; DCT_ID idtokenA, idtokenB; @@ -2594,6 +2627,10 @@ UniValue createpoolpair(const JSONRPCRequest& request) { metadata << static_cast(CustomTxType::CreatePoolPair) << poolPairMsg << pairSymbol; + if (targetHeight >= Params().GetConsensus().ClarkeQuayHeight) { + metadata << rewards; + } + CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); @@ -2648,7 +2685,7 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { {"status", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Pool Status new property (bool)"}, {"commission", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Pool commission, up to 10^-8"}, {"ownerAddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Address of the pool owner."}, - + {"customRewards", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Token reward to be paid on each block, multiple can be specified."}, }, }, {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", @@ -2668,10 +2705,12 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { }, RPCExamples{ HelpExampleCli("updatepoolpair", "'{\"pool\":\"POOL\",\"status\":true," - "\"commission\":0.01,\"ownerAddress\":\"Address\"}' " + "\"commission\":0.01,\"ownerAddress\":\"Address\"," + "\"customRewards\":\"[\\\"1@tokena\\\",\\\"10@tokenb\\\"]\"}' " "'[{\"txid\":\"id\",\"vout\":0}]'") + HelpExampleRpc("updatepoolpair", "'{\"pool\":\"POOL\",\"status\":true," - "\"commission\":0.01,\"ownerAddress\":\"Address\"}' " + "\"commission\":0.01,\"ownerAddress\":\"Address\"," + "\"customRewards\":\"[\\\"1@tokena\\\",\\\"10@tokenb\\\"]\"}' " "'[{\"txid\":\"id\",\"vout\":0}]'") }, }.Check(request); @@ -2688,6 +2727,7 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { bool status = true; CAmount commission = -1; CScript ownerAddress; + CBalances rewards; UniValue const & metaObj = request.params[0].get_obj(); UniValue const & txInputs = request.params[1]; @@ -2718,6 +2758,14 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { if (!metaObj["ownerAddress"].isNull()) { ownerAddress = DecodeScript(metaObj["ownerAddress"].getValStr()); } + if (!metaObj["customRewards"].isNull()) { + rewards = DecodeAmounts(pwallet->chain(), metaObj["customRewards"], ""); + + if (rewards.balances.empty()) { + // Add special case to wipe rewards + rewards.balances.insert(std::pair(DCT_ID{std::numeric_limits::max()}, std::numeric_limits::max())); + } + } const auto txVersion = GetTransactionVersion(targetHeight); CMutableTransaction rawTx(txVersion); @@ -2730,6 +2778,10 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { metadata << static_cast(CustomTxType::UpdatePoolPair) << poolId << status << commission << ownerAddress; + if (targetHeight >= Params().GetConsensus().ClarkeQuayHeight) { + metadata << rewards; + } + CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index 2f20a46d0c..2080de7f52 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -10,6 +10,7 @@ const unsigned char CPoolPairView::ByID ::prefix = 'i'; const unsigned char CPoolPairView::ByPair ::prefix = 'j'; const unsigned char CPoolPairView::ByShare ::prefix = 'k'; +const unsigned char CPoolPairView::Reward ::prefix = 'I'; Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, CPoolPair const & pool) { @@ -98,6 +99,24 @@ boost::optional > CPoolPairView::GetPoolPair(const return {}; } +Res CPoolPairView::SetPoolCustomReward(const DCT_ID &poolId, CBalances& rewards) +{ + DCT_ID poolID = poolId; + if (!GetPoolPair(poolID)) + { + return Res::Err("Error %s: poolID %s does not exist", __func__, poolID.ToString()); + } + + WriteBy(WrapVarInt(poolID.v), rewards); + return Res::Ok(); +} + +boost::optional CPoolPairView::GetPoolCustomReward(const DCT_ID &poolId) +{ + DCT_ID poolID = poolId; + return ReadBy(WrapVarInt(poolID.v)); +} + Res CPoolPair::Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function onTransfer, bool postBayfrontGardens) { if (in.nTokenId != idTokenA && in.nTokenId != idTokenB) { throw std::runtime_error("Error, input token ID (" + in.nTokenId.ToString() + ") doesn't match pool tokens (" + idTokenA.ToString() + "," + idTokenB.ToString() + ")"); diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index 3a94902a01..f7bb803574 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -9,6 +9,7 @@ #include #include +#include #include #include