diff --git a/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp b/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp index e699091444..dbafd938f3 100644 --- a/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp +++ b/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp @@ -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 diff --git a/contracts/sysio.bond/src/sysio.bond.cpp b/contracts/sysio.bond/src/sysio.bond.cpp index 9cd92abfb1..e26a92289f 100644 --- a/contracts/sysio.bond/src/sysio.bond.cpp +++ b/contracts/sysio.bond/src/sysio.bond.cpp @@ -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. @@ -139,7 +141,7 @@ constexpr uint64_t max_asset_amount = static_cast(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; } @@ -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) { diff --git a/contracts/sysio.bond/sysio.bond.wasm b/contracts/sysio.bond/sysio.bond.wasm index 3e8d2edf60..2b97806ad9 100755 Binary files a/contracts/sysio.bond/sysio.bond.wasm and b/contracts/sysio.bond/sysio.bond.wasm differ diff --git a/contracts/tests/sysio.bond_tests.cpp b/contracts/tests/sysio.bond_tests.cpp index 6fd6ed0adb..5c07f1bc36 100644 --- a/contracts/tests/sysio.bond_tests.cpp +++ b/contracts/tests/sysio.bond_tests.cpp @@ -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 { // 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); @@ -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 { @@ -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, @@ -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. diff --git a/docs/sysio-bond-integration.md b/docs/sysio-bond-integration.md index 43dc8e04d3..19133bd941 100644 --- a/docs/sysio-bond-integration.md +++ b/docs/sysio-bond-integration.md @@ -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