Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token upgrade #67

Closed
wants to merge 16 commits into from
Closed
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
2 changes: 1 addition & 1 deletion .cicd/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"prerelease":false
},
"cdt":{
"target":"hotstuff_integration",
"target":"main",
"prerelease":false
}
}
16 changes: 13 additions & 3 deletions contracts/eosio.system/src/producer_pay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ namespace eosiosystem {
void system_contract::claimrewards( const name& owner ) {
require_auth( owner );

const auto& prod = _producers.get( owner.value );
const auto& prod = _producers.get( owner.value, "producer not registered" );
check( prod.active(), "producer does not have an active key" );

check( _gstate.thresh_activated_stake_time != time_point(),
Expand All @@ -87,6 +87,8 @@ namespace eosiosystem {
check( ct - prod.last_claim_time > microseconds(useconds_per_day), "already claimed rewards within past day" );

const asset token_supply = token::get_supply(token_account, core_symbol().code() );
const asset token_max_supply = token::get_max_supply(token_account, core_symbol().code() );
const asset token_balance = token::get_balance(token_account, get_self(), core_symbol().code() );
const auto usecs_since_last_fill = (ct - _gstate.last_pervote_bucket_fill).count();

if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > time_point() ) {
Expand All @@ -101,9 +103,17 @@ namespace eosiosystem {
int64_t to_per_vote_pay = to_producers - to_per_block_pay;

if( new_tokens > 0 ) {
// issue new tokens or use existing eosio token balance
{
token::issue_action issue_act{ token_account, { {get_self(), active_permission} } };
issue_act.send( get_self(), asset(new_tokens, core_symbol()), "issue tokens for producer pay and savings" );
// issue new tokens if circulating supply does not exceed max supply
if ( token_supply.amount + new_tokens <= token_max_supply.amount ) {
token::issue_action issue_act{ token_account, { {get_self(), active_permission} } };
issue_act.send( get_self(), asset(new_tokens, core_symbol()), "issue tokens for producer pay and savings" );

// use existing eosio token balance if circulating supply exceeds max supply
} else {
check( token_balance.amount >= new_tokens, "insufficient system token balance for claiming rewards");
}
}
{
token::transfer_action transfer_act{ token_account, { {get_self(), active_permission} } };
Expand Down
52 changes: 42 additions & 10 deletions contracts/eosio.token/include/eosio.token/eosio.token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ namespace eosio {

/**
* The `eosio.token` sample system contract defines the structures and actions that allow users to create, issue, and manage tokens for EOSIO based blockchains. It demonstrates one way to implement a smart contract which allows for creation and management of tokens. It is possible for one to create a similar contract which suits different needs. However, it is recommended that if one only needs a token with the below listed actions, that one uses the `eosio.token` contract instead of developing their own.
*
*
* The `eosio.token` contract class also implements two useful public static methods: `get_supply` and `get_balance`. The first allows one to check the total supply of a specified token, created by an account and the second allows one to check the balance of a token for a specified account (the token creator account has to be specified as well).
*
*
* The `eosio.token` contract manages the set of tokens, accounts and their corresponding balances, by using two internal multi-index structures: the `accounts` and `stats`. The `accounts` multi-index table holds, for each row, instances of `account` object and the `account` object holds information about the balance of one token. The `accounts` table is scoped to an EOSIO account, and it keeps the rows indexed based on the token's symbol. This means that when one queries the `accounts` multi-index table for an account name the result is all the tokens that account holds at the moment.
*
*
* Similarly, the `stats` multi-index table, holds instances of `currency_stats` objects for each row, which contains information about current supply, maximum supply, and the creator account for a symbol token. The `stats` table is scoped to the token symbol. Therefore, when one queries the `stats` table for a token symbol the result is one single entry/row corresponding to the queried symbol token if it was previously created, or nothing, otherwise.
*/
class [[eosio::contract("eosio.token")]] token : public contract {
Expand All @@ -41,15 +41,34 @@ namespace eosio {
void create( const name& issuer,
const asset& maximum_supply);
/**
* This action issues to `to` account a `quantity` of tokens.
* This action issues to `to` account a `quantity` of tokens.
*
* @param to - the account to issue tokens to, it must be the same as the issuer,
* @param quantity - the amount of tokens to be issued,
* @memo - the memo string that accompanies the token issue transaction.
* @param memo - the memo string that accompanies the token issue transaction.
*/
[[eosio::action]]
void issue( const name& to, const asset& quantity, const string& memo );

/**
* Issues only the necessary tokens to bridge the gap between the current supply and the targeted total.
*
* @param to - the account to issue tokens to, it must be the same as the issuer,
* @param supply - the target total supply for the token.
* @param memo - the memo string that accompanies the token issue transaction.
*/
[[eosio::action]]
void issuefixed( const name& to, const asset& supply, const string& memo );

/**
* Set the maximum supply of the token.
*
* @param issuer - the issuer account setting the maximum supply.
* @param maximum_supply - the maximum supply of the token.
*/
[[eosio::action]]
void setmaxsupply( const name& issuer, const asset& maximum_supply );

/**
* The opposite for create action, if all validations succeed,
* it debits the statstable.supply amount.
Expand Down Expand Up @@ -104,15 +123,25 @@ namespace eosio {
static asset get_supply( const name& token_contract_account, const symbol_code& sym_code )
{
stats statstable( token_contract_account, sym_code.raw() );
const auto& st = statstable.get( sym_code.raw(), "invalid supply symbol code" );
return st.supply;
return statstable.get( sym_code.raw(), "invalid supply symbol code" ).supply;
}

static asset get_max_supply( const name& token_contract_account, const symbol_code& sym_code )
{
stats statstable( token_contract_account, sym_code.raw() );
return statstable.get( sym_code.raw(), "invalid supply symbol code" ).max_supply;
}

static name get_issuer( const name& token_contract_account, const symbol_code& sym_code )
{
stats statstable( token_contract_account, sym_code.raw() );
return statstable.get( sym_code.raw(), "invalid supply symbol code" ).issuer;
}

static asset get_balance( const name& token_contract_account, const name& owner, const symbol_code& sym_code )
{
accounts accountstable( token_contract_account, owner.value );
const auto& ac = accountstable.get( sym_code.raw(), "no balance with specified symbol" );
return ac.balance;
return accountstable.get( sym_code.raw(), "no balance with specified symbol" ).balance;
}

using create_action = eosio::action_wrapper<"create"_n, &token::create>;
Expand All @@ -121,7 +150,9 @@ namespace eosio {
using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;
using open_action = eosio::action_wrapper<"open"_n, &token::open>;
using close_action = eosio::action_wrapper<"close"_n, &token::close>;
private:
using issuefixed_action = eosio::action_wrapper<"issuefixed"_n, &token::issuefixed>;
using setmaxsupply_action = eosio::action_wrapper<"setmaxsupply"_n, &token::setmaxsupply>;

struct [[eosio::table]] account {
asset balance;

Expand All @@ -139,6 +170,7 @@ namespace eosio {
typedef eosio::multi_index< "accounts"_n, account > accounts;
typedef eosio::multi_index< "stat"_n, currency_stats > stats;

private:
void sub_balance( const name& owner, const asset& value );
void add_balance( const name& owner, const asset& value, const name& ram_payer );
};
Expand Down
32 changes: 32 additions & 0 deletions contracts/eosio.token/ricardian/eosio.token.contracts.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ This action will not result any any tokens being issued into circulation.

RAM will deducted from {{$action.account}}’s resources to create the necessary records.

<h1 class="contract">setmaxsupply</h1>

---
spec_version: "0.2.0"
title: Set Max Supply
summary: 'Set max supply for token'
icon: @ICON_BASE_URL@/@TOKEN_ICON_URI@
---

{{issuer}} will be allowed to issue tokens into circulation, up to a maximum supply of {{maximum_supply}}.

This action will not result any any tokens being issued into circulation.

<h1 class="contract">issue</h1>

---
Expand All @@ -47,6 +60,25 @@ If {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, or the

This action does not allow the total quantity to exceed the max allowed supply of the token.

<h1 class="contract">issuefixed</h1>

---
spec_version: "0.2.0"
title: Issue Fixed Supply of Tokens into Circulation
summary: 'Issue up to {{nowrap supply}} supply into circulation and transfer into {{nowrap to}}’s account'
icon: @ICON_BASE_URL@/@TOKEN_ICON_URI@
---

The token manager agrees to issue tokens up to {{supply}} fixed supply into circulation, and transfer it into {{to}}’s account.

{{#if memo}}There is a memo attached to the transfer stating:
{{memo}}
{{/if}}

If {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, or the token manager does not have a balance for {{asset_to_symbol_code quantity}}, the token manager will be designated as the RAM payer of the {{asset_to_symbol_code quantity}} token balance for {{to}}. As a result, RAM will be deducted from the token manager’s resources to create the necessary records.

This action does not allow the total quantity to exceed the max allowed supply of the token.

<h1 class="contract">open</h1>

---
Expand Down
28 changes: 27 additions & 1 deletion contracts/eosio.token/src/eosio.token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ void token::create( const name& issuer,
});
}


void token::issue( const name& to, const asset& quantity, const string& memo )
{
auto sym = quantity.symbol;
Expand All @@ -49,6 +48,33 @@ void token::issue( const name& to, const asset& quantity, const string& memo )
add_balance( st.issuer, quantity, st.issuer );
}

void token::issuefixed( const name& to, const asset& supply, const string& memo )
{
const asset circulating_supply = get_supply( get_self(), supply.symbol.code() );
check( circulating_supply.symbol == supply.symbol, "symbol precision mismatch" );
const asset quantity = supply - circulating_supply;
issue( to, quantity, memo );
}

void token::setmaxsupply( const name& issuer, const asset& maximum_supply )
{
auto sym = maximum_supply.symbol;
check( maximum_supply.is_valid(), "invalid supply");
check( maximum_supply.amount > 0, "max-supply must be positive");

stats statstable( get_self(), sym.code().raw() );
auto & st = statstable.get( sym.code().raw(), "token supply does not exist" );
check( issuer == st.issuer, "only issuer can set token maximum supply" );
require_auth( st.issuer );

check( maximum_supply.symbol == st.supply.symbol, "symbol precision mismatch" );
check( maximum_supply.amount >= st.supply.amount, "max supply is less than available supply");

statstable.modify( st, same_payer, [&]( auto& s ) {
s.max_supply = maximum_supply;
});
}

void token::retire( const asset& quantity, const string& memo )
{
auto sym = quantity.symbol;
Expand Down
30 changes: 26 additions & 4 deletions tests/eosio.system_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1157,14 +1157,36 @@ class eosio_system_tester : public TESTER {
base_tester::push_action(contract, "create"_n, contract, act );
}

void issue( const asset& amount, const name& manager = config::system_account_name ) {
base_tester::push_action( "eosio.token"_n, "issue"_n, manager, mutable_variant_object()
("to", manager )
("quantity", amount )
void issue( const asset& quantity, const name& to = config::system_account_name ) {
base_tester::push_action( "eosio.token"_n, "issue"_n, to, mutable_variant_object()
("to", to )
("quantity", quantity )
("memo", "")
);
}

void retire( const asset& quantity, const name& issuer = config::system_account_name ) {
base_tester::push_action( "eosio.token"_n, "retire"_n, issuer, mutable_variant_object()
("quantity", quantity )
("memo", "")
);
}

void issuefixed( const asset& supply, const name& to = config::system_account_name ) {
base_tester::push_action( "eosio.token"_n, "issuefixed"_n, to, mutable_variant_object()
("to", to )
("supply", supply )
("memo", "")
);
}

void setmaxsupply( const asset& maximum_supply, const name& issuer = config::system_account_name) {
base_tester::push_action( "eosio.token"_n, "setmaxsupply"_n, issuer, mutable_variant_object()
("issuer", issuer )
("maximum_supply", maximum_supply )
);
}

void transfer( const name& from, const name& to, const asset& amount, const name& manager = config::system_account_name ) {
base_tester::push_action( "eosio.token"_n, "transfer"_n, manager, mutable_variant_object()
("from", from)
Expand Down
47 changes: 37 additions & 10 deletions tests/eosio.system_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,7 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester, * boost::unit_test::t

// defproducerb tries to claim rewards but he's not on the list
{
BOOST_REQUIRE_EQUAL(wasm_assert_msg("unable to find key"),
BOOST_REQUIRE_EQUAL(wasm_assert_msg("producer not registered"),
push_action("defproducerb"_n, "claimrewards"_n, mvo()("owner", "defproducerb")));
}

Expand All @@ -1638,6 +1638,27 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester, * boost::unit_test::t
BOOST_REQUIRE(500 * 10000 > int64_t(double(initial_supply.get_amount()) * double(0.05)) - (supply.get_amount() - initial_supply.get_amount()));
BOOST_REQUIRE(500 * 10000 > int64_t(double(initial_supply.get_amount()) * double(0.04)) - (savings - initial_savings));
}

// test claimrewards when max supply is reached
{
produce_block(fc::hours(24));

const asset before_supply = get_token_supply();
const asset before_system_balance = get_balance(config::system_account_name);
const asset before_producer_balance = get_balance("defproducera"_n);

setmaxsupply( before_supply );
BOOST_REQUIRE_EQUAL(success(), push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));

const asset after_supply = get_token_supply();
const asset after_system_balance = get_balance(config::system_account_name);
const asset after_producer_balance = get_balance("defproducera"_n);

BOOST_REQUIRE_EQUAL(after_supply.get_amount() - before_supply.get_amount(), 0);
BOOST_REQUIRE_EQUAL(after_system_balance.get_amount() - before_system_balance.get_amount(), -1407793756);
BOOST_REQUIRE_EQUAL(after_producer_balance.get_amount() - before_producer_balance.get_amount(), 281558751);
}

} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(change_inflation, eosio_system_tester) try {
Expand Down Expand Up @@ -1738,7 +1759,8 @@ BOOST_FIXTURE_TEST_CASE(change_inflation, eosio_system_tester) try {
BOOST_AUTO_TEST_CASE(extreme_inflation) try {
eosio_system_tester t(eosio_system_tester::setup_level::minimal);
symbol core_symbol{CORE_SYM};
t.create_currency( "eosio.token"_n, config::system_account_name, asset((1ll << 62) - 1, core_symbol) );
const asset max_supply = asset((1ll << 62) - 1, core_symbol);
t.create_currency( "eosio.token"_n, config::system_account_name, max_supply );
t.issue( asset(10000000000000, core_symbol) );
t.deploy_contract();
t.produce_block();
Expand All @@ -1752,25 +1774,30 @@ BOOST_AUTO_TEST_CASE(extreme_inflation) try {

BOOST_REQUIRE_EQUAL(t.success(), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));
t.produce_block();
asset current_supply;
{
vector<char> data = t.get_row_by_account( "eosio.token"_n, name(core_symbol.to_symbol_code().value), "stat"_n, account_name(core_symbol.to_symbol_code().value) );
current_supply = t.token_abi_ser.binary_to_variant("currency_stats", data, abi_serializer::create_yield_function(eosio_system_tester::abi_serializer_max_time))["supply"].template as<asset>();
}
t.issue( asset((1ll << 62) - 1, core_symbol) - current_supply );
const asset current_supply = t.get_token_supply();
t.issue( max_supply - current_supply );

// empty system balance
// claimrewards operates by either `issue` new tokens or using the existing system balance
const asset system_balance = t.get_balance(config::system_account_name);
t.transfer( config::system_account_name, "eosio.null"_n, system_balance, config::system_account_name);
BOOST_REQUIRE_EQUAL(t.get_balance(config::system_account_name).get_amount(), 0);
BOOST_REQUIRE_EQUAL(t.get_token_supply().get_amount() - max_supply.get_amount(), 0);

// set maximum inflation
BOOST_REQUIRE_EQUAL(t.success(), t.setinflation(std::numeric_limits<int64_t>::max(), 50000, 40000));
t.produce_block();

t.produce_block(fc::hours(10*24));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("quantity exceeds available supply"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("insufficient system token balance for claiming rewards"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));

t.produce_block(fc::hours(11*24));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("magnitude of asset amount must be less than 2^62"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));

t.produce_block(fc::hours(24));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("overflow in calculating new tokens to be issued; inflation rate is too high"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));
BOOST_REQUIRE_EQUAL(t.success(), t.setinflation(500, 50000, 40000));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("quantity exceeds available supply"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));
BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("insufficient system token balance for claiming rewards"), t.push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));
} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(multiple_producer_pay, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try {
Expand Down
Loading
Loading