From a993d996fcc00a5118ac193f47464540102c2459 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Mon, 8 Apr 2024 00:29:42 +0200 Subject: [PATCH] Implement eosio.fees core logic ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/120 --- README.md | 9 ++ eosio.fees.contracts.md | 30 +++++- eosio.fees.cpp | 99 +++++++++++++++-- eosio.fees.hpp | 118 +++++++++++++++++---- eosio.fees.spec.ts | 92 ++++++++++++---- external/eosio.system/eosio.abi | 46 ++++++++ external/eosio.system/eosio.cpp | 34 +++++- external/eosio.system/eosio.wasm | Bin 22206 -> 26021 bytes external/eosio.token/eosio.token.cpp | 3 +- external/eosio.token/eosio.token.wasm | Bin 15318 -> 15311 bytes include/eosio.system/eosio.system.hpp | 25 ++++- include/eosio.token/eosio.token.hpp | 146 ++++++++++++++++++++++++++ package-lock.json | 66 +++++------- 13 files changed, 579 insertions(+), 89 deletions(-) create mode 100644 include/eosio.token/eosio.token.hpp diff --git a/README.md b/README.md index 6344cc7..43a0610 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,15 @@ The `eosio.fees` contract handles system fee distribution. +## Strategies + +The `eosio.fees` contract is designed to distribute fees from any outstanding EOS balance to a set of strategies. Each strategy is responsible for distributing the fees to a specific pool or contract based on a predefined logic. Each strategy has an associated weight which determines the percentage of fees that will be distributed to that strategy. + +| Strategy | Description | +| ------------- | --- | +| `donatetorex` | Donate to REX - Distributes fees to REX pool which is distributed to REX holders over a 30 day period | +| `buyramburn` | Buy RAM & Burn - locks up additional EOS in RAM pool while reducing the total circulating supply of RAM + ## Development and Testing ### Build Instructions diff --git a/eosio.fees.contracts.md b/eosio.fees.contracts.md index dc4703d..8882f7e 100644 --- a/eosio.fees.contracts.md +++ b/eosio.fees.contracts.md @@ -7,4 +7,32 @@ summary: 'distribute' icon: https://gateway.pinata.cloud/ipfs/QmZ4HSZDuSrZ4BHawtZRhVfwyYJ4DepNJqVDzxY59KveiM#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19 --- -Distribute the system fee to the corresponding account. \ No newline at end of file +Distribute the system fee to the corresponding account. + +

init

+ +--- +spec_version: "0.2.0" +title: init +summary: 'init' +icon: https://gateway.pinata.cloud/ipfs/QmZ4HSZDuSrZ4BHawtZRhVfwyYJ4DepNJqVDzxY59KveiM#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19 +--- + +

setstrategy

+ +--- +spec_version: "0.2.0" +title: setstrategy +summary: 'setstrategy' +icon: https://gateway.pinata.cloud/ipfs/QmZ4HSZDuSrZ4BHawtZRhVfwyYJ4DepNJqVDzxY59KveiM#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19 +--- + + +

delstrategy

+ +--- +spec_version: "0.2.0" +title: delstrategy +summary: 'delstrategy' +icon: https://gateway.pinata.cloud/ipfs/QmZ4HSZDuSrZ4BHawtZRhVfwyYJ4DepNJqVDzxY59KveiM#3830f1ce8cb07f7757dbcf383b1ec1b11914ac34a1f9d8b065f07600fa9dac19 +--- diff --git a/eosio.fees.cpp b/eosio.fees.cpp index c2764e3..18eea14 100644 --- a/eosio.fees.cpp +++ b/eosio.fees.cpp @@ -2,17 +2,104 @@ namespace eosio { -// @system or @user -[[eosio::on_notify("*::transfer")]] -void fees::on_transfer( const name from, const name to, const asset quantity, const string memo ) +[[eosio::action]] +void fees::init( const uint32_t epoch_time_interval ) { + require_auth( get_self() ); + + settings_table _settings( get_self(), get_self().value ); + auto settings = _settings.get_or_default(); + settings.epoch_time_interval = epoch_time_interval; + _settings.set(settings, get_self()); +} + +[[eosio::action]] +void fees::setstrategy( const name strategy, const uint16_t weight ) { + require_auth( get_self() ); + + strategies_table _strategies( get_self(), get_self().value ); + + // validate input + check(weight > 0, "weight must be greater than 0"); + check(STRATEGIES.find(strategy) != STRATEGIES.end(), "strategy not defined"); + + // update weights + auto itr = _strategies.find(strategy.value); + if (itr == _strategies.end()) { + _strategies.emplace(get_self(), [&](auto& row) { + row.strategy = strategy; + row.weight = weight; + }); + } else { + _strategies.modify(itr, get_self(), [&](auto& row) { + row.weight = weight; + }); + } +} + +[[eosio::action]] +void fees::delstrategy( const name strategy ) { - // ignore transfers not sent to this contract - if (to != get_self()) { return; } + require_auth( get_self() ); + + strategies_table _strategies( get_self(), get_self().value ); + auto &itr = _strategies.get(strategy.value, "strategy not found"); + _strategies.erase(itr); } +[[eosio::action]] void fees::distribute() { - require_auth( get_self() ); + // any authority is allowed to call this action + update_next_epoch(); + + strategies_table _strategies( get_self(), get_self().value ); + const uint16_t total_weight = get_total_weight(); + + // distributing fees in EOS + const asset balance = eosio::token::get_balance( "eosio.token"_n, get_self(), symbol_code("EOS") ); + + for ( auto& row : _strategies ) { + const asset fee_to_distribute = balance * row.weight / total_weight; + if (fee_to_distribute.amount <= 0) continue; // skip if no fee to distribute + + // Donate to REX + // Distributes fees to REX pool which is distributed to REX holders over a 30 day period + if ( row.strategy == "donatetorex"_n) { + eosiosystem::system_contract::donatetorex_action donatetorex( "eosio"_n, { get_self(), "active"_n }); + donatetorex.send( get_self(), fee_to_distribute, "system fees" ); + // Buy RAM & burn bytes + // locks up additional EOS in RAM pool while reducing the total circulating supply of RAM + } else if ( row.strategy == "buyramburn"_n) { + eosiosystem::system_contract::buyramburn_action buyramburn( "eosio"_n, { get_self(), "active"_n }); + buyramburn.send( get_self(), fee_to_distribute, "system fees" ); + } + } +} + +uint16_t fees::get_total_weight() +{ + strategies_table _strategies( get_self(), get_self().value ); + + uint16_t total_weight = 0; + for (auto& row : _strategies) { + total_weight += row.weight; + } + return total_weight; +} + +void fees::update_next_epoch() +{ + fees::settings_table _settings( get_self(), get_self().value ); + auto settings = _settings.get(); + + // handle epoch + const uint32_t now = current_time_point().sec_since_epoch(); + const uint32_t interval = settings.epoch_time_interval; + check( settings.next_epoch_timestamp.sec_since_epoch() <= now, "epoch not finished"); + + // update next epoch (round to the next interval) + settings.next_epoch_timestamp = time_point_sec( (now / interval) * interval + interval ); + _settings.set( settings, get_self() ); } } /// namespace eosio diff --git a/eosio.fees.hpp b/eosio.fees.hpp index ee9552d..cd6f617 100644 --- a/eosio.fees.hpp +++ b/eosio.fees.hpp @@ -2,27 +2,109 @@ #include #include +#include #include using namespace std; namespace eosio { - /** - * The `eosio.fees` contract handles system fees distribution. - */ - class [[eosio::contract("eosio.fees")]] fees : public contract { - public: - using contract::contract; - - /** - * Disallow sending tokens to this contract. - */ - [[eosio::on_notify("*::transfer")]] - void on_transfer(const name from, const name to, const asset quantity, const string memo); - - [[eosio::action]] - void distribute(); - - using distribute_action = eosio::action_wrapper<"distribute"_n, &fees::distribute>; - }; + + const set STRATEGIES = { + "buyramburn"_n, + "donatetorex"_n + }; + /** + * The `eosio.fees` contract handles system fees distribution. + */ + class [[eosio::contract("eosio.fees")]] fees : public contract { + public: + using contract::contract; + + /** + * ## TABLE `strategies` + * + * - `{name} strategy` - strategy name + * - `{uint16_t} weight` - strategy weight (proportional to the total weight of all strategies) + * + * ### example + * + * ```json + * [ + * { + * "strategy": "buyramburn", + * "weight": 60 + * }, + * { + * "strategy": "donatetorex", + * "weight": 30 + * } + * ] + * ``` + */ + struct [[eosio::table("strategies")]] strategies_row { + name strategy; + uint16_t weight; + + uint64_t primary_key() const { return strategy.value; } + }; + typedef eosio::multi_index< "strategies"_n, strategies_row > strategies_table; + + /** + * ## TABLE `settings` + * + * - `{uint32} epoch_time_interval` - epoch time interval in seconds (time between epoch distribution events) + * - `{time_point_sec} next_epoch_timestamp` - next epoch timestamp event to trigger strategy distribution + * + * ### example + * + * ```json + * { + * "epoch_time_interval": 600, + * "next_epoch_timestamp": "2024-04-07T00:00:00" + * } + * ``` + */ + struct [[eosio::table("settings")]] settings_row { + uint32_t epoch_time_interval = 600; // 10 minutes + time_point_sec next_epoch_timestamp; + }; + typedef eosio::singleton< "settings"_n, settings_row > settings_table; + + /** + * Initialize the contract with the epoch period. + * + * @param epoch_period - epoch period in seconds + */ + [[eosio::action]] + void init( const uint32_t epoch_period ); + + /** + * Set a strategy with a weight. + * + * @param strategy - strategy name + * @param weight - strategy weight + */ + [[eosio::action]] + void setstrategy( const name strategy, const uint16_t weight ); + + /** + * Delete a strategy. + * + * @param strategy - strategy name + */ + [[eosio::action]] + void delstrategy( const name strategy ); + + /** + * Distribute fees to all defined strategies. + */ + [[eosio::action]] + void distribute(); + + using distribute_action = eosio::action_wrapper<"distribute"_n, &fees::distribute>; + + private: + void update_next_epoch(); + uint16_t get_total_weight(); + }; } /// namespace eosio diff --git a/eosio.fees.spec.ts b/eosio.fees.spec.ts index b8db4e0..e413849 100644 --- a/eosio.fees.spec.ts +++ b/eosio.fees.spec.ts @@ -1,17 +1,19 @@ -import {Asset, Int64, Name} from '@wharfkit/antelope' -import {Blockchain, expectToThrow} from '@proton/vert' -import {describe, expect, test} from 'bun:test' +import { Asset, Int64, Name } from '@wharfkit/antelope' +import { describe, expect, test } from 'bun:test' +import { Blockchain, expectToThrow } from '@eosnetwork/vert' +import { TimePointSec } from "@greymass/eosio"; // Vert EOS VM const blockchain = new Blockchain() -const alice = 'alice' +const burn = 'eosio.null' +const rex = 'eosio.rex' +const ram = 'eosio.ram' const bob = 'bob' -const charles = 'charles' -blockchain.createAccounts(bob, alice, charles) +blockchain.createAccounts(burn, rex, ram, bob) const fees_contract = 'eosio.fees' const contracts = { - fee: blockchain.createContract(fees_contract, fees_contract, true), + fees: blockchain.createContract(fees_contract, fees_contract, true), token: blockchain.createContract('eosio.token', 'external/eosio.token/eosio.token', true), system: blockchain.createContract('eosio', 'external/eosio.system/eosio', true), fake: { @@ -23,22 +25,13 @@ const contracts = { function getTokenBalance(account: string, symcode: string) { const scope = Name.from(account).value.value const primary_key = Asset.SymbolCode.from(symcode).value.value - const row = contracts.fee.tables + const row = contracts.token.tables .accounts(scope) .getTableRow(primary_key) if (!row) return 0; return Asset.from(row.balance).units.toNumber() } -function getTokenSupply(symcode: string) { - const scope = Asset.SymbolCode.from(symcode).value.value - const row = contracts.fee.tables - .stat(scope) - .getTableRow(scope) - if (!row) return 0; - return Asset.from(row.supply).units.toNumber() -} - function getRamBytes(account: string) { const scope = Name.from(account).value.value const row = contracts.system.tables @@ -57,8 +50,67 @@ describe(fees_contract, () => { const supply = `1000000000.0000 EOS` await contracts.token.actions.create(['eosio.token', supply]).send() await contracts.token.actions.issue(['eosio.token', supply, '']).send() - await contracts.token.actions.transfer(['eosio.token', alice, '1000.0000 EOS', '']).send() - await contracts.token.actions.transfer(['eosio.token', bob, '1000.0000 EOS', '']).send() - await contracts.token.actions.transfer(['eosio.token', charles, '1000.0000 EOS', '']).send() }) + + test('eosio.fees::init', async () => { + await contracts.fees.actions.init([600]).send() + }) + + test("eosio.fees::setstrategy", async () => { + await contracts.fees.actions.setstrategy(['donatetorex', 600]).send(); + await contracts.fees.actions.setstrategy(['buyramburn', 300]).send(); + }); + + test("eosio.fees::distibute", async () => { + await contracts.token.actions.transfer(['eosio.token', fees_contract, '6000.0000 EOS', '']).send(); + const before = { + fees: { + balance: getTokenBalance(fees_contract, 'EOS'), + }, + ram: { + balance: getTokenBalance(ram, 'EOS'), + }, + rex: { + balance: getTokenBalance(rex, 'EOS'), + }, + burn: { + bytes: getRamBytes(burn), + }, + } + await contracts.fees.actions.distribute([]).send(); + + const after = { + fees: { + balance: getTokenBalance(fees_contract, 'EOS'), + }, + ram: { + balance: getTokenBalance(ram, 'EOS'), + }, + rex: { + balance: getTokenBalance(rex, 'EOS'), + }, + burn: { + bytes: getRamBytes(burn), + }, + } + + // bytes + expect(after.burn.bytes - before.burn.bytes).toBe(17507766) + + // EOS + expect(after.fees.balance - before.fees.balance).toBe(-60000000) + expect(after.rex.balance - before.rex.balance).toBe(40000000) + expect(after.ram.balance - before.ram.balance).toBe(20000000) + }); + + test('eosio.fees::distibute::error - epoch not finished', async () => { + const action = contracts.fees.actions.distribute([]).send(); + await expectToThrow(action, 'eosio_assert: epoch not finished') + }) + + test("eosio.fees::distibute - after 10 minutes & user authority", async () => { + const time = TimePointSec.fromInteger(600); + await blockchain.addTime(time); + await contracts.fees.actions.distribute([]).send(bob); // any user is authorized to call distribute + }); }) \ No newline at end of file diff --git a/external/eosio.system/eosio.abi b/external/eosio.system/eosio.abi index 74bdd17..96c95d1 100644 --- a/external/eosio.system/eosio.abi +++ b/external/eosio.system/eosio.abi @@ -21,6 +21,24 @@ } ] }, + { + "name": "buyramburn", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, { "name": "buyrambytes", "base": "", @@ -53,6 +71,24 @@ } ] }, + { + "name": "donatetorex", + "base": "", + "fields": [ + { + "name": "payer", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + }, + { + "name": "memo", + "type": "string" + } + ] + }, { "name": "eosio_global_state", "base": "", @@ -293,11 +329,21 @@ "type": "buyram", "ricardian_contract": "" }, + { + "name": "buyramburn", + "type": "buyramburn", + "ricardian_contract": "" + }, { "name": "buyrambytes", "type": "buyrambytes", "ricardian_contract": "" }, + { + "name": "donatetorex", + "type": "donatetorex", + "ricardian_contract": "" + }, { "name": "init", "type": "init", diff --git a/external/eosio.system/eosio.cpp b/external/eosio.system/eosio.cpp index a2c9187..7c14da6 100644 --- a/external/eosio.system/eosio.cpp +++ b/external/eosio.system/eosio.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -30,6 +31,9 @@ class [[eosio::contract("eosio")]] system_contract : public contract add_ram(receiver, bytes); reserve_ram(bytes); + eosio::token::transfer_action transfer( "eosio.token"_n, { get_self(), "active"_n }); + transfer.send( payer, "eosio.ram", quant, "buy ram" ); + // log buy ram action system_contract::logbuyram_action logbuyram_act{get_self(), {get_self(), "active"_n}}; logbuyram_act.send(payer, receiver, quant, bytes, 0); @@ -83,6 +87,35 @@ class [[eosio::contract("eosio")]] system_contract : public contract add_ram(to, bytes); } + /** + * Donatetorex action, donates funds to REX, increases REX pool return buckets + * Executes inline transfer from payer to system contract of tokens will be executed. + * + * @param payer - the payer of donated funds. + * @param quantity - the quantity of tokens to donated to REX with. + * @param memo - the memo string to accompany the transaction. + */ + [[eosio::action]] + void donatetorex( const name& payer, const asset& quantity, const std::string& memo ) + { + eosio::token::transfer_action transfer( "eosio.token"_n, { get_self(), "active"_n }); + transfer.send( payer, "eosio.rex", quantity, memo ); + } + + /** + * Buy RAM and immediately burn RAM. + * An inline transfer from payer to system contract of tokens will be executed. + * + * @param payer - the payer of buy RAM & burn. + * @param quantity - the quantity of tokens to buy RAM & burn with. + * @param memo - the memo string to accompany the transaction. + */ + [[eosio::action]] + void buyramburn( const name& payer, const asset& quantity, const std::string& memo ) + { + buyram(payer, "eosio.null"_n, quantity); + } + /** * Logging for buyram & buyrambytes action * @@ -99,7 +132,6 @@ class [[eosio::contract("eosio")]] system_contract : public contract require_recipient( receiver ); } - [[eosio::action]] void init() { diff --git a/external/eosio.system/eosio.wasm b/external/eosio.system/eosio.wasm index a3c38e748279c2a8e7060dcd36fee823b1643059..2021904cd80106621d6dba91789918d447497ff0 100755 GIT binary patch delta 5849 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 diff --git a/external/eosio.token/eosio.token.cpp b/external/eosio.token/eosio.token.cpp index 33a31ce..395b869 100644 --- a/external/eosio.token/eosio.token.cpp +++ b/external/eosio.token/eosio.token.cpp @@ -79,7 +79,8 @@ void token::transfer( const name& from, const string& memo ) { check( from != to, "cannot transfer to self" ); - require_auth( from ); + // ignore auth for testing to allow privileged eosio contract to transfer using donatetorex action + // require_auth( from ); check( is_account( to ), "to account does not exist"); auto sym = quantity.symbol.code(); stats statstable( get_self(), sym.raw() ); diff --git a/external/eosio.token/eosio.token.wasm b/external/eosio.token/eosio.token.wasm index debe528f140b15d7fb5ef222c58647466201bfd7..98570689f6fa2d0ba0c00a38b921fee9dcf3fad6 100755 GIT binary patch delta 81 zcmcase!hIeBPPbjn;$bdO4}}CVPmXktb<`?21kQzB~}#%CIoP7bB0`=EC8fC5?ufQ delta 88 zcmX?Keyx1NBPPaIn;$bdO1rFNVPmXktb<`?21kQzB~}#%CI; using buyrambytes_action = eosio::action_wrapper<"buyrambytes"_n, &system_contract::buyrambytes>; using buyram_action = eosio::action_wrapper<"buyram"_n, &system_contract::buyram>; + using buyramburn_action = eosio::action_wrapper<"buyramburn"_n, &system_contract::buyramburn>; using ramtransfer_action = eosio::action_wrapper<"ramtransfer"_n, &system_contract::ramtransfer>; + using donatetorex_action = eosio::action_wrapper<"donatetorex"_n, &system_contract::donatetorex>; using logbuyram_action = eosio::action_wrapper<"logbuyram"_n, &system_contract::logbuyram>; - struct [[eosio::table, eosio::contract("eosio.system")]] exchange_state { asset supply; struct connector { diff --git a/include/eosio.token/eosio.token.hpp b/include/eosio.token/eosio.token.hpp new file mode 100644 index 0000000..e4942df --- /dev/null +++ b/include/eosio.token/eosio.token.hpp @@ -0,0 +1,146 @@ +#pragma once + +#include +#include + +#include + +namespace eosiosystem { + class system_contract; +} + +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` 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 { + public: + using contract::contract; + + /** + * Allows `issuer` account to create a token in supply of `maximum_supply`. If validation is successful a new entry in statstable for token symbol scope gets created. + * + * @param issuer - the account that creates the token, + * @param maximum_supply - the maximum supply set for the token created. + * + * @pre Token symbol has to be valid, + * @pre Token symbol must not be already created, + * @pre maximum_supply has to be smaller than the maximum supply allowed by the system: 1^62 - 1. + * @pre Maximum supply must be positive; + */ + [[eosio::action]] + void create( const name& issuer, + const asset& maximum_supply); + /** + * 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. + */ + [[eosio::action]] + void issue( const name& to, const asset& quantity, const string& memo ); + + /** + * The opposite for create action, if all validations succeed, + * it debits the statstable.supply amount. + * + * @param quantity - the quantity of tokens to retire, + * @param memo - the memo string to accompany the transaction. + */ + [[eosio::action]] + void retire( const asset& quantity, const string& memo ); + + /** + * Allows `from` account to transfer to `to` account the `quantity` tokens. + * One account is debited and the other is credited with quantity tokens. + * + * @param from - the account to transfer from, + * @param to - the account to be transferred to, + * @param quantity - the quantity of tokens to be transferred, + * @param memo - the memo string to accompany the transaction. + */ + [[eosio::action]] + void transfer( const name& from, + const name& to, + const asset& quantity, + const string& memo ); + /** + * Allows `ram_payer` to create an account `owner` with zero balance for + * token `symbol` at the expense of `ram_payer`. + * + * @param owner - the account to be created, + * @param symbol - the token to be payed with by `ram_payer`, + * @param ram_payer - the account that supports the cost of this action. + * + * More information can be read [here](https://github.com/EOSIO/eosio.contracts/issues/62) + * and [here](https://github.com/EOSIO/eosio.contracts/issues/61). + */ + [[eosio::action]] + void open( const name& owner, const symbol& symbol, const name& ram_payer ); + + /** + * This action is the opposite for open, it closes the account `owner` + * for token `symbol`. + * + * @param owner - the owner account to execute the close action for, + * @param symbol - the symbol of the token to execute the close action for. + * + * @pre The pair of owner plus symbol has to exist otherwise no action is executed, + * @pre If the pair of owner plus symbol exists, the balance has to be zero. + */ + [[eosio::action]] + void close( const name& owner, const symbol& symbol ); + + 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; + } + + 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; + } + + using create_action = eosio::action_wrapper<"create"_n, &token::create>; + using issue_action = eosio::action_wrapper<"issue"_n, &token::issue>; + using retire_action = eosio::action_wrapper<"retire"_n, &token::retire>; + 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: + struct [[eosio::table]] account { + asset balance; + + uint64_t primary_key()const { return balance.symbol.code().raw(); } + }; + + struct [[eosio::table]] currency_stats { + asset supply; + asset max_supply; + name issuer; + + uint64_t primary_key()const { return supply.symbol.code().raw(); } + }; + + typedef eosio::multi_index< "accounts"_n, account > accounts; + typedef eosio::multi_index< "stat"_n, currency_stats > stats; + + void sub_balance( const name& owner, const asset& value ); + void add_balance( const name& owner, const asset& value, const name& ram_payer ); + }; + +} diff --git a/package-lock.json b/package-lock.json index 124c66e..428e403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@proton/vert": "^0.3.24", + "@eosnetwork/vert": "^1.0.0", "@wharfkit/antelope": "^1.0.7", "@wharfkit/contract": "^1.1.5" }, @@ -14,6 +14,30 @@ "typescript": "^5.3.3" } }, + "node_modules/@eosnetwork/vert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@eosnetwork/vert/-/vert-1.0.0.tgz", + "integrity": "sha512-k0LsIQobA0R5BFVemerrMnfdvsnqUEx9dUUyv6P0cgUKMb/CCnvkTFZOD29IXkOIifLJVYgeD6hYPu78IyRqeg==", + "dependencies": { + "@ethereumjs/util": "^8.0.0-beta.1", + "@greymass/eosio": "^0.5.5", + "bn.js": "^5.2.0", + "brorand": "^1.1.0", + "chai": "^4.3.6", + "colors": "^1.4.0", + "cross-fetch": "^3.1.5", + "elliptic": "^6.5.4", + "hash.js": "^1.1.7", + "js-sha3": "^0.8.0", + "json-diff": "^0.9.0", + "json-diff-ts": "^1.2.4", + "lodash": "^4.17.21", + "loglevel": "^1.8.0", + "loglevel-plugin-prefix": "^0.8.4", + "rustbn.js": "^0.2.0", + "sorted-btree": "^1.6.0" + } + }, "node_modules/@ethereumjs/rlp": { "version": "4.0.1", "license": "MPL-2.0", @@ -51,10 +75,6 @@ "version": "4.12.0", "license": "MIT" }, - "node_modules/@juanelas/base64": { - "version": "1.1.5", - "license": "MIT" - }, "node_modules/@noble/curves": { "version": "1.3.0", "license": "MIT", @@ -75,31 +95,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@proton/vert": { - "version": "0.3.24", - "license": "MIT", - "dependencies": { - "@ethereumjs/util": "^8.0.0-beta.1", - "@greymass/eosio": "^0.5.5", - "bigint-conversion": "^2.4.3", - "bn.js": "^5.2.0", - "brorand": "^1.1.0", - "chai": "^4.3.6", - "colors": "^1.4.0", - "cross-fetch": "^3.1.5", - "elliptic": "^6.5.4", - "hash.js": "^1.1.7", - "js-sha3": "^0.8.0", - "json-diff": "^0.9.0", - "json-diff-ts": "^1.2.4", - "lodash": "^4.17.21", - "lodash.set": "^4.3.2", - "loglevel": "^1.8.0", - "loglevel-plugin-prefix": "^0.8.4", - "rustbn.js": "^0.2.0", - "sorted-btree": "^1.6.0" - } - }, "node_modules/@scure/base": { "version": "1.1.5", "license": "MIT", @@ -208,13 +203,6 @@ "node": "*" } }, - "node_modules/bigint-conversion": { - "version": "2.4.3", - "license": "MIT", - "dependencies": { - "@juanelas/base64": "^1.1.2" - } - }, "node_modules/bn.js": { "version": "5.2.1", "license": "MIT" @@ -488,10 +476,6 @@ "version": "4.17.21", "license": "MIT" }, - "node_modules/lodash.set": { - "version": "4.3.2", - "license": "MIT" - }, "node_modules/loglevel": { "version": "1.9.1", "license": "MIT",