Skip to content
2 changes: 1 addition & 1 deletion contracts/contracts/rem.swap/include/rem.swap/rem.swap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ namespace eosio {
void create_user(const name &user, const public_key &owner_key,
const public_key &active_key, const asset &min_account_stake);

void validate_swap(const checksum256 &swap_hash) const;
void is_ready_to_finish(const checksum256 &swap_hash) const;
void validate_address(const name &chain_id, const string &address);
void validate_pubkey(const signature &sign, const checksum256 &digest, const string &swap_pubkey_str) const;
void cleanup_swaps();
Expand Down
43 changes: 19 additions & 24 deletions contracts/contracts/rem.swap/src/rem.swap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace eosio {
swap_params_data = swap_params_table.get();

check_pubkey_prefix(swap_pubkey);
check(is_block_producer(rampayer), "only top25 block producers approval is recorded");
check(quantity.is_valid(), "invalid quantity");
check(quantity.symbol == min_account_stake.symbol, "symbol precision mismatch");
check(quantity.amount >= min_account_stake.amount + producers_reward.amount, "the quantity must be greater "
Expand All @@ -40,42 +41,36 @@ namespace eosio {
check(current_time_point() > swap_timepoint, "swap cannot be initialized "
"with a future timestamp");

const bool is_producer = is_block_producer(rampayer);
if (swap_hash_it == swap_hash_idx.end()) {
swap_table.emplace(rampayer, [&](auto &s) {
s.key = swap_table.available_primary_key();
s.txid = txid;
s.swap_id = swap_hash;
s.swap_timestamp = swap_timestamp;
s.status = static_cast<int8_t>(swap_status::INITIALIZED);
if (is_producer) s.provided_approvals.push_back(rampayer);
s.provided_approvals.push_back(rampayer);
});
} else if (is_producer) {
check(is_producer, "block producer authorization required");
check(swap_hash_it->status != static_cast<int8_t>(swap_status::CANCELED), "swap already canceled");
check(swap_hash_it->status != static_cast<int8_t>(swap_status::FINISHED), "swap already finished");

} else {
const vector <name> &approvals = swap_hash_it->provided_approvals;
bool is_already_approved = std::find(approvals.begin(), approvals.end(), rampayer) == approvals.end();

check(is_already_approved, "approval already exists");

swap_table.modify(*swap_hash_it, rampayer, [&](auto &s) {
s.provided_approvals.push_back(rampayer);
});
}
// moved out, because existing case when the majority of the active producers = 1
if (is_producer) {
cleanup_swaps();
swap_hash_it = swap_hash_idx.find(swap_data::get_swap_hash(swap_hash));
bool is_status_issued = swap_hash_it->status == static_cast<int8_t>(swap_status::ISSUED);
if (is_swap_confirmed(swap_hash_it->provided_approvals) && !is_status_issued) {
issue_tokens(rampayer, quantity);
if (swap_hash_it->status == static_cast<int8_t>(swap_status::INITIALIZED)) {
swap_table.modify(*swap_hash_it, rampayer, [&](auto &s) {
s.status = static_cast<int8_t>(swap_status::ISSUED);
s.provided_approvals.push_back(rampayer);
});
}
}
cleanup_swaps();
swap_hash_it = swap_hash_idx.find(swap_data::get_swap_hash(swap_hash));
bool is_status_init = swap_hash_it->status == static_cast<int8_t>(swap_status::INITIALIZED);
if (is_status_init && is_swap_confirmed(swap_hash_it->provided_approvals)) {
issue_tokens(rampayer, quantity);
swap_table.modify(*swap_hash_it, rampayer, [&](auto &s) {
s.status = static_cast<int8_t>(swap_status::ISSUED);
});
}
}

void swap::finish(const name &rampayer, const name &receiver, const string &txid, const string &swap_pubkey_str,
Expand All @@ -94,7 +89,7 @@ namespace eosio {
return_address, return_chain_id, swap_timestamp
);

validate_swap(swap_hash);
is_ready_to_finish(swap_hash);
validate_pubkey( sign, digest, swap_pubkey_str );

auto swap_hash_idx = swap_table.get_index<"byhash"_n>();
Expand Down Expand Up @@ -132,7 +127,7 @@ namespace eosio {
return_chain_id, swap_timestamp
);

validate_swap(swap_hash);
is_ready_to_finish(swap_hash);
validate_pubkey( sign, digest, swap_pubkey_str );

auto swap_hash_idx = swap_table.get_index<"byhash"_n>();
Expand Down Expand Up @@ -176,7 +171,7 @@ namespace eosio {
auto swap_hash_idx = swap_table.get_index<"byhash"_n>();
auto swap_hash_it = swap_hash_idx.find(swap_data::get_swap_hash(swap_hash));

validate_swap(swap_hash);
is_ready_to_finish(swap_hash);
check(time_point_sec(current_time_point()) > swap_timepoint + swap_active_lifetime,
"swap has to be canceled after expiration");

Expand Down Expand Up @@ -273,7 +268,7 @@ namespace eosio {
assert_recover_key(digest, sign, swap_pubkey);
}

void swap::validate_swap(const checksum256 &swap_hash) const
void swap::is_ready_to_finish(const checksum256 &swap_hash) const
{
auto swap_hash_idx = swap_table.get_index<"byhash"_n>();
auto swap_hash_it = swap_hash_idx.find(swap_data::get_swap_hash(swap_hash));
Expand All @@ -286,7 +281,7 @@ namespace eosio {
auto swap_expiration_delta = current_time_point().time_since_epoch() - swap_lifetime.time_since_epoch();
check(time_point(swap_expiration_delta) < swap_timepoint, "swap lifetime expired");

check(is_swap_confirmed(swap_hash_it->provided_approvals), "not enough active producers approvals");
check(swap_hash_it->status == static_cast<int8_t>(swap_status::ISSUED), "not enough active producers approvals");
}

void swap::cleanup_swaps()
Expand Down
5 changes: 2 additions & 3 deletions contracts/contracts/rem.swap/src/system_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ namespace eosio {
vector<name> _producers;
for(const auto &producer: _gstate.last_schedule)
_producers.push_back(producer.first);
for(const auto &producer: _gstate.standby) {
for(const auto &producer: _gstate.standby)
_producers.push_back(producer.first);
}
return _producers;
}

Expand All @@ -88,7 +87,7 @@ namespace eosio {
uint8_t quantity_active_appr = 0;
for (const auto& producer: provided_approvals) {
auto prod_appr = std::find(_producers.begin(), _producers.end(), producer);
if ( prod_appr != _producers.end() ) {
if ( prod_appr != _producers.end() || producer == system_account ) {
++quantity_active_appr;
}
}
Expand Down
71 changes: 57 additions & 14 deletions unittests/rem_swap_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ rem_swap_tester::rem_swap_tester() {
permission_level{N(rem.swap), config::active_name}};
addchain(N(ethropsten), true, true, 1000000, 5000000, auths_level);
setswapparam(control->get_chain_id(), "0x81b7E08F65Bdf5648606c89998A9CC8164397647", "ethropsten");
produce_blocks();
}

BOOST_AUTO_TEST_SUITE(rem_swap_tests)
Expand All @@ -457,6 +458,7 @@ BOOST_FIXTURE_TEST_CASE(init_swap_test, rem_swap_tester) {
.swap_pubkey = get_pubkey_str(crypto::private_key::generate()),
.swap_timestamp = time_point_sec(control->head_block_time())
};
vector<name> producers(_producer_candidates.begin(), _producer_candidates.end());
/* swap id consist of swap_pubkey, txid, control->get_chain_id(), quantity, return_address,
* return_chain_id, swap_timepoint_seconds and separated by '*'. */
time_point swap_timepoint = init_swap_data.swap_timestamp.to_time_point();
Expand All @@ -467,16 +469,16 @@ BOOST_FIXTURE_TEST_CASE(init_swap_test, rem_swap_tester) {
string swap_id = sha256::hash(swap_payload);
asset before_init_balance = get_balance(N(rem.swap));

init_swap(N(rem.swap), init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap(N(proda), init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);

// 0 if a swap status initialized
auto data = get_singtable(N(rem.swap), N(swaps), "swap_data");
BOOST_REQUIRE_EQUAL("0", data["status"].as_string());

// 21 approvals, tokens will be issued after 2/3 + 1 approvals
for (const auto &producer : _producer_candidates) {
init_swap(producer, init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
for (size_t i = 1; i < producers.size(); ++i) {
init_swap(producers.at(i), init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);
}

Expand Down Expand Up @@ -511,6 +513,11 @@ BOOST_FIXTURE_TEST_CASE(init_swap_test, rem_swap_tester) {
init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id,
init_swap_data.swap_timestamp), eosio_assert_message_exception);
// only top25 block producers approval is recorded
BOOST_REQUIRE_THROW(init_swap(N(whale1), init_swap_data.txid, init_swap_data.swap_pubkey,
init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id,
init_swap_data.swap_timestamp), eosio_assert_message_exception);
// symbol precision mismatch
BOOST_REQUIRE_THROW(init_swap(init_swap_data.rampayer, init_swap_data.txid, init_swap_data.swap_pubkey,
asset::from_string("201.0000 SYS"), init_swap_data.return_address,
Expand Down Expand Up @@ -543,18 +550,37 @@ BOOST_FIXTURE_TEST_CASE(init_swap_after_cancel_test, rem_swap_tester) {
// swap can be canceled after expiration (1 week)
.swap_timestamp = time_point_sec::from_iso_string("2019-12-05T00:00:43.000")
};
for (const auto &producer : _producer_candidates) {
init_swap(producer, init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
vector<name> producers(_producer_candidates.begin(), _producer_candidates.end());
asset before_init_balance = get_balance(N(rem.swap));

uint32_t majority_prod = (producers.size() * 2 / 3) + 2;
for (size_t i = 0; i < majority_prod; ++i) {
init_swap(producers[i], init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);
}

asset after_init_balance = get_balance(N(rem.swap));
// after issue tokens, balance rem.swap should be a before issue balance + amount of tokens to be a swapped
BOOST_REQUIRE_EQUAL(before_init_balance + init_swap_data.quantity, after_init_balance);

cancel_swap(N(rem.swap), init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);

// swap already canceled
BOOST_REQUIRE_THROW(init_swap(N(proda), init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id,
init_swap_data.swap_timestamp), eosio_assert_message_exception);
for (size_t i = majority_prod; i < producers.size(); ++i) {
init_swap(producers[i], init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);
}

auto data = get_singtable(N(rem.swap), N(swaps), "swap_data");
auto after_cancel_balance = get_balance(N(rem.swap));
auto core_stats_after = get_stats(symbol(CORE_SYMBOL));

// after cancel, balance rem.swap should be equal a before init balance
BOOST_REQUIRE_EQUAL(before_init_balance, after_cancel_balance);
// -1 if a swap status cancel
BOOST_REQUIRE_EQUAL("-1", data["status"].as_string());
BOOST_REQUIRE_EQUAL(init_swap_data.txid, data["txid"].as_string());
BOOST_REQUIRE_EQUAL(core_stats_after["supply"].as_string(), "100000100.0000 " + string(CORE_SYMBOL_NAME));
} FC_LOG_AND_RETHROW()
}

Expand All @@ -566,15 +592,18 @@ BOOST_FIXTURE_TEST_CASE(finish_swap_test, rem_swap_tester) {
.swap_pubkey = get_pubkey_str(swap_key_priv),
.swap_timestamp = time_point_sec(control->head_block_time())
};
vector<name> producers(_producer_candidates.begin(), _producer_candidates.end());
// amount of provided approvals must be a 2/3 + 1 of active producers
uint32_t majority_prod = (producers.size() * 2 / 3) + 1;
time_point swap_timepoint = init_swap_data.swap_timestamp.to_time_point();
string swap_payload = join({init_swap_data.swap_pubkey.substr(3), init_swap_data.txid, control->get_chain_id(),
init_swap_data.quantity.to_string(), init_swap_data.return_address,
init_swap_data.return_chain_id, std::to_string(swap_timepoint.sec_since_epoch())});

string swap_id = sha256::hash(swap_payload);

for (const auto &producer : _producer_candidates) {
init_swap(producer, init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
for (size_t i = 0; i <= majority_prod; ++i) {
init_swap(producers[i], init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);
}

Expand Down Expand Up @@ -607,9 +636,7 @@ BOOST_FIXTURE_TEST_CASE(finish_swap_test, rem_swap_tester) {
// 2 if swap status finish
BOOST_REQUIRE_EQUAL("2", data["status"].as_string());
BOOST_REQUIRE_EQUAL(string(swap_timepoint), data["swap_timestamp"].as_string());
// amount of provided approvals must be a 2/3 + 1 of active producers
uint32_t majority = (_producer_candidates.size() * 2 / 3) + 1;
BOOST_TEST(majority <= data["provided_approvals"].get_array().size());
BOOST_TEST(majority_prod <= data["provided_approvals"].get_array().size());
// balance equal : receiver balance += swapped quantity - producers_reward
BOOST_REQUIRE_EQUAL(receiver_before_balance + init_swap_data.quantity - producers_reward,
receiver_after_balance);
Expand All @@ -628,6 +655,22 @@ BOOST_FIXTURE_TEST_CASE(finish_swap_test, rem_swap_tester) {
core_from_string("500.0000"), init_swap_data.return_address,
init_swap_data.return_chain_id, init_swap_data.swap_timestamp, sign),
eosio_assert_message_exception);
// init after finish swap
remswap_before_balance = get_balance(N(rem.swap));
auto core_stats_before = get_stats(symbol(CORE_SYMBOL));

for (size_t i = majority_prod + 1; i < _producer_candidates.size(); ++i) {
init_swap(producers[i], init_swap_data.txid, init_swap_data.swap_pubkey, init_swap_data.quantity,
init_swap_data.return_address, init_swap_data.return_chain_id, init_swap_data.swap_timestamp);
}

data = get_singtable(N(rem.swap), N(swaps), "swap_data");
remswap_after_balance = get_balance(N(rem.swap));
auto core_stats_after = get_stats(symbol(CORE_SYMBOL));

BOOST_REQUIRE_EQUAL("2", data["status"].as_string());
BOOST_REQUIRE_EQUAL(remswap_before_balance, remswap_after_balance);
BOOST_REQUIRE_EQUAL(core_stats_before["supply"], core_stats_after["supply"]);
} FC_LOG_AND_RETHROW()
}

Expand Down