Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
* transfer into this contract.
*
* Privileged: an action signed by an account pulls that account's tokens by an inline `transfer`
* under its `active` authority. The issuer pays the RAM of its `requests` and `escrows` rows and an
* underwriter that of its `bonds` row -- unless that account is itself privileged: a system contract
* (`sysio.synd` issuing for an envelope) has no RAM quota of its own, so its rows bill the `sysio` RAM
* pool instead. Later writes by other signers keep the row's payer. The contract's own singletons and
* yield pools bill the `sysio` RAM pool.
* under its `active` authority. The issuer pays the RAM of its `requests` and `escrows` rows --
* unless it is itself privileged: a system contract (`sysio.synd` issuing for an envelope) has no RAM
* quota of its own, so its rows bill the `sysio` RAM pool instead. Later writes by other signers keep
* the row's payer. Every `bonds` row, the contract's own singletons and the yield pools bill the
* `sysio` RAM pool, so an underwriter needs no RAM.
*/

#include <sysio/sysio.hpp>
Expand Down
12 changes: 7 additions & 5 deletions contracts/sysio.bond/src/sysio.bond.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ namespace custody = opp::shadow::custody;

// The contract's own rows -- `bondconfig`, `bondcounters`, `yieldpool` -- bill the sysio RAM pool,
// not this contract account (privileged-contract model, as sysio.opreg uses): the account stays
// finite at code+abi size. A request's rows bill the account that created them (`payer_for`): the
// issuer its `requests` and `escrows` rows, an underwriter its `bonds` row. Later writes by any signer
// keep the row's payer (`same_payer`), which a privileged contract may do.
// finite at code+abi size. So does a `bonds` row: it exists only against locked bond tokens, at most
// one per underwriter per request, so an underwriter needs no RAM of its own. An issuer's `requests`
// and `escrows` rows bill the issuer (`payer_for`): anyone may open a request, with a statement of
// its own size. Later writes by any signer keep the row's payer (`same_payer`), which a privileged
// contract may do.
constexpr name ram_payer = bond::SYSTEM_ACCOUNT;

/// Permission this contract sends its own `sysio.liq::addyield` under.
Expand Down Expand Up @@ -139,7 +141,7 @@ constexpr uint64_t max_asset_amount = static_cast<uint64_t>(asset::max_amount);
/// A privileged account is a system contract (`sysio.synd` issuing requests for its envelopes), and a
/// system contract has no RAM quota of its own -- it lives on what `sysio.roa` gifts it for its code
/// and abi -- so its rows bill the `sysio` RAM pool, as every system contract's own rows do. The ONE
/// place that decides the payer of a request, escrow or bond row.
/// place that decides the payer of a request or escrow row.
name payer_for(name account) {
return is_privileged(account) ? ram_payer : account;
}
Expand Down Expand Up @@ -442,7 +444,7 @@ void bond::accept(name underwriter, uint64_t request_id, uint64_t amount) {
bond_row row = bonds.try_get(key).value_or(
bond_row{.request_id = request_id, .underwriter = underwriter, .amount = 0});
credit_holding(row.yield, row.amount, bonded_amount, req->token_code, *custody);
bonds.set(payer_for(underwriter), key, row);
bonds.set(ram_payer, key, row);

const bool fully_bonded = bonded_amount == remainder;
requests.modify(same_payer, request_key{request_id}, [&](request_row& r) {
Expand Down
Binary file modified contracts/sysio.bond/sysio.bond.wasm
Binary file not shown.
39 changes: 31 additions & 8 deletions contracts/tests/sysio.bond_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,8 +855,9 @@ BOOST_FIXTURE_TEST_CASE(accept_and_request_checkpoint_the_live_index, sysio_bond
BOOST_REQUIRE_EQUAL(0u, wire_bond.yield.owed_wire);
} FC_LOG_AND_RETHROW()

// A bond row bills its underwriter; the request row the accept updates stays the issuer's.
BOOST_FIXTURE_TEST_CASE(accept_bills_the_underwriter, sysio_bond_tester) try {
// A bond row bills the sysio RAM pool, never its underwriter; the request row the accept updates
// stays the issuer's.
BOOST_FIXTURE_TEST_CASE(accept_bills_sysio, sysio_bond_tester) try {
Comment on lines -858 to +860

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love removing the general test, since it should still be applicable. Could you instead create a new test for the privileged account case and keep the old test passing as it was (with the non-privileged user paying)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change applies to every underwriter, not only privileged ones. accept takes any underwriter (require_auth only), and bonds.set now uses ram_payer for every row. Before, only a privileged underwriter's row billed sysio, through payer_for. The renamed test keeps the old test's case, a non-privileged underwriter (alice), with the new expected payer, so the old assertion can't pass without reverting the change. I've added a_privileged_underwriter_bills_sysio for a privileged underwriter and reworded the description to say every bonds row bills sysio.

// The bounty opens sysio.bond's own LIQSOL holder row on sysio.liq first, which sysio.liq bills.
BOOST_REQUIRE_EQUAL(success(), request("issuer"_n, "oppenvelope"_n, bytes(32, 'a'), LIQSOL, 10 * UNIT, 1 * UNIT));
const auto sysio_ram = ram_usage(SYSIO_ACCOUNT);
Expand All @@ -866,11 +867,33 @@ BOOST_FIXTURE_TEST_CASE(accept_bills_the_underwriter, sysio_bond_tester) try {
BOOST_REQUIRE_EQUAL(success(), accept("alice"_n, 1, 10 * UNIT));
std::string bond_key = request_key_of(1);
append_key_word(bond_key, "alice"_n.to_uint64_t());
BOOST_REQUIRE_EQUAL("alice"_n, payer_of("bonds"_n, bond_key));
BOOST_REQUIRE_EQUAL(SYSIO_ACCOUNT, payer_of("bonds"_n, bond_key));
BOOST_REQUIRE_EQUAL("issuer"_n, payer_of("requests"_n, request_key_of(1)));
BOOST_REQUIRE_GT(ram_usage("alice"_n), alice_ram);
BOOST_REQUIRE_EQUAL(alice_ram, ram_usage("alice"_n));
BOOST_REQUIRE_EQUAL(issuer_ram, ram_usage("issuer"_n));
BOOST_REQUIRE_EQUAL(sysio_ram, ram_usage(SYSIO_ACCOUNT));
BOOST_REQUIRE_GT(ram_usage(SYSIO_ACCOUNT), sysio_ram);
} FC_LOG_AND_RETHROW()

// A privileged underwriter -- a system contract -- has its bond row billed to the sysio RAM pool as
// well: every underwriter's bond row bills the pool, privileged or not.
BOOST_FIXTURE_TEST_CASE(a_privileged_underwriter_bills_sysio, sysio_bond_tester) try {
// sysio.tokens, the token registry, is a privileged system contract of the fixture.
constexpr auto system_contract = TOKENS_ACCOUNT;
BOOST_REQUIRE_EQUAL(success(), transfer_wire(SYSIO_ACCOUNT, system_contract, 100 * UNIT));
// The bounty opens sysio.bond's own WIRE balance row first, so the accept below writes only the bond row.
BOOST_REQUIRE_EQUAL(success(), request("issuer"_n, "oppenvelope"_n, bytes(32, 'a'), WIRE, 10 * UNIT, 1 * UNIT));
const auto sysio_ram = ram_usage(SYSIO_ACCOUNT);
const auto issuer_ram = ram_usage("issuer"_n);
const auto underwriter_ram = ram_usage(system_contract);

BOOST_REQUIRE_EQUAL(success(), accept(system_contract, 1, 10 * UNIT));
std::string bond_key = request_key_of(1);
append_key_word(bond_key, system_contract.to_uint64_t());
BOOST_REQUIRE_EQUAL(SYSIO_ACCOUNT, payer_of("bonds"_n, bond_key));
BOOST_REQUIRE_EQUAL("issuer"_n, payer_of("requests"_n, request_key_of(1)));
BOOST_REQUIRE_EQUAL(underwriter_ram, ram_usage(system_contract));
BOOST_REQUIRE_EQUAL(issuer_ram, ram_usage("issuer"_n));
BOOST_REQUIRE_GT(ram_usage(SYSIO_ACCOUNT), sysio_ram);
} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(accept_in_wire_pulls_wire, sysio_bond_tester) try {
Expand Down Expand Up @@ -1903,7 +1926,7 @@ BOOST_FIXTURE_TEST_CASE(durable_outcome_requires_issuer_acknowledgement, sysio_b
} FC_LOG_AND_RETHROW()

// A request still owed a claim keeps its row, but the bond rows already paid out go: one unclaimed
// payout holds no other underwriter's RAM, and the unpaid rows claim exactly as before.
// payout holds no other underwriter's row in RAM, and the unpaid rows claim exactly as before.
BOOST_FIXTURE_TEST_CASE(prune_erases_the_paid_bonds_of_a_request_it_keeps, sysio_bond_tester) try {
constexpr uint32_t window_sec = 3600;
BOOST_REQUIRE_EQUAL(success(), request("issuer"_n, "oppenvelope"_n, bytes(32, 'a'), LIQSOL, 10 * UNIT, 1 * UNIT,
Expand All @@ -1915,12 +1938,12 @@ BOOST_FIXTURE_TEST_CASE(prune_erases_the_paid_bonds_of_a_request_it_keeps, sysio
BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n));

// Within the retention period: alice's paid row goes at once, bob's stays, and so does the request.
const auto alice_ram = ram_usage("alice"_n);
const auto sysio_ram = ram_usage(SYSIO_ACCOUNT);
BOOST_REQUIRE_EQUAL(success(), prune(0, 10));
BOOST_REQUIRE_EQUAL(1u, bond_rows_of(1).size());
BOOST_REQUIRE_EQUAL(6 * UNIT, bond_amount_of(1, "bob"_n));
BOOST_REQUIRE_EQUAL(STATE_APPROVED, request_state_of(1));
BOOST_REQUIRE_LT(ram_usage("alice"_n), alice_ram);
BOOST_REQUIRE_LT(ram_usage(SYSIO_ACCOUNT), sysio_ram);
BOOST_REQUIRE_EQUAL(wasm_assert_msg(std::string(nothing_msg)), claim(1, "alice"_n));

// Past it, bob's unclaimed bond still keeps the request.
Expand Down
11 changes: 6 additions & 5 deletions docs/sysio-bond-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ The id the request will take is `bondcounters.next_request_id`, read before send
from one action take consecutive ids from there. An issuer that must not depend on that ordering finds
its request afterwards through the `bystatement` key instead, as the syndication contract's queue does.

The issuer pays the RAM of its request's `requests` and `escrows` rows, and each underwriter the RAM
of its `bonds` row. A privileged issuer or underwriter is the exception: a system contract (the
syndication contract, issuing for its envelopes) has no RAM quota of its own, so the rows created on
its behalf bill the `sysio` RAM pool instead. Later writes by other signers (a ruling, a claim) keep
those payers. `prune` releases the RAM.
The issuer pays the RAM of its request's `requests` and `escrows` rows. A privileged issuer is the
exception: a system contract (the syndication contract, issuing for its envelopes) has no RAM quota
of its own, so the rows created on its behalf bill the `sysio` RAM pool instead. A `bonds` row always
bills the `sysio` RAM pool, so an underwriter needs no RAM: the row exists only against locked bond
tokens, at most one per underwriter per request. Later writes by other signers (a ruling, a claim)
keep those payers. `prune` releases the RAM.

## The increment

Expand Down
Loading