diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 919c24fa70..d2fa4b6e54 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1099,6 +1099,11 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor for (const auto& kv : obj.balances) { const DCT_ID& tokenId = kv.first; + if (Params().NetworkIDString() == CBaseChainParams::MAIN && height >= static_cast(consensus.FortCanningCrunchHeight) && + mnview.GetLoanTokenByID(tokenId)) { + return Res::Err("Loan tokens cannot be minted"); + } + auto token = mnview.GetToken(tokenId); if (!token) { return Res::Err("token %s does not exist!", tokenId.ToString()); @@ -3402,14 +3407,14 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (!data) return Res::Err("No auction data to vault %s", obj.vaultId.GetHex()); - auto batch = mnview.GetAuctionBatch(obj.vaultId, obj.index); + auto batch = mnview.GetAuctionBatch({obj.vaultId, obj.index}); if (!batch) return Res::Err("No batch to vault/index %s/%d", obj.vaultId.GetHex(), obj.index); if (obj.amount.nTokenId != batch->loanAmount.nTokenId) return Res::Err("Bid token does not match auction one"); - auto bid = mnview.GetAuctionBid(obj.vaultId, obj.index); + auto bid = mnview.GetAuctionBid({obj.vaultId, obj.index}); if (!bid) { auto amount = MultiplyAmounts(batch->loanAmount.nValue, COIN + data->liquidationPenalty); if (amount > obj.amount.nValue) @@ -3434,7 +3439,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor //check balance CalculateOwnerRewards(obj.from); res = mnview.SubBalance(obj.from, obj.amount); - return !res ? res : mnview.StoreAuctionBid(obj.vaultId, obj.index, {obj.from, obj.amount}); + return !res ? res : mnview.StoreAuctionBid({obj.vaultId, obj.index}, {obj.from, obj.amount}); } Res operator()(const CCustomTxMessageNone&) const { @@ -3689,7 +3694,7 @@ class CCustomTxRevertVisitor : public CCustomTxVisitor } Res operator()(const CAuctionBidMessage& obj) const { - if (auto bid = mnview.GetAuctionBid(obj.vaultId, obj.index)) + if (auto bid = mnview.GetAuctionBid({obj.vaultId, obj.index})) EraseHistory(bid->first); return EraseHistory(obj.from); diff --git a/src/masternodes/rpc_tokens.cpp b/src/masternodes/rpc_tokens.cpp index f61e645d0d..ea55e3f4bb 100644 --- a/src/masternodes/rpc_tokens.cpp +++ b/src/masternodes/rpc_tokens.cpp @@ -681,12 +681,12 @@ UniValue minttokens(const JSONRPCRequest& request) { if (!token) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", kv.first.ToString())); } - auto& tokenImpl = static_cast(*token); - const Coin& authCoin = ::ChainstateActive().CoinsTip().AccessCoin(COutPoint(tokenImpl.creationTx, 1)); // always n=1 output - if (tokenImpl.IsDAT()) { + if (token->IsDAT()) { needFoundersAuth = true; + } else { + const Coin& authCoin = ::ChainstateActive().CoinsTip().AccessCoin(COutPoint(token->creationTx, 1)); // always n=1 output + auths.insert(authCoin.out.scriptPubKey); } - auths.insert(authCoin.out.scriptPubKey); } } rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, needFoundersAuth, optAuthTx, txInputs); diff --git a/src/masternodes/rpc_vault.cpp b/src/masternodes/rpc_vault.cpp index 9bcbda89ad..6ee1ef0ab6 100644 --- a/src/masternodes/rpc_vault.cpp +++ b/src/masternodes/rpc_vault.cpp @@ -80,11 +80,11 @@ namespace { UniValue batchArray{UniValue::VARR}; for (uint32_t i = 0; i < batchCount; i++) { UniValue batchObj{UniValue::VOBJ}; - auto batch = pcustomcsview->GetAuctionBatch(vaultId, i); + auto batch = pcustomcsview->GetAuctionBatch({vaultId, i}); batchObj.pushKV("index", int(i)); batchObj.pushKV("collaterals", AmountsToJSON(batch->collaterals.balances)); batchObj.pushKV("loan", tokenAmountString(batch->loanAmount)); - if (auto bid = pcustomcsview->GetAuctionBid(vaultId, i)) { + if (auto bid = pcustomcsview->GetAuctionBid({vaultId, i})) { UniValue bidObj{UniValue::VOBJ}; bidObj.pushKV("owner", ScriptToString(bid->first)); bidObj.pushKV("amount", tokenAmountString(bid->second)); diff --git a/src/masternodes/vault.cpp b/src/masternodes/vault.cpp index a1751ae078..f5d86b052a 100644 --- a/src/masternodes/vault.cpp +++ b/src/masternodes/vault.cpp @@ -117,8 +117,8 @@ Res CVaultView::EraseAuction(const CVaultId& vaultId, uint32_t height) if (it.Key().vaultId == vaultId) { CAuctionData data = it.Value(); for (uint32_t i = 0; i < data.batchCount; i++) { - EraseAuctionBid(vaultId, i); - EraseAuctionBatch(vaultId, i); + EraseAuctionBid({vaultId, i}); + EraseAuctionBatch({vaultId, i}); } EraseBy(it.Key()); return Res::Ok(); @@ -140,21 +140,26 @@ boost::optional CVaultView::GetAuction(const CVaultId& vaultId, ui return {}; } -Res CVaultView::StoreAuctionBatch(const CVaultId& vaultId, uint32_t id, const CAuctionBatch& batch) +Res CVaultView::StoreAuctionBatch(const AuctionStoreKey& key, const CAuctionBatch& batch) { - WriteBy(std::make_pair(vaultId, id), batch); + WriteBy(key, batch); return Res::Ok(); } -Res CVaultView::EraseAuctionBatch(const CVaultId& vaultId, uint32_t id) +Res CVaultView::EraseAuctionBatch(const AuctionStoreKey& key) { - EraseBy(std::make_pair(vaultId, id)); + EraseBy(key); return Res::Ok(); } -boost::optional CVaultView::GetAuctionBatch(const CVaultId& vaultId, uint32_t id) +boost::optional CVaultView::GetAuctionBatch(const AuctionStoreKey& key) { - return ReadBy(std::make_pair(vaultId, id)); + return ReadBy(key); +} + +void CVaultView::ForEachAuctionBatch(std::function callback) +{ + ForEach(callback); } void CVaultView::ForEachVaultAuction(std::function callback, uint32_t height, const CVaultId& vaultId) @@ -165,19 +170,24 @@ void CVaultView::ForEachVaultAuction(std::function(std::make_pair(vaultId, id), amount); + WriteBy(key, amount); return Res::Ok(); } -Res CVaultView::EraseAuctionBid(const CVaultId& vaultId, uint32_t id) +Res CVaultView::EraseAuctionBid(const AuctionStoreKey& key) { - EraseBy(std::make_pair(vaultId, id)); + EraseBy(key); return Res::Ok(); } -boost::optional CVaultView::GetAuctionBid(const CVaultId& vaultId, uint32_t id) +boost::optional CVaultView::GetAuctionBid(const AuctionStoreKey& key) +{ + return ReadBy(key); +} + +void CVaultView::ForEachAuctionBid(std::function callback) { - return ReadBy(std::make_pair(vaultId, id)); + ForEach(callback); } diff --git a/src/masternodes/vault.h b/src/masternodes/vault.h index c6884c875b..bfb851fa27 100644 --- a/src/masternodes/vault.h +++ b/src/masternodes/vault.h @@ -153,6 +153,9 @@ struct CAuctionBatch { class CVaultView : public virtual CStorageView { public: + using COwnerTokenAmount = std::pair; + using AuctionStoreKey = std::pair; + Res StoreVault(const CVaultId&, const CVaultData&); Res EraseVault(const CVaultId&); boost::optional GetVault(const CVaultId&) const; @@ -167,15 +170,16 @@ class CVaultView : public virtual CStorageView Res StoreAuction(const CVaultId& vaultId, const CAuctionData& data); Res EraseAuction(const CVaultId& vaultId, uint32_t height); boost::optional GetAuction(const CVaultId& vaultId, uint32_t height); - Res StoreAuctionBatch(const CVaultId& vaultId, uint32_t id, const CAuctionBatch& batch); - Res EraseAuctionBatch(const CVaultId& vaultId, uint32_t id); - boost::optional GetAuctionBatch(const CVaultId& vaultId, uint32_t id); + Res StoreAuctionBatch(const AuctionStoreKey& key, const CAuctionBatch& batch); + Res EraseAuctionBatch(const AuctionStoreKey& key); + boost::optional GetAuctionBatch(const AuctionStoreKey& vaultId); void ForEachVaultAuction(std::function callback, uint32_t height, const CVaultId& vaultId = {}); + void ForEachAuctionBatch(std::function callback); - using COwnerTokenAmount = std::pair; - Res StoreAuctionBid(const CVaultId& vaultId, uint32_t id, COwnerTokenAmount amount); - Res EraseAuctionBid(const CVaultId& vaultId, uint32_t id); - boost::optional GetAuctionBid(const CVaultId& vaultId, uint32_t id); + Res StoreAuctionBid(const AuctionStoreKey& key, COwnerTokenAmount amount); + Res EraseAuctionBid(const AuctionStoreKey& key); + boost::optional GetAuctionBid(const AuctionStoreKey& key); + void ForEachAuctionBid(std::function callback); struct VaultKey { static constexpr uint8_t prefix() { return 0x20; } }; struct OwnerVaultKey { static constexpr uint8_t prefix() { return 0x21; } }; diff --git a/src/masternodes/vaulthistory.cpp b/src/masternodes/vaulthistory.cpp index 1072a21ca7..180d0b0d46 100644 --- a/src/masternodes/vaulthistory.cpp +++ b/src/masternodes/vaulthistory.cpp @@ -81,7 +81,7 @@ void CVaultHistoryView::WriteVaultState(CCustomCSView& mnview, const CBlockIndex std::vector batches; if (auto data = mnview.GetAuction(vaultID, pindex.nHeight)) { for (uint32_t i{0}; i < data->batchCount; ++i) { - if (auto batch = mnview.GetAuctionBatch(vaultID, i)) { + if (auto batch = mnview.GetAuctionBatch({vaultID, i})) { batches.push_back(*batch); } } diff --git a/src/validation.cpp b/src/validation.cpp index 38c4fa9109..e82b8996ac 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2286,6 +2286,68 @@ static void UpdateDailyGovVariables(const std::mapnHeight >= chainparams.GetConsensus().EunosHeight) + { + const auto& incentivePair = chainparams.GetConsensus().newNonUTXOSubsidies.find(CommunityAccountType::IncentiveFunding); + UpdateDailyGovVariables(incentivePair, cache, pindex->nHeight); + } + + // Hard coded LP_DAILY_LOAN_TOKEN_REWARD change + if (pindex->nHeight >= chainparams.GetConsensus().FortCanningHeight) + { + const auto& incentivePair = chainparams.GetConsensus().newNonUTXOSubsidies.find(CommunityAccountType::Loan); + UpdateDailyGovVariables(incentivePair, cache, pindex->nHeight); + } + + // hardfork commissions update + const auto distributed = cache.UpdatePoolRewards( + [&](CScript const & owner, DCT_ID tokenID) { + cache.CalculateOwnerRewards(owner, pindex->nHeight); + return cache.GetBalance(owner, tokenID); + }, + [&](CScript const & from, CScript const & to, CTokenAmount amount) { + if (!from.empty()) { + auto res = cache.SubBalance(from, amount); + if (!res) { + LogPrintf("Custom pool rewards: can't subtract balance of %s: %s, height %ld\n", from.GetHex(), res.msg, pindex->nHeight); + return res; + } + } + if (!to.empty()) { + auto res = cache.AddBalance(to, amount); + if (!res) { + LogPrintf("Can't apply reward to %s: %s, %ld\n", to.GetHex(), res.msg, pindex->nHeight); + return res; + } + cache.UpdateBalancesHeight(to, pindex->nHeight + 1); + } + return Res::Ok(); + }, + pindex->nHeight + ); + + auto res = cache.SubCommunityBalance(CommunityAccountType::IncentiveFunding, distributed.first); + if (!res.ok) { + LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, pindex->phashBlock->GetHex()); + } else { + if (distributed.first != 0) + LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: event=ProcessRewardEvents fund=%s change=%s\n", GetCommunityAccountName(CommunityAccountType::IncentiveFunding), (CBalances{{{{0}, -distributed.first}}}.ToString())); + } + + if (pindex->nHeight >= chainparams.GetConsensus().FortCanningHeight) { + res = cache.SubCommunityBalance(CommunityAccountType::Loan, distributed.second); + if (!res.ok) { + LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, pindex->phashBlock->GetHex()); + } else { + if (distributed.second != 0) + LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: event=ProcessRewardEvents fund=%s change=%s\n", GetCommunityAccountName(CommunityAccountType::Loan), (CBalances{{{{0}, -distributed.second}}}.ToString())); + } + } +} + std::vector CollectAuctionBatches(const CCollateralLoans& collLoan, const TAmounts& collBalances, const TAmounts& loanBalances) { constexpr const uint64_t batchThreshold = 10000 * COIN; // 10k USD @@ -2830,7 +2892,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CDataStructureV0 splitKey{AttributeTypes::Oracles, OracleIDs::Splits, static_cast(pindex->nHeight)}; const auto splits = attributes->GetValue(splitKey, OracleSplits{}); - auto isSplitsBlock = splits.size() > 0 ? true : false; + const auto isSplitsBlock = splits.size() > 0; CreationTxs creationTxs; auto counter_n = 1; @@ -2917,64 +2979,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // make all changes to the new cache/snapshot to make it possible to take a diff later: CCustomCSView cache(mnview); - // Hard coded LP_DAILY_DFI_REWARD change - if (pindex->nHeight >= chainparams.GetConsensus().EunosHeight) - { - const auto& incentivePair = chainparams.GetConsensus().newNonUTXOSubsidies.find(CommunityAccountType::IncentiveFunding); - UpdateDailyGovVariables(incentivePair, cache, pindex->nHeight); - } - - // Hard coded LP_DAILY_LOAN_TOKEN_REWARD change - if (pindex->nHeight >= chainparams.GetConsensus().FortCanningHeight) - { - const auto& incentivePair = chainparams.GetConsensus().newNonUTXOSubsidies.find(CommunityAccountType::Loan); - UpdateDailyGovVariables(incentivePair, cache, pindex->nHeight); - } - - // hardfork commissions update - const auto distributed = cache.UpdatePoolRewards( - [&](CScript const & owner, DCT_ID tokenID) { - cache.CalculateOwnerRewards(owner, pindex->nHeight); - return cache.GetBalance(owner, tokenID); - }, - [&](CScript const & from, CScript const & to, CTokenAmount amount) { - if (!from.empty()) { - auto res = cache.SubBalance(from, amount); - if (!res) { - LogPrintf("Custom pool rewards: can't subtract balance of %s: %s, height %ld\n", from.GetHex(), res.msg, pindex->nHeight); - return res; - } - } - if (!to.empty()) { - auto res = cache.AddBalance(to, amount); - if (!res) { - LogPrintf("Can't apply reward to %s: %s, %ld\n", to.GetHex(), res.msg, pindex->nHeight); - return res; - } - cache.UpdateBalancesHeight(to, pindex->nHeight + 1); - } - return Res::Ok(); - }, - pindex->nHeight - ); - - auto res = cache.SubCommunityBalance(CommunityAccountType::IncentiveFunding, distributed.first); - if (!res.ok) { - LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, block.GetHash().ToString()); - } else { - if (distributed.first != 0) - LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: event=ProcessRewardEvents fund=%s change=%s\n", GetCommunityAccountName(CommunityAccountType::IncentiveFunding), (CBalances{{{{0}, -distributed.first}}}.ToString())); - } - - if (pindex->nHeight >= chainparams.GetConsensus().FortCanningHeight) { - res = cache.SubCommunityBalance(CommunityAccountType::Loan, distributed.second); - if (!res.ok) { - LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, block.GetHash().ToString()); - } else { - if (distributed.second != 0) - LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: event=ProcessRewardEvents fund=%s change=%s\n", GetCommunityAccountName(CommunityAccountType::Loan), (CBalances{{{{0}, -distributed.second}}}.ToString())); - } - } + // calculate rewards to current block + ProcessRewardEvents(pindex, cache, chainparams); // close expired orders, refund all expired DFC HTLCs at this block height ProcessICXEvents(pindex, cache, chainparams); @@ -3342,7 +3348,7 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca auto interestPart = DivideAmounts(batch.loanAmount.nValue, balance); batch.loanInterest = MultiplyAmounts(interestPart, interest); } - cache.StoreAuctionBatch(vaultId, i, batch); + cache.StoreAuctionBatch({vaultId, i}, batch); } // All done. Ready to save the overall auction. @@ -3372,15 +3378,17 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca assert(vault); for (uint32_t i = 0; i < data.batchCount; i++) { - auto batch = view.GetAuctionBatch(vaultId, i); + auto batch = view.GetAuctionBatch({vaultId, i}); assert(batch); - if (auto bid = view.GetAuctionBid(vaultId, i)) { + if (auto bid = view.GetAuctionBid({vaultId, i})) { auto bidOwner = bid->first; auto bidTokenAmount = bid->second; auto penaltyAmount = MultiplyAmounts(batch->loanAmount.nValue, COIN + data.liquidationPenalty); - assert(bidTokenAmount.nValue >= penaltyAmount); + if (bidTokenAmount.nValue < penaltyAmount) { + LogPrintf("WARNING: bidTokenAmount.nValue < penaltyAmount\n"); + } // penaltyAmount includes interest, batch as well, so we should put interest back // in result we have 5% penalty + interest via DEX to DFI and burn auto amountToBurn = penaltyAmount - batch->loanAmount.nValue + batch->loanInterest; @@ -3411,7 +3419,10 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca view.AddVaultCollateral(vaultId, amount); } - view.SubMintedTokens(batch->loanAmount.nTokenId, batch->loanAmount.nValue - batch->loanInterest); + auto res = view.SubMintedTokens(batch->loanAmount.nTokenId, batch->loanAmount.nValue - batch->loanInterest); + if (!res) { + LogPrintf("AuctionBid: SubMintedTokens failed: %s\n", res.msg); + } if (paccountHistoryDB) { AuctionHistoryKey key{data.liquidationHeight, bidOwner, vaultId, i}; @@ -4113,6 +4124,7 @@ static Res VaultSplits(CCustomCSView& view, ATTRIBUTES& attributes, const DCT_ID auto time = GetTimeMillis(); LogPrintf("Vaults rebalance in progress.. (token %d -> %d, height: %d)\n", oldTokenId.v, newTokenId.v, height); + std::vector> loanTokenAmounts; view.ForEachLoanTokenAmount([&](const CVaultId& vaultId, const CBalances& balances){ for (const auto& [tokenId, amount] : balances.balances) { @@ -4196,6 +4208,44 @@ static Res VaultSplits(CCustomCSView& view, ATTRIBUTES& attributes, const DCT_ID view.WriteInterestRate(std::make_pair(vaultId, newTokenId), rate, rate.height); } + std::vector> auctionBatches; + view.ForEachAuctionBatch([&](const CVaultView::AuctionStoreKey& key, const CAuctionBatch& value) { + if (value.loanAmount.nTokenId == oldTokenId) { + auctionBatches.emplace_back(key, value); + } + return true; + }); + + for (auto& [key, value] : auctionBatches) { + view.EraseAuctionBatch(key); + value.loanAmount.nTokenId = newTokenId; + value.loanAmount.nValue = CalculateNewAmount(multiplier, value.loanAmount.nValue); + value.loanInterest = CalculateNewAmount(multiplier, value.loanInterest); + + try { + const auto amount{value.collaterals.balances.at(oldTokenId)}; + value.collaterals.balances.erase(oldTokenId); + value.collaterals.balances[newTokenId] = CalculateNewAmount(multiplier, amount); + } catch (const std::out_of_range&) {} + + view.StoreAuctionBatch(key, value); + } + + std::vector> auctionBids; + view.ForEachAuctionBid([&](const CVaultView::AuctionStoreKey& key, const CVaultView::COwnerTokenAmount& value) { + if (value.second.nTokenId == oldTokenId) { + auctionBids.emplace_back(key, value); + } + return true; + }); + + for (auto& [key, value] : auctionBids) { + view.EraseAuctionBid(key); + value.second.nTokenId = newTokenId; + value.second.nValue = CalculateNewAmount(multiplier, value.second.nValue); + view.StoreAuctionBid(key, value); + } + LogPrintf("Vaults rebalance completed: (token %d -> %d, height: %d, time: %dms)\n", oldTokenId.v, newTokenId.v, height, GetTimeMillis() - time); @@ -4325,7 +4375,6 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin CAccounts addAccounts; CAccounts subAccounts; - view.ForEachBalance([&, multiplier = multiplier](CScript const& owner, const CTokenAmount& balance) { if (oldTokenId.v == balance.nTokenId.v) { const auto newBalance = CalculateNewAmount(multiplier, balance.nValue); @@ -4365,18 +4414,10 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin continue; } - if (view.GetLoanTokenByID(oldTokenId)) { - res = VaultSplits(view, *attributes, oldTokenId, newTokenId, pindex->nHeight, multiplier); - if (!res) { - LogPrintf("Vault splits failed: %s\n", res.msg); - continue; - } - } else { - auto res = attributes->Apply(view, pindex->nHeight); - if (!res) { - LogPrintf("Token split failed on Apply: %s\n", res.msg); - continue; - } + res = VaultSplits(view, *attributes, oldTokenId, newTokenId, pindex->nHeight, multiplier); + if (!res) { + LogPrintf("Token splits failed: %s\n", res.msg); + continue; } std::vector> updateAttributesKeys; diff --git a/src/validation.h b/src/validation.h index 1a62b5a0c0..43f0eb7ce9 100644 --- a/src/validation.h +++ b/src/validation.h @@ -771,6 +771,8 @@ class CChainState { static void ProcessOracleEvents(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams); + static void ProcessRewardEvents(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams); + static void ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams); static void ProcessTokenToGovVar(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams &chainparams); diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 983cab5cf7..1a00353100 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -19,17 +19,19 @@ class TokenSplitTest(DefiTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True + self.fort_canning_crunch = 600 self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=150', '-subsidytest=1']] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', f'-fortcanningcrunchheight={self.fort_canning_crunch}', '-subsidytest=1']] def run_test(self): self.setup_test_tokens() - self.token_split() self.setup_test_pools() + self.token_split() self.pool_split() self.setup_test_vaults() self.vault_split() self.check_govvar_deletion() + self.migrate_auction_batches() self.check_future_swap_refund() def setup_test_tokens(self): @@ -59,7 +61,7 @@ def setup_test_tokens(self): # Appoint oracle oracle_address = self.nodes[0].getnewaddress("", "legacy") - oracle = self.nodes[0].appointoracle(oracle_address, price_feed, 10) + self.oracle = self.nodes[0].appointoracle(oracle_address, price_feed, 10) self.nodes[0].generate(1) # Set Oracle prices @@ -71,7 +73,7 @@ def setup_test_tokens(self): {"currency": "USD", "tokenAmount": f"1@{self.symbolNVDA}"}, {"currency": "USD", "tokenAmount": f"1@{self.symbolMSFT}"}, ] - self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices) + self.nodes[0].setoracledata(self.oracle, int(time.time()), oracle_prices) self.nodes[0].generate(10) # Set loan tokens @@ -156,6 +158,21 @@ def setup_test_tokens(self): self.idNVDA = list(self.nodes[0].gettoken(self.symbolNVDA).keys())[0] self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0] + # Mint some loan tokens + self.nodes[0].minttokens([f'1000000@{self.symbolMSFT}', f'1000000@{self.symbolDUSD}']) + self.nodes[0].generate(1) + + # Create funded addresses + self.funded_addresses = [] + for _ in range(100): + amount = round(random.uniform(1, 1000), 8) + self.nodes[0].minttokens([f'{str(amount)}@{self.idTSLA}']) + self.nodes[0].generate(1) + address = self.nodes[0].getnewaddress() + self.nodes[0].accounttoaccount(self.address, {address: f'{str(amount)}@{self.idTSLA}'}) + self.nodes[0].generate(1) + self.funded_addresses.append([address, Decimal(str(amount))]) + def setup_test_pools(self): # Create pool pair @@ -379,9 +396,9 @@ def check_pool_split(self, pool_id, pool_symbol, token_id, token_symbol, token_s def token_split(self): # Move to FCC - self.nodes[0].generate(149 - self.nodes[0].getblockcount()) + self.nodes[0].generate(self.fort_canning_crunch - 1 - self.nodes[0].getblockcount()) - # Mined in 150 height, still using old code + # Mined in fork height, still using old code self.nodes[0].updateloantoken(self.idGOOGL, { 'name': 'AAAA', 'interest': 1 @@ -390,13 +407,16 @@ def token_split(self): self.nodes[0].generate(1) result = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] - assert_equal(result, {}) + assert_equal(result, {'v0/poolpairs/6/token_a_fee_pct': '0.01', + 'v0/poolpairs/6/token_b_fee_pct': '0.03', + 'v0/token/1/dex_in_fee_pct': '0.02', + 'v0/token/1/dex_out_fee_pct': '0.005'}) token = self.nodes[0].getloantoken(self.idGOOGL) assert_equal(token['token']['1']['name'], 'AAAA') assert_equal(token['interest'], Decimal(1)) - # Mining height 151 - migration of token to gov var + # Mining fork height - migration of token to gov var self.nodes[0].generate(1) result = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] @@ -439,17 +459,6 @@ def token_split(self): "collateralAddress": self.address }) - # Create funded addresses - funded_addresses = [] - for _ in range(100): - amount = round(random.uniform(1, 1000), 8) - self.nodes[0].minttokens([f'{str(amount)}@{self.idTSLA}']) - self.nodes[0].generate(1) - address = self.nodes[0].getnewaddress() - self.nodes[0].accounttoaccount(self.address, {address: f'{str(amount)}@{self.idTSLA}'}) - self.nodes[0].generate(1) - funded_addresses.append([address, Decimal(str(amount))]) - # Set expected minted amount minted = self.nodes[0].gettoken(self.idTSLA)[self.idTSLA]['minted'] * 2 @@ -478,7 +487,7 @@ def token_split(self): assert_equal(result[f'v0/token/{self.idTSLA}/loan_payback_fee_pct/{self.idTSLA}'], '0.25') # Check new balances - for [address, amount] in funded_addresses: + for [address, amount] in self.funded_addresses: account = self.nodes[0].getaccount(address) new_amount = Decimal(account[0].split('@')[0]) assert_equal(new_amount, amount * 2) @@ -489,7 +498,7 @@ def token_split(self): # Check new balances minted = Decimal('0') - for [address, amount] in funded_addresses: + for [address, amount] in self.funded_addresses: account = self.nodes[0].getaccount(address) amount_scaled = Decimal(truncate(str(amount * 2 / 3), 8)) new_amount = Decimal(account[0].split('@')[0]) @@ -693,6 +702,10 @@ def check_govvar_deletion(self): # Swap old for new values self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0] + # Unlock token + self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idTSLA}':'false'}}) + self.nodes[0].generate(1) + def check_future_swap_refund(self): # Set all futures attributes but set active to false @@ -712,8 +725,6 @@ def check_future_swap_refund(self): address_locked = self.nodes[0].getnewaddress("", "legacy") # Fund addresses - self.nodes[0].minttokens([f'2@{self.idMSFT}', f'2@{self.idDUSD}']) - self.nodes[0].generate(1) self.nodes[0].accounttoaccount(self.address, {address_msft: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']}) self.nodes[0].accounttoaccount(self.address, {address_locked: [f'1@{self.symbolMSFT}', f'1@{self.symbolDUSD}']}) self.nodes[0].generate(1) @@ -752,6 +763,79 @@ def check_future_swap_refund(self): result = self.nodes[0].getaccount(address_msft) assert_equal(result, [f'1.00000000@{self.symbolDUSD}', f'2.00000000@{self.symbolMSFT}']) + # Swap old for new values + self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0] + + # Unlock token + self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idMSFT}':'false'}}) + self.nodes[0].generate(1) + + def migrate_auction_batches(self): + + # Create vaults + vault1 = self.nodes[0].createvault(self.address, 'LOAN0001') + vault2 = self.nodes[0].createvault(self.address, 'LOAN0001') + self.nodes[0].generate(1) + + # Fund vaults + self.nodes[0].deposittovault(vault1, self.address, '1@DFI') + self.nodes[0].deposittovault(vault2, self.address, '2@DFI') + self.nodes[0].generate(1) + + # Take loans + self.nodes[0].takeloan({ + 'vaultId': vault1, + 'amounts': "1@MSFT"}) + self.nodes[0].takeloan({ + 'vaultId': vault2, + 'amounts': "1@MSFT"}) + self.nodes[0].takeloan({ + 'vaultId': vault2, + 'amounts': "1@MSFT"}) + self.nodes[0].generate(1) + + # Set Oracle prices + oracle_prices = [ + {"currency": "USD", "tokenAmount": f"1@{self.symbolDFI}"}, + {"currency": "USD", "tokenAmount": f"1@{self.symbolDUSD}"}, + {"currency": "USD", "tokenAmount": f"1@{self.symbolGOOGL}"}, + {"currency": "USD", "tokenAmount": f"1@{self.symbolTSLA}"}, + {"currency": "USD", "tokenAmount": f"1@{self.symbolNVDA}"}, + {"currency": "USD", "tokenAmount": f"2@{self.symbolMSFT}"}, + ] + self.nodes[0].setoracledata(self.oracle, int(time.time()), oracle_prices) + self.nodes[0].generate(10) + + # Check liquidation + result = self.nodes[0].getvault(vault1) + assert_equal(result['state'], 'inLiquidation') + result = self.nodes[0].getvault(vault2) + assert_equal(result['state'], 'inLiquidation') + + # Create bids + self.nodes[0].placeauctionbid(vault1, 0, self.address, f"1.1@{self.symbolMSFT}") + self.nodes[0].generate(1) + + # Token split + self.nodes[0].setgov({"ATTRIBUTES":{f'v0/oracles/splits/{str(self.nodes[0].getblockcount() + 2)}':f'{self.idMSFT}/2'}}) + self.nodes[0].generate(2) + + result = self.nodes[0].listauctions() + if result[0]['batches'][0]['loan'] == f'4.00000070@{self.symbolMSFT}': + assert_equal(result[1]['batches'][0]['loan'], f'2.00000036@{self.symbolMSFT}') + assert_equal(result[1]['batches'][0]['highestBid']['amount'], f'2.20000000@{self.symbolMSFT}') + else: + assert_equal(result[0]['batches'][0]['loan'], f'2.00000036@{self.symbolMSFT}') + assert_equal(result[0]['batches'][0]['highestBid']['amount'], f'2.20000000@{self.symbolMSFT}') + assert_equal(result[1]['batches'][0]['loan'], f'4.00000070@{self.symbolMSFT}') + + # Swap old for new values + self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0] + + # Unlock token + self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idMSFT}':'false'}}) + self.nodes[0].generate(1) + if __name__ == '__main__': TokenSplitTest().main()