diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 6734ea99232..83f702847fe 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -257,6 +257,7 @@ class CMainParams : public CChainParams { } consensus.burnAddress = GetScriptForDestination(DecodeDestination("8defichainBurnAddressXXXXXXXdRQkSm", *this)); + consensus.retiredBurnAddress = GetScriptForDestination(DecodeDestination("8defichainDSTBurnAddressXXXXaCAuTq", *this)); genesis = CreateGenesisBlock(1587883831, 0x1d00ffff, 1, initdist, CreateGenesisMasternodes()); // old=1231006505 consensus.hashGenesisBlock = genesis.GetHash(); @@ -443,6 +444,7 @@ class CTestNetParams : public CChainParams { initdist.push_back(CTxOut(100000000 * COIN, GetScriptForDestination(DecodeDestination("tahuMwb9eX83eJhf2vXL6NPzABy3Ca8DHi", *this)))); consensus.burnAddress = GetScriptForDestination(DecodeDestination("7DefichainBurnAddressXXXXXXXdMUE5n", *this)); + consensus.retiredBurnAddress = GetScriptForDestination(DecodeDestination("7DefichainDSTBurnAddressXXXXXzS4Hi", *this)); genesis = CreateGenesisBlock(1586099762, 0x1d00ffff, 1, initdist, CreateGenesisMasternodes()); // old=1296688602 consensus.hashGenesisBlock = genesis.GetHash(); @@ -611,6 +613,7 @@ class CDevNetParams : public CChainParams { initdist.push_back(CTxOut(100000000 * COIN, GetScriptForDestination(DecodeDestination("7LfqHbyh9dBQDjWB6MxcWvH2PBC5iY4wPa", *this)))); consensus.burnAddress = GetScriptForDestination(DecodeDestination("7DefichainBurnAddressXXXXXXXdMUE5n", *this)); + consensus.retiredBurnAddress = GetScriptForDestination(DecodeDestination("7DefichainDSTBurnAddressXXXXXzS4Hi", *this)); genesis = CreateGenesisBlock(1585132338, 0x1d00ffff, 1, initdist, CreateGenesisMasternodes()); // old=1296688602 consensus.hashGenesisBlock = genesis.GetHash(); @@ -781,6 +784,7 @@ class CRegTestParams : public CChainParams { // For testing send after Eunos: 93ViFmLeJVgKSPxWGQHmSdT5RbeGDtGW4bsiwQM2qnQyucChMqQ consensus.burnAddress = GetScriptForDestination(DecodeDestination("mfburnZSAM7Gs1hpDeNaMotJXSGA7edosG", *this)); + consensus.retiredBurnAddress = GetScriptForDestination(DecodeDestination("mfdefichainDSTBurnAddressXXXZcE1vs", *this)); genesis = CreateGenesisBlock(1579045065, 0x207fffff, 1, { CTxOut(consensus.baseBlockSubsidy, diff --git a/src/consensus/params.h b/src/consensus/params.h index 5817c704bbc..d11fcc503b9 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -91,6 +91,8 @@ struct Params { CAmount foundationShareDFIP1; /** Trackable burn address */ CScript burnAddress; + /** Previous burn address to transfer tokens from */ + CScript retiredBurnAddress; /** Struct to hold percentages for coinbase distribution. * Percentages are calculated out of 10000 */ diff --git a/src/validation.cpp b/src/validation.cpp index 44203f0aa2c..4a80a43889e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1791,6 +1791,41 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI } } + // Remove burn balance transfers + if (pindex->nHeight == Params().GetConsensus().EunosHeight) + { + std::set txOut; + auto shouldContinueToNextAccountHistory = [&txOut, block](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool + { + if (key.owner != Params().GetConsensus().burnAddress) { + return false; + } + + if (key.blockHeight != Params().GetConsensus().EunosHeight) { + return true; + } + + const auto & value = valueLazy.get(); + + if (value.category != uint8_t(CustomTxType::AccountToAccount)) { + return true; + } + + if (key.txn >= block.vtx.size()) { + txOut.insert(key.txn); + } + + return true; + }; + + AccountHistoryKey startKey({Params().GetConsensus().burnAddress, static_cast(Params().GetConsensus().EunosHeight), std::numeric_limits::max()}); + pburnHistoryDB->ForEachAccountHistory(shouldContinueToNextAccountHistory, startKey); + + for (const auto& out : txOut) { + pburnHistoryDB->EraseAccountHistory({Params().GetConsensus().burnAddress, static_cast(Params().GetConsensus().EunosHeight), out}); + } + } + // Erase any UTXO burns for (const auto& entries : eraseBurnEntries) { @@ -2461,7 +2496,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { if (tx.vout[j].scriptPubKey == Params().GetConsensus().burnAddress) { - writeBurnEntries.push_back({{Params().GetConsensus().burnAddress, static_cast(pindex->nHeight), i}, {block.vtx[i]->GetHash(), 0, {{DCT_ID{0}, tx.vout[j].nValue}}}}); + writeBurnEntries.push_back({{Params().GetConsensus().burnAddress, static_cast(pindex->nHeight), i}, + {block.vtx[i]->GetHash(), static_cast(CustomTxType::None), {{DCT_ID{0}, tx.vout[j].nValue}}}}); } } @@ -2561,6 +2597,31 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl cache.BayfrontFlagsCleanup(); } + // Move funds from old burn address to new one + if (pindex->nHeight == chainparams.GetConsensus().EunosHeight) + { + CBalances amounts; + cache.ForEachBalance([&amounts](CScript const & owner, CTokenAmount balance) { + if (owner != Params().GetConsensus().retiredBurnAddress) { + return false; + } + + amounts.Add({balance.nTokenId, balance.nValue}); + + return true; + }, BalanceKey{chainparams.GetConsensus().retiredBurnAddress, DCT_ID{}}); + + cache.SubBalances(chainparams.GetConsensus().retiredBurnAddress, amounts); + cache.AddBalances(chainparams.GetConsensus().burnAddress, amounts); + + // Add transfer as additional TX in block + size_t count{0}; + for (const auto& entry : amounts.balances) { + pburnHistoryDB->WriteAccountHistory({Params().GetConsensus().burnAddress, static_cast(pindex->nHeight), static_cast(block.vtx.size() + count++)}, + {uint256{}, static_cast(CustomTxType::AccountToAccount), {{entry.first, entry.second}}}); + } + } + // construct undo auto& flushable = cache.GetStorage(); auto undo = CUndo::Construct(mnview.GetStorage(), flushable.GetRaw()); diff --git a/test/functional/feature_eunos_balances.py b/test/functional/feature_eunos_balances.py new file mode 100755 index 00000000000..71c368a4701 --- /dev/null +++ b/test/functional/feature_eunos_balances.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test burn address tracking""" + +from test_framework.test_framework import DefiTestFramework + +from test_framework.authproxy import JSONRPCException +from test_framework.util import assert_equal + +from decimal import Decimal + +class TransferBurnTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [['-txnotokens=0', '-amkheight=1', '-eunosheight=200', '-dakotaheight=1']] + + def run_test(self): + + # Burn address + old_burn_address = "mfdefichainDSTBurnAddressXXXZcE1vs" + burn_address = "mfburnZSAM7Gs1hpDeNaMotJXSGA7edosG" + + self.nodes[0].generate(101) + + # Create funded account address + address = self.nodes[0].getnewaddress() + self.nodes[0].sendtoaddress(address, 1) + self.nodes[0].generate(1) + + # Create tokens + self.nodes[0].createtoken({ + "symbol": "GOLD", + "name": "shiny gold", + "collateralAddress": address + }) + self.nodes[0].generate(1) + + self.nodes[0].createtoken({ + "symbol": "SILVER", + "name": "shiny silver", + "collateralAddress": address + }) + self.nodes[0].generate(1) + + # Mint tokens + self.nodes[0].minttokens(["100@128"]) + self.nodes[0].generate(1) + + self.nodes[0].minttokens(["100@129"]) + self.nodes[0].generate(1) + + # Send tokens to burn address + self.nodes[0].accounttoaccount(address, {old_burn_address:"100@128"}) + self.nodes[0].generate(1) + + self.nodes[0].accounttoaccount(address, {old_burn_address:"100@129"}) + self.nodes[0].generate(1) + + # Check balance + result = self.nodes[0].getaccount(old_burn_address) + assert_equal(result[0], "100.00000000@GOLD#128") + assert_equal(result[1], "100.00000000@SILVER#129") + + # Move to fork height + self.nodes[0].generate(200 - self.nodes[0].getblockcount()) + + # Check funds have moved + assert_equal(len(self.nodes[0].getaccount(old_burn_address)), 0) + result = self.nodes[0].getaccount(burn_address) + assert_equal(result[0], "100.00000000@GOLD#128") + assert_equal(result[1], "100.00000000@SILVER#129") + + result = self.nodes[0].listburnhistory() + assert_equal(result[0]['owner'], burn_address) + assert_equal(result[0]['type'], 'AccountToAccount') + assert_equal(result[0]['amounts'][0], '100.00000000@SILVER#129') + assert_equal(result[1]['owner'], burn_address) + assert_equal(result[1]['type'], 'AccountToAccount') + assert_equal(result[1]['amounts'][0], '100.00000000@GOLD#128') + + result = self.nodes[0].getburnaddressinfo() + assert_equal(result['address'], burn_address) + assert_equal(result['amount'], Decimal('0.00000000')) + assert_equal(result['tokens'][0], '100.00000000@GOLD#128') + assert_equal(result['tokens'][1], '100.00000000@SILVER#129') + + # Revert fork block + self.nodes[0].invalidateblock(self.nodes[0].getblockhash(200)) + + assert_equal(len(self.nodes[0].getaccount(burn_address)), 0) + result = self.nodes[0].getaccount(old_burn_address) + assert_equal(result[0], "100.00000000@GOLD#128") + assert_equal(result[1], "100.00000000@SILVER#129") + + assert_equal(len(self.nodes[0].listburnhistory()), 0) + + result = self.nodes[0].getburnaddressinfo() + assert_equal(result['address'], burn_address) + assert_equal(result['amount'], Decimal('0.00000000')) + assert_equal(len(result['tokens']), 0) + +if __name__ == '__main__': + TransferBurnTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e467b04329c..8c3308770f5 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -251,6 +251,7 @@ 'feature_oracles.py', 'rpc_getmininginfo.py', 'feature_burn_address.py', + 'feature_eunos_balances.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ]