Skip to content
Merged
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
2 changes: 1 addition & 1 deletion contracts/sysio.liq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
5 changes: 2 additions & 3 deletions contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
51 changes: 15 additions & 36 deletions contracts/sysio.liq/src/sysio.liq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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<uint64_t>(quantity.amount)), "supply exceeds the asset range");
adjust_account(holder, quantity, ram_payer);
adjust_account(holder, quantity);
}

// ---------------------------------------------------------------------------
Expand All @@ -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<int64_t>(amount), st.supply.symbol }, ram_payer);
adjust_account(to, asset{ static_cast<int64_t>(amount), st.supply.symbol });
}

void liq::burn(sysio::slug_name token_code, uint64_t amount) {
Expand All @@ -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<int64_t>(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;
Expand Down Expand Up @@ -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<uint64_t>(quantity.amount)), "supply exceeds the asset range");
adjust_account(get_self(), quantity, ram_payer);
adjust_account(get_self(), quantity);

action(std::vector<permission_level>{ 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();
Expand All @@ -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) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -337,30 +322,24 @@ void liq::regliqpool(sysio::slug_name chain_code, sysio::slug_name token_code, s
const asset shadow{ static_cast<int64_t>(initial_chain_amount), sym };
const asset wire { static_cast<int64_t>(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<permission_level> 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<permission_level>{ active_of(SYSTEM_ACCOUNT), active_of(SWAP_ACCOUNT) },
get_self(), "transfer"_n,
std::make_tuple(SYSTEM_ACCOUNT, SWAP_ACCOUNT, shadow, std::string{})).send();
// 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<permission_level>{ payer_of(SYSTEM_ACCOUNT), active_of(SYSTEM_ACCOUNT), active_of(SWAP_ACCOUNT) },
action(std::vector<permission_level>{ 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,
Expand Down Expand Up @@ -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) {
Expand Down
23 changes: 0 additions & 23 deletions contracts/sysio.liq/sysio.liq.abi
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down Expand Up @@ -383,11 +365,6 @@
"type": "mintyield",
"ricardian_contract": ""
},
{
"name": "open",
"type": "open",
"ricardian_contract": ""
},
{
"name": "queueyield",
"type": "queueyield",
Expand Down
Binary file modified contracts/sysio.liq/sysio.liq.wasm
Binary file not shown.
30 changes: 2 additions & 28 deletions contracts/sysio.swap/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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"}],
Expand Down
2 changes: 1 addition & 1 deletion contracts/sysio.swap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading
Loading