Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Fixes to system contract delegatebw action #4295

Merged
merged 3 commits into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions contracts/eosio.system/delegate_bandwidth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ namespace eosiosystem {
{
require_auth( from );
eosio_assert( stake_net_delta != asset(0) || stake_cpu_delta != asset(0), "should stake non-zero amount" );
eosio_assert( std::abs( (stake_net_delta + stake_cpu_delta).amount )
>= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ),
"net and cpu deltas cannot be opposite signs" );

account_name source_stake_from = from;
if ( transfer ) {
Expand Down Expand Up @@ -273,8 +276,16 @@ namespace eosiosystem {
auto net_balance = stake_net_delta;
auto cpu_balance = stake_cpu_delta;
bool need_deferred_trx = false;
if ( req != refunds_tbl.end() ) { //need to update refund
refunds_tbl.modify( req, 0, [&]( refund_request& r ) {


// net and cpu are same sign by assertions in delegatebw and undelegatebw
// redundant assertion also at start of changebw to protect against misuse of changebw
bool is_undelegating = (net_balance.amount + cpu_balance.amount ) < 0;
bool is_delegating_to_self = (!transfer && from == receiver);

if( is_delegating_to_self || is_undelegating ) {
if ( req != refunds_tbl.end() ) { //need to update refund
refunds_tbl.modify( req, 0, [&]( refund_request& r ) {
if ( net_balance < asset(0) || cpu_balance < asset(0) ) {
r.request_time = now();
}
Expand All @@ -293,17 +304,19 @@ namespace eosiosystem {
cpu_balance = asset(0);
}
});
eosio_assert( asset(0) <= req->net_amount, "negative net refund amount" ); //should never happen
eosio_assert( asset(0) <= req->cpu_amount, "negative cpu refund amount" ); //should never happen

if ( req->net_amount == asset(0) && req->cpu_amount == asset(0) ) {
refunds_tbl.erase( req );
need_deferred_trx = false;
} else {
need_deferred_trx = true;
}
} else if ( net_balance < asset(0) || cpu_balance < asset(0) ) { //need to create refund
refunds_tbl.emplace( from, [&]( refund_request& r ) {
eosio_assert( asset(0) <= req->net_amount, "negative net refund amount" ); //should never happen
eosio_assert( asset(0) <= req->cpu_amount, "negative cpu refund amount" ); //should never happen

if ( req->net_amount == asset(0) && req->cpu_amount == asset(0) ) {
refunds_tbl.erase( req );
need_deferred_trx = false;
} else {
need_deferred_trx = true;
}

} else if ( net_balance < asset(0) || cpu_balance < asset(0) ) { //need to create refund
refunds_tbl.emplace( from, [&]( refund_request& r ) {
r.owner = from;
if ( net_balance < asset(0) ) {
r.net_amount = -net_balance;
Expand All @@ -315,8 +328,9 @@ namespace eosiosystem {
} // else r.cpu_amount = 0 by default constructor
r.request_time = now();
});
need_deferred_trx = true;
} // else stake increase requested with no existing row in refunds_tbl -> nothing to do with refunds_tbl
need_deferred_trx = true;
} // else stake increase requested with no existing row in refunds_tbl -> nothing to do with refunds_tbl
} /// end if is_delegating_to_self || is_undelegating

if ( need_deferred_trx ) {
eosio::transaction out;
Expand Down Expand Up @@ -367,6 +381,7 @@ namespace eosiosystem {
eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" );
eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" );
eosio_assert( stake_net_quantity + stake_cpu_quantity > asset(0), "must stake a positive amount" );
eosio_assert( !transfer || from != receiver, "cannot use transfer flag if delegating to self" );

changebw( from, receiver, stake_net_quantity, stake_cpu_quantity, transfer);
} // delegatebw
Expand Down
169 changes: 143 additions & 26 deletions unittests/eosio.system_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,72 @@ BOOST_FIXTURE_TEST_CASE( stake_unstake_with_transfer, eosio_system_tester ) try
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "alice1111111", core_from_string("400.0000"), core_from_string("200.0000") ) );
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );

edump((get_balance( "eosio.stake" )));
produce_block( fc::hours(3*24-1) );
produce_blocks(1);
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
//after 3 days funds should be released

produce_block( fc::hours(1) );
produce_blocks(1);

BOOST_REQUIRE_EQUAL( core_from_string("1300.0000"), get_balance( "alice1111111" ) );

//stake should be equal to what was staked in constructor, voting power should be 0
total = get_total_stake("alice1111111");
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("0.0000")), get_voter_info( "alice1111111" ) );

// Now alice stakes to bob with transfer flag
BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "alice1111111", "bob111111111", core_from_string("100.0000"), core_from_string("100.0000") ) );

} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE( stake_to_self_with_transfer, eosio_system_tester ) try {
cross_15_percent_threshold();

BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111" ) );
transfer( "eosio", "alice1111111", core_from_string("1000.0000"), "eosio" );

BOOST_REQUIRE_EQUAL( wasm_assert_msg("cannot use transfer flag if delegating to self"),
stake_with_transfer( "alice1111111", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") )
);

} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE( stake_while_pending_refund, eosio_system_tester ) try {
cross_15_percent_threshold();

issue( "eosio", core_from_string("1000.0000"), config::system_account_name );
issue( "eosio.stake", core_from_string("1000.0000"), config::system_account_name );
BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111" ) );

//eosio stakes for alice with transfer flag

transfer( "eosio", "bob111111111", core_from_string("1000.0000"), "eosio" );
BOOST_REQUIRE_EQUAL( success(), stake_with_transfer( "bob111111111", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );

//check that alice has both bandwidth and voting power
auto total = get_total_stake("alice1111111");
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("300.0000")), get_voter_info( "alice1111111" ) );

BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111" ) );

//alice stakes for herself
transfer( "eosio", "alice1111111", core_from_string("1000.0000"), "eosio" );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
//now alice's stake should be equal to transfered from eosio + own stake
total = get_total_stake("alice1111111");
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("410.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("600.0000")), get_voter_info( "alice1111111" ) );

//alice can unstake everything (including what was transfered)
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "alice1111111", core_from_string("400.0000"), core_from_string("200.0000") ) );
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );

produce_block( fc::hours(3*24-1) );
produce_blocks(1);
Expand All @@ -224,7 +289,7 @@ BOOST_FIXTURE_TEST_CASE( stake_unstake_with_transfer, eosio_system_tester ) try

BOOST_REQUIRE_EQUAL( core_from_string("1300.0000"), get_balance( "alice1111111" ) );

//stake should be equal to what was staked in constructor, votring power should be 0
//stake should be equal to what was staked in constructor, voting power should be 0
total = get_total_stake("alice1111111");
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as<asset>());
Expand Down Expand Up @@ -492,73 +557,125 @@ BOOST_FIXTURE_TEST_CASE( stake_from_refund, eosio_system_tester ) try {
cross_15_percent_threshold();

issue( "alice1111111", core_from_string("1000.0000"), config::system_account_name );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );

auto total = get_total_stake( "bob111111111" );
auto total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as<asset>());

REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("300.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("50.0000"), core_from_string("50.0000") ) );

//unstake a share
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "bob111111111", core_from_string("100.0000"), core_from_string("50.0000") ) );
total = get_total_stake( "bob111111111" );
BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as<asset>());

REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("400.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("600.0000"), get_balance( "alice1111111" ) );

//unstake a share
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "alice1111111", core_from_string("100.0000"), core_from_string("50.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("150.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("250.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("600.0000"), get_balance( "alice1111111" ) );
auto refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string( "50.0000"), refund["cpu_amount"].as<asset>() );
//XXX auto request_time = refund["request_time"].as_int64();

//stake less than pending refund, entire amount should be traken from refund
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("50.0000"), core_from_string("25.0000") ) );
total = get_total_stake( "bob111111111" );
//alice delegates to bob, should pull from liquid balance not refund
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("50.0000"), core_from_string("50.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("60.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("350.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111" ) );
refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string( "50.0000"), refund["cpu_amount"].as<asset>() );

//stake less than pending refund, entire amount should be taken from refund
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "alice1111111", core_from_string("50.0000"), core_from_string("25.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("160.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("85.0000"), total["cpu_weight"].as<asset>());
refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("50.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string("25.0000"), refund["cpu_amount"].as<asset>() );
//request time should stay the same
//BOOST_REQUIRE_EQUAL( request_time, refund["request_time"].as_int64() );
//balance shoud stay the same
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
//balance should stay the same
BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111" ) );

//stake exactly pending refund amount
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("50.0000"), core_from_string("25.0000") ) );
total = get_total_stake( "bob111111111" );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "alice1111111", core_from_string("50.0000"), core_from_string("25.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as<asset>());
//pending refund should be removed
refund = get_refund_request( "alice1111111" );
BOOST_TEST_REQUIRE( refund.is_null() );
//balance shoud stay the same
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
//balance should stay the same
BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111" ) );

//create pending refund again
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "bob111111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
total = get_total_stake( "bob111111111" );
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("10.0000"), total["cpu_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111" ) );
refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as<asset>() );

//stake more than pending refund
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("300.0000"), core_from_string("200.0000") ) );
total = get_total_stake( "bob111111111" );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "alice1111111", core_from_string("300.0000"), core_from_string("200.0000") ) );
total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("310.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["cpu_weight"].as<asset>());
REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("700.0000") ), get_voter_info( "alice1111111" ) );
refund = get_refund_request( "alice1111111" );
BOOST_TEST_REQUIRE( refund.is_null() );
//200 EOS should be taken from alice's account
BOOST_REQUIRE_EQUAL( core_from_string("500.0000"), get_balance( "alice1111111" ) );
//200 core tokens should be taken from alice's account
BOOST_REQUIRE_EQUAL( core_from_string("300.0000"), get_balance( "alice1111111" ) );

} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE( stake_to_another_user_not_from_refund, eosio_system_tester ) try {
cross_15_percent_threshold();

issue( "alice1111111", core_from_string("1000.0000"), config::system_account_name );
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );

auto total = get_total_stake( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );

REQUIRE_MATCHING_OBJECT( voter( "alice1111111", core_from_string("300.0000") ), get_voter_info( "alice1111111" ) );
BOOST_REQUIRE_EQUAL( core_from_string("700.0000"), get_balance( "alice1111111" ) );

//unstake
BOOST_REQUIRE_EQUAL( success(), unstake( "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
auto refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as<asset>() );
//auto orig_request_time = refund["request_time"].as_int64();

//stake to another user
BOOST_REQUIRE_EQUAL( success(), stake( "alice1111111", "bob111111111", core_from_string("200.0000"), core_from_string("100.0000") ) );
total = get_total_stake( "bob111111111" );
BOOST_REQUIRE_EQUAL( core_from_string("210.0000"), total["net_weight"].as<asset>());
BOOST_REQUIRE_EQUAL( core_from_string("110.0000"), total["cpu_weight"].as<asset>());
//stake should be taken from alices' balance, and refund request should stay the same
BOOST_REQUIRE_EQUAL( core_from_string("400.0000"), get_balance( "alice1111111" ) );
refund = get_refund_request( "alice1111111" );
BOOST_REQUIRE_EQUAL( core_from_string("200.0000"), refund["net_amount"].as<asset>() );
BOOST_REQUIRE_EQUAL( core_from_string("100.0000"), refund["cpu_amount"].as<asset>() );
//BOOST_REQUIRE_EQUAL( orig_request_time, refund["request_time"].as_int64() );

} FC_LOG_AND_RETHROW()

// Tests for voting
BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try {
Expand Down