From b8a200c7e9f6b9f82b1041eec7db22e740aeb22c Mon Sep 17 00:00:00 2001 From: nasirudeenbadirudeen87-cloud <318769136+nasirudeenbadirudeen87-cloud@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:19:55 +0100 Subject: [PATCH 1/3] feat(contract): enforce immutable per-token escrow caps --- ARCHITECTURE_ESSENTIALS.md | 9 ++- README.md | 10 +++ ROADMAP.md | 14 +++- contracts/escrow/src/lib.rs | 74 ++++++++++++++++++- contracts/escrow/tests/create_escrow.rs | 90 +++++++++++++++++++++-- contracts/escrow/tests/malicious_token.rs | 14 +++- contracts/escrow/tests/release_refund.rs | 6 +- contracts/escrow/tests/timeout_refund.rs | 14 +++- docs/adr/0006-per-token-escrow-caps.md | 46 ++++++++++++ docs/testnet-deployments.md | 5 ++ 10 files changed, 263 insertions(+), 19 deletions(-) create mode 100644 docs/adr/0006-per-token-escrow-caps.md diff --git a/ARCHITECTURE_ESSENTIALS.md b/ARCHITECTURE_ESSENTIALS.md index 3d5b571c..176a1d2b 100644 --- a/ARCHITECTURE_ESSENTIALS.md +++ b/ARCHITECTURE_ESSENTIALS.md @@ -10,7 +10,9 @@ ## Contract surface ``` +__constructor(maximum_amounts: Map) -> result // SOURCE ONLY (Phase E5); all caps must be positive create_escrow(payer, payee, token, amount, condition_ref, releaser, refund_after) -> escrow_id // IMPLEMENTED + LIVE ON TESTNET (Phase E1 + E2 + E4) +top_up(escrow_id, payer, amount) -> result // SOURCE ONLY (issue #56); original payer only, cumulative token cap applies get_status(escrow_id) -> EscrowStatus // enum: Funded | Released | Refunded — IMPLEMENTED + LIVE ON TESTNET (Phase E1) get_escrow(escrow_id) -> EscrowData // read-only: payer, payee, token, amount, condition_ref, status, releaser, refund_after — IMPLEMENTED + LIVE ON TESTNET (Phase E4) release(escrow_id, caller) -> result // IMPLEMENTED + LIVE ON TESTNET (Phase E2) — only escrow_id's `releaser` may call this, only while Funded @@ -21,7 +23,7 @@ refund(escrow_id, caller) -> result // IMPLEMENTED + LIVE ON TESTNET (Phase E by Phase E4, both last; no behavioral difference beyond the added params, just noting it so this doc and `src/lib.rs` don't drift. -All five functions are proven live on testnet as of the Phase E4 +The five pre-E5 functions are proven live on testnet as of the Phase E4 redeployment (`docs/testnet-deployments.md`, 2026-10-08 entry) — including a real timeout refund triggered by a non-releaser after its deadline, a real `release` after the deadline, and real rejections of a @@ -32,6 +34,11 @@ deployments are superseded (their contract IDs predate the `refund_after` param and `EscrowData` layout) and are kept only as historical record. +The E5 constructor cap map and `top_up` are source changes only until +generated bindings are refreshed and a new testnet deployment is +verified. A cap bump requires a new contract deployment and settlement +of escrows at the old contract ID; see ADR 0006. + `refund_after` is an optional unix timestamp in seconds set once at creation (`docs/adr/0004`): after it passes **anyone** may call `refund`, but the destination is unchanged — always the stored `payer` — diff --git a/README.md b/README.md index 80c2a243..eba50a29 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ Two reasons, stated plainly rather than blended together: | Function | Status | Description | |---|---|---| +| `__constructor(maximum_amounts)` | ✅ Implemented in source | Installs positive per-token maximum amounts; tokens absent from the map are rejected. | | `create_escrow(payer, payee, token, amount, condition_ref, releaser, refund_after) -> escrow_id` | ✅ Implemented | Pulls `amount` of `token` from `payer` (requires `payer` auth), stores an opaque `condition_ref`, sets status `Funded`. Rejects non-positive amounts, and any `refund_after` that isn't strictly in the future. | +| `top_up(escrow_id, payer, amount) -> Result<()>` | ✅ Implemented in source | Adds funds to a funded escrow, only with the original payer's authorization, while keeping the total at or below that token's deployment-time cap. | | `get_status(escrow_id) -> EscrowStatus` | ✅ Implemented | Read-only. Errors on an unknown id. | | `get_escrow(escrow_id) -> EscrowData` | ✅ Implemented | Read-only: everything stored about the escrow, including `releaser` and `refund_after`, so payees can check the deal before relying on it. | | `release(escrow_id, caller) -> Result<()>` | ✅ Implemented | Restricted to the escrow's designated `releaser`; moves funds to `payee`. Never gated by `refund_after`. | @@ -57,6 +59,14 @@ T2): instead of locking funds permanently, a deadline lets anyone return them to the payer. See [ADR 0004](docs/adr/0004-timeout-refund-path.md) for the decision and what it means for payees. +Each deployment now also receives an immutable map of maximum funded +amounts keyed by token address. Tokens omitted from the map are rejected; +the cap applies to each escrow's total, including later top-ups. Raising +a cap requires a new deployment and migration to a new contract ID. See +[ADR 0006](docs/adr/0006-per-token-escrow-caps.md). The generated SDK +bindings still need regeneration before the new constructor and `top_up` +surface can be used through the TypeScript client. + **Design rule, enforced deliberately:** the contract decides **who** may call `release`/`refund`, never **why**. `condition_ref` is an opaque string the contract stores but never interprets — all domain diff --git a/ROADMAP.md b/ROADMAP.md index 3894e468..4e6dc8eb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,7 +3,7 @@ > Update this file on every contribution that starts/completes/blocks an > item below. -**Status: Phase E1 done. Phase E2 done and live on testnet. Phase E3's `create_escrow`/`get_status`/`release`/`refund` wrapping done; SDK distribution decided (issue #5 closed — GitHub Release tarball, ADR 0003). Phase E4 (security review, issue #6) in progress: threat model drafted (`docs/threat-model.md`) and the timeout-refund item closed (issue #11 — ADR 0004 + implementation, live on testnet); independent review not yet done — blocking for any mainnet use.** +**Status: Phase E1 done. Phase E2 done and live on testnet. Phase E3's `create_escrow`/`get_status`/`release`/`refund` wrapping done; SDK distribution decided (issue #5 closed — GitHub Release tarball, ADR 0003). Phase E4 (security review, issue #6) in progress: threat model drafted (`docs/threat-model.md`) and the timeout-refund item closed (issue #11 — ADR 0004 + implementation, live on testnet); independent review not yet done — blocking for any mainnet use. Phase E5 contract features are in progress; new constructor and `top_up` bindings need regeneration before SDK use.** ## Phase E1 — Contract skeleton - [x] Soroban project scaffold (`contracts/escrow`), builds to wasm @@ -122,6 +122,14 @@ full-record read (Medium), [#14](https://github.com/DocForum/docforum-escrow/issues/14) offline SDK unit tests in CI (Medium). +## Phase E5 — Contract features +- [x] Immutable per-token escrow maximum (issue #56): deployments accept + a constructor map of token caps; tokens without entries are rejected. + `create_escrow` checks the cap, and `top_up` requires the original + payer and enforces the cumulative cap. See ADR 0006. Regression tests + cover at-cap, over-cap, missing-cap, and cumulative top-up behavior. + SDK bindings and a testnet deployment still need regeneration/release. + ## Explicitly out of scope for this repo - Any healthcare-specific logic (see README). - Fiat on/off-ramp, KYC — that's `docforum-core`'s concern if/when it @@ -129,6 +137,10 @@ repo's. ## Changelog +- 2026-10-16 — Issue #56 adds immutable per-token escrow caps at + construction, rejects missing or exceeded caps, and adds payer-only + `top_up` bounded by the cumulative escrow amount. ADR 0006 records the + staged rollout; SDK bindings and testnet redeployment remain pending. - 2026-09-09 — Repo created as part of the DocForum 3-repo org split. - 2026-09-14 — Phase E1 implemented: `create_escrow`/`get_status` on `soroban-sdk` 27.0.6, 4 tests passing, wasm build verified. Testnet diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index a900b94c..55c0cc6e 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -14,7 +14,7 @@ // consuming application (docforum-core), never here. #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, Env, String}; +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, Env, Map, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] @@ -46,6 +46,7 @@ pub struct EscrowData { enum DataKey { Escrow(u64), NextId, + MaximumAmounts, } #[contracterror] @@ -64,6 +65,12 @@ pub enum Error { /// across `create_escrow`'s transfer (a lying or short-delivering /// token) — see docs/threat-model.md T5. BalanceMismatch = 6, + /// The token pool does not cover the pending payout. + PoolInsufficient = 7, + /// The token has no immutable deployment-time cap configured. + TokenCapNotConfigured = 8, + /// The escrow amount or cumulative top-up would exceed its token cap. + AmountCapExceeded = 9, } #[contract] @@ -71,6 +78,18 @@ pub struct EscrowContract; #[contractimpl] impl EscrowContract { + /// Sets the immutable maximum funded escrow amount for each token. + /// Tokens absent from the map are not accepted. + pub fn __constructor(env: Env, maximum_amounts: Map) -> Result<(), Error> { + for (_, maximum) in maximum_amounts.iter() { + if maximum <= 0 { + return Err(Error::InvalidAmount); + } + } + env.storage().instance().set(&DataKey::MaximumAmounts, &maximum_amounts); + Ok(()) + } + /// Locks `amount` of `token` pulled from `payer`, held for `payee`, /// tagged with an opaque `condition_ref`. `releaser` is the only /// address that will later be permitted to call `release`/`refund` @@ -92,6 +111,7 @@ impl EscrowContract { if amount <= 0 { return Err(Error::InvalidAmount); } + Self::check_token_cap(&env, &token, amount)?; if let Some(deadline) = refund_after { if deadline <= env.ledger().timestamp() { return Err(Error::InvalidRefundAfter); @@ -123,6 +143,43 @@ impl EscrowContract { Ok(id) } + /// Adds funds to an existing funded escrow, bounded by the same + /// immutable per-token maximum used when it was created. Only its + /// original payer may authorize a top-up. + pub fn top_up(env: Env, escrow_id: u64, payer: Address, amount: i128) -> Result<(), Error> { + if amount <= 0 { + return Err(Error::InvalidAmount); + } + payer.require_auth(); + + let mut data: EscrowData = env + .storage() + .persistent() + .get(&DataKey::Escrow(escrow_id)) + .ok_or(Error::NotFound)?; + if payer != data.payer { + return Err(Error::Unauthorized); + } + if data.status != EscrowStatus::Funded { + return Err(Error::NotFunded); + } + let new_amount = data.amount.checked_add(amount).ok_or(Error::AmountCapExceeded)?; + Self::check_token_cap(&env, &data.token, new_amount)?; + + let token_client = token::Client::new(&env, &data.token); + let contract_address = env.current_contract_address(); + let balance_before = token_client.balance(&contract_address); + token_client.transfer(&payer, &contract_address, &amount); + let balance_after = token_client.balance(&contract_address); + if balance_after.checked_sub(balance_before) != Some(amount) { + return Err(Error::BalanceMismatch); + } + + data.amount = new_amount; + env.storage().persistent().set(&DataKey::Escrow(escrow_id), &data); + Ok(()) + } + /// Read-only status query. Errors if `escrow_id` doesn't exist. pub fn get_status(env: Env, escrow_id: u64) -> Result { let data: EscrowData = env @@ -222,4 +279,19 @@ impl EscrowContract { env.storage().instance().set(&DataKey::NextId, &(id + 1)); id } + + fn check_token_cap(env: &Env, token: &Address, amount: i128) -> Result<(), Error> { + let maximum_amounts: Map = env + .storage() + .instance() + .get(&DataKey::MaximumAmounts) + .unwrap_or(Map::new(env)); + let maximum = maximum_amounts + .get(token.clone()) + .ok_or(Error::TokenCapNotConfigured)?; + if amount > maximum { + return Err(Error::AmountCapExceeded); + } + Ok(()) + } } diff --git a/contracts/escrow/tests/create_escrow.rs b/contracts/escrow/tests/create_escrow.rs index e43daa09..9110df8a 100644 --- a/contracts/escrow/tests/create_escrow.rs +++ b/contracts/escrow/tests/create_escrow.rs @@ -3,11 +3,11 @@ // // release()/refund() are Phase E2 — see tests/release_refund.rs. -use docforum_escrow::{EscrowContract, EscrowContractClient, EscrowStatus}; +use docforum_escrow::{Error, EscrowContract, EscrowContractClient, EscrowStatus}; use soroban_sdk::{ testutils::Address as _, token::{StellarAssetClient, TokenClient}, - Address, Env, String, + Address, Env, Map, String, }; fn setup<'a>(env: &Env) -> (Address, TokenClient<'a>, StellarAssetClient<'a>) { @@ -18,6 +18,12 @@ fn setup<'a>(env: &Env) -> (Address, TokenClient<'a>, StellarAssetClient<'a>) { (contract_id.address(), token, asset) } +fn register_contract(env: &Env, token: &Address, maximum: i128) -> Address { + let mut maximum_amounts = Map::new(env); + maximum_amounts.set(token.clone(), maximum); + env.register(EscrowContract, (maximum_amounts,)) +} + #[test] fn create_escrow_locks_funds_and_reports_funded() { let env = Env::default(); @@ -29,7 +35,7 @@ fn create_escrow_locks_funds_and_reports_funded() { let releaser = Address::generate(&env); asset.mint(&payer, &1_000); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_contract(&env, &token_id, 1_000); let client = EscrowContractClient::new(&env, &contract_id); let escrow_id = client.create_escrow( @@ -59,7 +65,7 @@ fn create_escrow_assigns_sequential_ids() { let releaser = Address::generate(&env); asset.mint(&payer, &1_000); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_contract(&env, &token_id, 1_000); let client = EscrowContractClient::new(&env, &contract_id); let first = client.create_escrow(&payer, &payee, &token_id, &100, &String::from_str(&env, "ref-a"), &releaser, &None); @@ -79,7 +85,7 @@ fn create_escrow_rejects_non_positive_amount() { let payee = Address::generate(&env); let releaser = Address::generate(&env); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_contract(&env, &token_id, 1_000); let client = EscrowContractClient::new(&env, &contract_id); let result = client.try_create_escrow(&payer, &payee, &token_id, &0, &String::from_str(&env, "ref"), &releaser, &None); @@ -91,9 +97,81 @@ fn get_status_errors_for_unknown_escrow() { let env = Env::default(); env.mock_all_auths(); - let contract_id = env.register(EscrowContract, ()); + let contract_id = env.register(EscrowContract, (Map::::new(&env),)); let client = EscrowContractClient::new(&env, &contract_id); let result = client.try_get_status(&999); assert!(result.is_err()); } + +#[test] +fn create_escrow_accepts_amount_at_token_cap_and_rejects_over_cap() { + let env = Env::default(); + env.mock_all_auths(); + let (token_id, _token, asset) = setup(&env); + let payer = Address::generate(&env); + let payee = Address::generate(&env); + let releaser = Address::generate(&env); + asset.mint(&payer, &1_000); + let contract_id = register_contract(&env, &token_id, 300); + let client = EscrowContractClient::new(&env, &contract_id); + + let at_cap = client.create_escrow( + &payer, &payee, &token_id, &300, + &String::from_str(&env, "at-cap"), &releaser, &None, + ); + let over_cap = client.try_create_escrow( + &payer, &payee, &token_id, &301, + &String::from_str(&env, "over-cap"), &releaser, &None, + ); + + assert_eq!(at_cap, 0); + assert_eq!(over_cap, Err(Ok(Error::AmountCapExceeded))); +} + +#[test] +fn token_without_a_deployment_cap_is_rejected() { + let env = Env::default(); + env.mock_all_auths(); + let (token_id, _token, asset) = setup(&env); + let payer = Address::generate(&env); + asset.mint(&payer, &1_000); + let contract_id = env.register(EscrowContract, (Map::::new(&env),)); + let client = EscrowContractClient::new(&env, &contract_id); + + let result = client.try_create_escrow( + &payer, + &Address::generate(&env), + &token_id, + &1, + &String::from_str(&env, "uncapped-token"), + &Address::generate(&env), + &None, + ); + + assert_eq!(result, Err(Ok(Error::TokenCapNotConfigured))); +} + +#[test] +fn top_up_cannot_raise_funded_escrow_above_token_cap() { + let env = Env::default(); + env.mock_all_auths(); + let (token_id, token, asset) = setup(&env); + let payer = Address::generate(&env); + let payee = Address::generate(&env); + let releaser = Address::generate(&env); + asset.mint(&payer, &1_000); + let contract_id = register_contract(&env, &token_id, 500); + let client = EscrowContractClient::new(&env, &contract_id); + let escrow_id = client.create_escrow( + &payer, &payee, &token_id, &300, + &String::from_str(&env, "top-up"), &releaser, &None, + ); + + client.top_up(&escrow_id, &payer, &200); + let over_cap = client.try_top_up(&escrow_id, &payer, &1); + + assert_eq!(client.get_escrow(&escrow_id).amount, 500); + assert_eq!(over_cap, Err(Ok(Error::AmountCapExceeded))); + assert_eq!(token.balance(&payer), 500); +} diff --git a/contracts/escrow/tests/malicious_token.rs b/contracts/escrow/tests/malicious_token.rs index b421e503..02236a8b 100644 --- a/contracts/escrow/tests/malicious_token.rs +++ b/contracts/escrow/tests/malicious_token.rs @@ -18,7 +18,7 @@ use docforum_escrow::{EscrowContract, EscrowContractClient, Error}; use soroban_sdk::{ contract, contractimpl, contracttype, testutils::Address as _, - Address, Env, String, + Address, Env, Map, String, }; #[contracttype] @@ -30,6 +30,12 @@ enum MockKey { AttackCaller, } +fn register_escrow(env: &Env, token: &Address) -> Address { + let mut maximum_amounts = Map::new(env); + maximum_amounts.set(token.clone(), 1_000); + env.register(EscrowContract, (maximum_amounts,)) +} + fn mock_balance(env: &Env, id: &Address) -> i128 { env.storage() .persistent() @@ -154,7 +160,7 @@ fn create_escrow_rejects_lying_transfer() { let payee = Address::generate(&env); let releaser = Address::generate(&env); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_escrow(&env, &token_id); let client = EscrowContractClient::new(&env, &contract_id); let result = client.try_create_escrow( @@ -188,7 +194,7 @@ fn create_escrow_rejects_short_delivery() { let payee = Address::generate(&env); let releaser = Address::generate(&env); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_escrow(&env, &token_id); let client = EscrowContractClient::new(&env, &contract_id); let result = client.try_create_escrow( @@ -223,7 +229,7 @@ fn reentrant_transfer_cannot_double_release() { let payee = Address::generate(&env); let releaser = Address::generate(&env); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_escrow(&env, &token_id); let client = EscrowContractClient::new(&env, &contract_id); // Creating with this token is fine: the transfer in is honest, and diff --git a/contracts/escrow/tests/release_refund.rs b/contracts/escrow/tests/release_refund.rs index 00ec39e9..993222e1 100644 --- a/contracts/escrow/tests/release_refund.rs +++ b/contracts/escrow/tests/release_refund.rs @@ -9,7 +9,7 @@ use docforum_escrow::{EscrowContract, EscrowContractClient, EscrowStatus}; use soroban_sdk::{ testutils::Address as _, token::{StellarAssetClient, TokenClient}, - Address, Env, String, + Address, Env, Map, String, }; fn setup<'a>(env: &Env) -> (Address, TokenClient<'a>, StellarAssetClient<'a>) { @@ -36,7 +36,9 @@ fn fund_escrow(env: &Env, amount: i128) -> Fixture<'_> { let releaser = Address::generate(env); asset.mint(&payer, &1_000); - let contract_id = env.register(EscrowContract, ()); + let mut maximum_amounts = Map::new(env); + maximum_amounts.set(token_id.clone(), 1_000); + let contract_id = env.register(EscrowContract, (maximum_amounts,)); let client = EscrowContractClient::new(env, &contract_id); let escrow_id = client.create_escrow( diff --git a/contracts/escrow/tests/timeout_refund.rs b/contracts/escrow/tests/timeout_refund.rs index f98f8886..78020116 100644 --- a/contracts/escrow/tests/timeout_refund.rs +++ b/contracts/escrow/tests/timeout_refund.rs @@ -14,7 +14,7 @@ use docforum_escrow::{EscrowContract, EscrowContractClient, EscrowStatus}; use soroban_sdk::{ testutils::{Address as _, Ledger as _}, token::{StellarAssetClient, TokenClient}, - Address, Env, String, + Address, Env, Map, String, }; /// Ledger close time at escrow creation. @@ -30,6 +30,12 @@ fn setup<'a>(env: &Env) -> (Address, TokenClient<'a>, StellarAssetClient<'a>) { (contract_id.address(), token, asset) } +fn register_escrow(env: &Env, token: &Address) -> Address { + let mut maximum_amounts = Map::new(env); + maximum_amounts.set(token.clone(), 1_000); + env.register(EscrowContract, (maximum_amounts,)) +} + struct Fixture<'a> { client: EscrowContractClient<'a>, token: TokenClient<'a>, @@ -49,7 +55,7 @@ fn fund_escrow(env: &Env, refund_after: Option) -> Fixture<'_> { let releaser = Address::generate(env); asset.mint(&payer, &1_000); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_escrow(env, &token_id); let client = EscrowContractClient::new(env, &contract_id); let escrow_id = client.create_escrow( @@ -110,7 +116,7 @@ fn create_escrow_rejects_deadline_already_passed_or_now() { asset.mint(&payer, &1_000); env.ledger().set_timestamp(CREATED_AT); - let contract_id = env.register(EscrowContract, ()); + let contract_id = register_escrow(&env, &token_id); let client = EscrowContractClient::new(&env, &contract_id); let now = client.try_create_escrow( @@ -353,7 +359,7 @@ fn get_escrow_errors_for_unknown_escrow() { let env = Env::default(); env.mock_all_auths(); - let contract_id = env.register(EscrowContract, ()); + let contract_id = env.register(EscrowContract, (Map::::new(&env),)); let client = EscrowContractClient::new(&env, &contract_id); let result = client.try_get_escrow(&999); diff --git a/docs/adr/0006-per-token-escrow-caps.md b/docs/adr/0006-per-token-escrow-caps.md new file mode 100644 index 00000000..0c6ddb75 --- /dev/null +++ b/docs/adr/0006-per-token-escrow-caps.md @@ -0,0 +1,46 @@ +# ADR 0006: Immutable per-token escrow caps + +**Status:** accepted for the guarded beta + +## Context + +The contract holds all escrows for a token in one shared balance. A +deployment-time cap limits the amount one escrow can commit, including +later top-ups, and gives a staged launch a fixed bound that cannot be +raised by an administrator key. + +## Decision + +The constructor receives a map from token contract address to a positive +maximum amount in that token's smallest units. A token without an entry is +not accepted. `create_escrow` and `top_up` enforce the cap; top-ups also +require the original payer and an escrow that remains funded. A top-up +cannot make the total escrow amount exceed the configured maximum. + +There is no batch-creation entry point today. If one is added, it must +apply the same per-token check to every requested escrow before accepting +the batch. + +## Staged launch plan + +For each allowlisted token, choose a cap in its smallest units based on +the token's decimals and the intended initial beta exposure. Start at +25% of the reviewed beta target, increase to 50%, then 100% only after +the preceding stage has been monitored and reviewed. Each increase +requires deploying a new contract with a new cap map and moving new +escrows to its new contract ID; the existing deployment remains capped +and must settle its outstanding escrows before it is retired. This is a +conservative operational plan, not a mainnet authorization: the security +review in Phase E4 remains a hard prerequisite for any mainnet use. + +## Consequences + +- Caps are immutable for a deployment, which limits unilateral changes. +- An omitted token is rejected, avoiding accidental uncapped assets. +- Redeployment changes the contract ID and requires an explicit + migration and settlement plan. +- Caps limit individual escrow exposure, not aggregate liabilities in a + shared token pool; the solvency guard and token allowlist remain + necessary. +- The `create_escrow` constructor surface and `top_up` method require + regenerated SDK bindings before the TypeScript client can expose them. diff --git a/docs/testnet-deployments.md b/docs/testnet-deployments.md index 824e3a7b..3acc42b4 100644 --- a/docs/testnet-deployments.md +++ b/docs/testnet-deployments.md @@ -17,6 +17,11 @@ layout changed, so the old contract ID is no longer signature- or layout-compatible with the current contract source. The prior entries are kept as accurate history, not deleted. +This 2026-10-08 deployment predates issue #56's constructor cap map and +`top_up`; it has no per-token maximum. It must not be treated as a capped +beta deployment. A new deployment with an explicit cap map and regenerated +bindings is required before using the capped contract surface. + ### Verified live, on this deployment Every claim below is a real transaction or a real contract error From e124ae02051faf0f7eb8d7c1ee7f4204d75c3fc7 Mon Sep 17 00:00:00 2001 From: nasirudeenbadirudeen87-cloud <318769136+nasirudeenbadirudeen87-cloud@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:38:39 +0100 Subject: [PATCH 2/3] fix(contract): reserve unique error codes --- contracts/escrow/src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 55c0cc6e..ed1e6ab0 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -47,6 +47,7 @@ enum DataKey { Escrow(u64), NextId, MaximumAmounts, + Vesting(u64), } #[contracterror] @@ -66,11 +67,13 @@ pub enum Error { /// token) — see docs/threat-model.md T5. BalanceMismatch = 6, /// The token pool does not cover the pending payout. - PoolInsufficient = 7, + PoolInsufficient = 21, /// The token has no immutable deployment-time cap configured. - TokenCapNotConfigured = 8, + TokenCapNotConfigured = 22, /// The escrow amount or cumulative top-up would exceed its token cap. - AmountCapExceeded = 9, + AmountCapExceeded = 23, + /// A streamed escrow has a fixed funded total. + StreamingTopUpUnsupported = 28, } #[contract] @@ -163,6 +166,9 @@ impl EscrowContract { if data.status != EscrowStatus::Funded { return Err(Error::NotFunded); } + if env.storage().persistent().has(&DataKey::Vesting(escrow_id)) { + return Err(Error::StreamingTopUpUnsupported); + } let new_amount = data.amount.checked_add(amount).ok_or(Error::AmountCapExceeded)?; Self::check_token_cap(&env, &data.token, new_amount)?; From ba54b5c5098a787aa43a4232452c6fac56613534 Mon Sep 17 00:00:00 2001 From: nasirudeenbadirudeen87-cloud <232936640+nasirudeenbadirudeen87-cloud@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:28:35 +0100 Subject: [PATCH 3/3] chore(sdk): refresh generated token cap bindings --- sdk/src/generated/contract-client.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/src/generated/contract-client.ts b/sdk/src/generated/contract-client.ts index d8766d82..4752baa2 100644 --- a/sdk/src/generated/contract-client.ts +++ b/sdk/src/generated/contract-client.ts @@ -166,13 +166,13 @@ export const Errors = { 34: {message:"InvalidFundingDeadline"}, /** * The deployment sets per-token caps and `token` has none — see - * docs/adr/0012. Code 35 is left free for the pre-payout solvency + * docs/adr/0013. Code 35 is left free for the pre-payout solvency * check proposed in issue #38. */ 36: {message:"TokenCapNotConfigured"}, /** * The escrow amount, a top-up's resulting total, or a pooled funding - * target exceeds the token's deploy-time cap — see docs/adr/0012. + * target exceeds the token's deploy-time cap — see docs/adr/0013. */ 37: {message:"AmountCapExceeded"} } @@ -573,7 +573,7 @@ export class Client extends ContractClient { } constructor(public readonly options: ContractClientOptions) { super( - new ContractSpec([ "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAIgAAAAAAAAAITm90Rm91bmQAAAABAAAAAAAAAA1JbnZhbGlkQW1vdW50AAAAAAAAAgAAAAAAAAAMVW5hdXRob3JpemVkAAAAAwAAAAAAAAAJTm90RnVuZGVkAAAAAAAABAAAALpgcmVmdW5kX2FmdGVyYCB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24g4oCUIHNlZQpkb2NzL2Fkci8wMDA0OiBhIGRlYWRsaW5lIHRoYXQgaGFzIGFscmVhZHkgcGFzc2VkIHdvdWxkIGxldAphbnlvbmUgcmVmdW5kIHRoZSBlc2Nyb3cgYmVmb3JlIHRoZSByZWxlYXNlciBjb3VsZCBldmVyIGFjdC4AAAAAABJJbnZhbGlkUmVmdW5kQWZ0ZXIAAAAAAAUAAACjVGhlIGNvbnRyYWN0J3MgdG9rZW4gYmFsYW5jZSBkaWRuJ3QgcmlzZSBieSBleGFjdGx5IGBhbW91bnRgCmFjcm9zcyBgY3JlYXRlX2VzY3Jvd2AncyB0cmFuc2ZlciAoYSBseWluZyBvciBzaG9ydC1kZWxpdmVyaW5nCnRva2VuKSDigJQgc2VlIGRvY3MvdGhyZWF0LW1vZGVsLm1kIFQ1LgAAAAAPQmFsYW5jZU1pc21hdGNoAAAAAAYAAAEAYGNvbmRpdGlvbl9yZWZgIGV4Y2VlZGVkIHRoZSBtYXhpbXVtIGFsbG93ZWQgbGVuZ3RoIChjdXJyZW50bHkKNjQgYnl0ZXMpIOKAlCBzZWUgZG9jcy90aHJlYXQtbW9kZWwubWQgVDEwIGFuZCBST0FETUFQLm1kIFBoYXNlIEU1LgpCb3VuZGVkIHRvIGtlZXAgc3RvcmFnZSBhbmQgZXZlbnQgY29zdHMgcHJlZGljdGFibGU7IHJlamVjdCBhdApjcmVhdGlvbiBzbyBubyBvbi1jaGFpbiBzdG9yYWdlIGlzIHdhc3RlZCBvbiBhbiBvdmVyc2l6ZWQgcmVmLgAAABNDb25kaXRpb25SZWZUb29Mb25nAAAAAAcAAAA2YHRva2VuYCBpcyBub3QgaW4gdGhlIGltbXV0YWJsZSBkZXBsb3ktdGltZSBhbGxvd2xpc3QuAAAAAAAPRGlzYWxsb3dlZFRva2VuAAAAAAgAAACDVGhlIG9wdGVkLWluIHBheWVlIGNhbGxiYWNrIHJldHVybmVkIGFuIGVycm9yIG9yIGNvdWxkIG5vdCBiZQppbnZva2VkLiBUaGUgcmVsZWFzZSB0cmFuc2FjdGlvbiAoaW5jbHVkaW5nIGl0cyB0cmFuc2Zlcikgcm9sbHMgYmFjay4AAAAADkNhbGxiYWNrRmFpbGVkAAAAAAAJAAAAAAAAAAhPdmVyZmxvdwAAAAwAAABsVGhlIHJlbGVhc2VyIHNldCBpcyBlbXB0eSwgb3IgYHRocmVzaG9sZGAgaXMgMCBvciBsYXJnZXIgdGhhbiB0aGUKbnVtYmVyIG9mIHJlbGVhc2VycyDigJQgc2VlIGRvY3MvYWRyLzAwMDcuAAAAEEludmFsaWRUaHJlc2hvbGQAAAANAAAAQ01vcmUgdGhhbiBgTUFYX1JFTEVBU0VSU2AgKDEwKSBhZGRyZXNzZXMgd2VyZSBzdXBwbGllZCBhdCBjcmVhdGlvbi4AAAAAEFRvb01hbnlSZWxlYXNlcnMAAAAOAAAAn0Zld2VyIGRpc3RpbmN0IGFwcHJvdmFscyB3ZXJlIHN1cHBsaWVkIHRoYW4gdGhlIGVzY3JvdydzIHRocmVzaG9sZCwKb3IgYSBzaW5nbGUgcmVsZWFzZXIgdHJpZWQgdG8gc2V0dGxlIGEgcXVvcnVtIGVzY3JvdyB0aHJvdWdoIHRoZQpzaW5nbGUtY2FsbGVyIGVudHJ5IHBvaW50LgAAAAAPVGhyZXNob2xkTm90TWV0AAAAAA8AAABoVGhlIHNhbWUgYWRkcmVzcyBhcHBlYXJlZCB0d2ljZSBpbiBvbmUgYXBwcm92YWwgbGlzdC4gRHVwbGljYXRlcwpuZXZlciBjb3VudCB0d2ljZSB0b3dhcmQgdGhlIHRocmVzaG9sZC4AAAARRHVwbGljYXRlQXBwcm92ZXIAAAAAAAAQAAAAQFRoZSBzYW1lIGFkZHJlc3MgYXBwZWFyZWQgdHdpY2UgaW4gdGhlIHJlbGVhc2VyIHNldCBhdCBjcmVhdGlvbi4AAAARRHVwbGljYXRlUmVsZWFzZXIAAAAAAAARAAAAAAAAABJBY2NlcHRhbmNlUmVxdWlyZWQAAAAAABIAAAAAAAAAF0FjY2VwdGFuY2VOb3RDb25maWd1cmVkAAAAABMAAAAAAAAAD0FscmVhZHlBY2NlcHRlZAAAAAAUAAAAOFRoZSBuZXcgcmVsZWFzZXIgY2Fubm90IGJlIHRoZSBlc2Nyb3cgY29udHJhY3Qgb3IgcGF5ZWUuAAAAD0ludmFsaWRSZWxlYXNlcgAAAAAYAAAAfGByZWZ1bmRfdG9gIHdhcyB0aGUgZXNjcm93IGNvbnRyYWN0J3Mgb3duIGFkZHJlc3Mg4oCUIGZ1bmRzIHNlbnQKdGhlcmUgb24gcmVmdW5kIGNvdWxkIG5ldmVyIGJlIG1vdmVkIGFnYWluIChkb2NzL2Fkci8wMDA5KS4AAAAPSW52YWxpZFJlZnVuZFRvAAAAABUAAABEVGhlIGNvbmZpZ3VyZWQgcmVsZWFzZSB0aW1lIGlzIGluIHRoZSBwYXN0IG9yIGFmdGVyIHRoZSByZWZ1bmQgdGltZS4AAAAXSW52YWxpZFJlbGVhc2VOb3RCZWZvcmUAAAAAFgAAADFSZWxlYXNlIHdhcyBhdHRlbXB0ZWQgYmVmb3JlIHRoZSBjb25maWd1cmVkIHRpbWUuAAAAAAAAD1JlbGVhc2VUb29FYXJseQAAAAAXAAAArUEgYmF0Y2ggY2FsbCB3YXMgcGFzc2VkIG1vcmUgdGhhbiBgTUFYX0JBVENIX1NJWkVgIGl0ZW1zIOKAlCBzZWUKZG9jcy9hZHIvMDAwNS4gQ2FsbGVycyBtdXN0IGNodW5rLiBBcHBlbmRlZCBhdCB0aGUgZW5kIHNvIGV4aXN0aW5nCmRpc2NyaW1pbmFudHMga2VlcCB0aGVpciBvbi1jaGFpbiB2YWx1ZXMuAAAAAAAADUJhdGNoVG9vTGFyZ2UAAAAAAAAZAAAAiGBjb250cmlidXRlYC9gY2xhaW1fcmVmdW5kYC9gZ2V0X2NvbnRyaWJ1dGlvbmAgb24gYSBub24tcG9vbGVkCmVzY3Jvdywgb3IgYGdldF9wb29sZWRgIG9uIGEgbm9uLXBvb2xlZC91bmtub3duIGlkIOKAlCBzZWUKZG9jcy9hZHIvMDAxMS4AAAAJTm90UG9vbGVkAAAAAAAAGgAAAL9BIGNvbnRyaWJ1dGlvbiBhcnJpdmVkIGFmdGVyIHRoZSBwb29sZWQgZXNjcm93J3MgZnVuZGluZyBkZWFkbGluZS4KTGF0ZSBjb250cmlidXRpb25zIGNhbiBuZXZlciBiZSBhY2NlcHRlZDogdGhlIHJvdW5kJ3Mgb3V0Y29tZQoocmVsZWFzZSB2cyBwZXItY29udHJpYnV0b3IgcmVmdW5kcykgaXMgZml4ZWQgYXQgdGhlIGRlYWRsaW5lLgAAAAAVRnVuZGluZ0RlYWRsaW5lUGFzc2VkAAAAAAAAGwAAAL9BIGNvbnRyaWJ1dGlvbiB3b3VsZCBwdXNoIHRoZSBwb29sZWQgdG90YWwgcGFzdCB0aGUgZnVuZGluZyB0YXJnZXQKKG92ZXJmdW5kaW5nIGlzIHJlamVjdGVkLCBub3QgYWNjZXB0ZWQpLCB0aGUgdGFyZ2V0IGlzIGFscmVhZHkgbWV0LApvciBhIHJlZnVuZCB3YXMgY2xhaW1lZCBvbiBhIGZ1bGx5IGZ1bmRlZCBwb29sZWQgZXNjcm93LgAAAAAURnVuZGluZ1RhcmdldFJlYWNoZWQAAAAcAAAAdGByZWxlYXNlYCBvbiBhIHBvb2xlZCBlc2Nyb3cgd2hvc2UgZnVuZGVkIHRvdGFsIGlzIGJlbG93IHRoZQpmdW5kaW5nIHRhcmdldCDigJQgcGFydGlhbCBmdW5kaW5nIGlzIG5ldmVyIHJlbGVhc2FibGUuAAAAEUZ1bmRpbmdJbmNvbXBsZXRlAAAAAAAAHQAAAIZgY2xhaW1fcmVmdW5kYCBiZWZvcmUgdGhlIGZ1bmRpbmcgZGVhZGxpbmUgcGFzc2VkIG9uIGEgcm91bmQgdGhhdAppcyBzdGlsbCAob3Igbm93IGZ1bGx5KSBmdW5kZWQuIENsYWltcyBvcGVuIG9ubHkgZm9yIGEgZmFpbGVkIHJvdW5kLgAAAAAADVJlZnVuZE5vdE9wZW4AAAAAAAAeAAAAM1RoZSBzYW1lIGNvbnRyaWJ1dG9yIHRyaWVkIHRvIGNsYWltIGEgcmVmdW5kIHR3aWNlLgAAAAAPQWxyZWFkeVJlZnVuZGVkAAAAAB8AAAA7YGNsYWltX3JlZnVuZGAgYnkgYW4gYWRkcmVzcyB3aXRoIG5vIHJlY29yZGVkIGNvbnRyaWJ1dGlvbi4AAAAADk5vQ29udHJpYnV0aW9uAAAAAAAgAAAA82ByZWZ1bmRgL2ByZWZ1bmRfd2l0aF9hcHByb3ZhbHNgL2BjYW5jZWxgL2B0b3BfdXBgIG9uIGEgcG9vbGVkCmVzY3JvdzogcHVzaGluZyB0aGUgd2hvbGUgcG9vbGVkIHRvdGFsIHRvIG9uZSBhZGRyZXNzIHdvdWxkIHZpb2xhdGUKcGVyLWNvbnRyaWJ1dG9yIGFjY291bnRpbmcuIFBvb2xlZCByZWZ1bmRzIGFyZSBwdWxsLWJhc2VkCihgY2xhaW1fcmVmdW5kYCk7IHBvb2xlZCBmdW5kaW5nIGlzIGBjb250cmlidXRlYC1vbmx5LgAAAAAVTm90U3VwcG9ydGVkRm9yUG9vbGVkAAAAAAAAIQAAAEBUaGUgZnVuZGluZyBkZWFkbGluZSB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24uAAAAFkludmFsaWRGdW5kaW5nRGVhZGxpbmUAAAAAACIAAACcVGhlIGRlcGxveW1lbnQgc2V0cyBwZXItdG9rZW4gY2FwcyBhbmQgYHRva2VuYCBoYXMgbm9uZSDigJQgc2VlCmRvY3MvYWRyLzAwMTIuIENvZGUgMzUgaXMgbGVmdCBmcmVlIGZvciB0aGUgcHJlLXBheW91dCBzb2x2ZW5jeQpjaGVjayBwcm9wb3NlZCBpbiBpc3N1ZSAjMzguAAAAFVRva2VuQ2FwTm90Q29uZmlndXJlZAAAAAAAACQAAACEVGhlIGVzY3JvdyBhbW91bnQsIGEgdG9wLXVwJ3MgcmVzdWx0aW5nIHRvdGFsLCBvciBhIHBvb2xlZCBmdW5kaW5nCnRhcmdldCBleGNlZWRzIHRoZSB0b2tlbidzIGRlcGxveS10aW1lIGNhcCDigJQgc2VlIGRvY3MvYWRyLzAwMTIuAAAAEUFtb3VudENhcEV4Y2VlZGVkAAAAAAAAJQ==", + new ContractSpec([ "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAIgAAAAAAAAAITm90Rm91bmQAAAABAAAAAAAAAA1JbnZhbGlkQW1vdW50AAAAAAAAAgAAAAAAAAAMVW5hdXRob3JpemVkAAAAAwAAAAAAAAAJTm90RnVuZGVkAAAAAAAABAAAALpgcmVmdW5kX2FmdGVyYCB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24g4oCUIHNlZQpkb2NzL2Fkci8wMDA0OiBhIGRlYWRsaW5lIHRoYXQgaGFzIGFscmVhZHkgcGFzc2VkIHdvdWxkIGxldAphbnlvbmUgcmVmdW5kIHRoZSBlc2Nyb3cgYmVmb3JlIHRoZSByZWxlYXNlciBjb3VsZCBldmVyIGFjdC4AAAAAABJJbnZhbGlkUmVmdW5kQWZ0ZXIAAAAAAAUAAACjVGhlIGNvbnRyYWN0J3MgdG9rZW4gYmFsYW5jZSBkaWRuJ3QgcmlzZSBieSBleGFjdGx5IGBhbW91bnRgCmFjcm9zcyBgY3JlYXRlX2VzY3Jvd2AncyB0cmFuc2ZlciAoYSBseWluZyBvciBzaG9ydC1kZWxpdmVyaW5nCnRva2VuKSDigJQgc2VlIGRvY3MvdGhyZWF0LW1vZGVsLm1kIFQ1LgAAAAAPQmFsYW5jZU1pc21hdGNoAAAAAAYAAAEAYGNvbmRpdGlvbl9yZWZgIGV4Y2VlZGVkIHRoZSBtYXhpbXVtIGFsbG93ZWQgbGVuZ3RoIChjdXJyZW50bHkKNjQgYnl0ZXMpIOKAlCBzZWUgZG9jcy90aHJlYXQtbW9kZWwubWQgVDEwIGFuZCBST0FETUFQLm1kIFBoYXNlIEU1LgpCb3VuZGVkIHRvIGtlZXAgc3RvcmFnZSBhbmQgZXZlbnQgY29zdHMgcHJlZGljdGFibGU7IHJlamVjdCBhdApjcmVhdGlvbiBzbyBubyBvbi1jaGFpbiBzdG9yYWdlIGlzIHdhc3RlZCBvbiBhbiBvdmVyc2l6ZWQgcmVmLgAAABNDb25kaXRpb25SZWZUb29Mb25nAAAAAAcAAAA2YHRva2VuYCBpcyBub3QgaW4gdGhlIGltbXV0YWJsZSBkZXBsb3ktdGltZSBhbGxvd2xpc3QuAAAAAAAPRGlzYWxsb3dlZFRva2VuAAAAAAgAAACDVGhlIG9wdGVkLWluIHBheWVlIGNhbGxiYWNrIHJldHVybmVkIGFuIGVycm9yIG9yIGNvdWxkIG5vdCBiZQppbnZva2VkLiBUaGUgcmVsZWFzZSB0cmFuc2FjdGlvbiAoaW5jbHVkaW5nIGl0cyB0cmFuc2Zlcikgcm9sbHMgYmFjay4AAAAADkNhbGxiYWNrRmFpbGVkAAAAAAAJAAAAAAAAAAhPdmVyZmxvdwAAAAwAAABsVGhlIHJlbGVhc2VyIHNldCBpcyBlbXB0eSwgb3IgYHRocmVzaG9sZGAgaXMgMCBvciBsYXJnZXIgdGhhbiB0aGUKbnVtYmVyIG9mIHJlbGVhc2VycyDigJQgc2VlIGRvY3MvYWRyLzAwMDcuAAAAEEludmFsaWRUaHJlc2hvbGQAAAANAAAAQ01vcmUgdGhhbiBgTUFYX1JFTEVBU0VSU2AgKDEwKSBhZGRyZXNzZXMgd2VyZSBzdXBwbGllZCBhdCBjcmVhdGlvbi4AAAAAEFRvb01hbnlSZWxlYXNlcnMAAAAOAAAAn0Zld2VyIGRpc3RpbmN0IGFwcHJvdmFscyB3ZXJlIHN1cHBsaWVkIHRoYW4gdGhlIGVzY3JvdydzIHRocmVzaG9sZCwKb3IgYSBzaW5nbGUgcmVsZWFzZXIgdHJpZWQgdG8gc2V0dGxlIGEgcXVvcnVtIGVzY3JvdyB0aHJvdWdoIHRoZQpzaW5nbGUtY2FsbGVyIGVudHJ5IHBvaW50LgAAAAAPVGhyZXNob2xkTm90TWV0AAAAAA8AAABoVGhlIHNhbWUgYWRkcmVzcyBhcHBlYXJlZCB0d2ljZSBpbiBvbmUgYXBwcm92YWwgbGlzdC4gRHVwbGljYXRlcwpuZXZlciBjb3VudCB0d2ljZSB0b3dhcmQgdGhlIHRocmVzaG9sZC4AAAARRHVwbGljYXRlQXBwcm92ZXIAAAAAAAAQAAAAQFRoZSBzYW1lIGFkZHJlc3MgYXBwZWFyZWQgdHdpY2UgaW4gdGhlIHJlbGVhc2VyIHNldCBhdCBjcmVhdGlvbi4AAAARRHVwbGljYXRlUmVsZWFzZXIAAAAAAAARAAAAAAAAABJBY2NlcHRhbmNlUmVxdWlyZWQAAAAAABIAAAAAAAAAF0FjY2VwdGFuY2VOb3RDb25maWd1cmVkAAAAABMAAAAAAAAAD0FscmVhZHlBY2NlcHRlZAAAAAAUAAAAOFRoZSBuZXcgcmVsZWFzZXIgY2Fubm90IGJlIHRoZSBlc2Nyb3cgY29udHJhY3Qgb3IgcGF5ZWUuAAAAD0ludmFsaWRSZWxlYXNlcgAAAAAYAAAAfGByZWZ1bmRfdG9gIHdhcyB0aGUgZXNjcm93IGNvbnRyYWN0J3Mgb3duIGFkZHJlc3Mg4oCUIGZ1bmRzIHNlbnQKdGhlcmUgb24gcmVmdW5kIGNvdWxkIG5ldmVyIGJlIG1vdmVkIGFnYWluIChkb2NzL2Fkci8wMDA5KS4AAAAPSW52YWxpZFJlZnVuZFRvAAAAABUAAABEVGhlIGNvbmZpZ3VyZWQgcmVsZWFzZSB0aW1lIGlzIGluIHRoZSBwYXN0IG9yIGFmdGVyIHRoZSByZWZ1bmQgdGltZS4AAAAXSW52YWxpZFJlbGVhc2VOb3RCZWZvcmUAAAAAFgAAADFSZWxlYXNlIHdhcyBhdHRlbXB0ZWQgYmVmb3JlIHRoZSBjb25maWd1cmVkIHRpbWUuAAAAAAAAD1JlbGVhc2VUb29FYXJseQAAAAAXAAAArUEgYmF0Y2ggY2FsbCB3YXMgcGFzc2VkIG1vcmUgdGhhbiBgTUFYX0JBVENIX1NJWkVgIGl0ZW1zIOKAlCBzZWUKZG9jcy9hZHIvMDAwNS4gQ2FsbGVycyBtdXN0IGNodW5rLiBBcHBlbmRlZCBhdCB0aGUgZW5kIHNvIGV4aXN0aW5nCmRpc2NyaW1pbmFudHMga2VlcCB0aGVpciBvbi1jaGFpbiB2YWx1ZXMuAAAAAAAADUJhdGNoVG9vTGFyZ2UAAAAAAAAZAAAAiGBjb250cmlidXRlYC9gY2xhaW1fcmVmdW5kYC9gZ2V0X2NvbnRyaWJ1dGlvbmAgb24gYSBub24tcG9vbGVkCmVzY3Jvdywgb3IgYGdldF9wb29sZWRgIG9uIGEgbm9uLXBvb2xlZC91bmtub3duIGlkIOKAlCBzZWUKZG9jcy9hZHIvMDAxMS4AAAAJTm90UG9vbGVkAAAAAAAAGgAAAL9BIGNvbnRyaWJ1dGlvbiBhcnJpdmVkIGFmdGVyIHRoZSBwb29sZWQgZXNjcm93J3MgZnVuZGluZyBkZWFkbGluZS4KTGF0ZSBjb250cmlidXRpb25zIGNhbiBuZXZlciBiZSBhY2NlcHRlZDogdGhlIHJvdW5kJ3Mgb3V0Y29tZQoocmVsZWFzZSB2cyBwZXItY29udHJpYnV0b3IgcmVmdW5kcykgaXMgZml4ZWQgYXQgdGhlIGRlYWRsaW5lLgAAAAAVRnVuZGluZ0RlYWRsaW5lUGFzc2VkAAAAAAAAGwAAAL9BIGNvbnRyaWJ1dGlvbiB3b3VsZCBwdXNoIHRoZSBwb29sZWQgdG90YWwgcGFzdCB0aGUgZnVuZGluZyB0YXJnZXQKKG92ZXJmdW5kaW5nIGlzIHJlamVjdGVkLCBub3QgYWNjZXB0ZWQpLCB0aGUgdGFyZ2V0IGlzIGFscmVhZHkgbWV0LApvciBhIHJlZnVuZCB3YXMgY2xhaW1lZCBvbiBhIGZ1bGx5IGZ1bmRlZCBwb29sZWQgZXNjcm93LgAAAAAURnVuZGluZ1RhcmdldFJlYWNoZWQAAAAcAAAAdGByZWxlYXNlYCBvbiBhIHBvb2xlZCBlc2Nyb3cgd2hvc2UgZnVuZGVkIHRvdGFsIGlzIGJlbG93IHRoZQpmdW5kaW5nIHRhcmdldCDigJQgcGFydGlhbCBmdW5kaW5nIGlzIG5ldmVyIHJlbGVhc2FibGUuAAAAEUZ1bmRpbmdJbmNvbXBsZXRlAAAAAAAAHQAAAIZgY2xhaW1fcmVmdW5kYCBiZWZvcmUgdGhlIGZ1bmRpbmcgZGVhZGxpbmUgcGFzc2VkIG9uIGEgcm91bmQgdGhhdAppcyBzdGlsbCAob3Igbm93IGZ1bGx5KSBmdW5kZWQuIENsYWltcyBvcGVuIG9ubHkgZm9yIGEgZmFpbGVkIHJvdW5kLgAAAAAADVJlZnVuZE5vdE9wZW4AAAAAAAAeAAAAM1RoZSBzYW1lIGNvbnRyaWJ1dG9yIHRyaWVkIHRvIGNsYWltIGEgcmVmdW5kIHR3aWNlLgAAAAAPQWxyZWFkeVJlZnVuZGVkAAAAAB8AAAA7YGNsYWltX3JlZnVuZGAgYnkgYW4gYWRkcmVzcyB3aXRoIG5vIHJlY29yZGVkIGNvbnRyaWJ1dGlvbi4AAAAADk5vQ29udHJpYnV0aW9uAAAAAAAgAAAA82ByZWZ1bmRgL2ByZWZ1bmRfd2l0aF9hcHByb3ZhbHNgL2BjYW5jZWxgL2B0b3BfdXBgIG9uIGEgcG9vbGVkCmVzY3JvdzogcHVzaGluZyB0aGUgd2hvbGUgcG9vbGVkIHRvdGFsIHRvIG9uZSBhZGRyZXNzIHdvdWxkIHZpb2xhdGUKcGVyLWNvbnRyaWJ1dG9yIGFjY291bnRpbmcuIFBvb2xlZCByZWZ1bmRzIGFyZSBwdWxsLWJhc2VkCihgY2xhaW1fcmVmdW5kYCk7IHBvb2xlZCBmdW5kaW5nIGlzIGBjb250cmlidXRlYC1vbmx5LgAAAAAVTm90U3VwcG9ydGVkRm9yUG9vbGVkAAAAAAAAIQAAAEBUaGUgZnVuZGluZyBkZWFkbGluZSB3YXMgbm90IHN0cmljdGx5IGluIHRoZSBmdXR1cmUgYXQgY3JlYXRpb24uAAAAFkludmFsaWRGdW5kaW5nRGVhZGxpbmUAAAAAACIAAACcVGhlIGRlcGxveW1lbnQgc2V0cyBwZXItdG9rZW4gY2FwcyBhbmQgYHRva2VuYCBoYXMgbm9uZSDigJQgc2VlCmRvY3MvYWRyLzAwMTMuIENvZGUgMzUgaXMgbGVmdCBmcmVlIGZvciB0aGUgcHJlLXBheW91dCBzb2x2ZW5jeQpjaGVjayBwcm9wb3NlZCBpbiBpc3N1ZSAjMzguAAAAFVRva2VuQ2FwTm90Q29uZmlndXJlZAAAAAAAACQAAACEVGhlIGVzY3JvdyBhbW91bnQsIGEgdG9wLXVwJ3MgcmVzdWx0aW5nIHRvdGFsLCBvciBhIHBvb2xlZCBmdW5kaW5nCnRhcmdldCBleGNlZWRzIHRoZSB0b2tlbidzIGRlcGxveS10aW1lIGNhcCDigJQgc2VlIGRvY3MvYWRyLzAwMTMuAAAAEUFtb3VudENhcEV4Y2VlZGVkAAAAAAAAJQ==", "AAAAAQAAAHJPbmUgZW50cnkgb2YgYSBgY3JlYXRlX2VzY3Jvd3NgIGJhdGNoLiBNaXJyb3JzIHRoZSBzaW5nbGUtZXNjcm93CmNyZWF0aW9uIHBhcmFtZXRlcnMgbWludXMgdGhlIGJhdGNoLXdpZGUgYHBheWVyYC4AAAAAAAAAAAAKQ3JlYXRlSXRlbQAAAAAABgAAAAAAAAAGYW1vdW50AAAAAAALAAAAAAAAAA1jb25kaXRpb25fcmVmAAAAAAAAEAAAAAAAAAAFcGF5ZWUAAAAAAAATAAAAAAAAAAxyZWZ1bmRfYWZ0ZXIAAAPoAAAABgAAAAAAAAAIcmVsZWFzZXIAAAATAAAAAAAAAAV0b2tlbgAAAAAAABM=", "AAAAAQAAAAAAAAAAAAAACkVzY3Jvd0RhdGEAAAAAAA4AAAA1TGVkZ2VyIHRpbWVzdGFtcCBhdCB3aGljaCB0aGUgcGF5ZWUgYWNjZXB0ZWQsIGlmIGFueS4AAAAAAAALYWNjZXB0ZWRfYXQAAAAD6AAAAAYAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAANY29uZGl0aW9uX3JlZgAAAAAAABAAAACGSWYgdHJ1ZSwgaW52b2tlIHRoZSBjb250cmFjdCBwYXllZSdzIGBvbl9lc2Nyb3dfcmVsZWFzZWRgIGhvb2sgYWZ0ZXIKdGhlIHBheW91dCB0cmFuc2Zlci4gQSBmYWlsZWQgY2FsbGJhY2sgcmV2ZXJ0cyB0aGUgd2hvbGUgcmVsZWFzZS4AAAAAAAxub3RpZnlfcGF5ZWUAAAABAAAAAAAAAAVwYXllZQAAAAAAABMAAAB6VGhlIGZ1bmRlcjogYXV0aG9yaXNlcyBhbmQgcGF5cyBmb3IgdGhlIGVzY3JvdyBhdCBjcmVhdGlvbi4gQWxzbwp0aGUgcmVmdW5kIGRlc3RpbmF0aW9uIHVubGVzcyBgcmVmdW5kX3RvYCBzYXlzIG90aGVyd2lzZS4AAAAAAAVwYXllcgAAAAAAABMAAADFT3B0aW9uYWwgZGVhZGxpbmUgKHVuaXggc2Vjb25kcykgYWZ0ZXIgd2hpY2ggYHJlZnVuZGAgb3BlbnMgdG8KYW55b25lIOKAlCBzdGlsbCBvbmx5IHRvIGByZWZ1bmRfdG9gLiBgTm9uZWAgbWVhbnMgbm8gdGltZW91dCwKYmVoYXZpb3VyIGlkZW50aWNhbCB0byBiZWZvcmUgdGhpcyBmaWVsZCBleGlzdGVkIOKAlCBzZWUgZG9jcy9hZHIvMDAwNC4AAAAAAAAMcmVmdW5kX2FmdGVyAAAD6AAAAAYAAADhV2hlcmUgKipldmVyeSoqIHJlZnVuZCBwYXRoIChyZWxlYXNlciwgdGltZW91dCkgc2VuZHMgdGhlIGZ1bmRzLgpGaXhlZCBhdCBjcmVhdGlvbiB1bmRlciB0aGUgZnVuZGVyJ3MgYXV0aCBhbmQgbmV2ZXIgY2hvc2VuIGF0CnBheW91dCwgc28gaW52YXJpYW50IEkxIGhvbGRzOyBkZWZhdWx0cyB0byBgcGF5ZXJgIHdoZW4gb21pdHRlZCBhdApjcmVhdGlvbiDigJQgc2VlIGRvY3MvYWRyLzAwMDkuAAAAAAAACXJlZnVuZF90bwAAAAAAABMAAAA2RWFybGllc3QgbGVkZ2VyIHRpbWVzdGFtcCBhdCB3aGljaCByZWxlYXNlIGlzIGFsbG93ZWQuAAAAAAAScmVsZWFzZV9ub3RfYmVmb3JlAAAAAAPoAAAABgAAAL1BZGRyZXNzZXMgcGVybWl0dGVkIHRvIHNldHRsZSB0aGlzIGVzY3JvdyDigJQgc2VlIGRvY3MvYWRyLzAwMDIgZm9yIHRoZQpwZXItZXNjcm93IHJlbGVhc2VyLCBhbmQgZG9jcy9hZHIvMDAwNyBmb3IgTS1vZi1OIHNldHMuIEEgcGxhaW4Kc2luZ2xlLXJlbGVhc2VyIGVzY3JvdyBzdG9yZXMgZXhhY3RseSBvbmUgZW50cnkgaGVyZS4AAAAAAAAJcmVsZWFzZXJzAAAAAAAD6gAAABMAAAA5V2hldGhlciByZWxlYXNlIHJlcXVpcmVzIHRoZSBwYXllZSdzIGV4cGxpY2l0IGFjY2VwdGFuY2UuAAAAAAAAE3JlcXVpcmVzX2FjY2VwdGFuY2UAAAAAAQAAAAAAAAAGc3RhdHVzAAAAAAfQAAAADEVzY3Jvd1N0YXR1cwAAALpIb3cgbWFueSAqZGlzdGluY3QqIGFkZHJlc3NlcyBmcm9tIGByZWxlYXNlcnNgIG11c3QgYXV0aG9yaXplIG9uZQpzZXR0bGVtZW50LiBBbHdheXMgYD49IDFgIGFuZCBgPD0gcmVsZWFzZXJzLmxlbigpYDsgYDFgIG1lYW5zIGFueQpzaW5nbGUgcmVsZWFzZXIgY2FuIHNldHRsZSAodGhlIHByZS1xdW9ydW0gYmVoYXZpb3VyKS4AAAAAAAl0aHJlc2hvbGQAAAAAAAAEAAAAAAAAAAV0b2tlbgAAAAAAABM=", "AAAAAQAAAYBPbmUgZW50cnkgb2YgYSBgY3JlYXRlX2VzY3Jvd3NgIGJhdGNoLiBNaXJyb3JzIHRoZSBzaW5nbGUtZXNjcm93CmNyZWF0aW9uIHBhcmFtZXRlcnMgbWludXMgdGhlIGJhdGNoLXdpZGUgYHBheWVyYC4KSW1tdXRhYmxlIGZ1bmRpbmcgY29uZmlndXJhdGlvbiBvZiBhIHBvb2xlZCBlc2Nyb3cgKGlzc3VlICMxMTMsCmRvY3MvYWRyLzAwMTEpLiBFeGlzdGVuY2Ugb2YgdGhpcyByZWNvcmQgdW5kZXIgYERhdGFLZXk6OlBvb2xlZChpZClgIGlzCndoYXQgbWFrZXMgYW4gZXNjcm93ICJwb29sZWQiOyB0aGUgYWdncmVnYXRlIGZ1bmRlZCBhbW91bnQgbGl2ZXMgaW4KYEVzY3Jvd0RhdGEuYW1vdW50YCAob25lIHNvdXJjZSBvZiB0cnV0aCBzaGFyZWQgd2l0aCBgc2V0dGxlKClgKS4AAAAAAAAAClBvb2xlZERhdGEAAAAAAAIAAAEJVW5peCBzZWNvbmRzIHVudGlsIHdoaWNoIGBjb250cmlidXRlYCBpcyBhY2NlcHRlZC4gV2hlbiBpdCBwYXNzZXMKd2l0aCB0aGUgdGFyZ2V0IHVubWV0LCB0aGUgcm91bmQgaGFzIGZhaWxlZDogY29udHJpYnV0aW9ucyBhcmUKZnJvemVuIGZvcmV2ZXIgYW5kIGV2ZXJ5IGNvbnRyaWJ1dG9yIG1heSBwdWxsIHRoZWlyIGV4YWN0IHJlY29yZGVkCmFtb3VudCBiYWNrIHdpdGggYGNsYWltX3JlZnVuZGAg4oCUIHRoZSBlc2Nyb3cgY2FuIG5ldmVyIGJlIHJlbGVhc2VkLgAAAAAAABBmdW5kaW5nX2RlYWRsaW5lAAAABgAAAIFUb3RhbCB0aGUgY29udHJpYnV0b3JzIG11c3QgcmVhY2ggZm9yIHJlbGVhc2UgdG8gYmVjb21lIHBvc3NpYmxlLgpTdHJpY3RseSBwb3NpdGl2ZSwgZml4ZWQgYXQgY3JlYXRpb24sIG5ldmVyIGNoYW5nZWQgYWZ0ZXJ3YXJkcy4AAAAAAAAOZnVuZGluZ190YXJnZXQAAAAAAAs=", @@ -590,7 +590,7 @@ export class Client extends ContractClient { "AAAAAAAAADxSZWFkLW9ubHkgc3RhdHVzIHF1ZXJ5LiBFcnJvcnMgaWYgYGVzY3Jvd19pZGAgZG9lc24ndCBleGlzdC4AAAAKZ2V0X3N0YXR1cwAAAAAAAQAAAAAAAAAJZXNjcm93X2lkAAAAAAAABgAAAAEAAAPpAAAH0AAAAAxFc2Nyb3dTdGF0dXMAAAAD", "AAAAAAAAAhBCYXRjaCB2ZXJzaW9uIG9mIGByZWxlYXNlYDogcmVsZWFzZXMgZWFjaCBlc2Nyb3cgaW4gYGVzY3Jvd19pZHNgIGluCm9yZGVyIG9uIGJlaGFsZiBvZiBgY2FsbGVyYCwgd2hpY2ggbXVzdCBiZSBhIHJlbGVhc2VyIG9mIGV2ZXJ5IG9uZQpvZiB0aGVtLiBSZXR1cm5zIGEgcGVyLW9wIHJlc3VsdCB2ZWN0b3IgYWxpZ25lZCB3aXRoIGBlc2Nyb3dfaWRzYDoKYE9rKCgpKWAgZm9yIGEgc3VjY2Vzc2Z1bCByZWxlYXNlLCBgRXJyKGUpYCBmb3Igb25lIHRoZSBjb250cmFjdApyZWplY3RlZCAodW5rbm93biBpZCwgbm90IGZ1bmRlZCwgY2FsbGVyIG5vdCBhIHJlbGVhc2VyLCBiZWZvcmUKYHJlbGVhc2Vfbm90X2JlZm9yZWApLiBBIHJlamVjdGVkIG9wIGRvZXMgbm90IHN0b3AgdGhlIHJlc3Qg4oCUIHRoZQpjYWxsZXIgY2FuIGluc3BlY3QgdGhlIHZlY3RvciBhbmQgcmV0cnkgb25seSB0aGUgZmFpbHVyZXMuCgpgZXNjcm93X2lkcy5sZW4oKWAgbXVzdCBiZSBgPD0gTUFYX0JBVENIX1NJWkVgIOKAlCBzZWUgZG9jcy9hZHIvMDAwNS4AAAALc2V0dGxlX21hbnkAAAAAAgAAAAAAAAAGY2FsbGVyAAAAAAATAAAAAAAAAAplc2Nyb3dfaWRzAAAAAAPqAAAABgAAAAEAAAPpAAAD6gAAA+kAAAACAAAAAwAAAAM=", "AAAAAAAAA0JQdWxsLWJhc2VkIHJlZnVuZCAoaXNzdWUgIzExMywgZG9jcy9hZHIvMDAxMSk6IHBheXMgYGNvbnRyaWJ1dG9yYApleGFjdGx5IHRoZSBhbW91bnQgcmVjb3JkZWQgYWdhaW5zdCB0aGF0IGFkZHJlc3MsIG9uY2UuIEF2YWlsYWJsZQpvbmx5IGZvciBhICpmYWlsZWQqIGZ1bmRpbmcgcm91bmQg4oCUIHRoZSBmdW5kaW5nIGRlYWRsaW5lIGhhcyBwYXNzZWQKd2l0aCB0aGUgcG9vbGVkIHRvdGFsIHN0aWxsIGJlbG93IHRoZSB0YXJnZXQgKGBSZWZ1bmROb3RPcGVuYApvdGhlcndpc2UpIOKAlCBhbmQgb25seSB3aGlsZSB0aGUgZXNjcm93IGhhcyBub3QgYmVlbiByZWxlYXNlZAooYE5vdEZ1bmRlZGAgYWZ0ZXJ3YXJkczogcmVsZWFzZWQgZnVuZHMgY2FuIG5ldmVyIGJlIGNsYWltZWQpLgpFYWNoIGNsYWltIGlzIE8oMSkgd29yayDigJQgbm8gcGF0aCBpdGVyYXRlcyBjb250cmlidXRvcnMg4oCUIGFuZCBpdHMKb3duIGF1dGhvcml6YXRpb246IG5vIG9uZSBjYW4gY2xhaW0gb24gYW5vdGhlciBjb250cmlidXRvcidzCmJlaGFsZiAoYE5vQ29udHJpYnV0aW9uYCBmb3IgYW4gYWRkcmVzcyB3aXRoIG5vIHJlY29yZCksIGEgc2Vjb25kCmNsYWltIGZhaWxzIGBBbHJlYWR5UmVmdW5kZWRgLCBhbmQgdGhlIG9yZGVyIG9mIGNsYWltcyBjYW4gbmV2ZXIKY2hhbmdlIGFueW9uZSdzIGVudGl0bGVtZW50LiBDaGVja3MtZWZmZWN0cy1pbnRlcmFjdGlvbnM6IHRoZQpgcmVmdW5kZWRgIGZsYWcgYW5kIHRoZSBwb29sZWQgdG90YWwgYXJlIHdyaXR0ZW4gYmVmb3JlIHRoZQp0cmFuc2ZlciwgYW5kIGEgZmFpbGluZyB0cmFuc2ZlciByb2xscyB0aGUgd2hvbGUgY2xhaW0gYmFjay4AAAAAAAxjbGFpbV9yZWZ1bmQAAAACAAAAAAAAAAllc2Nyb3dfaWQAAAAAAAAGAAAAAAAAAAtjb250cmlidXRvcgAAAAATAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAahTZXRzIHRoZSBpbW11dGFibGUgdG9rZW4gYWxsb3dsaXN0IGFuZCBwZXItdG9rZW4gYW1vdW50IGNhcHMgYXQKZGVwbG95bWVudC4gQW4gZW1wdHkgYGFsbG93ZWRfdG9rZW5zYCBwcmVzZXJ2ZXMgdGhlIGxlZ2FjeSBiZWhhdmlvcgphbmQgYWxsb3dzIGFueSB0b2tlbiBhZGRyZXNzLiBBbiBlbXB0eSBgbWF4aW11bV9hbW91bnRzYCBtZWFucyBubwpjYXBzOyBvdGhlcndpc2Ugb25seSB0b2tlbnMgd2l0aCBhIGNhcCBlbnRyeSBjYW4gYmUgZXNjcm93ZWQsIGFuZCBubwplc2Nyb3cgb2YgdGhhdCB0b2tlbiBtYXkgZXZlciBob2xkIG1vcmUgdGhhbiBpdHMgY2FwIChpc3N1ZSAjNTYsCmRvY3MvYWRyLzAwMTIpLiBOZWl0aGVyIGNhbiBiZSBjaGFuZ2VkIGFmdGVyIGRlcGxveW1lbnQg4oCUIHJhaXNpbmcgYQpjYXAgbWVhbnMgcmVkZXBsb3lpbmcuAAAADV9fY29uc3RydWN0b3IAAAAAAAACAAAAAAAAAA5hbGxvd2VkX3Rva2VucwAAAAAD6gAAABMAAAAAAAAAD21heGltdW1fYW1vdW50cwAAAAPsAAAAEwAAAAsAAAABAAAD6QAAAAIAAAAD", + "AAAAAAAAAahTZXRzIHRoZSBpbW11dGFibGUgdG9rZW4gYWxsb3dsaXN0IGFuZCBwZXItdG9rZW4gYW1vdW50IGNhcHMgYXQKZGVwbG95bWVudC4gQW4gZW1wdHkgYGFsbG93ZWRfdG9rZW5zYCBwcmVzZXJ2ZXMgdGhlIGxlZ2FjeSBiZWhhdmlvcgphbmQgYWxsb3dzIGFueSB0b2tlbiBhZGRyZXNzLiBBbiBlbXB0eSBgbWF4aW11bV9hbW91bnRzYCBtZWFucyBubwpjYXBzOyBvdGhlcndpc2Ugb25seSB0b2tlbnMgd2l0aCBhIGNhcCBlbnRyeSBjYW4gYmUgZXNjcm93ZWQsIGFuZCBubwplc2Nyb3cgb2YgdGhhdCB0b2tlbiBtYXkgZXZlciBob2xkIG1vcmUgdGhhbiBpdHMgY2FwIChpc3N1ZSAjNTYsCmRvY3MvYWRyLzAwMTMpLiBOZWl0aGVyIGNhbiBiZSBjaGFuZ2VkIGFmdGVyIGRlcGxveW1lbnQg4oCUIHJhaXNpbmcgYQpjYXAgbWVhbnMgcmVkZXBsb3lpbmcuAAAADV9fY29uc3RydWN0b3IAAAAAAAACAAAAAAAAAA5hbGxvd2VkX3Rva2VucwAAAAAD6gAAABMAAAAAAAAAD21heGltdW1fYW1vdW50cwAAAAPsAAAAEwAAAAsAAAABAAAD6QAAAAIAAAAD", "AAAAAAAAAw9Mb2NrcyBgYW1vdW50YCBvZiBgdG9rZW5gIHB1bGxlZCBmcm9tIGBwYXllcmAsIGhlbGQgZm9yIGBwYXllZWAsCmRvY3MvdGhyZWF0LW1vZGVsLm1kIFQxMCBhbmQgUk9BRE1BUC5tZCBQaGFzZSBFNSkuIGByZWxlYXNlcmAgaXMgdGhlCmFkZHJlc3MgcGVybWl0dGVkIHRvIGNhbGwgYHJlbGVhc2VgL2ByZWZ1bmRgIG9uIHRoaXMgZXNjcm93Cihkb2NzL2Fkci8wMDAyKS4gYHJlZnVuZF9hZnRlcmAgaXMgb3B0aW9uYWwgKGRvY3MvYWRyLzAwMDQpLgpgbm90aWZ5X3BheWVlYCBvcHRzIGFuIGltcGxlbWVudGluZyBjb250cmFjdCBwYXllZSBpbnRvCmBvbl9lc2Nyb3dfcmVsZWFzZWQoZXNjcm93X2lkLCBhbW91bnQpYCAoZG9jcy9hZHIvMDAwNSkuIFRoZSBwYXllcgptdXN0IGF1dGhvcml6ZSBjcmVhdGlvbiwgYW5kIHRoZSBwYXllZSBtdXN0IGFsc28gYXV0aG9yaXplIGlmCmBub3RpZnlfcGF5ZWVgIGlzIHRydWUuIGByZWZ1bmRfdG9gIGlzIHRoZSBvcHRpb25hbCByZWZ1bmQKZGVzdGluYXRpb24gZm9yIGEgc3BvbnNvcmVkIGVzY3JvdyB3aGVyZSB0aGUgZnVuZGVyIChgcGF5ZXJgKSBpcwpub3Qgd2hvIHNob3VsZCBnZXQgdGhlIG1vbmV5IGJhY2sgKGRvY3MvYWRyLzAwMDkpOiBgTm9uZWAgbWVhbnMKYHBheWVyYDsgaXQgbWF5IG5vdCBiZSB0aGlzIGNvbnRyYWN0J3Mgb3duIGFkZHJlc3MsIGFuZCB0aGUKYHBheWVyYCdzIGF1dGggb24gdGhpcyBjYWxsIGNvdmVycyBpdC4gRm9yIE0tb2YtTiByZWxlYXNlcnMgdXNlCmBjcmVhdGVfZXNjcm93X3dpdGhfcmVsZWFzZXJzYC4AAAAADWNyZWF0ZV9lc2Nyb3cAAAAAAAAJAAAAAAAAAAVwYXllcgAAAAAAABMAAAAAAAAABXBheWVlAAAAAAAAEwAAAAAAAAAFdG9rZW4AAAAAAAATAAAAAAAAAAZhbW91bnQAAAAAAAsAAAAAAAAADWNvbmRpdGlvbl9yZWYAAAAAAAAQAAAAAAAAAAhyZWxlYXNlcgAAABMAAAAAAAAADHJlZnVuZF9hZnRlcgAAA+gAAAAGAAAAAAAAAAxub3RpZnlfcGF5ZWUAAAABAAAAAAAAAAlyZWZ1bmRfdG8AAAAAAAPoAAAAEwAAAAEAAAPpAAAABgAAAAM=", "AAAAAAAAAaNCYXRjaCB2ZXJzaW9uIG9mIGBjcmVhdGVfZXNjcm93YC4gQ3JlYXRlcyBvbmUgZXNjcm93IHBlciBpdGVtLCBpbgppbnB1dCBvcmRlciwgYW5kIHJldHVybnMgdGhlIG5ldyBpZHMgaW4gdGhlIHNhbWUgb3JkZXIgYXMgYGl0ZW1zYC4KYHBheWVyYCBhdXRob3JpemVzIG9uY2UgZm9yIHRoZSB3aG9sZSBjYWxsLiBGYWlscyBhdG9taWNhbGx5OiBpZgphbnkgaXRlbSBpcyBpbnZhbGlkIChiYWQgYW1vdW50LCBwYXN0IGByZWZ1bmRfYWZ0ZXJgLCBvdmVyc2l6ZWQKYGNvbmRpdGlvbl9yZWZgLCBzaG9ydC1kZWxpdmVyaW5nIHRva2VuKSB0aGUgd2hvbGUgY2FsbCByZXZlcnRzIGFuZApubyBlc2Nyb3dzIGFyZSBjcmVhdGVkLgoKYGl0ZW1zLmxlbigpYCBtdXN0IGJlIGA8PSBNQVhfQkFUQ0hfU0laRWAg4oCUIHNlZSBkb2NzL2Fkci8wMDA1LgAAAAAOY3JlYXRlX2VzY3Jvd3MAAAAAAAIAAAAAAAAABXBheWVyAAAAAAAAEwAAAAAAAAAFaXRlbXMAAAAAAAPqAAAH0AAAAApDcmVhdGVJdGVtAAAAAAABAAAD6QAAA+oAAAAGAAAAAw==", "AAAAAAAAAPhSb3RhdGVzIGEgZnVuZGVkIGVzY3JvdydzIHJlbGVhc2VyLiBCb3RoIHRoZSBjdXJyZW50IHJlbGVhc2VyIGFuZApwYXllciBtdXN0IGF1dGhvcml6ZSB0aGUgY2hhbmdlOyB0aGUgcGF5ZXIncyBjb25zZW50IHByZXZlbnRzIGEKY29tcHJvbWlzZWQgcmVsZWFzZXIgZnJvbSBzaWxlbnRseSBhcHBvaW50aW5nIGFuIGF0dGFja2VyLiBUaGUKcmVwbGFjZW1lbnQgY2Fubm90IGJlIHRoZSBjb250cmFjdCBpdHNlbGYgb3IgdGhlIHBheWVlLgAAAA9yb3RhdGVfcmVsZWFzZXIAAAAAAwAAAAAAAAAJZXNjcm93X2lkAAAAAAAABgAAAAAAAAAGY2FsbGVyAAAAAAATAAAAAAAAAAxuZXdfcmVsZWFzZXIAAAATAAAAAQAAA+kAAAACAAAAAw==",