diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 70a76e5091a..e688629cfa9 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -285,7 +285,7 @@ class ChainImpl : public Chain LOCK(cs_main); return pcustomcsview->GetMasternode(nodeId); } - std::unique_ptr existTokenGuessId(const std::string & str, DCT_ID & id) const override + boost::optional existTokenGuessId(const std::string & str, DCT_ID & id) const override { LOCK(cs_main); return pcustomcsview->GetTokenGuessId(str, id); diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 8c904912bd8..55a9b6f96b8 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -20,7 +20,7 @@ class CFeeRate; class CMasternode; class CRPCCommand; class CScheduler; -class CToken; +class CTokenImplementation; class CValidationState; class Coin; class uint256; @@ -152,7 +152,7 @@ class Chain virtual bool mnCanSpend(const uint256 & nodeId, int height) const = 0; virtual boost::optional mnExists(const uint256 & nodeId) const = 0; - virtual std::unique_ptr existTokenGuessId(const std::string & str, DCT_ID & id) const = 0; + virtual boost::optional existTokenGuessId(const std::string & str, DCT_ID & id) const = 0; //! Estimate fraction of total transactions verified if blocks up to //! the specified block hash are verified. diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index f5b2ea99325..cbf62b89101 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -151,6 +151,7 @@ const std::map>& ATTRIBUTES::displayKeys {EconomyKeys::PaybackDFITokens, "dfi_payback_tokens"}, {EconomyKeys::DFIP2203Current, "dfip2203_current"}, {EconomyKeys::DFIP2203Burned, "dfip2203_burned"}, + {EconomyKeys::DFIP2203Minted, "dfip2203_minted"}, } }, }; @@ -396,22 +397,27 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Current}; auto balances = GetValue(liveKey, CBalances{}); - - CHistoryWriters writers{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter view(mnview, height, ~0u, {}, uint8_t(CustomTxType::FutureSwapRefund), &writers); + auto txn = std::numeric_limits::max(); for (const auto& [key, value] : userFuturesValues) { - view.EraseFuturesUserValues(key); - auto res = view.SubBalance(*contractAddressValue, value.source); + mnview.EraseFuturesUserValues(key); + + CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter subView(mnview, height, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + auto res = subView.SubBalance(*contractAddressValue, value.source); if (!res) { return res; } + subView.Flush(); - res = view.AddBalance(key.owner, value.source); + CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter addView(mnview, height, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + res = addView.AddBalance(key.owner, value.source); if (!res) { return res; } + addView.Flush(); res = balances.Sub(value.source); if (!res) { @@ -419,8 +425,6 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei } } - view.Flush(); - attributes[liveKey] = balances; return Res::Ok(); diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 3ca09e0c981..604b3f2dd27 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -31,6 +31,7 @@ enum EconomyKeys : uint8_t { PaybackTokens = 'b', DFIP2203Current = 'c', DFIP2203Burned = 'd', + DFIP2203Minted = 'e', }; enum DFIPKeys : uint8_t { diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index bad7200fc91..c03c7c1dec6 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1095,24 +1095,25 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor Res operator()(const CMintTokensMessage& obj) const { // check auth and increase balance of token's owner for (const auto& kv : obj.balances) { - DCT_ID tokenId = kv.first; + const DCT_ID& tokenId = kv.first; - auto token = mnview.GetToken(kv.first); + auto token = mnview.GetToken(tokenId); if (!token) { return Res::Err("token %s does not exist!", tokenId.ToString()); } - auto tokenImpl = static_cast(*token); - auto mintable = MintableToken(tokenId, tokenImpl); + auto mintable = MintableToken(tokenId, *token); if (!mintable) { return std::move(mintable); } - auto minted = mnview.AddMintedTokens(tokenImpl.creationTx, kv.second); + + auto minted = mnview.AddMintedTokens(tokenId, kv.second); if (!minted) { return minted; } + CalculateOwnerRewards(*mintable.val); - auto res = mnview.AddBalance(*mintable.val, CTokenAmount{kv.first, kv.second}); + auto res = mnview.AddBalance(*mintable.val, CTokenAmount{tokenId, kv.second}); if (!res) { return res; } @@ -2786,7 +2787,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { if (auto collaterals = mnview.GetVaultCollaterals(obj.vaultId)) { - boost::optional>> tokenDUSD; + boost::optional>> tokenDUSD; if (static_cast(height) >= consensus.FortCanningRoadHeight) { tokenDUSD = mnview.GetToken("DUSD"); } @@ -2852,7 +2853,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor uint64_t totalLoansActivePrice = 0, totalLoansNextPrice = 0; for (const auto& kv : obj.amounts.balances) { - DCT_ID tokenId = kv.first; + const DCT_ID& tokenId = kv.first; auto loanToken = mnview.GetLoanTokenByID(tokenId); if (!loanToken) return Res::Err("Loan token with id (%s) does not exist!", tokenId.ToString()); @@ -2892,7 +2893,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return Res::Err("Exceed maximum loans"); } - res = mnview.AddMintedTokens(loanToken->creationTx, kv.second); + res = mnview.AddMintedTokens(tokenId, kv.second); if (!res) return res; @@ -2904,7 +2905,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return res; } - boost::optional>> tokenDUSD; + boost::optional>> tokenDUSD; if (static_cast(height) >= consensus.FortCanningRoadHeight) { tokenDUSD = mnview.GetToken("DUSD"); } @@ -3118,7 +3119,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (paybackTokenId == loanTokenId) { - res = mnview.SubMintedTokens(loanToken->creationTx, subLoan); + res = mnview.SubMintedTokens(loanTokenId, subLoan); if (!res) return res; diff --git a/src/masternodes/rpc_icxorderbook.cpp b/src/masternodes/rpc_icxorderbook.cpp index 096ed733816..531aaf3ab67 100644 --- a/src/masternodes/rpc_icxorderbook.cpp +++ b/src/masternodes/rpc_icxorderbook.cpp @@ -283,11 +283,10 @@ UniValue icxcreateorder(const JSONRPCRequest& request) { { LOCK(cs_main); DCT_ID idToken; - std::unique_ptr token; if (order.orderType == CICXOrder::TYPE_INTERNAL) { - token = pcustomcsview->GetTokenGuessId(tokenFromSymbol, idToken); + auto token = pcustomcsview->GetTokenGuessId(tokenFromSymbol, idToken); if (!token) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", tokenFromSymbol)); order.idToken = idToken; @@ -303,7 +302,7 @@ UniValue icxcreateorder(const JSONRPCRequest& request) { } else { - token = pcustomcsview->GetTokenGuessId(tokenToSymbol, idToken); + auto token = pcustomcsview->GetTokenGuessId(tokenToSymbol, idToken); if (!token) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", tokenToSymbol)); order.idToken = idToken; diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 11a81827873..ab01455a702 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -29,7 +29,7 @@ UniValue setLoanTokenToJSON(CLoanSetLoanTokenImplementation const& loanToken, DC if (!token) return (UniValue::VNULL); - loanTokenObj.pushKV("token", tokenToJSON(tokenId, *static_cast(token.get()), true)); + loanTokenObj.pushKV("token", tokenToJSON(tokenId, *token, true)); loanTokenObj.pushKV("fixedIntervalPriceId", loanToken.fixedIntervalPriceId.first + "/" + loanToken.fixedIntervalPriceId.second); loanTokenObj.pushKV("interest", ValueFromAmount(loanToken.interest)); @@ -131,9 +131,8 @@ UniValue setcollateraltoken(const JSONRPCRequest& request) { LOCK(cs_main); DCT_ID idToken; - std::unique_ptr token; - token = pcustomcsview->GetTokenGuessId(tokenSymbol, idToken); + auto token = pcustomcsview->GetTokenGuessId(tokenSymbol, idToken); if (!token) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", tokenSymbol)); collToken.idToken = idToken; diff --git a/src/masternodes/rpc_tokens.cpp b/src/masternodes/rpc_tokens.cpp index d43bfdcf41b..b72f33b0668 100644 --- a/src/masternodes/rpc_tokens.cpp +++ b/src/masternodes/rpc_tokens.cpp @@ -444,7 +444,7 @@ UniValue gettoken(const JSONRPCRequest& request) { DCT_ID id; auto token = pcustomcsview->GetTokenGuessId(request.params[0].getValStr(), id); if (token) { - return tokenToJSON(id, *static_cast(token.get()), true); + return tokenToJSON(id, *token, true); } throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Token not found"); } diff --git a/src/masternodes/tokens.cpp b/src/masternodes/tokens.cpp index 7f4478e255f..78ec22798a5 100644 --- a/src/masternodes/tokens.cpp +++ b/src/masternodes/tokens.cpp @@ -26,16 +26,12 @@ std::string trim_ws(std::string const & str) return str.substr(first, (last - first + 1)); } -std::unique_ptr CTokensView::GetToken(DCT_ID id) const +boost::optional CTokensView::GetToken(DCT_ID id) const { - if (auto tokenImpl = ReadBy(id)) { - return MakeUnique(*tokenImpl); - } - - return {}; + return ReadBy(id); } -boost::optional > > CTokensView::GetToken(const std::string & symbolKey) const +boost::optional>> CTokensView::GetToken(const std::string & symbolKey) const { DCT_ID id; if (ReadBy(symbolKey, id)) { @@ -55,7 +51,7 @@ boost::optional > CTokensView::GetTok return {}; } -std::unique_ptr CTokensView::GetTokenGuessId(const std::string & str, DCT_ID & id) const +boost::optional CTokensView::GetTokenGuessId(const std::string & str, DCT_ID & id) const { std::string const key = trim_ws(str); @@ -71,13 +67,13 @@ std::unique_ptr CTokensView::GetTokenGuessId(const std::string & str, DC auto pair = GetTokenByCreationTx(tx); if (pair) { id = pair->first; - return MakeUnique(pair->second); + return pair->second; } } else { auto pair = GetToken(key); if (pair) { id = pair->first; - return std::move(pair->second); + return pair->second; } } return {}; @@ -245,39 +241,37 @@ Res CTokensView::BayfrontFlagsCleanup() return Res::Ok(); } -Res CTokensView::AddMintedTokens(const uint256 &tokenTx, CAmount const & amount) +Res CTokensView::AddMintedTokens(DCT_ID const &id, CAmount const & amount) { - auto pair = GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("token with creationTx %s does not exist!", tokenTx.ToString()); + auto tokenImpl = GetToken(id); + if (!tokenImpl) { + return Res::Err("token with id %d does not exist!", id.v); } - CTokenImpl & tokenImpl = pair->second; - auto resMinted = SafeAdd(tokenImpl.minted, amount); - if (!resMinted.ok) { + auto resMinted = SafeAdd(tokenImpl->minted, amount); + if (!resMinted) { return Res::Err("overflow when adding to minted"); } - tokenImpl.minted = *resMinted.val; + tokenImpl->minted = resMinted; - WriteBy(pair->first, tokenImpl); + WriteBy(id, *tokenImpl); return Res::Ok(); } -Res CTokensView::SubMintedTokens(const uint256 &tokenTx, CAmount const & amount) +Res CTokensView::SubMintedTokens(DCT_ID const &id, CAmount const & amount) { - auto pair = GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("token with creationTx %s does not exist!", tokenTx.ToString()); + auto tokenImpl = GetToken(id); + if (!tokenImpl) { + return Res::Err("token with id %d does not exist!", id.v); } - CTokenImpl & tokenImpl = pair->second; - auto resMinted = tokenImpl.minted - amount; + auto resMinted = tokenImpl->minted - amount; if (resMinted < 0) { return Res::Err("not enough tokens exist to subtract this amount"); } - tokenImpl.minted = resMinted; + tokenImpl->minted = resMinted; - WriteBy(pair->first, tokenImpl); + WriteBy(id, *tokenImpl); return Res::Ok(); } diff --git a/src/masternodes/tokens.h b/src/masternodes/tokens.h index 69d73194801..3ec0d41907a 100644 --- a/src/masternodes/tokens.h +++ b/src/masternodes/tokens.h @@ -142,11 +142,11 @@ class CTokensView : public virtual CStorageView static const unsigned char DB_TOKEN_LASTID; // = 'L'; using CTokenImpl = CTokenImplementation; - std::unique_ptr GetToken(DCT_ID id) const; - boost::optional>> GetToken(std::string const & symbol) const; + boost::optional GetToken(DCT_ID id) const; + boost::optional>> GetToken(std::string const & symbol) const; // the only possible type of token (with creationTx) is CTokenImpl boost::optional> GetTokenByCreationTx(uint256 const & txid) const; - std::unique_ptr GetTokenGuessId(const std::string & str, DCT_ID & id) const; + boost::optional GetTokenGuessId(const std::string & str, DCT_ID & id) const; void ForEachToken(std::function)> callback, DCT_ID const & start = DCT_ID{0}); @@ -156,8 +156,8 @@ class CTokensView : public virtual CStorageView Res UpdateToken(uint256 const & tokenTx, CToken const & newToken, bool isPreBayfront); Res BayfrontFlagsCleanup(); - Res AddMintedTokens(uint256 const & tokenTx, CAmount const & amount); - Res SubMintedTokens(uint256 const & tokenTx, CAmount const & amount); + Res AddMintedTokens(DCT_ID const & id, CAmount const & amount); + Res SubMintedTokens(DCT_ID const & id, CAmount const & amount); // tags struct ID { static constexpr uint8_t prefix() { return 'T'; } }; diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index df678bc092d..10979f78b9e 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -60,7 +60,7 @@ ResVal GuessTokenAmount(interfaces::Chain const & chain, std::stri return {{tokenId, parsed.val->first}, Res::Ok()}; } catch (...) { // assuming it's token symbol, read DCT_ID from DB - std::unique_ptr token = chain.existTokenGuessId(parsed.val->second, tokenId); + auto token = chain.existTokenGuessId(parsed.val->second, tokenId); if (!token) { return Res::Err("Invalid Defi token: %s", parsed.val->second); } diff --git a/src/validation.cpp b/src/validation.cpp index bb73081992b..978997671a2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3247,9 +3247,7 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca view.AddVaultCollateral(vaultId, amount); } - if (auto loanToken = view.GetLoanTokenByID(batch->loanAmount.nTokenId)) { - view.SubMintedTokens(loanToken->creationTx, batch->loanAmount.nValue - batch->loanInterest); - } + view.SubMintedTokens(batch->loanAmount.nTokenId, batch->loanAmount.nValue - batch->loanInterest); if (paccountHistoryDB) { AuctionHistoryKey key{data.liquidationHeight, bidOwner, vaultId, i}; @@ -3343,16 +3341,21 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache }); CDataStructureV0 burnKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Burned}; + CDataStructureV0 mintedKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Minted}; auto burned = attributes->GetValue(burnKey, CBalances{}); + auto minted = attributes->GetValue(mintedKey, CBalances{}); std::map unpaidContracts; std::set deletionPending; - CHistoryWriters writers{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter view(cache, pindex->nHeight, ~0u, {}, uint8_t(CustomTxType::FutureSwapExecution), &writers); + auto txn = std::numeric_limits::max(); + + cache.ForEachFuturesUserValues([&](const CFuturesUserKey& key, const CFuturesUserValue& futuresValues){ + + CHistoryWriters writers{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter view(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapExecution), &writers); - view.ForEachFuturesUserValues([&](const CFuturesUserKey& key, const CFuturesUserValue& futuresValues){ deletionPending.insert(key); const auto source = view.GetLoanTokenByID(futuresValues.source.nTokenId); @@ -3367,9 +3370,11 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache const auto& premiumPrice = futuresPrices.at(destId).premium; if (premiumPrice > 0) { const auto total = DivideAmounts(futuresValues.source.nValue, premiumPrice); + view.AddMintedTokens(destId, total); CTokenAmount destination{destId, total}; view.AddBalance(key.owner, destination); burned.Add(futuresValues.source); + minted.Add(destination); LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Owner %s source %s destination %s\n", key.owner.GetHex(), futuresValues.source.ToString(), destination.ToString()); } @@ -3384,9 +3389,11 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache try { const auto& discountPrice = futuresPrices.at(futuresValues.source.nTokenId).discount; const auto total = MultiplyAmounts(futuresValues.source.nValue, discountPrice); + view.AddMintedTokens(tokenDUSD->first, total); CTokenAmount destination{tokenDUSD->first, total}; view.AddBalance(key.owner, destination); burned.Add(futuresValues.source); + minted.Add(destination); LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Payment Owner %s source %s destination %s\n", key.owner.GetHex(), futuresValues.source.ToString(), destination.ToString()); } catch (const std::out_of_range&) { @@ -3394,11 +3401,11 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache } } + view.Flush(); + return true; }, {static_cast(pindex->nHeight), {}, std::numeric_limits::max()}); - view.Flush(); - const auto contractAddressValue = GetFutureSwapContractAddress(); assert(contractAddressValue); @@ -3406,25 +3413,30 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache auto balances = attributes->GetValue(liveKey, CBalances{}); - CHistoryWriters refundWriters{paccountHistoryDB.get(), nullptr, nullptr}; - CAccountsHistoryWriter refundView(cache, pindex->nHeight, ~0u - 1, {}, uint8_t(CustomTxType::FutureSwapRefund), &refundWriters); - // Refund unpaid contracts for (const auto& [key, value] : unpaidContracts) { - refundView.SubBalance(*contractAddressValue, value.source); - refundView.AddBalance(key.owner, value.source); + + CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter subView(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + subView.SubBalance(*contractAddressValue, value.source); + subView.Flush(); + + CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter addView(cache, pindex->nHeight, txn--, {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + addView.AddBalance(key.owner, value.source); + addView.Flush(); + LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Refund Owner %s source %s destination %s\n", key.owner.GetHex(), value.source.ToString(), value.source.ToString()); balances.Sub(value.source); } - refundView.Flush(); - for (const auto& key : deletionPending) { cache.EraseFuturesUserValues(key); } attributes->attributes[burnKey] = burned; + attributes->attributes[mintedKey] = minted; if (!unpaidContracts.empty()) { attributes->attributes[liveKey] = balances; diff --git a/test/functional/feature_futures.py b/test/functional/feature_futures.py index cf0349afc5a..f73348ea738 100755 --- a/test/functional/feature_futures.py +++ b/test/functional/feature_futures.py @@ -11,6 +11,9 @@ from decimal import Decimal import time +def sort_history(e): + return e['txn'] + class FuturesTest(DefiTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -309,11 +312,27 @@ def test_dtoken_to_dusd(self): result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] assert_equal(result['v0/live/economy/dfip2203_current'], [f'1.00000000@{self.symbolTSLA}', f'1.00000000@{self.symbolGOOGL}', f'1.00000000@{self.symbolTWTR}', f'1.00000000@{self.symbolMSFT}']) assert('v0/live/economy/dfip2203_burned' not in result) + assert('v0/live/economy/dfip2203_minted' not in result) + + # Get token total minted before future swap + total_dusd = Decimal(self.nodes[0].gettoken(self.idDUSD)[self.idDUSD]['minted']) # Move to next futures block next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval - (self.nodes[0].getblockcount() % self.futures_interval)) self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + # Check total minted incremented as expected + new_total_dusd = Decimal(self.nodes[0].gettoken(self.idDUSD)[self.idDUSD]['minted']) + assert_equal(total_dusd + self.prices[0]["discountPrice"] + self.prices[1]["discountPrice"] + self.prices[2]["discountPrice"] + self.prices[3]["discountPrice"], new_total_dusd) + + # Check TXN ordering + txn_first = 4294967295 + result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0, 'txtype':'q'}) + result.sort(key = sort_history, reverse = True) + for result_entry in result: + assert_equal(result_entry['txn'], txn_first) + txn_first -= 1 + # Pending futures should now be empty result = self.nodes[0].listpendingfutureswaps() assert_equal(len(result), 0) @@ -420,15 +439,33 @@ def test_dusd_to_dtoken(self): result = self.nodes[0].getburninfo() assert_equal(result['dfip2203'], [f'1.00000000@{self.symbolTSLA}', f'1.00000000@{self.symbolGOOGL}', f'1.00000000@{self.symbolTWTR}', f'1.00000000@{self.symbolMSFT}']) - # Check DFI2203 address on listgovs, current shows pending, new swaps should not show up on burn. + # Check DFI2203 address on listgovs, current shows pending if any, burned shows + # deposits from executed swaps and minted shows output from executed swaps. result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] assert_equal(result['v0/live/economy/dfip2203_current'], [f'3992.10000000@{self.symbolDUSD}', f'1.00000000@{self.symbolTSLA}', f'1.00000000@{self.symbolGOOGL}', f'1.00000000@{self.symbolTWTR}', f'1.00000000@{self.symbolMSFT}']) assert_equal(result['v0/live/economy/dfip2203_burned'], [f'1.00000000@{self.symbolTSLA}', f'1.00000000@{self.symbolGOOGL}', f'1.00000000@{self.symbolTWTR}', f'1.00000000@{self.symbolMSFT}']) + assert_equal(result['v0/live/economy/dfip2203_minted'], [f'{self.prices[0]["discountPrice"] + self.prices[1]["discountPrice"] + self.prices[2]["discountPrice"] + self.prices[3]["discountPrice"]}@{self.symbolDUSD}']) + + # Get token total minted before future swap + total_tsla = Decimal(self.nodes[0].gettoken(self.idTSLA)[self.idTSLA]['minted']) + total_googl = Decimal(self.nodes[0].gettoken(self.idGOOGL)[self.idGOOGL]['minted']) + total_twtr = Decimal(self.nodes[0].gettoken(self.idTWTR)[self.idTWTR]['minted']) + total_msft = Decimal(self.nodes[0].gettoken(self.idMSFT)[self.idMSFT]['minted']) # Move to next futures block next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval - (self.nodes[0].getblockcount() % self.futures_interval)) self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + # Check minted totals incremented as expected + new_total_tsla = Decimal(self.nodes[0].gettoken(self.idTSLA)[self.idTSLA]['minted']) + new_total_googl = Decimal(self.nodes[0].gettoken(self.idGOOGL)[self.idGOOGL]['minted']) + new_total_twtr = Decimal(self.nodes[0].gettoken(self.idTWTR)[self.idTWTR]['minted']) + new_total_msft = Decimal(self.nodes[0].gettoken(self.idMSFT)[self.idMSFT]['minted']) + assert_equal(total_tsla + Decimal('1.00000000'), new_total_tsla) + assert_equal(total_googl + Decimal('1.00000000'), new_total_googl) + assert_equal(total_twtr + Decimal('1.00000000'), new_total_twtr) + assert_equal(total_msft + Decimal('1.00000000'), new_total_msft) + # Pending futures should now be empty result = self.nodes[0].listpendingfutureswaps() assert_equal(len(result), 0) @@ -792,25 +829,33 @@ def check_gov_var_change(self): self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2203/active':'false'}}) self.nodes[0].generate(1) - # Check refunds show up in history + # Check TXN ordering on Gov var refunds + txn_first = 4294967295 result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0, 'txtype':'w'}) + result.sort(key = sort_history, reverse = True) + for result_entry in result: + assert_equal(result_entry['blockHeight'], self.nodes[0].getblockcount()) + assert_equal(result_entry['type'], 'FutureSwapRefund') + assert_equal(result_entry['txn'], txn_first) + txn_first -= 1 + + # Check other refund entries assert_equal(result[0]['owner'], self.contract_address) - assert_equal(result[0]['blockHeight'], self.nodes[0].getblockcount()) - assert_equal(result[0]['type'], 'FutureSwapRefund') - assert_equal(result[0]['amounts'], [f'{-self.prices[0]["premiumPrice"] - self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) - assert_equal(result[1]['type'], 'FutureSwapRefund') - assert_equal(result[2]['type'], 'FutureSwapRefund') - assert_equal(result[1]['blockHeight'], self.nodes[0].getblockcount()) - assert_equal(result[2]['blockHeight'], self.nodes[0].getblockcount()) + assert_equal(result[2]['owner'], self.contract_address) + if result[0]['amounts'] != [f'{-self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']: + assert_equal(result[0]['amounts'], [f'{-self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) + if result[2]['amounts'] != [f'{-self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']: + assert_equal(result[2]['amounts'], [f'{-self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) if result[1]['owner'] == address_googl: assert_equal(result[1]['amounts'], [f'{self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) - assert_equal(result[2]['owner'], address_tsla) - assert_equal(result[2]['amounts'], [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) else: assert_equal(result[1]['owner'], address_tsla) assert_equal(result[1]['amounts'], [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) - assert_equal(result[2]['owner'], address_googl) - assert_equal(result[2]['amounts'], [f'{self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) + if result[3]['owner'] == address_googl: + assert_equal(result[3]['amounts'], [f'{self.prices[1]["premiumPrice"]}@{self.symbolDUSD}']) + else: + assert_equal(result[3]['owner'], address_tsla) + assert_equal(result[3]['amounts'], [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) # Balances should be restored result = self.nodes[0].getaccount(address_tsla) @@ -898,11 +943,12 @@ def unpaid_contract(self): address = self.nodes[0].getnewaddress("", "legacy") # Fund addresses - self.nodes[0].accounttoaccount(self.address, {address: f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}'}) + self.nodes[0].accounttoaccount(self.address, {address: f'{self.prices[0]["premiumPrice"] * 2}@{self.symbolDUSD}'}) self.nodes[0].generate(1) # Create user futures contract self.nodes[0].futureswap(address, f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}', int(self.idTSLA)) + self.nodes[0].futureswap(address, f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}', int(self.idTSLA)) self.nodes[0].generate(1) # Remove Oracle @@ -914,17 +960,24 @@ def unpaid_contract(self): self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) # Check refund in history - result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0}) + result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':0, 'txtype':'w'}) + result.sort(key = sort_history, reverse = True) assert_equal(result[0]['owner'], self.contract_address) assert_equal(result[0]['type'], 'FutureSwapRefund') assert_equal(result[0]['amounts'], [f'{-self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) assert_equal(result[1]['owner'], address) assert_equal(result[1]['type'], 'FutureSwapRefund') - assert_equal(result[0]['amounts'], [f'{-self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) + assert_equal(result[1]['amounts'], [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) + assert_equal(result[2]['owner'], self.contract_address) + assert_equal(result[2]['type'], 'FutureSwapRefund') + assert_equal(result[2]['amounts'], [f'{-self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) + assert_equal(result[3]['owner'], address) + assert_equal(result[3]['type'], 'FutureSwapRefund') + assert_equal(result[3]['amounts'], [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) # Check user has been refunded result = self.nodes[0].getaccount(address) - assert_equal(result, [f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}']) + assert_equal(result, [f'{self.prices[0]["premiumPrice"] * 2}@{self.symbolDUSD}']) # Check contract address result = self.nodes[0].getaccount(self.contract_address) @@ -956,11 +1009,11 @@ def rpc_history(self): # Check all swaps present result = self.nodes[0].listaccounthistory('all', {'txtype':'q'}) - assert_equal(len(result), 15) + assert_equal(len(result), 17) # Check all swap refunds present result = self.nodes[0].listaccounthistory('all', {'txtype':'w'}) - assert_equal(len(result), 9) + assert_equal(len(result), 12) # Check swap by specific address result = self.nodes[0].listaccounthistory(self.list_history[0]['swaps'][0]['address'], {'txtype':'q'})