townforge/tests/unit_tests/cc.cpp
2021-05-14 17:22:13 +00:00

4006 lines
151 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 <boost/filesystem.hpp>
#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"
#include "cc/cc_potential.h"
#include "cc/cc_terrain.h"
#include "cc/cc_food.h"
#include "cc/cc_calendar.h"
#include "cc/cc_mortgage.h"
#define PREDEFINED_BULDINGS 1
#define TEST_ACCOUNT 5
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 seed;
uint32_t mayor;
uint32_t treasury;
std::string name;
bool ignore;
bool operator==(const city_t &other) const { return seed == other.seed && 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;
uint16_t base_height;
uint32_t elevation_bonus;
uint8_t potential[NUM_POTENTIALS];
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;
uint32_t mortgage;
std::vector<uint8_t> tiles;
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, uint16_t base_height, uint32_t elevation_bonus, const uint8_t potential[NUM_POTENTIALS], uint32_t economic_power, uint64_t construction_height, const std::map<uint32_t, uint32_t> &budget, const std::vector<uint16_t> &palette, bool active, bool ignore, uint8_t fire_state, const std::string &name, uint32_t mortgage): in_use(in_use), owner(owner), city(city), role(role), x0(x0), y0(y0), x1(x1), y1(y1), repair(repair), base_height(base_height), elevation_bonus(elevation_bonus), economic_power(economic_power), construction_height(construction_height), budget(budget), palette(palette), active(active), ignore(ignore), fire_state(fire_state), name(name), mortgage(mortgage)
{
memcpy(this->potential, potential, NUM_POTENTIALS);
cc::get_empty_packed_tiles(tiles);
}
flag_t(): in_use(false) {}
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 && base_height == other.base_height && elevation_bonus == other.elevation_bonus && !memcmp(potential, other.potential, NUM_POTENTIALS) && 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 && mortgage == other.mortgage && tiles == other.tiles; }
};
struct shares_t
{
uint64_t shares[NUM_ROLES];
uint64_t payout[NUM_ROLES];
uint64_t weighted_shares;
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; if (weighted_shares != other.weighted_shares) 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 creator;
uint64_t creation_height;
std::string name;
bool ignore;
bool is_group;
uint32_t group;
std::string pdesc;
std::string sdesc;
std::vector<uint64_t> user_data;
bool operator==(const custom_item_t &other) const { return name == other.name && creator == other.creator && creation_height == other.creation_height && is_group == other.is_group && group == other.group && pdesc == other.pdesc && sdesc == other.sdesc && ignore == other.ignore && user_data == other.user_data; }
};
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<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;
std::map<uint32_t, uint64_t> item_count;
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 && cc_trade_used == other.cc_trade_used && cc_orders == other.cc_orders
&& shares == other.shares && special_events == other.special_events && items == other.items && item_count == other.item_count;
}
};
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 = 14; 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({});
}
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, const crypto::public_key &pmspk, const crypto::public_key &pmvpk, 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 for_all_cc_accounts(std::function<bool(const cryptonote::cc_account_data_t&)> f) const override
{
for (size_t i = 0; i < state.accounts.size(); ++i)
{
const auto &account = state.accounts[i];
if (account.public_key == DEAD_PKEY)
continue;
cryptonote::cc_account_data_t ad{};
ad.id = i;
ad.public_key = account.public_key;
ad.balance = account.balance;
ad.item_balances = account.item_balances;
ad.flags = account.flags;
ad.name = account.name;
ad.ignore = account.ignore;
if (!f(ad))
return false;
}
return true;
}
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 seed, 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, crypto::null_pkey, crypto::null_pkey);
state.cities.push_back({true, seed, 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.seed = state.cities[id].seed;
ccd.treasury = state.cities[id].treasury;
ccd.mayor = state.cities[id].mayor;
ccd.max_level = 0;
ccd.ignore = false;
ccd.moose = NUM_STARTING_MOOSE;
ccd.bears = NUM_STARTING_BEARS;
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.seed = city.seed;
ccd.treasury = city.treasury;
ccd.mayor = city.mayor;
ccd.max_level = 0;
ccd.ignore = false;
ccd.moose = NUM_STARTING_MOOSE;
ccd.bears = NUM_STARTING_BEARS;
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, const uint8_t potential[NUM_POTENTIALS], uint16_t base_height, uint32_t elevation_bonus) 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, base_height, elevation_bonus, potential, 0, height(), {}, palette, false, false, 0, "", 0};
return actual_id;
}
virtual void delete_cc_flag(uint32_t id, bool reserve) override
{
cc::get_empty_packed_tiles(state.flags[id].tiles);
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.base_height = flag.base_height;
fd.elevation_bonus = flag.elevation_bonus;
memcpy(fd.potential, flag.potential, sizeof(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;
fd.mortgage = flag.mortgage;
fd.tiles = flag.tiles;
if (!f(fd))
return false;
}
return true;
}
virtual bool get_cc_flag_data(uint32_t id, cryptonote::cc_flag_data_t &fd) const override
{
if (id >= state.flags.size() || !state.flags[id].in_use)
return false;
fd = {};
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.base_height = state.flags[id].base_height;
fd.elevation_bonus = state.flags[id].elevation_bonus;
fd.economic_power = state.flags[id].economic_power;
memcpy(fd.potential, state.flags[id].potential, sizeof(fd.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;
fd.mortgage = state.flags[id].mortgage;
fd.tiles = state.flags[id].tiles;
return true;
}
virtual void set_cc_flag_data(uint32_t id, const cryptonote::cc_flag_data_t &fd) override
{
state.flags[id] = {};
state.flags[id].in_use = true;
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].economic_power = fd.economic_power;
state.flags[id].base_height = fd.base_height;
state.flags[id].elevation_bonus = fd.elevation_bonus;
memcpy(state.flags[id].potential, fd.potential, sizeof(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;
state.flags[id].mortgage = fd.mortgage;
state.flags[id].tiles = fd.tiles;
}
virtual void set_cc_flag_role(uint32_t id, uint8_t role, uint32_t economic_power) override
{
state.flags[id].role = role;
state.flags[id].economic_power = economic_power;
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<uint8_t> &tiles) override
{
state.flags[id].tiles = tiles;
}
virtual uint32_t get_highest_cc_flag_id() const override
{
return state.flags.size();
}
#if 0
virtual void add_cc_used_nonce(uint64_t nonce, uint32_t account, const crypto::hash &txid) 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, uint32_t *account, crypto::hash *txid) const override
{
return state.nonces.find(nonce) != state.nonces.end();
}
#endif
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];
}
sd.weighted_shares = x.weighted_shares;
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 creator, uint64_t creation_height, const std::string &name, bool is_group, bool is_public, uint32_t group, const std::string &primary_description, const std::string &secondary_description, uint64_t gold, const std::vector<uint64_t> &user_data, const crypto::hash &hash)
{
uint32_t actual_id;
state.items.push_back({});
actual_id = state.items.size() - 1;
state.items[actual_id] = {true, creator, creation_height, name, false, is_group, group, primary_description, secondary_description, user_data};
return actual_id + ITEM_FIRST_USER;
}
void delete_cc_custom_item(uint32_t id)
{
id -= ITEM_FIRST_USER;
state.items[id].in_use = false;
}
bool get_cc_custom_item_data(uint32_t id, cc::cc_custom_item_t &cid) const
{
id -= ITEM_FIRST_USER;
if (!state.items[id].in_use)
return false;
cid.id = id + ITEM_FIRST_USER;
cid.creator = state.items[id].creator;
cid.creation_height = state.items[id].creation_height;
cid.name = state.items[id].name;
cid.ignore = state.items[id].ignore;
cid.is_group = state.items[id].is_group;
cid.group = state.items[id].group;
cid.primary_description = state.items[id].pdesc;
cid.secondary_description = state.items[id].sdesc;
cid.user_data = state.items[id].user_data;
return true;
}
void set_cc_custom_item_ignore(uint32_t id, bool ignore)
{
id -= ITEM_FIRST_USER;
if (!state.items[id].in_use)
return;
state.items[id].ignore = ignore;
}
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 = ITEM_FIRST_USER + i;
cid.name = item.name;
cid.is_group = item.is_group;
cid.group = item.group;
cid.primary_description = item.pdesc;
cid.secondary_description = item.sdesc;
cid.user_data = item.user_data;
if (!f(cid))
return false;
}
return true;
}
virtual uint64_t get_cc_item_count(uint32_t id) const
{
const auto it = state.item_count.find(id);
return it == state.item_count.end() ? 0 : it->second;
}
virtual void set_cc_item_count(uint32_t id, uint64_t count)
{
state.item_count[id] = count;
if (state.item_count[id] == 0)
state.item_count.erase(id);
}
virtual bool get_cc_runestones(uint32_t flag, std::vector<cc::runestone_t> &runestones) const
{
runestones.clear();
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;
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, 0, 0, c0));
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 1, 2, 0, 0, c1));
ASSERT_GT(c1, c0);
// square is cheaper for the same area
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 3, 3, 0, 0, c0));
ASSERT_TRUE(cc::get_new_flag_cost(0, 0, 1, 7, 0, 0, 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, "", crypto::null_pkey, crypto::null_pkey);
uint8_t potentials[NUM_POTENTIALS];
for (int i = 0; i < NUM_POTENTIALS; ++i)
potentials[i] = 100;
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, potentials, 32, 33);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(bdb->get_cc_flag_data(flag_id, fd));
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, "", crypto::null_pkey, crypto::null_pkey);
uint8_t potentials[NUM_POTENTIALS];
for (int i = 0; i < NUM_POTENTIALS; ++i)
potentials[i] = 100;
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, potentials, 32, 33);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(bdb->get_cc_flag_data(flag_id, fd));
ASSERT_EQ(fd.fire_state, 0);
}
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)14, (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 && base->cc_account) ? 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, 0, 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 && base->cc_account) ? 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, 0, 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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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_redeem_account)
{
cryptonote::cc_command_redeem_account_t redeem_account;
std::vector<cryptonote::cc_command_t> setup;
cryptonote::keypair keys = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys2 = cryptonote::keypair::generate(hw::get_device("default"));
cryptonote::keypair keys3 = cryptonote::keypair::generate(hw::get_device("default"));
crypto::secret_key derived_skey;
crypto::public_key derived_pkey;
cc::cc_derive_keys(keys.sec, derived_skey, derived_pkey);
// setup
cryptonote::cc_command_create_account_t create_account;
create_account.public_key = derived_pkey;
create_account.amount = CRYPTONOTE_CC_NEW_ACCOUNT_FEE * 2 + 1000;
create_account.name = "foo";
create_account.inviting_account = 0;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
// create an invitation
std::string invitation;
ASSERT_TRUE(cc::make_invitation(keys.sec, TEST_ACCOUNT, 420, 0, crypto::null_pkey, invitation));
cryptonote::blobdata inner;
crypto::signature inner_signature;
uint32_t account;
uint64_t amount, expiration;
crypto::public_key pkey;
crypto::secret_key secret_key;
crypto::signature secret_key_signature;
crypto::public_key recipient;
ASSERT_TRUE(cc::parse_invitation(invitation, inner, inner_signature, account, amount, expiration, pkey, recipient, secret_key, secret_key_signature));
ASSERT_EQ(account, TEST_ACCOUNT);
ASSERT_EQ(expiration, 0);
ASSERT_EQ(amount, 420);
ASSERT_EQ(recipient, crypto::null_pkey);
auto sign_invitation = [](cryptonote::cc_command_redeem_account_t &redeem_account, const crypto::hash *hash, const crypto::public_key &pkey, const crypto::secret_key &secret_key, const cryptonote::keypair *recipient = NULL) {
cryptonote::cc_command_t cmd = redeem_account;
cryptonote::cc_command_redeem_account_t &redeem_account2 = boost::get<cryptonote::cc_command_redeem_account_t>(cmd);
memset(&redeem_account2.invitation_signature, 0, sizeof(redeem_account2.invitation_signature));
memset(&redeem_account2.recipient_signature, 0, sizeof(redeem_account2.recipient_signature));
redeem_account2.cc_nonce = 0;
cryptonote::blobdata blob = cryptonote::t_serializable_object_to_blob(cmd);
crypto::hash signed_hash;
if (hash)
signed_hash = *hash;
else
crypto::cn_fast_hash(blob.data(), blob.size(), signed_hash);
crypto::generate_signature(signed_hash, pkey, secret_key, redeem_account.invitation_signature);
if (recipient)
crypto::generate_signature(signed_hash, recipient->pub, recipient->sec, redeem_account.recipient_signature);
else
memset(&redeem_account.recipient_signature, 0, sizeof(redeem_account.recipient_signature));
};
// signature on the wrong message
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, &crypto::null_hash, pkey, secret_key);
test_commands(false, setup, redeem_account, "signature on the wrong message");
// signature with the wrong key
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, derived_pkey, derived_skey);
test_commands(false, setup, redeem_account, "signature with the wrong key");
// wrong inviting account
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test";
redeem_account.inviting_account = TEST_ACCOUNT - 1;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key);
test_commands(false, setup, redeem_account, "wrong inviting account");
// miner intercepts
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key);
redeem_account.public_key = keys3.pub;
test_commands(false, setup, redeem_account, "miner intercepts");
// empty name
redeem_account.public_key = keys2.pub;
redeem_account.name = "";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key);
test_commands(false, setup, redeem_account, "empty name");
// invalid name
redeem_account.public_key = keys2.pub;
redeem_account.name = " a";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key);
test_commands(false, setup, redeem_account, "invalid name");
// good
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key);
test_commands(true, setup, redeem_account, "valid");
// invitation with a recipient
ASSERT_TRUE(cc::make_invitation(keys.sec, TEST_ACCOUNT, 420, 0, keys3.pub, invitation));
ASSERT_TRUE(cc::parse_invitation(invitation, inner, inner_signature, account, amount, expiration, pkey, recipient, secret_key, secret_key_signature));
ASSERT_EQ(account, TEST_ACCOUNT);
ASSERT_EQ(expiration, 0);
ASSERT_EQ(amount, 420);
ASSERT_EQ(recipient, keys3.pub);
// bad recipient
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test-with-recipient";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key, &keys2);
test_commands(false, setup, redeem_account, "bad recipient");
// invitation with a recipient
ASSERT_TRUE(cc::make_invitation(keys.sec, TEST_ACCOUNT, 420, 0, keys2.pub, invitation));
ASSERT_TRUE(cc::parse_invitation(invitation, inner, inner_signature, account, amount, expiration, pkey, recipient, secret_key, secret_key_signature));
ASSERT_EQ(account, TEST_ACCOUNT);
ASSERT_EQ(expiration, 0);
ASSERT_EQ(amount, 420);
ASSERT_EQ(recipient, keys2.pub);
// good
redeem_account.public_key = keys2.pub;
redeem_account.name = "redeem-test-with-recipient";
redeem_account.inviting_account = TEST_ACCOUNT;
redeem_account.invitation = inner;
sign_invitation(redeem_account, NULL, pkey, secret_key, &keys2);
test_commands(true, setup, redeem_account, "valid with recipient");
}
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
// deposit
transfer.cc_account = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
// withdraw
transfer.cc_account = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
// transfer to another
transfer.cc_account = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
// good
buy_land.cc_account = TEST_ACCOUNT;
buy_land.city = 0;
buy_land.x = 1000040;
buy_land.y = 1000050;
buy_land.wm1 = 8;
buy_land.hm1 = 8;
test_commands(true, setup, buy_land, "valid");
// too close to edge
buy_land.x = 50;
test_commands(false, setup, buy_land, "too close to edge");
buy_land.x = 1000040;
// too close to edge
buy_land.x = std::numeric_limits<uint32_t>::max() - 500;
test_commands(false, setup, buy_land, "too close to edge");
buy_land.x = 1000040;
// 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 = TEST_ACCOUNT;
// too costly
buy_land.wm1 = 255;
test_commands(false, setup, buy_land, "too costly");
buy_land.wm1 = 8;
// overlapping an existing flag
auto setup2 = setup;
buy_land2.cc_account = TEST_ACCOUNT + 1;
buy_land2.city = 0;
buy_land2.x = 1000020;
buy_land2.y = 1000052;
buy_land2.wm1 = 80;
buy_land2.hm1 = 8;
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;
uint32_t ox, oy;
cc::get_city_origin(0, ox, oy);
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
cryptonote::cc_command_buy_land_t buy_land;
buy_land.cc_account = TEST_ACCOUNT;
buy_land.city = 0;
buy_land.x = ox + 140;
buy_land.y = oy + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
cryptonote::cc_command_buy_items_t buy_blocks;
buy_blocks.cc_account = TEST_ACCOUNT;
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 = TEST_ACCOUNT;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
building_settings.construction_height = 1;
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 = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
buy_blocks.entries.resize(1);
// good
buy_blocks.cc_account = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
buy_labour.entries.resize(1);
// good
buy_labour.cc_account = TEST_ACCOUNT;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
cryptonote::cc_command_buy_items_t buy_blocks;
buy_blocks.cc_account = TEST_ACCOUNT;
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 = TEST_ACCOUNT;
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;
uint32_t ox, oy;
cc::get_city_origin(0, ox, oy);
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
buy_blocks.cc_account = TEST_ACCOUNT;
buy_blocks.entries.push_back({ITEM_BASIC_STONE, 1000});
buy_blocks.entries.push_back({ITEM_BASIC_WOOD, 1440});
buy_blocks.entries.push_back({ITEM_LABOUR, 8064});
setup.push_back(buy_blocks);
buy_blocks.cc_account = TEST_ACCOUNT + 1;
setup.push_back(buy_blocks);
buy_land.cc_account = TEST_ACCOUNT;
buy_land.city = 0;
buy_land.x = ox + 140;
buy_land.y = oy + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
buy_land.cc_account = TEST_ACCOUNT + 1;
buy_land.city = 0;
buy_land.x = ox + 240;
buy_land.y = oy + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
// good
building_settings.cc_account = TEST_ACCOUNT;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
building_settings.construction_height = 1;
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;
// invalid construction_height
building_settings.construction_height = 2;
test_commands(false, setup, building_settings, "invalid flag");
building_settings.construction_height = 1;
}
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;
uint32_t ox, oy;
cc::get_city_origin(0, ox, oy);
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
buy_items.cc_account = TEST_ACCOUNT;
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, 8064});
setup.push_back(buy_items);
buy_land.cc_account = TEST_ACCOUNT;
buy_land.city = 0;
buy_land.x = ox + 140;
buy_land.y = oy + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
buy_land.cc_account = TEST_ACCOUNT + 1;
buy_land.city = 0;
buy_land.x = ox + 240;
buy_land.y = oy + 50;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
building_settings.cc_account = TEST_ACCOUNT;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
building_settings.construction_height = 1;
setup.push_back(building_settings);
// good
assign.cc_account = TEST_ACCOUNT;
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 = TEST_ACCOUNT;
assign.flag = 700 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 1};
test_commands(false, setup, assign, "wrong flag");
// 0 amount
assign.cc_account = TEST_ACCOUNT;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 0};
test_commands(false, setup, assign, "0 amount");
// too many
assign.cc_account = TEST_ACCOUNT;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1, 999999999};
test_commands(false, setup, assign, "too many");
// invalid item
assign.cc_account = TEST_ACCOUNT;
assign.flag = 1 + PREDEFINED_BULDINGS;
assign.items[0] = {1111111111, 1};
test_commands(false, setup, assign, "invalid item");
// duplicate item
assign.cc_account = TEST_ACCOUNT;
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;
uint32_t ox, oy;
cc::get_city_origin(0, ox, oy);
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;
create_account.pmspk = crypto::null_pkey;
create_account.pmvpk = crypto::null_pkey;
setup.push_back(create_account);
buy_items.cc_account = TEST_ACCOUNT;
buy_items.entries.push_back({ITEM_BASIC_STONE, 1000});
buy_items.entries.push_back({ITEM_BASIC_WOOD, 1440});
buy_items.entries.push_back({ITEM_LABOUR, 8064});
setup.push_back(buy_items);
buy_land.cc_account = TEST_ACCOUNT;
buy_land.city = 0;
buy_land.x = ox + 140;
buy_land.y = oy + 150;
buy_land.wm1 = 11;
buy_land.hm1 = 11;
setup.push_back(buy_land);
building_settings.cc_account = TEST_ACCOUNT;
building_settings.flag = 1 + PREDEFINED_BULDINGS;
building_settings.role = ROLE_RESIDENTIAL1;
building_settings.economic_power = 100;
building_settings.construction_height = 1;
setup.push_back(building_settings);
// empty
palette.cc_account = TEST_ACCOUNT;
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));
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));
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));
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));
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];
uint32_t encoded_len;
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)14, (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, 0, 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);
cg.cc_nonce = cg1.cc_nonce = 0;
ASSERT_EQ(cg, cg1) << "cg: " << cryptonote::obj_to_json_str(cg) << ", cg1: " << cryptonote::obj_to_json_str(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, "", crypto::null_pkey, crypto::null_pkey);
uint8_t potential[NUM_POTENTIALS];
for (int i = 0; i < NUM_POTENTIALS; ++i)
potential[i] = 100;
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, 44, 45, 46, 47, potential, 32, 33);
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));
ASSERT_EQ(fd.role, ROLE_EMPTY);
ASSERT_EQ(fd.repair, 0);
bdb->set_cc_flag_role(flag_id, ROLE_AGRICULTURAL, 100);
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd));
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));
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));
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;
item_balances[COIN_ITEM_SETTLEMENT] = 10; // coin
item_balances[COIN_ITEM_SETTLEMENT+1] = 10; // coin
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), crypto::null_pkey, crypto::null_pkey);
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(42, players[1], "city 1");
uint32_t city2_id = bdb->allocate_new_cc_city(4242, 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
uint8_t potential[NUM_POTENTIALS];
for (int i = 0; i < NUM_POTENTIALS; ++i)
potential[i] = 200;
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)
{
uint32_t ox, oy;
cc::get_city_origin(flag_cities[i], ox, oy);
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, potential, 32, 33);
bdb->set_cc_flag_role(flags[i], ROLE_RESIDENTIAL1, 100);
bdb->set_cc_flag_repair(flags[i], DEFAULT_REPAIR);
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, 447589049972);
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, 268553429983);
uint64_t expected_full_subsidy = bdb->get_block_already_generated_coins(GAME_UPDATE_FREQUENCY - 1) * BLOCK_REWARD_SUBSIDY;
ASSERT_EQ(expected_full_subsidy, 719741185884);
uint64_t expected_available_subsidy = expected_full_subsidy - expected_full_subsidy * COIN_COLLECTION_SUBSIDY_PER_THOUSAND / 1000;
ASSERT_EQ(expected_available_subsidy, 716142479955);
bool found[2] = { false, false };
for (const auto &e: events)
{
if (e.account == treasuries[0] && strstr(e.event.c_str(), "Got 4475.89049972 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(), "Got 2685.53429983 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 ox, oy;
cc::get_city_origin(0, ox, oy);
uint32_t city_id = 0;
uint32_t account_id = bdb->allocate_new_cc_account(account_keys.pub, "", crypto::null_pkey, crypto::null_pkey);
uint8_t potential[NUM_POTENTIALS];
for (int i = 0; i < NUM_POTENTIALS; ++i)
potential[i] = 255;
uint32_t flag_id = bdb->allocate_new_cc_flag(NULL, account_id, city_id, ox + 44, oy + 44, ox + 47, oy + 47, potential, 32, 33);
const uint8_t S = ITEM_BASIC_STONE;
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_repair(flag_id, 1000000);
bdb->set_cc_flag_role(flag_id, ROLE_SAWMILL, 100);
cryptonote::cc_flag_data_t fd;
ASSERT_TRUE(db->get_cc_flag_data(flag_id, fd));
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, false, false, 0, 0, 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_GE(production.size(), 1);
ASSERT_LE(production.size(), 3);
uint32_t basic_equivalent = 0;
for (const auto &e: production)
{
ASSERT_GE(e.first, ITEM_FIRST_WOOD);
ASSERT_LE(e.first, ITEM_LAST_WOOD);
basic_equivalent += e.second * (e.first == ITEM_BASIC_WOOD ? 1 : e.first == ITEM_MEDIUM_WOOD ? 3 : 9);
ASSERT_GT(e.second, 0);
}
ASSERT_GT(basic_equivalent, 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);
const bool expect_medium_wood = false;
const bool expect_high_wood = false;
uint32_t item_id = 0xffffffffu, idx = 0;
ASSERT_EQ(c0.item_balances.size(), 4 + (expect_medium_wood ? 1 : 0) + (expect_high_wood ? 1 : 0));
ASSERT_EQ(c0.item_balances[idx].delta_account, account_id);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_BASIC_STONE);
ASSERT_LT(c0.item_balances[idx].delta, 0);
++idx;
ASSERT_EQ(c0.item_balances[idx].delta_account, 0);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_BASIC_WOOD);
//ASSERT_GT(c0.item_balances[idx].delta, 0); // heating uses up wood now
++idx;
if (expect_medium_wood)
{
ASSERT_EQ(c0.item_balances[idx].delta_account, 0);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_MEDIUM_WOOD);
ASSERT_GE(c0.item_balances[idx].delta, 0);
++idx;
}
if (expect_high_wood)
{
ASSERT_EQ(c0.item_balances[idx].delta_account, 0);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_HIGH_WOOD);
ASSERT_GE(c0.item_balances[idx].delta, 0);
++idx;
}
ASSERT_EQ(c0.item_balances[idx].delta_account, 0);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_LABOUR);
ASSERT_LT(c0.item_balances[idx].delta, 0);
++idx;
ASSERT_EQ(c0.item_balances[idx].delta_account, 0);
item_id += c0.item_balances[idx].delta_item + 1;
ASSERT_EQ(item_id, ITEM_FOOD_VEGETABLES);
ASSERT_LT(c0.item_balances[idx].delta, 0);
++idx;
}
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);
cmd = cryptonote::cc_command_add_city_specialization_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x22);
cmd = cryptonote::cc_command_sow_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x23);
cmd = cryptonote::cc_command_harvest_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x24);
cmd = cryptonote::cc_command_mint_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x25);
cmd = cryptonote::cc_command_smelt_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x26);
cmd = cryptonote::cc_command_cancel_nonce_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x27);
cmd = cryptonote::cc_command_create_script_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x28);
cmd = cryptonote::cc_command_start_script_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x29);
cmd = cryptonote::cc_command_script_choice_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2a);
cmd = cryptonote::cc_command_set_script_variable_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2b);
cmd = cryptonote::cc_command_create_mortgage_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2c);
cmd = cryptonote::cc_command_chop_wood_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2d);
cmd = cryptonote::cc_command_carve_runestone_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2e);
cmd = cryptonote::cc_command_create_auction_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x2f);
cmd = cryptonote::cc_command_auction_bid_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x30);
cmd = cryptonote::cc_command_enable_script_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x31);
cmd = cryptonote::cc_command_allow_styling_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x32);
cmd = cryptonote::cc_command_update_item_t();
ASSERT_EQ(get_cc_tag<uint8_t>(cmd), 0x33);
}
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, 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};
for (uint32_t size = 16; size <= 256; size *= 2)
{
uint32_t prev = 0;
for (uint8_t p: potential)
{
const uint8_t dummy_potential[NUM_POTENTIALS] = { p, p, p, p, p, p, p };
const uint32_t i = cc::get_cc_influence(0, 0, size, size, ROLE_AGRICULTURAL, 100, 0, dummy_potential, 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 uint8_t dummy_potential[NUM_POTENTIALS] = { 100, 100, 100, 100, 100, 100, 100 };
const uint32_t i = cc::get_cc_influence(0, 0, size, size, ROLE_AGRICULTURAL, 100, 0, dummy_potential, cc::SPECIAL_EVENT_NONE);
ASSERT_GE(i, prev);
ASSERT_LT(i, 10000); // sanity
prev = i;
}
uint32_t w = 64, h = 64;
const uint8_t dummy_potential[NUM_POTENTIALS] = { 100, 100, 100, 100, 100, 100, 100 };
prev = cc::get_cc_influence(0, 0, w, h, ROLE_AGRICULTURAL, 100, 0, dummy_potential, 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, 0, dummy_potential, 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 uint8_t dummy_potential[NUM_POTENTIALS] = { (uint8_t)sz, (uint8_t)sz, (uint8_t)sz, (uint8_t)sz, (uint8_t)sz, (uint8_t)sz, (uint8_t)sz };
const uint32_t i = cc::get_cc_influence(0, 0, sz, sz, ROLE_AGRICULTURAL, 1000, 0, dummy_potential, cc::SPECIAL_EVENT_NONE);
ASSERT_GT(i, prev);
prev = i;
}
}
TEST(cc_influence, roles)
{
cc::game_events_t events;
cryptonote::cc_command_game_update_t cg;
// 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, cg, 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, cg, events));
// agricultural buildings do not get some influence from military buildings
ASSERT_EQ(100, 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, cg, 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, cg, 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, cg, events));
ASSERT_EQ(103, 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, cg, events));
ASSERT_EQ(107, 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, cg, events));
ASSERT_EQ(107, 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, cg, events));
ASSERT_EQ(88, 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, cg, 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}, {}, 0, 1, 0, "building 1"}, {127, cmd, 4, {}, {}, {}, 0, 2, 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, 10, 0, "account 2"},
{200, cmd, 4, {}, {1}, {}, 0, 11, 0, "account 4, building 1, event 0"},
{200, cmd, 5, {}, {2}, {}, 0, 12, 0, "account 5, building 2, event 0"},
{200, cmd, 4, {}, {1}, {}, 0, 13, 0, "account 4, building 1, event 1"},
{200, cmd, 4, {}, {1}, {}, 0, 14, 0, "account 4, building 1, event 2"},
{200, cmd, 5, {}, {3}, {}, 0, 15, 0, "account 5, building 3, event 0"},
{200, cmd, 6, {}, {4}, {}, 0, 16, 0, "account 6, building 4, event 0"},
{200, cmd, 4, {}, {5}, {}, 0, 17, 0, "account 4, building 5, event 0"},
{200, cmd, 4, {}, {}, {}, 0, 18, 0, "account 4"},
{200, cmd, 1, {}, {}, {}, 0, 19, 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}, {}, 0, 90, 0, "building 1"}, {240, cmd, 4, {}, {}, {}, 0, 91, 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, 10, 0, "account 2"},
{200, cmd, 4, {}, {1}, {}, 0, 11, 0, "account 4, building 1, event 0"},
{200, cmd, 5, {}, {2}, {}, 0, 12, 0, "account 5, building 2, event 0"},
{200, cmd, 4, {}, {1}, {}, 0, 13, 0, "account 4, building 1, event 1"},
{200, cmd, 4, {}, {1}, {}, 0, 14, 0, "account 4, building 1, event 2"},
{200, cmd, 5, {}, {3}, {}, 0, 15, 0, "account 5, building 3, event 0"},
{200, cmd, 6, {}, {4}, {}, 0, 16, 0, "account 6, building 4, event 0"},
{200, cmd, 4, {}, {5}, {}, 0, 17, 0, "account 4, building 5, event 0"},
{200, cmd, 4, {}, {}, {}, 0, 18, 0, "account 4"},
{200, cmd, 1, {}, {}, {}, 0, 19, 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, bool>> buildings, buildings2;
ASSERT_EQ(cc::get_research_bonus(buildings), 0);
buildings.push_back(std::make_tuple(255, 255, 300, false));
ASSERT_GT(cc::get_research_bonus(buildings), 0);
buildings2.push_back(std::make_tuple(100, 100, 200, false));
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_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)
{
static const uint64_t T = GAME_UPDATE_FREQUENCY;
ASSERT_EQ(cc::get_accrual_steps(T-1, T-1), 0);
ASSERT_EQ(cc::get_accrual_steps(T, T), 1);
ASSERT_EQ(cc::get_accrual_steps(T+1, T+1), 0);
ASSERT_EQ(cc::get_accrual_steps(T, T + T), 2);
ASSERT_EQ(cc::get_accrual_steps(T, T + T-1), 1);
ASSERT_EQ(cc::get_accrual_steps(T, T + T+1), 2);
ASSERT_EQ(cc::get_accrual_steps(T, T + T * 8), 9);
ASSERT_EQ(cc::get_accrual_steps(T, T + T * 8 - 1), 8);
ASSERT_EQ(cc::get_accrual_steps(10, 10), 0);
ASSERT_EQ(cc::get_accrual_steps(1, T-1), 0);
ASSERT_EQ(cc::get_accrual_steps(1, T), 1);
ASSERT_EQ(cc::get_accrual_steps(1, T+1), 1);
ASSERT_EQ(cc::get_accrual_steps(1, 2*T), 2);
ASSERT_EQ(cc::get_accrual_steps(T, 2*T), 2);
ASSERT_EQ(cc::get_accrual_steps(T+1, 2*T), 1);
ASSERT_EQ(cc::get_accrual_steps(T+1, 2*T-1), 0);
static const constexpr uint64_t max_block = 2400;
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)
{
static const uint64_t T = GAME_UPDATE_FREQUENCY;
// 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, T, 0, T-1), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 0, T-1 + T), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 0, T-1 + T * 2), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 1001, T-1 + T * 2), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 0, T), 1001);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 0, T + T), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 0, T + T * 2), 1003);
ASSERT_EQ(cc::get_accrued_price(1000, 1, T, 1002, T + T * 2), 1002);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 0, T-1), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 0, T), 999);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 0, T - T), 1000);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 0, T + T), 998);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 0, T + T * 2), 997);
ASSERT_EQ(cc::get_accrued_price(1000, -1, T, 998, T + T * 2), 998);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, T, 0, T-1 + T * 0), std::numeric_limits<uint64_t>::max() - 25);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, T, 0, T-1 + T * 1), std::numeric_limits<uint64_t>::max() - 15);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, T, 0, T-1 + T * 2), std::numeric_limits<uint64_t>::max() - 5);
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, T, 0, T-1 + T * 3), std::numeric_limits<uint64_t>::max());
ASSERT_EQ(cc::get_accrued_price(std::numeric_limits<uint64_t>::max() - 25, 10, T, 0, T-1 + T * 4), std::numeric_limits<uint64_t>::max());
ASSERT_EQ(cc::get_accrued_price(25, -10, T, 0, T-1 + T * 0), 25);
ASSERT_EQ(cc::get_accrued_price(25, -10, T, 0, T-1 + T * 1), 15);
ASSERT_EQ(cc::get_accrued_price(25, -10, T, 0, T-1 + T * 2), 5);
ASSERT_EQ(cc::get_accrued_price(25, -10, T, 0, T-1 + T * 3), 1);
ASSERT_EQ(cc::get_accrued_price(25, -10, T, 0, T-1 + T * 4), 1);
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, special_event_history)
{
const uint64_t T1 = GAME_UPDATE_FREQUENCY;
const uint64_t T2 = GAME_UPDATE_FREQUENCY * 2;
const uint64_t T3 = GAME_UPDATE_FREQUENCY * 3;
const uint64_t T4 = GAME_UPDATE_FREQUENCY * 4;
const uint64_t T5 = GAME_UPDATE_FREQUENCY * 5;
const uint64_t T6 = GAME_UPDATE_FREQUENCY * 6;
const uint64_t T7 = GAME_UPDATE_FREQUENCY * 7;
ASSERT_EQ(cc::get_special_event_at({}, 0), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({}, 1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 0}}, 0), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 0}}, 1000000), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 1}}, 0), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 1}}, T1 - 1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 1}}, T1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 2}}, 0), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 2}}, T1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 2}}, T2 - 1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, 0, 2}}, T2), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 0}}, 0), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 0}}, T1 - 1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 0}}, T1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 0}}, 1000000), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, 0), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, T1 - 1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, T1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, T2), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, T3 - 1), cc::SPECIAL_EVENT_HEAT_WAVE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_HEAT_WAVE, T1, 2}}, T3), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, 0), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T1 - 1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T1), cc::SPECIAL_EVENT_RATS);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T2), cc::SPECIAL_EVENT_RATS);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T3 - 1), cc::SPECIAL_EVENT_RATS);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T3), cc::SPECIAL_EVENT_FLOOD);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T4 - 1), cc::SPECIAL_EVENT_FLOOD);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T4), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T5 - 1), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 1}}, T5), cc::SPECIAL_EVENT_FAD);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 2}}, T6), cc::SPECIAL_EVENT_FAD);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 2}}, T7 - 1), cc::SPECIAL_EVENT_FAD);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 2}}, T7), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 2}}, 100000), cc::SPECIAL_EVENT_NONE);
ASSERT_EQ(cc::get_special_event_at({{cc::SPECIAL_EVENT_RATS, T1, 2}, {cc::SPECIAL_EVENT_FLOOD, T3, 1}, {cc::SPECIAL_EVENT_FAD, T5, 0}}, 100000), cc::SPECIAL_EVENT_FAD);
}
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_heating)
{
uint32_t heating = 0xffffffff;
for (int32_t i = -256; i <= 0; i += 20)
{
uint32_t h = cc::get_heating_needs(i, 0, 0, 32, 32, ROLE_RESIDENTIAL1, 100, 100, 0);
ASSERT_LE(h, heating);
heating = h;
}
}
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"));
cryptonote::keypair recipient_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;
crypto::secret_key secret_key;
crypto::signature secret_key_signature;
crypto::public_key recipient;
ASSERT_TRUE(cc::make_invitation(base_keys.sec, 8, 0, 100, recipient_keys.pub, invitation));
ASSERT_FALSE(invitation.empty());
ASSERT_TRUE(cc::parse_invitation(invitation, inner, inner_signature, account, amount, expiration, pkey, recipient, secret_key, secret_key_signature));
ASSERT_EQ(account, 8);
ASSERT_EQ(amount, 0);
ASSERT_EQ(expiration, 100);
ASSERT_EQ(recipient, recipient_keys.pub);
}
TEST(cc, city_proximity)
{
static const uint32_t n_cities = 1024;
uint32_t cities[n_cities][2];
for (uint32_t city = 0; city < n_cities; ++city)
{
cc::get_city_origin(city, cities[city][0], cities[city][1]);
ASSERT_GE(cities[city][0], MIN_CITY_DISTANCE);
ASSERT_LE(cities[city][0], std::numeric_limits<uint32_t>::max() - MIN_CITY_DISTANCE);
ASSERT_GE(cities[city][1], MIN_CITY_DISTANCE);
ASSERT_LE(cities[city][1], std::numeric_limits<uint32_t>::max() - MIN_CITY_DISTANCE);
for (uint32_t c = 0; c < city; ++c)
{
const uint32_t dx = cities[city][0] > cities[c][0] ? (cities[city][0] - cities[c][0]) : (cities[c][0] - cities[city][0]);
const uint32_t dy = cities[city][1] > cities[c][1] ? (cities[city][1] - cities[c][1]) : (cities[c][1] - cities[city][1]);
ASSERT_GE(std::max(dx, dy), MIN_CITY_DISTANCE);
}
}
}
TEST(cc, terrain_is_deterministic)
{
const cc::cc_potential_state_t *state = cc::get_cc_potential_state(0, 0);
uint32_t ox, oy;
cc::get_city_origin(0, ox, oy);
uint16_t line0[8001], line1[8001];
cc::flush_terrain();
for (int32_t x = -4000; x <= 4000; ++x)
line0[x + 4000] = cc::get_cc_height(state, ox + x, oy);
cc::flush_terrain();
for (int32_t x = 4000; x >= -4000; --x)
line1[x + 4000] = cc::get_cc_height(state, ox + x, oy);
ASSERT_EQ(0, memcmp(line0, line1, 8001 * 2));
}
TEST(cc, farming_schedule)
{
static const uint64_t mid_jan = 16920;
static const uint64_t mid_mar = 100;
static const uint64_t late_apr = 2400;
static const uint64_t early_may = 3000;
static const uint64_t late_may = 4000;
static const uint64_t late_jun = 5800;
static const uint64_t mid_jul = 6840;
static const uint64_t mid_aug = 8600;
static const uint64_t mid_sep = 10400;
static const uint64_t mid_oct = 12000;
static const uint64_t mid_dec = 15400;
uint64_t blocks;
ASSERT_TRUE(cc::check_food_calendar_sanity());
auto check = [](uint64_t height, bool vegetables_sowing_season, bool vegetables_harvest_season, bool grain_sowing_season, bool grain_harvest_season)
{
ASSERT_EQ(cc::is_valid_sowing_time(CROP_VEGETABLES, height), vegetables_sowing_season);
ASSERT_EQ(cc::is_valid_harvest_time(CROP_VEGETABLES, height), vegetables_harvest_season);
ASSERT_EQ(cc::is_valid_sowing_time(CROP_GRAIN, height), grain_sowing_season);
ASSERT_EQ(cc::is_valid_harvest_time(CROP_GRAIN, height), grain_harvest_season);
};
// calendar:
// start of march: vegetable sowing season starts
// start of may: vegetable harvest season starts
// start of may: grain sowing season starts
// mid may: vegetable sowing season ends
// mid jun: grain sowing season ends
// end of june: vegetable harvest season ends
// start of august: grain harvest season starts
// end of september: grain harvest season ends
// sow/v har/v sow/g har/g
check(mid_jan, false, false, false, false);
// vegetable sowing season starts
check(mid_mar, true, false, false, false);
check(late_apr, true, false, false, false);
// vegetable harvest season starts
// grain sowing season starts
check(early_may, true, true, true, false);
// vegetable sowing season ends
check(late_may, false, true, true, false);
// grain sowing season ends
check(late_jun, false, true, false, false);
// vegetable harvest season ends
check(mid_jul, false, false, false, false);
// grain harvest season starts
check(mid_aug, false, false, false, true );
check(mid_sep, false, false, false, true );
// grain harvest season ends
check(mid_oct, false, false, false, false);
check(mid_dec, false, false, false, false);
for (uint64_t dh = 0; dh < 3; ++dh)
{
uint64_t DH = dh * BLOCKS_PER_GAME_YEAR;
// vegetables sowing season starts in early march
blocks = cc::get_blocks_since_sowing_season_start(CROP_VEGETABLES, mid_jan + DH);
ASSERT_GE(blocks, BLOCKS_PER_GAME_YEAR / 2);
ASSERT_LT(blocks, BLOCKS_PER_GAME_YEAR);
blocks = cc::get_blocks_since_sowing_season_start(CROP_VEGETABLES, mid_mar + DH);
ASSERT_LE(blocks, BLOCKS_PER_GAME_YEAR / 12);
blocks = cc::get_blocks_since_sowing_season_start(CROP_VEGETABLES, late_may + DH);
ASSERT_GE(blocks, 2 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LE(blocks, 3 * BLOCKS_PER_GAME_YEAR / 12);
blocks = cc::get_blocks_since_sowing_season_start(CROP_VEGETABLES, mid_dec + DH);
ASSERT_GE(blocks, 9 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LE(blocks, 11 * BLOCKS_PER_GAME_YEAR / 12);
// vegetables sowing season starts in early march
blocks = cc::get_blocks_till_sowing_season_start(CROP_VEGETABLES, mid_jan + DH);
ASSERT_GE(blocks, BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LE(blocks, 2 * BLOCKS_PER_GAME_YEAR / 12);
blocks = cc::get_blocks_till_sowing_season_start(CROP_VEGETABLES, mid_mar + DH);
ASSERT_GE(blocks, 11 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LT(blocks, BLOCKS_PER_GAME_YEAR);
blocks = cc::get_blocks_till_sowing_season_start(CROP_VEGETABLES, mid_dec + DH);
ASSERT_GE(blocks, 2 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LT(blocks, 4 * BLOCKS_PER_GAME_YEAR / 12);
// vegetables sowing season ends in mid may
blocks = cc::get_blocks_till_sowing_season_end(CROP_VEGETABLES, mid_dec + DH);
ASSERT_GE(blocks, 4 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LT(blocks, 5 * BLOCKS_PER_GAME_YEAR / 12);
blocks = cc::get_blocks_till_sowing_season_end(CROP_VEGETABLES, early_may + DH);
ASSERT_LT(blocks, BLOCKS_PER_GAME_YEAR / 12);
blocks = cc::get_blocks_till_sowing_season_end(CROP_VEGETABLES, late_jun + DH);
ASSERT_GE(blocks, 10 * BLOCKS_PER_GAME_YEAR / 12);
ASSERT_LT(blocks, 11 * BLOCKS_PER_GAME_YEAR / 12);
}
static const uint8_t crops[] = { CROP_VEGETABLES, CROP_GRAIN };
for (const uint8_t crop: crops)
{
ASSERT_TRUE(cryptonote::is_game_update_block(GAME_UPDATE_FREQUENCY + cc::get_blocks_till_sowing_season_start(crop, 0)));
ASSERT_TRUE(cryptonote::is_game_update_block(GAME_UPDATE_FREQUENCY + cc::get_blocks_till_sowing_season_end(crop, 0)));
ASSERT_TRUE(cryptonote::is_game_update_block(GAME_UPDATE_FREQUENCY + cc::get_blocks_till_harvest_season_start(crop, 0)));
ASSERT_TRUE(cryptonote::is_game_update_block(GAME_UPDATE_FREQUENCY + cc::get_blocks_till_harvest_season_end(crop, 0)));
}
}
TEST(cc, harvest_overflow)
{
uint32_t high_amount = cc::get_harvest_amount(0, 0, 255, 255, 300, CROP_GRAIN, 65536, 100, 255, 0, 10000, cc::SPECIAL_EVENT_GOOD_HARVEST, true, true, true, true, true, true, true, true, true, true, true, true, true);
uint32_t low_amount = cc::get_harvest_amount(0, 0, 31, 31, 100, CROP_GRAIN, 32000, 50, 127, 0, 1000000000, cc::SPECIAL_EVENT_NONE, false, false, false, false, false, false, false, false, false, false, false, false, false);
ASSERT_GE(high_amount, low_amount);
uint32_t amount0 = cc::get_harvest_amount(0, 0, 255, 255, 300, CROP_GRAIN, 65536, 100, 0, 0, 10000, cc::SPECIAL_EVENT_GOOD_HARVEST, true, true, true, true, true, true, true, true, true, true, true, true, true);
uint32_t amount1 = cc::get_harvest_amount(0, 0, 255, 255, 300, CROP_GRAIN, 65536, 100, 0, 255, 10000, cc::SPECIAL_EVENT_GOOD_HARVEST, true, true, true, true, true, true, true, true, true, true, true, true, true);
ASSERT_EQ(amount0, amount1);
uint32_t prev_amount = 0;
for (int n = 5; n <= 255; n += 5)
{
uint32_t amount = cc::get_harvest_amount(0, 0, n, n, 100 + n / 100 / 164, CROP_GRAIN, 65536, 100, n, 0, 10000, cc::SPECIAL_EVENT_GOOD_HARVEST, true, true, true, true, true, true, true, true, true, true, true, true, true);
ASSERT_GT(amount, prev_amount);
prev_amount = amount;
}
}
TEST(cc, nutrients)
{
ASSERT_EQ(cc::get_nutrients_recovery_increase(100, false), 0);
ASSERT_EQ(cc::get_nutrients_recovery_increase(99, false), 1);
ASSERT_GT(cc::get_nutrients_recovery_increase(0, false), 0);
ASSERT_GT(cc::get_nutrients_decrease_for_harvest(100), 0);
ASSERT_EQ(cc::get_nutrients_decrease_for_harvest(0), 0);
}
TEST(cc, farming_temperatures)
{
ASSERT_GE(cc::get_growth_temperature(CROP_VEGETABLES), cc::get_damage_temperature(CROP_VEGETABLES));
ASSERT_GE(cc::get_growth_temperature(CROP_GRAIN), cc::get_damage_temperature(CROP_GRAIN));
}
TEST(cc, crop_yield)
{
ASSERT_EQ(cc::get_crop_yield(CROP_GRAIN, CROP_START_YIELD, {}, false, false, 0), CROP_START_YIELD);
const uint32_t yield = cc::get_crop_yield(CROP_GRAIN, CROP_START_YIELD, std::vector<int32_t>(10000, 1000), false, false, 0);
ASSERT_LE(yield, 65536);
ASSERT_GE(yield, 65536 * 9 / 10);
const uint32_t yield_1 = cc::get_crop_yield(CROP_GRAIN, CROP_START_YIELD, std::vector<int32_t>(10000, 1000), false, false, 1);
const uint32_t yield_2 = cc::get_crop_yield(CROP_GRAIN, CROP_START_YIELD, std::vector<int32_t>(10000, 1000), false, false, 2);
const uint32_t yield_3 = cc::get_crop_yield(CROP_GRAIN, CROP_START_YIELD, std::vector<int32_t>(10000, 1000), false, false, 3);
ASSERT_TRUE(about_equal(yield * .8, yield_1));
ASSERT_TRUE(about_equal(yield * .8 * .8, yield_2));
ASSERT_TRUE(about_equal(yield * .8 * .8 * .8, yield_3));
}
static void check_mortgage_progression(uint64_t creation_height, uint32_t num_ticks_delay, uint32_t num_tick_payments)
{
uint32_t ticks = 0;
bool matured = false, paid = false;
cc::mortgage_phase_t last_phase = cc::mortgage_phase_delay;
const uint64_t maturity_height = cc::get_mortgage_maturity_height(creation_height, num_ticks_delay, num_tick_payments);
ASSERT_GE(creation_height, 2 * GAME_UPDATE_FREQUENCY + 8);
for (uint64_t h = creation_height - (2 * GAME_UPDATE_FREQUENCY + 8); h <= creation_height + (num_ticks_delay + num_tick_payments + 2) * GAME_UPDATE_FREQUENCY; ++h)
{
const cc::mortgage_phase_t phase = cc::get_mortgage_phase(h, creation_height, num_ticks_delay, num_tick_payments);
ASSERT_GE(phase, last_phase);
ASSERT_EQ(phase == cc::mortgage_phase_maturity, h == maturity_height);
switch (phase)
{
case cc::mortgage_phase_delay: break;
case cc::mortgage_phase_tick: if (cryptonote::is_game_update_block(h)) ++ticks; break;
case cc::mortgage_phase_maturity: ASSERT_FALSE(matured); ASSERT_EQ(cc::get_mortgage_maturity_height(creation_height, num_ticks_delay, num_tick_payments), h); matured = true; break;
case cc::mortgage_phase_paid: paid = true; break;
}
last_phase = phase;
}
ASSERT_EQ(ticks, num_tick_payments);
ASSERT_TRUE(matured);
ASSERT_TRUE(paid);
}
TEST(cc, mortgage)
{
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY - 1, 0, 0), GAME_UPDATE_FREQUENCY);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY, 0, 0), GAME_UPDATE_FREQUENCY * 2);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY + 1, 0, 0), GAME_UPDATE_FREQUENCY * 2);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY - 1, 1, 0), GAME_UPDATE_FREQUENCY * 2);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY, 1, 0), GAME_UPDATE_FREQUENCY * 3);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY + 1, 1, 0), GAME_UPDATE_FREQUENCY * 3);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY - 1, 0, 1), GAME_UPDATE_FREQUENCY * 2);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY, 0, 1), GAME_UPDATE_FREQUENCY * 3);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY + 1, 0, 1), GAME_UPDATE_FREQUENCY * 3);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY - 1, 1, 1), GAME_UPDATE_FREQUENCY * 3);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY, 1, 1), GAME_UPDATE_FREQUENCY * 4);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY + 1, 1, 1), GAME_UPDATE_FREQUENCY * 4);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY - 1, GAME_UPDATE_FREQUENCY - 1, 0, 0), cc::mortgage_phase_delay);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY, GAME_UPDATE_FREQUENCY - 1, 0, 0), cc::mortgage_phase_maturity);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY, GAME_UPDATE_FREQUENCY - 1, 1, 0), cc::mortgage_phase_delay);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY, GAME_UPDATE_FREQUENCY - 1, 0, 1), cc::mortgage_phase_tick);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY * 2, GAME_UPDATE_FREQUENCY - 1, 0, 1), cc::mortgage_phase_maturity);
ASSERT_EQ(cc::get_mortgage_phase(GAME_UPDATE_FREQUENCY * 3, GAME_UPDATE_FREQUENCY - 1, 0, 1), cc::mortgage_phase_paid);
ASSERT_EQ(cc::get_mortgage_maturity_height(GAME_UPDATE_FREQUENCY + 1, 2, 0xffffffff), GAME_UPDATE_FREQUENCY * (0xffffffff + (uint64_t)4));
for (int dch = -1; dch <= 1; ++dch)
for (int d = 0; d < 4; ++d)
for (int t = 0; t < 4; ++t)
check_mortgage_progression(8 * GAME_UPDATE_FREQUENCY + dch, d, t);
}
TEST(cc, firewood_labour_starts_at_1)
{
ASSERT_GT(cc::get_labour_cost_to_chop_wood(1), 0);
}
TEST(cc, auction_end_tune)
{
auto make_empty_auction = [](uint64_t h){ cc::auction_t a{}; a.creation_height = h; a.base_ticks = MIN_AUCTION_BASE_TICKS; return a; };
static const uint64_t TICK = GAME_UPDATE_FREQUENCY;
static const uint64_t AUCTION_TIME = MIN_AUCTION_BASE_TICKS * TICK;
ASSERT_EQ(cc::get_auction_end_time(make_empty_auction(1), 1), std::make_pair(AUCTION_TIME + TICK, false));
ASSERT_EQ(cc::get_auction_end_time(make_empty_auction(1), AUCTION_TIME), std::make_pair(AUCTION_TIME + TICK, false));
ASSERT_EQ(cc::get_auction_end_time(make_empty_auction(TICK - 1), AUCTION_TIME + TICK), std::make_pair(AUCTION_TIME + TICK * 2, false));
ASSERT_EQ(cc::get_auction_end_time(make_empty_auction(TICK), AUCTION_TIME + TICK), std::make_pair(AUCTION_TIME + TICK * 2, false));
}
TEST(cc, land_tax)
{
uint64_t land_tax_d0_l0_c0_a256;
uint64_t land_tax_d0_l0_c0_a1024;
uint64_t land_tax_d10000_l0_c0_a256;
uint64_t land_tax_d10000_l0_c0_a1024;
uint64_t land_tax_d10000_l0_c1_a256;
uint64_t land_tax_d10000_l0_c1_a1024;
uint64_t land_tax_d10000_l1_c1_a256;
uint64_t land_tax_d10000_l1_c1_a1024;
ASSERT_TRUE(cc::get_land_tax(0, 0, 16, 16, 0, 0, 100, 0, 0, land_tax_d0_l0_c0_a256));
ASSERT_TRUE(cc::get_land_tax(0, 0, 32, 32, 0, 0, 100, 0, 0, land_tax_d0_l0_c0_a1024));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+16, 16, 0, 0, 100, 0, 0, land_tax_d10000_l0_c0_a256));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+32, 32, 0, 0, 100, 0, 0, land_tax_d10000_l0_c0_a1024));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+16, 16, 0, 0, 100, 0, 1, land_tax_d10000_l0_c1_a256));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+32, 32, 0, 0, 100, 0, 1, land_tax_d10000_l0_c1_a1024));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+16, 16, 0, 0, 100, 1, 1, land_tax_d10000_l1_c1_a256));
ASSERT_TRUE(cc::get_land_tax(10000, 0, 10000+32, 32, 0, 0, 100, 1, 1, land_tax_d10000_l1_c1_a1024));
ASSERT_GT(land_tax_d0_l0_c0_a1024, land_tax_d0_l0_c0_a256);
ASSERT_GT(land_tax_d10000_l0_c0_a1024, land_tax_d10000_l0_c0_a256);
ASSERT_GT(land_tax_d10000_l0_c0_a256, land_tax_d0_l0_c0_a256);
ASSERT_GT(land_tax_d10000_l0_c0_a1024, land_tax_d0_l0_c0_a1024);
ASSERT_GT(land_tax_d10000_l0_c0_a256, land_tax_d10000_l0_c1_a256);
ASSERT_GT(land_tax_d10000_l0_c0_a1024, land_tax_d10000_l0_c1_a1024);
ASSERT_GT(land_tax_d10000_l1_c1_a1024, land_tax_d10000_l1_c1_a256);
}