From 262202e04d27978c0fbb7a050c7c8c0aab1a9d44 Mon Sep 17 00:00:00 2001 From: Mihailo Milenkovic Date: Fri, 9 Jun 2023 09:14:09 +0200 Subject: [PATCH] Transferdomain changes to master (#2035) * Transferdomain and dependencies. * Fix rpc command * Minor refinements * Use reference wrapper * Cleanup EthAuth * Cleanup auth, add auth strategy * Hoist IsSpent check out * Fix univalue stack location * Move error strings into DeFiErrors * Rename CTransferDomain and change add/sub calls --------- Co-authored-by: Prasanna Loganathar Co-authored-by: Shoham Chakraborty Co-authored-by: Peter Bushnell --- .dockerignore | 11 +- .gitignore | 11 +- src/amount.h | 2 +- src/masternodes/balances.h | 42 ++++-- src/masternodes/errors.h | 48 ++++++ src/masternodes/evm.h | 2 +- src/masternodes/ffi_temp_stub.h | 4 +- src/masternodes/mn_checks.cpp | 242 ++++++++++++++++++------------- src/masternodes/mn_checks.h | 18 ++- src/masternodes/mn_rpc.cpp | 28 ++-- src/masternodes/mn_rpc.h | 2 +- src/masternodes/rpc_accounts.cpp | 172 +++++++++++++--------- src/masternodes/rpc_customtx.cpp | 32 +++- src/masternodes/rpc_evm.cpp | 6 +- src/masternodes/validation.cpp | 31 ++++ src/rpc/client.cpp | 1 + 16 files changed, 437 insertions(+), 215 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3dca3c2ddff..08b61ae038d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ .code +.cache .idea # Editors. @@ -12,11 +13,12 @@ # Build build/ +build.*/ out/ *.exe *.pdb -# Univalue +# Univalue src/defi src/defid @@ -141,7 +143,6 @@ contrib/devtools/split-debug.sh # CI scratch area used for tests /ci/scratch/ -# Potential rust paths to keep things clean -# as we switch between branches -/src/rust/target -/lib/target \ No newline at end of file +# Rust local artifacts +/lib/target +*.bin \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3dca3c2ddff..08b61ae038d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .code +.cache .idea # Editors. @@ -12,11 +13,12 @@ # Build build/ +build.*/ out/ *.exe *.pdb -# Univalue +# Univalue src/defi src/defid @@ -141,7 +143,6 @@ contrib/devtools/split-debug.sh # CI scratch area used for tests /ci/scratch/ -# Potential rust paths to keep things clean -# as we switch between branches -/src/rust/target -/lib/target \ No newline at end of file +# Rust local artifacts +/lib/target +*.bin \ No newline at end of file diff --git a/src/amount.h b/src/amount.h index 07d553b0ef0..868de715fa7 100644 --- a/src/amount.h +++ b/src/amount.h @@ -78,7 +78,7 @@ struct DCT_ID { static constexpr CAmount COIN = 100000000; static constexpr CAmount CENT = 1000000; static constexpr int64_t WEI_IN_GWEI = 1000000000; -static constexpr int64_t CAMOUNT_TO_WEI = 10; +static constexpr int64_t CAMOUNT_TO_GWEI = 10; //Converts the given value to decimal format string with COIN precision. inline std::string GetDecimalString(CAmount nValue) diff --git a/src/masternodes/balances.h b/src/masternodes/balances.h index fd5a619ac54..00014ef9ce4 100644 --- a/src/masternodes/balances.h +++ b/src/masternodes/balances.h @@ -208,24 +208,44 @@ struct CUtxosToAccountMessage { } }; -enum CTransferBalanceType : uint8_t { - AccountToAccount = 0x00, - EvmIn = 0x01, - EvmOut = 0x02, +enum VMDomain : uint8_t { + NONE = 0x00, + // UTXO Reserved + UTXO = 0x01, + DVM = 0x02, + EVM = 0x03, }; -struct CTransferBalanceMessage { - uint8_t type; - CAccounts from; // from -> balances - CAccounts to; // to -> balances +struct CTransferDomainItem +{ + CScript address; + CTokenAmount amount; + uint8_t domain; + + // Optional data that'll vary based on the domain. + // EVMData, UTXOData with inputs, etc. + // Currently, unused. + std::vector data; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { - READWRITE(type); - READWRITE(from); - READWRITE(to); + READWRITE(address); + READWRITE(amount); + READWRITE(domain); + READWRITE(data); + } +}; + +struct CTransferDomainMessage { + std::vector> transfers; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(transfers); } }; diff --git a/src/masternodes/errors.h b/src/masternodes/errors.h index ceb19b5cd60..e43080a4d42 100644 --- a/src/masternodes/errors.h +++ b/src/masternodes/errors.h @@ -393,6 +393,54 @@ class DeFiErrors { static Res AccountsFuturesErase() { return Res::Err("Failed to erase futures"); } + + static Res TransferDomainNotEnoughBalance(const std::string address) { + return Res::Err("Not enough balance in %s to cover \"EVM\" domain transfer", address); + } + + static Res InvalidAuth() { + return Res::Err("tx must have at least one input from account owner"); + } + + static Res TransferDomainEVMNotEnabled() { + return Res::Err("Cannot create tx, EVM is not enabled"); + } + + static Res TransferDomainSameDomain() { + return Res::Err("Cannot transfer inside same domain"); + } + + static Res TransferDomainUnequalAmount() { + return Res::Err("Source amount must be equal to destination amount"); + } + + static Res TransferDomainIncorrectToken() { + return Res::Err("For transferdomain, only DFI token is currently supported"); + } + + static Res TransferDomainETHSourceAddress() { + return Res::Err("Src address must not be an ETH address in case of \"DVM\" domain"); + } + + static Res TransferDomainDFISourceAddress() { + return Res::Err("Src address must be an ETH address in case of \"EVM\" domain"); + } + + static Res TransferDomainInvalidSourceDomain() { + return Res::Err("Invalid domain set for \"src\" argument"); + } + + static Res TransferDomainETHDestinationAddress() { + return Res::Err("Dst address must not be an ETH address in case of \"DVM\" domain"); + } + + static Res TransferDomainDVMDestinationAddress() { + return Res::Err("Dst address must be an ETH address in case of \"EVM\" domain"); + } + + static Res TransferDomainInvalidDestinationDomain() { + return Res::Err("Invalid domain set for \"dst\" argument"); + } }; #endif // DEFI_MASTERNODES_ERRORS_H diff --git a/src/masternodes/evm.h b/src/masternodes/evm.h index c4cdf0deb6b..104bf94165b 100644 --- a/src/masternodes/evm.h +++ b/src/masternodes/evm.h @@ -13,7 +13,7 @@ constexpr const uint16_t EVM_TX_SIZE = 32768; using CRawEvmTx = TBytes; -extern std::string CTransferBalanceTypeToString(const CTransferBalanceType type); +extern std::string CTransferDomainToString(const VMDomain domain); struct CEvmTxMessage { CRawEvmTx evmTx; diff --git a/src/masternodes/ffi_temp_stub.h b/src/masternodes/ffi_temp_stub.h index ccf316dca1a..034da40030a 100644 --- a/src/masternodes/ffi_temp_stub.h +++ b/src/masternodes/ffi_temp_stub.h @@ -44,9 +44,9 @@ inline uint64_t evm_get_balance(::rust::Str address) { return {}; } -inline void evm_add_balance(::std::uint64_t context, ::rust::Str address, ::std::array<::std::uint8_t, 32> amount) {} +inline void evm_add_balance(::std::uint64_t context, ::rust::Str address, ::std::array<::std::uint8_t, 32> amount, ::std::array<::std::uint8_t, 32> native_tx_hash) {} -inline bool evm_sub_balance(::std::uint64_t context, ::rust::Str address, ::std::array<::std::uint8_t, 32> amount) { +inline bool evm_sub_balance(::std::uint64_t context, ::rust::Str address, ::std::array<::std::uint8_t, 32> amount, ::std::array<::std::uint8_t, 32> native_tx_hash) { return {}; } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 81182dc7afb..c976523fcb4 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -142,8 +142,8 @@ std::string ToString(CustomTxType type) { return "Vote"; case CustomTxType::UnsetGovVariable: return "UnsetGovVariable"; - case CustomTxType::TransferBalance: - return "TransferBalance"; + case CustomTxType::TransferDomain: + return "TransferDomain"; case CustomTxType::EvmTx: return "EvmTx"; case CustomTxType::None: @@ -303,8 +303,8 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) { return CCustomTxMessageNone{}; case CustomTxType::UnsetGovVariable: return CGovernanceUnsetMessage{}; - case CustomTxType::TransferBalance: - return CTransferBalanceMessage{}; + case CustomTxType::TransferDomain: + return CTransferDomainMessage{}; case CustomTxType::EvmTx: return CEvmTxMessage{}; case CustomTxType::None: @@ -435,7 +435,7 @@ class CCustomMetadataParseVisitor { CGovernanceUnsetMessage>()) return IsHardforkEnabled(consensus.GrandCentralHeight); else if constexpr (IsOneOf()) return IsHardforkEnabled(consensus.NextNetworkUpgradeHeight); else if constexpr (IsOneOfbalances.count(loanTokenId)) + if (!loanAmounts->balances.count(loanTokenId)) return DeFiErrors::LoanInvalidTokenForSymbol(loanToken->symbol); const auto ¤tLoanAmount = loanAmounts->balances.at(loanTokenId); @@ -3805,102 +3800,48 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { return mnview.AddProposalVote(obj.propId, obj.masternodeId, vote); } - Res operator()(const CTransferBalanceMessage &obj) const { - if (!IsEVMEnabled(height, mnview)) { - return Res::Err("Cannot create tx, EVM is not enabled"); + Res operator()(const CTransferDomainMessage &obj) const { + auto res = ValidateTransferDomain(tx, height, coins, mnview, consensus, obj); + if (!res) { + return res; } - // owner auth - auto res = Res::Ok(); - if (obj.type != CTransferBalanceType::EvmOut) - for (const auto &kv : obj.from) { - res = HasAuth(kv.first); + // Iterate over array of transfers + for (const auto &[src, dst] : obj.transfers) { + if (src.domain == VMDomain::DVM) { + // Subtract balance from DFI address + CBalances balance; + balance.Add(src.amount); + res = mnview.SubBalances(src.address, balance); if (!res) return res; - } - - // compare - const auto sumFrom = SumAllTransfers(obj.from); - const auto sumTo = SumAllTransfers(obj.to); - - if (sumFrom != sumTo) - return Res::Err("sum of inputs (from) != sum of outputs (to)"); - - if (obj.type == CTransferBalanceType::AccountToAccount) { - res = SubBalancesDelShares(obj.from); - if (!res) - return res; - res = AddBalancesSetShares(obj.to); - if (!res) - return res; - } else if (obj.type == CTransferBalanceType::EvmIn) { - res = SubBalancesDelShares(obj.from); - if (!res) - return res; - - for (const auto& [addr, balances] : obj.to) { + } else if (src.domain == VMDomain::EVM) { + // Subtract balance from ETH address CTxDestination dest; - if (ExtractDestination(addr, dest)) { - if (dest.index() != WitV16KeyEthHashType) { - return Res::Err("To address must be an ETH address in case of \"evmin\" transfertype"); - } - } - - const auto toAddress = std::get(dest); - - for (const auto& [id, amount] : balances.balances) { - if (id != DCT_ID{0}) { - return Res::Err("For EVM in transfers, only DFI token is currently supported"); - } - - arith_uint256 balanceIn = amount; - balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI; - evm_add_balance(evmContext, HexStr(toAddress.begin(), toAddress.end()), ArithToUint256(balanceIn).ToArrayReversed()); + ExtractDestination(src.address, dest); + const auto fromAddress = std::get(dest); + arith_uint256 balanceIn = src.amount.nValue; + balanceIn *= CAMOUNT_TO_GWEI * WEI_IN_GWEI; + if (!evm_sub_balance(evmContext, HexStr(fromAddress.begin(), fromAddress.end()), ArithToUint256(balanceIn).ToArrayReversed(), tx.GetHash().ToArrayReversed())) { + return DeFiErrors::TransferDomainNotEnoughBalance(EncodeDestination(dest)); } } - } else if (obj.type == CTransferBalanceType::EvmOut) { - - for (const auto& [addr, balances] : obj.from) { + if (dst.domain == VMDomain::DVM) { + // Add balance to DFI address + CBalances balance; + balance.Add(dst.amount); + res = mnview.AddBalances(dst.address, balance); + if (!res) + return res; + } else if (dst.domain == VMDomain::EVM) { + // Add balance to ETH address CTxDestination dest; - if (ExtractDestination(addr, dest)) { - if (dest.index() != WitV16KeyEthHashType) { - return Res::Err("From address must be an ETH address in case of \"evmout\" transfertype"); - } - } - bool foundAuth = false; - for (const auto &input : tx.vin) { - const Coin &coin = coins.AccessCoin(input.prevout); - std::vector vRet; - if (Solver(coin.out.scriptPubKey, vRet) == txnouttype::TX_PUBKEYHASH) - { - auto it = input.scriptSig.begin(); - CPubKey pubkey(input.scriptSig.begin() + *it + 2, input.scriptSig.end()); - auto script = GetScriptForDestination(WitnessV16EthHash(pubkey)); - if (script == addr) - foundAuth = true; - } - } - if (!foundAuth) - return Res::Err("authorization not found for %s in the tx", ScriptToString(addr)); - - const auto fromAddress = std::get(dest); - - for (const auto& [id, amount] : balances.balances) { - if (id != DCT_ID{0}) { - return Res::Err("For EVM out transfers, only DFI token is currently supported"); - } - - arith_uint256 balanceIn = amount; - balanceIn *= CAMOUNT_TO_WEI * WEI_IN_GWEI; - if (!evm_sub_balance(evmContext, HexStr(fromAddress.begin(), fromAddress.end()), ArithToUint256(balanceIn).ToArrayReversed())) { - return Res::Err("Not enough balance in %s to cover EVM out", EncodeDestination(dest)); - } - } + ExtractDestination(dst.address, dest); + const auto toAddress = std::get(dest); + arith_uint256 balanceIn = dst.amount.nValue; + balanceIn *= CAMOUNT_TO_GWEI * WEI_IN_GWEI; + evm_add_balance(evmContext, HexStr(toAddress.begin(), toAddress.end()), ArithToUint256(balanceIn).ToArrayReversed(), tx.GetHash().ToArrayReversed()); } - - res = AddBalancesSetShares(obj.to); - if (!res) - return res; } return res; @@ -3928,6 +3869,109 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { Res operator()(const CCustomTxMessageNone &) const { return Res::Ok(); } }; +Res HasAuth(const CTransaction &tx, const CCoinsViewCache &coins, const CScript &auth, AuthStrategy strategy) { + for (const auto &input : tx.vin) { + const Coin &coin = coins.AccessCoin(input.prevout); + if (coin.IsSpent()) continue; + if (strategy == AuthStrategy::DirectPubKeyMatch) { + if (coin.out.scriptPubKey == auth) { + return Res::Ok(); + } + } else if (strategy == AuthStrategy::EthKeyMatch) { + std::vector vRet; + if (Solver(coin.out.scriptPubKey, vRet) == txnouttype::TX_PUBKEYHASH) + { + auto it = input.scriptSig.begin(); + CPubKey pubkey(input.scriptSig.begin() + *it + 2, input.scriptSig.end()); + auto script = GetScriptForDestination(WitnessV16EthHash(pubkey)); + if (script == auth) + return Res::Ok(); + } + } + } + return DeFiErrors::InvalidAuth(); +} + +Res ValidateTransferDomain(const CTransaction &tx, + uint32_t height, + const CCoinsViewCache &coins, + CCustomCSView &mnview, + const Consensus::Params &consensus, + const CTransferDomainMessage &obj) +{ + auto res = Res::Ok(); + + // Check if EVM feature is active + if (!IsEVMEnabled(height, mnview)) { + return DeFiErrors::TransferDomainEVMNotEnabled(); + } + + // Iterate over array of transfers + for (const auto &[src, dst] : obj.transfers) { + // Reject if transfer is within same domain + if (src.domain == dst.domain) + return DeFiErrors::TransferDomainSameDomain(); + + // Check for amounts out equals amounts in + if (src.amount.nValue != dst.amount.nValue) + return DeFiErrors::TransferDomainUnequalAmount(); + + // Restrict only for use with DFI token for now + if (src.amount.nTokenId != DCT_ID{0} || dst.amount.nTokenId != DCT_ID{0}) + return DeFiErrors::TransferDomainIncorrectToken(); + + CTxDestination dest; + + // Soruce validation + // Check domain type + if (src.domain == VMDomain::DVM) { + // Reject if source address is ETH address + if (ExtractDestination(src.address, dest)) { + if (dest.index() == WitV16KeyEthHashType) { + return DeFiErrors::TransferDomainETHSourceAddress(); + } + } + // Check for authorization on source address + res = HasAuth(tx, coins, src.address); + if (!res) + return res; + } else if (src.domain == VMDomain::EVM) { + // Reject if source address is DFI address + if (ExtractDestination(src.address, dest)) { + if (dest.index() != WitV16KeyEthHashType) { + return DeFiErrors::TransferDomainDFISourceAddress(); + } + } + // Check for authorization on source address + res = HasAuth(tx, coins, src.address, AuthStrategy::EthKeyMatch); + if (!res) + return res; + } else + return DeFiErrors::TransferDomainInvalidSourceDomain(); + + // Destination validation + // Check domain type + if (dst.domain == VMDomain::DVM) { + // Reject if source address is ETH address + if (ExtractDestination(dst.address, dest)) { + if (dest.index() == WitV16KeyEthHashType) { + return DeFiErrors::TransferDomainETHDestinationAddress(); + } + } + } else if (dst.domain == VMDomain::EVM) { + // Reject if source address is DFI address + if (ExtractDestination(dst.address, dest)) { + if (dest.index() != WitV16KeyEthHashType) { + return DeFiErrors::TransferDomainDVMDestinationAddress(); + } + } + } else + return DeFiErrors::TransferDomainInvalidDestinationDomain(); + } + + return res; +} + Res CustomMetadataParse(uint32_t height, const Consensus::Params &consensus, const std::vector &metadata, diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index e9c3df4e22b..ef2301f3ef6 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -61,6 +61,11 @@ class CCustomTxVisitor { Res IsOnChainGovernanceEnabled() const; }; +enum AuthStrategy: uint32_t { + DirectPubKeyMatch = 0, + EthKeyMatch = 1, +}; + constexpr uint8_t MAX_POOL_SWAPS = 3; enum CustomTxErrCodes : uint32_t { @@ -144,7 +149,7 @@ enum class CustomTxType : uint8_t { ProposalFeeRedistribution = 'Y', UnsetGovVariable = 'Z', // EVM - TransferBalance = '8', + TransferDomain = '8', EvmTx = '9', }; @@ -210,7 +215,7 @@ inline CustomTxType CustomTxCodeToType(uint8_t ch) { case CustomTxType::Vote: case CustomTxType::CreateVoc: case CustomTxType::UnsetGovVariable: - case CustomTxType::TransferBalance: + case CustomTxType::TransferDomain: case CustomTxType::EvmTx: case CustomTxType::None: return type; @@ -450,7 +455,7 @@ using CCustomTxMessage = std::variant; CCustomTxMessage customTypeToMessage(CustomTxType txType); @@ -510,6 +515,13 @@ Res SwapToDFIorDUSD(CCustomCSView &mnview, Res storeGovVars(const CGovernanceHeightMessage &obj, CCustomCSView &view); bool IsTestNetwork(); bool IsEVMEnabled(const int height, const CCustomCSView &view); +Res HasAuth(const CTransaction &tx, const CCoinsViewCache &coins, const CScript &auth, AuthStrategy strategy = AuthStrategy::DirectPubKeyMatch); +Res ValidateTransferDomain(const CTransaction &tx, + uint32_t height, + const CCoinsViewCache &coins, + CCustomCSView &mnview, + const Consensus::Params &consensus, + const CTransferDomainMessage &obj); inline bool OraclePriceFeed(CCustomCSView &view, const CTokenCurrencyPair &priceFeed) { // Allow hard coded DUSD/USD diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index da3662cb766..91327633541 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -302,7 +302,7 @@ static std::optional GetAuthInputOnly(CWalletCoinsUnlocker& pwallet, CTxD auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, locked_chain->mutex()); - + // Note, for auth, we call this with 1 as max count, so should early exit pwallet->AvailableCoins(*locked_chain, vecOutputs, true, &cctl, 1, MAX_MONEY, MAX_MONEY, 1, coinSelectOpts); @@ -316,7 +316,7 @@ static std::optional GetAuthInputOnly(CWalletCoinsUnlocker& pwallet, CTxD return txin; } -static CTransactionRef CreateAuthTx(CWalletCoinsUnlocker& pwallet, +static CTransactionRef CreateAuthTx(CWalletCoinsUnlocker& pwallet, std::set const & auths, int32_t txVersion, const CoinSelectionOptions &coinSelectOpts) { @@ -404,8 +404,8 @@ std::vector GetAuthInputsSmart(CWalletCoinsUnlocker& pwallet, int32_t txV throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Incorrect authorization for " + EncodeDestination(destination)); } auto authInput = GetAuthInputOnly( - pwallet, - destination, + pwallet, + destination, coinSelectOpts); if (authInput) { result.push_back(authInput.value()); @@ -501,14 +501,16 @@ std::optional GetFuturesBlock(const uint32_t typeId) return FutureSwapHeightInfo{attributes->GetValue(startKey, CAmount{}), attributes->GetValue(blockKey, CAmount{})}; } -std::string CTransferBalanceTypeToString(const CTransferBalanceType type) { - switch (type) { - case CTransferBalanceType::AccountToAccount: - return "AccountToAccount"; - case CTransferBalanceType::EvmIn: - return "EvmIn"; - case CTransferBalanceType::EvmOut: - return "EvmOut"; +std::string CTransferDomainToString(const VMDomain domain) { + switch (domain) { + case VMDomain::NONE: + return "NONE"; + case VMDomain::UTXO: + return "UTXO"; + case VMDomain::DVM: + return "DVM"; + case VMDomain::EVM: + return "EVM"; } return "Unknown"; } @@ -568,7 +570,7 @@ UniValue setgov(const JSONRPCRequest& request) { if (!attributes) { throw JSONRPCError(RPC_INVALID_REQUEST, "Failed to convert Gov var to attributes"); } - + LOCK(cs_main); const auto attrMap = attributes->GetAttributesMap(); for (const auto& [key, value] : attrMap) { diff --git a/src/masternodes/mn_rpc.h b/src/masternodes/mn_rpc.h index f6021d959b7..5f1d3e2062f 100644 --- a/src/masternodes/mn_rpc.h +++ b/src/masternodes/mn_rpc.h @@ -95,5 +95,5 @@ CScript CreateScriptForHTLC(const JSONRPCRequest &request, uint32_t &blocks, std CPubKey PublickeyFromString(const std::string &pubkey); std::optional AmIFounder(CWallet *const pwallet); std::optional GetFuturesBlock(const uint32_t typeId); -std::string CTransferBalanceTypeToString(const CTransferBalanceType type); +std::string CTransferDomainToString(const VMDomain domain); #endif // DEFI_MASTERNODES_MN_RPC_H diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 0c1690090c1..22ed931a0ba 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -1951,83 +1951,133 @@ UniValue sendtokenstoaddress(const JSONRPCRequest& request) { return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } -UniValue transferbalance(const JSONRPCRequest& request) { +UniValue transferdomain(const JSONRPCRequest& request) { auto pwallet = GetWallet(request); - RPCHelpMan{"transferbalance", - "Creates (and submits to local node and network) a tx to transfer balance from DFI/ETH address to DFI/ETH address.\n" + - HelpRequiringPassphrase(pwallet) + "\n", - { - {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of transfer: evmin/evmout."}, - {"from", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", + RPCHelpMan{"transferdomain", + "Creates (and submits to local node and network) a tx to transfer balance from DFI/ETH address to DFI/ETH address.\n" + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"array", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of src and dst json objects", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The source defi address is the key, the value is amount in amount@token format. " - "If multiple tokens are to be transferred, specify an array [\"amount1@t1\", \"amount2@t2\"]"}, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"src", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "Source arguments", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "Source address"}, + {"amount", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount transfered, the value is amount in amount@token format"}, + {"domain", RPCArg::Type::NUM, RPCArg::Optional::NO, "Domain of source: 1 - DVM, 2 - EVM"}, + {"data", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Optional data"}, + }, + }, + {"dst", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "Destination arguments", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "Destination address"}, + {"amount", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount transfered, the value is amount in amount@token format"}, + {"domain", RPCArg::Type::NUM, RPCArg::Optional::NO, "Domain of source: 1 - DVM, 2 - EVM"}, + {"data", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Optional data"}, + }, + } + }, + }, }, - }, - {"to", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The defi address is the key, the value is amount in amount@token format. " - "If multiple tokens are to be transferred, specify an array [\"amount1@t1\", \"amount2@t2\"]"}, + }, + }, + RPCResult{ + "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" + }, + RPCExamples{ + HelpExampleCli("transferdomain", R"('[{"src":{"address":, "amount\":"1.0@DFI", "domain": 1}, {"dst":{"address":, "amount":"1.0@DFI", "domain": 2}}]')") + + HelpExampleCli("transferdomain", R"('[{"src":{"address":, "amount\":"1.0@DFI", "domain": 2}, {"dst":{"address":, "amount":"1.0@DFI", "domain": 1}}]')") }, - }, - }, - RPCResult{ - "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" - }, - RPCExamples{ - HelpExampleCli("transferbalance", R"(acctoacc '{\"\":\"1.0@DFI\"}' '{\"\":\"1.0@DFI\"}')") - }, }.Check(request); if (pwallet->chain().isInitialBlockDownload()) - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot transferbalance while still in Initial Block Download"); + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot transferdomain while still in Initial Block Download"); pwallet->BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VOBJ}, false); + RPCTypeCheck(request.params, {UniValue::VARR}, false); - int targetHeight; - { - LOCK(cs_main); - targetHeight = ::ChainActive().Height() + 1; - } + UniValue srcDstArray(UniValue::VARR); - CTransferBalanceMessage msg; + srcDstArray = request.params[0].get_array(); + + CTransferDomainMessage msg; + std::set auths; try { - if (!request.params[0].isNull()){ - auto type = request.params[0].getValStr(); - msg.type = CTransferBalanceType::AccountToAccount; - - if (type == "evmin") - msg.type = CTransferBalanceType::EvmIn; - else if (type == "evmout") - msg.type = CTransferBalanceType::EvmOut; - } else - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"type\" must not be null"); - - if (!request.params[1].isNull()) - msg.from = DecodeRecipients(pwallet->chain(), request.params[1].get_obj()); - else - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); + for (unsigned int i=0; i < srcDstArray.size(); i++) { + const UniValue& elem = srcDstArray[i]; + RPCTypeCheck(elem, {UniValue::VOBJ, UniValue::VOBJ}, false); + + const UniValue& srcObj = elem["src"].get_obj(); + const UniValue& dstObj = elem["dst"].get_obj(); + + CTransferDomainItem src, dst; + + if (!srcObj["address"].isNull()) + src.address = DecodeScript(srcObj["address"].getValStr()); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, src argument \"address\" must not be null"); + + if (!srcObj["amount"].isNull()) + src.amount = DecodeAmount(pwallet->chain(), srcObj["amount"], ""); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, src argument \"amount\" must not be null"); + + if (!srcObj["domain"].isNull()) + src.domain = static_cast(srcObj["domain"].get_int()); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, src argument \"domain\" must not be null"); + + if (src.domain == VMDomain::DVM) { + auths.insert(src.address); + } else if (src.domain == VMDomain::EVM) { + const auto key = AddrToPubKey(pwallet, ScriptToString(src.address)); + const auto auth = GetScriptForDestination(PKHash(key.GetID())); + auths.insert(auth); + } else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, src argument \"domain\" must be either 1 (DFI token to EVM) or 2 (EVM to DFI token)"); + + if (!srcObj["data"].isNull()) + src.data.assign(srcObj["data"].getValStr().begin(), srcObj["data"].getValStr().end()); + + if (!dstObj["address"].isNull()) + dst.address = DecodeScript(dstObj["address"].getValStr()); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, dst argument \"address\" must not be null"); + + if (!dstObj["amount"].isNull()) + dst.amount = DecodeAmount(pwallet->chain(), dstObj["amount"], ""); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, dst argument \"amount\" must not be null"); + + if (!dstObj["domain"].isNull()) + dst.domain = static_cast(dstObj["domain"].get_int()); + else + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, dst argument \"domain\" must not be null"); + + if (!dstObj["data"].isNull()) + dst.data.assign(dstObj["data"].getValStr().begin(), dstObj["data"].getValStr().end()); + + msg.transfers.push_back({src, dst}); - if (!request.params[2].isNull()){ - const auto recipients = DecodeRecipientsGetRecipients(request.params[2].get_obj()); - const auto accounts = DecodeRecipients(pwallet->chain(), recipients); - msg.to = accounts; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"to\" must not be null"); } } catch(std::runtime_error& e) { throw JSONRPCError(RPC_INVALID_PARAMETER, e.what()); } - CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); + int targetHeight; + { + LOCK(cs_main); + targetHeight = ::ChainActive().Height() + 1; + } - metadata << static_cast(CustomTxType::TransferBalance) - << msg; + CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); + metadata << static_cast(CustomTxType::TransferDomain) + << msg; CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); @@ -2036,19 +2086,6 @@ UniValue transferbalance(const JSONRPCRequest& request) { CMutableTransaction rawTx(txVersion); CTransactionRef optAuthTx; - std::set auths; - if (msg.type != CTransferBalanceType::EvmOut) { - for(auto& address : msg.from){ - auths.insert(address.first); - } - } else { - for(auto& address : msg.from) { - const auto key = AddrToPubKey(pwallet, ScriptToString(address.first)); - const auto auth = GetScriptForDestination(PKHash(key.GetID())); - auths.insert(auth); - } - } - UniValue txInputs(UniValue::VARR); rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false, optAuthTx, txInputs); @@ -2909,6 +2946,7 @@ static const CRPCCommand commands[] = {"accounts", "accounthistorycount", &accounthistorycount, {"owner", "options"}}, {"accounts", "listcommunitybalances", &listcommunitybalances, {}}, {"accounts", "sendtokenstoaddress", &sendtokenstoaddress, {"from", "to", "selectionMode"}}, + {"accounts", "transferdomain", &transferdomain, {"array"}}, {"accounts", "getburninfo", &getburninfo, {}}, {"accounts", "executesmartcontract", &executesmartcontract, {"name", "amount", "inputs"}}, {"accounts", "futureswap", &futureswap, {"address", "amount", "destination", "inputs"}}, diff --git a/src/masternodes/rpc_customtx.cpp b/src/masternodes/rpc_customtx.cpp index 175c2c90bc7..09e7ada520d 100644 --- a/src/masternodes/rpc_customtx.cpp +++ b/src/masternodes/rpc_customtx.cpp @@ -546,10 +546,34 @@ class CCustomTxRpcVisitor { rpcInfo.pushKV("vote", CProposalVoteToString(vote)); } - void operator()(const CTransferBalanceMessage &obj) const { - rpcInfo.pushKV("type", CTransferBalanceTypeToString(static_cast(obj.type))); - rpcInfo.pushKV("from", accountsInfo(obj.from)); - rpcInfo.pushKV("to", accountsInfo(obj.to)); + void operator()(const CTransferDomainMessage &obj) const { + UniValue array{UniValue::VARR}; + + for (const auto &[src, dst] : obj.transfers) { + UniValue srcJson{UniValue::VOBJ}; + UniValue dstJson{UniValue::VOBJ}; + std::array, + 2> items { + std::make_pair(std::ref(srcJson), src), + std::make_pair(std::ref(dstJson), dst) + }; + + for (auto &[j, o]: items) { + j.pushKV("address", ScriptToString(o.address)); + j.pushKV("amount", o.amount.ToString()); + j.pushKV("domain", CTransferDomainToString(static_cast(o.domain))); + if (!o.data.empty()) { + j.pushKV("data", std::string(o.data.begin(), o.data.end())); + } + } + + UniValue elem{UniValue::VOBJ}; + elem.pushKV("src", srcJson); + elem.pushKV("dst", dstJson); + array.push_back(elem); + } + + rpcInfo.pushKV("transfers", array); } void operator()(const CEvmTxMessage &obj) const { diff --git a/src/masternodes/rpc_evm.cpp b/src/masternodes/rpc_evm.cpp index 6b295c31b29..d7d3c44adec 100644 --- a/src/masternodes/rpc_evm.cpp +++ b/src/masternodes/rpc_evm.cpp @@ -76,7 +76,7 @@ UniValue evmtx(const JSONRPCRequest& request) { } const arith_uint256 valueParam = AmountFromValue(request.params[5]); - const auto value = ArithToUint256(valueParam * CAMOUNT_TO_WEI * WEI_IN_GWEI); + const auto value = ArithToUint256(valueParam * CAMOUNT_TO_GWEI * WEI_IN_GWEI); rust::Vec input{}; if (!request.params[6].isNull()) { @@ -94,6 +94,7 @@ UniValue evmtx(const JSONRPCRequest& request) { std::copy(key.begin(), key.end(), privKey.begin()); const auto signedTx = create_and_sign_tx(CreateTransactionContext{chainID, nonce.ToArrayReversed(), gasPrice.ToArrayReversed(), gasLimit.ToArrayReversed(), to, value.ToArrayReversed(), input, privKey}); + std::vector evmTx(signedTx.size()); std::copy(signedTx.begin(), signedTx.end(), evmTx.begin()); @@ -124,11 +125,10 @@ static const CRPCCommand commands[] = { // category name actor (function) params // --------------- ---------------------- --------------------- ---------- - {"evm", "evmtx", &evmtx, {"rawEvmTx", "inputs"}}, + {"evm", "evmtx", &evmtx, {"from", "nonce", "gasPrice", "gasLimit", "to", "value", "data"}}, }; void RegisterEVMRPCCommands(CRPCTable& tableRPC) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); } - diff --git a/src/masternodes/validation.cpp b/src/masternodes/validation.cpp index ab65e70b49b..90e110576d4 100644 --- a/src/masternodes/validation.cpp +++ b/src/masternodes/validation.cpp @@ -2352,6 +2352,37 @@ static void ProcessGrandCentralEvents(const CBlockIndex* pindex, CCustomCSView& cache.SetVariable(*attributes); } +static void RevertTransferDomain(const CTransferDomainMessage &obj, CCustomCSView &mnview) { + // NOTE: Each domain's revert is handle by it's own domain module. This function reverts only the DVM aspect. EVM will handle it's own revert. + for (const auto &[src, dst] : obj.transfers) { + if (src.domain == VMDomain::DVM) + mnview.AddBalance(src.address, src.amount); + if (dst.domain == VMDomain::DVM) + mnview.SubBalance(dst.address, dst.amount); + } +} + +static void RevertFailedTransferDomainTxs(const std::vector &failedTransactions, const CBlock& block, const Consensus::Params &consensus, const int height, CCustomCSView &mnview) { + std::set potentialTxsToUndo; + for (const auto &txStr : failedTransactions) { + potentialTxsToUndo.insert(uint256S(txStr)); + } + + std::set txsToUndo; + for (const auto &tx : block.vtx) { + if (tx && potentialTxsToUndo.count(tx->GetHash())) { + std::vector metadata; + const auto txType = GuessCustomTxType(*tx, metadata, false); + if (txType == CustomTxType::TransferDomain) { + auto txMessage = customTypeToMessage(txType); + assert(CustomMetadataParse(height, consensus, metadata, txMessage)); + auto obj = std::get(txMessage); + RevertTransferDomain(obj, mnview); + } + } + } +} + void ProcessDeFiEvent(const CBlock &block, const CBlockIndex* pindex, CCustomCSView& mnview, const CCoinsViewCache& view, const CChainParams& chainparams, const CreationTxs &creationTxs) { CCustomCSView cache(mnview); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c8bb06b64d7..37b4264db96 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -324,6 +324,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "isappliedcustomtx", 1, "blockHeight" }, { "sendtokenstoaddress", 0, "from" }, { "sendtokenstoaddress", 1, "to" }, + { "transferdomain", 0, "array" }, { "getanchorteams", 0, "blockHeight" }, { "getactivemasternodecount", 0, "blockCount" }, { "appointoracle", 1, "pricefeeds" },