From a9cbcb495c09bcc30f724f8f5fc786a6119c009d Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 3 Jun 2026 14:58:56 +0500 Subject: [PATCH 1/4] choreL update support crate --- crates/config/src/program_config.rs | 19 + crates/support-scripts/src/program_risc0.rs | 43 +- crates/support/Cargo.lock | 151 +------ crates/support/Dockerfile | 4 + crates/support/README.md | 268 ++++++++++++- crates/support/app/src/main.rs | 111 +++--- crates/support/contracts/ImageID.sol | 2 +- crates/support/host/Cargo.toml | 2 +- crates/support/host/src/bin/profile_risc0.rs | 4 +- crates/support/host/src/lib.rs | 180 +++++++-- crates/support/methods/guest/Cargo.lock | 370 +----------------- crates/support/program/Cargo.toml | 4 +- crates/support/program/src/lib.rs | 4 +- crates/support/scripts/container/start.sh | 26 ++ crates/support/types/Cargo.toml | 1 + crates/support/types/src/lib.rs | 79 +++- .../.enclave/generated/contracts/ImageID.sol | 2 +- examples/CRISP/enclave.config.yaml | 11 +- .../crisp-contracts/deployed_contracts.json | 2 +- 19 files changed, 615 insertions(+), 668 deletions(-) diff --git a/crates/config/src/program_config.rs b/crates/config/src/program_config.rs index 55690b575d..0762116a67 100644 --- a/crates/config/src/program_config.rs +++ b/crates/config/src/program_config.rs @@ -21,6 +21,25 @@ pub struct BoundlessConfig { pub program_url: Option, #[serde(default = "default_true")] pub onchain: bool, + // --- Offer params (all optional, fall back to defaults in build_offer_params) --- + /// Minimum price in ETH (default: 0.001) + #[serde(default)] + pub min_price_eth: Option, + /// Maximum price in ETH (default: 0.03) + #[serde(default)] + pub max_price_eth: Option, + /// Total timeout in seconds (default: 1200 = 20 min) + #[serde(default)] + pub timeout_secs: Option, + /// Lock timeout in seconds (default: 600 = 10 min) + #[serde(default)] + pub lock_timeout_secs: Option, + /// Ramp-up period in seconds (default: 120 = 2 min) + #[serde(default)] + pub ramp_up_secs: Option, + /// Lock collateral in ZKC (default: 5.0) + #[serde(default)] + pub lock_collateral_zkc: Option, } fn default_true() -> bool { diff --git a/crates/support-scripts/src/program_risc0.rs b/crates/support-scripts/src/program_risc0.rs index d238ba0945..a5e2817982 100644 --- a/crates/support-scripts/src/program_risc0.rs +++ b/crates/support-scripts/src/program_risc0.rs @@ -34,27 +34,54 @@ impl ProgramSupportApi for ProgramSupportRisc0 { bail!("start must be run with risc0 config available"); }; - let risc0_dev_mode_str = risc0_config.risc0_dev_mode.to_string(); - let mut args = vec!["--risc0-dev-mode", risc0_dev_mode_str.as_str()]; + let mut args: Vec = vec![ + "--risc0-dev-mode".into(), + risc0_config.risc0_dev_mode.to_string(), + ]; // Boundless support if let Some(boundless) = &risc0_config.boundless { - args.extend(["--rpc-url", boundless.rpc_url.as_str()]); - args.extend(["--private-key", boundless.private_key.as_str()]); + args.extend_from_slice(&[ + "--rpc-url".into(), + boundless.rpc_url.clone(), + "--private-key".into(), + boundless.private_key.clone(), + ]); if let Some(jwt) = &boundless.pinata_jwt { - args.extend(["--pinata-jwt", jwt.as_str()]); + args.extend_from_slice(&["--pinata-jwt".into(), jwt.clone()]); } if let Some(url) = &boundless.program_url { - args.extend(["--program-url", url.as_str()]); + args.extend_from_slice(&["--program-url".into(), url.clone()]); } let onchain = if boundless.onchain { "true" } else { "false" }; - args.extend(["--boundless-onchain", onchain]); + args.extend_from_slice(&["--boundless-onchain".into(), onchain.into()]); + + // Offer params — push flag + value as owned Strings + if let Some(v) = boundless.min_price_eth { + args.extend_from_slice(&["--boundless-min-price-eth".into(), v.to_string()]); + } + if let Some(v) = boundless.max_price_eth { + args.extend_from_slice(&["--boundless-max-price-eth".into(), v.to_string()]); + } + if let Some(v) = boundless.timeout_secs { + args.extend_from_slice(&["--boundless-timeout-secs".into(), v.to_string()]); + } + if let Some(v) = boundless.lock_timeout_secs { + args.extend_from_slice(&["--boundless-lock-timeout-secs".into(), v.to_string()]); + } + if let Some(v) = boundless.ramp_up_secs { + args.extend_from_slice(&["--boundless-ramp-up-secs".into(), v.to_string()]); + } + if let Some(v) = boundless.lock_collateral_zkc { + args.extend_from_slice(&["--boundless-lock-collateral-zkc".into(), v.to_string()]); + } } - run_bash_script(&cwd, &script, &args).await?; + let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + run_bash_script(&cwd, &script, &arg_refs).await?; Ok(()) } diff --git a/crates/support/Cargo.lock b/crates/support/Cargo.lock index efe34d2c28..c9f2d987df 100644 --- a/crates/support/Cargo.lock +++ b/crates/support/Cargo.lock @@ -1351,12 +1351,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" @@ -2000,17 +1994,6 @@ dependencies = [ "regex", ] -[[package]] -name = "bigint-poly" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/bigint-poly#9eca04d2aa473c5ead1e5a13adc8ad11bf250e4a" -dependencies = [ - "num-bigint", - "num-traits", - "serde", - "thiserror 1.0.69", -] - [[package]] name = "bincode" version = "1.3.3" @@ -2099,19 +2082,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "blake3" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - [[package]] name = "block" version = "0.1.6" @@ -2515,12 +2485,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "cookie" version = "0.16.2" @@ -2986,22 +2950,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "e3-bfv-client" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "anyhow", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80)", - "e3-greco-helpers", - "fhe", - "fhe-traits", - "rand 0.8.5", - "thiserror 1.0.69", - "zkfhe-greco", - "zkfhe-shared", -] - [[package]] name = "e3-compute-provider" version = "0.1.5" @@ -3020,58 +2968,16 @@ dependencies = [ "zk-kit-imt", ] -[[package]] -name = "e3-compute-provider" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "ark-bn254 0.4.0", - "ark-ff 0.4.2", - "e3-bfv-client", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80)", - "hex", - "lean-imt", - "light-poseidon", - "num-bigint", - "num-traits", - "rayon", - "serde", - "sha3", - "zk-kit-imt", -] - [[package]] name = "e3-fhe-params" version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" +source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" dependencies = [ "alloy-dyn-abi", "alloy-primitives", "fhe", "num-bigint", "thiserror 1.0.69", - "zkfhe-shared", -] - -[[package]] -name = "e3-fhe-params" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" -dependencies = [ - "fhe", - "num-bigint", - "thiserror 1.0.69", -] - -[[package]] -name = "e3-greco-helpers" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "fhe", - "fhe-math", - "num-bigint", - "zkfhe-shared", ] [[package]] @@ -3080,7 +2986,7 @@ version = "0.1.0" dependencies = [ "actix-web", "anyhow", - "e3-compute-provider 0.1.5", + "e3-compute-provider", "e3-support-host", "e3-support-types", "env_logger", @@ -3103,8 +3009,8 @@ dependencies = [ "boundless-market", "bytemuck", "dotenvy", - "e3-compute-provider 0.1.5", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae)", + "e3-compute-provider", + "e3-fhe-params", "e3-user-program", "fhe", "fhe-traits", @@ -3125,6 +3031,7 @@ name = "e3-support-types" version = "0.1.0" dependencies = [ "anyhow", + "derivative", "hex", "serde", "serde_json", @@ -3134,8 +3041,8 @@ dependencies = [ name = "e3-user-program" version = "0.1.0" dependencies = [ - "e3-compute-provider 0.1.7", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae)", + "e3-compute-provider", + "e3-fhe-params", "fhe", "fhe-traits", ] @@ -4890,7 +4797,6 @@ dependencies = [ "num-integer", "num-traits", "rand 0.8.5", - "serde", ] [[package]] @@ -8295,49 +8201,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "zkfhe-greco" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/zkfhe-generator#6b424983347a4aa04c9725941f71ca6a4bed863e" -dependencies = [ - "anyhow", - "bigint-poly", - "blake3", - "fhe", - "fhe-math", - "fhe-traits", - "itertools 0.14.0", - "num-bigint", - "num-traits", - "rand 0.8.5", - "rayon", - "serde", - "serde_json", - "tempfile", - "toml", - "zkfhe-shared", -] - -[[package]] -name = "zkfhe-shared" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/zkfhe-generator#6b424983347a4aa04c9725941f71ca6a4bed863e" -dependencies = [ - "anyhow", - "bigint-poly", - "chrono", - "fhe", - "fhe-math", - "fhe-traits", - "num-bigint", - "num-traits", - "rand 0.8.5", - "serde", - "serde_json", - "thiserror 1.0.69", - "toml", -] - [[package]] name = "zstd" version = "0.13.3" diff --git a/crates/support/Dockerfile b/crates/support/Dockerfile index d32c990567..937037c1fb 100644 --- a/crates/support/Dockerfile +++ b/crates/support/Dockerfile @@ -39,6 +39,10 @@ RUN rzup install rust ${RISC0_TOOLCHAIN} RUN rzup install r0vm ${RISC0_VERSION} RUN rzup install cargo-risczero ${RISC0_VERSION} +RUN sudo ln -sf "$(readlink -f "$(command -v r0vm)")" /usr/local/bin/r0vm && \ + test -x /usr/local/bin/r0vm +ENV RISC0_SERVER_PATH=/usr/local/bin/r0vm + ENV PATH="/home/${USERNAME}/.foundry/bin:${PATH}" RUN curl -L https://foundry.paradigm.xyz | bash RUN foundryup diff --git a/crates/support/README.md b/crates/support/README.md index c246a8eca7..463f7290e7 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -1,11 +1,9 @@ -This is a project to build the support container to allow risc0 to be run within docker by -`enclave program start` +# E3 Support — RISC Zero + Boundless Compute Provider -The container is built using the github workflow [here](../../.github/workflows/support-docker.yml) -You can also build it locally by using the `./scripts/build.sh` script. - -To develop on this you should log into the container by running `./scripts/dev.sh` and then you can -run `cargo build --locked` with access to the risc0 environment. +Docker-based compute provider that runs FHE homomorphic computations and proves them via +[Boundless](https://boundless.network) (a decentralized ZK proving market). The container exposes an +HTTP API on port 13151 that receives encrypted ciphertexts, runs the FHE computation, submits a +proof request to Boundless, and sends the result back via webhook callback. ```mermaid graph TD @@ -14,38 +12,266 @@ graph TD AA["./.enclave/support/ctl/start"] A --> AA end - M["instigator"] --"http\:\/\/localhost\:13151\/run_compute (cb in payload)"--> D - D --"http\:\/\/someurl.com"--> O["callback server receives results"] + M["E3 instigator (CRISP server)"] --"POST /run_compute (with callback_url)"--> D + D --"webhook callback"--> O["callback server (CRISP) publishes on-chain"] AA --listen on port 13151--> D subgraph C["e3-support (container)"] - D["app"] - E["host"] - F["types"] - G["compute-provider"] - H["methods (risc0)"] - I["guest (risc0)"] - J["user-program"] + D["app (actix HTTP server)"] + E["host (Boundless client)"] + F["types (WebhookPayload)"] + G["compute-provider (FHE + merkle)"] + H["methods (risc0 build)"] + I["guest (risc0 zkVM program)"] + J["user-program (fhe_processor)"] D --> E D --> F D --> G - E --> H E --> G E --> J - H --> I - I --> G I --> J end ``` -NOTE: This is outside of the main workspace because it needs to be run within it's own context in +## Architecture + +- **`app/`** — Actix HTTP server (`e3-support-app` binary). Exposes `/run_compute` (POST) and + `/health` (GET/HEAD). +- **`host/`** — Boundless SDK integration. Builds the client, submits proof requests, waits for + fulfillment. +- **`types/`** — Shared types: `ComputeRequest` (what the server receives) and `WebhookPayload` + (tagged enum sent back). +- **`methods/`** — RISC Zero build crate. Compiles the guest program. +- **`guest/`** — The RISC Zero zkVM guest program. Runs `fhe_processor` (homomorphic ciphertext + summation) and commits the `ComputeResult`. +- **`program/`** — The FHE processor (`fhe_processor`): sums BFV ciphertexts homomorphically. + +## Webhook Payload Format + +The callback server receives a tagged-enum JSON payload: + +**Success:** + +```json +{ "status": "completed", "e3_id": 123, "ciphertext": "0x...", "proof": "0x..." } +``` + +**Failure:** + +```json +{ "status": "failed", "e3_id": 123, "error": "Computation failed: ..." } +``` + +This matches the format expected by CRISP and `E3ProgramServer` in `crates/program-server`. + +--- + +## Full E3 Flow — Step by Step + +### Prerequisites + +1. **RISC Zero toolchain** — `rzup install` +2. **Docker** — for the support container +3. **Pinata account** — for IPFS program uploads (get a JWT at https://pinata.cloud) +4. **Boundless wallet** — an Ethereum private key with ETH (for gas) and ZKC (for collateral) on the + Boundless-supported chain +5. **Enclave CLI** — `cargo install --locked --path ./crates/cli --bin enclave -f` + +### Step 1: Configure `enclave.config.yaml` + +```yaml +program: + dev: false + risc0: + risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev (fake proofs) + boundless: + rpc_url: 'https://sepolia.base.org' # or your RPC URL + private_key: '${PRIVATE_KEY}' # use env var for secrets! + pinata_jwt: '${PINATA_JWT}' + program_url: 'https://gateway.pinata.cloud/ipfs/Qm...' # after upload (Step 3) + onchain: true + # Optional — custom auction params (defaults shown): + # min_price_eth: 0.001 + # max_price_eth: 0.03 + # timeout_secs: 1200 + # lock_timeout_secs: 600 + # ramp_up_secs: 120 + # lock_collateral_zkc: 5.0 +``` + +### Step 2: Compile the RISC Zero Guest Program + +```bash +enclave program compile +``` + +This builds the guest ELF binary inside the Docker container. Output goes to +`./target/riscv-guest/methods/guests/riscv32im-risc0-zkvm-elf/release/program.bin`. + +### Step 3: Upload Program to IPFS (Pinata) + +```bash +enclave program upload +``` + +This uploads the compiled guest ELF to Pinata IPFS and caches the resulting URL at +`./target/.program_url`. Copy this URL into your `enclave.config.yaml` as +`program.risc0.boundless.program_url` to avoid re-uploading the program at runtime. + +### Step 4: Deploy Enclave Contracts + Start Ciphernodes + +```bash +# Deploy contracts to local Hardhat / testnet +pnpm evm:deploy + +# Start the ciphernode network +enclave start +``` + +This boots the ciphernodes, which listen for E3 requests, perform DKG, and await ciphertext outputs. + +### Step 5: Start the Program Server (Boundless-backed) + +```bash +enclave program start +``` + +This starts the Docker container running `e3-support-app` on port 13151. If Boundless config is +present, it will submit proofs to the Boundless market. Otherwise it falls back to dev mode. + +### Step 6: Submit an E3 Request + +The E3 request is submitted on-chain by the instigator (e.g., CRISP coordination server): + +```solidity +// On-chain: Enclave.request(params) +enclave.request(E3RequestParams({ + threshold: [M, N], + inputWindow: [start, end], + e3Program: crispProgramAddress, + e3ProgramParams: encodedParams, + computeProviderParams: "", + customParams: "" +})); +``` + +This triggers: + +1. Fee payment (1 USDC) +2. Committee selection via sortition +3. DKG (C0-C5 proofs) → committee public key published on-chain +4. Stage → `KeyPublished` + +### Step 7: Encrypt Inputs & Submit to Compute Provider + +The instigator encrypts data under the committee's aggregate public key, then POSTs to the program +server: + +```bash +curl -X POST http://localhost:13151/run_compute \ + -H "Content-Type: application/json" \ + -d '{ + "e3_id": 1, + "params": "0x...", + "ciphertext_inputs": [["0x...", 0], ["0x...", 1]], + "callback_url": "http://host.local:4000/state/add-result" + }' +``` + +The program server: + +1. Returns `{"status":"processing","e3_id":1}` immediately +2. Runs FHE computation (homomorphic sum) locally → ciphertext output +3. Submits proof request to Boundless market +4. Waits for a prover to fulfill the request +5. Sends webhook callback with + `{"status":"completed","e3_id":1,"ciphertext":"0x...","proof":"0x..."}` + +### Step 8: Webhook Handler Publishes On-Chain + +The callback server (e.g., CRISP) receives the webhook and calls: + +```solidity +enclave.publishCiphertextOutput(e3Id, ciphertextOutput, proof); +``` + +This transitions the E3 stage to `CiphertextReady`. + +### Step 9: Decryption & Completion + +The ciphernodes detect `CiphertextReady`, produce decryption shares (C6 proofs), the active +aggregator combines them (C7 proof), and publishes the plaintext on-chain. Stage → `Complete`, +rewards distributed. + +--- + +## Boundless Offer Parameters + +All parameters are configurable via environment variables (or `enclave.config.yaml`). Defaults: + +| Parameter | Env Var | Default | Description | +| ------------ | ------------------------------- | ------- | ---------------------------- | +| Min price | `BOUNDLESS_MIN_PRICE_ETH` | `0.001` | Starting price in ETH | +| Max price | `BOUNDLESS_MAX_PRICE_ETH` | `0.03` | Maximum price in ETH | +| Timeout | `BOUNDLESS_TIMEOUT_SECS` | `1200` | Total request lifetime (sec) | +| Lock timeout | `BOUNDLESS_LOCK_TIMEOUT_SECS` | `600` | Prover lock duration (sec) | +| Ramp-up | `BOUNDLESS_RAMP_UP_SECS` | `120` | Price ramp-up period (sec) | +| Collateral | `BOUNDLESS_LOCK_COLLATERAL_ZKC` | `5.0` | ZKC locked per request | + +These can also be set in `enclave.config.yaml` under `program.risc0.boundless`: + +```yaml +boundless: + min_price_eth: 0.002 + max_price_eth: 0.05 + timeout_secs: 1800 + # ... +``` + +--- + +## Building the Container + +```bash +# Local build +./scripts/build.sh + +# With push to registry +./scripts/build.sh --push +``` + +The container is also built by the GitHub workflow at `.github/workflows/support-docker.yml`. + +## Development + +To develop inside the container (with RISC Zero toolchain available): + +```bash +./scripts/dev.sh +``` + +Inside the container: + +```bash +cargo build --locked +cargo run --bin e3-support-app +``` + +## Testing + +```bash +# Test the HTTP endpoint with a fixture payload +./curl_test.sh +``` + +NOTE: This is outside of the main workspace because it needs to be run within its own context in order to isolate risc0. NOTE: We are attempting to isolate risc0 - it is anticipated that we will have to use feature flags -to tody this up so that we can compile more of the code and enable rust-analyzer to work outside of +to tidy this up so that we can compile more of the code and enable rust-analyzer to work outside of the risc0 environment for this project. **NOTE: currently this is an open relay which is a known issue** diff --git a/crates/support/app/src/main.rs b/crates/support/app/src/main.rs index 5919af8132..8de740fd68 100644 --- a/crates/support/app/src/main.rs +++ b/crates/support/app/src/main.rs @@ -6,7 +6,7 @@ use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer, Result as ActixResult}; use e3_compute_provider::FHEInputs; -use e3_support_types::{ComputationStatus, ComputeRequest, WebhookPayload}; +use e3_support_types::{ComputeRequest, WebhookPayload}; use serde::Serialize; #[derive(Serialize, Debug)] @@ -15,46 +15,45 @@ struct ProcessingResponse { e3_id: u64, } -async fn call_webhook( - callback_url: &str, - e3_id: u64, - status: ComputationStatus, - proof: Vec, - ciphertext: Vec, - error: Option, -) -> anyhow::Result<()> { +async fn call_webhook(callback_url: &str, payload: &WebhookPayload) -> anyhow::Result<()> { + let (e3_id, status_label, ciphertext_len, proof_len) = match payload { + WebhookPayload::Completed { + e3_id, + ciphertext, + proof, + } => (*e3_id, "completed", ciphertext.len(), proof.len()), + WebhookPayload::Failed { e3_id, error } => { + println!("call_webhook() - status: failed, error: {}", error); + (*e3_id, "failed", 0, 0) + } + }; + println!( - "call_webhook() - status: {:?}, ciphertext len: {}, proof len: {}", - status, - ciphertext.len(), - proof.len() + "call_webhook() - status: {}, ciphertext len: {}, proof len: {}", + status_label, ciphertext_len, proof_len ); - let payload = WebhookPayload { - e3_id, - status, - ciphertext, - proof, - error, - }; - - let json_payload = serde_json::to_string_pretty(&payload)?; + + let json_payload = serde_json::to_string_pretty(payload)?; println!("Sending webhook payload:"); println!("{}", json_payload); println!("callback_url: {}", callback_url); - + let response = reqwest::Client::new() .post(callback_url) - .json(&payload) + .json(payload) .send() .await?; - + println!("Webhook response status: {}", response.status()); if !response.status().is_success() { let error_body = response.text().await?; println!("Webhook error response: {}", error_body); - return Err(anyhow::anyhow!("Webhook failed with status and body: {}", error_body)); + return Err(anyhow::anyhow!( + "Webhook failed with status and body: {}", + error_body + )); } - + response.error_for_status()?; println!("✓ Webhook called successfully for E3 {}", e3_id); Ok(()) @@ -62,24 +61,23 @@ async fn call_webhook( async fn run_computation_async(fhe_inputs: FHEInputs) -> anyhow::Result<(Vec, Vec)> { println!("running computation..."); - let result = tokio::task::spawn_blocking(move || e3_support_host::run_compute(fhe_inputs)).await?; - + let result = + tokio::task::spawn_blocking(move || e3_support_host::run_compute(fhe_inputs)).await?; + match result { - Ok((boundless_output, ciphertext)) => { - match boundless_output { - e3_support_host::BoundlessOutput::Success { seal, .. } => { - println!( - "have result from computation! seal len: {}, ciphertext len: {}", - seal.len(), - ciphertext.len() - ); - Ok((seal, ciphertext)) - } - e3_support_host::BoundlessOutput::Error { error } => { - Err(anyhow::anyhow!("Boundless request failed: {}", error)) - } + Ok((boundless_output, ciphertext)) => match boundless_output { + e3_support_host::BoundlessOutput::Success { seal, .. } => { + println!( + "have result from computation! seal len: {}, ciphertext len: {}", + seal.len(), + ciphertext.len() + ); + Ok((seal, ciphertext)) } - } + e3_support_host::BoundlessOutput::Error { error } => { + Err(anyhow::anyhow!("Boundless request failed: {}", error)) + } + }, Err(e3_support_host::ComputeError::BoundlessFailed(msg)) => { Err(anyhow::anyhow!("Boundless request failed: {}", msg)) } @@ -98,32 +96,25 @@ async fn process_computation_background( Ok((proof, ciphertext)) => { println!("computation finished!"); println!("handling webhook delivery..."); - call_webhook( - callback_url, + let payload = WebhookPayload::Completed { e3_id, - ComputationStatus::Completed, - proof, ciphertext, - None, - ) - .await?; + proof, + }; + call_webhook(callback_url, &payload).await?; println!("Computation completed for E3 {}", e3_id); Ok(()) } Err(e) => { let error_msg = e.to_string(); eprintln!("Computation failed for E3 {}: {}", e3_id, error_msg); - - call_webhook( - callback_url, + + let payload = WebhookPayload::Failed { e3_id, - ComputationStatus::Failed, - vec![], - vec![], - Some(format!("Compute failed: {}", error_msg)), - ) - .await?; - + error: format!("Compute failed: {}", error_msg), + }; + call_webhook(callback_url, &payload).await?; + Err(e) } } diff --git a/crates/support/contracts/ImageID.sol b/crates/support/contracts/ImageID.sol index c0f19e618d..f37983df9b 100644 --- a/crates/support/contracts/ImageID.sol +++ b/crates/support/contracts/ImageID.sol @@ -19,5 +19,5 @@ pragma solidity ^0.8.20; library ImageID { - bytes32 public constant PROGRAM_ID = bytes32(0x3ec51be1b9917603f2c6c82dee266daf822c51dbdca293d491f0c6bdad620cbc); + bytes32 public constant PROGRAM_ID = bytes32(0xc36e34f0b40876593adc519e4cccf8795ec90e9bfef0a44f20be865e7cb7f0a2); } diff --git a/crates/support/host/Cargo.toml b/crates/support/host/Cargo.toml index 0173e262e6..382aa80d83 100644 --- a/crates/support/host/Cargo.toml +++ b/crates/support/host/Cargo.toml @@ -26,4 +26,4 @@ tracing-subscriber = { workspace = true } boundless-market = { workspace = true } url = { workspace = true } dotenvy = { workspace = true } -e3-fhe-params = { workspace = true } +e3-fhe-params = { workspace = true, features = ["abi-encoding"] } diff --git a/crates/support/host/src/bin/profile_risc0.rs b/crates/support/host/src/bin/profile_risc0.rs index a3050df919..15379e866d 100644 --- a/crates/support/host/src/bin/profile_risc0.rs +++ b/crates/support/host/src/bin/profile_risc0.rs @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use e3_compute_provider::FHEInputs; -use e3_fhe_params::DEFAULT_BFV_PRESET; +use e3_fhe_params::BfvPreset; use e3_fhe_params::{build_bfv_params_from_set_arc, encode_bfv_params}; use e3_support_host::run_risc0_compute; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; @@ -16,7 +16,7 @@ fn main() { println!("Starting RISC0 profiling with mock ciphertexts..."); // Use InsecureThresholdBfv512 parameter set - let param_set = DEFAULT_BFV_PRESET.into(); + let param_set = BfvPreset::InsecureThresholdBfv512.into(); let params = build_bfv_params_from_set_arc(param_set); println!( diff --git a/crates/support/host/src/lib.rs b/crates/support/host/src/lib.rs index 386ff3732c..040c1a8816 100644 --- a/crates/support/host/src/lib.rs +++ b/crates/support/host/src/lib.rs @@ -8,8 +8,6 @@ use alloy_primitives::utils::{parse_ether, parse_units}; use alloy_signer_local::PrivateKeySigner; use anyhow::{Context, Error, Result}; use bincode::serialize; -use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; -use risc0_ethereum_contracts::groth16; use boundless_market::{ client::ClientError, contracts::{boundless_market::MarketError, FulfillmentData}, @@ -22,7 +20,10 @@ use e3_compute_provider::{ }; use e3_user_program::fhe_processor; use methods::PROGRAM_ELF; -use std::{ops::Bound, time::{Duration, Instant}}; +use risc0_ethereum_contracts::groth16; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use std::error::Error as _; +use std::time::{Duration, Instant}; use url::Url; pub struct BoundlessProvider; @@ -91,10 +92,68 @@ fn to_output_error(e: E) -> BoundlessOutput { } } +/// Read optional environment variable as f64, returning None if unset or invalid. +fn env_opt_f64(key: &str) -> Option { + std::env::var(key).ok().and_then(|v| v.parse().ok()) +} + +/// Read optional environment variable as u64 (seconds), returning None if unset or invalid. +fn env_opt_secs(key: &str) -> Option { + std::env::var(key).ok().and_then(|v| v.parse().ok()) +} + +/// Build the OfferParams from environment variables, using sensible defaults. +/// +/// Pricing is tuned for FHE ciphertext summation (moderate cycle count): +/// - min_price starts low (0.00005 ETH ≈ $0.13) so early bids stay cheap +/// - max_price caps at 0.002 ETH ($5) to prevent runaway costs +/// - 10 min timeout gives provers time to discover the request +/// - 5 min lock_timeout is ample for FHE sum execution +/// - 1 min ramp_up starts the reverse Dutch auction quickly +/// - 2 ZKC collateral is low to attract provers without excessive lockup +fn build_offer() -> OfferParams { + let min_price = env_opt_f64("BOUNDLESS_MIN_PRICE_ETH") + .map(|v| parse_ether(&format!("{}", v)).unwrap()) + .unwrap_or_else(|| parse_ether("0.00005").unwrap()); + let max_price = env_opt_f64("BOUNDLESS_MAX_PRICE_ETH") + .map(|v| parse_ether(&format!("{}", v)).unwrap()) + .unwrap_or_else(|| parse_ether("0.002").unwrap()); + let timeout = env_opt_secs("BOUNDLESS_TIMEOUT_SECS") + .map(|v| v as u32) + .unwrap_or(10 * 60); + let lock_timeout = env_opt_secs("BOUNDLESS_LOCK_TIMEOUT_SECS") + .map(|v| v as u32) + .unwrap_or(5 * 60); + let ramp_up = env_opt_secs("BOUNDLESS_RAMP_UP_SECS") + .map(|v| v as u32) + .unwrap_or(1 * 60); + let zkc = env_opt_f64("BOUNDLESS_LOCK_COLLATERAL_ZKC").unwrap_or(2.0); + let collateral: alloy_primitives::U256 = parse_units(&format!("{}", zkc), 18).unwrap().into(); + + OfferParams::builder() + .min_price(min_price) + .max_price(max_price) + .timeout(timeout) + .lock_timeout(lock_timeout) + .ramp_up_period(ramp_up) + .lock_collateral(collateral) + .into() +} + async fn boundless_prove(input: &ComputeInput) -> BoundlessOutput { match boundless_prove_inner(input).await { Ok(output) => output, - Err(e) => to_output_error(e), + Err(e) => { + // Print the full error chain so the root cause is visible in logs. + eprintln!("✗ Boundless proof request FAILED:"); + eprintln!(" Error: {:#}", e); + let mut source = e.source(); + while let Some(s) = source { + eprintln!(" Caused by: {}", s); + source = s.source(); + } + to_output_error(e) + } } } @@ -119,6 +178,13 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result } }; + // Diagnostic: log what we're connecting to (key and API path never logged). + println!( + "Boundless client: caller={}, storage_provider={}", + private_key.address(), + storage_provider.is_some(), + ); + let client = Client::builder() .with_rpc_url(rpc_url) .with_private_key(private_key) @@ -127,34 +193,21 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result .await .context("Failed to build Boundless client")?; - let input_bytes = encode_input(&serialize(input).unwrap()) - .context("Failed to encode input")?; - + let input_bytes = encode_input(&serialize(input).unwrap()).context("Failed to encode input")?; + let program_url = std::env::var("PROGRAM_URL").ok(); + let stdin_size = input_bytes.len(); - let request = if let Some(url) = program_url { + let request = if let Some(ref url) = program_url { println!("Using pre-uploaded program: {}", url); - let parsed_url = url.parse::() - .context("Failed to parse program URL")?; - + let parsed_url = url.parse::().context("Failed to parse program URL")?; + client .new_request() .with_program_url(parsed_url) .context("Failed to create new request")? .with_stdin(input_bytes) - .with_offer( - // This auction begins with a flat period, allowing early bidding before the ramp-up begins. - // The price then increases linearly to 0.03 ETH over 2 mins. - // The maximum price of 0.03 ETH remains for 8 mins, - // after which the price drops to 0 ETH for the expiry period of 10 mins. - OfferParams::builder() - .min_price(parse_ether("0.001").unwrap()) // Minimum price in ETH - .max_price(parse_ether("0.03").unwrap()) // Maximum price in ETH - .timeout(20 * 60) // Total timeout in seconds (20 minutes) - .lock_timeout(10 * 60) // Lock timeout in seconds (10 minutes) - .ramp_up_period(2 * 60) // Ramp up period in seconds (2 minutes) - .lock_collateral(parse_units("5", 18).unwrap()), // 5 ZKC - ) + .with_offer(build_offer()) } else { println!( "Warning: Uploading {}MB program at runtime", @@ -164,19 +217,73 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result .new_request() .with_program(PROGRAM_ELF) .with_stdin(input_bytes) + .with_offer(build_offer()) }; let onchain = std::env::var("BOUNDLESS_ONCHAIN").unwrap_or_else(|_| "true".to_string()) == "true"; + println!( + "Boundless submission: onchain={}, program_url={:?}, stdin_size={}", + onchain, program_url, stdin_size, + ); + let (request_id, expires_at) = if onchain { - println!("Submitting onchain..."); - client.submit_onchain(request).await + println!("Building request..."); + let proof_request = match client.build_request(request).await { + Ok(r) => { + println!("✓ Request built successfully (id: {:x})", r.id); + r + } + Err(e) => { + eprintln!("✗ Build request FAILED:"); + eprintln!(" Debug: {:?}", e); + eprintln!(" Display: {:#}", e); + let mut source = e.source(); + while let Some(s) = source { + eprintln!(" Caused by: {}", s); + source = s.source(); + } + return Err(anyhow::anyhow!("Failed to build request: {:#}", e)); + } + }; + + println!("Submitting onchain (request id: {:x})...", proof_request.id); + match client.submit_request_onchain(&proof_request).await { + Ok(result) => { + println!("✓ Onchain submission successful"); + result + } + Err(e) => { + eprintln!("✗ Onchain submission FAILED:"); + eprintln!(" Display: {:#}", e); + let mut source = e.source(); + while let Some(s) = source { + eprintln!(" Caused by: {}", s); + source = s.source(); + } + return Err(anyhow::anyhow!("Failed to submit onchain: {:#}", e)); + } + } } else { println!("Submitting offchain..."); - client.submit_offchain(request).await - } - .context("Failed to submit request")?; + match client.submit_offchain(request).await { + Ok(result) => { + println!("✓ Offchain submission successful"); + result + } + Err(e) => { + eprintln!("✗ Offchain submission FAILED:"); + eprintln!(" Error: {:#}", e); + let mut source = e.source(); + while let Some(s) = source { + eprintln!(" Caused by: {}", s); + source = s.source(); + } + return Err(anyhow::anyhow!("Failed to submit offchain: {:#}", e)); + } + } + }; println!("Request ID: {:x}, waiting for fulfillment...", request_id); @@ -207,8 +314,8 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result } }; - let decoded_journal: ComputeResult = bincode::deserialize(&journal) - .context("Failed to decode journal")?; + let decoded_journal: ComputeResult = risc0_zkvm::serde::from_slice(&journal) + .map_err(|e| anyhow::anyhow!("Failed to decode journal: {}", e))?; Ok(BoundlessOutput::Success { result: decoded_journal, @@ -311,15 +418,10 @@ pub fn run_risc0_compute( ) -> std::result::Result<(Risc0Output, Vec), ComputeError> { let risc0_provider = Risc0Provider; - let mut provider = ComputeManager::new( - risc0_provider, - params.clone(), - fhe_processor, - false, - None, - ); + let mut provider = + ComputeManager::new(risc0_provider, params.clone(), fhe_processor, false, None); let output = provider.start(); - Ok(output) + Ok(output) } diff --git a/crates/support/methods/guest/Cargo.lock b/crates/support/methods/guest/Cargo.lock index 86d53cdc06..d6f85fc924 100644 --- a/crates/support/methods/guest/Cargo.lock +++ b/crates/support/methods/guest/Cargo.lock @@ -164,15 +164,6 @@ dependencies = [ "serde", ] -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.98" @@ -570,12 +561,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" @@ -611,17 +596,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "bigint-poly" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/bigint-poly#9eca04d2aa473c5ead1e5a13adc8ad11bf250e4a" -dependencies = [ - "num-bigint", - "num-traits", - "serde", - "thiserror 1.0.69", -] - [[package]] name = "bincode" version = "1.3.3" @@ -679,19 +653,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "blake3" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - [[package]] name = "block" version = "0.1.6" @@ -730,12 +691,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - [[package]] name = "byte-slice-cast" version = "1.2.3" @@ -799,20 +754,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - [[package]] name = "cobs" version = "0.3.0" @@ -860,12 +801,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "core-foundation" version = "0.9.4" @@ -1036,22 +971,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "e3-bfv-client" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "anyhow", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80)", - "e3-greco-helpers", - "fhe", - "fhe-traits", - "rand 0.8.5", - "thiserror 1.0.69", - "zkfhe-greco", - "zkfhe-shared", -] - [[package]] name = "e3-compute-provider" version = "0.1.5" @@ -1070,66 +989,24 @@ dependencies = [ "zk-kit-imt", ] -[[package]] -name = "e3-compute-provider" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "ark-bn254 0.4.0", - "ark-ff 0.4.2", - "e3-bfv-client", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80)", - "hex", - "lean-imt", - "light-poseidon", - "num-bigint", - "num-traits", - "rayon", - "serde", - "sha3", - "zk-kit-imt", -] - [[package]] name = "e3-fhe-params" version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" +source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" dependencies = [ "alloy-dyn-abi", "alloy-primitives", "fhe", "num-bigint", "thiserror 1.0.69", - "zkfhe-shared", -] - -[[package]] -name = "e3-fhe-params" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" -dependencies = [ - "fhe", - "num-bigint", - "thiserror 1.0.69", -] - -[[package]] -name = "e3-greco-helpers" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=7a361de36d7ccd8f421b874008895cc07f246b80#7a361de36d7ccd8f421b874008895cc07f246b80" -dependencies = [ - "fhe", - "fhe-math", - "num-bigint", - "zkfhe-shared", ] [[package]] name = "e3-user-program" version = "0.1.0" dependencies = [ - "e3-compute-provider 0.1.7", - "e3-fhe-params 0.1.7 (git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae)", + "e3-compute-provider", + "e3-fhe-params", "fhe", "fhe-traits", ] @@ -1472,7 +1349,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", - "e3-compute-provider 0.1.5", + "e3-compute-provider", "e3-user-program", "risc0-zkvm", ] @@ -1531,30 +1408,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1620,31 +1473,12 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "js-sys" -version = "0.3.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.13.4" @@ -1842,7 +1676,6 @@ dependencies = [ "num-integer", "num-traits", "rand 0.8.5", - "serde", ] [[package]] @@ -2076,7 +1909,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit", ] [[package]] @@ -2743,15 +2576,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2945,27 +2769,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.7.3" @@ -2975,20 +2778,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime 0.6.11", - "toml_write", - "winnow", -] - [[package]] name = "toml_edit" version = "0.23.7" @@ -2996,7 +2785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime 0.7.3", + "toml_datetime", "toml_parser", "winnow", ] @@ -3010,12 +2799,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tracing" version = "0.1.41" @@ -3136,110 +2919,12 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.111", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -3322,46 +3007,3 @@ dependencies = [ "hex", "tiny-keccak", ] - -[[package]] -name = "zkfhe-greco" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/zkfhe-generator#6b424983347a4aa04c9725941f71ca6a4bed863e" -dependencies = [ - "anyhow", - "bigint-poly", - "blake3", - "fhe", - "fhe-math", - "fhe-traits", - "itertools 0.14.0", - "num-bigint", - "num-traits", - "rand 0.8.5", - "rayon", - "serde", - "serde_json", - "tempfile", - "toml", - "zkfhe-shared", -] - -[[package]] -name = "zkfhe-shared" -version = "0.1.0" -source = "git+https://github.com/gnosisguild/zkfhe-generator#6b424983347a4aa04c9725941f71ca6a4bed863e" -dependencies = [ - "anyhow", - "bigint-poly", - "chrono", - "fhe", - "fhe-math", - "fhe-traits", - "num-bigint", - "num-traits", - "rand 0.8.5", - "serde", - "serde_json", - "thiserror 1.0.69", - "toml", -] diff --git a/crates/support/program/Cargo.toml b/crates/support/program/Cargo.toml index e281d133bf..04f8bd6915 100644 --- a/crates/support/program/Cargo.toml +++ b/crates/support/program/Cargo.toml @@ -6,5 +6,5 @@ edition = "2024" [dependencies] fhe = { workspace = true } fhe-traits = { workspace = true } -e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "7a361de36d7ccd8f421b874008895cc07f246b80" } -e3-fhe-params = { git = "https://github.com/gnosisguild/enclave", rev = "dbdaaee1db9d868adacc03f75bb2ac85024359ae" } +e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "632766e4ed1ceeccdeb023a56f16413a33be6f46" } +e3-fhe-params = { git = "https://github.com/gnosisguild/enclave", rev = "dbdaaee1db9d868adacc03f75bb2ac85024359ae", features = ["abi-encoding"] } diff --git a/crates/support/program/src/lib.rs b/crates/support/program/src/lib.rs index 2a9809fcfd..839a495e5d 100644 --- a/crates/support/program/src/lib.rs +++ b/crates/support/program/src/lib.rs @@ -4,14 +4,14 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use e3_fhe_params::decode_bfv_params_arc; use e3_compute_provider::FHEInputs; +use e3_fhe_params::decode_bfv_params_arc; use fhe::bfv::Ciphertext; use fhe_traits::{DeserializeParametrized, Serialize}; /// CRISP Implementation of the CiphertextProcessor function pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec { - let params = decode_bfv_params_arc(&fhe_inputs.params); + let params = decode_bfv_params_arc(&fhe_inputs.params).expect("Failed to decode BFV params"); let mut sum = Ciphertext::zero(¶ms); for ciphertext_bytes in &fhe_inputs.ciphertexts { diff --git a/crates/support/scripts/container/start.sh b/crates/support/scripts/container/start.sh index d0218822ad..5ed6a6526e 100755 --- a/crates/support/scripts/container/start.sh +++ b/crates/support/scripts/container/start.sh @@ -2,6 +2,8 @@ # Clear any existing environment variables unset RISC0_DEV_MODE RPC_URL PRIVATE_KEY PINATA_JWT PROGRAM_URL BOUNDLESS_ONCHAIN +unset BOUNDLESS_MIN_PRICE_ETH BOUNDLESS_MAX_PRICE_ETH +unset BOUNDLESS_TIMEOUT_SECS BOUNDLESS_LOCK_TIMEOUT_SECS BOUNDLESS_RAMP_UP_SECS BOUNDLESS_LOCK_COLLATERAL_ZKC # Parse command line arguments POSITIONAL=() @@ -31,6 +33,30 @@ while [[ $# -gt 0 ]]; do export BOUNDLESS_ONCHAIN="$2" shift 2 ;; + --boundless-min-price-eth) + export BOUNDLESS_MIN_PRICE_ETH="$2" + shift 2 + ;; + --boundless-max-price-eth) + export BOUNDLESS_MAX_PRICE_ETH="$2" + shift 2 + ;; + --boundless-timeout-secs) + export BOUNDLESS_TIMEOUT_SECS="$2" + shift 2 + ;; + --boundless-lock-timeout-secs) + export BOUNDLESS_LOCK_TIMEOUT_SECS="$2" + shift 2 + ;; + --boundless-ramp-up-secs) + export BOUNDLESS_RAMP_UP_SECS="$2" + shift 2 + ;; + --boundless-lock-collateral-zkc) + export BOUNDLESS_LOCK_COLLATERAL_ZKC="$2" + shift 2 + ;; *) POSITIONAL+=("$1") shift diff --git a/crates/support/types/Cargo.toml b/crates/support/types/Cargo.toml index 4389bfb609..6d9a016ee4 100644 --- a/crates/support/types/Cargo.toml +++ b/crates/support/types/Cargo.toml @@ -8,3 +8,4 @@ serde.workspace = true serde_json.workspace = true anyhow.workspace = true hex.workspace = true +derivative = "=2.2.0" diff --git a/crates/support/types/src/lib.rs b/crates/support/types/src/lib.rs index 2ef89cd432..79836048cf 100644 --- a/crates/support/types/src/lib.rs +++ b/crates/support/types/src/lib.rs @@ -5,6 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use anyhow::Result; +use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Serialize, Deserialize, Debug)] @@ -23,23 +24,28 @@ pub struct ComputeRequest { pub callback_url: Option, } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum ComputationStatus { - Completed, - Failed, -} - -#[derive(Serialize, Debug)] -pub struct WebhookPayload { - pub e3_id: u64, - pub status: ComputationStatus, - #[serde(serialize_with = "serialize_as_hex")] - pub ciphertext: Vec, - #[serde(serialize_with = "serialize_as_hex")] - pub proof: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +/// Tagged-enum webhook payload matching the format expected by CRISP and E3ProgramServer. +/// Serializes as: `{"status":"completed","e3_id":123,"ciphertext":"0x...","proof":"0x..."}` +/// or: `{"status":"failed","e3_id":123,"error":"message"}` +#[derive(Derivative, Serialize, Deserialize)] +#[derivative(Debug)] +#[serde(tag = "status", rename_all = "lowercase")] +pub enum WebhookPayload { + Completed { + e3_id: u64, + #[serde(serialize_with = "serialize_as_hex")] + #[serde(deserialize_with = "deserialize_hex_string")] + #[derivative(Debug = "ignore")] + ciphertext: Vec, + #[serde(serialize_with = "serialize_as_hex")] + #[serde(deserialize_with = "deserialize_hex_string")] + #[derivative(Debug = "ignore")] + proof: Vec, + }, + Failed { + e3_id: u64, + error: String, + }, } fn serialize_as_hex(bytes: &Vec, serializer: S) -> Result @@ -146,19 +152,52 @@ mod tests { } #[test] - fn test_webhook_payload_serialization() { - let payload = WebhookPayload { + fn test_webhook_payload_serialization_completed() { + let payload = WebhookPayload::Completed { e3_id: 12345, ciphertext: vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef], proof: vec![0xde, 0xad, 0xbe, 0xef], }; let json = serde_json::to_string(&payload).expect("Failed to serialize"); - let expected = r#"{"e3_id":12345,"ciphertext":"0x0123456789abcdef","proof":"0xdeadbeef"}"#; + let expected = r#"{"status":"completed","e3_id":12345,"ciphertext":"0x0123456789abcdef","proof":"0xdeadbeef"}"#; assert_eq!(json, expected); } + #[test] + fn test_webhook_payload_serialization_failed() { + let payload = WebhookPayload::Failed { + e3_id: 12345, + error: "Computation failed".to_string(), + }; + + let json = serde_json::to_string(&payload).expect("Failed to serialize"); + let expected = r#"{"status":"failed","e3_id":12345,"error":"Computation failed"}"#; + + assert_eq!(json, expected); + } + + #[test] + fn test_webhook_deserialize_roundtrip() { + // CRISP expects this format + let json = + r#"{"status":"completed","e3_id":12345,"ciphertext":"0xabcdef","proof":"0x123456"}"#; + let payload: WebhookPayload = serde_json::from_str(json).unwrap(); + match payload { + WebhookPayload::Completed { + e3_id, + ciphertext, + proof, + } => { + assert_eq!(e3_id, 12345); + assert_eq!(ciphertext, vec![0xab, 0xcd, 0xef]); + assert_eq!(proof, vec![0x12, 0x34, 0x56]); + } + _ => panic!("Expected Completed"), + } + } + #[test] fn test_deserialize_compute_large_payload() { let json = r#"{"e3_id":0,"params":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000fc00100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000003fffffff000001","ciphertext_inputs":[["0x0a8a6c08021080101a806ca049c3e165dd0d724db8e7ff91bd3cbdcaa21b19e3f5d254b350b328207a2d854f4b244107b674477d101f50daafd23feca3771c0923bfc2a19a419e095aa967a0d7ed0c7b59665acbe2fe8ea7a99874546e2092f93f37be13879d8dd6364d06b18f3fdf4a678d31b7456222e772f9c6c49ba9e4d7541fd859905f357acc380e3bc259eb987a800d20feb5fc4ec89a5d6ba519ffcd7d75e6ceb448852592db96bf1ba3e29054b4aad860a6f433eda762cce5960a23f19ca5f6dff895bf5bb78a15e3808a608b9ed38603481edaa9208f0887841ac4af289cf6b6486042dbf5e2ac1b53199beeef6129a45df4c6521664f304b955108539dd14596065a14d3f339936b421dd47bb66b2a49b367e40c8f9dd09697bc04aef671d1db930b0855326a8eb31975415d500b0040e6e56f40b6574e5ff2cb154d6168015522e504de9b25ecdcf3837d854017be5c80bb847f368958cc1ae157e82475ee52c9dc7b5e5ef334201c370af95a3af1c790ea9e27a538e44e3d82a6b0181b7ad883e4f7def4d2706c208b3e7441d9afcca4dce9247fab7762a6ce2d1624b3e18672610f219bc9755c8314265185fdcaa3dc203bd79ec6e6ed4a7b0b3ad5db383cbf290acb4ae9eaa8904d3216df99b68acd1efaa78207b89aa7814d05835b501680dfbf8d8079dc01312ca147f54234f010370df9ffbcfedc737c87bded7a901cea5a3077ee7fc4af96f8e6386245abf707a6b68f1fabc4b6b0d1704c45852aba599db14fb1cb62d6a1740d5813d6bacda9107fb6b0a1e26c2d5388fbafd9d53b6c0e26298742d3c20ec17cde0720805295ccc8d5fc930bf683b83ab644c8c59b55d4a0418938f44f597cdec69c97c8a1baef1f674cc18afaab16a3138ec1ac664c200659b55f18fabb7db1b17251e63b05a556f129659504f6a2a0207cbf2ffe353aa816ef078ad4f6ec2a6a2e5a7bac9b3104b1b6c234811e1bbd415594151b73a334b4c3f28dd865348568b3a4fa406e56dd7b1b0562859c8cc1d0dbd2b72d34c5e4c9a7625024b6e3918e1346aa9c82280787c4de8842620ff78483eebe5349de09703afe32eb938e68c2030e9c0a8e4ec730279a1eb63291801d391cc89b6d13febfaf85c82c832af74681c0483efb6ca8a384b33775720246832bbcaec495efd92f72602e682a75c9d9f6d0ad35e73d1a4017a3868adb52ebe5a080ff6c1b8c7355eb39730b59c3c6e9a24c483bc997eabbdfa778eebe9d3f44e2e195b8740ed960bb27dc1fe08edd8bac12b264cecbe548dfd8264ed7669d7534a24f098aee146d46fc5efb6ada42af40899b0de9ffe27fdfbdbc6f914d6be23d9bc15691876a7d64c7aaa0fd58132f514fd3e772ae6b8dfc76e939576efec7bec78214a7aa2675402cbfd1c44f39af967004ac409b6743ca4f3a7acd50ad4af945c4d2abe0397079eb39735cc7ee1418026bc6822b8e42d0dc46b1e2c7db2862fa057c312c3c76e82d2e0987d27b7225e4b40eb67edcc2b35e671884937f2992c25c00fd8cd24e987ffb0674627dd613b2393040a70d5f7caa12010651d0d7ebddf17acff6b156562e61086e68971955c03fe0a7355038ea108302df3f47a91dbcd4fdc24a5e37920923f61c3a8cb665dc41687c3f6108f4dfd4536e305fa394a92e554f5fe3cbcb7f6c1a2b6799ef2690315ba07964fdafc64944d6c54a600362cdc6e4e00d1747cfcf9c433422607d51ce54dff2d7c09dcbf9fc282d4621c8d881e6643eabb28ed20188d6cf67ab7ba7f45fab1aca01d26b4a0076a6b6f029427ad2cc7babda3fb75cc044d70cf4aa9b6da079d318b5b4d8379a1913e80c5268ca4151db34da60343ecfd5fbc4fa5749cbdc8d432180dff3a480c8acfa6197c400c42e3f8eacebbd769e4ddd2374de9c750f84063a827715457272559035fdcb6e39fdaac699d7285be86d3aa64486085d3ad9c43344c9b3736c2469b9c7eb00cf98f0466f07f738cd3c96e12ef0728f20116ad462fa9cba27b7b7b0f6b18e94050c19fd674e43a2127bff51045ba975540790ef36fe3bfc2db738475b36f6abf5e9140bcddc95b1ef31992c38e9c96553d662f5f9b352b33237cc46efdf5f2e35980f07342678ade5ac5258463973f4d05f63edd0479723ae6a701989433c7d0dd4534d82e73acf22e4486d1c61e890e0246020b4b21563a4290ec4361b91517cf8c63847d48b36d8fb36c0dddc454f80671bf2d9d71f35307bc789948e27d9f23b079fe2ee1ba9e0504de1ca324c24bd915e049394bbf7c28fd327ab906a18abb6f33b40cf01a1217961948e8417adb55d54b7ad18e04c8da27f881bd554cfa46636e11d0a60ae5dd7f2e7ad27800ce53e0ac3dcb2e8556485b874b16f49486f9d26acd1d6afbcb06e1da93717fb6e94f8f0cd80840ad3f1ead39dd3faa2fc7818228963b1db2400984f94513ddb427176a63e4af53da9c31f9d279439a53b07e19ab68085f5ebd9091b2cdf4fd85d19ea757cdaf462b13871ed5a918b48aace7c5ac1287bcca734c65c5e330de3c96ea6eda9ab1450afb830e5afc88b2b925955097c4ad1609465368fda745843da614a97f7411d79268b4f2361e9bd83c83528dbf1a87fc6f89a7e2abd6bfa27c1be2270d28b6963142a07e6ee8608806f34247e2848e7a5befc8bc87b221fca7bce629d1c72e242f40e44edf71f062501947d68b2fcc6e038ce8a43dac30e7eff4e74f3c9d8f642ae90b57a6ab886bfb60d3ba42840270b15830a75538cc33fcc2bb149217530754ff370719d125f2b41096cfa4c745de9089e69dbd086a06a704abf108331a20a5ca1a72b723ffa157c6c1cc7b8a97bbbf3928f014748c9fa89542eaa6016c664eebd6cb01db4839c2083d5a4cc38a871cffeb3a52667c593ef5bf65c11b4ea4732bd3a6fbeb8ac389de161c565a6653499ffab5a71b09ace40dd79b6d7ee919db59541ed7ab694d817e37e467dc8429bd58b8ebdaa6c1380b0269a87e6fbea6edcaac0b42f5798cf0cde4a3d5cf5ddb724858b63fae88826b115d00c00bf0a909c1d364ba5766c890eb4fb865b2edb3d6726c61d449406a438c7f1c853329a62cc791f928ff449c4f289aef8a72da06b772aedb21113c27e79fdff81550f7f5269ead35348116eaf8401459efd3da0ee6918429ba16335d535f0af3d33d8a4c146bbe390b0ae6228aa41b37ba9177f5fdec944ff8156cb4ba786145caa47e4c3513be0fb8e9cbb220aa647f9c87cec202b8d625c639c8324e6233a10d0d74dcbe6d391cb7f5bd3e9ce30299cb4e01e569bfdc0324d503737e9c28c58e3269deb1688bc70c0db4c6e4c088c5d1aaf31c598c010a5590fc03d9262601741dbf0662aa71f14fc4cb3643fd6559b3407f2f3469ca208187e22f6bcca28df708aa93e8dc85a64489565cd69966cbad6a0ed805bff40c1c9ab84ccc6c4fc5ba8aa71720274301702df3c874813977ce2f2026213924a66557b88fb526e12b943bf9bdbc496c2e35c4193457650797b599b6362bb6869939c0fbb51486112a8ec38ce49d3b3a3990895f0aefba7bfb928357551589346830eb3882cc840965eb55b8860a2594e613cde2613663327c062feaa3bfbe192c5c53a29166add212d7ace1bc858fc65725163dd5fcce9e12f1102e320ecc623904339e370514e0ebc4a3ec8ab11bd099ff8805695436548433fad5a69d7e08b3b8e1569a0eb79a77e2252442434393008bc2bdb003b2175b79f47593d99146d041c6df7abf8bbed6a957e7deca154d53d597625849050c453ac2b8bccbf3b49989d8d0276223dcb97e9d3f209640fa2d455538fffff1502987953d5a34e27088515bd08cabce3d9ff2e35ed40f3b464a1797896eb05f79714a855b013d864714e16a553ecf79f71ee935871bccaa30c408e188a48f4538413ae13fd2091fa1a36acc981753e937ddb90a20af6f9eb2679ba5ba50a77d69b5ee62f11d1bad3d3efe31bdf108f1620417933170bc9062c739b3848ac61f68cc02b01987b74d33a6ef2025dc512e412153ef6a69fbd767516582dd090ad42b4e74c726cf49fa79f7ee18fa003420f5559f345c5b7deaaafdf33dd1664926ec339da5e08cec6a207f264323d70f90109a3621fb5bf2ce0672aa9c4bb52f66a0b661866c9792328bcf9b39c0073bab28757a006c9c3d862d6e208a16f35714f4e562fc10881f503277e035b9bdb9b2851204f5fe78347c6cb34fe3094fc5b41433fd509bbddb549840ded7aea3e13e3226893852e037c12f1ddff92ae671db83e64f24245e2148194b006448bfc6a3ca1b961c49f6850f8115f3c17c7452cef01aa260a2f648bcc4551ed00ae3b124f0bb37a2b50a206483cdd72673bc366c88396b76ecd789bc32c52a59cfb322a1758d7be23599e2f41a9d50aa3ca12739f0ea9fa82840dbb113fffd6e9e1fed7af23938c81d1801b1551216180f6a891b50a4336f67d4bcec74d8748ca2289da14cdb975c22a00f746183fb2407c38f006a246e9b1f562ad6a0ddc95f820c9768975310558044df1c44c6abc099753272ec06650a4a1117cdc10662bd19f66d9c2d8596ca2eab7137fc731f53a93154f37dc24b254d1c03d1d85244dfda7fb8ffe8711f6bdab0da62f7bc77f172c4d485f878aeb1ba4fc9ef1883bac943982341a39753ec2ef9b5c305d706a8b48c69a119986e4a35ba87036ccf1a2f3306f07b5776a25659b7256581a9bc471dd86c4f07b348dda7bd4f9f902e41b0155b0307209d5d61c400c1347b4de54caf3161ccb3d7f0b28661d57e737dc625ae60c587d3c55fe039dfc5afde9d4c159e1945f42586617501ad8b1aa0d8cd2449ab7f6df7aed7cb73b90c06753456b929039012451957670d1ac9370f28e5b6581965f8b143ddf2e2e5fbb6d8e43f7de75ee7358749047e97169899285e85b6fcfd4e4bf3238e8ac109e6b399c9209469b7804390970b9d0bfdae2ddb5a9e3bec72bbf458e1a23faca596cd1a432ba9e294503a97870e42ecabd3b5e372b9021d96951e98b85e4899f18d818189e64497f89d9e3ea93fafdb6ab31d88322e6f82341535ca39520827b8af3966c16ef6bcebabe58546f206b63745214c8d2b2d31ab1a54638402ac48ce253e8de8226f3640f989ff5968a1809dc010feb1a8693782aba839563c8c18d2d23a7969878f1585019a0c4475f3413479eee743f29d26a1bdde1922bf9fc61bbdd0ab390fe7cf74cc7ced8242c2c024e097fd17a96dc1e6cf5974d8b9469aade07334da346f589890a1d98fdbeb3a268c165ed4caf2655a2133095aa1e4574f260d79355123feff27cad8b5c51cc1e8bfba3af0834e90875e34129253e9d4659ef75ff71cfc49fe82b1c57b3f1deb3e9a37daf35e37f52cfe98a56ed9d3633ab21d39376a4c6e55771983ee57693b2dc28e6a935f84071d2729d308b8ad192568800ada82ae9ee44269189951f120939fc73f2318f75ed4d89cc135ddf2a5d648dfc530d55c63ca4283ffb7153fe86a37a549878995e4fd48dbc816e3a7eb705f8373ca1d1c3cc07de5cbb5dd5b0ff68a8e6f64f76c60e3362a10851321383a7f98aac1e0006c0fbe193c2a7827a298fd7d5e30cbc661b93172e4e6144f6bf2d23abcb7b8740b6f8459f3bce6177f6d0a92a26e9683c76f0e6b615cb09d251896d30c23e0ec1ce88433b8f8ba00014382bc87e0769588b5b5b970046dc9274e56d466ee4ef59d4287185aa4e6b9dc241145421443ab0c631b579462513a87468a7ea08c4f76d2d4d788017a4892a4efe60207f7c884bcd6a72ed45ceb6e99246f18b0b94d854a27a1084224299ea2669029ed01957952ce10075804bdff3d4ae0e58115319fc146481902fad05923e1bdf3a1511bdbeda1ae9d587edaa02acf0ac613889c4ab7e8b5bdd28e85427921e15c0b4f1c8404bc76d40d138e5deb02d632d7aa4d2fe8460ab8136928b456ddd4f642dc24ac0e0c21946392b8ec5f74f4ee9ff0f66d1042333396d4cf14f0cd6c8d76746af2440d70b22810e34f5d34d679a82118320fdb397e65fe2279337a226161197e72a3146d006f24fa88647293d959d8956a480cee9c298d734ace59297f0e51506da0ae462fa966d869bcb7923278b4e74926384ff9ef102a71d74dac0290e670d1123440484aeec7c77ea1388214422c27994239017c1b2473540b662bc30222affca45b054fc1903991bcff310321fa37af7af15f2051d11d8b12a44f20ed4dabc8f4be5c2ed91c35e9ba597db04fa8c1169c1765fca0e7884446c4787cae17ea7970d566ce218de3c76a7e65cdce6eade69e8aab807093057f4489606ba4ef49e70fc78974ac2d7305cabf142f3d0277dd995b13eef79b0fc397b60bce1827bb75e7cecaec8ee33ef0e0fc01600c8d6e4961801639aeb287159f7e7ca0aa151772a66952ba9c91a803c59d15c81e2431faa3274aac8bb2feafb490f46acc508d4d018b39a140f69ae252f798abf2d3671fd6ab08995b4a4ed85be958b7f44b837453b6596b610b7abe2d06842fb4e95142367e80d2dabe6b8c17c27316747328bc93d665dca6280654557bd14caa9ed1c808bf12bc2cc47ef2f4773f7fe596dd6ebde449ca20bd3946e67074229a69ac1f08dee5d9ace9c3d9ab7dfdfd9771fc6c7c2366116ebd7417dec676d1ee48de0a4a498c2eb031c89a82ba8f6916989ccf32c3c029af45a08d0d30199897d05b29a120c299812e7c8f992383992324861d57f27a553cb6b50a39003b567a293f496969bf5a6f4a3a197fd41b3fcabad659b426398942cd8b79fda00d644ec1429e60c3f16d3000c7b751818a548722026deeae5bc9dd295debbf52296adfce9e8715d6649942cc23bf80b38c2dc82f8ed1cfa30a0d9bb1d8c4a4eece32fb7d8e3e792ca8879785151a0e5443b63f81c9e52f842c2dd5633ca97a1d550bdcf7b165cb04cb381984483f0a49e79dbc578ca731d0bdc7e48921ec371f40b28ca92b99f2c6a022c23f23f9ac8eaf9e9cbd7a35fe8546dde4cbe7024ef9cc1b161e56fe8d8394372ea871efbcde6ab292797853a8feb11fb95ec02ec9ffddef8eb58e94b949a33c08fcdc5190ce14b60e82ca9012bd9869f376b6bed0d8582ff1f782e2821fd410a5249ec3bb5ea0efbc01df1c5859c179a7d64d2645f6fa02ca93d88c998f644dcd892d25a6a9a449bcebbc87f4b0861b5af3165bd30638e61236ff2f857160a8ae9cf9309efbc8fe94ff8e6fc7454f1c81ddce47dcc0bb80192352b4e25554250151c21a21104abdbe8507348763beff441739eed1aca278a244e812e5efaca70127a16a3c9c64779b2de4eaf936e5f159f8f38d5197575fc83ecae499b6a0e427dadc7717e166009a4026ba04938a55f00dab1587a4fdf75d89ae6d0c9c21debd128445fc922804097bfe260311fb19aa1d1835c1f0db069982e7a9e2ce09ff8913bf9e3fcc9988de2ffc237acac8f54f95b6e1794513c89e2dad62b5775f588f5fc6d2a48a43b9174332040bc89a27e1512facefba82d77f9a3216a36f28271502bb784cfd5f5715c806f04b46273bab8fc0a8b12f39d4d4da79bdb865598a774e710e44327ca33bc39d70e93d36cd05bbc3d56b997d9f2ec01c46e64af25b6005091d0c04de503d46a046c9a146aa35f0b06a7bd877e84d2cf52d6bf8c353d807c4d962eee67f2d5aafc6071fd62a10120abe17fac0281a0478d53b37eba1cd17e31ef4af12f42f52694982300f2a046fb3af606a9ff257cfe9ab0171f4ddc6e45d2613c16fd3336394c13764b98a2fe8f22f85bdca4967bb7c0cd0711fdaea11f56be8dc79ca079bed6d73957d3fa816ae883613bcb98699b833d55e22167a4c5303e5a5a0e8c3f5f385b269b9297eaca3aa023b91ac1cfe193042fa92a26ccdd96f522d729335b5abdc6aab80eefdf02e85a97b4a0a21d5173ad451294cbeb99288a1d6acccb087a4d9b3218dc3ad84868b3b21d01acd4cfc3a97a3579f2f23db1d955307008d9251999d362bec24d50ce11038fff143d70b5b8523c60fb2f56ea4b5101046e46b219c09f181ab3d0c6edb7cf2c7f901427314f0f0ef6636f7d670c8b241d8ce9d3161e1a7d736e47135c6167e26422d684515dc6d90dde11f2a38423ef2cf9b25c8ce0c97d1f8fbd3f4b7d5a3cf97d6424bd532345c4e4024bd3ed6ceac4c922632d42bb4f187509d63f32fb0181cd061e8ccdc45f2a30702389c798490e9b82a0b4c23dae3bf95e246312eaecdda39d9d4b601fca0b2bda3d26250fd35655d2517f068927965273a9a6d14cfe908f0466befdee963b766bd18a85b9af93dae09688d10cfeabdd3405737bd9ce484b16184772c3a40c9695fb54dba677bd1db8aa64b69ef2069e14493e7e18b9e86cfdbbb85198d73a932a6e972d830a0de56fda439507dc95e27db1c1512774aa91a4042ad17e2c356e94cf3bcdd247b39e5825440f65552b006db2923cca9e22f200fb21e6b7a7534e67d5a79670cd03659cc3501d752613341c413558de1ce4b52d2d3d5243b0e1f1c3a3d14bc6ae6947cd11157040804d2412d27b20d567e0ba88c03c1b60196fccb44a43a75ea0d0b70e465bdbcafaf74df63f5d1f5d958a59da490bd166dd18c60a726d0b56a1c67b04241a7f169fad65144ba0f0150abcec159c1693089ee93dcc37c2d099dc7f68181971aeef873a7e20d7772413d5cd245f3d9a5e56e0807139872bc332ae7df88ff36f4ced695582e986706d686359bf23903f994e5fa239c30fcb3dc85d3f8e28197a50b31c58dd19a8d33286fb03f923323072fe91a337a8d63aeb8b33e35011db017acd3287ac3e222f4534fd097e102eec9f22031b0ff0eae8ebdda1f1eb36cb540160fe985cfa28b36490fd350b5fe78a76758eb8bd26234832075e2435fcae22f60d6206cfe7bad8e5858e17800d3f55cfed826b920ec582674cf1ffa0c2517540b3742b09f0117cfb63cd68b13bf61f10742ab301a859122d862cd2c9bb9a70e58cad3c896686f937b21f653b35275c7b292b044d0e82868467f5d293f6e6ec7370c9ee7721b44dc6f19470726fe79dbe51f2a03bcf462f2194b0caaecc00550ef59edc6aa9acce9c2128263b782c5aba11e01ddba83c94e394d805c635b5ba01d55742554b3f2c80a05ca7257bfda43583e9e5cec06d9d0d55fd36ba42bda87662243a2978ee3427ade52075573018cd0b429fe7276f9fcff19f5df1596d76e253f8d6228821a5da697abcfe70ecc243934b0f75361f607ab8a2ee00dc5a2db003089039fd19d63c569aed4795debd782b262e8b968c5bcdeba373be6a41dfa08ad64da32ef7cb42c13a0f757c13e2d40bd8e9eb209b345bbb422ac8627f20b2d3db75f7f497f338f96ec013c81044e5761f0a562aebe1471cf5c47cacfabaaa93f9d659a90c3919f7fc4a9a9c1fd0435cdcc64815378aefeb182c95883cc6e18032e67067c59b2ccc094fb9d52f571873fbb59960050e2a04bece86a4d23640a0e251ba502ec4e2b3e0851ca98f88bd707a0aa4f2b507c15e4b6d38f2aebaab994ff884cb4ec0b3e7742b9f912bc63db025722787b5bbd7318fa362e83687e33233fc419ec2b65336cc88b10c73944f20ee4b57cc6d01e00505b0197be281a316006deb57ca466fd8e1c67122a8d95289f1bc4ebea5f30c3bfb7bdd322a4a993560e075951375fd901ae2a6b531b35ffb0c0797b3aca5e13d0063ea60e2ad9af26e2ff31b6dab7d7705e5475daa187aed78b2814c460742140e4375f9feeff8f0e67750bce584f0c2aa1627ea49f7cec1fda009dc47479359982a75c0a0e9e62e30e8ed113053cb4995e04997b2d1610fa58915ea4c87af5ef7e86d839a98a42686648e6210220fbc3392815c72d67a2e7d9d84590a4a733c4fe5e539ce418be57eebe16b2d8059d61f61a6f4c9a314d99973557fb2a57f54e3ff24398954d71fb9403ee44cee0e83dc8a90af13a57ba60b9040b575ee7045246e1279439f88a875d9dcfde6d9b132e46cafb43f5353a623b11b0d357e19470f764a53c43922feb3a9995bc8535ae4e577166837090775fdb4d94ee07bc13e780110207ca424fef4cbeb95bfe4da4fb151a8b3f2e864071a964c4746867f4fbc091b62a30860b9d61658f14615736eb244cc923a16fb5462290769d4e9cef84918aa806baf16cfb383397165ddd39ce9e9ea1524133eabe820447c1bd50878a1569db9fcbceca624e9e7695b7cfaafcd2e9ff876f05f6da03848caede660b8bba9dbe898ca05c0a076ee95ff213cad1b3854b51349aebe065a2b1d1f5c9ab080c27a15a9120ef9193eabe60f38505f62d3d473d9236f81d0a359c506eb4530185b8071178b8545df1f831e93d3992282df185d698471522868bee153dfa1678571fa9a800b035ec08d37f62b19849410db87915464260c7678b485e053a90bbac8b364aefc448aeb176231eb7800b17648023a9acaa69c193b9fff902f6f1c09dc10141b931f38a53860f20c57f3b2f2ab8e0c0d858a13ee3d0f73395929528854d9a2a8fac97fdc4384516ed54c114c96dc88f0dcdb2f921be29778a1b61b2c4b6339d6844227a7bf3b8c344f0b1f2eef8a27048ea785c7181a018721400b1c4b604497de4546d954c76186dc12986cbf08aa90bee611ac28c426c2040dd29e8437a2d11c29a1dac00476012338911e0fa245df97a6cdb8ef23d13506a838f19c43fd33b11476782d58135ed7189da165e41fa04890f15409062f391e381adaf33619571ccfcbcd60742e2d430dcab8425ce7dfbd46ef3a72e027a376f1a2fd1e92e78b86d5d82d45a66f9e55a32fa4b0096f5abe089187c8b032b4b9f224042bd0a45a5b6fd3e29c167b5e715cc89549fb40e2e4e96fbced9de4b4ea8e4018c665d17d6076208141a94bc5362583b2bbffc9fc8d79306be6a310c95a9b6be38ce429237b74c332e1ee1bea197917a1a2e4b7abf7abe3eacee0a945b138df1fd6b8942788cad7f116ed1dc5a419b1ce1fc3965ee17a3766ebd58c5ab61058cf6ee1f321c518614646cea26911914851c312f372583f385e79a1dd2ece6455dd30245dc046742a8b25763e6cb7e940432f46d10c5be70d81954067ff3f9db070df81cfc4da0db1d2a733e2770febf22858a766e984ca8b7b607cd749bd3a8ea0147d840bd24ab0022688bf942b651bc0a5651a479392eb507da1a3d9de2015e57b72ac1abcabe2c407ce6894087e565e27bd3ee9eb156d384839c01c5357df067e95fdb896b956906a734d5f6cf767882f2a45ab63e46f984030ad9a51b82e38ef8ccbdba7a0617f5ff499433d168a7d60194c076c3a66c1b52bf9cd0efcdc4cfc92b7f3c7a4dd0f7a12c1cd58a477a072033c156a7fc81ae128b9b3d50cc41acdb56b129ec2edb6d1b81ecd60737217382157ac1ea63e56c28f82c6d3facde849d4bb5cff9e749a88f7afeb464225d6f334c03f70b3da3bc743e7f7c5eaa46937c4e412a498ea1063ce4115d74565faf6f6f81cddac4595a32ab8f9867494e4793d1b5965558e264f7b65085441007adde5634feaebaca9cb0c4fbd33942fcdd7ece91664c54524f00d6dfb9563421527dcd219ff54b2578dede9ab9e2e01b5e5012385de900747caa4eeb772943fefffcf862be39080c2614c4d2d02ade05f30129d01007643e2e5105de0775f8f9bd63a83be110ac8b47d5c1986fad0fb3f7cfef8230b9e6eeceec85df2758631b1fc5bcbb91a3f8de6f7251a31e22ddf822cadd66adaadccd0a8853cda0ea06e2825aa6a8530412d88cf37845bc0443d3de39f7338c5898f82c268f60278d366235768c692946009a93d9f6f21ccd38b52301dab871016fe534b0528cde20e8d30bb8b3143875d84aefd0e2264b39098578871d0ede2cbcb274251ee0ce92ded6edd794089b7f9ad131a1ed69f349ab91b8d82f2cc76d9e3ad216404964d2a8d2e9226c3121730770cf43be4ec216cabd1c637b652c15964bbbd8bb22ef6080eecfac4b99bf87e16f4973299cfcf0d666317b178ab8c56b3ca8d7b0da257ae7f875db68ec2da25722ac7fcc1a1fbcfb3b8b771db8f6b4f9d079265cec3a343fa4a90c8281a3e2068d915fd4ccb1bc8a679e11595bd84f9024404e986316b450c87352f7e818a48083573af94b41571ac81aeef6c9c97436e622143bd2fdd82a54ecf0d3a254528c0218bc322cc72d5ba4e6e737cd1321b55145727772337b5667ac8a60b783c30130dd7961b2e63159cc9ef866219d2e316f5bb54cd399a4a5c3f465b924337f1f9d522087ef76bbe4a605dbf72ca09ef27274c77e765df97eb974f84b39a228f65d5558439cc7259dc4ea0b308c2afb7346236496dfe89a4b4986558d6822fb409ab59dd2d9552cb595a40b83aa7bd1cf0562bc97cf704f25d710799dbea51cd0cab61d3891a1fdbbe46343c593fca944acfff6887eef099f895a13772aca46265b3a87d4792beabcb97827205da5b93aa61275f2f619743caec9327fa981bcc549393c0b58ac2f076f10506378bf963cbe55b0f06c9a71c251d22990aee58e31587857028c107f5e610c1c780b378e78bb2b761aedb9e764cd7bc26b5425197ab833892b8454ab02e91215340f032ce6f35249517289ff62266a30d636c532f065b5540c1c55e746ce4c605f53f3844dfc12c24c4ab941613cb2acf8571219a1ad04ec2f5568f454c089b82186afa26a0b8d5ad36a79d73f1ab9a9ae3980e41ac827ecb5bab47a86841f14134aae92f2debb3813f315d909bf3dcc16707ba9fed3f471ad442108468661fcec68424f7cdaa62cf35a2b355437d42269a551f87866cec04d0e421aff55f7c80ab439ded71b50dd03365fa71167537fc450b83fa442e1c91de25c09d07d51bb1775e95d75e74420fa51c71770ae1c0830a56d648fef688ef9779406ed4b5e965a9a667947f28f241d0a22e8f01df4c18ef4e3cb5304ade9fe45747050250e64f9585d5de7f783b37ba9368f482bd5a4d2d9d1ecf819ebb0be6442a531d7a1c458078e399f37b4f251adb56b7c972355c6a0de6b7cbc740fec8065035d7166acd87390f13d5baf7d6edf176275181f288cb1b24242291d133d8838b69e6c9351f090abf52da73ee16b8a98c808a2afb0c486dd186e245075b1f28d70068aba8b907215901ac0dabcc3417f08aa1fa485941860865dc0d904d39bf2f18fce14030ae958879ce9deaf78b83cb4de9a57efbe56a7aabd834cfcce75123ca9f036f3cef1d56937a84d3bb92744a515252b0f3bdc9d82a03ad639f7e13fc8d6fee9aa8a561c787c391d237f4ad892f1d50e37284ccdf7539afa53c53bf97326d72d71a9b70133029aa169b998c3ea757dd2555180a4beb95825c6f39bfae8c15bcdb4be0a6cca7eea462f562ab23f53ca0a800d440746bdb3b4aeac234eac7acf05288181a72ac40fe2a8e0cdc0ff5282bc0aed6ed99b7f060cf92cfd8beea2ee30b2463bdb8ff236ac3068ca5d7c295ec61417fc6b802f1bcf425bdecd2342a962d2ee7ea00e1850830e5f45667fc150b30f0fa2effe1adac431da05b4eae913636ac6c69fb9666d889ce21a21725e444355b200efb4d9296738840e88ead1798cfe9dd4918ddf02c78dff8bdfc382287fdf8980d88b513224cf2afb3d73f248b99e3e97eb414ee266b3cee0d08a9880b0acb157fca424420319bdfe587734d568cde589bb80bfbd79924e3393ac550271e6fa13d70f8491a276157f5fd11ab6b09e94288304486bcc2b636c79af0d64496b7513f6bb34ae9a656e5d0e44605852076fa4c606e5d9131b9ac6f1fdc4e415d663cca6e76fa6a43483d97f17e4de38104db7974643cc068862c1c56b903cef19fcb16150c0b7c7c1172c58e388c33a1b55c728ab95954c515afba56bff5147acf7f3005942714c1cd3b24ed9f3b4b8418c5d6d3a3442d7cb0870f0a5bca6fb4ce31c87e59f320c34d57aff72e2c7de0eeb3998f4a5841f949909d786932b41b05f2dd4e190ba7284045fadc4c0b057c79d00d30e30aa31dbb02c5867fe2d81612eb64316979adc69f9e7668d12aa8b1cc28e7606aaf8a03f11caa67ebb821cf69bfbeff78c544759ab760ca211defd33f0018adf69eab503634dc2958a0231418fad840600b915ccb318193d0716f1206fec5846ccfbd43f985bf9474846b710b5456b9cc2275bc9bc83375e83c87b31668927523d50bff35ca11d8f5c2d23e5423b3a9a496eaec7145e419425e0b5644046c1a60b363a6e06ebbd668138ad4521b1cf09f591a80ed36ae37671b90330a180fc97be3d579940fbf9368de2178a087ecf9eeebdb81fe6e8e2f0549f7ca790d78914c4cc74731e092033f57d918e74fbb664d7e47760a6e1adfc6e67706465ddffa05dff9435eaef6b218668819274764123b4f1a1f1c16f01df4c102efa17752d0357de1184d5ac0b72150e56394c09a8007178f014905b420c81b1a457ef7cfa268cfb4b16216a8238b95466cb11e02824feaf841e7594a65eaaef75f6de5043db3a8dbba764e98add0dbeab420a93f874c5de458fe73e3b175df6c97052c10641d7a8ad310f4416ed84e837c6088e9685394d9a41d8b7e35fd49de1544c90c6113bb16d8cf6e7b365762122260a6c7ce36ac0e0c914e558423791d7c22cf505eb6debdc82c15eeab882e6214e018be2174ba8a3e1f0fdd87c9883d5e3c65cc8c649dcd268cb41f3488adaddde719ed5fd9ca571126cdd6ec76e9f1e831cddf9bb932d26586f934382e15038286ca068160636beb3e1af45c45b42976385adbc2c216875b1d4c6e743c21c241ab4c592e9b95565d0dcbef265446fa1aab340017c87a70a9aec75a8456e52affccbe4011c71287d69c6252acaa8ef47131f1403ff927ac985c40d724e5685c3f482acda6216710d83351c8671dc4a2e693398af92ecb0c9888b9a664c9d70054287738467f9ce13902ff41f4b8f1270eadd50f3a6f3e255275795531eaf8caf034925caffe36343540848f6e6f03033cbbf9c517e06cba124401b2c5985ea60eb52ad88ed9af17b251ba2ba3c59b0b2dcdaaed59bc3a34527f2df832a0d0d6b08cc2e801d332e445e921bd8de2b285d0e4e3d87b1d638aadaf75e0a265b5e1b1170d80bd73b8ad3342a7a981d43835835a62e1c73f3ec7293eff4e91607bb9a365876981a3c93d9aff0831380c6858c6c45c54249843ac6693d530f445dad5afd0f1b7316efc5262225b79cc44942a88bd00a1d98122b730cbae148b7abbc1a6a8dc454d53ef6ead421709ef84b5e7ddaa33c047b3902d4c78c09f8b91f4244068c3dcad9da3f3411a75380ff86d46b50c15e37da32d5869b9de60b907154ccbd6e667c4ab8ddcd730b23d96d0c841932742ec4351e58e6f878eb3d66c268ea3bc2745dee1e8e905293c647cfba938cfda9636868be0a645249fa1165965e02d8eba91860f28cedfffdf435eb5b9c5eab18956af4ea5c449435d6d2927b8206d9192bc0bc1a75f98ececea21d8087551e8b709279372cdcea4f5ecd29efbb7261040180448e293a7d2e71fa407a1962e5c970c603ac44fb4636b37582957b2b7eaf93408118a40ecf8175c8c3d4497df790407182a45f4ceaefb4df68294eec6f8308f5ad7808de8af5eda19d1ca224b3dda516412d2eb76d14bceaaff94ecc976b7f0a761d712825373a5a634245d2df02eec2950c2bd5c125022e6af75f9ff4448cc8c84c2e3081f7e4c1c3c4fd8439db822ac30ac24e8747269bc72b6c71e2d6c6a3720cc54a1d95636054a8e437227c09fea15a46b214ff445dd91a1c8f6fcf5878b6ed8e624aea8777549a80106e5e9b9428e64c3d5630878c033a0e14b5bc306102f380c1d7eaded4ad20b92660fcdb1fd8547d7fd7663bc1b298941b1b4bc15a6964feee961612cc398ca14a74c30982811db9cafdaac5bb5e22d3fc055738f42853f6007fbd198d0414621540386075ebef65a953cf072321d8c2cb8fd3b9f114ff88bca9d0717179b2733f83fbd6ef6b6f645605e3e20675e4b7eb519eb9bd0a50e1e73b8b93418a1677e4c42c371e720fad30134ee2ec4130fc7157f40817cca05ba1e6e585bd8aacf73c0b0ca4fb0de263199277e772594901418a7bc92ca3b787cdf103910bf7a25a48693501f0c1d3d81652fc7af935ec8aa70772d64249e1d68c1c5ad5705e67e9af387a7ba1faae7a69474dd10ca50c1691bf1dd7ba3cf0335ae341701db62bdf231219296c30a991dc370994a08c9204c00c043ff685dd26dbf4298a005cb2352fbd453abf8303739e2731c1ea0406cfa434550a3b93c486a8563de78e8f02bb2ed5ddb358efd6ed39a7610ab2cd00a4c5f29323a0a6a7783a5f5d1e43f88bc428841b0b46bceee1f60e52c16f2e833b15c48736a62050db111808952fa12572f9a52c7f5c31f77fa8159fa95e1d0a10f9e2aa831d98c8d8d2d97d1aa768694de823e78db257ac05fba35708acea795adff54fdbbd9caa9329601abecd27a679b75c53fde5386ca188d83c02097b82064337e402f58df7c1ba86466fbf23392b3c055fae44d72e5c21acf8e281e087b1952be9500d93c6d0d415c4b1fbea5f31dad8064ab7cda41b8eb8052285e64fbfa6fa12db84230c47aa582905926135766e87b4722d8d78ac1affd2318b5b52d37faeb2ac6cc36095f8de3be2cb7a8dda78e6642efaa0bd3d8866509d622adec0d6c1b628f7ca3ac62cd898a5cf870557847c6ea326b4cf4081e2bff183b45cf2689d174562538e34325b07524fcc8cce4cec5fdd8d74c9605d62f93e8ebdb4be0944583dc1d6e2aeb74b6ae978643ae71342e9e1072d717881d2c1e488e87d8b31a38e3e77c37c5402851573ba66482f175f1327174469b7850428a026d8545e9e150e8c739309bb6867f5a26d637eab865a3d97ed91520aa9f8ea9eb1e80ab7c4d7f5a095dcc3eed11427b4a02b9bbb9d7a708290793421ebb69d827d3cf69bbc755d9b7d90320cdabe81a19479639af5d8926d99e9ecd999333e110f4622ca0cd1d003394dcf4547cddc0fbc157709590ea56f1a39e642d0232cd667535b2ee233025ec1846f80850747034bc1491e5fd7b09c3133c0b3a04393f788a5f63d67bd70f45cf446b07e91da3797b2e485fda1f60344558a7a24c5858bd753b422fd568ef37992782ed90b811e3250e1f61ff01b7ef8020e55d76544c5277befc6662c5605d3a02944d6e81cbec2133a9ef3b9a686ee4e3ab58e8ddb1a082af6d91a2686c59b041ad900ba1a1ba9e867b078c3086a5fe8938bc34bf2719610e9319b2692a9f3e6ada18b014ef59ffe8503ce28775d59ec62347bc9ef646f35c099350391354d454852dcbadf2da9f74ff890847c6ccc0f304061d18d3e9d5d1bcb7bcff2ff297c48cd3c2a9930b3d55aa49856c2696c98559990909d2d966407e2dae527a39074863b15186fba55d0c33fbb46ac4a1e596bc647552447e9fbf93e70fc89a88373d8a19d3c29ce4d980b8580e5bc199b8d1f166f60e88e766c1cdb658b93a82704be97e12532141a409ce9a1767118b77379c57b00b085dc643f84a849cf4dbb0b00442a8be01b1496ea5b6f21c43d2f85ef6265fc58ac2a29247cc8ffb3b333daeb386f42b816ba68fa3373691ded54143ccc8473d279a00574a3732f7ca0217d431907e7ada6237ef2e556b04ef999b41fb5d4acd9145a54152367fc86ee509fbf088dddf2c486fbbce8293f3e248046739346f891cb428328e5a161a3ce49fce92410044922346c480ae00523cbd86c2ebc962ee95c6dc4d36447ad88668e6fba64c41da002e371bcc83d7c9f3df5685cae0581272b923ca29878e19fe6f16cfa72ae1dd24dd88aa3704ddc26e2164214e4b9fdc6a494ca7986df01c2196bb6062d34e4c6b29988c2b68bdfd96840aa8d6998ce2058294614e0331ccd2e8e86e5205a55a69db78afa706de10a4c1766255f9a71effc0a29f7c49fad73067d6486fbcde0c88d15e54f3153ce8eccc76a1cac24ba2f710aa16e1f93a1a235e628940c05b6a4ebc53073b767de18b5aecf118afef486fa033b451f145d2c03ebdf47fd8a6a1a42d05d4c2e28c6edd9c0733fe8bbf0f7d1fd1710cdcfd34eb82f80f0b42e05b5d40ae7fe7823deaf5af004d8cb50d8ebbe9b465882c2fd0267c82edf7255df91653080e94d0da44f1919241ff187219d33e385ea43cb36da7e652b3b5b9a229c4c74eb7f1a3011e2cc2edb93fdb00bf7fafbb28d3b5e4d626b29cbac94ec19a772eb7e27dcc38d6309b5feff03bcce8907f6b9720b02f08a8456954cf7718c04461c44ead0c36679dd414eb34f03f29d10db68507ed1f5d08b3754b4974feddc85dd22968f283fc83ad2b4a7feae9e1522415c7f0f818df363b01c1dc9405c5bbab8257ede9dbf02e10736c14e88b293481620719a27378c3ae015357ece9e706a8b1dc8fbb5a2dfb373f63e84bc5393d42d21eee4b5be99e6fe6fef77746049ea781930ae07f924b745b83862b6da37133aca7b6745c46fb035129352a45a7566ef3a53d5ad265421521fdcde90d29849ba7ca321206aa343902ad0efda87ec812d39f512be76121dcce4cce858ee66c743d37de0007e8a63f3ab0fab3e87a90a03377a8d1f38ee93c2b7ec561594e62b43da5862d51e14106bb162a5b54c119e03cd16b46d90b1567f4d5f22e9f8505bfe26d5affad99ba842cc72254a57465819b3484cefb02ff4324d7418f6d5d50727da163e032f275183a4b2f4950843a35cf916a4810aad2580b0de2328d41f4ff8f224cc15626a226da66ccb21869166bfb00c144bcd5d2113aaea3a6652644648b4299440a4015babfb199cb8b89eb599a956eade5e79e86cf87e4c05dc753c749407ca82189741db092ab2cbea875cf4a60335f3b61809b630e5754513b4eafe32da1e9e02bf6dd322f64155c1c17819fa76cbd196d192a7a81ec50df7ba0bd91681aa065cd0511cd05be43bd1c790a508fb131f91d6b71545170cba29c6c69665e01d6866c24635cfb50c6d24f83368342ef09936e31309ba3ddc3542b40aa63cf3ac272ea028a9bcf50b89f8c9f4fd8fab27ecc296d5c313b8c925ede2ca17576e428323b4476252e4a13752d09bfa48b6c15e3df8a3beb91f9c5237fd9df890e5af4a30bb9322d09f193a0ecf40eac9c3654f5fe88e7fd702bfa8c2bf5afe1ecf212d1fa20e1bd6236d709e286f55bb244f3521ecedc94e4039523f55f49c428442ae8a72cb8a104ee227c161b6e48d38ff676d8b262f5476040e0b025d0853bbc9a88192b927fb1d0d770f74b06b1b76c46dcc9eaa6cbce637aba6a980d1313ea76183061f793fe3df6a2c7e5badffbef0cc2d4a7272a3d781000874000c6dedb13d8e65574b3a766b6553b72b93e3fe81c85220da7cb4627ae062f90db660cdbae98f22c173fdcfeab0214fb35383f197113e96a0d2342fefa2f75e297a92e8c65b971e2338862af1a34b2d1eda3b44559edf38ad0985c68d032c45a50ab146d73e106905515b086a56a2efdd0a59bf7f20010a8a6c08021080101a806cc40a5a5e343aee2824b13e7ed4fb4de0cf1398b32fc5a5fdb22e7730f36fabf13b2fb370c2f839f5d0fb6fd3682a98d458a311083ebe20f8476b3abe63c6f9782b1e3e21e6cfbf16dad7bc61747f79a13313874ceab8b619f97b9110d2d07eefe65d7dfd3c347980634fe855caf2796d865a3258abb0d109eab7029d8453cdba2d0b99585cd8757816da59704f028b688535d5a7105a49b6c4bcb38912965e064b485f8599c31808f6b13a16ac2546a24668a3f1a9fb26b14e0b1a3dcd0250192fbc1dfb33c48631ce125084c4fdc2770dc8ec6208997a1bbc99f14eb2bc3050c5177d3678f37011484d7cb8f8d81f8f14f1b9b5a559c210e858480f9ae76408ac7431e01e31317858d4676f2550ea4a5e59a4da03cb11f68fd7091a1489a2a391616171a6ca733254f8d731ca91c11819e3135b2010f54948a3853b966d55d8c8c993e8ec1ded1efcbcbdadcb0468ce3cb1dac35561ed369d3f6b655a92960c4cf4520d0b7fd5505248d7cf9825c5ea35d991db943bb42a867255341fa9a9381389353c6004df18f1c629bf2de786a0291830c7f1f6bce65e2654f7cdbb3c4dbbdc4e62f7417e53d9e8fb1b8ff0ec4a9804bc2ea029561324292767c2c92ee5d31a3d1273dae232ceefc3168036e35723f0cbacf7ebd0a879f9f7bc0e24c90def80706379272b9384682a8d58b6cf28bd17fdc0a04d9e14b3330275eb50a5a9f1ace136a98358f985d549853045e5c0bdd61cb9b01bff8226a46b98734d51dac928c3806b1ab4bafa245f658c33be6e0ff4103cbe7806dea0f7071ef60a877757fc64bdcf2ab4bde05e9fd9d2145ff576cf0b3dbb4b0c674588216bf5e381f506895967ce837263f94095a537b5972dfc5fb6de8d99ea8b8be00fb04fd83dcb2bda36e420f9d87e43e0c559b95605a6effac66372464ae2680c45c67ccd68996e21f080416ae45bd621de88893d537b4c70223a47f8419ed20cbb6a19a095f2122b5df8306b2e1e665a5300269f399ac20a10dfa85252a3c8b70a47e6c11391cfed49f06425f6f140524cf2f23450d975b62303b4f2b478848c83e879222654fcc5f7cb0a34499e3ced1800722da54aef809a9d7654ee0837c41423145a54d1d40d22719a9f659156e184f406493ff7e48a0e37f31dd968d990a1edccebda1f6b55e6f9e46646b5cdad66de43718de1eba0fe11d43cf38a6bf428ce5e79eb2332d6e50583c7baa349301190d3e53f4495f07a0966c47d800dba1e53c7da241287cc77f5f6cd38aef7f91e751ed918af8802a768c536d19fdb15654c380171303378b6fed33cdf61c1f526f61b0c3e9e0cac4bb88de5fb55108f35d3ae47d9f8914c3d883b7a80afd4c2875a38d166a98d78169c448330b8a07956ba52845c1145a82f19fbad4ac171d17940d5c449e7526bb9f7190928123cd98d8a78a8eefde90be4ff4597ac102432faa1752b334b05b7d52049aecc0acc9934bb486300a23f5aea60a754ec895f1ecbc1710a32e559b79d2754acb000ee648ae5c782425ce3dc26a22d85575a8b02016853c471e05f189507f4ab700a5177ca1d501bbd54eb93e5c9ee92f79f79fc5377edd7694bca346cc67a4c0baaee03354f57b8d57b065a1b8df0131cb7583b1b6aaf6f8ab37839006f328aa6b2e5f8bc8488e7f90307a4917b9a0f78cc8b2b2e68d41363dd29d0f890a84f3bdea1cdc0f47fd620059c90524dffbfe9d1d9c48fedd4aed0c5e0dfb4c81b91fe9f4d0b611d4793dab526423dfab0d530dee889fedda823ec9a7213a7323503da49125cb9d93b0dd4ce641bd815a9009c9cd769f32de500358b1fb01e023b54e09783820bbb25e2009f9bd9f8fa8bddc64cdc90180edbdad11ba15674c9c8097428fc49b59643b68a11c6c0ba00d2a8a3001a9b1e4352b31c639a57698a18a049302bc95f78024dc9b9e8457723735d60fca2f0b5934360236fcf1b17c044efb507a6c3d69b05659f538eb4a97eb34636f6053f6619ecde2aabefe6409051c7567b17cfafff93111130a7f84270d91a2c758085cf88ea878e70ed4ced7f3641c67eb2545f2c76024735a274d3521642f6582ef7003840e7f012350fd514b72fa66f59bf865084857c215644edb9e0e75143b1afe221b345982588049715eb0042126ad8ddc70428928f47ed79579855383c75ab7df7a11e9216e995b1171fc55031110852e893950457e423f3bc861686908a2af5c65edb2e5c5716d27f0dc175e89099c8f3289042e2a0c7b8dad546a5097bbb0ec5a4487a97106e3b1f2cc81f966cdadf47e9fdec40623634911a9ef493476c39b3b15a29312668574853ad0202c24cead2e186e39477695cbce98dbeff3d1086144404927296824b84b6b28886f9e7924bb64b550c174dac2f806f916e78619d0b9bed92928fc9319361dafed6f682a839ea049626a74dd3accb61b028968d5fd162684a6357d5b221360cd8adde9f6ec52b803aa2c9d2f9f2cdd1223ecfeb212bbae78329f07a586fcd016f75490fc943a755ef7aec1fd5dfacdabfd71e92ffb00e823afd7c7c33e9c32f7fe0253978f7b654063bc766a23b5848a6b91d151cb8c7af5d49d2142ea518e2ae2424ca8a90684d97cc6c13ebad034a043fbecfcfe529b5cf75b6139cc4e1f3fe90575c4b8d06c07f4cc465d02f074b610be717b1123fb24245fe30c411018e4838b2eebc7c65663494790e08424abdd84e9752b0e689dc6539b802bd2daaf9bb9a24a6f322add96b80f24e79d499340823cdf69f67faf66ddc92055941b9991377ba0118ea86257132e7f99325715bffa7242ecddfe8051b05bf16f8b21df98c78f25eeb125537534032ab0b0de1b9e4c94064172e37880f05893e818904435d045369fa78cd80c2ca56779b461348d44615ab44dab10bbf7fcbb8da1f3757901a39dc4dba0de65b4a3362ecb07e81623a2d7b05685e197169311433dbee448ba93e80b0d0580e5ed231a4fa78251fa02830a437df8e76c668e7cf8394c3fa838552e82c97be755536da9372165d7afbb2997931d736d5388a732edb5aa54774adb45aad60829ea0b56d897c9060af9e5763fab6f5f231c115fe5ede8a6fefb9669b8a9d6f92235528afe5be1c19592e317cde339bc2fe9f5b6b40f00495002320fadac0855ffdb593c1d459ffdca721f624ef1d47d3f4e3329269480274d1f7aec1c86c9bb65adf4a36b06e6cad4774e7a5c5636cc6e9b1fe5ebad9cb30f5afc8b31ec16dbda5e3808ec9f5a9ad1de5fe8cd9ba29b295ca25a341833b33e1c8745eb44e9fb910c438545777ec875fc7ccd2f79d62f947c82e3f2daac82bbab2ecfa4ed3f39129e1c4a0f91eab6bc8a7303ecf55eb666c0039a9b733fb1e6d7d6cb0a852a26bc160529223f78dcfb388d6a0ef4929321bb8b561c5cfa2cbdb04a23d500056f847f393040495b7270588f2f7477cc90ed91093910b093ea510c4b96891380d539e1c971de0d352475838f22f5e4d10c089edf27838a0ca092c700f88401b62a8e62a9578cab647d1dfd53b031a7602751983f7a852f444b6a50651753072fdebfb67a86e420034492eeb94531e86652e08063233c84ab3121def3794d54189b08c3d55705f0af6d4e664188fc6115e985240238afea4eb1e26b91a0f451427b401dc1cdc816fc7f769ad463900d85563fd950fbe0a51713873fa97673e702084b00bdcb4028ac0a5974259de8657ab5b5e0df66c28c2307896103b142e4024c6416df4e4cb1ad934a348ab4a94932e75d063b71e8557e3bfea407e774c434afbda9a418a5bf39ee37e82e9780a67f94cad916ba6ae63735873528318180a8e7a9d60b366f2a8a05a2b368fd36685d1546d88218e7ca99b585e0aa228eea2aa8a896c77ec2569c9a6678e441f6fc44c4da7b3c8eb805e711adcd0d0ab0bc64cba3134a046752d2911748f5e07ec796837ab7787fb533308826a397686a53411d731d341127bd62d7a469ade421859f4fac7d3a7dc3f79ae824b8280489aaf45b7f1b73aaf82900f6bdf0340b9443f242c389449efd6ffed88eb38de8547afd55f7fd59e8c064126d094fba943369f5477d963c36fcad0eead625393ad7b2f903e7e0d9e1bf43618bfae9d5ee9743c7aa7480120cd134ab8ded4125373a76a9348748e2b9ee43ffa54548ddd175aceaae045fd32b5e94c68a131c41fbaba16667d2fd7b7feff6aa5596fe79dc5ce615f045060e625b1c285173dc9a73b99f185d4f2d279723b8ca243b95be151197da9b8597377ad361bfb0a0d410919a6dd4a3286a25a8eefa296a93fe629d8eb688bae4794f99136a78d698a8df0933dfcb5d0324b6431d377597cf0d1e24d44be50da7b00153201b21d885449cdcef7e26995d4a94227a69e1eb7e1405d79d18c4e94b2c9ac9e2b23c7a94d58f3e81a6e4b314ceffd6f162e50d1e23b846049a3a0d3bafaea480a85ce017355cba563cdd96c99183affdfae9dfde777b66bf583c41a6d3a7ab41380ebfbd63ea9181f0c65f0cfa68dae9395a7bc11cfbd5f94f1799a501554c5a8a3226712753f9f5825a50620fd1c5f5723bb34c2191bccd6eacdb2c3a0368d6f8d0c5743d25f948b9a80c915dd3edcdff42ed57d2bf4f8aa477aa7327260ba52be9d409abb573f56be311b55d684f637590e52433bd3280664215d502120e5fbc3f5763f1bd5459233776a44ec8e0348510330d8f20a69fd03962fc18422034507b0e4c3d272985b72aa94f561c4841526c15e268b1c6b8d4be47ed3ed765da7b6843605b25b1ff3741a6146f6958698a46c0f61c10c205145ce2cf79480199c94d92feeadce8a9b8c34b8c87c6f807e81d3bd002fa4945d012c1b1c2e5dc9d45f52f64b139bb436339ae7a1c4e314142e0771c28e27bd6867a53cc222848e0d292c2e70d9bd5cb1d3f9624844fbe49fe02da09161a4bff2ab56d8f1c6c3e71f8d73e706a7636b9a0b88826fa8ca7b121c5e7fdca80e3fb7c1bf48dcc872655cc5c6f4176d3559e687b1244836a2d44eb70aeb1b54adbd771984ee7ff0b415b98d5d8957032751bae77226196c48068c4287703c0beb0c20adfb11462c82f9d400bcb9317564292ea9afddc9d177b9f8386dc2d2f9c69b02f3f1fa2e7531c6c60b03447f5b98447d049902ee5b53ce9a9f54830a216ddf576fd2e17b5db6132c64f30880468fca4877015f0a28ff55753b1c535c8170335445a27cf6d07cb3c939d3c0c3d679245dad437ffcfe59853eeed91fb83ee49a4f5649563ded657a3664d269e902951e38e9c417c40fd6720c7afa30007e00710d9f3a982ca771cf6fecd32d7596b86158c363becca480d3b80a8c0b7c24537bb607a2ce0fd4c1883f4da984b8d125fc814fd6561080a5b202ca652c4e2540e8a8d48cba462d61c5039c6865d5233e36de07f4c615b3483be1542df8ff8ff65f86d61a20215b138c2b2b140568b66f216cdbd5a9a0e3592e94faba56e4f168740aecc849ebf695bfb49f234bd649ddd63afdac719649d09dce8a0f647e39906217fae1dec1acc9585f76ef418a4da92509e1aa3cc7a0d9dcf8415f0f12ea3b248132168dd3128eee5692b7d2223d6f45cb809fda742309c1a19595340e46e4b677d561018260edff6ffef213acfe571496f5a3c6e67a350878e0a453a9b90feddc9cb50eb919028100fcf9d7776edabfbaf25accccb4bdfd10955bc9dfe43e1076a980aa40af6cca67b53a85e2c343e2e1992415b73e62ce5f216cb6d4cf77200d6cc4ff87e99236dcd4c7a0ec95c92431e005e6ab49304298176cf475524f34a6eb5df593cf841b844739ec60e7c3ffc2b6f581c473b06251131d82cf1e6e26b88e2df00cb4e5e0eaf2f17700b58c674cac2ab08f7342bda833b219a781c072dbda63369206ae069bd281f1d886807ed18fe386a9fde17b18774025f3c6cc9f97b7906c2a8620581e94b79957cabaab202f045905c60f8a1c78dbb74ea34ed95ae7a1f35f67f30c2caba2134f4bd32cb4bf81e2efb72e9fdb8e57a940be50df13d04f39eeab82a8f43461918f71c8d175b2a7fe098f39ad15b26b4a04aa863ce508e5796843465b81477cc56184a9905bb325b412b359510ba169ccc0e46dc4567734de9dbaa1deda791b90bd5adea2c7c7d4ff42024430778adc3eec32115992595793c6cca2774e42e714418a6d84cc868489db3d3c1d1c934cf9c2bcc43e0b1a7e85452bac1041531e5d9206f514609b98ac3c7d410cdf717128bd39d0ece6f4d377d6eace3f892d6f973659efd1183a3a9ecbdea46a6af922a53f1220d4347c8268a784a00f1933af4f9a0dc251b3dbb3758e77d3914a51ccebfd593b07f4140bb1e08c32ad367c193f5322699b121557c2b732e84a9caf5c77bdffb3c6366238c5d5dfe6f060ca8a3699086e1f6494b74b02f49caaeaa880029005a0552542b667320a05d90f37e3cd4d1d221cf2067b045b7255fa50ba776df1b0fffeaa0a046d09a8f13520ea7e1884c5fcc1d1c82817501649a30a6df415bf403f528d7cf5b127db1805fc2549947a3e8cca9890b0fdf25378d7d136eb44f0e2aa046cd56ffc7026f08d9c630f90b750c7e56b22b971e06911946620e613d4140249e6a2fb2c0dcfdfa0c96ee8955af10f1d19a06130103cb24871a5ea018373992df2b1246e544dcb5202fb9c580fd626f79781fd7e0b4d7492aefdd5508287a0b109fb376ded9dd58ffa47a18b066069d4084024e0b0995cb6b9177ab2467af0ce72ed0f9f1f591536a17f9a93e2e4a25751605b2b76be006d6e1cf48f0e39bca6d361f7f98d52776737a02cc7ca572683c953196585168037ad5458b62e41d8540c2987cecfbd2154afcefb36afee3dbc495bb451a9b0efb15ec2f172eca85463161d9fd4ac57098d2f3830a5f2ebb0daa012a443463fa44f5905b30b26fa9149f8d860fdbd80203c856845338e72a7c1ca48ce784e83bfc3474b20d42416ddf9be1c89c76bdf1f9cc39966795ed882a8f3a274a3426b0ec6a15b3d63a8cbb4aa27ba80548a56b0a3e924d9a54974f7a4fb7d00746d03fb458829569029f39e4946b8c627edf9e41fa4cffe11969212b4a0f35990a19be5c07356ed8afd41080de013502700a238124f0570b0d371f861f6af8d093f562734b68db1b2997f0b3e0c2ce1aec980b2a03523a73f8a065dffb8dd17515f0e8ac4d271a627f6cc649415650b309d89757dd63448d97424b0af68be878b6846efcad7779f10a6ea484ecd9ec3002895f0d96d65a68c81b7699af28b66cbaa528a102dd7bf423a3a4174dce46af6fa5e98aef0105e244dffea6d8f2f1e10bcd69cab32ff7b455479dac07dd402ad69f934c549722c48980aca5582322ae36befe26440926290affef1748b5eaef807e847ed68623a4407e8380efc4c706d0fe0d103803efd7a0ef719774d650e9fdf7dd9b71beb45427d2d6f150aff61d935bddc60b38d9af1530f7a0472ee220336e96da68b29687427f09a94fc47f9d7e078eb92a3df59870e0e02f32f53776a5d29c74bfabf82fd46e713fa7b4c3544668265a4db942102b2f127eaadb57af5981ed04f2c28e7d922f3b3998dc7867199d6e4cb32f4a22b5824f9c9c01067afb3277dbd0da0c7a4f43b1eec362d6ed77ac014cde31f06708ee9bd21b709e874705e345955a125aa86e2f74d5311cba9a78219ce7aae5e392975e5519971baecd602024bcaabf4ef37973d488486e0fe5ebc30c333636c5ca131faacd7bbeb465c49094e485c672608eaf903138b8736a4f4bff230ede9176c349bbeb932f27cd3e0a0d9f0a8d5157912db833b0312d4a1257c4bbd9b31c3cb80ad355cd2a4f62834aa552e5e047fdc23c878309c9c80f626789e998095931e9e6512047a45ab4d730b75a68c22053ed0d9a2a5edd4a9caa012b03f1e992b42dbf8964036401b56ebbe680b35997231548a8632d6f4702fffb3dedb575ff0aff2ecde771a8c529b67a9decf40ee476a90a3a2f3cee61f827344ae02f3547b1e696f1128e58a8c1fd7e077e433c0a35aa7216bee91a9277677c8c2bebb3cf42bed2fc45e0ce406437d209c01c3f1fc27b61acde25cfd7b40f1b2722e1a428d05913f5c4ae9bab05de704d060715a5e8bb298a68a2231d1b08702cd7a52420205dbab9daffb0505584a6a9f449d306475bf68368ab4cee06597c703a89f4ff4dafd8317403e7d781d586a5916eb6f814ddb0375ced7d51ef4650ab2c48108ddca1f01b01800de41a5297426a54bfe2415b0a9896ccc4f3b973b45e0b1a4fa59f430e8e67b17d03368690397a457dd06be64d0279ef4751923332827abf32e7582697d655bcd3944403541d78bd5e5487623004327b3a8d984b6b89e18f2ddbac928e87c55bbea7c797a9cbc681ae257b4e58658935fb1ff87deebc755884b420fc77949c523c59f505740290e17501d871b4331e5a28e8c00d875302a306e819e3c8b915cfac72d5ff52ef5162f5953355792959dd5d4be46e54f9c38cfe9fdcb71fad9b0a02eef0ddb5329134d313a087a4aa49e36ebfdedf2eec6a723d565ba092b87d4528a6f2ccb2cea6b0f3f7fafede9183b24ddfc42467e7cdd094a2b1299c81a6fe8b7f13a5574fa1b2090c559724ad90916e0a079716f0c720ae2a22184db917945a4192a2f9765ad12132c8822fb4173120bdf6d91baf67864c8b3805fb87b92832251b45f0452139a367b7bcd5f2cc071836d2fe2c2f982d71632e4bed7682611f7e91c58ba111ba9e36dbcc296580b8ced40a7670b0759492e765edbe1f33ddc004a413440fb301096e55983fbc58ab1b420e5023303d7aab8e6fab1206044626f38b60493fa0068eba6cb974621d3152416a07637eecef4ca3e1895e2e5f79b66fd353db4babeddba2320f9eeb529148b15b6849c24313b17beff15bbc7cdebfb9dd980edd6a246df52bbb9749065f4d81e7af3daaa209914bbabc5bd40dbdb753680140ee05175230b74cb8b6c30ef4862686d18a332b41255a29eb37e9eea2e5cecc8f29cf0605c55b0b0b9553e231089400c0adf629052c9856ced87b96cf06b7aab580554e5c156dd81d564d661e1a803ca185f8b8ee7364653f827e86c2734ce9e65912b7239be93363980ad87190a4c989584f3df6940a9d1b6f9bdc4f75cfbc21566714b2e4d51e61aafaaa2f9a4494db78d347b6e93ac0e03e9a92796b3bb25b0ee85eec95784b175eed0bc6d5b502f11d3b7cfdd78a7dcd3959804a8953ce623842988c1b7abe04df5bc4d6ee90ba24ef4d85e321770f1abb8f39729d881b875bbf2731e899349b7796878c7d727855261121e33293a5e5fdbcc57134b8efb2fe0eb31ec34702913eeaa377cc1fb135a9495a0a1667a88de6086fee22fe573fb4a1e11d66be6168709333cc516326512e2a515b6ca9b0ef7d31096eb490096b32e57ddbd73fa334eb539b8f1a5f45fffe2d7c85c124588b8e645058364df45013c6cad1e729434f90f1b37507ce459f33ef77fe4644279f2cb398945c286fb4fa2bc1b5e44d5cd69faa98f6f9b9f7ed7e8f6982922b1b332a2e26d6ea9c14b8a4c403e449ca832dea4406f21399e5d1d4c45feae6ed318a7f40667b3d96822e7a6efdeb88c04e80347e73aa3cc3c361b455f1b5b334c32a3d32528ef6ca28b5ac68338627065c0aca53af1ee66880252b822d59f8d099c122906e390d7821caf20d0375082e83d548bad0f6b4e424c576941bb50234ac06bd2afc2f4fb9354ca501e273002582293af82559821a65257604ac10a2d2cce3e39e9028a29cb2452cfcc1baf2868c3d32ca3919232caedfd4a3c13309751a6b4189cfabf442cc938064b6beb754dc11198ed29e5c5ad6625e0e0b8c37b37a2781dd45c936698d51f36029f93fc3fb88d8792f998c0cac930043db8d262e6347a15d94dd128fc2b2e50c79e7fd039f8cec35dcf3397ee44c9e21dff66b4daeedfb4e6b4a79b723b2e2c2cd721587b99a7d41274691be7c8b9f8144eeccb453954ca11f7cd70628e52f9cc7eb2ac6cbf51a338b4f3c868091df803c81ff81a9447c9ab0f7d4317ea1f9138dcf0852efe907cbd28516dfb6546f25324053c83b0d0ab9b1b4ee181c4a7882775fc708c8171f3854c4ff0c76e72b0b818bd232f3aeb8825946d793552b3a79029b471cbdbfdc7fbd764cbc59cccaf67ee5cbde869e6f44701bd62c4c604019b9c4a28426137eb55c857ef92304d3d24134ada35ebdda7512067f6bd16d7c1d1ea6e0ed8e27cc395c5131c06bbe8d5a94169eb7e80b7779beaeb1d552e545954107e2decf315840c30e97fe3ad177f487c8a04a355e5c1f9efd0e5974572c222a437b23e62339d2a38b58c7fd0c0892ca2c5bb198e798fce395ffb5911cfaffe8a55f23f7fc906596089081850c362e64bb6f69ce3296801bd32db9cd1edc6f0bdc03796f28e9e6ae92afc4cd390f0b2c94c34e3f76b28c97c1cbab9b16bb1c4d680a840ad05c3f40a2957ad8fa1d49c1940074ea55eeda292cadaf05edfd22317c9d48ea26b708ef6db48c9d8ed968ac138e9ef7373056b7f98505f27de10c360a084c63c82ac0f3bbc7a047f8ddd16dd063953158369f8ed48dc878cd1069fc2f7d001fc17f956db2000718769d7eac07328a2e24793fbb8dbda5f8b515928885ca65e72c544f54d07b3a6489f22202332396e37a02e15796e6e6aa7c9e9df6b8cc2a34194b6cd734e59cb608498e9b41bf5d23d63b9d6d15a412ccaccee1cc74d9a0e7e164757dfca4a642b75f44a1d88758db6a1b28afbdbbe28bc3a89f21c08e2a0c941b5cce308421f0bc6a62455120c4c0af0238ac5998c02fea7f42bfa239d01e39007f2d53e1ae7e48789f883098c5a0f5db3b39a51e109312f35780f526f532ecd428412127d2f7f5c7a9ca0df0635e557da30e9d86f1678781bb95d089c847393fe51bf3fbdb4121786cf46ed1d2f976a446ae230842aed51786812e6a4e43d90057abe8843f8c30f811065c6e49ef26e840c463b560b3b892204fa2cad1f9fd3c6c7a25dd4db956c2f5bed0d5a2b3d08225174a108af9415e70c61862f3b51596439a98087a66e4352cd7d70b4a8b9f98985de6fabf8cdeef8ec5a2e790ba3007bc716b72bd21b8962ca9d445abf4ab1272f3194aa397e0e57e790e44cd6b16cdeb987b948532924727fd1eb25a3cec752a175c599c1e60af1aba977c25118bcf67d242b682a646d03e50fd2ebe5ea61a627603432833cafe52d6f07e1a6f45e48216bd057c25d57661af31e3dc27a018f156a273b45aca344c665ca3660b46a281a37f29dd34cf09ade186e3bcee512393adec99e1a6eb27fd4eef5a561b3a6bb2b7d6b96fd48c35aae4301f65a03548fc33d92a5ff035c618028852604a91fd31c3bf5953942cdf511c05649c84b7aca3aac5e1bf74768f926834b415eff60d5c12307b81e4ad49c22b4a9c6f0c6164504121074a1dd20a202fb877762c313f1484bb39ef5c134e705477198a5d2a42548836c9f6585fe629b287a4125b668c91d3571aa689ac66c40c20b5a5518192a0b1a458baab89c5ae738f9592b22770b647042549b23080aec0ff3f08ccb2cd85eabb18c305f06ed0fd29bdb0c7933bbe0947e709e056076126f9980ca51d6e172102d66b8fad996cbac4b70bcae218abbcbe9336e48c5c0154a6e0dcf3452e28c5018ad24ba6110614b851d24ff609673b176a62647e168be9db8c688672c2e3494e4e0553cf7d9c4ede27181a5e670264855a459b20d0f62e4be811ca87cb8681e58a21f892174f1e3b8d3fc51377cac1a6995ee6c86f5286bdf28e8266181261b13680c82a24b6c7b5047d9306ef4606dda5a1b2b8d016b585aaff23985c5d18cd9a9e03bef061c4906b0a044ac990f090bd037e86559307bf0d3b5be515b6f0b8aa4f4e582a9de1fe040f35d542c8afc5823b1ef4ecc612fa3c25e1226064fd4e2b9f88475dc3fa7058ac737ca85dc17808b649a2aefcc60c0b00bb7fe6f0794fd285f8afd029d8f0e23ea5fb9b34afc4bf5957d2b36c0b5d6ddfe2f17188e9c2bdb3d183c38002e1d18e3bd4723fa6c70be85bd26e3eb7131da6b1ec2ff3954b019f1dde9cca69d50d1d9b7469f210b1f0f9740756f426a194cdb0f7a3339470e539eda0443c490f6071615dde096f6b3ff573e3ca7861408b5dea735a75d13ca8bd5852249668960cfaff101c575071615a1f27942c93e261f0eec5ad3fa7c4e925999292be2222b1da928afda55cf34aeee8b641f8a1144a5ccc5da6c8aa23c7c937530965fdff1da838d11f7d484384b544ad53ec54ba544be0eeb27bd5987dbbde0137e10b907f7aa987f331f736638770cb509055f5bc60dc618e66bc15771d88efff8c9c6037c26e4216aeff0d2b341f48c6ec00c820ff0c8e69b83b81d59648f2f043f0b48a70e70535a1446e3c7c9f5caaccd3f98fe95ea65ec8ece3d9e7b8e836fbb1e7a44bc8d3133a1550287515f97bcaec4538056066d8a1533e00db804357404bd926fe7f193c7b41b8fd0a944c1683383aec9dd0476cc62a2c7010965da2cc016a8c44df0ab4741a3c1a52f8dd45b30795a531f02be86b851e9cfff65ee5495fde3aeae4d82a24a375eacd7a71cbac5299b552179cf89cc48d325a5418a7f8964014ffe2aaa2234f12c3e10614fc5fa4b9da994c05ca75c78ef29b3147a32fa4da90b7225702c793ccd4d68daf326b85e60bc28557109b400718933bdd62081418db176c5bd759040b7096682f592bb94b889b70d15a051a80c41800329bfb8b41c59d3c4f02494b60cf77584f2899871d6dccdfcd60fe0839d6a5d604b41358b6ec2f6a71501937d3eb250e70e8c19483a64abfc2a2624dbb63e2c81586417cd0487452c2ff059238a6eaa451ec44cdc2180c7a03f07ec146c19dbccc5e1e68f2ff5402f54630a3c5be17a2c75bc2039b301b3defc3efc291abfe98d6db6cb1227583a0f7abaa903543d4c9de3fb040b6f064a71f81cdbca5e389b4da240c135065138887007fea831b91ee6299cebfc03645181c726c72153a6bbc9ba9a6f8c7c5850475b6aa0a9f1eea1288a64977ae17646cf8c109851116d98e3f4f7e9ed2f3f5d8ef319eb333ccef576c09dcbd0f89c648166f17bca0aca799feae2a9689da6f5a956637d5036b8522a9b9923a7ad360539b00aa79ce1f1d237325b348bbede44258bb2f8b1b7119c174133c9620645e89c8fdc8e5d5a6a88fb4f5c3a8b5f050929071ba99de3634b97fc535732b655badd7f5f0ba5ed1adee9665145256f20d3a92f31a87f9bfe96c0a09307c438f7f52c7a10445022bf1068082ba37f6636beca13e57731851b118a9fd662c5e0a5a41633a68464a95ba241b97cef5d6aabeacba3d5e4e18d1fda18ef1b1bc23dc4226a3b86d32c84ca56a883012b6ac8196180bbccd3a347a55b9fc6bded44686ef8ac416bbd03b1b9b154f70212e5977f60d804f527497928abd372d220beccddb0108a7ae7ff7813eee21e4fef72c15171e6d73d13b4541d7a6838ab9394c5ac8412c35e7db7e81663b7a3384153ddb2ec4a67558180faf5691a02cbc15c03968f85fc4fff191537c6d5f77e8ce36e013d75322685e845fba008dfb2dd8771175bf0f9964106c9b7c04288a820c134a68a7796924e6aba6e6751864cc2a1943cc1f0925b79bcfd2c2cf76760e0072cfb2107e7d3ea41c349de9381c3c263665674674188c4d5bb7d4efdb8892d986564ce342edd93f18c28837e893a1f80c5bd0ee0a9e42d2005bf018b3448eadcc5364d94c6b7ae4e1f609029b5e191e9003186103f2df0603f6ba4da24702fef16080d24cd1a59ac5fda327b93f5c7e8cead87770e562da363adcba4feb630c51a839ff8d2539481e7f4f076afa835c69fc90d01886ecf70c70a46b4f1301c75feb4f6e5e2f126acf82c9f12e806af04e25290962db5c43899a77d0b8d6a7193bd3021297c47c067bf744bf6a1bd9e807e86f5b871499149ce57a72683cb710067fcdd75889d46bad7ec5463ccd75e45ae85fe6d4217da3289d0d9331ce1c9926f7f7ba335609c11d5b559105e79d9cf461b3cd2583440e37ae934561a8c2a7ee40bd630a064120a6c4648a11edea0d0054a8521363f51979bed301492bf4f458e81729bbd53102c3cafb6cb13837c8ccc0006c1c297f1cdcf38a535cc84f767cd99870db4535eda39df6c9c92352aa70c3da0355c6db5215250884cf1bc144058e699eeeb95eaeae2775cfefe23fe35e2716774e5af0011b31ed56b2aaff1e6ccdce673f124445bc8dfb04439e157097e1f3f97bc26aff6bb015418cd9476ace0c832d4a18e3dc35e293cdef1bfde508be814db448836a0950b23b77bd9413e0c5114813436668a133cad7086f93593612a81ab62adbe7d4f07ff78b4cd7f88f71edb685e25d33c800012f68fae51bad93a5cb412949a6283b969dad2647ec8c729a6ffae5b14034a28936b09c6e8c8180455c465adb4535a3e1165e9a3429cfa7b406dd86016801b5a0b1027562d242cde2fc14bfb440d20d99d573fed25c00b86fdf767d5d7753b4e05b4a05acec2be60f97c998b3a6aea27e6be940eae22da331a82243c1538f7d264d6dd1962b0c499c9bf9d2c4a2aec9b4dd7cccee9dc91c9044e0ffbc5c12bf699ed2d28f80b0cfef6d82bcb0fe543b61ac72bad344dbc023a86243219114f25d72cad332ac3500c32e33a64655b78d5173a4afb8bbe06b01f420f3fee36ed0095457d9de9de0d671650c04966232ec44a734782262c0a196d90c56e9b61961a0c37de2f7300773ebc701706c069efddb84b3ae8937e0a78ca3360b35646415fe38fa64308c0b8ae17e9bb5c03b12d72527efd2f2ca3602793c3901115aa38ee7947df0d3bfe474f893ee830322134be25b7d85c763c504f75849d13689e34781256836f60a2f5eb63da12f558b65dac6539610549743460c354b90ac2f2efec8b4c6ef0a07caa9e1240b402d46cbcfcc5e5394aee696de938c33fdb4b84370460abf921eacb3ee7f2d05cce6f360a19ff80b6681b4c583c7b8d13c99c8c191214607323f6ec9af489bcbcc196d5be57c4ca58071c54e4e6f6ad906cd3deec8e789e0e1af588c3170c5bde50c21565c538c093c0d98c6f5084790fe65f82539cb8ec898a819e14f986c0ad5dc6c7ec1087dcc6e939d4b9cff8407f656f59e7fc72e159f29ef09209b9de142fbd359c7f6882bb6b105fc24909defd74ea2102881b237ad6ea29d885cdba8a20a769869ac985aa7598b4bd1e98b62ba1f18115c091b9ddefac8259d295f9faf5350f7d3f5276f80e43ef803d2b35c167f10fe37e4e4ae254e3bb23985a96fb74953353849100b8eb7f230eadbc98dc9f8132de4da07c7d5eb835edc06b3c2c315246e79f486bcc02a5b06af8332755c244fb8f005f64743c4d56f43f291808c6620592d5929b83f92650f103646d76428a5214f5f9c635be70e881951ce3dce230ebfb755038a920cf13615cb5e53e165f82a0d28bf366b42d7012986de1793feef26eb1fdb60702a32766aae9696cba13e025caa3e740e11c4572c3f7b0a90c3c82894e31c0430ed26faf752320b993c40e92c567acb44e62318031bd1f242d96614680af9f88e3a7c64512afa9b4d2df1c17c519892a80f3d321d2d1f8882f5f53841583af3e07d4a970e2b2b42d2c6d353c0721d7dcbebf98e865319bdf173de289aff3054a68fc4be5865e8cba9568a7e009dd3d80071086476ed8bfd1badfe4c24382c32bfebb1a301aeddb70954de1d4c9dbd1bdab8228250fc30840d600809af90686cae00ee76e7d6647321c970aa877eb85d14b26224d945e355188460704c85e2c992272ddc1098d3f12132419dc0a6f9fc17917233a2eafb073a730f1dcd4242bed3564af01d88ca17aa08f49fcc0ce4cfc86ba337c90e731307710b0325abddc7bb1cc7e670d83bb15e0f7baff02a36a5b40b69b2f8cd9c7b32b5647f15b5dc683aa29b14b83d12d1568ba39d2497ff19b15ac464ca977dce2945b9eb416f41749ef1dea511d003f479a3f88d080ea09f29d4291ab387e9018e8d7ab953719853edc8c2b4daed6083d769f6f07c7d46e82c7f53bfacc8b1cc0137b6fe9939f2f7e84a4775ab7ac39258e0a0daf9170d74e6c3f3c9c90e56a4d8033ce2c4f7335f88993ca0a24c8d42feecb35c7b5b1b4687761d7171a3deda275c00b32f9e6fb3eba5acd553afb313a9da58e4de220034321b3cc1f518352c305c11c2ebf1f96f986c462eb5e027952d6a832de4f357653bfe535f032a59d0bbfcea2969909c99ad35525624d3fcc28e152a8ce391bf2c6c6818ea36adf43b35b165e5683c5bf50013429ae5a2e7c5456e5f10c4413332b008a2cd8e677f813681d754cd5e3ec13dbb03aa3088b61041e41764ba3e7600b1f8b5ce9ed9166686de05f5c7928fdf849bd0a9b72aa0f46302249a5ab939416db0af414e47f523f6f7613b63547ef132ba67c456685cf6a98c90dca0cdc9e68dc328c4aa8ec3e2ba7104290e06b2a92ef53e30cb24afc5f22ebac8273fd0f192bc13f6b50a980e7abe55e5f935ca43d4863944189866714e9c1a82c8bd586360383df92d672efb7cf402df9d72d10fdadcebbbbe4ea0f6ec8b72743adcb5fb14c31369f3210dfc8d6b4c5e19e63e56b37a5a8b5750a0f7f16c2818ff0cbbbf9e92581bf655b0f3df8276a4a7903596f1ae8826307fb7978f9aec6bb298bab4f8ede3cac45349f70544c7aee8ee49f928b7a7f597776aaf68b254247e410b429e7d936bd35ec65089ccc2aa86c56ad2273ebbfcecbd752d913dd65f62a5b947d7bf63db3bcf608053569fe392dcecdcaaa10031e09ed45e5769d6f8c3b92e128e570238d8a3c641621c0b331156bc7e85ee4dfe4ca05da896c5e557375130d814f647c421dd6b794963c0e49309cd13ab6ea0abf7d2baaeec32f733bd39988a6a5298dc31c31bfa0e78af17a87cda158dd2b78e7bb9ff12b91c2c9d67c555098c1b71c79651116bc2d1312704d885402f94b78453a1d97e82f1ad413b940be527c51bcb81d108bf94c3a9ee5c04b19a38e933a4f6fbad24a4e0690ff184ef095f18e0c3ecf87fa34097b37493fb8794bb07036406159be797350dd8373fdcca70f39be4d8bec3aaa1b9b58be42f80e0fb8a82fbf1ee8193bdb15e21e243b0de385fb22048a916a15d04554eb214b81f54bc04ac6e290899718687d6af490756bd3bb55cc6d0e530068d54fdde6243a6170026c33daa82bc736a7c77259ef557656da3a865c5de70b0c65639801719ff63c18547f64c93ce4c58243186f52dfff9556088e8f3299f048f0ed69bee21fda64aca7d92b344c1a7f921a4e90a31747bc110165bdfdc0d9eb0266470cd3614b5d003d9f45a1409327629548bf7dbc556696857112a445709731d17797932d337dd9cad8ec42525dee7b35001c80184d3d03949a99816571a809877627bc3dbc341d86d0ac9c659ac676b5428ed5850ecdaebe140fa8cc4760bf15d886aad4676ee336acd467289a26909c14826a4c0cd07886e8bba864c10b8e551038bd22d62547377320790a55bcddfc15eec7756d1faf95edeff7d651c152e9eda95c7a4bf3a511e8c97556f26556977ac193cb3a072591b94a06757905c2a6ae0d9cc817312b01e27c275eb309a99cf45dc748678ad52b872b05a465e82d98a913cec8506781ad3c74235219500a9a3b8bc5f4ee3a1e4070d0a833186dcba224116011573d956451f63eaccf12e3c6a4651c21fba6dfecd830aeb25ef97649ea884b0f8a8ccbcca14473e6717c659967544b1aa2108c16ef9c7f0d90517bfdd36432c8fcaccb806393ce1eeb5e5bb558911da71be13cfb83df90b949f6a64d57656816f9047407759dba413d0ca941e20aeb3e131f59a81b835a32cd8233f4c66e5fc4fc8a79ad8ce8d378ad52e342e91297ad599b80b29901d996aad6efef50c9854871a2d160193f4849efaffc8e8750395f7b12ddc613a6697b74e8d72f53d8101da5cd4a54f36d4d924c05704a9da68745745772018e9ed8bd476b930d0f9f92c7dc3abfce9e802a47b9ddafef0684611bc7e1557ad82b34ea3f627f4b8941c4b457616add2d40368f1be7bf28d03a18b583b6f07fef42891776b11712b99d6c89429152e57fb96f17ed56ac10ef48c6fca8922eb191db4c23f44e1ca63906a4d2786a5b4957007ba4c75ce00dd72d1f798e654bb936ea0ec54d2b5acdc95d8a3f8c55f516ecb1496faa99158f025ccd5a3f65e976b5799f3dd22d84a1b46829b0882d154ed55931b8e48a82715503c1a9f97b4f3482ac9959372ae55b1e09739b7c9336ef5fbfcb68bf31c90fb4fce181fa2ede8cd3e83357923a702086dc2a1352eb88d3935b1269f21f045cb3c9f9c6fc6e746ea9989869eb9fdd5067bf72af4ba291ca235c28b645c1dfd22a709bc4499f047d9c46b54ce020aa3a9ecb0b54ad95dba6e2dabcc653b63fe19c77fa368f9e9207588b33b85f9d3285697fd218c3e89295f685ccb6120d600de157504424b108c5c1f799d57194040f08fe83ce748b3d9d0b646b8b1fb9a353ab9548b2cd34bca40309ae93be2a051d3dff8ee4e6673fba5646eb5f63e6b32556d3e9d8036bb55c5996de9c0852e93dfe79181c91717f44291b99387ed41569b23e8e09789fe3e4b36df18a5e9518a42d9c5a77d0973299abeceba15f281e70cd5061c162345ed134b68340c37a60d719115d2d0197eb90fe09170f1b4fdb36e53581b1ddeca62c47aed61f90c547ac152854fe2c76f746d48e5be18148038c8952026d1dd6469f135bc54ff323624696106f3235480ee68088d82ba41df978c5f47bf2f497b72d77de5a8c531c6c7a023b6d5f3392f1c962ef5de45d17ae5521f062105b55dbdae4a4679a2c29fc0825f8696f79ea9d8ef454ff10c83200696bc7a3bc235b29e0c97db473518469109d3cbcd0dd188775dce184168d911fceb6fb436f44da25673fe8f8a510760ba8af0750200cd34f33190c8e9a7f46b83814a826d5dd4cfdb21b1e3404598734ad958bc5604de993055cda5f2d36dd10d8c7457347bc8ca022c6dba73cf3233dcb99ea21e992c80d7f9316a09ac8e3dc02835b3928ea3716ec3f71ece138114b1a8d549d77e20d5777afcddb5c7a8c0c5d903a02d5707fd1afd9d8b962c4f9e1cd22d852843eb6fb7aef12b30dbeef7e30d192e1b8f7c7dcb0a4ff10e6b90b5afa700ef6e4e1fc9b7cff254711c9dd04597bb31507bed87f516311f38df4ba87cf2c57ca3c619a0edf3d7f1e04efc5750fdf04fe9cec670832bb16e249ac8788f41d67a917069fbaba659759581fa2b0a10f78b926b857befb2eb2570ebb9a2295a4c002648733d0fb9aa89f35541c74bbc36944160fdf15f38f4e1f4dae35150f1f97c71d313d087a31693af164272bf145f4fc54a9d80fdb9a98a4f3325655deba242f25d70ff47702714de1979eddfd83bc2ecc0d42a052c480f704127fd0651a85793ef16bf5b6feae9951c32bf36ee52bec8dc22001",0],["0x0a8a6c08021080101a806cc3319b5df1c8ca5bd9148831a54a296ee8692e8e2bcfb697aa49d031f4e1e74b3f4893e30eda0d44ce2fe9d902cab4e837782e18d4c7807c52fedfe4bc02605c6b0dff0af05efadd364c794527fd8ce044a42650a13242b3f80e96ac2861167c7264ca220185834ef71bbba7bf2850aa2ed72b2345672d7f29a39634b1f3e4eb9f055b7992d84e0feb2afed0b9dbdbae3bcf433bc4ee39df235600bbfab6d6803a67f434b7dda4947c4e174144e03e74714ecb64745c03d21a0a5aebdfa0fe2e2a07ffadf5344d1eb15e7001e57463e4dcd38ac2d6c3427301602980a17e1717f48f6b915033011afad2b8e1abab98d3245eb218b3eb6d3afa072a767c8f51795ca2506496cf9c058d121e88ad720e57d8af7449b4a66ad3e89f2f05f592fd9c1dcbfa85b336e51324f5ecddd0c78b486528616485db1f1f3849dc768b1cf7c219c7d6123ff5c48c3e0a96d94f34ca32e11a2d05be1542b3ac9d2cf59718108676e97ed353846bcc81ab7af14289ce7f11029113919468f000455440743cd16504aeb0d87c4a8dae61a179230ff30145bf414dac12e81604796158b2fc0bab52583a5ad52c47270977f156b37f175b6b91de7441b594dc174c41c6d47403f4f94baea74a9f7782aa92eecdbe3231cf3a98b995466d0fc327e53356dd13b7cd4ac2f7811c468951b82bf2c1b50e6c6a6d050a1c374235b9d5584d2530c06203196b0b8c2954755b02466b1c8fe71f2fb9af57f3b514c9ef2c5d96031eaad2d1314da681fba9f7eb3e545f0d6e28efa084219277783941305fecaa6d9b7d3f3d93c6e02d53d955ec1f07b7af95b139734515c88eff9529b9dd9a9c82694cdd0885b4007ee0992cf8fd100d148721cdc9af10fc6347dac93c5cb1de5b7abf20ae513eec43e03d8ed218a38e088914b58bb69267bb5b7e494623a84f0da7ff2810b3efbe8367d5235e0471ac32287b6803709fc686a1ca118a79b9e6b65586c328e33fb0d52de785c8a6f1dc56f11a90b01cb678078fdb7c0890179ac44670ab80a3a6cc6ce0415c2647099d6034fa22effb5e73fa66ba0982b849de898ba4f5cd5c19f508457f8b28b382a60dfbab22746896d71072a9e70462d29930290c38900175323c285232527b84e6793fa98ad2dcea9daf9021f09ffb6f824a16d9e96333a257a3aa666d98aa2cb589e9b18acd62839f736fb123f82a5d6b388f6044e8781166013cd47b2ba4fe8a301bf3e3a831f6f0a07c88f6c309ad90e5012c0bac243455954db06f9f698093281b04d930477ff3d50001c0bf677beee1be64a807379b3f91f5977a6a7de504c9cfea5dbc8d5188b98ec7bf50f38d8e2fc1ba6de9ab7ddbd18b419df3e1834505e564838cd761947366a20120b2bc993cc6ef457b3b1d78b2fcdcb29b1f8569fb714fa18e1f59761838b94a6a6829db2336990aaf241bd2aaec0c1081eb36161f4d644ea64b6f6d7905fc9b4783348183696e178dabc74fad0166669675645b41dc2a9927415d29b81c19eea3cf021c183e398750f76aab4bc07c97ece5130ecd53100308bf35e7688b5563153a8002feefe82a63eb540b23903c4817fad40826116f5277dde3c855e10fde07a9a54d45c9f854f94b9234efa1b020167d52542fed897960078d55942273134be864c5c97bc1035e1abd4d138bb3e12664fb0ec014185e2f4f8c4b6c2e1ab33cd935340340db53bb03562f8c9b10c8640fc0e3e8ea08f7954d529ba61907c20ecb3befdbccd522c1cdd75b15f2c9fa560f44f90a57566ae86a9c5513366d59e7ffe0aa1de399b4148c12734b2317038a25b6e8a72f21f782fec51f24d16894f308d5367e595131bb5f73cf501b3345bad54a42e06e078a1693c063e48139d3939d1839e7560c3f55e5d8ac78760df9f0bb1bfecf8c51e611a6ba833815e38760225161a6309c5359e8f1b7ed9d707007b17d1a870ba7ac25e0cbc12923883214829fac999bd56077bc48e20e20cf23b759ddc125e58a67cac34b3c01ab2ce905195d166e5429f69eb4402d809d05cf596ecb9d0d4f20bcb133bba0fbb704311cdb68317eebbdcde8cd80ec6ba20774e1b6d0e8d2b0ed6c9964d9f1e65dac2bd74a89f4a4dccc9e91920549f1b87fcfd33c24c4e2d8ff7ae59287160372b14f2e8bcc8ae410cf3be2c18149df686333217025f62b53a2df0dc6c573e1c07c5030726cbe7c78405afcf91567efffc066d4f2d76b81e8e824e69bb58192f402c1ba0bdfc632f5907daef4e7340627e26cf371918596897e1ec51f4568dfe9ed6406cbcd812847b4edcb8a1ff69015735bf4bcac011f8e5a62c85ac6be97b5b91a1314f1d37eab0e8e61dc24a155b41306272eebc4da8df6e2856a74edb692984b94af223e57dbd8eb7f23ada0498d9aed26ed0770c7be86a7320d7a534477b3b5c7e64e2229e79fbfb4072f68706fc47ca0ff4e138a7982349d0bba1232f98f39f28469470c4e0e95883178e595dcd06414006e200714baab0e5925af9d1c3313514d375280a4b0fc402220dda8621e4ff4e173142b113e40547a3598edd7ed60b72bdd22d62fdbcf5e4113016687c9d1af01e2fbb6aa0daf2cfd13abf7d36075caa2056c83d39e7c34d2f89a433a326ab01ff27953f865919be6866361991335b9f68e597d2b5bf1b732a38afcb33cc6933b32433f9cc72c7d0a9319fe55a531e744213037b3453e0444a50c36b89b699abd3d5951568d2432671310ac2960f06aca48497880546a6e3f0b4f3dd3a78cbc5c6465eb5b12a791de1b8a27e4c49f2c1c923e0eeaab94de3210ac4dfb67697a365740da550aab8704fe638a9a00c1f0427c80309c3d80956616fd36d8df1b5f98987d23cb79296247795a46b6c259adcf8fe6013f76dfa48137282e45cd102de060597c6ba5cdd64ed78c4eb5935197193ab2831504b64642ffabf1718950ab124282a4cc06bc9a2e84d52c5eb6895d5987d0b4ade4c31797591411eeb2c4e30b74ddd851ba2ef8345893efd5a146e40ecd7619e4292349a8814d0438b662fde8c3130c121928620ac5c5babc74014bda986aac4474fc5c983af422596fd88763ee485992a33708eb48ca57253ce1620447a88497d7d570eff72d1d4cb46005202cf5f6c0d477ff9ad8f8eb8e1537ce1f5a6e8d080b3b28183f8cc82f5c7a9069d578a99095da6e79080a81ed27526ee8c3614273d9050c28ebe27f31623003dfef51776e3078a2d2f7f353585a367c50cc931114ff87797c65d35c943ddb9f04643b5b079ec0767c06eb43846f4cab0095291b847fe18c4615d21d7267d0b2a8ddba6bab7515fc6aeadac8e930abc6820f5e1fa3182f725182c5b59bed3c47eac7e27db39790fee88fece2327dea6dc1887e415706b4f1ce5420bc062c0314dfc66ce3a3234a90dca4c71bfc06189edba318e23b189d4499518e7d9f8aface33cb6a9e0c45505bc3da9700740bbac0158a55caf35b93310aefbe2ca2b1d0416a4d55592ed068e65d364cf80674d6c6ffb79d6247c4914b0ae34400ec3def61b3f7d5416600fc985b6f318a1f183f5a5f14c09ccc6791661fcf477a75fc0ad9c80aef45dea6f5068f09d23ea72db5934cfca4c7ce5a230aabba563805bba4d4c27d4ea117d51d4df8d28804b1a837306ac84a460f4b8c2bb5648751f474db64b1326dc5e1c0631aed12a48249f3092cc1f88595088fa9218165eabf6e7b31c0eaac37cc005d7eef284c9957d17b14a4e41719eee641147308b2dc032b0c0ced5d67a9326d5d039ecf3a62dbda07b4ab0d6cc201e17debf69b642bf06729b25138c2745bb200854ced5758d883fe127e918bd2af2da5e899bbf83bb3a42b350befc9b1b1b25daa05c7382046e23e4a658b7fac99b0729614c60396e3ce848ce06eb4f77f0a7e6cb4124a451eaf2848b5e7f2616c3504d1c5c714802375340c910b902dbd7757f80a5825ccf8466fb15c8d85897d4d048aba1cc48ecce90ce9ac9c87ce7daed5bed8f6c79975f347a638c83a67784be7cd0fd64b95574f1189d608ab3f5728e0ea3eb70c29293f51d4b1d4498827b7fb6f968780b03a590335435dd11cac8fad110ad11dabb4afa40bd9632b279a233d3363ce05f09abf86b64f99e8eaded1e4c131dee8c05689749874766768891cc86aa3f8bb9ed5fceb4bc3701c7cc439a8a3ca92204f4f7534b87ba1197da6c7f009e2a1bd0af09c12cf91024fb2c798ce2e4a2b4fa6c60735245155f5496eec6ee836f94a5102e5a74e1c059287c7ae34a0b3a917224ce7b2e596c43475f6f7ae2302239c3bfad1802fec29300df28132e38222ea0609ab3526515fadfafeb8019e6fcd798143a37cdf1bdb434c4b8e5cee91e01d2080d5d12f8ac6ad2a3a421732fde139cbbab219cac320d63d02a011af806e68362fea72bff4fb11be197b022862a4e48bcf6e8d7a745afae69b96b8643fdb3b3db821697f77ea350c08043631a4c65a81b5352669216e5a44fd3a0d0ee1e427d41a99b7d1e0cc5c5919f92c7a9d6966f6f158a40076b8290b22c783b0b245b961d2dfe7d176522680b35092d7d2c8779e452c00f6bffe788033087a1730b7a47910dcdd60fcc57ce750cf925cc5d9c7056786b1361b35aa60ec081b4730fa9746babdfbb2640cc4bc98a157a4ae8d6b20d74bf68f17d08b6deaea4d715dc1a74a2cc4ca8031ca6922f1e434e09c52bfb97e9a66e53103180fe4bbf51a4126e180c3ac0a82c16c5cf02356f39db877035edd05d4afe759ab267c2e8869aae4c11530bfcbfc6b0974a14232e73d920a0c11bf3bd452835193d1b4084566e03bbd13da2c01db06d79b7407b10b2a91435671bd59f4a59b9baa4b7213310ab6343020c91dd0cb9f70b0919bf35a4a2caedf95248e73f2dc1ac6765d825f2d53a827c0165316ab4ab1d124ef785118b5527ddfb12b806669dc34d78547868674c4f6d8f744ceae5ec28c19be281acc842b8d26b2fadfaec839e8d9d92b034d0193e4ccf373868c5d16a7bebdaebe8c31929c8ad936d74ec5a0425bdfa507cdccf57514515ed5503fc4b707cfbc0336a5f70745640f70a39c1e98a29959a873bc90afcf6e172a6e2bcbea716293f66933b9640b864871b370d5121624c4991545d801b045904cc61022ed99c70619984fe8cb370912e302df6c01f935f13c2202ee4c3422830746f1387d9e898929d5097a53ca0ddd804120ba0195993343d70ac5db85ad93844f8a9a3e6968a27d48808a4948e74909ad4b47ec17be53364532b213a741529be98b2e861709ac8e425885de58e189331da7277d2958b1d6b76e7255bd69f8ec9dadbb8a167a31d393d9bcf5517aa90eab46b1716df454981bd95e958058747c6099078da94d7914ed6e82ce90e96a9e01bdc0110ed13cea1b58e095145289096698838d776187b754930ced4d08aef8af96829696a02b40b7c5689da27b64831024c80821a5ffe42d78ae2103ca76ce5438a264e5ca5a94f8a7308c2c3df7949b6af653f93b9ce2bce99c1f43b1a9237a680dc971f6590b2c4d4821616638bb35acaa18ad0d1534110a8eeb9fcc91e77b5dd65118384297916312f75447e5e1549057bbca2a485d12235f2e3c477e0ad23d6603c617a003f363be94cd8d99e0d00b3bbb519728383f0def22f1f77cf62c42a4dd582b7fb254bb8f342c1129f546f02bb3e614d21a121e626c285e5e995010c6bd51d252627d2fefb0b2c7399f0bdd2ab1543bed32d558fb570b133885bd1712cdef582035b2f97f7a5f6e985af60e4a5b430f0cbbaf51c66bb0ec901c7ca04f87501895a065dd43ac2352c2dabf8711a76abf5de7da151021879244b6e41638de6a3522ee3f14e74ab755758407eb516b4aebd6eeec127317119f9ddf9edc21daa0ccbe898ecb9737da00cd2bf73f395bce8852e7caed3c2bb9d67ed1a811640b8e2b36eb9d151218d0ba61b52f7a686aa694c2a76e12df8b3ce97df2a7fe4cd75e290bda3e756e79a53f755e51aa6200c502850e97bb5c1f2bd97a872a299ab4b0fd691ebb160b1327eee3d1a1c0f4cf0decb77faa7d7601cb52e54c3b49c4e56ce730c05b1c34a9660210e67599dd63fabc58b422f9c78a487cf3af3fe6f9106d4bfc1ccbc7c7054aceb4b44c971949409f3e28ad9d12c6119f146f0d00cd7a1a90333a2627a565f43d13633cfe2e970b087fdd898ccc36d6677696f6a2ca618d8969c83e11e7d53aee1e5729273712eb52b927a356a3321e8a54e8e1dcb4774410d82b21d800429e7a34cf49b8561ef6b7f48db426a6bbda606ec282768fb36f5f508d5b1a1995e7b03c5da7e348552a4d0b02a508472dc791a9aa201ecd2ffbea41de41632f4d11a8b9bf51eb47aadbb4aabb83472044d546557500ae2eb487282261728c4249cc1b5fc22f75bdd069c99732334c471a50c2c944d2563c67b93901e0304333b407a4ab2289db5a9cb0b3efad1d357e329199a3b9693595f0e1b5f2a0355a110761655f9bf94eaef537e648f23bbd57bdc53b5790c5b23b50e0003e0ac603bf9f4ce0632004b0407b87da898c0ce89ce4718f382daf347e69f873ecdc82d4151b86694782569fb114979e8f499c3e48377f7eb4e84b63ff841c972dc195c14cec134c7ed425222dbb704bb09beb8a6fd7067e3c9dd6c26a8aee5c59922052d6d1367956edf672b62245f044af38b78e67b780523b610c0b40879faf4fe71224659aba7852a9d95d87eab777dec8787576cd33b827b8d33aedfca5c21a4317c0d554fb78321992f8da9e11c286b50aa9f42dbe474c611f7f6e41647e2effe5b61282a7d0a7adae4c4a22b54596075eaa729668e731c6d040cc1429725bffbadd620757e38c57ac5ed36ff6861a75db7c32b41459f5209e709e0708a8f825cf50eff912049332f159ba6e5a2a05ceb243e9972ca4b1a2984ddbd19f3b5159474c0a6d00befc794c23b3b9160b6e57d804b506e17161dd1614e2be8b4d30cfda057aeaa5d75182cf870d66aab9b3ad927dcdfb212fa83be8089f935fa5bc21269a60a59c0d59e6914e1b8556211ce2e57d06b387dce4cd3e9aa6b6430859f823e6f94c1cbb4ec599028ccced912726679cf9ca9c40cde1fea539a5130e5756fc5750a125110f57b158778b0c25a61ced4f7ba63ed6f67ac77cb9a23c3d1e9355f1f04182a0ef0a9a9758283f1100830aeb9a66bc4da38c5047a149165620718641b943dc0bf3fe991f3aa0e3aecb8e019651c262d5378c2fb3f86c50e49f62d6d47bb90f7f10e5120aeab73c9384b8ccd23801037c5af5aba4d507ebdcb3bfde67ac7c1bc7e7d3bf632189f47cea9ac61318818fdc1fdfcae63a4c421b43e0ed36a461eccf485ca86bf5347b0c2a2bb0af6257b9044355a45ba1d486cba67456c0c606e0b654fc45462d56371a625447617409b0f1891782addbf8e3877f38e49b634a8bc2e17b47e49e482b4157cc15680e4542a5fe270fe7001d663fe374f8e5cf64c01458c639c8785227200315283ea01465d001dd031f56b846ea02dfadd6ddecd7482cc31614400d557d0ddc816577adc77347e29297a19ee21c1dfe7aad972f0a3dddce5c1d42474f205097091a4f6edefc218d219dc5304d5dbf27985c29a0ce7eced298d1970353037b4eacf69a4808f1e3ef9cb726336c970699dd83385278ff63ac472c4923927cd12eb9376de17d0640006e1e0ee90e10836462552a02c220179d7f1f9058a12be5835ff350db5c94cd2e92c5cd25950bb4760fb920c210a14d1905552f43366c98724fb2459bf0fffe5c202af3d293cc97c6c0ae1cf5486fb78e0cc0d26aed4819fc050b1b2c8f1175d10c94e6942a56b028f7f3f25a59c6cc8735cf823cc7c21859093d5accd8dec34a6dcbeb790f12e82f59b3b62ff8b9ff0a9d12c688f72e51ef7b8967bcf459487f4ba361559fa2da7eea647608410057174cb7afd86353abe59c521975a7e0ebed3a9b05b696a83063ad693ec4738f6cbc816ca03f97e4fedb59674be736e8cce508628a93511d20cd3f417fed7e10bdb5ead332183eb20c59acfb0c77adbb7c2dd3f16394fed64c62bae9f97b94303597e9cffeb7dcef628d63549ee484432ae2c5b6987d40cfff6e11b6037a1bc3e0f48bf87d8e1e30bcfbc739a2307dfe12e5e23aec7ec4802ab4e5afd4fa7bcdb3b81d8341cab08645f10947f1b772cb2391bdff0573d335f148457624116c72b20a5e53ec983f6e601dfe152e35e4b28de209f6aa4055c271cd85ea04746bd4efaa4816e8a71a8d61ed88d3791f9606b965d8aaad7741a9f6bf06d55e3d1bba1b88f2bdb6ae9e48db89f40e3d65eab834167837130a4e80d3a9838d5ba0d09c011f80aeaf5a3e8a291672408898b4933849a468b786464bbb3fb7d944e8bb9ef85d1fd671b2792d5faf68c5e307bda8df5daf543f6ecf3919a63b3d6800257bab48afe18274b8a1a08f823670a33fe58aebd2123cc46b754bee7318eae4637c2a6885d8d1d5f6539b152681b986c297f17116e17cdde777ba39aa54402774eebaf093ffebc8db90d071812c02849665f03171eeff40f527be475d8f49d87ac77454ec24c214b9e2eeaf973176654c5238cd272b57166145bfd917b8213041d605eae8e33eeb31d83069d046cb482eb6bff7c885bbb029f4dc120cb3853fe18a345ccc9281a867642a0823e5e02ef01635470b44c92b917481d93090a95a75b8cf7ffae94922a928c26846dd8df14a07e737495990e22d82fe19a2f3e06252a8b1a299a4a1d483a4e9abbb0e261b1100c03b87b126fd074037b9a84f8a891462f5aaff53ad5b09fae1459f217543ff00485eba91800de8a58afe5951b06af1e2cb6a25e679da1ced0d4e0b3421b00e712e1208e1dbd10118e81457c6fcfdaf5e8c6570096b7bab3b08d4fd56200bee44b89f3a03bf6e245a50cbfcd4287cdbf41a8820b57102ce49ceb482a9b2362e223e5a99d4efa31f27244265493066b74695a960ac0976097ca62a9c740706907fffcaf11e4501bc05aed9b3d34ed7922b740aba00d2c4e3424125ac3dcb2c3cd743bd159a1443ea5cb27d6d3d09c927d84598f37511efb4e77343dddba507507d73719d7b53a43d7710ceb196795677d73962fc2f9e3d7c0602240bf5ff0556098077be4d75230266e936b2f7eeceb7a35baf9a91a2c8faa1b1f96d9d5b8b551caf0e94420e3edd126ef1dddd4d0e0961592b1cb3b2b67290663c086a0d96929fadd74b0da3dd64d3885b2f9e2e275f54b0ae3f1edac0fe568a28108f93bd9e9d2f4cc8c6265e3ee79c9e416d4d124145270cee7b41a4f32716c9742ab6cbfe3cd6e9298df8becd717fac63c5a7880169691d12a31b0763310d7e558e2c1afa6ed8e9ac776a50fe7cae2c3100e8f484fae1565b5cdd2ddb2aac7d9062e8cb25e75311d0b447c8149245c92a76cfac862173b37074aba19153ae7baa1f780ea899d99397fcc25fafb780bdf0b43978e325a7d355721ed2b4361eb08ece897670744beef4d31e52703e0736f8846f4c1a4678824bcc4eee0ac0a64106e586fbfedbd246fa7ffb090b1e4e807b1766d0820ea328654aaba569ad0290a2195349d25485e35668ef113054c39cba9153d212485ec2d3fe6d3c1b888fd6f47b479703598582b048552f9a3981ccb544c1a4836da60f776c5f3e485a8b5907903472ca2ab9f5ce92fe2434f444970e1a757fe19843ed55c1c6489b963664980b030c034ca92d5ff3a00197db40239f937bfdaafe4b1b4ba85bf6d041b96f5a422e754ffc5705248226bffe006e8efe943f5ee74df69ce5734cee1faa4e9be43edaf5e763a5cabd2048a89936143e8489489915d6ab1a51a2d85379045c99ee6267daf8ca67991ce9189d314d61b07b93241b4fc4c7dee8c9b7eecc2b4b9e74ec89706f47dd17eab6d08bf77195f1659733e7e2384246f3024df6faa50f76a6b556de4ac9b16926fda83a686ce2cbb98aba9e050bd5fb44a66d16617b4695be24bbb1337bd0aab29f504d6d6eb89d0a949786ac64954494cceeb6f3c4c87c39bc9c3f06e321dd8c172fc355c9faa8740809e1d325955d1dd4258d88bc20e931c572fbec36f5a4e045179356486f8ee53e1401c61127b8c5a17cb81614951e5e889366ab04243f08fb481ffdae619973ffed5bcc98cad271bd882b65719f5bd3e2a35896dc4e219dfa1a377d27879d0e97cbe046ed40b629773cc279076a300bb51d0ede08e148364300ec9b2ccf809e628daaa622160cf705b7186884ec5cf45b3a297b76b8add4067539b375544dc17dab27df841036bb2d2526705d184f9727d87aadc2d352c61174449b4417a93cf44d8103f09a0c03d2bc02e345bdb2ab8923b8a074ff48ed7cedad936cb633d39e273d152eaeba3b9e381f9067a6f4b1756a1346c966b77a550cad884699a3ec5aa31dc2adc12693915f21951faeb4c91d19577a0c7209e5d3bbca8d84c605a8918cd9761ed1240f258881f3608afad4415fd74bdda742a83da4a399a436aed7133ff24414ebbef1e2764d4c34d17b8d06bee52c338b59264f080ff06e728c5c8cc9a62d41316e796aeb17783031bda05aafd8e9656ca0f004884c33f87f4b1886c24e23fc7b59fc83077642245f4a584128111721c9aeb19fc6cdeab204a817367f04453ee8b58c4ebb16c7fbb62116f0e10f9dc288c3daf1631cb3e66ac5e7a0ebdc60a35ea7b486ee62c97e10e59a93837fd7e8e0e9d53c79d666a6432d1c78127ef6cac5a4fce8e85407d651b6e6fdac8283ba50512b8267a326e856dedc91489b2036d8cfdf3c571cde2ef5a4a6228abf440b43b29bcddd5a0237c002bb1b2a85378834ed97ccdcaff962da3583b1e63d2ef6b391b887d67eecb82cee4128e2f71086bd5762a1860a45c59d94fb7e845cd2f5416ac1f80bf12315190ef0526d532a1e9d9a8157fbb44a7cfee66de0a8b0480ea16dff23b4c9dce31131039aa6f6af41a772ea19ff3c5df7416686154e06c6e20424daddeafe3e6e6ae2cafcff58fec7ec45b288ef73e9916405204f2adaaffefe6a5ecd93ba602ca79223f994ba487f99ddd8bd0c35600e11048789b279ef585d37e62ce1d1c437239a58511df68bd316063787177f8a91925176f69a7450cb03828698ca31905a27f98318cab30186d0f34e2ff45cb365776a0f94f5e44e0410cc051e5b96266e26682fcea87604d0d73361fdb9fb957d31db5b07f4b5d97b52ebaae31b067359afb0c8d68dee37bc442cf5ff86b05538dc8d31fb7768cbd3756490a30d194065d849c57e791287260435b3acceca1363b96eaf52bed43cc6ba3b554403a47beb7ce32bcbcb18c56e6d918e36f6b420640f867e4982342b57ae1e2126f5bd1398913935af5b57852b8fda4a277cdba6ccd850ae578ca2622af0896d7872227f029c5a9529488f9ad5625ae5a7a6567d87e8a3a84329d1b9f0c2b3711c7ff37f14ab6f81780aaa7c7bc3434eee2284f02dba4110396697d43a2a3b64cf25dec64a2c59943839ac1b45a059f366236ba2c0d8d7a42da04684c0b212de066ae4cb8de50ab1472caf2d53a718dec4d64e31b2aee32616119d8a3558317237212d18d2ffc5e0a5616a7906b91c888ac6a76f70a15236beffee275964268ac5f91e955842d91c2bbd04c19b1a8a9d5e6df05bf224aca4517d8db9f0b9086b3a053cd7f7a15082ed0413f6eec98088203c78e8ded7192623329b4a533fabff89b89cf100ca1574ab15255e84c2033481dbd5ac0464b1f5e79731308df18d9164b2aa51818ea9dcc058fd8e983d38d7e84f89483c6c03ecf65fbdd4f3eed1f654eeadc7225faed914478ed94409689d216f03a306a3c52e2c153b1de98d7773266bd5a5e930868620c9d20544b2b0e157abc0c664302bd0a7bede6cf157a542407aaa30895b57515cc265e8d571918712c774abf68c42dfb6e5b67167d1a0b03d49d99812ccdcb4907cd6ca52afb9a82b17254250cbf56c2886b71cd2570168310ba7ae6ef46520632686d28c0ed495629a59b361004819ac13c8b1b27735ada548b91af4695d1247034ad9af3b2ba6659101b49ab3fe3276756e7ddca48d0d26cd4f5867b8ecaedc9644a9209b00fd076bda19e15c3fea4e02fc6159c5b96b0d31649c99928352eeaefc3179f82511b37f7a25739e279cbf7c3eabae2488bd0389496f3e4112536a473bc1233f27b472468ca847eee082a6afa4ca365a87362603ccb61935101c997b7ee946e89d47abc3d0abd883914c194c17cc17b510a19585881f224e89ed237a90ebe2fe7bc47e7274222b7d16ac9d56cd007c570a8f4f9052babb73f899d677fa2edf7babff38e5f718ed09b8148d4dc5aa24df54e2ad34067b91bafabf89dfd19a4c835e9dc528565949bb78bf1f886e0e1981d86e3ee9103240ac14981b074af45cc61c40f581a4ba630a920ff9e14b5a8ac8cbc1ac2863a6a64f144add63683e1713f5eb2edf832229b45912ece8d971ee3c3773d2653144847f6e6ea89449cf9cf1a7c6f0f676dc4ad67af5a880979fba322dbee26725cbf57971281d6b87a7cf02515dec94c7618054e58dd5937498f28f752e46157f7a232437c3a0cdb4a6781a09f91485392c11519f4618588a03aa1c6bff54ef2225c1080731cfe30dc237a4cae3421a98243d1a0b42366ac9256dac8dfe7694ee3869fdc28967caff931f5a7400f50d42b9453eda8b9e29bb9eccbc99592b87f26f42904fdb24837503d47e1e123a5cf278accd82f95ae3a0fcd20fcb73f4d8e4cb9eda50863a84fe21a34da368889f814015122fa33b859dd7de701b3160383223813451cdb5534f966a956efe2218fc9cba9952b5f6a9020bcabb23e1c0028e0b3f26a003d169bdac4f54beffa27e2f5f9cad71db9d3a097035acba205a9c60898338da3f32fafa4cf5dadc2e70336f4268518048b98f8637640f0ea45ad29646fd4fbc270b661b5a5bc596e0d1b2207666fa6283504ff88c21563c4b03e0817d3e3f4a9fdaf1ddc668574b2341c6afc67ae3377322ac9626c85e088cac7387234b7d5743ef93cc7df7076bcdd36de3e1deb00c6a2e2a34abe0dbe8fc75ee0198ce8b14ec8bc5d6fb211fde84d9629babcae5001ad4e6ec8afffa9cd02f355b466dcd8a81115f98ad896cbbed33c8dc2d3247a598e4ae78e2277748ead8a5071633b4512204098c4d9a0b717125ce53993759fb12abc550cf1415fce5001c397d9629c237a7a3e609317db5b3b483d4e99331100cb7f417a899fb92be3f6546aa3849314c863ad1d78c74bf97a0b79a3cc8bafc3ad58aa6a5da5ad8d5fd09a9d679d96d12d4311da3591556592e381e9239cb77bd66d9065d8dd2616d2807b55376d8e907ede7f34383cc0a53c300c6e6b6a498fc060f871305a2d560f926e2c7ba668452d73d44af62d15afceaf2e939cceeb50bc98d079c4482da75636061a334c400892f2b1ee1da8e32c7004e5285737c1b311a432f2b4408fb69a15d36234489a7f1f50c451d8275f4083a9124f7fd070eac8be98f16a0c5f467f93b2cb5f283ed0dc022a45946e5c46b39e0be9bc844164ce032b430587e2419752dec1b67c5aff5ff72758965de79b5122b80ab292b4704a0ae5ec9b3176e6d647627c53f52d4e363af7db58ed976ac4f63adaf19775f297e599c01bc85425e822d0c3963889be7afe7aa9584c912ef23fec578f6efb4b46dff66bd5fce6446f8a1f880947c33fd81298539b63f2b88881a2d4f064bd945cf598ffdad4dac5e70a1c386f2fa9a92d3fbd4029e27018c8dab866cd313fa045ad914cb1ce7f9fd727effb3d9d34b12fef06c1c0db513a2715d9a247b52de1005d87ba20e3bfc3b06986021c4f834cab4c86098d7549fa4d26fff7c779aa1106090a4be096ec7bf9b975af6f4c174282f4893501dc462b5f0d3e9c56aec1f65c489a77bd8429c180b3094da23b098e620ad94f86a3bdd87225ad40cdf07f291a06c909dd9202ead4266db2d39cc52ab7dacb868f967bc01b8e79e125e8f43f323cb86712a0b94e12d9c9ab524db75bbf49c0f75b1cf83e3752a2de2621d5e3f9e469f6a25585ff444936a12f2d704bd6365a555a4e182410d9a85d84cf4dc40d57088d06452cad2457eeb0792fda119b21625b0c048816dd5356462085ac82c81a6982fdda285d5fb14e87d11edbdc21e4958db8c8083617b3195a3e74457aa6be4ac12c288b6d999109e6f7bafcef8b669cbbe352437b3bb4803628e8e2fa2282b677ed41afd2a48550f80b8ca281e8f10e39769df87c51de50dc550a9e9bee3ea7eec1b2d35fabf2c3fb129a02a895799707a36cc192a1821dbeddd61323d711ab03022df2a438971821329ebfc27ae89b468a9ae30a0a8dfd4192f996e5afbee98e0fcf82d0885daf4f614a03597d9d873b2a0c4d88d02cee84e4037c3ba5895bb88f2a6979789c9a32d68612cc31ee8f9c39092d645913feda93b53b44db136b3576a3c877df9006651ef36f9712d306ac6ecedd2f3597c93cd8285592a85eac0223bd3d5a041a66bfa6864901f9217f36b13f6c8a47772de206e27cc6802702e3011549d6af9bf6b14bce532ee7ba1a1a1fbe15f3732b8ed806af1108e56d43f20d2724f9621f307615eb4060412af5ec7d9029fd3a339e5d9585268005268abf1755f9be78ceecb0bcbfcb96f09c1627dc1b22b28597388a97f43b1dbf0203dd6654a005a2b6442c4893b62a28534c49f1a234abc94bcaeab075825e599fb27224e4254099d297e2a659a79745368fb1f9e38d042008fb3e035c68ef0b799a3c14ad38893b63c7e189754128be43c9130502f2931fcf910729edb2d3ee71aec9df98d269730df66aa1a0f5b7043774b2a3bb4d57761b5ef702fd35c35e34f9b0f476a83d32295b48ec62e42ec0cc4fe0b9e7f1d3b205a6fbf848ed347dbe3d339a296d0b467783119f6cdea07be058c64e9c96c781f39a214d64acbaae211ce76dd6bc4aebb365bc3917ea27cdb53e5849b0dff068ddaef33a9a3a92000fe284528c9f591592bfb98fde74246e08dc6a001a91118cc9ca9c05186d3aff33d53d0577c5061ddd3ca7ca5bbc3dbe2ff63d54d23dd3cb8d12a4b2ae65e111b6b335e6bfbcf07cbf6663a179b6c21d8e5ad6b678223203c54083e5772671474b713185378def76392c934dd896755613b3dfa3c816b013922db4e77919ec4e4be1dcd0ae98d1738e53902d4ce86afc184b21f7da1371458bf68dda0ddb25c486bf88d754c1e10524d067f9d77af7d0220652357de578766af2676fa2a22c9dee118e54a75268a0f15e6bc825b0f7d34cbe65c510eae604d3b987038f45df0b4bbff97629e623ee14f48b893e66186c6edc961ab6292082d3f368ed74f663272ea5400852f09d5c6be4530b99b87d22b05aed8a646ca8bb6ea29c66f9786cec8566b1f1ced4bc7b933338d7076cabe2e2d7706cb30cf63d82026894fd8929766e8f5b622ece3b29a52971de73cb9c50bcf76b259302fe71175ccaa16359556d2162be8cb9e6fd7e3d4ad5d722fe8276c3bb3b19e15ec4bddf4b675e22d26ab99abbd23b818eb75d41d8c3d250ef239434f7961176de77971a87eb245aa2fc652796e9dcb2f0d021315c8f23b5ca5338f6d6b16625285b4f5f4f1aaa46bab1e434fd885c0e0532127d415047423c6dbc753e3385514a0acdfb6d5ba988ceea40db458d2b4001c02858b62654d736208d0a954a18174ea63a48c0795f0c27020715656d0b049fbf9cf3630ebb4240cf69824e10da3c6744866ea20bc07171fa33598b4e5ee5eaa5d59973886524f278f831e8b71787dac358b9a05c271289496c842ec49865f43e1e1d888afb3d85b3a8b74c17321897e3c5fa8abc51844aca9374ff066db410e2a4b45ddb2fabf1b202c2391fc3266accbde91a95318f35afb69883ee5ec3721bba12213c7181de23e6651655d2b9619d4aac08de66989afcffa2e5c1ee67b04958fa58bb4bdab3fa299a97b59e499721817e83998478156bad449558b11200234352f3d33c745ab26ab111a073509a90c6514175d295fa71695d8e485290ba63f136778eb864ad000673d4daf53e782a5e52684892aab6a4fd0430e404ce4195d880a0067018247ddd2cd07f4b19a8d65369e2aa6e74e42d5a6b32ec3ec32f4a7480f81358ef84ee201e10cba1379897ac62df5b359a357e344efb72763271157da8aca1bc9f4a2d39ffd4173e89dcdf6bae37c43d2c665ed38dc6d17e38a0f691cde3fa04d3d9c1df7325131c7734f582ccf15578fa8e0c6f3bf164bf2896671e624dfbea87bf08204041e6ea5c57cd14ac676ccc0727c8d55b14c4a1a6f3f227912686bad380d0bbabf3ea10b44efdf8ed6f2fd359773fcf4abec3f7f3ac6feef96a33e0436fd632969e4e127b4fb7579cda127af98e56160be2ddf7e6d8b868a8491a0e25152a6912ad6bdc561f63169315cefef202575fcd0b9c7de473cf3a447049292b95e08c0a3bbf5a969a7e563c3cb016da033b7e5e68ce78c2a877cb2ba806b21aa5a37ea175d11c3b548e1c47c6460d91419214603862f6728861f8f36d2682b1f177716857c3eb345f0641e569e31cb992b6c2046fbf083adc38044bbdf17bb115f707a7f72da8503fb6aa7a082b2b1905cf35d416ffa996723a23123f5567a5c9cad55cfeb470c394123e2874c27cf9ec3cb830e6edc2b66f43b4342f45fe260022f175e94279e2a202a18a052888aef459ff82e1505fb343869c793d56fd0e58555dd28c87242147d81787c57e1f8a964a1e8839986d308f0ad3f9acb65109eb19d821bd58c19cf30e071d8132a4cbe782d4054b870ab71c8e88a47c934644cb3b164feecd2d54272b0e4d54ed9e7483d1309d3ed79b46ae1feb65abe11ae879496c10a0479569e533d78b321b30242681f3a6f6460f608d853b748710602460771c578eb6c4d7c0ca0b8d46557947048d5d8c05c80bc2f12ec3a2de865047ce8e8c8edcbcd26ddbe5f25ffea6f7a76d4b693bed20f69da7ef5c892f4e78e53b6e68ea86e83f0da67b7474244ee0f4c71d4e10654ce6663bc746bdd984c91fc47c9e740b70635f9365de39652f1c0ba24e6d5db5da3c0203691add207aa9ef38dcc261d97497613a3663187d01ccba51793ee43837729b070168ff01c2e33c57f91c962c621942795223d4700025175287359a25a9954b81591e390ad580e88dd0416593d1022312294785d97e1854a797150c19d4e9467dba068a8112434ce4f49a7125aaad95050b68c40ddbabe531399870ea39386732f30ca67d1d6b59bbed9c55592ef67556d054c05741c5a1afc3a4b0a34f4e89bbb7d2493966cb0c473a0a6fc40cec1f71429c7a826a286c7d84f1f00bb66275cf6f0271136e7b599a06f95f7158e6f432dc35686aaf6c3ed3e3de856abb88e80552c4f3d2a3bacc981b1a49d83434c707fbf7fcb6bdb36ee4159d7a59854ede3b8dc21759f047a481041f25fceb00cdf403efbcc90f82b39e2ee050a105043e7e22c6eb6c1d12ca8b5695e109a3f4fd2fbd56373a4e0f34229d666abe0d1ad62e2a66946972af508e888797ba4b913e7c659b3f5715362e953fcc3494c904ebf9e86352845985b23e4e6cb642d402b2382327e8a25f2ec616ba5f7b42ac1192f862468d711c66662fe965f8743b8ddee4588a00916a8d3ac82b07f2ad78cc4fff6897a0a4b14881f5c746b8304a7413ae2fa5856cc75beb14d27b7d966b5137f658520acf11ddb4ba8f9b2041d906179c03c749c90a2a53d4a2bedcac3ad680e17b585e2348005d7f7fd1b2c72bac3e9512195578b0721215819429e59f189e5b681e63b9bb0528565c6c413e8fb06b93385b446e3427e1189ee0f91c97605d25a244d4ed63d9c551c1851af75b38648114e0592b9cac74cabbf28b93166fc46e336c065d1f09a0f26cc6d0a7b480fa52a79ac6d006bd4f95eed6e6d47b5eee066da00cf27c4c2a5cd136f36f48c20d81e1ab1c3aab6d6cc7f8dd8bed31a60951818fbef26eabde0d93ef1b1d4b206192ee0e3b484e5c6fe603891ff507b01e146bb674b457420031cfdc0809384daf0901afd58e429f32c25d8286e1c70a7b7a126c8e3801ff4d91c71f33ad2daab27b30397da7d104314f84391744de33a67aec1f47e3e933f142e42845b040fa2682232462752d453f84eb8e5aa2e82852dbcc64490c61c98cf2bb0051d230a3d726426fb8857404962951f9f1970c8f708ed93fe4ce9e8bfc7262f5c120a133f329b0f005f4e5f59839d3e007c027c118bda957d8a6eb697a832656a6b17750f809314477e5032729bddd47c9b7c6ebc2baa0982d6ea1275f5f16d9a3c987628bd3c1e9fe558816f044440d1ba2367af1044afec53ccdbe042e8c0fdf8fd772fd9cc813125b7b5e9f0124fe15cc920cc0abdb6c8cdfc67d5019167fd2cf5d060cc0b64a6aaf03423a816aafe3b250ea048b52835991fe9444c5c0ff4e94859d7c881c73708876bf3478e9817394eb784b324b2918044e51246b9b4a108a89b3453a852d3d40c9851f573510f58519f4beaf06b4b578e7d9bf346a621e188a4ba4f3137d3e9dcd83ed317ff1971eb42182f0b8c70b24ecab4132e01d6ae65b149e18cdcdf50c05038188b8408e87fd5df998fa6365406d820a7abb2062db37593c31ca523db22827f761c8d9ec4a8d2c1112dcde7a8f114102a10cce708be77755352ab9f1c2b980ad6da6a0c046ea024ee92b98296b293264200daa4398b3731764fc6d68a3add782567186b3ea8673f84e54abb859cd81170377ff3cee91e5cbdb3e37072ef262f698eb43c35cd526b3525c4dc6468cf760bb81784911fb513846274946b05608f687c4dc970e5d2a97b0532fdb6b3bdc176fd4937fbbf1b18803762f2bab1abb0a46003613bad8c7a596d691d86b909f6bdff11ed62fdeb2d71b0ca8f1b96519ae532d8e6c4854b3615f2ce1a9b53f3f1f41cd733f647a04076370b524fe2111d3f66e9c85a936391ac4b0adc0df553a11240d4af6ebda207608c473db8111949b756b46dc26fa70c9e80988fccd0ec6e2ffddbd3e12d8bc77fdb5d87963d32dc3a9f174510f530d3157e9abdeeb0b72113abeb945faa39f7a0f507f8096a7a6da674c49ea752d46936a8a812d1a4d475ae349942f59def4c38147037d89956127c321ae16722cfef8107fa7248110861605a5961a37586673f756b431d169133de7340da04ae9ff00f0e09385ccabb3f49a89c8663eebf6e721f992b994f77bdd81db84c6fbd21b2bc609877aee6d438da6b20f63b5cd4b5f96f7949312bd9d827d4f0db56bf0c2959c600986c70a884262e3b1d889c25c5ec936f4af59040009b7853603ec5b88ff07c3f96c6e5ea62409b22ce6898f792c222499a11bfcc101990ca9082eb6808f85acff3002adb6b99c344b13b489967fad0689f30e975baa23dce933b136471dbcb07a38d04e0f72cfd13f8bc89e111b42cf1e453871462b93ac96b7e570a6db3db83f9dbbea7b353b6e7f2b36e55d6a88ee220d2a2796d448e305820010a8a6c08021080101a806cca51809f6fb028bac197a1ad2b2aa3a9ca7e1f29e640d91fb2e89476bb2d14e4b14aa2af28ed8f66e884a4dddaca97dd84f1ae5cac96986f514152806ecf71c7b2abb22312e4dfd3aa6b2acf96885bce250bac75b45bfd5bb4ca1b140e8be097220474c23fdef63d7b951105a0745354f3773a4c04848432e10dbde3033aeede189c1d1c7e08f4557bdfb708a44b0754eb183dcb47f6affb554c7d4b851162ee4e5e57e66361311fdb7d5f1efd942199107fb61e477ce239e1badc0180b3eeadad1fb7774d6b2454c1e596044063767c21022066f563d4d49f203e21748961109a1cc3263856897d41d7fd7822c6f0c71f63c6f40e3b9d291005f94ee1bfe29405c89f19d28f26a2370ec9cf9167a2447bcafd8627b8d091756ed5037412795a19486386e1f2250c80f42b758d6b284ec3146b369387af07e98ef39c1fb536579ac9f39a3f1c809c5ee3d9b6666ecc7edadc3c45e4b9cd611b33fcdad85356c8d5943027ba91d3db8fd606503bc47aae6f430b62a15cb762921b53bb1e72a22062579865409eb70407bb1984b84e167f799a80e66db69adb195d58a5005968c55197cca467679ece0c80ed94976202fd0eb34635fdfda421bac7064769ea85cef8e8f9a1c89d59146ead0e7ed5ebb2d74c2b75fece75a5ac721892ae2bce38b12d858a61ea386a5e811fe5f477e2dda001e6bec1adaf5f77efd7a97bd1def6742fba28818a975d06cb74c92b062578c65fe6299a85f28a7bf44318dc3c81d6c3daa6f01693beeb5b00159583ec2de1e8e350a4339a014001ff2837714cb6dd226582cf4b9c5e3e83d5ddb23eaa2ad0e9777fb73fd7ad34e7890ec4614984a399ee135e6ea168c270313574ab37537d421244832c8cce09c5c1d74d3823014167f2e01f5a33bba2b7bc9cb7f101507ebc27cce5dd19288c8ee68828e1f191dcba7187c8ab25aabb33feb843e6c0c7325df43627271b3017fe27c62a6dd719c9c385fbf57c5b9a5f983f44534c8371e181eb29c168cdefd6befe8de3b7c938e6d61496cb92f3b141da2707d4570fc4e559acf912a8be0c2eb8d8958f8cb73497da499944377f5be83d8f0d54fbb6e5d76f0ec714c0177dcf7e9455473d99e8e1bb74b8273e14dab62890ed45726a1424c8d360c600c7cdd49c3b0559131e2759ff11dad5448b56a1191819b7ba7d70916d5b6b75a4abeecf14253e6ff71dd36b001ab8d10479653a64a3074775be9251aa18bff3dc236c9c2a12aa597085fb9900763733d305ccc10c21ffd1d8f3a128979f3664de4d7eaa87e22ad62072ca7c729d2acd682e66b058e6bca192de134e16803090537f797302b277c1d256d72b71406d30150e8ccc2b555f81a65bc7f63ab80cf19a84b1b35da933a5ad46d35ecaee9e22a68891c11157dfcef7773b37ed10b65a56dd8931e7e1aea10a43c0d42b0ea6aa017fe9bd0c5d9c5c1c7c51c51ead05ef77bddc327a59bdc0875d0b7ef8824fae892f46855e342af4cf8424b4bbc1da56fbfa735d56a8b5d94a4690632a56629d2a4b2b0a634f83bfc92cb77847fab265dbb4aabce07f5ccfa1c397cba9b627e7f9327b142d0d9aaf10110573a9789d2edd6a04f6506dfbe1a26182948feff185363ed4b8ae595e120a7098e6accc69df8fef256f2b0fa05d212670f4d55830843ad59b3829074e55b7117c2ea91c22092f6ae0585e7eb1d569d27ad2c16355d5a83df7805c5645ca2ccc6a20215c48ee0f0ab77b3203f61e5916542cb99fedb1e3e18f6d4e370cc2ca73f2fe059688047a4a067d626aa1aaa03616ef8447e3fe311a583f4cbda175f8b9ddb3062afa626d582f21753deee94df19b0eadf9ffd72a9af623b902d106f7322d94e8e6c8201d053497cea11a8b9a97d1e76b8d1c4f749d34c268f784e1d35ff8504aed4431140087dea1c043bc01fea4c5000519d7959720d9e5e45bf9a6c74890e04d44dad266df3bce699dd25e462d089d272d65cc3446c8b33bbbb16ab921b20e14b159b2beaace745653d801bcef30af5bc2a76816c02dde348a8bd4663350742f682d22b27e2b4712341f23811e151cc339dde6fa637137475ccfd1f02cd38f212eaff8d52bf4229647eab61690542f7364dee53fc41ce629a22fec802f41f52f19c3047ad3e85d6175486676cfbae47546401c86a7f5dac96cb65b194fbd3a1895d4b5cc54cb30594d81674a1212205214d624e10e0d300ee36ad5c2211191401d28c56bc7126d3961dda7532ebfb05a4ff206374007fe49968bb2fb8e4cb2d95dfebbd7f207fa8ae6fc535564e6cfa76af8596e56138af4b685b52f7744ca12396ca78e918271ab1444e65f5a79c3327bacfb8280bf5bc54b4cf3d1c03b998d7d9b2487d9d42b1405fb25f9530dc3f1fad04863d9bf5cf4bed1102be34883c7f1dbde224a3ca31430438e1abc23490f65f8d39bd3b0e79adf91b824191a16dde672754392b857a245e40979d2fb6226dea5f17e0d9c5b4c519f1b08bf9e32e314cf4275fce2031cb3977f946e18c2655ef6fb0f571826af2a1cf9a1dfbee4c07bc13659b609751e227a30555a43862020d4347fab9812efa82f235e775c6f1e29d9371db0592a9a4bfcee6d7bc779ce5fe11fd558dbb1463db8889e7658b9f0d2d71f4c2bdb3caba1a73f785741d3b47af729f390760dd8b67587b06fea1ad78fe61a2cf6978fefb282b0df576182fa3077d8c8bf722b8f6a26f889de3326c5230c6293b6e18c12c73ac42799bf91acac3484696b724c0f60f060a5cfd8a52187af85f4becdb7aff67bb0c5ef57b67ca455a0cdafaff7d96b267d265ffc0105f57de09922ef72561c82e16fac5cc9f1b3f968e45990a5fedb3403a13d8b577a6c2dfbeb911ca924012763d198b902378a94701b9edcf5a48f57d1c526598f9d678e503769fcc2d7246f955ae257efc44cedcadb44921f109631cd8987eadea41bf35c54e44e23accdb5c514d6622b0d51f4705594d272c6962481c037bf15b0e50ec42e370ecb1c3202c8ec4ddc36beceaa2f505baa3a89c1c4bacd872b7b89b99e9accdc802131c347540acf30b20ca42dece9f6549b9b33416426b4d67aa7b1d2304b0d3d3e606e1dd225b69a865e8916c28b1f8c7a8ec0213fd421186838a69bb38a2d442168e29cca916aa8c9cb133ba34e739ea93d54fe2f58ca8d24210a59193b9aecc9ae7c4176ebc07858c323788848611e4821f2b57213ac290314a45f7f31f2f68f0023beb3f473844c19ad281672b3aeda7005ef11baeefc492f04eca6cb6f0925c62898343958a5f10ee828bc51c7e2bdd07efac5eb9ff41d1fb46f240ac837635e964ecbc431abf57a9eb681f720ef661edfee907d66e550327f0fc61af0e2489b2232a543fa5b4d8bc75cafae7db71ffd3c555fcb5416fe3d7506c5e03f1850785605df8c6fcd1e41fc485f12d421980632bf85a408c42ce430c0688a2f2062be7ecbae4c319317285c2d5b8bc66087e6c892f1f85451a02711aa81da20e8d3c6b8d0fa8779f8ca2bdd4af1cb83dddeb1de43d6d9e2c1c26f91e216c26517b1ea54d36a4c2bbd7f999b1ad2c3f6f9ebbde6f2fd236519fa12feaecf9796cc8cebb843edc166fbd2ec3b91c1ccdefdbe42b22a938118b86b74c821d974cd65e14d5c7b384182d1ecfa22670d6e818f25da7eace1aa80c0f5ef7a208da22faa029393cbf52687b80c02de2ead3010d86e2728dbc031c88253a3977f9b6228ca760b29a47d1a71a1473058ca7c2c8adf38965b748a523ee9c6d44a4f288da885258bea149a2f3c25f77bdded0355cde33b32f55f1c9104fddbd13a17fed4ac27a1f798b98f7f9058b3d8ec630c6d1887c2c2d9ea89dc79d6e5d1b0fa202b6e3160c0ce1a0b6aa2456cb9a708f9a8480d94df87f6030dafc80e5f51bd7181065309b20bc156d58dd1ede6a73fe5b970e7bbba58e615841de209b560a6464cf381624793e2cb3012eaefb371fed189e1cde846781341f965d43f59f1e234b77e62cab81dc3a1a79d9de9cd64f7677d5a14fd7b1c50e91db4d8208dbe3e1ad67399e2bb9715b059097f47134d9bc8dcc91ff16e60cf084881db05059b747e275a880fbef8b7271dbfc354009f832420736f0ea70c015f2dfbb46adb69c20595baa9d66366876e843a74ac92c0647ab29e16cad1b1d76615741a547963d172e56fad058d4f0cb9281ba78be66d9d09cfb1ba5971fdd077e1a2bb77942461f04da4f7fbee5e8247c3fae0e7fe664f2f564399496682dec224a9bf502c974c5ab6318bccc127354d870f3c94659988be135dcde6e804b1cb76ae44a54f38712689b0cd86fb2cd2a4bf257c91b6cd474b629d5616d4b47ef282d1b74d633749342963c424b8cc3bd3c8a944e48ef888755d09571e3e8b86439bd7777cd6dc922f0dd1a236442514ef8131c628a0b73e6841518ec88f693f1e16d8f39a52706c631e44d20e71894b00c1f14b75a3e58fdc27dce0581cd46bd9ae66f54520842ed060d9eae67230c58df9b8694fc443d4530b51e7ff5ba44a3aeed4ac6b7bbbb125075e1933d7a0fe997296b12f0136a6cf5c78f1c24560f4a0c99e1038529c4f8f3244ce2ec56132cf45ca12989a6ac41a897f34d83c0168ca71c83060097cef3cf7f97b4d78f1c1d0fd01c18b4ef5835632bf20080c579d1e82e7d032f3c1e7e9bb295c8319e88dd001c69106d74e4ec0c3dd5d24bd1cff2c287ca61ae5b45e4d2d659233be3ce6a7c232d1c535a28086f4b1fc1ae25bc8196520ec1429413deb2d777b35069304a928c12526d2b4755ab052011a0fd6b5501f3193ac49b37ffa85d0be031c972d47e6a390149a08d8a84314dc3fb086cf5cfb2ce66087ea5ed9ffc80b10cab0d05fa179dd20fcea5cb922e750c60b74a2844afc5872aa6f43f4659562e3841d5d2d5335dff24ae360a697effbcaee3c3b511213446e53eb924243ab9c04c9f90123377a038a09b9932a0bf78fdc40d3b4c6706630649e797089df761f25baa62a2eeff55049d2b3fc566802498b3ce10ae69de25cf9c881ed786907c199c3bba7c835debe55419e87445a402ff6cc169cc152695c2005039eb1a55275f958f933223cb0145cf67581b64c0740dc3df73a465c9fe74a064b9e855edb72ccc80dd0090143e28450e5ebb1c1a13eb4cd3d5f93aa1354a3f18386ae358ae061406393cce531959725aa945652a14a3ce40100368699dec490c7d0b494145d48a35396046faca1cf9b3cb39d8392e3728c22adfaa327ad54bcca94df2d949a3bf9b84f1faca6c6ae612e99949a11f03f7ec045efd0207d21c75bd64537c9f6465cb2e917985d7ef0ba320c5dd713fc5c8718eeb1e50f37eeb176dd37ad7a50f6d0c666b7bbc05e4e168b07ff2a87c025c8b51eb60a2c8e04c745be1aeae87711a1f1c030b8623a82064ceeacae6f9f04dd67466330756b425f776e9919d1cf56a6dd4bccf0c5465120212ead57b65478f189fd3803b3c6963c67bf30fee8f6a80dffeaa427cde1b34012f1a2d2d4775d395d4c5537071c29aa6cad2d09f66800bc787ab700c90c028c2b7246d64d0840f0cdfdc89db41617f6e72e65516c1e8cc944e76899663f29746eaf5bd7e22b81428abd2193370a545f47e3a1e025c697da235ce500f2535cb4d416bdfc433b91b2f8852369c313c88d483ec9f9f59e70ab8cb504c13c6fe465067ad02361db2546aa6182f0538ad0d6e2a40a81fcac68c97af900ac6813cc174cd69e4df2699b42e915745d0b0ad601abe784d39823dcf3878f373f9e2350cf0eb0ee7ce6fb155649e06297174dd000d9af83c8c4320f0425617879dbc8b6733b9e42646efa95206bf84336dee38841f3813ada30976b9af372e48684eddeb7076df5afcdbae6989f8ed56845d34ec557bf749d3bc629aff0395464dc4666dece07dc31795d73edc284785e8052ec57d8138b3347ba547981c591c873468087ab5eca1398dec718b8347e879ccef782093e5eaf82412d1308b2e794387448cb04a750e8295c684e6b9d2a7d4e97f06b631cb5ed148e8ea7210e8020f41b4317006077096e64ad73b3c9e0bd2ea3863ee9142d00690bd61e1d1499006fc4bfa9ad54b993261f779490d0971f18148bf3678d70601b9a1938cc8ae66ac52499d0ef692e23bb769c3fbb60df42214c552469591188bcd0768c65ea3c90c002bc9b50bded4527fecec42921c6f349b8cbce3ee07704e4c9896ba524e006e3c8509c6a82574b5e4547de5022b287896e943344f32b7c2f144dd49934f301930ce74f46fd8e000c6b56e1c5cc531a5996315028828159e0c6b8ad7874f4d478182ddb4b8d864313a116711ead8ef45b1bc131645655f387ab13247406a7bde67f9a01cc4b25a7f8a46b2deb4b53dd1b61401b6ca4a0ca1f8206f460c4d0afbdbb364ac0f56ea56f1b9ff44f51f55423c371a375c6197c000b37ab4575f9f2c1a4ac06d2d37ca4028fb566f0b0a7a3a8120ea9edf8913f03e2a5ba56797525220dca17f28b740c004b12651c914ed37d7da669f7c7290f144c8f656ae4cfcbded82752ee2d2c9e622d5d083b1249d2dad020acb1398112a96b4ea7925e691158486bb609666170179576a8529a2df4ca1aacdd72596811a27f43ab46115943a2e931a230a8347a1688240b0a8c58f5bacf3f721a96fb6124ad6a92dc596ff1dc96012fbdedd095749a4b1334041c01cc82450999ef7987fa08cf6a681d323e5f4030be248570dea2ec5a33dadcabb98a0869a9e308f7a54c660cf6b8fa92fc79ffc2c218234203e45fa4e253255aea9ac6e03c715d9908829b3e9f36ad5a068298aa5c5e47b05e2fdb4517749c6149c8c8e9c9ee27bc64bab5f02deafa4bd663783251a5840d733554f17e325c234d78c3ee68ec4ff0547d5ed09e93c67a4d5f355a53e12cb330e4f044250c90a58f41541e87afa7ec2c2268ec95bc7644828d0b105191c4c6a6641c9344810b5bca1864e578a9f765d8827c5782012320e83399ecdee4606650aff3dca5d680de18cd2b1e4159a63034514442d7786e323216fede840f792fb8ac9feda1c38ae8f0d3837825b1a5c1e81fd1e06ed97724137304c6ba67df69e6c3085175e100c3eea911b1b85aec953755b9c3b76d29e2f69f2f3f8ff859f6a3a3edeccae8f266140c281b95a9da0733a57627ed7f23b52ed705fd27f7544867f9c13bc81e4252d8121d8fd471ade562a961344b35bd63b78dae83ea4c61282440ddb519cb0ae5c2e48bffe7171cc2b645d1ff1428878250d969d7a204ed95ee78f119c9d7b8990a9178a351e48d49433f5d5e1f79a01ee759168a721ca9dc36865092b6e7fc5af1ba338a75377b5eaddcb7196f57dea9be9b082e6d149fe2f34da8cb16e62735e80b2a8c959b041578f37cb6125ab7a1ea6fed9d68285f0db10c21ca87b6eb45a8f04d55436d22883f788ccb60d78d0ea34e989f020d5aaf123186d41487980df028c7f8b325755ff0a09b24910033ad6b7cbd03efed54dca1a59b42e8f11bdd8319d3a2e0cb9f614ed315e68a65bb681278fb97ab62b5351aec2766ce5d67522f2cceeb20e077fa6d0f1faf611f1d11cc00506f7124e46ed3711a8dc3f9d475b1a92c32a7a00f2a16e02727a4b467f99063501715fe9c888000dc03dffd0f963613deed7106920c8d003cdd144c184117d373c050aa664ab1aa0d874b8ec3d0e075ee29f965212ed06c361a18690c987cc7fa44b19d01e7dd7bab2119d55cd4bf112f79f375b54f8a1b6f1e8c51a0b3abe2708fe1c00b927c467d33f6a65a7572fb39a85578fd375f67fec14f8893f1c3652863177f38d1cefecc41d38706d5f03d5ecb160ead5805336e78ae1af1ef24ca86d507368723f71056794a2b999cddc975712245c213e221328a2ba8c5ae14e8639f4aec34df7116045d1e0a17ae5386c242d502c2f3242eace9f9aa33ac862f77bca6494ad7c531aa32c1aca70c702fe52c2a2c1a5ad43c8614dfb323edfe00de127b741e5c5f8000d892d982ec6782cbe4d183257e8715a52fbcd845b684999eba74c769661db3049de41dd47e523b79685d79f41286ff5d165b462f1d33b8f9ed5b6660605ce65b63172412061cd0a055be6414019ded66443d52ad6fef801f4830e33526399b4ca356cfd67e4bbf9ce7641e70df73a0141975fb8b7c0a2d71140904d7dcda3f8851bf8a207189b29c25617543a07a7ab3788c2bd6a7ddf531d444a1b43d5330d2887bc1271386051dcca5607caad836b0b8d9472e133594f2506c8edd3052a79085827e32d682fe13941563f0f8d888caf374909265ad5ee869094258212f75a1fddd931d511992dbce5bb4ec52ea7bf0b9bae5be67117eb45d7d7d523e3190b4f814eb77ee410c40e1fd5820747546b69849ee6514cb29eff5554c3d22e1fe0ce7114f540953f1b5d780b0486449950df22ba93221ad7008424308d6358a3704a1b449845669072ef58a5b68f40147af8231ccba2ab4e2751df1bbb00085ed112a7148954e5c5e8cecdc4b5f0b1e33de3351ae93b1241e04fe55dbaf1c8df079e0731bddfdea75cd41573c703ae3d10eaec247d6e4c217db27543ca630f12d6618b2437508aca230cef127971938db02aa57b782ab448888faeb19048c18a2d3b7b4c179581b3cdc1953a7f6f96b719b0f2e75fbf6359f1fe1890f4cb4f8f1f064a4a891ed65958b4bc1cef84a3f36825a15b326939229f1b7c4897f99bcf206b3b65f29ea7274fef2704dfc2605d9438b556cd2cc1eb0986bc356219c70df8d22da60b3e45baa768292cac53480f738a5ebc6b1a64950bf3809f9c37ff97f4a4c3a14fcc1ae39b775bd0efd01428d41d82c8b2ccceecf4333cc4cbd9f03c42b781be8f0a495c2997a9edcc73b9e900b02173718c0feccb52a28ddb8cb439c5c7254978ac06ea601c08f3f7a824a951a6590e1bed0257e0384f1b20a10252d53713472190ed15dc09ed005f4e808060435330603b9d3b856a8d9979c0eb747ebf946a9b73b4d39a239f7f0eccc36021d1b3348b9681f9de1115342a1aaa7019a2e9d4f4d29863fe0f343977caaf03d4990cdde431c279749bda1c247fe76bc07c5a14a6414e64a607a4f48ba3f20432642ad88213f1ece7ce8cbf1a95cee334d60aa366223d35ff207ecc6683976f3d3193fd25a257cc420513554ee30598764efc07d11ee3980794e775bb71c8d7b8ccd7f3533df68e7d2f17228b6f76172ac7c8ddae5baf844ea02ff5c17b363b4564389d449551e785193353676f81ad72a7e15e23f2bf11a2d1a399c6f3b22c082c49fa57166b0434f21fa8e659b3f8eae83289aba1136c48e11967c62c9f94334f5354e83da6fa3f4dde915cbb829914351a17fb4eda0811410ac4181ad41c8c4c7f04edd028f0e77217d7d606961f8eb07fc23064d8b9c73b12b1a634543bbc52601283a7ea1995dc459191ce002aa216df7f35615a0598598aa18ac5319992c2a73d77ef98575192ba9a8322834faa9a38f3d4863339c55f4f3972ce8e2b79dd112eae87fcb5c95ba8bbcb867da56e7b42a6ad919f56293600a40697f82fc2551c6aa2cbd20610acefa003b1a4e1c08248803c940844730cd22df38677ea2ccb6e6fd5e30036d92d3668f3c0464881367517f04c15ff77cd508c1f42bc5a4ad243c116b78184ba34a3bdbd4edd8062a8982926e36d916a8ef52db205e65a71568c17a50a742d1cee993ca1479a90120524c0ee0ce6266c4bae96e81aa6537a04ad820b683b1c8a01397be4c69cc96131f1a9bcb611b03546c082e63b6a1cd324661a30a91841294654123e6346513a4c69d98d9074706387897476145cbbdee48d5e587e6160b1f506200c46b8a4be24a345b8cadc2ddf4e1e777873b4adeceed2b6fd739f63c0fc73b29c24685c57989acf539e876f5ee13d3c9c4934e523b1cd3610a8e8585d963d4c13fd5d9fc935a9d0c1a8179aae1501f4743d7278ef08b92ee6a9d574b627435d836a5f3d7cbc64fc6d3c7751ea00090a2258aa9a877ec198ede3f84af892fa3ecf113f036667c743ee2d2c111f006eaa848de2628bbbac82e1adb7b91f2165d9e26eb3efdc9ac3a3ed1c90120bb8cd4eaa7a4b779477a4db47508e621c6e7279f8c7fead38aa6a7190291a902b6c6d190001f511a4c146e080e9fc4422fd281bb03529ab7432a798227a769167e610ec58d79c9fce50ec66501276e96aaa20f0c2840e8d8be2913cf9e6d617b9e8c4292ec3de4d0d0d2378ebf64bf0abdc4f05e9a9be664a526d3ccac50dd689631f8a259cc98e5ded2f0bec288cc7d2f3dd39ad84a8d3ced964a483ea1dbbc8de54295f69b02de86b06b4044fbc79cfd3a71b3a584f4777d7bd9904fccdea3842069f3e37bd750295500b53000ff6b03aa1647ce3199cdb1e52937adf58bdfef7299e211989e972183261265766e1dcb01b7779c4ec16df03a639e461b917f668f338c5ddbe98e6a2d12aeb994fb33665c7faced75f888cad5f1a58605038bbf715ca5c4d07ee3a0af9c29da545ba66c279e0afa580ed95a62d86871d741f805e2aaaf9ebf55f1835751fe0c667deb6cc970c728e5cbea49934fc7636a03453e9cc84a03f46d1d45cfd25c948edcb4ea277942d2d2ed4422eb89dc7f374e3101d730767739f642faa3e585e7efcdbefa35223b8e2f740ab7441f60385eaee0337aedb758a3693a72b2cf92e6d119469451677d2cc3cf81c2df76eee59cfab7748213a0435f2d1f28c5ccaeedd45aae23c70e48f0cffe2fe059d42f4a8bd2204b425d9ea970f7788260d89cec29fb0f5234aab238af7e9aa89c33992b86f84be73aaf959ee44e58b6b7ce8e5cb1e1ed9a2e49971cdadc97c1c7c0b2212dd3743e779ca3685063fab308c907f7d8ed662c69abb378ea5ece02b1cf5b2a23d98e3259f1a71092676eab7ddcd0267e61054e3049d39996fbc6fbcce2113a774cc2b1ed611f4571e7a2afe45bfa362b450715006619ddde58c6805c4b5d8278838c3ff9a33ba4f534b870a3a2d77e6de26b3ff3b74251f3271190aedc8a2e9ae9c80db393bad6ae9379b5d01d6fc161eeae1bc945d86cda537c2966ae6536010d43894955f03295da361757f9dc21c3ddd52ff1d68a9d60dd01a361c0b97f29f942d5da8c48d9c3c403332089433b72c66a69b0aab8f45737d91ea3027dcdc083c79b1b42c09c5fdd09dc403d44f3e19e6af7c324acc82d265ddd04b4c333f91464a2adf04cd17e22fb2cd7a72404d31e66e1d48e8b92ab7ca3bb4d4f4e301342bdce04ef1fd9c06240a403c1387edcfac9d862412c5638b58475ea4e831302a4938358e54647c6a1a764bb79a801451f6a3e7bb4f27efa72737447db72d77ef42a9fdfa2dc52bc06c8ab6fefaff92edfd3cd95ac57d7ac3a8ae6bdfefa1542a0cae653f7b78626fce19736a373896be22a245357671ff4fc3172066e70f30b9dac186943b92cd63796ee555c64ae34df577f0a81d648f1a55997c58df296a18c89f0a48c3c214108fa009f6d59f44242a877494f1b4eb9e92447b17fd63fa2d32a7bd88dc050fa85dc585a83c346ed82817b22593f444554a77ac6855b50640a1cc0e4b6e112e1bdd4ba1fce9e3788c50287cb96c50bde9563b11cd41cbe2338901bd1da4b9523995d4924fd66b5d070951757ca28361f2d322677705bc8c9fbe8043551cca46ad1c1c5d4d5b73a787321102fca6a50a215648cec49045b5c95dfa5233e60df80799d2fa746ac5785b5163a1e632c9e8dceeb0a857056e60597f0a31d113810a5e5b1254892521cae4a10f321aa478444cfd831d7cf3a14333bc890166d70043a4c7256177df981a8eb72dd4e7dd34a71d5c4a33f48168186aa51f1b03ea92a4c8214b0ca9f76c255f068602b54b15b1394b762d3f212951bea56a7d6cb5d0d124714f03c6a3300bb1bdb8faa730f18facaeafec1de34c78c00702fa2188658dff7e8270603110299ee314c404340f936073316593957dcbb798b057b01e743438caec716ca0fd5cd22f04454bb64bfae9360ced000491645a505c7b98a2d088d9167c6b3e3bf20abee4b04a05c0e47325936b4a5fed06d4d955f84254ab394975314d2df19f495d854a86ba06984421ec194a0d278c3f9cff99fbe3c3b8d29e3c73dff4d6a69173b3c48b967b47ad4a7ae5a2db3f7477e9b92f99468936685d1f566341f82186f53cfe098bd75b6e75e6c5196588578fa19e31131ab330a3531091d48e0f69152b077eac82f3287353756165062746356927c2e9fa2ec028d3a6580c8f1d84bb0fe897924a074f575f6eb756f5d89a6cc47232276dd83171f54f505cb28ae34c7f3bac3ab649933f4dcd6bccf7430d37b0e53abef0eb599f79be237cad6b19c748e0e503b9a7146f16c716bda7c4e2d5ee84a595f6abae029a2dc1cd5992439d603c92c18446b6ae34fa4e101c6af92054a5bab740f509b8915bd84ef86b86ccaeffc46d358db737e065cc7cd28db50c720a406fcb53c91fd7ea37d8304af1e036256397b5241a540dd1658e00873bd7b54b5d62158aad92185de760a974c4d0de3556823f2d269ecf7af9e209bc798b1ab53aea094b02df91a373b297b7565973ef52fdb0cf868439db8e9b88c6ced779c809fb7bc4d2946d1318ebe3679154ff510a5df173a47e9b9296dc96f3f43d219e4dacb90cdca8ff4cf75ecc60b43a79afdc6294bdb534b24da6533e8e54ebb1a34a0e7bdbf6dd4c79ffe9eadc3a002e2a53f0582953da8861b8cba715f4a3042d4e863d88a178b003b9a05b60b6b5feff3f0b42aaa814e79890b3f664e045e52c8bf873942e14543bb39e49f1007053da237f26b7bdc4b0ae7530913b0a629bc9838150276a3f40edaf75b481d3d1becddf14c39bffaf7fff2c1f8e828d32eeb43bf65242c7ad64b1d8a3e81112eec2f36bf5fe34a8bd43726c976ffaa0f41361a32cdcf5feb35706ebf6effa281d93d5dfb7f925c7c24e96a5b79036ba3e8db7f52cbf35aaf0f50d4c2308d03a6bdd58445f95fd4a347069cf26913c6f5379933a47a3211776fbaec01f6b2e212de4379cd7d367fac28897a821e856c578a2c65517fe7d867dc8df702edf3519a79107c885612342961300b32f68768f5cd616c862ae242b4e513ff711f2052809b4faddb982312ab95654bc6669623f6cb9136dd9d6555ad877fc065c405fbb1f58a5c393a4438f8a74534ccf6184f50b37207e7521ddb5e145570836d16f3ab27e318804d47bd4b33c140a3029516ce5162966723f903069446b8a8f544cb42d86528d2538d7eb32c435040337306919d7b9028e9d8de4d03a091bb7f915dedc0dc8ce8e6da8aeb0be210aeaae459b28d28f282f1a481b821f0d6a3fbd7f1c518b022b48992ac155aa4516021e942c8395241ab6719e64b7783ac2bb5e3e9d3eac0ef794281c42a64db73585e6dfe5902878232595b489f8d17c3dce3e26cd61288f003f110f9525dd6fc65a8c1bfdee3d7cfef891bd546e90e0c24698c50aafe8ede905257bc8b8f7cacffa90ee2b9b618afdae3de3bbd74a5c8a572df9c7ac8090a4efcf3d0a06b648669bc1d4bf17fdc7d1bf3425881c5059c87a30f5b79c92940f499005ef60d31f41143d919048fadbb7b7bda08d98031cd04fda6567cb8f671d76538d9da8345c04ba130652c4d6e9c95bb52e72d8059a7d99497f08c9fcaf03e7776523dd491bcfed0d6ff69d083e7f09d8469a4be78d8bc508dfc02f1e45f4e3ed9f5a25d41dd290c1c32a3a2847ebffa3ac300576aae59f24a954a6e57d3f4f84d8151b4076ac82665a0ea0c86a7f9115dc28a3ce34d6775b465611d07af445be5fa05a897850edef3bbc81cef43ac80df37c378aab2f8e28d8120694ca66289c50d963106beb146e95490734d749bbb2c49525945efbf670aba9ff7487ffe7972d5c422738ffd0c22aa722dcfa728a5866b30c368420e7a01ad326c7119d2fbbfa77dd80adca8f2360b387c386faa62f12524f322d5ee15cbf1d9ae1238eec586b28293d353c35bf0a914b19b22fd600837945b46ef80f20b04de7716e5d62cb85e48cc33c6396951bc7149a35d5e040982ea2675273cdbacf1822d6f01a8462a4b3f1a1cb44a7059d49fb732e9593b872563d9deefefb327a748d8098c3d82e2fe4ac46f213919d3dc2ad12ec87164c2eb1b78f133bba3e513d8f911181621a7cfd138423b17556d9263f625b618c74ee90afe891b73098460fb427683c9172d137caa977ad6ebab93f14b28613f60be6871b9bab80fe12e8a75b231b68bb37d925b942f3551a4f308cf5c8008c72e9ea3f3e21e187da1ad73cbd7a14c538c46b5621add6246f1d7980c9ff363da112d39469ab8bf67ecc087ab650d9be309aadf139584ba5f0ce082cae5bf51c77c21eaf8973c3218878f9c134a9910ce2dedfc65b833f587d03c065c024942f5e2b8f48ae80b6c11638ee45d25c91099d07121126000759ab5c4cfb72eb4b8decab6abbec1f4fa5ac32300cb3f6c64c82c9b037c4f188bf9da701fe84c1ab53092eced7c9aee69ca82caa6247a4e43c3c6542f20eb77568f8055b196850aac10eb7553c75e340675957a36ce9088c7791973ad33edb88dec96f8acbdeb949eddace60acadd9d3e3c1e65b65d42e22d0ca4cecac1ef63604ffe7a6bda658ad6baf8d09321ea117560a836fd44370793653fa0a8e6d084b33c1e01d4b9bf96c6e28d841f48c8d9590d8a99d06af7639a86c3609cc35b32e9e98454dc73ad034690214f75da4d63aa47e5c3168fa9e73e4d3d474efeabb5675b77a194a076becd0c5795776839697562f7fa54977075cf0764377eecda40f4c7f0c82124ac479244264418c11792d33294cb4fffdf9dd25a5b101a7e67eef825ff80754d1419df5df648222f8de777ccc37e835435fe0c378f8bd5e34c2e76977f60ed17ff7497803850d469d59469dca2253366a28c60c769ee5e07e5e450696577305859cb65edcf9cb69c949ae49e41e8ac16adc95080e97582a09a963d6b2749b0b41737f041cf529187aaeceea4e566add29d1c4a6a771c7254c7934abade89317a1b32aaa628356c72f643fc5379cf1eedbd0060309e65d8f30b1305d7cfb1bae60ea264260c48f3fd14e595b504ab4454e63462bb3430b2b6fe751ced4646570ef0c0dc8dcc6077b2ebf738e96cee8fe194455939c8802698dea9e983bc251eb5015dfe1cc802a2e525f0d990185b4c64954a94f0d6a4fe2abeecee27c4eba28a63be77e83a217b89883f4e5cab1cf4b04a5c07857fceb6276a3b32bff1f4f2850caea84f468ccd1b13946c011ebefb88d17a70d16591d7d990973475f322ffe86c70cdb66082a8f8d91e21dcd126c1dde4ccacc0a9e25e7cd7350d0823668c646f56029eec5a64cc66591b4494ad631201c64dd1cdb32d9ee74c4cb1a6ef5a13074203c4341a5d3107a29752eb3ce772a3c995938362f224264786189796e43a08d4f19ee309114392ab5deb6bb2cd0b28540d06c4e31ad73867e804484f7ed3538f0d5017206b0bd78755edf80f3421c74ff08564d39c702a6ae3671706cef5359580002ccb9ad48bf5ce5a07f44315b88a002420e084709b89ca25bc6f71b61f445f52de0503678e06f60e82d7896b8aa9131151df5d81c56572c10fd3d19cf16a319205a9014058a006ff6f64593f40e71b3d08f076845b98404128286b3731882fb1ff36ed24424b4cc18766986e8fd411bb389e9af81b49177d96a9b613f579da5a2e42d30ea0239b405e58d0deaf7634171d0499f4bf8aabb42c50a089b7484c4adfd3eb036c816b9c876601241f910c841d17b29d6f28862e155a0052f9c3640f3e85bd383ef54d997fd7c067126f40188a9c453a17dc2d35050738788c014bf9aeb45bdb0fc040a3dae9087843b7a7d61f736710c7246843faf1b159429555db921d3b6afbab29b487f99e22ad23a223ed953ef0558d3eaf690c2feeb587b53e85cd4f7460cc94dceef788abef63fc43585fae5c1ba451989b98b955b923ef28772d5de73791519800929ae90c966d2108f77be48d537f7f976bf526d422a3825f79099425dbdcc692bac4312a1816c573e6585d1c7848983d0f778a4cef8fa42351eb600d2abcfabbcf4fe884c524182755343a65ed4649e016fe55b38c5c4ad656a0fef36c5ff6d7cd2364a866a15f87e6d8efa4ee4ae17704f22280e3dd7021c13b0790cd53175a165355fc7fc818fc8fb947d281a81dfa6950a5e0946e667ff1c8408fc9b145900d8e71a91f9da8d2621627bc39e0cce4a714ed35affa7b0d602ff5f74b2e4aa4c165e1179b0413493bba42354bb21f021237f4fa51086f6f1a4b8926c68f2686efde9f3189a6eb7b0628ee7136c45bfe19113c5ecf8793b2baf64073cc4001c04ed6224adcd6dbe6739f80ad25150602f845d82fddb2b4a70477eaf63681b656fd07a86ce20c1d5077d3702ac7d8db563bee75d5b1b4fc18445232d29f4c477b0aaa668d8f36699b31331e8d7c485b8d6f2cc753d24cda807660b9305def828e7f88d8052c16d69b1a183e5592d7233224cabe76f357177124550ccdd8caad9e3a494a6db74f6ffa6acefbd37e8faafdd31d69716b3ee5922fd82a20343c8fd8f52cc1a0a7bbaf68f160bf007298c0a6724271cab4d800a7d9d074b12260a7bcd7fe613308b2559ec10def2753129826c019ad891456a599f97dd0a8bc629410912ca781ed51c5e74de646f73e6feca16dc5c1dbf20834edd2bc2f50c19d613a8cb4c6406781f4e533b0aff13084f8df3555d0834cb3e24db0ff2f41d1d3f21e7bc81202f56ef7e6607d169c39c6df1d9538745e9fff829e09a0f910e9a5d315501efa46c12d4af8587fe660c95154cf1c23b56635217d77577c7f9be1ff52c514df03229ceede0233f5048e82025f8a63bc3fe857dd01ba2c5844137b3a410257f2f318fa766a3b8c03c1186b927b724fdee1150e662a832ca1d91fd44de3c5da86e0172b6eccbffa626e487033d445b1d301432ebe23b03d832312482878f6706a73ab41b1c0c0607c78d44ab949292fc5be0ff9594dada1fe7340a78502bf712290a24c4d39abe18e57f72d6f268823e8917c07462602b43a0b31c34f0fe7a3182465b849b07b8379d2b8c8cd5c2cff07f283e4502f8b3f75329e7a8748ff0560c28a3defd9e19d7d75b309343e1b34a0363b783064e1297c0f341bf588a9753df9d7d664b8cea83cf99cd1f7d157801188b792f96d480152ffda80398b36c1891f77dfbb39455e323846e4bc043785d86ba579b028858e153a808a8423dae771712cbddeec0e1a37a0c11bfe5f9d60028c0d54eaac1491ee9eb6adf9483b8386cdc02c9b3d92e4c5d87bc5ce1c8c88182077139432fc35f8bf5c382b7d33d833c50209cd004c49be28680932e6b79c745ad6d5247fd84e03fb78a6a2f8952c3d350b565a0edf93889065ca671984aeb44323dc9dc4ee64902bb1e00e12157c4db2f6cab8ce09acb961afa48a58446a678b19f6d9edd8e6f3880cd713dec1c5003fece8c9214518e2afb7fb2b59529c71c5a50c99352bff9432ef3a34da9d4cbc13105c45c100538fcf17a891668c889b91200e0934d5b7eb2f8c6fd60712742953c36ea6b1a4fc7e1403f71077c8cb0a58fd7fc94c9eb7b26ee44b4d123918e03e064263ee7651bca931f447eccd4d8ace1662ad79bdd80f25446c2a8f3e19b3fdf94620952f79511b0eaf5d3872f4b7e5038e0628ddb50440c12e5e4eb6d57c37ce56cfab5eafa6e7c65053783b618962d07d549146e595ae4761a3912d9d7cc97f76bacf0ace92aadeee7664d50c63241dd99d8ce02cfc7aacfdca5441671be9f28a8aa7c34bde965f7878db559201c6d78e9d0f11aeff961fc4f6cbf89124e0e792e8cc7af62b130b8aa6b28ef1b7242dc651413d8b622cce56bf39c5b483074add091d6a45bd8a63576e065d8a01b9762344f1dae45dac3bfbb0ecb39ce206e75151d819dbbfb7bd95dfa8e81e2d1c0959abfb74e59683a9739ee78f2332085f52b342e1c2649db0202b9a721d52dc0ce3bfc6028e78de5182b60f5cc9a80ee7f01076f4816c3d5ffeb1874d58136d07b260f069c5e0b064df19884e50f9d55f5af31b65d57d26ae78d816408c51ad4c5cd16a83cd333594f3955799bb2e9ae6fe1faa1c927a9a7d68c544d285d177aecd253c67290b5b5c1858af9c9305b21fc1ce5d8b49b61f0bc22f746cd6a537a39970dce09c2ffaed8ce20d4b8105fd1c3bec1746e26057aec542b6e0f9aec5511fec1331aac9af4e9a70940b36963103e2e68bbf8bab41cf93434600c2b4749904c6109217e9167df1225c9e153e15e1b3f8a03b4c07b29b4e856c3258d73cd9978bef0a2fc48dc8c621ae6eca8ae14a42df29bd5b9f8b3aecf907bf2dd119f24b52a69486ea4a78286c219fa8706c488a3be95fc01fca255f76f266871f1c59d05290ccd6df0c72cce285549573cfaf0e94e58aef790c22f7d45ba34e570cf3177c396b190a9b3471b0acad51f7e9c97e6b51f44cb936b5121bce8d2689ab7a15aa1b7d06e22038f27963deaf110c31b5fb2bffdbd0f4d17441142862412cc09344142a8c5a04ec6c14edc9b22db758d23187d18518edd0a131bcb7c8f9008728912dc31ca149b2a01363dc9d9b9d842e8afc8158dff47a6f0be510bc6e84391de527e514a847aed0f6e96dabb954074a7693039c1bff2284d427538315c2df1397a69cb119463e9f1635a7c83599226e0527f20dcfadb087cfdb8da5d0f1d9114cd7d834ad5d210324ff6776dafe44853a9745436a24d2914a8b5afc9291aeebfa0ff7b918f71c2cbe58e1b4e14474bf21c2fc1439cc6bab3f95eafe7ef0b5872478ba1e7e59ccc4b13577844d288043c2914dbb368c8d9eb6c0583995736c7620fe39d65d72432578a8bab90839206a1ce2565d23e67c352527476f0f619fecb253c663bc273948c262b74b628855090a58c78f41d5452c49a57e133b8f87f3b04e1f4b0544efaf2a215b7fbb44e0bdc7bcfceff343c9ea2dcccbcf92708fb3a633d20e55c57b8fb2c58d63810d7fb3c148b4b79ba6e7cd167465e1dbd6473337cd890c01cb4d9f5d66ee7c99d38183f6df06fa0b6d0f8a413ffe189d06af350192e30fc8aa211641f0e0f9d2a76aec9906a19af17f27071194c75100e0f5edce561bafc3a55bcbb3452ad0b0c862d8e62c984879ce026817673d1dbc08bd82df6a691cc2cfb09f3c8ab8111322cbc366db71c3189ec557b4a978e1776442d10ed8433aa3e748858cc43901904f0f51a70d960b9a570406070df94a97b79721b9517f97ac09d9c5c57e27445e8fad05e96504d2695604955f8c65bc8fa8408cdb3e1014da7928cc9ba01649668c47f7c9b8889ba1402d384c659eea3e146f8c05c0887682a55b64ca3022c9cbeb208b4428578c84e181cb502650e5676f08b3d7f40704825c22c83cc4ecf660d437db95b2b6d9841cf5e182c0b5373aeaa47870379b2c1176e45710c4c074b7116d0ff12922fe2001",1]],"callback_url":"http://127.0.0.1:8080"} diff --git a/examples/CRISP/.enclave/generated/contracts/ImageID.sol b/examples/CRISP/.enclave/generated/contracts/ImageID.sol index a78154c076..0ef06801ce 100755 --- a/examples/CRISP/.enclave/generated/contracts/ImageID.sol +++ b/examples/CRISP/.enclave/generated/contracts/ImageID.sol @@ -19,5 +19,5 @@ pragma solidity ^0.8.20; library ImageID { - bytes32 public constant PROGRAM_ID = bytes32(0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2); + bytes32 public constant PROGRAM_ID = bytes32(0x0ad904cfaec1eeefa9b89a11020086ec51454423db1fee3b1ab614fff97368d6); } diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index e874f3b0cb..c32b48564d 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -27,11 +27,18 @@ program: # risc0: # risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev mode (fake proofs) # boundless: - # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY" + # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY_HERE" # private_key: "PRIVATE_KEY" # Use env vars for secrets - # pinata_jwt: "PINATA_JWT" # For uploading programs + # pinata_jwt: "PINATA_JWT" # For uploading programs to IPFS # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain + # Optional — custom auction parameters (defaults shown): + # min_price_eth: 0.001 + # max_price_eth: 0.03 + # timeout_secs: 1200 + # lock_timeout_secs: 600 + # ramp_up_secs: 120 + # lock_collateral_zkc: 5.0 nodes: cn1: address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index a3096d4581..ec705822d0 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -287,7 +287,7 @@ "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", "verifierAddress": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", "honkVerifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" + "imageId": "0x0ad904cfaec1eeefa9b89a11020086ec51454423db1fee3b1ab614fff97368d6" } }, "MockVotingToken": { From 580f484e8ebf5cd0f86bc48d57439c4e8023b65d Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 3 Jun 2026 15:29:44 +0500 Subject: [PATCH 2/4] fix: review fixes --- crates/support/app/src/main.rs | 5 +-- crates/support/contracts/ImageID.sol | 2 +- crates/support/host/src/lib.rs | 54 +++++++++++++++------------- crates/support/tests/Elf.sol | 4 +-- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/crates/support/app/src/main.rs b/crates/support/app/src/main.rs index 8de740fd68..74a068a7f6 100644 --- a/crates/support/app/src/main.rs +++ b/crates/support/app/src/main.rs @@ -33,10 +33,7 @@ async fn call_webhook(callback_url: &str, payload: &WebhookPayload) -> anyhow::R status_label, ciphertext_len, proof_len ); - let json_payload = serde_json::to_string_pretty(payload)?; - println!("Sending webhook payload:"); - println!("{}", json_payload); - println!("callback_url: {}", callback_url); + println!("Sending webhook to: {}", callback_url); let response = reqwest::Client::new() .post(callback_url) diff --git a/crates/support/contracts/ImageID.sol b/crates/support/contracts/ImageID.sol index f37983df9b..6dec581b62 100644 --- a/crates/support/contracts/ImageID.sol +++ b/crates/support/contracts/ImageID.sol @@ -19,5 +19,5 @@ pragma solidity ^0.8.20; library ImageID { - bytes32 public constant PROGRAM_ID = bytes32(0xc36e34f0b40876593adc519e4cccf8795ec90e9bfef0a44f20be865e7cb7f0a2); + bytes32 public constant PROGRAM_ID = bytes32(0xc36e34f0b40876593adc519e4cccf8795ec90e9bfef0a44f20be865e7cb7f0a2); } diff --git a/crates/support/host/src/lib.rs b/crates/support/host/src/lib.rs index 040c1a8816..2686ad1c44 100644 --- a/crates/support/host/src/lib.rs +++ b/crates/support/host/src/lib.rs @@ -103,41 +103,47 @@ fn env_opt_secs(key: &str) -> Option { } /// Build the OfferParams from environment variables, using sensible defaults. -/// -/// Pricing is tuned for FHE ciphertext summation (moderate cycle count): -/// - min_price starts low (0.00005 ETH ≈ $0.13) so early bids stay cheap -/// - max_price caps at 0.002 ETH ($5) to prevent runaway costs -/// - 10 min timeout gives provers time to discover the request -/// - 5 min lock_timeout is ample for FHE sum execution -/// - 1 min ramp_up starts the reverse Dutch auction quickly -/// - 2 ZKC collateral is low to attract provers without excessive lockup -fn build_offer() -> OfferParams { - let min_price = env_opt_f64("BOUNDLESS_MIN_PRICE_ETH") - .map(|v| parse_ether(&format!("{}", v)).unwrap()) - .unwrap_or_else(|| parse_ether("0.00005").unwrap()); - let max_price = env_opt_f64("BOUNDLESS_MAX_PRICE_ETH") - .map(|v| parse_ether(&format!("{}", v)).unwrap()) - .unwrap_or_else(|| parse_ether("0.002").unwrap()); +fn build_offer() -> Result { + let min_price = if let Some(v) = env_opt_f64("BOUNDLESS_MIN_PRICE_ETH") { + if v.is_sign_negative() || v.is_nan() { + anyhow::bail!("BOUNDLESS_MIN_PRICE_ETH must be a non-negative number, got: {}", v); + } + parse_ether(&format!("{}", v)).context("Invalid BOUNDLESS_MIN_PRICE_ETH")? + } else { + parse_ether("0.001").context("Invalid default min_price")? + }; + let max_price = if let Some(v) = env_opt_f64("BOUNDLESS_MAX_PRICE_ETH") { + if v.is_sign_negative() || v.is_nan() { + anyhow::bail!("BOUNDLESS_MAX_PRICE_ETH must be a non-negative number, got: {}", v); + } + parse_ether(&format!("{}", v)).context("Invalid BOUNDLESS_MAX_PRICE_ETH")? + } else { + parse_ether("0.03").context("Invalid default max_price")? + }; let timeout = env_opt_secs("BOUNDLESS_TIMEOUT_SECS") .map(|v| v as u32) - .unwrap_or(10 * 60); + .unwrap_or(20 * 60); let lock_timeout = env_opt_secs("BOUNDLESS_LOCK_TIMEOUT_SECS") .map(|v| v as u32) - .unwrap_or(5 * 60); + .unwrap_or(10 * 60); let ramp_up = env_opt_secs("BOUNDLESS_RAMP_UP_SECS") .map(|v| v as u32) - .unwrap_or(1 * 60); - let zkc = env_opt_f64("BOUNDLESS_LOCK_COLLATERAL_ZKC").unwrap_or(2.0); - let collateral: alloy_primitives::U256 = parse_units(&format!("{}", zkc), 18).unwrap().into(); + .unwrap_or(2 * 60); + let zkc = env_opt_f64("BOUNDLESS_LOCK_COLLATERAL_ZKC").unwrap_or(5.0); + if zkc.is_sign_negative() || zkc.is_nan() { + anyhow::bail!("BOUNDLESS_LOCK_COLLATERAL_ZKC must be a non-negative number, got: {}", zkc); + } + let collateral: alloy_primitives::U256 = + parse_units(&format!("{}", zkc), 18).context("Invalid BOUNDLESS_LOCK_COLLATERAL_ZKC")?.into(); - OfferParams::builder() + Ok(OfferParams::builder() .min_price(min_price) .max_price(max_price) .timeout(timeout) .lock_timeout(lock_timeout) .ramp_up_period(ramp_up) .lock_collateral(collateral) - .into() + .into()) } async fn boundless_prove(input: &ComputeInput) -> BoundlessOutput { @@ -207,7 +213,7 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result .with_program_url(parsed_url) .context("Failed to create new request")? .with_stdin(input_bytes) - .with_offer(build_offer()) + .with_offer(build_offer()?) } else { println!( "Warning: Uploading {}MB program at runtime", @@ -217,7 +223,7 @@ async fn boundless_prove_inner(input: &ComputeInput) -> Result .new_request() .with_program(PROGRAM_ELF) .with_stdin(input_bytes) - .with_offer(build_offer()) + .with_offer(build_offer()?) }; let onchain = diff --git a/crates/support/tests/Elf.sol b/crates/support/tests/Elf.sol index e2a1e945cd..4647a56f75 100644 --- a/crates/support/tests/Elf.sol +++ b/crates/support/tests/Elf.sol @@ -19,6 +19,6 @@ pragma solidity ^0.8.20; library Elf { - string public constant PROGRAM_PATH = - "/home/ace/main/gnosis/enclave/crates/support/target/riscv-guest/methods/guests/riscv32im-risc0-zkvm-elf/release/program.bin"; + string public constant PROGRAM_PATH = + "/home/ace/main/gnosis/enclave/crates/support/target/riscv-guest/methods/guests/riscv32im-risc0-zkvm-elf/release/program.bin"; } From d758f8557bf19e187b6812b5b122fc493f825474 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 3 Jun 2026 15:34:50 +0500 Subject: [PATCH 3/4] fix: optimize pricing --- crates/support/host/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/support/host/src/lib.rs b/crates/support/host/src/lib.rs index 2686ad1c44..e72ffdbef8 100644 --- a/crates/support/host/src/lib.rs +++ b/crates/support/host/src/lib.rs @@ -110,7 +110,7 @@ fn build_offer() -> Result { } parse_ether(&format!("{}", v)).context("Invalid BOUNDLESS_MIN_PRICE_ETH")? } else { - parse_ether("0.001").context("Invalid default min_price")? + parse_ether("0.00005").context("Invalid default min_price")? }; let max_price = if let Some(v) = env_opt_f64("BOUNDLESS_MAX_PRICE_ETH") { if v.is_sign_negative() || v.is_nan() { @@ -118,18 +118,18 @@ fn build_offer() -> Result { } parse_ether(&format!("{}", v)).context("Invalid BOUNDLESS_MAX_PRICE_ETH")? } else { - parse_ether("0.03").context("Invalid default max_price")? + parse_ether("0.002").context("Invalid default max_price")? }; let timeout = env_opt_secs("BOUNDLESS_TIMEOUT_SECS") .map(|v| v as u32) - .unwrap_or(20 * 60); + .unwrap_or(10 * 60); let lock_timeout = env_opt_secs("BOUNDLESS_LOCK_TIMEOUT_SECS") .map(|v| v as u32) - .unwrap_or(10 * 60); + .unwrap_or(5 * 60); let ramp_up = env_opt_secs("BOUNDLESS_RAMP_UP_SECS") .map(|v| v as u32) - .unwrap_or(2 * 60); - let zkc = env_opt_f64("BOUNDLESS_LOCK_COLLATERAL_ZKC").unwrap_or(5.0); + .unwrap_or(1 * 60); + let zkc = env_opt_f64("BOUNDLESS_LOCK_COLLATERAL_ZKC").unwrap_or(2.0); if zkc.is_sign_negative() || zkc.is_nan() { anyhow::bail!("BOUNDLESS_LOCK_COLLATERAL_ZKC must be a non-negative number, got: {}", zkc); } From c9c073219580741e17df287a61499c455c962c46 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 3 Jun 2026 18:46:20 +0500 Subject: [PATCH 4/4] fix: review comments --- crates/config/src/program_config.rs | 12 ++++----- crates/support/README.md | 16 ++++++------ crates/support/host/src/bin/profile_risc0.rs | 26 +++++++++++++++---- examples/CRISP/enclave.config.yaml | 24 ++++++++++------- templates/default/enclave.config.yaml | 27 +++++++++++++++----- tests/integration/enclave.config.yaml | 13 ++++++++++ 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/crates/config/src/program_config.rs b/crates/config/src/program_config.rs index 0762116a67..edb9128609 100644 --- a/crates/config/src/program_config.rs +++ b/crates/config/src/program_config.rs @@ -22,22 +22,22 @@ pub struct BoundlessConfig { #[serde(default = "default_true")] pub onchain: bool, // --- Offer params (all optional, fall back to defaults in build_offer_params) --- - /// Minimum price in ETH (default: 0.001) + /// Minimum price in ETH (default: 0.00005) #[serde(default)] pub min_price_eth: Option, - /// Maximum price in ETH (default: 0.03) + /// Maximum price in ETH (default: 0.002) #[serde(default)] pub max_price_eth: Option, - /// Total timeout in seconds (default: 1200 = 20 min) + /// Total timeout in seconds (default: 600 = 10 min) #[serde(default)] pub timeout_secs: Option, - /// Lock timeout in seconds (default: 600 = 10 min) + /// Lock timeout in seconds (default: 300 = 5 min) #[serde(default)] pub lock_timeout_secs: Option, - /// Ramp-up period in seconds (default: 120 = 2 min) + /// Ramp-up period in seconds (default: 60 = 1 min) #[serde(default)] pub ramp_up_secs: Option, - /// Lock collateral in ZKC (default: 5.0) + /// Lock collateral in ZKC (default: 2.0) #[serde(default)] pub lock_collateral_zkc: Option, } diff --git a/crates/support/README.md b/crates/support/README.md index 463f7290e7..e8cc03daef 100644 --- a/crates/support/README.md +++ b/crates/support/README.md @@ -212,14 +212,14 @@ rewards distributed. All parameters are configurable via environment variables (or `enclave.config.yaml`). Defaults: -| Parameter | Env Var | Default | Description | -| ------------ | ------------------------------- | ------- | ---------------------------- | -| Min price | `BOUNDLESS_MIN_PRICE_ETH` | `0.001` | Starting price in ETH | -| Max price | `BOUNDLESS_MAX_PRICE_ETH` | `0.03` | Maximum price in ETH | -| Timeout | `BOUNDLESS_TIMEOUT_SECS` | `1200` | Total request lifetime (sec) | -| Lock timeout | `BOUNDLESS_LOCK_TIMEOUT_SECS` | `600` | Prover lock duration (sec) | -| Ramp-up | `BOUNDLESS_RAMP_UP_SECS` | `120` | Price ramp-up period (sec) | -| Collateral | `BOUNDLESS_LOCK_COLLATERAL_ZKC` | `5.0` | ZKC locked per request | +| Parameter | Env Var | Default | Description | +| ------------ | ------------------------------- | --------- | ---------------------------- | +| Min price | `BOUNDLESS_MIN_PRICE_ETH` | `0.00005` | Starting price in ETH | +| Max price | `BOUNDLESS_MAX_PRICE_ETH` | `0.002` | Maximum price in ETH | +| Timeout | `BOUNDLESS_TIMEOUT_SECS` | `600` | Total request lifetime (sec) | +| Lock timeout | `BOUNDLESS_LOCK_TIMEOUT_SECS` | `300` | Prover lock duration (sec) | +| Ramp-up | `BOUNDLESS_RAMP_UP_SECS` | `60` | Price ramp-up period (sec) | +| Collateral | `BOUNDLESS_LOCK_COLLATERAL_ZKC` | `2.0` | ZKC locked per request | These can also be set in `enclave.config.yaml` under `program.risc0.boundless`: diff --git a/crates/support/host/src/bin/profile_risc0.rs b/crates/support/host/src/bin/profile_risc0.rs index 15379e866d..a16cb73273 100644 --- a/crates/support/host/src/bin/profile_risc0.rs +++ b/crates/support/host/src/bin/profile_risc0.rs @@ -5,8 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use e3_compute_provider::FHEInputs; -use e3_fhe_params::BfvPreset; -use e3_fhe_params::{build_bfv_params_from_set_arc, encode_bfv_params}; +use e3_fhe_params::{build_bfv_params_from_set_arc, encode_bfv_params, BfvPreset}; use e3_support_host::run_risc0_compute; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::{FheEncoder, FheEncrypter, Serialize}; @@ -15,9 +14,26 @@ use rand::thread_rng; fn main() { println!("Starting RISC0 profiling with mock ciphertexts..."); - // Use InsecureThresholdBfv512 parameter set - let param_set = BfvPreset::InsecureThresholdBfv512.into(); - let params = build_bfv_params_from_set_arc(param_set); + // BFV preset is configurable via env var, defaulting to insecure threshold + // for fast profiling. Set BFV_PRESET to one of: + // INSECURE_THRESHOLD_BFV_512 | INSECURE_DKG_BFV_512 | + // SECURE_THRESHOLD_BFV_8192 | SECURE_DKG_BFV_8192 + let param_set: BfvPreset = match std::env::var("BFV_PRESET").ok().as_deref() { + Some("INSECURE_DKG_512") => BfvPreset::InsecureDkg512, + Some("SECURE_THRESHOLD_BFV_8192") => BfvPreset::SecureThresholdBfv8192, + Some("SECURE_DKG_8192") => BfvPreset::SecureDkg8192, + Some(other) => { + eprintln!( + "Warning: unknown BFV_PRESET={}, using default InsecureThresholdBfv512", + other + ); + BfvPreset::InsecureThresholdBfv512 + } + None => BfvPreset::InsecureThresholdBfv512, + }; + println!("Using BFV preset: {:?}", param_set); + + let params = build_bfv_params_from_set_arc(param_set.into()); println!( "Generated BFV parameters: degree={}, plaintext_modulus={}", diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index c32b48564d..9ebf0b5214 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -27,18 +27,24 @@ program: # risc0: # risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev mode (fake proofs) # boundless: - # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY_HERE" + # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY" # private_key: "PRIVATE_KEY" # Use env vars for secrets - # pinata_jwt: "PINATA_JWT" # For uploading programs to IPFS + # pinata_jwt: "PINATA_JWT" # For uploading programs # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain - # Optional — custom auction parameters (defaults shown): - # min_price_eth: 0.001 - # max_price_eth: 0.03 - # timeout_secs: 1200 - # lock_timeout_secs: 600 - # ramp_up_secs: 120 - # lock_collateral_zkc: 5.0 + # Optional — custom auction parameters (defaults shown): + # min_price_eth: 0.00005 + # max_price_eth: 0.002 + # timeout_secs: 600 + # lock_timeout_secs: 300 + # ramp_up_secs: 60 + # lock_collateral_zkc: 2.0 + # Default profile: multithread uses (logical CPUs - 1) Rayon workers and the same concurrent job + # limit. Reserve threads with `multithread_reserve_threads` (default 1 for Actix / libp2p). + # Example override on a 16-core host: + # node: + # multithread_reserve_threads: 1 + # multithread_concurrent_jobs: 8 nodes: cn1: address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 69362b3cdd..c91a925a78 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -22,12 +22,27 @@ chains: deploy_block: 5 program: dev: true -# Default profile: multithread uses (logical CPUs - 1) Rayon workers and the same concurrent job -# limit. Reserve threads with `multithread_reserve_threads` (default 1 for Actix / libp2p). -# Example override on a 16-core host: -# node: -# multithread_reserve_threads: 1 -# multithread_concurrent_jobs: 8 + # risc0: + # risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev mode (fake proofs) + # boundless: + # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY" + # private_key: "PRIVATE_KEY" # Use env vars for secrets + # pinata_jwt: "PINATA_JWT" # For uploading programs + # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program + # onchain: true # true = onchain requests, false = offchain + # Optional — custom auction parameters (defaults shown): + # min_price_eth: 0.00005 + # max_price_eth: 0.002 + # timeout_secs: 600 + # lock_timeout_secs: 300 + # ramp_up_secs: 60 + # lock_collateral_zkc: 2.0 + # Default profile: multithread uses (logical CPUs - 1) Rayon workers and the same concurrent job + # limit. Reserve threads with `multithread_reserve_threads` (default 1 for Actix / libp2p). + # Example override on a 16-core host: + # node: + # multithread_reserve_threads: 1 + # multithread_concurrent_jobs: 8 nodes: cn1: address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" diff --git a/tests/integration/enclave.config.yaml b/tests/integration/enclave.config.yaml index bfc30da4f3..4a9b2e2fce 100644 --- a/tests/integration/enclave.config.yaml +++ b/tests/integration/enclave.config.yaml @@ -32,6 +32,19 @@ program: # pinata_jwt: "PINATA_JWT" # For uploading programs # program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program # onchain: true # true = onchain requests, false = offchain + # Optional — custom auction parameters (defaults shown): + # min_price_eth: 0.00005 + # max_price_eth: 0.002 + # timeout_secs: 600 + # lock_timeout_secs: 300 + # ramp_up_secs: 60 + # lock_collateral_zkc: 2.0 + # Default profile: multithread uses (logical CPUs - 1) Rayon workers and the same concurrent job + # limit. Reserve threads with `multithread_reserve_threads` (default 1 for Actix / libp2p). + # Example override on a 16-core host: + # node: + # multithread_reserve_threads: 1 + # multithread_concurrent_jobs: 8 nodes: cn1: