diff --git a/contracts/sysio.liq/README.md b/contracts/sysio.liq/README.md index dbb9690541..1d3b758a29 100644 --- a/contracts/sysio.liq/README.md +++ b/contracts/sysio.liq/README.md @@ -68,7 +68,7 @@ and sets the tick parameters. The pre-launch positions are replayed by | `burn(token_code, amount)` | `sysio.synd` | Burn shadow out of `sysio.synd`'s own row | | `mintyield(chain_code, token_code, amount)` | `sysio.synd` | LIQ_YIELD into the pending balance | | `queueyield(sym)` | none | Pending yield into the swap's reservoir | -| `transfer`, `open`, `close`, `claim` | holder | `sysio.token`'s shape; `close` refuses while yield is owed | +| `transfer`, `close`, `claim` | holder | `sysio.token`'s shape, balance rows billed to `sysio`; `close` refuses while yield is owed | | `addyield(from, quantity, target)` | `from` | Distribute WIRE to `target`'s holders | | `regliqpool(...)` | privileged caller, epoch 0 | Seed a shadow's yield pool | diff --git a/contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp b/contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp index 83f7c1ea00..536abdcfb5 100644 --- a/contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp +++ b/contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp @@ -135,7 +135,6 @@ namespace sysio { /// and sysio.bond may debit their own balance. Settles both yield positions, preserves supply, /// and requires the Andon cord clear, including self-settlement. [[sysio::action]] void settle(name custodian, name beneficiary, asset quantity); - [[sysio::action]] void open(name owner, symbol symbol, name ram_payer); /// Erase `owner`'s empty row for `symbol`. Refused while the row is still /// owed yield, so closing never discards WIRE. [[sysio::action]] void close(name owner, symbol symbol); @@ -261,9 +260,9 @@ namespace sysio { /// The one funnel every balance move goes through: settle `row` at `index`, /// stamp it, then apply `delta`. Nothing else writes `balance`. static void settle_and_adjust(opp::shadow::account& row, u128 index, const asset& delta); - /// Apply `delta` to `owner`'s row for its symbol through the funnel. A row + /// Apply `delta` to `owner`'s row for its symbol through the funnel, billed to `sysio`. A row /// created here is stamped at the current index, so no history is credited. - void adjust_account(name owner, const asset& delta, name payer); + void adjust_account(name owner, const asset& delta); /// Grow `sym`'s supply by `quantity`; false (and no change) past its headroom. bool grow_supply(symbol_code sym, uint64_t quantity); /// Advance `sym`'s index by `quantity` WIRE over its supply, carrying the diff --git a/contracts/sysio.liq/src/sysio.liq.cpp b/contracts/sysio.liq/src/sysio.liq.cpp index 924619fcc4..05439a9f0f 100644 --- a/contracts/sysio.liq/src/sysio.liq.cpp +++ b/contracts/sysio.liq/src/sysio.liq.cpp @@ -36,9 +36,6 @@ constexpr std::string_view CLAIM_MEMO = "sysio.liq claim"; /// Memo of the transfer that brings the WIRE `creditowed` credits in from sysio.synd. constexpr std::string_view CREDIT_MEMO = "sysio.liq creditowed"; -/// The `sysio.payer` seat: an inline action to an unprivileged contract that bills a -/// row to `actor` must carry it beside `actor`'s active permission. -permission_level payer_of(name actor) { return permission_level{ actor, "sysio.payer"_n }; } permission_level active_of(name actor) { return permission_level{ actor, "active"_n }; } void drop(const char* path, const char* reason) { @@ -86,7 +83,7 @@ void liq::recredit(name holder, asset quantity) { const currency_stats st = stat_of(quantity.symbol.code()); check(quantity.symbol == st.supply.symbol, symbol_precision_mismatch_msg); check(grow_supply(quantity.symbol.code(), static_cast(quantity.amount)), "supply exceeds the asset range"); - adjust_account(holder, quantity, ram_payer); + adjust_account(holder, quantity); } // --------------------------------------------------------------------------- @@ -100,7 +97,7 @@ void liq::mint(name to, sysio::slug_name token_code, uint64_t amount) { const currency_stats st = stat_by_token(token_code); const symbol_code sym = st.supply.symbol.code(); check(grow_supply(sym, amount), "supply exceeds the asset range"); - adjust_account(to, asset{ static_cast(amount), st.supply.symbol }, ram_payer); + adjust_account(to, asset{ static_cast(amount), st.supply.symbol }); } void liq::burn(sysio::slug_name token_code, uint64_t amount) { @@ -109,7 +106,7 @@ void liq::burn(sysio::slug_name token_code, uint64_t amount) { const currency_stats st = stat_by_token(token_code); const asset quantity{ static_cast(amount), st.supply.symbol }; // Settle, then burn: the row keeps every subunit of yield accrued to now. - adjust_account(SYND_ACCOUNT, -quantity, ram_payer); + adjust_account(SYND_ACCOUNT, -quantity); stats statstable(get_self()); statstable.modify(ram_payer, symbol_key{ st.supply.symbol.code().raw() }, [&](currency_stats& s) { s.supply -= quantity; @@ -200,10 +197,9 @@ void liq::queueyield(symbol_code sym) { // Minted to this contract and handed on in the same transaction, so its row is // settled at one index and accrues nothing on the way through. check(grow_supply(sym, static_cast(quantity.amount)), "supply exceeds the asset range"); - adjust_account(get_self(), quantity, ram_payer); + adjust_account(get_self(), quantity); - action(std::vector{ payer_of(get_self()), active_of(get_self()) }, - SWAP_ACCOUNT, "fundyield"_n, + action(active_of(get_self()), SWAP_ACCOUNT, "fundyield"_n, std::make_tuple(get_self(), st.pair_symbol, quantity)).send(); action(active_of(get_self()), get_self(), "transfer"_n, std::make_tuple(get_self(), SWAP_ACCOUNT, quantity, std::string{})).send(); @@ -230,8 +226,8 @@ void liq::transfer(name from, name to, asset quantity, string memo) { check(quantity.symbol == st.supply.symbol, symbol_precision_mismatch_msg); check(memo.size() <= MAX_MEMO_BYTES, "memo has more than 256 bytes"); - adjust_account(from, -quantity, ram_payer); - adjust_account(to, quantity, ram_payer); + adjust_account(from, -quantity); + adjust_account(to, quantity); } void liq::settle(name custodian, name beneficiary, asset quantity) { @@ -246,21 +242,10 @@ void liq::settle(name custodian, name beneficiary, asset quantity) { if (custodian == beneficiary) { const auto row = accounts(get_self(), custodian.value).try_get(symbol_key{quantity.symbol.code().raw()}); check(row && row->balance.amount >= quantity.amount, overdrawn_balance_msg); - adjust_account(custodian, asset{0, quantity.symbol}, ram_payer); + adjust_account(custodian, asset{0, quantity.symbol}); } else { - adjust_account(custodian, -quantity, ram_payer); - adjust_account(beneficiary, quantity, ram_payer); - } -} - -void liq::open(name owner, symbol symbol, name ram_payer_) { - require_auth(ram_payer_); - check(is_account(owner), "owner account does not exist"); - const currency_stats st = stat_of(symbol.code()); - check(st.supply.symbol == symbol, symbol_precision_mismatch_msg); - accounts holdings(get_self(), owner.value); - if (!holdings.contains(symbol_key{ symbol.code().raw() })) { - adjust_account(owner, asset{ 0, symbol }, ram_payer_); + adjust_account(custodian, -quantity); + adjust_account(beneficiary, quantity); } } @@ -337,22 +322,16 @@ void liq::regliqpool(sysio::slug_name chain_code, sysio::slug_name token_code, s const asset shadow{ static_cast(initial_chain_amount), sym }; const asset wire { static_cast(initial_wire_amount), WIRE_SYM }; const extended_symbol shadow_symbol{ sym, get_self() }; - const extended_symbol wire_symbol { WIRE_SYM, TOKEN_ACCOUNT }; // The LCO liq is protocol-owned and already in outpost custody: its shadow is // minted to sysio, which seeds the pool with it and holds the pool's shares. check(grow_supply(sym.code(), initial_chain_amount), "supply exceeds the asset range"); - adjust_account(SYSTEM_ACCOUNT, shadow, ram_payer); + adjust_account(SYSTEM_ACCOUNT, shadow); stats statstable(get_self()); statstable.modify(ram_payer, symbol_key{ sym.code().raw() }, [&](currency_stats& s) { s.pair_symbol = pair_symbol.code(); }); - const std::vector sysio_billed{ payer_of(SYSTEM_ACCOUNT), active_of(SYSTEM_ACCOUNT) }; - action(sysio_billed, SWAP_ACCOUNT, "openext"_n, - std::make_tuple(SYSTEM_ACCOUNT, SYSTEM_ACCOUNT, shadow_symbol)).send(); - action(sysio_billed, SWAP_ACCOUNT, "openext"_n, - std::make_tuple(SYSTEM_ACCOUNT, SYSTEM_ACCOUNT, wire_symbol)).send(); // The shadow has no pair yet, so its seed carries the swap's own authority. action(std::vector{ active_of(SYSTEM_ACCOUNT), active_of(SWAP_ACCOUNT) }, get_self(), "transfer"_n, @@ -360,7 +339,7 @@ void liq::regliqpool(sysio::slug_name chain_code, sysio::slug_name token_code, s // The WIRE side is the T5 dex earmark, drained from the treasury. action(active_of(SYSTEM_ACCOUNT), TOKEN_ACCOUNT, "transfer"_n, std::make_tuple(SYSTEM_ACCOUNT, SWAP_ACCOUNT, wire, std::string{})).send(); - action(std::vector{ payer_of(SYSTEM_ACCOUNT), active_of(SYSTEM_ACCOUNT), active_of(SWAP_ACCOUNT) }, + action(std::vector{ active_of(SYSTEM_ACCOUNT), active_of(SWAP_ACCOUNT) }, SWAP_ACCOUNT, "inittoken"_n, std::make_tuple(SYSTEM_ACCOUNT, pair_symbol, extended_asset{ shadow, get_self() }, extended_asset{ wire, TOKEN_ACCOUNT }, fee, SYSTEM_ACCOUNT, @@ -403,19 +382,19 @@ void liq::settle_and_adjust(opp::shadow::account& row, u128 index, const asset& check(row.balance.amount >= 0, overdrawn_balance_msg); } -void liq::adjust_account(name owner, const asset& delta, name payer) { +void liq::adjust_account(name owner, const asset& delta) { accounts holdings(get_self(), owner.value); const symbol_key key{ delta.symbol.code().raw() }; const u128 index = current_index(delta.symbol.code()); const auto row = holdings.try_get(key); if (!row) { check(delta.amount >= 0, "no balance object found"); - holdings.emplace(payer, key, opp::shadow::account{ delta, index, 0 }); + holdings.emplace(ram_payer, key, opp::shadow::account{ delta, index, 0 }); return; } opp::shadow::account updated = *row; settle_and_adjust(updated, index, delta); - holdings.modify(payer, key, [&](opp::shadow::account& a) { a = updated; }); + holdings.modify(ram_payer, key, [&](opp::shadow::account& a) { a = updated; }); } bool liq::grow_supply(symbol_code sym, uint64_t quantity) { diff --git a/contracts/sysio.liq/sysio.liq.abi b/contracts/sysio.liq/sysio.liq.abi index eae479ea67..b0ec60b8d0 100644 --- a/contracts/sysio.liq/sysio.liq.abi +++ b/contracts/sysio.liq/sysio.liq.abi @@ -175,24 +175,6 @@ } ] }, - { - "name": "open", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "symbol", - "type": "symbol" - }, - { - "name": "ram_payer", - "type": "name" - } - ] - }, { "name": "pending_yield", "base": "", @@ -383,11 +365,6 @@ "type": "mintyield", "ricardian_contract": "" }, - { - "name": "open", - "type": "open", - "ricardian_contract": "" - }, { "name": "queueyield", "type": "queueyield", diff --git a/contracts/sysio.liq/sysio.liq.wasm b/contracts/sysio.liq/sysio.liq.wasm index a392320ef0..f72f872cb5 100755 Binary files a/contracts/sysio.liq/sysio.liq.wasm and b/contracts/sysio.liq/sysio.liq.wasm differ diff --git a/contracts/sysio.swap/Commands.md b/contracts/sysio.swap/Commands.md index 43935b8619..12726463b8 100644 --- a/contracts/sysio.swap/Commands.md +++ b/contracts/sysio.swap/Commands.md @@ -6,20 +6,12 @@ Configure the contract (deployment step, the contract's own authority): the cont cleos push action evolutiondex setconfig '["sysio", {"contract":"eosio.token", "sym":"4,EOS"}]' -p evolutiondex -Open a channel in the contract. This channel will store your trading tokens. You need to create one channel for each token you plan to trade. The second input below is the ram payer, and the authorizer must be the ram payer. - - cleos push action evolutiondex openext '["YOUR_ACCOUNT", "YOUR_ACCOUNT", {"contract":"eosio.token", "sym":"4,EOS"}]' -p YOUR_ACCOUNT - -Open a channel for the second token you wish to trade in evodex: - - cleos push action evolutiondex openext '["YOUR_ACCOUNT", "YOUR_ACCOUNT", {"contract":"pesocontract", "sym":"4,PESO"}]' -p YOUR_ACCOUNT - Close the contract's channel for a specific token. In case there are funds there, it returns them to the account "TO". cleos push action evolutiondex closeext '["YOUR_ACCOUNT", "TO", {"contract":"eosio.token", "sym":"4,EOS"}, "memo"]' -p YOUR_ACCOUNT -Fill your account with the desired tokens. The contract accepts only the system token and the first legs of existing pairs; anything else is refused. A pair's first leg has no pair yet when its seed is deposited, so that one transfer also carries the contract's authority. +Fill your account with the desired tokens. The contract stores each token you deposit in a channel, created by your first deposit of that token and billed to sysio. The contract accepts only the system token and the first legs of existing pairs; anything else is refused. A pair's first leg has no pair yet when its seed is deposited, so that one transfer also carries the contract's authority. cleos push action eosio.token transfer '["YOUR_ACCOUNT", "evolutiondex", "100.0000 EOS", "memo"]' -p YOUR_ACCOUNT @@ -117,7 +109,7 @@ See evotoken stats: cleos get table evolutiondex EOSPESO stat In many practical cases, users will prefer to run many actions in a single transaction. -For example, if you want to add liquidity, you will probably prefer to close the accounts in the contract evolutiondex corresponding to the external tokens, to avoid spending RAM. To that end, you may run: +For example, to deposit, add liquidity and close the channels of the external tokens in one transaction, you may run: cleos push transaction addliquidity.json @@ -127,24 +119,6 @@ where the file addliquidity.json contains: "actions": [ { - "account": "evolutiondex", - "name": "openext", - "authorization": [{"actor": "YOUR_ACCOUNT","permission": "active"}], - "data": { - "user": "YOUR_ACCOUNT", - "payer": "YOUR_ACCOUNT", - "ext_symbol": {"contract":"eosio.token", "sym":"4,EOS"} - } - },{ - "account": "evolutiondex", - "name": "openext", - "authorization": [{"actor": "YOUR_ACCOUNT","permission": "active"}], - "data": { - "user": "YOUR_ACCOUNT", - "payer": "YOUR_ACCOUNT", - "ext_symbol": {"contract":"pesocontract", "sym":"4,PESO"} - } - },{ "account": "eosio.token", "name": "transfer", "authorization": [{"actor": "YOUR_ACCOUNT","permission": "active"}], diff --git a/contracts/sysio.swap/README.md b/contracts/sysio.swap/README.md index 5f1ebe7c7b..044dbced14 100644 --- a/contracts/sysio.swap/README.md +++ b/contracts/sysio.swap/README.md @@ -33,7 +33,7 @@ Every pair keeps two cumulative-price accumulators in the `priceaccum` table, on A pair may be created as a *yield pool* by naming its first leg as a shadow token (`yield_leg` in `inittoken`; an empty value makes a plain pool). A shadow token (`sysio.opp.common/shadow_yield.hpp`) pays WIRE yield to its holders through a cumulative index, and the contract, holding the pool's shadow, is such a holder. What it is owed is computed from the token's public state and settled into the pool's other leg with no shares minted, so every existing share appreciates. Settlement runs immediately before every mint and burn, so yield always belongs to the shares that existed when it was earned, and anyone may run it between them with `accrueyield`. The mechanics are deliberately strict: the contract credits the pool, records a receipt for the exact amount keyed by the shadow contract, and calls the token's `claim`; the transfer that delivers the payout must match the receipt, in the same transaction, or the transaction fails. Deposit accounts are not yield-bearing: the contract's whole shadow holding, deposits included, earns for the pool. One pair per token means one yield pool per shadow. -A yield pool also has a *reservoir* (`reservoirs` table): shadow queued to be sold through the pool, held by the contract but in no pool and no deposit. It is filled with `fundyield`, which is typed and memo-free so that a contract can fund inline: the funder announces the pair and the exact amount, then transfers that amount of the shadow to the contract, in the same transaction or later. The transfer that matches the announcement fills the reservoir; while an announcement is pending, any other transfer from the funder is refused, and a new announcement replaces the pending one. The announcement's row is billed to the funder and refunded when the delivery lands or when they call `cancelyield`, which is also how a funder who changes their mind stops their transfers being routed to the reservoir. A pair's fee authority sets its tick parameters with `setyield`: the horizon over which the reservoir is meant to sell and the ceiling on one clip as basis points of the pool's shadow side. The ceiling may not exceed the pair's fee, and `changefee` will not lower the fee below it: a sandwich around one tick pays only when the clip moves the price by more than the attacker's two fees, and a clip is at most the ceiling's share of the shadow side it sells into, so none pays beyond a unit of rounding. `tickyield` holds the stored ceiling to the fee as well, so a zero-fee yield pool never sells. +A yield pool also has a *reservoir* (`reservoirs` table): shadow queued to be sold through the pool, held by the contract but in no pool and no deposit. It is filled with `fundyield`, which is typed and memo-free so that a contract can fund inline: the funder announces the pair and the exact amount, then transfers that amount of the shadow to the contract, in the same transaction or later. The transfer that matches the announcement fills the reservoir; while an announcement is pending, any other transfer from the funder is refused, and a new announcement replaces the pending one. The announcement's row is billed to `sysio` and erased when the delivery lands or when the funder calls `cancelyield`, which is also how a funder who changes their mind stops their transfers being routed to the reservoir. A pair's fee authority sets its tick parameters with `setyield`: the horizon over which the reservoir is meant to sell and the ceiling on one clip as basis points of the pool's shadow side. The ceiling may not exceed the pair's fee, and `changefee` will not lower the fee below it: a sandwich around one tick pays only when the clip moves the price by more than the attacker's two fees, and a clip is at most the ceiling's share of the shadow side it sells into, so none pays beyond a unit of rounding. `tickyield` holds the stored ceiling to the fee as well, so a zero-fee yield pool never sells. That bounds one tick, not the sale. A position held across several ticks pays the two fees once and collects every clip's move, so it gains once what sells during the hold exceeds about the fee's share of the shadow side. At a 10 bps fee with 1% of the side queued over a day, that takes a few hours. Such a position carries the hold's price risk, and the lowered price is open to arbitrage; a longer horizon or a higher fee lengthens the hold. diff --git a/contracts/sysio.swap/include/sysio.swap/sysio.swap.hpp b/contracts/sysio.swap/include/sysio.swap/sysio.swap.hpp index c2bfe97da8..b1f89677ea 100644 --- a/contracts/sysio.swap/include/sysio.swap/sysio.swap.hpp +++ b/contracts/sysio.swap/include/sysio.swap/sysio.swap.hpp @@ -44,6 +44,9 @@ namespace sysio { /// FEE_DENOMINATOR/fee units, a window that is a thousand whole tokens for a /// zero-precision symbol. static constexpr uint64_t MIN_SWAP_FEE = 1; + /// The account billed for this contract's rows. `sysio.swap` holds only the RAM + /// its code needs, so its rows are billed to `sysio`, as `sysio.token`'s are. + static constexpr name RAM_PAYER = "sysio"_n; using contract::contract; /// Deployment configuration, required before anything else works. The fee @@ -67,7 +70,8 @@ namespace sysio { /// `yield_leg`, when set, must be the first leg and names it as a shadow /// token, making the pair a yield pool: the pool absorbs the WIRE yield /// distributed on the shadow it holds, and sells queued yield shadow through - /// itself. Empty makes a plain pool. Both seeds must already be on deposit. + /// itself. Empty makes a plain pool. The pair's rows are billed to `sysio`. + /// Both seeds must already be on deposit. /// The system token's always can be; the first leg has no pair yet, so its /// seed transfer must also carry this contract's authority. [[sysio::action]] void inittoken(name user, symbol new_symbol, @@ -119,11 +123,9 @@ namespace sysio { /// announcement per account is pending at a time; a new one replaces it, and /// while one is pending any other transfer from `from` is refused. Typed and /// memo-free, so a contract can do both steps inline in one transaction. - /// Requires `from`'s authority. - /// Requires `from`'s authority, and bills `from` for the row, so the - /// announcement carries `{from, sysio.payer}` alongside its active permission. + /// Requires `from`'s authority. The row is billed to `sysio`. [[sysio::action]] void fundyield(name from, symbol_code pair_token, asset quantity); - /// Drop `from`'s pending announcement and refund its row. Their transfers go + /// Drop `from`'s pending announcement and erase its row. Their transfers go /// back to being ordinary deposits. Announcing again replaces a pending row, /// so this is for leaving none at all. Requires `from`'s authority. [[sysio::action]] void cancelyield(name from); @@ -151,7 +153,8 @@ namespace sysio { /// Requires `setyield` to have run. No authorization is required. [[sysio::action]] void tickyield(symbol_code pair_token); [[sysio::on_notify("*::transfer")]] void ontransfer(name from, name to, asset quantity, string memo); - [[sysio::action]] void openext( const name& user, const name& payer, const extended_symbol& ext_symbol); + /// Erase `user`'s deposit row for `ext_symbol`, sending any balance to `to`. + /// Deposit rows are billed to `sysio` and created by the first credit. [[sysio::action]] void closeext ( const name& user, const name& to, const extended_symbol& ext_symbol, string memo); [[sysio::action]] void withdraw(name user, name to, extended_asset to_withdraw, string memo); [[sysio::action]] void addliquidity(name user, asset to_buy, asset max_asset1, asset max_asset2); @@ -167,7 +170,8 @@ namespace sysio { [[sysio::action]] void transfer(const name& from, const name& to, const asset& quantity, const string& memo ); - [[sysio::action]] void open( const name& owner, const symbol& symbol, const name& ram_payer ); + /// Erase `owner`'s empty pair-token balance row for `symbol`. Balance rows are billed to + /// `sysio` and created by the first credit. [[sysio::action]] void close( const name& owner, const symbol& symbol ); private: @@ -330,6 +334,8 @@ namespace sysio { /// owed amount and collides with the receipt the first one left. currency_stats accrue(const pair_key& key, const currency_stats& token); + /// Credit (positive) or debit (negative) `value` to `owner`'s deposit of its token. + /// The first credit creates the row, billed to `sysio`; a debit needs the balance. void add_signed_ext_balance( const name& owner, const extended_asset& value ); void add_signed_liq(name user, asset to_buy, bool is_buying, asset max_asset1, asset max_asset2); void memoexchange(name user, extended_asset ext_asset_in, string_view details); @@ -373,7 +379,9 @@ namespace sysio { /// interval is weighted at the price that actually held during it. void update_price_accumulators(const currency_stats& token); void placeindex(name user, symbol evo_symbol, extended_asset pool1, extended_asset pool2 ); - void add_balance( const name& owner, const asset& value, const name& ram_payer ); + /// Credit `value` to `owner`'s pair-token balance, creating the row; billed to `sysio`. + void add_balance( const name& owner, const asset& value ); + /// Debit `value` from `owner`'s pair-token balance; billed to `sysio`. void sub_balance( const name& owner, const asset& value ); }; } diff --git a/contracts/sysio.swap/ricardian/sysio.swap.contracts.md b/contracts/sysio.swap/ricardian/sysio.swap.contracts.md index 1cc6f620ef..eb5d35d0d8 100644 --- a/contracts/sysio.swap/ricardian/sysio.swap.contracts.md +++ b/contracts/sysio.swap/ricardian/sysio.swap.contracts.md @@ -1,19 +1,3 @@ -

openext

- ---- -spec_version: "0.2.0" -title: Open extended Balance -summary: 'Open a zero quantity extended balance for {{nowrap user}}' ---- - -{{ram_payer}} agrees to establish a zero quantity extended balance for {{user}} -for the {{ext_symbol}} extended symbol. - -If {{user}} does not have an extended balance for {{ext_symbol}}, {{ram_payer}} will be designated as the RAM payer of {{user}}'s extended balance for {{ext_symbol}}. As a result, RAM will be deducted from {{ram_payer}}’s resources to create the necessary records. - -The authorization of {{ram_payer}} is required. - -

closeext

--- @@ -28,7 +12,7 @@ If the extended balance is nonzero, it will be transfered to {{to}} before closi In order to function properly, it is necessary that the contract of {{ext_symbol}} permanently has a transfer action that satisfies the conditions (1), (2), (6) of the present contract's transfer action. -RAM will be refunded to the RAM payer of {{user}}'s extended balance for {{ext_symbol}}. +RAM will be refunded to sysio, which pays for every extended balance. The authorization of {{user}} is required. @@ -45,9 +29,9 @@ This action is executed as a response to a notification of a transfer action wit The transfer is accepted only if the extended symbol formed by the transfer's contract and the symbol {{asset_to_symbol quantity}} is the system token set by setconfig, or is the first leg of the pair it forms with the system token, or the transfer carries this contract's authority (the authority that creates pairs, so that a pair's first leg can be seeded before the pair exists). Any other transfer fails. -The default response is to deposit {{quantity}} to {{from}}'s extended balance for that extended symbol. This is only possible if such extended balance previously exists. +The default response is to deposit {{quantity}} to {{from}}'s extended balance for that extended symbol. If {{from}} does not have that extended balance, it is created, and its RAM is billed to sysio. -If {{memo}} starts with "deposit to:", the account {{from}} will be replaced by the subsequent content of {{memo}} whenever it is possible. +If {{memo}} starts with "deposit to:", the account {{from}} will be replaced by the subsequent content of {{memo}} whenever it is possible. The named account must exist. Two typed routes take precedence over the memo, and each accepts exactly one transfer. If {{from}} is a shadow token from which this contract has a yield payout outstanding (see accrueyield), {{quantity}} must be that payout, which was already credited to the pool; the receipt is retired and nothing else is deposited. Otherwise, if {{from}} has a yield funding pending (see fundyield), {{quantity}} must be the announced amount, which fills the announced pair's reservoir. In either case any other transfer from {{from}} fails. @@ -87,7 +71,7 @@ The pair token supply minted is the square root of the product of the two initia If {{yield_leg}} is given it must be the first leg, and the pair becomes a yield pool on that shadow token: the yield the contract is owed on the shadow it holds is settled into the second leg without minting, before every addliquidity and remliquidity and on accrueyield. Without {{yield_leg}} the pair is a plain pool. -RAM will be deducted from {{user}}’s resources to create the necessary records. +The RAM of the necessary records is billed to sysio. Authorization of {{user}} and of the contract is required. @@ -210,7 +194,7 @@ summary: 'Announce {{nowrap quantity}} of shadow for the reservoir of {{nowrap p {{from}} announces the transfer of exactly {{quantity}}, in the shadow symbol of the yield pool {{pair_token}}, to this contract. The transfer of {{quantity}} from {{from}} that follows, in this transaction or a later one, is added to the reservoir of {{pair_token}}, the shadow queued to be sold through the pool, and not to {{from}}'s extended balance. While the announcement is pending, any other transfer from {{from}} to this contract is refused. A new announcement by {{from}} replaces the pending one. When the delivery fills a reservoir that was empty, the pair's tick clock restarts. -RAM for the record of the announcement will be deducted from {{from}}'s resources, and returned by cancelyield or by the delivery. +The RAM of the record of the announcement is billed to sysio, and returned by cancelyield or by the delivery. Authorization of {{from}} is required. @@ -223,7 +207,7 @@ title: Cancel yield funding summary: 'Drop the pending funding announcement of {{nowrap from}}' --- -{{from}} agrees to withdraw their pending fundyield announcement. The record is erased and its RAM returned to {{from}}, and transfers from {{from}} to this contract are treated as ordinary deposits again. The action fails if {{from}} has no pending announcement. +{{from}} agrees to withdraw their pending fundyield announcement. The record is erased and its RAM returned to sysio, and transfers from {{from}} to this contract are treated as ordinary deposits again. The action fails if {{from}} has no pending announcement. Authorization of {{from}} is required. @@ -276,20 +260,7 @@ summary: 'Close {{nowrap owner}}’s zero quantity balance' {{owner}} agree to close their zero quantity balance for the {{symbol_to_symbol_code symbol}} token. -RAM will be refunded to the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}. - - -

open

- ---- -spec_version: "0.2.0" -title: Open Token Balance -summary: 'Open a zero quantity balance for {{nowrap owner}}' ---- - -{{ram_payer}} agrees to establish a zero quantity balance for {{owner}} for the {{symbol_to_symbol_code symbol}} token. - -If {{owner}} does not have a balance for {{symbol_to_symbol_code symbol}}, {{ram_payer}} will be designated as the RAM payer of the {{symbol_to_symbol_code symbol}} token balance for {{owner}}. As a result, RAM will be deducted from {{ram_payer}}’s resources to create the necessary records. +RAM will be refunded to sysio, which pays for every {{symbol_to_symbol_code symbol}} token balance.

transfer

@@ -305,9 +276,7 @@ summary: 'Send {{nowrap quantity}} from {{nowrap from}} to {{nowrap to}}' (2) {{#if memo}} There is a memo attached to the transfer stating: {{memo}} {{/if}} -(3) If {{from}} is not already the RAM payer of their {{asset_to_symbol_code quantity}} token balance, {{from}} will be designated as such. As a result, RAM will be deducted from {{from}}’s resources to refund the original RAM payer. - -(4) If {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, {{from}} 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 {{from}}’s resources to create the necessary records. +(3) If {{to}} does not have a balance for {{asset_to_symbol_code quantity}}, one is created. The RAM of both balances is billed to sysio, not to {{from}} or {{to}}. -(5) The account {{from}} is notified. -(6) The account {{to}} is notified. \ No newline at end of file +(4) The account {{from}} is notified. +(5) The account {{to}} is notified. \ No newline at end of file diff --git a/contracts/sysio.swap/sysio.swap.abi b/contracts/sysio.swap/sysio.swap.abi index 9f90819beb..5e430dfcc1 100644 --- a/contracts/sysio.swap/sysio.swap.abi +++ b/contracts/sysio.swap/sysio.swap.abi @@ -341,42 +341,6 @@ } ] }, - { - "name": "open", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "symbol", - "type": "symbol" - }, - { - "name": "ram_payer", - "type": "name" - } - ] - }, - { - "name": "openext", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "payer", - "type": "name" - }, - { - "name": "ext_symbol", - "type": "extended_symbol" - } - ] - }, { "name": "pair_identity_key", "base": "", @@ -644,16 +608,6 @@ "type": "inittoken", "ricardian_contract": "" }, - { - "name": "open", - "type": "open", - "ricardian_contract": "" - }, - { - "name": "openext", - "type": "openext", - "ricardian_contract": "" - }, { "name": "remliquidity", "type": "remliquidity", diff --git a/contracts/sysio.swap/sysio.swap.cpp b/contracts/sysio.swap/sysio.swap.cpp index d16ad07f68..ec18bbf196 100644 --- a/contracts/sysio.swap/sysio.swap.cpp +++ b/contracts/sysio.swap/sysio.swap.cpp @@ -16,16 +16,6 @@ namespace { namespace sysio { -void swap::openext( const name& user, const name& payer, const extended_symbol& ext_symbol) { - check( is_account( user ), "user account does not exist" ); - require_auth( payer ); - evodexacnts acnts( get_self(), user.value ); - const auto key = key_of(ext_symbol); - if( !acnts.contains(key) ) { - acnts.emplace( payer, key, evodex_account{ extended_asset{0, ext_symbol} } ); - } -} - void swap::closeext( const name& user, const name& to, const extended_symbol& ext_symbol, string memo) { require_auth( user ); evodexacnts acnts( get_self(), user.value ); @@ -167,7 +157,7 @@ void swap::add_signed_liq(name user, asset to_add, bool is_buying, add_signed_ext_balance(user, -to_pay1); add_signed_ext_balance(user, -to_pay2); - (to_add.amount > 0)? add_balance(user, to_add, user) : sub_balance(user, -to_add); + (to_add.amount > 0)? add_balance(user, to_add) : sub_balance(user, -to_add); update_price_accumulators(token); statstable.modify( name{}, key, [&]( auto& a ) { a.supply += to_add; @@ -286,7 +276,7 @@ void swap::setconfig(name fee_authority, extended_symbol system_token) { check( is_account( system_token.get_contract() ), "system token contract does not exist" ); check( system_token.get_symbol().is_valid(), "invalid system token symbol" ); swapconfig_t config( get_self() ); - config.set( swap_config{ fee_authority, system_token }, get_self() ); + config.set( swap_config{ fee_authority, system_token }, RAM_PAYER ); } swap::swap_config swap::configured() const { @@ -330,7 +320,7 @@ std::optional yield_leg) // shadow can have. check( *yield_leg == initial_pool1.get_extended_symbol(), "yield_leg must be the pair's first leg" ); reservoirs reservoir_table( get_self() ); - reservoir_table.emplace( user, key, reservoir{ extended_asset{ 0, *yield_leg } } ); + reservoir_table.emplace( RAM_PAYER, key, reservoir{ extended_asset{ 0, *yield_leg } } ); } check( 0 <= initial_fee && initial_fee <= MAX_FEE, "fee out of range" ); if (fee_authority == name{}) { @@ -339,7 +329,7 @@ std::optional yield_leg) check( is_account( fee_authority ), "fee authority account does not exist" ); } - statstable.emplace( user, key, currency_stats{ + statstable.emplace( RAM_PAYER, key, currency_stats{ .supply = new_token, .max_supply = asset{MAX, new_symbol}, .issuer = get_self(), @@ -352,11 +342,11 @@ std::optional yield_leg) } ); priceaccums accums( get_self() ); - accums.emplace( user, key, price_accumulator{ .last_update = current_time_point() } ); + accums.emplace( RAM_PAYER, key, price_accumulator{ .last_update = current_time_point() } ); placeindex(user, new_symbol, initial_pool1, initial_pool2 ); // The locked shares count toward supply but are credited to nobody. - add_balance(user, new_token - locked_shares, user); + add_balance(user, new_token - locked_shares); add_signed_ext_balance(user, -initial_pool1); add_signed_ext_balance(user, -initial_pool2); } @@ -364,7 +354,7 @@ std::optional yield_leg) void swap::placeindex(name user, symbol evo_symbol, extended_asset pool1, extended_asset pool2 ) { evoindexes indextable( get_self() ); - indextable.emplace( user, identity_of(pool1.get_extended_symbol(), pool2.get_extended_symbol()), + indextable.emplace( RAM_PAYER, identity_of(pool1.get_extended_symbol(), pool2.get_extended_symbol()), pair_index{ evo_symbol }, "the pool is already indexed" ); } @@ -431,7 +421,7 @@ swap::currency_stats swap::accrue(const pair_key& key, const currency_stats& tok const extended_asset payout{ asset{ int64_t(owed), payout_symbol.get_symbol() }, payout_symbol.get_contract() }; yieldpayouts payouts( get_self() ); - payouts.emplace( get_self(), contract_key{ shadow.get_contract() }, + payouts.emplace( RAM_PAYER, contract_key{ shadow.get_contract() }, payout_receipt{ token.supply.symbol.code(), payout }, "a yield payout from this contract is still pending" ); // The pool changes: close the accumulators' interval at the old price first. @@ -454,11 +444,9 @@ void swap::fundyield(name from, symbol_code pair_token, asset quantity) { const extended_symbol& shadow = require_yield_leg(*token); check( quantity.symbol == shadow.get_symbol(), "quantity must be in the pair's shadow symbol" ); check( quantity.amount > 0, "quantity must be positive" ); - // Billed to `from`: only the matching transfer erases the row, so an - // announcement nobody delivers would otherwise sit on the contract's RAM, - // one per account that ever called this. cancelyield refunds it. + // One row per funder, erased by the matching transfer or by cancelyield. yieldfunds funds( get_self() ); - funds.upsert( from, funder_key{ from }, + funds.upsert( RAM_PAYER, funder_key{ from }, fund_receipt{ pair_token, extended_asset{ quantity, shadow.get_contract() } } ); } @@ -623,13 +611,19 @@ swap::pair_identity_key swap::identity_of(const extended_symbol& a, const extend void swap::add_signed_ext_balance( const name& user, const extended_asset& to_add ) { check( to_add.quantity.is_valid(), "invalid asset" ); + if (to_add.quantity.amount == 0) return; evodexacnts acnts( get_self(), user.value ); const auto key = key_of(to_add.get_extended_symbol()); - check( acnts.contains(key), "extended_symbol not registered for this user,\ - please run openext action or write exchange details in the memo of your transfer"); - acnts.modify( name{}, key, [&]( auto& a ) { - a.balance += to_add; - check( a.balance.quantity.amount >= 0, "insufficient funds"); - }); + if (acnts.contains(key)) { + acnts.modify( RAM_PAYER, key, [&]( auto& a ) { + a.balance += to_add; + check( a.balance.quantity.amount >= 0, "insufficient funds"); + }); + return; + } + // The first credit creates the row; a debit with no row has nothing to draw on. + check( to_add.quantity.amount > 0, "insufficient funds" ); + check( is_account( user ), "user account does not exist" ); + acnts.emplace( RAM_PAYER, key, evodex_account{ to_add } ); } } // namespace sysio diff --git a/contracts/sysio.swap/sysio.swap.wasm b/contracts/sysio.swap/sysio.swap.wasm index 183f9372a4..a010885aae 100755 Binary files a/contracts/sysio.swap/sysio.swap.wasm and b/contracts/sysio.swap/sysio.swap.wasm differ diff --git a/contracts/sysio.swap/token_functions.cpp b/contracts/sysio.swap/token_functions.cpp index f6f8c2f669..2c962cd549 100644 --- a/contracts/sysio.swap/token_functions.cpp +++ b/contracts/sysio.swap/token_functions.cpp @@ -20,10 +20,8 @@ void swap::transfer( const name& from, const name& to, const asset& quantity, check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); check( memo.size() <= 256, "memo has more than 256 bytes" ); - auto payer = has_auth( to ) ? to : from; - sub_balance( from, quantity ); - add_balance( to, quantity, payer ); + add_balance( to, quantity ); if (to == get_self()) ontransfer(from, to, quantity, memo); // line added to code from eosio.token } @@ -35,40 +33,17 @@ void swap::sub_balance( const name& owner, const asset& value ) { check( from.has_value(), "no balance object found" ); check( from->balance.amount >= value.amount, "overdrawn balance" ); - from_acnts.modify( owner, key, [&]( auto& a ) { + from_acnts.modify( RAM_PAYER, key, [&]( auto& a ) { a.balance -= value; }); } -void swap::add_balance( const name& owner, const asset& value, const name& ram_payer ) +void swap::add_balance( const name& owner, const asset& value ) { accounts to_acnts( get_self(), owner.value ); - const account_key key{ value.symbol.code().raw() }; - if( !to_acnts.contains( key ) ) { - to_acnts.emplace( ram_payer, key, account{ value } ); - } else { - to_acnts.modify( name{}, key, [&]( auto& a ) { - a.balance += value; - }); - } -} - -void swap::open( const name& owner, const symbol& symbol, const name& ram_payer ) -{ - require_auth( ram_payer ); - - check( is_account( owner ), "owner account does not exist" ); - - auto sym_code_raw = symbol.code().raw(); - stats statstable( get_self() ); - const auto st = statstable.get( pair_key{ sym_code_raw }, "symbol does not exist" ); - check( st.supply.symbol == symbol, "symbol precision mismatch" ); - - accounts acnts( get_self(), owner.value ); - const account_key key{ sym_code_raw }; - if( !acnts.contains( key ) ) { - acnts.emplace( ram_payer, key, account{ asset{0, symbol} } ); - } + to_acnts.upsert( RAM_PAYER, account_key{ value.symbol.code().raw() }, + account{ value }, // default if new + [&]( auto& a ) { a.balance += value; } ); // updater if exists } void swap::close( const name& owner, const symbol& symbol ) diff --git a/contracts/sysio.token/include/sysio.token/sysio.token.hpp b/contracts/sysio.token/include/sysio.token/sysio.token.hpp index 3319f50481..6e0ca5bf45 100644 --- a/contracts/sysio.token/include/sysio.token/sysio.token.hpp +++ b/contracts/sysio.token/include/sysio.token/sysio.token.hpp @@ -72,22 +72,8 @@ namespace sysio { 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/SYSIO/sysio.contracts/issues/62) - * and [here](https://github.com/SYSIO/sysio.contracts/issues/61). - */ - [[sysio::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`. + * Erases the empty balance row of `owner` for token `symbol`. Balance rows are + * billed to `sysio` and created by the first credit. * * @param owner - the owner account to execute the close action for, * @param symbol - the symbol of the token to execute the close action for. @@ -116,7 +102,6 @@ namespace sysio { using issue_action = sysio::action_wrapper<"issue"_n, &token::issue>; using retire_action = sysio::action_wrapper<"retire"_n, &token::retire>; using transfer_action = sysio::action_wrapper<"transfer"_n, &token::transfer>; - using open_action = sysio::action_wrapper<"open"_n, &token::open>; using close_action = sysio::action_wrapper<"close"_n, &token::close>; // Account row + key types are public so foreign contracts can read @@ -152,7 +137,7 @@ namespace sysio { using stats = kv::scoped_table<"stat"_n, stat_key, currency_stats>; void sub_balance( const name& owner, const asset& value ); - void add_balance( const name& owner, const asset& value, const name& ram_payer ); + void add_balance( const name& owner, const asset& value ); }; } diff --git a/contracts/sysio.token/sysio.token.abi b/contracts/sysio.token/sysio.token.abi index 1a41eaf86e..945660e77a 100644 --- a/contracts/sysio.token/sysio.token.abi +++ b/contracts/sysio.token/sysio.token.abi @@ -87,24 +87,6 @@ } ] }, - { - "name": "open", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "symbol", - "type": "symbol" - }, - { - "name": "ram_payer", - "type": "name" - } - ] - }, { "name": "retire", "base": "", @@ -168,11 +150,6 @@ "type": "issue", "ricardian_contract": "" }, - { - "name": "open", - "type": "open", - "ricardian_contract": "" - }, { "name": "retire", "type": "retire", diff --git a/contracts/sysio.token/sysio.token.bad.abi b/contracts/sysio.token/sysio.token.bad.abi index 6a6ddc31ac..fe70e0b37b 100644 --- a/contracts/sysio.token/sysio.token.bad.abi +++ b/contracts/sysio.token/sysio.token.bad.abi @@ -77,24 +77,6 @@ } ] }, - { - "name": "open", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "symbol", - "type": "symbol" - }, - { - "name": "ram_payer", - "type": "name" - } - ] - }, { "name": "retire", "base": "", @@ -148,11 +130,6 @@ "type": "issue", "ricardian_contract": "" }, - { - "name": "open", - "type": "open", - "ricardian_contract": "" - }, { "name": "retire", "type": "retire", diff --git a/contracts/sysio.token/sysio.token.cpp b/contracts/sysio.token/sysio.token.cpp index d907bf8733..76c41496be 100644 --- a/contracts/sysio.token/sysio.token.cpp +++ b/contracts/sysio.token/sysio.token.cpp @@ -50,7 +50,7 @@ void token::issue( const name& to, const asset& quantity, const string& memo ) s.supply += quantity; }); - add_balance( st.issuer, quantity, ram_payer ); + add_balance( st.issuer, quantity ); } void token::retire( const asset& quantity, const string& memo ) @@ -96,7 +96,7 @@ void token::transfer( const name& from, check( memo.size() <= 256, "memo has more than 256 bytes" ); sub_balance( from, quantity ); - add_balance( to, quantity, ram_payer ); + add_balance( to, quantity ); } void token::sub_balance( const name& owner, const asset& value ) { @@ -107,7 +107,7 @@ void token::sub_balance( const name& owner, const asset& value ) { }); } -void token::add_balance( const name& owner, const asset& value, const name& ram_payer ) +void token::add_balance( const name& owner, const asset& value ) { accounts to_acnts( get_self(), owner.value ); to_acnts.upsert( ram_payer, acct_key{value.symbol.code().raw()}, @@ -115,23 +115,6 @@ void token::add_balance( const name& owner, const asset& value, const name& ram_ [&]( auto& a ) { a.balance += value; } ); // updater if exists } -void token::open( const name& owner, const symbol& symbol, const name& ram_payer ) -{ - require_auth( ram_payer ); - - check( is_account( owner ), "owner account does not exist" ); - - auto sym_code_raw = symbol.code().raw(); - stats statstable( get_self(), sym_code_raw ); - const auto& st = statstable.get( stat_key{sym_code_raw}, "symbol does not exist" ); - check( st.supply.symbol == symbol, "symbol precision mismatch" ); - - accounts acnts( get_self(), owner.value ); - if( !acnts.contains( acct_key{sym_code_raw} ) ) { - acnts.emplace( ram_payer, acct_key{sym_code_raw}, account{asset{0, symbol}} ); - } -} - void token::close( const name& owner, const symbol& symbol ) { require_auth( owner ); diff --git a/contracts/sysio.token/sysio.token.wasm b/contracts/sysio.token/sysio.token.wasm index bf17660e29..9b9bb315ae 100755 Binary files a/contracts/sysio.token/sysio.token.wasm and b/contracts/sysio.token/sysio.token.wasm differ diff --git a/contracts/tests/sysio.kicker_tests.cpp b/contracts/tests/sysio.kicker_tests.cpp index d35802fed4..b56b1a4409 100644 --- a/contracts/tests/sysio.kicker_tests.cpp +++ b/contracts/tests/sysio.kicker_tests.cpp @@ -110,8 +110,6 @@ BOOST_AUTO_TEST_CASE(call_pattern_independence) { BOOST_FIXTURE_TEST_CASE(twap_after_trade_and_current_supply, kicker_tester) { wait(math::day_sec); const auto old_gift = expected(); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{WIRE_SYM, TOKEN_ACCOUNT})); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{LIQSOL_SYM, LIQ_ACCOUNT})); BOOST_REQUIRE_EQUAL(success(), transfer_wire(SYSIO_ACCOUNT, "bob"_n, seed)); BOOST_REQUIRE_EQUAL(success(), transfer_wire("bob"_n, SWAP_ACCOUNT, seed)); BOOST_REQUIRE_EQUAL(success(), exchange("bob"_n, POOL_SYM, extended_asset{asset(seed, WIRE_SYM), TOKEN_ACCOUNT}, @@ -137,8 +135,6 @@ BOOST_FIXTURE_TEST_CASE(twap_after_trade_and_current_supply, kicker_tester) { BOOST_FIXTURE_TEST_CASE(large_same_block_trade_cannot_amplify_gift, kicker_tester) { constexpr uint64_t attack = 10'000'000 * UNIT; wait(3 * math::day_sec); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{WIRE_SYM, TOKEN_ACCOUNT})); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{LIQSOL_SYM, LIQ_ACCOUNT})); BOOST_REQUIRE_EQUAL(success(), transfer_wire(SYSIO_ACCOUNT, "bob"_n, attack)); BOOST_REQUIRE_EQUAL(success(), transfer_wire("bob"_n, SWAP_ACCOUNT, attack)); const auto baseline = expected(); @@ -743,8 +739,6 @@ BOOST_AUTO_TEST_CASE(one_block_spike_cashed_in_one_block_pays_the_inflated_gift) BOOST_REQUIRE_EQUAL(t.success(), t.change(200, 1, cap)); BOOST_REQUIRE_EQUAL(t.success(), t.configure(t.budget, 1, t.SYSIO_ACCOUNT, math::min_twap_window_sec, tightest)); - BOOST_REQUIRE_EQUAL(t.success(), t.openext("bob"_n, "bob"_n, extended_symbol{t.WIRE_SYM, t.TOKEN_ACCOUNT})); - BOOST_REQUIRE_EQUAL(t.success(), t.openext("bob"_n, "bob"_n, extended_symbol{t.LIQSOL_SYM, t.LIQ_ACCOUNT})); BOOST_REQUIRE_EQUAL(t.success(), t.transfer_wire(t.SYSIO_ACCOUNT, "bob"_n, pump)); BOOST_REQUIRE_EQUAL(t.success(), t.transfer_wire("bob"_n, t.SWAP_ACCOUNT, pump)); t.wait(math::min_twap_window_sec); @@ -871,8 +865,6 @@ BOOST_FIXTURE_TEST_CASE(pump_straddling_a_checkpoint_funds_no_payout, kicker_tes }; BOOST_REQUIRE_EQUAL(success(), configure(budget, 1, SYSIO_ACCOUNT, window)); BOOST_REQUIRE_EQUAL(100'000 * UNIT, supply()); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{WIRE_SYM, TOKEN_ACCOUNT})); - BOOST_REQUIRE_EQUAL(success(), openext("bob"_n, "bob"_n, extended_symbol{LIQSOL_SYM, LIQ_ACCOUNT})); BOOST_REQUIRE_EQUAL(success(), transfer_wire(SYSIO_ACCOUNT, "bob"_n, pump)); BOOST_REQUIRE_EQUAL(success(), transfer_wire("bob"_n, SWAP_ACCOUNT, pump)); diff --git a/contracts/tests/sysio.liq_tests.cpp b/contracts/tests/sysio.liq_tests.cpp index 2951bc31f5..46167b3f87 100644 --- a/contracts/tests/sysio.liq_tests.cpp +++ b/contracts/tests/sysio.liq_tests.cpp @@ -115,15 +115,14 @@ BOOST_FIXTURE_TEST_CASE(settle_before_mutate_conserves_every_subunit, sysio_liq_ BOOST_REQUIRE_EQUAL(0u, pot()); BOOST_REQUIRE_EQUAL(0, owed("alice"_n)); - // A row opened after the index moved is stamped, not zeroed: nothing is owed, - // claiming sends nothing, and a later credit accrues only from here. - BOOST_REQUIRE_EQUAL(success(), open("dave"_n, LIQSOL_SYM, "dave"_n)); + // A row created after the index moved is stamped, not zeroed: nothing is owed, + // claiming sends nothing, and later yield accrues only from here. + BOOST_REQUIRE_EQUAL(success(), mint("dave"_n, 100 * UNIT)); BOOST_REQUIRE_EQUAL(index(), account_row("dave"_n)["index_checkpoint"].as_uint128()); + BOOST_REQUIRE_EQUAL(0, owed("dave"_n)); const int64_t dave_before = wire_balance("dave"_n); BOOST_REQUIRE_EQUAL(success(), claim("dave"_n)); BOOST_REQUIRE_EQUAL(dave_before, wire_balance("dave"_n)); - BOOST_REQUIRE_EQUAL(success(), mint("dave"_n, 100 * UNIT)); - BOOST_REQUIRE_EQUAL(0, owed("dave"_n)); BOOST_REQUIRE_EQUAL(success(), addyield("carol"_n, 50 * UNIT)); // 500 supply: dave holds a fifth BOOST_REQUIRE_EQUAL(10 * UNIT, owed("dave"_n)); @@ -192,7 +191,6 @@ BOOST_FIXTURE_TEST_CASE(the_index_grows_past_64_bits, sysio_liq_tester) try { BOOST_FIXTURE_TEST_CASE(addyield_distributes_only_supplied_wire, sysio_liq_tester) try { constexpr int64_t intake = 100 * UNIT; BOOST_REQUIRE_EQUAL(success(), mint("alice"_n, 100 * UNIT)); - BOOST_REQUIRE_EQUAL(success(), openext("alice"_n, "alice"_n, extended_symbol{WIRE_SYM, TOKEN_ACCOUNT})); BOOST_REQUIRE_EQUAL(success(), transfer_wire("alice"_n, SWAP_ACCOUNT, intake)); const auto treasury_before = wire_balance(SYSIO_ACCOUNT); const auto holder_before = wire_balance("alice"_n); @@ -328,11 +326,6 @@ BOOST_FIXTURE_TEST_CASE(a_holder_can_sell_shadow_into_the_yield_pool_for_wire, s BOOST_REQUIRE_EQUAL(success(), mint("alice"_n, 100 * UNIT)); BOOST_REQUIRE_EQUAL(success(), regliqpool(SOLANA, LIQSOL, POOL_SYM, 1000 * UNIT, 1000 * UNIT)); - const extended_symbol shadow_ext{ LIQSOL_SYM, LIQ_ACCOUNT }; - const extended_symbol wire_ext{ WIRE_SYM, TOKEN_ACCOUNT }; - BOOST_REQUIRE_EQUAL(success(), openext("alice"_n, "alice"_n, shadow_ext)); - BOOST_REQUIRE_EQUAL(success(), openext("alice"_n, "alice"_n, wire_ext)); - constexpr int64_t sold = 50 * UNIT; BOOST_REQUIRE_EQUAL(success(), transfer_shadow("alice"_n, SWAP_ACCOUNT, sold)); BOOST_REQUIRE_EQUAL(50 * UNIT, shadow_balance("alice"_n)); diff --git a/contracts/tests/sysio.swap_tests.cpp b/contracts/tests/sysio.swap_tests.cpp index aee3f3e11e..c529651fc8 100644 --- a/contracts/tests/sysio.swap_tests.cpp +++ b/contracts/tests/sysio.swap_tests.cpp @@ -110,8 +110,11 @@ class sysio_swap_tester : public tester { set_abi( "anothertoken"_n, contracts::token_abi().data() ); set_privileged( "anothertoken"_n ); + // Privileged like the deployment makes it (sysio.roa::setsyscode): its + // pair-token balance rows are billed to sysio. set_code( "sysio.swap"_n, contracts::swap_wasm() ); set_abi( "sysio.swap"_n, contracts::swap_abi().data() ); + set_privileged( "sysio.swap"_n ); set_code( "carol"_n, contracts::token_wasm() ); set_abi( "carol"_n, contracts::token_abi().data() ); @@ -225,20 +228,6 @@ class sysio_swap_tester : public tester { ( "memo", memo) ); } - action_result open( name owner, symbol sym, name ram_payer ) { - return push_action( "sysio.swap"_n, owner, "open"_n, mvo() - ( "owner", owner) - ( "symbol", sym) - ( "ram_payer", ram_payer) - ); - } - action_result openext( name user, name payer, extended_symbol ext_symbol ) { - return push_action( "sysio.swap"_n, payer, "openext"_n, mvo() - ( "user", user) - ( "payer", payer) - ( "ext_symbol", ext_symbol) - ); - } action_result closeext ( const name user, const name to, const extended_symbol ext_symbol ){ return push_action( "sysio.swap"_n, user, "closeext"_n, mvo() ( "user", user ) @@ -267,10 +256,10 @@ class sysio_swap_tester : public tester { action_result inittoken( name user, symbol new_symbol, extended_asset initial_pool1, extended_asset initial_pool2, int initial_fee, name fee_authority, asset locked_shares, std::optional yield_leg = std::nullopt ){ - // inittoken bills the new rows to `user`, so the action must carry the - // user's sysio.payer permission in addition to the two active authorities. + // inittoken needs the user's and the contract's authority; the pair's rows + // are billed to sysio. return push_swap_action( "inittoken"_n, - { {user, config::sysio_payer_name}, {user, config::active_name}, {"sysio.swap"_n, config::active_name} }, + { {user, config::active_name}, {"sysio.swap"_n, config::active_name} }, mvo() ( "user", user) ("new_symbol", new_symbol) @@ -306,10 +295,9 @@ class sysio_swap_tester : public tester { vector pending_payout( name contract ) { return get_kv_row( "sysio.swap"_n, "yieldpayouts"_n, { contract.to_uint64_t() } ); } - // fundyield is the funder's own call; the fixture adds its payer permission, - // which the row is billed to. + // fundyield is the funder's own call; its row is billed to sysio. action_result fundyield( name from, symbol_code pair_token, asset quantity ) { - return push_action( "sysio.swap"_n, from, "fundyield"_n, mvo() + return push_swap_action( "fundyield"_n, { {from, config::active_name} }, mvo() ( "from", from )( "pair_token", pair_token )( "quantity", quantity ) ); } @@ -320,6 +308,18 @@ class sysio_swap_tester : public tester { int64_t ram_usage( name account ) const { return control->get_resource_limits_manager().get_account_ram_usage( account ); } + // The account billed for `user`'s pair-token balance row of `pair_token`. + name balance_payer( name user, symbol_code pair_token ) const { + return row_payer( "accounts"_n, { user.to_uint64_t(), pair_token.value } ); + } + // The account billed for `user`'s deposit row of `ext`. + name deposit_payer( name user, const extended_symbol& ext ) const { + return row_payer( "evodexacnts"_n, { user.to_uint64_t(), ext.contract.to_uint64_t(), ext.sym.value() } ); + } + action_result send_pair_token( name from, name to, asset quantity ) { + return push_action( "sysio.swap"_n, from, "transfer"_n, mvo() + ( "from", from )( "to", to )( "quantity", quantity )( "memo", "" ) ); + } // The pending funding announced by `funder`, as a variant; null when none. fc::variant pending_funding( name funder ) { const auto data = get_kv_row( "sysio.swap"_n, "yieldfunds"_n, { funder.to_uint64_t() } ); @@ -346,9 +346,9 @@ class sysio_swap_tester : public tester { action announce; announce.account = "sysio.swap"_n; announce.name = "fundyield"_n; - // The announcement's row is billed to the funder, so it carries the payer - // permission as well as the active one. - announce.authorization = { {from, config::sysio_payer_name}, {from, config::active_name} }; + // The announcement's row is billed to sysio, so it carries only the funder's + // active permission. + announce.authorization = { {from, config::active_name} }; announce.data = abi_ser.variant_to_binary( "fundyield", mvo()( "from", from )( "pair_token", pair )( "quantity", quantity ), fixture_abi_yield_function() ); @@ -518,8 +518,13 @@ class sysio_swap_tester : public tester { const auto data = get_kv_row( "sysio.liq"_n, "yieldidx"_n, { sym.value } ); return data.empty() ? shadow_index_row{ 0, 0, 0 } : fc::raw::unpack( data ); } - // A user's deposit of any extended symbol, resolved without `extend`. + // A user's deposit of any extended symbol, resolved without `extend`; 0 without a row. int64_t deposit_of( name user, const extended_symbol& ext ); + // Whether `user` has a deposit row for `ext`. + bool has_deposit( name user, const extended_symbol& ext ) { + return !get_kv_row( "sysio.swap"_n, "evodexacnts"_n, + { user.to_uint64_t(), ext.contract.to_uint64_t(), ext.sym.value() } ).empty(); + } action_result addliquidity(name user, asset to_buy, asset max_asset1, asset max_asset2) { return push_action( "sysio.swap"_n, user, "addliquidity"_n, mvo() ( "user", user ) @@ -601,14 +606,24 @@ class sysio_swap_tester : public tester { // Raw KV read of a composite-keyed row: every key word big-endian, in order // (for a scoped table the scope is the first word). Empty when absent. - vector get_kv_row( name code, name table, std::initializer_list key_words ) { + static std::string kv_key( std::initializer_list key_words ) { std::string key; for (uint64_t word : key_words) for (int shift = 56; shift >= 0; shift -= 8) key.push_back( char((word >> shift) & 0xff) ); - const auto row = control->kv().find_row( code, chain::compute_table_id(table.to_uint64_t()), key ); + return key; + } + vector get_kv_row( name code, name table, std::initializer_list key_words ) { + const auto row = control->kv().find_row( code, chain::compute_table_id(table.to_uint64_t()), kv_key( key_words ) ); if (!row) return {}; return vector( row->value.begin(), row->value.end() ); } + // The account billed for a sysio.swap row, keyed as get_kv_row keys it; the test fails without the row. + name row_payer( name table, std::initializer_list key_words ) const { + const auto row = control->kv().find_row( "sysio.swap"_n, chain::compute_table_id(table.to_uint64_t()), + kv_key( key_words ) ); + BOOST_REQUIRE_MESSAGE( row.has_value(), "no " << table << " row" ); + return row->payer; + } // A user's deposit of `sym`, with the token contract resolved as `extend` does: // the evodexacnts row keyed by the extended symbol, scoped by the user. // Declared here, defined after `extend`. @@ -655,14 +670,6 @@ class sysio_swap_tester : public tester { BOOST_REQUIRE_EQUAL( success(), issue( "anothertoken"_n, "bob"_n, "bob"_n, asset::from_string("461168601842738.7903 VOICE"), "") ); BOOST_REQUIRE_EQUAL( success(), issue( "sysio.token"_n, "alice"_n, "alice"_n, asset::from_string("46116860184273879.03 TUSD"), "") ); } - void many_openext() { - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, system_token ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, extended_symbol{symbol::from_string("4,VOICE"), "anothertoken"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, extended_symbol{symbol::from_string("2,TUSD"), "sysio.token"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "bob"_n, "alice"_n, system_token ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "bob"_n, "alice"_n, extended_symbol{symbol::from_string("4,VOICE"), "anothertoken"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "bob"_n, "alice"_n, extended_symbol{symbol::from_string("2,TUSD"), "sysio.token"_n}) ); - } // The deposits the pools are seeded from. The system token is always // accepted; VOICE and TUSD have no pair yet, so their deposits carry the // contract's authority. @@ -867,7 +874,7 @@ vector sysio_swap_tester::total() { int64_t sysio_swap_tester::deposit_of( name user, const extended_symbol& ext ) { const auto data = get_kv_row( "sysio.swap"_n, "evodexacnts"_n, { user.to_uint64_t(), ext.contract.to_uint64_t(), ext.sym.value() } ); - BOOST_REQUIRE_MESSAGE( !data.empty(), "no deposit row for " << user << " " << ext.sym ); + if (data.empty()) return 0; const auto row = abi_ser.binary_to_variant( "evodex_account", data, fixture_abi_yield_function() ); return to_int( fc::json::to_string( row["balance"]["quantity"], @@ -893,11 +900,9 @@ struct sysio_swap_yield_tester : public sysio_swap_tester { void setup_yield_pool() { create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); many_transfer(); BOOST_REQUIRE_EQUAL( success(), shadow_mint( "alice"_n, asset( ShadowIssuance, SHD4 ) ) ); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "bob"_n, asset( BobDonationBudget, WIRE9 ), "" ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, SHADOW ) ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "sysio.liq"_n, "alice"_n, asset( YieldPoolShadow, SHD4 ), "" ) ); BOOST_REQUIRE_EQUAL( success(), inittoken( "alice"_n, SHEO6, shd( YieldPoolShadow ), extend( asset( YieldPoolWire, WIRE9 ) ), 10, name{}, 0, SHADOW ) ); @@ -936,7 +941,6 @@ struct sysio_swap_yield_tester : public sysio_swap_tester { const int64_t front_run = YieldPoolShadow / 100; BOOST_REQUIRE_LT( 0, yield_reference::sandwich_gain( YieldPoolShadow, YieldPoolWire, fee, uint32_t(fee + 1), front_run ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "bob"_n, "bob"_n, SHADOW ) ); BOOST_REQUIRE_EQUAL( success(), shadow_transfer( "alice"_n, "bob"_n, asset( front_run, SHD4 ), "" ) ); BOOST_REQUIRE_EQUAL( success(), shadow_transfer( "bob"_n, "sysio.swap"_n, asset( front_run, SHD4 ), "" ) ); // Settle what the pool is owed first, so the front-run is priced against @@ -971,7 +975,6 @@ int64_t sysio_swap_tester::settle_swap( name user, symbol_code pair, asset in, s void sysio_swap_tester::setup_pools() { create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); many_transfer(); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("168601842738.7903 EOS"), "") ); @@ -997,9 +1000,6 @@ BOOST_FIXTURE_TEST_CASE( add_rem_liquidity, sysio_swap_tester ) try { transfer( "anothertoken"_n, "bob"_n, "alice"_n, asset::from_string("500000000.0000 VOICE"), ""); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - - many_openext(); - transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("10000000.0000 EOS"), ""); seed_transfer( "anothertoken"_n, "alice"_n, asset::from_string("200000000.0000 VOICE"), ""); @@ -1098,7 +1098,6 @@ BOOST_FIXTURE_TEST_CASE( exchange_action, sysio_swap_tester ) try { create_tokens_and_issue(); transfer( "anothertoken"_n, "bob"_n, "alice"_n, asset::from_string("500000000.0000 VOICE"), ""); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - many_openext(); transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("10000000.0000 EOS"), ""); seed_transfer( "anothertoken"_n, "alice"_n, asset::from_string("200000000.0000 VOICE"), ""); inittoken( "alice"_n, EVO4, @@ -1187,7 +1186,6 @@ BOOST_FIXTURE_TEST_CASE( increasing_poolvalue, sysio_swap_tester) try { create_tokens_and_issue(); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - many_openext(); many_transfer(); transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("168601842738.7903 EOS"), ""); @@ -1298,8 +1296,6 @@ BOOST_FIXTURE_TEST_CASE( memoexchange_test, sysio_swap_tester ) try { transfer( "anothertoken"_n, "bob"_n, "alice"_n, asset::from_string("0.0001 VOICE"), ""); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - - many_openext(); many_transfer(); BOOST_REQUIRE_EQUAL( success(), @@ -1405,28 +1401,6 @@ BOOST_FIXTURE_TEST_CASE( the_other_actions, sysio_swap_tester ) try { BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2->abi, abi_evo), true); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - // add_signed_ext_balance - BOOST_REQUIRE_EQUAL( wasm_assert_msg("extended_symbol not registered for this user,\ - please run openext action or write exchange details in the memo of your transfer"), - transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("0.1000 EOS"), "") ); - - // OPENEXT - BOOST_REQUIRE_EQUAL( error("missing authority of natalia"), - push_action( "sysio.swap"_n, "bob"_n, "openext"_n, mvo() - ( "user", "bob"_n)( "payer", "natalia"_n ) - ( "ext_symbol", extended_symbol{VOICE4, "anothertoken"_n} )) - ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, - extended_symbol{EOS4, "sysio.token"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, - extended_symbol{VOICE4, "anothertoken"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, - extended_symbol{VOICE4, "anothertoken"_n}) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg( "user account does not exist"), - openext( "cat"_n, "alice"_n, extended_symbol{VOICE4, "anothertoken"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, - extended_symbol{VOICE4, "anothertoken"_n}) ); - // ONTRANSFER BOOST_REQUIRE_EQUAL( wasm_assert_msg("This transfer is not for sysio.swap"), transfer( "badtoken"_n, "alice"_n, "bob"_n, asset::from_string("1000.0000 EOS"), "")); @@ -1434,8 +1408,14 @@ BOOST_FIXTURE_TEST_CASE( the_other_actions, sysio_swap_tester ) try { transfer( "badtoken"_n, "alice"_n, "sysio.swap"_n, asset::from_string("-1000.0000 EOS"), "")); BOOST_REQUIRE_EQUAL( wasm_assert_msg("Donation not accepted"), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("1000.0000 EOS"), "deposit to: sysio.swap") ); + // The first credit creates the deposit row; a deposit for an account that does not + // exist is refused. + BOOST_REQUIRE_EQUAL( wasm_assert_msg("user account does not exist"), transfer( "sysio.token"_n, "alice"_n, + "sysio.swap"_n, asset::from_string("1000.0000 EOS"), "deposit to: cat") ); + BOOST_REQUIRE( !has_deposit( "alice"_n, extended_symbol{EOS4, "sysio.token"_n} ) ); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("1000.0000 EOS"), "") ); + BOOST_REQUIRE_EQUAL( 1000'0000, deposit_of( "alice"_n, extended_symbol{EOS4, "sysio.token"_n} ) ); // Only the system token and the first legs of existing pairs are accepted; // a first leg's seed gets in ahead of its pair only with the contract's authority. BOOST_REQUIRE_EQUAL( wasm_assert_msg("token is not a leg of any pair"), transfer( "anothertoken"_n, "alice"_n, "sysio.swap"_n, @@ -1456,6 +1436,10 @@ BOOST_FIXTURE_TEST_CASE( the_other_actions, sysio_swap_tester ) try { extend(asset::from_string("-0.0001 EOS")) )); BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient funds"), withdraw( "alice"_n, "bob"_n, extend(asset::from_string("0.0003 EOS")) )); + // A debit with no deposit row has nothing to draw on. + BOOST_REQUIRE( !has_deposit( "bob"_n, extended_symbol{EOS4, "sysio.token"_n} ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient funds"), withdraw( "bob"_n, "alice"_n, + extend(asset::from_string("0.0001 EOS")) )); // INITTOKEN BOOST_REQUIRE_EQUAL( error("missing authority of sysio.swap"), @@ -1562,7 +1546,6 @@ struct sysio_swap_unconfigured_tester : public sysio_swap_tester { BOOST_FIXTURE_TEST_CASE( nothing_works_before_setconfig, sysio_swap_unconfigured_tester ) try { create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); // Without a system token nothing can be deposited, with or without the // contract's authority, and no pair can be created, whoever its authority is. BOOST_REQUIRE_EQUAL( wasm_assert_msg("swap not configured"), @@ -1596,7 +1579,6 @@ BOOST_FIXTURE_TEST_CASE( nothing_works_before_setconfig, sysio_swap_unconfigured BOOST_FIXTURE_TEST_CASE( seed_locks_shares, sysio_swap_tester ) try { create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); many_transfer(); const int64_t minted = 100'000'000'000; // sqrt(1e10 * 1e12) const int64_t locked = 5'000'000; @@ -1649,7 +1631,6 @@ BOOST_FIXTURE_TEST_CASE( seed_locks_shares, sysio_swap_tester ) try { BOOST_FIXTURE_TEST_CASE( yield_leg_rules, sysio_swap_tester ) try { create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); many_transfer(); const extended_symbol voice{ VOICE4, "anothertoken"_n }; const extended_symbol tusd{ TUSD2, "sysio.token"_n }; @@ -1946,13 +1927,10 @@ BOOST_FIXTURE_TEST_CASE( yield_accrues_before_a_swap_is_priced, sysio_swap_yield BOOST_FIXTURE_TEST_CASE( yield_payout_route_is_exact, sysio_swap_yield_tester ) try { setup_yield_pool(); // A transfer from a shadow contract with no claim outstanding is an ordinary - // deposit, and needs the ordinary deposit row: the payout route only exists - // while a receipt does. + // deposit, which creates its deposit row: the payout route only exists while a + // receipt does. BOOST_REQUIRE_EQUAL( success(), shadow_mint( "sysio.liq"_n, asset( 1'0000, SHD4 ) ) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("extended_symbol not registered for this user," - " please run openext action or write exchange details in the memo of your transfer"), - shadow_transfer( "sysio.liq"_n, "sysio.swap"_n, asset( 1'0000, SHD4 ), "" ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "sysio.liq"_n, "alice"_n, SHADOW ) ); + BOOST_REQUIRE( !has_deposit( "sysio.liq"_n, SHADOW ) ); BOOST_REQUIRE_EQUAL( success(), shadow_transfer( "sysio.liq"_n, "sysio.swap"_n, asset( 1'0000, SHD4 ), "" ) ); BOOST_REQUIRE_EQUAL( 1'0000, deposit_of( "sysio.liq"_n, SHADOW ) ); BOOST_REQUIRE_EQUAL( YieldPoolWire, system_balance( SHEO.value ).at(1) ); @@ -1994,7 +1972,11 @@ BOOST_FIXTURE_TEST_CASE( yield_funding_fills_the_reservoir, sysio_swap_yield_tes // untouched, and the delivery goes to the reservoir. const int64_t first = 100'0000; const int64_t alice_deposit = deposit_of( "alice"_n, SHADOW ); + const int64_t alice_ram = ram_usage( "alice"_n ); BOOST_REQUIRE_EQUAL( success(), fundyield( "alice"_n, SHEO, asset( first, SHD4 ) ) ); + // The announcement is billed to sysio, not to the funder. + BOOST_REQUIRE_EQUAL( config::system_account_name, row_payer( "yieldfunds"_n, { "alice"_n.to_uint64_t() } ) ); + BOOST_REQUIRE_EQUAL( alice_ram, ram_usage( "alice"_n ) ); auto pending = pending_funding( "alice"_n ); BOOST_REQUIRE_EQUAL( SHEO6.name(), pending["pair"].as_string() ); BOOST_REQUIRE_EQUAL( asset( first, SHD4 ).to_string(), pending["quantity"]["quantity"].as_string() ); @@ -2028,14 +2010,15 @@ BOOST_FIXTURE_TEST_CASE( yield_funding_fills_the_reservoir, sysio_swap_yield_tes BOOST_REQUIRE_EQUAL( first + second + third, reservoir_of( SHEO ) ); BOOST_REQUIRE( pending_funding( "alice"_n ).is_null() ); - // The announcement's row is the funder's, not the contract's, and cancelling - // gives it back. Otherwise one abandoned announcement per account that ever - // called fundyield would sit on the contract's RAM with no way to reclaim it. + // The announcement's row is billed to sysio, not to the funder or the contract, + // and cancelling frees it. { - const int64_t swap_ram = ram_usage( "sysio.swap"_n ); - const int64_t bob_ram = ram_usage( "bob"_n ); + const int64_t sysio_ram = ram_usage( config::system_account_name ); + const int64_t swap_ram = ram_usage( "sysio.swap"_n ); + const int64_t bob_ram = ram_usage( "bob"_n ); BOOST_REQUIRE_EQUAL( success(), fundyield( "bob"_n, SHEO, asset( 5'0000, SHD4 ) ) ); - BOOST_REQUIRE_LT( bob_ram, ram_usage( "bob"_n ) ); + BOOST_REQUIRE_LT( sysio_ram, ram_usage( config::system_account_name ) ); + BOOST_REQUIRE_EQUAL( bob_ram, ram_usage( "bob"_n ) ); BOOST_REQUIRE_EQUAL( swap_ram, ram_usage( "sysio.swap"_n ) ); // While it is pending bob cannot deposit anything else... BOOST_REQUIRE_EQUAL( wasm_assert_msg("yield funding does not match the pending fundyield"), @@ -2044,6 +2027,7 @@ BOOST_FIXTURE_TEST_CASE( yield_funding_fills_the_reservoir, sysio_swap_yield_tes BOOST_REQUIRE_EQUAL( error("missing authority of bob"), cancelyield( "bob"_n, "alice"_n ) ); BOOST_REQUIRE_EQUAL( success(), cancelyield( "bob"_n ) ); BOOST_REQUIRE( pending_funding( "bob"_n ).is_null() ); + BOOST_REQUIRE_EQUAL( sysio_ram, ram_usage( config::system_account_name ) ); BOOST_REQUIRE_EQUAL( bob_ram, ram_usage( "bob"_n ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("no pending fundyield"), cancelyield( "bob"_n ) ); // Ordinary deposits work again. @@ -2528,9 +2512,7 @@ BOOST_FIXTURE_TEST_CASE( fee_authority_configuration, sysio_swap_tester ) try { BOOST_REQUIRE_EQUAL( success(), create( "sysio.token"_n, "alice"_n, asset::from_string("1000.0000 CVO") ) ); BOOST_REQUIRE_EQUAL( success(), issue( "sysio.token"_n, "alice"_n, "alice"_n, asset::from_string("1000.0000 CVO"), "" ) ); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); many_transfer(); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, extended_symbol{ CVO4, "sysio.token"_n } ) ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "sysio.token"_n, "alice"_n, asset::from_string("1000.0000 CVO"), "" ) ); // EVO adopts the configured authority (sysio); ETUSD names alice as its own. @@ -2569,7 +2551,6 @@ BOOST_FIXTURE_TEST_CASE( indextable, sysio_swap_tester ) try { abi_def abi_evo; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2->abi, abi_evo), true); abi_ser.set_abi(abi_evo, fixture_abi_yield_function()); - many_openext(); transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("10000000.0000 EOS"), ""); seed_transfer( "anothertoken"_n, "alice"_n, asset::from_string("200000000.0000 VOICE"), ""); @@ -2601,11 +2582,10 @@ BOOST_FIXTURE_TEST_CASE( indextable, sysio_swap_tester ) try { BOOST_REQUIRE( get_kv_row( "sysio.swap"_n, "evoindex"_n, { "sysio.token"_n.to_uint64_t(), EOS4.value(), "anothertoken"_n.to_uint64_t(), VOICE4.value() } ).empty() ); - // A first leg with a different precision is a different token: it has no - // deposit row. A second leg that is not exactly the system token, by + // A first leg with a different precision is a different token: alice has none + // of it on deposit. A second leg that is not exactly the system token, by // precision or by contract, is not a pair. - BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol not registered for this user,\ - please run openext action or write exchange details in the memo of your transfer"), + BOOST_REQUIRE_EQUAL(wasm_assert_msg("insufficient funds"), inittoken( "alice"_n, EOS4, extend(asset::from_string("1.00000 VOICE")), extend(asset::from_string("1.0000 EOS")), 10, name{}) ); @@ -2898,7 +2878,6 @@ BOOST_FIXTURE_TEST_CASE( precision_extremes, sysio_swap_tester ) try { // trades can be pushed to the int64 asset ceiling with no external limit. create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); const asset all_eos = asset::from_string("461168601842738.7903 EOS"); const asset all_voice = asset::from_string("461168601842738.7903 VOICE"); const asset all_tusd = asset::from_string("46116860184273879.03 TUSD"); @@ -3000,7 +2979,6 @@ BOOST_FIXTURE_TEST_CASE( minimum_fee_closes_the_free_window, sysio_swap_tester ) // 1000.0000 EOS against 1000.0000 VOICE, 10 bps. create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("2000.0000 EOS"), "") ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "anothertoken"_n, "bob"_n, asset::from_string("2000.0000 VOICE"), "deposit to: alice") ); BOOST_REQUIRE_EQUAL( success(), inittoken( "alice"_n, EVO4, @@ -3054,8 +3032,6 @@ BOOST_FIXTURE_TEST_CASE( guard_semantics_on_the_memo_path, sysio_swap_tester ) t abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); // badtoken's EOS is a different token from the system token's EOS: it is the // pair's first leg, seeded with the contract's authority. - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, extended_symbol{EOS4, "badtoken"_n}) ); - BOOST_REQUIRE_EQUAL( success(), openext( "alice"_n, "alice"_n, SYSTEM_TOKEN) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("token is not a leg of any pair"), transfer( "badtoken"_n, "alice"_n, "sysio.swap"_n, asset::from_string("2000.0000 EOS"), "") ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "badtoken"_n, "alice"_n, asset::from_string("2000.0000 EOS"), "") ); @@ -3191,7 +3167,6 @@ BOOST_FIXTURE_TEST_CASE( price_accumulators_span_extreme_prices, sysio_swap_test // needs the high limb and the average still recovers the price exactly. create_tokens_and_issue(); abi_ser.set_abi( swap_abi_def(), fixture_abi_yield_function() ); - many_openext(); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("461168601842738.7903 EOS"), "") ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "anothertoken"_n, "bob"_n, asset::from_string("461168601842738.7903 VOICE"), "deposit to: alice") ); const int64_t init_max = 1'000'000'000'000'000 - 1; @@ -3232,7 +3207,7 @@ BOOST_FIXTURE_TEST_CASE( abi_surface_is_pinned, sysio_swap_tester ) try { for (const auto& a : abi.actions) actions.insert(a.name.to_string()); const std::set expected_actions{ "accrueyield", "addliquidity", "cancelyield", "changefee", "close", "closeext", "exchange", "fundyield", - "inittoken", "open", "openext", "remliquidity", "setconfig", "setyield", "sync", "tickyield", "transfer", + "inittoken", "remliquidity", "setconfig", "setyield", "sync", "tickyield", "transfer", "withdraw" }; BOOST_REQUIRE( actions == expected_actions ); @@ -3338,7 +3313,6 @@ BOOST_FIXTURE_TEST_CASE( a_freeze_refuses_every_token_move, sysio_swap_tester ) create_tokens_and_issue(); transfer( "anothertoken"_n, "bob"_n, "alice"_n, asset::from_string("500000000.0000 VOICE"), ""); abi_ser.set_abi(swap_abi, fixture_abi_yield_function()); - many_openext(); BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, asset::from_string("10000000.0000 EOS"), "") ); BOOST_REQUIRE_EQUAL( success(), seed_transfer( "anothertoken"_n, "alice"_n, @@ -3369,7 +3343,6 @@ BOOST_FIXTURE_TEST_CASE( a_freeze_refuses_every_token_move, sysio_swap_tester ) // What moves nothing runs. BOOST_REQUIRE_EQUAL( success(), sync( EVO ) ); BOOST_REQUIRE_EQUAL( success(), changefee( EVO, 20 ) ); - BOOST_REQUIRE_EQUAL( success(), openext( "carol"_n, "alice"_n, system_token ) ); BOOST_REQUIRE_EQUAL( success(), andon::clear(*this, andon_abi_ser) ); BOOST_REQUIRE_EQUAL( success(), exchange( "alice"_n, EVO, extend(asset::from_string("1.0000 EOS")), @@ -3414,4 +3387,75 @@ BOOST_FIXTURE_TEST_CASE( a_freeze_refuses_inittoken_accrual_and_a_selling_tick, extend(asset::from_string("1.00 TUSD")), extend( asset( 1'0000, WIRE9 ) ), 10, name{}) ); } FC_LOG_AND_RETHROW() + +// Pair-token balance rows are billed to sysio: minting, sending, receiving and closing leave the +// holders' RAM unchanged. +BOOST_FIXTURE_TEST_CASE( pair_token_balances_are_billed_to_sysio, sysio_swap_tester ) try { + const name sysio_account = config::system_account_name; + setup_pools(); + BOOST_REQUIRE_EQUAL( sysio_account, balance_payer( "alice"_n, EVO ) ); + + // A send into a new row bills neither party. + int64_t alice_ram = ram_usage( "alice"_n ); + int64_t bob_ram = ram_usage( "bob"_n ); + BOOST_REQUIRE_EQUAL( success(), send_pair_token( "alice"_n, "bob"_n, asset::from_string("100.0000 EVO") ) ); + BOOST_REQUIRE_EQUAL( alice_ram, ram_usage( "alice"_n ) ); + BOOST_REQUIRE_EQUAL( bob_ram, ram_usage( "bob"_n ) ); + BOOST_REQUIRE_EQUAL( sysio_account, balance_payer( "bob"_n, EVO ) ); + + // The sender of a row it did not create keeps its RAM, and so does the recipient. + BOOST_REQUIRE_EQUAL( success(), send_pair_token( "bob"_n, "alice"_n, asset::from_string("40.0000 EVO") ) ); + BOOST_REQUIRE_EQUAL( bob_ram, ram_usage( "bob"_n ) ); + BOOST_REQUIRE_EQUAL( alice_ram, ram_usage( "alice"_n ) ); + BOOST_REQUIRE_EQUAL( sysio_account, balance_payer( "bob"_n, EVO ) ); + BOOST_REQUIRE_EQUAL( sysio_account, balance_payer( "alice"_n, EVO ) ); + + // Shares minted by addliquidity are billed to sysio too. + BOOST_REQUIRE_EQUAL( success(), addliquidity( "alice"_n, asset::from_string("50.0000 EVO"), + asset::from_string("1000000.0000 VOICE"), + asset::from_string("1000000.0000 EOS") ) ); + BOOST_REQUIRE_EQUAL( alice_ram, ram_usage( "alice"_n ) ); + BOOST_REQUIRE_EQUAL( sysio_account, balance_payer( "alice"_n, EVO ) ); + + // Emptying and closing a row leaves the holder's RAM unchanged. + BOOST_REQUIRE_EQUAL( success(), send_pair_token( "bob"_n, "alice"_n, asset::from_string("60.0000 EVO") ) ); + BOOST_REQUIRE_EQUAL( success(), push_action( "sysio.swap"_n, "bob"_n, "close"_n, mvo() + ( "owner", "bob"_n )( "symbol", EVO4 ) ) ); + BOOST_REQUIRE_EQUAL( 0, lp_balance( "bob"_n, EVO ) ); + BOOST_REQUIRE_EQUAL( bob_ram, ram_usage( "bob"_n ) ); +} FC_LOG_AND_RETHROW() + +// The configuration, the rows inittoken writes and the deposit rows are billed to +// sysio. The first credit creates a deposit row; depositing, withdrawing and closing +// leave the holders' RAM unchanged. +BOOST_FIXTURE_TEST_CASE( swap_rows_are_billed_to_sysio, sysio_swap_tester ) try { + const name sysio_account = config::system_account_name; + BOOST_REQUIRE_EQUAL( sysio_account, row_payer( "swapconfig"_n, { "swapconfig"_n.to_uint64_t() } ) ); + setup_pools(); + BOOST_REQUIRE_EQUAL( sysio_account, row_payer( "stat"_n, { EVO.value } ) ); + BOOST_REQUIRE_EQUAL( sysio_account, row_payer( "priceaccum"_n, { EVO.value } ) ); + BOOST_REQUIRE_EQUAL( sysio_account, row_payer( "evoindex"_n, + { "anothertoken"_n.to_uint64_t(), VOICE4.value(), "sysio.token"_n.to_uint64_t(), EOS4.value() } ) ); + BOOST_REQUIRE_EQUAL( sysio_account, deposit_payer( "alice"_n, SYSTEM_TOKEN ) ); + + // A deposit for another account creates that account's row; neither is billed. + // setup_pools deposited all of alice's EOS, so she withdraws some to send. + BOOST_REQUIRE_EQUAL( success(), withdraw( "alice"_n, "alice"_n, extend( asset::from_string("1.0000 EOS") ) ) ); + BOOST_REQUIRE( !has_deposit( "carol"_n, SYSTEM_TOKEN ) ); + const int64_t alice_ram = ram_usage( "alice"_n ); + const int64_t carol_ram = ram_usage( "carol"_n ); + BOOST_REQUIRE_EQUAL( success(), transfer( "sysio.token"_n, "alice"_n, "sysio.swap"_n, + asset::from_string("1.0000 EOS"), "deposit to: carol" ) ); + BOOST_REQUIRE_EQUAL( 1'0000, deposit_of( "carol"_n, SYSTEM_TOKEN ) ); + BOOST_REQUIRE_EQUAL( sysio_account, deposit_payer( "carol"_n, SYSTEM_TOKEN ) ); + BOOST_REQUIRE_EQUAL( alice_ram, ram_usage( "alice"_n ) ); + BOOST_REQUIRE_EQUAL( carol_ram, ram_usage( "carol"_n ) ); + + // Withdrawing and closing leave the holder's RAM unchanged. + BOOST_REQUIRE_EQUAL( success(), withdraw( "carol"_n, "carol"_n, extend( asset::from_string("0.5000 EOS") ) ) ); + BOOST_REQUIRE_EQUAL( success(), closeext( "carol"_n, "carol"_n, SYSTEM_TOKEN ) ); + BOOST_REQUIRE( !has_deposit( "carol"_n, SYSTEM_TOKEN ) ); + BOOST_REQUIRE_EQUAL( carol_ram, ram_usage( "carol"_n ) ); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/contracts/tests/sysio.synd_tests.cpp b/contracts/tests/sysio.synd_tests.cpp index 8078a5645c..b41839f6ea 100644 --- a/contracts/tests/sysio.synd_tests.cpp +++ b/contracts/tests/sysio.synd_tests.cpp @@ -240,8 +240,7 @@ class sysio_synd_tester : public tester { deploy(TOKENS_ACCOUNT, contracts::tokens_wasm(), contracts::tokens_abi(), tokens_abi_ser); deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi_ser); deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi_ser); - // The swap is unprivileged, as it is deployed; it bills a user's rows to the user. - deploy(SWAP_ACCOUNT, contracts::swap_wasm(), contracts::swap_abi(), swap_abi_ser, false); + deploy(SWAP_ACCOUNT, contracts::swap_wasm(), contracts::swap_abi(), swap_abi_ser); deploy(LIQ_ACCOUNT, contracts::liq_wasm(), contracts::liq_abi(), liq_abi_ser); deploy(SYND_ACCOUNT, contracts::synd_wasm(), contracts::synd_abi(), synd_abi_ser); deploy(BOND_ACCOUNT, contracts::bond_wasm(), contracts::bond_abi(), bond_abi_ser); diff --git a/contracts/tests/sysio.token_tests.cpp b/contracts/tests/sysio.token_tests.cpp index ca1ad31280..e5e4c9ef3e 100644 --- a/contracts/tests/sysio.token_tests.cpp +++ b/contracts/tests/sysio.token_tests.cpp @@ -100,16 +100,6 @@ class sysio_token_tester : public tester { ); } - action_result open( account_name owner, - const string& symbolname, - account_name ram_payer ) { - return push_action( ram_payer, "open"_n, mvo() - ( "owner", owner ) - ( "symbol", symbolname ) - ( "ram_payer", ram_payer ) - ); - } - action_result close( account_name owner, const string& symbolname ) { return push_action( owner, "close"_n, mvo() @@ -258,6 +248,12 @@ BOOST_FIXTURE_TEST_CASE( issue_tests, sysio_token_tester ) try { issue( "alice"_n, asset::from_string("-1.000 TKN"), "hola" ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "tokens can only be issued to issuer account" ), + push_action( "alice"_n, "issue"_n, mvo() + ( "to", "bob" ) + ( "quantity", asset::from_string("1.000 TKN") ) + ( "memo", "" ) ) ); + BOOST_REQUIRE_EQUAL( success(), issue( "alice"_n, asset::from_string("1.000 TKN"), "hola" ) ); @@ -399,52 +395,6 @@ BOOST_FIXTURE_TEST_CASE( transfer_rejects_bytes_after_arguments, sysio_token_tes ); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( open_tests, sysio_token_tester ) try { - - auto token = create( "alice"_n, asset::from_string("1000 CERO")); - - auto alice_balance = get_account("alice"_n, "0,CERO"); - BOOST_REQUIRE_EQUAL(true, alice_balance.is_null() ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("tokens can only be issued to issuer account"), - push_action( "alice"_n, "issue"_n, mvo() - ( "to", "bob") - ( "quantity", asset::from_string("1000 CERO") ) - ( "memo", "") ) ); - BOOST_REQUIRE_EQUAL( success(), issue( "alice"_n, asset::from_string("1000 CERO"), "issue" ) ); - - alice_balance = get_account("alice"_n, "0,CERO"); - REQUIRE_MATCHING_OBJECT( alice_balance, mvo() - ("balance", "1000 CERO") - ); - - auto bob_balance = get_account("bob"_n, "0,CERO"); - BOOST_REQUIRE_EQUAL(true, bob_balance.is_null() ); - - BOOST_REQUIRE_EQUAL( wasm_assert_msg("owner account does not exist"), - open( "nonexistent"_n, "0,CERO", "alice"_n ) ); - BOOST_REQUIRE_EQUAL( success(), - open( "bob"_n, "0,CERO", "alice"_n ) ); - - bob_balance = get_account("bob"_n, "0,CERO"); - REQUIRE_MATCHING_OBJECT( bob_balance, mvo() - ("balance", "0 CERO") - ); - - BOOST_REQUIRE_EQUAL( success(), transfer( "alice"_n, "bob"_n, asset::from_string("200 CERO"), "hola" ) ); - - bob_balance = get_account("bob"_n, "0,CERO"); - REQUIRE_MATCHING_OBJECT( bob_balance, mvo() - ("balance", "200 CERO") - ); - - BOOST_REQUIRE_EQUAL( wasm_assert_msg( "symbol does not exist" ), - open( "carol"_n, "0,INVALID", "alice"_n ) ); - - BOOST_REQUIRE_EQUAL( wasm_assert_msg( "symbol precision mismatch" ), - open( "carol"_n, "1,CERO", "alice"_n ) ); - -} FC_LOG_AND_RETHROW() - BOOST_FIXTURE_TEST_CASE( close_tests, sysio_token_tester ) try { auto token = create( "alice"_n, asset::from_string("1000 CERO")); diff --git a/contracts/tests/sysio_liq_tester.hpp b/contracts/tests/sysio_liq_tester.hpp index 783a78b0be..896b372eb5 100644 --- a/contracts/tests/sysio_liq_tester.hpp +++ b/contracts/tests/sysio_liq_tester.hpp @@ -64,7 +64,7 @@ class sysio_liq_tester : public tester { deploy(CHAINS_ACCOUNT, contracts::chains_wasm(), contracts::chains_abi(), chains_abi_ser, true); deploy(TOKENS_ACCOUNT, contracts::tokens_wasm(), contracts::tokens_abi(), tokens_abi_ser, true); deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi_ser, true); - deploy(SWAP_ACCOUNT, contracts::swap_wasm(), contracts::swap_abi(), swap_abi_ser, false); + deploy(SWAP_ACCOUNT, contracts::swap_wasm(), contracts::swap_abi(), swap_abi_ser, true); deploy(LIQ_ACCOUNT, contracts::liq_wasm(), contracts::liq_abi(), liq_abi_ser, true); // WIRE: the treasury supply on sysio, and working balances for the funders. @@ -161,9 +161,6 @@ class sysio_liq_tester : public tester { action_result transfer_shadow(name from, name to, int64_t amount, const std::string& memo = "") { return push_liq(from, "transfer"_n, mvo()("from", from)("to", to)("quantity", asset(amount, LIQSOL_SYM))("memo", memo)); } - action_result open(name owner, symbol sym, name ram_payer) { - return push_liq(ram_payer, "open"_n, mvo()("owner", owner)("symbol", sym)("ram_payer", ram_payer)); - } action_result close(name owner, symbol sym) { return push_liq(owner, "close"_n, mvo()("owner", owner)("symbol", sym)); } @@ -195,25 +192,15 @@ class sysio_liq_tester : public tester { action_result tickyield(symbol pair = POOL_SYM, name signer = "alice"_n) { return push(SWAP_ACCOUNT, swap_abi_ser, signer, "tickyield"_n, mvo()("pair_token", pair.to_symbol_code())); } - // The swap is unprivileged and bills a user's rows to the user, so these carry the - // user's `sysio.payer` beside `active`, as the swap suite's own pushes do. - /// A user's deposit row on the swap for one token, RAM billed to `payer`. - action_result openext(name user, name payer, const extended_symbol& ext_symbol) { - return push(SWAP_ACCOUNT, swap_abi_ser, payer, "openext"_n, mvo() - ("user", user)("payer", payer)("ext_symbol", ext_symbol), - sysio_system::test_support::payer_authorization(payer)); - } /// Sell `ext_asset_in` from the user's deposit into `pair` for at least `min_expected`. action_result exchange(name user, symbol pair, const extended_asset& ext_asset_in, const asset& min_expected) { return push(SWAP_ACCOUNT, swap_abi_ser, user, "exchange"_n, mvo() - ("user", user)("pair_token", pair.to_symbol_code())("ext_asset_in", ext_asset_in)("min_expected", min_expected), - sysio_system::test_support::payer_authorization(user)); + ("user", user)("pair_token", pair.to_symbol_code())("ext_asset_in", ext_asset_in)("min_expected", min_expected)); } /// Move `to_withdraw` from the user's deposit on the swap to `to`'s wallet. action_result withdraw(name user, name to, const extended_asset& to_withdraw) { return push(SWAP_ACCOUNT, swap_abi_ser, user, "withdraw"_n, mvo() - ("user", user)("to", to)("to_withdraw", to_withdraw)("memo", ""), - sysio_system::test_support::payer_authorization(user)); + ("user", user)("to", to)("to_withdraw", to_withdraw)("memo", "")); } // --- rows --- diff --git a/docs/emergency-stop-playbook.md b/docs/emergency-stop-playbook.md index 6d02b3d4c0..de00c1e230 100644 --- a/docs/emergency-stop-playbook.md +++ b/docs/emergency-stop-playbook.md @@ -108,7 +108,7 @@ Each item is a deploy or upgrade step. An item not done is a freeze that cannot | While the cord is pulled | Refused or deferred | Still runs | |---|---|---| -| `sysio.swap` | every action that moves tokens (among them `exchange`, `addliquidity`, `remliquidity`, `withdraw`, `closeext` of a funded deposit, `tickyield` and `accrueyield` with yield owed, `inittoken`) | reads, and actions that move nothing (`sync`, `changefee`, `openext`, `setyield`) | +| `sysio.swap` | every action that moves tokens (among them `exchange`, `addliquidity`, `remliquidity`, `withdraw`, `closeext` of a funded deposit, `tickyield` and `accrueyield` with yield owed, `inittoken`) | reads, and actions that move nothing (`sync`, `changefee`, `setyield`) | | `sysio.liq` | `transfer` to anything but a custody contract (`sysio.bond`, `sysio.synd`, `sysio.opreg`), so holders cannot move shadow LIQ between themselves; `claim` by anyone but a custody contract; `queueyield` | `mint`; transfers into a custody contract; `claim` by a custody contract (it moves WIRE into custody) | | `sysio.bond` | `claim` | `request`, `addbounty`, `accept`, `hold`, `approve`, `rslvvalid`, `rslvinvalid`, `sweepyield`, `prune` | | `sysio.kicker` | `kick` returns without paying and without writing: `last_kick` stays where it was, so accrual keeps running and the first paying `kick` after the clear covers it, within the 72-hour catch-up | `setconfig`, `addpool`, `setpool`, `rmpool` | diff --git a/programs/clio/main.cpp b/programs/clio/main.cpp index 41be4975c4..8e7fc2013d 100644 --- a/programs/clio/main.cpp +++ b/programs/clio/main.cpp @@ -825,17 +825,6 @@ fc::variant regproducer_variant(const account_name& producer, const public_key_t ; } -chain::action create_open(const string& contract, const name& owner, symbol sym, const name& ram_payer) { - auto open_ = fc::mutable_variant_object - ("owner", owner) - ("symbol", sym) - ("ram_payer", ram_payer); - return action { - get_account_permissions(tx_permission, {ram_payer, sysio::chain::config::active_name}), - name(contract), "open"_n, variant_to_bin( name(contract), "open"_n, open_ ) - }; -} - chain::action create_transfer(const string& contract, const name& sender, const name& recipient, asset amount, const string& memo ) { auto transfer = fc::mutable_variant_object @@ -3053,7 +3042,6 @@ int main( int argc, char** argv ) { string recipient; string amount; string memo; - bool pay_ram = false; auto transfer_cmd = app.add_subcommand("transfer", localized("Transfer tokens from account to account")); transfer_cmd->add_option("sender", sender, localized("The account sending tokens"))->required(); @@ -3061,18 +3049,12 @@ int main( int argc, char** argv ) { transfer_cmd->add_option("amount", amount, localized("The amount of tokens to send"))->required(); transfer_cmd->add_option("memo", memo, localized("The memo for the transfer")); transfer_cmd->add_option("--contract,-c", con, localized("The contract that controls the token, defaults to sysio.token for asset amounts")); - transfer_cmd->add_flag("--pay-ram-to-open", pay_ram, localized("Pay RAM to open recipient's token balance row")); add_standard_transaction_options_plus_signing(transfer_cmd, "sender@active"); transfer_cmd->callback([&] { auto transfer_amount = to_asset(name(con), amount); auto transfer = create_transfer(con, name(sender), name(recipient), transfer_amount, memo); - if (!pay_ram) { - send_actions( { transfer }, signing_keys_opt.get_keys()); - } else { - auto open_ = create_open(con, name(recipient), transfer_amount.get_symbol(), name(sender)); - send_actions( { open_, transfer }, signing_keys_opt.get_keys()); - } + send_actions( { transfer }, signing_keys_opt.get_keys()); }); // Net subcommand diff --git a/unittests/kv_token_tests.cpp b/unittests/kv_token_tests.cpp index c3913df072..f2e8831b7f 100644 --- a/unittests/kv_token_tests.cpp +++ b/unittests/kv_token_tests.cpp @@ -41,11 +41,6 @@ struct kv_token_tester : validating_tester { fc::mutable_variant_object()("quantity", quantity)("memo", memo)); } - void open(name owner, symbol sym, name payer) { - push_action("sysio.token"_n, "open"_n, payer, - fc::mutable_variant_object()("owner", owner)("symbol", sym)("ram_payer", payer)); - } - void close(name owner, symbol sym) { push_action("sysio.token"_n, "close"_n, owner, fc::mutable_variant_object()("owner", owner)("symbol", sym)); @@ -96,21 +91,20 @@ BOOST_FIXTURE_TEST_CASE(kv_token_retire, kv_token_tester) { BOOST_TEST_MESSAGE("Token retire succeeded with KV-backed sysio.token"); } -BOOST_FIXTURE_TEST_CASE(kv_token_open_close, kv_token_tester) { +BOOST_FIXTURE_TEST_CASE(kv_token_close, kv_token_tester) { auto sym = symbol(SY(4, WIRE)); create_token("alice"_n, asset(1000000000, sym)); + issue("alice"_n, asset(10000, sym)); produce_block(); - // Open account for bob (alice pays) - open("bob"_n, sym, "alice"_n); + // Empty alice's balance, then close the zero-balance row + transfer("alice"_n, "bob"_n, asset(10000, sym)); produce_block(); - - // Close bob's zero-balance account - close("bob"_n, sym); + close("alice"_n, sym); produce_block(); - BOOST_TEST_MESSAGE("Token open/close succeeded with KV-backed sysio.token"); + BOOST_TEST_MESSAGE("Token close succeeded with KV-backed sysio.token"); } BOOST_FIXTURE_TEST_CASE(kv_token_overdrawn_check, kv_token_tester) { diff --git a/unittests/sysio_system_tester.hpp b/unittests/sysio_system_tester.hpp index c11fc7d460..07690af327 100644 --- a/unittests/sysio_system_tester.hpp +++ b/unittests/sysio_system_tester.hpp @@ -76,16 +76,6 @@ class sysio_system_tester : public validating_tester { get_balance(name("sysio")) + get_balance(name("sysio.ramfee")) + get_balance(name("sysio.stake")) + get_balance(name("sysio.ram")) ); } - action_result open( account_name owner, - const string& symbolname, - account_name ram_payer ) { - return push_action( ram_payer, "open"_n, mvo() - ( "owner", owner ) - ( "symbol", symbolname ) - ( "ram_payer", ram_payer ) - ); - } - transaction_trace_ptr setup_producer_accounts( const std::vector& accounts ) { account_name creator(config::system_account_name); signed_transaction trx;