Skip to content

Commit

Permalink
Solved issue with split transaction creation in ticket purchase,
Browse files Browse the repository at this point in the history
Updated ticket purchase unit tests.
  • Loading branch information
Sebastian Rusu committed Apr 9, 2020
1 parent 1cf8779 commit cec7c66
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 200 deletions.
199 changes: 0 additions & 199 deletions src/test/ticket_purchase_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,6 @@

BOOST_FIXTURE_TEST_SUITE(ticket_purchase_tests, BasicTestingSetup)

static CWallet testWallet;
static std::vector<COutput> vCoins;
std::vector<std::unique_ptr<CWalletTx>> wtxn;

static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
{
static int nextLockTime = 0;
CMutableTransaction tx;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
tx.vout.resize(nInput+1);
tx.vout[nInput].nValue = nValue;
if (fIsFromMe) {
// IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
// so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
tx.vin.resize(1);
}
std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));
if (fIsFromMe)
{
wtx->fDebitCached = true;
wtx->nDebitCached = 1;
}
COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
vCoins.push_back(output);
wtxn.emplace_back(std::move(wtx));
}

static void empty_wallet()
{
vCoins.clear();
wtxn.clear();
}

// test correctness of stake pool fee percent validation
BOOST_AUTO_TEST_CASE(stake_pool_fee_percent_test)
{
Expand Down Expand Up @@ -311,170 +278,4 @@ BOOST_AUTO_TEST_CASE(ticket_purchase_estimated_sizes)
BOOST_CHECK_EQUAL(GetEstimatedSizeOfBuyTicketTx(false), static_cast<size_t>(292));
}

// test the transaction for purchasing tickets
BOOST_AUTO_TEST_CASE(ticket_purchase_transaction)
{
LOCK(testWallet.cs_wallet);

std::vector<std::string> txHashes;
CWalletError we;
const std::string ticketAddress{"PfgnJQeSrEtpAupGtfm5NT4uKQ6v6Yjft3"};
const std::string vspAddress{"PmR5pdoseES3fDgupW52edBPcEHaSSDGbg"};
const CTxDestination ticketAddr = DecodeDestination(ticketAddress);
const CTxDestination vspAddr = DecodeDestination(vspAddress);
const CAmount spendLimit{100 * COIN};
const CAmount feeRate{1 * COIN};
const double vspFeePercent{5};

// validate a single ticket, with or without VSP fee
auto checkTicket = [&](uint256 txHash, bool useVsp) {
// Transaction

BOOST_CHECK(txHash != uint256());

const CWalletTx* wtx = testWallet.GetWalletTx(txHash);
BOOST_CHECK(wtx != nullptr);

BOOST_CHECK(wtx->tx != nullptr);
const CTransaction& tx = *(wtx->tx);

// Inputs

BOOST_CHECK_EQUAL(tx.vin.size(), static_cast<size_t>(useVsp ? 2 : 1)); // (vsp) + user

// Outputs

BOOST_CHECK_EQUAL(tx.vout.size(), static_cast<size_t>(useVsp ? 6 : 4)); // declaration + stake + (vsp commitment + vsp change +) user commitment + user change

// check ticket declaration
BOOST_CHECK_EQUAL(ParseTxClass(tx), TX_BuyTicket);
BOOST_CHECK_EQUAL(tx.vout[0].nValue, 0);

// make sure that the stake address and value are correct
int64_t ticketPrice = CalculateNextRequiredStakeDifficulty(chainActive.Tip(), Params().GetConsensus());
BOOST_CHECK(tx.vout[1].scriptPubKey == GetScriptForDestination(ticketAddr));
BOOST_CHECK_EQUAL(tx.vout[1].nValue, ticketPrice);

// precalculate the necessary values
size_t txSize = GetEstimatedSizeOfBuyTicketTx(useVsp);
CFeeRate txFeeRate{feeRate};
CAmount ticketFee{std::max(txFeeRate.GetFee(txSize), testWallet.minTxFee.GetFee(txSize))};
CAmount neededPerTicket = ticketPrice + ticketFee;
CAmount vspFee = (useVsp ? StakePoolTicketFee(ticketPrice, ticketFee, chainActive.Height(), vspFeePercent) : 0);
CAmount forcedChange = std::max(neededPerTicket * 1 / 100, ticketFee); // 1% of needed per ticket, but no less than ticket fee
uint32_t userOutput = 2 + (useVsp ? 2 : 0);

// check VSP outputs, if present
// make sure that the contributor address matches the VSP address, the contributed amount is the VSP fee
// and that the change is the forced change
if (useVsp) {
TicketContribData tcd;
BOOST_CHECK(ParseTicketContrib(tx, 2, tcd));
BOOST_CHECK_EQUAL(tcd.nVersion, 1);
BOOST_CHECK_EQUAL(tcd.contributedAmount, vspFee);
BOOST_CHECK_EQUAL(tcd.voteFees, TicketContribData::NoFees);
BOOST_CHECK_EQUAL(tcd.revokeFees, TicketContribData::NoFees);
BOOST_CHECK_EQUAL(tcd.whichAddr, 1);
BOOST_CHECK(tcd.rewardAddr == boost::get<CKeyID>(vspAddr));
BOOST_CHECK_EQUAL(tx.vout[2].nValue, 0);

BOOST_CHECK(tx.vout[3].scriptPubKey == GetScriptForDestination(vspAddr));
BOOST_CHECK_EQUAL(tx.vout[3].nValue, forcedChange);
}

// check user outputs
// make sure that the contributor address is owned by the wallet, the contributed amount reflects the correct value,
// also considering the VSP fee if present and that the change is the forced change
TicketContribData tcd;
BOOST_CHECK(ParseTicketContrib(tx, userOutput, tcd));
BOOST_CHECK_EQUAL(tcd.nVersion, 1);
BOOST_CHECK_EQUAL(tcd.contributedAmount, neededPerTicket - vspFee);
BOOST_CHECK_EQUAL(tcd.voteFees, TicketContribData::NoFees);
BOOST_CHECK_EQUAL(tcd.revokeFees, TicketContribData::NoFees);
BOOST_CHECK_EQUAL(tcd.whichAddr, 1);
BOOST_CHECK(testWallet.IsMine(CTxOut(0, GetScriptForDestination(tcd.rewardAddr))));
BOOST_CHECK_EQUAL(tx.vout[userOutput].nValue, 0);

BOOST_CHECK(testWallet.IsMine(tx.vout[userOutput+1]));
BOOST_CHECK_EQUAL(tx.vout[userOutput+1].nValue, forcedChange);
};

// Single purchase, no VSP
{
empty_wallet();

add_coin(1 * COIN);
std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, 1, "", 0.0, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::WALLET_INSUFFICIENT_FUNDS);

add_coin(100 * COIN);
std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, 1, "", 0.0, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::WALLET_INSUFFICIENT_FUNDS);

add_coin(1000000 * COIN);
std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, 1, "", 0.0, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::SUCCESSFUL);

BOOST_CHECK_EQUAL(txHashes.size(), static_cast<size_t>(1));

uint256 txHash = uint256S(txHashes[0]);

checkTicket(txHash, false);
}

// Single purchase, with VSP
{
empty_wallet();

add_coin(1000000 * COIN);

std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, 1, vspAddress, vspFeePercent, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::SUCCESSFUL);

BOOST_CHECK_EQUAL(txHashes.size(), static_cast<size_t>(1));

uint256 txHash = uint256S(txHashes[0]);

checkTicket(txHash, true);
}

// Multiple purchase, no VSP
{
empty_wallet();

add_coin(1000000 * COIN);

unsigned int numTickets{10};

std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, numTickets, "", 0.0, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::SUCCESSFUL);

BOOST_CHECK_EQUAL(txHashes.size(), static_cast<size_t>(numTickets));

for (unsigned int i = 0; i < numTickets; ++i) {
uint256 txHash = uint256S(txHashes[i]);
checkTicket(txHash, false);
}
}

// Multiple purchase, with VSP
{
empty_wallet();

add_coin(1000000 * COIN);

unsigned int numTickets{10};

std::tie(txHashes, we) = testWallet.PurchaseTicket("", spendLimit, 1, ticketAddress, numTickets, vspAddress, vspFeePercent, 0, feeRate);
BOOST_CHECK_EQUAL(we.code, CWalletError::SUCCESSFUL);

BOOST_CHECK_EQUAL(txHashes.size(), static_cast<size_t>(numTickets));

for (unsigned int i = 0; i < numTickets; ++i) {
uint256 txHash = uint256S(txHashes[i]);
checkTicket(txHash, true);
}
}
}

BOOST_AUTO_TEST_SUITE_END()
Loading

0 comments on commit cec7c66

Please sign in to comment.