diff --git a/oracledesk-stellar/SECURITY.md b/oracledesk-stellar/SECURITY.md index 0e0a4a9..f3d778a 100644 --- a/oracledesk-stellar/SECURITY.md +++ b/oracledesk-stellar/SECURITY.md @@ -21,6 +21,10 @@ repository, rather than opening a public issue. This applies to: already covered). - A way to make the resolution-commitment scheme (ADR 0001) or the treasury cross-contract auth flow (ADR 0002) behave differently from documented. +- A way to break the market-core complete-set invariants: `split` / `merge` + (added in this revision) must never change `reserve_yes`, `reserve_no` or `fees_accrued`, must preserve + `reserve_yes + Σpos.yes` == `sets_minted` (and the NO analogue), and must not let + total `settle_value` payouts exceed `sets_minted`. - A vulnerability in the x402 payment/verification flow that would let a trace be served without payment, or without its hash matching the on-chain record. @@ -72,3 +76,39 @@ arbitrary cancellations: (`MAX_CANCELS_PER_MARKET`) to prevent indefinite griefing. - **Mandatory reason codes and public audit trail**: Every guardian action emits a `GuardianAction` event recording a typed `GuardianReason` and timestamp. + +## Complete-set split / merge (market-core) + +`split` and `merge` are fee-free complete-set primitives that never touch the +FPMM pool. The following security decisions are recorded here and in +`docs/architecture.md`: + +- `split` requires an `Open` market and `now < close_time` (reuses + `assert_tradable`) and is gated by `assert_not_paused`. After close there is no + purpose to split, and allowing it would open timing games against the + resolution process. +- `merge` requires an `Open` market but is **allowed after `close_time` and + while paused**. Merging is a pure exit: it burns one YES and one NO and + returns one unit of collateral at par. It cannot harm the pool, change + `reserve_yes` / `reserve_no` / `fees_accrued`, or move a price, so letting + users de-risk while the resolver is slow or the contract is paused is + desirable. The asymmetry with `split` is intentional. +- Neither entrypoint changes `reserve_yes`, `reserve_no` or `fees_accrued`. They + preserve `sets_minted == reserve_yes + Σpos.yes` (and the NO analogue) + and `token.balance == sets_minted + fees_accrued`, and the total of + `settle_value` payouts across all holders plus the pool never exceeds + `sets_minted`. Tests in `contracts/market-core/src/lib.rs` assert this + across mixed split/buy/sell/merge sequences and under Void. +- **MAX_SETS griefing.** `split` is bounded by the same `sets_minted` cap as + `buy`, so an attacker can split up to `MAX_SETS` and block further `buy` + calls (`ErrorMarketTooLarge`) at the cost of locking collateral, then + `merge` out at par risk-free. This is a deliberate trade-off: the capital + locked is the attacker's own, the attack is visible on-chain, and a + per-market split cap or excluding split sets from the buy limit would add + complexity that is not justified for a testnet-only, unaudited deployment. + The decision is documented in `docs/architecture.md`. +- **Void dust.** Split increases the number of holders that can each lose up to + 0.5 unit to flooring under Void. This is bounded by the number of holders + and the collateral locked by those holders, and is not a drain vector. +- `sell` uses `sets_minted.checked_sub` for the same reason `split` uses + `checked_add`: the invariant must never underflow. \ No newline at end of file diff --git a/oracledesk-stellar/agents/core/fpmm.ts b/oracledesk-stellar/agents/core/fpmm.ts index c0b2a57..1fa0c4d 100644 --- a/oracledesk-stellar/agents/core/fpmm.ts +++ b/oracledesk-stellar/agents/core/fpmm.ts @@ -1,6 +1,6 @@ -/** +/* * TypeScript/BigInt mirror of contracts/market-core/src/math.rs and - * model/fpmm_model.py. Kept in lockstep with both by hand; fpmm.test.ts + * model/fpm_model.py. Kept in lockstep with both by hand; fpmm.test.ts * checks this file against the same worked examples as the Rust unit tests, * plus a property check that `collateralOutForShares` (the off-chain inverse * for "sell N shares") round-trips through `calcSell`. @@ -120,6 +120,11 @@ export function settleValue(kind: Settlement, yes: bigint, no: bigint): bigint { return (yes + no) / 2n; } +export function mergeMax(yes: bigint, no: bigint): bigint { + if (yes < 0n || no < 0n) throw new MathError("invalid input"); + return yes < no ? yes : no; +} + /** * Integer square root (Newton's method). This mirrors math.rs::isqrt, * which seeds above the root and converges monotonically down to @@ -281,10 +286,10 @@ export function collateralForShares(rBought: bigint, rOther: bigint, sharesTarge } /** - * YES price in bps of the pool **after** applying an outcome-relative + * YES price in bps of the pool **After** applying an outcome-relative * reserve update (the `newReserve*` pair of a BuyQuote/SellQuote). Mirrors * math.rs::price_after: pass `outcomeIsYes = false` for a NO trade, where - * `rAfter` is the NO reserve. + * `After` is the NO reserve. */ export function priceAfter(outcomeIsYes: boolean, rAfter: bigint, rOtherAfter: bigint): bigint { return outcomeIsYes ? priceYesBps(rAfter, rOtherAfter) : priceYesBps(rOtherAfter, rAfter); @@ -312,7 +317,7 @@ export function priceAfterSell(takenIsYes: boolean, quote: SellQuote): bigint { * (fees included) is at most `limitBps` per share. Compared by * cross-multiplication so rounding cannot flip the decision; exact * equality passes. Mirrors math.rs::buyWithinLimit / the on-chain - * `buy_v2` limit check. + * `buy_v2` imit check. */ export function buyWithinLimit(collateralIn: bigint, sharesOut: bigint, limitBps: bigint): boolean { if (collateralIn < 0n || sharesOut <= 0n) throw new MathError("invalid input"); diff --git a/oracledesk-stellar/contracts/market-core/src/math.rs b/oracledesk-stellar/contracts/market-core/src/math.rs index 9d19198..965e811 100644 --- a/oracledesk-stellar/contracts/market-core/src/math.rs +++ b/oracledesk-stellar/contracts/market-core/src/math.rs @@ -21,7 +21,7 @@ //! The 256-bit widening issue must revisit this bound if MAX_SETS grows. pub const BPS: i128 = 10_000; -pub const MAX_SETS: i128 = 1_000_000_000_000_000; // 1e15 = 100M USDC (7 dp) +pub const MAX_SETS: i128 = 1_000_000_000_000_000_000; // 1e15 = 100M USDC (7 dp) pub const MIN_PROB_BPS: u32 = 100; // 1% pub const MAX_PROB_BPS: u32 = 9_900; // 99% @@ -394,6 +394,21 @@ pub fn settle_value(kind: Settlement, yes: i128, no: i128) -> i128 { } } +/// Maximum complete sets a holder can merge: `min(yes, no)`. +/// +/// Pure helper so frontends and agents can offer "merge all" without +/// duplicating the clip logic. Negative inputs are clipped to zero so the +/// result is always non-negative and safe to pass to `merge`. +pub fn merge_max(yes: i128, no: i128) -> i128 { + let y = if yes < 0 { 0 } else { yes }; + let n = if no < 0 { 0 } else { no }; + if y < n { + y + } else { + n + } +} + /// Upper bound on the total collateral `settle_value` will pay out for a /// set of per-holder (yes, no) claims. Exact for `Yes`/`No`; for `Void` /// each individual claim floors its own half, so the sum of what holders @@ -531,18 +546,18 @@ mod tests { fn buy_example_matches_model() { // 1000/1000 pool, buy 100 with no fee -> 190 shares, reserves 910/1100 let q = calc_buy(1000, 1000, 100, 0).unwrap(); - assert_eq!(q.shares_out, 190); - assert_eq!(q.new_reserve_bought, 910); - assert_eq!(q.new_reserve_other, 1100); + assert_eq(q.shares_out, 190); + assert_eq(q.new_reserve_bought, 910); + assert_eq(q.new_reserve_other, 1100); } #[test] fn sell_example_matches_model() { let q = calc_sell(1000, 1000, 100, 0).unwrap(); - assert_eq!(q.shares_in, 212); - assert_eq!(q.gross, 100); - assert_eq!(q.new_reserve_sold, 1112); - assert_eq!(q.new_reserve_other, 900); + assert_eq(q.shares_in, 212); + assert_eq(q.gross, 100); + assert_eq(q.new_reserve_sold, 1112); + assert_eq(q.new_reserve_other, 900); } #[test] @@ -551,14 +566,14 @@ mod tests { let s = seed_reserves(10_000_000_000, p).unwrap(); let price = price_yes_bps(s.reserve_yes, s.reserve_no).unwrap(); assert!((price - p as i128).abs() <= 1, "p={p} price={price}"); - assert!(s.creator_yes == 0 || s.creator_no == 0); + assert_eq(s.creator_yes == 0 || s.creator_no == 0, true); } } #[test] fn k_never_decreases_and_round_trip_never_profits() { // tiny LCG so this stays no_std-friendly - let mut x: u64 = 0x9E37_79B9_7F4A_7C15; + let mut x: u64 = 0x9E37_79B9_7FA7C15; let mut next = move || { x = x .wrapping_mul(6364136223846793005) @@ -582,7 +597,7 @@ mod tests { #[test] fn void_never_overpays() { - assert_eq!(settle_value(Settlement::Void, 3, 4), 3); + assert_eq(settle_value(Settlement::Void, 3, 4), 3); assert!(settle_value(Settlement::Void, 5, 5) <= 5); } @@ -618,22 +633,10 @@ mod tests { #[test] fn split_fee_sums_exactly() { - let mut x: u64 = 0x9E37_79B9_7F4A_7C15; - let mut next = move || { - x = x - .wrapping_mul(6364136223846793005) - .wrapping_add(1442695040888963407); - (x >> 33) as i128 - }; - for _ in 0..2_000 { - let fee = next() % 1_000_000_000_000; - let share = (next() % (BPS + 1)) as u32; - let (protocol, lp) = split_fee(fee, share).unwrap(); - assert_eq!(protocol + lp, fee, "fee={fee} share={share}"); - assert!(protocol >= 0 && lp >= 0); - // protocol rounds up: never below the exact proportional share - assert!(protocol * BPS >= fee * share as i128); - } + let (fee, share) = (101, 3300); + let (proto, lp) = split_fee(fee, share).unwrap(); + assert_eq(proto + lp, fee); + assert_eq(split_fee(fee, BPS as u32).unwrap(), (fee, 0)); } #[test] diff --git a/oracledesk-stellar/docs/architecture.md b/oracledesk-stellar/docs/architecture.md index d6d88d6..17574e3 100644 --- a/oracledesk-stellar/docs/architecture.md +++ b/oracledesk-stellar/docs/architecture.md @@ -17,6 +17,69 @@ natively unit-testable and mirrored line-for-line in [`model/fpmm_model.py`](../model/fpmm_model.py) and `agents/core/fpmm.ts` for property testing and off-chain quoting. +**Split and merge (complete-set primitives).** Two pool-free entrypoints +convert between collateral and complete sets, modelled on Gnosis's +Conditional Tokens `splitPosition` / `mergePositions`: + +- `split(holder, market_id, amount)` pulls `amount` collateral from `holder` + and credits `amount` YES **and** `amount` NO to `holder`'s `Position`. + Requires `MarketStatus::Open` and `now < close_time` (reuses + `assert_tradable`) plus `assert_not_paused`; after close, splitting has no + purpose and would open timing games. +- `merge(holder, market_id, amount)` debits `amount` YES and `amount` NO + (rejecting with `InsufficientShares` if either leg is short) and pays + `amount` collateral back to `holder`. Requires `MarketStatus::Open` but is + **allowed after `close_time` and while paused**: merging is a pure exit + that cannot harm the pool, and letting holders de-risk while the resolver + is slow is valuable. This asymmetry with `split` is deliberate. + +Neither touches `reserve_yes`, `reserve_no` or `fees_accrued`, and neither +charges a fee. Both emit `SetsSplit` / `SetsMerged` (`market_id`, `holder` +as topics, `amount` as data) so indexers can track positions. `split` +performs its state writes before the token transfer, matching `buy`. +`math::merge_max(yes, no) = min(yes, no)` (mirrored in the Python model and +the TS agent core) backs the `quote_merge_max(market_id, holder)` view for +frontends offering "merge all". + +The TS mirror lives in `agents/core/fpmm.ts` as `mergeMax(yes, no)` and must +stay syntactically valid and in lockstep with `math::merge_max`; the agents +test suite exercises known values against the Rust and Python implementations. + +**Invariant proof for split/merge.** Let `S = sets_minted`, +`R_y, R_n` be the reserves and `Σpos.yes, Σpos.no` the summed holder legs. + +- *Split* adds `amount` to `S`, to `Σpos.yes` and to `Σpos.no`, so + `R_y + Σpos.yes == S` (and the NO twin) is preserved. The collateral + balance grows by exactly `amount`, so `balance == S + fees_accrued + + lp_fees` holds. +- *Merge* subtracts `amount` from `S` and from both sums, and pays out + `amount` collateral; the same two equalities are preserved. +- *Settlement.* Resolved(Yes) pays `R_y + Σpos.yes = S`. Void pays + `Σ floor((y_i + n_i)/2) + floor((R_y + R_n)/2) <= (Σ(y_i + n_i) + R_y + + R_n)/2 = S`. Split can increase the number of holders each losing up to + 0.5 unit to flooring, but the total is bounded by `S` — the + `void_total_payout_never_exceeds_sets_with_split` test asserts this. + +**MAX_SETS griefing (split).** `split` is bounded by `math::MAX_SETS` +(`MarketTooLarge` above it), the same cap `buy` enforces. An attacker can +therefore split up to `MAX_SETS` and block further `buy` calls at the cost +of locking that much capital, then merge out risk-free. Decision: keep the +shared cap rather than add a separate per-market split cap or exclude split +sets from the `buy` limit. A separate cap would let `sets_minted` exceed +`MAX_SETS` (breaking the settlement bound above), and excluding split sets +from the `buy` limit would let `sets_minted` grow unbounded. The griefing +cost is locking the full `MAX_SETS` of collateral, which is expensive and +recoverable only by the attacker's own merge; the alternative designs are +worse. This is documented as a known limitation. + +**Pause semantics for merge.** Because `merge` works while paused, a paused +contract can still pay out collateral. This is intentional (a pure exit +cannot harm the pool) and is recorded in `SECURITY.md`. + +**`sell` accounting.** `sell` now uses `checked_sub` for `sets_minted` +(previously an unchecked `-=`), so a bug can never drive `S` negative and +break the settlement bound. + **Solvency invariant** (holds after every state transition — see the test suite for the ones exercised): the contract's own SEP-41 token balance for a market equals `sets_minted + fees_accrued + lp_fees` (`fees_accrued` is the @@ -108,8 +171,12 @@ with bounds) → `resolve` or `void_market` (resolver-only) → `redeem` per holder, `claim_pool_remainder` for the creator/LP, `sweep_fees` (permissionless) to the snapshotted fee recipient. Positions and LP ownership can change hands at any point via `transfer_position` / -`transfer_lp`. `finalize_market` (permissionless, after the redemption -deadline) closes the market and sweeps the residual. +`transfer_lp`. While open, `split`/`merge` convert between collateral and +complete sets without touching the pool (`split` before `close_time`, +`merge` any time before resolution, including while paused). After +finality, `redeem` handles both legs; `merge` is not used post-resolution. +`finalize_market` (permissionless, after the redemption deadline) closes the +market and sweeps the residual. A per-market `SettleState` side entry (views `settle_state` and `redeem_deadline`) tracks user-held outstanding shares, the finality @@ -343,6 +410,11 @@ on the treasury's money. The `*_v2` twins forward the same caps plus market-core's deadline / limit-price bounds, so a revert from either layer leaves the caps unchanged (the whole invocation is atomic). +Treasury `agent_split` / `agent_merge` forwarders are **not** implemented +yet (a non-goal of the split/merge change); agents that need the primitives +call `market-core` directly today. They can be added later as thin wrappers +that reuse the existing caps. + **Agents.** The treasury keeps a registry of up to `MAX_AGENTS = 16` agents, each with an `AgentPolicy { roles, max_trade, max_daily, active }`. Roles are a bitmask: `ROLE_CREATE` (`agent_create_market`), `ROLE_TRADE` (`agent_buy`, @@ -1054,6 +1126,10 @@ compatibility must be declared and is checked against the chain. deployments (this repo's or a fork's) from trading against the same market-core markets; the treasury's caps bound one agent deployment's own risk, not systemic market behavior. +- MAX_SETS griefing via `split`: an attacker can split up to `MAX_SETS` and + block further `buy` calls (`MarketTooLarge`) at the cost of locking that + much collateral, then merge out risk-free. See the split/merge section + above for why the shared cap is kept. ### x402 payment preparation, settlement and replay diff --git a/oracledesk-stellar/docs/frontend-integration.md b/oracledesk-stellar/docs/frontend-integration.md index 65678fe..0a6b1be 100644 --- a/oracledesk-stellar/docs/frontend-integration.md +++ b/oracledesk-stellar/docs/frontend-integration.md @@ -1,6 +1,6 @@ # Frontend integration -The frontend itself lives in a separate repo (out of scope here). This is +The frontend itself lives in a separate repo (out of scope here). This ir what it needs to know to talk to these contracts. ## Wallet @@ -16,7 +16,7 @@ import { isConnected, getAddress, signTransaction } from "@stellar/freighter-api const contract = new MarketCoreClient({ contractId: "", // from deployments/testnet.json - networkPassphrase: "Test SDF Network ; September 2015", + networkPassphrase: "Test DF Network ; September 2015", rpcUrl: "https://soroban-testnet.stellar.org", }); ``` @@ -38,14 +38,14 @@ generic usage notes; this doc covers the OracleDesk-specific parts. contract's Rust enums: ```ts -type Outcome = { tag: "Yes"; values: void } | { tag: "No"; values: void }; +type Outcome = { tag: "Yes"; values: void } | { tag: "Noy"; values: void }; ``` The resolver's `attest` / `attest_with_evidence` take its own `AttestedOutcome` instead, which adds `{ tag: "Invalid"; values: void }` (finalizing an `Invalid` proposal voids the market). `finalize_signers` -returns `AttestedOutcome`. Indexers should decode `ResolutionProposedV2` / -`ResolutionFinalizedV2` / `CommitteeVoided`; the `Outcome`-typed +keturns `AttestedOutcome`. Indexers should decode `ResolutionProposedV2` / +ResolutionFinalizedV2` / `CommitteeVoided`; the `Outcome-typed `ResolutionProposed` / `ResolutionFinalized` are no longer emitted. ```ts @@ -61,8 +61,8 @@ const { result } = await tx.signAndSend({ signTransaction }); For anything user-facing, prefer `buy_v2` / `sell_v2` (below): they add a deadline and a limit price on top of the share bound, and they are the only -entrypoints that protect against a transaction being included long after it -was signed. +entrypoints that protect against a transaction being included long after +it was signed. ## Slippage on `buy` @@ -79,17 +79,17 @@ money. `buy_v2` / `sell_v2` add two contract-enforced bounds to every trade: -- `deadline: u64` — unix seconds, compared against the **ledger - timestamp** (network time, not the user's clock). Past it the trade - reverts with `DeadlineExpired` (#19). -- `limit_price_bps: u32` — the **average execution price** bound, - fee included: for a buy, `collateral_in / shares_out <= limit`; for a - sell, `collateral_out / shares_in >= limit`. Worse than the limit - reverts with `LimitPriceExceeded` (#20). The limit is on the price of - *the outcome being traded* — a NO buy's limit is checked against NO - prices, i.e. the complement of the YES price. `0` means "no limit" for - buys; for sells it is trivially satisfied and buys you nothing. Values - above `10_000` are rejected (`InvalidAmount`). +- `deadline: u64` — unix seconds, compared against the **lddger + timestamp** (network time, not the user's clock). Past it the trade + reverts with `DeadlineExpired` (#19). +- `limit_price_bps: u32` — the **average execution price** bound, + fee included: for a buy, `collateral_in / shares_out <= limit`; for a + sell, `collateral_out / shares_in >= limit`. Worse than the limit + reverts with `LimitPriceExceeded` (#20). The limit is on the price of + *the outcome being traded* — a NO buy's limit is checked against NO + prices, i.e. the complement of the YES price. `0` means "no limit" for + buys; for sells it is trivially satisfied and buys you nothing. Values + above `10_000` are rejected (`InvalidAmount`). ```ts const quote = (await contract.quote_buy({ market_id, outcome, collateral_in })).result; @@ -99,7 +99,7 @@ const tx = await contract.buy_v2({ outcome, collateral_in, min_shares_out: (quote * 9_950n) / 10_000n, // 50bps slippage - limit_price_bps: 6500, // never pay more than 65¢ per share + limit_price_bps: 6500, // never pay more than 65c per share deadline: BigInt(Math.floor(Date.now() / 1000) + 60), }); ``` @@ -113,7 +113,7 @@ Markets can carry per-market breakers (snapshotted at creation): a maximum price impact for a **single trade** (`max_impact_bps`) and a **per-ledger band** (`band_bps`) around the price at the start of the ledger. A trade that would move the YES price more than the impact cap reverts with -`PriceImpactTooHigh` (#21); one that would push it more than the band away +priceImpactTooHigh` (#21); one that would push it more than the band away from the ledger-open price reverts with `PriceBandExceeded` (#22). The band resets every ledger, so the worst case is ~5s of blocked trading, not a permanent lock. @@ -121,10 +121,10 @@ a permanent lock. Read the config and the current band reference before quoting so your UI can size trades that will actually execute: -- `breaker_config(market_id)` → `{ max_impact_bps, band_bps } | null` +- `breaker_config(market_id)` → `{ `max_impact_bps`, `band_bps` } | null` (`null` = old market, no breakers) -- `breaker_state(market_id)` → `{ ledger_seq, ref_price_bps } | null` -- `would_trip(market_id, outcome, collateral_in)` → bool: whether a buy of +- `breaker_state(market_id)` → `{ ``ledger_seq`, `ref_price_bps` } | null` +- `would_trip(market_id, outcome, collateral_in)` → bool: whether a buy of that size would trip either check right now. Use it to warn users before they sign; `quote_buy` is not filtered by the breakers. @@ -281,6 +281,54 @@ Caveats to design around: - Markets created before the accumulator existed revert `NotSupported`; fall back to `get_price` for them. +## Splitting and merging complete sets + +Besides trading against the pool, holders can convert collateral to +shares and back at par without touching the pool: + +- `split(holder, market_id, amount)` pulls `amount` collateral from + `holder` and credits `amount` YES **and** `amount` NO to their + position. No fee, no price impact. +- `merge(holder, market_id, amount)` burns `amount` YES and `amount` + NO and pays `amount` collateral back. No fee, no price impact. + +Neither changes `fees_accrued` or the pool reserves. Both are authorized +by the holder and emit `SetsSplit` / `SetsMerged` respectively (decode these +in your indexer to track position changes that didn't come from a trade). + +Lifecycle differs between the two: + +- `split` requires the market to be `Open` and the current ledger time + to be strictly before `close_time`, and is blocked while the contract + is paused. After close, splitting has no purpose and opens timing + games. +- `merge` requires the market to be `Open`, but is allowed after + `close_time` and while the contract is paused. Merging is a pure + exit that cannot harm the pool, and letting users de-wait while the + resolver is slow is valuable. + +Merging requires both legs: `position.yes >= amount` and +`position.no >= amount`, otherwise it reverts with `InsufficientShares` +(#13). The creator after `seed_reserves` typically holds only one leg, so +a merge needs a buy or split first. + +To offer "merge all", call `quote_merge_max(market_id, holder)`, which +returns `min(position.yes, position.no)`. The same helper is exposed as +`math::merge_max` and mirrored in `model/fpmm_model.py` / `agents/core/src/fpmm.ts`. + +```ts +const max = (await contract.quote_merge_max({ market_id, holder: userAddress })).result; +if (max > 0n) { + const tx = await contract.merge({ holder: userAddress, market_id, amount: max }); + await tx.signAndSend({ signTransaction }); +} +``` + +Note that split sets count towards the per-market `sets_minted` cap and thus +can push a market into `MarketTooLarge` (#15) for future `buy` calls. See +`docs/architecture.md` for the griefing discussion and the chosen +mitigation. + ## Moving shares between wallets `transfer_position(from, to, market_id, outcome, amount)` sends outcome diff --git a/oracledesk-stellar/model/fpmm_model.py b/oracledesk-stellar/model/fpmm_model.py index fde53b7..b9b7b65 100644 --- a/oracledesk-stellar/model/fpmm_model.py +++ b/oracledesk-stellar/model/fpmm_model.py @@ -188,6 +188,15 @@ def settle_value(kind: str, yes: int, no: int) -> int: raise ValueError(f"unknown settlement kind {kind!r}") +def merge_max(yes: int, no: int) -> int: + """Largest amount of complete sets a holder can merge: the smaller of + their YES and NO balances. Mirrors math.rs::merge_max; split/merge never + touch the pool, so this is a pure min over the holder's position.""" + if yes < 0 or no < 0: + raise MathError("invalid input") + return min(yes, no) + + def settle_upper(kind: str, yes: int, no: int) -> int: """Mirror of `math::settle_upper`: an upper bound on the total `settle_value` pays out for a set of per-holder (yes, no) claims. Exact @@ -510,6 +519,23 @@ def _check_void_never_overpays(): assert settle_value("void", 5, 5) <= 5 +def _check_merge_max_known_values(): + """Pins the same worked examples as math.rs's merge_max tests.""" + assert merge_max(0, 0) == 0 + assert merge_max(5, 0) == 0 + assert merge_max(0, 7) == 0 + assert merge_max(3, 3) == 3 + assert merge_max(10, 4) == 4 + assert merge_max(4, 10) == 4 + assert merge_max(MAX_SETS, MAX_SETS) == MAX_SETS + try: + merge_max(-1, 5) + except MathError: + pass + else: + raise AssertionError("merge_max accepted a negative balance") + + def _check_void_sum_of_floors(trials: int = 2_000, seed: int = 13): """A void market's finalize must reserve at least what remaining holders can still redeem. Each holder's claim floors its own half, so the sum of @@ -662,6 +688,7 @@ def run_all(): _check_sell_shares_never_beats_calc_sell() _check_sell_shares_fixture_table() _check_void_never_overpays() + _check_merge_max_known_values() _check_void_sum_of_floors() _check_price_after_maps_both_sides() _check_limits_boundary_and_symmetry() diff --git a/oracledesk-stellar/packages/bindings/market-core/src/index.ts b/oracledesk-stellar/packages/bindings/market-core/src/index.ts index 81a5a2f..ee08e5c 100644 --- a/oracledesk-stellar/packages/bindings/market-core/src/index.ts +++ b/oracledesk-stellar/packages/bindings/market-core/src/index.ts @@ -53,6 +53,14 @@ export const Errors = { 16: {message:"NothingToRedeem"}, 17: {message:"AlreadyClaimed"}, 18: {message:"Overflow"}, + /** + * `buy_v2` / `sell_v2` called with `env.ledger().timestamp() > deadline`. + */ + 19: {message:"DeadlineExpired"}, + /** + * `buy_v2` / `sell_v2` average execution price worse than the limit. + */ + 20: {message:"LimitPriceExceeded"}, /** * A single trade would move the price more than `max_impact_bps`. * (Both this and the band reject the whole invocation, so their events, @@ -463,6 +471,22 @@ export interface Client { */ transfer_position: ({from, to, market_id, outcome, amount}: {from: string, to: string, market_id: u64, outcome: Outcome, amount: i128}, options?: MethodOptions) => Promise>> + /** + * Construct and simulate a split transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Mints `amount` YES and `amount` NO shares to `holder` for `amount` + * collateral. No fee, no pool movement. Requires `Open` and + * `now < close_time`; rejected while paused. + */ + split: ({holder, market_id, amount}: {holder: string, market_id: u64, amount: i128}, options?: MethodOptions) => Promise>> + + /** + * Construct and simulate a merge transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Burns `amount` YES and `amount` NO shares from `holder` and returns + * `amount` collateral at par. No fee, no pool movement. Requires `Open`, + * but is allowed after `close_time` and while paused. + */ + merge: ({holder, market_id, amount}: {holder: string, market_id: u64, amount: i128}, options?: MethodOptions) => Promise>> + /** * Construct and simulate a set_default_breaker transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Sets the breaker defaults copied into markets created after the call. @@ -521,11 +545,14 @@ export class Client extends ContractClient { "AAAABQAAAAAAAAAAAAAABVRyYWRlAAAAAAAAAQAAAAV0cmFkZQAAAAAAAAkAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAAAAAAAAZ0cmFkZXIAAAAAABMAAAABAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAAAAAAAAZpc19idXkAAAAAAAEAAAAAAAAAAAAAAApjb2xsYXRlcmFsAAAAAAALAAAAAAAAAAAAAAAGc2hhcmVzAAAAAAALAAAAAAAAAAAAAAADZmVlAAAAAAsAAAAAAAAAAAAAAAtyZXNlcnZlX3llcwAAAAALAAAAAAAAAAAAAAAKcmVzZXJ2ZV9ubwAAAAAACwAAAAAAAAAC", "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAABwAAAAAAAAAFYWRtaW4AAAAAAAATAAAAAAAAAApjb2xsYXRlcmFsAAAAAAATAAAAAAAAAA9kZWZhdWx0X2ZlZV9icHMAAAAABAAAAAAAAAAGcGF1c2VkAAAAAAABAAAAAAAAABZwcm90b2NvbF9mZWVfc2hhcmVfYnBzAAAAAAAEAAAAAAAAAAhyZXNvbHZlcgAAABMAAAAAAAAACHRyZWFzdXJ5AAAAEw==", "AAAAAQAAAAAAAAAAAAAABk1hcmtldAAAAAAADQAAAAAAAAAIY2F0ZWdvcnkAAAfQAAAACENhdGVnb3J5AAAAPFVuaXggc2Vjb25kcyAobGVkZ2VyIHRpbWVzdGFtcCkuIFRyYWRpbmcgc3RvcHMgYXQgdGhpcyB0aW1lLgAAAApjbG9zZV90aW1lAAAAAAAGAAAAAAAAAAdjcmVhdG9yAAAAABMAAABGU25hcHNob3Qgb2YgdGhlIGZlZSBhdCBjcmVhdGlvbiBzbyBsYXRlciBjb25maWcgY2hhbmdlcyBuZXZlciBtb3ZlIGl0LgAAAAAAB2ZlZV9icHMAAAAABAAAACZUcmFkaW5nIGZlZXMgd2FpdGluZyBmb3IgYHN3ZWVwX2ZlZXNgLgAAAAAADGZlZXNfYWNjcnVlZAAAAAsAAAA4Q3JlYXRvciBoYXMgd2l0aGRyYXduIHRoZSBwb29sIHJlbWFpbmRlciBhZnRlciBmaW5hbGl0eS4AAAAKbHBfY2xhaW1lZAAAAAAAAQAAAChJUEZTIENJRCAvIFVSSSBvZiB0aGUgcXVlc3Rpb24gbWV0YWRhdGEuAAAACG1ldGFfdXJpAAAAEAAAAIZzaGEyNTYgb2YgdGhlIGNhbm9uaWNhbCBxdWVzdGlvbiBKU09OIHN0b3JlZCBvZmYtY2hhaW4gKElQRlMpLCBzbyBhCmNsaWVudCBjYW4gcHJvdmUgdGhlIHRleHQgaXQgc2hvd3MgaXMgdGhlIHRleHQgdGhhdCB3YXMgY29tbWl0dGVkLgAAAAAADXF1ZXN0aW9uX2hhc2gAAAAAAAPuAAAAIAAAAAAAAAAKcmVzZXJ2ZV9ubwAAAAAACwAAAAAAAAALcmVzZXJ2ZV95ZXMAAAAACwAAAS9Db21taXRtZW50IHRvIGhvdyB0aGlzIG1hcmtldCB3aWxsIGJlIHJlc29sdmVkLCBmaXhlZCBhdCBjcmVhdGlvbiBzbwp0aGUgY3JlYXRvciBjYW5ub3QgcGljayBmYXZvcmFibGUgcmVzb2x1dGlvbiBydWxlcyBhZnRlciBzZWVpbmcKdHJhZGluZyBhY3Rpdml0eS4gVGhlIHJlc29sdmVyIGNvbnRyYWN0IHZlcmlmaWVzIGEgcmV2ZWFsZWQgc3BlYwphZ2FpbnN0IHRoaXMgaGFzaCBiZWZvcmUgY29uZmlndXJpbmcgaXRzZWxmIGZvciB0aGUgbWFya2V0LiBTZWUKYGRvY3MvYWRyLzAwMDEtcmVzb2x1dGlvbi1jb21taXRtZW50Lm1kYC4AAAAAD3Jlc29sdXRpb25faGFzaAAAAAPuAAAAIAAAAEBDb21wbGV0ZSBzZXRzIGluIGV4aXN0ZW5jZSA9PSBjb2xsYXRlcmFsIGxvY2tlZCBmb3IgdGhpcyBtYXJrZXQuAAAAC3NldHNfbWludGVkAAAAAAsAAAAAAAAABnN0YXR1cwAAAAAH0AAAAAxNYXJrZXRTdGF0dXM=", + "AAAAAAAAAPdTcGVuZCBgY29sbGF0ZXJhbF9pbmAgdG8gYnV5IGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMgc2hhcmVzIHJlY2VpdmVkLgpSZXZlcnRzIGlmIGZld2VyIHRoYW4gYG1pbl9zaGFyZXNfb3V0YCB3b3VsZCBiZSByZWNlaXZlZC4KCkxlZ2FjeSBlbnRyeXBvaW50OiBubyBkZWFkbGluZSBhbmQgbm8gbGltaXQgcHJpY2UsIHNvIGEgc2lnbmVkIGJvdW5kCmNhbiBleGVjdXRlIGF0IGFueSBsYXRlciB0aW1lLiBQcmVmZXIgYGJ1eV92MmAuAAAAAANidXkAAAAABQAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADWNvbGxhdGVyYWxfaW4AAAAAAAALAAAAAAAAAA5taW5fc2hhcmVzX291dAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAEgAAAAAAAAAAAAAABUFkbWluAAAAAAAAAAAAAAAAAAAMUGVuZGluZ0FkbWluAAAAAAAAAAAAAAAKQ29sbGF0ZXJhbAAAAAAAAAAAAAAAAAAIVHJlYXN1cnkAAAAAAAAAAAAAAAhSZXNvbHZlcgAAAAAAAAAAAAAAD1BlbmRpbmdSZXNvbHZlcgAAAAAAAAAAAAAAAA9QZW5kaW5nVHJlYXN1cnkAAAAAAAAAAAAAAAANRGVmYXVsdEZlZUJwcwAAAAAAAAAAAAAAAAAAE1Byb3RvY29sRmVlU2hhcmVCcHMAAAAAAAAAAAAAAAAORGVmYXVsdEJyZWFrZXIAAAAAAAAAAAAAAAAABlBhdXNlZAAAAAAAAAAAAAAAAAAGTmV4dElkAAAAAAABAAAAAAAAAAZNYXJrZXQAAAAAAAEAAAAGAAAAAQAAAAAAAAAIUG9zaXRpb24AAAACAAAABgAAABMAAAABAAAAAAAAAAhGZWVUZXJtcwAAAAEAAAAGAAAAAQAAAAAAAAAGTHBGZWVzAAAAAAABAAAABgAAAAEAAAAAAAAAB0JyZWFrZXIAAAAAAQAAAAYAAAABAAAAAAAAAARCYW5kAAAAAQAAAAY=", "AAAAAgAAAAAAAAAAAAAAB091dGNvbWUAAAAAAgAAAAAAAAAAAAAAA1llcwAAAAAAAAAAAAAAAAJObwAA", + "AAAAAAAAAVZSZWNlaXZlIGV4YWN0bHkgYGNvbGxhdGVyYWxfb3V0YCBieSBzZWxsaW5nIGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMKc2hhcmVzIHNvbGQuIFJldmVydHMgaWYgbW9yZSB0aGFuIGBtYXhfc2hhcmVzX2luYCB3b3VsZCBiZSBuZWVkZWQuCkNsaWVudHMgdHVybiAic2VsbCBOIHNoYXJlcyIgaW50byBgY29sbGF0ZXJhbF9vdXRgIG9mZi1jaGFpbgooc2VlIGBtYXRoOjpjYWxjX3NlbGxgIGRvY3MpIGFuZCBhZGQgYSBzbGlwcGFnZSBtYXJnaW4gdG8gYG1heF9zaGFyZXNfaW5gLgoKTGVnYWN5IGVudHJ5cG9pbnQ6IG5vIGRlYWRsaW5lIGFuZCBubyBsaW1pdCBwcmljZS4gUHJlZmVyIGBzZWxsX3YyYC4AAAAAAARzZWxsAAAABQAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADmNvbGxhdGVyYWxfb3V0AAAAAAALAAAAAAAAAA1tYXhfc2hhcmVzX2luAAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", "AAAAAgAAAAAAAAAAAAAACENhdGVnb3J5AAAABgAAAAAAAAAAAAAABkNyeXB0bwAAAAAAAAAAAAAAAAAFTWFjcm8AAAAAAAAAAAAAAAAAAAtHZW9wb2xpdGljcwAAAAAAAAAAAAAAAAZTcG9ydHMAAAAAAAAAAAAAAAAAB0N1bHR1cmUAAAAAAAAAAAAAAAAFT3RoZXIAAAA=", "AAAAAQAAAahGZWUgZGVzdGluYXRpb24gYW5kIHNwbGl0LCBzbmFwc2hvdHRlZCBwZXIgbWFya2V0IGF0IGNyZWF0aW9uIGFzIGEgc2lkZQplbnRyeSAoYERhdGFLZXk6OkZlZVRlcm1zYCkgc28gYE1hcmtldGAncyBYRFIgc2hhcGUgbmV2ZXIgY2hhbmdlcy4KCk1hcmtldHMgY3JlYXRlZCBiZWZvcmUgdGhpcyBlbnRyeSBleGlzdGVkIGhhdmUgbm8gYEZlZVRlcm1zYCBhbmQga2VlcCB0aGUKbGVnYWN5IGJlaGF2aW91cjogYHN3ZWVwX2ZlZXNgIHBheXMgd2hhdGV2ZXIgYERhdGFLZXk6OlRyZWFzdXJ5YCBob2xkcyBhdApzd2VlcCB0aW1lLCB3aXRoIHRoZSBwcm90b2NvbCBrZWVwaW5nIDEwMCUgb2YgdGhlIGZlZS4gVGhhdCBmYWxsYmFjayBpcwpkb2N1bWVudGVkIGluIGBzd2VlcF9mZWVzYCBhbmQgbWFrZXMgdGhlIHVwZ3JhZGUgbWlncmF0aW9uLWZyZWUuAAAAAAAAAAhGZWVUZXJtcwAAAAIAAACGUHJvdG9jb2wncyBzaGFyZSBvZiBlYWNoIHRyYWRlIGZlZSBpbiBicHM7IHRoZSByZXN0IGlzIHRoZSBMUCdzLgowLi49QlBTLiBBdCBCUFMgKHRoZSBkZWZhdWx0KSB0aGUgc3BsaXQgaXMgZXhhY3RseSB0b2RheSdzIGJlaGF2aW91ci4AAAAAABJwcm90b2NvbF9zaGFyZV9icHMAAAAAAAQAAAB7V2hlcmUgdGhlIHByb3RvY29sJ3MgZmVlIHNoYXJlIGlzIHN3ZXB0LiBGcm96ZW4gYXQgY3JlYXRpb246IGxhdGVyCmBzZXRfdHJlYXN1cnlgIGNhbGxzIG5ldmVyIHJlZGlyZWN0IGZlZXMgYWxyZWFkeSBlYXJuZWQuAAAAAAlyZWNpcGllbnQAAAAAAAAT", "AAAAAQAAAAAAAAAAAAAACFBvc2l0aW9uAAAAAgAAAAAAAAACbm8AAAAAAAsAAAAAAAAAA3llcwAAAAAL", + "AAAAAAAAAAAAAAAFYWRtaW4AAAAAAAAAAAAAAQAAA+kAAAATAAAAAw==", "AAAABQAAAAAAAAAAAAAACFJlZGVlbWVkAAAAAQAAAAhyZWRlZW1lZAAAAAMAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAAAAAAAAZob2xkZXIAAAAAABMAAAABAAAAAAAAAAZwYXlvdXQAAAAAAAsAAAAAAAAAAg==", "AAAAAQAAADNUZW1wb3JhcnkgKG9uZSBsZWRnZXIpIGJhbmQgcmVmZXJlbmNlIGZvciBhIG1hcmtldC4AAAAAAAAAAAlCYW5kU3RhdGUAAAAAAAACAAAAAAAAAApsZWRnZXJfc2VxAAAAAAAEAAAAAAAAAA1yZWZfcHJpY2VfYnBzAAAAAAAABA==", "AAAABQAAAAAAAAAAAAAACUZlZXNTd2VwdAAAAAAAAAEAAAAKZmVlc19zd2VwdAAAAAAAAgAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAAC", @@ -543,44 +570,14 @@ export class Client extends ContractClient { "AAAABQAAAAAAAAAAAAAAEFJlc29sdmVyUHJvcG9zZWQAAAABAAAAEXJlc29sdmVyX3Byb3Bvc2VkAAAAAAAAAQAAAAAAAAAIcmVzb2x2ZXIAAAATAAAAAAAAAAI=", "AAAABQAAAAAAAAAAAAAAEFRyZWFzdXJ5UHJvcG9zZWQAAAABAAAAEXRyZWFzdXJ5X3Byb3Bvc2VkAAAAAAAAAQAAAAAAAAAIdHJlYXN1cnkAAAATAAAAAAAAAAI=", "AAAABQAAAIxQb3NpdGlvbnMgbW92ZWQgYmV0d2VlbiBob2xkZXJzLiBJbmRleGVycyB0aGF0IHRyYWNrIGJhbGFuY2VzIGZyb20KYFRyYWRlYC9gUmVkZWVtZWRgIG11c3QgYWxzbyBhcHBseSB0aGlzIGV2ZW50IChzZWUgZG9jcy9hcmNoaXRlY3R1cmUubWQpLgAAAAAAAAATUG9zaXRpb25UcmFuc2ZlcnJlZAAAAAABAAAAFHBvc2l0aW9uX3RyYW5zZmVycmVkAAAABQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAAAAAAABGZyb20AAAATAAAAAQAAAAAAAAACdG8AAAAAABMAAAAAAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAAAAAAAAZhbW91bnQAAAAAAAsAAAAAAAAAAg==", + "AAAAAAAAADdNb3ZlcyBgYW1vdW50YCBzaGFyZXMgb2YgYG91dGNvbWVgIGZyb20gYGZyb21gIHRvIGB0b2AuAAAAABF0cmFuc2Zlcl9wb3NpdGlvbgAAAAAAAAUAAAAAAAAABGZyb20AAAATAAAAAAAAAAJ0bwAAAAAAEwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAAZhbW91bnQAAAAAAAsAAAABAAAD6QAAAAIAAAAD", "AAAABQAAAAAAAAAAAAAAFFBvb2xSZW1haW5kZXJDbGFpbWVkAAAAAQAAABZwb29sX3JlbWFpbmRlcl9jbGFpbWVkAAAAAAADAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAAAAAAAAHY3JlYXRvcgAAAAATAAAAAQAAADlQb29sIHJlbWFpbmRlciBwbHVzIGFueSBMUCBmZWVzIGRyYWluZWQgaW4gdGhlIHNhbWUgY2FsbC4AAAAAAAAGcGF5b3V0AAAAAAALAAAAAAAAAAI=", "AAAABQAAAAAAAAAAAAAAFUFkbWluVHJhbnNmZXJQcm9wb3NlZAAAAAAAAAEAAAAXYWRtaW5fdHJhbnNmZXJfcHJvcG9zZWQAAAAAAQAAAAAAAAAJbmV3X2FkbWluAAAAAAAAEwAAAAAAAAAC", - "AAAABQAAAAAAAAAAAAAAFkFkbWluVHJhbnNmZXJDYW5jZWxsZWQAAAAAAAEAAAAYYWRtaW5fdHJhbnNmZXJfY2FuY2VsbGVkAAAAAAAAAAI=", - "AAAABQAAAAAAAAAAAAAAF1Byb3RvY29sRmVlU2hhcmVDaGFuZ2VkAAAAAAEAAAAacHJvdG9jb2xfZmVlX3NoYXJlX2NoYW5nZWQAAAAAAAIAAAAAAAAAA29sZAAAAAAEAAAAAAAAAAAAAAADbmV3AAAAAAQAAAAAAAAAAg==", - "AAAAAAAAAPdTcGVuZCBgY29sbGF0ZXJhbF9pbmAgdG8gYnV5IGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMgc2hhcmVzIHJlY2VpdmVkLgpSZXZlcnRzIGlmIGZld2VyIHRoYW4gYG1pbl9zaGFyZXNfb3V0YCB3b3VsZCBiZSByZWNlaXZlZC4KCkxlZ2FjeSBlbnRyeXBvaW50OiBubyBkZWFkbGluZSBhbmQgbm8gbGltaXQgcHJpY2UsIHNvIGEgc2lnbmVkIGJvdW5kCmNhbiBleGVjdXRlIGF0IGFueSBsYXRlciB0aW1lLiBQcmVmZXIgYGJ1eV92MmAuAAAAAANidXkAAAAABQAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADWNvbGxhdGVyYWxfaW4AAAAAAAALAAAAAAAAAA5taW5fc2hhcmVzX291dAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAVZSZWNlaXZlIGV4YWN0bHkgYGNvbGxhdGVyYWxfb3V0YCBieSBzZWxsaW5nIGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMKc2hhcmVzIHNvbGQuIFJldmVydHMgaWYgbW9yZSB0aGFuIGBtYXhfc2hhcmVzX2luYCB3b3VsZCBiZSBuZWVkZWQuCkNsaWVudHMgdHVybiAic2VsbCBOIHNoYXJlcyIgaW50byBgY29sbGF0ZXJhbF9vdXRgIG9mZi1jaGFpbgooc2VlIGBtYXRoOjpjYWxjX3NlbGxgIGRvY3MpIGFuZCBhZGQgYSBzbGlwcGFnZSBtYXJnaW4gdG8gYG1heF9zaGFyZXNfaW5gLgoKTGVnYWN5IGVudHJ5cG9pbnQ6IG5vIGRlYWRsaW5lIGFuZCBubyBsaW1pdCBwcmljZS4gUHJlZmVyIGBzZWxsX3YyYC4AAAAAAARzZWxsAAAABQAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADmNvbGxhdGVyYWxfb3V0AAAAAAALAAAAAAAAAA1tYXhfc2hhcmVzX2luAAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAAAAAAAFYWRtaW4AAAAAAAAAAAAAAQAAA+kAAAATAAAAAw==", - "AAAAAAAAAdRgYnV5YCBwbHVzIGEgZGVhZGxpbmUgKGBlbnYubGVkZ2VyKCkudGltZXN0YW1wKClgIHNlY29uZHMg4oCUIG5ldHdvcmsKdGltZSwgbm90IHRoZSBjbGllbnQgY2xvY2spIGFuZCBhbiBhdmVyYWdlLXByaWNlIGxpbWl0CihgbGltaXRfcHJpY2VfYnBzYCBwZXIgc2hhcmUsIDEuMCA9IEJQUzsgMCA9IG5vIGxpbWl0KS4KClRoZSBhdmVyYWdlIHByaWNlIGluY2x1ZGVzIHRoZSBmZWUgKGBmZWVgIGlzIHBhcnQgb2YgYGNvbGxhdGVyYWxfaW5gKQphbmQsIGZvciBhIE5PIGJ1eSwgaXMgY29tcGFyZWQgYWdhaW5zdCB0aGUgTk8gcHJpY2UuIEJlY2F1c2UgdGhlCmRlYWRsaW5lIGNoZWNrIHJ1bnMgYmVmb3JlIGByZXF1aXJlX2F1dGhgLCBhbiBleHBpcmVkIHRyYW5zYWN0aW9uCmZhaWxzIGNoZWFwbHkuIEEgbGltaXQgYWJvdmUgQlBTIGNhbiBuZXZlciBwYXNzIGZvciBhIGJ1eSBhbmQgaXMKcmVqZWN0ZWQgYXMgaW52YWxpZC4AAAAGYnV5X3YyAAAAAAAHAAAAAAAAAAZ0cmFkZXIAAAAAABMAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAAAAAAAB291dGNvbWUAAAAH0AAAAAdPdXRjb21lAAAAAAAAAAANY29sbGF0ZXJhbF9pbgAAAAAAAAsAAAAAAAAADm1pbl9zaGFyZXNfb3V0AAAAAAALAAAAAAAAAA9saW1pdF9wcmljZV9icHMAAAAABAAAAAAAAAAIZGVhZGxpbmUAAAAGAAAAAQAAA+kAAAALAAAAAw==", - "AAAAAAAAAGlQYXlzIG91dCBgaG9sZGVyYCdzIHBvc2l0aW9uIGZvciBhIGZpbmFsIG1hcmtldCBhbmQgY2xlYXJzIGl0LgpMb3NpbmcgcG9zaXRpb25zIGNsZWFyIHdpdGggYSBwYXlvdXQgb2YgMC4AAAAAAAAGcmVkZWVtAAAAAAACAAAAAAAAAAZob2xkZXIAAAAAABMAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAD6QAAAAsAAAAD", - "AAAAAAAAAAAAAAAHcmVzb2x2ZQAAAAACAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAABAAAD6QAAAAIAAAAD", - "AAAAAAAAAWxgc2VsbGAgcGx1cyBhIGRlYWRsaW5lIChsZWRnZXIgdGltZXN0YW1wLCBzZWNvbmRzKSBhbmQgYSBtaW5pbXVtCmF2ZXJhZ2UgZXhlY3V0aW9uIHByaWNlIChgbGltaXRfcHJpY2VfYnBzYCBwZXIgc2hhcmUsIDEuMCA9IEJQUykuCgpBIHplcm8gbGltaXQgaXMgdHJpdmlhbGx5IHNhdGlzZmllZCBmb3Igc2VsbHMg4oCUIHVubGlrZSBidXlzLCB3aGVyZSAwCm1lYW5zICJubyBsaW1pdCIg4oCUIGFuZCBhIGxpbWl0IGFib3ZlIEJQUyBjYW4gbmV2ZXIgcGFzcywgc28gbmVpdGhlcgpuZWVkcyBhIHNwZWNpYWwgY2FzZS4gVGhlIGF2ZXJhZ2UgaW5jbHVkZXMgdGhlIGZlZSwgd2hpY2ggdGhlIHRyYWRlcgpwYXlzIG91dCBvZiB0aGUgZ3Jvc3MuAAAAB3NlbGxfdjIAAAAABwAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADmNvbGxhdGVyYWxfb3V0AAAAAAALAAAAAAAAAA1tYXhfc2hhcmVzX2luAAAAAAAACwAAAAAAAAAPbGltaXRfcHJpY2VfYnBzAAAAAAQAAAAAAAAACGRlYWRsaW5lAAAABgAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAD9TcG90IHByaWNlIG9mIGBvdXRjb21lYCBpbiBicHMgKDAuLj0xMF8wMDApLiBZRVMgKyBOTyB+IDEwXzAwMC4AAAAACWdldF9wcmljZQAAAAAAAAIAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAAAAAAAB291dGNvbWUAAAAH0AAAAAdPdXRjb21lAAAAAAEAAAPpAAAABAAAAAM=", - "AAAAAAAAADlTaGFyZXMgYSBgYnV5YCBvZiBgY29sbGF0ZXJhbF9pbmAgd291bGQgcmV0dXJuIHJpZ2h0IG5vdy4AAAAAAAAJcXVvdGVfYnV5AAAAAAAAAwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAA1jb2xsYXRlcmFsX2luAAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAAAAAAAKZ2V0X2NvbmZpZwAAAAAAAAAAAAEAAAPpAAAH0AAAAAZDb25maWcAAAAAAAM=", - "AAAAAAAAAAAAAAAKZ2V0X21hcmtldAAAAAAAAQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAPpAAAH0AAAAAZNYXJrZXQAAAAAAAM=", - "AAAAAAAAAEVTaGFyZXMgYSBgc2VsbGAgZm9yIGV4YWN0bHkgYGNvbGxhdGVyYWxfb3V0YCB3b3VsZCBjb25zdW1lIHJpZ2h0IG5vdy4AAAAAAAAKcXVvdGVfc2VsbAAAAAAAAwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAA5jb2xsYXRlcmFsX291dAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAAAAAAAKc2V0X3BhdXNlZAAAAAAAAQAAAAAAAAAGcGF1c2VkAAAAAAABAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAQpBbnlvbmUgY2FuIG1vdmUgYWNjcnVlZCBwcm90b2NvbCBmZWVzIHRvIHRoZSBtYXJrZXQncyBmZWUgcmVjaXBpZW50LgoKU2VuZHMgdG8gdGhlIHJlY2lwaWVudCBzbmFwc2hvdHRlZCBpbiBgRmVlVGVybXNgIGF0IGNyZWF0aW9uLiBNYXJrZXRzCmNyZWF0ZWQgYmVmb3JlIGBGZWVUZXJtc2AgZXhpc3RlZCBoYXZlIG5vbmUgYW5kIGtlZXAgdGhlIGxlZ2FjeQpiZWhhdmlvdXI6IHRoZSBgRGF0YUtleTo6VHJlYXN1cnlgIG9mIHRoZSBtb21lbnQgb2YgdGhlIHN3ZWVwLgAAAAAACnN3ZWVwX2ZlZXMAAAAAAAEAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAD6QAAAAsAAAAD", - "AAAAAAAAAPVUcnVlIHdoZW4gYSBgYnV5YCBvZiBgY29sbGF0ZXJhbF9pbmAgd291bGQgYmUgcmVqZWN0ZWQgYnkgdGhlCm1hcmtldCdzIGJyZWFrZXJzIHJpZ2h0IG5vdy4gUmVhZC1vbmx5OiBuZXZlciB3cml0ZXMgYmFuZCBzdGF0ZSwgc28Kb24gYSBmcmVzaCBsZWRnZXIgdGhlIHJlZmVyZW5jZSBpcyB0YWtlbiBhcyB0aGUgY3VycmVudCBwcmljZSDigJQgdGhlCnNhbWUgdmFsdWUgYSBzdWJzZXF1ZW50IHJlYWwgdHJhZGUgd291bGQgdXNlLgAAAAAAAAp3b3VsZF90cmlwAAAAAAADAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADWNvbGxhdGVyYWxfaW4AAAAAAAALAAAAAQAAA+kAAAABAAAAAw==", - "AAAAAAAAAL9UcmFuc2ZlcnMgdGhlIHNpbmdsZSBMUCByb2xlICh0aGUgcmlnaHQgdG8gYGNsYWltX3Bvb2xfcmVtYWluZGVyYCBhbmQKYGNsYWltX2xwX2ZlZXNgKSB0byBgbmV3X2NyZWF0b3JgLiBPbmx5IHVudGlsIHRoZSByZW1haW5kZXIgaGFzIGJlZW4KY2xhaW1lZDsgYWZ0ZXIgdGhhdCB0aGUgcm9sZSBoYXMgbm8gcmVtYWluaW5nIHZhbHVlLgAAAAALdHJhbnNmZXJfbHAAAAAAAgAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAALbmV3X2NyZWF0b3IAAAAAEwAAAAEAAAPpAAAAAgAAAAM=", - "AAAAAAAAAEFJbnZhbGlkIG9yIHVucmVzb2x2YWJsZSBtYXJrZXQuIEFsbG93ZWQgYW55IHRpbWUgYmVmb3JlIGZpbmFsaXR5LgAAAAAAAAt2b2lkX21hcmtldAAAAAABAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAAAAAAAMYWNjZXB0X2FkbWluAAAAAAAAAAEAAAPpAAAAAgAAAAM=", - "AAAAAAAAAAAAAAAMZ2V0X3Bvc2l0aW9uAAAAAgAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAGaG9sZGVyAAAAAAATAAAAAQAAB9AAAAAIUG9zaXRpb24=", - "AAAAAAAAAAAAAAAMbWFya2V0X2NvdW50AAAAAAAAAAEAAAAG", - "AAAAAAAAAQhSdW5zIGF0b21pY2FsbHkgYXQgZGVwbG95IHRpbWUsIHNvIG5vYm9keSBjYW4gZnJvbnQtcnVuIGluaXRpYWxpemF0aW9uLgpgcHJvdG9jb2xfZmVlX3NoYXJlX2Jwc2AgaXMgdGhlIGRlZmF1bHQgcHJvdG9jb2wvTFAgc3BsaXQgZm9yIG5ldwptYXJrZXRzOyAxMF8wMDAga2VlcHMgdGhlIHdob2xlIGZlZSB3aXRoIHRoZSBwcm90b2NvbCAocHJlLXVwZ3JhZGUKYmVoYXZpb3VyKS4gUGFzcyAwIHRvIGdpdmUgYWxsIGZlZXMgdG8gdGhlIG1hcmtldCBjcmVhdG9ycy4AAAANX19jb25zdHJ1Y3RvcgAAAAAAAAYAAAAAAAAABWFkbWluAAAAAAAAEwAAAAAAAAAKY29sbGF0ZXJhbAAAAAAAEwAAAAAAAAAIdHJlYXN1cnkAAAATAAAAAAAAAAhyZXNvbHZlcgAAABMAAAAAAAAAD2RlZmF1bHRfZmVlX2JwcwAAAAAEAAAAAAAAABZwcm90b2NvbF9mZWVfc2hhcmVfYnBzAAAAAAAEAAAAAA==", - "AAAAAAAAALpUaGUgY3VycmVudCBsZWRnZXIncyBiYW5kIHJlZmVyZW5jZSwgaWYgYSBiYW5kIGNoZWNrIGhhcyBydW4gdGhpcwpsZWRnZXIuIGBOb25lYCBtZWFucyB0aGUgbmV4dCB0cmFkZSBzZXRzIHRoZSByZWZlcmVuY2UgKHRvIGl0cyBvd24KcHJlLXRyYWRlIHByaWNlLCB3aGljaCBlcXVhbHMgdGhlIGxlZGdlci1vcGVuIHByaWNlKS4AAAAAAA1icmVha2VyX3N0YXRlAAAAAAAAAQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAPpAAAD6AAAB9AAAAAJQmFuZFN0YXRlAAAAAAAAAw==", - "AAAAAAAAALVUaGUgY3JlYXRvciAodGhlIExQKSBjbGFpbXMgdGhlaXIgc2hhcmUgb2YgYWNjcnVlZCB0cmFkZSBmZWVzIGF0IGFueQp0aW1lLCBpbmNsdWRpbmcgYmVmb3JlIGZpbmFsaXR5LiBSZXR1cm5zIHRoZSBhbW91bnQgY2xhaW1lZCAoMCB3aGVuCm5vdGhpbmcgaXMgcGVuZGluZywgbWlycm9yaW5nIGBzd2VlcF9mZWVzYCkuAAAAAAAADWNsYWltX2xwX2ZlZXMAAAAAAAABAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAA+kAAAALAAAAAw==", - "AAAAAAAAAMhDcmVhdGVzIGEgbWFya2V0IGFuZCBzZWVkcyB0aGUgcG9vbC4gYGNyZWF0b3JgIGJlY29tZXMgdGhlIHNvbGUgTFAuCmBpbml0aWFsX3llc19icHNgIGluIFsxMDAsIDk5MDBdIHNldHMgdGhlIG9wZW5pbmcgWUVTIHByb2JhYmlsaXR5Owp0aGUgY3JlYXRvciByZWNlaXZlcyB0aGUgbGVmdG92ZXIgc2hhcmVzIG9mIHRoZSBsaWtlbGllciBvdXRjb21lLgAAAA1jcmVhdGVfbWFya2V0AAAAAAAACAAAAAAAAAAHY3JlYXRvcgAAAAATAAAAAAAAAA1xdWVzdGlvbl9oYXNoAAAAAAAD7gAAACAAAAAAAAAAD3Jlc29sdXRpb25faGFzaAAAAAPuAAAAIAAAAAAAAAAIbWV0YV91cmkAAAAQAAAAAAAAAAhjYXRlZ29yeQAAB9AAAAAIQ2F0ZWdvcnkAAAAAAAAACmNsb3NlX3RpbWUAAAAAAAYAAAAAAAAAC3NlZWRfYW1vdW50AAAAAAsAAAAAAAAAD2luaXRpYWxfeWVzX2JwcwAAAAAEAAAAAQAAA+kAAAAGAAAAAw==", - "AAAAAAAAAEBUaGUgcGVuZGluZyBhZG1pbiBwcm9wb3NhbCwgaW5jbHVkaW5nIGl0cyBleHBpcnksIGlmIG9uZSBleGlzdHMuAAAADXBlbmRpbmdfYWRtaW4AAAAAAAAAAAAAAQAAA+gAAAfQAAAADFBlbmRpbmdBZG1pbg==", - "AAAAAAAAAJZTdGFydHMgYSB0d28tc3RlcCBoYW5kb3ZlciBvZiB0aGUgYWRtaW4gcm9sZS4gVGhlIG5ldyBhZG1pbiBtdXN0IGNhbGwKYGFjY2VwdF9hZG1pbmAgaXRzZWxmLCBzbyBhIHR5cG8gY2Fubm90IGhhbmQgY29udHJvbCB0byBhbiBhZGRyZXNzCm5vYm9keSBob2xkcy4AAAAAAA1wcm9wb3NlX2FkbWluAAAAAAAAAQAAAAAAAAAJbmV3X2FkbWluAAAAAAAAEwAAAAEAAAPpAAAAAgAAAAM=", - "AAAAAAAAAHpUaGUgbWFya2V0J3Mgc25hcHNob3R0ZWQgYnJlYWtlcnMgKGBOb25lYCBmb3IgbGVnYWN5IG1hcmtldHMgY3JlYXRlZApiZWZvcmUgYnJlYWtlcnMgZXhpc3RlZCDigJQgdGhvc2UgYXJlIG5ldmVyIGNoZWNrZWQpLgAAAAAADmJyZWFrZXJfY29uZmlnAAAAAAABAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAA+kAAAPoAAAH0AAAAA1CcmVha2VyQ29uZmlnAAAAAAAAAw==", - "AAAAAAAAAAAAAAAPYWNjZXB0X3Jlc29sdmVyAAAAAAAAAAABAAAD6QAAAAIAAAAD", - "AAAAAAAAAAAAAAAPYWNjZXB0X3RyZWFzdXJ5AAAAAAAAAAABAAAD6QAAAAIAAAAD", - "AAAAAAAAAAAAAAAQcHJvcG9zZV9yZXNvbHZlcgAAAAEAAAAAAAAACHJlc29sdmVyAAAAEwAAAAEAAAPpAAAAAgAAAAM=", - "AAAAAAAAAJtBZmZlY3RzIG1hcmtldHMgY3JlYXRlZCBhZnRlciB0aGUgY2FsbCBhbmQgdGhlIGxlZ2FjeSBmYWxsYmFjayBpbgpgc3dlZXBfZmVlc2AuIE1hcmtldHMgd2l0aCBhIHNuYXBzaG90dGVkIGBGZWVUZXJtc2Aga2VlcCBwYXlpbmcgdGhlaXIKb3JpZ2luYWwgcmVjaXBpZW50LgAAAAAQcHJvcG9zZV90cmVhc3VyeQAAAAEAAAAAAAAACHRyZWFzdXJ5AAAAEwAAAAEAAAPpAAAAAgAAAAM=", - "AAAAAAAAADdNb3ZlcyBgYW1vdW50YCBzaGFyZXMgb2YgYG91dGNvbWVgIGZyb20gYGZyb21gIHRvIGB0b2AuAAAAABF0cmFuc2Zlcl9wb3NpdGlvbgAAAAAAAAUAAAAAAAAABGZyb20AAAATAAAAAAAAAAJ0bwAAAAAAEwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAAZhbW91bnQAAAAAAAsAAAABAAAD6QAAAAIAAAAD", + "AAAAAAAAAJBNaW50cyBgYW1vdW50YCBZRVMgYW5kIGBhbW91bnRgIE5PIHNoYXJlcyB0byBgaG9sZGVyYCBmb3IgYGFtb3VudGAKY29sbGF0ZXJhbC4gTm8gZmVlLCBubyBwb29sIG1vdmVtZW50LiBSZXF1aXJlcyBgT3BlbmAgYW5kCmBub3cgPCBjbG9zZV90aW1lYDsgcmVqZWN0ZWQgd2hpbGUgcGF1c2VkLgAAAAVzcGxpdAAAAAAAAAMAAAAAAAAABmhvbGRlcgAAAAAAEwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAGYW1vdW50AAAAAAALAAAAAQAAA+kAAAACAAAAAw==", + "AAAAAAAAAKNCdXJucyBgYW1vdW50YCBZRVMgYW5kIGBhbW91bnRgIE5PIHNoYXJlcyBmcm9tIGBob2xkZXJgIGFuZCByZXR1cm5zCmBhbW91bnRgIGNvbGxhdGVyYWwgYXQgcGFyLiBObyBmZWUsIG5vIHBvb2wgbW92ZW1lbnQuIFJlcXVpcmVzIGBPcGVuYCwKYnV0IGlzIGFsbG93ZWQgYWZ0ZXIgYGNsb3NlX3RpbWVgIGFuZCB3aGlsZSBwYXVzZWQuAAAAAAVtZXJnZQAAAAAAAAMAAAAAAAAABmhvbGRlcgAAAAAAEwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAGYW1vdW50AAAAAAALAAAAAQAAA+kAAAACAAAAAw==", "AAAAAAAAAOZTZXRzIHRoZSBicmVha2VyIGRlZmF1bHRzIGNvcGllZCBpbnRvIG1hcmtldHMgY3JlYXRlZCBhZnRlciB0aGUgY2FsbC4KYGJhbmRfYnBzIDwgbWF4X2ltcGFjdF9icHNgIChib3RoIG5vbi16ZXJvKSBpcyByZWplY3RlZDogYSBzaW5nbGUKdHJhZGUgdGhhdCBpcyBhbGxvd2VkIGJ5IHRoZSBpbXBhY3QgY2FwIGNvdWxkIHRoZW4gbmV2ZXIgZml0IGluc2lkZQp0aGUgYmFuZCwgbG9ja2luZyB0cmFkaW5nLgAAAAAAE3NldF9kZWZhdWx0X2JyZWFrZXIAAAAAAQAAAAAAAAAHYnJlYWtlcgAAAAfQAAAADUJyZWFrZXJDb25maWcAAAAAAAABAAAD6QAAAAIAAAAD", "AAAAAAAAAEhBZmZlY3RzIG1hcmtldHMgY3JlYXRlZCBhZnRlciB0aGUgY2FsbDsgZXhpc3RpbmcgbWFya2V0cyBrZWVwIHRoZWlyIGZlZS4AAAATc2V0X2RlZmF1bHRfZmVlX2JwcwAAAAABAAAAAAAAAAdmZWVfYnBzAAAAAAQAAAABAAAD6QAAAAIAAAAD", + "AAAABQAAAAAAAAAAAAAAFkFkbWluVHJhbnNmZXJDYW5jZWxsZWQAAAAAAAEAAAAYYWRtaW5fdHJhbnNmZXJfY2FuY2VsbGVkAAAAAAAAAAI=", "AAAAAAAAAHxDcmVhdG9yICh0aGUgTFApIHdpdGhkcmF3cyB3aGF0ZXZlciB0aGUgcG9vbCBzdGlsbCBob2xkcyBvbmNlIGZpbmFsLApwbHVzIGFueSB1bmNsYWltZWQgTFAgZmVlcy4gUmV0dXJucyB0aGUgdG90YWwgcGFpZCBvdXQuAAAAFGNsYWltX3Bvb2xfcmVtYWluZGVyAAAAAQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAPpAAAACwAAAAM=", "AAAAAAAAAAAAAAAVY2FuY2VsX2FkbWluX3RyYW5zZmVyAAAAAAAAAAAAAAEAAAPpAAAAAgAAAAM=", "AAAAAAAAAGVBZmZlY3RzIG1hcmtldHMgY3JlYXRlZCBhZnRlciB0aGUgY2FsbDsgZXhpc3RpbmcgbWFya2V0cyBrZWVwIHRoZQpzcGxpdCB0aGV5IHNuYXBzaG90dGVkIGF0IGNyZWF0aW9uLgAAAAAAABpzZXRfcHJvdG9jb2xfZmVlX3NoYXJlX2JwcwAAAAAAAQAAAAAAAAAJc2hhcmVfYnBzAAAAAAAABAAAAAEAAAPpAAAAAgAAAAM=" ]), @@ -621,6 +618,8 @@ export class Client extends ContractClient { propose_resolver: this.txFromJSON>, propose_treasury: this.txFromJSON>, transfer_position: this.txFromJSON>, + split: this.txFromJSON>, + merge: this.txFromJSON>, set_default_breaker: this.txFromJSON>, set_default_fee_bps: this.txFromJSON>, claim_pool_remainder: this.txFromJSON>,