Skip to content

Commit

Permalink
Merge pull request #102 from EOS-Nation/feature/ramtransfer
Browse files Browse the repository at this point in the history
Transferable RAM
  • Loading branch information
ericpassmore authored Jan 16, 2024
2 parents c6113db + d1e6d97 commit fa4424b
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 43 deletions.
25 changes: 25 additions & 0 deletions contracts/eosio.system/include/eosio.system/eosio.system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,26 @@ namespace eosiosystem {
[[eosio::action]]
void sellram( const name& account, int64_t bytes );

/**
* Transfer ram action, reduces sender's quota by bytes and increase receiver's quota by bytes.
*
* @param from - the ram sender account,
* @param to - the ram receiver account,
* @param bytes - the amount of ram to transfer in bytes.
*/
[[eosio::action]]
void ramtransfer( const name& from, const name& to, int64_t bytes );

/**
* Logging for ram changes
*
* @param owner - the ram owner account,
* @param bytes - the bytes balance change,
* @param ram_bytes - the ram bytes held by owner after the action.
*/
[[eosio::action]]
void logramchange( const name& owner, int64_t bytes, int64_t ram_bytes );

/**
* Refund action, this action is called after the delegation-period to claim all pending
* unstaked tokens belonging to owner.
Expand Down Expand Up @@ -1418,6 +1438,8 @@ namespace eosiosystem {
using buyram_action = eosio::action_wrapper<"buyram"_n, &system_contract::buyram>;
using buyrambytes_action = eosio::action_wrapper<"buyrambytes"_n, &system_contract::buyrambytes>;
using sellram_action = eosio::action_wrapper<"sellram"_n, &system_contract::sellram>;
using ramtransfer_action = eosio::action_wrapper<"ramtransfer"_n, &system_contract::ramtransfer>;
using logramchange_action = eosio::action_wrapper<"logramchange"_n, &system_contract::logramchange>;
using refund_action = eosio::action_wrapper<"refund"_n, &system_contract::refund>;
using regproducer_action = eosio::action_wrapper<"regproducer"_n, &system_contract::regproducer>;
using regproducer2_action = eosio::action_wrapper<"regproducer2"_n, &system_contract::regproducer2>;
Expand Down Expand Up @@ -1496,6 +1518,9 @@ namespace eosiosystem {
void changebw( name from, const name& receiver,
const asset& stake_net_quantity, const asset& stake_cpu_quantity, bool transfer );
void update_voting_power( const name& voter, const asset& total_update );
void set_resource_ram_bytes_limits( const name& owner );
void reduce_ram( const name& owner, int64_t bytes );
void add_ram( const name& owner, int64_t bytes );

// defined in voting.cpp
void register_producer( const name& producer, const eosio::block_signing_authority& producer_authority, const std::string& url, uint16_t location );
Expand Down
11 changes: 11 additions & 0 deletions contracts/eosio.system/ricardian/eosio.system.contracts.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,17 @@ icon: @ICON_BASE_URL@/@RESOURCE_ICON_URI@

Sell {{bytes}} bytes of unused RAM from account {{account}} at market price. This transaction will incur a 0.5% fee on the proceeds which depend on market rates.

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

---
spec_version: "0.2.0"
title: Transfer RAM from Account
summary: 'Transfer unused RAM from {{nowrap from}} to {{nowrap to}}'
icon: @ICON_BASE_URL@/@RESOURCE_ICON_URI@
---

Transfer {{bytes}} bytes of unused RAM from account {{from}} to account {{to}}.

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

---
Expand Down
113 changes: 74 additions & 39 deletions contracts/eosio.system/src/delegate_bandwidth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,7 @@ namespace eosiosystem {
_gstate.total_ram_bytes_reserved += uint64_t(bytes_out);
_gstate.total_ram_stake += quant_after_fee.amount;

user_resources_table userres( get_self(), receiver.value );
auto res_itr = userres.find( receiver.value );
if( res_itr == userres.end() ) {
res_itr = userres.emplace( receiver, [&]( auto& res ) {
res.owner = receiver;
res.net_weight = asset( 0, core_symbol() );
res.cpu_weight = asset( 0, core_symbol() );
res.ram_bytes = bytes_out;
});
} else {
userres.modify( res_itr, receiver, [&]( auto& res ) {
res.ram_bytes += bytes_out;
});
}

auto voter_itr = _voters.find( res_itr->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( res_itr->owner, ram_bytes, net, cpu );
set_resource_limits( res_itr->owner, res_itr->ram_bytes + ram_gift_bytes, net, cpu );
}
add_ram( receiver, bytes_out );
}

/**
Expand All @@ -111,13 +91,7 @@ namespace eosiosystem {
void system_contract::sellram( const name& account, int64_t bytes ) {
require_auth( account );
update_ram_supply();

check( bytes > 0, "cannot sell negative byte" );

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

asset tokens_out;
auto itr = _rammarket.find(ramcore_symbol.raw());
Expand All @@ -134,17 +108,6 @@ namespace eosiosystem {
//// this shouldn't happen, but just in case it does we should prevent it
check( _gstate.total_ram_stake >= 0, "error, attempt to unstake more tokens than previously staked" );

userres.modify( res_itr, account, [&]( auto& res ) {
res.ram_bytes -= bytes;
});

auto voter_itr = _voters.find( res_itr->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( res_itr->owner, ram_bytes, net, cpu );
set_resource_limits( res_itr->owner, res_itr->ram_bytes + ram_gift_bytes, net, cpu );
}

{
token::transfer_action transfer_act{ token_account, { {ram_account, active_permission}, {account, active_permission} } };
transfer_act.send( ram_account, account, asset(tokens_out), "sell ram" );
Expand All @@ -158,6 +121,78 @@ namespace eosiosystem {
}
}

/**
* This action will transfer RAM bytes from one account to another.
*/
void system_contract::ramtransfer( const name& from, const name& to, int64_t bytes ) {
require_auth( from );
update_ram_supply();
reduce_ram( from, bytes );
add_ram( to, bytes );
require_recipient( from );
require_recipient( to );
}

[[eosio::action]]
void system_contract::logramchange( const name& owner, int64_t bytes, int64_t ram_bytes )
{
require_auth( get_self() );
require_recipient( owner );
}

void 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" );

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

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

void system_contract::add_ram( const name& owner, int64_t bytes ) {
check( bytes > 0, "cannot add negative byte" );
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 );
if ( res_itr == userres.end() ) {
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;
});
} else {
userres.modify( res_itr, same_payer, [&]( auto& res ) {
res.ram_bytes += bytes;
});
}
set_resource_ram_bytes_limits( owner );

// logging
system_contract::logramchange_action logramchange_act{ get_self(), { {get_self(), active_permission} } };
logramchange_act.send( owner, bytes, res_itr->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 );

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 );
}
}

void validate_b1_vesting( int64_t stake ) {
const int64_t base_time = 1527811200; /// Friday, June 1, 2018 12:00:00 AM UTC
const int64_t current_time = 1638921540; /// Tuesday, December 7, 2021 11:59:00 PM UTC
Expand Down
42 changes: 42 additions & 0 deletions tests/eosio.system_ram_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <boost/test/unit_test.hpp>
#include <eosio/chain/contract_table_objects.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/wast_to_wasm.hpp>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <fc/log/logger.hpp>
#include <eosio/chain/exceptions.hpp>

#include "eosio.system_tester.hpp"

using namespace eosio_system;

BOOST_AUTO_TEST_SUITE(eosio_system_ram_tests)

BOOST_FIXTURE_TEST_CASE( ram_transfer, eosio_system_tester ) try {
const std::vector<account_name> accounts = { "alice"_n, "bob"_n };
create_accounts_with_resources( accounts );
const account_name alice = accounts[0];
const account_name bob = accounts[1];

transfer( config::system_account_name, alice, core_sym::from_string("100.0000"), config::system_account_name );
transfer( config::system_account_name, bob, core_sym::from_string("100.0000"), config::system_account_name );
BOOST_REQUIRE_EQUAL( success(), buyrambytes( alice, alice, 10000 ) );
BOOST_REQUIRE_EQUAL( success(), buyrambytes( bob, bob, 10000 ) );

const uint64_t alice_before = get_total_stake( alice )["ram_bytes"].as_uint64();
const uint64_t bob_before = get_total_stake( bob )["ram_bytes"].as_uint64();

BOOST_REQUIRE_EQUAL( success(), ramtransfer( alice, bob, 1000 ) );

const uint64_t alice_after = get_total_stake( alice )["ram_bytes"].as_uint64();
const uint64_t bob_after = get_total_stake( bob )["ram_bytes"].as_uint64();

BOOST_REQUIRE_EQUAL( alice_before - 1000, alice_after );
BOOST_REQUIRE_EQUAL( bob_before + 1000, bob_after );

} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()
9 changes: 8 additions & 1 deletion tests/eosio.system_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ class eosio_system_tester : public TESTER {
return buyram( account_name(payer), account_name(receiver), eosin );
}

action_result ramtransfer( const account_name& from, const account_name& to, uint32_t bytes ) {
return push_action( from, "ramtransfer"_n, mvo()( "from",from)("to",to)("bytes",bytes) );
}
action_result ramtransfer( std::string_view from, std::string_view to, uint32_t bytes ) {
return ramtransfer( account_name(from), account_name(to), bytes );
}

action_result buyrambytes( const account_name& payer, account_name receiver, uint32_t numbytes ) {
return push_action( payer, "buyrambytes"_n, mvo()( "payer",payer)("receiver",receiver)("bytes",numbytes) );
}
Expand Down Expand Up @@ -707,7 +714,7 @@ class eosio_system_tester : public TESTER {
memcpy( data.data(), itr->value.data(), data.size() );
return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_return_buckets", data, abi_serializer::create_yield_function(abi_serializer_max_time) );
}

void setup_rex_accounts( const std::vector<account_name>& accounts,
const asset& init_balance,
const asset& net = core_sym::from_string("80.0000"),
Expand Down
5 changes: 2 additions & 3 deletions tests/eosio.system_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3929,7 +3929,7 @@ BOOST_FIXTURE_TEST_CASE( rex_rounding_issue, eosio_system_tester ) try {
auto rent_and_go = [&] (int cnt) {
for(auto& rb : rexborrowers) {
BOOST_REQUIRE_EQUAL( success(),
push_action( rb, "rentcpu"_n,
push_action( rb, "rentcpu"_n,
mvo()
("from", rb)
("receiver", rb)
Expand Down Expand Up @@ -4123,7 +4123,6 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_small_rex, eosio_system_tester ) try {

} FC_LOG_AND_RETHROW()


BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try {

const int64_t ratio = 10000;
Expand Down Expand Up @@ -5532,7 +5531,7 @@ BOOST_FIXTURE_TEST_CASE( b1_vesting, eosio_system_tester ) try {
BOOST_REQUIRE_EQUAL( wasm_assert_msg("b1 can only claim their tokens over 10 years"),
unstake( b1, b1, final_amount, final_amount ) );

BOOST_REQUIRE_EQUAL( wasm_assert_msg("must vote for at least 21 producers or for a proxy before buying REX"),
BOOST_REQUIRE_EQUAL( wasm_assert_msg("must vote for at least 21 producers or for a proxy before buying REX"),
unstaketorex( b1, b1, final_amount - small_amount, final_amount - small_amount ) );

BOOST_REQUIRE_EQUAL( error("missing authority of eosio"), vote( b1, { }, "proxyaccount"_n ) );
Expand Down

0 comments on commit fa4424b

Please sign in to comment.