townforge/tests/unit_tests/cc.cpp
2020-05-29 23:20:51 +00:00

3382 lines
119 KiB
C++

// Copyright (c) 2019, Crypto City
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include <set>
#include "gtest/gtest.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cc/cc.h"
#include "cc/cc_badge.h"
#include "cc/cc_palette.h"
#include "cc/cc_game_update.h"
#include "cryptonote_core/blockchain.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/tx_pool.h"
#include "blockchain_db/testdb.h"
#include "game/selection.h"
#include "game/magica.h"
#include "cc/rectanglizer.h"
#include "cc/cc_influence.h"
#include "cc/cc_discoveries.h"
#include "cc/cc_special_events.h"
#include "cc/cc_game_events.h"
#include "cc/cc_temperature.h"
#include "cc/cc_invitation.h"
#define PREDEFINED_BULDINGS 1
namespace
{
class TestDB: public cryptonote::BaseTestDB
{
private:
struct block_t
{
size_t weight;
};
struct account_t
{
crypto::public_key public_key;
uint64_t balance;
std::map<uint32_t, uint32_t> item_balances;
std::vector<uint32_t> flags;
std::string name;
bool ignore;
bool operator==(const account_t &other) const { return public_key == other.public_key && balance == other.balance && item_balances == other.item_balances && flags == other.flags && name == other.name && ignore == other.ignore; }
};
struct city_t
{
bool in_use;
uint32_t ox;
uint32_t oy;
uint32_t mayor;
uint32_t treasury;
std::string name;
bool ignore;
bool operator==(const city_t &other) const { return ox == other.ox && oy == other.oy && mayor == other.mayor && treasury == other.treasury && name == other.name && ignore == other.ignore; }
};
struct flag_t
{
bool in_use;
uint32_t owner;
uint32_t city;
uint8_t role;
uint32_t x0;
uint32_t y0;
uint32_t x1;
uint32_t y1;
uint32_t repair;
uint8_t stability;
uint8_t potential;
uint32_t economic_power;
uint64_t construction_height;
std::map<uint32_t, uint32_t> budget;
std::vector<uint16_t> palette;
uint32_t active: 1;
uint32_t ignore: 1;
uint8_t fire_state;
std::string name;
bool operator==(const flag_t &other) const { return owner == other.owner && city == other.city && role == other.role && x0 == other.x0 && y0 == other.y0 && x1 == other.x1 && y1 == other.y1 && repair == other.repair && stability == other.stability && potential == other.potential && economic_power == other.economic_power && construction_height == other.construction_height && budget == other.budget && palette == other.palette && active == other.active && fire_state == other.fire_state && name == other.name && ignore == other.ignore; }
};
struct tile_t
{
std::vector<uint8_t> blocks;
bool operator==(const tile_t &other) const { return blocks == other.blocks; }
};
struct shares_t
{
uint64_t shares[NUM_ROLES];
uint64_t payout[NUM_ROLES];
bool operator==(const shares_t &other) const { for (int i = 0; i < NUM_ROLES; ++i) if (shares[i] != other.shares[i] || payout[i] != other.payout[i]) return false; return true; }
};
struct special_event_t
{
uint32_t event;
uint64_t start_height;
uint8_t duration;
bool operator==(const special_event_t &other) const { return event == other.event && start_height == other.start_height && duration == other.duration; }
};
struct custom_item_t
{
bool in_use;
uint32_t amount;
std::string name;
bool ignore;
std::string pdesc;
std::string sdesc;
bool operator==(const custom_item_t &other) const { return amount == other.amount && name == other.name && pdesc == other.pdesc && sdesc == other.sdesc && ignore == other.ignore; }
};
static crypto::public_key DEAD_PKEY;
public:
struct state_t
{
std::vector<account_t> accounts;
std::vector<city_t> cities;
std::vector<flag_t> flags;
std::set<uint64_t> nonces;
std::map<std::pair<uint32_t, uint32_t>, tile_t> tiles;
std::map<uint64_t, uint32_t> cc_trade_used;
std::map<uint64_t, cryptonote::order_t<crypto::hash>> cc_orders;
std::map<uint64_t, shares_t> shares;
std::map<uint32_t, std::vector<special_event_t>> special_events;
std::vector<custom_item_t> items;
bool operator==(const state_t &other) const
{
std::map<uint32_t, account_t> a0, a1;
std::map<uint32_t, city_t> c0, c1;
std::map<uint32_t, flag_t> f0, f1;
for (size_t i = 0; i < accounts.size(); ++i) if (accounts[i].public_key != DEAD_PKEY) a0[i] = accounts[i];
for (size_t i = 0; i < other.accounts.size(); ++i) if (other.accounts[i].public_key != DEAD_PKEY) a1[i] = accounts[i];
for (size_t i = 0; i < cities.size(); ++i) if (cities[i].in_use) c0[i] = cities[i];
for (size_t i = 0; i < other.cities.size(); ++i) if (other.cities[i].in_use) c1[i] = cities[i];
for (size_t i = 0; i < flags.size(); ++i) if (flags[i].in_use) f0[i] = flags[i];
for (size_t i = 0; i < other.flags.size(); ++i) if (other.flags[i].in_use) f1[i] = flags[i];
return nonces == other.nonces && a0 == a1 && c0 == c1 && f0 == f1 && tiles == other.tiles && cc_trade_used == other.cc_trade_used && cc_orders == other.cc_orders
&& shares == other.shares && special_events == other.special_events && items == other.items;
}
};
public:
TestDB()
{
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
DEAD_PKEY = keys.pub;
reset();
m_open = true;
}
state_t get_state() const { return state; }
virtual void add_block( const cryptonote::block& blk
, size_t block_weight
, uint64_t long_term_block_weight
, const cryptonote::difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, uint64_t num_rct_outs
, const crypto::hash& blk_hash
) override {
blocks.push_back({block_weight});
}
virtual uint64_t height() const override { return blocks.size(); }
virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override {
crypto::hash hash = crypto::null_hash;
*(uint64_t*)&hash = height;
return hash;
}
virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override {
uint64_t h = height();
crypto::hash top = crypto::null_hash;
if (h)
*(uint64_t*)&top = h - 1;
if (block_height)
*block_height = h - 1;
return top;
}
virtual cryptonote::block get_top_block() const override { cryptonote::block b; b.major_version = b.minor_version = 12; return b; }
virtual void pop_block(cryptonote::block &blk, std::vector<cryptonote::transaction> &txs) override { blocks.pop_back(); }
virtual void reset()
{
state = state_t();
state.accounts.push_back({DEAD_PKEY, 0, {}, {}});
state.flags.push_back({false});
}
virtual uint64_t get_block_already_generated_coins(const uint64_t &height) const
{
uint64_t coins = 0;
for (uint64_t h = 0; h <= height; ++h)
coins += 10000000000 - h * 9999;
return coins;
}
// CC
virtual uint32_t allocate_new_cc_account(const crypto::public_key &public_key, const std::string &name, uint32_t inviting_account = 0) override
{
if (name.size() > MAX_CC_NAME_LENGTH)
throw std::runtime_error("Name is too long");
state.accounts.push_back({public_key, 0, {}, {}, name, false});
return state.accounts.size() - 1;
}
virtual bool lookup_cc_account(const crypto::public_key &public_key, uint32_t &id) const override
{
for (size_t i = 1; i < state.accounts.size(); ++i)
{
if (state.accounts[i].public_key == public_key)
{
id = i;
return true;
}
}
return false;
}
virtual void delete_cc_account(uint32_t id) override
{
if (id > 0 && id < state.accounts.size())
{
state.accounts[id].public_key = DEAD_PKEY;
}
}
virtual uint32_t get_num_cc_accounts() const override
{
size_t c = 0;
for (size_t i = 1; i < state.accounts.size(); ++i)
if (state.accounts[i].public_key != DEAD_PKEY)
++c;
return c;
}
virtual bool get_cc_account_data(uint32_t id, cryptonote::cc_account_data_t &data) const override
{
if (id == 0 || id >= state.accounts.size() || state.accounts[id].public_key == DEAD_PKEY)
return false;
data.id = id;
data.public_key = state.accounts[id].public_key;
data.balance = state.accounts[id].balance;
data.item_balances = state.accounts[id].item_balances;
data.flags = state.accounts[id].flags;
data.name = state.accounts[id].name;
data.ignore = state.accounts[id].ignore;
return true;
}
virtual void set_cc_account_balance(uint32_t id, uint64_t balance) override
{
state.accounts[id].balance = balance;
}
virtual void set_cc_account_item_balances(uint32_t id, const std::map<uint32_t, uint32_t> &item_balances) override
{
state.accounts[id].item_balances = item_balances;
}
virtual void set_cc_account_ignore(uint32_t id, bool ignore) override
{
state.accounts[id].ignore = ignore;
}
virtual void add_cc_account_flag(uint32_t id, uint32_t flag_id) override
{
state.accounts[id].flags.push_back(flag_id);
}
virtual void remove_cc_account_flag(uint32_t id, uint32_t flag_id) override
{
auto i = std::find(state.accounts[id].flags.begin(), state.accounts[id].flags.end(), flag_id);
if (i != state.accounts[id].flags.end())
state.accounts[id].flags.erase(i);
}
virtual uint32_t allocate_new_cc_city(uint32_t ox, uint32_t oy, uint32_t mayor, const std::string &name) override
{
std::string treasury_name = (name.empty() ? ("City " + std::to_string(state.cities.size() - 1)) : name) + " treasury";
uint32_t treasury = allocate_new_cc_account(crypto::null_pkey, treasury_name);
state.cities.push_back({true, ox, oy, mayor, treasury, name, false});
return state.cities.size() - 1;
}
virtual bool get_cc_city_data(uint32_t id, cryptonote::cc_city_data_t &ccd) const override
{
if (id >= state.cities.size() || !state.cities[id].in_use)
return false;
ccd.id = id;
ccd.ox = state.cities[id].ox;
ccd.oy = state.cities[id].oy;
ccd.treasury = state.cities[id].treasury;
ccd.mayor = state.cities[id].mayor;
return true;
}
virtual void delete_cc_city(uint32_t id) override
{
state.cities[id].in_use = false;
}
virtual uint32_t get_num_cc_cities() const override
{
size_t c = 0;
for (size_t i = 0; i < state.cities.size(); ++i)
if (state.cities[i].in_use)
++c;
return c;
}
virtual void set_cc_city_ignore(uint32_t id, bool ignore) override
{
state.cities[id].ignore = ignore;
}
virtual bool for_all_cc_cities(std::function<bool(const cryptonote::cc_city_data_t &ccd)> f) const override
{
for (size_t i = 0; i < state.cities.size(); ++i)
{
const auto &city = state.cities[i];
if (!city.in_use)
continue;
cryptonote::cc_city_data_t ccd;
ccd.id = i;
ccd.ox = city.ox;
ccd.oy = city.oy;
ccd.treasury = city.treasury;
ccd.mayor = city.mayor;
if (!f(ccd))
return false;
}
return true;
}
virtual uint32_t allocate_new_cc_flag(const uint32_t *id, uint32_t owner_id, uint32_t city_id, uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint8_t stability) override
{
uint32_t actual_id;
if (!id)
{
state.flags.push_back({});
actual_id = state.flags.size() - 1;
}
else
{
actual_id = *id;
}
std::vector<uint16_t> palette;
cc::set_default_palette(palette, height());
state.flags[actual_id] = {true, owner_id, city_id, ROLE_EMPTY, x0, y0, x1, y1, 0, stability, 0, 0, 0, {}, palette, false, false, 127, ""};
return actual_id;
}
virtual void delete_cc_flag(uint32_t id) override
{
for (uint32_t y = state.flags[id].y0; y <= state.flags[id].y1; ++y)
{
for (uint32_t x = state.flags[id].x0; x <= state.flags[id].x1; ++x)
{
auto it = state.tiles.find(std::make_pair(x, y));
if (it != state.tiles.end())
state.tiles.erase(it);
}
}
state.flags[id].in_use = false;
}
virtual bool for_all_cc_flags(std::function<bool(const cryptonote::cc_flag_data_t&)> f) const override
{
for (size_t i = 0; i < state.flags.size(); ++i)
{
const auto &flag = state.flags[i];
if (!flag.in_use)
continue;
cryptonote::cc_flag_data_t fd;
fd.id = i;
fd.owner = flag.owner;
fd.city = flag.city;
fd.role = flag.role;
fd.x0 = flag.x0;
fd.y0 = flag.y0;
fd.x1 = flag.x1;
fd.y1 = flag.y1;
fd.repair = flag.repair;
fd.stability = flag.stability;
fd.potential = flag.potential;
fd.economic_power = flag.economic_power;
fd.construction_height = flag.construction_height;
fd.budget = flag.budget;
fd.active = flag.active;
fd.fire_state = flag.fire_state;
fd.ignore = flag.ignore;
fd.palette = flag.palette;
if (!f(fd))
return false;
}
return true;
}
virtual bool get_cc_flag_data(uint32_t id, cryptonote::cc_flag_data_t &fd, std::vector<std::vector<uint8_t>> *tiles) const override
{
if (id >= state.flags.size() || !state.flags[id].in_use)
return false;
fd.id = id;
fd.owner = state.flags[id].owner;
fd.city = state.flags[id].city;
fd.role = state.flags[id].role;
fd.x0 = state.flags[id].x0;
fd.y0 = state.flags[id].y0;
fd.x1 = state.flags[id].x1;
fd.y1 = state.flags[id].y1;
fd.repair = state.flags[id].repair;
fd.stability = state.flags[id].stability;
fd.economic_power = state.flags[id].economic_power;
fd.potential = state.flags[id].potential;
fd.construction_height = state.flags[id].construction_height;
fd.budget = state.flags[id].budget;
fd.active = state.flags[id].active;
fd.fire_state = state.flags[id].fire_state;
fd.ignore = state.flags[id].ignore;
fd.palette = state.flags[id].palette;
if (tiles)
{
tiles->resize((fd.x1-fd.x0+1) * (fd.y1-fd.y0+1));
unsigned int idx = 0;
for (uint32_t y = fd.y0; y <= fd.y1; ++y)
{
for (uint32_t x = fd.x0; x <= fd.x1; ++x)
{
auto it = state.tiles.find(std::make_pair(x, y));
if (it != state.tiles.end())
(*tiles)[idx] = it->second.blocks;
++idx;
}
}
}
return true;
}
virtual void set_cc_flag_data(uint32_t id, const cryptonote::cc_flag_data_t &fd) override
{
state.flags[id].owner = fd.owner;
state.flags[id].city = fd.city;
state.flags[id].role = fd.role;
state.flags[id].x0 = fd.x0;
state.flags[id].y0 = fd.y0;
state.flags[id].x1 = fd.x1;
state.flags[id].y1 = fd.y1;
state.flags[id].repair = fd.repair;
state.flags[id].stability = fd.stability;
state.flags[id].economic_power = fd.economic_power;
state.flags[id].potential = fd.potential;
state.flags[id].construction_height = fd.construction_height;
state.flags[id].budget = fd.budget;
state.flags[id].active = fd.active;
state.flags[id].fire_state = fd.fire_state;
state.flags[id].ignore = fd.ignore;
state.flags[id].palette = fd.palette;
}
virtual void set_cc_flag_role(uint32_t id, uint8_t role, uint32_t economic_power, uint8_t potential) override
{
state.flags[id].role = role;
state.flags[id].economic_power = economic_power;
state.flags[id].potential = potential;
state.flags[id].construction_height = height() - 1;
}
virtual void set_cc_flag_name(uint32_t id, const std::string &name)
{
state.flags[id].name = name;
}
virtual void set_cc_flag_owner(uint32_t id, uint32_t owner) override
{
state.flags[id].owner = owner;
}
virtual void set_cc_flag_repair(uint32_t id, uint32_t repair) override
{
state.flags[id].repair = repair;
}
virtual void set_cc_flag_budget(uint32_t id, const std::map<uint32_t, uint32_t> &budget) override
{
state.flags[id].budget = budget;
}
virtual void set_cc_flag_fire_state(uint32_t id, uint8_t fire_state) override
{
state.flags[id].fire_state = fire_state;
}
virtual void set_cc_flag_active(uint32_t id, bool active) override
{
state.flags[id].active = active;
}
virtual void set_cc_flag_ignore(uint32_t id, bool ignore) override
{
state.flags[id].ignore = ignore;
}
virtual void set_cc_flag_tiles(uint32_t id, const std::vector<std::vector<uint8_t>> &tiles) override
{
const uint32_t x0 = state.flags[id].x0;
const uint32_t y0 = state.flags[id].y0;
const uint32_t x1 = state.flags[id].x1;
const uint32_t y1 = state.flags[id].y1;
unsigned int idx = 0;
for (uint32_t y = y0; y <= y1; ++y)
{
for (uint32_t x = x0; x <= x1; ++x)
{
if (tiles[idx].empty())
{
state.tiles.erase(std::make_pair(x, y));
}
else
state.tiles[std::make_pair(x, y)].blocks = tiles[idx];
++idx;
}
}
}
virtual void remove_cc_flag_blocks(uint32_t id)
{
const uint32_t x0 = state.flags[id].x0;
const uint32_t y0 = state.flags[id].y0;
const uint32_t x1 = state.flags[id].x1;
const uint32_t y1 = state.flags[id].y1;
unsigned int idx = 0;
for (uint32_t y = y0; y <= y1; ++y)
{
for (uint32_t x = x0; x <= x1; ++x)
{
state.tiles.erase(std::make_pair(x, y));
}
}
}
virtual uint32_t get_num_cc_flags() const override
{
size_t c = 0;
for (size_t i = 0; i < state.flags.size(); ++i)
if (state.flags[i].in_use)
++c;
return c;
}
virtual void add_cc_used_nonce(uint64_t nonce) override
{
state.nonces.insert(nonce);
}
virtual void remove_cc_used_nonce(uint64_t nonce) override
{
state.nonces.erase(nonce);
}
virtual bool is_cc_nonce_used(uint64_t nonce) const override
{
return state.nonces.find(nonce) != state.nonces.end();
}
virtual void set_cc_trade_used(uint64_t nonce, uint32_t used) override
{
if (used == 0)
state.cc_trade_used.erase(nonce);
else
state.cc_trade_used[nonce] = used;
}
virtual uint32_t get_cc_trade_used(uint64_t nonce) const override
{
auto i = state.cc_trade_used.find(nonce);
if (i == state.cc_trade_used.end())
return 0;
return i->second;
}
virtual void add_cc_order(const crypto::hash &txid, bool mined, uint32_t account, uint64_t nonce, bool bid, uint32_t type, uint32_t id, uint32_t amount, uint64_t price, uint64_t accrual_start_height, int64_t accrual, uint64_t accrual_price_limit, uint64_t expiration) override
{
state.cc_orders[nonce] = {txid, mined, account, bid, nonce, type, id, amount, price, accrual_start_height, accrual, accrual_price_limit, expiration};
}
virtual void remove_cc_order(uint64_t nonce)
{
state.cc_orders.erase(nonce);
}
virtual void get_cc_orders(std::vector<cryptonote::order_t<crypto::hash>> &trades, bool bids, bool offers, const std::vector<uint32_t> &type, const std::vector<uint32_t> &id) const
{
for (const auto &e: state.cc_orders)
{
if (e.second.bid && !bids)
continue;
if (!e.second.bid && !offers)
continue;
if (!type.empty() && std::find(type.begin(), type.end(), e.second.type) == type.end())
continue;
if (!id.empty() && std::find(id.begin(), id.end(), e.second.id) == id.end())
continue;
trades.push_back({e.second.txid, e.second.mined, e.second.account, e.second.bid, e.first, e.second.type, e.second.id, e.second.amount, e.second.price, e.second.accrual_start_height, e.second.accrual, e.second.accrual_price_limit, e.second.expiration});
}
}
virtual bool get_cc_transaction_hash_from_nonce(uint64_t nonce, crypto::hash &txid, bool &mined) const override
{
auto i = state.cc_orders.find(nonce);
if (i == state.cc_orders.end())
return false;
txid = i->second.txid;
mined = i->second.mined;
return true;
}
virtual bool get_cc_transaction_account_from_nonce(uint64_t nonce, uint32_t &account) const override
{
auto i = state.cc_orders.find(nonce);
if (i == state.cc_orders.end())
return false;
account = i->second.account;
return true;
}
virtual bool get_cc_pruned_transaction_blob_from_nonce(uint64_t nonce, cryptonote::blobdata &bd, bool &mined, bool blockchain, bool txpool, cryptonote::relay_category tx_category) const override
{
auto i = state.cc_orders.find(nonce);
if (i == state.cc_orders.end())
return false;
if (i->second.mined && !blockchain)
return false;
if (!i->second.mined && !txpool)
return false;
bd = {};
mined = i->second.mined;
return true;
}
virtual void set_cc_shares(const cryptonote::cc_shares_data_t &sd) override
{
shares_t s;
for (int i = 0; i < NUM_ROLES; ++i)
{
s.shares[i] = sd.shares[i];
s.payout[i] = sd.payout[i];
}
state.shares[sd.height] = s;
}
virtual void remove_cc_shares(uint32_t city, uint64_t height) override
{
state.shares.erase(height);
}
virtual bool get_cc_shares(uint32_t city, uint64_t height, cryptonote::cc_shares_data_t &sd) const override
{
if (state.shares.empty())
return false;
auto x = state.shares.begin()->second;
for (auto e: state.shares)
{
if (e.first > height)
break;
x = e.second;
}
for (int i = 0; i < NUM_ROLES; ++i)
{
sd.shares[i] = x.shares[i];
sd.payout[i] = x.payout[i];
}
return true;
}
virtual void set_cc_special_events(uint32_t city, const std::vector<cc::special_event_data_t> &sed)
{
std::vector<special_event_t> se;
for (const auto &e: sed)
se.push_back({e.special_event, e.start_height, e.duration});
state.special_events[city] = std::move(se);
}
virtual void get_cc_special_events(uint32_t city, std::vector<cc::special_event_data_t> &sed) const
{
sed.clear();
auto i = state.special_events.find(city);
if (i == state.special_events.end())
return;
for (const auto &e: i->second)
sed.push_back({e.event, e.start_height, e.duration});
}
uint32_t allocate_new_cc_custom_item(uint32_t amount, const std::string &name, const std::string &primary_description, const std::string &secondary_description)
{
uint32_t actual_id;
state.items.push_back({});
actual_id = state.items.size() - 1;
state.items[actual_id] = {true, amount, name, false, primary_description, secondary_description};
return actual_id;
}
void delete_cc_custom_item(uint32_t id)
{
state.items[id].in_use = false;
}
bool get_cc_custom_item_data(uint32_t id, cc::cc_custom_item_t &cid) const
{
if (!state.items[id].in_use)
return false;
cid.id = id;
cid.amount = state.items[id].amount;
cid.name = state.items[id].name;
cid.primary_description = state.items[id].pdesc;
cid.secondary_description = state.items[id].sdesc;
return true;
}
void set_cc_custom_item_ignore(uint32_t id, bool ignore)
{
if (!state.items[id].in_use)
return;
state.items[id].ignore = ignore;
}
void set_cc_custom_item_amount(uint32_t id, uint32_t amount)
{
if (!state.items[id].in_use)
return;
state.items[id].amount = amount;
}
bool for_all_cc_custom_items(std::function<bool(const cc::cc_custom_item_t &cid)> f) const
{
for (size_t i = 0; i < state.items.size(); ++i)
{
const auto &item = state.items[i];
if (!item.in_use)
continue;
cc::cc_custom_item_t cid;
cid.id = i;
cid.amount = item.amount;
cid.name = item.name;
cid.primary_description = item.pdesc;
cid.secondary_description = item.sdesc;
if (!f(cid))
return false;
}
return true;
}
private:
std::vector<block_t> blocks;
state_t state;
};
crypto::public_key TestDB::DEAD_PKEY = crypto::null_pkey;
}
TEST(cc_command, tx_defaults_to_none)
{
cryptonote::transaction tx;
ASSERT_EQ(tx.cc_cmd.type(), typeid(cryptonote::cc_command_none_t));
}
TEST(cc_command, none)
{
cryptonote::cc_command_none_t cmd;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 0);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, create_account)
{
cryptonote::cc_command_create_account_t cmd;
cmd.public_key = crypto::null_pkey;
cmd.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 42;
cmd.name = "name";
cmd.inviting_account = 0;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 0);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 42);
}
TEST(cc_command, transfer_deposit)
{
cryptonote::cc_command_transfer_t cmd;
cmd.in_amount = 0;
cmd.public_key = crypto::null_pkey;
cmd.out_amount = 1000;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 0);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 1000);
}
TEST(cc_command, transfer_withdrawal)
{
cryptonote::cc_command_transfer_t cmd;
cmd.in_amount = 1000;
cmd.public_key = crypto::null_pkey;
cmd.out_amount = 0;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 1000);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 1000);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, transfer_mixed)
{
cryptonote::cc_command_transfer_t cmd;
cmd.in_amount = 1000;
cmd.public_key = crypto::null_pkey;
cmd.out_amount = 1500;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 1000);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 1000);
ASSERT_EQ(cc_out, 1500);
}
TEST(cc_command, buy_land)
{
cryptonote::cc_command_buy_land_t cmd;
cmd.city = 0;
cmd.x = 40;
cmd.y = 50;
cmd.wm1 = 8;
cmd.hm1 = 4;
cmd.cost = 74983743;
ASSERT_EQ(cc::get_cc_command_cost(cmd), 74983743);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, build)
{
cryptonote::cc_command_build_t cmd;
cmd.flag = 1;
cmd.dx = 40;
cmd.dy = 50;
cmd.wm1 = 8;
cmd.hm1 = 4;
cmd.height = 0;
cmd.remove = false;
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, buy_blocks)
{
cryptonote::cc_command_buy_items_t cmd;
cmd.entries.resize(1);
cmd.entries[0].type = ITEM_BASIC_STONE;
cmd.entries[0].amount = 1;
uint64_t c1 = cc::get_cc_command_cost(cmd);
ASSERT_GT(c1, 0);
cmd.entries[0].amount = 50;
uint64_t c50 = cc::get_cc_command_cost(cmd);
ASSERT_EQ(c50, 50 * c1);
cmd.entries[0].amount = std::numeric_limits<uint32_t>::max();
uint64_t cmax32 = cc::get_cc_command_cost(cmd);
ASSERT_EQ(cmax32, std::numeric_limits<uint32_t>::max() * c1);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, assign_items)
{
cryptonote::cc_command_assign_items_t cmd;
cmd.items.resize(1);
cmd.items[0].type = ITEM_BASIC_STONE;
cmd.items[0].amount = 1;
uint64_t c1 = cc::get_cc_command_cost(cmd);
ASSERT_EQ(c1, 0);
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, repair)
{
cryptonote::cc_command_repair_t cmd;
cmd.flags.push_back({1, 0});
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, rename)
{
cryptonote::cc_command_rename_t cmd;
cmd.flag = 1;
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc_command, research)
{
cryptonote::cc_command_research_t cmd;
cmd.discovery = DISCOVERY_IMPROVED_YOKE;
cmd.amount = 1000;
uint64_t cc_in = (uint64_t)-1, cc_out = (uint64_t)-1;
cc::get_cc_command_in_out(cmd, cc_in, cc_out);
ASSERT_EQ(cc_in, 0);
ASSERT_EQ(cc_out, 0);
}
TEST(cc, generator_types)
{
for (uint8_t role = 0; role < NUM_ROLES; ++role)
{
ASSERT_EQ(cc::is_generator(role), role == ROLE_STONECUTTER || role == ROLE_SAWMILL || role == ROLE_KILN || role == ROLE_SMELTER || role == ROLE_WORKFORCE);
}
}
TEST(cc, increasing_costs)
{
uint64_t c0, c1;
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 1, 1, c0));
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 1, 2, c1));
ASSERT_GT(c1, c0);
// square is cheaper for the same area
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 3, 3, c0));
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 1, 7, c1));
ASSERT_GT(c1, c0);
for (uint32_t h = 1; h < 65536; ++h)
{
ASSERT_GE(cc::get_build_labour_cost_for_height(h), cc::get_build_labour_cost_for_height(h-1));
}
}
TEST(cc, buildings_start_inactive)
{
cryptonote::cc_command_game_update_t cmd;
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg, cg0 = cc::create_cc_game_update_command(*db, events);
cryptonote::keypair account_keys = cryptonote::keypair::generate(hw::get_device("default"));
uint32_t city_id = 0;
uint32_t account_id = bdb->allocate_new_cc_account(account_keys.pub, "");
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, 49);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(bdb->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.active, false);
}
TEST(cc, buildings_start_not_on_fire)
{
cryptonote::cc_command_game_update_t cmd;
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg, cg0 = cc::create_cc_game_update_command(*db, events);
cryptonote::keypair account_keys = cryptonote::keypair::generate(hw::get_device("default"));
uint32_t city_id = 0;
uint32_t account_id = bdb->allocate_new_cc_account(account_keys.pub, "");
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, 49);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(bdb->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.fire_state, 127);
}
static void test_commands(bool good, const std::vector<cryptonote::cc_command_t> &setup, const cryptonote::cc_command_t &cmd, const char *label, const std::function<void(TestDB*)> &check = [](TestDB*)->void{})
{
std::unique_ptr<cryptonote::Blockchain> bc;
cryptonote::tx_memory_pool txpool(*bc);
bc.reset(new cryptonote::Blockchain(txpool));
struct get_test_options {
const std::pair<uint8_t, uint64_t> hard_forks[2];
const cryptonote::test_options test_options = {
hard_forks,
0,
};
get_test_options(): hard_forks{std::make_pair((uint8_t)12, (uint64_t)0), std::make_pair((uint8_t)0, (uint64_t)0)} {}
} opts;
cryptonote::Blockchain *blockchain = bc.get();
ASSERT_TRUE(blockchain != NULL) << label;
TestDB *db = new TestDB();
bool r = blockchain->init(db, cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL);
ASSERT_TRUE(r) << label;
for (const auto &s: setup)
{
cryptonote::tx_verification_context tvc{};
const cryptonote::cc_command_base_t *base = cryptonote::get_cc_command_base(s);
uint64_t balance, cost = cc::get_cc_command_cost(s);
bool r0 = base ? db->get_cc_account_balance(base->cc_account, balance) : true;
bool r1 = r0 && cost <= balance;
bool r2 = r1 && cc::check_cc_command(*db, s, tvc);
ASSERT_TRUE(r2) << label << " (r0 " << r0 << ", r1 " << r1 << ", r2 " << r2 << ", cost " << cost << ", account " << (base ? base->cc_account : 0) << ", balance " << balance << ")";
cc::game_events_t events;
r = cc::execute_cc_command(*db, s, events);
ASSERT_TRUE(r) << label;
}
cryptonote::tx_verification_context tvc{};
const cryptonote::cc_command_base_t *base = cryptonote::get_cc_command_base(cmd);
uint64_t balance, cost = cc::get_cc_command_cost(cmd);
bool r0 = base ? db->get_cc_account_balance(base->cc_account, balance) : true;
bool r1 = r0 && cost <= balance;
bool r2 = r1 && cc::check_cc_command(*db, cmd, tvc);
ASSERT_EQ(r2, good) << label << " (r0 " << r0 << ", r1 " << r1 << ", r2 " << r2 << ", cost " << cost << ", account " << (base ? base->cc_account : 0) << ", balance " << balance << ")";
if (good)
{
const TestDB::state_t s0 = db->get_state();
cc::game_events_t events;
r = cc::execute_cc_command(*db, cmd, events);
ASSERT_TRUE(r) << label;
if (check)
check(db);
r = cc::revert_cc_command(*db, cmd);
ASSERT_TRUE(r) << label;
const TestDB::state_t s1 = db->get_state();
ASSERT_EQ(s0, s1) << label;
}
}
TEST(cc_command, execute_none)
{
test_commands(true, {}, cryptonote::cc_command_none_t(), "valid");
}
TEST(cc_command, execute_create_account)
{
cryptonote::cc_command_create_account_t create_account;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// good
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE;
create_account.name = "create-test";
create_account.inviting_account = 0;
test_commands(true, {}, create_account, "valid");
// bad key
create_account.public_key = crypto::null_pkey;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE;
test_commands(false, {}, create_account, "bad key");
// bad name
create_account.name = " a";
test_commands(false, {}, create_account, "bad name");
create_account.name = "create-test";
// bad inviting account
create_account.inviting_account = 83;
test_commands(false, {}, create_account, "bad name");
create_account.inviting_account = 0;
// low amount
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE - 1;
test_commands(false, {}, create_account, "low amount");
// good, plus funding
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 42;
test_commands(true, {}, create_account, "with funding");
}
TEST(cc_command, execute_transfer_deposit)
{
cryptonote::cc_command_transfer_t transfer;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
// deposit
transfer.cc_account = 4;
transfer.in_amount = 0;
transfer.public_key = keys.pub;
transfer.out_amount = 5000;
test_commands(true, setup, transfer, "valid");
// overflow
transfer.in_amount = 0;
transfer.public_key = keys.pub;
transfer.out_amount = std::numeric_limits<uint64_t>::max();
test_commands(false, setup, transfer, "overflow");
}
TEST(cc_command, execute_transfer_withdrawal)
{
cryptonote::cc_command_transfer_t transfer;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
// withdraw
transfer.cc_account = 4;
transfer.in_amount = 1000;
transfer.public_key = crypto::null_pkey;
transfer.out_amount = 0;
test_commands(true, setup, transfer, "valid");
// more than balance
transfer.in_amount = 1001;
transfer.public_key = crypto::null_pkey;
transfer.out_amount = 0;
test_commands(false, setup, transfer, "more than balance");
// more than balance
transfer.in_amount = std::numeric_limits<uint64_t>::max();
transfer.public_key = crypto::null_pkey;
transfer.out_amount = 0;
test_commands(false, setup, transfer, "max value");
}
TEST(cc_command, execute_transfer_mixed)
{
cryptonote::cc_command_transfer_t transfer;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys1 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys2 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys1.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
create_account.public_key = keys2.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2000;
create_account.name = "foo 2";
create_account.inviting_account = 0;
setup.push_back(create_account);
// transfer to another
transfer.cc_account = 4;
transfer.in_amount = 500;
transfer.public_key = keys2.pub;
transfer.out_amount = 500;
test_commands(true, setup, transfer, "valid");
// more than balance
transfer.in_amount = 1001;
transfer.public_key = keys2.pub;
transfer.out_amount = 1001;
test_commands(false, setup, transfer, "more than balance");
// those checks are made at the tx layer, since they're OK for non bare txes
#if 0
// receive more than sent
transfer.in_amount = 500;
transfer.public_key = keys2.pub;
transfer.out_amount = 501;
test_commands(false, setup, transfer, "more than sent");
// receive less than sent
transfer.in_amount = 500;
transfer.public_key = keys2.pub;
transfer.out_amount = 499;
test_commands(false, setup, transfer, "less than sent");
#endif
}
TEST(cc_command, execute_buy_land)
{
cryptonote::cc_command_buy_land_t buy_land;
cryptonote::cc_command_buy_land_t buy_land2;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys1 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys2 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys1.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 20*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
create_account.public_key = keys2.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 20*COIN;
create_account.name = "foo 2";
create_account.inviting_account = 0;
setup.push_back(create_account);
// good
buy_land.cc_account = 4;
buy_land.city = 0;
buy_land.x = 40;
buy_land.y = 50;
buy_land.wm1 = 8;
buy_land.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
test_commands(true, setup, buy_land, "valid");
// bad city
buy_land.city = 1;
test_commands(false, setup, buy_land, "bad city");
buy_land.city = 0;
// bad owner
buy_land.cc_account = 42;
test_commands(false, setup, buy_land, "bad owner");
buy_land.cc_account = 4;
// too costly
buy_land.wm1 = 255;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
test_commands(false, setup, buy_land, "too costly");
buy_land.wm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
// overlapping an existing flag
auto setup2 = setup;
buy_land2.cc_account = 5;
buy_land2.city = 0;
buy_land2.x = 20;
buy_land2.y = 52;
buy_land2.wm1 = 80;
buy_land2.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land2.x, buy_land2.y, buy_land2.x + buy_land2.wm1, buy_land2.y + buy_land2.hm1, buy_land2.cost));
setup2.push_back(buy_land2);
test_commands(false, setup2, buy_land, "overlapping");
}
// here
TEST(cc_command, execute_build)
{
cryptonote::cc_command_build_t build;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys1 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys2 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys1.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
create_account.public_key = keys2.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000*COIN;
create_account.name = "foo 2";
create_account.inviting_account = 0;
setup.push_back(create_account);
cryptonote::cc_command_buy_land_t buy_land;
buy_land.cc_account = 4;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 140;
buy_land.y = FIRST_CITY_Y + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
cryptonote::cc_command_buy_items_t buy_blocks;
buy_blocks.cc_account = 4;
cryptonote::cc_command_buy_items_t::entry_t e;
e.type = ITEM_BASIC_STONE;
e.amount = 800;
buy_blocks.entries.push_back(e);
e.type = ITEM_BASIC_WOOD;
e.amount = 1440;
buy_blocks.entries.push_back(e);
e.type = ITEM_LABOUR;
e.amount = 28000;
buy_blocks.entries.push_back(e);
setup.push_back(buy_blocks);
cryptonote::cc_command_building_settings_t building_settings;
building_settings.cc_account = 4;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
setup.push_back(building_settings);
// good
static const uint8_t block_data[] = { 2, ITEM_BASIC_STONE, 0, ITEM_BASIC_WOOD, 0x83, 0, 0, ITEM_BASIC_WOOD, 0, 0}; // S0W0000W0
build.cc_account = 4;
build.flag = 1 + PREDEFINED_BULDINGS;
build.dx = 1;
build.dy = 1;
build.wm1 = 2;
build.hm1 = 2;
build.height = 0;
build.remove = false;
build.block_data = std::vector<uint8_t>(block_data, block_data + sizeof(block_data));
test_commands(true, setup, build, "valid");
}
TEST(cc_command, execute_buy_blocks)
{
cryptonote::cc_command_buy_items_t buy_blocks;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
buy_blocks.entries.resize(1);
// good
buy_blocks.cc_account = 4;
buy_blocks.entries[0].type = ITEM_BASIC_STONE;
buy_blocks.entries[0].amount = 1;
test_commands(true, setup, buy_blocks, "valid");
// cannot buy block 0
buy_blocks.entries[0].type = 0;
buy_blocks.entries[0].amount = 1;
test_commands(false, setup, buy_blocks, "block 0");
// cannot buy 0 blocks
buy_blocks.entries[0].type = ITEM_BASIC_STONE;
buy_blocks.entries[0].amount = 0;
test_commands(false, setup, buy_blocks, "0 blocks");
}
TEST(cc_command, execute_buy_labour)
{
cryptonote::cc_command_buy_items_t buy_labour;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
buy_labour.entries.resize(1);
// good
buy_labour.cc_account = 4;
buy_labour.entries[0].type = ITEM_LABOUR;
buy_labour.entries[0].amount = 12;
test_commands(true, setup, buy_labour, "valid");
// cannot buy 0 labour
buy_labour.entries[0].type = ITEM_LABOUR;
buy_labour.entries[0].amount = 0;
test_commands(false, setup, buy_labour, "0 items");
}
TEST(cc_command, execute_trade_maker)
{
cryptonote::cc_command_trade_t trade;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 1000*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
cryptonote::cc_command_buy_items_t buy_blocks;
buy_blocks.cc_account = 4;
cryptonote::cc_command_buy_items_t::entry_t e;
e.type = ITEM_BASIC_STONE;
e.amount = 100;
buy_blocks.entries.push_back(e);
e.type = ITEM_BASIC_WOOD;
e.amount = 10;
buy_blocks.entries.push_back(e);
setup.push_back(buy_blocks);
// good bid
trade.cc_account = 4;
trade.bid = true;
trade.type = cryptonote::cc_command_trade_t::type_item;
trade.id = ITEM_BASIC_STONE;
trade.amount = 10;
trade.price = 10;
trade.accrual_start_height = 0;
trade.accrual = 0;
trade.accrual_price_limit = 0;
trade.flag_construction_height = 0;
trade.expiration = MIN_TRADE_EXPIRATION + 1000;
trade.cost = 0;
test_commands(true, setup, trade, "valid bid");
// good offer
trade.bid = false;
test_commands(true, setup, trade, "valid offer");
// bad type
trade.type = 0xff;
test_commands(false, setup, trade, "bad type");
trade.type = cryptonote::cc_command_trade_t::type_item;
// bad id
trade.id = 0;
test_commands(false, setup, trade, "bad id");
trade.id = ITEM_BASIC_STONE;
// bad expiration
trade.expiration = 1;
test_commands(false, setup, trade, "bad expiration");
trade.expiration = MIN_TRADE_EXPIRATION + 1000;
}
TEST(cc_command, execute_building_settings)
{
cryptonote::cc_command_buy_land_t buy_land;
cryptonote::cc_command_buy_items_t buy_blocks;
cryptonote::cc_command_building_settings_t building_settings;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys4 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys5 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys4.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2500*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
create_account.public_key = keys5.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2500*COIN;
create_account.name = "foo 2";
create_account.inviting_account = 0;
setup.push_back(create_account);
buy_blocks.cc_account = 4;
buy_blocks.entries.push_back({ITEM_BASIC_STONE, 1000});
buy_blocks.entries.push_back({ITEM_BASIC_WOOD, 500});
buy_blocks.entries.push_back({ITEM_LABOUR, 2500});
setup.push_back(buy_blocks);
buy_blocks.cc_account = 5;
setup.push_back(buy_blocks);
buy_land.cc_account = 4;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 140;
buy_land.y = FIRST_CITY_Y + 50;
buy_land.wm1 = 8;
buy_land.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
buy_land.cc_account = 5;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 240;
buy_land.y = FIRST_CITY_Y + 50;
buy_land.wm1 = 8;
buy_land.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
// good
building_settings.cc_account = 4;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
test_commands(true, setup, building_settings, "valid");
// invalid flag
building_settings.flag = 8 + PREDEFINED_BULDINGS;
test_commands(false, setup, building_settings, "invalid flag");
building_settings.flag = 1 + PREDEFINED_BULDINGS;
// valid flag owned by someone else
building_settings.flag = 2 + PREDEFINED_BULDINGS;
test_commands(false, setup, building_settings, "flag not owned");
building_settings.flag = 1 + PREDEFINED_BULDINGS;
// cannot set to empty
building_settings.role = ROLE_EMPTY;
test_commands(false, setup, building_settings, "empty role");
building_settings.role = ROLE_AGRICULTURAL;
// nor to out of range
building_settings.role = NUM_ROLES;
test_commands(false, setup, building_settings, "invalid role");
building_settings.role = ROLE_AGRICULTURAL;
// cannot set 0 economic power
building_settings.economic_power = 0;
test_commands(false, setup, building_settings, "0 economic power");
building_settings.economic_power = 100;
// nor a non canonical value that's in range
building_settings.economic_power = 404;
test_commands(false, setup, building_settings, "404 economic power");
building_settings.economic_power = 100;
// nor silly value
building_settings.economic_power = 100000;
test_commands(false, setup, building_settings, "silly economic power");
building_settings.economic_power = 100;
}
TEST(cc_command, execute_assign_items)
{
cryptonote::cc_command_buy_land_t buy_land;
cryptonote::cc_command_buy_items_t buy_items;
cryptonote::cc_command_building_settings_t building_settings;
cryptonote::cc_command_assign_items_t assign;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys4 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys5 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys4.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2000*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
create_account.public_key = keys5.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2000*COIN;
create_account.name = "foo 2";
create_account.inviting_account = 0;
setup.push_back(create_account);
buy_items.cc_account = 4;
buy_items.entries.push_back({ITEM_BASIC_STONE, 1000});
buy_items.entries.push_back({ITEM_BASIC_WOOD, 1500});
buy_items.entries.push_back({ITEM_LABOUR, 5200});
setup.push_back(buy_items);
buy_land.cc_account = 4;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 140;
buy_land.y = FIRST_CITY_Y + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
buy_land.cc_account = 5;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 240;
buy_land.y = FIRST_CITY_Y + 50;
buy_land.wm1 = 8;
buy_land.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
building_settings.cc_account = 4;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
setup.push_back(building_settings);
// good
assign.cc_account = 4;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items.push_back({ITEM_BASIC_STONE, 1});
test_commands(true, setup, assign, "valid");
// wrong account
assign.cc_account = 4444;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 1};
test_commands(false, setup, assign, "wrong account");
// wrong flag
assign.cc_account = 4;
assign.flag = 700 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 1};
test_commands(false, setup, assign, "wrong flag");
// 0 amount
assign.cc_account = 4;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 0};
test_commands(false, setup, assign, "0 amount");
// too many
assign.cc_account = 4;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 999999999};
test_commands(false, setup, assign, "too many");
// invalid item
assign.cc_account = 4;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1111111111, 1};
test_commands(false, setup, assign, "invalid item");
// duplicate item
assign.cc_account = 4;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {ITEM_BASIC_STONE, 1};
assign.items.push_back({ITEM_BASIC_STONE, 1});
test_commands(false, setup, assign, "duplicate item");
assign.items.pop_back();
}
TEST(cc_command, execute_palette)
{
cryptonote::cc_command_buy_land_t buy_land;
cryptonote::cc_command_buy_items_t buy_items;
cryptonote::cc_command_building_settings_t building_settings;
cryptonote::cc_command_palette_t palette;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys4 = cryptonote::keypair::generate(hw::get_device("default"));
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = keys4.pub;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE + 2500*COIN;
create_account.name = "foo";
create_account.inviting_account = 0;
setup.push_back(create_account);
buy_items.cc_account = 4;
buy_items.entries.push_back({ITEM_BASIC_STONE, 1000});
buy_items.entries.push_back({ITEM_BASIC_WOOD, 500});
buy_items.entries.push_back({ITEM_LABOUR, 2500});
setup.push_back(buy_items);
buy_land.cc_account = 4;
buy_land.city = 0;
buy_land.x = FIRST_CITY_X + 140;
buy_land.y = FIRST_CITY_Y + 150;
buy_land.wm1 = 8;
buy_land.hm1 = 4;
ASSERT_TRUE(cc::get_new_flag_cost(buy_land.x, buy_land.y, buy_land.x + buy_land.wm1, buy_land.y + buy_land.hm1, buy_land.cost));
setup.push_back(buy_land);
building_settings.cc_account = 4;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
setup.push_back(building_settings);
// empty
palette.cc_account = 4;
palette.flag = 1 + PREDEFINED_BULDINGS;
test_commands(false, setup, palette, "empty");
// good - set index 255 to default sandstone
palette.flag = 1 + PREDEFINED_BULDINGS;
palette.palette = {{255, cc::BLOCK_VARIANT_SANDSTONE_BASIC}};
test_commands(true, setup, palette, "good", [](TestDB *db) {
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(1 + PREDEFINED_BULDINGS, fd, NULL));
ASSERT_EQ(fd.palette.size(), 256);
ASSERT_EQ(fd.palette[0], cc::BLOCK_VARIANT_NONE);
ASSERT_EQ(fd.palette[254], cc::BLOCK_VARIANT_NONE);
ASSERT_EQ(fd.palette[255], cc::BLOCK_VARIANT_SANDSTONE_BASIC);
});
// bad - changes nothing
palette.palette = {{1, 0}};
test_commands(false, setup, palette, "no change");
// good - set 2 to sandstone cobblestones intead of default granite
palette.palette = {{2, cc::BLOCK_VARIANT_SANDSTONE_COBBLESTONES - cc::BLOCK_VARIANT_GRANITE_BASIC}};
test_commands(true, setup, palette, "good", [](TestDB *db) {
std::vector<uint16_t> default_palette;
cc::set_default_palette(default_palette, 0);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(1 + PREDEFINED_BULDINGS, fd, NULL));
ASSERT_EQ(fd.palette.size(), std::max<size_t>(3, default_palette.size()));
ASSERT_EQ(fd.palette[0], cc::BLOCK_VARIANT_NONE);
ASSERT_EQ(fd.palette[1], cc::BLOCK_VARIANT_SANDSTONE_BASIC);
ASSERT_EQ(fd.palette[2], cc::BLOCK_VARIANT_SANDSTONE_COBBLESTONES);
});
// good - set granite cobblestones instead of default sandstone
palette.palette = {{1, cc::BLOCK_VARIANT_GRANITE_COBBLESTONES - cc::BLOCK_VARIANT_SANDSTONE_BASIC}};
test_commands(true, setup, palette, "good", [](TestDB *db) {
std::vector<uint16_t> default_palette;
cc::set_default_palette(default_palette, 0);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(1 + PREDEFINED_BULDINGS, fd, NULL));
ASSERT_EQ(fd.palette.size(), default_palette.size());
ASSERT_EQ(fd.palette[0], cc::BLOCK_VARIANT_NONE);
ASSERT_EQ(fd.palette[1], cc::BLOCK_VARIANT_GRANITE_COBBLESTONES);
});
// bad - invalid variant
palette.palette = {{1, (int16_t)cc::NUM_BLOCK_VARIANTS}};
test_commands(false, setup, palette, "invalid variant");
// good - removes palette index 1
palette.palette = {{1, -cc::BLOCK_VARIANT_SANDSTONE_BASIC}};
test_commands(true, setup, palette, "good", [](TestDB *db) {
std::vector<uint16_t> default_palette;
cc::set_default_palette(default_palette, 0);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(1 + PREDEFINED_BULDINGS, fd, NULL));
ASSERT_EQ(fd.palette.size(), default_palette.size());
ASSERT_EQ(fd.palette[0], cc::BLOCK_VARIANT_NONE);
ASSERT_EQ(fd.palette[1], cc::BLOCK_VARIANT_NONE);
});
// bad - duplicate index
palette.palette = {{1, 1}, {1, 1}};
test_commands(false, setup, palette, "duplicate index");
// bad - unsorted indices
palette.palette = {{2, 1}, {1, 1}};
test_commands(false, setup, palette, "unsorted indices");
}
TEST(cc_block_encoding, empty)
{
std::vector<uint8_t> blocks;
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 0);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 1)
{
std::vector<uint8_t> blocks = {3};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 2);
ASSERT_EQ(encoded[idx++], 0);
ASSERT_EQ(encoded[idx++], 3);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 4)
{
std::vector<uint8_t> blocks = {3, 3, 3, 3};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 2);
ASSERT_EQ(encoded[idx++], 0x83);
ASSERT_EQ(encoded[idx++], 3);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 2_2)
{
std::vector<uint8_t> blocks = {3, 3, 2, 2};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 4);
ASSERT_EQ(encoded[idx++], 0x81);
ASSERT_EQ(encoded[idx++], 3);
ASSERT_EQ(encoded[idx++], 0x81);
ASSERT_EQ(encoded[idx++], 2);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 2_1_1_1_1_1_2)
{
std::vector<uint8_t> blocks = {3, 3, 1, 2, 3, 4, 5, 2, 2};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 10);
ASSERT_EQ(encoded[idx++], 0x81);
ASSERT_EQ(encoded[idx++], 3);
ASSERT_EQ(encoded[idx++], 0x4);
ASSERT_EQ(encoded[idx++], 1);
ASSERT_EQ(encoded[idx++], 2);
ASSERT_EQ(encoded[idx++], 3);
ASSERT_EQ(encoded[idx++], 4);
ASSERT_EQ(encoded[idx++], 5);
ASSERT_EQ(encoded[idx++], 0x81);
ASSERT_EQ(encoded[idx++], 2);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 1_1_1_1)
{
std::vector<uint8_t> blocks = {1, 2, 3, 4};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
EXPECT_EQ(encoded_len, 5);
EXPECT_EQ(encoded[idx++], 0x3);
EXPECT_EQ(encoded[idx++], 1);
EXPECT_EQ(encoded[idx++], 2);
EXPECT_EQ(encoded[idx++], 3);
EXPECT_EQ(encoded[idx++], 4);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 1_2_1)
{
std::vector<uint8_t> blocks = {1, 2, 2, 4};
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 6);
ASSERT_EQ(encoded[idx++], 0x0);
ASSERT_EQ(encoded[idx++], 1);
ASSERT_EQ(encoded[idx++], 0x81);
ASSERT_EQ(encoded[idx++], 2);
ASSERT_EQ(encoded[idx++], 0x0);
ASSERT_EQ(encoded[idx++], 4);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 127)
{
std::vector<uint8_t> blocks(127, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 2);
ASSERT_EQ(encoded[idx++], 0xfe);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 128)
{
std::vector<uint8_t> blocks(128, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 2);
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 129)
{
std::vector<uint8_t> blocks(129, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 4);
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_EQ(encoded[idx++], 0);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 384)
{
std::vector<uint8_t> blocks(384, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 6);
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 65536)
{
std::vector<uint8_t> blocks(65536, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_TRUE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
ASSERT_EQ(encoded_len, 512*2);
for (int i = 0; i < 512; ++i)
{
ASSERT_EQ(encoded[idx++], 0xff);
ASSERT_EQ(encoded[idx++], 9);
}
ASSERT_TRUE(cc::decode_blocks(encoded, encoded_len, decoded, &decoded_len));
ASSERT_EQ(decoded_len, blocks.size());
ASSERT_EQ(blocks, std::vector<uint8_t>(decoded, decoded + decoded_len));
}
TEST(cc_block_encoding, 65537)
{
std::vector<uint8_t> blocks(65537, 9);
uint8_t encoded[65536], decoded[65536];
uint32_t encoded_len, decoded_len;
unsigned int idx = 0;
ASSERT_FALSE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
}
TEST(cc_block_encoding, encoding_too_large)
{
std::vector<uint8_t> blocks;
blocks.resize(65500);
for (int i = 0; i < 65500; ++i)
blocks[i] = i;
uint8_t encoded[65536];
uint32_t encoded_len;
ASSERT_FALSE(cc::encode_blocks(blocks.data(), blocks.size(), encoded, &encoded_len));
}
TEST(cc_block_encoding, decoding_too_large_new_run)
{
std::vector<uint8_t> encoded;
for (int i = 0; i < 513; ++i)
{
encoded.push_back(0xff);
encoded.push_back(0);
}
uint8_t decoded[65537];
decoded[65536] = 0x42;
uint32_t decoded_len;
EXPECT_FALSE(cc::decode_blocks(encoded.data(), encoded.size(), decoded, &decoded_len));
ASSERT_EQ(decoded[65536], 0x42);
}
TEST(cc_block_encoding, decoding_too_large_in_raw_run)
{
std::vector<uint8_t> encoded;
for (int i = 0; i < 511; ++i)
{
encoded.push_back(0xff);
encoded.push_back(0);
}
encoded.push_back(0xfe);
encoded.push_back(0);
encoded.push_back(2);
encoded.push_back(0);
encoded.push_back(0);
uint8_t decoded[65537];
decoded[65536] = 0x42;
uint32_t decoded_len;
EXPECT_FALSE(cc::decode_blocks(encoded.data(), encoded.size(), decoded, &decoded_len));
ASSERT_EQ(decoded[65536], 0x42);
}
TEST(cc_block_encoding, decoding_too_large_in_rle_run)
{
std::vector<uint8_t> encoded;
for (int i = 0; i < 511; ++i)
{
encoded.push_back(0xff);
encoded.push_back(0);
}
encoded.push_back(0xfe);
encoded.push_back(0);
encoded.push_back(0x82);
encoded.push_back(0);
uint8_t decoded[65537];
decoded[65536] = 0x42;
uint32_t decoded_len;
EXPECT_FALSE(cc::decode_blocks(encoded.data(), encoded.size(), decoded, &decoded_len));
ASSERT_EQ(decoded[65536], 0x42);
}
TEST(cc_selection, empty)
{
Selection s;
ASSERT_TRUE(s.is_empty());
}
TEST(cc_selection, union)
{
Selection s_empty;
Selection s_one(4,4,4,4);
Selection s;
Selection s_two_diag_slash(4,4,5,5);
Selection s_two_diag_backslash(4,4,5,5);
s_two_diag_slash.clear(4, 4);
s_two_diag_slash.clear(5, 5);
ASSERT_EQ(s_two_diag_slash.get_num_selected_points(), 2);
s_two_diag_backslash.clear(4, 5);
s_two_diag_backslash.clear(5, 4);
ASSERT_EQ(s_two_diag_backslash.get_num_selected_points(), 2);
s = s_empty;
s.set_union(s_empty);
ASSERT_EQ(s, s_empty);
s = s_empty;
s.set_union(s_one);
ASSERT_EQ(s, s_one);
s = s_one;
s.set_union(s_empty);
ASSERT_EQ(s, s_one);
s = s_one;
s.set_union(s_one);
ASSERT_EQ(s, s_one);
s = s_empty;
s.set_union(s_two_diag_slash);
ASSERT_EQ(s.get_num_selected_points(), 2);
s = s_one;
s.set_union(s_two_diag_slash);
ASSERT_EQ(s.get_num_selected_points(), 3);
s = s_one;
s.set_union(s_two_diag_backslash);
ASSERT_EQ(s.get_num_selected_points(), 2);
ASSERT_EQ(s, s_two_diag_backslash);
s = s_two_diag_slash;
s.set_union(s_two_diag_slash);
ASSERT_EQ(s.get_num_selected_points(), 2);
ASSERT_EQ(s, s_two_diag_slash);
s = s_two_diag_slash;
s.set_union(s_two_diag_backslash);
ASSERT_EQ(s.get_num_selected_points(), 4);
Selection s1, s3;
s1.set_rectangle(1, 1, 1, 1);
s3.set_rectangle(3, 3, 3, 3);
s = s1;
s.set_union(s3);
ASSERT_EQ(s.get_num_selected_points(), 2);
ASSERT_TRUE(s.is_selected(1, 1));
ASSERT_FALSE(s.is_selected(2, 2));
ASSERT_TRUE(s.is_selected(3, 3));
s1.set_rectangle(2, 1, 2, 3);
s3.set_rectangle(1, 2, 3, 2);
s = s1;
s.set_union(s3);
ASSERT_EQ(s.get_num_selected_points(), 5);
ASSERT_TRUE(s.is_selected(2, 1));
ASSERT_TRUE(s.is_selected(2, 2));
ASSERT_TRUE(s.is_selected(2, 3));
ASSERT_TRUE(s.is_selected(1, 2));
ASSERT_TRUE(s.is_selected(3, 2));
}
TEST(cc_selection, intersection)
{
Selection s_empty;
Selection s_one(4,4,4,4);
Selection s;
Selection s_two_diag_slash(4,4,5,5);
Selection s_two_diag_backslash(4,4,5,5);
s_two_diag_slash.clear(4, 4);
s_two_diag_slash.clear(5, 5);
ASSERT_EQ(s_two_diag_slash.get_num_selected_points(), 2);
s_two_diag_backslash.clear(4, 5);
s_two_diag_backslash.clear(5, 4);
ASSERT_EQ(s_two_diag_backslash.get_num_selected_points(), 2);
s = s_empty;
s.set_intersection(s_empty);
ASSERT_EQ(s, s_empty);
s = s_one;
s.set_intersection(s_empty);
ASSERT_EQ(s, s_empty);
s = s_empty;
s.set_intersection(s_one);
ASSERT_EQ(s, s_empty);
s = s_one;
s.set_intersection(s_one);
ASSERT_EQ(s, s_one);
s = s_one;
s.set_intersection(s_two_diag_backslash);
ASSERT_EQ(s, s_one);
s = s_one;
s.set_intersection(s_two_diag_slash);
ASSERT_EQ(s.get_num_selected_points(), 0);
s = s_two_diag_backslash;
s.set_intersection(s_two_diag_slash);
ASSERT_EQ(s.get_num_selected_points(), 0);
Selection s1, s3;
s1.set_rectangle(2, 1, 2, 3);
s3.set_rectangle(1, 2, 3, 2);
s = s1;
s.set_intersection(s3);
ASSERT_EQ(s.get_num_selected_points(), 1);
ASSERT_TRUE(s.is_selected(2, 2));
}
TEST(cc_selection, difference)
{
Selection s_empty;
Selection s_one(4,4,4,4);
Selection s;
Selection s_two_diag_slash(4,4,5,5);
Selection s_two_diag_backslash(4,4,5,5);
s_two_diag_slash.clear(4, 4);
s_two_diag_slash.clear(5, 5);
ASSERT_EQ(s_two_diag_slash.get_num_selected_points(), 2);
s_two_diag_backslash.clear(4, 5);
s_two_diag_backslash.clear(5, 4);
ASSERT_EQ(s_two_diag_backslash.get_num_selected_points(), 2);
s = s_empty;
s.set_difference(s_empty);
ASSERT_EQ(s, s_empty);
s = s_one;
s.set_difference(s_empty);
ASSERT_EQ(s, s_one);
s = s_empty;
s.set_difference(s_one);
ASSERT_EQ(s, s_empty);
s = s_one;
s.set_difference(s_one);
ASSERT_EQ(s.get_num_selected_points(), 0);
s = s_one;
s.set_difference(s_two_diag_backslash);
ASSERT_EQ(s.get_num_selected_points(), 0);
s = s_one;
s.set_difference(s_two_diag_slash);
ASSERT_EQ(s, s_one);
s = s_two_diag_backslash;
s.set_difference(s_two_diag_slash);
ASSERT_EQ(s, s_two_diag_backslash);
Selection s1, s3;
s1.set_rectangle(2, 1, 2, 3);
s3.set_rectangle(1, 2, 3, 2);
s = s1;
s.set_difference(s3);
ASSERT_EQ(s.get_num_selected_points(), 2);
ASSERT_TRUE(s.is_selected(2, 1));
ASSERT_TRUE(s.is_selected(2, 3));
}
TEST(cc_magica, 1x1x1x1)
{
MagicaModel m;
std::list<MagicaModel> mm;
std::string s;
m.width = m.height = m.depth = 1;
m.data.push_back(1);
ASSERT_TRUE(save_magica_data({m}, s));
ASSERT_TRUE(load_magica_data(mm, s));
ASSERT_EQ(mm.size(), 1);
ASSERT_EQ(mm.front(), m);
}
TEST(cc_magica, 1x255x255x255)
{
MagicaModel m;
std::list<MagicaModel> mm;
std::string s;
m.width = m.height = m.depth = 255;
m.data.resize(255*255*255);
for (size_t i = 0; i < 255*255*255; ++i)
m.data[i] = rand() & 1;
ASSERT_TRUE(save_magica_data({m}, s));
ASSERT_TRUE(load_magica_data(mm, s));
ASSERT_EQ(mm.size(), 1);
ASSERT_EQ(mm.front(), m);
}
TEST(cc_magica, 12x36x19_4x8x2_1x1x2)
{
MagicaModel m0, m1, m2;
std::list<MagicaModel> mm;
std::string s;
m0.width = 12;
m0.height = 36;
m0.depth = 19;
for (size_t i = 0; i < 12 * 36 * 19; ++i)
m0.data.push_back(rand() & 1);
m1.width = 4;
m1.height = 8;
m1.depth = 2;
for (size_t i = 0; i < 4 * 8 * 2; ++i)
m1.data.push_back(rand() & 1);
m2.width = 1;
m2.height = 1;
m2.depth = 2;
for (size_t i = 0; i < 1 * 1 * 2; ++i)
m2.data.push_back(rand() & 1);
ASSERT_TRUE(save_magica_data({m0, m1, m2}, s));
ASSERT_TRUE(load_magica_data(mm, s));
ASSERT_EQ(mm.size(), 3);
std::list<MagicaModel>::const_iterator i = mm.begin();
ASSERT_EQ(*i++, m0);
ASSERT_EQ(*i++, m1);
ASSERT_EQ(*i++, m2);
}
static void test_game_command(TestDB *db, bool good, const cryptonote::cc_command_game_update_t &cmd, const char *label)
{
std::unique_ptr<cryptonote::Blockchain> bc;
cryptonote::tx_memory_pool txpool(*bc);
bc.reset(new cryptonote::Blockchain(txpool));
struct get_test_options {
const std::pair<uint8_t, uint64_t> hard_forks[2];
const cryptonote::test_options test_options = {
hard_forks,
0,
};
get_test_options(): hard_forks{std::make_pair((uint8_t)12, (uint64_t)0), std::make_pair((uint8_t)0, (uint64_t)0)} {}
} opts;
cryptonote::Blockchain *blockchain = bc.get();
ASSERT_TRUE(blockchain != NULL) << label;
bool r = blockchain->init(db, cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL);
ASSERT_TRUE(r) << label;
uint64_t cost = cc::get_cc_command_cost(cmd);
ASSERT_EQ(cost, 0);
cryptonote::tx_verification_context tvc{};
r = cc::check_cc_command(*db, cmd, tvc);
ASSERT_EQ(r, good) << label;
if (good)
{
const TestDB::state_t s0 = db->get_state();
cc::game_events_t events;
r = cc::execute_cc_command(*db, cmd, events);
ASSERT_TRUE(r) << label;
r = cc::revert_cc_command(*db, cmd);
ASSERT_TRUE(r) << label;
const TestDB::state_t s1 = db->get_state();
ASSERT_EQ(s0, s1) << label;
}
}
TEST(cc_game, empty)
{
cryptonote::cc_command_game_update_t cmd;
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg = cc::create_cc_game_update_command(*db, events);
cryptonote::cc_command_game_update_t cg1 = cc::create_cc_game_update_command(*db, events);
ASSERT_EQ(cg, cg1);
ASSERT_EQ(cg.cities.size(), 1);
const auto &c = cg.cities[0];
ASSERT_EQ(c.city_id, 0);
ASSERT_EQ(c.derelict.size(), 0);
ASSERT_EQ(c.repair.size(), 0);
ASSERT_EQ(c.balances.size(), 0);
ASSERT_EQ(c.item_balances.size(), 0);
}
TEST(cc_game, derelict)
{
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg, cg0 = cc::create_cc_game_update_command(*db, events);
cryptonote::keypair account_keys = cryptonote::keypair::generate(hw::get_device("default"));
uint32_t city_id = 0;
uint32_t account_id = bdb->allocate_new_cc_account(account_keys.pub, "");
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, 49);
db->set_cc_flag_tiles(flag_id, {{},{},{},{4},{},{6},{},{0,1,2},{}});
db->set_cc_account_balance(1, 1000*COIN); // mayor
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.role, ROLE_EMPTY);
ASSERT_EQ(fd.repair, 0);
bdb->set_cc_flag_role(flag_id, ROLE_AGRICULTURAL, 100, 100);
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.role, ROLE_AGRICULTURAL);
const uint32_t just_enough_repair = (MAX_DECAY_RATE * INACTIVE_DECAY_PERCENTAGE / 100) * NO_MILITARY_DECAY_MULTIPLIER / 100 + 1;
db->set_cc_flag_repair(flag_id, just_enough_repair);
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.repair, just_enough_repair);
cg = cc::create_cc_game_update_command(*db, events);
ASSERT_NE(cg0, cg) << "cg0: " << cryptonote::obj_to_json_str(cg0) << ", cg: " << cryptonote::obj_to_json_str(cg);
ASSERT_EQ(cg.cities.size(), 1);
const auto &c1 = cg.cities[0];
ASSERT_EQ(c1.derelict.size(), 0);
ASSERT_EQ(c1.defaulted.size(), 1);
ASSERT_EQ(c1.defaulted[0].id, flag_id);
ASSERT_EQ(c1.repair.size(), 0 + PREDEFINED_BULDINGS);
// give enough money for land tax
db->set_cc_flag_repair(flag_id, just_enough_repair);
bdb->set_cc_account_balance(account_id, 1000*COIN);
cg = cc::create_cc_game_update_command(*db, events);
ASSERT_NE(cg0, cg);
ASSERT_EQ(cg.cities.size(), 1);
const auto &c2 = cg.cities[0];
ASSERT_EQ(c2.defaulted.size(), 0);
ASSERT_EQ(c2.derelict.size(), 0);
ASSERT_EQ(c2.repair.size(), 1 + PREDEFINED_BULDINGS);
ASSERT_EQ(PREDEFINED_BULDINGS, 1);
ASSERT_EQ(c2.repair[0].delta_id, 1);
ASSERT_EQ(c2.repair[1].delta_id, 0);
db->set_cc_flag_repair(flag_id, MIN_DECAY_RATE);
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.repair, MIN_DECAY_RATE);
cg = cc::create_cc_game_update_command(*db, events);
ASSERT_NE(cg0, cg);
ASSERT_EQ(cg.cities.size(), 1);
const auto &c3 = cg.cities[0];
ASSERT_EQ(c3.defaulted.size(), 0);
ASSERT_EQ(c3.derelict.size(), 1);
ASSERT_EQ(c3.derelict[0].id, flag_id);
ASSERT_EQ(c3.repair.size(), 0 + PREDEFINED_BULDINGS);
}
TEST(cc_game, subsidy)
{
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::game_events_t events;
cryptonote::keypair account_keys[4];
// a few players
std::map<uint32_t, uint32_t> item_balances;
item_balances[ITEM_FOOD_VEGETABLES] = 10000;
item_balances[ITEM_BASIC_WOOD] = 10000;
uint32_t players[sizeof(account_keys) / sizeof(account_keys[0])];
for (size_t i = 0; i < sizeof(account_keys) / sizeof(account_keys[0]); ++i)
{
account_keys[i] = cryptonote::keypair::generate(hw::get_device("default"));
players[i] = bdb->allocate_new_cc_account(account_keys[i].pub, "Player " + std::to_string(i));
bdb->set_cc_account_balance(players[i], 100000000000);
bdb->set_cc_account_item_balances(players[i], item_balances);
}
// two extra cities
uint32_t city1_id = bdb->allocate_new_cc_city(500, 500, players[1], "city 1");
uint32_t city2_id = bdb->allocate_new_cc_city(5000, 5000, players[2], "city 2");
uint32_t treasuries[3];
ASSERT_TRUE(bdb->get_cc_city_treasury(0, treasuries[0]));
ASSERT_TRUE(bdb->get_cc_city_treasury(city1_id, treasuries[1]));
ASSERT_TRUE(bdb->get_cc_city_treasury(city2_id, treasuries[2]));
// build some stuff in all cities but one
uint32_t flags[8];
uint32_t flag_cities[8] = { 0, 0, 0, 0, 2, 2, 2, 0, };
uint32_t dx = 100, dy = 100;
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i)
{
const uint32_t ox = flag_cities[i] == 0 ? FIRST_CITY_X : 5000;
const uint32_t oy = flag_cities[i] == 0 ? FIRST_CITY_Y : 5000;
flags[i] = bdb->allocate_new_cc_flag(NULL, players[i%(sizeof(players)/sizeof(players[0]))], flag_cities[i], ox + dx, oy + dy, ox + dx + 80 - 1, oy + dy + 80 - 1, 200);
bdb->set_cc_flag_role(flags[i], ROLE_RESIDENTIAL1, 100, 200);
bdb->set_cc_flag_repair(flags[i], DEFAULT_REPAIR);
if (i != 5)
bdb->set_cc_flag_tiles(flags[i], std::vector<std::vector<uint8_t>>(80*80, std::vector<uint8_t>(1, 1)));
dx += 100;
dy += 100;
}
for (int i = 1; i <= GAME_UPDATE_FREQUENCY; ++i)
{
db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(i-1), 1, {});
}
cryptonote::cc_command_game_update_t cg = cc::create_cc_game_update_command(*db, events);
ASSERT_EQ(cg.cities.size(), 3);
ASSERT_EQ(cg.cities[0].city_id, 0);
ASSERT_EQ(cg.cities[0].subsidy, 224959616539);
ASSERT_EQ(cg.cities[1].city_id, city1_id);
ASSERT_EQ(cg.cities[1].subsidy, 0);
ASSERT_EQ(cg.cities[2].city_id, city2_id);
ASSERT_EQ(cg.cities[2].subsidy, 134975769923);
uint64_t expected_subsidy = bdb->get_block_already_generated_coins(GAME_UPDATE_FREQUENCY - 1) * BLOCK_REWARD_SUBSIDY;
ASSERT_EQ(expected_subsidy, 224959616539 + 134975769923);
bool found[2] = { false, false };
for (const auto &e: events)
{
if (e.account == treasuries[0] && strstr(e.event.c_str(), "Gets 2249.59616539 subsidy"))
found[0] = true;
ASSERT_FALSE(e.account == treasuries[1] && strstr(e.event.c_str(), "subsidy"));
if (e.account == treasuries[2] && strstr(e.event.c_str(), "Gets 1349.75769923 subsidy"))
found[1] = true;
}
ASSERT_TRUE(found[0]);
ASSERT_TRUE(found[1]);
}
TEST(cc_game, generator)
{
cryptonote::cc_command_game_update_t cmd;
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
bdb->set_cc_account_balance(1, 1000*COIN); // mayor
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg, cg0 = cc::create_cc_game_update_command(*db, events);
cryptonote::keypair account_keys = cryptonote::keypair::generate(hw::get_device("default"));
uint32_t city_id = 0;
uint32_t account_id = bdb->allocate_new_cc_account(account_keys.pub, "");
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, FIRST_CITY_X + 44, FIRST_CITY_Y + 44, FIRST_CITY_X + 47, FIRST_CITY_Y + 47, 49);
const uint8_t S = ITEM_BASIC_STONE;
const uint8_t W = ITEM_BASIC_WOOD;
std::vector<uint8_t> column = {S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S};
db->set_cc_flag_tiles(flag_id, {{},{},{},{4},{},{6},{},{0,S,W},{}, {}, column, column, column, {S}, {S}, {}});
db->set_cc_flag_repair(flag_id, 1000000);
bdb->set_cc_flag_role(flag_id, ROLE_SAWMILL, 100, 255);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd, NULL));
ASSERT_EQ(fd.role, ROLE_SAWMILL);
uint64_t monetary_cost;
std::vector<std::pair<uint32_t, uint32_t>> costs, production;
ASSERT_TRUE(cc::get_building_cost_production(GAME_UPDATE_FREQUENCY, fd.x0, fd.y0, fd.x1, fd.y1, fd.role, fd.economic_power, GAME_UPDATE_FREQUENCY - fd.construction_height, fd.potential, monetary_cost, costs, production));
ASSERT_EQ(monetary_cost, 0);
ASSERT_EQ(costs.size(), 3);
ASSERT_EQ(costs[0].first, ITEM_BASIC_WOOD);
ASSERT_GT(costs[0].second, 0);
ASSERT_EQ(costs[1].first, ITEM_BASIC_STONE);
ASSERT_GT(costs[1].second, 0);
ASSERT_EQ(costs[2].first, ITEM_LABOUR);
ASSERT_GT(costs[2].second, 0);
ASSERT_EQ(production.size(), 3);
ASSERT_EQ(production[0].first, ITEM_HIGH_WOOD);
ASSERT_EQ(production[1].first, ITEM_MEDIUM_WOOD);
ASSERT_EQ(production[2].first, ITEM_BASIC_WOOD);
ASSERT_GT(9 * production[0].second + 3 * production[1].second + production[2].second, costs[0].second);
bdb->set_cc_account_balance(account_id, 1000*COIN);
std::map<uint32_t, uint32_t> item_balances;
item_balances[ITEM_BASIC_WOOD] = 1000000;
item_balances[ITEM_BASIC_STONE] = 1000000;
item_balances[ITEM_LABOUR] = 1000000;
item_balances[ITEM_FOOD_VEGETABLES] = 1000000;
bdb->set_cc_account_item_balances(account_id, item_balances);
cg = cc::create_cc_game_update_command(*db, events);
ASSERT_EQ(cg.cities.size(), 1);
const auto &c0 = cg.cities[0];
ASSERT_EQ(c0.defaulted.size(), 0);
ASSERT_EQ(c0.derelict.size(), 0);
ASSERT_EQ(c0.repair.size(), 1 + PREDEFINED_BULDINGS);
ASSERT_EQ(c0.item_balances.size(), 6);
uint32_t item_id = 0xffffffffu;
ASSERT_EQ(c0.item_balances[0].delta_account, account_id);
item_id += c0.item_balances[0].delta_item + 1;
ASSERT_EQ(item_id, ITEM_BASIC_STONE);
ASSERT_LT(c0.item_balances[0].delta, 0);
ASSERT_EQ(c0.item_balances[1].delta_account, 0);
item_id += c0.item_balances[1].delta_item + 1;
ASSERT_EQ(item_id, ITEM_BASIC_WOOD);
ASSERT_GT(c0.item_balances[1].delta, 0);
ASSERT_EQ(c0.item_balances[2].delta_account, 0);
item_id += c0.item_balances[2].delta_item + 1;
ASSERT_EQ(item_id, ITEM_MEDIUM_WOOD);
ASSERT_GE(c0.item_balances[2].delta, 0);
ASSERT_EQ(c0.item_balances[3].delta_account, 0);
item_id += c0.item_balances[3].delta_item + 1;
ASSERT_EQ(item_id, ITEM_HIGH_WOOD);
ASSERT_GE(c0.item_balances[3].delta, 0);
ASSERT_EQ(c0.item_balances[4].delta_account, 0);
item_id += c0.item_balances[4].delta_item + 1;
ASSERT_EQ(item_id, ITEM_LABOUR);
ASSERT_LT(c0.item_balances[4].delta, 0);
ASSERT_EQ(c0.item_balances[5].delta_account, 0);
item_id += c0.item_balances[5].delta_item + 1;
ASSERT_EQ(item_id, ITEM_FOOD_VEGETABLES);
ASSERT_LT(c0.item_balances[5].delta, 0);
}
TEST(cc, type_tags)
{
cryptonote::cc_command_t cmd;
cmd = cryptonote::cc_command_none_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x00);
cmd = cryptonote::cc_command_create_account_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x01);
cmd = cryptonote::cc_command_transfer_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x02);
cmd = cryptonote::cc_command_buy_land_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x03);
cmd = cryptonote::cc_command_build_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x04);
cmd = cryptonote::cc_command_buy_items_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x05);
cmd = cryptonote::cc_command_game_update_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x06);
cmd = cryptonote::cc_command_trade_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x07);
cmd = cryptonote::cc_command_building_settings_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x08);
cmd = cryptonote::cc_command_assign_items_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x09);
cmd = cryptonote::cc_command_repair_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0a);
cmd = cryptonote::cc_command_rename_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0b);
cmd = cryptonote::cc_command_chat_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0c);
cmd = cryptonote::cc_command_demolish_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0d);
cmd = cryptonote::cc_command_research_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0e);
cmd = cryptonote::cc_command_found_city_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x0f);
cmd = cryptonote::cc_command_give_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x10);
cmd = cryptonote::cc_command_match_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x11);
cmd = cryptonote::cc_command_new_item_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x12);
cmd = cryptonote::cc_command_dividend_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x13);
cmd = cryptonote::cc_command_ignore_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x14);
cmd = cryptonote::cc_command_event_badge_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x15);
cmd = cryptonote::cc_command_resize_flag_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x16);
cmd = cryptonote::cc_command_destroy_items_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x17);
cmd = cryptonote::cc_command_define_attribute_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x18);
cmd = cryptonote::cc_command_increase_attribute_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x19);
cmd = cryptonote::cc_command_edit_player_profile_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1a);
cmd = cryptonote::cc_command_dice_roll_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1b);
cmd = cryptonote::cc_command_hunt_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1c);
cmd = cryptonote::cc_command_palette_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1d);
cmd = cryptonote::cc_command_fight_fire_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1e);
cmd = cryptonote::cc_command_service_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x1f);
cmd = cryptonote::cc_command_redeem_account_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x20);
cmd = cryptonote::cc_command_destroy_flag_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x21);
}
TEST(cc, staff)
{
ASSERT_EQ(0, cc::get_staff(0));
ASSERT_EQ(0, cc::get_staff(STAFF_BUILDINGS_PER_PLAYER));
ASSERT_EQ(1, cc::get_staff(STAFF_BUILDINGS_PER_PLAYER + 1));
ASSERT_EQ(1, cc::get_staff(STAFF_BUILDINGS_PER_PLAYER + STAFF_BUILDINGS_PER_STAFF));
ASSERT_EQ(2, cc::get_staff(STAFF_BUILDINGS_PER_PLAYER + STAFF_BUILDINGS_PER_STAFF + 1));
for (uint32_t buildings = 0; buildings < 1000; ++buildings)
ASSERT_GE(cc::get_staff(buildings + 1), cc::get_staff(buildings));
}
TEST(cc, distance)
{
/* 0 1 2 3 4
* 0 x x x x x
* 1 x x x x x
* 2 x x x x x
* 3 x x x x x
* 4 x x x x x
*/
ASSERT_EQ(2, cc::get_distance(0, 0, 1, 1, 3, 3, 4, 4));
ASSERT_EQ(2, cc::get_distance(3, 3, 4, 4, 0, 0, 1, 1));
ASSERT_EQ(0, cc::get_distance(0, 0, 4, 4, 1, 1, 3, 3));
ASSERT_EQ(0, cc::get_distance(1, 1, 3, 3, 0, 0, 4, 4));
ASSERT_EQ(1, cc::get_distance(0, 0, 1, 1, 2, 2, 4, 4)); // top left
ASSERT_EQ(1, cc::get_distance(1, 0, 3, 1, 1, 2, 3, 4)); // top
ASSERT_EQ(1, cc::get_distance(3, 0, 4, 1, 0, 2, 2, 4)); // top right
ASSERT_EQ(1, cc::get_distance(0, 1, 1, 3, 2, 1, 4, 3)); // left
ASSERT_EQ(1, cc::get_distance(3, 1, 4, 3, 0, 1, 2, 3)); // right
ASSERT_EQ(1, cc::get_distance(0, 3, 1, 4, 2, 0, 4, 2)); // bottom left
ASSERT_EQ(1, cc::get_distance(1, 3, 3, 4, 1, 0, 3, 2)); // bottom
ASSERT_EQ(1, cc::get_distance(3, 3, 4, 4, 0, 0, 2, 2)); // bottom right
ASSERT_EQ(0, cc::get_distance(0, 0, 2, 2, 2, 2, 4, 4)); // top left
ASSERT_EQ(0, cc::get_distance(1, 0, 3, 2, 1, 2, 3, 4)); // top
ASSERT_EQ(0, cc::get_distance(2, 0, 4, 2, 0, 2, 2, 4)); // top right
ASSERT_EQ(0, cc::get_distance(0, 1, 2, 3, 2, 1, 4, 3)); // left
ASSERT_EQ(0, cc::get_distance(2, 1, 4, 3, 0, 1, 2, 3)); // right
ASSERT_EQ(0, cc::get_distance(0, 2, 2, 2, 2, 0, 4, 2)); // bottom left
ASSERT_EQ(0, cc::get_distance(1, 2, 3, 4, 1, 0, 3, 2)); // bottom
ASSERT_EQ(0, cc::get_distance(2, 2, 4, 4, 0, 0, 2, 2)); // bottom right
}
TEST(cc, distance_percentage)
{
ASSERT_EQ(0, cc::get_distance_percentage(0, 1));
ASSERT_EQ(0, cc::get_distance_percentage(0, 1000));
ASSERT_EQ(100, cc::get_distance_percentage(1, 1));
ASSERT_EQ(100, cc::get_distance_percentage(1000, 1000));
ASSERT_EQ(200, cc::get_distance_percentage(2000, 1000));
ASSERT_EQ(50, cc::get_distance_percentage(500, 1000, 50));
ASSERT_EQ(100, cc::get_distance_percentage(500, 1000, 100));
ASSERT_EQ(220, cc::get_distance_percentage(2200, 1000));
ASSERT_EQ(221, cc::get_distance_percentage(2201, 1000));
ASSERT_EQ(221, cc::get_distance_percentage(2209, 1000));
ASSERT_EQ(221, cc::get_distance_percentage(2210, 1000));
}
TEST(cc, connection_squares)
{
unsigned d;
// disjoint
ASSERT_EQ(cc::connection_squares(0, 0, 0, 0, 2, 2, 2, 2, d), 0);
// corners
ASSERT_EQ(cc::connection_squares(1, 1, 1, 1, 2, 2, 2, 2, d), 0);
ASSERT_EQ(cc::connection_squares(1, 1, 1, 1, 2, 2, 2, 2, d), 0);
ASSERT_EQ(cc::connection_squares(2, 1, 2, 1, 1, 2, 1, 2, d), 0);
ASSERT_EQ(cc::connection_squares(1, 2, 1, 2, 2, 1, 2, 1, d), 0);
// full tile
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 1, 2, 1, 2, d), 1); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 2, 1, 2, 1, d), 1); ASSERT_EQ(d, cc::dir_north);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 2, 3, 2, 3, d), 1); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 3, 2, 3, 2, d), 1); ASSERT_EQ(d, cc::dir_west);
// one tile in the middle
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 1, 1, 1, 3, d), 1); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 1, 1, 3, 1, d), 1); ASSERT_EQ(d, cc::dir_north);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 1, 3, 3, 3, d), 1); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(2, 2, 2, 2, 3, 1, 3, 3, d), 1); ASSERT_EQ(d, cc::dir_west);
// 0 1 2 3 4 5
// . . . . . . 5
// . . . . . . 4
// . . x x . . 3
// . . x x . . 2
// . . . . . . 1
// . . . . . . 0
// one tile, corners
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 0, 1, 2, d), 1); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 0, 2, 1, d), 1); ASSERT_EQ(d, cc::dir_north);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 3, 0, 5, 1, d), 1); ASSERT_EQ(d, cc::dir_north);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 4, 0, 5, 2, d), 1); ASSERT_EQ(d, cc::dir_west);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 3, 1, 5, d), 1); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 4, 2, 5, d), 1); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 3, 4, 5, 5, d), 1); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 4, 3, 5, 5, d), 1); ASSERT_EQ(d, cc::dir_west);
// full 2
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 0, 1, 5, d), 2); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 0, 5, 1, d), 2); ASSERT_EQ(d, cc::dir_north);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 4, 0, 5, 5, d), 2); ASSERT_EQ(d, cc::dir_west);
ASSERT_EQ(cc::connection_squares(2, 2, 3, 3, 0, 4, 5, 5, d), 2); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(0, 0, 1, 5, 2, 2, 3, 3, d), 2); ASSERT_EQ(d, cc::dir_west);
ASSERT_EQ(cc::connection_squares(0, 0, 5, 1, 2, 2, 3, 3, d), 2); ASSERT_EQ(d, cc::dir_south);
ASSERT_EQ(cc::connection_squares(4, 0, 5, 5, 2, 2, 3, 3, d), 2); ASSERT_EQ(d, cc::dir_east);
ASSERT_EQ(cc::connection_squares(0, 4, 5, 5, 2, 2, 3, 3, d), 2); ASSERT_EQ(d, cc::dir_north);
}
TEST(cc_influence, monotonic)
{
static const uint8_t potential[] = {0, 80, 105, 180, 255};
static const uint32_t gap = 4;
for (uint32_t size = 16; size <= 256; size *= 2)
{
uint32_t prev = 0;
for (uint32_t p: potential)
{
const uint32_t i = cc::get_cc_influence(0, 0, size, size, ROLE_AGRICULTURAL, 100, p, cc::SPECIAL_EVENT_NONE);
ASSERT_GE(i, prev);
ASSERT_LT(i, 10000); // sanity
prev = i;
}
}
uint32_t prev = 0;
for (uint32_t size = 16; size <= 256; size *= 2)
{
const uint32_t i = cc::get_cc_influence(0, 0, size, size, ROLE_AGRICULTURAL, 100, 100, cc::SPECIAL_EVENT_NONE);
ASSERT_GE(i, prev);
ASSERT_LT(i, 10000); // sanity
prev = i;
}
uint32_t w = 64, h = 64;
prev = cc::get_cc_influence(0, 0, w, h, ROLE_AGRICULTURAL, 100, 100, cc::SPECIAL_EVENT_NONE);
for (uint32_t scale = 0; scale < 4; ++scale)
{
w *= 2;
h /= 2;
const uint32_t i = cc::get_cc_influence(0, 0, w, h, ROLE_AGRICULTURAL, 100, 100, cc::SPECIAL_EVENT_NONE);
ASSERT_LE(i, prev);
ASSERT_LT(i, 10000); // sanity
prev = i;
}
// check no overflow
prev = 0;
for (uint32_t sz = 5; sz <= 255; sz += 5)
{
const uint32_t i = cc::get_cc_influence(0, 0, sz, sz, ROLE_AGRICULTURAL, 1000, sz, cc::SPECIAL_EVENT_NONE);
ASSERT_GT(i, prev);
prev = i;
}
}
TEST(cc_influence, roles)
{
cc::game_events_t events;
// no influence, score stays at 100%
// E A C I C R1 R2 R3 M C S W K S W R R
ASSERT_EQ(100, cc::calculate_influence_bonus(ROLE_AGRICULTURAL, std::vector<uint32_t>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
// influence by everying but military, agricultural buildings do not care
ASSERT_EQ(100, cc::calculate_influence_bonus(ROLE_AGRICULTURAL, std::vector<uint32_t>{0, 10, 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 10, 10, 10, 10, 10}.data(), 0, 0, events));
// agricultural buildings do get some influence from military buildings
ASSERT_EQ(105, cc::calculate_influence_bonus(ROLE_AGRICULTURAL, std::vector<uint32_t>{0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
// residential buildings get a bonus for a commercial building and get a bonus from one military building, but penalties from more
ASSERT_EQ( 0, cc::calculate_influence_bonus(ROLE_RESIDENTIAL2, std::vector<uint32_t>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
ASSERT_EQ(100, cc::calculate_influence_bonus(ROLE_RESIDENTIAL1, std::vector<uint32_t>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
ASSERT_EQ(105, cc::calculate_influence_bonus(ROLE_RESIDENTIAL1, std::vector<uint32_t>{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
ASSERT_EQ(110, cc::calculate_influence_bonus(ROLE_RESIDENTIAL1, std::vector<uint32_t>{0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
ASSERT_EQ(105, cc::calculate_influence_bonus(ROLE_RESIDENTIAL1, std::vector<uint32_t>{0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
ASSERT_EQ(100, cc::calculate_influence_bonus(ROLE_RESIDENTIAL1, std::vector<uint32_t>{0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0}.data(), 0, 0, events));
}
TEST(cc, events)
{
cryptonote::BlockchainDB *db = cryptonote::new_db();
ASSERT_TRUE(db);
boost::filesystem::path path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
const std::string filename = (path / db->get_db_name()).string();
db->open(filename, 0);
cryptonote::db_wtxn_guard guard(db);
const uint8_t cmd = get_cc_tag<uint8_t>(cryptonote::cc_command_game_update_t());
// starts off empty
std::vector<cc::game_event_t> events;
ASSERT_TRUE(db->get_cc_events(0, 0, 0, events));
ASSERT_TRUE(events.empty());
// can delete nothing
db->remove_cc_events(7434);
ASSERT_TRUE(db->get_cc_events(0, 0, 0, events));
ASSERT_TRUE(events.empty());
// add a couple at height 127
db->add_cc_events(127, {{127, cmd, 4, 1, ITEM_NONE, 0, 0, "building 1"}, {127, cmd, 4, 0, ITEM_NONE, 0, 0, "no building"}});
ASSERT_TRUE(db->get_cc_events(127, 0, 0, events));
ASSERT_EQ(events.size(), 2);
// add a bundle at 200
db->add_cc_events(200, {
{200, cmd, 2, 0, ITEM_NONE, 0, 0, "account 2"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 0"},
{200, cmd, 5, 2, ITEM_NONE, 0, 0, "account 5, building 2, event 0"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 1"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 2"},
{200, cmd, 5, 3, ITEM_NONE, 0, 0, "account 5, building 3, event 0"},
{200, cmd, 6, 4, ITEM_NONE, 0, 0, "account 6, building 4, event 0"},
{200, cmd, 4, 5, ITEM_NONE, 0, 0, "account 4, building 5, event 0"},
{200, cmd, 4, 0, ITEM_NONE, 0, 0, "account 4"},
{200, cmd, 1, 0, ITEM_NONE, 0, 0, "account 1"}
});
ASSERT_TRUE(db->get_cc_events(200, 0, 0, events));
ASSERT_EQ(events.size(), 10);
ASSERT_TRUE(db->get_cc_events(200, 4, 0, events));
ASSERT_EQ(events.size(), 5);
ASSERT_TRUE(db->get_cc_events(200, 5, 0, events));
ASSERT_EQ(events.size(), 2);
ASSERT_TRUE(db->get_cc_events(200, 6, 0, events));
ASSERT_EQ(events.size(), 1);
ASSERT_TRUE(db->get_cc_events(200, 7, 6, events));
ASSERT_EQ(events.size(), 0);
ASSERT_TRUE(db->get_cc_events(200, 8, 0, events));
ASSERT_EQ(events.size(), 0);
ASSERT_TRUE(db->get_cc_events(200, 4, 4, events));
ASSERT_EQ(events.size(), 0);
ASSERT_TRUE(db->get_cc_events(200, 4, 1, events));
ASSERT_EQ(events.size(), 3);
ASSERT_TRUE(db->get_cc_events(200, 4, 5, events));
ASSERT_EQ(events.size(), 1);
ASSERT_TRUE(db->get_cc_events(200, 5, 1, events));
ASSERT_EQ(events.size(), 0);
ASSERT_TRUE(db->get_cc_events(200, 1, 0, events));
ASSERT_EQ(events.size(), 1);
// 127 still exists, unchanged
ASSERT_TRUE(db->get_cc_events(127, 0, 0, events));
ASSERT_EQ(events.size(), 2);
// add at 240
db->add_cc_events(240, {{240, cmd, 4, 1, ITEM_NONE, 0, 0, "building 1"}, {240, cmd, 4, 0, ITEM_NONE, 0, 0, "no building"}});
ASSERT_TRUE(db->get_cc_events(240, 0, 0, events));
ASSERT_EQ(events.size(), 2);
ASSERT_TRUE(db->get_cc_events(200, 0, 0, events));
ASSERT_EQ(events.size(), 10);
// delete 200
db->remove_cc_events(200);
ASSERT_TRUE(db->get_cc_events(240, 0, 0, events));
ASSERT_EQ(events.size(), 2);
ASSERT_TRUE(db->get_cc_events(200, 0, 0, events));
ASSERT_EQ(events.size(), 0);
ASSERT_TRUE(db->get_cc_events(127, 0, 0, events));
ASSERT_EQ(events.size(), 2);
// we can add 200 back
db->add_cc_events(200, {
{200, cmd, 2, 0, ITEM_NONE, 0, 0, "account 2"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 0"},
{200, cmd, 5, 2, ITEM_NONE, 0, 0, "account 5, building 2, event 0"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 1"},
{200, cmd, 4, 1, ITEM_NONE, 0, 0, "account 4, building 1, event 2"},
{200, cmd, 5, 3, ITEM_NONE, 0, 0, "account 5, building 3, event 0"},
{200, cmd, 6, 4, ITEM_NONE, 0, 0, "account 6, building 4, event 0"},
{200, cmd, 4, 5, ITEM_NONE, 0, 0, "account 4, building 5, event 0"},
{200, cmd, 4, 0, ITEM_NONE, 0, 0, "account 4"},
{200, cmd, 1, 0, ITEM_NONE, 0, 0, "account 1"}
});
ASSERT_TRUE(db->get_cc_events(200, 0, 0, events));
ASSERT_EQ(events.size(), 10);
}
TEST(cc, discoveries)
{
std::vector<std::tuple<uint32_t, uint32_t, uint32_t>> buildings, buildings2;
ASSERT_EQ(cc::get_research_bonus(buildings), 0);
buildings.push_back(std::make_tuple(255, 255, 300));
ASSERT_GT(cc::get_research_bonus(buildings), 0);
buildings2.push_back(std::make_tuple(100, 100, 200));
ASSERT_GE(cc::get_research_bonus(buildings), cc::get_research_bonus(buildings2));
ASSERT_EQ(cc::get_research_age_adjusted_difficulty(100 * COIN, 0, 0), 100 * COIN);
ASSERT_LT(cc::get_research_age_adjusted_difficulty(100 * COIN, 10000, 0), 100 * COIN);
ASSERT_GT(cc::get_research_age_adjusted_difficulty(100 * COIN, 10000, 0), 0);
ASSERT_LT(cc::get_research_age_adjusted_difficulty(100 * COIN, 10000, 100 * COIN), 50 * COIN);
ASSERT_GT(cc::get_discovery_chance_scaled(100 * COIN, 0, 100 * COIN, 0, 0, 1), RESEARCH_CHANCE_BASE_SCALE / 2);
ASSERT_GE(cc::get_discovery_chance_scaled(100 * COIN, 0, 100 * COIN, 0, 0, 1), cc::get_discovery_chance_scaled(MIN_RESEARCH_AMOUNT, 100 * COIN, 100 * COIN, 0, 0, 1));
}
static bool about_equal(uint64_t x, uint64_t y)
{
return std::abs(double(x) / double(y) - 1.0f) < 0.001;
}
TEST(cc, cities)
{
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 1) >> 32, 1);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 2) >> 32, 2);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 3) >> 32, 2);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 4) >> 32, 3);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 7) >> 32, 3);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 8) >> 32, 4);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 15) >> 32, 4);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 16) >> 32, 5);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 31) >> 32, 5);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 32) >> 32, 6);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 63) >> 32, 6);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 64) >> 32, 7);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 127) >> 32, 7);
EXPECT_EQ(cc::get_ideal_cities_32(IDEAL_CITIES_SHARE_SCALE * 128) >> 32, 8);
EXPECT_TRUE(about_equal(cc::get_new_city_cost(0, IDEAL_CITIES_SHARE_SCALE), BASE_CITY_COST));
EXPECT_TRUE(about_equal(cc::get_new_city_cost(1, IDEAL_CITIES_SHARE_SCALE * 2), BASE_CITY_COST));
EXPECT_TRUE(about_equal(cc::get_new_city_cost(2, IDEAL_CITIES_SHARE_SCALE * 4), BASE_CITY_COST));
EXPECT_TRUE(about_equal(cc::get_new_city_cost(3, IDEAL_CITIES_SHARE_SCALE * 8), BASE_CITY_COST));
EXPECT_TRUE(about_equal(cc::get_new_city_cost(4, IDEAL_CITIES_SHARE_SCALE * 16), BASE_CITY_COST));
EXPECT_EQ(cc::get_town_level(1), 0);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE - 1), 0);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE), 1);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE - 1), 1);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE), 2);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE - 1), 2);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE), 3);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE - 1), 3);
EXPECT_EQ(cc::get_town_level(CITY_LEVEL_SHARE_SCALE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE * CITY_NEXT_LEVEL_MULTIPLE), 4);
}
TEST(cc, accrual_steps)
{
ASSERT_EQ(cc::get_accrual_steps(359, 359), 0);
ASSERT_EQ(cc::get_accrual_steps(360, 360), 1);
ASSERT_EQ(cc::get_accrual_steps(361, 361), 0);
ASSERT_EQ(cc::get_accrual_steps(360, 360 + 360), 2);
ASSERT_EQ(cc::get_accrual_steps(360, 360 + 359), 1);
ASSERT_EQ(cc::get_accrual_steps(360, 360 + 361), 2);
ASSERT_EQ(cc::get_accrual_steps(360, 360 + 360 * 8), 9);
ASSERT_EQ(cc::get_accrual_steps(360, 360 + 360 * 8 - 1), 8);
ASSERT_EQ(cc::get_accrual_steps(10, 10), 0);
ASSERT_EQ(cc::get_accrual_steps(1, 359), 0);
ASSERT_EQ(cc::get_accrual_steps(1, 360), 1);
ASSERT_EQ(cc::get_accrual_steps(1, 361), 1);
ASSERT_EQ(cc::get_accrual_steps(1, 720), 2);
ASSERT_EQ(cc::get_accrual_steps(360, 720), 2);
ASSERT_EQ(cc::get_accrual_steps(361, 720), 1);
ASSERT_EQ(cc::get_accrual_steps(361, 719), 0);
static const constexpr uint64_t max_block = 1200;
bool is_update[max_block + 1];
for (uint64_t i = 1; i <= max_block; ++i)
is_update[i] = cryptonote::is_game_update_block(i);
for (uint64_t i = 1; i <= max_block; ++i)
{
for (uint64_t j = i; j <max_block; ++j)
{
uint64_t count = 0;
for (uint64_t k = i; k <= j; ++k)
if (is_update[k])
++count;
ASSERT_EQ(cc::get_accrual_steps(i, j), count);
}
}
}
TEST(cc, accrual)
{
// base accrual start limit now
ASSERT_EQ(cc::get_accrued_price(1000, 0, 0, 0, 10000), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 359), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 359 + 360), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 359 + 360 * 2), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 1001, 359 + 360 * 2), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 360), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 360 + 360), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 0, 360 + 360 * 2), 1003);
ASSERT_EQ(cc::get_accrued_price(1000, 1, 360, 1002, 360 + 360 * 2), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 0, 359), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 0, 360), 999);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 0, 360 - 360), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 0, 360 + 360), 998);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 0, 360 + 360 * 2), 997);
ASSERT_EQ(cc::get_accrued_price(1000, -1, 360, 998, 360 + 360 * 2), 998);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, 360, 0, 359 + 360 * 0), std::numeric_limits<uint64_t>::max() - 25);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, 360, 0, 359 + 360 * 1), std::numeric_limits<uint64_t>::max() - 15);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, 360, 0, 359 + 360 * 2), std::numeric_limits<uint64_t>::max() - 5);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, 360, 0, 359 + 360 * 3), std::numeric_limits<uint64_t>::max());
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, 360, 0, 359 + 360 * 4), std::numeric_limits<uint64_t>::max());
ASSERT_EQ(cc::get_accrued_price(25, -10, 360, 0, 359 + 360 * 0), 25);
ASSERT_EQ(cc::get_accrued_price(25, -10, 360, 0, 359 + 360 * 1), 15);
ASSERT_EQ(cc::get_accrued_price(25, -10, 360, 0, 359 + 360 * 2), 5);
ASSERT_EQ(cc::get_accrued_price(25, -10, 360, 0, 359 + 360 * 3), 0);
ASSERT_EQ(cc::get_accrued_price(25, -10, 360, 0, 359 + 360 * 4), 0);
static_assert(GAME_UPDATE_FREQUENCY > 100);
ASSERT_EQ(cc::get_accrued_price(100, 1, 100, 200000, 99), 100);
ASSERT_EQ(cc::get_accrued_price(100, 1, 100, 200000, 100), 100);
ASSERT_EQ(cc::get_accrued_price(100, 1, 100, 200000, 101), 100);
ASSERT_EQ(cc::get_accrued_price(100, 1, 100, 200000, 100 + GAME_UPDATE_FREQUENCY), 101);
ASSERT_EQ(cc::get_accrued_price(100, 1, GAME_UPDATE_FREQUENCY, 200000, GAME_UPDATE_FREQUENCY - 1), 100);
ASSERT_EQ(cc::get_accrued_price(100, 1, GAME_UPDATE_FREQUENCY, 200000, GAME_UPDATE_FREQUENCY), 101);
ASSERT_EQ(cc::get_accrued_price(100, 1, GAME_UPDATE_FREQUENCY, 200000, GAME_UPDATE_FREQUENCY + 1), 101);
}
TEST(cc, add32clamp)
{
int32_t min32i = std::numeric_limits<int32_t>::min(), max32i = std::numeric_limits<int32_t>::max();
uint32_t min32u = std::numeric_limits<uint32_t>::min(), max32u = std::numeric_limits<uint32_t>::max();
auto test = [](int32_t value, uint32_t delta) -> int32_t { add32clamp(&value, delta); return value; };
ASSERT_EQ(min32i, (int32_t)0x80000000);
ASSERT_EQ(max32i, 0x7fffffff);
ASSERT_EQ(min32u, 0);
ASSERT_EQ(max32u, 0xffffffff);
ASSERT_EQ(test(0, 0), 0);
ASSERT_EQ(test(0, 1), 1);
ASSERT_EQ(test(-1, 0), -1);
ASSERT_EQ(test(-1, 1), 0);
ASSERT_EQ(test(-1, 2), 1);
ASSERT_EQ(test(0, max32u), max32i);
ASSERT_EQ(test(0, max32i), max32i);
ASSERT_EQ(test(1, max32i-1), max32i);
ASSERT_EQ(test(1, max32u), max32i);
ASSERT_EQ(test(1, max32i), max32i);
ASSERT_EQ(test(-1, max32i), max32i - 1);
ASSERT_EQ(test(-1, max32i+1), max32i);
ASSERT_EQ(test(-1, max32u), max32i);
ASSERT_EQ(test(min32i, -min32i), 0);
ASSERT_EQ(test(min32i, max32u), max32i);
ASSERT_EQ(test(min32i, max32u - 1), max32i - 1);
ASSERT_EQ(test(min32i, 1), min32i + 1);
}
TEST(cc, rectanglizer)
{
std::vector<std::tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint8_t>> rectangles;
ASSERT_FALSE(cc::rectanglizer(1, 1, {}, rectangles));
ASSERT_FALSE(cc::rectanglizer(0, 0, {1}, rectangles));
ASSERT_FALSE(cc::rectanglizer(0, 1, {1}, rectangles));
ASSERT_FALSE(cc::rectanglizer(1, 0, {1}, rectangles));
ASSERT_TRUE(cc::rectanglizer(0, 0, {}, rectangles));
ASSERT_TRUE(rectangles.empty());
ASSERT_TRUE(cc::rectanglizer(1, 1, {0}, rectangles));
ASSERT_TRUE(rectangles.empty());
ASSERT_TRUE(cc::rectanglizer(1, 1, {1}, rectangles));
ASSERT_EQ(rectangles.size(), 1);
ASSERT_EQ(rectangles[0], std::make_tuple(0, 0, 1, 1, 1));
ASSERT_TRUE(cc::rectanglizer(2, 2, {1, 0, 0, 1}, rectangles));
ASSERT_EQ(rectangles.size(), 2);
ASSERT_EQ(rectangles[0], std::make_tuple(0, 0, 1, 1, 1));
ASSERT_EQ(rectangles[1], std::make_tuple(1, 1, 1, 1, 1));
ASSERT_TRUE(cc::rectanglizer(2, 2, {0, 0, 0, 1}, rectangles));
ASSERT_EQ(rectangles.size(), 1);
ASSERT_EQ(rectangles[0], std::make_tuple(1, 1, 1, 1, 1));
ASSERT_TRUE(cc::rectanglizer(2, 2, {0, 0, 1, 1}, rectangles));
ASSERT_EQ(rectangles.size(), 1);
ASSERT_EQ(rectangles[0], std::make_tuple(0, 1, 2, 1, 1));
ASSERT_TRUE(cc::rectanglizer(2, 2, {0, 1, 0, 1}, rectangles));
ASSERT_EQ(rectangles.size(), 1);
ASSERT_EQ(rectangles[0], std::make_tuple(1, 0, 1, 2, 1));
ASSERT_TRUE(cc::rectanglizer(2, 2, {1, 1, 1, 1}, rectangles));
ASSERT_EQ(rectangles.size(), 1);
ASSERT_EQ(rectangles[0], std::make_tuple(0, 0, 2, 2, 1));
ASSERT_TRUE(cc::rectanglizer(8, 8,
{
1, 1, 1, 1, 0, 1, 1, 0,
1, 1, 1, 0, 3, 1, 1, 0,
1, 0, 1, 1, 3, 1, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1,
0, 2, 2, 0, 0, 0, 1, 1,
2, 2, 2, 1, 0, 0, 1, 1,
1, 2, 2, 1, 0, 0, 2, 1,
1, 1, 1, 2, 1, 0, 1, 1,
// 0 1 2 3 4 5 6 7
// 0 a, a, a, a, 0, b, b, 0,
// 1 c, c, c, 0, d, b, b, 0,
// 2 e, 0, f, f, d, b, b, 0,
// 3 e, 0, g, 0, 0, 0, h, h,
// 4 0, i, i, 0, 0, 0, h, h,
// 5 j, i, i, k, 0, 0, h, h,
// 6 l, i, i, k, 0, 0, m, n,
// 7 l, o, o, p, q, 0, r, n,
}, rectangles));
ASSERT_EQ(rectangles.size(), 'r' - 'a' + 1);
ASSERT_EQ(rectangles['a' - 'a'], std::make_tuple(0, 0, 4, 1, 1));
ASSERT_EQ(rectangles['b' - 'a'], std::make_tuple(5, 0, 2, 3, 1));
ASSERT_EQ(rectangles['c' - 'a'], std::make_tuple(0, 1, 3, 1, 1));
ASSERT_EQ(rectangles['d' - 'a'], std::make_tuple(4, 1, 1, 2, 3));
ASSERT_EQ(rectangles['e' - 'a'], std::make_tuple(0, 2, 1, 2, 1));
ASSERT_EQ(rectangles['f' - 'a'], std::make_tuple(2, 2, 2, 1, 1));
ASSERT_EQ(rectangles['g' - 'a'], std::make_tuple(2, 3, 1, 1, 1));
ASSERT_EQ(rectangles['h' - 'a'], std::make_tuple(6, 3, 2, 3, 1));
ASSERT_EQ(rectangles['i' - 'a'], std::make_tuple(1, 4, 2, 3, 2));
ASSERT_EQ(rectangles['j' - 'a'], std::make_tuple(0, 5, 1, 1, 2));
ASSERT_EQ(rectangles['k' - 'a'], std::make_tuple(3, 5, 1, 2, 1));
ASSERT_EQ(rectangles['l' - 'a'], std::make_tuple(0, 6, 1, 2, 1));
ASSERT_EQ(rectangles['m' - 'a'], std::make_tuple(6, 6, 1, 1, 2));
ASSERT_EQ(rectangles['n' - 'a'], std::make_tuple(7, 6, 1, 2, 1));
ASSERT_EQ(rectangles['o' - 'a'], std::make_tuple(1, 7, 2, 1, 1));
ASSERT_EQ(rectangles['p' - 'a'], std::make_tuple(3, 7, 1, 1, 2));
ASSERT_EQ(rectangles['q' - 'a'], std::make_tuple(4, 7, 1, 1, 1));
ASSERT_EQ(rectangles['r' - 'a'], std::make_tuple(6, 7, 1, 1, 1));
}
TEST(cc, special_events)
{
TestDB *db = new TestDB();
cryptonote::BlockchainDB *bdb = db;
cc::add_init_state(*bdb);
cc::special_event_data_t event_data;
cc::special_event_list_t test_events[2] = {
{ 0, 0, 0, 0, "none", "" },
{ 1, 1, 1, 3, "test", "" }
};
// genesis
db->add_block({}, 0, 0, {}, 0, 1, {});
// nothing yet
ASSERT_FALSE(cc::get_active_special_event(*db, 0, event_data));
// to next game update
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
// update, 100% chance we get the test event
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_TRUE(cc::get_active_special_event(*db, 0, event_data));
ASSERT_EQ(event_data.special_event, 1);
uint64_t event_start_height = bdb->height() - 1;
ASSERT_EQ(event_data.start_height, event_start_height);
ASSERT_EQ(event_data.duration, 0);
// to next game update
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
// update, 100% chance for the test event to lapse
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_FALSE(cc::get_active_special_event(*db, 0, event_data));
std::vector<cc::special_event_data_t> events;
db->get_cc_special_events(0, events);
ASSERT_EQ(events.size(), 1);
ASSERT_EQ(events[0].special_event, 1);
ASSERT_EQ(events[0].start_height, GAME_UPDATE_FREQUENCY);
ASSERT_EQ(events[0].duration, 1);
// pop, the event gets back to active
cryptonote::block blk;
std::vector<cryptonote::transaction> txs;
if (cryptonote::is_game_update_block(db->height() - 1))
cc::revert_special_events(*db, 0);
bdb->pop_block(blk, txs);
ASSERT_TRUE(cc::get_active_special_event(*db, 0, event_data));
ASSERT_EQ(event_data.special_event, 1);
ASSERT_EQ(event_data.start_height, event_start_height);
ASSERT_EQ(event_data.duration, 0);
// pop enough to get back to before the event start
for (int i = 0; i < GAME_UPDATE_FREQUENCY; ++i)
{
if (cryptonote::is_game_update_block(db->height() - 1))
cc::revert_special_events(*db, 0);
bdb->pop_block(blk, txs);
}
// we should have no event active, nor history of any
ASSERT_FALSE(cc::get_active_special_event(*db, 0, event_data));
db->get_cc_special_events(0, events);
ASSERT_EQ(events.size(), 0);
// switch the stop probability to 0.001%, check it does end after max duration 3
test_events[1].stop_one_in = 100000;
for (int i = 0; i < 3; ++i)
{
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
// update, 100% chance we get the test event
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_TRUE(cc::get_active_special_event(*db, 0, event_data));
ASSERT_EQ(event_data.special_event, 1);
if (i == 0)
event_start_height = bdb->height() - 1;
ASSERT_EQ(event_data.start_height, event_start_height);
ASSERT_EQ(event_data.duration, 0);
}
// next update, it ends
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
// update, 100% chance we get the test event
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_FALSE(cc::get_active_special_event(*db, 0, event_data));
db->get_cc_special_events(0, events);
ASSERT_EQ(events.size(), 1);
ASSERT_EQ(events[0].special_event, 1);
ASSERT_EQ(events[0].start_height, GAME_UPDATE_FREQUENCY);
ASSERT_EQ(events[0].duration, 3);
// switch max duration back to 1
test_events[1].max_duration = 1;
// next update, it starts again
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_TRUE(cc::get_active_special_event(*db, 0, event_data));
ASSERT_EQ(event_data.special_event, 1);
uint64_t event_start_height_2 = bdb->height() - 1;
ASSERT_EQ(event_data.start_height, event_start_height_2);
ASSERT_EQ(event_data.duration, 0);
db->get_cc_special_events(0, events);
ASSERT_EQ(events.size(), 2);
ASSERT_EQ(events[0].special_event, 1);
ASSERT_EQ(events[0].start_height, event_start_height);
ASSERT_EQ(events[0].duration, 3);
ASSERT_EQ(events[1].special_event, 1);
ASSERT_EQ(events[1].start_height, event_start_height_2);
ASSERT_EQ(events[1].duration, 0);
// and it stops after one other update
do { db->add_block({}, 0, 0, {}, bdb->get_block_already_generated_coins(bdb->height()-1), 1, {}); } while (!cryptonote::is_game_update_block(bdb->height()-1));
ASSERT_TRUE(cryptonote::is_game_update_block(bdb->height()-1));
cc::update_special_events(*db, 0, cc::update_special_events(*db, 0, bdb->top_block_hash(), 1, test_events));
ASSERT_FALSE(cc::get_active_special_event(*db, 0, event_data));
db->get_cc_special_events(0, events);
ASSERT_EQ(events.size(), 2);
ASSERT_EQ(events[0].special_event, 1);
ASSERT_EQ(events[0].start_height, event_start_height);
ASSERT_EQ(events[0].duration, 3);
ASSERT_EQ(events[1].special_event, 1);
ASSERT_EQ(events[1].start_height, event_start_height_2);
ASSERT_EQ(events[1].duration, 1);
}
TEST(cc, base_temperature)
{
for (uint64_t h = 0; h < 1440 * 7; ++h)
{
const int32_t t = cc::get_base_temperature(h);
ASSERT_GE(t, -100);
ASSERT_LE(t, +100);
}
}
TEST(cc, test_temperature)
{
for (int i = 4; i <= 256; i *= 4)
{
int32_t t = cc::get_heating_needs(0, 0, 0, i, i, ROLE_RESIDENTIAL1, 100);
MGINFO("" << (i*i) << ": " << t);
}
}
TEST(cc, badge_score)
{
for (int i = 0; i < 32; ++i)
{
std::vector<uint32_t> badges;
for (int j = 0; j < NUM_BADGE_LEVELS; ++j)
badges.push_back(rand() % 8);
const std::pair<uint64_t, uint32_t> score = cc::get_badge_score(badges);
ASSERT_EQ(badges, cc::get_badges_from_score(score.first));
}
}
TEST(cc, invitation)
{
std::string invitation;
cryptonote::keypair base_keys = cryptonote::keypair::generate(hw::get_device("default"));
uint32_t account;
uint64_t amount, expiration;
crypto::public_key pkey;
cryptonote::blobdata inner;
crypto::signature inner_signature;
ASSERT_TRUE(cc::make_invitation(base_keys.sec, 8, 0, 100, invitation));
ASSERT_FALSE(invitation.empty());
ASSERT_TRUE(cc::parse_invitation(invitation, inner, inner_signature, account, amount, expiration, pkey));
ASSERT_EQ(account, 8);
ASSERT_EQ(amount, 0);
ASSERT_EQ(expiration, 100);
}