From a9bb15852df2b730b46f069169b734826aff5540 Mon Sep 17 00:00:00 2001 From: Prasanna Loganathar Date: Tue, 25 Jan 2022 15:29:26 +0530 Subject: [PATCH] Refactor smart contracts (#1057) * Refactor smart contracts * Make tests executable * Refactor smart contract execution RPC * Refine CWalletCoinsUnlocker ctors --- src/chainparams.cpp | 9 +- src/chainparams.h | 2 + src/masternodes/mn_checks.cpp | 137 +++++++------- src/masternodes/mn_rpc.cpp | 3 +- src/masternodes/mn_rpc.h | 2 + src/masternodes/rpc_accounts.cpp | 179 ++++++++++-------- .../feature_loan_estimatecollateral.py | 0 test/functional/feature_loan_estimateloan.py | 0 test/functional/feature_loan_interest.py | 0 test/functional/feature_loan_vaultstate.py | 0 test/functional/feature_smart_contracts.py | 0 test/functional/rpc_listvaulthistory.py | 0 test/functional/rpc_mn_basic.py | 0 13 files changed, 175 insertions(+), 157 deletions(-) mode change 100644 => 100755 test/functional/feature_loan_estimatecollateral.py mode change 100644 => 100755 test/functional/feature_loan_estimateloan.py mode change 100644 => 100755 test/functional/feature_loan_interest.py mode change 100644 => 100755 test/functional/feature_loan_vaultstate.py mode change 100644 => 100755 test/functional/feature_smart_contracts.py mode change 100644 => 100755 test/functional/rpc_listvaulthistory.py mode change 100644 => 100755 test/functional/rpc_mn_basic.py diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 90851cf67a..df55537e40 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -4,7 +4,6 @@ // file LICENSE or http://www.opensource.org/licenses/mit-license.php. #include - #include #include #include @@ -244,7 +243,7 @@ class CMainParams : public CChainParams { consensus.accountDestruction.insert(GetScriptForDestination(DecodeDestination("8UAhRuUFCyFUHEPD7qvtj8Zy2HxF5HH5nb", *this))); consensus.smartContracts.clear(); - consensus.smartContracts["DFIP2201"] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); + consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); // owner base58, operator base58 vMasternodes.push_back({"8PuErAcazqccCVzRcc8vJ3wFaZGm4vFbLe", "8J846CKFF83Jcj5m4EReJmxiaJ6Jy1Y6Ea"}); @@ -460,7 +459,7 @@ class CTestNetParams : public CChainParams { consensus.accountDestruction.insert(GetScriptForDestination(DecodeDestination("75jrurn8tkDLhZ3YPyzhk6D9kc1a4hBrmM", *this))); // cSmsVpoR6dSW5hPNKeGwC561gXHXcksdQb2yAFQdjbSp5MUyzZqr consensus.smartContracts.clear(); - consensus.smartContracts["DFIP2201"] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); + consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); // owner base58, operator base58 vMasternodes.push_back({"7LMorkhKTDjbES6DfRxX2RiNMbeemUkxmp", "7KEu9JMKCx6aJ9wyg138W3p42rjg19DR5D"}); @@ -641,7 +640,7 @@ class CDevNetParams : public CChainParams { consensus.foundationMembers.emplace(GetScriptForDestination(DecodeDestination("7L29itepC13pgho1X2y7mcuf4WjkBi7x2w", *this))); consensus.smartContracts.clear(); - consensus.smartContracts["DFIP2201"] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); + consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); // owner base58, operator base58 vMasternodes.push_back({"7M3g9CSERjLdXisE5pv2qryDbURUj9Vpi1", "7Grgx69MZJ4wDKRx1bBxLqTnU9T3quKW7n"}); @@ -828,7 +827,7 @@ class CRegTestParams : public CChainParams { consensus.accountDestruction.insert(GetScriptForDestination(DecodeDestination("mxiaFfAnCoXEUy4RW8NgsQM7yU5YRCiFSh", *this))); consensus.smartContracts.clear(); - consensus.smartContracts["DFIP2201"] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); + consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); // owner base58, operator base58 vMasternodes.push_back({"mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU", "mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy"}); diff --git a/src/chainparams.h b/src/chainparams.h index 059c1b0f05..2ec2c0d662 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -119,6 +119,8 @@ class CChainParams std::set genesisTeam; }; +const auto SMART_CONTRACT_DFIP_2201 = "DFIP2201"; + /** * Creates and returns a std::unique_ptr of the chosen chain. * @returns a CChainParams* of the chosen chain. diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index a304bb07db..70c495d2a5 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1343,96 +1343,96 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return !res ? res : addBalancesSetShares(obj.to); } - Res operator()(const CSmartContractMessage& obj) const { + + Res HandleDFIP2201Contract(const CSmartContractMessage& obj) const { const auto attributes = mnview.GetAttributes(); - if (!attributes) { - return Res::Err("DFIP2201 smart contract is not enabled"); - } + if (!attributes) + return Res::Err("Attributes unavailable"); CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::Active}; - if (!attributes->GetValue(activeKey, false)) { + + if (!attributes->GetValue(activeKey, false)) return Res::Err("DFIP2201 smart contract is not enabled"); - } - if (obj.accounts.empty()) { - return Res::Err("No address and amount entries found"); - } + if (obj.name != SMART_CONTRACT_DFIP_2201) + return Res::Err("DFIP2201 contract mismatch - got: " + obj.name); - if (obj.name == Params().GetConsensus().smartContracts.begin()->first) { - if (obj.accounts.size() != 1) { - return Res::Err("Only one address entry expected for " + obj.name); - } + if (obj.accounts.size() != 1) + return Res::Err("Only one address entry expected for " + obj.name); - if (obj.accounts.begin()->second.balances.size() != 1) { - return Res::Err("Only one amount entry expected for " + obj.name); - } + if (obj.accounts.begin()->second.balances.size() != 1) + return Res::Err("Only one amount entry expected for " + obj.name); - const auto& script = obj.accounts.begin()->first; - if (!HasAuth(script)) { - return Res::Err("Must have at least one input from supplied address"); - } + const auto& script = obj.accounts.begin()->first; + if (!HasAuth(script)) + return Res::Err("Must have at least one input from supplied address"); - const auto& id = obj.accounts.begin()->second.balances.begin()->first; - const auto& amount = obj.accounts.begin()->second.balances.begin()->second; + const auto& id = obj.accounts.begin()->second.balances.begin()->first; + const auto& amount = obj.accounts.begin()->second.balances.begin()->second; - if (amount <= 0) { - return Res::Err("Amount out of range"); - } + if (amount <= 0) + return Res::Err("Amount out of range"); - CDataStructureV0 minSwapKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::MinSwap}; - auto minSwap = attributes->GetValue(minSwapKey, CAmount{0}); + CDataStructureV0 minSwapKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::MinSwap}; + auto minSwap = attributes->GetValue(minSwapKey, CAmount{0}); - if (minSwap && amount < minSwap) { - return Res::Err("Below minimum swapable amount, must be at least " + ValueFromAmount(minSwap).getValStr() + " BTC"); - } + if (minSwap && amount < minSwap) + return Res::Err("Below minimum swapable amount, must be at least " + ValueFromAmount(minSwap).getValStr() + " BTC"); - const auto token = mnview.GetToken(id); - if (!token) { - return Res::Err("Specified token not found"); - } + const auto token = mnview.GetToken(id); + if (!token) + return Res::Err("Specified token not found"); - if (token->symbol != "BTC" || token->name != "Bitcoin" || !token->IsDAT()) { - return Res::Err("Only Bitcoin can be swapped in " + obj.name); - } + if (token->symbol != "BTC" || token->name != "Bitcoin" || !token->IsDAT()) + return Res::Err("Only Bitcoin can be swapped in " + obj.name); - auto res = mnview.SubBalance(script, {id, amount}); - if (!res) { - return res; - } + auto res = mnview.SubBalance(script, {id, amount}); + if (!res) + return res; - const std::pair btcUsd{"BTC","USD"}; - const std::pair dfiUsd{"DFI","USD"}; + const std::pair btcUsd{"BTC","USD"}; + const std::pair dfiUsd{"DFI","USD"}; - bool useNextPrice{false}, requireLivePrice{true}; - auto resVal = mnview.GetValidatedIntervalPrice(btcUsd, useNextPrice, requireLivePrice); - if (!resVal) { - return std::move(resVal); - } + bool useNextPrice{false}, requireLivePrice{true}; + auto resVal = mnview.GetValidatedIntervalPrice(btcUsd, useNextPrice, requireLivePrice); + if (!resVal) + return std::move(resVal); - CDataStructureV0 premiumKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::Premium}; - auto premium = attributes->GetValue(premiumKey, CAmount{2500000}); + CDataStructureV0 premiumKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::Premium}; + auto premium = attributes->GetValue(premiumKey, CAmount{2500000}); - const auto& btcPrice = MultiplyAmounts(*resVal.val, premium + COIN); + const auto& btcPrice = MultiplyAmounts(*resVal.val, premium + COIN); - resVal = mnview.GetValidatedIntervalPrice(dfiUsd, useNextPrice, requireLivePrice); - if (!resVal) { - return std::move(resVal); - } + resVal = mnview.GetValidatedIntervalPrice(dfiUsd, useNextPrice, requireLivePrice); + if (!resVal) + return std::move(resVal); - const auto totalDFI = MultiplyAmounts(DivideAmounts(btcPrice, *resVal.val), amount); + const auto totalDFI = MultiplyAmounts(DivideAmounts(btcPrice, *resVal.val), amount); - res = mnview.SubBalance(Params().GetConsensus().smartContracts.begin()->second, {{0}, totalDFI}); - if (!res) { - return res; - } + res = mnview.SubBalance(Params().GetConsensus().smartContracts.begin()->second, {{0}, totalDFI}); + if (!res) + return res; - res = mnview.AddBalance(script, {{0}, totalDFI}); - if (!res) { - return res; - } + res = mnview.AddBalance(script, {{0}, totalDFI}); + if (!res) + return res; + + return Res::Ok(); + } - return Res::Ok(); + Res operator()(const CSmartContractMessage& obj) const { + if (obj.accounts.empty()) { + return Res::Err("Contract account parameters missing"); } + auto contracts = Params().GetConsensus().smartContracts; + + auto contract = contracts.find(obj.name); + if (contract == contracts.end()) + return Res::Err("Specified smart contract not found"); + + // Convert to switch when it's long enough. + if (obj.name == SMART_CONTRACT_DFIP_2201) + return HandleDFIP2201Contract(obj); return Res::Err("Specified smart contract not found"); } @@ -3263,8 +3263,11 @@ Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTr TBytes dummy; switch(GuessCustomTxType(tx, dummy)) { - case CustomTxType::TakeLoan: case CustomTxType::PaybackLoan: case CustomTxType::DepositToVault: - case CustomTxType::WithdrawFromVault: case CustomTxType::UpdateVault: + case CustomTxType::TakeLoan: + case CustomTxType::PaybackLoan: + case CustomTxType::DepositToVault: + case CustomTxType::WithdrawFromVault: + case CustomTxType::UpdateVault: return Res::Err("This type of transaction is not possible around hard fork height"); break; default: diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index eb7d028021..f7f65611d8 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -202,7 +202,8 @@ static CTransactionRef send(CTransactionRef tx, CTransactionRef optAuthTx) { return tx; } -CWalletCoinsUnlocker::CWalletCoinsUnlocker(std::shared_ptr pwallet) : pwallet(std::move(pwallet)) { +CWalletCoinsUnlocker::CWalletCoinsUnlocker(std::shared_ptr pwallet) : + pwallet(std::move(pwallet)) { } CWalletCoinsUnlocker::~CWalletCoinsUnlocker() { diff --git a/src/masternodes/mn_rpc.h b/src/masternodes/mn_rpc.h index d3396b11ec..2d4f2138ce 100644 --- a/src/masternodes/mn_rpc.h +++ b/src/masternodes/mn_rpc.h @@ -39,6 +39,8 @@ class CWalletCoinsUnlocker { std::vector coins; public: explicit CWalletCoinsUnlocker(std::shared_ptr pwallet); + CWalletCoinsUnlocker(const CWalletCoinsUnlocker&) = delete; + CWalletCoinsUnlocker(CWalletCoinsUnlocker&&) = default; ~CWalletCoinsUnlocker(); CWallet* operator->(); CWallet& operator*(); diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 428d2503b5..4b1fde08bb 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -1687,10 +1687,8 @@ UniValue sendtokenstoaddress(const JSONRPCRequest& request) { execTestTx(CTransaction(rawTx), targetHeight, optAuthTx); return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); - } - UniValue getburninfo(const JSONRPCRequest& request) { RPCHelpMan{"getburninfo", "\nReturns burn address and burnt coin and token information.\n" @@ -1823,6 +1821,100 @@ UniValue getburninfo(const JSONRPCRequest& request) { return result; } +UniValue HandleSendDFIP2201DFIInput(const JSONRPCRequest& request, CWalletCoinsUnlocker pwallet, + const std::pair& contractPair, CTokenAmount amount) { + CUtxosToAccountMessage msg{}; + msg.to = {{contractPair.second, {{{{0}, amount.nValue}}}}}; + + CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); + metadata << static_cast(CustomTxType::UtxosToAccount) + << msg; + CScript scriptMeta; + scriptMeta << OP_RETURN << ToByteVector(metadata); + + int targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + const auto txVersion = GetTransactionVersion(targetHeight); + CMutableTransaction rawTx(txVersion); + + rawTx.vout.push_back(CTxOut(amount.nValue, scriptMeta)); + + // change + CCoinControl coinControl; + CTxDestination dest; + ExtractDestination(Params().GetConsensus().foundationShareScript, dest); + coinControl.destChange = dest; + + // Only use inputs from dest + coinControl.matchDestination = dest; + + // fund + fund(rawTx, pwallet, {}, &coinControl); + + // check execution + execTestTx(CTransaction(rawTx), targetHeight); + + return signsend(rawTx, pwallet, {})->GetHash().GetHex(); +} + +UniValue HandleSendDFIP2201BTCInput(const JSONRPCRequest& request, CWalletCoinsUnlocker pwallet, + const std::pair& contractPair, CTokenAmount amount) { + if (request.params[2].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "BTC source address must be provided for " + contractPair.first); + } + CTxDestination dest = DecodeDestination(request.params[2].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + const auto script = GetScriptForDestination(dest); + + CSmartContractMessage msg{}; + msg.name = contractPair.first; + msg.accounts = {{script, {{{amount.nTokenId, amount.nValue}}}}}; + // encode + CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); + metadata << static_cast(CustomTxType::SmartContract) + << msg; + CScript scriptMeta; + scriptMeta << OP_RETURN << ToByteVector(metadata); + + int targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + const auto txVersion = GetTransactionVersion(targetHeight); + CMutableTransaction rawTx(txVersion); + + rawTx.vout.emplace_back(0, scriptMeta); + + CTransactionRef optAuthTx; + std::set auth{script}; + rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auth, false, optAuthTx, request.params[3]); + + // Set change address + CCoinControl coinControl; + coinControl.destChange = dest; + + // fund + fund(rawTx, pwallet, optAuthTx, &coinControl); + + // check execution + execTestTx(CTransaction(rawTx), targetHeight, optAuthTx); + + return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); +} + +UniValue HandleSendDFIP2201(const JSONRPCRequest& request, CWalletCoinsUnlocker pwallet) { + auto contracts = Params().GetConsensus().smartContracts; + const auto& contractPair = contracts.find(SMART_CONTRACT_DFIP_2201); + assert(contractPair != contracts.end()); + + CTokenAmount amount = DecodeAmount(pwallet->chain(), request.params[1].get_str(), "amount"); + + if (amount.nTokenId.v == 0) { + return HandleSendDFIP2201DFIInput(request, std::move(pwallet), *contractPair, amount); + } else { + return HandleSendDFIP2201BTCInput(request, std::move(pwallet), *contractPair, amount); + } +} UniValue executesmartcontract(const JSONRPCRequest& request) { auto pwallet = GetWallet(request); @@ -1861,92 +1953,11 @@ UniValue executesmartcontract(const JSONRPCRequest& request) { pwallet->BlockUntilSyncedToCurrentChain(); const auto& contractName = request.params[0].get_str(); - CTokenAmount amount = DecodeAmount(pwallet->chain(),request.params[1].get_str(), "amount"); - if (contractName == "dbtcdfiswap") { - const auto& contractPair= Params().GetConsensus().smartContracts.begin(); - if (amount.nTokenId.v == 0) { - CUtxosToAccountMessage msg{}; - msg.to = {{contractPair->second, {{{{0}, amount.nValue}}}}}; - - CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::UtxosToAccount) - << msg; - CScript scriptMeta; - scriptMeta << OP_RETURN << ToByteVector(metadata); - - int targetHeight = chainHeight(*pwallet->chain().lock()) + 1; - - const auto txVersion = GetTransactionVersion(targetHeight); - CMutableTransaction rawTx(txVersion); - - rawTx.vout.push_back(CTxOut(amount.nValue, scriptMeta)); - - // change - CCoinControl coinControl; - CTxDestination dest; - ExtractDestination(Params().GetConsensus().foundationShareScript, dest); - coinControl.destChange = dest; - - // Only use inputs from dest - coinControl.matchDestination = dest; - - // fund - fund(rawTx, pwallet, {}, &coinControl); - - // check execution - execTestTx(CTransaction(rawTx), targetHeight); - - return signsend(rawTx, pwallet, {})->GetHash().GetHex(); - } else { - if (request.params[2].isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "BTC source address must be provided for " + contractPair->first); - } - - CTxDestination dest = DecodeDestination(request.params[2].get_str()); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); - } - const auto script = GetScriptForDestination(dest); - - CSmartContractMessage msg{}; - msg.name = contractPair->first; - msg.accounts = {{script, {{{amount.nTokenId, amount.nValue}}}}}; - - // encode - CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::SmartContract) - << msg; - CScript scriptMeta; - scriptMeta << OP_RETURN << ToByteVector(metadata); - - int targetHeight = chainHeight(*pwallet->chain().lock()) + 1; - - const auto txVersion = GetTransactionVersion(targetHeight); - CMutableTransaction rawTx(txVersion); - - rawTx.vout.emplace_back(0, scriptMeta); - - CTransactionRef optAuthTx; - std::set auth{script}; - rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auth, false, optAuthTx, request.params[3]); - - // Set change address - CCoinControl coinControl; - coinControl.destChange = dest; - - // fund - fund(rawTx, pwallet, optAuthTx, &coinControl); - - // check execution - execTestTx(CTransaction(rawTx), targetHeight, optAuthTx); - - return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); - } + return HandleSendDFIP2201(request, std::move(pwallet)); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Specified smart contract not found"); } - return NullUniValue; } diff --git a/test/functional/feature_loan_estimatecollateral.py b/test/functional/feature_loan_estimatecollateral.py old mode 100644 new mode 100755 diff --git a/test/functional/feature_loan_estimateloan.py b/test/functional/feature_loan_estimateloan.py old mode 100644 new mode 100755 diff --git a/test/functional/feature_loan_interest.py b/test/functional/feature_loan_interest.py old mode 100644 new mode 100755 diff --git a/test/functional/feature_loan_vaultstate.py b/test/functional/feature_loan_vaultstate.py old mode 100644 new mode 100755 diff --git a/test/functional/feature_smart_contracts.py b/test/functional/feature_smart_contracts.py old mode 100644 new mode 100755 diff --git a/test/functional/rpc_listvaulthistory.py b/test/functional/rpc_listvaulthistory.py old mode 100644 new mode 100755 diff --git a/test/functional/rpc_mn_basic.py b/test/functional/rpc_mn_basic.py old mode 100644 new mode 100755