From f3ef5eb41e78e04cf90648bcc0b312ec48171e82 Mon Sep 17 00:00:00 2001 From: Peter John Bushnell Date: Wed, 25 May 2022 18:53:00 +0100 Subject: [PATCH] Refund future swaps on split (#1290) * Refund future swaps on split * Prevent future swap for locked token Co-authored-by: Prasanna Loganathar --- src/masternodes/accounts.h | 3 +- src/masternodes/govvariables/attributes.h | 7 +-- src/masternodes/mn_checks.cpp | 8 +++ src/masternodes/rpc_accounts.cpp | 2 +- src/validation.cpp | 9 ++- test/functional/feature_token_split.py | 73 +++++++++++++++++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/masternodes/accounts.h b/src/masternodes/accounts.h index a720df62cb..b44c991a05 100644 --- a/src/masternodes/accounts.h +++ b/src/masternodes/accounts.h @@ -73,7 +73,8 @@ class CAccountsView : public virtual CStorageView ResVal GetFuturesUserValues(const CFuturesUserKey& key); Res EraseFuturesUserValues(const CFuturesUserKey& key); boost::optional GetMostRecentFuturesHeight(); - void ForEachFuturesUserValues(std::function callback, const CFuturesUserKey& start = {}); + void ForEachFuturesUserValues(std::function callback, const CFuturesUserKey& start = + {std::numeric_limits::max(), {}, std::numeric_limits::max()}); // tags struct ByBalanceKey { static constexpr uint8_t prefix() { return 'a'; } }; diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 2dc9e89699..1e0553eaf5 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -229,9 +229,12 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator& displayOracleIDs(); static const std::map>& displayKeys(); + Res RefundFuturesContracts(CCustomCSView &mnview, const uint32_t height, const uint32_t tokenID = std::numeric_limits::max()); + private: friend class CGovView; bool futureBlockUpdated{}; + std::set tokenSplits{}; std::set changed; std::map attributes; @@ -247,10 +250,6 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator applyVariable); - Res RefundFuturesContracts(CCustomCSView &mnview, const uint32_t height, const uint32_t tokenID = std::numeric_limits::max()); - -private: - std::set tokenSplits{}; }; #endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 3e26e10707..37c432ac4d 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1514,11 +1514,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (!loanToken) { return Res::Err("Could not get destination loan token %d. Set valid destination.", obj.destination); } + + if (mnview.AreTokensLocked({obj.destination})) { + return Res::Err("Cannot create future swap for locked token"); + } } else { if (obj.destination != 0) { return Res::Err("Destination should not be set when source amount is a dToken"); } + if (mnview.AreTokensLocked({obj.source.nTokenId.v})) { + return Res::Err("Cannot create future swap for locked token"); + } + CDataStructureV0 tokenKey{AttributeTypes::Token, obj.source.nTokenId.v, TokenKeys::DFIP2203Enabled}; const auto enabled = attributes->GetValue(tokenKey, true); if (!enabled) { diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 943d2aa63d..84f3de68e5 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -2262,7 +2262,7 @@ UniValue listpendingfutureswaps(const JSONRPCRequest& request) { listFutures.push_back(value); return true; - }, {std::numeric_limits::max(), {}, std::numeric_limits::max()}); + }); return listFutures; } diff --git a/src/validation.cpp b/src/validation.cpp index 86c90d583c..6b6f319ad6 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4230,6 +4230,13 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin auto view{cache}; + // Refund affected future swaps + auto res = attributes->RefundFuturesContracts(view, std::numeric_limits::max(), id); + if (!res) { + LogPrintf("Token split failed on refunding futures: %s\n", res.msg); + continue; + } + const DCT_ID oldTokenId{id}; auto token = view.GetToken(oldTokenId); @@ -4239,7 +4246,7 @@ void CChainState::ProcessTokenSplits(const CBlock& block, const CBlockIndex* pin } std::string newTokenSuffix = "/v"; - auto res = GetTokenSuffix(cache, *attributes, oldTokenId.v, newTokenSuffix); + res = GetTokenSuffix(cache, *attributes, oldTokenId.v, newTokenSuffix); if (!res) { LogPrintf("Token split failed on GetTokenSuffix %s\n", res.msg); continue; diff --git a/test/functional/feature_token_split.py b/test/functional/feature_token_split.py index 0126d6bf5f..ee8c0e529f 100755 --- a/test/functional/feature_token_split.py +++ b/test/functional/feature_token_split.py @@ -30,6 +30,7 @@ def run_test(self): self.setup_test_vaults() self.vault_split() self.check_govvar_deletion() + self.check_future_swap_refund() def setup_test_tokens(self): self.nodes[0].generate(101) @@ -40,6 +41,7 @@ def setup_test_tokens(self): self.symbolTSLA = 'TSLA' self.symbolGOOGL = 'GOOGL' self.symbolNVDA = 'NVDA' + self.symbolMSFT = 'MSFT' self.symbolGD = 'GOOGL-DUSD' # Store address @@ -52,6 +54,7 @@ def setup_test_tokens(self): {"currency": "USD", "token": self.symbolGOOGL}, {"currency": "USD", "token": self.symbolTSLA}, {"currency": "USD", "token": self.symbolNVDA}, + {"currency": "USD", "token": self.symbolMSFT}, ] # Appoint oracle @@ -66,6 +69,7 @@ def setup_test_tokens(self): {"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"1@{self.symbolMSFT}"}, ] self.nodes[0].setoracledata(oracle, int(time.time()), oracle_prices) self.nodes[0].generate(10) @@ -107,6 +111,15 @@ def setup_test_tokens(self): }) self.nodes[0].generate(1) + self.nodes[0].setloantoken({ + 'symbol': self.symbolMSFT, + 'name': self.symbolMSFT, + 'fixedIntervalPriceId': f"{self.symbolMSFT}/USD", + 'mintable': True, + 'interest': 0 + }) + self.nodes[0].generate(1) + # Set collateral tokens self.nodes[0].setcollateraltoken({ 'token': self.symbolDFI, @@ -141,6 +154,7 @@ def setup_test_tokens(self): self.idGOOGL = list(self.nodes[0].gettoken(self.symbolGOOGL).keys())[0] self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0] self.idNVDA = list(self.nodes[0].gettoken(self.symbolNVDA).keys())[0] + self.idMSFT = list(self.nodes[0].gettoken(self.symbolMSFT).keys())[0] def setup_test_pools(self): @@ -678,6 +692,65 @@ def check_govvar_deletion(self): # Swap old for new values self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0] + def check_future_swap_refund(self): + + # Set all futures attributes but set active to false + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2203/reward_pct':'0.05', + 'v0/params/dfip2203/block_period':'2880' + }}) + self.nodes[0].generate(1) + + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2203/active':'true' + }}) + self.nodes[0].generate(1) + + # Create addresses for futures + address_msft = self.nodes[0].getnewaddress("", "legacy") + 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) + + # Create user futures contracts + self.nodes[0].futureswap(address_msft, f'1@{self.symbolDUSD}', int(self.idMSFT)) + self.nodes[0].generate(1) + self.nodes[0].futureswap(address_msft, f'1@{self.symbolMSFT}') + self.nodes[0].generate(1) + + # Check pending swaps + result = self.nodes[0].listpendingfutureswaps() + assert_equal(result[0]['owner'], address_msft) + assert_equal(result[0]['source'], f'1.00000000@{self.symbolMSFT}') + assert_equal(result[0]['destination'], self.symbolDUSD) + assert_equal(result[1]['owner'], address_msft) + assert_equal(result[1]['source'], f'1.00000000@{self.symbolDUSD}') + assert_equal(result[1]['destination'], self.symbolMSFT) + + # Check balance empty + result = self.nodes[0].getaccount(address_msft) + assert_equal(result, []) + + # 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(1) + + # Test creating future swaps with locked tokens + assert_raises_rpc_error(-32600, 'Cannot create future swap for locked token', self.nodes[0].futureswap, address_locked, f'1@{self.symbolDUSD}', int(self.idMSFT)) + assert_raises_rpc_error(-32600, 'Cannot create future swap for locked token', self.nodes[0].futureswap, address_locked, f'1@{self.symbolMSFT}') + + # Move to split block + self.nodes[0].generate(1) + + # Check balance returned with multiplier applied + result = self.nodes[0].getaccount(address_msft) + assert_equal(result, [f'1.00000000@{self.symbolDUSD}', f'2.00000000@{self.symbolMSFT}']) + if __name__ == '__main__': TokenSplitTest().main()