Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
tar zcf build.tar.gz build
ctest --test-dir build/tests --output-on-failure -j $(nproc)
- name: Upload builddir
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: builddir
path: build.tar.gz
65 changes: 64 additions & 1 deletion contracts/eosio.system/include/eosio.system/eosio.system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,9 +472,19 @@ namespace eosiosystem {
};


struct [[eosio::table, eosio::contract("eosio.system")]] gifted_ram {
name giftee;
name gifter;
int64_t ram_bytes = 0;

uint64_t primary_key() const { return giftee.value; } // unique as one giftee can only hold gifted ram from one gifter
};


typedef eosio::multi_index< "userres"_n, user_resources > user_resources_table;
typedef eosio::multi_index< "delband"_n, delegated_bandwidth > del_bandwidth_table;
typedef eosio::multi_index< "refunds"_n, refund_request > refunds_table;
typedef eosio::multi_index< "giftedram"_n, gifted_ram > gifted_ram_table;

// `rex_pool` structure underlying the rex pool table. A rex pool table entry is defined by:
// - `version` defaulted to zero,
Expand Down Expand Up @@ -1318,6 +1328,57 @@ namespace eosiosystem {
[[eosio::action]]
action_return_sellram sellram( const name& account, int64_t bytes );

/**
* Gift ram action, which transfers `bytes` of ram from `gifter` (`from`) to `giftee` (`to`),
* with the characteristic that the transfered ram is encumbered, meaning it can only be
* returned to the gifter via the `ungiftram` action. It cannot be traded, sold, re-gifted,
* or transfered otherwise. Its only use is for storing data.
*
* In addition:
* - requires that giftee does not hold gifted ram by someone else than gifter,
* as one account can only hold gifted ram from one gifter at any time.
* - current gifter can gift additional ram to a giftee at any time (no restriction)
* - the fact of receiving gifted ram does not add any restriction to an
* account, besides the usage restictions on the gifted ram itself.
* For example, the account can purchase additional ram, and transfer,
* trade or sell this additional ram freely.
* - this will update both:
* a. the `gifted_ram` table to record the gift
* b. the `user_resources_table` to increase the usable ram
* - the ram cost of adding a row in the gifted_ram table will be incurred
* by the gifter.
*
* @param from - the account ram is transfered from, i.e. the gifter
* @param to - the account ram is transfered to, i.e. the giftee
* @param bytes - the amount of ram to be transfered in bytes,
* @param memo - the memo string to accompany the transaction.
*/
[[eosio::action]]
action_return_ramtransfer giftram( const name from, const name to, int64_t bytes, const std::string& memo );

/**
* Return gifted ram (the full amount) to the gifter.
*
* - asserts that the `to` parameter is correct, i.e. that it matches the account which gifted the
* encumbered ram currently held by the `from` account.
* - there is currently no built-in incentive for a giftee to return gifted ram.
* - if `from` account is found to hold gifted ram, and has enough
* ram available to return the gift, this action will:
* a. transfer gifted ram back to gifter (full gifted amount)
* b. remove row from the gifted_ram table.
* c. unlock ram in the giftee's account equal to the overhead of
* removed row.
* d. decrease `ram_bytes` by the returned amount in `user_resources_table`
* - returned ram is unencumbered, which means there are no restrictions
* on its use.
*
* @param from - the account ram is transfered from, i.e. the giftee returning the gifted RAM
* @param to - the account ram is transfered to, i.e. the gifter
* @param memo - the memo string to accompany the transaction.
*/
[[eosio::action]]
action_return_ramtransfer ungiftram( const name from, const name to, const std::string& memo );

/**
* Logging for sellram action
*
Expand Down Expand Up @@ -1789,6 +1850,8 @@ namespace eosiosystem {
using buyrambytes_action = eosio::action_wrapper<"buyrambytes"_n, &system_contract::buyrambytes>;
using logbuyram_action = eosio::action_wrapper<"logbuyram"_n, &system_contract::logbuyram>;
using sellram_action = eosio::action_wrapper<"sellram"_n, &system_contract::sellram>;
using giftram_action = eosio::action_wrapper<"giftram"_n, &system_contract::giftram>;
using ungiftram_action = eosio::action_wrapper<"ungiftram"_n, &system_contract::ungiftram>;
using logsellram_action = eosio::action_wrapper<"logsellram"_n, &system_contract::logsellram>;
using ramtransfer_action = eosio::action_wrapper<"ramtransfer"_n, &system_contract::ramtransfer>;
using ramburn_action = eosio::action_wrapper<"ramburn"_n, &system_contract::ramburn>;
Expand Down Expand Up @@ -1868,7 +1931,7 @@ namespace eosiosystem {
void changebw( name from, const name& receiver,
const asset& stake_net_quantity, const asset& stake_cpu_quantity, bool transfer );
int64_t update_voting_power( const name& voter, const asset& total_update );
void set_resource_ram_bytes_limits( const name& owner );
void set_resource_ram_bytes_limits( const name& owner, int64_t bytes );
int64_t reduce_ram( const name& owner, int64_t bytes );
int64_t add_ram( const name& owner, int64_t bytes );
void update_stake_delegated( const name from, const name receiver, const asset stake_net_delta, const asset stake_cpu_delta );
Expand Down
84 changes: 73 additions & 11 deletions contracts/eosio.system/src/delegate_bandwidth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,56 @@ namespace eosiosystem {
require_recipient(account);
}

action_return_ramtransfer system_contract::giftram( const name from, const name to, int64_t bytes, const std::string& memo ) {
require_auth( from );
require_recipient(from);
require_recipient(to);

check( bytes > 0, "must gift positive bytes" );
check(is_account(to), "to=" + to.to_string() + " account does not exist");

// add ram gift to `gifted_ram_table`
gifted_ram_table giftedram(get_self(), get_self().value);
auto giftedram_itr = giftedram.find(to.value);
if (giftedram_itr == giftedram.end()) {
giftedram.emplace(from, [&](auto& res) { // gifter pays the ram costs for gift storage
res.giftee = to;
res.gifter = from;
res.ram_bytes = bytes;
});
} else {
check(giftedram_itr->gifter == from,
"A single RAM gifter is allowed at any one time per account, currently holding RAM gifted by: " +
giftedram_itr->gifter.to_string());
giftedram.modify(giftedram_itr, same_payer, [&](auto& res) {
res.ram_bytes += bytes;
});
}

return ramtransfer( from, to, bytes, memo );
}

action_return_ramtransfer system_contract::ungiftram( const name from, const name to, const std::string& memo ) {
require_auth( from );
require_recipient(from);
require_recipient(to);

check(is_account(to), "to=" + to.to_string() + " account does not exist");

// check the amount to return from `gifted_ram_table`
gifted_ram_table giftedram(get_self(), get_self().value);
auto giftedram_itr = giftedram.find(from.value);
check(giftedram_itr != giftedram.end(), "Account " + from.to_string() + " does not hold any gifted RAM");
check(giftedram_itr->gifter == to, "Returning RAM to wrong gifter, should be: " + giftedram_itr->gifter.to_string());
int64_t returned_bytes = giftedram_itr->ram_bytes;

// we erase the `gifted_ram_table` row before the call to `ramtransfer`, so the `reduce_ram()` will work even
// if we just have `returned_bytes` available in `user_resources_table`
giftedram.erase(giftedram_itr);

return ramtransfer( from, to, returned_bytes, memo );
}

/**
* This action will transfer RAM bytes from one account to another.
*/
Expand Down Expand Up @@ -206,17 +256,26 @@ namespace eosiosystem {
require_recipient( owner );
}

// this is called when transfering or selling ram, and encumbered (gifted) ram cannot be sold or transferred.
// so if we hold some gifted ram, deduct the amount from the ram available to be sold or transfered.
int64_t system_contract::reduce_ram( const name& owner, int64_t bytes ) {
check( bytes > 0, "cannot reduce negative byte" );

user_resources_table userres( get_self(), owner.value );
auto res_itr = userres.find( owner.value );
check( res_itr != userres.end(), "no resource row" );
check( res_itr->ram_bytes >= bytes, "insufficient quota" );

gifted_ram_table giftedram( get_self(), get_self().value );
auto giftedram_itr = giftedram.find( owner.value );
bool holding_gifted_ram = (giftedram_itr != giftedram.end());

auto available_bytes = holding_gifted_ram ? res_itr->ram_bytes - giftedram_itr->ram_bytes : res_itr->ram_bytes;
check( available_bytes >= bytes, "insufficient quota" );

userres.modify( res_itr, same_payer, [&]( auto& res ) {
res.ram_bytes -= bytes;
});
set_resource_ram_bytes_limits( owner );
set_resource_ram_bytes_limits( owner, res_itr->ram_bytes );

// logging
system_contract::logramchange_action logramchange_act{ get_self(), { {get_self(), active_permission} }};
Expand All @@ -229,35 +288,39 @@ namespace eosiosystem {
check( is_account(owner), "owner=" + owner.to_string() + " account does not exist");
user_resources_table userres( get_self(), owner.value );
auto res_itr = userres.find( owner.value );

int64_t updated_ram_bytes = 0;
if ( res_itr == userres.end() ) {
// only when `owner == null_account` can the row not be found (see `ramburn`)
userres.emplace( owner, [&]( auto& res ) {
res.owner = owner;
res.net_weight = asset( 0, core_symbol() );
res.cpu_weight = asset( 0, core_symbol() );
res.ram_bytes = bytes;
});
updated_ram_bytes = bytes;
} else {
// row should exist as it is always created when creating the account, see `native::newaccount`
userres.modify( res_itr, same_payer, [&]( auto& res ) {
res.ram_bytes += bytes;
});
updated_ram_bytes = res_itr->ram_bytes;
}
set_resource_ram_bytes_limits( owner );

set_resource_ram_bytes_limits( owner, updated_ram_bytes );

// logging
system_contract::logramchange_action logramchange_act{ get_self(), { {get_self(), active_permission} } };
logramchange_act.send( owner, bytes, res_itr->ram_bytes );
return res_itr->ram_bytes;
logramchange_act.send( owner, bytes, updated_ram_bytes );
return updated_ram_bytes;
}

void system_contract::set_resource_ram_bytes_limits( const name& owner ) {
user_resources_table userres( get_self(), owner.value );
auto res_itr = userres.find( owner.value );

void system_contract::set_resource_ram_bytes_limits( const name& owner, int64_t res_bytes ) {
auto voter_itr = _voters.find( owner.value );
if ( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) {
int64_t ram_bytes, net, cpu;
get_resource_limits( owner, ram_bytes, net, cpu );
set_resource_limits( owner, res_itr->ram_bytes + ram_gift_bytes, net, cpu );
set_resource_limits( owner, res_bytes + ram_gift_bytes, net, cpu );
}
}

Expand All @@ -269,7 +332,6 @@ namespace eosiosystem {
return { total_vesting, vested };
}


void validate_b1_vesting( int64_t new_stake, asset stake_change ) {
const auto [total_vesting, vested] = get_b1_vesting_info();
auto unvestable = total_vesting - vested;
Expand Down
23 changes: 19 additions & 4 deletions contracts/eosio.token/include/eosio.token/eosio.token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ namespace eosio {
using std::string;

/**
* 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` 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 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.
* 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.
* 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 {
public:
Expand Down
6 changes: 3 additions & 3 deletions tests/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ struct contracts {

namespace system_contracts::testing::test_contracts {

static std::vector<uint8_t> blockinfo_tester_wasm()
[[maybe_unused]] static std::vector<uint8_t> blockinfo_tester_wasm()
{
return eosio::testing::read_wasm(
"${CMAKE_BINARY_DIR}/contracts/test_contracts/blockinfo_tester/blockinfo_tester.wasm");
}
static std::vector<uint8_t> sendinline_wasm()
[[maybe_unused]] static std::vector<uint8_t> sendinline_wasm()
{
return eosio::testing::read_wasm(
"${CMAKE_BINARY_DIR}/contracts/test_contracts/sendinline/sendinline.wasm");
}
static std::vector<char> sendinline_abi()
[[maybe_unused]] static std::vector<char> sendinline_abi()
{
return eosio::testing::read_abi(
"${CMAKE_BINARY_DIR}/contracts/test_contracts/sendinline/sendinline.abi");
Expand Down
8 changes: 4 additions & 4 deletions tests/eosio.powerup_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,9 @@ BOOST_AUTO_TEST_CASE(rent_tests) try {
powerup_tester t;
// This test is flaky. It only passes when head block time is on the second.
// https://github.com/AntelopeIO/reference-contracts/issues/104 tracks this issue.
if ((t.control->head_block_time().time_since_epoch().count() % 1000000ll) != 0) {
if ((t.control->head().block_time().time_since_epoch().count() % 1000000ll) != 0) {
t.produce_block();
BOOST_REQUIRE_EQUAL((t.control->head_block_time().time_since_epoch().count() % 1000000ll), 0);
BOOST_REQUIRE_EQUAL((t.control->head().block_time().time_since_epoch().count() % 1000000ll), 0);
}

BOOST_REQUIRE_EQUAL(t.wasm_assert_msg("powerup hasn't been initialized"), //
Expand Down Expand Up @@ -586,9 +586,9 @@ BOOST_AUTO_TEST_CASE(rent_tests) try {
auto init = [](auto& t, bool rex) {
// This test is flaky. It only passes when head block time is on the second.
// https://github.com/AntelopeIO/reference-contracts/issues/104 tracks this issue.
if ((t.control->head_block_time().time_since_epoch().count() % 1000000ll) != 0) {
if ((t.control->head().block_time().time_since_epoch().count() % 1000000ll) != 0) {
t.produce_block();
BOOST_REQUIRE_EQUAL((t.control->head_block_time().time_since_epoch().count() % 1000000ll), 0);
BOOST_REQUIRE_EQUAL((t.control->head().block_time().time_since_epoch().count() % 1000000ll), 0);
}

BOOST_REQUIRE_EQUAL("", t.configbw(t.make_config([&](auto& config) {
Expand Down
8 changes: 4 additions & 4 deletions tests/eosio.system_blockinfo_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ try {

produce_blocks(1);

auto start_block_height = control->head_block_num();
auto start_block_timestamp = control->head_block_time();
auto start_block_height = control->head().block_num();
auto start_block_timestamp = control->head().block_time();

std::vector<block_info_record> expected_table;
auto add_to_expected_table = [&expected_table](uint32_t block_height, fc::time_point block_timestamp) {
Expand Down Expand Up @@ -370,8 +370,8 @@ try {

produce_blocks(1); // Now there should be one entry in blockinfo table.

auto start_block_height = control->head_block_num();
auto start_block_timestamp = control->head_block_time();
auto start_block_height = control->head().block_num();
auto start_block_timestamp = control->head().block_time();

BOOST_REQUIRE(8 < start_block_height); // Test assumes it is safe to subtract 8 from start_block_height.

Expand Down
Loading