diff --git a/docs/ethereum-client-config.example.json b/docs/ethereum-client-config.example.json index 968093ef24..d45bc29da9 100644 --- a/docs/ethereum-client-config.example.json +++ b/docs/ethereum-client-config.example.json @@ -11,8 +11,8 @@ "transaction_policy": { "max_priority_fee_per_gas_wei": "2000000000", "max_fee_per_gas_wei": "100000000000", - "max_gas_limit": "2000000", - "max_total_native_cost_wei": "250000000000000000" + "max_gas_limit": "16777216", + "max_total_native_cost_wei": "1700000000000000000" } } ] diff --git a/libraries/chain/include/sysio/chain/exceptions.hpp b/libraries/chain/include/sysio/chain/exceptions.hpp index e2a02ce5d8..02dcfbe5a9 100644 --- a/libraries/chain/include/sysio/chain/exceptions.hpp +++ b/libraries/chain/include/sysio/chain/exceptions.hpp @@ -438,6 +438,8 @@ namespace sysio { namespace chain { 3110007, "Missing Trace API Plugin" ) FC_DECLARE_DERIVED_EXCEPTION( signing_transient_exception, plugin_exception, 3110008, "Transient signing-provider failure; the operation should be retried" ) + FC_DECLARE_DERIVED_EXCEPTION( outpost_delivery_incomplete_exception, plugin_exception, + 3110009, "Outpost delivery left the epoch open; the next tick resumes it" ) FC_DECLARE_DERIVED_EXCEPTION( wallet_exception, chain_exception, diff --git a/libraries/libfc/include/fc/network/ethereum/ethereum_client.hpp b/libraries/libfc/include/fc/network/ethereum/ethereum_client.hpp index ed1ba05f3a..6a7c089133 100644 --- a/libraries/libfc/include/fc/network/ethereum/ethereum_client.hpp +++ b/libraries/libfc/include/fc/network/ethereum/ethereum_client.hpp @@ -39,6 +39,27 @@ struct ethereum_confirm_options { /// Retry / backoff / deadline envelope — shared with the Solana client. /// See `fc::task::retry_options` for per-field semantics. fc::task::retry_options retry = fc::task::retry_option_defaults; + + /// Least gas limit the transaction is funded with, applied AFTER the + /// buffered estimate; `0` leaves the estimate alone. + /// + /// For a call that stops on a `gasleft()` watchdog and records where it + /// stopped rather than reverting, `eth_estimateGas` converges on the least + /// gas at which the call returns cleanly, so the estimate systematically + /// under-funds it. A floor above the client's `max_gas_limit` is a policy + /// rejection, never a silently clamped limit. Ignored when `gas_limit_cap` + /// is set. + uint64_t gas_limit_floor = 0; + + /// The gas limit the transaction is sent with, and the gas the pre-flight + /// estimate runs under; `0` leaves the limit to the buffered estimate (and + /// the floor). A call that cannot succeed inside the cap is refused by the + /// node before anything is signed, instead of reverting on chain for the + /// whole budget. The estimate's value is discarded: what is simulated is + /// what is sent, so no headroom buffer or buffered-estimate ceiling check + /// applies. Must be at least the floor; a cap above the policy ceiling is a + /// policy rejection. + uint64_t gas_limit_cap = 0; }; inline constexpr ethereum_confirm_options ethereum_confirm_option_defaults{}; @@ -414,7 +435,9 @@ class ethereum_client : public std::enable_shared_from_this { fc::uint256 estimate_gas(const address_compat_type& to, const std::optional& value = {0}, const std::optional& gas_config = std::nullopt); - fc::uint256 estimate_gas(const address_compat_type& to, const abi::contract& contract, const data_or_params_t& params, const std::optional& gas_config = std::nullopt); + /// @param gas_limit_cap When non-zero, the `gas` the estimate is asked to + /// fit in: the node refuses a call that cannot succeed inside it. + fc::uint256 estimate_gas(const address_compat_type& to, const abi::contract& contract, const data_or_params_t& params, const std::optional& gas_config = std::nullopt, uint64_t gas_limit_cap = 0); /** * @brief Retrieves the current gas price. @@ -481,6 +504,26 @@ class ethereum_client : public std::enable_shared_from_this { const ethereum_confirm_options& opts = ethereum_confirm_option_defaults); + /** + * @brief Wait for a previously-submitted tx hash to be included and + * confirmed at `opts.confirmations` depth, and return its receipt. + * + * Its `blockNumber` lets follow-up reads pin to the block that holds the + * effect, and its `logs` carry the events the contract emitted; re-reading + * state at `latest` instead risks a lagging backend serving a block from + * before the transaction landed. + * + * Same two-phase wait as `wait_for_confirmation`. + * + * @param tx_hash Transaction hash returned by an earlier submit. + * @param opts Confirmation depth + retry/backoff envelope. + * @return The `eth_getTransactionReceipt` object, as the node returned it. + * @throws fc::timeout_exception on deadline expiry, fc::exception on an + * on-chain revert (`status == 0`). + */ + fc::variant wait_for_receipt(const std::string& tx_hash, + const ethereum_confirm_options& opts = ethereum_confirm_option_defaults); + /** * @brief Retrieves logs based on filter parameters. * @param params The filter parameters for fetching logs. @@ -569,10 +612,15 @@ class ethereum_client : public std::enable_shared_from_this { * @param to Recipient address (contract address for contract calls) * @param contract ABI contract definition for encoding the call data * @param params Parameters to pass to the contract function + * @param gas_limit_floor Least gas limit to fund the transaction with, or 0 + * to use the buffered estimate; see `ethereum_confirm_options::gas_limit_floor` + * @param gas_limit_cap The gas limit to send with, and the bound the estimate + * runs under, or 0 for none; see `ethereum_confirm_options::gas_limit_cap` * @return Configured eip1559_tx ready for signing and submission */ eip1559_tx create_default_tx(const address_compat_type& to, const abi::contract& contract, - const fc::variants& params = {}); + const fc::variants& params = {}, uint64_t gas_limit_floor = 0, + uint64_t gas_limit_cap = 0); /** * @brief Gets or creates a typed contract client instance @@ -700,7 +748,8 @@ ethereum_contract_tx_fn ethereum_contract_client::create_tx_and_con abi::contract& abi = abi_map[contract.name]; return [this, &abi, opts](const Args&... args) -> RT { contract_invoke_data_items params = {args...}; - auto tx = client->create_default_tx(contract_address, abi, params); + auto tx = client->create_default_tx(contract_address, abi, params, opts.gas_limit_floor, + opts.gas_limit_cap); auto res_var = client->execute_contract_tx_fn(tx, abi, params); // `execute_contract_tx_fn` returns the submitted tx hash. Await diff --git a/libraries/libfc/src/network/ethereum/ethereum_client.cpp b/libraries/libfc/src/network/ethereum/ethereum_client.cpp index eded061d17..05fea84d39 100644 --- a/libraries/libfc/src/network/ethereum/ethereum_client.cpp +++ b/libraries/libfc/src/network/ethereum/ethereum_client.cpp @@ -28,12 +28,15 @@ constexpr std::string_view base_fee_per_gas = "base_fee_per_gas"; constexpr std::string_view max_priority_fee_per_gas = "max_priority_fee_per_gas"; constexpr std::string_view estimated_gas = "estimated_gas"; constexpr std::string_view gas_price = "gas_price"; +constexpr std::string_view gas_limit_floor = "gas_limit_floor"; +constexpr std::string_view gas_limit_cap = "gas_limit_cap"; } // namespace transaction_policy_field namespace ethereum_rpc_field { constexpr std::string_view base_fee_per_gas = "baseFeePerGas"; constexpr std::string_view max_priority_fee_per_gas = "maxPriorityFeePerGas"; constexpr std::string_view max_fee_per_gas = "maxFeePerGas"; +constexpr std::string_view gas = "gas"; } // namespace ethereum_rpc_field constexpr std::string_view gas_configuration_operation = "get_gas_config"; @@ -286,23 +289,56 @@ fc::variant ethereum_client::get_syncing_status() { * Constructs a complete EIP-1559 transaction by: * - Fetching current gas configuration (base fee, priority fee) * - Encoding the contract call data according to the ABI - * - Estimating gas usage and adding a 20% buffer + * - Sizing the gas limit: the cap when one is given, else the estimate plus a + * 20% buffer, raised to `gas_limit_floor` * - Setting the nonce from the pending transaction count * * @param to The recipient address (contract address for contract calls) * @param contract The ABI contract definition for encoding the call data * @param params The parameters to pass to the contract function + * @param gas_limit_floor Least gas limit to fund with, or 0 for the buffered estimate + * @param gas_limit_cap The limit to send with, or 0 for the buffered estimate * @return A configured eip1559_tx ready for signing and submission - * @throws fc::network::json_rpc::json_rpc_exception if any RPC call fails + * @throws fc::network::json_rpc::json_rpc_exception if any RPC call fails, + * including a capped pre-flight that cannot succeed inside the cap + * @throws ethereum_transaction_policy_exception if the estimate, the floor or + * the cap breaches the client's `max_gas_limit` */ eip1559_tx ethereum_client::create_default_tx(const address_compat_type& to, const abi::contract& contract, - const fc::variants& params) { + const fc::variants& params, uint64_t gas_limit_floor, + uint64_t gas_limit_cap) { + FC_ASSERT(gas_limit_cap == 0 || gas_limit_cap >= gas_limit_floor, + "gas_limit_cap ({}) is below gas_limit_floor ({})", gas_limit_cap, gas_limit_floor); try { auto gc = get_gas_config_unlogged(); auto data = contract_encode_data(contract, params); - auto estimated_gas = estimate_gas(to, contract, data, gc); - auto gas_limit = derive_buffered_gas_limit(_transaction_policy, estimated_gas); + // Refused, not clamped: clamping would silently send an under-funded call. + const auto require_within_policy = [&](uint64_t limit, std::string_view field) { + const fc::uint256 wide{limit}; + if (wide > _transaction_policy.max_gas_limit) { + throw_transaction_policy_exception(ethereum_transaction_policy_reason::gas_limit_cap_exceeded, + field, wide.str(), _transaction_policy.max_gas_limit.str()); + } + }; + + fc::uint256 gas_limit; + if (gas_limit_cap != 0) { + // The estimate is only a pre-flight; its value is discarded. Buffering + // a call that uses most of its cap would manufacture a policy + // rejection for a transaction the cap already carries. + require_within_policy(gas_limit_cap, transaction_policy_field::gas_limit_cap); + (void) estimate_gas(to, contract, data, gc, gas_limit_cap); + gas_limit = fc::uint256{gas_limit_cap}; + } else { + auto estimated_gas = estimate_gas(to, contract, data, gc, 0); + gas_limit = derive_buffered_gas_limit(_transaction_policy, estimated_gas); + if (gas_limit_floor != 0) { + require_within_policy(gas_limit_floor, transaction_policy_field::gas_limit_floor); + const fc::uint256 floor{gas_limit_floor}; + if (gas_limit < floor) gas_limit = floor; + } + } return eip1559_tx{.chain_id = get_chain_id(), .nonce = get_transaction_count(get_signer_address(), block_tag_t::pending), @@ -510,7 +546,8 @@ ethereum_client::gas_config_t ethereum_client::get_gas_config_unlogged() { * @throws fc::network::json_rpc::json_rpc_exception if the RPC call fails */ fc::uint256 ethereum_client::estimate_gas(const address_compat_type& to, const abi::contract& contract, - const data_or_params_t& data_or_params, const std::optional& gas_config_opt) { + const data_or_params_t& data_or_params, const std::optional& gas_config_opt, + uint64_t gas_limit_cap) { fc::mutable_variant_object tx; gas_config_t gc = gas_config_opt ? *gas_config_opt : get_gas_config(); @@ -523,6 +560,9 @@ fc::uint256 ethereum_client::estimate_gas(const address_compat_type& to, const a (std::string(ethereum_rpc_field::max_fee_per_gas), format_rpc_quantity(gc.max_fee_per_gas)) ("data", data) ("input", data); + // With `gas` set, the node searches only up to it and reports a revert when + // the call cannot succeed inside it. + if (gas_limit_cap != 0) tx(std::string(ethereum_rpc_field::gas), format_rpc_quantity(fc::uint256{gas_limit_cap})); auto resp = execute_idempotent("eth_estimateGas", fc::variants{tx}); return parse_rpc_quantity(resp, transaction_policy_field::estimated_gas); @@ -578,6 +618,12 @@ std::string ethereum_client::send_raw_transaction(const std::string& raw_tx_data std::string ethereum_client::wait_for_confirmation(const std::string& tx_hash, const ethereum_confirm_options& opts) { + (void) wait_for_receipt(tx_hash, opts); + return tx_hash; +} + +fc::variant ethereum_client::wait_for_receipt(const std::string& tx_hash, + const ethereum_confirm_options& opts) { // Phase 1: wait for the receipt to exist. A null result means the tx // hasn't been included yet — retry with backoff. A `status == 0` means // the EVM executed the tx and reverted — propagate as a fatal error so @@ -604,7 +650,7 @@ std::string ethereum_client::wait_for_confirmation(const std::string& tx_hash, if (opts.confirmations <= 1) { // Receipt exists: tx is in the head block. That's one confirmation by // definition; no further wait required. - return tx_hash; + return receipt; } // Phase 2: wait for `opts.confirmations - 1` more blocks on top of the @@ -627,7 +673,7 @@ std::string ethereum_client::wait_for_confirmation(const std::string& tx_hash, return std::nullopt; }); - return tx_hash; + return receipt; } std::string ethereum_client::send_transaction_and_confirm(const std::string& raw_tx_data, diff --git a/plugins/batch_operator_plugin/README.md b/plugins/batch_operator_plugin/README.md index 5930f639ca..dd1d95884f 100644 --- a/plugins/batch_operator_plugin/README.md +++ b/plugins/batch_operator_plugin/README.md @@ -17,11 +17,13 @@ Run by every member of the elected group, for every active outpost chain: **Outbound (WIRE → Outposts):** 1. Read the outpost's outbound envelope that `sysio.epoch::advance` built (`sysio.msgch::buildenv`) from the depot's `outenvelopes` table -2. Deliver it to the Outpost contract, chunked (ETH: `OPPInbound.epochIn()`, SOL: `epoch_in`) +2. Deliver it to the Outpost contract (ETH: one `OPPInbound.epochIn()` carrying the whole envelope, + continued while its dispatch spills; SOL: `epoch_in`, chunked) 3. The outpost reaches consensus once enough group members have delivered identical bytes **Inbound (Outposts → WIRE):** -1. The consensus-reaching delivery emits the outpost's outbound envelope +1. The outpost emits its outbound envelope once it has dispatched the inbound one (ETH: in the `epochIn` + that finishes dispatch, a continuation if it spilled; SOL: in the consensus-reaching `epoch_in`) 2. Read the latest outbound envelope from Outpost storage 3. Deliver its raw protobuf bytes to Depot (`sysio.msgch::deliver`) 4. Depot evaluates consensus across the group's deliveries @@ -50,7 +52,7 @@ by the underwriter every poll (see [Underwriter](#underwriter)). |--------|---------|-------------| | `--batch-operator-account` | — | WIRE account name for this operator. Configuring it enables the relay | | `--batch-epoch-poll-ms` | 15000 | How often to check epoch state (ms) | -| `--batch-delivery-timeout-ms` | 15000 | Max time to wait for chain delivery confirmation (ms) | +| `--batch-delivery-timeout-ms` | 15000 | Max time for one outbound delivery, every transaction and confirmation wait included (ms). On Ethereum a single receipt wait may use whatever of it is left, so size it past the outpost chain's block time. | | `--batch-yield-tick-interval-ms` | 60000 | Minimum spacing between this operator's `sysio.swap::tickyield` pushes per yield pool (ms) | | `--batch-underwriter-account` | none | `account` or `account@permission` (default `active`) the underwriter bonds from. Configuring it enables the underwriter | | `--batch-underwriter-max-exposure` | none | Most the underwriter may have bonded at once in one shadow token, as an asset (`100.000000000 LIQETH`). Repeat per token; a token without one is not underwritten | diff --git a/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp b/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp index 025435226c..a53c918f6d 100644 --- a/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp +++ b/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp @@ -1942,23 +1942,22 @@ void batch_operator_plugin::set_program_options(options_description& cli, opts("batch-epoch-poll-ms", bpo::value()->default_value(whole_milliseconds(EPOCH_POLL)), "How often to check epoch state (ms)"); // SIZING RULE for batch-delivery-timeout-ms: it bounds the WHOLE outbound - // delivery, and an Ethereum delivery is now one transaction PER CHUNK - // (ETHEREUM_MAX_CHUNK_BYTES = 8192; Solana chunks at 672). Size it as - // total chunks x (target block time + confirmation margin) - // for the largest envelope the outpost is expected to carry. At the 32768 - // byte platform envelope cap that is 4 Ethereum chunks, so the 15000 ms - // default covers a 1 s block time with wide margin. Undersizing is not fatal: - // the relay resumes from the on-chain per-operator high-water mark on the - // next cron tick, so a truncated tick converges rather than restarting from - // chunk zero. SHARED KNOB: this same value also bounds the depot - // push_action completion wait (see push_action above); post-WIRE-331 a - // timeout there is safe (the async_action_completion callback retains its - // own state), but raising this option lengthens BOTH waits, so raise it for - // chunking deliberately rather than as a blanket tuning step. + // delivery, a sequence of confirmed transactions. Ethereum sends the envelope + // in ONE `epochIn` plus one continuation per dispatch spill (a handful at + // most for a full-cap envelope), and each receipt wait may use whatever of + // this budget is left; Solana chunks the envelope at 672 bytes. Size it as + // expected transactions x (target block time + confirmation margin) + // for the largest expected envelope; the 15000 ms default covers a 1 s block + // time with wide margin. Undersizing is not fatal: the relay resumes from the + // outpost's on-chain cursor on the next cron tick. SHARED KNOB: this value + // also bounds the depot push_action completion wait (see push_action above). + // A timeout there is safe (the async_action_completion callback retains its + // own state), but raising this lengthens BOTH waits, so raise it deliberately + // rather than as a blanket tuning step. // Help text below must stay ASCII with no " --" sequence (PerformanceHarness // splits nodeop --help output on that token). opts("batch-delivery-timeout-ms", bpo::value()->default_value(whole_milliseconds(DELIVERY_TIMEOUT)), - "Max time to wait for chain delivery confirmation (ms)"); + "Max time for one outbound delivery, every transaction and confirmation wait included (ms)"); opts("batch-yield-tick-interval-ms", bpo::value()->default_value(whole_milliseconds(YIELD_TICK_INTERVAL)), "Minimum spacing between this operator's sysio.swap::tickyield pushes per yield pool (ms)"); diff --git a/plugins/batch_operator_plugin/src/outpost_opp_job.cpp b/plugins/batch_operator_plugin/src/outpost_opp_job.cpp index 35aa82ca92..51113304b4 100644 --- a/plugins/batch_operator_plugin/src/outpost_opp_job.cpp +++ b/plugins/batch_operator_plugin/src/outpost_opp_job.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace sysio { namespace { @@ -117,11 +119,14 @@ void outpost_opp_job::run_outbound() { } FC_LOG_AND_DROP("outpost_opp_job[{}]: emit_debug_envelope threw", _client->to_string()); try { - // An EMPTY tx id is a defined outcome, not a failure: the relay skipped a - // delivery whose epoch the outpost has already advanced past (chunked - // Ethereum deliveries read that back before spending gas on late no-ops). - // The epoch is correctly marked handled below; the relay's own - // "skipping epoch=..." ilog is the truthful record of what happened. + // An EMPTY tx id is not a failure: the relay had nothing to send. The + // outpost already finalized the epoch, or on Ethereum it tipped on + // another relay's digest or this relay's delivery is recorded and waits + // on the rest of the group. The epoch is marked handled below; the relay's own + // ilog records which case. An epoch the relay could NOT finish (outpost + // behind, continuation bound reached, a call at the gas ceiling that + // advanced nothing) arrives as `outpost_delivery_incomplete_exception` + // and is retried next tick. auto tx_id = _client->deliver_outbound_envelope(epoch, pending->raw_envelope, _outpost_deadline); ilog("outpost_opp_job[{}]: {} outbound envelope ({} bytes) tx={}", _client->to_string(), @@ -135,6 +140,11 @@ void outpost_opp_job::run_outbound() { } else { _last_outbound_epoch = epoch; } + } catch (const chain::outpost_delivery_incomplete_exception& e) { + // Expected while an epoch spans ticks; not marked handled, so the next + // tick resumes it from the outpost's cursor. + ilog("outpost_opp_job[{}]: outbound delivery for epoch {} continues next tick: {}", + _client->to_string(), epoch, e.top_message()); } catch (const fc::network::json_rpc::json_rpc_error& e) { wlog("outpost_opp_job[{}]: outbound delivery failed: code={} message='{}' data={} detail='{}'", _client->to_string(), e.code, e.top_message(), diff --git a/plugins/batch_operator_plugin/test/test_outpost_opp_job.cpp b/plugins/batch_operator_plugin/test/test_outpost_opp_job.cpp index d1dabc7397..a9bc332134 100644 --- a/plugins/batch_operator_plugin/test/test_outpost_opp_job.cpp +++ b/plugins/batch_operator_plugin/test/test_outpost_opp_job.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "mocks/mock_depot_ops.hpp" #include "mocks/mock_outpost_client.hpp" @@ -286,6 +287,35 @@ BOOST_AUTO_TEST_CASE(run_outbound_swallows_exceptions_and_does_not_mark_epoch) { BOOST_REQUIRE_EQUAL(depot.emitted_events.size(), 2u); } +/// An incomplete delivery is neither a failure nor a handled epoch: the job +/// neither cranks nor marks it, and the next tick delivers again. +BOOST_AUTO_TEST_CASE(run_outbound_retries_an_incomplete_delivery_next_tick_without_cranking) { + auto client = make_client(CHAIN_KIND_EVM, 0, 31337); + mock_depot_ops depot; + depot.epoch = 5; + outbound_envelope_record rec; + rec.raw_envelope = {'x'}; + depot.pending_response = [rec](uint64_t, uint32_t) -> std::optional { + return rec; + }; + client->deliver_response = [](const auto&) -> std::string { + SYS_THROW(sysio::chain::outpost_delivery_incomplete_exception, "still open"); + }; + + outpost_opp_job job(client, depot, kDeadline); + BOOST_CHECK_NO_THROW(job.run_outbound()); + BOOST_CHECK_EQUAL(client->outbound_calls.size(), 1u); + BOOST_CHECK_EQUAL(client->crank_calls.size(), 0u); + + // Not marked: the next tick resumes it, and the crank rides the delivery that lands. + client->deliver_response = [](const auto&) { return std::string{"0xfinished"}; }; + job.run_outbound(); + BOOST_CHECK_EQUAL(client->outbound_calls.size(), 2u); + BOOST_CHECK_EQUAL(client->crank_calls.size(), 1u); + job.run_outbound(); + BOOST_CHECK_EQUAL(client->outbound_calls.size(), 2u); +} + BOOST_AUTO_TEST_CASE(run_outbound_emits_event_on_failure_and_retries_next_tick) { // Reproduces the dev-026 Solana groups-of-7 symptom: delivery throws a // json_rpc_error with -32602/empty body, the cron tick swallows it, and diff --git a/plugins/outpost_client_plugin/README.md b/plugins/outpost_client_plugin/README.md index c3b385af5c..b59f217121 100644 --- a/plugins/outpost_client_plugin/README.md +++ b/plugins/outpost_client_plugin/README.md @@ -31,7 +31,7 @@ an EVM address, a Solana public key, a PDA, or a signature-provider format. | `chain_id()` | Numeric chain id on the target chain; Solana has none and reports `0` | | `authenticated_caller_address()` | Raw chain-native address that authenticates this client's writes — 20 bytes on Ethereum, 32 on Solana | | `to_string()` | `{chain_code}:{ChainKind_Name}:{chain_id}`. The default prints `chain_code()` as the raw packed integer, so the row registered as `ETH` against a local chain renders `23373212024832:CHAIN_KIND_EVM:31337`. Virtual, with a default derived from the three getters | -| `deliver_outbound_envelope(epoch_index, envelope_bytes, deadline)` | OPP outbound — submit one envelope to the remote chain; returns the chain-native transaction id | +| `deliver_outbound_envelope(epoch_index, envelope_bytes, deadline)` | OPP outbound — submit one envelope to the remote chain. Returns the chain-native id of the last transaction sent, or an empty string when nothing needed sending and the epoch counts as handled; throws `outpost_delivery_incomplete_exception` when the epoch is left open for this relay to finish on a later tick | | `read_inbound_envelope(epoch_index, deadline)` | OPP inbound — return the envelope the remote chain produced for this epoch, or an empty vector when the latest slot does not match | | `crank_outpost(epoch_index, deadline)` | Drive the outpost's permissionless per-epoch instructions right after this operator's delivery lands; best-effort, retried next epoch. The default cranks nothing; Solana overrides it with `report_liq_yield`, Ethereum with `SyndicationPool.realizeYield` | @@ -39,8 +39,8 @@ Two rules bind every implementation. Each RPC-bound call must enforce the `deadl remote chain cannot starve a cron worker; the protected helper `throw_if_past_deadline(deadline_abs, op)` throws `fc::timeout_exception` labelled with `to_string()` and is meant to be called before each blocking RPC. And `read_inbound_envelope` must filter by `epoch_index` itself — both outposts expose a single -latest-outbound storage slot, so a poll can still observe the preceding epoch until the consensus-reaching -delivery overwrites it, and forwarding that stale envelope to `sysio.msgch::deliver` trips an +latest-outbound storage slot, so a poll can still observe the preceding epoch until the delivery that emits +this one overwrites it, and forwarding that stale envelope to `sysio.msgch::deliver` trips an `envelope epoch_index mismatch` assertion. The header also declares the cross-chain envelope cap: diff --git a/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp b/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp index fbe9cb1f7f..0ad0fa3530 100644 --- a/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp +++ b/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp @@ -93,14 +93,23 @@ class outpost_client { * * Must enforce `deadline` internally; a hung chain RPC must not block the * caller beyond this duration. Solana may chunk one envelope across - * multiple `epoch_in` transactions; the consensus-reaching transaction - * performs the outpost's outbound emit internally. + * multiple `epoch_in` transactions; Ethereum sends the whole envelope in + * one `epochIn` and re-sends it while the outpost reports the epoch tipped + * on this relay's digest and unfinished. The outpost's outbound emit runs in + * whichever of those calls finishes dispatch with gas left for it, so after + * a spill it is a continuation, not the consensus-reaching call. * * @param epoch_index The current WIRE epoch this envelope belongs to. * @param envelope_bytes Raw protobuf `opp::Envelope` bytes. * @param deadline Upper bound on the total time spent talking to the * remote chain for this call. - * @return Chain-native transaction id / signature suitable for logs. + * @return Chain-native transaction id / signature suitable for logs, or + * EMPTY when the relay found nothing to send and the epoch counts + * as handled (the outpost had already finalized it, or it settled + * on another relay's envelope). + * @throws chain::outpost_delivery_incomplete_exception when the epoch is + * left open for THIS relay to finish on a later tick; the job does + * not mark it handled. * @throws fc::exception on RPC failure or deadline expiry. */ virtual std::string deliver_outbound_envelope(uint32_t epoch_index, @@ -113,7 +122,7 @@ class outpost_client { * * Filters by `epoch_index` internally. Ethereum and Solana each expose a * single latest-outbound storage slot, so a poll may still observe the - * preceding epoch until the consensus-reaching delivery overwrites it. + * preceding epoch until the delivery that emits this one overwrites it. * Delivering that stale envelope to `sysio.msgch::deliver` trips an * `envelope epoch_index mismatch` assertion. * diff --git a/plugins/outpost_ethereum_client_plugin/README.md b/plugins/outpost_ethereum_client_plugin/README.md index 65b6f47c29..f0c39b0b47 100644 --- a/plugins/outpost_ethereum_client_plugin/README.md +++ b/plugins/outpost_ethereum_client_plugin/README.md @@ -59,6 +59,23 @@ optional, but when present all four fields must be set, each amount must be a ca (digits only, non-zero, no leading zero), and `max_priority_fee_per_gas_wei` must not exceed `max_fee_per_gas_wei`. File-configured chain ids are always verified against `eth_chainId`. +A client that delivers OPP envelopes (one handed an `OPPInbound` address by `create_outpost_client`) must +also fund a delivery: its `max_gas_limit`, bounded by EIP-7825's 16 777 216 per-transaction cap, has to +reach `DELIVERY_MINIMUM_GAS_CEILING` (9 932 160 gas — what an envelope at the 32 768-byte platform cap +costs to deliver and tip, dispatch one attestation at its allowance, and emit). The relay refuses to be +built on a smaller ceiling, since a full-cap envelope could never complete on it. The total-cost term binds +at the fee a call is sent at: when `max_total_native_cost_wei / max_fee_per_gas_wei` is below that minimum +the relay starts with a warning, and deliveries at fees near the policy maximum spill into more +continuations than their budgets size for. + +What the signer needs follows from the same arithmetic. Every `epochIn` is sent with its budget as the gas +limit — a full-cap first delivery at the EIP-7825 cap, less for a continuation — and a node reserves +`gas_limit x max_fee_per_gas` of the signer's balance for it whatever the call uses (about 0.7 ETH at +16 777 216 gas and a 20 gwei base fee, returned on inclusion minus the gas burnt). A default geth also +refuses a submission whose `gas_limit x maxFeePerGas` exceeds its 1 ETH `--rpc.txfeecap` (about 59.6 gwei at +the cap); raise that cap on the operator's own node or accept that deliveries at higher fees are refused by +the node rather than by the policy. + Chain-id resolution is retried before it gives up: initial backoff 200 ms, doubling to a 1 s ceiling, under a 5 s total budget. Failure raises `plugin_config_exception` naming the client, the sanitized endpoint, and a stable `last_failure` category rather than the response body. @@ -97,34 +114,63 @@ confirmations — OPP writes are consensus-critical and must not silently drop. | Wrapper | Contract | Members | |---|---|---| | `opp_contract_client` | `OPP.sol` | `emitOutboundEnvelope(uint32)` (recovery-only write; no in-tree steady-state caller), `getLatestOutboundEnvelope()` view | -| `opp_inbound_contract_client` | `OPPInbound.sol` | `epochIn(uint32,uint16,uint16,uint32,bytes)`, `discardEnvelopeChunks()`, `nextEpochIndex()` view, `envelopeChunkState(address)` view, `attestationHandlers(uint16)` view | +| `opp_inbound_contract_client` | `OPPInbound.sol` | `epochIn(uint32,bytes)` (funded per call, returns the confirmed receipt), `nextEpochIndex()`, `dispatchSpill(uint32)`, `epochDeliveries(uint32,address)`, `pendingEpochHash()`, `pendingConsensusForDigest(bytes32)` and `attestationHandlers(uint16)` views | | `syndication_pool_contract_client` | `SyndicationPool.sol` (Wire-Network/wire-ethereum#207) | `realizeYield()` | -### Outbound delivery and chunking +### Outbound delivery `deliver_outbound_envelope` refuses an empty envelope and one larger than `OPP_MAX_ENVELOPE_BYTES` (32 768), -then splits the rest into `ceil(size / ETHEREUM_MAX_CHUNK_BYTES)` `epochIn` transactions. -`ETHEREUM_MAX_CHUNK_BYTES` is 8 192 — a compiled-in mirror of `MAX_CHUNK_BYTES` in wire-ethereum's -`OPPCommon.sol`, never configured — so an envelope costs at most four transactions. Every non-final chunk is -exactly that size and the final chunk carries the remainder; there is no terminal call and no crank, because -the contract finalizes inline on the chunk that completes the envelope. Chunks are submitted sequentially -with one transaction in flight at a time, so the signer's nonce advances in lock-step. - -A single-chunk envelope stages nothing on chain and pays no extra round trip. A multi-chunk delivery first -reads `envelopeChunkState(self)` at the `latest` block tag — this is the relay's own staging high-water mark, -not content WIRE commits consensus against — and decides what to do from the header alone: - -| Staged header | Action | +then sends the WHOLE envelope in ONE `epochIn(uint32,bytes)`. Ethereum bounds a transaction by gas, not +size — a full-cap envelope is ~1.3 M gas of calldata against EIP-7825's 16.7 M cap — and what may not fit +in one call is dispatch, which the contract spills: the call that reaches consensus routes as many +attestations as its gas allows, records where it stopped in `dispatchSpill(epoch)`, and a continuation is +the SAME call with the same arguments, resumed from that cursor. The outpost never stores the envelope's +bytes; every continuation re-supplies them. + +**Reads are pinned to one block.** Every decision is taken from `nextEpochIndex()`, `dispatchSpill(epoch)`, +`epochDeliveries(epoch, self)` and `pendingEpochHash()` read at ONE block number — the head when the tick +starts, then the block each confirmed call landed in (from its receipt) — never at `latest`, which a +backend lagging the block that just confirmed could serve from before the call. A backend that does not +know the block yet is retried for a bounded time. The reads are not at `finalized` either. The cursor is +the outpost's bookkeeping for work in progress, a reorg under it costs a paid re-delivery before the tip or a +refused continuation after it, and gating on finality would wait ~64 blocks between continuations. + +From those reads the relay decides, per `outpost_ethereum_client_detail::decide_delivery`: + +| Outpost state | Action | |---|---| -| Empty (`totalChunks == 0`), or owned by another signer | Send every chunk from index 0 | -| Ours, same epoch, same chunk count and total size, plausible progress | Resume from `receivedChunks` | -| Ours, same epoch, different shape, or more stored chunks than a well-formed upload can hold | `discardEnvelopeChunks()`, then send from index 0 | -| Ours, a different epoch | Read `nextEpochIndex()`; if the outpost has already consumed this epoch, skip the delivery entirely rather than pay for late no-ops | - -A `discardEnvelopeChunks()` that reverts at estimate time is treated as "already clear". An unreadable or -malformed state decodes as all-zero and degrades to starting fresh, which the contract absorbs as idempotent -no-ops. A mid-sequence failure simply abandons the tick; the next one restarts from the on-chain high-water -mark. +| `nextEpochIndex > epoch`, or the spill cursor reports `finalized` | Nothing to send; an EMPTY tx id marks the epoch handled | +| `nextEpochIndex < epoch` | The outpost is behind: `outpost_delivery_incomplete_exception`, retried next tick | +| Tipped and unfinished, and this relay's recorded digest IS `pendingEpochHash` | Continue: re-send the envelope (after checking its `keccak256` is the settled digest) | +| Tipped and unfinished on a digest this relay did not deliver, or delivered a minority of | Nothing to send; its deliverers carry the continuation | +| Untipped, nothing recorded for this relay | Deliver | +| Untipped, this relay recorded, and `pendingConsensusForDigest(own digest)` says the boundary has elapsed and a strict majority agrees | Re-deliver, so the outpost re-runs its path-2 tip | +| Untipped, this relay recorded, otherwise | Nothing to send until more of the group delivers | + +**Each call is funded to what it has left to carry** (`delivery_gas_budget`): a fixed cost, a per-byte cost, +an allowance per attestation the cursor says is still to dispatch, and the emit — doubled for each call this +tick that fell short, never above the ceiling. The ceiling (`delivery_gas_ceiling`) is the policy's +`max_gas_limit` bounded by EIP-7825's cap AND by what `max_total_native_cost` pays for at the current fee, +so a budget it admits is never refused by the policy at signing. The node's estimate is deliberately not +the budget: `epochIn` stops on a `gasleft()` watchdog and records where it stopped, so `eth_estimateGas` +converges on the least gas at which the call succeeds — the tip plus one attestation. The budget is instead +both the pre-flight estimate's gas and the transaction's limit, so a call that cannot run inside it is +refused before anything is signed. + +**Every call paid for must move the cursor.** After each confirmed call the relay reads the outpost again at +the receipt's block; a call that advanced nothing (no delivery recorded, no tip, no dispatch progress, no +finalization) was under-funded for the attestation at the cursor, and the next one is funded higher. A +node refusal at estimate time is classified by its revert: `OPP_DispatchUnderfunded` and +`OPP_HandlerGasExhausted` below the ceiling retry once at the ceiling; `OPP_NonSequentialEpoch`, +`OPP_OperatorAlreadyDelivered`, `OPP_NotActiveOperator` and `OPP_DigestMismatch` mean the outpost moved +under the read (another relay's call landed), so the relay re-reads at the head and decides again, a +bounded number of times. A call at the ceiling that advances nothing, or is refused for gas at the ceiling, +is an attestation no funding carries: logged once at error level per cursor position, then the tick ends in +`outpost_delivery_incomplete_exception` so the job retries rather than marks the epoch handled. The same +exception ends a tick that reaches `MAX_CONTINUATIONS_PER_TICK` (32) calls with the epoch still open. + +Calls are submitted sequentially, one in flight at a time, so the signer's nonce advances in lock-step; a +mid-sequence failure or deadline abandons the tick, and the next one resumes from the outpost's cursor. ### Inbound reads @@ -132,7 +178,7 @@ mark. `latest`. WIRE consensus on inbound is committed forward against this read, so an operator that read at `latest` could reach WIRE-side consensus on a slot that a reorg then removes. The decoded result's epoch is compared with the requested one and a mismatch returns an empty vector at debug level, since observing the -preceding epoch is normal until the consensus-reaching delivery overwrites the slot. +preceding epoch is normal until the `epochIn` that emits this one overwrites the slot. ### Outpost cranks @@ -254,13 +300,20 @@ Startup and configuration: Per-client runtime lines are prefixed with the SPI label `outpost_ethereum_client[{chain_code}:{ChainKind}:{chain_id}]`: -- `epochIn chunk sent epoch=... chunk=.../... bytes=... tx=...` for each delivered chunk. -- `resuming epoch=... delivery at chunk ...`, `discarded a superseded epoch=... staging header`, and - `skipping epoch=... delivery — the outpost has ...` on the chunk-resume paths. +- `epochIn delivered|continued epoch=... bytes=... gas=... block=... tx=... — recorded=... tipped=... + dispatched=... finalized=...` for each confirmed call, summarising the `OPPInbound` events in its receipt. +- `skipping epoch=... delivery — the outpost has already finalized it`, `tipped on a digest this relay did + not deliver`, `delivery already recorded; consensus not tipped`, `re-delivering to run the boundary tip` + and `not delivered — the outpost is still on epoch ...` for each decision that sends nothing (or + re-sends). +- `refused at ... gas (...); retrying at the ceiling` at warning level for a gas refusal below the ceiling, + and `cannot advance past attestation N at the ceiling` at error level — once per cursor position — when + no funding carries the attestation there; `still open after N calls in one tick` at warning level when + the per-tick bound is reached. - `read inbound envelope epoch=... bytes=...` after a successful inbound read. -- Warning-level lines for every malformed view result (`envelopeChunkState returned non-string variant`, - `latestOutboundEnvelope data_ not a string`, and siblings); an epoch mismatch on the inbound slot stays at - debug level so steady-state polling is not noisy. +- Warning-level lines for every malformed view result (`latestOutboundEnvelope data_ not a string` and + siblings); an epoch mismatch on the inbound slot stays at debug level so steady-state polling is not + noisy. ## Tests @@ -272,11 +325,14 @@ ninja -C build/debug test_outpost_ethereum_client_plugin Two suites run in that binary. `outpost_ethereum_client_plugin` covers option registration, chain-id resolution and verification (explicit, RPC-resolved, mismatched, malformed, out-of-range, and transient transport failure), signature-provider rejection cases, contract-client construction and ABI encoding, the -chunk-count and chunk-resume decision tables, and every delivery path — single-chunk, multi-chunk in order, -resume from a staged high-water mark, peer-owned header, superseded header, reverting discard, epoch -advanced, deadline abandonment, and the empty and over-cap rejections — and the outpost crank: idle without -the pool ABI or a registered handler, binding the registered pool and re-binding on a change, the pool's own -three refusals, unrecognised reverts and protocol errors, and deadline abandonment. +delivery decision table, settlement and receipt classification, the gas budget and ceilings, and every +delivery path — one call, tip-and-spill continued in the same tick, resume from a tipped cursor, minority +and never-delivered digests, consensus retry gated on the outpost's own majority view, pinned-block reads, +escalation after a call that advanced nothing and the stall report at the ceiling, revert classification, +outpost behind, the per-tick bound, epoch advanced, deadline abandonment, and the empty and over-cap +rejections — and the outpost crank: idle without the pool ABI or a registered handler, binding the +registered pool and re-binding on a change, the pool's own three refusals, unrecognised reverts and protocol +errors, and deadline abandonment. `outpost_ethereum_transaction_policy_tests` covers the expenditure-policy boundary: that a rejection happens before signing or broadcasting, that exact caps pass through once, that two clients enforce their own policies, that file and CLI configuration produce the expected policies, and that a partial client map is diff --git a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp index a8e4aec9ee..5006a11d28 100644 --- a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp +++ b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp @@ -1,8 +1,13 @@ #pragma once +#include +#include +#include + #include #include #include +#include #include #include @@ -56,36 +61,223 @@ struct opp_contract_client : ethereum_contract_client { : ethereum_contract_call_fn{}) {} }; +/// EIP-7825's per-transaction gas cap (2^24) — the most any single `epochIn` +/// can be funded with, whatever the client's policy allows. +inline constexpr uint64_t EIP_7825_TX_GAS_CAP = 16'777'216; + +/** + * @brief The most gas any `epochIn` may be funded with under the policy's + * static limit: `max_gas_limit`, bounded by EIP-7825's cap. + * @param policy The client's local expenditure policy. + * @return The ceiling, in gas. + */ +inline uint64_t delivery_gas_ceiling(const ethereum_transaction_policy& policy) { + const fc::uint256 cap{EIP_7825_TX_GAS_CAP}; + return policy.max_gas_limit < cap ? policy.max_gas_limit.convert_to() : EIP_7825_TX_GAS_CAP; +} + +/** + * @brief The most gas any `epochIn` may be funded with at a given fee: the + * static ceiling, further bounded by what `max_total_native_cost` + * pays for at `max_fee_per_gas`. + * + * The policy refuses a transaction whose `gas_limit * max_fee_per_gas` + * exceeds `max_total_native_cost`, so a limit the static ceiling admits can + * still be a rejection at the fee the call is sent at. Sizing to this + * ceiling turns that late rejection into a smaller budget the contract + * spills across continuations instead. + * + * @param policy The client's local expenditure policy. + * @param max_fee_per_gas The fee the call will be sent at. + * @return The ceiling, in gas; the static ceiling when the fee is zero. + */ +inline uint64_t delivery_gas_ceiling(const ethereum_transaction_policy& policy, const fc::uint256& max_fee_per_gas) { + const uint64_t static_ceiling = delivery_gas_ceiling(policy); + if (max_fee_per_gas == 0) return static_ceiling; + const fc::uint256 affordable = policy.max_total_native_cost / max_fee_per_gas; + return affordable < fc::uint256{static_ceiling} ? affordable.convert_to() : static_ceiling; +} + +/// Gas an `epochIn` pays before its dispatch loop, independent of size: the +/// transaction base, the delivery record, and the consensus bookkeeping a tip +/// adds. A ~700-byte tip reaches the loop at ~320k (wire-ethereum +/// `OPPCapacity`, the under-funded probe); rounded up. +inline constexpr uint64_t DELIVERY_FIXED_GAS = 500'000; +/// Gas an `epochIn` pays per envelope byte: calldata (EIP-7623 floors a +/// calldata-heavy transaction at 40 gas a byte), the linear decode (~50 gas +/// a byte, measured at the cap) and the dispatch walk; rounded up. +inline constexpr uint64_t DELIVERY_GAS_PER_BYTE = 120; +/// Gas allowed per attestation still to dispatch. A remit is tens of +/// thousands; a routine rotation seating seven unseen operators is ~1.15M. +/// An attestation that costs more spills, and the next call escalates. +inline constexpr uint64_t ATTESTATION_GAS_ALLOWANCE = 1'500'000; +/// Gas left for the outbound emit the finishing call attempts: +/// `OPPInbound.EmitAttemptGasFloor`, below which the contract defers it. +inline constexpr uint64_t EMIT_GAS_ALLOWANCE = 4'000'000; + +/// The least static ceiling the relay accepts: what a full-cap envelope costs +/// to deliver and tip, dispatch one attestation at its allowance, and emit. +/// Under less, the largest envelope the platform allows cannot complete on +/// this client, so the relay refuses to be built on it rather than discover +/// that one epoch at a time. +inline constexpr uint64_t DELIVERY_MINIMUM_GAS_CEILING = DELIVERY_FIXED_GAS + + OPP_MAX_ENVELOPE_BYTES * DELIVERY_GAS_PER_BYTE + + ATTESTATION_GAS_ALLOWANCE + EMIT_GAS_ALLOWANCE; + +/// What one `epochIn` has to carry, from which its gas budget is sized. +struct delivery_gas_request { + /// Encoded envelope size. + uint64_t envelope_bytes = 0; + /// Attestations the outpost has not dispatched yet, or `std::nullopt` + /// when the envelope could not be read: the budget is then the ceiling. + std::optional remaining_attestations; + /// Calls already sent for this epoch in this tick that spilled: each one + /// doubles the budget, so a run of attestations dearer than the allowance + /// costs at most a logarithmic number of extra calls. + uint32_t escalations = 0; + /// `delivery_gas_ceiling`. + uint64_t ceiling = 0; +}; + +/** + * @brief Gas one `epochIn` is funded with. + * + * Sized to what the call has left to do rather than to the node's estimate or + * to the ceiling. The estimate is systematically wrong for this call: + * `OPPInbound.epochIn` stops dispatching on a `gasleft()` watchdog and records + * where it stopped, so `eth_estimateGas` converges on the least gas at which + * the call SUCCEEDS — the tip plus one attestation — and funding to it would + * cost one transaction per attestation. The ceiling is wrong the other way: + * a node reserves `gas_limit * max_fee_per_gas` of the signer's balance + * whatever the call uses, so a small envelope funded to the cap is refused for + * balance at exactly the fee levels where delivery matters most. + * + * The contract spills what does not fit and the next call carries on, so an + * under-estimate costs a continuation, never the epoch. + * + * @param request What the call has to carry. + * @return The budget, in gas. + */ +inline uint64_t delivery_gas_budget(const delivery_gas_request& request) { + if (!request.remaining_attestations) return request.ceiling; + uint64_t budget = DELIVERY_FIXED_GAS + request.envelope_bytes * DELIVERY_GAS_PER_BYTE + + uint64_t{*request.remaining_attestations} * ATTESTATION_GAS_ALLOWANCE + + EMIT_GAS_ALLOWANCE; + for (uint32_t i = 0; i < request.escalations && budget < request.ceiling; ++i) budget *= 2; + return std::min(budget, request.ceiling); +} + +/** + * @brief Confirmation options for one `epochIn`: funded to exactly + * `gas_budget` (floor and cap both), its receipt awaited for + * `receipt_wait`. + * + * The receipt wait replaces the library's default retry budget, which would + * otherwise cap every wait at its own 15 s however long the delivery was + * given: on a chain whose blocks come slower than that, raising + * `batch-delivery-timeout-ms` would not let a single wait outlast one block. + * + * @param gas_budget The budget from `delivery_gas_budget`. + * @param receipt_wait How long to wait for the receipt. + * @return The options the call is sent with. + */ +inline ethereum_confirm_options delivery_confirm_options(uint64_t gas_budget, fc::microseconds receipt_wait) { + ethereum_confirm_options options = ethereum_confirm_option_defaults; + options.gas_limit_floor = gas_budget; + options.gas_limit_cap = gas_budget; + options.retry.total_timeout = receipt_wait; + return options; +} + +/// `eth_getTransactionReceipt` field names the relay reads. +namespace ethereum_receipt_field { +inline constexpr auto block_number = "blockNumber"; +inline constexpr auto logs = "logs"; +} // namespace ethereum_receipt_field + +/// What one `epochIn` left on chain, read from its receipt once it confirmed. +struct epoch_in_receipt { + /// The transaction's hash, for logs. + std::string tx_hash; + /// The block that holds the transaction. Every read that decides the + /// relay's next move is pinned to it, so a backend lagging behind the block + /// that just confirmed cannot report the state from before the call. + fc::uint256 block_number; + /// The receipt's `logs`, as the node returned them: what the call did. + fc::variants logs; +}; + +/** + * @brief Read an `epoch_in_receipt` out of an `eth_getTransactionReceipt` result. + * @param tx_hash The confirmed transaction's hash. + * @param receipt The receipt object as `ethereum_client::wait_for_receipt` returned it. + * @return The block number and logs the relay acts on. + * @throws fc::exception when the receipt is not an object or carries no block number. + */ +inline epoch_in_receipt parse_epoch_in_receipt(std::string tx_hash, const fc::variant& receipt) { + FC_ASSERT(receipt.is_object(), "transaction receipt for {} is not an object", tx_hash); + const auto& object = receipt.get_object(); + FC_ASSERT(object.contains(ethereum_receipt_field::block_number), + "transaction receipt for {} carries no blockNumber", tx_hash); + epoch_in_receipt parsed{.tx_hash = std::move(tx_hash), + .block_number = fc::to_uint256(object[ethereum_receipt_field::block_number]), + .logs = {}}; + if (object.contains(ethereum_receipt_field::logs) && object[ethereum_receipt_field::logs].is_array()) { + parsed.logs = object[ethereum_receipt_field::logs].get_array(); + } + return parsed; +} + +/// ABI entry names of `OPPInbound.sol` the typed wrapper binds. +namespace opp_inbound_abi_name { +inline constexpr auto epoch_in = "epochIn"; +inline constexpr auto next_epoch_index = "nextEpochIndex"; +inline constexpr auto dispatch_spill = "dispatchSpill"; +inline constexpr auto epoch_deliveries = "epochDeliveries"; +inline constexpr auto pending_epoch_hash = "pendingEpochHash"; +inline constexpr auto pending_consensus_for_digest = "pendingConsensusForDigest"; +inline constexpr auto attestation_handlers = "attestationHandlers"; +} // namespace opp_inbound_abi_name + /// Typed contract client for OPPInbound.sol. Same confirmed-default -/// policy as `opp_contract_client` for write paths. +/// policy as `opp_contract_client` for the write path. struct opp_inbound_contract_client : ethereum_contract_client { - /// `epochIn(uint32 epochIndex, uint16 chunkIndex, uint16 totalChunks, - /// uint32 totalBytes, bytes chunkData)` — ONE chunk of the - /// envelope. The contract stages every chunk but the last and finalizes - /// inline on the chunk that completes the envelope; there is no terminal - /// call and no crank. `chunkData` rides as a hex-encoded string because the - /// libfc ABI encoder takes `dt::bytes` that way (see - /// `ethereum_abi::encode_dynamic_data`). + /// `epochIn(uint32 epochIndex, bytes envelopeData)` — the WHOLE envelope in + /// ONE call. The call that reaches consensus dispatches as many attestations + /// as its gas allows and records where it stopped; a continuation is the + /// SAME call with the same arguments, resumed from the contract's cursor. + /// Not a plain `ethereum_contract_tx_fn`, which binds its options once at + /// construction: the gas budget (see `delivery_gas_budget`) and `deadline` + /// differ per call. Returns the confirmed receipt because the relay's next + /// move is decided from the block the call landed in. `envelopeData` rides + /// as a hex-encoded string because the libfc ABI encoder takes `dt::bytes` + /// that way (see `ethereum_abi::encode_dynamic_data`). /// - /// `ethereum_contract_tx_fn` binds every argument as a non-const lvalue - /// reference, so callers must materialize named locals for all five. - ethereum_contract_tx_fn epoch_in; - /// `discardEnvelopeChunks()` — staged-owner-only recovery, resetting every - /// header this signer owns. Invoked by the relay when it finds a - /// CURRENT-epoch staging header whose shape belongs to a superseded - /// envelope; reverts (`OPP_ChunkBufferMissing`) when nothing is staged, - /// which the relay treats as "already clear". - ethereum_contract_tx_fn discard_envelope_chunks; + /// The ABI arguments are bound as non-const lvalue references, so callers + /// must materialize named locals for both. + std::function + epoch_in; /// `nextEpochIndex()` view — the epoch the outpost is currently accepting. ethereum_contract_call_fn next_epoch_index; - /// `envelopeChunkState(address operator_)` view — the OWNER-BOUND staging - /// header, the relay's resume read. Block tag rides first per - /// `ethereum_contract_call_fn`; the address argument is a hex string. - /// Returns the raw `eth_call` hex — `create_call` does not - /// auto-decode, so the caller pushes it back through `contract_decode_data` - /// against this ABI entry (the same shape `read_inbound_envelope` uses for - /// `getLatestOutboundEnvelope`). - ethereum_contract_call_fn envelope_chunk_state; + /// `dispatchSpill(uint32 epochIndex)` view — where a tipped epoch's dispatch + /// stands: `(tipped, dispatched, complete, finalized)`. Block tag rides + /// first per `ethereum_contract_call_fn`. Returns the raw `eth_call` hex — + /// `create_call` does not auto-decode, so the caller pushes it + /// back through `contract_decode_data` against this ABI entry. + ethereum_contract_call_fn dispatch_spill; + /// `epochDeliveries(uint32 epochIndex, address operator_)` view — the digest + /// `operator_` delivered for the epoch, or zero when it has not delivered. + ethereum_contract_call_fn epoch_deliveries; + /// `pendingEpochHash()` view — the digest consensus settled on for the epoch + /// the outpost is processing. + ethereum_contract_call_fn pending_epoch_hash; + /// `pendingConsensusForDigest(bytes32 digest)` view — how close the current + /// epoch is to tipping on `digest`: `(nextEpoch, agreeing, groupSize, + /// currentEpochStartedAtTs, epochDurationSec_)`. The relay reads it before a + /// consensus retry, so a re-delivery is sent only when the contract's own + /// path-2 predicate would fire. The digest rides as `0x`-hex. + ethereum_contract_call_fn pending_consensus_for_digest; /// `attestationHandlers(uint16 attestationType)` view — the outpost's own /// inbound routing table: the `IOPPReceiver` registered for one attestation /// type, `address(0)` when none is and `ATTESTATION_BLACKHOLE` when governance @@ -99,13 +291,25 @@ struct opp_inbound_contract_client : ethereum_contract_client { const address_compat_type& contract_address, const std::vector& contracts) : ethereum_contract_client(client, contract_address, contracts) - , epoch_in(create_tx_and_confirm( - get_abi("epochIn"))) - , discard_envelope_chunks( - create_tx_and_confirm(get_abi("discardEnvelopeChunks"))) - , next_epoch_index(create_call(get_abi("nextEpochIndex"))) - , envelope_chunk_state(create_call(get_abi("envelopeChunkState"))) - , attestation_handlers(create_call(get_abi("attestationHandlers"))) {} + , epoch_in([this](uint32_t& epoch_index, std::string& envelope_hex, uint64_t gas_budget, + fc::time_point deadline) -> epoch_in_receipt { + const auto& abi = get_abi(opp_inbound_abi_name::epoch_in); + contract_invoke_data_items params = {epoch_index, envelope_hex}; + auto tx = this->client->create_default_tx(this->contract_address, abi, params, gas_budget, gas_budget); + const auto tx_hash = this->client->execute_contract_tx_fn(tx, abi, params).as_string(); + // Measured after the send, so the wait gets exactly what is left. + const auto receipt_wait = std::max(fc::microseconds(), deadline - fc::time_point::now()); + return parse_epoch_in_receipt( + tx_hash, this->client->wait_for_receipt(tx_hash, delivery_confirm_options(gas_budget, receipt_wait))); + }) + , next_epoch_index(create_call(get_abi(opp_inbound_abi_name::next_epoch_index))) + , dispatch_spill(create_call(get_abi(opp_inbound_abi_name::dispatch_spill))) + , epoch_deliveries( + create_call(get_abi(opp_inbound_abi_name::epoch_deliveries))) + , pending_epoch_hash(create_call(get_abi(opp_inbound_abi_name::pending_epoch_hash))) + , pending_consensus_for_digest( + create_call(get_abi(opp_inbound_abi_name::pending_consensus_for_digest))) + , attestation_handlers(create_call(get_abi(opp_inbound_abi_name::attestation_handlers))) {} }; /// Typed contract client for wire-ethereum's `SyndicationPool.sol`, the liq diff --git a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp index cfa7009121..932c8e2f77 100644 --- a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp +++ b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -14,115 +15,199 @@ namespace sysio { -/// Per-`epochIn` chunk payload limit on Ethereum. -/// -/// MIRROR DUTY — compiled into BOTH sides and never configured: this value must -/// equal `MAX_CHUNK_BYTES` in wire-ethereum's `contracts/outpost/OPPCommon.sol` -/// (re-declared as the public constant `OPPInbound.MAX_CHUNK_BYTES` so the -/// hardhat drift-guard suite can pin it). Deliveries carrying more than this in -/// a single transaction were demonstrated to break the Ethereum envelope -/// exchange, so `deliver_outbound_envelope` splits an envelope into -/// `ceil(size / ETHEREUM_MAX_CHUNK_BYTES)` transactions: every non-final chunk -/// is EXACTLY this many bytes and the final chunk carries the remainder. -/// -/// A multiple of 32 so the contract's staging-cell writes stay word-aligned. -/// Against `OPP_MAX_ENVELOPE_BYTES` this yields at most -/// `ceil(32'768 / 8'192) == 4` chunks — and there is no terminal call: the -/// contract finalizes inline on the chunk that completes the envelope. This is -/// the Ethereum analogue of `SOLANA_MAX_CHUNK_BYTES` (672). -inline constexpr size_t ETHEREUM_MAX_CHUNK_BYTES = 8'192; - namespace outpost_ethereum_client_detail { -/// Number of `epochIn` transactions one envelope of `total_bytes` costs. -/// Ceil-division, matching `OPPInbound`'s own `totalChunks` validation — the -/// contract rejects any delivery whose declared `totalChunks` differs. -constexpr uint16_t chunk_count_for(size_t total_bytes) { - return static_cast((total_bytes + ETHEREUM_MAX_CHUNK_BYTES - 1) / - ETHEREUM_MAX_CHUNK_BYTES); -} +/// Decoded `OPPInbound.dispatchSpill(uint32)` view result — where a tipped +/// epoch's processing stands. All-false/zero for an epoch that has not tipped; +/// an unreadable response is an error, never this. +struct dispatch_spill { + /// Consensus settled the epoch's digest and dispatch began. + bool tipped = false; + /// Attestations routed so far — also where the next continuation resumes. + uint16_t dispatched = 0; + /// Every attestation has been routed. + bool complete = false; + /// The outbound envelope has been emitted and `nextEpochIndex` advanced. + bool finalized = false; +}; -/// Decoded `OPPInbound.envelopeChunkState(address)` view result — the -/// owner-bound staging header this operator has (or has not) left on chain. -/// -/// A never-staged / fully-reclaimed header decodes as all-zero, which the -/// contract spells `totalChunks == SLOT_EMPTY (0)`; `decide_chunk_resume` -/// treats that as "nothing to adopt". -struct envelope_chunk_state { - /// WIRE epoch the staged header belongs to. - uint32_t epoch_index = 0; - /// Staging owner, as the ABI decoder returns it (lower-case `0x`-hex), or - /// empty when the read failed / no header exists. - std::string owner; - /// Declared chunk count of the staged envelope; 0 == empty header. - uint16_t total_chunks = 0; - /// Chunks actually STORED on chain. The final chunk is never stored, so - /// this never reaches `total_chunks` for a well-formed header. - uint16_t received_chunks = 0; - /// Declared total envelope size of the staged envelope. - uint32_t total_bytes = 0; - /// Sum of the stored cells' lengths — diagnostics only. - uint64_t stored_bytes = 0; +/// How this relay's recorded delivery for an epoch relates to what the +/// outpost settled on. +enum class delivery_settlement { + /// Nothing is recorded under this relay for the epoch. + never_delivered, + /// Recorded, and the epoch has not tipped: the settled digest is not yet + /// this epoch's, so there is nothing to compare against. + recorded, + /// Recorded on a digest other than the one consensus settled: a minority + /// envelope, which must not be used to resume the epoch. + divergent, + /// Recorded on the settled digest: this relay may continue the epoch. + settled }; -/// What the relay should do about the staging header it just read. -enum class chunk_resume_action { - /// Nothing adoptable on chain — send every chunk from index 0. - start_fresh, - /// Our own header for this exact epoch and shape — continue from its - /// high-water mark. - resume, - /// Our own CURRENT-epoch header with a different shape (a superseded - /// envelope for the same epoch) — `discardEnvelopeChunks()` first, then - /// send from index 0. - discard_and_restart, - /// Our own header for a DIFFERENT epoch — read `nextEpochIndex()` to find - /// out whether the epoch under delivery has already been consumed; if it - /// has, the delivery is skipped entirely (it would only buy late-no-op gas). - confirm_epoch_advanced +/// Classify this relay's delivery record for an epoch. +/// +/// @param own_digest `epochDeliveries(epoch, self)` as a normalised word +/// (lower-case hex, no prefix); the zero word records nothing. +/// @param settled_digest `pendingEpochHash()` normalised the same way. +/// @param tipped `dispatchSpill(epoch).tipped`; until the tip the +/// settled digest belongs to an earlier epoch. +/// @throws fc::exception when either word is not exactly 32 bytes of hex: a +/// malformed read must not pass as "never delivered". +delivery_settlement classify_settlement(std::string_view own_digest, std::string_view settled_digest, + bool tipped); + +/// Decoded `OPPInbound.pendingConsensusForDigest(bytes32)` view result. +struct pending_consensus { + /// The epoch the outpost is accepting. + uint32_t next_epoch = 0; + /// Deliveries recorded for the digest in that epoch. + uint32_t agreeing = 0; + /// Members of the group scheduled for that epoch. + uint32_t group_size = 0; + /// `block.timestamp` at which the outpost's current epoch began. + uint64_t epoch_started_at = 0; + /// The outpost's mirror of the depot's `epoch_duration_sec`. + uint32_t epoch_duration_sec = 0; }; -/// `decide_chunk_resume`'s verdict. -struct chunk_resume_decision { - chunk_resume_action action = chunk_resume_action::start_fresh; - /// First chunk index to send. Meaningful for `resume`; 0 for every other - /// action. - uint16_t start_chunk = 0; +/// The contract's path-2 predicate (`.claude/rules/opp-consensus.md`): the +/// boundary has elapsed and a strict majority of the group agrees. A +/// re-delivery sent while this is false is a paid no-op; one sent while it is +/// true re-runs the tip and settles the epoch. +/// +/// @param consensus the view, read for the digest this relay delivered. +/// @param now_sec wall clock, seconds since the epoch. +bool majority_tip_reachable(const pending_consensus& consensus, uint64_t now_sec); + +/// What the relay should do about the epoch it is holding, given the outpost's +/// view of that epoch. +enum class delivery_action { + /// Nothing recorded and the epoch has not tipped: deliver the envelope. + deliver, + /// This relay's delivery is recorded, the epoch has not tipped, and the + /// contract's path-2 predicate holds: re-send the same envelope so the + /// outpost re-runs the tip (idempotent on a same-digest re-delivery). + retry_consensus, + /// This relay's delivery is recorded and the epoch has not tipped; a + /// re-delivery would change nothing until more of the group delivers (or, + /// before the outpost's own boundary, until that boundary passes). + await_peers, + /// The epoch tipped on THIS relay's digest and is not finished: re-supply + /// the envelope so the outpost dispatches the next stretch. + continue_dispatch, + /// The epoch tipped on a digest this relay did not deliver. Only a relay + /// whose recorded delivery matches the settled digest may continue it, so + /// there is nothing for this one to send. + wait_for_deliverer, + /// The outpost has already finalized this epoch: nothing to send. + already_finalized, + /// The outpost is still on an earlier epoch: a delivery for this one would + /// be refused as non-sequential, and the epoch must be retried once the + /// outpost catches up. + outpost_behind }; /// Compare two EVM addresses for identity, tolerating an absent `0x` prefix /// and EIP-55 checksum casing on either side. bool same_evm_address(std::string_view lhs, std::string_view rhs); -/// True when `revert_data` is exactly `OPP_ChunkBufferMissing(owner_address)`. +/// Decide the relay's next move for `epoch_index`. /// -/// `discardEnvelopeChunks()` raises that error, and only that error, when it -/// finds nothing of ours to clear — the one revert meaning the staging header -/// is already in the state the caller wanted. Every other revert (a wrong -/// implementation behind the proxy, a reentrancy guard, an unrecognised -/// selector) must not be mistaken for it. +/// Pure and side-effect free so the table is unit-testable without an EVM +/// node; the caller performs whichever RPC the verdict names. /// -/// @param revert_data `json_rpc_error::data`, the node's revert bytes as -/// `0x`-hex; empty or truncated data returns false. -/// @param owner_address the signer this relay expects the contract to name. -bool is_chunk_buffer_missing_revert(std::string_view revert_data, std::string_view owner_address); +/// @param next_epoch_index the outpost's `nextEpochIndex()`. +/// @param epoch_index WIRE epoch being delivered. +/// @param spill the outpost's `dispatchSpill(epoch_index)`. +/// @param settlement this relay's delivery record, see `classify_settlement`. +/// @param majority_reachable `majority_tip_reachable` for the digest this relay +/// delivered; consulted only for a recorded, untipped epoch. +delivery_action decide_delivery(uint32_t next_epoch_index, + uint32_t epoch_index, + const dispatch_spill& spill, + delivery_settlement settlement, + bool majority_reachable); + +/// Everything the relay reads about one epoch at ONE block, so the pieces +/// cannot disagree with each other. +struct delivery_progress { + /// `nextEpochIndex()`. + uint32_t next_epoch_index = 0; + /// `dispatchSpill(epoch)`. + dispatch_spill spill; + /// `epochDeliveries(epoch, self)`, normalised; the zero word when none. + std::string own_digest; + /// `pendingEpochHash()`, normalised. + std::string settled_digest; +}; + +/// Whether the outpost moved the epoch forward between two reads: the +/// invariant every call the relay pays for must satisfy. A call that leaves +/// every field where it was did nothing, and sending it again at the same +/// budget would do nothing again. +bool advanced(const delivery_progress& before, const delivery_progress& after); + +/// `OPPInbound.epochIn` refusals the relay tells apart in the node's revert +/// bytes. Every one is raised at estimate time, before anything is signed. +enum class epoch_in_revert { + /// `OPP_DispatchUnderfunded(uint256,uint256)`: the budget does not reach + /// the contract's dispatch floor. + dispatch_underfunded, + /// `OPP_HandlerGasExhausted(address,uint16,uint256)`: the first handler of + /// the call ran out of its bounded gas — the budget is too small for the + /// attestation at the cursor, or no budget is. + handler_gas_exhausted, + /// `OPP_NonSequentialEpoch(uint32,uint32)`: the outpost is on another epoch. + non_sequential_epoch, + /// `OPP_OperatorAlreadyDelivered(uint32,address)`: a re-delivery the + /// contract treats as already counted. + operator_already_delivered, + /// `OPP_NotActiveOperator(address)`: this signer is not in the epoch's + /// group, or is not the settled digest's deliverer for a continuation. + not_active_operator, + /// `OPP_DigestMismatch(bytes32,bytes32)`: the envelope supplied for a + /// continuation is not the one consensus settled on. + digest_mismatch +}; -/// Decide how to resume (or abandon) a chunked delivery given the on-chain -/// staging header. +/// Classify an `epochIn` revert. `std::nullopt` for a protocol error (the node +/// never ran the call) and for any revert that is not one of the refusals +/// above, exactly shaped: an unknown revert must not pass as a known one. /// -/// Pure and side-effect free so the decision table is unit-testable without an -/// EVM node; the caller performs whichever RPC the returned action names. +/// @param rpc_code `json_rpc_error::code`. +/// @param revert_data `json_rpc_error::data`, the node's revert bytes as `0x`-hex. +std::optional classify_epoch_in_revert(int rpc_code, std::string_view revert_data); + +/// What one confirmed `epochIn` did, read from the `OPPInbound` events in its +/// receipt. Absent events mean the call recorded nothing new, which is the +/// contract's benign no-op for a late or redundant call. +struct delivery_receipt_summary { + /// `EpochDelivery(epoch, self, digest)`: this relay's delivery was recorded. + bool recorded = false; + /// `EpochConsensus(epoch, digest, count)`: the call tipped the epoch. + bool tipped = false; + /// `EpochDispatchProgressed(epoch, dispatched)`: where the call's dispatch + /// stopped, when it dispatched at all. + std::optional dispatched; + /// `EpochComplete(epoch)`: the call emitted and finalized the epoch. + bool finalized = false; +}; + +/// Summarise the `OPPInbound` events in one receipt's `logs`. /// -/// @param staged header read back from `envelopeChunkState(self)`. -/// @param self_address this relay's own signer address. -/// @param epoch_index WIRE epoch being delivered. -/// @param total_chunks chunk count of the envelope being delivered. -/// @param total_bytes size of the envelope being delivered. -chunk_resume_decision decide_chunk_resume(const envelope_chunk_state& staged, - std::string_view self_address, - uint32_t epoch_index, - uint16_t total_chunks, - uint32_t total_bytes); +/// @param logs the receipt's `logs` array, as the node returned it. +/// @param inbound_address the `OPPInbound` the relay delivers to; logs from +/// any other address (a handler's, a pool's) are ignored. +/// @param epoch_index the epoch delivered; events for another are ignored. +/// @param operator_address this relay's signer, for `EpochDelivery`. +delivery_receipt_summary summarize_delivery_receipt(const fc::variants& logs, std::string_view inbound_address, + uint32_t epoch_index, std::string_view operator_address); + +/// `keccak256` of the envelope bytes as a normalised word — the digest the +/// outpost records for a delivery and settles consensus on. +std::string envelope_digest_word(const std::vector& envelope_bytes); /// Why `SyndicationPool.realizeYield()` refused, read from the node's revert bytes. enum class realize_yield_refusal { @@ -154,6 +239,12 @@ std::optional classify_realize_yield_revert(std::string_v /// the hex is not exactly one word of hex digits with a zero 12-byte pad. std::optional address_from_word(std::string_view raw_hex); +/// How many attestations `envelope_bytes` carries across its messages, or +/// `std::nullopt` when the bytes do not decode as an OPP envelope. The relay +/// sizes a delivery's gas budget from it; an unreadable envelope is funded to +/// the ceiling and left to the contract to judge. +std::optional count_envelope_attestations(const std::vector& envelope_bytes); + /// True when `handler_address` names a contract the outpost routes to: neither /// `address(0)` (nothing registered) nor `ATTESTATION_BLACKHOLE` (governance /// dropped the type). @@ -174,6 +265,10 @@ bool is_routable_handler(std::string_view handler_address); */ class outpost_ethereum_client : public outpost_client { public: + /// @throws chain::plugin_config_exception when an OPPInbound address is + /// given and the client's static gas ceiling is below + /// `DELIVERY_MINIMUM_GAS_CEILING`: no envelope at the platform cap + /// could complete on it. outpost_ethereum_client(ethereum_client_entry_ptr entry, std::string opp_addr, std::string opp_inbound_addr, @@ -188,6 +283,22 @@ class outpost_ethereum_client : public outpost_client { std::vector authenticated_caller_address() const override; // to_string() inherits the base-class default: "{chain_code}:{ChainKind}:{chain_id}". + /// Deliver the envelope in ONE `epochIn`, then continue it while the + /// outpost reports it tipped on this relay's digest and unfinished. Every + /// decision is taken from a read pinned to one block — the head at the + /// start of the tick, then the block each confirmed call landed in — and + /// every call paid for must move the outpost's cursor, or the next one is + /// funded higher; a call at the ceiling that moves nothing is reported. + /// + /// @return The last transaction hash sent, or EMPTY when nothing was sent + /// because the outpost had already finalized the epoch or there is + /// nothing for this relay to send (the epoch tipped on another + /// digest, or this relay's delivery is recorded and awaits peers). + /// @throws chain::outpost_delivery_incomplete_exception when the tick ends + /// with the epoch still open for THIS relay to act on: the outpost + /// is behind, the per-tick continuation bound was reached, or a + /// call at the ceiling advanced nothing. The job retries next tick. + /// @throws fc::exception on transport failure or deadline expiry. std::string deliver_outbound_envelope(uint32_t epoch_index, const std::vector& envelope_bytes, fc::microseconds deadline) override; @@ -211,10 +322,8 @@ class outpost_ethereum_client : public outpost_client { const ethereum_client_entry_ptr& entry() const { return _entry; } const std::string& opp_address() const { return _opp_addr; } const std::string& opp_inbound_address() const { return _opp_inbound_addr; } - /// This relay's own signer address in `0x`-hex — the identity every staging - /// header is bound to. Derived once at construction; the chunk resume path - /// compares it against `envelopeChunkState`'s `owner` on every multi-chunk - /// delivery, so it is cached rather than re-derived per tick. + /// This relay's own signer address in `0x`-hex — the identity the outpost + /// records deliveries under. Derived once at construction. const std::string& signer_address_hex() const { return _signer_address_hex; } /// The liq syndication pool the crank drives, `0x`-hex, or empty until the /// outpost's `DESYNDICATE_LIQ` handler has been discovered. @@ -233,32 +342,32 @@ class outpost_ethereum_client : public outpost_client { /// @throws fc::exception on transport failure. std::optional discover_syndication_pool(); - /// Read `OPPInbound.envelopeChunkState(self)` at `latest` and decode it. + /// Read everything `deliver_outbound_envelope` decides from — the epoch + /// cursor, the spill cursor, this relay's delivery record and the settled + /// digest — at ONE block. /// - /// `latest` is correct here (unlike `read_inbound_envelope`, which reads at - /// `finalized`): this is our OWN staging high-water mark, not delivered - /// content WIRE consensus commits against. Reading it at `finalized` would - /// re-send chunks already staged in unfinalized blocks every tick. + /// Pinned to a block number, never `latest`: the block is the head read at + /// the start of the tick, or the block a call this tick confirmed in, so a + /// backend lagging behind it cannot hand back the pre-call state and have + /// the relay pay for the call twice. A backend that does not know the block + /// yet is retried for a bounded time. /// - /// A malformed / unreadable response yields an all-zero state, which - /// degrades to `start_fresh` — the contract absorbs the replayed chunks as - /// idempotent no-ops. + /// Not `finalized` either (unlike `read_inbound_envelope`): finality lags + /// head by roughly 64 blocks, and a continuation gated on it would wait that + /// out between every stretch of dispatch. A reorg under the read costs a + /// paid re-delivery before the tip or a refused continuation after it; a + /// send missed because the read was behind is covered by the per-call + /// progress invariant and the next tick. /// - /// @throws fc::exception on transport failure or deadline expiry. - outpost_ethereum_client_detail::envelope_chunk_state read_envelope_chunk_state(); + /// @throws fc::exception on transport failure, deadline expiry, or an + /// unparsable response. + outpost_ethereum_client_detail::delivery_progress read_progress(uint32_t epoch_index, const fc::uint256& block); - /// First chunk index to send for this delivery, or `std::nullopt` when the - /// epoch has already advanced past `epoch_index` and the delivery must be - /// skipped outright. - /// - /// Multi-chunk deliveries only — a single-chunk envelope stages nothing, so - /// the dominant case pays no extra RPC. - /// - /// @throws fc::exception on transport failure or deadline expiry. - std::optional resume_chunk_index(uint32_t epoch_index, - uint16_t total_chunks, - uint32_t total_bytes, - fc::time_point deadline_abs); + /// Read `OPPInbound.pendingConsensusForDigest(digest)` at `block` and decode it. + /// @throws fc::exception on transport failure, deadline expiry, or an + /// unparsable response. + outpost_ethereum_client_detail::pending_consensus read_pending_consensus(const std::string& digest_word, + const fc::uint256& block); ethereum_client_entry_ptr _entry; std::string _opp_addr; @@ -274,6 +383,10 @@ class outpost_ethereum_client : public outpost_client { std::shared_ptr _syndication_pool_client; // nullable /// Cached `0x`-hex signer address — see `signer_address_hex()`. std::string _signer_address_hex; + /// The (epoch, cursor) a ceiling-funded call last failed to advance, so the + /// error is reported once and repeats stay at debug level until the cursor + /// moves. + std::optional> _reported_stall; uint64_t _outpost_id; uint32_t _chain_id; }; diff --git a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp index b2b139bf2f..81bb4652db 100644 --- a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp +++ b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -14,7 +15,10 @@ #include #include #include +#include +#include +#include #include #include @@ -32,17 +36,33 @@ constexpr std::string_view OP_READ_EMITTED_DIGEST = "read_emitted_envelope_diges constexpr std::string_view OP_REALIZE_YIELD = "crank_outpost:realizeYield"; +/// Retry label for the pinned progress read, see `PINNED_READ_RETRY`. +constexpr std::string_view OP_READ_PROGRESS = "ethereum:read_progress"; + /// Execution APIs code for a call the node executed and that reverted, as distinct from a /// protocol error such as a parse failure, where the node never ran the call at all. Not an /// EIP-1474 code — that spec only defines the negative range; 3 is geth's convention, which /// `ethereum/execution-apis` standardised. constexpr int ethereum_execution_reverted_code = 3; -/// Solidity signature of the revert `discardEnvelopeChunks()` raises when it has nothing of -/// ours to clear. Hashed rather than written out as its selector so a reader can check this -/// against the ABI directly; `chunk_buffer_missing_selector_is_pinned` holds the hash to the -/// four bytes the contract actually emits. -constexpr auto chunk_buffer_missing_signature = "OPP_ChunkBufferMissing(address)"; +/// Solidity signatures of the `OPPInbound.epochIn` refusals the relay tells apart +/// (wire-ethereum `OPPErrors.sol`). Hashed rather than written out as selectors so a reader +/// can check them against the ABI directly; `epoch_in_revert_selectors_are_pinned` holds +/// each to the four bytes the contract actually emits. `OPP_HandlerGasExhausted` carries the +/// attestation type as the `uint16` the enum's ABI encoding is. +constexpr auto dispatch_underfunded_signature = "OPP_DispatchUnderfunded(uint256,uint256)"; +constexpr auto handler_gas_exhausted_signature = "OPP_HandlerGasExhausted(address,uint16,uint256)"; +constexpr auto non_sequential_epoch_signature = "OPP_NonSequentialEpoch(uint32,uint32)"; +constexpr auto operator_already_delivered_signature = "OPP_OperatorAlreadyDelivered(uint32,address)"; +constexpr auto not_active_operator_signature = "OPP_NotActiveOperator(address)"; +constexpr auto digest_mismatch_signature = "OPP_DigestMismatch(bytes32,bytes32)"; + +/// Solidity signatures of the `OPPInbound` events a delivery's receipt carries, hashed to +/// the topic the node reports; `delivery_event_topics_are_pinned` holds each to the bytes. +constexpr auto epoch_delivery_event_signature = "EpochDelivery(uint32,address,bytes32)"; +constexpr auto epoch_consensus_event_signature = "EpochConsensus(uint32,bytes32,uint32)"; +constexpr auto epoch_dispatch_progressed_event_signature = "EpochDispatchProgressed(uint32,uint16)"; +constexpr auto epoch_complete_event_signature = "EpochComplete(uint32)"; /// The three refusals `SyndicationPool.realizeYield()` raises for its own reasons /// (Wire-Network/wire-ethereum#207), hashed the same way; @@ -55,13 +75,15 @@ constexpr auto pool_underbacked_signature = "WIRE_PoolUnderbacked(uint64,uin constexpr auto enforced_pause_signature = "EnforcedPause()"; /// `ATTESTATION_BLACKHOLE` in wire-ethereum's `OPPCommon.sol`: the handler governance -/// registers to drop an attestation type on purpose. Mirror duty, like -/// `ETHEREUM_MAX_CHUNK_BYTES`. +/// registers to drop an attestation type on purpose. A compiled-in mirror of the contract +/// constant, never configured. constexpr auto attestation_blackhole_address = "0x000000000000000000000000000000000000dead"; /// ABI entry names and decoded-output field keys of the outpost contracts this /// client drives. Grouped per contract so a Solidity rename is one edit here -/// rather than a scatter of string literals. +/// rather than a scatter of string literals. The OPPInbound entry names live +/// with the wrapper (`opp_inbound_abi_name`); only its decoded field keys are +/// here. namespace opp_abi { constexpr auto view_latest_outbound_envelope = "getLatestOutboundEnvelope"; namespace field { @@ -78,23 +100,59 @@ constexpr auto tx_realize_yield = "realizeYield"; } // namespace syndication_pool_abi namespace opp_inbound_abi { -constexpr auto view_envelope_chunk_state = "envelopeChunkState"; -constexpr auto view_next_epoch_index = "nextEpochIndex"; namespace field { -constexpr auto epoch_index = "epochIndex"; -constexpr auto owner = "owner"; -constexpr auto total_chunks = "totalChunks"; -constexpr auto received_chunks = "receivedChunks"; -constexpr auto total_bytes = "totalBytes"; -constexpr auto stored_bytes = "storedBytes"; +constexpr auto tipped = "tipped"; +constexpr auto dispatched = "dispatched"; +constexpr auto complete = "complete"; +constexpr auto finalized = "finalized"; +constexpr auto next_epoch = "nextEpoch"; +constexpr auto agreeing = "agreeing"; +constexpr auto group_size = "groupSize"; +constexpr auto current_epoch_started_at = "currentEpochStartedAtTs"; +constexpr auto epoch_duration_sec = "epochDurationSec_"; } } // namespace opp_inbound_abi +/// `eth_getTransactionReceipt` log entry field names. +namespace receipt_log_field { +constexpr auto address = "address"; +constexpr auto topics = "topics"; +constexpr auto data = "data"; +} // namespace receipt_log_field + +/// Most `epochIn` calls one tick sends for one epoch before handing the rest +/// to the next tick. A bound, not a budget: a full-cap envelope spills a +/// handful of times at most, and a cursor still advancing past this many is +/// worth a log line and a fresh tick rather than an unbounded loop inside one. +constexpr uint32_t MAX_CONTINUATIONS_PER_TICK = 32; + +/// Most times one tick re-reads the outpost after the node refused a call for +/// a reason that says the state moved under the read (`OPP_NonSequentialEpoch` +/// and its siblings). A refusal that survives this many fresh reads is not a +/// race, and the tick reports it instead of reading again. +constexpr uint32_t MAX_STALE_REFUSALS_PER_TICK = 2; + +/// How the pinned progress read retries a backend that does not know the +/// block yet (a load-balanced endpoint behind the one that confirmed the +/// transaction). Short, because the block exists: the backend is behind by +/// seconds, not minutes, and the tick's own deadline bounds it anyway. +constexpr fc::task::retry_options PINNED_READ_RETRY{ + .initial_backoff = fc::milliseconds(250), + .max_backoff = fc::seconds(1), + .total_timeout = fc::seconds(10), +}; + constexpr size_t EVM_ABI_WORD_BYTES = 32; constexpr size_t EVM_ADDRESS_BYTES = 20; constexpr size_t EVM_SELECTOR_BYTES = 4; constexpr size_t HEX_PREFIX_CHARS = 2; constexpr size_t HEX_CHARS_PER_BYTE = 2; +constexpr size_t EVM_ABI_WORD_HEX_CHARS = EVM_ABI_WORD_BYTES * HEX_CHARS_PER_BYTE; +constexpr size_t EVM_SELECTOR_HEX_CHARS = EVM_SELECTOR_BYTES * HEX_CHARS_PER_BYTE; +/// Hex digits a `uint64` occupies at the low end of a word; a word with a +/// non-zero digit above them does not fit a `uint64`. +constexpr size_t UINT64_HEX_CHARS = sizeof(uint64_t) * HEX_CHARS_PER_BYTE; +constexpr int HEX_RADIX = 16; constexpr size_t MAX_ENVELOPE_HEX_CHARS = HEX_PREFIX_CHARS + OPP_MAX_ENVELOPE_BYTES * HEX_CHARS_PER_BYTE; constexpr size_t MAX_LATEST_OUTBOUND_RPC_BYTES = @@ -103,6 +161,11 @@ constexpr size_t MAX_LATEST_OUTBOUND_RPC_BYTES = constexpr size_t MAX_LATEST_OUTBOUND_RPC_HEX_CHARS = HEX_PREFIX_CHARS + MAX_LATEST_OUTBOUND_RPC_BYTES * HEX_CHARS_PER_BYTE; +/// Topic index of the first indexed argument in a receipt log (`topics[0]` is +/// the event signature). +constexpr size_t FIRST_INDEXED_TOPIC = 1; +constexpr size_t SECOND_INDEXED_TOPIC = 2; + /// Drop a leading `0x` / `0X` if present. Hex is not self-delimiting, so every /// comparison below works on the prefix-free form. std::string_view strip_hex_prefix(std::string_view value) { @@ -141,11 +204,15 @@ std::optional abi_uint_output(const fc::variant& value) { return std::nullopt; } +/// The keccak256 of a Solidity signature as prefix-free lower-case hex: the +/// event topic, or (its first four bytes) the function / error selector. +std::string signature_hash_hex(const char* signature) { + return fc::crypto::keccak256::hash(std::string(signature)).str(); +} + /// The four-byte selector of a Solidity function or error `signature`, as hex. std::string selector_hex(const char* signature) { - return fc::crypto::keccak256::hash(std::string(signature)) - .str() - .substr(0, EVM_SELECTOR_BYTES * HEX_CHARS_PER_BYTE); + return signature_hash_hex(signature).substr(0, EVM_SELECTOR_HEX_CHARS); } /// True when `abis` declares a function named `name`. @@ -155,6 +222,140 @@ bool has_function_abi(const std::vector& abis, std::string_v }); } +/// Hex normalised for comparison: lower-case with no `0x` prefix. +std::string normalized_hex(std::string_view value) { + std::string hex{strip_hex_prefix(value)}; + std::ranges::transform(hex, hex.begin(), [](unsigned char c) { return std::tolower(c); }); + return hex; +} + +/// A 32-byte word as the decoder renders it, normalised as by `normalized_hex`. +/// Empty when the value is not a string. +std::string normalized_word(const fc::variant& value) { + if (!value.is_string()) return {}; + return normalized_hex(value.as_string()); +} + +/// Whether a normalised word is the zero word (or empty — nothing recorded). +bool is_zero_word(std::string_view word) { + return std::ranges::all_of(word, [](char c) { return c == '0'; }); +} + +/// Whether `word` is exactly one ABI word of hex digits. +bool is_hex_word(std::string_view word) { + return word.size() == EVM_ABI_WORD_HEX_CHARS && + std::ranges::all_of(word, [](unsigned char c) { return std::isxdigit(c) != 0; }); +} + +/// The unsigned integer one normalised ABI word holds, or `std::nullopt` when +/// the word is malformed or does not fit a `uint64`. +std::optional word_to_uint(std::string_view word) { + if (!is_hex_word(word)) return std::nullopt; + const auto high = word.substr(0, EVM_ABI_WORD_HEX_CHARS - UINT64_HEX_CHARS); + if (!is_zero_word(high)) return std::nullopt; + try { + return std::stoull(std::string(word.substr(high.size())), nullptr, HEX_RADIX); + } catch (const std::exception&) { + return std::nullopt; + } +} + +/// The `n`-th ABI word of a receipt log's normalised `data`, or `std::nullopt` +/// when the data is shorter than that. +std::optional data_word(std::string_view data, size_t n) { + const size_t offset = n * EVM_ABI_WORD_HEX_CHARS; + if (data.size() < offset + EVM_ABI_WORD_HEX_CHARS) return std::nullopt; + return data.substr(offset, EVM_ABI_WORD_HEX_CHARS); +} + +/// The raw `eth_call` hex of one OPPInbound view, asserted to be a string. +std::string view_hex(const fc::variant& raw, std::string_view client_label, const char* view) { + FC_ASSERT(raw.is_string(), "outpost_ethereum_client[{}]: {} returned a non-string variant", client_label, view); + return raw.as_string(); +} + +/// `OPPInbound.nextEpochIndex()` at `block`. +uint32_t read_next_epoch_index(opp_inbound_contract_client& inbound, const eth::block_number_or_tag_t& block, + std::string_view client_label) { + const auto raw = view_hex(inbound.next_epoch_index(block), client_label, opp_inbound_abi_name::next_epoch_index); + // A single-output view decodes to the bare value. + const auto value = + abi_uint_output(eth::contract_decode_data(inbound.get_abi(opp_inbound_abi_name::next_epoch_index), raw)); + FC_ASSERT(value.has_value(), "outpost_ethereum_client[{}]: nextEpochIndex was not a parsable numeric output", + client_label); + return static_cast(*value); +} + +/// `OPPInbound.dispatchSpill(epoch_index)` at `block`, decoded. +/// +/// Fails closed on every field: an unparsable one must not be silently read +/// as "not tipped", which would send a fresh delivery into an epoch that has +/// already settled and be refused as a non-deliverer. +detail::dispatch_spill read_dispatch_spill(opp_inbound_contract_client& inbound, uint32_t epoch_index, + const eth::block_number_or_tag_t& block, std::string_view client_label) { + // `ethereum_contract_call_fn` binds its arguments as non-const lvalue + // references, so the epoch needs a named local. + uint32_t epoch_arg = epoch_index; + const auto raw = view_hex(inbound.dispatch_spill(block, epoch_arg), client_label, opp_inbound_abi_name::dispatch_spill); + const auto decoded = eth::contract_decode_data(inbound.get_abi(opp_inbound_abi_name::dispatch_spill), raw); + dlog("outpost_ethereum_client[{}]: dispatchSpill({}) decoded={}", client_label, epoch_index, + fc::json::to_string(decoded, fc::json::yield_function_t{})); + FC_ASSERT(decoded.is_object(), "outpost_ethereum_client[{}]: dispatchSpill result was not a variant object", + client_label); + + const auto& object = decoded.get_object(); + // The decoder hands a Solidity `bool` back as a native bool; anything else is a malformed read. + const auto read_bool = [&](const char* key) { + FC_ASSERT(object.contains(key), "outpost_ethereum_client[{}]: dispatchSpill result missing '{}'", client_label, + key); + FC_ASSERT(object[key].is_bool(), "outpost_ethereum_client[{}]: dispatchSpill '{}' was not a boolean output", + client_label, key); + return object[key].as_bool(); + }; + FC_ASSERT(object.contains(opp_inbound_abi::field::dispatched), + "outpost_ethereum_client[{}]: dispatchSpill result missing '{}'", client_label, + opp_inbound_abi::field::dispatched); + const auto dispatched = abi_uint_output(object[opp_inbound_abi::field::dispatched]); + FC_ASSERT(dispatched.has_value(), "outpost_ethereum_client[{}]: dispatchSpill '{}' was not a numeric output", + client_label, opp_inbound_abi::field::dispatched); + + detail::dispatch_spill spill; + spill.tipped = read_bool(opp_inbound_abi::field::tipped); + spill.dispatched = static_cast(*dispatched); + spill.complete = read_bool(opp_inbound_abi::field::complete); + spill.finalized = read_bool(opp_inbound_abi::field::finalized); + return spill; +} + +/// `OPPInbound.epochDeliveries(epoch_index, operator)` at `block`, normalised. +std::string read_own_digest(opp_inbound_contract_client& inbound, uint32_t epoch_index, + const std::string& operator_address, const eth::block_number_or_tag_t& block, + std::string_view client_label) { + uint32_t epoch_arg = epoch_index; + std::string operator_arg = operator_address; + const auto raw = view_hex(inbound.epoch_deliveries(block, epoch_arg, operator_arg), client_label, + opp_inbound_abi_name::epoch_deliveries); + return normalized_word(eth::contract_decode_data(inbound.get_abi(opp_inbound_abi_name::epoch_deliveries), raw)); +} + +/// `OPPInbound.pendingEpochHash()` at `block`, normalised. +std::string read_settled_digest(opp_inbound_contract_client& inbound, const eth::block_number_or_tag_t& block, + std::string_view client_label) { + const auto raw = view_hex(inbound.pending_epoch_hash(block), client_label, opp_inbound_abi_name::pending_epoch_hash); + return normalized_word(eth::contract_decode_data(inbound.get_abi(opp_inbound_abi_name::pending_epoch_hash), raw)); +} + +/// The block parameter for a read pinned to `block`. +eth::block_number_or_tag_t pinned(const fc::uint256& block) { + return eth::format_rpc_quantity(block); +} + +/// `json_rpc_error::data` as the revert hex it carries, or empty when the +/// node attached none. +std::string revert_hex(const fc::network::json_rpc::json_rpc_error& refusal) { + return refusal.data.is_string() ? refusal.data.as_string() : std::string{}; +} + } // namespace namespace outpost_ethereum_client_detail { @@ -165,79 +366,145 @@ bool same_evm_address(std::string_view lhs, std::string_view rhs) { return same_hex(left, strip_hex_prefix(rhs)); } -bool is_chunk_buffer_missing_revert(std::string_view revert_data, std::string_view owner_address) { - constexpr size_t selector_chars = EVM_SELECTOR_BYTES * HEX_CHARS_PER_BYTE; - constexpr size_t word_chars = EVM_ABI_WORD_BYTES * HEX_CHARS_PER_BYTE; - constexpr size_t address_chars = EVM_ADDRESS_BYTES * HEX_CHARS_PER_BYTE; +delivery_settlement classify_settlement(std::string_view own_digest, std::string_view settled_digest, + bool tipped) { + FC_ASSERT(is_hex_word(own_digest), "epochDeliveries did not return one 32-byte word: '{}'", own_digest); + FC_ASSERT(is_hex_word(settled_digest), "pendingEpochHash did not return one 32-byte word: '{}'", settled_digest); + if (is_zero_word(own_digest)) return delivery_settlement::never_delivered; + // Until the tip, `pendingEpochHash` is an earlier epoch's digest (or the + // zero word on a fresh outpost): nothing to compare this epoch's record to. + if (!tipped) return delivery_settlement::recorded; + return same_hex(own_digest, settled_digest) ? delivery_settlement::settled : delivery_settlement::divergent; +} - const auto data = strip_hex_prefix(revert_data); - // Selector plus exactly one address word. Requiring the exact length is no more brittle - // than the selector test: an error taking different arguments hashes differently. - if (data.size() != selector_chars + word_chars) return false; - - if (!same_hex(data.substr(0, selector_chars), selector_hex(chunk_buffer_missing_signature))) - return false; - - // The error's sole argument is the contract's `msg.sender`, so the word must be this - // relay's own signer, left-padded into 32 bytes. - const auto word = data.substr(selector_chars); - if (word.find_first_not_of('0') < word_chars - address_chars) return false; - return same_evm_address(word.substr(word_chars - address_chars), owner_address); -} - -chunk_resume_decision decide_chunk_resume(const envelope_chunk_state& staged, - std::string_view self_address, - uint32_t epoch_index, - uint16_t total_chunks, - uint32_t total_bytes) { - // An empty header (`totalChunks == SLOT_EMPTY`) is the reclaimable state the - // contract hands back for "never staged" and "already reset". Nothing to - // adopt; chunk 0 stages it. - if (staged.total_chunks == 0) { - return {chunk_resume_action::start_fresh, 0}; - } +bool majority_tip_reachable(const pending_consensus& consensus, uint64_t now_sec) { + if (consensus.group_size == 0) return false; + const uint64_t boundary = consensus.epoch_started_at + consensus.epoch_duration_sec; + // STRICT majority, as the contract computes it (`groupSize / 2 + 1`). + const uint32_t majority = consensus.group_size / 2 + 1; + return now_sec >= boundary && consensus.agreeing >= majority; +} - // A header owned by someone else is not ours to continue or discard: the - // view is owner-bound, so this can only mean the read was answered for a - // different signer. Start from 0 and let the contract's ownership guard - // arbitrate. - if (!same_evm_address(staged.owner, self_address)) { - return {chunk_resume_action::start_fresh, 0}; +delivery_action decide_delivery(uint32_t next_epoch_index, + uint32_t epoch_index, + const dispatch_spill& spill, + delivery_settlement settlement, + bool majority_reachable) { + // The outpost is past this epoch, or has closed it: every transaction + // would be a paid late no-op. `finalized` is checked as well as the epoch + // cursor because the two are written in one call and reads that race a + // just-mined block may see one before the other. + if (next_epoch_index > epoch_index || spill.finalized) { + return delivery_action::already_finalized; } - - // Our own header, but for a different epoch. On a path-2 consensus retry - // this is the signature of an epoch that advanced underneath us, so the - // caller confirms against `nextEpochIndex()` before spending gas on late - // no-op transactions. - if (staged.epoch_index != epoch_index) { - return {chunk_resume_action::confirm_epoch_advanced, 0}; + // The outpost has not reached this epoch: `epochIn` refuses it as + // non-sequential until the earlier one finalizes. + if (next_epoch_index < epoch_index) { + return delivery_action::outpost_behind; } + // Tipped but unfinished. The contract admits a continuation only from a + // relay whose recorded delivery IS the settled digest, so a relay that has + // not delivered, or delivered a minority envelope, has nothing to send. + if (spill.tipped) { + return settlement == delivery_settlement::settled ? delivery_action::continue_dispatch + : delivery_action::wait_for_deliverer; + } + if (settlement == delivery_settlement::never_delivered) return delivery_action::deliver; + // Recorded and untipped: the rest of the group has yet to deliver. A + // re-delivery re-runs the tip, which only settles anything once the + // contract's path-2 predicate holds. + return majority_reachable ? delivery_action::retry_consensus : delivery_action::await_peers; +} - // Our own CURRENT-epoch header whose declared shape belongs to a different - // envelope, or one claiming more stored chunks than a well-formed upload can - // hold (the final chunk is never stored, so `receivedChunks` tops out at - // `totalChunks - 1`). Either way the staged cells are unusable: discard, - // then upload from 0. - if (staged.total_chunks != total_chunks || staged.total_bytes != total_bytes || - staged.received_chunks >= total_chunks) { - return {chunk_resume_action::discard_and_restart, 0}; +bool advanced(const delivery_progress& before, const delivery_progress& after) { + if (after.next_epoch_index > before.next_epoch_index) return true; + if (after.spill.finalized && !before.spill.finalized) return true; + if (after.spill.complete && !before.spill.complete) return true; + if (after.spill.tipped && !before.spill.tipped) return true; + if (after.spill.dispatched > before.spill.dispatched) return true; + return is_zero_word(before.own_digest) && !is_zero_word(after.own_digest); +} + +std::optional classify_epoch_in_revert(int rpc_code, std::string_view revert_data) { + if (rpc_code != ethereum_execution_reverted_code) return std::nullopt; + const auto data = strip_hex_prefix(revert_data); + if (data.size() < EVM_SELECTOR_HEX_CHARS) return std::nullopt; + const auto selector = data.substr(0, EVM_SELECTOR_HEX_CHARS); + const auto argument_chars = data.size() - EVM_SELECTOR_HEX_CHARS; + // Exact shape: an error with other arguments hashes differently, and a + // payload that does not fit the error is not that error. + const auto is = [&](const char* signature, size_t words) { + return argument_chars == words * EVM_ABI_WORD_HEX_CHARS && same_hex(selector, selector_hex(signature)); + }; + if (is(dispatch_underfunded_signature, 2)) return epoch_in_revert::dispatch_underfunded; + if (is(handler_gas_exhausted_signature, 3)) return epoch_in_revert::handler_gas_exhausted; + if (is(non_sequential_epoch_signature, 2)) return epoch_in_revert::non_sequential_epoch; + if (is(operator_already_delivered_signature, 2)) return epoch_in_revert::operator_already_delivered; + if (is(not_active_operator_signature, 1)) return epoch_in_revert::not_active_operator; + if (is(digest_mismatch_signature, 2)) return epoch_in_revert::digest_mismatch; + return std::nullopt; +} + +delivery_receipt_summary summarize_delivery_receipt(const fc::variants& logs, std::string_view inbound_address, + uint32_t epoch_index, std::string_view operator_address) { + const auto delivery_topic = signature_hash_hex(epoch_delivery_event_signature); + const auto consensus_topic = signature_hash_hex(epoch_consensus_event_signature); + const auto progressed_topic = signature_hash_hex(epoch_dispatch_progressed_event_signature); + const auto complete_topic = signature_hash_hex(epoch_complete_event_signature); + + delivery_receipt_summary summary; + for (const auto& entry : logs) { + if (!entry.is_object()) continue; + const auto& log = entry.get_object(); + if (!log.contains(receipt_log_field::address) || !log[receipt_log_field::address].is_string()) continue; + if (!same_evm_address(log[receipt_log_field::address].as_string(), inbound_address)) continue; + if (!log.contains(receipt_log_field::topics) || !log[receipt_log_field::topics].is_array()) continue; + const auto& topics = log[receipt_log_field::topics].get_array(); + if (topics.empty() || !topics.front().is_string()) continue; + const auto topic = normalized_word(topics.front()); + const auto data = log.contains(receipt_log_field::data) ? normalized_word(log[receipt_log_field::data]) + : std::string{}; + // The epoch of an event that indexes it; `EpochComplete` carries its + // epoch in the data instead. + const auto indexed_epoch = [&]() -> std::optional { + if (topics.size() <= FIRST_INDEXED_TOPIC) return std::nullopt; + return word_to_uint(normalized_word(topics[FIRST_INDEXED_TOPIC])); + }; + + if (same_hex(topic, delivery_topic)) { + if (topics.size() <= SECOND_INDEXED_TOPIC) continue; + const auto operator_word = address_from_word(normalized_word(topics[SECOND_INDEXED_TOPIC])); + if (indexed_epoch() == epoch_index && operator_word && same_evm_address(*operator_word, operator_address)) { + summary.recorded = true; + } + } else if (same_hex(topic, consensus_topic)) { + if (indexed_epoch() == epoch_index) summary.tipped = true; + } else if (same_hex(topic, progressed_topic)) { + const auto dispatched = data_word(data, 0).and_then(word_to_uint); + if (indexed_epoch() == epoch_index && dispatched && *dispatched <= std::numeric_limits::max()) { + summary.dispatched = static_cast(*dispatched); + } + } else if (same_hex(topic, complete_topic)) { + if (data_word(data, 0).and_then(word_to_uint) == epoch_index) summary.finalized = true; + } } + return summary; +} - return {chunk_resume_action::resume, staged.received_chunks}; +std::string envelope_digest_word(const std::vector& envelope_bytes) { + const std::span bytes{reinterpret_cast(envelope_bytes.data()), + envelope_bytes.size()}; + return normalized_hex(fc::crypto::keccak256::hash(bytes).str()); } std::optional classify_realize_yield_revert(std::string_view revert_data) { - constexpr size_t selector_chars = EVM_SELECTOR_BYTES * HEX_CHARS_PER_BYTE; - constexpr size_t word_chars = EVM_ABI_WORD_BYTES * HEX_CHARS_PER_BYTE; - const auto data = strip_hex_prefix(revert_data); - if (data.size() < selector_chars) return std::nullopt; - const auto selector = data.substr(0, selector_chars); - const auto argument_chars = data.size() - selector_chars; - // Exact shape, as for `OPP_ChunkBufferMissing`: an error with other arguments hashes - // differently, and a payload that does not fit the error is not that error. + if (data.size() < EVM_SELECTOR_HEX_CHARS) return std::nullopt; + const auto selector = data.substr(0, EVM_SELECTOR_HEX_CHARS); + const auto argument_chars = data.size() - EVM_SELECTOR_HEX_CHARS; + // Exact shape, as for the `epochIn` refusals. const auto is = [&](const char* signature, size_t words) { - return argument_chars == words * word_chars && same_hex(selector, selector_hex(signature)); + return argument_chars == words * EVM_ABI_WORD_HEX_CHARS && same_hex(selector, selector_hex(signature)); }; if (is(no_yield_signature, 0)) return realize_yield_refusal::no_yield; if (is(yield_below_deadband_signature, 2)) return realize_yield_refusal::below_deadband; @@ -246,16 +513,25 @@ std::optional classify_realize_yield_revert(std::string_v return std::nullopt; } +std::optional count_envelope_attestations(const std::vector& envelope_bytes) { + sysio::opp::Envelope envelope; + if (!envelope.ParseFromArray(envelope_bytes.data(), static_cast(envelope_bytes.size()))) { + return std::nullopt; + } + uint64_t count = 0; + for (const auto& message : envelope.messages()) count += message.payload().attestations_size(); + if (count > std::numeric_limits::max()) return std::nullopt; + return static_cast(count); +} + std::optional address_from_word(std::string_view raw_hex) { - constexpr size_t word_chars = EVM_ABI_WORD_BYTES * HEX_CHARS_PER_BYTE; constexpr size_t address_chars = EVM_ADDRESS_BYTES * HEX_CHARS_PER_BYTE; const auto word = strip_hex_prefix(raw_hex); - if (word.size() != word_chars) return std::nullopt; - if (!std::ranges::all_of(word, [](unsigned char c) { return std::isxdigit(c) != 0; })) return std::nullopt; + if (!is_hex_word(word)) return std::nullopt; // An address word is left-padded with zeros; anything else in the pad is not an address. - if (word.find_first_not_of('0') < word_chars - address_chars) return std::nullopt; - return "0x" + std::string(word.substr(word_chars - address_chars)); + if (word.find_first_not_of('0') < EVM_ABI_WORD_HEX_CHARS - address_chars) return std::nullopt; + return "0x" + std::string(word.substr(EVM_ABI_WORD_HEX_CHARS - address_chars)); } bool is_routable_handler(std::string_view handler_address) { @@ -295,11 +571,34 @@ outpost_ethereum_client::outpost_ethereum_client( if (!_opp_inbound_addr.empty()) { _opp_inbound_client = _entry->client->get_contract(_opp_inbound_addr, _abis); + + // A ceiling below what a full-cap envelope needs cannot deliver, so it + // is refused here rather than discovered one stuck epoch at a time. + // The total-cost term only warns: it binds at the fee a call is sent + // at, and only the policy's maximum fee is known here. + const auto& policy = _entry->client->transaction_policy(); + const uint64_t ceiling = delivery_gas_ceiling(policy); + SYS_ASSERT(ceiling >= DELIVERY_MINIMUM_GAS_CEILING, chain::plugin_config_exception, + "outpost_ethereum_client[{}]: transaction policy max_gas_limit {} funds at most {} gas per " + "epochIn, below the {} a full-cap envelope needs to deliver, dispatch one attestation and " + "emit; raise max_gas_limit for client {}", + to_string(), policy.max_gas_limit.str(), ceiling, DELIVERY_MINIMUM_GAS_CEILING, + policy.client_id); + const uint64_t affordable_at_max_fee = delivery_gas_ceiling(policy, policy.max_fee_per_gas); + const bool total_cost_bounded = + policy.max_total_native_cost != eth::maximum_ethereum_transaction_policy_value(); + if (total_cost_bounded && affordable_at_max_fee < DELIVERY_MINIMUM_GAS_CEILING) { + wlog("outpost_ethereum_client[{}]: transaction policy max_total_native_cost {} wei funds only {} gas " + "per epochIn at max_fee_per_gas {} wei, below the {} a full-cap envelope needs; deliveries at " + "fees near the policy maximum will spill into more continuations than the budget sizes for", + to_string(), policy.max_total_native_cost.str(), affordable_at_max_fee, policy.max_fee_per_gas.str(), + DELIVERY_MINIMUM_GAS_CEILING); + } } - // Every OPPInbound staging header is bound to the delivering signer, so the - // chunk-resume read needs this relay's own address on every multi-chunk - // delivery. Derive it once here rather than per tick. + // OPPInbound keys recorded deliveries by signer, and a continuation is + // admitted only from a signer whose recorded digest is the settled one. + // Derive the address once here rather than per tick. _signer_address_hex = fc::to_hex(_entry->client->get_signer_address(), /*add_prefix=*/true); } @@ -313,179 +612,59 @@ outpost_ethereum_client::authenticated_caller_address() const { return {address.begin(), address.end()}; } -detail::envelope_chunk_state outpost_ethereum_client::read_envelope_chunk_state() { - detail::envelope_chunk_state state; - - const auto& abi = _opp_inbound_client->get_abi(opp_inbound_abi::view_envelope_chunk_state); - - // Read at `latest`. This is our OWN staging high-water mark, not delivered - // content WIRE commits consensus against, so the `finalized`-tag reasoning - // that governs `read_inbound_envelope` does not apply — reading it at - // `finalized` would re-send every chunk staged in an unfinalized block. - std::string operator_address = _signer_address_hex; - const auto raw_hex_var = - _opp_inbound_client->envelope_chunk_state(eth::block_tag_t::latest, operator_address); - if (!raw_hex_var.is_string()) { - wlog("outpost_ethereum_client[{}]: envelopeChunkState returned non-string variant", - to_string()); - return state; - } - const std::string raw_hex = raw_hex_var.as_string(); - if (raw_hex.empty() || raw_hex == "0x") { - dlog("outpost_ethereum_client[{}]: envelopeChunkState returned empty hex", to_string()); - return state; - } - - const auto decoded = eth::contract_decode_data(abi, raw_hex); - dlog("outpost_ethereum_client[{}]: envelopeChunkState decoded={}", - to_string(), fc::json::to_string(decoded, fc::json::yield_function_t{})); - if (!decoded.is_object()) { - wlog("outpost_ethereum_client[{}]: envelopeChunkState result was not a variant object", - to_string()); - return state; - } - - const auto& obj = decoded.get_object(); - for (const auto* key : {opp_inbound_abi::field::epoch_index, - opp_inbound_abi::field::owner, - opp_inbound_abi::field::total_chunks, - opp_inbound_abi::field::received_chunks, - opp_inbound_abi::field::total_bytes, - opp_inbound_abi::field::stored_bytes}) { - if (!obj.contains(key)) { - wlog("outpost_ethereum_client[{}]: envelopeChunkState result missing '{}' key", - to_string(), key); - return state; - } - } - - const auto& owner_var = obj[opp_inbound_abi::field::owner]; - if (!owner_var.is_string()) { - wlog("outpost_ethereum_client[{}]: envelopeChunkState owner was not a string", to_string()); - return state; - } - - // Fail closed on the fields the resume DECISION reads: an unparsable one - // must not be silently read as 0, which would fabricate an empty header. - const auto epoch_index = abi_uint_output(obj[opp_inbound_abi::field::epoch_index]); - const auto total_chunks = abi_uint_output(obj[opp_inbound_abi::field::total_chunks]); - const auto received_chunks = abi_uint_output(obj[opp_inbound_abi::field::received_chunks]); - const auto total_bytes = abi_uint_output(obj[opp_inbound_abi::field::total_bytes]); - if (!epoch_index || !total_chunks || !received_chunks || !total_bytes) { - wlog("outpost_ethereum_client[{}]: envelopeChunkState carried an unparsable numeric field", - to_string()); - return state; - } - - state.owner = owner_var.as_string(); - state.epoch_index = static_cast(*epoch_index); - state.total_chunks = static_cast(*total_chunks); - state.received_chunks = static_cast(*received_chunks); - state.total_bytes = static_cast(*total_bytes); - // `storedBytes` is a uint256 on chain and feeds diagnostics ONLY — it is not - // an input to `decide_chunk_resume`. Parse it separately with a 0 default so - // a value above UINT64_MAX cannot discard an otherwise-valid header and - // force a needless re-upload of chunks the outpost already holds. - state.stored_bytes = abi_uint_output(obj[opp_inbound_abi::field::stored_bytes]).value_or(0); - return state; -} - -std::optional outpost_ethereum_client::resume_chunk_index( - uint32_t epoch_index, - uint16_t total_chunks, - uint32_t total_bytes, - fc::time_point deadline_abs) { - throw_if_past_deadline(deadline_abs, OP_DELIVER_OUTBOUND); - const auto staged = read_envelope_chunk_state(); - const auto decision = detail::decide_chunk_resume( - staged, _signer_address_hex, epoch_index, total_chunks, total_bytes); - - dlog("outpost_ethereum_client[{}]: chunk resume epoch={} total_chunks={} action={} " - "staged(epoch={} owner={} chunks={} received={} bytes={})", - to_string(), epoch_index, total_chunks, - magic_enum::enum_name(decision.action), - staged.epoch_index, staged.owner, staged.total_chunks, - staged.received_chunks, staged.total_bytes); - - switch (decision.action) { - case detail::chunk_resume_action::start_fresh: - return uint16_t{0}; - - case detail::chunk_resume_action::resume: - ilog("outpost_ethereum_client[{}]: resuming epoch={} delivery at chunk {}/{} " - "from the on-chain staging high-water mark", - to_string(), epoch_index, decision.start_chunk, total_chunks); - return decision.start_chunk; - - case detail::chunk_resume_action::discard_and_restart: { - throw_if_past_deadline(deadline_abs, OP_DELIVER_OUTBOUND); - try { - const auto result = _opp_inbound_client->discard_envelope_chunks(); - ilog("outpost_ethereum_client[{}]: discarded a superseded epoch={} staging header " - "(staged chunks={} bytes={}) tx={}", - to_string(), epoch_index, staged.total_chunks, staged.total_bytes, - result.as_string()); - } catch (const fc::network::json_rpc::json_rpc_error& e) { - // Only one revert means the header is already clear, and it has to be identified by - // what the node returned, not by the exception's type or its code alone. A - // protocol-level JSON-RPC error says the node never ran the call at all; a code-3 - // revert carrying anything else — a wrong implementation behind the proxy, a - // reentrancy guard, an unrecognised selector — says it ran and failed for a reason - // this path cannot interpret. Either way the discard may not have happened, and - // tolerating it would upload against a staging header that is still stale. - if (e.code != ethereum_execution_reverted_code || - !detail::is_chunk_buffer_missing_revert(e.data.is_string() ? e.data.as_string() : std::string{}, - _signer_address_hex)) - throw; - // `OPP_ChunkBufferMissing(self)` from `eth_estimateGas` means the header is already - // clear, which is exactly the state we wanted. Tolerate it and upload from chunk 0. - // - // Deliberately NOT widened: a revert first observed at RECEIPT time - // (status=0) is raised by `wait_for_confirmation` as a generic - // `fc::exception` (`ethereum_client.cpp` "Ethereum tx {} reverted - // (status=0)"), and so are transport failures and deadline expiry. - // All three propagate and abandon the tick, which is benign: the next - // cron tick re-reads the staging header and resumes from whatever the - // chain actually holds. - dlog("outpost_ethereum_client[{}]: discardEnvelopeChunks reverted at estimate time, " - "treating the staging header as already clear: code={} message='{}'", - to_string(), e.code, e.top_message()); - } - return uint16_t{0}; - } - - case detail::chunk_resume_action::confirm_epoch_advanced: { - throw_if_past_deadline(deadline_abs, OP_DELIVER_OUTBOUND); - const auto next_var = _opp_inbound_client->next_epoch_index(eth::block_tag_t::latest); - // `nextEpochIndex()` declares a single output, and `contract_decode_data` - // returns a lone component directly rather than wrapping it. - const auto next_epoch = - next_var.is_string() - ? abi_uint_output(eth::contract_decode_data( - _opp_inbound_client->get_abi(opp_inbound_abi::view_next_epoch_index), - next_var.as_string())) - : std::optional{}; - if (!next_epoch) { - wlog("outpost_ethereum_client[{}]: could not read nextEpochIndex while resolving a " - "stale staging header; delivering epoch={} from chunk 0", - to_string(), epoch_index); - return uint16_t{0}; - } - if (*next_epoch > epoch_index) { - ilog("outpost_ethereum_client[{}]: skipping epoch={} delivery — the outpost has " - "already advanced to epoch {}", - to_string(), epoch_index, *next_epoch); - return std::nullopt; - } - return uint16_t{0}; - } - } +detail::delivery_progress outpost_ethereum_client::read_progress(uint32_t epoch_index, const fc::uint256& block) { + const auto at = pinned(block); + // A backend that has not seen `block` yet answers with a JSON-RPC error + // rather than stale state; that is the one failure worth waiting out. + // Everything else (a malformed response, the deadline) propagates. + return fc::task::retry_until( + OP_READ_PROGRESS, PINNED_READ_RETRY, [&]() -> std::optional { + try { + detail::delivery_progress progress; + progress.next_epoch_index = read_next_epoch_index(*_opp_inbound_client, at, to_string()); + progress.spill = read_dispatch_spill(*_opp_inbound_client, epoch_index, at, to_string()); + progress.own_digest = read_own_digest(*_opp_inbound_client, epoch_index, _signer_address_hex, at, to_string()); + progress.settled_digest = read_settled_digest(*_opp_inbound_client, at, to_string()); + dlog("outpost_ethereum_client[{}]: epoch={} at block {}: next_epoch={} spill(tipped={} dispatched={} " + "complete={} finalized={}) own={} settled={}", + to_string(), epoch_index, block.str(), progress.next_epoch_index, progress.spill.tipped, + progress.spill.dispatched, progress.spill.complete, progress.spill.finalized, progress.own_digest, + progress.settled_digest); + return progress; + } catch (const fc::network::json_rpc::json_rpc_error& refusal) { + dlog("outpost_ethereum_client[{}]: progress read at block {} refused ({}); retrying", to_string(), + block.str(), refusal.to_string()); + return std::nullopt; + } + }); +} - // Unreachable: the switch above covers every `chunk_resume_action`. An enum - // can still hold an out-of-range value, so the compiler requires a fallback; - // uploading from chunk 0 is the conservative one (the contract absorbs any - // replayed chunk as an idempotent no-op). - return uint16_t{0}; +detail::pending_consensus outpost_ethereum_client::read_pending_consensus(const std::string& digest_word, + const fc::uint256& block) { + std::string digest_arg = "0x" + digest_word; + const auto raw = view_hex(_opp_inbound_client->pending_consensus_for_digest(pinned(block), digest_arg), to_string(), + opp_inbound_abi_name::pending_consensus_for_digest); + const auto decoded = eth::contract_decode_data( + _opp_inbound_client->get_abi(opp_inbound_abi_name::pending_consensus_for_digest), raw); + FC_ASSERT(decoded.is_object(), + "outpost_ethereum_client[{}]: pendingConsensusForDigest result was not a variant object", to_string()); + const auto& object = decoded.get_object(); + const auto read = [&](const char* key) -> uint64_t { + FC_ASSERT(object.contains(key), "outpost_ethereum_client[{}]: pendingConsensusForDigest result missing '{}'", + to_string(), key); + const auto value = abi_uint_output(object[key]); + FC_ASSERT(value.has_value(), + "outpost_ethereum_client[{}]: pendingConsensusForDigest '{}' was not a numeric output", to_string(), + key); + return *value; + }; + detail::pending_consensus consensus; + consensus.next_epoch = static_cast(read(opp_inbound_abi::field::next_epoch)); + consensus.agreeing = static_cast(read(opp_inbound_abi::field::agreeing)); + consensus.group_size = static_cast(read(opp_inbound_abi::field::group_size)); + consensus.epoch_started_at = read(opp_inbound_abi::field::current_epoch_started_at); + consensus.epoch_duration_sec = static_cast(read(opp_inbound_abi::field::epoch_duration_sec)); + return consensus; } std::string outpost_ethereum_client::deliver_outbound_envelope( @@ -510,53 +689,276 @@ std::string outpost_ethereum_client::deliver_outbound_envelope( "of {} bytes; OPPInbound will reject it", to_string(), total, OPP_MAX_ENVELOPE_BYTES); - const uint16_t total_chunks = detail::chunk_count_for(total); - - // RESUME — multi-chunk deliveries only. A single-chunk envelope stages - // nothing on chain (OPPInbound's fast path assembles straight from - // calldata), so the dominant case pays no extra round trip. - uint16_t start_chunk = 0; - if (total_chunks > 1) { - const auto resume = - resume_chunk_index(epoch_index, total_chunks, static_cast(total), deadline_abs); - if (!resume) { - // The outpost advanced past this epoch; every chunk would be a paid - // late no-op. Returning empty marks the epoch handled for this tick. - return {}; - } - start_chunk = *resume; + // ONE call carries the whole envelope. Ethereum bounds a transaction by + // gas, not size, and a full-cap envelope is ~1.3 M gas of calldata against + // EIP-7825's 16.7 M cap; what may not fit is DISPATCH, which the contract + // spills across continuations of this same call. The envelope is + // re-supplied on every continuation — the outpost never stores its bytes. + const std::string envelope_hex = fc::to_hex(envelope_bytes.data(), static_cast(total)); + // The digest the outpost records for this delivery and settles consensus on. + const std::string local_digest = detail::envelope_digest_word(envelope_bytes); + + // Each call is funded to what it has left to carry (`delivery_gas_budget`), + // never to a fixed figure: the attestations the outpost's cursor says are + // still to dispatch, doubled for every call this tick that fell short. The + // ceiling is what the policy allows AT THE CURRENT FEE, so a budget it + // admits is a budget the policy's total-cost term will not refuse at signing. + const auto attestation_count = detail::count_envelope_attestations(envelope_bytes); + const auto gas_config = _entry->client->get_gas_config(); + const uint64_t gas_ceiling = + delivery_gas_ceiling(_entry->client->transaction_policy(), gas_config.max_fee_per_gas); + if (!attestation_count) { + wlog("outpost_ethereum_client[{}]: epoch={} envelope ({} bytes) does not decode as an OPP " + "envelope; funding delivery to the ceiling ({} gas)", + to_string(), epoch_index, total, gas_ceiling); } + // Every decision is taken from the outpost's state at ONE block: the head + // when the tick starts, then the block each confirmed call landed in. A + // read at `latest` could be served by a backend behind the block that just + // confirmed and report the state from before the call. + fc::uint256 pinned_block = _entry->client->get_block_number(); + + // A call that cannot be sent at its budget, or that was sent and moved + // nothing, is a stalled epoch: logged at error once per stall position, + // then at debug until the cursor moves. The throw makes the job retry. + const auto report_stall = [&](const detail::dispatch_spill& spill, const std::string& reason) { + const std::pair where{epoch_index, spill.dispatched}; + if (_reported_stall != where) { + _reported_stall = where; + elog("outpost_ethereum_client[{}]: epoch={} cannot advance past attestation {} at the ceiling ({} gas): " + "{}; the next tick retries, and the epoch stays open until the outpost accepts the call", + to_string(), epoch_index, spill.dispatched, gas_ceiling, reason); + } else { + dlog("outpost_ethereum_client[{}]: epoch={} still stalled at attestation {}: {}", to_string(), epoch_index, + spill.dispatched, reason); + } + SYS_THROW(chain::outpost_delivery_incomplete_exception, + "outpost_ethereum_client[{}]: epoch={} stalled at attestation {} at the ceiling ({} gas): {}", + to_string(), epoch_index, spill.dispatched, gas_ceiling, reason); + }; + // Sequential, receipt-confirmed submission — one transaction in flight at a // time, so the signer's nonce advances in lock-step and a mid-sequence - // failure simply abandons the tick. The next cron tick restarts from the - // on-chain high-water mark; the contract absorbs any replayed chunk as an - // idempotent no-op. - std::string last_tx; - for (uint16_t chunk = start_chunk; chunk < total_chunks; ++chunk) { + // failure simply abandons the tick. The next cron tick re-reads the + // outpost's cursor and picks up wherever the chain actually is. + std::string last_tx; + uint32_t sends = 0; + uint32_t escalations = 0; + uint32_t stale_refusals = 0; + uint64_t last_budget = 0; + std::optional before_last_send; + for (;;) { throw_if_past_deadline(deadline_abs, OP_DELIVER_OUTBOUND); - const size_t offset = static_cast(chunk) * ETHEREUM_MAX_CHUNK_BYTES; - const size_t length = std::min(ETHEREUM_MAX_CHUNK_BYTES, total - offset); - - // `ethereum_contract_tx_fn` binds every argument as a non-const lvalue - // reference, so each one needs a named local (the same constraint that - // preserves the RPC's chain-native byte encoding). - uint32_t epoch_arg = epoch_index; - uint16_t chunk_arg = chunk; - uint16_t chunks_arg = total_chunks; - uint32_t bytes_arg = static_cast(total); - std::string chunk_hex = - fc::to_hex(envelope_bytes.data() + offset, static_cast(length)); - - const auto result = - _opp_inbound_client->epoch_in(epoch_arg, chunk_arg, chunks_arg, bytes_arg, chunk_hex); - last_tx = result.as_string(); - ilog("outpost_ethereum_client[{}]: epochIn chunk sent epoch={} chunk={}/{} bytes={} tx={}", - to_string(), epoch_index, chunk, total_chunks, length, last_tx); - } + const auto progress = read_progress(epoch_index, pinned_block); + const auto settlement = + detail::classify_settlement(progress.own_digest, progress.settled_digest, progress.spill.tipped); + + // A recorded, untipped delivery is re-sent only when the outpost's own + // numbers say the re-send would tip it — and only when this tick has + // not just sent one, whose tip check ran in that very transaction. + bool majority_reachable = false; + std::optional consensus; + if (sends == 0 && settlement == detail::delivery_settlement::recorded && + progress.next_epoch_index == epoch_index) { + consensus = read_pending_consensus(progress.own_digest, pinned_block); + majority_reachable = + detail::majority_tip_reachable(*consensus, uint64_t{fc::time_point::now().sec_since_epoch()}); + } + const auto action = detail::decide_delivery(progress.next_epoch_index, epoch_index, progress.spill, + settlement, majority_reachable); + dlog("outpost_ethereum_client[{}]: epoch={} action={} next_epoch={} " + "spill(tipped={} dispatched={} complete={} finalized={}) settlement={} sends={}", + to_string(), epoch_index, magic_enum::enum_name(action), progress.next_epoch_index, + progress.spill.tipped, progress.spill.dispatched, progress.spill.complete, progress.spill.finalized, + magic_enum::enum_name(settlement), sends); + + switch (action) { + case detail::delivery_action::already_finalized: + if (last_tx.empty()) { + // Nothing sent: the outpost was already past this epoch when the + // tick began. An EMPTY tx id tells the job the epoch is handled. + ilog("outpost_ethereum_client[{}]: skipping epoch={} delivery — the outpost has " + "already finalized it (nextEpochIndex={})", + to_string(), epoch_index, progress.next_epoch_index); + } + return last_tx; + + case detail::delivery_action::outpost_behind: + // Not a handled epoch: the outpost will accept this delivery once it + // finalizes the earlier one, and the next tick must try again. + ilog("outpost_ethereum_client[{}]: epoch={} not delivered — the outpost is still on epoch {}; " + "retrying next tick", + to_string(), epoch_index, progress.next_epoch_index); + SYS_THROW(chain::outpost_delivery_incomplete_exception, + "outpost_ethereum_client[{}]: epoch={} not delivered — the outpost is still on epoch {}", + to_string(), epoch_index, progress.next_epoch_index); + + case detail::delivery_action::wait_for_deliverer: + if (settlement == detail::delivery_settlement::divergent) { + // The depot handed this relay different bytes than its peers. + wlog("outpost_ethereum_client[{}]: epoch={} tipped on digest {} but this relay delivered {}; its " + "deliverers carry the continuation (dispatched={} complete={})", + to_string(), epoch_index, progress.settled_digest, progress.own_digest, progress.spill.dispatched, + progress.spill.complete); + } else { + ilog("outpost_ethereum_client[{}]: epoch={} tipped on a digest this relay did not deliver " + "(dispatched={} complete={}); its deliverers carry the continuation", + to_string(), epoch_index, progress.spill.dispatched, progress.spill.complete); + } + return last_tx; + + case detail::delivery_action::await_peers: + if (sends == 0) { + // A consensus retry that arrives before the OUTPOST's boundary (the + // job gates it on the depot's, which leads the outpost's) is not a + // used-up retry: the epoch stays open so the job tries again next + // tick, until the boundary passes and the majority view decides. + const uint64_t boundary = consensus->epoch_started_at + consensus->epoch_duration_sec; + if (uint64_t{fc::time_point::now().sec_since_epoch()} < boundary) { + ilog("outpost_ethereum_client[{}]: epoch={} delivery recorded; the outpost's boundary ({}) has not " + "passed, so a re-delivery could not tip it yet; retrying next tick", + to_string(), epoch_index, boundary); + SYS_THROW(chain::outpost_delivery_incomplete_exception, + "outpost_ethereum_client[{}]: epoch={} recorded; outpost boundary {} not reached", + to_string(), epoch_index, boundary); + } + ilog("outpost_ethereum_client[{}]: epoch={} delivery already recorded; consensus not tipped " + "(agreeing={} group={} boundary={}); nothing to send until more of the group delivers", + to_string(), epoch_index, consensus->agreeing, consensus->group_size, boundary); + } else { + dlog("outpost_ethereum_client[{}]: epoch={} delivery recorded; consensus not yet tipped", to_string(), + epoch_index); + } + return last_tx; + + case detail::delivery_action::retry_consensus: + ilog("outpost_ethereum_client[{}]: epoch={} re-delivering to run the boundary tip (agreeing={} " + "group={} boundary={})", + to_string(), epoch_index, consensus->agreeing, consensus->group_size, + consensus->epoch_started_at + consensus->epoch_duration_sec); + break; + + case detail::delivery_action::continue_dispatch: + // The contract resumes only from the bytes that hash to the settled + // digest. An envelope that does not is not this epoch's — nothing + // to continue with, and no transaction to pay for finding that out. + if (!same_hex(local_digest, progress.settled_digest)) { + wlog("outpost_ethereum_client[{}]: epoch={} is tipped on digest {} but the envelope held hashes to " + "{}; this relay cannot continue it", + to_string(), epoch_index, progress.settled_digest, local_digest); + return last_tx; + } + break; + + case detail::delivery_action::deliver: + break; + } + + // The call this tick last paid for must have moved the outpost's cursor; + // one that did not was under-funded for the attestation at the cursor + // (the contract records a spill only when it dispatched at least one), + // and the next is funded higher. At the ceiling there is nothing higher. + if (before_last_send) { + if (!detail::advanced(*before_last_send, progress)) { + if (last_budget >= gas_ceiling) { + report_stall(progress.spill, "the last call advanced nothing"); + } + ++escalations; + } else if (action == detail::delivery_action::continue_dispatch) { + // A continuation behind a call this tick already sent means that + // call spilled: what it was funded with did not carry its work. + ++escalations; + } + } + if (sends >= MAX_CONTINUATIONS_PER_TICK) { + wlog("outpost_ethereum_client[{}]: epoch={} still open after {} calls in one tick (dispatched={} " + "complete={}); the next tick resumes from the outpost's cursor", + to_string(), epoch_index, sends, progress.spill.dispatched, progress.spill.complete); + SYS_THROW(chain::outpost_delivery_incomplete_exception, + "outpost_ethereum_client[{}]: epoch={} still open after {} calls in one tick", to_string(), + epoch_index, sends); + } - return last_tx; + throw_if_past_deadline(deadline_abs, OP_DELIVER_OUTBOUND); + // Before the tip nothing is dispatched, so every attestation is still + // to carry; after it the outpost's cursor says how many remain, and a + // complete dispatch leaves only the emit. + std::optional remaining; + if (attestation_count) { + const uint32_t done = progress.spill.tipped ? progress.spill.dispatched : 0; + remaining = progress.spill.complete ? 0 : (*attestation_count > done ? *attestation_count - done : 0); + } + uint64_t gas_budget = delivery_gas_budget(delivery_gas_request{ + .envelope_bytes = total, + .remaining_attestations = remaining, + .escalations = escalations, + .ceiling = gas_ceiling, + }); + dlog("outpost_ethereum_client[{}]: epoch={} funding epochIn with {} gas (remaining={} " + "escalations={} ceiling={})", + to_string(), epoch_index, gas_budget, + remaining ? std::to_string(*remaining) : "unknown", escalations, gas_ceiling); + + // The ABI arguments are bound as non-const lvalue references, so each + // one needs a named local. + uint32_t epoch_arg = epoch_index; + std::string payload_hex = envelope_hex; + epoch_in_receipt receipt; + try { + receipt = _opp_inbound_client->epoch_in(epoch_arg, payload_hex, gas_budget, deadline_abs); + } catch (const fc::network::json_rpc::json_rpc_error& refusal) { + // The node refused the call before anything was signed: the + // pre-flight estimate runs under the budget. + const auto revert = detail::classify_epoch_in_revert(refusal.code, revert_hex(refusal)); + if (!revert) throw; + switch (*revert) { + case detail::epoch_in_revert::dispatch_underfunded: + case detail::epoch_in_revert::handler_gas_exhausted: + // The budget's fault below the ceiling: retry once with everything + // the policy allows. At the ceiling it is the envelope's — an + // attestation no funding carries — and the tick reports it rather + // than spend the ceiling on finding out again. + if (gas_budget >= gas_ceiling) report_stall(progress.spill, refusal.to_string()); + wlog("outpost_ethereum_client[{}]: epoch={} refused at {} gas ({}); retrying at the " + "ceiling ({} gas)", + to_string(), epoch_index, gas_budget, magic_enum::enum_name(*revert), gas_ceiling); + gas_budget = gas_ceiling; + receipt = _opp_inbound_client->epoch_in(epoch_arg, payload_hex, gas_budget, deadline_abs); + break; + + case detail::epoch_in_revert::non_sequential_epoch: + case detail::epoch_in_revert::operator_already_delivered: + case detail::epoch_in_revert::not_active_operator: + case detail::epoch_in_revert::digest_mismatch: + // The outpost's state is not what the pinned read said — another + // relay's call landed since — or this relay has no standing for + // the call. Read again at the head and decide from that rather + // than guess; a refusal that survives fresh reads is reported. + if (++stale_refusals > MAX_STALE_REFUSALS_PER_TICK) throw; + ilog("outpost_ethereum_client[{}]: epoch={} refused ({}); re-reading the outpost at the head", + to_string(), epoch_index, magic_enum::enum_name(*revert)); + pinned_block = _entry->client->get_block_number(); + before_last_send.reset(); + continue; + } + } + + ++sends; + last_tx = receipt.tx_hash; + last_budget = gas_budget; + pinned_block = receipt.block_number; + before_last_send = progress; + + const auto summary = + detail::summarize_delivery_receipt(receipt.logs, _opp_inbound_addr, epoch_index, _signer_address_hex); + ilog("outpost_ethereum_client[{}]: epochIn {} epoch={} bytes={} gas={} block={} tx={} — recorded={} " + "tipped={} dispatched={} finalized={}", + to_string(), action == detail::delivery_action::continue_dispatch ? "continued" : "delivered", + epoch_index, total, gas_budget, receipt.block_number.str(), receipt.tx_hash, summary.recorded, + summary.tipped, summary.dispatched ? std::to_string(*summary.dispatched) : "-", summary.finalized); + } } std::vector outpost_ethereum_client::read_inbound_envelope( @@ -736,8 +1138,8 @@ void outpost_ethereum_client::bind_syndication_pool( } std::optional outpost_ethereum_client::discover_syndication_pool() { - // The routing table is configuration, not delivered content, so `latest` is right for the - // same reason it is for the staging-header read. + // The routing table is configuration, not delivered content, so `latest` is right: it is + // not state this relay is mid-way through changing, and a stale read costs one idle crank. uint16_t attestation_type = static_cast( magic_enum::enum_integer(sysio::opp::types::ATTESTATION_TYPE_DESYNDICATE_LIQ)); const auto raw = _opp_inbound_client->attestation_handlers(eth::block_tag_t::latest, attestation_type); @@ -795,7 +1197,7 @@ void outpost_ethereum_client::crank_outpost(uint32_t epoch_index, fc::microsecon // is the job's to log as a failed crank, exactly like a transport failure. const auto refusal = e.code == ethereum_execution_reverted_code - ? detail::classify_realize_yield_revert(e.data.is_string() ? e.data.as_string() : std::string{}) + ? detail::classify_realize_yield_revert(revert_hex(e)) : std::nullopt; if (!refusal) throw; switch (*refusal) { diff --git a/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp b/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp index 0f8eced462..a9594648f9 100644 --- a/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp +++ b/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp @@ -44,6 +44,13 @@ constexpr std::string_view rpc_url_placeholder_a = "RPC_URL_A"; constexpr std::string_view rpc_url_placeholder_b = "RPC_URL_B"; constexpr std::string_view test_chain_id_quantity = "0x7a69"; constexpr std::string_view ethereum_mainnet_chain_id_quantity = "0x1"; +/// Receipt deadline for a wrapper call whose receipt is already there. +constexpr int64_t receipt_deadline_seconds = 30; +/// Receipt deadline for a wrapper call whose receipt never appears: far below +/// the library's default retry budget, so a wait that ignored it would show. +constexpr int64_t short_receipt_deadline_ms = 300; +/// A receipt wait longer than the library's default retry budget. +constexpr int64_t long_receipt_wait_seconds = 90; using tcp = boost::asio::ip::tcp; /** One-shot JSON-RPC endpoint used to preserve coverage of the historical three-field CLI client form. */ @@ -126,36 +133,34 @@ ethabi::contract no_argument_function(std::string name) { }; } -/** Build a function ABI with one dynamic-bytes argument. */ -ethabi::contract bytes_argument_function(std::string name) { +/** Build the whole-envelope `epochIn(uint32,bytes)` function ABI. */ +ethabi::contract whole_envelope_epoch_in_function(std::string name) { return ethabi::contract{ .name = std::move(name), .type = ethabi::invoke_target_type::function, - .inputs = {ethabi::component_type{"data", ethabi::data_type::bytes}}, + .inputs = {ethabi::component_type{"epochIndex", ethabi::data_type::uint32}, + ethabi::component_type{"envelopeData", ethabi::data_type::bytes}}, .outputs = {}, }; } -/** Build the chunked `epochIn` function ABI. */ -ethabi::contract chunked_epoch_in_function(std::string name) { +/** Build the `epochDeliveries(uint32,address)` view ABI. */ +ethabi::contract epoch_deliveries_function(std::string name) { return ethabi::contract{ .name = std::move(name), .type = ethabi::invoke_target_type::function, .inputs = {ethabi::component_type{"epochIndex", ethabi::data_type::uint32}, - ethabi::component_type{"chunkIndex", ethabi::data_type::uint16}, - ethabi::component_type{"totalChunks", ethabi::data_type::uint16}, - ethabi::component_type{"totalBytes", ethabi::data_type::uint32}, - ethabi::component_type{"chunkData", ethabi::data_type::bytes}}, + ethabi::component_type{"operator_", ethabi::data_type::address}}, .outputs = {}, }; } -/** Build a function ABI with one address argument. */ -ethabi::contract address_argument_function(std::string name) { +/** Build the `pendingConsensusForDigest(bytes32)` view ABI. */ +ethabi::contract bytes32_argument_function(std::string name) { return ethabi::contract{ .name = std::move(name), .type = ethabi::invoke_target_type::function, - .inputs = {ethabi::component_type{"operator_", ethabi::data_type::address}}, + .inputs = {ethabi::component_type{"digest", ethabi::data_type::bytes32}}, .outputs = {}, }; } @@ -215,13 +220,18 @@ class recording_ethereum_client final : public ethereum_client { } if (method == "eth_estimateGas") { estimate_gas_params = params; - return fc::variant("0x342"); + return fc::variant(estimate_response); } if (method == "eth_getTransactionCount") return fc::variant("0x0"); if (method == "eth_sendRawTransaction") { ++broadcast_count; return fc::variant(std::string(transaction_hash)); } + if (method == "eth_getTransactionReceipt") { + ++receipt_reads; + return receipt_response; + } + if (method == "eth_blockNumber") return fc::variant(block_number_response); FC_THROW_EXCEPTION(fc::invalid_arg_exception, "unexpected fake RPC method {}", method); } @@ -232,6 +242,13 @@ class recording_ethereum_client final : public ethereum_client { std::vector methods; fc::variant estimate_gas_params; + /** What `eth_estimateGas` answers; 834 by default, which buffers to 1000. */ + std::string estimate_response = "0x342"; + /** What `eth_getTransactionReceipt` answers; null (not yet included) by default. */ + fc::variant receipt_response; + /** What `eth_blockNumber` answers. */ + std::string block_number_response = "0x10"; + size_t receipt_reads = 0; size_t broadcast_count = 0; }; @@ -481,6 +498,164 @@ BOOST_AUTO_TEST_CASE(default_transaction_uses_priority_fee_in_estimate_payload_a BOOST_CHECK_EQUAL(sign_count.load(), 0u); } +/// A gas-limit floor raises an under-estimate to the floor, leaves a larger +/// buffered estimate alone, and is refused — never clamped — above the policy +/// ceiling. +BOOST_AUTO_TEST_CASE(gas_limit_floor_raises_an_under_estimate_and_is_policy_bounded) { + std::atomic sign_count = 0; + const auto provider = make_recording_signer(sign_count); + auto policy = bounded_policy(); + policy.max_gas_limit = 2000; + auto client = std::make_shared(provider, policy); + + // The fake estimate is 834 -> buffered 1000. + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0).gas_limit, + 1000); + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 900).gas_limit, + 1000); + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 1500).gas_limit, + 1500); + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 2000).gas_limit, + 2000); + expect_policy_rejection([&] { + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 2001); + }); + BOOST_CHECK_EQUAL(sign_count.load(), 0u); + BOOST_CHECK_EQUAL(client->broadcast_count, 0u); +} + +/// A cap IS the limit: the transaction is sent with exactly it and the estimate +/// is only a pre-flight under it (its value is not an input, and the +/// buffered-estimate ceiling check does not apply). A cap below the floor or +/// above the policy ceiling is refused. +BOOST_AUTO_TEST_CASE(gas_limit_cap_is_the_limit_and_runs_the_estimate_under_it) { + std::atomic sign_count = 0; + const auto provider = make_recording_signer(sign_count); + auto policy = bounded_policy(); + policy.max_gas_limit = 2000; + auto client = std::make_shared(provider, policy); + + // No cap: the estimate payload carries no `gas`. + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 0); + BOOST_CHECK(!client->estimate_gas_params.get_array().front().get_object().contains("gas")); + + // The fake estimate is 834 -> buffered 1000; a cap of 900 is sent as 900 ... + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 900).gas_limit, + 900); + BOOST_CHECK_EQUAL( + client->estimate_gas_params.get_array().front().get_object()["gas"].as_string(), "0x384"); + // ... and a cap of 1200 as 1200: the estimate's value is not an input to the limit. + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 1200).gas_limit, + 1200); + BOOST_CHECK_EQUAL( + client->estimate_gas_params.get_array().front().get_object()["gas"].as_string(), "0x4b0"); + // Floor and cap equal: the transaction carries exactly that. + BOOST_CHECK_EQUAL( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 1500, 1500).gas_limit, + 1500); + // A buffered estimate over the policy is a rejection without a cap and + // irrelevant with one: 1000 buffers to 1200 > 1000, yet a 1000 cap is sent. + client->estimate_response = "0x3e8"; + policy.max_gas_limit = 1000; + auto tight_client = std::make_shared(provider, policy); + tight_client->estimate_response = "0x3e8"; + expect_policy_rejection([&] { + tight_client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 0); + }); + BOOST_CHECK_EQUAL( + tight_client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 1000).gas_limit, + 1000); + // A cap above the policy ceiling is a rejection, not a clamp. + expect_policy_rejection([&] { + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 0, 2001); + }); + // A cap below the floor is a caller error. + BOOST_CHECK_THROW( + client->create_default_tx(std::string(contract_address), no_argument_function("submit"), {}, 1500, 900), + fc::exception); + BOOST_CHECK_EQUAL(sign_count.load(), 0u); + BOOST_CHECK_EQUAL(client->broadcast_count, 0u); + BOOST_CHECK_EQUAL(tight_client->broadcast_count, 0u); +} + +/// The confirmation wait returns the receipt (block number and logs) so a +/// caller can pin its next reads to the block holding the effect; a reverted +/// receipt is an error, and the depth wait still applies. +BOOST_AUTO_TEST_CASE(wait_for_receipt_returns_the_confirmed_receipt) { + std::atomic sign_count = 0; + const auto provider = make_recording_signer(sign_count); + auto client = std::make_shared(provider, bounded_policy()); + const std::string hash{transaction_hash}; + const fc::variants logs{fc::mutable_variant_object("address", std::string(contract_address))}; + client->receipt_response = fc::mutable_variant_object("status", "0x1")("blockNumber", "0x10")("logs", logs); + + const auto receipt = client->wait_for_receipt(hash); + BOOST_REQUIRE(receipt.is_object()); + BOOST_CHECK_EQUAL(receipt.get_object()["blockNumber"].as_string(), "0x10"); + BOOST_CHECK_EQUAL(receipt.get_object()["logs"].get_array().size(), 1u); + BOOST_CHECK_EQUAL(client->receipt_reads, 1u); + BOOST_CHECK_EQUAL(client->wait_for_confirmation(hash), hash); + + // Two confirmations: the receipt's block plus one, which the fake chain has reached. + ethereum_confirm_options two_deep = ethereum_confirm_option_defaults; + two_deep.confirmations = 2; + client->block_number_response = "0x11"; + BOOST_CHECK(client->wait_for_receipt(hash, two_deep).is_object()); + BOOST_CHECK(std::ranges::count(client->methods, "eth_blockNumber") == 1); + + client->receipt_response = fc::mutable_variant_object("status", "0x0")("blockNumber", "0x10"); + BOOST_CHECK_THROW(client->wait_for_receipt(hash), fc::exception); + BOOST_CHECK_EQUAL(sign_count.load(), 0u); +} + +/// One `epochIn`'s options fund it to exactly its budget and wait for its +/// receipt as long as asked, past the library's default retry budget too. +BOOST_AUTO_TEST_CASE(delivery_confirm_options_carry_the_budget_and_the_receipt_wait) { + constexpr uint64_t budget = 1'234'567; + const auto options = sysio::delivery_confirm_options(budget, fc::seconds(long_receipt_wait_seconds)); + BOOST_CHECK_EQUAL(options.gas_limit_floor, budget); + BOOST_CHECK_EQUAL(options.gas_limit_cap, budget); + BOOST_CHECK(options.retry.total_timeout == fc::seconds(long_receipt_wait_seconds)); + BOOST_CHECK(options.retry.total_timeout > ethereum_confirm_option_defaults.retry.total_timeout); +} + +/// The wrapper stops waiting for a receipt at the given deadline, not at a +/// budget of its own. +BOOST_AUTO_TEST_CASE(epoch_in_waits_for_its_receipt_until_the_given_deadline) { + std::atomic sign_count = 0; + const auto provider = make_recording_signer(sign_count); + auto client = std::make_shared(provider, bounded_policy()); + sysio::opp_inbound_contract_client inbound{ + client, + std::string(contract_address), + {whole_envelope_epoch_in_function("epochIn"), no_argument_function("nextEpochIndex"), + uint32_argument_function("dispatchSpill"), epoch_deliveries_function("epochDeliveries"), + no_argument_function("pendingEpochHash"), bytes32_argument_function("pendingConsensusForDigest"), + uint16_argument_function("attestationHandlers")}, + }; + uint32_t epoch_index = 1; + std::string envelope = "01"; + + // No receipt ever appears. + const auto start = fc::time_point::now(); + BOOST_CHECK_THROW( + inbound.epoch_in(epoch_index, envelope, 999, start + fc::milliseconds(short_receipt_deadline_ms)), + fc::timeout_exception); + const auto waited = fc::time_point::now() - start; + + BOOST_CHECK(waited >= fc::milliseconds(short_receipt_deadline_ms)); + BOOST_CHECK(waited < ethereum_confirm_option_defaults.retry.total_timeout); + BOOST_CHECK_GE(client->receipt_reads, 1u); + // The call was sent; only the wait gave up. + BOOST_CHECK_EQUAL(client->broadcast_count, 1u); +} + BOOST_AUTO_TEST_CASE(all_typed_write_wrappers_share_the_policy_enforced_path) { std::atomic sign_count = 0; const auto provider = make_recording_signer(sign_count); @@ -491,21 +666,26 @@ BOOST_AUTO_TEST_CASE(all_typed_write_wrappers_share_the_policy_enforced_path) { sysio::opp_inbound_contract_client inbound{ client, std::string(contract_address), - {chunked_epoch_in_function("epochIn"), no_argument_function("nextEpochIndex"), - no_argument_function("discardEnvelopeChunks"), - address_argument_function("envelopeChunkState"), + {whole_envelope_epoch_in_function("epochIn"), no_argument_function("nextEpochIndex"), + uint32_argument_function("dispatchSpill"), epoch_deliveries_function("epochDeliveries"), + no_argument_function("pendingEpochHash"), bytes32_argument_function("pendingConsensusForDigest"), uint16_argument_function("attestationHandlers")}, }; - // Both OPPInbound write wrappers — the per-chunk delivery and the staged - // recovery — must be rejected by the policy before signing. + // OPPInbound's one write wrapper: a budget above the policy ceiling (999 + // here) is refused before signing; one at the ceiling is sent with exactly + // it and returns the confirmed receipt. uint32_t epoch_index = 1; - uint16_t chunk_index = 0; - uint16_t total_chunks = 1; - uint32_t total_bytes = 1; - std::string chunk = "01"; - expect_policy_rejection( - [&] { inbound.epoch_in(epoch_index, chunk_index, total_chunks, total_bytes, chunk); }); - expect_policy_rejection([&] { inbound.discard_envelope_chunks(); }); + std::string envelope = "01"; + const auto deadline = fc::time_point::now() + fc::seconds(receipt_deadline_seconds); + expect_policy_rejection([&] { inbound.epoch_in(epoch_index, envelope, 1000, deadline); }); + client->receipt_response = fc::mutable_variant_object("status", "0x1")("blockNumber", "0x10")("logs", fc::variants{}); + const auto receipt = inbound.epoch_in(epoch_index, envelope, 999, deadline); + BOOST_CHECK_EQUAL(receipt.block_number, fc::uint256{16}); + BOOST_CHECK(receipt.logs.empty()); + // The budget reached the pre-flight as the gas the call must fit in. + BOOST_CHECK_EQUAL(client->estimate_gas_params.get_array().front().get_object()["gas"].as_string(), "0x3e7"); + BOOST_CHECK_EQUAL(sign_count.load(), 1u); + BOOST_CHECK_EQUAL(client->broadcast_count, 1u); sysio::opp_contract_client opp{ client, @@ -522,8 +702,9 @@ BOOST_AUTO_TEST_CASE(all_typed_write_wrappers_share_the_policy_enforced_path) { }; expect_policy_rejection([&] { pool.realize_yield(); }); - BOOST_CHECK_EQUAL(sign_count.load(), 0u); - BOOST_CHECK_EQUAL(client->broadcast_count, 0u); + // Only the in-policy delivery reached the signer and the wire. + BOOST_CHECK_EQUAL(sign_count.load(), 1u); + BOOST_CHECK_EQUAL(client->broadcast_count, 1u); } BOOST_AUTO_TEST_CASE(plugin_startup_attaches_unified_client_policies) { diff --git a/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp b/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp index 93d5520281..58a0f81613 100644 --- a/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp +++ b/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -199,39 +200,46 @@ constexpr size_t rpc_length_oversized_envelope_bytes = sysio::OPP_MAX_ENVELOPE_B constexpr char malformed_envelope_byte = static_cast(0xff); constexpr char oversized_envelope_fill_byte = static_cast(0x01); -// ── Chunked `epochIn` fixtures ─────────────────────────────────────────── +// ── Whole-envelope `epochIn` fixtures ──────────────────────────────────── constexpr std::string_view test_opp_inbound_address = "e7f1725E7734CE288F8367e1Bb143E90bb3F0512"; -/// keccak256("epochIn(uint32,uint16,uint16,uint32,bytes)")[0..4). -constexpr std::string_view epoch_in_selector = "c3e558bc"; +/// keccak256("epochIn(uint32,bytes)")[0..4). +constexpr std::string_view epoch_in_selector = "004e356a"; +/// keccak256("epochIn(uint32,uint16,uint16,uint32,bytes)")[0..4) — the retired chunked form. +constexpr std::string_view retired_chunked_epoch_in_selector = "c3e558bc"; /// keccak256("epochIn(bytes)")[0..4) — the retired single-transaction form. constexpr std::string_view retired_single_bytes_epoch_in_selector = "cfae3118"; -constexpr size_t evm_address_bytes = 20; -constexpr size_t evm_address_hex_chars = evm_address_bytes * hex_chars_per_byte; -constexpr size_t epoch_in_input_count = 5; -/// A peer operator's address, used to prove an unowned staging header is ignored. -constexpr std::string_view foreign_operator_address = - "0x90F79bf6EB2c4f870365E785982E1f101E93b906"; -/// Envelope sizes exercising the chunk loop: three full chunks + a remainder, -/// and the exact platform cap (four full chunks). -constexpr size_t three_chunk_envelope_bytes = 2 * sysio::ETHEREUM_MAX_CHUNK_BYTES + 3'616; +constexpr size_t epoch_in_input_count = 2; +/// The digest consensus settled on, a digest that diverges from it, and the +/// zero word the outpost reports for a relay that never delivered. +constexpr std::string_view settled_digest_word = + "1111111111111111111111111111111111111111111111111111111111111111"; +constexpr std::string_view divergent_digest_word = + "2222222222222222222222222222222222222222222222222222222222222222"; +constexpr std::string_view zero_digest_word = + "0000000000000000000000000000000000000000000000000000000000000000"; +/// A mid-size envelope — large enough that a chunked relay would have split it. +constexpr size_t mid_envelope_bytes = 20'000; /// `derive_buffered_gas_limit` applies a ×1.2 buffer, so a policy pinned at /// EIP-7825's per-transaction cap rejects any estimate above 13 981 013. -constexpr uint64_t eip_7825_tx_gas_cap = 16'777'216; constexpr uint64_t under_cap_gas_estimate = 13'000'000; constexpr uint64_t under_cap_buffered_gas_limit = 15'600'000; constexpr uint64_t over_cap_gas_estimate = 14'000'000; +/// A policy ceiling below EIP-7825's cap, to prove the floor follows the policy. +constexpr uint64_t below_cap_policy_gas_limit = 9'000'000; + /// JSON-RPC error code anvil/geth report for a reverted call. constexpr int contract_revert_rpc_code = 3; - /// JSON-RPC parse error: the node never executed the call, so nothing on chain changed. constexpr int json_rpc_parse_error_code = -32700; - -/// `keccak256("OPP_ChunkBufferMissing(address)")[0..4]`, written out rather than recomputed so -/// these tests pin the client's own hashing of that signature instead of restating it. -constexpr std::string_view chunk_buffer_missing_selector = "dca69e67"; -/// A selector the client must not accept. `OPP_NoChunkSlot(address)` is a real neighbouring -/// error of the same shape, so a length or padding check alone would let it through. -constexpr std::string_view no_chunk_slot_selector = "9ecbc704"; +/// The server error geth answers a read pinned to a block it does not have yet +/// with ("header not found") — what a load-balanced backend behind the one that +/// confirmed the relay's transaction returns. +constexpr int unknown_block_rpc_code = -32000; +constexpr std::string_view unknown_block_rpc_message = "header not found"; + +/// A one-address custom error the client has no reading for — the same shape as +/// `AccessManagedUnauthorized(address)`, a different selector. +constexpr std::string_view unrelated_address_error_selector = "dca69e67"; /// Any address that is not this relay's signer, for the revert naming a different operator. constexpr std::string_view test_other_operator_address = "0x00000000000000000000000000000000deadbeef"; @@ -374,34 +382,20 @@ std::string encode_latest_outbound_result(uint32_t epoch, const std::vector script) + : ethereum_client(provider, std::variant{std::string(latest_slot_test_rpc_url)}, + std::move(policy)) + , _script(std::move(script)) {} + + fc::variant execute(const std::string& method, const fc::variant& params) override { + if (const auto scripted = answer(method)) return *scripted; + return ethereum_client::execute(method, params); + } + fc::variant execute_idempotent(const std::string& method, const fc::variant& params) override { + if (const auto scripted = answer(method)) return *scripted; + return ethereum_client::execute_idempotent(method, params); + } + +private: + std::optional answer(const std::string& method) { + if (method == "eth_blockNumber") { + ++_script->block_number_reads; + return fc::variant(format_rpc_quantity(_script->head_block)); + } + if (method == "eth_maxPriorityFeePerGas") return fc::variant(std::string(test_priority_fee_hex)); + if (method == "eth_getBlockByNumber") { + return fc::variant(fc::mutable_variant_object("baseFeePerGas", std::string(test_base_fee_hex))); + } + return std::nullopt; + } + + std::shared_ptr _script; +}; + +/// Encode the raw return bytes for the `pendingConsensusForDigest(bytes32)` +/// view — five static outputs, concatenated. +std::string encode_pending_consensus_result(uint32_t next_epoch, uint32_t agreeing, uint32_t group_size, + uint64_t epoch_started_at, uint32_t epoch_duration_sec) { + return std::string(hex_prefix) + abi_word(next_epoch) + abi_word(agreeing) + abi_word(group_size) + + abi_word(epoch_started_at) + abi_word(epoch_duration_sec); +} + +/// One receipt log entry as a node returns it. +fc::variant receipt_log(std::string_view address, const std::vector& topics, std::string data_hex) { + fc::variants topic_variants; + for (const auto& topic : topics) topic_variants.emplace_back(topic); + return fc::mutable_variant_object("address", std::string(address))("topics", std::move(topic_variants))( + "data", std::string(hex_prefix) + std::move(data_hex)); +} + +std::string prefixed(std::string_view word) { return std::string(hex_prefix) + std::string(word); } + +fc::variant epoch_delivery_log(std::string_view address, uint32_t epoch, std::string_view operator_hex, + std::string_view digest_word) { + return receipt_log(address, {prefixed(epoch_delivery_topic), prefixed(abi_word(epoch)), + encode_address_word(operator_hex)}, + std::string(digest_word)); +} +fc::variant epoch_consensus_log(std::string_view address, uint32_t epoch, std::string_view digest_word, + uint32_t count) { + return receipt_log(address, {prefixed(epoch_consensus_topic), prefixed(abi_word(epoch))}, + std::string(digest_word) + abi_word(count)); +} +fc::variant epoch_dispatch_progressed_log(std::string_view address, uint32_t epoch, uint16_t dispatched) { + return receipt_log(address, {prefixed(epoch_dispatch_progressed_topic), prefixed(abi_word(epoch))}, + abi_word(dispatched)); +} +fc::variant epoch_complete_log(std::string_view address, uint32_t epoch) { + return receipt_log(address, {prefixed(epoch_complete_topic)}, abi_word(epoch)); +} + +/// A node's refusal of `epochIn` with one of the contract's custom errors: the +/// execution-reverted code and the selector followed by `words` zero words. +fc::network::json_rpc::json_rpc_error epoch_in_refusal(std::string_view selector, size_t words) { + std::string data = prefixed(selector); + for (size_t i = 0; i < words; ++i) data += abi_word(0); + return fc::network::json_rpc::json_rpc_error(contract_revert_rpc_code, "execution reverted", fc::variant(data)); +} + +/// A node's refusal of a read pinned to a block it has not seen yet — a +/// load-balanced backend behind the one that confirmed the relay's call. +fc::network::json_rpc::json_rpc_error unknown_block_refusal() { + return fc::network::json_rpc::json_rpc_error(unknown_block_rpc_code, std::string(unknown_block_rpc_message)); +} + /// Harness binding a real `outpost_ethereum_client` to a stubbed OPPInbound -/// wrapper. +/// wrapper over a scripted chain. /// /// `ethereum_client::get_contract` caches one typed wrapper per address, so a /// wrapper materialized here is the SAME object the client resolves in its /// constructor — replacing its `std::function` members intercepts every RPC at -/// the typed callable boundary, exactly as the `getLatestOutboundEnvelope` -/// coverage above does. -struct chunked_delivery_fixture { - ~chunked_delivery_fixture() { +/// the typed callable boundary. The stubbed `epochIn` confirms in a fresh block +/// each time and answers with a receipt the case scripts; every view records +/// the block it was pinned to. +struct whole_envelope_delivery_fixture { + ~whole_envelope_delivery_fixture() { outpost.reset(); inbound.reset(); tester.reset(); @@ -435,31 +568,87 @@ struct chunked_delivery_fixture { } std::unique_ptr tester; + std::shared_ptr rpc; std::shared_ptr inbound; std::unique_ptr outpost; - std::vector chunk_calls; - size_t chunk_state_reads = 0; - size_t discard_calls = 0; - size_t next_epoch_reads = 0; - - /// Response the stubbed `envelopeChunkState` view returns; the default is - /// the all-zero (never staged) header. - std::string chunk_state_response = - encode_envelope_chunk_state_result(0, std::string(evm_address_hex_chars, '0'), 0, 0, 0, 0); - /// Response the stubbed `nextEpochIndex` view returns. - std::string next_epoch_index_response = encode_next_epoch_index_result(0); - /// When set, the stubbed `discardEnvelopeChunks` write throws it. - std::optional discard_failure; + /// Every `epochIn` that confirmed. + std::vector delivery_calls; + /// The budget of every `epochIn` attempt, refused ones included. + std::vector attempted_budgets; + /// The receipt deadline of every `epochIn` attempt, refused ones included. + std::vector attempted_deadlines; + /// Per attempt, in order, a refusal the stub throws instead of confirming; + /// attempts past the end confirm. + std::vector> refusals; + /// Per `dispatchSpill` read attempt, in order, an error the stub throws + /// instead of answering — a backend that cannot serve the pinned block yet. + /// Attempts past the end answer. A refused attempt consumes no response. + std::vector> spill_read_refusals; + size_t next_epoch_reads = 0; + size_t spill_reads = 0; + /// Every `dispatchSpill` read attempt, refused ones included. + size_t spill_read_attempts = 0; + size_t settlement_reads = 0; + size_t consensus_reads = 0; + /// The block parameter of every stubbed view read, in order. + std::vector read_blocks; + /// The digest every `pendingConsensusForDigest` read asked about. + std::vector consensus_digests; + + /// Responses the stubbed `nextEpochIndex` view returns, one per read; the + /// last one repeats. The default is an outpost on the delivered epoch. + std::vector next_epoch_index_responses{encode_next_epoch_index_result(test_wire_epoch)}; + /// Responses the stubbed `dispatchSpill` view returns, one per read in + /// order; the last one repeats. The default is the never-tipped cursor. + std::vector spill_responses{encode_dispatch_spill_result(false, 0, false, false)}; + /// Responses the stubbed `epochDeliveries(epoch, self)` view returns, one + /// per read; the last repeats. The default: nothing recorded on the first + /// read, a record on every read after it (the delivery landed). + std::vector own_delivery_responses{encode_word_result(zero_digest_word), + encode_word_result(settled_digest_word)}; + /// Response the stubbed `pendingEpochHash` view returns. + std::string pending_hash_response = encode_word_result(settled_digest_word); + /// Response the stubbed `pendingConsensusForDigest` view returns. The + /// default: this relay alone has delivered, the boundary long past. + std::string consensus_response = encode_pending_consensus_result(test_wire_epoch, 1, test_group_size, 0, 0); + /// Receipt logs per confirmed call, in order; the last repeats. The default + /// is the delivery record alone. `std::nullopt` entries use the default. + std::vector> receipt_logs; /// Wall-clock the FIRST stubbed `epochIn` burns before returning, standing in /// for a slow chain. Applied only to the first call so a deadline set below - /// it expires deterministically at the SECOND chunk's pre-flight check. - std::chrono::milliseconds first_chunk_delay{0}; + /// it expires deterministically at the next pre-flight check. + std::chrono::milliseconds first_call_delay{0}; + + template + static const T& response_at(const std::vector& responses, size_t n) { + return responses[std::min(n, responses.size() - 1)]; + } + + /// Script the outpost as settled on `envelope`'s own digest with this relay + /// as its deliverer — the shape every continuation case needs. The record + /// appears from the `recorded_from_read`-th read on: 0 when the tick finds + /// it already there (a resume), 1 when the tick's own delivery lands it. + void settle_on(const std::vector& envelope, size_t recorded_from_read = 0) { + const auto digest = sysio::outpost_ethereum_client_detail::envelope_digest_word(envelope); + own_delivery_responses.assign(recorded_from_read, encode_word_result(zero_digest_word)); + own_delivery_responses.push_back(encode_word_result(digest)); + pending_hash_response = encode_word_result(digest); + } + + /// The distinct blocks the view reads were pinned to, adjacent repeats collapsed. + std::vector blocks_read() const { + std::vector blocks; + for (const auto& block : read_blocks) { + if (blocks.empty() || blocks.back() != block) blocks.push_back(block); + } + return blocks; + } }; /// Build an envelope of exactly `size` bytes whose content varies per index, so -/// a mis-sliced chunk cannot accidentally compare equal to the right one. -std::vector make_chunked_envelope(size_t size) { +/// a mis-sliced payload cannot accidentally compare equal to the right one. +std::vector make_envelope(size_t size) { std::vector envelope(size); for (size_t i = 0; i < size; ++i) { envelope[i] = static_cast((i * 31 + 7) & 0xff); @@ -475,21 +664,48 @@ std::vector serialize_envelope(uint32_t epoch) { return {serialized.begin(), serialized.end()}; } -/// Stand up a `chunked_delivery_fixture`: a real `outpost_ethereum_client` -/// whose OPPInbound wrapper has every typed callable replaced by a recording -/// stub. The caller owns the returned fixture; the stubs capture it by -/// reference, so it must not be moved after this returns. +/// Serialize a protobuf envelope whose one message carries `attestations` +/// attestations of `payload_bytes` each — the shape the delivery budget is +/// sized from. +std::vector serialize_envelope_with_attestations(uint32_t epoch, uint32_t attestations, + size_t payload_bytes) { + sysio::opp::Envelope envelope; + envelope.set_epoch_index(epoch); + auto* payload = envelope.add_messages()->mutable_payload(); + for (uint32_t i = 0; i < attestations; ++i) { + auto* entry = payload->add_attestations(); + entry->set_data(std::string(payload_bytes, static_cast('a' + (i % 26)))); + entry->set_data_size(static_cast(payload_bytes)); + } + const auto serialized = envelope.SerializeAsString(); + return {serialized.begin(), serialized.end()}; +} + /// The app, signer, chain connection and client entry every relay fixture /// stands on. The connection points at a port nothing listens on, so a wrapper -/// a case forgot to stub fails loudly instead of dialing anything. +/// a case forgot to stub fails loudly instead of dialing anything; the few +/// chain-level RPCs the relay issues itself are answered by `rpc`. struct relay_test_stack { std::unique_ptr tester; fc::crypto::signature_provider_ptr sig_provider; + std::shared_ptr rpc; ethereum_client_ptr eth_client; std::shared_ptr entry; }; -relay_test_stack create_relay_test_stack() { +/// The maximum policy, with `max_gas_limit` overridden when a case needs a ceiling. +ethereum_transaction_policy relay_test_policy(fc::uint256 max_gas_limit = maximum_ethereum_transaction_policy_value()) { + return ethereum_transaction_policy{ + .client_id = std::string(latest_slot_test_entry_id), + .chain_id = test_evm_chain_id, + .max_priority_fee_per_gas = maximum_ethereum_transaction_policy_value(), + .max_fee_per_gas = maximum_ethereum_transaction_policy_value(), + .max_gas_limit = max_gas_limit, + .max_total_native_cost = maximum_ethereum_transaction_policy_value(), + }; +} + +relay_test_stack create_relay_test_stack(ethereum_transaction_policy transaction_policy = relay_test_policy()) { relay_test_stack stack; stack.tester = create_app(); @@ -500,18 +716,9 @@ relay_test_stack create_relay_test_stack() { std::string(latest_slot_test_public_key), to_private_key_spec(std::string(latest_slot_test_private_key))); - ethereum_transaction_policy transaction_policy{ - .client_id = std::string(latest_slot_test_entry_id), - .chain_id = test_evm_chain_id, - .max_priority_fee_per_gas = maximum_ethereum_transaction_policy_value(), - .max_fee_per_gas = maximum_ethereum_transaction_policy_value(), - .max_gas_limit = maximum_ethereum_transaction_policy_value(), - .max_total_native_cost = maximum_ethereum_transaction_policy_value(), - }; - stack.eth_client = std::make_shared( - stack.sig_provider, - std::variant{std::string(latest_slot_test_rpc_url)}, - std::move(transaction_policy)); + stack.rpc = std::make_shared(); + stack.eth_client = std::make_shared(stack.sig_provider, std::move(transaction_policy), + stack.rpc); stack.entry = std::make_shared(); stack.entry->id = latest_slot_test_entry_id; @@ -521,10 +728,13 @@ relay_test_stack create_relay_test_stack() { return stack; } -std::unique_ptr create_chunked_delivery_fixture() { - auto fixture = std::make_unique(); +/// Stand up a `whole_envelope_delivery_fixture`. The stubs capture it by +/// reference, so it must not be moved after this returns. +std::unique_ptr create_whole_envelope_delivery_fixture() { + auto fixture = std::make_unique(); auto stack = create_relay_test_stack(); fixture->tester = std::move(stack.tester); + fixture->rpc = stack.rpc; auto eth_client = stack.eth_client; auto abis = load_abi_fixture(opp_inbound_abi_fixture); @@ -534,40 +744,72 @@ std::unique_ptr create_chunked_delivery_fixture() { BOOST_REQUIRE(fixture->inbound); auto* raw = fixture.get(); - raw->inbound->epoch_in = [raw](uint32_t& epoch_index, - uint16_t& chunk_index, - uint16_t& total_chunks, - uint32_t& total_bytes, - std::string& chunk_hex) -> fc::variant { - raw->chunk_calls.push_back( - observed_chunk_call{epoch_index, chunk_index, total_chunks, total_bytes, chunk_hex}); - if (raw->chunk_calls.size() == 1 && raw->first_chunk_delay.count() > 0) { - std::this_thread::sleep_for(raw->first_chunk_delay); + raw->inbound->epoch_in = [raw, inbound_address](uint32_t& epoch_index, std::string& envelope_hex, + uint64_t gas_budget, + fc::time_point deadline) -> sysio::epoch_in_receipt { + const size_t attempt = raw->attempted_budgets.size(); + raw->attempted_budgets.push_back(gas_budget); + raw->attempted_deadlines.push_back(deadline); + if (attempt < raw->refusals.size() && raw->refusals[attempt]) throw *raw->refusals[attempt]; + + raw->delivery_calls.push_back(observed_delivery_call{epoch_index, envelope_hex, gas_budget}); + if (raw->delivery_calls.size() == 1 && raw->first_call_delay.count() > 0) { + std::this_thread::sleep_for(raw->first_call_delay); } - return fc::variant(std::string(hex_prefix) + abi_word(raw->chunk_calls.size())); + const size_t call = raw->delivery_calls.size(); + sysio::epoch_in_receipt receipt; + receipt.tx_hash = std::string(hex_prefix) + abi_word(call); + receipt.block_number = fc::uint256{test_head_block + call}; + const auto scripted = raw->receipt_logs.empty() + ? std::nullopt + : whole_envelope_delivery_fixture::response_at(raw->receipt_logs, call - 1); + receipt.logs = scripted ? *scripted + : fc::variants{epoch_delivery_log(inbound_address, epoch_index, + raw->outpost->signer_address_hex(), + settled_digest_word)}; + return receipt; }; - raw->inbound->envelope_chunk_state = - [raw](const block_number_or_tag_t& block, std::string& operator_address) -> fc::variant { - // The resume read is our OWN staging high-water mark, so it must be - // taken at `latest` — reading it at `finalized` would replay chunks - // staged in unfinalized blocks on every tick. - BOOST_CHECK(std::holds_alternative(block)); - BOOST_CHECK(std::get(block) == block_tag_t::latest); - BOOST_CHECK(!operator_address.empty()); - ++raw->chunk_state_reads; - return fc::variant(raw->chunk_state_response); - }; - raw->inbound->discard_envelope_chunks = [raw]() -> fc::variant { - ++raw->discard_calls; - if (raw->discard_failure) throw *raw->discard_failure; - return fc::variant(std::string(hex_prefix) + abi_word(0)); + // Every bookkeeping read is pinned to a block number — the head the tick + // started at, or the block the last call confirmed in — never a tag. + const auto note_pinned = [raw](const block_number_or_tag_t& block) { + BOOST_REQUIRE(std::holds_alternative(block)); + raw->read_blocks.push_back(std::get(block)); + }; + raw->inbound->next_epoch_index = [raw, note_pinned](const block_number_or_tag_t& block) -> fc::variant { + note_pinned(block); + return fc::variant( + whole_envelope_delivery_fixture::response_at(raw->next_epoch_index_responses, raw->next_epoch_reads++)); }; - raw->inbound->next_epoch_index = [raw](const block_number_or_tag_t& block) -> fc::variant { - BOOST_CHECK(std::holds_alternative(block)); - BOOST_CHECK(std::get(block) == block_tag_t::latest); - ++raw->next_epoch_reads; - return fc::variant(raw->next_epoch_index_response); + raw->inbound->dispatch_spill = + [raw, note_pinned](const block_number_or_tag_t& block, uint32_t& epoch_index) -> fc::variant { + note_pinned(block); + BOOST_CHECK_EQUAL(epoch_index, test_wire_epoch); + const size_t attempt = raw->spill_read_attempts++; + if (attempt < raw->spill_read_refusals.size() && raw->spill_read_refusals[attempt]) { + throw *raw->spill_read_refusals[attempt]; + } + return fc::variant(whole_envelope_delivery_fixture::response_at(raw->spill_responses, raw->spill_reads++)); + }; + raw->inbound->epoch_deliveries = + [raw, note_pinned](const block_number_or_tag_t& block, uint32_t& epoch_index, + std::string& operator_address) -> fc::variant { + note_pinned(block); + BOOST_CHECK_EQUAL(epoch_index, test_wire_epoch); + BOOST_CHECK_EQUAL(operator_address, raw->outpost->signer_address_hex()); + return fc::variant( + whole_envelope_delivery_fixture::response_at(raw->own_delivery_responses, raw->settlement_reads++)); + }; + raw->inbound->pending_epoch_hash = [raw, note_pinned](const block_number_or_tag_t& block) -> fc::variant { + note_pinned(block); + return fc::variant(raw->pending_hash_response); }; + raw->inbound->pending_consensus_for_digest = + [raw, note_pinned](const block_number_or_tag_t& block, std::string& digest) -> fc::variant { + note_pinned(block); + ++raw->consensus_reads; + raw->consensus_digests.push_back(digest); + return fc::variant(raw->consensus_response); + }; fixture->outpost = std::make_unique( stack.entry, @@ -579,35 +821,23 @@ std::unique_ptr create_chunked_delivery_fixture() { return fixture; } -/// Assert that `calls` covers `[first_chunk, total_chunks)` of `envelope` with -/// exact slices: every non-final chunk is exactly `ETHEREUM_MAX_CHUNK_BYTES` -/// and the final one carries the remainder. -void check_chunk_call_sequence(const std::vector& calls, - const std::vector& envelope, - uint32_t epoch_index, - uint16_t first_chunk) { - const auto total_chunks = sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()); - BOOST_REQUIRE_EQUAL(calls.size(), static_cast(total_chunks - first_chunk)); - - for (size_t i = 0; i < calls.size(); ++i) { - const auto& call = calls[i]; - const auto chunk = static_cast(first_chunk + i); +/// Assert that every observed `epochIn` carried `envelope` whole, addressed to +/// `epoch_index`. +void check_delivery_calls(const std::vector& calls, + const std::vector& envelope, + uint32_t epoch_index, + size_t expected_calls) { + BOOST_REQUIRE_EQUAL(calls.size(), expected_calls); + const auto expected_hex = fc::to_hex(envelope.data(), static_cast(envelope.size())); + for (const auto& call : calls) { BOOST_CHECK_EQUAL(call.epoch_index, epoch_index); - BOOST_CHECK_EQUAL(call.chunk_index, chunk); - BOOST_CHECK_EQUAL(call.total_chunks, total_chunks); - BOOST_CHECK_EQUAL(call.total_bytes, static_cast(envelope.size())); - - const size_t offset = static_cast(chunk) * sysio::ETHEREUM_MAX_CHUNK_BYTES; - const size_t length = - std::min(sysio::ETHEREUM_MAX_CHUNK_BYTES, envelope.size() - offset); - if (chunk + 1 < total_chunks) { - BOOST_CHECK_EQUAL(length, sysio::ETHEREUM_MAX_CHUNK_BYTES); - } - BOOST_CHECK_EQUAL(call.chunk_hex, - fc::to_hex(envelope.data() + offset, static_cast(length))); + BOOST_CHECK_EQUAL(call.envelope_hex, expected_hex); } } +/// The block parameter a read pinned to `block` carries. +std::string pinned_block_hex(uint64_t block) { return format_rpc_quantity(fc::uint256{block}); } + // ── `crank_outpost` fixtures ───────────────────────────────────────────── constexpr std::string_view syndication_pool_abi_fixture = "ethereum-abi-syndication-pool.json"; constexpr std::string_view zero_evm_address = "0x0000000000000000000000000000000000000000"; @@ -616,7 +846,7 @@ constexpr std::string_view attestation_blackhole_address = "0x000000000000000000 constexpr std::string_view test_syndication_pool_address = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"; constexpr std::string_view test_moved_syndication_pool_address = "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9"; /// `keccak256("WIRE_NoYield()")[0..4]` and the pool's other two refusals, written out so these -/// tests pin the client's own hashing of the signatures (as `chunk_buffer_missing_selector` does). +/// tests pin the client's own hashing of the signatures rather than restating it. constexpr std::string_view no_yield_selector = "053716d1"; constexpr std::string_view yield_below_deadband_selector = "45441430"; constexpr std::string_view pool_underbacked_selector = "358cc7e9"; @@ -628,14 +858,6 @@ constexpr std::string_view access_managed_unauthorized_selector = "068ca9d8"; constexpr uint64_t test_yield_delta = 5; constexpr uint64_t test_yield_deadband = 10; -/// ABI-encode one address as the single word an `address` getter returns. -std::string encode_address_word(std::string_view address_hex) { - std::string_view address = address_hex; - if (address.starts_with("0x") || address.starts_with("0X")) address.remove_prefix(2); - return std::string(hex_prefix) + std::string(evm_abi_word_hex_chars - address.size(), '0') + - std::string(address); -} - /// ABI-encode a two-`uint64` custom error the way a node returns it in `error.data`. std::string encode_two_word_revert(std::string_view selector, uint64_t first, uint64_t second) { return std::string(hex_prefix) + std::string(selector) + abi_word(first) + abi_word(second); @@ -959,23 +1181,32 @@ BOOST_AUTO_TEST_CASE(opp_inbound_contract_client_construction) try { BOOST_CHECK(!abis.empty()); // Every ABI entry the typed `opp_inbound_contract_client` binds at - // construction: the chunked write, the owner-bound resume read, the - // recovery write, and the epoch view the resume path falls back on. - bool has_epoch_in = false, has_next_epoch = false; + // construction: the whole-envelope write and the five views the + // deliver-or-continue decision reads — and none of the retired staged-chunk surface. + bool has_epoch_in = false, has_next_epoch = false, has_spill = false; + bool has_deliveries = false, has_pending_hash = false, has_pending_consensus = false; bool has_discard = false, has_chunk_state = false; for (auto& c : abis) { if (c.name == "epochIn") has_epoch_in = true; if (c.name == "nextEpochIndex") has_next_epoch = true; + if (c.name == "dispatchSpill") has_spill = true; + if (c.name == "epochDeliveries") has_deliveries = true; + if (c.name == "pendingEpochHash") has_pending_hash = true; + if (c.name == "pendingConsensusForDigest") has_pending_consensus = true; if (c.name == "discardEnvelopeChunks") has_discard = true; if (c.name == "envelopeChunkState") has_chunk_state = true; } BOOST_CHECK(has_epoch_in); BOOST_CHECK(has_next_epoch); - BOOST_CHECK(has_discard); - BOOST_CHECK(has_chunk_state); + BOOST_CHECK(has_spill); + BOOST_CHECK(has_deliveries); + BOOST_CHECK(has_pending_hash); + BOOST_CHECK(has_pending_consensus); + BOOST_CHECK(!has_discard); + BOOST_CHECK(!has_chunk_state); } FC_LOG_AND_RETHROW(); -BOOST_AUTO_TEST_CASE(epoch_in_abi_encoding_with_chunk_params) try { +BOOST_AUTO_TEST_CASE(epoch_in_abi_encoding_whole_envelope) try { auto abis = load_abi_fixture(opp_inbound_abi_fixture); // Find the epochIn ABI entry @@ -985,30 +1216,23 @@ BOOST_AUTO_TEST_CASE(epoch_in_abi_encoding_with_chunk_params) try { } BOOST_REQUIRE(epoch_in_abi != nullptr); - // Chunked delivery: (uint32 epochIndex, uint16 chunkIndex, uint16 totalChunks, - // uint32 totalBytes, bytes chunkData) + // Whole-envelope delivery: (uint32 epochIndex, bytes envelopeData). BOOST_REQUIRE_EQUAL(epoch_in_abi->inputs.size(), epoch_in_input_count); BOOST_CHECK(epoch_in_abi->inputs[0].type == eth::abi::data_type::uint32); - BOOST_CHECK(epoch_in_abi->inputs[1].type == eth::abi::data_type::uint16); - BOOST_CHECK(epoch_in_abi->inputs[2].type == eth::abi::data_type::uint16); - BOOST_CHECK(epoch_in_abi->inputs[3].type == eth::abi::data_type::uint32); - BOOST_CHECK(epoch_in_abi->inputs[4].type == eth::abi::data_type::bytes); + BOOST_CHECK(epoch_in_abi->inputs[1].type == eth::abi::data_type::bytes); - // Encode the five chunk params — this is what the batch operator now does. - std::string test_chunk_hex = "120c0a040800100012040800100028deeef5ce06300138"; + // Encode the two params — this is what the batch operator does. + std::string test_envelope_hex = "120c0a040800100012040800100028deeef5ce06300138"; auto encoded = contract_encode_data( *epoch_in_abi, - std::vector{fc::variant(uint64_t{test_wire_epoch}), - fc::variant(uint64_t{0}), - fc::variant(uint64_t{1}), - fc::variant(uint64_t{test_chunk_hex.size() / hex_chars_per_byte}), - fc::variant(test_chunk_hex)}); + std::vector{fc::variant(uint64_t{test_wire_epoch}), fc::variant(test_envelope_hex)}); BOOST_CHECK(!encoded.empty()); - // keccak256("epochIn(uint32,uint16,uint16,uint32,bytes)")[0..4) — the old - // single-`bytes` selector (0xcfae3118) is gone with the old signature. + // Neither retired form's selector survives. BOOST_CHECK_EQUAL(encoded.substr(0, evm_function_selector_hex_chars), std::string(epoch_in_selector)); + BOOST_CHECK(encoded.substr(0, evm_function_selector_hex_chars) != + std::string(retired_chunked_epoch_in_selector)); BOOST_CHECK(encoded.substr(0, evm_function_selector_hex_chars) != std::string(retired_single_bytes_epoch_in_selector)); @@ -1258,135 +1482,346 @@ BOOST_AUTO_TEST_CASE(read_emitted_envelope_digest_reads_the_outpost_record) try } FC_LOG_AND_RETHROW(); // --------------------------------------------------------------------------- -// Chunked WIRE -> Ethereum envelope delivery +// Whole-envelope WIRE -> Ethereum delivery // --------------------------------------------------------------------------- -/// Pin the compiled chunk constants and the ceil-division that derives -/// `totalChunks`. These values are mirrored in wire-ethereum's -/// `OPPCommon.sol`; a silent drift on either side breaks every delivery, so -/// both sides carry an equivalent pin. -BOOST_AUTO_TEST_CASE(envelope_chunk_count_math) try { - namespace chunking = sysio::outpost_ethereum_client_detail; - - BOOST_CHECK_EQUAL(sysio::ETHEREUM_MAX_CHUNK_BYTES, 8'192u); - // Word-aligned so OPPInbound's staging-cell writes stay whole-word. - BOOST_CHECK_EQUAL(sysio::ETHEREUM_MAX_CHUNK_BYTES % evm_abi_word_bytes, 0u); - BOOST_CHECK_EQUAL(sysio::OPP_MAX_ENVELOPE_BYTES, 32'768u); - - BOOST_CHECK_EQUAL(chunking::chunk_count_for(1), 1u); - BOOST_CHECK_EQUAL(chunking::chunk_count_for(sysio::ETHEREUM_MAX_CHUNK_BYTES), 1u); - BOOST_CHECK_EQUAL(chunking::chunk_count_for(sysio::ETHEREUM_MAX_CHUNK_BYTES + 1), 2u); - BOOST_CHECK_EQUAL(chunking::chunk_count_for(2 * sysio::ETHEREUM_MAX_CHUNK_BYTES), 2u); - BOOST_CHECK_EQUAL(chunking::chunk_count_for(2 * sysio::ETHEREUM_MAX_CHUNK_BYTES + 1), 3u); - BOOST_CHECK_EQUAL(chunking::chunk_count_for(3 * sysio::ETHEREUM_MAX_CHUNK_BYTES), 3u); - // The platform cap is an exact multiple: four full chunks, no remainder. - BOOST_CHECK_EQUAL(chunking::chunk_count_for(sysio::OPP_MAX_ENVELOPE_BYTES), 4u); - BOOST_CHECK_EQUAL(sysio::OPP_MAX_ENVELOPE_BYTES % sysio::ETHEREUM_MAX_CHUNK_BYTES, 0u); - - // Ragged tail: the first chunks are exactly MAX_CHUNK_BYTES, the last is - // the remainder. - BOOST_CHECK_EQUAL(chunking::chunk_count_for(three_chunk_envelope_bytes), 3u); - BOOST_CHECK_EQUAL(three_chunk_envelope_bytes - 2 * sysio::ETHEREUM_MAX_CHUNK_BYTES, 3'616u); - - // Unlike Solana — whose relay sends `chunks + 1` transactions because a - // zero-data terminal call triggers finalization — Ethereum finalizes inline - // on the chunk that completes the envelope, so the transaction count equals - // the chunk count exactly. The delivery cases below pin that by counting - // observed `epochIn` calls against `chunk_count_for`. +/// The most an `epochIn` may be funded with follows the client's policy +/// ceiling and never exceeds EIP-7825's cap. +BOOST_AUTO_TEST_CASE(delivery_gas_ceiling_is_the_policy_ceiling_bounded_by_the_cap) try { + auto policy = relay_test_policy(); + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy), sysio::EIP_7825_TX_GAS_CAP); + + policy.max_gas_limit = fc::uint256{sysio::EIP_7825_TX_GAS_CAP}; + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy), sysio::EIP_7825_TX_GAS_CAP); + + policy.max_gas_limit = fc::uint256{below_cap_policy_gas_limit}; + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy), below_cap_policy_gas_limit); +} FC_LOG_AND_RETHROW(); + +/// At a fee, the ceiling is also what the policy's total-cost term pays for: +/// one ether at a hundred gwei is ten million gas, under the cap; a zero fee +/// or an ample budget leaves the static ceiling in force. +BOOST_AUTO_TEST_CASE(delivery_gas_ceiling_is_bounded_by_the_total_cost_at_the_fee) try { + auto policy = relay_test_policy(); + policy.max_total_native_cost = fc::uint256{one_ether_wei}; + constexpr uint64_t affordable_at_hundred_gwei = one_ether_wei / hundred_gwei; + static_assert(affordable_at_hundred_gwei < sysio::EIP_7825_TX_GAS_CAP); + + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy, fc::uint256{hundred_gwei}), affordable_at_hundred_gwei); + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy, fc::uint256{0}), sysio::EIP_7825_TX_GAS_CAP); + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy, fc::uint256{1}), sysio::EIP_7825_TX_GAS_CAP); + // The static ceiling still binds when it is the lower of the two. + policy.max_gas_limit = fc::uint256{below_cap_policy_gas_limit}; + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy, fc::uint256{1}), below_cap_policy_gas_limit); + BOOST_CHECK_EQUAL(sysio::delivery_gas_ceiling(policy, fc::uint256{hundred_gwei}), + std::min(affordable_at_hundred_gwei, below_cap_policy_gas_limit)); +} FC_LOG_AND_RETHROW(); + +/// A delivery is funded to what it has left to carry — fixed cost, bytes, an +/// allowance per attestation still to dispatch, the emit — doubled per spill +/// already met this tick and never past the ceiling; an envelope the relay +/// cannot read is funded to the ceiling outright. +BOOST_AUTO_TEST_CASE(delivery_gas_budget_follows_the_remaining_work_and_escalates_to_the_ceiling) try { + constexpr uint64_t bytes = 1'000; + constexpr uint64_t ceiling = sysio::EIP_7825_TX_GAS_CAP; + const auto budget = [&](uint32_t remaining, uint32_t escalations) { + return sysio::delivery_gas_budget(sysio::delivery_gas_request{ + .envelope_bytes = bytes, .remaining_attestations = remaining, .escalations = escalations, .ceiling = ceiling}); + }; + const uint64_t base = sysio::DELIVERY_FIXED_GAS + bytes * sysio::DELIVERY_GAS_PER_BYTE + sysio::EMIT_GAS_ALLOWANCE; + + BOOST_CHECK_EQUAL(budget(0, 0), base); + BOOST_CHECK_EQUAL(budget(1, 0), base + sysio::ATTESTATION_GAS_ALLOWANCE); + BOOST_CHECK_EQUAL(budget(3, 0), base + 3 * sysio::ATTESTATION_GAS_ALLOWANCE); + // Each spill doubles, and the ceiling is where doubling stops. + BOOST_CHECK_EQUAL(budget(1, 1), 2 * (base + sysio::ATTESTATION_GAS_ALLOWANCE)); + BOOST_CHECK_EQUAL(budget(1, 2), ceiling); + BOOST_CHECK_EQUAL(budget(1, 40), ceiling); + // A full envelope's worth of attestations is above the ceiling on its own. + BOOST_CHECK_EQUAL(budget(70, 0), ceiling); + // Unreadable: the ceiling, whatever else is known. + BOOST_CHECK_EQUAL(sysio::delivery_gas_budget(sysio::delivery_gas_request{ + .envelope_bytes = bytes, .remaining_attestations = std::nullopt, .escalations = 0, .ceiling = ceiling}), + ceiling); + // Counting is the relay's own read of the envelope. + BOOST_CHECK_EQUAL(*sysio::outpost_ethereum_client_detail::count_envelope_attestations( + serialize_envelope_with_attestations(test_wire_epoch, 3, 16)), + 3u); + BOOST_CHECK(!sysio::outpost_ethereum_client_detail::count_envelope_attestations(make_envelope(mid_envelope_bytes))); + // The least ceiling the relay accepts is one full-cap delivery's worth. + BOOST_CHECK_EQUAL(sysio::DELIVERY_MINIMUM_GAS_CEILING, + sysio::DELIVERY_FIXED_GAS + sysio::OPP_MAX_ENVELOPE_BYTES * sysio::DELIVERY_GAS_PER_BYTE + + sysio::ATTESTATION_GAS_ALLOWANCE + sysio::EMIT_GAS_ALLOWANCE); + BOOST_CHECK(sysio::DELIVERY_MINIMUM_GAS_CEILING < sysio::EIP_7825_TX_GAS_CAP); +} FC_LOG_AND_RETHROW(); + +/// A relay that delivers is refused a policy whose ceiling cannot complete a +/// full-cap envelope; one that only reads the outpost (no OPPInbound address) +/// is not, since it never funds a delivery. +BOOST_AUTO_TEST_CASE(relay_refuses_a_policy_ceiling_below_one_full_delivery) try { + static_assert(undeliverable_policy_gas_limit < sysio::DELIVERY_MINIMUM_GAS_CEILING); + auto stack = create_relay_test_stack(relay_test_policy(fc::uint256{undeliverable_policy_gas_limit})); + auto abis = load_abi_fixture(opp_inbound_abi_fixture); + const auto opp_abis = load_abi_fixture(opp_abi_fixture); + abis.insert(abis.end(), opp_abis.begin(), opp_abis.end()); + + BOOST_CHECK_THROW(sysio::outpost_ethereum_client(stack.entry, std::string{}, std::string(test_opp_inbound_address), + abis, test_outpost_chain_code, test_evm_chain_id), + sysio::chain::plugin_config_exception); + BOOST_CHECK_NO_THROW(sysio::outpost_ethereum_client(stack.entry, std::string(test_opp_address), std::string{}, + abis, test_outpost_chain_code, test_evm_chain_id)); + + stack.tester.reset(); + appbase::application::reset_app_singleton(); +} FC_LOG_AND_RETHROW(); + +/// Across a tipped-and-spilled epoch the budget tracks the outpost's cursor: +/// everything before the tip, what remains after it, only the emit once +/// dispatch is complete — each continuation doubled for the spill before it. +/// Every read is pinned: the head when the tick starts, then the block each +/// confirmed call landed in. +BOOST_AUTO_TEST_CASE(delivery_funds_each_call_to_its_remaining_work) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = serialize_envelope_with_attestations(test_wire_epoch, 2, 64); + fixture->spill_responses = { + encode_dispatch_spill_result(false, 0, false, false), // before the delivery + encode_dispatch_spill_result(true, 1, false, false), // tipped, spilled after 1 + encode_dispatch_spill_result(true, 2, true, false), // dispatched, emit outstanding + encode_dispatch_spill_result(true, 2, true, true), // finalized + }; + fixture->settle_on(envelope, /*recorded_from_read=*/1); + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 3); + + const auto expected = [&](uint32_t remaining, uint32_t escalations) { + return sysio::delivery_gas_budget(sysio::delivery_gas_request{ + .envelope_bytes = envelope.size(), + .remaining_attestations = remaining, + .escalations = escalations, + .ceiling = sysio::EIP_7825_TX_GAS_CAP}); + }; + BOOST_CHECK_EQUAL(fixture->delivery_calls[0].gas_budget, expected(2, 0)); + BOOST_CHECK_EQUAL(fixture->delivery_calls[1].gas_budget, expected(1, 1)); + BOOST_CHECK_EQUAL(fixture->delivery_calls[2].gas_budget, expected(0, 2)); + BOOST_CHECK(fixture->delivery_calls[0].gas_budget < fixture->delivery_calls[1].gas_budget); + BOOST_CHECK(fixture->delivery_calls[0].gas_budget < sysio::EIP_7825_TX_GAS_CAP); + + // One head read, then a read at each of the three receipt blocks. + BOOST_CHECK_EQUAL(fixture->rpc->block_number_reads, 1u); + const std::vector expected_blocks{pinned_block_hex(test_head_block), pinned_block_hex(test_head_block + 1), + pinned_block_hex(test_head_block + 2), pinned_block_hex(test_head_block + 3)}; + const auto blocks = fixture->blocks_read(); + BOOST_CHECK_EQUAL_COLLECTIONS(blocks.begin(), blocks.end(), expected_blocks.begin(), expected_blocks.end()); +} FC_LOG_AND_RETHROW(); + +/// The deliver-or-continue decision table, exercised without an EVM node. +BOOST_AUTO_TEST_CASE(delivery_decision_table) try { + namespace detail = sysio::outpost_ethereum_client_detail; + using action = detail::delivery_action; + using settlement = detail::delivery_settlement; + + const detail::dispatch_spill untipped{}; + const detail::dispatch_spill mid_flight{.tipped = true, .dispatched = 3, .complete = false, .finalized = false}; + const detail::dispatch_spill emit_pending{.tipped = true, .dispatched = 9, .complete = true, .finalized = false}; + const detail::dispatch_spill finalized{.tipped = true, .dispatched = 9, .complete = true, .finalized = true}; + const auto decide = [](uint32_t next, const detail::dispatch_spill& spill, settlement s, bool majority) { + return detail::decide_delivery(next, test_wire_epoch, spill, s, majority); + }; + + // Not tipped, nothing recorded: deliver. + BOOST_CHECK(decide(test_wire_epoch, untipped, settlement::never_delivered, false) == action::deliver); + BOOST_CHECK(decide(test_wire_epoch, untipped, settlement::never_delivered, true) == action::deliver); + // Not tipped, recorded: re-deliver only when the outpost's own majority view says it would tip. + BOOST_CHECK(decide(test_wire_epoch, untipped, settlement::recorded, true) == action::retry_consensus); + BOOST_CHECK(decide(test_wire_epoch, untipped, settlement::recorded, false) == action::await_peers); + + // Tipped and unfinished: only the settled digest's deliverer continues. + BOOST_CHECK(decide(test_wire_epoch, mid_flight, settlement::settled, false) == action::continue_dispatch); + BOOST_CHECK(decide(test_wire_epoch, emit_pending, settlement::settled, false) == action::continue_dispatch); + BOOST_CHECK(decide(test_wire_epoch, mid_flight, settlement::never_delivered, false) == action::wait_for_deliverer); + BOOST_CHECK(decide(test_wire_epoch, mid_flight, settlement::divergent, false) == action::wait_for_deliverer); + BOOST_CHECK(decide(test_wire_epoch, emit_pending, settlement::divergent, true) == action::wait_for_deliverer); + + // Closed, by either signal: nothing to send. + BOOST_CHECK(decide(test_wire_epoch, finalized, settlement::settled, false) == action::already_finalized); + BOOST_CHECK(decide(test_different_wire_epoch, untipped, settlement::never_delivered, false) == action::already_finalized); + BOOST_CHECK(decide(test_different_wire_epoch, mid_flight, settlement::settled, false) == action::already_finalized); + + // The outpost has not reached this epoch: a delivery would be refused as non-sequential. + BOOST_CHECK(decide(test_stale_wire_epoch, untipped, settlement::never_delivered, false) == action::outpost_behind); + BOOST_CHECK(decide(test_stale_wire_epoch, mid_flight, settlement::settled, false) == action::outpost_behind); } FC_LOG_AND_RETHROW(); -/// The client hashes `OPP_ChunkBufferMissing(address)` to recognise the one tolerable discard -/// revert. This pins that hash against the four bytes the deployed contract actually emits, so -/// a wrong signature string cannot quietly make every revert unrecognisable — which would look -/// like nothing more than a few extra abandoned ticks. -BOOST_AUTO_TEST_CASE(chunk_buffer_missing_selector_is_pinned) try { - namespace chunking = sysio::outpost_ethereum_client_detail; - const std::string self{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}; - - BOOST_CHECK(chunking::is_chunk_buffer_missing_revert( - encode_address_revert(chunk_buffer_missing_selector, self), self)); - // The node lower-cases what it returns; the configured signer may be checksummed. - BOOST_CHECK(chunking::is_chunk_buffer_missing_revert( - encode_address_revert(chunk_buffer_missing_selector, self), fc::to_lower(self))); - - // Right selector, wrong operator. - BOOST_CHECK(!chunking::is_chunk_buffer_missing_revert( - encode_address_revert(chunk_buffer_missing_selector, foreign_operator_address), self)); - // Right shape, different error. - BOOST_CHECK(!chunking::is_chunk_buffer_missing_revert( - encode_address_revert(no_chunk_slot_selector, self), self)); - // Nothing to identify it by. - BOOST_CHECK(!chunking::is_chunk_buffer_missing_revert("", self)); - BOOST_CHECK(!chunking::is_chunk_buffer_missing_revert("0x", self)); - - // A non-zero byte in the padding is not our address however it decodes. - auto corrupt_padding = encode_address_revert(chunk_buffer_missing_selector, self); - corrupt_padding[hex_prefix.size() + chunk_buffer_missing_selector.size()] = '1'; - BOOST_CHECK(!chunking::is_chunk_buffer_missing_revert(corrupt_padding, self)); +/// This relay's record is classified against the settled digest only once the +/// epoch tipped; a malformed word is an error, never "nothing recorded". +BOOST_AUTO_TEST_CASE(delivery_settlement_classification) try { + namespace detail = sysio::outpost_ethereum_client_detail; + using settlement = detail::delivery_settlement; + + BOOST_CHECK(detail::classify_settlement(zero_digest_word, settled_digest_word, false) == settlement::never_delivered); + BOOST_CHECK(detail::classify_settlement(zero_digest_word, settled_digest_word, true) == settlement::never_delivered); + BOOST_CHECK(detail::classify_settlement(settled_digest_word, settled_digest_word, false) == settlement::recorded); + BOOST_CHECK(detail::classify_settlement(divergent_digest_word, settled_digest_word, false) == settlement::recorded); + BOOST_CHECK(detail::classify_settlement(settled_digest_word, settled_digest_word, true) == settlement::settled); + BOOST_CHECK(detail::classify_settlement(divergent_digest_word, settled_digest_word, true) == settlement::divergent); + // Checksum casing on either side does not split a match. + const std::string upper{"1111111111111111111111111111111111111111111111111111111111111111"}; + BOOST_CHECK(detail::classify_settlement(upper, settled_digest_word, true) == settlement::settled); + + BOOST_CHECK_THROW(detail::classify_settlement("", settled_digest_word, true), fc::exception); + BOOST_CHECK_THROW(detail::classify_settlement(settled_digest_word, "0x", true), fc::exception); + BOOST_CHECK_THROW(detail::classify_settlement(std::string(settled_digest_word) + "0", settled_digest_word, true), + fc::exception); } FC_LOG_AND_RETHROW(); -/// The resume decision table, exercised without an EVM node. -BOOST_AUTO_TEST_CASE(chunk_resume_decision_table) try { - namespace chunking = sysio::outpost_ethereum_client_detail; - using action = chunking::chunk_resume_action; +/// The contract's path-2 predicate, mirrored: the boundary elapsed AND a +/// strict majority agreeing; an empty group never tips. +BOOST_AUTO_TEST_CASE(majority_tip_is_the_contracts_path_2_predicate) try { + namespace detail = sysio::outpost_ethereum_client_detail; + constexpr uint64_t started = 1'000; + constexpr uint32_t duration = 60; + constexpr uint64_t boundary = started + duration; + const auto view = [](uint32_t agreeing, uint32_t group) { + return detail::pending_consensus{.next_epoch = test_wire_epoch, .agreeing = agreeing, .group_size = group, + .epoch_started_at = started, .epoch_duration_sec = duration}; + }; + + BOOST_CHECK(detail::majority_tip_reachable(view(test_majority, test_group_size), boundary)); + BOOST_CHECK(detail::majority_tip_reachable(view(test_group_size, test_group_size), boundary + 1)); + BOOST_CHECK(!detail::majority_tip_reachable(view(test_majority, test_group_size), boundary - 1)); + BOOST_CHECK(!detail::majority_tip_reachable(view(test_majority - 1, test_group_size), boundary)); + BOOST_CHECK(!detail::majority_tip_reachable(view(0, 0), boundary)); + // A group of one tips on its own delivery; the strict majority of five is three. + BOOST_CHECK(detail::majority_tip_reachable(view(1, 1), boundary)); + BOOST_CHECK(detail::majority_tip_reachable(view(3, 5), boundary)); + BOOST_CHECK(!detail::majority_tip_reachable(view(2, 5), boundary)); +} FC_LOG_AND_RETHROW(); - // Same address, EIP-55 checksummed and all-lower-case — the ABI decoder - // returns the latter, operators and tooling quote the former. - const std::string self{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}; - const std::string self_lower{"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}; - const uint16_t total_chunks = 4; - const uint32_t total_bytes = sysio::OPP_MAX_ENVELOPE_BYTES; +/// The progress invariant: every field the outpost moves counts as progress, +/// and nothing else does. +BOOST_AUTO_TEST_CASE(advanced_tracks_every_cursor_field) try { + namespace detail = sysio::outpost_ethereum_client_detail; + const detail::delivery_progress before{.next_epoch_index = test_wire_epoch, + .spill = {}, + .own_digest = std::string(zero_digest_word), + .settled_digest = std::string(zero_digest_word)}; + BOOST_CHECK(!detail::advanced(before, before)); + + auto recorded = before; + recorded.own_digest = std::string(settled_digest_word); + BOOST_CHECK(detail::advanced(before, recorded)); + auto tipped = before; + tipped.spill.tipped = true; + BOOST_CHECK(detail::advanced(before, tipped)); + auto dispatched = tipped; + dispatched.spill.dispatched = 1; + BOOST_CHECK(detail::advanced(tipped, dispatched)); + BOOST_CHECK(!detail::advanced(dispatched, dispatched)); + auto complete = dispatched; + complete.spill.complete = true; + BOOST_CHECK(detail::advanced(dispatched, complete)); + auto finalized = complete; + finalized.spill.finalized = true; + BOOST_CHECK(detail::advanced(complete, finalized)); + auto next_epoch = before; + next_epoch.next_epoch_index = test_different_wire_epoch; + BOOST_CHECK(detail::advanced(before, next_epoch)); + // The settled digest changing on its own is another epoch's business, not progress here. + auto resettled = before; + resettled.settled_digest = std::string(settled_digest_word); + BOOST_CHECK(!detail::advanced(before, resettled)); +} FC_LOG_AND_RETHROW(); - const auto decide = [&](const chunking::envelope_chunk_state& staged) { - return chunking::decide_chunk_resume(staged, self, test_wire_epoch, total_chunks, total_bytes); +/// The `epochIn` refusals are identified by selector AND shape, exactly as the +/// pool's are, and only for a call the node executed. +BOOST_AUTO_TEST_CASE(epoch_in_revert_selectors_are_pinned) try { + namespace detail = sysio::outpost_ethereum_client_detail; + using revert = detail::epoch_in_revert; + const auto data = [](std::string_view selector, size_t words) { + return epoch_in_refusal(selector, words).data.as_string(); }; - // Address comparison tolerates prefix and EIP-55 casing differences. - BOOST_CHECK(chunking::same_evm_address(self, self_lower)); - BOOST_CHECK(chunking::same_evm_address(self, self.substr(hex_prefix.size()))); - BOOST_CHECK(!chunking::same_evm_address(self, foreign_operator_address)); - BOOST_CHECK(!chunking::same_evm_address("", self)); - - // No header at all -> start at chunk 0. - BOOST_CHECK(decide({}).action == action::start_fresh); - BOOST_CHECK_EQUAL(decide({}).start_chunk, 0u); - - // A peer's header is not ours to continue or discard. - chunking::envelope_chunk_state foreign{ - test_wire_epoch, std::string(foreign_operator_address), total_chunks, 2, total_bytes, 0}; - BOOST_CHECK(decide(foreign).action == action::start_fresh); - BOOST_CHECK_EQUAL(decide(foreign).start_chunk, 0u); - - // Our own header, same epoch and shape -> resume at the high-water mark. - chunking::envelope_chunk_state matching{test_wire_epoch, self, total_chunks, 2, total_bytes, 0}; - BOOST_CHECK(decide(matching).action == action::resume); - BOOST_CHECK_EQUAL(decide(matching).start_chunk, 2u); - - // Same epoch, different shape -> discard the superseded cells, restart. - chunking::envelope_chunk_state wrong_chunk_count = matching; - wrong_chunk_count.total_chunks = total_chunks - 1; - BOOST_CHECK(decide(wrong_chunk_count).action == action::discard_and_restart); - - chunking::envelope_chunk_state wrong_size = matching; - wrong_size.total_bytes = total_bytes - 1; - BOOST_CHECK(decide(wrong_size).action == action::discard_and_restart); - - // The final chunk is never stored, so receivedChunks can never reach - // totalChunks; a header claiming otherwise is unusable. - chunking::envelope_chunk_state over_received = matching; - over_received.received_chunks = total_chunks; - BOOST_CHECK(decide(over_received).action == action::discard_and_restart); - - // Our own header for a different epoch -> confirm against nextEpochIndex. - chunking::envelope_chunk_state stale = matching; - stale.epoch_index = test_stale_wire_epoch; - BOOST_CHECK(decide(stale).action == action::confirm_epoch_advanced); - BOOST_CHECK_EQUAL(decide(stale).start_chunk, 0u); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(dispatch_underfunded_selector, 2)) == + revert::dispatch_underfunded); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(handler_gas_exhausted_selector, 3)) == + revert::handler_gas_exhausted); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(non_sequential_epoch_selector, 2)) == + revert::non_sequential_epoch); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(operator_already_delivered_selector, 2)) == + revert::operator_already_delivered); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(not_active_operator_selector, 1)) == + revert::not_active_operator); + BOOST_CHECK(detail::classify_epoch_in_revert(contract_revert_rpc_code, data(digest_mismatch_selector, 2)) == + revert::digest_mismatch); + + // Wrong shape, wrong selector, a protocol error, or no bytes: not a known refusal. + BOOST_CHECK(!detail::classify_epoch_in_revert(contract_revert_rpc_code, data(dispatch_underfunded_selector, 1))); + BOOST_CHECK(!detail::classify_epoch_in_revert(contract_revert_rpc_code, data(not_active_operator_selector, 2))); + BOOST_CHECK(!detail::classify_epoch_in_revert(contract_revert_rpc_code, data(no_yield_selector, 0))); + BOOST_CHECK(!detail::classify_epoch_in_revert(json_rpc_parse_error_code, data(dispatch_underfunded_selector, 2))); + BOOST_CHECK(!detail::classify_epoch_in_revert(contract_revert_rpc_code, "")); + BOOST_CHECK(!detail::classify_epoch_in_revert(contract_revert_rpc_code, "0x")); +} FC_LOG_AND_RETHROW(); + +/// A confirmed call's receipt is summarised from the `OPPInbound` events in it, +/// for this epoch and this relay only; logs from other contracts, other epochs +/// and other operators are ignored. +BOOST_AUTO_TEST_CASE(delivery_receipts_summarise_the_outposts_events) try { + namespace detail = sysio::outpost_ethereum_client_detail; + const std::string inbound{test_opp_inbound_address}; + const std::string self{test_other_operator_address}; + const std::string other{test_syndication_pool_address}; + + const auto nothing = detail::summarize_delivery_receipt({}, inbound, test_wire_epoch, self); + BOOST_CHECK(!nothing.recorded && !nothing.tipped && !nothing.dispatched && !nothing.finalized); + + const fc::variants full{ + epoch_delivery_log(inbound, test_wire_epoch, self, settled_digest_word), + epoch_consensus_log(inbound, test_wire_epoch, settled_digest_word, test_group_size), + epoch_dispatch_progressed_log(inbound, test_wire_epoch, 4), + epoch_complete_log(inbound, test_wire_epoch), + }; + const auto summary = detail::summarize_delivery_receipt(full, inbound, test_wire_epoch, self); + BOOST_CHECK(summary.recorded); + BOOST_CHECK(summary.tipped); + BOOST_REQUIRE(summary.dispatched.has_value()); + BOOST_CHECK_EQUAL(*summary.dispatched, 4u); + BOOST_CHECK(summary.finalized); + // Addresses compare without their checksum casing. + std::string upper_inbound = inbound; + std::ranges::transform(upper_inbound, upper_inbound.begin(), [](unsigned char c) { return std::toupper(c); }); + BOOST_CHECK(detail::summarize_delivery_receipt(full, upper_inbound, test_wire_epoch, self).recorded); + + const fc::variants foreign{ + epoch_delivery_log(other, test_wire_epoch, self, settled_digest_word), // another contract + epoch_delivery_log(inbound, test_wire_epoch, other, settled_digest_word), // another operator + epoch_consensus_log(inbound, test_different_wire_epoch, settled_digest_word, 1), // another epoch + epoch_dispatch_progressed_log(inbound, test_different_wire_epoch, 4), + epoch_complete_log(inbound, test_different_wire_epoch), + fc::variant("not a log object"), + }; + const auto ignored = detail::summarize_delivery_receipt(foreign, inbound, test_wire_epoch, self); + BOOST_CHECK(!ignored.recorded && !ignored.tipped && !ignored.dispatched && !ignored.finalized); + + // The receipt parser keeps the block and the logs, and refuses a receipt without a block. + const auto parsed = sysio::parse_epoch_in_receipt( + "0x1", fc::mutable_variant_object("blockNumber", pinned_block_hex(test_head_block))("logs", full)); + BOOST_CHECK_EQUAL(parsed.tx_hash, "0x1"); + BOOST_CHECK_EQUAL(parsed.block_number, fc::uint256{test_head_block}); + BOOST_CHECK_EQUAL(parsed.logs.size(), full.size()); + BOOST_CHECK(sysio::parse_epoch_in_receipt("0x1", fc::mutable_variant_object("blockNumber", "0x1")).logs.empty()); + BOOST_CHECK_THROW(sysio::parse_epoch_in_receipt("0x1", fc::mutable_variant_object("logs", full)), fc::exception); + BOOST_CHECK_THROW(sysio::parse_epoch_in_receipt("0x1", fc::variant("0x1")), fc::exception); + + // The digest the relay compares against the settled one is keccak256 of the bytes. + BOOST_CHECK_EQUAL(detail::envelope_digest_word({}), keccak256_of_empty); } FC_LOG_AND_RETHROW(); -/// Guard rails before any transaction is signed. BOOST_AUTO_TEST_CASE(delivery_rejects_empty_and_over_cap_envelopes) try { - auto fixture = create_chunked_delivery_fixture(); + auto fixture = create_whole_envelope_delivery_fixture(); BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( test_wire_epoch, {}, fc::seconds(test_rpc_deadline_seconds)), @@ -1397,340 +1832,574 @@ BOOST_AUTO_TEST_CASE(delivery_rejects_empty_and_over_cap_envelopes) try { test_wire_epoch, over_cap, fc::seconds(test_rpc_deadline_seconds)), fc::assert_exception); - BOOST_CHECK_EQUAL(fixture->chunk_calls.size(), 0u); - BOOST_CHECK_EQUAL(fixture->chunk_state_reads, 0u); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 0u); + BOOST_CHECK_EQUAL(fixture->spill_reads, 0u); } FC_LOG_AND_RETHROW(); -/// The dominant case: an envelope at or below one chunk is a single -/// transaction that stages nothing, so the resume read is skipped entirely. -BOOST_AUTO_TEST_CASE(single_chunk_delivery_skips_the_resume_read) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(sysio::ETHEREUM_MAX_CHUNK_BYTES); +/// The dominant case: the whole envelope goes in ONE call, and once the +/// delivery is recorded without tipping (the rest of the group has yet to +/// deliver) the tick stops rather than re-sending a paid no-op. +BOOST_AUTO_TEST_CASE(delivery_sends_the_whole_envelope_in_one_call) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(sysio::OPP_MAX_ENVELOPE_BYTES); const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); BOOST_CHECK(!tx.empty()); - BOOST_CHECK_EQUAL(fixture->chunk_state_reads, 0u); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + // Opaque bytes carry no attestation count to size a budget from: the + // ceiling, and the contract judges them. + BOOST_CHECK_EQUAL(fixture->delivery_calls[0].gas_budget, sysio::EIP_7825_TX_GAS_CAP); + // Read at the head before the delivery, and once more at the receipt's + // block after it to learn it was recorded and did not tip. + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 2u); + BOOST_CHECK_EQUAL(fixture->spill_reads, 2u); + BOOST_CHECK_EQUAL(fixture->settlement_reads, 2u); + const std::vector expected_blocks{pinned_block_hex(test_head_block), pinned_block_hex(test_head_block + 1)}; + const auto blocks = fixture->blocks_read(); + BOOST_CHECK_EQUAL_COLLECTIONS(blocks.begin(), blocks.end(), expected_blocks.begin(), expected_blocks.end()); + // The tip check ran in the delivery itself: no consensus read follows a send. + BOOST_CHECK_EQUAL(fixture->consensus_reads, 0u); } FC_LOG_AND_RETHROW(); -/// A multi-chunk delivery with nothing staged sends every chunk in order, each -/// exactly MAX_CHUNK_BYTES but the last. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_sends_every_chunk_in_order) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); +/// A consensus retry against an epoch the outpost has already moved past is +/// abandoned before a single transaction is signed — it would only buy a late +/// no-op — and the EMPTY tx id marks the epoch handled. +BOOST_AUTO_TEST_CASE(delivery_skips_when_the_outpost_epoch_advanced) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->next_epoch_index_responses = {encode_next_epoch_index_result(test_different_wire_epoch)}; - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->chunk_state_reads, 1u); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 1u); } FC_LOG_AND_RETHROW(); -/// Resume: an own, matching header advances the start index to the on-chain -/// high-water mark. Without this a tick whose budget covers k transactions -/// re-sends chunks 0..k-1 forever and never reaches chunk k. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_resumes_from_the_staged_high_water_mark) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - constexpr uint16_t staged_chunks = 2; - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - staged_chunks, - static_cast(envelope.size()), - static_cast(staged_chunks) * sysio::ETHEREUM_MAX_CHUNK_BYTES); +/// The cursor's own `finalized` flag closes the epoch even when the epoch +/// cursor read has not caught up with it. +BOOST_AUTO_TEST_CASE(delivery_skips_an_epoch_the_cursor_reports_finalized) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = {encode_dispatch_spill_result(true, 5, true, true)}; - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->chunk_state_reads, 1u); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, staged_chunks); + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 1u); } FC_LOG_AND_RETHROW(); -/// `storedBytes` is a uint256 the relay only logs. A value beyond the uint64 -/// range must NOT invalidate the header: doing so would drop back to chunk 0 -/// and re-upload cells the outpost already holds. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_resumes_despite_an_oversized_stored_bytes_field) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - constexpr uint16_t staged_chunks = 2; - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - staged_chunks, - static_cast(envelope.size()), - /*stored_bytes=*/0, - stored_bytes_above_uint64_word()); - - fixture->outpost->deliver_outbound_envelope( - test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); +/// An outpost still on an earlier epoch cannot take this delivery yet: nothing +/// is sent, and the tick ends in the retry exception rather than marking the +/// epoch handled. +BOOST_AUTO_TEST_CASE(delivery_waits_for_an_outpost_that_is_behind) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->next_epoch_index_responses = {encode_next_epoch_index_result(test_stale_wire_epoch)}; - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, staged_chunks); + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); } FC_LOG_AND_RETHROW(); -/// Mid-sequence deadline expiry — the scenario resume exists for. -/// -/// The first chunk burns more wall clock than the whole delivery budget, so the -/// SECOND chunk's pre-flight `throw_if_past_deadline` fires: the tick abandons -/// after a PARTIAL, correctly-ordered prefix rather than rolling back. The next -/// cron tick then resumes from the on-chain high-water mark instead of -/// re-sending chunk 0 forever (the livelock this design removes). -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_abandons_on_deadline_then_resumes_next_tick) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - BOOST_REQUIRE_EQUAL( - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), 3u); +/// A delivery that tips and spills is continued in the SAME tick: the relay +/// re-supplies the envelope until the outpost reports the epoch finalized. +BOOST_AUTO_TEST_CASE(delivery_that_tips_and_spills_continues_in_the_same_tick) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(sysio::OPP_MAX_ENVELOPE_BYTES); + fixture->spill_responses = { + encode_dispatch_spill_result(false, 0, false, false), // before the delivery + encode_dispatch_spill_result(true, 4, false, false), // tipped, spilled after 4 + encode_dispatch_spill_result(true, 11, true, false), // dispatched, emit outstanding + encode_dispatch_spill_result(true, 11, true, true), // finalized + }; + fixture->settle_on(envelope, /*recorded_from_read=*/1); - constexpr int64_t delivery_budget_ms = 50; - fixture->first_chunk_delay = std::chrono::milliseconds{delivery_budget_ms * 4}; + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( - test_wire_epoch, envelope, fc::milliseconds(delivery_budget_ms)), - fc::timeout_exception); + BOOST_CHECK(!tx.empty()); + // The delivery, then two continuations — every one carrying the whole envelope. + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 3); + BOOST_CHECK_EQUAL(fixture->spill_reads, 4u); + BOOST_CHECK_EQUAL(fixture->settlement_reads, 4u); +} FC_LOG_AND_RETHROW(); - // Exactly one chunk landed, and it is a correct prefix of the envelope. - BOOST_REQUIRE_EQUAL(fixture->chunk_calls.size(), 1u); - BOOST_CHECK_EQUAL(fixture->chunk_calls[0].chunk_index, 0u); - BOOST_CHECK_EQUAL(fixture->chunk_calls[0].chunk_hex, - fc::to_hex(envelope.data(), sysio::ETHEREUM_MAX_CHUNK_BYTES)); - - // Next tick: the outpost reports the one staged chunk, and the relay picks - // up at chunk 1 rather than restarting. - fixture->chunk_calls.clear(); - fixture->first_chunk_delay = std::chrono::milliseconds{0}; - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()), - sysio::ETHEREUM_MAX_CHUNK_BYTES); +/// Resume: a tick that finds the epoch already tipped on this relay's digest +/// picks the continuation up without a fresh delivery — the contract would +/// refuse one, and the relay does not pay to find that out. +BOOST_AUTO_TEST_CASE(delivery_continues_a_tipped_epoch_it_delivered_until_it_finalizes) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = { + encode_dispatch_spill_result(true, 3, false, false), + encode_dispatch_spill_result(true, 3, true, false), + encode_dispatch_spill_result(true, 3, true, true), + }; + fixture->settle_on(envelope); - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 1); + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 2); + BOOST_CHECK_EQUAL(fixture->settlement_reads, 3u); } FC_LOG_AND_RETHROW(); -/// A staging header owned by a peer is never adopted and never discarded — the -/// delivery simply starts at chunk 0 and lets the contract's ownership guard -/// arbitrate. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_ignores_a_peer_owned_header) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - foreign_operator_address, - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 2, - static_cast(envelope.size()), - 0); +/// A continuation is sent only with the bytes consensus settled on: an +/// envelope whose digest is not the settled one is this relay's problem to +/// report, not the outpost's to refuse for a fee. +BOOST_AUTO_TEST_CASE(delivery_does_not_continue_with_an_envelope_that_is_not_the_settled_digest) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = {encode_dispatch_spill_result(true, 3, false, false)}; + fixture->own_delivery_responses = {encode_word_result(settled_digest_word)}; + fixture->pending_hash_response = encode_word_result(settled_digest_word); - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); - BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 0u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); } FC_LOG_AND_RETHROW(); -/// A CURRENT-epoch header describing a different envelope is discarded first, -/// then the upload restarts from chunk 0. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_discards_a_superseded_current_epoch_header) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()) - 1, // a different envelope, same epoch - sysio::ETHEREUM_MAX_CHUNK_BYTES); +/// A relay that never delivered has no claim on a tipped epoch's +/// continuation: its deliverers carry it, and this relay sends nothing. +BOOST_AUTO_TEST_CASE(delivery_leaves_a_tipped_epoch_it_did_not_deliver_to_its_deliverers) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = {encode_dispatch_spill_result(true, 2, false, false)}; + fixture->own_delivery_responses = {encode_word_result(zero_digest_word)}; - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->discard_calls, 1u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->settlement_reads, 1u); } FC_LOG_AND_RETHROW(); -/// A reverting discard means the header is ALREADY clear (the contract's -/// `OPP_ChunkBufferMissing`), which is exactly the state the relay wanted: -/// log it and upload from chunk 0. Only transport failures abandon the tick. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_treats_a_reverting_discard_as_already_clear) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); +/// A relay whose delivery settled on a DIFFERENT digest — a minority envelope +/// — must not resume the epoch from its own divergent bytes. +BOOST_AUTO_TEST_CASE(delivery_does_not_continue_from_a_minority_digest) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = {encode_dispatch_spill_result(true, 2, false, false)}; + fixture->own_delivery_responses = {encode_word_result(divergent_digest_word)}; - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()) - 1, - sysio::ETHEREUM_MAX_CHUNK_BYTES); - fixture->discard_failure = fc::network::json_rpc::json_rpc_error( - contract_revert_rpc_code, "execution reverted", - fc::variant{encode_address_revert(chunk_buffer_missing_selector, - fixture->outpost->signer_address_hex())}); - - fixture->outpost->deliver_outbound_envelope( + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->discard_calls, 1u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); } FC_LOG_AND_RETHROW(); -/// A revert the client cannot identify is NOT proof the staging header is clear. -/// -/// Code 3 says only that the EVM executed and reverted. A node whose proxy points at a -/// mismatched implementation, or that returns no revert bytes at all, produces the same code -/// for a completely different reason — and the superseded header may still be staged. Each of -/// these must abandon the tick rather than upload chunk 0 against it. -/// -/// Abandoning is self-healing rather than terminal: this path is only reached when a header -/// WAS read, so the next cron tick re-reads it and either resumes or starts fresh. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_abandons_on_an_unrecognized_discard_revert) try { - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - // Built against a throwaway fixture so the cases can name this relay's real signer. - const auto signer = create_chunked_delivery_fixture()->outpost->signer_address_hex(); - - const std::vector> unrecognized{ - {"no revert bytes at all", ""}, - {"an empty revert payload", "0x"}, - {"a neighbouring error of identical shape", - encode_address_revert(no_chunk_slot_selector, signer)}, - {"our error naming a different operator", - encode_address_revert(chunk_buffer_missing_selector, test_other_operator_address)}, - {"our selector with the argument truncated away", - "0x" + std::string(chunk_buffer_missing_selector)}, +/// A recorded, untipped delivery is re-sent only when the outpost's own +/// consensus view says the re-send would tip it — the boundary elapsed and a +/// strict majority agreeing — and only once per tick. The ordinary first +/// delivery of a tick (`delivery_sends_the_whole_envelope_in_one_call`) is the +/// recorded-untipped-after-a-send case, which returns normally. +BOOST_AUTO_TEST_CASE(delivery_retries_consensus_only_when_the_outposts_majority_view_allows) try { + const uint64_t now = fc::time_point::now().sec_since_epoch(); + constexpr uint32_t duration = 60; + const auto consensus = [&](uint32_t agreeing, uint64_t started_at) { + return encode_pending_consensus_result(test_wire_epoch, agreeing, test_group_size, started_at, duration); }; - for (const auto& [description, revert_data] : unrecognized) { - BOOST_TEST_CONTEXT(description) { - auto fixture = create_chunked_delivery_fixture(); - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()) - 1, - sysio::ETHEREUM_MAX_CHUNK_BYTES); - fixture->discard_failure = fc::network::json_rpc::json_rpc_error( - contract_revert_rpc_code, "execution reverted", fc::variant{revert_data}); + // Boundary elapsed, majority agreeing: one re-delivery, then the tick stops. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->own_delivery_responses = {encode_word_result(settled_digest_word)}; + fixture->consensus_response = consensus(test_majority, now - duration - 1); + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + BOOST_CHECK_EQUAL(fixture->consensus_reads, 1u); + BOOST_REQUIRE_EQUAL(fixture->consensus_digests.size(), 1u); + BOOST_CHECK_EQUAL(fixture->consensus_digests.front(), prefixed(settled_digest_word)); + } + // Majority not reached: nothing to send. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->own_delivery_responses = {encode_word_result(settled_digest_word)}; + fixture->consensus_response = consensus(test_majority - 1, now - duration - 1); + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); + BOOST_CHECK_EQUAL(fixture->consensus_reads, 1u); + } + // Boundary not elapsed on the outpost: nothing to send, however many agree + // — and not a used-up retry either: the epoch stays open for the next tick. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->own_delivery_responses = {encode_word_result(settled_digest_word)}; + fixture->consensus_response = consensus(test_group_size, now + duration); + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); + BOOST_CHECK_EQUAL(fixture->consensus_reads, 1u); + } +} FC_LOG_AND_RETHROW(); - BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( - test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), - fc::exception); +/// A call that confirms but moves nothing — no record, no tip, no dispatch +/// progress — was under-funded for the attestation at the cursor: the next is +/// funded double, and a call at the ceiling that still moves nothing ends the +/// tick in the retry exception instead of a fourth paid no-op. +BOOST_AUTO_TEST_CASE(delivery_escalates_after_a_call_that_advanced_nothing_and_stops_at_the_ceiling) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = serialize_envelope_with_attestations(test_wire_epoch, 1, 16); + // The outpost never records the delivery, whatever is sent. + fixture->own_delivery_responses = {encode_word_result(zero_digest_word)}; + fixture->receipt_logs = {fc::variants{}}; - BOOST_CHECK_EQUAL(fixture->discard_calls, 1u); - BOOST_CHECK(fixture->chunk_calls.empty()); - } + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + + const auto expected = [&](uint32_t escalations) { + return sysio::delivery_gas_budget(sysio::delivery_gas_request{ + .envelope_bytes = envelope.size(), + .remaining_attestations = 1, + .escalations = escalations, + .ceiling = sysio::EIP_7825_TX_GAS_CAP}); + }; + BOOST_REQUIRE_EQUAL(expected(2), sysio::EIP_7825_TX_GAS_CAP); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 3); + BOOST_CHECK_EQUAL(fixture->delivery_calls[0].gas_budget, expected(0)); + BOOST_CHECK_EQUAL(fixture->delivery_calls[1].gas_budget, expected(1)); + BOOST_CHECK_EQUAL(fixture->delivery_calls[2].gas_budget, sysio::EIP_7825_TX_GAS_CAP); + // Each read after a send was pinned to that send's block. + BOOST_CHECK_EQUAL(fixture->blocks_read().size(), 4u); + + // The next tick finds the same stall and reports it the same way, without + // paying for the under-funded calls again: straight to the ceiling. + fixture->delivery_calls.clear(); + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 3u); +} FC_LOG_AND_RETHROW(); + +/// Every `epochIn` attempt, the retry after a refusal included, waits for its +/// receipt until the delivery's own deadline rather than for a fixed budget of +/// its own, so a deadline sized past one slow block lets a single wait outlast it. +BOOST_AUTO_TEST_CASE(delivery_awaits_each_receipt_until_the_delivery_deadline) try { + /// Longer than the 15 s the library's default retry budget gives one wait. + constexpr int64_t long_delivery_deadline_seconds = 60; + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = serialize_envelope_with_attestations(test_wire_epoch, 2, 64); + fixture->refusals = {epoch_in_refusal(dispatch_underfunded_selector, 2)}; + + const auto before = fc::time_point::now(); + const auto tx = fixture->outpost->deliver_outbound_envelope(test_wire_epoch, envelope, + fc::seconds(long_delivery_deadline_seconds)); + const auto after = fc::time_point::now(); + + BOOST_CHECK(!tx.empty()); + BOOST_REQUIRE_EQUAL(fixture->attempted_deadlines.size(), 2u); + for (const auto& deadline : fixture->attempted_deadlines) { + BOOST_CHECK(deadline >= before + fc::seconds(long_delivery_deadline_seconds)); + BOOST_CHECK(deadline <= after + fc::seconds(long_delivery_deadline_seconds)); } } FC_LOG_AND_RETHROW(); -/// A JSON-RPC protocol error is NOT a revert, and must abandon the tick rather than be tolerated. -/// -/// A parse error means the node never executed the call, so the staging header may still hold -/// the superseded envelope. Treating it as "already clear" would upload chunk 0 against that -/// stale header. Such a response is required by the JSON-RPC specification to carry a null id, -/// which the transport now decodes as json_rpc_error rather than a bare fc::exception, so this -/// catch discriminates on the code instead of on the exception type. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_abandons_on_a_protocol_error_during_discard) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()) - 1, - sysio::ETHEREUM_MAX_CHUNK_BYTES); - // The payload is a well-formed OPP_ChunkBufferMissing naming this signer, so only the code - // distinguishes it. A JSON-RPC error object may carry `data`, and with an empty one here the - // selector check would reject it first and the code guard would never be exercised. - fixture->discard_failure = fc::network::json_rpc::json_rpc_error( - json_rpc_parse_error_code, "Parse error", - fc::variant{encode_address_revert(chunk_buffer_missing_selector, - fixture->outpost->signer_address_hex())}); +/// A node refusal for gas below the ceiling is retried once at the ceiling; +/// one at the ceiling is the stalled-epoch report, with nothing sent. +BOOST_AUTO_TEST_CASE(delivery_retries_a_gas_refusal_at_the_ceiling_and_reports_one_there) try { + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = serialize_envelope_with_attestations(test_wire_epoch, 2, 64); + fixture->refusals = {epoch_in_refusal(dispatch_underfunded_selector, 2)}; + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + BOOST_REQUIRE_EQUAL(fixture->attempted_budgets.size(), 2u); + BOOST_CHECK(fixture->attempted_budgets[0] < sysio::EIP_7825_TX_GAS_CAP); + BOOST_CHECK_EQUAL(fixture->attempted_budgets[1], sysio::EIP_7825_TX_GAS_CAP); + BOOST_CHECK_EQUAL(fixture->delivery_calls[0].gas_budget, sysio::EIP_7825_TX_GAS_CAP); + } + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); // unreadable: funded to the ceiling outright + fixture->refusals = {epoch_in_refusal(handler_gas_exhausted_selector, 3)}; + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_REQUIRE_EQUAL(fixture->attempted_budgets.size(), 1u); + BOOST_CHECK_EQUAL(fixture->attempted_budgets[0], sysio::EIP_7825_TX_GAS_CAP); + } + // Any other refusal is the job's to see. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->refusals = {fc::network::json_rpc::json_rpc_error(json_rpc_parse_error_code, "parse error")}; + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::network::json_rpc::json_rpc_error); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 1u); + } +} FC_LOG_AND_RETHROW(); - BOOST_CHECK_THROW( - fixture->outpost->deliver_outbound_envelope(test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), - fc::exception); +/// A refusal that says the outpost moved under the read — here, the epoch +/// finalized between the read and the send — is answered by a fresh read at +/// the head, not a guess; one that survives fresh reads is reported. +BOOST_AUTO_TEST_CASE(delivery_rereads_at_the_head_after_a_stale_refusal) try { + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->refusals = {epoch_in_refusal(non_sequential_epoch_selector, 2)}; + fixture->next_epoch_index_responses = {encode_next_epoch_index_result(test_wire_epoch), + encode_next_epoch_index_result(test_different_wire_epoch)}; + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(tx.empty()); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 1u); + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 2u); + BOOST_CHECK_EQUAL(fixture->rpc->block_number_reads, 2u); + } + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->refusals = {epoch_in_refusal(not_active_operator_selector, 1), + epoch_in_refusal(not_active_operator_selector, 1), + epoch_in_refusal(not_active_operator_selector, 1)}; + fixture->own_delivery_responses = {encode_word_result(zero_digest_word)}; + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::network::json_rpc::json_rpc_error); + BOOST_CHECK_EQUAL(fixture->delivery_calls.size(), 0u); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 3u); + } +} FC_LOG_AND_RETHROW(); - BOOST_CHECK_EQUAL(fixture->discard_calls, 1u); - BOOST_CHECK(fixture->chunk_calls.empty()); +/// A progress read refused by a backend that has not seen the pinned block yet +/// is retried AT THAT BLOCK, never taken as "not tipped" (which would send a +/// duplicate delivery) and never re-read at a fresh head. Covered at the +/// tick-start head and at the block the relay's own call just confirmed in. +BOOST_AUTO_TEST_CASE(delivery_retries_a_progress_read_the_backend_cannot_serve_yet) try { + const std::vector expected_blocks{pinned_block_hex(test_head_block), + pinned_block_hex(test_head_block + 1)}; + // Refused once at the head, before anything is sent. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_read_refusals = {unknown_block_refusal()}; + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + // Three attempts at the cursor: refused at the head, answered at the head, + // answered at the receipt's block. Only the answers consume a response. + BOOST_CHECK_EQUAL(fixture->spill_read_attempts, 3u); + BOOST_CHECK_EQUAL(fixture->spill_reads, 2u); + // The refused attempt's earlier reads are discarded and re-read whole. + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 3u); + const auto blocks = fixture->blocks_read(); + BOOST_CHECK_EQUAL_COLLECTIONS(blocks.begin(), blocks.end(), expected_blocks.begin(), expected_blocks.end()); + BOOST_CHECK_EQUAL(fixture->rpc->block_number_reads, 1u); + } + // Refused once at the receipt's block, right after the relay's own delivery. + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_read_refusals = {std::nullopt, unknown_block_refusal()}; + + const auto tx = fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + + BOOST_CHECK(!tx.empty()); + // ONE delivery: the refused read did not read as "nothing recorded". + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + BOOST_CHECK_EQUAL(fixture->spill_read_attempts, 3u); + BOOST_CHECK_EQUAL(fixture->spill_reads, 2u); + BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 3u); + // The retry stayed on the receipt's block; the head was never re-read. + const auto blocks = fixture->blocks_read(); + BOOST_CHECK_EQUAL_COLLECTIONS(blocks.begin(), blocks.end(), expected_blocks.begin(), expected_blocks.end()); + BOOST_CHECK_EQUAL(fixture->rpc->block_number_reads, 1u); + } } FC_LOG_AND_RETHROW(); -/// A stale OWN header on a consensus retry is the signature of an epoch that -/// advanced underneath us. One `nextEpochIndex()` read confirms it, and the -/// delivery is abandoned before a single transaction is signed — otherwise the -/// retry pays for up to `totalChunks` late no-ops. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_skips_when_the_outpost_epoch_advanced) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - // A header epoch differing from the delivery epoch triggers the - // confirmation read. - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_stale_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()), - sysio::ETHEREUM_MAX_CHUNK_BYTES); - fixture->next_epoch_index_response = encode_next_epoch_index_result(test_different_wire_epoch); +/// A backend that never catches up to the pinned block within the tick's +/// deadline ends the tick in a timeout — which the job retries — never in a +/// decision taken without the read, and never in a second, paid delivery. +BOOST_AUTO_TEST_CASE(delivery_times_out_rather_than_guess_when_the_pinned_block_never_serves) try { + /// More refusals than the retry's backoff can attempt inside the deadline. + constexpr size_t persistent_refusal_attempts = 64; + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_read_refusals.push_back(std::nullopt); // the head read before the delivery answers + fixture->spill_read_refusals.insert(fixture->spill_read_refusals.end(), persistent_refusal_attempts, + unknown_block_refusal()); - const auto tx = fixture->outpost->deliver_outbound_envelope( - test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::timeout_exception); - BOOST_CHECK(tx.empty()); - BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 1u); - BOOST_CHECK_EQUAL(fixture->chunk_calls.size(), 0u); - BOOST_CHECK_EQUAL(fixture->discard_calls, 0u); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + // The read was retried, not abandoned after the first refusal … + BOOST_CHECK_GT(fixture->spill_read_attempts, 2u); + BOOST_CHECK_EQUAL(fixture->spill_reads, 1u); + // … and every retry stayed pinned to the receipt's block. + const std::vector expected_blocks{pinned_block_hex(test_head_block), + pinned_block_hex(test_head_block + 1)}; + const auto blocks = fixture->blocks_read(); + BOOST_CHECK_EQUAL_COLLECTIONS(blocks.begin(), blocks.end(), expected_blocks.begin(), expected_blocks.end()); + BOOST_CHECK_EQUAL(fixture->rpc->block_number_reads, 1u); } FC_LOG_AND_RETHROW(); -/// The same stale-header shape when the outpost has NOT advanced: the epoch is -/// still deliverable, so the upload proceeds from chunk 0. -BOOST_AUTO_TEST_CASE(multi_chunk_delivery_proceeds_when_the_outpost_epoch_has_not_advanced) try { - auto fixture = create_chunked_delivery_fixture(); - auto envelope = make_chunked_envelope(three_chunk_envelope_bytes); - - fixture->chunk_state_response = encode_envelope_chunk_state_result( - test_stale_wire_epoch, - fixture->outpost->signer_address_hex(), - sysio::outpost_ethereum_client_detail::chunk_count_for(envelope.size()), - 1, - static_cast(envelope.size()), - sysio::ETHEREUM_MAX_CHUNK_BYTES); - fixture->next_epoch_index_response = encode_next_epoch_index_result(test_wire_epoch); - - fixture->outpost->deliver_outbound_envelope( +/// A tick sends at most `MAX_CONTINUATIONS_PER_TICK` calls; an epoch still +/// open after that is handed to the next tick through the retry exception, +/// never marked handled. +BOOST_AUTO_TEST_CASE(delivery_ends_the_tick_at_the_continuation_bound) try { + constexpr uint16_t continuation_bound = 32; + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + // Every call dispatches one more attestation and spills again. + for (uint16_t dispatched = 1; dispatched <= continuation_bound + 1; ++dispatched) { + fixture->spill_responses.push_back(encode_dispatch_spill_result(true, dispatched, false, false)); + } + fixture->spill_responses.erase(fixture->spill_responses.begin()); // drop the never-tipped default + fixture->settle_on(envelope); + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + sysio::chain::outpost_delivery_incomplete_exception); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, continuation_bound); + // The bound was decided from a read AFTER the last send, at its block. + BOOST_CHECK_EQUAL(fixture->spill_reads, continuation_bound + 1u); +} FC_LOG_AND_RETHROW(); + +/// The cursor readers fail closed: a spill word that does not decode, or a +/// digest that is not one 32-byte word, is an error, never "not tipped" or +/// "never delivered" — either of which would send a fresh delivery into an +/// epoch that may already have settled. +BOOST_AUTO_TEST_CASE(delivery_fails_closed_on_malformed_cursor_reads) try { + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->spill_responses = {std::string(hex_prefix) + abi_word(1)}; // one word of four + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::exception); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); + } + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->own_delivery_responses = {std::string(hex_prefix)}; // no word at all + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::exception); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); + } + { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(mid_envelope_bytes); + fixture->next_epoch_index_responses = {"0xzz"}; + // The ABI decoder's own refusal of a non-hex response, not an fc::exception. + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + std::exception); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 0u); + } +} FC_LOG_AND_RETHROW(); + +/// A transport failure on a continuation propagates after the delivery that +/// landed stands: the tick abandons, nothing is rolled back, and the next tick +/// resumes from the outpost's cursor. +BOOST_AUTO_TEST_CASE(delivery_propagates_a_transport_failure_on_a_continuation) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(sysio::OPP_MAX_ENVELOPE_BYTES); + fixture->spill_responses = { + encode_dispatch_spill_result(false, 0, false, false), + encode_dispatch_spill_result(true, 4, false, false), + }; + fixture->settle_on(envelope, /*recorded_from_read=*/1); + fixture->refusals = {std::nullopt, fc::network::json_rpc::json_rpc_error(json_rpc_parse_error_code, "gateway timeout")}; + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)), + fc::network::json_rpc::json_rpc_error); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + BOOST_CHECK_EQUAL(fixture->attempted_budgets.size(), 2u); +} FC_LOG_AND_RETHROW(); + +/// Mid-sequence deadline expiry, the scenario the cursor exists for: the +/// delivery outlasts the whole budget, so the continuation's pre-flight +/// `throw_if_past_deadline` abandons the tick with nothing rolled back, and the +/// next tick continues from the cursor instead of re-delivering. +BOOST_AUTO_TEST_CASE(delivery_abandons_on_deadline_then_continues_next_tick) try { + auto fixture = create_whole_envelope_delivery_fixture(); + auto envelope = make_envelope(sysio::OPP_MAX_ENVELOPE_BYTES); + + constexpr int64_t delivery_budget_ms = 50; + fixture->first_call_delay = std::chrono::milliseconds{delivery_budget_ms * 4}; + fixture->spill_responses = { + encode_dispatch_spill_result(false, 0, false, false), + encode_dispatch_spill_result(true, 6, false, false), + }; + fixture->settle_on(envelope, /*recorded_from_read=*/1); + + BOOST_CHECK_THROW(fixture->outpost->deliver_outbound_envelope( + test_wire_epoch, envelope, fc::milliseconds(delivery_budget_ms)), + fc::timeout_exception); + + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); + + // Next tick: the outpost reports the epoch tipped on our digest with six + // attestations routed, and the relay continues rather than re-delivering. + fixture->delivery_calls.clear(); + fixture->first_call_delay = std::chrono::milliseconds{0}; + fixture->spill_reads = 0; + fixture->spill_responses = { + encode_dispatch_spill_result(true, 6, false, false), + encode_dispatch_spill_result(true, 6, true, true), + }; + + const auto tx = fixture->outpost->deliver_outbound_envelope( test_wire_epoch, envelope, fc::seconds(test_rpc_deadline_seconds)); - BOOST_CHECK_EQUAL(fixture->next_epoch_reads, 1u); - check_chunk_call_sequence(fixture->chunk_calls, envelope, test_wire_epoch, 0); + BOOST_CHECK(!tx.empty()); + check_delivery_calls(fixture->delivery_calls, envelope, test_wire_epoch, 1); } FC_LOG_AND_RETHROW(); // ── `crank_outpost` ────────────────────────────────────────────────────── -/// The pool's three refusals are identified by selector AND shape, exactly as -/// `OPP_ChunkBufferMissing` is: a neighbouring error, a role error, a refusal with the wrong -/// argument count, and no bytes at all are none of them. +/// The pool's three refusals are identified by selector AND shape, exactly as the `epochIn` +/// refusals are: a neighbouring error, a role error, a refusal with the wrong argument count, +/// and no bytes at all are none of them. BOOST_AUTO_TEST_CASE(realize_yield_refusal_selectors_are_pinned) try { namespace crank = sysio::outpost_ethereum_client_detail; using refusal = crank::realize_yield_refusal; @@ -1756,7 +2425,7 @@ BOOST_AUTO_TEST_CASE(realize_yield_refusal_selectors_are_pinned) try { BOOST_CHECK(!crank::classify_realize_yield_revert( encode_address_revert(access_managed_unauthorized_selector, test_other_operator_address))); BOOST_CHECK(!crank::classify_realize_yield_revert( - encode_address_revert(chunk_buffer_missing_selector, test_other_operator_address))); + encode_address_revert(unrelated_address_error_selector, test_other_operator_address))); BOOST_CHECK(!crank::classify_realize_yield_revert("")); BOOST_CHECK(!crank::classify_realize_yield_revert("0x")); } FC_LOG_AND_RETHROW(); @@ -1938,7 +2607,7 @@ BOOST_AUTO_TEST_CASE(gas_limit_policy_bounds_the_buffered_limit_at_the_eip_7825_ .chain_id = test_evm_chain_id, .max_priority_fee_per_gas = maximum_ethereum_transaction_policy_value(), .max_fee_per_gas = maximum_ethereum_transaction_policy_value(), - .max_gas_limit = fc::uint256{eip_7825_tx_gas_cap}, + .max_gas_limit = fc::uint256{sysio::EIP_7825_TX_GAS_CAP}, .max_total_native_cost = maximum_ethereum_transaction_policy_value(), }; diff --git a/tests/fixtures/ethereum-abi-opp-current.json b/tests/fixtures/ethereum-abi-opp-current.json index ab1e98df9e..b1e243e52e 100644 --- a/tests/fixtures/ethereum-abi-opp-current.json +++ b/tests/fixtures/ethereum-abi-opp-current.json @@ -78,11 +78,97 @@ "name": "InvalidInitialization", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "position", + "type": "uint256" + } + ], + "name": "MalformedEnvelope", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "position", + "type": "uint256" + } + ], + "name": "MalformedVarint", + "type": "error" + }, { "inputs": [], "name": "NotInitializing", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "nextEpochIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "deliveries", + "type": "uint32" + } + ], + "name": "OPP_BootstrapWindowClosed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "expected", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "found", + "type": "bytes32" + } + ], + "name": "OPP_DigestMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gasLeft", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "floor", + "type": "uint256" + } + ], + "name": "OPP_DispatchUnderfunded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "expected", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "actual", + "type": "uint32" + } + ], + "name": "OPP_EnvelopeEpochMismatch", + "type": "error" + }, { "inputs": [ { @@ -115,6 +201,27 @@ "name": "OPP_EpochHashMismatch", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "handler", + "type": "address" + }, + { + "internalType": "AttestationType", + "name": "attestationType", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasBound", + "type": "uint256" + } + ], + "name": "OPP_HandlerGasExhausted", + "type": "error" + }, { "inputs": [ { @@ -145,17 +252,23 @@ { "inputs": [ { - "internalType": "uint256", - "name": "required", - "type": "uint256" - }, + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "OPP_InternalDispatchOnly", + "type": "error" + }, + { + "inputs": [ { - "internalType": "uint256", - "name": "provided", - "type": "uint256" + "internalType": "string", + "name": "reason", + "type": "string" } ], - "name": "OPP_InsufficientSignatureWeight", + "name": "OPP_InvalidInitialRoster", "type": "error" }, { @@ -179,6 +292,27 @@ "name": "OPP_InvalidRetentionConfig", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "expected", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "OPP_MessageChainMismatch", + "type": "error" + }, { "inputs": [ { @@ -205,6 +339,22 @@ "name": "OPP_NoPendingAttestations", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "messageId", + "type": "bytes" + } + ], + "name": "OPP_NonCanonicalMessageId", + "type": "error" + }, { "inputs": [ { @@ -216,6 +366,22 @@ "name": "OPP_NonCanonicalPreviousEpochHash", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "previousMessageId", + "type": "bytes" + } + ], + "name": "OPP_NonCanonicalPreviousMessageId", + "type": "error" + }, { "inputs": [ { @@ -232,6 +398,27 @@ "name": "OPP_NonSequentialEpoch", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "OPP_NonSequentialMessage", + "type": "error" + }, { "inputs": [ { @@ -269,6 +456,38 @@ "name": "OPP_OperatorAlreadyDelivered", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "projectedEnvelopeBytes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxEnvelopeBytes", + "type": "uint256" + } + ], + "name": "OPP_OutboundMessageUnpackable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "projectedBytes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxBytes", + "type": "uint256" + } + ], + "name": "OPP_OutboundQueueFull", + "type": "error" + }, { "inputs": [ { @@ -315,12 +534,27 @@ { "inputs": [ { - "internalType": "AttestationType", - "name": "attestationType", - "type": "uint16" + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "rosterEpochIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "activeGroupIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "groupCount", + "type": "uint32" } ], - "name": "OPP_UnhandledAttestationType", + "name": "OPP_UnmappedEpochGroup", "type": "error" }, { @@ -378,6 +612,38 @@ "name": "AuthorityUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + } + ], + "name": "EnvelopeRetentionCatchUpPruned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "previousRetentionEpochs", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "retentionEpochs", + "type": "uint32" + } + ], + "name": "EnvelopeRetentionConfigUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -417,6 +683,32 @@ "name": "Upgraded", "type": "event" }, + { + "inputs": [], + "name": "ENVELOPE_BASE_BYTES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_CHUNK_BYTES", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "MAX_ENVELOPE_BYTES", @@ -430,6 +722,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "MESSAGE_ESTIMATE_OVERHEAD_BYTES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OUTBOUND_WORD_COUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "UPGRADE_INTERFACE_VERSION", @@ -631,6 +949,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "initializeOutboundQueueBytes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "isConsumingScheduledOp", @@ -672,12 +997,12 @@ }, { "inputs": [], - "name": "latestOutboundEnvelope", + "name": "latestOutboundEpoch", "outputs": [ { - "internalType": "bytes", + "internalType": "uint32", "name": "", - "type": "bytes" + "type": "uint32" } ], "stateMutability": "view", @@ -685,12 +1010,12 @@ }, { "inputs": [], - "name": "latestOutboundEpoch", + "name": "oppInboundAddress", "outputs": [ { - "internalType": "uint32", + "internalType": "address", "name": "", - "type": "uint32" + "type": "address" } ], "stateMutability": "view", @@ -725,6 +1050,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "outboundLen", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "outboundRetentionConfig", @@ -751,6 +1089,24 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromWord", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toWord", + "type": "uint256" + } + ], + "name": "prewarmOutbound", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "proxiableUUID", @@ -777,6 +1133,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "queuedEstimatedBytes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "queuedMessageCount", @@ -1028,6 +1397,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "setInboundDispatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oppInbound_", + "type": "address" + } + ], + "name": "setOPPInbound", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1047,48 +1442,48 @@ "type": "function" } ], - "bytecode": "0x60a0806040523460c857306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009081549060ff8260401c1660b957506001600160401b036002600160401b0319828216016075575b6040516142ba90816100cd82396080518181816106a2015261370a0152f35b6001600160401b031990911681179091556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80806056565b63f92ee8a960e01b8152600490fd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806304676e38146101e45780630d6f1e9d146101df57806327e935a1146101da57806347453a2a146101d55780634f1ef286146101d05780634fca79cb146101cb57806352d1902d146101c6578063621fbd59146101c15780637a9e5e4b146101bc5780638fb36037146101b757806393ec06be146101b257806395fabcf0146101ad578063a3ad9cc3146101a8578063a593aec2146101a3578063a9e50c101461019e578063ad3cb1cc14610199578063b244542b14610194578063b8f8d7e51461018f578063bf7e214f1461018a578063c16a8a5614610185578063c4d66de814610180578063c94c93e71461017b578063d5b5272a14610176578063e006490014610171578063e0c14aa81461016c578063e2fc04ae14610167578063e36f9a0714610162578063e44efb5a1461015d5763f5786b4b14610158575f80fd5b6115f5565b6115d6565b6115b9565b61149c565b6110e9565b6110ac565b611010565b610fe1565b610eb5565b610e92565b610e5e565b610e3b565b610e0c565b610c07565b610bcf565b610af3565b610819565b6107fc565b6107df565b61078f565b610715565b6106f9565b610690565b6105e4565b6104d9565b61033b565b6102e4565b610290565b610208565b63ffffffff8116036101f757565b5f80fd5b3590610206826101e9565b565b346101f75760203660031901126101f7576060600435610227816101e9565b63ffffffff8091165f52600960205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b5f9103126101f757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346101f7575f3660031901126101f75763ffffffff600c54166102b1610c97565b906102ce604051928392835260406020840152604083019061026c565b0390f35b60209060031901126101f75760043590565b346101f7576102f2366102d2565b6102fc3633612905565b8015610315576004541561030c57005b6004555f600355005b604051636ad43cc360e11b8152600490fd5b634e487b7160e01b5f52603260045260245ffd5b346101f757610349366102d2565b6006548110156101f75760065f527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01546040516001600160a01b039091168152602090f35b600435906001600160a01b03821682036101f757565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103d457604052565b6103a5565b602081019081106001600160401b038211176103d457604052565b604081019081106001600160401b038211176103d457604052565b60e081019081106001600160401b038211176103d457604052565b6001600160401b0381116103d457604052565b90601f801991011681019081106001600160401b038211176103d457604052565b604051906102068261040f565b60405190610206826103b9565b6001600160401b0381116103d457601f01601f191660200190565b81601f820112156101f7578035906104aa82610478565b926104b8604051948561043d565b828452602083830101116101f757815f926020809301838601378301015290565b60403660031901126101f7576104ed61038f565b6024356001600160401b0381116101f75761050c903690600401610493565b90610515613700565b61051f3633612905565b610527613700565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f93816105b3575b5061057957604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f80516020614245833981519152830361059a576105989250613cea565b005b604051632a87526960e21b815260048101849052602490fd5b6105d691945060203d6020116105dd575b6105ce818361043d565b810190612496565b925f61054f565b503d6105c4565b346101f75760203660031901126101f7576102ce600435610604816101e9565b60405f818051610613816103b9565b828152826020820152015263ffffffff8092165f5260096020526001815f206001600160401b03835194610646866103b9565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b346101f7575f3660031901126101f7577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036106e75760206040515f805160206142458339815191528152f35b60405163703e46dd60e11b8152600490fd5b346101f7575f3660031901126101f75760205f54604051908152f35b346101f75760203660031901126101f75761072e61038f565b5f80516020614225833981519152546001600160a01b0391908216330361077857803b1561075f5761059890612a21565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b346101f7575f3660031901126101f7575f805160206142258339815191525460a01c60ff16156107d7576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f6107c4565b346101f7575f3660031901126101f7576020604051620100008152f35b346101f7575f3660031901126101f7576020600354604051908152f35b346101f75760203660031901126101f757600435610836816101e9565b6108403633612905565b600c5463ffffffff1661085e610855826118a4565b63ffffffff1690565b63ffffffff831603610ac457506108736118f8565b63ffffffff821660608201526108a46108946001600160401b034216611935565b6001600160401b03166040830152565b6108ac612a79565b60a08201525f60085480151580610952575b7fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88761094d866109416108ef88612b56565b9161090062010000845111156121f7565b610922835160208501206109148184612f19565b61091d83612f7f565b600d55565b61092b83612269565b63ffffffff1663ffffffff19600c541617600c55565b60405191829182610df8565b0390a1005b5f6101005b838210610a6e575b50506109759083159081610a66575b5015611b50565b8161097f81611c2b565b60c085019081525f5b828110610a2e57505061099a91611c8e565b905f5b828110610a0057505f5b8181106109ef575050916109416108ef61094d936109e57fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88796600855565b93508294506108be565b6001906109fa612125565b016109a7565b80610a28610a18610a1360019486611b35565b611951565b50610a2283611951565b90611fb6565b0161099d565b6001919250610a5c81610a4081611951565b50610a4c865191611a57565b610a568383611c7a565b52611c7a565b5001908391610988565b90505f61096e565b610a8a610a85610a7f849794611951565b50611a57565b612ae8565b9062010000610a998383611b35565b11610abb57610aad600192610ab392611b35565b92611b42565b940190610957565b5081945061095f565b610acd906118a4565b6040516307d3e83d60e51b815263ffffffff918216600482015291166024820152604490fd5b346101f75760203660031901126101f757600435610b10816101e9565b610b1a3633612905565b63ffffffff80821690815f52600960205260405f20541615610b8a5761059891610b6b610b7f92610b64610855610b56600a5463ffffffff1690565b600c5463ffffffff16612fd7565b101561240b565b63ffffffff165f52600960205260405f2090565b60015f918281550155565b60405162461bcd60e51b815260206004820152601760248201527f4f50503a207265636f7264206e6f742070726573656e740000000000000000006044820152606490fd5b346101f7575f3660031901126101f75760206001600160401b0360015416604051908152f35b60405190610c02826103d9565b5f8252565b346101f7575f3660031901126101f7576102ce604051610c26816103f4565b60058152640352e302e360dc1b602082015260405191829160208352602083019061026c565b634e487b7160e01b5f525f60045260245ffd5b90600182811c92168015610c8d575b6020831014610c7957565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610c6e565b604051905f82600b5491610caa83610c5f565b80835292602090600190818116908115610d345750600114610cd5575b50506102069250038361043d565b915092600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9935f925b828410610d1c57506102069450505081016020015f80610cc7565b85548885018301529485019487945092810192610d01565b9150506020925061020694915060ff191682840152151560051b8201015f80610cc7565b9060405191825f8254610d6a81610c5f565b908184526020946001916001811690815f14610dd65750600114610d98575b5050506102069250038361043d565b5f90815285812095935091905b818310610dbe57505061020693508201015f8080610d89565b85548884018501529485019487945091830191610da5565b9250505061020694925060ff191682840152151560051b8201015f8080610d89565b906020610e0992818152019061026c565b90565b346101f7575f3660031901126101f7576102ce610e27610c97565b60405191829160208352602083019061026c565b346101f7575f3660031901126101f757602063ffffffff600c5416604051908152f35b346101f7575f3660031901126101f7575f80516020614225833981519152546040516001600160a01b039091168152602090f35b346101f7575f3660031901126101f757602063ffffffff600a5416604051908152f35b346101f75760203660031901126101f757610ece61038f565b5f8051602061426583398151915254906001600160401b0360ff8360401c1615921680159081610fd9575b6001149081610fcf575b159081610fc6575b50610fb4575f80516020614265833981519152805467ffffffffffffffff19166001179055610f3e9082610f9057612ff6565b610f4457005b5f80516020614265833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290806020810161094d565b5f80516020614265833981519152805460ff60401b1916600160401b179055612ff6565b60405163f92ee8a960e01b8152600490fd5b9050155f610f0b565b303b159150610f03565b839150610ef9565b346101f75760203660031901126101f7576004355f526005602052602060ff60405f2054166040519015158152f35b346101f75760203660031901126101f75760043561102d816101e9565b6110373633612905565b604051611043816103d9565b63ffffffff8092168082521561106757511663ffffffff19600a541617600a555f80f35b60405162461bcd60e51b815260206004820152601d60248201527f4f50503a20696e76616c696420726574656e74696f6e20636f6e6669670000006044820152606490fd5b346101f7575f3660031901126101f7576020600854604051908152f35b6004359061ffff821682036101f757565b359061ffff821682036101f757565b346101f7576040806003193601126101f7576111036110c9565b6024356001600160401b0381116101f757611122903690600401610493565b9160045415611205576111353633612905565b5f805160206142258339815191525461116490611158906001600160a01b031681565b6001600160a01b031690565b81516368fc2b7760e11b815261ffff841662010000176004820152336024820152908290829060449082905afa908115611200575f916111d2575b50156111af576105988383613157565b51639a28d60760e01b815233600482015261ffff91909116602482015260449150fd5b6111f29150823d84116111f9575b6111ea818361043d565b810190612465565b505f61119f565b503d6111e0565b61248b565b5163738c481160e01b8152600490fd5b91908260409103126101f75760405161122d816103f4565b8092803560ff811681036101f75782526020908101359161124d836101e9565b0152565b91906080838203126101f75760206112856040519261126f846103f4565b6040849661127d8382611215565b865201611215565b910152565b35906001600160401b03821682036101f757565b6001600160401b0381116103d45760051b60200190565b91909160409081818503126101f75781516112cf816103f4565b809482356112dc816101e9565b8252602092838101356001600160401b03918282116101f757019180601f840112156101f75782359061130e8261129e565b9661131b8151988961043d565b828852868089019360051b860101948286116101f757878101935b86851061134857505050505050500152565b84358681116101f75782019060609081601f1984880301126101f757845191611370836103b9565b61137b8c85016110da565b83528584013561138a816101e9565b8c840152830135918883116101f7576113aa878d80969581960101610493565b86820152815201940193611336565b9060406020610206936113e084825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b90610e0991602081526114146020820183516113b9565b602082015190611432610140928360a084015261016083019061026c565b9160c0611478611453604087015195601f199687878303018588015261026c565b606087015163ffffffff1660e08601526080870151868683030161010087015261026c565b60a08601516001600160401b0316610120850152940151928285030191015261026c565b346101f7576003196040368201126101f757600480356001600160401b03928382116101f7576101409082360301126101f7576114d761045e565b906114e436828501611251565b825260848101358481116101f75761150190843691840101610493565b602083015260a48101358481116101f75761152190843691840101610493565b604083015261153260c482016101fb565b606083015260e48101358481116101f75761155290843691840101610493565b6080830152611564610104820161128a565b60a0830152610124810135908482116101f757836115859236920101610493565b60c08201526024359283116101f7576115a76115ad926102ce943691016112b5565b906125d7565b604051918291826113fd565b346101f7575f3660031901126101f7576020600454604051908152f35b346101f7575f3660031901126101f75760206004541515604051908152f35b346101f757611603366102d2565b600490815415611880576116173633612905565b80156118705781541461162657005b5f81556003549081156118615761163b611bb3565b915f5480155f146117f5575061164f6132f9565b604090604085015261167c61166c6001600160401b034216611935565b6001600160401b031660a0860152565b611684611bf0565b600181529161169281613343565b916020928385019081525f5b8381106117825750505050806116b7836116d6966125d7565b93818501519060405196879283926347f5343160e01b84528301610df8565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4801561120057611749945f91611765575b505f5561173661171a60a08501516001600160401b031690565b6001600160401b03166001600160401b03196001541617600155565b61173e611c09565b92835282015261351d565b61175c611757600854611b42565b600855565b6105985f600355565b61177c9150823d84116105dd576105ce818361043d565b5f611700565b806117ee8161179b611795600195613050565b506133a7565b886117a8825161ffff1690565b9101516117dd6117bc825163ffffffff1690565b6117d16117c761046b565b61ffff9095168552565b63ffffffff16838c0152565b87820152855190610a568383611c7a565b500161169e565b604051630c9b3d8760e21b8152838101918252905f9082908190602001038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4908115611200575f9161183f575b5061164f565b61185b91503d805f833e611853818361043d565b810190612550565b5f611839565b60405163783bc0f160e11b8152fd5b50604051636ad43cc360e11b8152fd5b5060405163738c481160e01b8152fd5b634e487b7160e01b5f52601160045260245ffd5b90600163ffffffff809316019182116118b957565b611890565b604051906118cb826103f4565b5f6020838281520152565b604051906118e3826103f4565b816118ec6118be565b815260206112856118be565b604051906119058261040f565b606060c0838281526119156118d6565b60208201525f60408201525f838201525f60808201528260a08201520152565b906103e86001600160401b03809316029182169182036118b957565b60075481101561198a57600a9060075f52027fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68801905f90565b610327565b9060405161199c816103f4565b915460ff8116835260081c63ffffffff166020830152565b90604080516119c2816103f4565b809363ffffffff80825416835260018092018054916119e08361129e565b956119ee604051978861043d565b83875260209160208801935f5260205f20915f945b868610611a1857505050505050505060200152565b60028589928451611a28816103b9565b86885461ffff8116835260101c1683820152611a45858901610d58565b86820152815201940195019492611a03565b90604051611a64816103f4565b602061128560088395604051611a798161040f565b604051611a85816103f4565b611a8e8361198f565b8152611a9c6001840161198f565b868201528152611aae60028301610d58565b85820152611abe60038301610d58565b6040820152611ae4611ad7600484015463ffffffff1690565b63ffffffff166060830152565b611af060058301610d58565b6080820152611b1c611b0c60068401546001600160401b031690565b6001600160401b031660a0830152565b611b2860078301610d58565b60c08201528552016119b4565b919082018092116118b957565b5f1981146118b95760010190565b15611b5757565b60405162461bcd60e51b815260206004820152602e60248201527f4f50503a20717565756564206d6573736167652065786365656473204d41585f60448201526d454e56454c4f50455f425954455360901b6064820152608490fd5b60405190611bc08261040f565b606060c083611bcd6118d6565b81528260208201528260408201525f838201528260808201525f60a08201520152565b60405190611bfd826103f4565b60606020835f81520152565b60405190611c16826103f4565b81611c1f611bb3565b81526020611285611bf0565b90611c358261129e565b611c42604051918261043d565b8281528092611c53601f199161129e565b01905f5b828110611c6357505050565b602090611c6e611c09565b82828501015201611c57565b805182101561198a5760209160051b010190565b919082039182116118b957565b818110611ca6575050565b5f8155600101611c9b565b90601f8211611cbe575050565b61020691600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9906020601f840160051c83019310611d07575b601f0160051c0190611c9b565b9091508190611cfa565b9190601f8111611d2057505050565b610206925f5260205f20906020601f840160051c83019310611d0757601f0160051c0190611c9b565b919091828114611e3057611d5d8354610c5f565b6001600160401b0381116103d457611d7f81611d798454610c5f565b84611d11565b5f93601f8211600114611dbe57611daf92939482915f92611db3575b50508160011b915f199060031b1c19161790565b9055565b015490505f80611d9b565b611dd0601f198316915f5260205f2090565b94611dde845f5260205f2090565b915f5b818110611e1857509583600195969710611e00575b505050811b019055565b01545f1960f88460031b161c191690555f8080611df6565b87830154845560019384019390920191602001611de1565b509050565b611e3f8154610c5f565b9081611e49575050565b81601f5f9311600114611e5a575055565b908083918252611e79601f60208420940160051c840160018501611c9b565b5555565b600160401b82116103d4578054828255808310611e9957505050565b6001916001600160ff1b0382811683036118b957841684036118b9575f5260205f209060011b81019260011b015b828110611ed357505050565b805f60029255611ee4838201611e35565b01611ec7565b818114611f7757815491611efe8383611e7d565b5f5260205f20905f5260205f20905f905b838210611f1c5750505050565b806001918403611f37575b6002809101930191019091611f0f565b8054845461ffff191661ffff919091161784558054845465ffffffff0000191665ffffffff0000909116178455611f72818301858401611d49565b611f27565b5050565b818103611f86575050565b60018083611fae63ffffffff610206965416859063ffffffff1663ffffffff19825416179055565b019101611eea565b9061212057818114159182611fca57505050565b600880918461020695611fe1575b50019101611f7b565b80612091575b50611ff86002820160028601611d49565b6120086003820160038601611d49565b61203561201c600483015463ffffffff1690565b600486019063ffffffff1663ffffffff19825416179055565b6120456005820160058601611d49565b61207b61205c60068301546001600160401b031690565b60068601906001600160401b03166001600160401b0319825416179055565b61208b6007820160078601611d49565b5f611fd8565b6120ee575b6001840160018201908181036120ad575b50611fe7565b8154815460ff191660ff919091161781556120e79163ffffffff9054851c1664ffffffff0082549160081b169064ffffffff001916179055565b5f806120a7565b8054845460ff191660ff919091161784558054845464ffffffff00191690831c60081b64ffffffff0016178455612096565b610c4c565b60075480156121e3575f190161213a81611951565b612120575f815560015f6001830155600960029261215a60028201611e35565b61216660038201611e35565b5f600482015561217860058201611e35565b5f600682015561218a60078201611e35565b5f600882015501908154915f8155826121a7575b50505050600755565b6001600160ff1b03831683036118b9575f5260205f209160011b8201915b8281101561219e57805f8592556121dd838201611e35565b016121c5565b634e487b7160e01b5f52603160045260245ffd5b156121fe57565b60405162461bcd60e51b815260206004820152603a60248201527f4f50503a20656e76656c6f70652065786365656473204d41585f454e56454c4f60448201527f50455f42595445532028657374696d61746f72206472696674290000000000006064820152608490fd5b9081516001600160401b0381116103d45761228e81612289600b54610c5f565b611cb1565b602080601f83116001146122ce575081906122be93945f926122c35750508160011b915f199060031b1c19161790565b600b55565b015190505f80611d9b565b90601f19831694612300600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db990565b925f905b87821061233b575050836001959610612323575b505050811b01600b55565b01515f1960f88460031b161c191690555f8080612318565b80600185968294968601518155019501930190612304565b91909182516001600160401b0381116103d45761237481611d798454610c5f565b602080601f83116001146123a557508190611daf9394955f926122c35750508160011b915f199060031b1c19161790565b90601f198316956123b9855f5260205f2090565b925f905b8882106123f3575050836001959697106123db57505050811b019055565b01515f1960f88460031b161c191690555f8080611df6565b806001859682949686015181550195019301906123bd565b1561241257565b60405162461bcd60e51b815260206004820152602560248201527f4f50503a207265636f7264207374696c6c20696e20726574656e74696f6e2077604482015264696e646f7760d81b6064820152608490fd5b91908260409103126101f757815180151581036101f757602090920151610e09816101e9565b6040513d5f823e3d90fd5b908160209103126101f7575190565b90602080835260609060608401908063ffffffff94858151168288015201519460408080830152865180945260808201908360808660051b8501019801955f935b8685106124f95750505050505050505090565b90919293949596979998868061253b600193868f89908f607f198a8603018d52519061ffff8251168552868201511686850152015191818a820152019061026c565b9b9c9a019897969190910194019291906124e6565b6020818303126101f7578051906001600160401b0382116101f7570181601f820112156101f75780519061258382610478565b92612591604051948561043d565b828452602083830101116101f757815f9260208093018386015e8301015290565b90916125c9610e099360408452604084019061026c565b91602081840391015261026c565b906125e0611bb3565b506040805163765809ab60e11b81526004926020929173__$b91d2ad766fd07dc99f56c1a0b020615bb$__919084818061261c858a83016124a5565b0381865af4908115611200575f91612877575b5060808701908582518051155f146127f35750508451630c9b3d8760e21b8152878101918252905f90829081906020010381875af49283156112005761269c9361268f93612685935f926127d7575b50526131ef565b5163ffffffff1690565b63ffffffff166060870152565b815162409a0960e81b81525f81806126b6898983016113fd565b0381855af4908115611200575f916127bd575b50858401805180518061278257505052815163b93612a760e01b8152938385806126f5898583016113fd565b0381855af49283156112005761272e955f958695612763575b505051630c9b3d8760e21b81529081019283529384928391829160200190565b03915af4908115611200575f91612749575b5060c082015290565b61275d91503d805f833e611853818361043d565b5f612740565b61277a929550803d106105dd576105ce818361043d565b925f8061270e565b9150939250848496959296012090855190860120036127a2575050505090565b51630ca2e06d60e11b815292839261057592919084016125b2565b6127d191503d805f833e611853818361043d565b5f6126c9565b6127ec9192503d805f833e611853818361043d565b905f61267e565b9091935061281492508551809381926347f5343160e01b83528a8301610df8565b0381865af4908115611200575f9161285a575b5081810361283657505061269c565b8351631d97afef60e31b815280870192835260208301919091529081906040010390fd5b6128719150853d87116105dd576105ce818361043d565b5f612827565b61288e9150853d87116105dd576105ce818361043d565b5f61262f565b6004116101f7575f90600490565b6001600160e01b031990358181169392600481106128bf57505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f80516020614225833981519152805461293d906001600160a01b031661293461292e86612894565b906128a2565b9084309161367f565b901561294a575b50505050565b63ffffffff1615612a01575f80516020614225833981519152805460ff60a01b1916600160a01b17905561298b90611158908190546001600160a01b031690565b91823b156101f7576129b6925f9283604051809681958294634a63ebf760e11b8452600484016128ce565b03925af18015611200576129e8575b505f80516020614225833981519152805460ff60a01b191690555f808080612944565b806129f56129fb9261042a565b80610262565b5f6129c5565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f8051602061422583398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b600d5480612a945750604051612a8e816103d9565b5f815290565b60405190630c9b3d8760e21b825260048201525f8160248173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4908115611200575f91612ad4575090565b610e0991503d805f833e611853818361043d565b61010090602080910190602082510151515f925b818410612b0a575050505090565b909192936018808201918281116118b9576040612b2b888887510151611c7a565b5101515101018091116118b95793600101929190612afc565b805191908290602001825e015f815290565b612b5e610bf5565b612b66613911565b92604080518095602094858301612b7c91612b44565b612b8591612b44565b0394601f19958681018252612b9a908261043d565b8451612ba590613759565b908251918291868301612bb791612b44565b612bc091612b44565b038681018252612bd0908261043d565b612bd8613928565b908251918291868301612bea91612b44565b612bf391612b44565b038681018252612c03908261043d565b83850151612c1090613795565b908151612c23906001600160401b031690565b612c2c90613a38565b908351918291878301612c3e91612b44565b612c4791612b44565b038781018252612c57908261043d565b8251918291868301612c6891612b44565b612c7191612b44565b038681018252612c81908261043d565b612c8961393b565b908251918291868301612c9b91612b44565b612ca491612b44565b038681018252612cb4908261043d565b848201516001600160401b0316612cca90613a38565b908251918291868301612cdc91612b44565b612ce591612b44565b038681018252612cf5908261043d565b612cfd61394e565b908251918291868301612d0f91612b44565b612d1891612b44565b038681018252612d28908261043d565b6060850151612d3c9063ffffffff16613a38565b908251918291868301612d4e91612b44565b612d5791612b44565b038681018252612d67908261043d565b612d6f613961565b908251918291868301612d8191612b44565b612d8a91612b44565b038681018252612d9a908261043d565b6080850151612dae9063ffffffff16613a38565b908251918291868301612dc091612b44565b612dc991612b44565b038681018252612dd9908261043d565b612de1613974565b908251918291868301612df391612b44565b612dfc91612b44565b038681018252612e0c908261043d565b60a0850151612e1a90613759565b908251918291868301612e2c91612b44565b612e3591612b44565b038681018252612e45908261043d565b915f925b60c08601805151851015612f0f57612e8f612f0560c093612ef9612ead612ea78a6001978f612ea1908f612e958f612e8f612e826139b9565b91519b8c94850190612b44565b90612b44565b0390810188528761043d565b51611c7a565b51613ab7565b612eeb8d8c612edf8c612e8f612ed2612ecd88516001600160401b031690565b613a38565b9151988994850190612b44565b0390810185528461043d565b88519485938c850190612b44565b038a810183528261043d565b9401939050612e49565b5095509350505050565b63ffffffff8082165f52600960205260405f2090815416612f7a57612f50600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b612f969063ffffffff91829182600a541690612fd7565b16908115159081612fbf575b50612faa5750565b5f5260096020525f6001604082208281550155565b9050815f52600960205260405f20541615155f612fa2565b63ffffffff91908282168382161115612fef57031690565b5050505f90565b61301f90613002613bb7565b61300a613bb7565b613012613bb7565b61301a613bb7565b612a21565b613027613bb7565b61302f613bb7565b60c860405161303d816103d9565b5260c863ffffffff19600a541617600a55565b60025481101561198a5760025f5260011b7f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01905f90565b929190612120578051835461ffff191661ffff91909116178355600180930190602080910151938451916001600160401b0383116103d4576130d4836130ce8654610c5f565b86611d11565b602091601f84116001146131065750508190611daf9394955f926122c35750508160011b915f199060031b1c19161790565b95601f9291921984169661311d865f5260205f2090565b935f915b89831061314057505050836001959697106123db57505050811b019055565b838501518655948501949381019391810191613121565b600254916003548084115f146131ac575061020692506131856040519261317d846103f4565b61ffff168352565b60208201526131a66003546131a161319c82611b42565b600355565b613050565b90613088565b6131b590611b42565b60035561ffff604051926131c8846103f4565b1682526020820152600160401b8210156103d4576131a68260016102069401600255613050565b613222916131fb610bf5565b906132046139d9565b612e8f613268604092604051978891612e8f60209889850190612b44565b0396613236601f199889810183528261043d565b61325c61324d612ecd6108558a5163ffffffff1690565b60405194859389850190612b44565b0387810183528261043d565b915f925b848601805151851015612f0f57612e8f6132ef612e8f612ef989956132e18d6132bb6132b58d60019a612ea18f6132a98f91612e8f612e82613928565b0386810189528861043d565b51613be5565b92612edf6132d3612ecd86516001600160401b031690565b8d519788938d850190612b44565b885194859388850190612b44565b940193905061326c565b604051613305816103d9565b5f8152905f368137565b6040519061331c826103f4565b6001825260203681840137565b60405190613336826103f4565b600a825260203681840137565b9061334d8261129e565b60409061335d604051918261043d565b838152809361336e601f199161129e565b01915f5b83811061337f5750505050565b602090825161338d816103b9565b5f8152825f81830152606085830152828601015201613372565b906040516133b4816103f4565b60206112856001839561ffff815416855201610d58565b8151815463ffffffff191663ffffffff91821617825591600180920160208092015191808351936133fc8585611e7d565b01915f52805f205f925b8484106134165750505050505050565b8051805183548583015161ffff90921665ffffffffffff199091161790891660101b65ffffffff0000161783556040015180519187840191906001600160401b0384116103d45782869261346f86611d798d9754610c5f565b8390601f87116001146134af5795806134a092600297985f926122c35750508160011b915f199060031b1c19161790565b90555b01920193019290613406565b9590601f198216966134c4845f5260205f2090565b975f5b818110613504575091879891846002999594106134ec575b505050811b0190556134a3565b01515f1960f88460031b161c191690555f80806134df565b838301518a5597909801978d978b9793840193016134c7565b600754600160401b8110156103d45780600161353c9201600755611951565b61212057600860208361367660c061020696516135d6815186613598825161357260ff8251168d9060ff1660ff19825416179055565b909101518a5464ffffffff00191660089190911b64ffffffff0016178a5563ffffffff90565b878060018c019301516135b960ff825116859060ff1660ff19825416179055565b0151825464ffffffff001916911660081b64ffffffff0016179055565b6135e68582015160028901612353565b6135f7604082015160038901612353565b61362461360b606083015163ffffffff1690565b600489019063ffffffff1663ffffffff19825416179055565b613635608082015160058901612353565b61366b61364c60a08301516001600160401b031690565b60068901906001600160401b03166001600160401b0319825416179055565b015160078601612353565b015191016133cb565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b038411176103d4576040935f9385528380528360205251915afa6136ee57565b9150505f51906020518060201c150290565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811630811491821561373e575b50506106e757565b5f805160206142458339815191525416141590505f80613736565b610e09612e8f916137876137766001600160401b03835116613a38565b916040519485936020850190612b44565b03601f19810183528261043d565b61379d610bf5565b906137a6613911565b60408051938460209384928383016137bd91612b44565b6137c691612b44565b0393601f199485810187526137db908761043d565b80516137e690613d8f565b9586516137f9906001600160401b031690565b61380290613a38565b90845191829185830161381491612b44565b61381d91612b44565b03868101825261382d908261043d565b835196879184830161383e91612b44565b61384791612b44565b038581018752613857908761043d565b61385f613928565b95835196879184830161387191612b44565b61387a91612b44565b03858101875261388a908761043d565b015161389590613d8f565b9081516138a8906001600160401b031690565b6138b190613a38565b9481519586918583016138c391612b44565b6138cc91612b44565b0384810186526138dc908661043d565b5193849283016138eb91612b44565b6138f491612b44565b039081018252610e09908261043d565b80511561198a5760200190565b61391961330f565b600a61392482613904565b5390565b61393061330f565b601261392482613904565b61394361330f565b602861392482613904565b61395661330f565b603061392482613904565b61396961330f565b603861392482613904565b61397c613329565b60a25f5b607f808311156139a55790608060019284161760208286010153019060071c90613980565b50607f600192166020828501015301815290565b6139c1613329565b61014260015f60c26020850153019060071c90613980565b6139e161330f565b600861392482613904565b6139f461330f565b601061392482613904565b613a0761330f565b601a61392482613904565b613a1a61330f565b603261392482613904565b613a2d61330f565b604261392482613904565b9060809160806001600160401b03821610613a9357613a55613329565b5f5b607f80841115613a7b57908560019285161760208285010153019160071c91613a57565b5090929350607f600192166020828501015301815290565b909150613a9e61330f565b9060f81b6001600160f81b0319165f1a61392482613904565b613abf610bf5565b90613ac8613911565b6040805193846020938492838301613adf91612b44565b613ae891612b44565b0393601f19948581018752613afd908761043d565b8051613b0890613e47565b958651613b1b906001600160401b031690565b613b2490613a38565b908451918291858301613b3691612b44565b613b3f91612b44565b038681018252613b4f908261043d565b8351968791848301613b6091612b44565b613b6991612b44565b038581018752613b79908761043d565b613b81613928565b958351968791848301613b9391612b44565b613b9c91612b44565b038581018752613bac908761043d565b0151613895906131ef565b60ff5f805160206142658339815191525460401c1615613bd357565b604051631afcd79f60e31b8152600490fd5b610e09613c26612e8f92613cde613cca612e8f613cd1613c03610bf5565b94613c0c6139d9565b9060409485928351998a91612e8f60209b8c850190612b44565b0398613c3a601f199a8b810183528261043d565b613c7989613c6d613c5d612ecd613c56613c56895161ffff1690565b61ffff1690565b612e8f8851958694850190612b44565b038b810183528261043d565b613c8889613c6d613c5d6139ec565b613ca589613c6d613c5d612ecd6108558489015163ffffffff1690565b613cbe613cb06139ff565b85519d8e938c850190612b44565b038981018c528b61043d565b0151613759565b9151968794850190612b44565b0390810183528261043d565b90813b15613d6e575f8051602061424583398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115613d5357613d5091614181565b50565b505034613d5c57565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b610e09612e8f612e8f613cde613e37612ecd6108556020612e8f98613e2c612e8f613e06613dbb610bf5565b613dd5613dc66139d9565b6040519d8e9389850190612b44565b039a613de9601f199c8d810183528261043d565b613c6d61324d612ecd613e00613e00895160ff1690565b60ff1690565b613e20613e116139ec565b6040519a8b9387850190612b44565b0389810189528861043d565b015163ffffffff1690565b6040519586936020850190612b44565b613e4f610bf5565b90613e58613911565b91604080518094602093848301613e6e91612b44565b613e7791612b44565b0392601f19938481018652613e8c908661043d565b8051613e9790613795565b948551613eaa906001600160401b031690565b613eb390613a38565b908351918291868301613ec591612b44565b613ece91612b44565b038581018252613ede908261043d565b8251958691858301613eef91612b44565b613ef891612b44565b038481018652613f08908661043d565b613f10613928565b948251958691858301613f2291612b44565b613f2b91612b44565b038481018652613f3b908661043d565b82810151613f4890613759565b948251958691858301613f5a91612b44565b613f6391612b44565b038481018652613f73908661043d565b613f7b6139ff565b948251958691858301613f8d91612b44565b613f9691612b44565b038481018652613fa6908661043d565b81810151613fb390613759565b948251958691858301613fc591612b44565b613fce91612b44565b038481018652613fde908661043d565b613fe661393b565b948251958691858301613ff891612b44565b61400191612b44565b038481018652614011908661043d565b60608101516140259063ffffffff16613a38565b94825195869185830161403791612b44565b61404091612b44565b038481018652614050908661043d565b614058613a12565b94825195869185830161406a91612b44565b61407391612b44565b038481018652614083908661043d565b608081015161409190613759565b9482519586918583016140a391612b44565b6140ac91612b44565b0384810186526140bc908661043d565b6140c4613961565b9482519586918583016140d691612b44565b6140df91612b44565b0384810186526140ef908661043d565b60a08101516001600160401b031661410690613a38565b94825195869185830161411891612b44565b61412191612b44565b038481018652614131908661043d565b614139613a25565b94825195869185830161414b91612b44565b61415491612b44565b038481018652614164908661043d565b60c0015161417190613759565b905193849283016138eb91612b44565b5f80610e0993602081519101845af43d156141bd573d916141a183610478565b926141af604051948561043d565b83523d5f602085013e6141c1565b6060915b906141e857508051156141d657602081519101fd5b60405163d6bda27560e01b8152600490fd5b8151158061421b575b6141f9575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156141f156fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a26469706673582212201dd53cc2caf86754ed88247c9b779f72471445dd4bcf6d516a7bf3bb29c0dce864736f6c63430008190033", - "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c806304676e38146101e45780630d6f1e9d146101df57806327e935a1146101da57806347453a2a146101d55780634f1ef286146101d05780634fca79cb146101cb57806352d1902d146101c6578063621fbd59146101c15780637a9e5e4b146101bc5780638fb36037146101b757806393ec06be146101b257806395fabcf0146101ad578063a3ad9cc3146101a8578063a593aec2146101a3578063a9e50c101461019e578063ad3cb1cc14610199578063b244542b14610194578063b8f8d7e51461018f578063bf7e214f1461018a578063c16a8a5614610185578063c4d66de814610180578063c94c93e71461017b578063d5b5272a14610176578063e006490014610171578063e0c14aa81461016c578063e2fc04ae14610167578063e36f9a0714610162578063e44efb5a1461015d5763f5786b4b14610158575f80fd5b6115f5565b6115d6565b6115b9565b61149c565b6110e9565b6110ac565b611010565b610fe1565b610eb5565b610e92565b610e5e565b610e3b565b610e0c565b610c07565b610bcf565b610af3565b610819565b6107fc565b6107df565b61078f565b610715565b6106f9565b610690565b6105e4565b6104d9565b61033b565b6102e4565b610290565b610208565b63ffffffff8116036101f757565b5f80fd5b3590610206826101e9565b565b346101f75760203660031901126101f7576060600435610227816101e9565b63ffffffff8091165f52600960205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b5f9103126101f757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346101f7575f3660031901126101f75763ffffffff600c54166102b1610c97565b906102ce604051928392835260406020840152604083019061026c565b0390f35b60209060031901126101f75760043590565b346101f7576102f2366102d2565b6102fc3633612905565b8015610315576004541561030c57005b6004555f600355005b604051636ad43cc360e11b8152600490fd5b634e487b7160e01b5f52603260045260245ffd5b346101f757610349366102d2565b6006548110156101f75760065f527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01546040516001600160a01b039091168152602090f35b600435906001600160a01b03821682036101f757565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103d457604052565b6103a5565b602081019081106001600160401b038211176103d457604052565b604081019081106001600160401b038211176103d457604052565b60e081019081106001600160401b038211176103d457604052565b6001600160401b0381116103d457604052565b90601f801991011681019081106001600160401b038211176103d457604052565b604051906102068261040f565b60405190610206826103b9565b6001600160401b0381116103d457601f01601f191660200190565b81601f820112156101f7578035906104aa82610478565b926104b8604051948561043d565b828452602083830101116101f757815f926020809301838601378301015290565b60403660031901126101f7576104ed61038f565b6024356001600160401b0381116101f75761050c903690600401610493565b90610515613700565b61051f3633612905565b610527613700565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f93816105b3575b5061057957604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f80516020614245833981519152830361059a576105989250613cea565b005b604051632a87526960e21b815260048101849052602490fd5b6105d691945060203d6020116105dd575b6105ce818361043d565b810190612496565b925f61054f565b503d6105c4565b346101f75760203660031901126101f7576102ce600435610604816101e9565b60405f818051610613816103b9565b828152826020820152015263ffffffff8092165f5260096020526001815f206001600160401b03835194610646866103b9565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b346101f7575f3660031901126101f7577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036106e75760206040515f805160206142458339815191528152f35b60405163703e46dd60e11b8152600490fd5b346101f7575f3660031901126101f75760205f54604051908152f35b346101f75760203660031901126101f75761072e61038f565b5f80516020614225833981519152546001600160a01b0391908216330361077857803b1561075f5761059890612a21565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b346101f7575f3660031901126101f7575f805160206142258339815191525460a01c60ff16156107d7576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f6107c4565b346101f7575f3660031901126101f7576020604051620100008152f35b346101f7575f3660031901126101f7576020600354604051908152f35b346101f75760203660031901126101f757600435610836816101e9565b6108403633612905565b600c5463ffffffff1661085e610855826118a4565b63ffffffff1690565b63ffffffff831603610ac457506108736118f8565b63ffffffff821660608201526108a46108946001600160401b034216611935565b6001600160401b03166040830152565b6108ac612a79565b60a08201525f60085480151580610952575b7fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88761094d866109416108ef88612b56565b9161090062010000845111156121f7565b610922835160208501206109148184612f19565b61091d83612f7f565b600d55565b61092b83612269565b63ffffffff1663ffffffff19600c541617600c55565b60405191829182610df8565b0390a1005b5f6101005b838210610a6e575b50506109759083159081610a66575b5015611b50565b8161097f81611c2b565b60c085019081525f5b828110610a2e57505061099a91611c8e565b905f5b828110610a0057505f5b8181106109ef575050916109416108ef61094d936109e57fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88796600855565b93508294506108be565b6001906109fa612125565b016109a7565b80610a28610a18610a1360019486611b35565b611951565b50610a2283611951565b90611fb6565b0161099d565b6001919250610a5c81610a4081611951565b50610a4c865191611a57565b610a568383611c7a565b52611c7a565b5001908391610988565b90505f61096e565b610a8a610a85610a7f849794611951565b50611a57565b612ae8565b9062010000610a998383611b35565b11610abb57610aad600192610ab392611b35565b92611b42565b940190610957565b5081945061095f565b610acd906118a4565b6040516307d3e83d60e51b815263ffffffff918216600482015291166024820152604490fd5b346101f75760203660031901126101f757600435610b10816101e9565b610b1a3633612905565b63ffffffff80821690815f52600960205260405f20541615610b8a5761059891610b6b610b7f92610b64610855610b56600a5463ffffffff1690565b600c5463ffffffff16612fd7565b101561240b565b63ffffffff165f52600960205260405f2090565b60015f918281550155565b60405162461bcd60e51b815260206004820152601760248201527f4f50503a207265636f7264206e6f742070726573656e740000000000000000006044820152606490fd5b346101f7575f3660031901126101f75760206001600160401b0360015416604051908152f35b60405190610c02826103d9565b5f8252565b346101f7575f3660031901126101f7576102ce604051610c26816103f4565b60058152640352e302e360dc1b602082015260405191829160208352602083019061026c565b634e487b7160e01b5f525f60045260245ffd5b90600182811c92168015610c8d575b6020831014610c7957565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610c6e565b604051905f82600b5491610caa83610c5f565b80835292602090600190818116908115610d345750600114610cd5575b50506102069250038361043d565b915092600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9935f925b828410610d1c57506102069450505081016020015f80610cc7565b85548885018301529485019487945092810192610d01565b9150506020925061020694915060ff191682840152151560051b8201015f80610cc7565b9060405191825f8254610d6a81610c5f565b908184526020946001916001811690815f14610dd65750600114610d98575b5050506102069250038361043d565b5f90815285812095935091905b818310610dbe57505061020693508201015f8080610d89565b85548884018501529485019487945091830191610da5565b9250505061020694925060ff191682840152151560051b8201015f8080610d89565b906020610e0992818152019061026c565b90565b346101f7575f3660031901126101f7576102ce610e27610c97565b60405191829160208352602083019061026c565b346101f7575f3660031901126101f757602063ffffffff600c5416604051908152f35b346101f7575f3660031901126101f7575f80516020614225833981519152546040516001600160a01b039091168152602090f35b346101f7575f3660031901126101f757602063ffffffff600a5416604051908152f35b346101f75760203660031901126101f757610ece61038f565b5f8051602061426583398151915254906001600160401b0360ff8360401c1615921680159081610fd9575b6001149081610fcf575b159081610fc6575b50610fb4575f80516020614265833981519152805467ffffffffffffffff19166001179055610f3e9082610f9057612ff6565b610f4457005b5f80516020614265833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290806020810161094d565b5f80516020614265833981519152805460ff60401b1916600160401b179055612ff6565b60405163f92ee8a960e01b8152600490fd5b9050155f610f0b565b303b159150610f03565b839150610ef9565b346101f75760203660031901126101f7576004355f526005602052602060ff60405f2054166040519015158152f35b346101f75760203660031901126101f75760043561102d816101e9565b6110373633612905565b604051611043816103d9565b63ffffffff8092168082521561106757511663ffffffff19600a541617600a555f80f35b60405162461bcd60e51b815260206004820152601d60248201527f4f50503a20696e76616c696420726574656e74696f6e20636f6e6669670000006044820152606490fd5b346101f7575f3660031901126101f7576020600854604051908152f35b6004359061ffff821682036101f757565b359061ffff821682036101f757565b346101f7576040806003193601126101f7576111036110c9565b6024356001600160401b0381116101f757611122903690600401610493565b9160045415611205576111353633612905565b5f805160206142258339815191525461116490611158906001600160a01b031681565b6001600160a01b031690565b81516368fc2b7760e11b815261ffff841662010000176004820152336024820152908290829060449082905afa908115611200575f916111d2575b50156111af576105988383613157565b51639a28d60760e01b815233600482015261ffff91909116602482015260449150fd5b6111f29150823d84116111f9575b6111ea818361043d565b810190612465565b505f61119f565b503d6111e0565b61248b565b5163738c481160e01b8152600490fd5b91908260409103126101f75760405161122d816103f4565b8092803560ff811681036101f75782526020908101359161124d836101e9565b0152565b91906080838203126101f75760206112856040519261126f846103f4565b6040849661127d8382611215565b865201611215565b910152565b35906001600160401b03821682036101f757565b6001600160401b0381116103d45760051b60200190565b91909160409081818503126101f75781516112cf816103f4565b809482356112dc816101e9565b8252602092838101356001600160401b03918282116101f757019180601f840112156101f75782359061130e8261129e565b9661131b8151988961043d565b828852868089019360051b860101948286116101f757878101935b86851061134857505050505050500152565b84358681116101f75782019060609081601f1984880301126101f757845191611370836103b9565b61137b8c85016110da565b83528584013561138a816101e9565b8c840152830135918883116101f7576113aa878d80969581960101610493565b86820152815201940193611336565b9060406020610206936113e084825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b90610e0991602081526114146020820183516113b9565b602082015190611432610140928360a084015261016083019061026c565b9160c0611478611453604087015195601f199687878303018588015261026c565b606087015163ffffffff1660e08601526080870151868683030161010087015261026c565b60a08601516001600160401b0316610120850152940151928285030191015261026c565b346101f7576003196040368201126101f757600480356001600160401b03928382116101f7576101409082360301126101f7576114d761045e565b906114e436828501611251565b825260848101358481116101f75761150190843691840101610493565b602083015260a48101358481116101f75761152190843691840101610493565b604083015261153260c482016101fb565b606083015260e48101358481116101f75761155290843691840101610493565b6080830152611564610104820161128a565b60a0830152610124810135908482116101f757836115859236920101610493565b60c08201526024359283116101f7576115a76115ad926102ce943691016112b5565b906125d7565b604051918291826113fd565b346101f7575f3660031901126101f7576020600454604051908152f35b346101f7575f3660031901126101f75760206004541515604051908152f35b346101f757611603366102d2565b600490815415611880576116173633612905565b80156118705781541461162657005b5f81556003549081156118615761163b611bb3565b915f5480155f146117f5575061164f6132f9565b604090604085015261167c61166c6001600160401b034216611935565b6001600160401b031660a0860152565b611684611bf0565b600181529161169281613343565b916020928385019081525f5b8381106117825750505050806116b7836116d6966125d7565b93818501519060405196879283926347f5343160e01b84528301610df8565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4801561120057611749945f91611765575b505f5561173661171a60a08501516001600160401b031690565b6001600160401b03166001600160401b03196001541617600155565b61173e611c09565b92835282015261351d565b61175c611757600854611b42565b600855565b6105985f600355565b61177c9150823d84116105dd576105ce818361043d565b5f611700565b806117ee8161179b611795600195613050565b506133a7565b886117a8825161ffff1690565b9101516117dd6117bc825163ffffffff1690565b6117d16117c761046b565b61ffff9095168552565b63ffffffff16838c0152565b87820152855190610a568383611c7a565b500161169e565b604051630c9b3d8760e21b8152838101918252905f9082908190602001038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4908115611200575f9161183f575b5061164f565b61185b91503d805f833e611853818361043d565b810190612550565b5f611839565b60405163783bc0f160e11b8152fd5b50604051636ad43cc360e11b8152fd5b5060405163738c481160e01b8152fd5b634e487b7160e01b5f52601160045260245ffd5b90600163ffffffff809316019182116118b957565b611890565b604051906118cb826103f4565b5f6020838281520152565b604051906118e3826103f4565b816118ec6118be565b815260206112856118be565b604051906119058261040f565b606060c0838281526119156118d6565b60208201525f60408201525f838201525f60808201528260a08201520152565b906103e86001600160401b03809316029182169182036118b957565b60075481101561198a57600a9060075f52027fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68801905f90565b610327565b9060405161199c816103f4565b915460ff8116835260081c63ffffffff166020830152565b90604080516119c2816103f4565b809363ffffffff80825416835260018092018054916119e08361129e565b956119ee604051978861043d565b83875260209160208801935f5260205f20915f945b868610611a1857505050505050505060200152565b60028589928451611a28816103b9565b86885461ffff8116835260101c1683820152611a45858901610d58565b86820152815201940195019492611a03565b90604051611a64816103f4565b602061128560088395604051611a798161040f565b604051611a85816103f4565b611a8e8361198f565b8152611a9c6001840161198f565b868201528152611aae60028301610d58565b85820152611abe60038301610d58565b6040820152611ae4611ad7600484015463ffffffff1690565b63ffffffff166060830152565b611af060058301610d58565b6080820152611b1c611b0c60068401546001600160401b031690565b6001600160401b031660a0830152565b611b2860078301610d58565b60c08201528552016119b4565b919082018092116118b957565b5f1981146118b95760010190565b15611b5757565b60405162461bcd60e51b815260206004820152602e60248201527f4f50503a20717565756564206d6573736167652065786365656473204d41585f60448201526d454e56454c4f50455f425954455360901b6064820152608490fd5b60405190611bc08261040f565b606060c083611bcd6118d6565b81528260208201528260408201525f838201528260808201525f60a08201520152565b60405190611bfd826103f4565b60606020835f81520152565b60405190611c16826103f4565b81611c1f611bb3565b81526020611285611bf0565b90611c358261129e565b611c42604051918261043d565b8281528092611c53601f199161129e565b01905f5b828110611c6357505050565b602090611c6e611c09565b82828501015201611c57565b805182101561198a5760209160051b010190565b919082039182116118b957565b818110611ca6575050565b5f8155600101611c9b565b90601f8211611cbe575050565b61020691600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9906020601f840160051c83019310611d07575b601f0160051c0190611c9b565b9091508190611cfa565b9190601f8111611d2057505050565b610206925f5260205f20906020601f840160051c83019310611d0757601f0160051c0190611c9b565b919091828114611e3057611d5d8354610c5f565b6001600160401b0381116103d457611d7f81611d798454610c5f565b84611d11565b5f93601f8211600114611dbe57611daf92939482915f92611db3575b50508160011b915f199060031b1c19161790565b9055565b015490505f80611d9b565b611dd0601f198316915f5260205f2090565b94611dde845f5260205f2090565b915f5b818110611e1857509583600195969710611e00575b505050811b019055565b01545f1960f88460031b161c191690555f8080611df6565b87830154845560019384019390920191602001611de1565b509050565b611e3f8154610c5f565b9081611e49575050565b81601f5f9311600114611e5a575055565b908083918252611e79601f60208420940160051c840160018501611c9b565b5555565b600160401b82116103d4578054828255808310611e9957505050565b6001916001600160ff1b0382811683036118b957841684036118b9575f5260205f209060011b81019260011b015b828110611ed357505050565b805f60029255611ee4838201611e35565b01611ec7565b818114611f7757815491611efe8383611e7d565b5f5260205f20905f5260205f20905f905b838210611f1c5750505050565b806001918403611f37575b6002809101930191019091611f0f565b8054845461ffff191661ffff919091161784558054845465ffffffff0000191665ffffffff0000909116178455611f72818301858401611d49565b611f27565b5050565b818103611f86575050565b60018083611fae63ffffffff610206965416859063ffffffff1663ffffffff19825416179055565b019101611eea565b9061212057818114159182611fca57505050565b600880918461020695611fe1575b50019101611f7b565b80612091575b50611ff86002820160028601611d49565b6120086003820160038601611d49565b61203561201c600483015463ffffffff1690565b600486019063ffffffff1663ffffffff19825416179055565b6120456005820160058601611d49565b61207b61205c60068301546001600160401b031690565b60068601906001600160401b03166001600160401b0319825416179055565b61208b6007820160078601611d49565b5f611fd8565b6120ee575b6001840160018201908181036120ad575b50611fe7565b8154815460ff191660ff919091161781556120e79163ffffffff9054851c1664ffffffff0082549160081b169064ffffffff001916179055565b5f806120a7565b8054845460ff191660ff919091161784558054845464ffffffff00191690831c60081b64ffffffff0016178455612096565b610c4c565b60075480156121e3575f190161213a81611951565b612120575f815560015f6001830155600960029261215a60028201611e35565b61216660038201611e35565b5f600482015561217860058201611e35565b5f600682015561218a60078201611e35565b5f600882015501908154915f8155826121a7575b50505050600755565b6001600160ff1b03831683036118b9575f5260205f209160011b8201915b8281101561219e57805f8592556121dd838201611e35565b016121c5565b634e487b7160e01b5f52603160045260245ffd5b156121fe57565b60405162461bcd60e51b815260206004820152603a60248201527f4f50503a20656e76656c6f70652065786365656473204d41585f454e56454c4f60448201527f50455f42595445532028657374696d61746f72206472696674290000000000006064820152608490fd5b9081516001600160401b0381116103d45761228e81612289600b54610c5f565b611cb1565b602080601f83116001146122ce575081906122be93945f926122c35750508160011b915f199060031b1c19161790565b600b55565b015190505f80611d9b565b90601f19831694612300600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db990565b925f905b87821061233b575050836001959610612323575b505050811b01600b55565b01515f1960f88460031b161c191690555f8080612318565b80600185968294968601518155019501930190612304565b91909182516001600160401b0381116103d45761237481611d798454610c5f565b602080601f83116001146123a557508190611daf9394955f926122c35750508160011b915f199060031b1c19161790565b90601f198316956123b9855f5260205f2090565b925f905b8882106123f3575050836001959697106123db57505050811b019055565b01515f1960f88460031b161c191690555f8080611df6565b806001859682949686015181550195019301906123bd565b1561241257565b60405162461bcd60e51b815260206004820152602560248201527f4f50503a207265636f7264207374696c6c20696e20726574656e74696f6e2077604482015264696e646f7760d81b6064820152608490fd5b91908260409103126101f757815180151581036101f757602090920151610e09816101e9565b6040513d5f823e3d90fd5b908160209103126101f7575190565b90602080835260609060608401908063ffffffff94858151168288015201519460408080830152865180945260808201908360808660051b8501019801955f935b8685106124f95750505050505050505090565b90919293949596979998868061253b600193868f89908f607f198a8603018d52519061ffff8251168552868201511686850152015191818a820152019061026c565b9b9c9a019897969190910194019291906124e6565b6020818303126101f7578051906001600160401b0382116101f7570181601f820112156101f75780519061258382610478565b92612591604051948561043d565b828452602083830101116101f757815f9260208093018386015e8301015290565b90916125c9610e099360408452604084019061026c565b91602081840391015261026c565b906125e0611bb3565b506040805163765809ab60e11b81526004926020929173__$b91d2ad766fd07dc99f56c1a0b020615bb$__919084818061261c858a83016124a5565b0381865af4908115611200575f91612877575b5060808701908582518051155f146127f35750508451630c9b3d8760e21b8152878101918252905f90829081906020010381875af49283156112005761269c9361268f93612685935f926127d7575b50526131ef565b5163ffffffff1690565b63ffffffff166060870152565b815162409a0960e81b81525f81806126b6898983016113fd565b0381855af4908115611200575f916127bd575b50858401805180518061278257505052815163b93612a760e01b8152938385806126f5898583016113fd565b0381855af49283156112005761272e955f958695612763575b505051630c9b3d8760e21b81529081019283529384928391829160200190565b03915af4908115611200575f91612749575b5060c082015290565b61275d91503d805f833e611853818361043d565b5f612740565b61277a929550803d106105dd576105ce818361043d565b925f8061270e565b9150939250848496959296012090855190860120036127a2575050505090565b51630ca2e06d60e11b815292839261057592919084016125b2565b6127d191503d805f833e611853818361043d565b5f6126c9565b6127ec9192503d805f833e611853818361043d565b905f61267e565b9091935061281492508551809381926347f5343160e01b83528a8301610df8565b0381865af4908115611200575f9161285a575b5081810361283657505061269c565b8351631d97afef60e31b815280870192835260208301919091529081906040010390fd5b6128719150853d87116105dd576105ce818361043d565b5f612827565b61288e9150853d87116105dd576105ce818361043d565b5f61262f565b6004116101f7575f90600490565b6001600160e01b031990358181169392600481106128bf57505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f80516020614225833981519152805461293d906001600160a01b031661293461292e86612894565b906128a2565b9084309161367f565b901561294a575b50505050565b63ffffffff1615612a01575f80516020614225833981519152805460ff60a01b1916600160a01b17905561298b90611158908190546001600160a01b031690565b91823b156101f7576129b6925f9283604051809681958294634a63ebf760e11b8452600484016128ce565b03925af18015611200576129e8575b505f80516020614225833981519152805460ff60a01b191690555f808080612944565b806129f56129fb9261042a565b80610262565b5f6129c5565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f8051602061422583398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b600d5480612a945750604051612a8e816103d9565b5f815290565b60405190630c9b3d8760e21b825260048201525f8160248173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4908115611200575f91612ad4575090565b610e0991503d805f833e611853818361043d565b61010090602080910190602082510151515f925b818410612b0a575050505090565b909192936018808201918281116118b9576040612b2b888887510151611c7a565b5101515101018091116118b95793600101929190612afc565b805191908290602001825e015f815290565b612b5e610bf5565b612b66613911565b92604080518095602094858301612b7c91612b44565b612b8591612b44565b0394601f19958681018252612b9a908261043d565b8451612ba590613759565b908251918291868301612bb791612b44565b612bc091612b44565b038681018252612bd0908261043d565b612bd8613928565b908251918291868301612bea91612b44565b612bf391612b44565b038681018252612c03908261043d565b83850151612c1090613795565b908151612c23906001600160401b031690565b612c2c90613a38565b908351918291878301612c3e91612b44565b612c4791612b44565b038781018252612c57908261043d565b8251918291868301612c6891612b44565b612c7191612b44565b038681018252612c81908261043d565b612c8961393b565b908251918291868301612c9b91612b44565b612ca491612b44565b038681018252612cb4908261043d565b848201516001600160401b0316612cca90613a38565b908251918291868301612cdc91612b44565b612ce591612b44565b038681018252612cf5908261043d565b612cfd61394e565b908251918291868301612d0f91612b44565b612d1891612b44565b038681018252612d28908261043d565b6060850151612d3c9063ffffffff16613a38565b908251918291868301612d4e91612b44565b612d5791612b44565b038681018252612d67908261043d565b612d6f613961565b908251918291868301612d8191612b44565b612d8a91612b44565b038681018252612d9a908261043d565b6080850151612dae9063ffffffff16613a38565b908251918291868301612dc091612b44565b612dc991612b44565b038681018252612dd9908261043d565b612de1613974565b908251918291868301612df391612b44565b612dfc91612b44565b038681018252612e0c908261043d565b60a0850151612e1a90613759565b908251918291868301612e2c91612b44565b612e3591612b44565b038681018252612e45908261043d565b915f925b60c08601805151851015612f0f57612e8f612f0560c093612ef9612ead612ea78a6001978f612ea1908f612e958f612e8f612e826139b9565b91519b8c94850190612b44565b90612b44565b0390810188528761043d565b51611c7a565b51613ab7565b612eeb8d8c612edf8c612e8f612ed2612ecd88516001600160401b031690565b613a38565b9151988994850190612b44565b0390810185528461043d565b88519485938c850190612b44565b038a810183528261043d565b9401939050612e49565b5095509350505050565b63ffffffff8082165f52600960205260405f2090815416612f7a57612f50600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b612f969063ffffffff91829182600a541690612fd7565b16908115159081612fbf575b50612faa5750565b5f5260096020525f6001604082208281550155565b9050815f52600960205260405f20541615155f612fa2565b63ffffffff91908282168382161115612fef57031690565b5050505f90565b61301f90613002613bb7565b61300a613bb7565b613012613bb7565b61301a613bb7565b612a21565b613027613bb7565b61302f613bb7565b60c860405161303d816103d9565b5260c863ffffffff19600a541617600a55565b60025481101561198a5760025f5260011b7f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01905f90565b929190612120578051835461ffff191661ffff91909116178355600180930190602080910151938451916001600160401b0383116103d4576130d4836130ce8654610c5f565b86611d11565b602091601f84116001146131065750508190611daf9394955f926122c35750508160011b915f199060031b1c19161790565b95601f9291921984169661311d865f5260205f2090565b935f915b89831061314057505050836001959697106123db57505050811b019055565b838501518655948501949381019391810191613121565b600254916003548084115f146131ac575061020692506131856040519261317d846103f4565b61ffff168352565b60208201526131a66003546131a161319c82611b42565b600355565b613050565b90613088565b6131b590611b42565b60035561ffff604051926131c8846103f4565b1682526020820152600160401b8210156103d4576131a68260016102069401600255613050565b613222916131fb610bf5565b906132046139d9565b612e8f613268604092604051978891612e8f60209889850190612b44565b0396613236601f199889810183528261043d565b61325c61324d612ecd6108558a5163ffffffff1690565b60405194859389850190612b44565b0387810183528261043d565b915f925b848601805151851015612f0f57612e8f6132ef612e8f612ef989956132e18d6132bb6132b58d60019a612ea18f6132a98f91612e8f612e82613928565b0386810189528861043d565b51613be5565b92612edf6132d3612ecd86516001600160401b031690565b8d519788938d850190612b44565b885194859388850190612b44565b940193905061326c565b604051613305816103d9565b5f8152905f368137565b6040519061331c826103f4565b6001825260203681840137565b60405190613336826103f4565b600a825260203681840137565b9061334d8261129e565b60409061335d604051918261043d565b838152809361336e601f199161129e565b01915f5b83811061337f5750505050565b602090825161338d816103b9565b5f8152825f81830152606085830152828601015201613372565b906040516133b4816103f4565b60206112856001839561ffff815416855201610d58565b8151815463ffffffff191663ffffffff91821617825591600180920160208092015191808351936133fc8585611e7d565b01915f52805f205f925b8484106134165750505050505050565b8051805183548583015161ffff90921665ffffffffffff199091161790891660101b65ffffffff0000161783556040015180519187840191906001600160401b0384116103d45782869261346f86611d798d9754610c5f565b8390601f87116001146134af5795806134a092600297985f926122c35750508160011b915f199060031b1c19161790565b90555b01920193019290613406565b9590601f198216966134c4845f5260205f2090565b975f5b818110613504575091879891846002999594106134ec575b505050811b0190556134a3565b01515f1960f88460031b161c191690555f80806134df565b838301518a5597909801978d978b9793840193016134c7565b600754600160401b8110156103d45780600161353c9201600755611951565b61212057600860208361367660c061020696516135d6815186613598825161357260ff8251168d9060ff1660ff19825416179055565b909101518a5464ffffffff00191660089190911b64ffffffff0016178a5563ffffffff90565b878060018c019301516135b960ff825116859060ff1660ff19825416179055565b0151825464ffffffff001916911660081b64ffffffff0016179055565b6135e68582015160028901612353565b6135f7604082015160038901612353565b61362461360b606083015163ffffffff1690565b600489019063ffffffff1663ffffffff19825416179055565b613635608082015160058901612353565b61366b61364c60a08301516001600160401b031690565b60068901906001600160401b03166001600160401b0319825416179055565b015160078601612353565b015191016133cb565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b038411176103d4576040935f9385528380528360205251915afa6136ee57565b9150505f51906020518060201c150290565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811630811491821561373e575b50506106e757565b5f805160206142458339815191525416141590505f80613736565b610e09612e8f916137876137766001600160401b03835116613a38565b916040519485936020850190612b44565b03601f19810183528261043d565b61379d610bf5565b906137a6613911565b60408051938460209384928383016137bd91612b44565b6137c691612b44565b0393601f199485810187526137db908761043d565b80516137e690613d8f565b9586516137f9906001600160401b031690565b61380290613a38565b90845191829185830161381491612b44565b61381d91612b44565b03868101825261382d908261043d565b835196879184830161383e91612b44565b61384791612b44565b038581018752613857908761043d565b61385f613928565b95835196879184830161387191612b44565b61387a91612b44565b03858101875261388a908761043d565b015161389590613d8f565b9081516138a8906001600160401b031690565b6138b190613a38565b9481519586918583016138c391612b44565b6138cc91612b44565b0384810186526138dc908661043d565b5193849283016138eb91612b44565b6138f491612b44565b039081018252610e09908261043d565b80511561198a5760200190565b61391961330f565b600a61392482613904565b5390565b61393061330f565b601261392482613904565b61394361330f565b602861392482613904565b61395661330f565b603061392482613904565b61396961330f565b603861392482613904565b61397c613329565b60a25f5b607f808311156139a55790608060019284161760208286010153019060071c90613980565b50607f600192166020828501015301815290565b6139c1613329565b61014260015f60c26020850153019060071c90613980565b6139e161330f565b600861392482613904565b6139f461330f565b601061392482613904565b613a0761330f565b601a61392482613904565b613a1a61330f565b603261392482613904565b613a2d61330f565b604261392482613904565b9060809160806001600160401b03821610613a9357613a55613329565b5f5b607f80841115613a7b57908560019285161760208285010153019160071c91613a57565b5090929350607f600192166020828501015301815290565b909150613a9e61330f565b9060f81b6001600160f81b0319165f1a61392482613904565b613abf610bf5565b90613ac8613911565b6040805193846020938492838301613adf91612b44565b613ae891612b44565b0393601f19948581018752613afd908761043d565b8051613b0890613e47565b958651613b1b906001600160401b031690565b613b2490613a38565b908451918291858301613b3691612b44565b613b3f91612b44565b038681018252613b4f908261043d565b8351968791848301613b6091612b44565b613b6991612b44565b038581018752613b79908761043d565b613b81613928565b958351968791848301613b9391612b44565b613b9c91612b44565b038581018752613bac908761043d565b0151613895906131ef565b60ff5f805160206142658339815191525460401c1615613bd357565b604051631afcd79f60e31b8152600490fd5b610e09613c26612e8f92613cde613cca612e8f613cd1613c03610bf5565b94613c0c6139d9565b9060409485928351998a91612e8f60209b8c850190612b44565b0398613c3a601f199a8b810183528261043d565b613c7989613c6d613c5d612ecd613c56613c56895161ffff1690565b61ffff1690565b612e8f8851958694850190612b44565b038b810183528261043d565b613c8889613c6d613c5d6139ec565b613ca589613c6d613c5d612ecd6108558489015163ffffffff1690565b613cbe613cb06139ff565b85519d8e938c850190612b44565b038981018c528b61043d565b0151613759565b9151968794850190612b44565b0390810183528261043d565b90813b15613d6e575f8051602061424583398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115613d5357613d5091614181565b50565b505034613d5c57565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b610e09612e8f612e8f613cde613e37612ecd6108556020612e8f98613e2c612e8f613e06613dbb610bf5565b613dd5613dc66139d9565b6040519d8e9389850190612b44565b039a613de9601f199c8d810183528261043d565b613c6d61324d612ecd613e00613e00895160ff1690565b60ff1690565b613e20613e116139ec565b6040519a8b9387850190612b44565b0389810189528861043d565b015163ffffffff1690565b6040519586936020850190612b44565b613e4f610bf5565b90613e58613911565b91604080518094602093848301613e6e91612b44565b613e7791612b44565b0392601f19938481018652613e8c908661043d565b8051613e9790613795565b948551613eaa906001600160401b031690565b613eb390613a38565b908351918291868301613ec591612b44565b613ece91612b44565b038581018252613ede908261043d565b8251958691858301613eef91612b44565b613ef891612b44565b038481018652613f08908661043d565b613f10613928565b948251958691858301613f2291612b44565b613f2b91612b44565b038481018652613f3b908661043d565b82810151613f4890613759565b948251958691858301613f5a91612b44565b613f6391612b44565b038481018652613f73908661043d565b613f7b6139ff565b948251958691858301613f8d91612b44565b613f9691612b44565b038481018652613fa6908661043d565b81810151613fb390613759565b948251958691858301613fc591612b44565b613fce91612b44565b038481018652613fde908661043d565b613fe661393b565b948251958691858301613ff891612b44565b61400191612b44565b038481018652614011908661043d565b60608101516140259063ffffffff16613a38565b94825195869185830161403791612b44565b61404091612b44565b038481018652614050908661043d565b614058613a12565b94825195869185830161406a91612b44565b61407391612b44565b038481018652614083908661043d565b608081015161409190613759565b9482519586918583016140a391612b44565b6140ac91612b44565b0384810186526140bc908661043d565b6140c4613961565b9482519586918583016140d691612b44565b6140df91612b44565b0384810186526140ef908661043d565b60a08101516001600160401b031661410690613a38565b94825195869185830161411891612b44565b61412191612b44565b038481018652614131908661043d565b614139613a25565b94825195869185830161414b91612b44565b61415491612b44565b038481018652614164908661043d565b60c0015161417190613759565b905193849283016138eb91612b44565b5f80610e0993602081519101845af43d156141bd573d916141a183610478565b926141af604051948561043d565b83523d5f602085013e6141c1565b6060915b906141e857508051156141d657602081519101fd5b60405163d6bda27560e01b8152600490fd5b8151158061421b575b6141f9575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156141f156fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a26469706673582212201dd53cc2caf86754ed88247c9b779f72471445dd4bcf6d516a7bf3bb29c0dce864736f6c63430008190033", + "bytecode": "0x60a0806040523460c857306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009081549060ff8260401c1660b957506001600160401b036002600160401b0319828216016075575b6040516147a190816100cd82396080518181816109570152613bd20152f35b6001600160401b031990911681179091556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80806056565b63f92ee8a960e01b8152600490fd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806302ddde7b1461028457806304676e381461027f5780630d6f1e9d1461027a57806316a91b741461027557806327e935a11461027057806347453a2a1461026b5780634b3254fb146102665780634ed891d0146102615780634f1ef2861461025c5780634fca79cb1461025757806352d1902d14610252578063621fbd591461024d5780636744c79f146102485780637a9e5e4b146102435780638cbc18391461023e5780638fb360371461023957806393ec06be1461023457806395fabcf01461022f578063a3ad9cc31461022a578063a593aec214610225578063a9e50c1014610220578063ad3cb1cc1461021b578063b632ab5714610216578063b8f8d7e514610211578063bf7e214f1461020c578063c16a8a5614610207578063c4d66de814610202578063c8c14d17146101fd578063c94c93e7146101f8578063cde78421146101f3578063d5b5272a146101ee578063de152649146101e9578063e0064900146101e4578063e0c14aa8146101df578063e0c1c0b4146101da578063e2fc04ae146101d5578063e36f9a07146101d0578063e44efb5a146101cb5763f5786b4b146101c6575f80fd5b61193a565b61191b565b6118fe565b6117e1565b611515565b6113e9565b6113ac565b61138f565b61129a565b611272565b611243565b611227565b61110d565b6110ea565b6110b6565b611093565b611077565b611032565b610ffa565b610f00565b610c12565b610bf5565b610bd9565b610b89565b610ac2565b610a48565b6109d4565b6109ae565b610945565b610899565b61078e565b610628565b6105c8565b61056e565b610511565b6104ed565b610431565b610393565b610297565b5f91031261029357565b5f80fd5b34610293575f366003190112610293575f8051602061474c833981519152805460ff8160401c16908115610363575b50610351575f8051602061474c833981519152805467ffffffffffffffff19166002179055805460ff60401b1916600160401b179055610304611f66565b5f8051602061474c833981519152805460ff60401b19169055604051600281527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29080602081015b0390a1005b60405163f92ee8a960e01b8152600490fd5b600291506001600160401b031610155f6102c6565b63ffffffff81160361029357565b359061039182610378565b565b346102935760203660031901126102935760606004356103b281610378565b63ffffffff8091165f52600960205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60409063ffffffff61042e949316815281602082015201906103ed565b90565b34610293575f3660031901126102935761045d6104546104105463ffffffff1690565b63ffffffff1690565b61046681611fe8565b90610479610473826105f8565b60051c90565b5f5b8181106104d5575050601f811690816104b2575b8261049f600c5463ffffffff1690565b6104ae60405192839283610411565b0390f35b81602091038301019060015f199160200360031b1b011981511690525f8061048f565b806001916010015460208260051b870101520161047b565b34610293575f36600319011261029357602063ffffffff6104105416604051908152f35b346102935760203660031901126102935760043561052f3633612d6b565b8015610548576004541561053f57005b6004555f600355005b604051636ad43cc360e11b8152600490fd5b634e487b7160e01b5f52603260045260245ffd5b34610293576020366003190112610293576004356006548110156102935760065f527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01546040516001600160a01b039091168152602090f35b34610293575f3660031901126102935760206040516104008152f35b634e487b7160e01b5f52601160045260245ffd5b90601f820180921161060657565b6105e4565b9061010091820180921161060657565b9190820180921161060657565b34610293575f3660031901126102935760206040516101188152f35b600435906001600160a01b038216820361029357565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761068957604052565b61065a565b602081019081106001600160401b0382111761068957604052565b604081019081106001600160401b0382111761068957604052565b60e081019081106001600160401b0382111761068957604052565b6001600160401b03811161068957604052565b90601f801991011681019081106001600160401b0382111761068957604052565b60405190610391826106c4565b604051906103918261066e565b6001600160401b03811161068957601f01601f191660200190565b81601f820112156102935780359061075f8261072d565b9261076d60405194856106f2565b8284526020838301011161029357815f926020809301838601378301015290565b6040366003190112610293576107a2610644565b6024356001600160401b038111610293576107c1903690600401610748565b906107ca613bc8565b6107d43633612d6b565b6107dc613bc8565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610868575b5061082e57604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f8051602061472c833981519152830361084f5761084d92506141b2565b005b604051632a87526960e21b815260048101849052602490fd5b61088b91945060203d602011610892575b61088381836106f2565b8101906128a0565b925f610804565b503d610879565b34610293576020366003190112610293576104ae6004356108b981610378565b60405f8180516108c88161066e565b828152826020820152015263ffffffff8092165f5260096020526001815f206001600160401b038351946108fb8661066e565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b34610293575f366003190112610293577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316300361099c5760206040515f8051602061472c8339815191528152f35b60405163703e46dd60e11b8152600490fd5b34610293575f3660031901126102935760205f54604051908152f35b8015150361029357565b34610293576020366003190112610293576004356109f1816109ca565b600f546001600160a01b031633819003610a2a57507fcf8be8503aed7d1346a99aa4bcf2fadebd86e033736ff955ae30798c1fa172755d005b60449060405190636e01d1ef60e11b82523360048301526024820152fd5b3461029357602036600319011261029357610a61610644565b5f8051602061470c833981519152546001600160a01b03919082163303610aab57803b15610a925761084d90612e87565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b3461029357604036600319011261029357602435600435610ae33633612d6b565b63ffffffff6104105416610b445781811080610b38575b610b039061201a565b818110610b0c57005b80610b32610b1b600193612066565b81549060031b906001821b915f19901b1916179055565b01610b03565b50610400821115610afa565b60405162461bcd60e51b815260206004820152601f60248201527f4f50503a206f7574626f756e642062616e6b20616c7265616479206c697665006044820152606490fd5b34610293575f366003190112610293575f8051602061470c8339815191525460a01c60ff1615610bd1576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f610bbe565b34610293575f3660031901126102935760206040516180008152f35b34610293575f366003190112610293576020600354604051908152f35b3461029357602036600319011261029357600435610c2f81610378565b610c393633612d6b565b600c5463ffffffff16610c4e61045482612078565b63ffffffff831603610ed15750610c636120c7565b63ffffffff82166060820152610c94610c846001600160401b034216612104565b6001600160401b03166040830152565b610c9c612edf565b60a08201525f600854908115159182610d3e575b7fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88761034c86610d32610ce188612f60565b91610cf161800084511115612793565b610d1383516020850120610d058184613323565b610d0e83613389565b600d55565b610d1c836133e1565b63ffffffff1663ffffffff19600c541617600c55565b60405191829182612805565b5f92836101005b838210610e70575b5050610d639083159081610e68575b501561212e565b81610d6d81612209565b60c086019081525f5b828110610e30575050610d889161226c565b905f5b828110610e0257505f5b818110610df1575050610ce161034c93610de7610de27fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d8879795610dda610d3296600855565b600e5461226c565b600e55565b9350829450610cb0565b600190610dfc6126c1565b01610d95565b80610e2a610e1a610e156001948661061b565b611caa565b50610e2483611caa565b90612552565b01610d8b565b6001919250610e5e81610e4281611caa565b50610e4e865191611e88565b610e588383612258565b52612258565b5001908391610d76565b90505f610d5c565b610e8d610e88610e8284979894611caa565b50611e88565b612c9e565b90618000610e9b838361061b565b11610ec757610eb982610eb3600194610ebf9461061b565b9461061b565b96612120565b940190610d45565b5081959450610d4d565b610eda90612078565b6040516307d3e83d60e51b815263ffffffff918216600482015291166024820152604490fd5b3461029357602036600319011261029357600435610f1d81610378565b610f273633612d6b565b63ffffffff9081811691825f52600960205260405f20541615610fb557610f84610f8f91610f70610f68610454610f63600c5463ffffffff1690565b613432565b851115612816565b63ffffffff165f52600960205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b60405162461bcd60e51b815260206004820152601760248201527f4f50503a207265636f7264206e6f742070726573656e740000000000000000006044820152606490fd5b34610293575f3660031901126102935760206001600160401b0360015416604051908152f35b6040519061102d8261068e565b5f8252565b34610293575f366003190112610293576104ae604051611051816106a9565b60058152640352e302e360dc1b60208201526040519182916020835260208301906103ed565b34610293575f3660031901126102935760206040516120008152f35b34610293575f36600319011261029357602063ffffffff600c5416604051908152f35b34610293575f366003190112610293575f8051602061470c833981519152546040516001600160a01b039091168152602090f35b34610293575f36600319011261029357602063ffffffff600a5416604051908152f35b3461029357602036600319011261029357611126610644565b5f8051602061474c83398151915254906001600160401b0360ff8360401c161592168015908161121f575b6001149081611215575b15908161120c575b50610351575f8051602061474c833981519152805467ffffffffffffffff1916600117905561119690826111e857613445565b61119c57005b5f8051602061474c833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290806020810161034c565b5f8051602061474c833981519152805460ff60401b1916600160401b179055613445565b9050155f611163565b303b15915061115b565b839150611151565b34610293575f3660031901126102935760206040516101008152f35b34610293576020366003190112610293576004355f526005602052602060ff60405f2054166040519015158152f35b34610293575f36600319011261029357600f546040516001600160a01b039091168152602090f35b34610293576020366003190112610293576004356112b781610378565b6112c13633612d6b565b604051906112ce8261068e565b63ffffffff81168083521561134a577f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69161132a611311600a5463ffffffff1690565b9163ffffffff90511663ffffffff19600a541617600a55565b6040805163ffffffff92831681529290911660208301528190810161034c565b60405162461bcd60e51b815260206004820152601d60248201527f4f50503a20696e76616c696420726574656e74696f6e20636f6e6669670000006044820152606490fd5b34610293575f366003190112610293576020600e54604051908152f35b34610293575f366003190112610293576020600854604051908152f35b6004359061ffff8216820361029357565b359061ffff8216820361029357565b3461029357604080600319360112610293576114036113c9565b6024356001600160401b03811161029357611422903690600401610748565b9160045415611505576114353633612d6b565b5f8051602061470c8339815191525461146490611458906001600160a01b031681565b6001600160a01b031690565b81516368fc2b7760e11b815261ffff841662010000176004820152336024820152908290829060449082905afa908115611500575f916114d2575b50156114af5761084d8383613669565b51639a28d60760e01b815233600482015261ffff91909116602482015260449150fd5b6114f29150823d84116114f9575b6114ea81836106f2565b810190612870565b505f61149f565b503d6114e0565b612895565b5163738c481160e01b8152600490fd5b346102935760203660031901126102935761152e610644565b6115383633612d6b565b600f80546001600160a01b0319166001600160a01b0392909216919091179055005b919082604091031261029357604051611572816106a9565b8092803560ff811681036102935782526020908101359161159283610378565b0152565b91906080838203126102935760206115ca604051926115b4846106a9565b604084966115c2838261155a565b86520161155a565b910152565b35906001600160401b038216820361029357565b6001600160401b0381116106895760051b60200190565b9190916040908181850312610293578151611614816106a9565b8094823561162181610378565b8252602092838101356001600160401b039182821161029357019180601f8401121561029357823590611653826115e3565b96611660815198896106f2565b828852868089019360051b8601019482861161029357878101935b86851061168d57505050505050500152565b84358681116102935782019060609081601f198488030112610293578451916116b58361066e565b6116c08c85016113da565b8352858401356116cf81610378565b8c84015283013591888311610293576116ef878d80969581960101610748565b8682015281520194019361167b565b90604060206103919361172584825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b9061042e91602081526117596020820183516116fe565b602082015190611777610140928360a08401526101608301906103ed565b9160c06117bd611798604087015195601f19968787830301858801526103ed565b606087015163ffffffff1660e0860152608087015186868303016101008701526103ed565b60a08601516001600160401b031661012085015294015192828503019101526103ed565b346102935760031960403682011261029357600480356001600160401b0392838211610293576101409082360301126102935761181c610713565b9061182936828501611596565b825260848101358481116102935761184690843691840101610748565b602083015260a48101358481116102935761186690843691840101610748565b604083015261187760c48201610386565b606083015260e48101358481116102935761189790843691840101610748565b60808301526118a961010482016115cf565b60a08301526101248101359084821161029357836118ca9236920101610748565b60c0820152602435928311610293576118ec6118f2926104ae943691016115fa565b906129e1565b60405191829182611742565b34610293575f366003190112610293576020600454604051908152f35b34610293575f3660031901126102935760206004541515604051908152f35b3461029357602080600319360112610293576004908135825415611c99576119623633612d6b565b8015611c885782541461197157005b5f82556003548015611c7757611985612191565b5f5480611c0b5750611995611f9e565b60409060408301526119c26119b26001600160401b034216612104565b6001600160401b031660a0840152565b6119ca6121ce565b600181529283916119da8261380b565b8684019081525f5b838110611b8f57505050506119f6916129e1565b91611a188180850151604051809381926347f5343160e01b8352898301612805565b038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91611b72575b505f55611a75611a5960a08501516001600160401b031690565b6001600160401b03166001600160401b03196001541617600155565b611a7d6121e7565b928352820152611a8c81612c9e565b917fcf8be8503aed7d1346a99aa4bcf2fadebd86e033736ff955ae30798c1fa172755c611b2457611abf83600e5461061b565b906120008211611aff575050611af691611adb610de2926139e5565b611aee611ae9600854612120565b600855565b600e5461061b565b61084d5f600355565b604080516306f8bcbb60e51b8152918201928352612000602084015290918291010390fd5b618000611b308461060b565b11611b465750611af691611adb610de2926139e5565b611b4f8361060b565b6040805163048c985b60e51b815292830191825261800060208301528291010390fd5b611b899150823d84116108925761088381836106f2565b5f611a3f565b60019192939450611bff81611bac611ba68261349f565b5061386f565b8a611bb9825161ffff1690565b910151611bee611bcd825163ffffffff1690565b611be2611bd8610720565b61ffff9095168552565b63ffffffff16838e0152565b87820152855190610e588383612258565b500190859392916119e2565b604051630c9b3d8760e21b8152858101918252905f9082908190602001038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91611c55575b50611995565b611c7191503d805f833e611c6981836106f2565b81019061295a565b5f611c4f565b60405163783bc0f160e11b81528390fd5b604051636ad43cc360e11b81528390fd5b60405163738c481160e01b81528390fd5b600754811015611ce357600a9060075f52027fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68801905f90565b61055a565b90604051611cf5816106a9565b915460ff8116835260081c63ffffffff166020830152565b90600182811c92168015611d3b575b6020831014611d2757565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611d1c565b9060405191825f8254611d5781611d0d565b908184526020946001916001811690815f14611dc35750600114611d85575b505050610391925003836106f2565b5f90815285812095935091905b818310611dab57505061039193508201015f8080611d76565b85548884018501529485019487945091830191611d92565b9250505061039194925060ff191682840152151560051b8201015f8080611d76565b9060408051611df3816106a9565b809363ffffffff8082541683526001809201805491611e11836115e3565b95611e1f60405197886106f2565b83875260209160208801935f5260205f20915f945b868610611e4957505050505050505060200152565b60028589928451611e598161066e565b86885461ffff8116835260101c1683820152611e76858901611d45565b86820152815201940195019492611e34565b90604051611e95816106a9565b60206115ca60088395604051611eaa816106c4565b604051611eb6816106a9565b611ebf83611ce8565b8152611ecd60018401611ce8565b868201528152611edf60028301611d45565b85820152611eef60038301611d45565b6040820152611f15611f08600484015463ffffffff1690565b63ffffffff166060830152565b611f2160058301611d45565b6080820152611f4d611f3d60068401546001600160401b031690565b6001600160401b031660a0830152565b611f5960078301611d45565b60c0820152855201611de5565b5f806008545b808210611f7a575050600e55565b9091611f8b610e88610e8285611caa565b8101809111610606579160010190611f6c565b604051611faa8161068e565b5f8152905f368137565b60405190611fc1826106a9565b6001825260203681840137565b60405190611fdb826106a9565b600a825260203681840137565b90611ff28261072d565b611fff60405191826106f2565b8281528092612010601f199161072d565b0190602036910137565b1561202157565b60405162461bcd60e51b815260206004820152601a60248201527f4f50503a20696e76616c6964207072657761726d2072616e67650000000000006044820152606490fd5b610400811015611ce357601001905f90565b90600163ffffffff8093160191821161060657565b6040519061209a826106a9565b5f6020838281520152565b604051906120b2826106a9565b816120bb61208d565b815260206115ca61208d565b604051906120d4826106c4565b606060c0838281526120e46120a5565b60208201525f60408201525f838201525f60808201528260a08201520152565b906103e86001600160401b038093160291821691820361060657565b5f1981146106065760010190565b1561213557565b60405162461bcd60e51b815260206004820152602e60248201527f4f50503a20717565756564206d6573736167652065786365656473204d41585f60448201526d454e56454c4f50455f425954455360901b6064820152608490fd5b6040519061219e826106c4565b606060c0836121ab6120a5565b81528260208201528260408201525f838201528260808201525f60a08201520152565b604051906121db826106a9565b60606020835f81520152565b604051906121f4826106a9565b816121fd612191565b815260206115ca6121ce565b90612213826115e3565b61222060405191826106f2565b8281528092612231601f19916115e3565b01905f5b82811061224157505050565b60209061224c6121e7565b82828501015201612235565b8051821015611ce35760209160051b010190565b9190820391821161060657565b634e487b7160e01b5f525f60045260245ffd5b818110612297575050565b5f815560010161228c565b9190601f81116122b157505050565b610391925f5260205f20906020601f840160051c830193106122db575b601f0160051c019061228c565b90915081906122ce565b9190918281146123cc576122f98354611d0d565b6001600160401b0381116106895761231b816123158454611d0d565b846122a2565b5f93601f821160011461235a5761234b92939482915f9261234f575b50508160011b915f199060031b1c19161790565b9055565b015490505f80612337565b61236c601f198316915f5260205f2090565b9461237a845f5260205f2090565b915f5b8181106123b45750958360019596971061239c575b505050811b019055565b01545f1960f88460031b161c191690555f8080612392565b8783015484556001938401939092019160200161237d565b509050565b6123db8154611d0d565b90816123e5575050565b81601f5f93116001146123f6575055565b908083918252612415601f60208420940160051c84016001850161228c565b5555565b600160401b821161068957805482825580831061243557505050565b6001916001600160ff1b0382811683036106065784168403610606575f5260205f209060011b81019260011b015b82811061246f57505050565b805f600292556124808382016123d1565b01612463565b8181146125135781549161249a8383612419565b5f5260205f20905f5260205f20905f905b8382106124b85750505050565b8060019184036124d3575b60028091019301910190916124ab565b8054845461ffff191661ffff919091161784558054845465ffffffff0000191665ffffffff000090911617845561250e8183018584016122e5565b6124c3565b5050565b818103612522575050565b6001808361254a63ffffffff610391965416859063ffffffff1663ffffffff19825416179055565b019101612486565b906126bc5781811415918261256657505050565b60088091846103919561257d575b50019101612517565b8061262d575b5061259460028201600286016122e5565b6125a460038201600386016122e5565b6125d16125b8600483015463ffffffff1690565b600486019063ffffffff1663ffffffff19825416179055565b6125e160058201600586016122e5565b6126176125f860068301546001600160401b031690565b60068601906001600160401b03166001600160401b0319825416179055565b61262760078201600786016122e5565b5f612574565b61268a575b600184016001820190818103612649575b50612583565b8154815460ff191660ff919091161781556126839163ffffffff9054851c1664ffffffff0082549160081b169064ffffffff001916179055565b5f80612643565b8054845460ff191660ff919091161784558054845464ffffffff00191690831c60081b64ffffffff0016178455612632565b612279565b600754801561277f575f19016126d681611caa565b6126bc575f815560015f600183015560096002926126f6600282016123d1565b612702600382016123d1565b5f6004820155612714600582016123d1565b5f6006820155612726600782016123d1565b5f600882015501908154915f815582612743575b50505050600755565b6001600160ff1b0383168303610606575f5260205f209160011b8201915b8281101561273a57805f8592556127798382016123d1565b01612761565b634e487b7160e01b5f52603160045260245ffd5b1561279a57565b60405162461bcd60e51b815260206004820152603a60248201527f4f50503a20656e76656c6f70652065786365656473204d41585f454e56454c4f60448201527f50455f42595445532028657374696d61746f72206472696674290000000000006064820152608490fd5b90602061042e9281815201906103ed565b1561281d57565b60405162461bcd60e51b815260206004820152602560248201527f4f50503a207265636f7264207374696c6c20696e20726574656e74696f6e2077604482015264696e646f7760d81b6064820152608490fd5b91908260409103126102935760208251612889816109ca565b92015161042e81610378565b6040513d5f823e3d90fd5b90816020910312610293575190565b90602080835260609060608401908063ffffffff94858151168288015201519460408080830152865180945260808201908360808660051b8501019801955f935b8685106129035750505050505050505090565b909192939495969799988680612945600193868f89908f607f198a8603018d52519061ffff8251168552868201511686850152015191818a82015201906103ed565b9b9c9a019897969190910194019291906128f0565b602081830312610293578051906001600160401b038211610293570181601f820112156102935780519061298d8261072d565b9261299b60405194856106f2565b8284526020838301011161029357815f9260208093018386015e8301015290565b90916129d361042e936040845260408401906103ed565b9160208184039101526103ed565b906129ea612191565b506040805163765809ab60e11b81526004926020929173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__9190848180612a26858a83016128af565b0381865af4908115611500575f91612c81575b5060808701908582518051155f14612bfd5750508451630c9b3d8760e21b8152878101918252905f90829081906020010381875af492831561150057612aa693612a9993612a8f935f92612be1575b5052613701565b5163ffffffff1690565b63ffffffff166060870152565b815162409a0960e81b81525f8180612ac089898301611742565b0381855af4908115611500575f91612bc7575b508584018051805180612b8c57505052815163b93612a760e01b815293838580612aff89858301611742565b0381855af492831561150057612b38955f958695612b6d575b505051630c9b3d8760e21b81529081019283529384928391829160200190565b03915af4908115611500575f91612b53575b5060c082015290565b612b6791503d805f833e611c6981836106f2565b5f612b4a565b612b84929550803d106108925761088381836106f2565b925f80612b18565b915093925084849695929601209085519086012003612bac575050505090565b51630ca2e06d60e11b815292839261082a92919084016129bc565b612bdb91503d805f833e611c6981836106f2565b5f612ad3565b612bf69192503d805f833e611c6981836106f2565b905f612a88565b90919350612c1e92508551809381926347f5343160e01b83528a8301612805565b0381865af4908115611500575f91612c64575b50818103612c40575050612aa6565b8351631d97afef60e31b815280870192835260208301919091529081906040010390fd5b612c7b9150853d87116108925761088381836106f2565b5f612c31565b612c989150853d87116108925761088381836106f2565b5f612a39565b61010090602080910190602082510151515f925b818410612cc0575050505090565b90919293601880820191828111610606576040612ce1888887510151612258565b5101515101018091116106065793600101929190612cb2565b600411610293575f90600490565b6001600160e01b03199035818116939260048110612d2557505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f8051602061470c8339815191528054612da3906001600160a01b0316612d9a612d9486612cfa565b90612d08565b90843091613b47565b9015612db0575b50505050565b63ffffffff1615612e67575f8051602061470c833981519152805460ff60a01b1916600160a01b179055612df190611458908190546001600160a01b031690565b91823b1561029357612e1c925f9283604051809681958294634a63ebf760e11b845260048401612d34565b03925af1801561150057612e4e575b505f8051602061470c833981519152805460ff60a01b191690555f808080612daa565b80612e5b612e61926106df565b80610289565b5f612e2b565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f8051602061470c83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b600d5480612efa5750604051612ef48161068e565b5f815290565b60405190630c9b3d8760e21b825260048201525f8160248173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91612f3a575090565b61042e91503d805f833e611c6981836106f2565b805191908290602001825e015f815290565b612f68611020565b612f70613dd9565b92604080518095602094858301612f8691612f4e565b612f8f91612f4e565b0394601f19958681018252612fa490826106f2565b8451612faf90613c21565b908251918291868301612fc191612f4e565b612fca91612f4e565b038681018252612fda90826106f2565b612fe2613df0565b908251918291868301612ff491612f4e565b612ffd91612f4e565b03868101825261300d90826106f2565b8385015161301a90613c5d565b90815161302d906001600160401b031690565b61303690613f00565b90835191829187830161304891612f4e565b61305191612f4e565b03878101825261306190826106f2565b825191829186830161307291612f4e565b61307b91612f4e565b03868101825261308b90826106f2565b613093613e03565b9082519182918683016130a591612f4e565b6130ae91612f4e565b0386810182526130be90826106f2565b848201516001600160401b03166130d490613f00565b9082519182918683016130e691612f4e565b6130ef91612f4e565b0386810182526130ff90826106f2565b613107613e16565b90825191829186830161311991612f4e565b61312291612f4e565b03868101825261313290826106f2565b60608501516131469063ffffffff16613f00565b90825191829186830161315891612f4e565b61316191612f4e565b03868101825261317190826106f2565b613179613e29565b90825191829186830161318b91612f4e565b61319491612f4e565b0386810182526131a490826106f2565b60808501516131b89063ffffffff16613f00565b9082519182918683016131ca91612f4e565b6131d391612f4e565b0386810182526131e390826106f2565b6131eb613e3c565b9082519182918683016131fd91612f4e565b61320691612f4e565b03868101825261321690826106f2565b60a085015161322490613c21565b90825191829186830161323691612f4e565b61323f91612f4e565b03868101825261324f90826106f2565b915f925b60c086018051518510156133195761329961330f60c0936133036132b76132b18a6001978f6132ab908f61329f8f61329961328c613e81565b91519b8c94850190612f4e565b90612f4e565b039081018852876106f2565b51612258565b51613f7f565b6132f58d8c6132e98c6132996132dc6132d788516001600160401b031690565b613f00565b9151988994850190612f4e565b039081018552846106f2565b88519485938c850190612f4e565b038a81018352826106f2565b9401939050613253565b5095509350505050565b63ffffffff8082165f52600960205260405f20908154166133845761335a600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b6133a09063ffffffff91829182600a541690614649565b169081151590816133c9575b506133b45750565b5f5260096020525f6001604082208281550155565b9050815f52600960205260405f20541615155f6133ac565b805190601f8201908183116106065760059160051c915f5b83811061341c575050505063ffffffff610410911663ffffffff19825416179055565b80821b83016020015160108201556001016133f9565b61042e9063ffffffff600a541690614649565b61346e9061345161407f565b61345961407f565b61346161407f565b61346961407f565b612e87565b61347661407f565b61347e61407f565b60c860405161348c8161068e565b5260c863ffffffff19600a541617600a55565b600254811015611ce35760025f5260011b7f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01905f90565b91909182516001600160401b038111610689576134f8816123158454611d0d565b602080601f83116001146135345750819061234b9394955f926135295750508160011b915f199060031b1c19161790565b015190505f80612337565b90601f19831695613548855f5260205f2090565b925f905b8882106135825750508360019596971061356a57505050811b019055565b01515f1960f88460031b161c191690555f8080612392565b8060018596829496860151815501950193019061354c565b9291906126bc578051835461ffff191661ffff91909116178355600180930190602080910151938451916001600160401b038311610689576135e6836135e08654611d0d565b866122a2565b602091601f8411600114613618575050819061234b9394955f926135295750508160011b915f199060031b1c19161790565b95601f9291921984169661362f865f5260205f2090565b935f915b898310613652575050508360019596971061356a57505050811b019055565b838501518655948501949381019391810191613633565b600254916003548084115f146136be575061039192506136976040519261368f846106a9565b61ffff168352565b60208201526136b86003546136b36136ae82612120565b600355565b61349f565b9061359a565b6136c790612120565b60035561ffff604051926136da846106a9565b1682526020820152600160401b821015610689576136b8826001610391940160025561349f565b6137349161370d611020565b90613716613ea1565b61329961377a60409260405197889161329960209889850190612f4e565b0396613748601f19988981018352826106f2565b61376e61375f6132d76104548a5163ffffffff1690565b60405194859389850190612f4e565b038781018352826106f2565b915f925b8486018051518510156133195761329961380161329961330389956137f38d6137cd6137c78d60019a6132ab8f6137bb8f9161329961328c613df0565b038681018952886106f2565b516140ad565b926132e96137e56132d786516001600160401b031690565b8d519788938d850190612f4e565b885194859388850190612f4e565b940193905061377e565b90613815826115e3565b60409061382560405191826106f2565b8381528093613836601f19916115e3565b01915f5b8381106138475750505050565b60209082516138558161066e565b5f8152825f8183015260608583015282860101520161383a565b9060405161387c816106a9565b60206115ca6001839561ffff815416855201611d45565b8151815463ffffffff191663ffffffff91821617825591600180920160208092015191808351936138c48585612419565b01915f52805f205f925b8484106138de5750505050505050565b8051805183548583015161ffff90921665ffffffffffff199091161790891660101b65ffffffff0000161783556040015180519187840191906001600160401b03841161068957828692613937866123158d9754611d0d565b8390601f871160011461397757958061396892600297985f926135295750508160011b915f199060031b1c19161790565b90555b019201930192906138ce565b9590601f1982169661398c845f5260205f2090565b975f5b8181106139cc575091879891846002999594106139b4575b505050811b01905561396b565b01515f1960f88460031b161c191690555f80806139a7565b838301518a5597909801978d978b97938401930161398f565b600754600160401b81101561068957806001613a049201600755611caa565b6126bc576008602083613b3e60c06103919651613a9e815186613a608251613a3a60ff8251168d9060ff1660ff19825416179055565b909101518a5464ffffffff00191660089190911b64ffffffff0016178a5563ffffffff90565b878060018c01930151613a8160ff825116859060ff1660ff19825416179055565b0151825464ffffffff001916911660081b64ffffffff0016179055565b613aae85820151600289016134d7565b613abf6040820151600389016134d7565b613aec613ad3606083015163ffffffff1690565b600489019063ffffffff1663ffffffff19825416179055565b613afd6080820151600589016134d7565b613b33613b1460a08301516001600160401b031690565b60068901906001600160401b03166001600160401b0319825416179055565b0151600786016134d7565b01519101613893565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b03841117610689576040935f9385528380528360205251915afa613bb657565b9150505f51906020518060201c150290565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215613c06575b505061099c57565b5f8051602061472c8339815191525416141590505f80613bfe565b61042e61329991613c4f613c3e6001600160401b03835116613f00565b916040519485936020850190612f4e565b03601f1981018352826106f2565b613c65611020565b90613c6e613dd9565b6040805193846020938492838301613c8591612f4e565b613c8e91612f4e565b0393601f19948581018752613ca390876106f2565b8051613cae90614257565b958651613cc1906001600160401b031690565b613cca90613f00565b908451918291858301613cdc91612f4e565b613ce591612f4e565b038681018252613cf590826106f2565b8351968791848301613d0691612f4e565b613d0f91612f4e565b038581018752613d1f90876106f2565b613d27613df0565b958351968791848301613d3991612f4e565b613d4291612f4e565b038581018752613d5290876106f2565b0151613d5d90614257565b908151613d70906001600160401b031690565b613d7990613f00565b948151958691858301613d8b91612f4e565b613d9491612f4e565b038481018652613da490866106f2565b519384928301613db391612f4e565b613dbc91612f4e565b03908101825261042e90826106f2565b805115611ce35760200190565b613de1611fb4565b600a613dec82613dcc565b5390565b613df8611fb4565b6012613dec82613dcc565b613e0b611fb4565b6028613dec82613dcc565b613e1e611fb4565b6030613dec82613dcc565b613e31611fb4565b6038613dec82613dcc565b613e44611fce565b60a25f5b607f80831115613e6d5790608060019284161760208286010153019060071c90613e48565b50607f600192166020828501015301815290565b613e89611fce565b61014260015f60c26020850153019060071c90613e48565b613ea9611fb4565b6008613dec82613dcc565b613ebc611fb4565b6010613dec82613dcc565b613ecf611fb4565b601a613dec82613dcc565b613ee2611fb4565b6032613dec82613dcc565b613ef5611fb4565b6042613dec82613dcc565b9060809160806001600160401b03821610613f5b57613f1d611fce565b5f5b607f80841115613f4357908560019285161760208285010153019160071c91613f1f565b5090929350607f600192166020828501015301815290565b909150613f66611fb4565b9060f81b6001600160f81b0319165f1a613dec82613dcc565b613f87611020565b90613f90613dd9565b6040805193846020938492838301613fa791612f4e565b613fb091612f4e565b0393601f19948581018752613fc590876106f2565b8051613fd09061430f565b958651613fe3906001600160401b031690565b613fec90613f00565b908451918291858301613ffe91612f4e565b61400791612f4e565b03868101825261401790826106f2565b835196879184830161402891612f4e565b61403191612f4e565b03858101875261404190876106f2565b614049613df0565b95835196879184830161405b91612f4e565b61406491612f4e565b03858101875261407490876106f2565b0151613d5d90613701565b60ff5f8051602061474c8339815191525460401c161561409b57565b604051631afcd79f60e31b8152600490fd5b61042e6140ee613299926141a66141926132996141996140cb611020565b946140d4613ea1565b9060409485928351998a9161329960209b8c850190612f4e565b0398614102601f199a8b81018352826106f2565b614141896141356141256132d761411e61411e895161ffff1690565b61ffff1690565b6132998851958694850190612f4e565b038b81018352826106f2565b61415089614135614125613eb4565b61416d896141356141256132d76104548489015163ffffffff1690565b614186614178613ec7565b85519d8e938c850190612f4e565b038981018c528b6106f2565b0151613c21565b9151968794850190612f4e565b039081018352826106f2565b90813b15614236575f8051602061472c83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a280511561421b5761421891614668565b50565b50503461422457565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b61042e6132996132996141a66142ff6132d76104546020613299986142f46132996142ce614283611020565b61429d61428e613ea1565b6040519d8e9389850190612f4e565b039a6142b1601f199c8d81018352826106f2565b61413561375f6132d76142c86142c8895160ff1690565b60ff1690565b6142e86142d9613eb4565b6040519a8b9387850190612f4e565b038981018952886106f2565b015163ffffffff1690565b6040519586936020850190612f4e565b614317611020565b90614320613dd9565b9160408051809460209384830161433691612f4e565b61433f91612f4e565b0392601f1993848101865261435490866106f2565b805161435f90613c5d565b948551614372906001600160401b031690565b61437b90613f00565b90835191829186830161438d91612f4e565b61439691612f4e565b0385810182526143a690826106f2565b82519586918583016143b791612f4e565b6143c091612f4e565b0384810186526143d090866106f2565b6143d8613df0565b9482519586918583016143ea91612f4e565b6143f391612f4e565b03848101865261440390866106f2565b8281015161441090613c21565b94825195869185830161442291612f4e565b61442b91612f4e565b03848101865261443b90866106f2565b614443613ec7565b94825195869185830161445591612f4e565b61445e91612f4e565b03848101865261446e90866106f2565b8181015161447b90613c21565b94825195869185830161448d91612f4e565b61449691612f4e565b0384810186526144a690866106f2565b6144ae613e03565b9482519586918583016144c091612f4e565b6144c991612f4e565b0384810186526144d990866106f2565b60608101516144ed9063ffffffff16613f00565b9482519586918583016144ff91612f4e565b61450891612f4e565b03848101865261451890866106f2565b614520613eda565b94825195869185830161453291612f4e565b61453b91612f4e565b03848101865261454b90866106f2565b608081015161455990613c21565b94825195869185830161456b91612f4e565b61457491612f4e565b03848101865261458490866106f2565b61458c613e29565b94825195869185830161459e91612f4e565b6145a791612f4e565b0384810186526145b790866106f2565b60a08101516001600160401b03166145ce90613f00565b9482519586918583016145e091612f4e565b6145e991612f4e565b0384810186526145f990866106f2565b614601613eed565b94825195869185830161461391612f4e565b61461c91612f4e565b03848101865261462c90866106f2565b60c0015161463990613c21565b90519384928301613db391612f4e565b63ffffffff9190828216838216111561466157031690565b5050505f90565b5f8061042e93602081519101845af43d156146a4573d916146888361072d565b9261469660405194856106f2565b83523d5f602085013e6146a8565b6060915b906146cf57508051156146bd57602081519101fd5b60405163d6bda27560e01b8152600490fd5b81511580614702575b6146e0575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156146d856fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a26469706673582212206d0b8bf89bf266756ee2e2b3eefbac5d0ed3b41f57b9ea9047b840c51040157264736f6c63430008190033", + "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c806302ddde7b1461028457806304676e381461027f5780630d6f1e9d1461027a57806316a91b741461027557806327e935a11461027057806347453a2a1461026b5780634b3254fb146102665780634ed891d0146102615780634f1ef2861461025c5780634fca79cb1461025757806352d1902d14610252578063621fbd591461024d5780636744c79f146102485780637a9e5e4b146102435780638cbc18391461023e5780638fb360371461023957806393ec06be1461023457806395fabcf01461022f578063a3ad9cc31461022a578063a593aec214610225578063a9e50c1014610220578063ad3cb1cc1461021b578063b632ab5714610216578063b8f8d7e514610211578063bf7e214f1461020c578063c16a8a5614610207578063c4d66de814610202578063c8c14d17146101fd578063c94c93e7146101f8578063cde78421146101f3578063d5b5272a146101ee578063de152649146101e9578063e0064900146101e4578063e0c14aa8146101df578063e0c1c0b4146101da578063e2fc04ae146101d5578063e36f9a07146101d0578063e44efb5a146101cb5763f5786b4b146101c6575f80fd5b61193a565b61191b565b6118fe565b6117e1565b611515565b6113e9565b6113ac565b61138f565b61129a565b611272565b611243565b611227565b61110d565b6110ea565b6110b6565b611093565b611077565b611032565b610ffa565b610f00565b610c12565b610bf5565b610bd9565b610b89565b610ac2565b610a48565b6109d4565b6109ae565b610945565b610899565b61078e565b610628565b6105c8565b61056e565b610511565b6104ed565b610431565b610393565b610297565b5f91031261029357565b5f80fd5b34610293575f366003190112610293575f8051602061474c833981519152805460ff8160401c16908115610363575b50610351575f8051602061474c833981519152805467ffffffffffffffff19166002179055805460ff60401b1916600160401b179055610304611f66565b5f8051602061474c833981519152805460ff60401b19169055604051600281527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29080602081015b0390a1005b60405163f92ee8a960e01b8152600490fd5b600291506001600160401b031610155f6102c6565b63ffffffff81160361029357565b359061039182610378565b565b346102935760203660031901126102935760606004356103b281610378565b63ffffffff8091165f52600960205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60409063ffffffff61042e949316815281602082015201906103ed565b90565b34610293575f3660031901126102935761045d6104546104105463ffffffff1690565b63ffffffff1690565b61046681611fe8565b90610479610473826105f8565b60051c90565b5f5b8181106104d5575050601f811690816104b2575b8261049f600c5463ffffffff1690565b6104ae60405192839283610411565b0390f35b81602091038301019060015f199160200360031b1b011981511690525f8061048f565b806001916010015460208260051b870101520161047b565b34610293575f36600319011261029357602063ffffffff6104105416604051908152f35b346102935760203660031901126102935760043561052f3633612d6b565b8015610548576004541561053f57005b6004555f600355005b604051636ad43cc360e11b8152600490fd5b634e487b7160e01b5f52603260045260245ffd5b34610293576020366003190112610293576004356006548110156102935760065f527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01546040516001600160a01b039091168152602090f35b34610293575f3660031901126102935760206040516104008152f35b634e487b7160e01b5f52601160045260245ffd5b90601f820180921161060657565b6105e4565b9061010091820180921161060657565b9190820180921161060657565b34610293575f3660031901126102935760206040516101188152f35b600435906001600160a01b038216820361029357565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761068957604052565b61065a565b602081019081106001600160401b0382111761068957604052565b604081019081106001600160401b0382111761068957604052565b60e081019081106001600160401b0382111761068957604052565b6001600160401b03811161068957604052565b90601f801991011681019081106001600160401b0382111761068957604052565b60405190610391826106c4565b604051906103918261066e565b6001600160401b03811161068957601f01601f191660200190565b81601f820112156102935780359061075f8261072d565b9261076d60405194856106f2565b8284526020838301011161029357815f926020809301838601378301015290565b6040366003190112610293576107a2610644565b6024356001600160401b038111610293576107c1903690600401610748565b906107ca613bc8565b6107d43633612d6b565b6107dc613bc8565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610868575b5061082e57604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f8051602061472c833981519152830361084f5761084d92506141b2565b005b604051632a87526960e21b815260048101849052602490fd5b61088b91945060203d602011610892575b61088381836106f2565b8101906128a0565b925f610804565b503d610879565b34610293576020366003190112610293576104ae6004356108b981610378565b60405f8180516108c88161066e565b828152826020820152015263ffffffff8092165f5260096020526001815f206001600160401b038351946108fb8661066e565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b34610293575f366003190112610293577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316300361099c5760206040515f8051602061472c8339815191528152f35b60405163703e46dd60e11b8152600490fd5b34610293575f3660031901126102935760205f54604051908152f35b8015150361029357565b34610293576020366003190112610293576004356109f1816109ca565b600f546001600160a01b031633819003610a2a57507fcf8be8503aed7d1346a99aa4bcf2fadebd86e033736ff955ae30798c1fa172755d005b60449060405190636e01d1ef60e11b82523360048301526024820152fd5b3461029357602036600319011261029357610a61610644565b5f8051602061470c833981519152546001600160a01b03919082163303610aab57803b15610a925761084d90612e87565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b3461029357604036600319011261029357602435600435610ae33633612d6b565b63ffffffff6104105416610b445781811080610b38575b610b039061201a565b818110610b0c57005b80610b32610b1b600193612066565b81549060031b906001821b915f19901b1916179055565b01610b03565b50610400821115610afa565b60405162461bcd60e51b815260206004820152601f60248201527f4f50503a206f7574626f756e642062616e6b20616c7265616479206c697665006044820152606490fd5b34610293575f366003190112610293575f8051602061470c8339815191525460a01c60ff1615610bd1576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f610bbe565b34610293575f3660031901126102935760206040516180008152f35b34610293575f366003190112610293576020600354604051908152f35b3461029357602036600319011261029357600435610c2f81610378565b610c393633612d6b565b600c5463ffffffff16610c4e61045482612078565b63ffffffff831603610ed15750610c636120c7565b63ffffffff82166060820152610c94610c846001600160401b034216612104565b6001600160401b03166040830152565b610c9c612edf565b60a08201525f600854908115159182610d3e575b7fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d88761034c86610d32610ce188612f60565b91610cf161800084511115612793565b610d1383516020850120610d058184613323565b610d0e83613389565b600d55565b610d1c836133e1565b63ffffffff1663ffffffff19600c541617600c55565b60405191829182612805565b5f92836101005b838210610e70575b5050610d639083159081610e68575b501561212e565b81610d6d81612209565b60c086019081525f5b828110610e30575050610d889161226c565b905f5b828110610e0257505f5b818110610df1575050610ce161034c93610de7610de27fe93b186dfb145614c1bb2c64e844f7211236c8c919656e5e745d6cdb6260d8879795610dda610d3296600855565b600e5461226c565b600e55565b9350829450610cb0565b600190610dfc6126c1565b01610d95565b80610e2a610e1a610e156001948661061b565b611caa565b50610e2483611caa565b90612552565b01610d8b565b6001919250610e5e81610e4281611caa565b50610e4e865191611e88565b610e588383612258565b52612258565b5001908391610d76565b90505f610d5c565b610e8d610e88610e8284979894611caa565b50611e88565b612c9e565b90618000610e9b838361061b565b11610ec757610eb982610eb3600194610ebf9461061b565b9461061b565b96612120565b940190610d45565b5081959450610d4d565b610eda90612078565b6040516307d3e83d60e51b815263ffffffff918216600482015291166024820152604490fd5b3461029357602036600319011261029357600435610f1d81610378565b610f273633612d6b565b63ffffffff9081811691825f52600960205260405f20541615610fb557610f84610f8f91610f70610f68610454610f63600c5463ffffffff1690565b613432565b851115612816565b63ffffffff165f52600960205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b60405162461bcd60e51b815260206004820152601760248201527f4f50503a207265636f7264206e6f742070726573656e740000000000000000006044820152606490fd5b34610293575f3660031901126102935760206001600160401b0360015416604051908152f35b6040519061102d8261068e565b5f8252565b34610293575f366003190112610293576104ae604051611051816106a9565b60058152640352e302e360dc1b60208201526040519182916020835260208301906103ed565b34610293575f3660031901126102935760206040516120008152f35b34610293575f36600319011261029357602063ffffffff600c5416604051908152f35b34610293575f366003190112610293575f8051602061470c833981519152546040516001600160a01b039091168152602090f35b34610293575f36600319011261029357602063ffffffff600a5416604051908152f35b3461029357602036600319011261029357611126610644565b5f8051602061474c83398151915254906001600160401b0360ff8360401c161592168015908161121f575b6001149081611215575b15908161120c575b50610351575f8051602061474c833981519152805467ffffffffffffffff1916600117905561119690826111e857613445565b61119c57005b5f8051602061474c833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290806020810161034c565b5f8051602061474c833981519152805460ff60401b1916600160401b179055613445565b9050155f611163565b303b15915061115b565b839150611151565b34610293575f3660031901126102935760206040516101008152f35b34610293576020366003190112610293576004355f526005602052602060ff60405f2054166040519015158152f35b34610293575f36600319011261029357600f546040516001600160a01b039091168152602090f35b34610293576020366003190112610293576004356112b781610378565b6112c13633612d6b565b604051906112ce8261068e565b63ffffffff81168083521561134a577f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69161132a611311600a5463ffffffff1690565b9163ffffffff90511663ffffffff19600a541617600a55565b6040805163ffffffff92831681529290911660208301528190810161034c565b60405162461bcd60e51b815260206004820152601d60248201527f4f50503a20696e76616c696420726574656e74696f6e20636f6e6669670000006044820152606490fd5b34610293575f366003190112610293576020600e54604051908152f35b34610293575f366003190112610293576020600854604051908152f35b6004359061ffff8216820361029357565b359061ffff8216820361029357565b3461029357604080600319360112610293576114036113c9565b6024356001600160401b03811161029357611422903690600401610748565b9160045415611505576114353633612d6b565b5f8051602061470c8339815191525461146490611458906001600160a01b031681565b6001600160a01b031690565b81516368fc2b7760e11b815261ffff841662010000176004820152336024820152908290829060449082905afa908115611500575f916114d2575b50156114af5761084d8383613669565b51639a28d60760e01b815233600482015261ffff91909116602482015260449150fd5b6114f29150823d84116114f9575b6114ea81836106f2565b810190612870565b505f61149f565b503d6114e0565b612895565b5163738c481160e01b8152600490fd5b346102935760203660031901126102935761152e610644565b6115383633612d6b565b600f80546001600160a01b0319166001600160a01b0392909216919091179055005b919082604091031261029357604051611572816106a9565b8092803560ff811681036102935782526020908101359161159283610378565b0152565b91906080838203126102935760206115ca604051926115b4846106a9565b604084966115c2838261155a565b86520161155a565b910152565b35906001600160401b038216820361029357565b6001600160401b0381116106895760051b60200190565b9190916040908181850312610293578151611614816106a9565b8094823561162181610378565b8252602092838101356001600160401b039182821161029357019180601f8401121561029357823590611653826115e3565b96611660815198896106f2565b828852868089019360051b8601019482861161029357878101935b86851061168d57505050505050500152565b84358681116102935782019060609081601f198488030112610293578451916116b58361066e565b6116c08c85016113da565b8352858401356116cf81610378565b8c84015283013591888311610293576116ef878d80969581960101610748565b8682015281520194019361167b565b90604060206103919361172584825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b9061042e91602081526117596020820183516116fe565b602082015190611777610140928360a08401526101608301906103ed565b9160c06117bd611798604087015195601f19968787830301858801526103ed565b606087015163ffffffff1660e0860152608087015186868303016101008701526103ed565b60a08601516001600160401b031661012085015294015192828503019101526103ed565b346102935760031960403682011261029357600480356001600160401b0392838211610293576101409082360301126102935761181c610713565b9061182936828501611596565b825260848101358481116102935761184690843691840101610748565b602083015260a48101358481116102935761186690843691840101610748565b604083015261187760c48201610386565b606083015260e48101358481116102935761189790843691840101610748565b60808301526118a961010482016115cf565b60a08301526101248101359084821161029357836118ca9236920101610748565b60c0820152602435928311610293576118ec6118f2926104ae943691016115fa565b906129e1565b60405191829182611742565b34610293575f366003190112610293576020600454604051908152f35b34610293575f3660031901126102935760206004541515604051908152f35b3461029357602080600319360112610293576004908135825415611c99576119623633612d6b565b8015611c885782541461197157005b5f82556003548015611c7757611985612191565b5f5480611c0b5750611995611f9e565b60409060408301526119c26119b26001600160401b034216612104565b6001600160401b031660a0840152565b6119ca6121ce565b600181529283916119da8261380b565b8684019081525f5b838110611b8f57505050506119f6916129e1565b91611a188180850151604051809381926347f5343160e01b8352898301612805565b038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91611b72575b505f55611a75611a5960a08501516001600160401b031690565b6001600160401b03166001600160401b03196001541617600155565b611a7d6121e7565b928352820152611a8c81612c9e565b917fcf8be8503aed7d1346a99aa4bcf2fadebd86e033736ff955ae30798c1fa172755c611b2457611abf83600e5461061b565b906120008211611aff575050611af691611adb610de2926139e5565b611aee611ae9600854612120565b600855565b600e5461061b565b61084d5f600355565b604080516306f8bcbb60e51b8152918201928352612000602084015290918291010390fd5b618000611b308461060b565b11611b465750611af691611adb610de2926139e5565b611b4f8361060b565b6040805163048c985b60e51b815292830191825261800060208301528291010390fd5b611b899150823d84116108925761088381836106f2565b5f611a3f565b60019192939450611bff81611bac611ba68261349f565b5061386f565b8a611bb9825161ffff1690565b910151611bee611bcd825163ffffffff1690565b611be2611bd8610720565b61ffff9095168552565b63ffffffff16838e0152565b87820152855190610e588383612258565b500190859392916119e2565b604051630c9b3d8760e21b8152858101918252905f9082908190602001038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91611c55575b50611995565b611c7191503d805f833e611c6981836106f2565b81019061295a565b5f611c4f565b60405163783bc0f160e11b81528390fd5b604051636ad43cc360e11b81528390fd5b60405163738c481160e01b81528390fd5b600754811015611ce357600a9060075f52027fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68801905f90565b61055a565b90604051611cf5816106a9565b915460ff8116835260081c63ffffffff166020830152565b90600182811c92168015611d3b575b6020831014611d2757565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611d1c565b9060405191825f8254611d5781611d0d565b908184526020946001916001811690815f14611dc35750600114611d85575b505050610391925003836106f2565b5f90815285812095935091905b818310611dab57505061039193508201015f8080611d76565b85548884018501529485019487945091830191611d92565b9250505061039194925060ff191682840152151560051b8201015f8080611d76565b9060408051611df3816106a9565b809363ffffffff8082541683526001809201805491611e11836115e3565b95611e1f60405197886106f2565b83875260209160208801935f5260205f20915f945b868610611e4957505050505050505060200152565b60028589928451611e598161066e565b86885461ffff8116835260101c1683820152611e76858901611d45565b86820152815201940195019492611e34565b90604051611e95816106a9565b60206115ca60088395604051611eaa816106c4565b604051611eb6816106a9565b611ebf83611ce8565b8152611ecd60018401611ce8565b868201528152611edf60028301611d45565b85820152611eef60038301611d45565b6040820152611f15611f08600484015463ffffffff1690565b63ffffffff166060830152565b611f2160058301611d45565b6080820152611f4d611f3d60068401546001600160401b031690565b6001600160401b031660a0830152565b611f5960078301611d45565b60c0820152855201611de5565b5f806008545b808210611f7a575050600e55565b9091611f8b610e88610e8285611caa565b8101809111610606579160010190611f6c565b604051611faa8161068e565b5f8152905f368137565b60405190611fc1826106a9565b6001825260203681840137565b60405190611fdb826106a9565b600a825260203681840137565b90611ff28261072d565b611fff60405191826106f2565b8281528092612010601f199161072d565b0190602036910137565b1561202157565b60405162461bcd60e51b815260206004820152601a60248201527f4f50503a20696e76616c6964207072657761726d2072616e67650000000000006044820152606490fd5b610400811015611ce357601001905f90565b90600163ffffffff8093160191821161060657565b6040519061209a826106a9565b5f6020838281520152565b604051906120b2826106a9565b816120bb61208d565b815260206115ca61208d565b604051906120d4826106c4565b606060c0838281526120e46120a5565b60208201525f60408201525f838201525f60808201528260a08201520152565b906103e86001600160401b038093160291821691820361060657565b5f1981146106065760010190565b1561213557565b60405162461bcd60e51b815260206004820152602e60248201527f4f50503a20717565756564206d6573736167652065786365656473204d41585f60448201526d454e56454c4f50455f425954455360901b6064820152608490fd5b6040519061219e826106c4565b606060c0836121ab6120a5565b81528260208201528260408201525f838201528260808201525f60a08201520152565b604051906121db826106a9565b60606020835f81520152565b604051906121f4826106a9565b816121fd612191565b815260206115ca6121ce565b90612213826115e3565b61222060405191826106f2565b8281528092612231601f19916115e3565b01905f5b82811061224157505050565b60209061224c6121e7565b82828501015201612235565b8051821015611ce35760209160051b010190565b9190820391821161060657565b634e487b7160e01b5f525f60045260245ffd5b818110612297575050565b5f815560010161228c565b9190601f81116122b157505050565b610391925f5260205f20906020601f840160051c830193106122db575b601f0160051c019061228c565b90915081906122ce565b9190918281146123cc576122f98354611d0d565b6001600160401b0381116106895761231b816123158454611d0d565b846122a2565b5f93601f821160011461235a5761234b92939482915f9261234f575b50508160011b915f199060031b1c19161790565b9055565b015490505f80612337565b61236c601f198316915f5260205f2090565b9461237a845f5260205f2090565b915f5b8181106123b45750958360019596971061239c575b505050811b019055565b01545f1960f88460031b161c191690555f8080612392565b8783015484556001938401939092019160200161237d565b509050565b6123db8154611d0d565b90816123e5575050565b81601f5f93116001146123f6575055565b908083918252612415601f60208420940160051c84016001850161228c565b5555565b600160401b821161068957805482825580831061243557505050565b6001916001600160ff1b0382811683036106065784168403610606575f5260205f209060011b81019260011b015b82811061246f57505050565b805f600292556124808382016123d1565b01612463565b8181146125135781549161249a8383612419565b5f5260205f20905f5260205f20905f905b8382106124b85750505050565b8060019184036124d3575b60028091019301910190916124ab565b8054845461ffff191661ffff919091161784558054845465ffffffff0000191665ffffffff000090911617845561250e8183018584016122e5565b6124c3565b5050565b818103612522575050565b6001808361254a63ffffffff610391965416859063ffffffff1663ffffffff19825416179055565b019101612486565b906126bc5781811415918261256657505050565b60088091846103919561257d575b50019101612517565b8061262d575b5061259460028201600286016122e5565b6125a460038201600386016122e5565b6125d16125b8600483015463ffffffff1690565b600486019063ffffffff1663ffffffff19825416179055565b6125e160058201600586016122e5565b6126176125f860068301546001600160401b031690565b60068601906001600160401b03166001600160401b0319825416179055565b61262760078201600786016122e5565b5f612574565b61268a575b600184016001820190818103612649575b50612583565b8154815460ff191660ff919091161781556126839163ffffffff9054851c1664ffffffff0082549160081b169064ffffffff001916179055565b5f80612643565b8054845460ff191660ff919091161784558054845464ffffffff00191690831c60081b64ffffffff0016178455612632565b612279565b600754801561277f575f19016126d681611caa565b6126bc575f815560015f600183015560096002926126f6600282016123d1565b612702600382016123d1565b5f6004820155612714600582016123d1565b5f6006820155612726600782016123d1565b5f600882015501908154915f815582612743575b50505050600755565b6001600160ff1b0383168303610606575f5260205f209160011b8201915b8281101561273a57805f8592556127798382016123d1565b01612761565b634e487b7160e01b5f52603160045260245ffd5b1561279a57565b60405162461bcd60e51b815260206004820152603a60248201527f4f50503a20656e76656c6f70652065786365656473204d41585f454e56454c4f60448201527f50455f42595445532028657374696d61746f72206472696674290000000000006064820152608490fd5b90602061042e9281815201906103ed565b1561281d57565b60405162461bcd60e51b815260206004820152602560248201527f4f50503a207265636f7264207374696c6c20696e20726574656e74696f6e2077604482015264696e646f7760d81b6064820152608490fd5b91908260409103126102935760208251612889816109ca565b92015161042e81610378565b6040513d5f823e3d90fd5b90816020910312610293575190565b90602080835260609060608401908063ffffffff94858151168288015201519460408080830152865180945260808201908360808660051b8501019801955f935b8685106129035750505050505050505090565b909192939495969799988680612945600193868f89908f607f198a8603018d52519061ffff8251168552868201511686850152015191818a82015201906103ed565b9b9c9a019897969190910194019291906128f0565b602081830312610293578051906001600160401b038211610293570181601f820112156102935780519061298d8261072d565b9261299b60405194856106f2565b8284526020838301011161029357815f9260208093018386015e8301015290565b90916129d361042e936040845260408401906103ed565b9160208184039101526103ed565b906129ea612191565b506040805163765809ab60e11b81526004926020929173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__9190848180612a26858a83016128af565b0381865af4908115611500575f91612c81575b5060808701908582518051155f14612bfd5750508451630c9b3d8760e21b8152878101918252905f90829081906020010381875af492831561150057612aa693612a9993612a8f935f92612be1575b5052613701565b5163ffffffff1690565b63ffffffff166060870152565b815162409a0960e81b81525f8180612ac089898301611742565b0381855af4908115611500575f91612bc7575b508584018051805180612b8c57505052815163b93612a760e01b815293838580612aff89858301611742565b0381855af492831561150057612b38955f958695612b6d575b505051630c9b3d8760e21b81529081019283529384928391829160200190565b03915af4908115611500575f91612b53575b5060c082015290565b612b6791503d805f833e611c6981836106f2565b5f612b4a565b612b84929550803d106108925761088381836106f2565b925f80612b18565b915093925084849695929601209085519086012003612bac575050505090565b51630ca2e06d60e11b815292839261082a92919084016129bc565b612bdb91503d805f833e611c6981836106f2565b5f612ad3565b612bf69192503d805f833e611c6981836106f2565b905f612a88565b90919350612c1e92508551809381926347f5343160e01b83528a8301612805565b0381865af4908115611500575f91612c64575b50818103612c40575050612aa6565b8351631d97afef60e31b815280870192835260208301919091529081906040010390fd5b612c7b9150853d87116108925761088381836106f2565b5f612c31565b612c989150853d87116108925761088381836106f2565b5f612a39565b61010090602080910190602082510151515f925b818410612cc0575050505090565b90919293601880820191828111610606576040612ce1888887510151612258565b5101515101018091116106065793600101929190612cb2565b600411610293575f90600490565b6001600160e01b03199035818116939260048110612d2557505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f8051602061470c8339815191528054612da3906001600160a01b0316612d9a612d9486612cfa565b90612d08565b90843091613b47565b9015612db0575b50505050565b63ffffffff1615612e67575f8051602061470c833981519152805460ff60a01b1916600160a01b179055612df190611458908190546001600160a01b031690565b91823b1561029357612e1c925f9283604051809681958294634a63ebf760e11b845260048401612d34565b03925af1801561150057612e4e575b505f8051602061470c833981519152805460ff60a01b191690555f808080612daa565b80612e5b612e61926106df565b80610289565b5f612e2b565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f8051602061470c83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b600d5480612efa5750604051612ef48161068e565b5f815290565b60405190630c9b3d8760e21b825260048201525f8160248173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4908115611500575f91612f3a575090565b61042e91503d805f833e611c6981836106f2565b805191908290602001825e015f815290565b612f68611020565b612f70613dd9565b92604080518095602094858301612f8691612f4e565b612f8f91612f4e565b0394601f19958681018252612fa490826106f2565b8451612faf90613c21565b908251918291868301612fc191612f4e565b612fca91612f4e565b038681018252612fda90826106f2565b612fe2613df0565b908251918291868301612ff491612f4e565b612ffd91612f4e565b03868101825261300d90826106f2565b8385015161301a90613c5d565b90815161302d906001600160401b031690565b61303690613f00565b90835191829187830161304891612f4e565b61305191612f4e565b03878101825261306190826106f2565b825191829186830161307291612f4e565b61307b91612f4e565b03868101825261308b90826106f2565b613093613e03565b9082519182918683016130a591612f4e565b6130ae91612f4e565b0386810182526130be90826106f2565b848201516001600160401b03166130d490613f00565b9082519182918683016130e691612f4e565b6130ef91612f4e565b0386810182526130ff90826106f2565b613107613e16565b90825191829186830161311991612f4e565b61312291612f4e565b03868101825261313290826106f2565b60608501516131469063ffffffff16613f00565b90825191829186830161315891612f4e565b61316191612f4e565b03868101825261317190826106f2565b613179613e29565b90825191829186830161318b91612f4e565b61319491612f4e565b0386810182526131a490826106f2565b60808501516131b89063ffffffff16613f00565b9082519182918683016131ca91612f4e565b6131d391612f4e565b0386810182526131e390826106f2565b6131eb613e3c565b9082519182918683016131fd91612f4e565b61320691612f4e565b03868101825261321690826106f2565b60a085015161322490613c21565b90825191829186830161323691612f4e565b61323f91612f4e565b03868101825261324f90826106f2565b915f925b60c086018051518510156133195761329961330f60c0936133036132b76132b18a6001978f6132ab908f61329f8f61329961328c613e81565b91519b8c94850190612f4e565b90612f4e565b039081018852876106f2565b51612258565b51613f7f565b6132f58d8c6132e98c6132996132dc6132d788516001600160401b031690565b613f00565b9151988994850190612f4e565b039081018552846106f2565b88519485938c850190612f4e565b038a81018352826106f2565b9401939050613253565b5095509350505050565b63ffffffff8082165f52600960205260405f20908154166133845761335a600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b6133a09063ffffffff91829182600a541690614649565b169081151590816133c9575b506133b45750565b5f5260096020525f6001604082208281550155565b9050815f52600960205260405f20541615155f6133ac565b805190601f8201908183116106065760059160051c915f5b83811061341c575050505063ffffffff610410911663ffffffff19825416179055565b80821b83016020015160108201556001016133f9565b61042e9063ffffffff600a541690614649565b61346e9061345161407f565b61345961407f565b61346161407f565b61346961407f565b612e87565b61347661407f565b61347e61407f565b60c860405161348c8161068e565b5260c863ffffffff19600a541617600a55565b600254811015611ce35760025f5260011b7f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01905f90565b91909182516001600160401b038111610689576134f8816123158454611d0d565b602080601f83116001146135345750819061234b9394955f926135295750508160011b915f199060031b1c19161790565b015190505f80612337565b90601f19831695613548855f5260205f2090565b925f905b8882106135825750508360019596971061356a57505050811b019055565b01515f1960f88460031b161c191690555f8080612392565b8060018596829496860151815501950193019061354c565b9291906126bc578051835461ffff191661ffff91909116178355600180930190602080910151938451916001600160401b038311610689576135e6836135e08654611d0d565b866122a2565b602091601f8411600114613618575050819061234b9394955f926135295750508160011b915f199060031b1c19161790565b95601f9291921984169661362f865f5260205f2090565b935f915b898310613652575050508360019596971061356a57505050811b019055565b838501518655948501949381019391810191613633565b600254916003548084115f146136be575061039192506136976040519261368f846106a9565b61ffff168352565b60208201526136b86003546136b36136ae82612120565b600355565b61349f565b9061359a565b6136c790612120565b60035561ffff604051926136da846106a9565b1682526020820152600160401b821015610689576136b8826001610391940160025561349f565b6137349161370d611020565b90613716613ea1565b61329961377a60409260405197889161329960209889850190612f4e565b0396613748601f19988981018352826106f2565b61376e61375f6132d76104548a5163ffffffff1690565b60405194859389850190612f4e565b038781018352826106f2565b915f925b8486018051518510156133195761329961380161329961330389956137f38d6137cd6137c78d60019a6132ab8f6137bb8f9161329961328c613df0565b038681018952886106f2565b516140ad565b926132e96137e56132d786516001600160401b031690565b8d519788938d850190612f4e565b885194859388850190612f4e565b940193905061377e565b90613815826115e3565b60409061382560405191826106f2565b8381528093613836601f19916115e3565b01915f5b8381106138475750505050565b60209082516138558161066e565b5f8152825f8183015260608583015282860101520161383a565b9060405161387c816106a9565b60206115ca6001839561ffff815416855201611d45565b8151815463ffffffff191663ffffffff91821617825591600180920160208092015191808351936138c48585612419565b01915f52805f205f925b8484106138de5750505050505050565b8051805183548583015161ffff90921665ffffffffffff199091161790891660101b65ffffffff0000161783556040015180519187840191906001600160401b03841161068957828692613937866123158d9754611d0d565b8390601f871160011461397757958061396892600297985f926135295750508160011b915f199060031b1c19161790565b90555b019201930192906138ce565b9590601f1982169661398c845f5260205f2090565b975f5b8181106139cc575091879891846002999594106139b4575b505050811b01905561396b565b01515f1960f88460031b161c191690555f80806139a7565b838301518a5597909801978d978b97938401930161398f565b600754600160401b81101561068957806001613a049201600755611caa565b6126bc576008602083613b3e60c06103919651613a9e815186613a608251613a3a60ff8251168d9060ff1660ff19825416179055565b909101518a5464ffffffff00191660089190911b64ffffffff0016178a5563ffffffff90565b878060018c01930151613a8160ff825116859060ff1660ff19825416179055565b0151825464ffffffff001916911660081b64ffffffff0016179055565b613aae85820151600289016134d7565b613abf6040820151600389016134d7565b613aec613ad3606083015163ffffffff1690565b600489019063ffffffff1663ffffffff19825416179055565b613afd6080820151600589016134d7565b613b33613b1460a08301516001600160401b031690565b60068901906001600160401b03166001600160401b0319825416179055565b0151600786016134d7565b01519101613893565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b03841117610689576040935f9385528380528360205251915afa613bb657565b9150505f51906020518060201c150290565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215613c06575b505061099c57565b5f8051602061472c8339815191525416141590505f80613bfe565b61042e61329991613c4f613c3e6001600160401b03835116613f00565b916040519485936020850190612f4e565b03601f1981018352826106f2565b613c65611020565b90613c6e613dd9565b6040805193846020938492838301613c8591612f4e565b613c8e91612f4e565b0393601f19948581018752613ca390876106f2565b8051613cae90614257565b958651613cc1906001600160401b031690565b613cca90613f00565b908451918291858301613cdc91612f4e565b613ce591612f4e565b038681018252613cf590826106f2565b8351968791848301613d0691612f4e565b613d0f91612f4e565b038581018752613d1f90876106f2565b613d27613df0565b958351968791848301613d3991612f4e565b613d4291612f4e565b038581018752613d5290876106f2565b0151613d5d90614257565b908151613d70906001600160401b031690565b613d7990613f00565b948151958691858301613d8b91612f4e565b613d9491612f4e565b038481018652613da490866106f2565b519384928301613db391612f4e565b613dbc91612f4e565b03908101825261042e90826106f2565b805115611ce35760200190565b613de1611fb4565b600a613dec82613dcc565b5390565b613df8611fb4565b6012613dec82613dcc565b613e0b611fb4565b6028613dec82613dcc565b613e1e611fb4565b6030613dec82613dcc565b613e31611fb4565b6038613dec82613dcc565b613e44611fce565b60a25f5b607f80831115613e6d5790608060019284161760208286010153019060071c90613e48565b50607f600192166020828501015301815290565b613e89611fce565b61014260015f60c26020850153019060071c90613e48565b613ea9611fb4565b6008613dec82613dcc565b613ebc611fb4565b6010613dec82613dcc565b613ecf611fb4565b601a613dec82613dcc565b613ee2611fb4565b6032613dec82613dcc565b613ef5611fb4565b6042613dec82613dcc565b9060809160806001600160401b03821610613f5b57613f1d611fce565b5f5b607f80841115613f4357908560019285161760208285010153019160071c91613f1f565b5090929350607f600192166020828501015301815290565b909150613f66611fb4565b9060f81b6001600160f81b0319165f1a613dec82613dcc565b613f87611020565b90613f90613dd9565b6040805193846020938492838301613fa791612f4e565b613fb091612f4e565b0393601f19948581018752613fc590876106f2565b8051613fd09061430f565b958651613fe3906001600160401b031690565b613fec90613f00565b908451918291858301613ffe91612f4e565b61400791612f4e565b03868101825261401790826106f2565b835196879184830161402891612f4e565b61403191612f4e565b03858101875261404190876106f2565b614049613df0565b95835196879184830161405b91612f4e565b61406491612f4e565b03858101875261407490876106f2565b0151613d5d90613701565b60ff5f8051602061474c8339815191525460401c161561409b57565b604051631afcd79f60e31b8152600490fd5b61042e6140ee613299926141a66141926132996141996140cb611020565b946140d4613ea1565b9060409485928351998a9161329960209b8c850190612f4e565b0398614102601f199a8b81018352826106f2565b614141896141356141256132d761411e61411e895161ffff1690565b61ffff1690565b6132998851958694850190612f4e565b038b81018352826106f2565b61415089614135614125613eb4565b61416d896141356141256132d76104548489015163ffffffff1690565b614186614178613ec7565b85519d8e938c850190612f4e565b038981018c528b6106f2565b0151613c21565b9151968794850190612f4e565b039081018352826106f2565b90813b15614236575f8051602061472c83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a280511561421b5761421891614668565b50565b50503461422457565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b61042e6132996132996141a66142ff6132d76104546020613299986142f46132996142ce614283611020565b61429d61428e613ea1565b6040519d8e9389850190612f4e565b039a6142b1601f199c8d81018352826106f2565b61413561375f6132d76142c86142c8895160ff1690565b60ff1690565b6142e86142d9613eb4565b6040519a8b9387850190612f4e565b038981018952886106f2565b015163ffffffff1690565b6040519586936020850190612f4e565b614317611020565b90614320613dd9565b9160408051809460209384830161433691612f4e565b61433f91612f4e565b0392601f1993848101865261435490866106f2565b805161435f90613c5d565b948551614372906001600160401b031690565b61437b90613f00565b90835191829186830161438d91612f4e565b61439691612f4e565b0385810182526143a690826106f2565b82519586918583016143b791612f4e565b6143c091612f4e565b0384810186526143d090866106f2565b6143d8613df0565b9482519586918583016143ea91612f4e565b6143f391612f4e565b03848101865261440390866106f2565b8281015161441090613c21565b94825195869185830161442291612f4e565b61442b91612f4e565b03848101865261443b90866106f2565b614443613ec7565b94825195869185830161445591612f4e565b61445e91612f4e565b03848101865261446e90866106f2565b8181015161447b90613c21565b94825195869185830161448d91612f4e565b61449691612f4e565b0384810186526144a690866106f2565b6144ae613e03565b9482519586918583016144c091612f4e565b6144c991612f4e565b0384810186526144d990866106f2565b60608101516144ed9063ffffffff16613f00565b9482519586918583016144ff91612f4e565b61450891612f4e565b03848101865261451890866106f2565b614520613eda565b94825195869185830161453291612f4e565b61453b91612f4e565b03848101865261454b90866106f2565b608081015161455990613c21565b94825195869185830161456b91612f4e565b61457491612f4e565b03848101865261458490866106f2565b61458c613e29565b94825195869185830161459e91612f4e565b6145a791612f4e565b0384810186526145b790866106f2565b60a08101516001600160401b03166145ce90613f00565b9482519586918583016145e091612f4e565b6145e991612f4e565b0384810186526145f990866106f2565b614601613eed565b94825195869185830161461391612f4e565b61461c91612f4e565b03848101865261462c90866106f2565b60c0015161463990613c21565b90519384928301613db391612f4e565b63ffffffff9190828216838216111561466157031690565b5050505f90565b5f8061042e93602081519101845af43d156146a4573d916146888361072d565b9261469660405194856106f2565b83523d5f602085013e6146a8565b6060915b906146cf57508051156146bd57602081519101fd5b60405163d6bda27560e01b8152600490fd5b81511580614702575b6146e0575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156146d856fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a26469706673582212206d0b8bf89bf266756ee2e2b3eefbac5d0ed3b41f57b9ea9047b840c51040157264736f6c63430008190033", "linkReferences": { "contracts/outpost/OPPCommon.sol": { - "OPPCommon": [ + "OPPCommonV2": [ { "length": 20, - "start": 6055 + "start": 6889 }, { "length": 20, - "start": 6371 + "start": 7417 }, { "length": 20, - "start": 9925 + "start": 10959 }, { "length": 20, - "start": 11131 + "start": 12257 } ] } }, "deployedLinkReferences": { "contracts/outpost/OPPCommon.sol": { - "OPPCommon": [ + "OPPCommonV2": [ { "length": 20, - "start": 5850 + "start": 6684 }, { "length": 20, - "start": 6166 + "start": 7212 }, { "length": 20, - "start": 9720 + "start": 10754 }, { "length": 20, - "start": 10926 + "start": 12052 } ] } diff --git a/tests/fixtures/ethereum-abi-opp-inbound-current.json b/tests/fixtures/ethereum-abi-opp-inbound-current.json index a2b784677b..9a41bfecb4 100644 --- a/tests/fixtures/ethereum-abi-opp-inbound-current.json +++ b/tests/fixtures/ethereum-abi-opp-inbound-current.json @@ -101,83 +101,89 @@ "type": "error" }, { - "inputs": [], - "name": "NotInitializing", + "inputs": [ + { + "internalType": "uint256", + "name": "position", + "type": "uint256" + } + ], + "name": "MalformedEnvelope", "type": "error" }, { "inputs": [ { - "internalType": "address", - "name": "operator_", - "type": "address" + "internalType": "uint256", + "name": "position", + "type": "uint256" } ], - "name": "OPP_ChunkBufferMissing", + "name": "MalformedEnvelope", "type": "error" }, { "inputs": [ { - "internalType": "uint32", - "name": "epochIndex", - "type": "uint32" + "internalType": "uint256", + "name": "position", + "type": "uint256" } ], - "name": "OPP_ChunkBufferShapeMismatch", + "name": "MalformedVarint", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", "type": "error" }, { "inputs": [ { - "internalType": "uint16", - "name": "chunkIndex", - "type": "uint16" + "internalType": "uint32", + "name": "nextEpochIndex", + "type": "uint32" }, { - "internalType": "uint16", - "name": "totalChunks", - "type": "uint16" + "internalType": "uint32", + "name": "deliveries", + "type": "uint32" } ], - "name": "OPP_ChunkIndexOutOfRange", + "name": "OPP_BootstrapWindowClosed", "type": "error" }, { "inputs": [ { - "internalType": "uint16", + "internalType": "bytes32", "name": "expected", - "type": "uint16" + "type": "bytes32" }, { - "internalType": "uint16", - "name": "actual", - "type": "uint16" + "internalType": "bytes32", + "name": "found", + "type": "bytes32" } ], - "name": "OPP_ChunkOutOfOrder", + "name": "OPP_DigestMismatch", "type": "error" }, { "inputs": [ - { - "internalType": "uint16", - "name": "chunkIndex", - "type": "uint16" - }, { "internalType": "uint256", - "name": "expected", + "name": "gasLeft", "type": "uint256" }, { "internalType": "uint256", - "name": "actual", + "name": "floor", "type": "uint256" } ], - "name": "OPP_ChunkSizeMismatch", + "name": "OPP_DispatchUnderfunded", "type": "error" }, { @@ -228,6 +234,27 @@ "name": "OPP_EpochHashMismatch", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "handler", + "type": "address" + }, + { + "internalType": "AttestationType", + "name": "attestationType", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "gasBound", + "type": "uint256" + } + ], + "name": "OPP_HandlerGasExhausted", + "type": "error" + }, { "inputs": [ { @@ -258,33 +285,23 @@ { "inputs": [ { - "internalType": "uint256", - "name": "required", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "provided", - "type": "uint256" + "internalType": "address", + "name": "caller", + "type": "address" } ], - "name": "OPP_InsufficientSignatureWeight", + "name": "OPP_InternalDispatchOnly", "type": "error" }, { "inputs": [ { - "internalType": "uint16", - "name": "expected", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "actual", - "type": "uint16" + "internalType": "string", + "name": "reason", + "type": "string" } ], - "name": "OPP_InvalidChunkCount", + "name": "OPP_InvalidInitialRoster", "type": "error" }, { @@ -308,6 +325,27 @@ "name": "OPP_InvalidRetentionConfig", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "expected", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "actual", + "type": "bytes32" + } + ], + "name": "OPP_MessageChainMismatch", + "type": "error" + }, { "inputs": [ { @@ -329,31 +367,52 @@ "name": "OPP_NoAttestationsSent", "type": "error" }, + { + "inputs": [], + "name": "OPP_NoPendingAttestations", + "type": "error" + }, { "inputs": [ { - "internalType": "address", - "name": "operator_", - "type": "address" + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "messageId", + "type": "bytes" } ], - "name": "OPP_NoChunkSlot", + "name": "OPP_NonCanonicalMessageId", "type": "error" }, { - "inputs": [], - "name": "OPP_NoPendingAttestations", + "inputs": [ + { + "internalType": "bytes", + "name": "previousEnvelopeHash", + "type": "bytes" + } + ], + "name": "OPP_NonCanonicalPreviousEpochHash", "type": "error" }, { "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, { "internalType": "bytes", - "name": "previousEnvelopeHash", + "name": "previousMessageId", "type": "bytes" } ], - "name": "OPP_NonCanonicalPreviousEpochHash", + "name": "OPP_NonCanonicalPreviousMessageId", "type": "error" }, { @@ -372,6 +431,27 @@ "name": "OPP_NonSequentialEpoch", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "OPP_NonSequentialMessage", + "type": "error" + }, { "inputs": [ { @@ -409,6 +489,22 @@ "name": "OPP_OperatorAlreadyDelivered", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "projectedEnvelopeBytes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxEnvelopeBytes", + "type": "uint256" + } + ], + "name": "OPP_OutboundMessageUnpackable", + "type": "error" + }, { "inputs": [ { @@ -468,6 +564,32 @@ "name": "OPP_UnauthorizedAttestationType", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "rosterEpochIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "activeGroupIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "groupCount", + "type": "uint32" + } + ], + "name": "OPP_UnmappedEpochGroup", + "type": "error" + }, { "inputs": [ { @@ -571,6 +693,43 @@ "name": "AttestationDelivered", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "handler", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "messageID", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "AttestationType", + "name": "attestationType", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequenceNumber", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "gasConsumed", + "type": "uint256" + } + ], + "name": "AttestationFailed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -638,19 +797,25 @@ "anonymous": false, "inputs": [ { - "indexed": true, + "indexed": false, "internalType": "uint32", "name": "epochIndex", "type": "uint32" }, { - "indexed": true, - "internalType": "address", - "name": "operator_", - "type": "address" + "indexed": false, + "internalType": "uint32", + "name": "attestedEpochIndex", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "enum OPPInboundLib.GroupsRejection", + "name": "rejection", + "type": "uint8" } ], - "name": "EnvelopeChunksDiscarded", + "name": "BatchOperatorGroupsRejected", "type": "event" }, { @@ -685,37 +850,6 @@ "name": "EnvelopeRetentionConfigUpdated", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint32", - "name": "epochIndex", - "type": "uint32" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator_", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "chunkIndex", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "totalChunks", - "type": "uint16" - } - ], - "name": "EpochChunk", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -779,6 +913,25 @@ "name": "EpochDelivery", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "dispatched", + "type": "uint16" + } + ], + "name": "EpochDispatchProgressed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -809,12 +962,18 @@ "inputs": [ { "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" + "internalType": "uint32", + "name": "groupCount", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "operatorCount", + "type": "uint32" } ], - "name": "Initialized", + "name": "InitialRosterInstalled", "type": "event" }, { @@ -822,18 +981,12 @@ "inputs": [ { "indexed": false, - "internalType": "address", - "name": "newReserveManager", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "oldReserveManager", - "type": "address" + "internalType": "uint64", + "name": "version", + "type": "uint64" } ], - "name": "ReserveManagerAddressSet", + "name": "Initialized", "type": "event" }, { @@ -851,20 +1004,7 @@ }, { "inputs": [], - "name": "MAX_CHUNKS", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_CHUNK_BYTES", + "name": "FIRST_INBOUND_EPOCH_INDEX", "outputs": [ { "internalType": "uint32", @@ -875,19 +1015,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "MAX_CHUNK_SLOTS", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "MAX_ENVELOPE_BYTES", @@ -901,19 +1028,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "MIN_SIG_WEIGHT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "OPERATORS_PER_GROUP", @@ -927,19 +1041,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "SLOT_EMPTY", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "UPGRADE_INTERFACE_VERSION", @@ -1001,59 +1102,46 @@ { "inputs": [ { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" + "internalType": "uint32", + "name": "epochIndex_", + "type": "uint32" } ], - "name": "batchOpGroups", + "name": "authorizedGroupIndexFor", "outputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "internalType": "uint32", + "name": "groupIndex", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "ok", + "type": "bool" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [], - "name": "chunkCellCount", - "outputs": [ + "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + }, { - "internalType": "address", - "name": "operator_", - "type": "address" + "internalType": "uint256", + "name": "", + "type": "uint256" } ], - "name": "chunkHeaderOf", + "name": "batchOpGroups", "outputs": [ { - "internalType": "uint256", - "name": "position", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "found", - "type": "bool" + "internalType": "address", + "name": "", + "type": "address" } ], "stateMutability": "view", @@ -1085,52 +1173,35 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "discardEnvelopeChunks", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { - "internalType": "address", - "name": "operator_", - "type": "address" + "internalType": "uint32", + "name": "", + "type": "uint32" } ], - "name": "envelopeChunkState", + "name": "dispatchSpill", "outputs": [ { - "internalType": "uint32", - "name": "epochIndex", - "type": "uint32" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint16", - "name": "totalChunks", - "type": "uint16" + "internalType": "bool", + "name": "tipped", + "type": "bool" }, { "internalType": "uint16", - "name": "receivedChunks", + "name": "dispatched", "type": "uint16" }, { - "internalType": "uint32", - "name": "totalBytes", - "type": "uint32" + "internalType": "bool", + "name": "complete", + "type": "bool" }, { - "internalType": "uint256", - "name": "storedBytes", - "type": "uint256" + "internalType": "bool", + "name": "finalized", + "type": "bool" } ], "stateMutability": "view", @@ -1223,24 +1294,9 @@ "name": "epochIndex", "type": "uint32" }, - { - "internalType": "uint16", - "name": "chunkIndex", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "totalChunks", - "type": "uint16" - }, - { - "internalType": "uint32", - "name": "totalBytes", - "type": "uint32" - }, { "internalType": "bytes", - "name": "chunkData", + "name": "envelopeData", "type": "bytes" } ], @@ -1285,6 +1341,29 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "AttestationType", + "name": "attestationType", + "type": "uint16" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint32", + "name": "epochIndex_", + "type": "uint32" + } + ], + "name": "handleInternalAttestation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1314,6 +1393,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "inboundMessageTip", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "inboundRetentionConfig", @@ -1333,6 +1425,52 @@ "internalType": "address", "name": "oppManager", "type": "address" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "activeGroupIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + }, + { + "components": [ + { + "components": [ + { + "internalType": "ChainKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "address_", + "type": "bytes" + } + ], + "internalType": "struct ChainAddress[]", + "name": "operators", + "type": "tuple[]" + } + ], + "internalType": "struct BatchOperatorGroup[]", + "name": "groups", + "type": "tuple[]" + }, + { + "internalType": "uint32", + "name": "epochDurationSec", + "type": "uint32" + } + ], + "internalType": "struct BatchOperatorGroups", + "name": "initialGroups", + "type": "tuple" } ], "name": "initialize", @@ -1341,8 +1479,55 @@ "type": "function" }, { - "inputs": [], - "name": "initializeChunkSlots", + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "activeGroupIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "epochIndex", + "type": "uint32" + }, + { + "components": [ + { + "components": [ + { + "internalType": "ChainKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "address_", + "type": "bytes" + } + ], + "internalType": "struct ChainAddress[]", + "name": "operators", + "type": "tuple[]" + } + ], + "internalType": "struct BatchOperatorGroup[]", + "name": "groups", + "type": "tuple[]" + }, + { + "internalType": "uint32", + "name": "epochDurationSec", + "type": "uint32" + } + ], + "internalType": "struct BatchOperatorGroups", + "name": "initialGroups", + "type": "tuple" + } + ], + "name": "installInitialRoster", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1367,13 +1552,24 @@ "type": "function" }, { - "inputs": [], - "name": "isConsumingScheduledOp", + "inputs": [ + { + "internalType": "address", + "name": "operator_", + "type": "address" + }, + { + "internalType": "uint32", + "name": "epochIndex_", + "type": "uint32" + } + ], + "name": "isActiveOperatorForEpoch", "outputs": [ { - "internalType": "bytes4", + "internalType": "bool", "name": "", - "type": "bytes4" + "type": "bool" } ], "stateMutability": "view", @@ -1381,12 +1577,12 @@ }, { "inputs": [], - "name": "lastMessageID", + "name": "isConsumingScheduledOp", "outputs": [ { - "internalType": "bytes32", + "internalType": "bytes4", "name": "", - "type": "bytes32" + "type": "bytes4" } ], "stateMutability": "view", @@ -1579,80 +1775,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "pendingEpoch", - "outputs": [ - { - "internalType": "bytes", - "name": "envelopeHash", - "type": "bytes" - }, - { - "components": [ - { - "components": [ - { - "internalType": "ChainKind", - "name": "kind", - "type": "uint8" - }, - { - "internalType": "uint32", - "name": "id", - "type": "uint32" - } - ], - "internalType": "struct ChainId", - "name": "start", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "ChainKind", - "name": "kind", - "type": "uint8" - }, - { - "internalType": "uint32", - "name": "id", - "type": "uint32" - } - ], - "internalType": "struct ChainId", - "name": "end", - "type": "tuple" - } - ], - "internalType": "struct Endpoints", - "name": "endpoints", - "type": "tuple" - }, - { - "internalType": "uint64", - "name": "epochTimestamp", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "epochIndex", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "epochEnvelopeIndex", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "previousEnvelopeHash", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "pendingEpochHash", @@ -1666,19 +1788,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "pendingMessageCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "previousEpochHash", @@ -1739,12 +1848,12 @@ }, { "inputs": [], - "name": "reserveManagerAddress", + "name": "rosterEpochIndex", "outputs": [ { - "internalType": "address", + "internalType": "uint32", "name": "", - "type": "address" + "type": "uint32" } ], "stateMutability": "view", @@ -1833,19 +1942,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "newReserveManager", - "type": "address" - } - ], - "name": "setReserveManagerAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1865,88 +1961,88 @@ "type": "function" } ], - "bytecode": "0x60a0806040523460da57306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009081549060ff8260401c1660cb57506001600160401b036002600160401b0319828216016087575b5f805463ffffffff19166001178155600c556040516149b490816100df8239608051818181610a9f0152612f890152f35b6001600160401b031990911681179091556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80806056565b63f92ee8a960e01b8152600490fd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80630fa92c531461038f578063135a70ef1461038a578063151b8f4e146103855780631bdf0a721461038057806320857e651461037b57806322fe388b14610376578063239e1d67146103715780632945698b1461036c5780632c92892f146103675780633367cca514610362578063360511351461035d5780633915ba631461035857806343df05c9146102d65780634ab47f43146103535780634f1ef2861461034e5780635126a52d1461034957806352d1902d146103445780635701a93f1461033f5780635c0c8f441461033a5780635d4e25ce146103355780635ed25346146103305780635ff8195e1461032b578063621fbd59146103265780637a9e5e4b146103215780637b7781221461031c5780637e283cea146103175780637e5db78f1461031257806380e643b61461030d5780638bdebf21146103085780638d9f616b146103035780638fb36037146102fe57806393ec06be146102f957806399b1a19c146102f4578063ad3cb1cc146102ef578063af243e89146102ea578063b41565b8146102e5578063b632ab57146102e0578063b8262d86146102db578063bee323cd146102d6578063bf7e214f146102d1578063bfa9980c146102cc578063c0d0373e146102c7578063c1e8b5e3146102c2578063c268863f146102bd578063c3e558bc146102b8578063c45fdcb7146102b3578063c4d66de8146102ae578063d0841f22146102a9578063d1ad8d01146102a4578063d5b5272a1461029f578063e3dff2af1461029a578063e55ba57614610295578063ea8dbafe14610290578063f2c905941461028b578063f552501a146102865763fdfd07df14610281575f80fd5b611a4a565b611908565b61170a565b6116e2565b6116a3565b611686565b6115d8565b61157e565b611556565b6114c9565b611466565b611341565b611319565b6112fc565b6111f0565b61118f565b61115b565b6107ee565b61107b565b61105f565b610fb3565b610f80565b610f3b565b610ef2565b610ed6565b610e86565b610e47565b610e21565b610dfd565b610d25565b610ca9565b610c50565b610bd6565b610bb9565b610b96565b610b7b565b610b5e565b610b19565b610af6565b610a8d565b610a5b565b61093f565b610808565b6107bc565b610715565b6106e8565b6106cb565b61069e565b61067c565b610661565b6105b3565b610495565b610472565b610457565b6103a6565b63ffffffff8116036103a257565b5f80fd5b346103a25760203660031901126103a25760206004356103c581610394565b63ffffffff8091165f52600e825260405f205416604051908152f35b5f9103126103a257565b634e487b7160e01b5f52601160045260245ffd5b90612000820180921161040e57565b6103eb565b906004820180921161040e57565b906008820180921161040e57565b9190820180921161040e57565b5f1981019190821161040e57565b9190820391821161040e57565b346103a2575f3660031901126103a2576020604051601c8152f35b346103a2575f3660031901126103a257602063ffffffff600f5416604051908152f35b346103a2575f3660031901126103a25763ffffffff805f541690815f52600e6020528060405f2054169160ff60115460201c1680610570575b1561055d5760105415610558576001600160401b03926105549160105f526105075f8051602061493f8339815191525463ffffffff1690565b600f549260405196879685169460201c169286936001600160401b039060809497969260a087019863ffffffff8096818094168a5216602089015216604087015216606085015216910152565b0390f35b6113c7565b82610554916001600160401b0394610507565b5060105415156104ce565b61ffff8116036103a257565b602435906001600160a01b03821682036103a257565b600435906001600160a01b03821682036103a257565b346103a25760403660031901126103a2576004356105d08161057b565b61ffff6105db610587565b916105e6363361213b565b165f818152600460209081526040918290205482516001600160a01b03868116825291821692810192909252929182917f46d568db0216a0215fe7f56360f9fa1a002c4bf0773add9cd62cd06eff1601899190a25f52600460205260405f2091166bffffffffffffffffffffffff60a01b8254161790555f80f35b346103a2575f3660031901126103a257602060405160078152f35b346103a2575f3660031901126103a257602063ffffffff5f5416604051908152f35b346103a25760203660031901126103a2576004355f52601b602052602060ff60405f205416604051908152f35b346103a2575f3660031901126103a2576020600c54604051908152f35b346103a25760203660031901126103a257602061070b61070661059d565b611a90565b6040519015158152f35b346103a25760203660031901126103a2576001600160401b0363ffffffff610554815f5416805f52601760205260405f206004355f526020528260405f2054169061076660115460ff9060201c1690565b806107b1575b156107875761050761077c6113db565b505463ffffffff1690565b6107ac6107a28263ffffffff165f52600e60205260405f2090565b5463ffffffff1690565b610507565b50601054151561076c565b346103a25760203660031901126103a2576004355f526012602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a25760206040515f8152f35b346103a25760203660031901126103a2576004355f52601a602052602060ff60405f205416604051908152f35b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761086457604052565b610835565b602081019081106001600160401b0382111761086457604052565b604081019081106001600160401b0382111761086457604052565b6001600160401b03811161086457604052565b60e081019081106001600160401b0382111761086457604052565b90601f801991011681019081106001600160401b0382111761086457604052565b6001600160401b03811161086457601f01601f191660200190565b929192610915826108ee565b9161092360405193846108cd565b8294818452818301116103a2578281602093845f960137010152565b60403660031901126103a25761095361059d565b6024356001600160401b0381116103a257366023820112156103a257610983903690602481600401359101610909565b9061098c612f7f565b610996363361213b565b61099e612f7f565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610a2a575b506109f057604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f8051602061491f8339815191528303610a1157610a0f925061377d565b005b604051632a87526960e21b815260048101849052602490fd5b610a4d91945060203d602011610a54575b610a4581836108cd565b81019061288a565b925f6109c6565b503d610a3b565b346103a25760203660031901126103a2576004355f526013602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a2577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163003610ae45760206040515f8051602061491f8339815191528152f35b60405163703e46dd60e11b8152600490fd5b346103a2575f3660031901126103a257602063ffffffff60155416604051908152f35b346103a25760203660031901126103a257610b3261059d565b610b3c363361213b565b600380546001600160a01b0319166001600160a01b0392909216919091179055005b346103a2575f3660031901126103a2576020600254604051908152f35b346103a2575f3660031901126103a257602060405160048152f35b346103a2575f3660031901126103a257602063ffffffff60115416604051908152f35b346103a2575f3660031901126103a2576020600154604051908152f35b346103a25760203660031901126103a257610bef61059d565b5f805160206148ff833981519152546001600160a01b03919082163303610c3957803b15610c2057610a0f9061225e565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b346103a25760403660031901126103a2576020610ca0600435610c7281610394565b63ffffffff610c7f610587565b91165f52600d835260405f209060018060a01b03165f5260205260405f2090565b54604051908152f35b346103a25760203660031901126103a2577f2b797207c3f71ed55e7ee9f751f9d4e23795bff49d40a0d3d05e160e1257798d610ce361059d565b610ced363361213b565b601680546001600160a01b039283166001600160a01b031982168117909255604080519283529216602082015290819081015b0390a1005b346103a2575f3660031901126103a257610d3d6122b6565b601c545f905f5b818110610d71578215610d5957610a0f611c42565b60405163dca69e6760e01b8152336004820152602490fd5b610d7a816113fa565b50549261ffff8460c01c16158015610de6575b610ddc575080610da7610da16001936113fa565b90611b5a565b819363ffffffff3391167f77018e8d096d16121cecd1c24da099cbde586f6f93c2f1352a90eac66e5a45455f80a35b01610d44565b9250600190610dd6565b50602084901c6001600160a01b0316331415610d8d565b346103a2575f3660031901126103a257602060115460ff60405191831c1615158152f35b346103a2575f3660031901126103a25760206001600160401b0360185416604051908152f35b346103a25760203660031901126103a25761ffff600435610e678161057b565b165f526004602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a2575f805160206148ff8339815191525460a01c60ff1615610ece576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f610ebb565b346103a2575f3660031901126103a25760206040516180008152f35b346103a2575f3660031901126103a257602060ff600f5460601c166040519015158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346103a2575f3660031901126103a257610554604051610f5a81610884565b60058152640352e302e360dc1b6020820152604051918291602083526020830190610f17565b346103a25760203660031901126103a2576004355f52601960205260206001600160401b0360405f205416604051908152f35b346103a25760203660031901126103a257610554600435610fd381610394565b60405f818051610fe281610849565b828152826020820152015263ffffffff8092165f5260146020526001815f206001600160401b0383519461101586610849565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b346103a2575f3660031901126103a25760206040516120008152f35b346103a2575f3660031901126103a2575f8051602061495f833981519152805460ff8160401c16908115611146575b50611134575f8051602061495f833981519152805467ffffffffffffffff19166002179055805460ff60401b1916600160401b1790556110e86122f5565b5f8051602061495f833981519152805460ff60401b19169055604051600281527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2908060208101610d20565b60405163f92ee8a960e01b8152600490fd5b600291506001600160401b031610155f6110aa565b346103a2575f3660031901126103a2575f805160206148ff833981519152546040516001600160a01b039091168152602090f35b346103a25760203660031901126103a25760c06111b26111ad61059d565b611ba4565b93949290916040519563ffffffff809516875260018060a01b0316602087015261ffff809216604087015216606085015216608083015260a0820152f35b346103a25760203660031901126103a25760043561120d81610394565b611217363361213b565b63ffffffff90816112396107a28363ffffffff165f52601460205260405f2090565b16156112dc5761125d6112586112535f5463ffffffff1690565b612430565b612466565b82821692811683116112b657506112856112909163ffffffff165f52601460205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b604051631cc9a6c760e21b815263ffffffff928316600482015291166024820152604490fd5b604051630529520560e31b815263ffffffff919091166004820152602490fd5b346103a2575f3660031901126103a2576020601d54604051908152f35b346103a2575f3660031901126103a2576003546040516001600160a01b039091168152602090f35b346103a25760a03660031901126103a25760043561135e81610394565b60243561136a8161057b565b604435916113778361057b565b60643561138381610394565b608435936001600160401b03938486116103a257366023870112156103a25785600401359485116103a25736602486880101116103a2576024610a0f960193611c2c565b634e487b7160e01b5f52603260045260245ffd5b601054156105585760105f525f8051602061493f833981519152905f90565b601c5481101561055857601c5f527f0e4562a10381dec21b205ed72637e6b1b523bdd0e4d4d50af5cd23dd4500a21101905f90565b6010548110156105585760105f525f8051602061493f83398151915201905f90565b8054821015610558575f5260205f2001905f90565b346103a25760403660031901126103a257600435602435906010548110156103a25760105f525f8051602061493f8339815191520180548210156103a2576020916114b091611451565b905460405160039290921b1c6001600160a01b03168152f35b346103a25760203660031901126103a2576114e261059d565b5f8051602061495f83398151915290815460ff8160401c16908115611541575b50611134575f8051602061495f833981519152805467ffffffffffffffff191660021790556110e891805460ff60401b1916600160401b179055612044565b600291506001600160401b031610155f611502565b346103a2575f3660031901126103a2576020600f546001600160401b0360405191831c168152f35b346103a25760203660031901126103a257606060043561159d81610394565b63ffffffff8091165f52601460205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b346103a25760203660031901126103a2576004356115f581610394565b6115ff363361213b565b60405161160b81610869565b63ffffffff908183168082521561167457601580549151831663ffffffff1983161790556040805163ffffffff93909216831682529290911660208201527f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69181908101610d20565b604051639ca50c6160e01b8152600490fd5b346103a2575f3660031901126103a2576020600b54604051908152f35b346103a25760203660031901126103a25763ffffffff6004356116c581610394565b6116cf363361213b565b1663ffffffff19600f541617600f555f80f35b346103a2575f3660031901126103a2576016546040516001600160a01b039091168152602090f35b346103a25760203660031901126103a257604061172d61172861059d565b612362565b825191825215156020820152f35b604051905f6009546001918160011c9260018316908115611822575b60209060208610831461180e57858852879460208601939081156117ef575060011461178f575b50505061178d925003836108cd565b565b9250936117bd60095f527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af90565b945f935b8285106117d95750505061178d9350015f808061177e565b86548585015295860195889550938101936117c1565b9250505061178d9491925060ff19168252151560051b015f808061177e565b634e487b7160e01b5f52602260045260245ffd5b93607f1693611757565b6040519061183982610884565b60065460ff8116835260081c63ffffffff166020830152565b6040519061185f82610884565b60075460ff8116835260081c63ffffffff166020830152565b926001600160401b03906118de60206118a06119059a98969995610120808a52890190610f17565b986118c1828901825163ffffffff6020809260ff8151168552015116910152565b0151805160ff1660608801526020015163ffffffff166080870152565b1660a084015263ffffffff80921660c08401521660e0820152610100818403910152610f17565b90565b346103a2575f3660031901126103a2576040515f6005546001918160011c9260018316908115611a40575b60209060208610831461180e5785875286946020860193908115611a2157506001146119c1575b84611967818603826108cd565b60405161197381610884565b61197b61182c565b8152611985611852565b602082015261055460085463ffffffff61199d61173b565b916040519586956001600160401b03848460601c16948460401c1693169187611878565b9250936119ef60055f527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090565b945f935b828510611a0b575050506119679350015f808061195a565b86548585015295860195879550938101936119f3565b925050506119679491925060ff19168252151560051b015f808061195a565b93607f1693611933565b346103a25760403660031901126103a2576020600435611a6981610394565b63ffffffff8091165f526017825260405f206024355f52825260405f205416604051908152f35b601154611aa59060201c60ff161590565b1590565b611b545763ffffffff80611abe60105463ffffffff1690565b168015611b4d575f5b8183821610611ad857505050505f90565b80611ae3849261142f565b5080545f5b818110611afb5750505060010116611ac7565b9091929350611b22611b0d8285611451565b905460039190911b1c6001600160a01b031690565b6001600160a01b03888116911614611b41576001019085939291611ae8565b50505050505050600190565b5050505f90565b50600190565b90611b64575f9055565b634e487b7160e01b5f525f60045260245ffd5b9081600d1b91808304612000149015171561040e57565b908160021b918083046004149015171561040e57565b611bad90612362565b15611c1d57601c5481101561055857601c5f527f0e4562a10381dec21b205ed72637e6b1b523bdd0e4d4d50af5cd23dd4500a211015461ffff90818160d01c1690611bf782611b77565b63ffffffff821694602083901c6001600160a01b03169460c084901c16939260e01c9190565b505f905f905f905f905f905f90565b90611c429594939291611c3d6122b6565b611cbc565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b90600161ffff8093160191821161040e57565b909280606093602095601d855286850152846040850152818585015260808401375f8282018401850152601f01601f191601010190565b6040513d5f823e3d90fd5b91939093611cce5f5463ffffffff1690565b63ffffffff948582169286861692848410612038578484036120115750611cf7611aa133611a90565b611ff957611d078983838b612479565b5f965f9861ffff9485841693600185145f14611d3a575050505050505050611d349061178d953691610909565b90612b3f565b91939599509193959950611d4d336125b2565b999095869a15611fe15760019b611d63886113fa565b5096875491848360c01c16908115908115611fd5575b5015611f2357505050818516611f015760e088901b6001600160e01b031916640100000000600160c01b033360201b1663ffffffff8c161761ffff60c01b60c086901b16171786555b611dd5611dce86611c67565b61ffff1690565b03611dfe575050505091611d349391611df861178d99945461ffff9060d01c1690565b90612723565b929a9495509596509750611e35919650611e2c73__$a62ffa76e12530392778037a67b422bd63$__94611b8e565b9089169061042f565b91803b156103a257611e60945f9360405196879485938493633535237960e21b855260048501611c7a565b03915af48015611efc577fbe287004f9392c9ea6ab7463d51604830991743d8d3364ae2566b5bc8607110c93611ede93611ebf92611ee3575b50611ea387611c67565b815461ffff60d01b191660d09190911b61ffff60d01b16179055565b6040805161ffff96871681529590911660208601523394918291820190565b0390a3565b80611ef0611ef69261089f565b806103e1565b5f611e99565b611cb1565b604051635d551ad560e01b81525f600482015261ffff86166024820152604490fd5b602083901c6001600160a01b031633148015918591611fca575b50508015611fbb575b611f9c575060d01c8216858316818110611f8b578103611f665750611dc2565b604051635d551ad560e01b815261ffff91821660048201529086166024820152604490fd5b505050505050505050505050505050565b604051633b52f0fb60e01b8152911663ffffffff166004820152602490fd5b50808a168260e01c1415611f46565b14159050835f611f3d565b9050828416105f611d79565b6040516327b2f1c160e21b8152336004820152602490fd5b604051632af0051560e21b8152336004820152602490fd5b6040516307d3e83d60e51b815263ffffffff91821660048201529087166024820152604490fd5b50505050505050505050565b6120659061205061374f565b61205861374f565b61206061374f565b61225e565b61206d61374f565b6120756122f5565b5f805463ffffffff19908116600117909155600f80546bffffffffffffffffffffffff19164260201b6bffffffffffffffff000000001617905560c890816040516120bf81610869565b526015541617601555565b6004116103a2575f90600490565b6001600160e01b031990358181169392600481106120f557505050565b60040360031b82901b16169150565b60209260409260018060a01b0316825282848301528083830152805f60608401375f8282018401850152601f01601f191601010190565b5f805160206148ff8339815191528054612173906001600160a01b031661216a612164866120ca565b906120d8565b90843091612efe565b9015612180575b50505050565b63ffffffff161561223e575f805160206148ff833981519152805460ff60a01b1916600160a01b1790556121ce906121c29081905b546001600160a01b031690565b6001600160a01b031690565b91823b156103a2576121f9925f9283604051809681958294634a63ebf760e11b845260048401612104565b03925af18015611efc5761222b575b505f805160206148ff833981519152805460ff60a01b191690555f80808061217a565b80611ef06122389261089f565b5f612208565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f805160206148ff83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805c6122e3576001905d565b604051633ee5aeb560e01b8152600490fd5b5f5b6007811061233757505f5b601c811061230d5750565b601d908154600160401b8110156108645760018101908184551015610558576001915f5201612302565b601c90815491600160401b831015610864578261235a91600180950190556113fa565b5050016122f7565b5f915f91601c54905f809161238761237e5f5463ffffffff1690565b63ffffffff1690565b925b848110612397575050505050565b6123a0816113fa565b505461ffff8160c01c16158015612415575b61240c5763ffffffff809116908582146123fd5788159081156123f1575b506123e1575b506001905b01612389565b90975060019650915086866123d6565b9050841681115f6123d0565b50909750600196505050505050565b506001906123db565b50602081901c6001600160a01b0390811690841614156123b2565b63ffffffff9060018183161115612448575f19011690565b50505f90565b63ffffffff91908282168382161115611b4d57031690565b6119059063ffffffff601554169061244e565b9163ffffffff811690811580156125a7575b61257f57506124ad611dce6124a76124a2846103ff565b61043c565b600d1c90565b61ffff80841691818116830361255a57508416928184101561253457506124d6611dce85611c67565b03612529576124e76124ed92611b77565b9061044a565b915b8281036124fb57505050565b604051632ac20c8f60e21b815261ffff92909216600483015260248201929092526044810191909152606490fd5b5050612000916124ef565b6040516385d12c3160e01b815261ffff8681166004830152919091166024820152604490fd5b6040516328e2405760e01b815261ffff91821660048201529085166024820152604490fd5b604051630c6f042160e31b815263ffffffff9190911660048201526180006024820152604490fd5b50618000821161248b565b601c54906125c660115460ff9060201c1690565b612672575f19905f5b8381106125ef57505090505f1981146125e85790600190565b505f905f90565b6125f8816113fa565b505461ffff8160c01c16158015612653575b1561262b57505f198314612623575b6001905b016125cf565b915081612619565b60201c6001600160a01b03908116908316146126495760019061261d565b9250505090600190565b5061266561237e5f5463ffffffff1690565b63ffffffff82161061260a565b601054915f5b83811061268957505050505f905f90565b6126928161142f565b505f9080545b8083106126aa57505050600101612678565b6126bd611b0d8484999798969599611451565b6001600160a01b038581169116036126e15750505050508110156125e85790600190565b9091929493956001019190612698565b906126fb826108ee565b61270860405191826108cd565b8281528092612719601f19916108ee565b0190602036910137565b9493929161273963ffffffff61273f92166126f1565b95611b8e565b905f905f92601d545b61ffff83168510612760575050506020915085010137565b90919284830180841161040e578281101561055857601d5f527f6d4407e7be21f808e6509aa9fa9143369579dd7d760fe20a2c09680fc146134f01906040515f83549360019085821c82871691821561286f575b602097888310841461180e5782875260208701939081156128505750600114612812575b5050505092600192826127f36128059461ffff9703826108cd565b8d83838351958694019201015e61042f565b9501949392919050612748565b61282391929394505f5260205f2090565b925f935b82851061283f575050500182826127f36128056127d8565b805484860152938701938101612827565b60ff191684525050151560051b019150839050826127f36128056127d8565b90607f16906127b4565b906020611905928181520190610f17565b908160209103126103a2575190565b906040602061178d936128c084825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b604080830163ffffffff9283815116855260208091015194604082820152855180935260609360608201908360608660051b8501019801955f935b86851061292b5750505050505050505090565b90919293949596979998868061296d600193868f89908f605f198a8603018d52519061ffff8251168552868201511686850152015191818a8201520190610f17565b9b9c9a01989796919091019401929190612918565b908082519081815260208091019281808460051b8301019501935f915b8483106129af5750505050505090565b9091929394958480612a80600193601f198682030187528a519083612a7060408451908085526129e28186018351612899565b8382015161014092612a5e612a47612a21612a0960c09588878d01526101808c0190610f17565b9584015195603f1996878c83030160e08d0152610f17565b606084015163ffffffff166101008b01526080840151868b8303016101208c0152610f17565b9460a0830151908901906001600160401b03169052565b01519085830301610160860152610f17565b92015190848184039101526128dd565b980193019301919493929061299f565b906119059160208152815190612ab461014092836020840152610160830190610f17565b91612ac760208501516040840190612899565b60408401516001600160401b031660c0830152606084015163ffffffff1660e0830152608084015163ffffffff1661010083015260c0612b1a60a086015194601f19958686830301610120870152610f17565b9401519282850301910152612982565b63ffffffff80911690811461040e5760010190565b93929190612b4c90613154565b60209081808201510151612b61815160ff1690565b90600260ff831614801590612ee5575b612eb45750506060810195612b8a875163ffffffff1690565b9063ffffffff80821690831603612e8e57505060a081015182815180155f14612dcd5750505060025480612dad57505b60405163791b84ad60e11b8152828180612bd78560048301612a90565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4928315611efc575f93612d8e575b5050612c3a612c28612c14885163ffffffff1690565b63ffffffff165f52600d60205260405f2090565b335f9081526020919091526040902090565b548015612c8d578203612c525761178d949550613391565b6109ec612c63875163ffffffff1690565b60405163082085ab60e21b815263ffffffff90911660048201523360248201529081906044820190565b50612d5a61237e61178d969784612cc433612caf612c14855163ffffffff1690565b9060018060a01b03165f5260205260405f2090565b55612d17612ced612cd9835163ffffffff1690565b63ffffffff165f52600e60205260405f2090565b612d03612cfe825463ffffffff1690565b612b2a565b63ffffffff1663ffffffff19825416179055565b612d50612ced86612d43612d2f855163ffffffff1690565b63ffffffff165f52601760205260405f2090565b905f5260205260405f2090565b5163ffffffff1690565b6040518381523391907fcafc00e7c35f462868d78e8228850e885bd0870978967c446c6bb67122a3fb2890602090a3613391565b612da5929350803d10610a5457610a4581836108cd565b905f80612bfe565b604051631d734b9160e01b815260048101919091525f6024820152604490fd5b03612e52576040516347f5343160e01b815290838280612df08460048301612879565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4918215611efc575f92612e6f575b508115612e525750600254808203612e30575050612bba565b604051631d734b9160e01b815260048101919091526024810191909152604490fd5b604051630ca3ddf160e01b81529081906109ec9060048301612879565b612e87919250843d8611610a5457610a4581836108cd565b905f612e17565b604051635070175360e01b815263ffffffff918216600482015291166024820152604490fd5b8301516040516385a89b3360e01b815246600482015260ff92909216602483015263ffffffff166044820152606490fd5b508084015163ffffffff1663ffffffff16461415612b71565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b03841117610864576040935f9385528380528360205251915afa612f6d57565b9150505f51906020518060201c150290565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215612fbd575b5050610ae457565b5f8051602061491f8339815191525416141590505f80612fb5565b60405190612fe582610884565b5f6020838281520152565b60405190612ffd82610884565b81613006612fd8565b81526020613012612fd8565b910152565b60405190613024826108b2565b606060c083828152613034612ff0565b60208201525f60408201525f838201525f60808201528260a08201520152565b5f19811461040e5760010190565b6001600160401b0381116108645760051b60200190565b60405190613086826108b2565b606060c083613093612ff0565b81528260208201528260408201525f838201528260808201525f60a08201520152565b604051906130c382610884565b60606020835f81520152565b604051906130dc82610884565b816130e5613079565b815260206130126130b6565b906130fb82613062565b61310860405191826108cd565b8281528092613119601f1991613062565b01905f5b82811061312957505050565b6020906131346130cf565b8282850101520161311d565b80518210156105585760209160051b010190565b9061315d613017565b915f8151905f805b83811061332d5750613176906130f1565b9160c086019283525f915b81811061318f575050505050565b61319990856138dc565b90816001600160401b0391828116600a81145f146131ca57505050506131bf9085613a95565b919087525b90613181565b60128103613212575050506131e261320b92876138dc565b9116906132016131fc6131f5848461042f565b838a613920565b613ac4565b60208a015261042f565b905b6131c4565b6028810361323f57505050509061322c61320d92866138dc565b6001600160401b03909116604089015291565b6030810361326f57505050509061320d61325e63ffffffff93876138dc565b9316606089019063ffffffff169052565b6038810361329f57505050509061320d61328e63ffffffff93876138dc565b9316608089019063ffffffff169052565b60a281036132c157505050506132b59085613a95565b919060a08801526131c4565b6101420361331d5750506132d861320b92876138dc565b9116906133176132fa6132f56132ee858561042f565b848b613920565b6139cc565b95875161330682613054565b976133118383613140565b52613140565b5061042f565b61320b9350600716915086613822565b61333a90859493946138dc565b6101426001600160401b038394931614613366575b600761335e9293169086613822565b929192613165565b600761337461335e93613054565b92505061334f565b90600163ffffffff8093160191821161040e57565b929391909360608401946133af6107a2612cd9885163ffffffff1690565b60115460201c60ff1680613744575b1561373e576133ce61077c6113db565b905b819063ffffffff80931615613736575b506133f96107a284612d43612d2f8c5163ffffffff1690565b916134126134068361337c565b60011c637fffffff1690565b90600f54926134316001600160401b0394858482169160201c1661042f565b4210159180808716921682109383613729575b505050811591613721575b501561371757906134cc839261346761352f95600b55565b61349461348061347b8c5163ffffffff1690565b61337c565b63ffffffff1663ffffffff195f5416175f55565b600f80546cffffffffffffffffff000000001916429290921660201b6bffffffffffffffff000000001691909117600160601b179055565b7fa77ed82a4a8c2f7b412630e92b1c8f59e6645b05820a8b0ed43ae5fac990491c61351f61350161237e8b5163ffffffff1690565b6040805186815263ffffffff90951660208601529093918291820190565b0390a2865163ffffffff16613b70565b613545613540865163ffffffff1690565b613bd6565b613705575b50600354613560906001600160a01b03166121c2565b92833b156103a257604051636744c79f60e01b80825260016004830152945f908290602490829084905af18015611efc576136f2575b505f5b60c084015180518210156135c757816135bf6135b960c094600194613140565b51613ca1565b019050613599565b50506003549193909250906135e4906001600160a01b03166121c2565b90813b156103a2575f90604051928391825281838161360a60048201905f602083019252565b03925af18015611efc576136df575b50613625600b54600255565b7fe784b22f2061de501b77364cab02ee109528afe4ddc8d423d076f6b49021b58e61366e613657835163ffffffff1690565b60405163ffffffff90911681529081906020820190565b0390a16003546136959061368a906001600160a01b03166121c2565b915163ffffffff1690565b90803b156103a25760405163a3ad9cc360e01b815263ffffffff9290921660048301525f908290602490829084905af18015611efc576136d25750565b80611ef061178d9261089f565b80611ef06136ec9261089f565b5f613619565b80611ef06136ff9261089f565b5f613596565b610da1613711916113fa565b5f61354a565b5050505050915050565b90505f61344f565b16111590505f8080613444565b90505f6133e0565b806133d0565b5060105415156133be565b60ff5f8051602061495f8339815191525460401c161561376b57565b604051631afcd79f60e31b8152600490fd5b90813b15613801575f8051602061491f83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a28051156137e6576137e391614028565b50565b5050346137ef57565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b6001600160401b039283169283613843575061383e92506138dc565b905090565b9192600181036138595750505061190590610421565b6002810361387757506119059261386f916138dc565b91169061042f565b60051415915061388c90505761190590610413565b60405162461bcd60e51b815260206004820152602260248201527f50726f746f62756652756e74696d653a20756e6b6e6f776e2077697265207479604482015261706560f01b6064820152608490fd5b91908201602001905f805b60806007600186515f1a94607f8616851b179601920192161561391357603f82116103a25792906138e7565b9293909203601f19019150565b9181811015806139c1575b15613970578161393a9161044a565b90613944826126f1565b9282613951575b50505090565b015f5b8281101561394b57602081830181015185830182015201613954565b60405162461bcd60e51b8152602060048201526024808201527f50726f746f62756652756e74696d653a20736c696365206f7574206f6620626f604482015263756e647360e01b6064820152608490fd5b50825181111561392b565b906139d56130cf565b915f8151905b8181106139e757505050565b6139f190836138dc565b90816001600160401b0391828116600a81145f14613a4757505050613a19613a3f92856138dc565b911690613a38613a33613a2c848461042f565b8388613920565b6141f7565b875261042f565b905b906139db565b601203613a85575050613a5d613a7f92856138dc565b911690613a75613a70613a2c848461042f565b6140da565b602088015261042f565b90613a41565b613a7f9350600716915084613822565b9190613aa96001600160401b0391846138dc565b911681019283821161040e57613ac0918491613920565b9190565b90613acd612ff0565b915f8151905b818110613adf57505050565b613ae990836138dc565b90816001600160401b0391828116600a81145f14613b3157505050613b11613b2992856138dc565b911690613a38613b24613a2c848461042f565b61435a565b905b90613ad3565b601203613b60575050613b47613b5a92856138dc565b911690613a75613b24613a2c848461042f565b90613b2b565b613b5a9350600716915084613822565b63ffffffff8082165f52601460205260405f2090815416613bd157613ba7600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b613bed9063ffffffff91829182601554169061244e565b16908115159081613c16575b50613c015750565b5f5260146020525f6001604082208281550155565b9050815f52601460205260405f20541615155f613bf9565b90613c46602091949394604084526040840190610f17565b6001600160a01b03909416910152565b9193926001600160401b039061ffff613c79604094606087526060870190610f17565b9616602085015216910152565b60409061ffff61190594931681528160208201520190610f17565b905f5b6020908180850151015180518210156140215781613cc191613140565b51805161ffff16908161ee0f811461400e5761ee138114613ff65761ee218114613f9f5761ee208114613f485761ee1b14613e6a57613d126121c26121b58461ffff165f52600460205260405f2090565b6001600160a01b038116929091908315613e225761dead8414613dd25760408092015192803b156103a2578251633bbe5e5b60e21b8152935f918591829084908290613d62908860048401613c86565b03925af18015611efc576001967f6615e715ef29da98af4d4b50f6beb57ec1a6a61bb207b117e5fb58632755e93794613db592613dbf575b508951015192519283926001600160401b0388169184613c56565b0390a25b01613ca4565b80611ef0613dcc9261089f565b5f613d9a565b9150507f06a84e8c796b3ecc778e5832b8ba6869cc1bf4822409bfd7f852021f87256d0c91506001938651015190613e1a6040519283926001600160401b0387169184613c56565b0390a1613db9565b9150507ff1a904444e5437715f6092bdea001e8e9fa7124d1b19aed02311ac2553da0dea91506001938651015190613e1a6040519283926001600160401b0387169184613c56565b6040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a257835163d85349a560e01b8152975f92899283918291613ec09160048401613c2e565b03915af4958615611efc57613f0a7f6615e715ef29da98af4d4b50f6beb57ec1a6a61bb207b117e5fb58632755e93794613f2d92600199613f35575b50546001600160a01b031690565b91895101519251928392888060a01b0316956001600160401b0388169184613c56565b0390a2613db9565b80611ef0613f429261089f565b5f613efc565b506040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a257835163462fd82d60e01b8152975f92899283918291613ec09160048401613c2e565b506040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a2578351634f6b2fdf60e01b8152975f92899283918291613ec09160048401613c2e565b506001935061400991506040015161455c565b613db9565b506001935061400991506040015161443f565b5050509050565b5f8061190593602081519101845af43d15614064573d91614048836108ee565b9261405660405194856108cd565b83523d5f602085013e61462b565b60609161462b565b6040519061407982610849565b60606040835f81525f60208201520152565b9061409582613062565b6140a260405191826108cd565b82815280926140b3601f1991613062565b01905f5b8281106140c357505050565b6020906140ce61406c565b828285010152016140b7565b906140e36130b6565b915f8151905f805b8381106141a957506140fc9061408b565b91602086019283525f915b818110614115575050505050565b61411f90856138dc565b90816001600160401b0391828116600881145f1461416257505050509061415c61414e63ffffffff93876138dc565b9316889063ffffffff169052565b90614107565b60120361419957505061417861419392876138dc565b9116906133176132fa61418e6132ee858561042f565b61468e565b9061415c565b6141939350600716915086613822565b6141b690859493946138dc565b60126001600160401b0383949316146141e1575b60076141d99293169086613822565b9291926140eb565b60076141ef6141d993613054565b9250506141ca565b90614200613079565b915f8151905b81811061421257505050565b61421c90836138dc565b90816001600160401b0391828116600a81145f1461425f5750505061424461425792856138dc565b911690613a386131fc613a2c848461042f565b905b90614206565b9192509060128103614284575050506142789083613a95565b91906020860152614259565b601a81036142a5575050506142999083613a95565b91906040860152614259565b602881036142d957505050906142d46142c363ffffffff93856138dc565b9316606087019063ffffffff169052565b614259565b603281036142fa575050506142ee9083613a95565b91906080860152614259565b6038810361432657505050906143136142d492846138dc565b6001600160401b0390911660a087015291565b6042036143455750506143399083613a95565b919060c0860152614259565b61435492506007169084613822565b90614259565b90614363612fd8565b915f8151905b81811061437557505050565b61437f90836138dc565b80916001600160401b038116600881145f146143b9575050509060ff6143a86143af93856138dc565b939061475a565b1685525b90614369565b6010036143de575050906143d263ffffffff92846138dc565b921660208601526143b3565b6143ed92506007169084613822565b906143b3565b908160609103126103a257604080519161440c83610849565b805161441781610394565b8352602081015161442781610394565b6020840152015180151581036103a257604082015290565b606061447163ffffffff600f54169260405193849283926374f9c0eb60e01b8452608060048501526084840190610f17565b9060106024840152601260448401526064830152038173__$a62ffa76e12530392778037a67b422bd63$__5af48015611efc57604061450e9161178d935f9161452d575b506144dd6144c7825163ffffffff1690565b63ffffffff1663ffffffff196011541617601155565b6145076144f1602083015163ffffffff1690565b63ffffffff1663ffffffff19600f541617600f55565b0151151590565b64ff0000000060115491151560201b169064ff00000000191617601155565b61454f915060603d606011614555575b61454781836108cd565b8101906143f3565b5f6144b5565b503d61453d565b6001600160401b039060018260185416019082821161040e5773__$a62ffa76e12530392778037a67b422bd63$__90813b156103a2575f916145bc91604051809681948293637401af5d60e01b845260e0600485015260e4840190610f17565b90601260248401526013604484015260196064840152601a6084840152601b60a4840152871660c483015203915af4918215611efc5761178d92614618575b506001600160401b03166001600160401b03196018541617601855565b80611ef06146259261089f565b5f6145fb565b90614652575080511561464057602081519101fd5b60405163d6bda27560e01b8152600490fd5b81511580614685575b614663575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b1561465b565b9061469761406c565b915f8151905b8181106146a957505050565b6146b390836138dc565b80916001600160401b038116600881145f146146f257505050906146ec6146dd6146e493856138dc565b93906147aa565b61ffff168652565b9061469d565b60108103614726575050509061472161471063ffffffff93856138dc565b9316602087019063ffffffff169052565b6146ec565b601a036147455750506147399083613a95565b919060408601526146ec565b61475492506007169084613822565b906146ec565b6001600160401b031680156147a55760018114611b54576002811461479f57600381146147995760249060405190632cd3748760e21b82526004820152fd5b50600390565b50600290565b505f90565b6001600160401b0381168015612448576107d18082146148f85750610bb98082146148f85750610bba8082146148f85750610bbc8082146148f85750610bbe8082146148f8575061aa008082146148f8575061ee008082146148f8575061ee028082146148f8575061ee048082146148f8575061ee068082146148f8575061ee108082146148f8575061ee118082146148f8575061ee138082146148f8575061ee0f8082146148f8575061ee158082146148f8575061ee168082146148f8575061ee178082146148f8575061ee188082146148f8575061ee198082146148f8575061ee1b8082146148f8575061ee1c8082146148f8575061ee1e8082146148f8575061ee1f8082146148f8575061ee208082146148f8575061ee218082146148f8575061ee2280911461383e57604051632cd3748760e21b81526001600160401b0383166004820152602490fd5b9150509056fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc1b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220c83eea418b14c7847807a669a42dd27a5506963582b408019c06d10beba85c8264736f6c63430008190033", - "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c80630fa92c531461038f578063135a70ef1461038a578063151b8f4e146103855780631bdf0a721461038057806320857e651461037b57806322fe388b14610376578063239e1d67146103715780632945698b1461036c5780632c92892f146103675780633367cca514610362578063360511351461035d5780633915ba631461035857806343df05c9146102d65780634ab47f43146103535780634f1ef2861461034e5780635126a52d1461034957806352d1902d146103445780635701a93f1461033f5780635c0c8f441461033a5780635d4e25ce146103355780635ed25346146103305780635ff8195e1461032b578063621fbd59146103265780637a9e5e4b146103215780637b7781221461031c5780637e283cea146103175780637e5db78f1461031257806380e643b61461030d5780638bdebf21146103085780638d9f616b146103035780638fb36037146102fe57806393ec06be146102f957806399b1a19c146102f4578063ad3cb1cc146102ef578063af243e89146102ea578063b41565b8146102e5578063b632ab57146102e0578063b8262d86146102db578063bee323cd146102d6578063bf7e214f146102d1578063bfa9980c146102cc578063c0d0373e146102c7578063c1e8b5e3146102c2578063c268863f146102bd578063c3e558bc146102b8578063c45fdcb7146102b3578063c4d66de8146102ae578063d0841f22146102a9578063d1ad8d01146102a4578063d5b5272a1461029f578063e3dff2af1461029a578063e55ba57614610295578063ea8dbafe14610290578063f2c905941461028b578063f552501a146102865763fdfd07df14610281575f80fd5b611a4a565b611908565b61170a565b6116e2565b6116a3565b611686565b6115d8565b61157e565b611556565b6114c9565b611466565b611341565b611319565b6112fc565b6111f0565b61118f565b61115b565b6107ee565b61107b565b61105f565b610fb3565b610f80565b610f3b565b610ef2565b610ed6565b610e86565b610e47565b610e21565b610dfd565b610d25565b610ca9565b610c50565b610bd6565b610bb9565b610b96565b610b7b565b610b5e565b610b19565b610af6565b610a8d565b610a5b565b61093f565b610808565b6107bc565b610715565b6106e8565b6106cb565b61069e565b61067c565b610661565b6105b3565b610495565b610472565b610457565b6103a6565b63ffffffff8116036103a257565b5f80fd5b346103a25760203660031901126103a25760206004356103c581610394565b63ffffffff8091165f52600e825260405f205416604051908152f35b5f9103126103a257565b634e487b7160e01b5f52601160045260245ffd5b90612000820180921161040e57565b6103eb565b906004820180921161040e57565b906008820180921161040e57565b9190820180921161040e57565b5f1981019190821161040e57565b9190820391821161040e57565b346103a2575f3660031901126103a2576020604051601c8152f35b346103a2575f3660031901126103a257602063ffffffff600f5416604051908152f35b346103a2575f3660031901126103a25763ffffffff805f541690815f52600e6020528060405f2054169160ff60115460201c1680610570575b1561055d5760105415610558576001600160401b03926105549160105f526105075f8051602061493f8339815191525463ffffffff1690565b600f549260405196879685169460201c169286936001600160401b039060809497969260a087019863ffffffff8096818094168a5216602089015216604087015216606085015216910152565b0390f35b6113c7565b82610554916001600160401b0394610507565b5060105415156104ce565b61ffff8116036103a257565b602435906001600160a01b03821682036103a257565b600435906001600160a01b03821682036103a257565b346103a25760403660031901126103a2576004356105d08161057b565b61ffff6105db610587565b916105e6363361213b565b165f818152600460209081526040918290205482516001600160a01b03868116825291821692810192909252929182917f46d568db0216a0215fe7f56360f9fa1a002c4bf0773add9cd62cd06eff1601899190a25f52600460205260405f2091166bffffffffffffffffffffffff60a01b8254161790555f80f35b346103a2575f3660031901126103a257602060405160078152f35b346103a2575f3660031901126103a257602063ffffffff5f5416604051908152f35b346103a25760203660031901126103a2576004355f52601b602052602060ff60405f205416604051908152f35b346103a2575f3660031901126103a2576020600c54604051908152f35b346103a25760203660031901126103a257602061070b61070661059d565b611a90565b6040519015158152f35b346103a25760203660031901126103a2576001600160401b0363ffffffff610554815f5416805f52601760205260405f206004355f526020528260405f2054169061076660115460ff9060201c1690565b806107b1575b156107875761050761077c6113db565b505463ffffffff1690565b6107ac6107a28263ffffffff165f52600e60205260405f2090565b5463ffffffff1690565b610507565b50601054151561076c565b346103a25760203660031901126103a2576004355f526012602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a25760206040515f8152f35b346103a25760203660031901126103a2576004355f52601a602052602060ff60405f205416604051908152f35b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761086457604052565b610835565b602081019081106001600160401b0382111761086457604052565b604081019081106001600160401b0382111761086457604052565b6001600160401b03811161086457604052565b60e081019081106001600160401b0382111761086457604052565b90601f801991011681019081106001600160401b0382111761086457604052565b6001600160401b03811161086457601f01601f191660200190565b929192610915826108ee565b9161092360405193846108cd565b8294818452818301116103a2578281602093845f960137010152565b60403660031901126103a25761095361059d565b6024356001600160401b0381116103a257366023820112156103a257610983903690602481600401359101610909565b9061098c612f7f565b610996363361213b565b61099e612f7f565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610a2a575b506109f057604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b0390fd5b905f8051602061491f8339815191528303610a1157610a0f925061377d565b005b604051632a87526960e21b815260048101849052602490fd5b610a4d91945060203d602011610a54575b610a4581836108cd565b81019061288a565b925f6109c6565b503d610a3b565b346103a25760203660031901126103a2576004355f526013602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a2577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163003610ae45760206040515f8051602061491f8339815191528152f35b60405163703e46dd60e11b8152600490fd5b346103a2575f3660031901126103a257602063ffffffff60155416604051908152f35b346103a25760203660031901126103a257610b3261059d565b610b3c363361213b565b600380546001600160a01b0319166001600160a01b0392909216919091179055005b346103a2575f3660031901126103a2576020600254604051908152f35b346103a2575f3660031901126103a257602060405160048152f35b346103a2575f3660031901126103a257602063ffffffff60115416604051908152f35b346103a2575f3660031901126103a2576020600154604051908152f35b346103a25760203660031901126103a257610bef61059d565b5f805160206148ff833981519152546001600160a01b03919082163303610c3957803b15610c2057610a0f9061225e565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b346103a25760403660031901126103a2576020610ca0600435610c7281610394565b63ffffffff610c7f610587565b91165f52600d835260405f209060018060a01b03165f5260205260405f2090565b54604051908152f35b346103a25760203660031901126103a2577f2b797207c3f71ed55e7ee9f751f9d4e23795bff49d40a0d3d05e160e1257798d610ce361059d565b610ced363361213b565b601680546001600160a01b039283166001600160a01b031982168117909255604080519283529216602082015290819081015b0390a1005b346103a2575f3660031901126103a257610d3d6122b6565b601c545f905f5b818110610d71578215610d5957610a0f611c42565b60405163dca69e6760e01b8152336004820152602490fd5b610d7a816113fa565b50549261ffff8460c01c16158015610de6575b610ddc575080610da7610da16001936113fa565b90611b5a565b819363ffffffff3391167f77018e8d096d16121cecd1c24da099cbde586f6f93c2f1352a90eac66e5a45455f80a35b01610d44565b9250600190610dd6565b50602084901c6001600160a01b0316331415610d8d565b346103a2575f3660031901126103a257602060115460ff60405191831c1615158152f35b346103a2575f3660031901126103a25760206001600160401b0360185416604051908152f35b346103a25760203660031901126103a25761ffff600435610e678161057b565b165f526004602052602060018060a01b0360405f205416604051908152f35b346103a2575f3660031901126103a2575f805160206148ff8339815191525460a01c60ff1615610ece576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f610ebb565b346103a2575f3660031901126103a25760206040516180008152f35b346103a2575f3660031901126103a257602060ff600f5460601c166040519015158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346103a2575f3660031901126103a257610554604051610f5a81610884565b60058152640352e302e360dc1b6020820152604051918291602083526020830190610f17565b346103a25760203660031901126103a2576004355f52601960205260206001600160401b0360405f205416604051908152f35b346103a25760203660031901126103a257610554600435610fd381610394565b60405f818051610fe281610849565b828152826020820152015263ffffffff8092165f5260146020526001815f206001600160401b0383519461101586610849565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b346103a2575f3660031901126103a25760206040516120008152f35b346103a2575f3660031901126103a2575f8051602061495f833981519152805460ff8160401c16908115611146575b50611134575f8051602061495f833981519152805467ffffffffffffffff19166002179055805460ff60401b1916600160401b1790556110e86122f5565b5f8051602061495f833981519152805460ff60401b19169055604051600281527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2908060208101610d20565b60405163f92ee8a960e01b8152600490fd5b600291506001600160401b031610155f6110aa565b346103a2575f3660031901126103a2575f805160206148ff833981519152546040516001600160a01b039091168152602090f35b346103a25760203660031901126103a25760c06111b26111ad61059d565b611ba4565b93949290916040519563ffffffff809516875260018060a01b0316602087015261ffff809216604087015216606085015216608083015260a0820152f35b346103a25760203660031901126103a25760043561120d81610394565b611217363361213b565b63ffffffff90816112396107a28363ffffffff165f52601460205260405f2090565b16156112dc5761125d6112586112535f5463ffffffff1690565b612430565b612466565b82821692811683116112b657506112856112909163ffffffff165f52601460205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b604051631cc9a6c760e21b815263ffffffff928316600482015291166024820152604490fd5b604051630529520560e31b815263ffffffff919091166004820152602490fd5b346103a2575f3660031901126103a2576020601d54604051908152f35b346103a2575f3660031901126103a2576003546040516001600160a01b039091168152602090f35b346103a25760a03660031901126103a25760043561135e81610394565b60243561136a8161057b565b604435916113778361057b565b60643561138381610394565b608435936001600160401b03938486116103a257366023870112156103a25785600401359485116103a25736602486880101116103a2576024610a0f960193611c2c565b634e487b7160e01b5f52603260045260245ffd5b601054156105585760105f525f8051602061493f833981519152905f90565b601c5481101561055857601c5f527f0e4562a10381dec21b205ed72637e6b1b523bdd0e4d4d50af5cd23dd4500a21101905f90565b6010548110156105585760105f525f8051602061493f83398151915201905f90565b8054821015610558575f5260205f2001905f90565b346103a25760403660031901126103a257600435602435906010548110156103a25760105f525f8051602061493f8339815191520180548210156103a2576020916114b091611451565b905460405160039290921b1c6001600160a01b03168152f35b346103a25760203660031901126103a2576114e261059d565b5f8051602061495f83398151915290815460ff8160401c16908115611541575b50611134575f8051602061495f833981519152805467ffffffffffffffff191660021790556110e891805460ff60401b1916600160401b179055612044565b600291506001600160401b031610155f611502565b346103a2575f3660031901126103a2576020600f546001600160401b0360405191831c168152f35b346103a25760203660031901126103a257606060043561159d81610394565b63ffffffff8091165f52601460205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b346103a25760203660031901126103a2576004356115f581610394565b6115ff363361213b565b60405161160b81610869565b63ffffffff908183168082521561167457601580549151831663ffffffff1983161790556040805163ffffffff93909216831682529290911660208201527f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69181908101610d20565b604051639ca50c6160e01b8152600490fd5b346103a2575f3660031901126103a2576020600b54604051908152f35b346103a25760203660031901126103a25763ffffffff6004356116c581610394565b6116cf363361213b565b1663ffffffff19600f541617600f555f80f35b346103a2575f3660031901126103a2576016546040516001600160a01b039091168152602090f35b346103a25760203660031901126103a257604061172d61172861059d565b612362565b825191825215156020820152f35b604051905f6009546001918160011c9260018316908115611822575b60209060208610831461180e57858852879460208601939081156117ef575060011461178f575b50505061178d925003836108cd565b565b9250936117bd60095f527f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af90565b945f935b8285106117d95750505061178d9350015f808061177e565b86548585015295860195889550938101936117c1565b9250505061178d9491925060ff19168252151560051b015f808061177e565b634e487b7160e01b5f52602260045260245ffd5b93607f1693611757565b6040519061183982610884565b60065460ff8116835260081c63ffffffff166020830152565b6040519061185f82610884565b60075460ff8116835260081c63ffffffff166020830152565b926001600160401b03906118de60206118a06119059a98969995610120808a52890190610f17565b986118c1828901825163ffffffff6020809260ff8151168552015116910152565b0151805160ff1660608801526020015163ffffffff166080870152565b1660a084015263ffffffff80921660c08401521660e0820152610100818403910152610f17565b90565b346103a2575f3660031901126103a2576040515f6005546001918160011c9260018316908115611a40575b60209060208610831461180e5785875286946020860193908115611a2157506001146119c1575b84611967818603826108cd565b60405161197381610884565b61197b61182c565b8152611985611852565b602082015261055460085463ffffffff61199d61173b565b916040519586956001600160401b03848460601c16948460401c1693169187611878565b9250936119ef60055f527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090565b945f935b828510611a0b575050506119679350015f808061195a565b86548585015295860195879550938101936119f3565b925050506119679491925060ff19168252151560051b015f808061195a565b93607f1693611933565b346103a25760403660031901126103a2576020600435611a6981610394565b63ffffffff8091165f526017825260405f206024355f52825260405f205416604051908152f35b601154611aa59060201c60ff161590565b1590565b611b545763ffffffff80611abe60105463ffffffff1690565b168015611b4d575f5b8183821610611ad857505050505f90565b80611ae3849261142f565b5080545f5b818110611afb5750505060010116611ac7565b9091929350611b22611b0d8285611451565b905460039190911b1c6001600160a01b031690565b6001600160a01b03888116911614611b41576001019085939291611ae8565b50505050505050600190565b5050505f90565b50600190565b90611b64575f9055565b634e487b7160e01b5f525f60045260245ffd5b9081600d1b91808304612000149015171561040e57565b908160021b918083046004149015171561040e57565b611bad90612362565b15611c1d57601c5481101561055857601c5f527f0e4562a10381dec21b205ed72637e6b1b523bdd0e4d4d50af5cd23dd4500a211015461ffff90818160d01c1690611bf782611b77565b63ffffffff821694602083901c6001600160a01b03169460c084901c16939260e01c9190565b505f905f905f905f905f905f90565b90611c429594939291611c3d6122b6565b611cbc565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b90600161ffff8093160191821161040e57565b909280606093602095601d855286850152846040850152818585015260808401375f8282018401850152601f01601f191601010190565b6040513d5f823e3d90fd5b91939093611cce5f5463ffffffff1690565b63ffffffff948582169286861692848410612038578484036120115750611cf7611aa133611a90565b611ff957611d078983838b612479565b5f965f9861ffff9485841693600185145f14611d3a575050505050505050611d349061178d953691610909565b90612b3f565b91939599509193959950611d4d336125b2565b999095869a15611fe15760019b611d63886113fa565b5096875491848360c01c16908115908115611fd5575b5015611f2357505050818516611f015760e088901b6001600160e01b031916640100000000600160c01b033360201b1663ffffffff8c161761ffff60c01b60c086901b16171786555b611dd5611dce86611c67565b61ffff1690565b03611dfe575050505091611d349391611df861178d99945461ffff9060d01c1690565b90612723565b929a9495509596509750611e35919650611e2c73__$a62ffa76e12530392778037a67b422bd63$__94611b8e565b9089169061042f565b91803b156103a257611e60945f9360405196879485938493633535237960e21b855260048501611c7a565b03915af48015611efc577fbe287004f9392c9ea6ab7463d51604830991743d8d3364ae2566b5bc8607110c93611ede93611ebf92611ee3575b50611ea387611c67565b815461ffff60d01b191660d09190911b61ffff60d01b16179055565b6040805161ffff96871681529590911660208601523394918291820190565b0390a3565b80611ef0611ef69261089f565b806103e1565b5f611e99565b611cb1565b604051635d551ad560e01b81525f600482015261ffff86166024820152604490fd5b602083901c6001600160a01b031633148015918591611fca575b50508015611fbb575b611f9c575060d01c8216858316818110611f8b578103611f665750611dc2565b604051635d551ad560e01b815261ffff91821660048201529086166024820152604490fd5b505050505050505050505050505050565b604051633b52f0fb60e01b8152911663ffffffff166004820152602490fd5b50808a168260e01c1415611f46565b14159050835f611f3d565b9050828416105f611d79565b6040516327b2f1c160e21b8152336004820152602490fd5b604051632af0051560e21b8152336004820152602490fd5b6040516307d3e83d60e51b815263ffffffff91821660048201529087166024820152604490fd5b50505050505050505050565b6120659061205061374f565b61205861374f565b61206061374f565b61225e565b61206d61374f565b6120756122f5565b5f805463ffffffff19908116600117909155600f80546bffffffffffffffffffffffff19164260201b6bffffffffffffffff000000001617905560c890816040516120bf81610869565b526015541617601555565b6004116103a2575f90600490565b6001600160e01b031990358181169392600481106120f557505050565b60040360031b82901b16169150565b60209260409260018060a01b0316825282848301528083830152805f60608401375f8282018401850152601f01601f191601010190565b5f805160206148ff8339815191528054612173906001600160a01b031661216a612164866120ca565b906120d8565b90843091612efe565b9015612180575b50505050565b63ffffffff161561223e575f805160206148ff833981519152805460ff60a01b1916600160a01b1790556121ce906121c29081905b546001600160a01b031690565b6001600160a01b031690565b91823b156103a2576121f9925f9283604051809681958294634a63ebf760e11b845260048401612104565b03925af18015611efc5761222b575b505f805160206148ff833981519152805460ff60a01b191690555f80808061217a565b80611ef06122389261089f565b5f612208565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b5f805160206148ff83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00805c6122e3576001905d565b604051633ee5aeb560e01b8152600490fd5b5f5b6007811061233757505f5b601c811061230d5750565b601d908154600160401b8110156108645760018101908184551015610558576001915f5201612302565b601c90815491600160401b831015610864578261235a91600180950190556113fa565b5050016122f7565b5f915f91601c54905f809161238761237e5f5463ffffffff1690565b63ffffffff1690565b925b848110612397575050505050565b6123a0816113fa565b505461ffff8160c01c16158015612415575b61240c5763ffffffff809116908582146123fd5788159081156123f1575b506123e1575b506001905b01612389565b90975060019650915086866123d6565b9050841681115f6123d0565b50909750600196505050505050565b506001906123db565b50602081901c6001600160a01b0390811690841614156123b2565b63ffffffff9060018183161115612448575f19011690565b50505f90565b63ffffffff91908282168382161115611b4d57031690565b6119059063ffffffff601554169061244e565b9163ffffffff811690811580156125a7575b61257f57506124ad611dce6124a76124a2846103ff565b61043c565b600d1c90565b61ffff80841691818116830361255a57508416928184101561253457506124d6611dce85611c67565b03612529576124e76124ed92611b77565b9061044a565b915b8281036124fb57505050565b604051632ac20c8f60e21b815261ffff92909216600483015260248201929092526044810191909152606490fd5b5050612000916124ef565b6040516385d12c3160e01b815261ffff8681166004830152919091166024820152604490fd5b6040516328e2405760e01b815261ffff91821660048201529085166024820152604490fd5b604051630c6f042160e31b815263ffffffff9190911660048201526180006024820152604490fd5b50618000821161248b565b601c54906125c660115460ff9060201c1690565b612672575f19905f5b8381106125ef57505090505f1981146125e85790600190565b505f905f90565b6125f8816113fa565b505461ffff8160c01c16158015612653575b1561262b57505f198314612623575b6001905b016125cf565b915081612619565b60201c6001600160a01b03908116908316146126495760019061261d565b9250505090600190565b5061266561237e5f5463ffffffff1690565b63ffffffff82161061260a565b601054915f5b83811061268957505050505f905f90565b6126928161142f565b505f9080545b8083106126aa57505050600101612678565b6126bd611b0d8484999798969599611451565b6001600160a01b038581169116036126e15750505050508110156125e85790600190565b9091929493956001019190612698565b906126fb826108ee565b61270860405191826108cd565b8281528092612719601f19916108ee565b0190602036910137565b9493929161273963ffffffff61273f92166126f1565b95611b8e565b905f905f92601d545b61ffff83168510612760575050506020915085010137565b90919284830180841161040e578281101561055857601d5f527f6d4407e7be21f808e6509aa9fa9143369579dd7d760fe20a2c09680fc146134f01906040515f83549360019085821c82871691821561286f575b602097888310841461180e5782875260208701939081156128505750600114612812575b5050505092600192826127f36128059461ffff9703826108cd565b8d83838351958694019201015e61042f565b9501949392919050612748565b61282391929394505f5260205f2090565b925f935b82851061283f575050500182826127f36128056127d8565b805484860152938701938101612827565b60ff191684525050151560051b019150839050826127f36128056127d8565b90607f16906127b4565b906020611905928181520190610f17565b908160209103126103a2575190565b906040602061178d936128c084825163ffffffff6020809260ff8151168552015116910152565b015191019063ffffffff6020809260ff8151168552015116910152565b604080830163ffffffff9283815116855260208091015194604082820152855180935260609360608201908360608660051b8501019801955f935b86851061292b5750505050505050505090565b90919293949596979998868061296d600193868f89908f605f198a8603018d52519061ffff8251168552868201511686850152015191818a8201520190610f17565b9b9c9a01989796919091019401929190612918565b908082519081815260208091019281808460051b8301019501935f915b8483106129af5750505050505090565b9091929394958480612a80600193601f198682030187528a519083612a7060408451908085526129e28186018351612899565b8382015161014092612a5e612a47612a21612a0960c09588878d01526101808c0190610f17565b9584015195603f1996878c83030160e08d0152610f17565b606084015163ffffffff166101008b01526080840151868b8303016101208c0152610f17565b9460a0830151908901906001600160401b03169052565b01519085830301610160860152610f17565b92015190848184039101526128dd565b980193019301919493929061299f565b906119059160208152815190612ab461014092836020840152610160830190610f17565b91612ac760208501516040840190612899565b60408401516001600160401b031660c0830152606084015163ffffffff1660e0830152608084015163ffffffff1661010083015260c0612b1a60a086015194601f19958686830301610120870152610f17565b9401519282850301910152612982565b63ffffffff80911690811461040e5760010190565b93929190612b4c90613154565b60209081808201510151612b61815160ff1690565b90600260ff831614801590612ee5575b612eb45750506060810195612b8a875163ffffffff1690565b9063ffffffff80821690831603612e8e57505060a081015182815180155f14612dcd5750505060025480612dad57505b60405163791b84ad60e11b8152828180612bd78560048301612a90565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4928315611efc575f93612d8e575b5050612c3a612c28612c14885163ffffffff1690565b63ffffffff165f52600d60205260405f2090565b335f9081526020919091526040902090565b548015612c8d578203612c525761178d949550613391565b6109ec612c63875163ffffffff1690565b60405163082085ab60e21b815263ffffffff90911660048201523360248201529081906044820190565b50612d5a61237e61178d969784612cc433612caf612c14855163ffffffff1690565b9060018060a01b03165f5260205260405f2090565b55612d17612ced612cd9835163ffffffff1690565b63ffffffff165f52600e60205260405f2090565b612d03612cfe825463ffffffff1690565b612b2a565b63ffffffff1663ffffffff19825416179055565b612d50612ced86612d43612d2f855163ffffffff1690565b63ffffffff165f52601760205260405f2090565b905f5260205260405f2090565b5163ffffffff1690565b6040518381523391907fcafc00e7c35f462868d78e8228850e885bd0870978967c446c6bb67122a3fb2890602090a3613391565b612da5929350803d10610a5457610a4581836108cd565b905f80612bfe565b604051631d734b9160e01b815260048101919091525f6024820152604490fd5b03612e52576040516347f5343160e01b815290838280612df08460048301612879565b038173__$b91d2ad766fd07dc99f56c1a0b020615bb$__5af4918215611efc575f92612e6f575b508115612e525750600254808203612e30575050612bba565b604051631d734b9160e01b815260048101919091526024810191909152604490fd5b604051630ca3ddf160e01b81529081906109ec9060048301612879565b612e87919250843d8611610a5457610a4581836108cd565b905f612e17565b604051635070175360e01b815263ffffffff918216600482015291166024820152604490fd5b8301516040516385a89b3360e01b815246600482015260ff92909216602483015263ffffffff166044820152606490fd5b508084015163ffffffff1663ffffffff16461415612b71565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b03841117610864576040935f9385528380528360205251915afa612f6d57565b9150505f51906020518060201c150290565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215612fbd575b5050610ae457565b5f8051602061491f8339815191525416141590505f80612fb5565b60405190612fe582610884565b5f6020838281520152565b60405190612ffd82610884565b81613006612fd8565b81526020613012612fd8565b910152565b60405190613024826108b2565b606060c083828152613034612ff0565b60208201525f60408201525f838201525f60808201528260a08201520152565b5f19811461040e5760010190565b6001600160401b0381116108645760051b60200190565b60405190613086826108b2565b606060c083613093612ff0565b81528260208201528260408201525f838201528260808201525f60a08201520152565b604051906130c382610884565b60606020835f81520152565b604051906130dc82610884565b816130e5613079565b815260206130126130b6565b906130fb82613062565b61310860405191826108cd565b8281528092613119601f1991613062565b01905f5b82811061312957505050565b6020906131346130cf565b8282850101520161311d565b80518210156105585760209160051b010190565b9061315d613017565b915f8151905f805b83811061332d5750613176906130f1565b9160c086019283525f915b81811061318f575050505050565b61319990856138dc565b90816001600160401b0391828116600a81145f146131ca57505050506131bf9085613a95565b919087525b90613181565b60128103613212575050506131e261320b92876138dc565b9116906132016131fc6131f5848461042f565b838a613920565b613ac4565b60208a015261042f565b905b6131c4565b6028810361323f57505050509061322c61320d92866138dc565b6001600160401b03909116604089015291565b6030810361326f57505050509061320d61325e63ffffffff93876138dc565b9316606089019063ffffffff169052565b6038810361329f57505050509061320d61328e63ffffffff93876138dc565b9316608089019063ffffffff169052565b60a281036132c157505050506132b59085613a95565b919060a08801526131c4565b6101420361331d5750506132d861320b92876138dc565b9116906133176132fa6132f56132ee858561042f565b848b613920565b6139cc565b95875161330682613054565b976133118383613140565b52613140565b5061042f565b61320b9350600716915086613822565b61333a90859493946138dc565b6101426001600160401b038394931614613366575b600761335e9293169086613822565b929192613165565b600761337461335e93613054565b92505061334f565b90600163ffffffff8093160191821161040e57565b929391909360608401946133af6107a2612cd9885163ffffffff1690565b60115460201c60ff1680613744575b1561373e576133ce61077c6113db565b905b819063ffffffff80931615613736575b506133f96107a284612d43612d2f8c5163ffffffff1690565b916134126134068361337c565b60011c637fffffff1690565b90600f54926134316001600160401b0394858482169160201c1661042f565b4210159180808716921682109383613729575b505050811591613721575b501561371757906134cc839261346761352f95600b55565b61349461348061347b8c5163ffffffff1690565b61337c565b63ffffffff1663ffffffff195f5416175f55565b600f80546cffffffffffffffffff000000001916429290921660201b6bffffffffffffffff000000001691909117600160601b179055565b7fa77ed82a4a8c2f7b412630e92b1c8f59e6645b05820a8b0ed43ae5fac990491c61351f61350161237e8b5163ffffffff1690565b6040805186815263ffffffff90951660208601529093918291820190565b0390a2865163ffffffff16613b70565b613545613540865163ffffffff1690565b613bd6565b613705575b50600354613560906001600160a01b03166121c2565b92833b156103a257604051636744c79f60e01b80825260016004830152945f908290602490829084905af18015611efc576136f2575b505f5b60c084015180518210156135c757816135bf6135b960c094600194613140565b51613ca1565b019050613599565b50506003549193909250906135e4906001600160a01b03166121c2565b90813b156103a2575f90604051928391825281838161360a60048201905f602083019252565b03925af18015611efc576136df575b50613625600b54600255565b7fe784b22f2061de501b77364cab02ee109528afe4ddc8d423d076f6b49021b58e61366e613657835163ffffffff1690565b60405163ffffffff90911681529081906020820190565b0390a16003546136959061368a906001600160a01b03166121c2565b915163ffffffff1690565b90803b156103a25760405163a3ad9cc360e01b815263ffffffff9290921660048301525f908290602490829084905af18015611efc576136d25750565b80611ef061178d9261089f565b80611ef06136ec9261089f565b5f613619565b80611ef06136ff9261089f565b5f613596565b610da1613711916113fa565b5f61354a565b5050505050915050565b90505f61344f565b16111590505f8080613444565b90505f6133e0565b806133d0565b5060105415156133be565b60ff5f8051602061495f8339815191525460401c161561376b57565b604051631afcd79f60e31b8152600490fd5b90813b15613801575f8051602061491f83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a28051156137e6576137e391614028565b50565b5050346137ef57565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b6001600160401b039283169283613843575061383e92506138dc565b905090565b9192600181036138595750505061190590610421565b6002810361387757506119059261386f916138dc565b91169061042f565b60051415915061388c90505761190590610413565b60405162461bcd60e51b815260206004820152602260248201527f50726f746f62756652756e74696d653a20756e6b6e6f776e2077697265207479604482015261706560f01b6064820152608490fd5b91908201602001905f805b60806007600186515f1a94607f8616851b179601920192161561391357603f82116103a25792906138e7565b9293909203601f19019150565b9181811015806139c1575b15613970578161393a9161044a565b90613944826126f1565b9282613951575b50505090565b015f5b8281101561394b57602081830181015185830182015201613954565b60405162461bcd60e51b8152602060048201526024808201527f50726f746f62756652756e74696d653a20736c696365206f7574206f6620626f604482015263756e647360e01b6064820152608490fd5b50825181111561392b565b906139d56130cf565b915f8151905b8181106139e757505050565b6139f190836138dc565b90816001600160401b0391828116600a81145f14613a4757505050613a19613a3f92856138dc565b911690613a38613a33613a2c848461042f565b8388613920565b6141f7565b875261042f565b905b906139db565b601203613a85575050613a5d613a7f92856138dc565b911690613a75613a70613a2c848461042f565b6140da565b602088015261042f565b90613a41565b613a7f9350600716915084613822565b9190613aa96001600160401b0391846138dc565b911681019283821161040e57613ac0918491613920565b9190565b90613acd612ff0565b915f8151905b818110613adf57505050565b613ae990836138dc565b90816001600160401b0391828116600a81145f14613b3157505050613b11613b2992856138dc565b911690613a38613b24613a2c848461042f565b61435a565b905b90613ad3565b601203613b60575050613b47613b5a92856138dc565b911690613a75613b24613a2c848461042f565b90613b2b565b613b5a9350600716915084613822565b63ffffffff8082165f52601460205260405f2090815416613bd157613ba7600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b613bed9063ffffffff91829182601554169061244e565b16908115159081613c16575b50613c015750565b5f5260146020525f6001604082208281550155565b9050815f52601460205260405f20541615155f613bf9565b90613c46602091949394604084526040840190610f17565b6001600160a01b03909416910152565b9193926001600160401b039061ffff613c79604094606087526060870190610f17565b9616602085015216910152565b60409061ffff61190594931681528160208201520190610f17565b905f5b6020908180850151015180518210156140215781613cc191613140565b51805161ffff16908161ee0f811461400e5761ee138114613ff65761ee218114613f9f5761ee208114613f485761ee1b14613e6a57613d126121c26121b58461ffff165f52600460205260405f2090565b6001600160a01b038116929091908315613e225761dead8414613dd25760408092015192803b156103a2578251633bbe5e5b60e21b8152935f918591829084908290613d62908860048401613c86565b03925af18015611efc576001967f6615e715ef29da98af4d4b50f6beb57ec1a6a61bb207b117e5fb58632755e93794613db592613dbf575b508951015192519283926001600160401b0388169184613c56565b0390a25b01613ca4565b80611ef0613dcc9261089f565b5f613d9a565b9150507f06a84e8c796b3ecc778e5832b8ba6869cc1bf4822409bfd7f852021f87256d0c91506001938651015190613e1a6040519283926001600160401b0387169184613c56565b0390a1613db9565b9150507ff1a904444e5437715f6092bdea001e8e9fa7124d1b19aed02311ac2553da0dea91506001938651015190613e1a6040519283926001600160401b0387169184613c56565b6040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a257835163d85349a560e01b8152975f92899283918291613ec09160048401613c2e565b03915af4958615611efc57613f0a7f6615e715ef29da98af4d4b50f6beb57ec1a6a61bb207b117e5fb58632755e93794613f2d92600199613f35575b50546001600160a01b031690565b91895101519251928392888060a01b0316956001600160401b0388169184613c56565b0390a2613db9565b80611ef0613f429261089f565b5f613efc565b506040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a257835163462fd82d60e01b8152975f92899283918291613ec09160048401613c2e565b506040908101516016805473__$a62ffa76e12530392778037a67b422bd63$__96919392906001600160a01b0316873b156103a2578351634f6b2fdf60e01b8152975f92899283918291613ec09160048401613c2e565b506001935061400991506040015161455c565b613db9565b506001935061400991506040015161443f565b5050509050565b5f8061190593602081519101845af43d15614064573d91614048836108ee565b9261405660405194856108cd565b83523d5f602085013e61462b565b60609161462b565b6040519061407982610849565b60606040835f81525f60208201520152565b9061409582613062565b6140a260405191826108cd565b82815280926140b3601f1991613062565b01905f5b8281106140c357505050565b6020906140ce61406c565b828285010152016140b7565b906140e36130b6565b915f8151905f805b8381106141a957506140fc9061408b565b91602086019283525f915b818110614115575050505050565b61411f90856138dc565b90816001600160401b0391828116600881145f1461416257505050509061415c61414e63ffffffff93876138dc565b9316889063ffffffff169052565b90614107565b60120361419957505061417861419392876138dc565b9116906133176132fa61418e6132ee858561042f565b61468e565b9061415c565b6141939350600716915086613822565b6141b690859493946138dc565b60126001600160401b0383949316146141e1575b60076141d99293169086613822565b9291926140eb565b60076141ef6141d993613054565b9250506141ca565b90614200613079565b915f8151905b81811061421257505050565b61421c90836138dc565b90816001600160401b0391828116600a81145f1461425f5750505061424461425792856138dc565b911690613a386131fc613a2c848461042f565b905b90614206565b9192509060128103614284575050506142789083613a95565b91906020860152614259565b601a81036142a5575050506142999083613a95565b91906040860152614259565b602881036142d957505050906142d46142c363ffffffff93856138dc565b9316606087019063ffffffff169052565b614259565b603281036142fa575050506142ee9083613a95565b91906080860152614259565b6038810361432657505050906143136142d492846138dc565b6001600160401b0390911660a087015291565b6042036143455750506143399083613a95565b919060c0860152614259565b61435492506007169084613822565b90614259565b90614363612fd8565b915f8151905b81811061437557505050565b61437f90836138dc565b80916001600160401b038116600881145f146143b9575050509060ff6143a86143af93856138dc565b939061475a565b1685525b90614369565b6010036143de575050906143d263ffffffff92846138dc565b921660208601526143b3565b6143ed92506007169084613822565b906143b3565b908160609103126103a257604080519161440c83610849565b805161441781610394565b8352602081015161442781610394565b6020840152015180151581036103a257604082015290565b606061447163ffffffff600f54169260405193849283926374f9c0eb60e01b8452608060048501526084840190610f17565b9060106024840152601260448401526064830152038173__$a62ffa76e12530392778037a67b422bd63$__5af48015611efc57604061450e9161178d935f9161452d575b506144dd6144c7825163ffffffff1690565b63ffffffff1663ffffffff196011541617601155565b6145076144f1602083015163ffffffff1690565b63ffffffff1663ffffffff19600f541617600f55565b0151151590565b64ff0000000060115491151560201b169064ff00000000191617601155565b61454f915060603d606011614555575b61454781836108cd565b8101906143f3565b5f6144b5565b503d61453d565b6001600160401b039060018260185416019082821161040e5773__$a62ffa76e12530392778037a67b422bd63$__90813b156103a2575f916145bc91604051809681948293637401af5d60e01b845260e0600485015260e4840190610f17565b90601260248401526013604484015260196064840152601a6084840152601b60a4840152871660c483015203915af4918215611efc5761178d92614618575b506001600160401b03166001600160401b03196018541617601855565b80611ef06146259261089f565b5f6145fb565b90614652575080511561464057602081519101fd5b60405163d6bda27560e01b8152600490fd5b81511580614685575b614663575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b1561465b565b9061469761406c565b915f8151905b8181106146a957505050565b6146b390836138dc565b80916001600160401b038116600881145f146146f257505050906146ec6146dd6146e493856138dc565b93906147aa565b61ffff168652565b9061469d565b60108103614726575050509061472161471063ffffffff93856138dc565b9316602087019063ffffffff169052565b6146ec565b601a036147455750506147399083613a95565b919060408601526146ec565b61475492506007169084613822565b906146ec565b6001600160401b031680156147a55760018114611b54576002811461479f57600381146147995760249060405190632cd3748760e21b82526004820152fd5b50600390565b50600290565b505f90565b6001600160401b0381168015612448576107d18082146148f85750610bb98082146148f85750610bba8082146148f85750610bbc8082146148f85750610bbe8082146148f8575061aa008082146148f8575061ee008082146148f8575061ee028082146148f8575061ee048082146148f8575061ee068082146148f8575061ee108082146148f8575061ee118082146148f8575061ee138082146148f8575061ee0f8082146148f8575061ee158082146148f8575061ee168082146148f8575061ee178082146148f8575061ee188082146148f8575061ee198082146148f8575061ee1b8082146148f8575061ee1c8082146148f8575061ee1e8082146148f8575061ee1f8082146148f8575061ee208082146148f8575061ee218082146148f8575061ee2280911461383e57604051632cd3748760e21b81526001600160401b0383166004820152602490fd5b9150509056fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc1b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220c83eea418b14c7847807a669a42dd27a5506963582b408019c06d10beba85c8264736f6c63430008190033", + "bytecode": "0x60a0806040523460d657306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a009081549060ff8260401c1660c757506001600160401b036002600160401b0319828216016083575b600163ffffffff195f5416175f55604051614ad490816100db8239608051818181610df30152613b950152f35b6001600160401b031990911681179091556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f80806056565b63f92ee8a960e01b8152600490fd5b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80624e356a1461032357806308a9a6de1461031e5780630fa92c5314610319578063151b8f4e146103145780631bdf0a721461030f5780631d7a88271461030a57806320857e651461030557806322fe388b14610300578063239e1d67146102fb5780632945698b146102f65780632f4d9fa9146102f15780633367cca5146102ec57806336051135146102e75780633915ba63146102e25780634ab47f43146102dd5780634cc04c94146102d85780634f1ef286146102d35780635126a52d146102ce57806352d1902d146102c9578063533ae1cc146102c45780635701a93f146102bf5780635c0c8f44146102ba5780635d4e25ce146102b55780635ff8195e146102b05780637a9e5e4b146102ab5780637b778122146102a657806380e643b6146102a15780638b75e9291461029c5780638bdebf21146102975780638d9f616b146102925780638fb360371461028d57806393ec06be1461028857806399b1a19c14610283578063ad3cb1cc1461027e578063af243e8914610279578063b1635f9f14610274578063b41565b81461026f578063bf7e214f1461026a578063c0d0373e14610265578063c268863f14610260578063c45fdcb71461025b578063cd2ad3fb14610256578063d0841f2214610251578063d1ad8d011461024c578063d3f0730614610247578063d5b5272a14610242578063e3dff2af1461023d578063e55ba576146102385763fdfd07df14610233575f80fd5b611789565b61174a565b61172d565b61167f565b61164b565b6115f1565b6115c9565b611564565b611501565b611489565b611373565b61133f565b611293565b6111e5565b6111b2565b61116d565b611124565b611108565b6110b8565b611079565b611053565b611018565b610ff4565b610f97565b610f19565b610ef6565b610ed9565b610e90565b610e6d565b610e4a565b610de1565b610daf565b610ca6565b610c8b565b610c5e565b610c2c565b610be2565b610ba8565b610b8b565b610b5e565b610b3c565b610b21565b610a80565b61092a565b61088b565b610868565b610823565b610737565b610374565b63ffffffff81160361033657565b5f80fd5b359061034582610328565b565b9181601f84011215610336578235916001600160401b038311610336576020838186019501011161033657565b346103365760403660031901126103365760043561039181610328565b6024356001600160401b038111610336576103b0903690600401610347565b90917f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0091825c6103ec575f936103e9926001855d6117cf565b5d005b604051633ee5aeb560e01b8152600490fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b0382111761042d57604052565b6103fe565b602081019081106001600160401b0382111761042d57604052565b604081019081106001600160401b0382111761042d57604052565b606081019081106001600160401b0382111761042d57604052565b6001600160401b03811161042d57604052565b60c081019081106001600160401b0382111761042d57604052565b60e081019081106001600160401b0382111761042d57604052565b90601f801991011681019081106001600160401b0382111761042d57604052565b6001600160401b03811161042d5760051b60200190565b6001600160401b03811161042d57601f01601f191660200190565b92919261052b82610504565b9161053960405193846104cc565b829481845281830111610336578281602093845f960137010152565b9080601f83011215610336578160206105709335910161051f565b90565b91906080838203126103365760409283519161058e83610412565b8294823561059b81610328565b84526020808401356105ac81610328565b858201526001600160401b0392848301358481116103365785019080601f83011215610336578135906105de826104ed565b956105eb865197886104cc565b828752848701918560059460051b8601019481861161033657868101935b86851061062f57505050505050505060609261062a9284928601520161033a565b910152565b843584811161033657820188601f1982860301126103365789519061065382610432565b8981013590868211610336570184603f820112156103365789810135610678816104ed565b916106858d5193846104cc565b8183528c8c8401928b1b82010190878211610336578c94929593918e8201915b8183106106bf575050508394508152815201940193610609565b91809496509690929496358a8111610336578f90830190603f19828c030112610336578f90918f928251906106f38261044d565b838101359060ff82168203610336578e9183526060810135918211610336578594610721928f920101610555565b83820152815201930190918d95939694926106a5565b34610336576020366003190112610336576001600160401b0360043581811161033657610768903690600401610573565b906107733633612265565b63ffffffff805f5416805f52600660205261079660405f2063ffffffff90541690565b916001821490811591610817575b506107ed576107eb836107be866107b9611a0c565b612477565b42166bffffffffffffffff000000006007549160201b16906bffffffffffffffff00000000191617600755565b005b6040516325ea24eb60e11b815263ffffffff918216600482015291166024820152604490fd5b0390fd5b9050821615155f6107a4565b3461033657602036600319011261033657602060043561084281610328565b63ffffffff8091165f526006825260405f205416604051908152f35b5f91031261033657565b34610336575f36600319011261033657602063ffffffff60075416604051908152f35b34610336575f366003190112610336576001600160401b0363ffffffff610915815f5416805f5260066020528260405f205416906108c881612950565b6007549260405196879685169460201c169286936001600160401b039060809497969260a087019863ffffffff8096818094168a5216602089015216604087015216606085015216910152565b0390f35b6001600160a01b0381160361033657565b346103365760403660031901126103365760043561094781610919565b6001600160401b039060243582811161033657610968903690600401610573565b5f80516020614a7f833981519152549260ff8460401c1615931680159081610a6c575b6001149081610a62575b159081610a59575b50610a47575f80516020614a7f833981519152805467ffffffffffffffff191660011790556109d09183610a2357611a75565b6109d657005b5f80516020614a7f833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29080602081015b0390a1005b5f80516020614a7f833981519152805460ff60401b1916600160401b179055611a75565b60405163f92ee8a960e01b8152600490fd5b9050155f61099d565b303b159150610995565b84915061098b565b61ffff81160361033657565b3461033657604036600319011261033657600435610a9d81610a74565b61ffff60243591610aad83610919565b610ab73633612265565b16805f526003602052807f46d568db0216a0215fe7f56360f9fa1a002c4bf0773add9cd62cd06eff160189604060018060a01b039485825f2054169582519116958682526020820152a25f90815260036020526040902080546001600160a01b0319169091179055005b34610336575f36600319011261033657602060405160078152f35b34610336575f36600319011261033657602063ffffffff5f5416604051908152f35b34610336576020366003190112610336576004355f526012602052602060ff60405f205416604051908152f35b34610336575f366003190112610336576020601454604051908152f35b34610336576020366003190112610336576020610bd8600435610bca81610919565b63ffffffff5f541690611be8565b6040519015158152f35b34610336576020366003190112610336576001600160401b0363ffffffff610915815f5416805f52600e60205260405f206004355f526020528260405f205416906108c881612950565b34610336576020366003190112610336576004355f52600a602052602060018060a01b0360405f205416604051908152f35b34610336576020366003190112610336576004355f526011602052602060ff60405f205416604051908152f35b34610336575f36600319011261033657602060405160018152f35b604036600319011261033657600435610cbe81610919565b6024356001600160401b03811161033657610cdd903690600401610555565b90610ce6613b8b565b610cf03633612265565b610cf8613b8b565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610d7e575b50610d4657604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b905f80516020614a3f8339815191528303610d65576107eb92506142e0565b604051632a87526960e21b815260048101849052602490fd5b610da191945060203d602011610da8575b610d9981836104cc565b810190611e79565b925f610d20565b503d610d8f565b34610336576020366003190112610336576004355f52600b602052602060018060a01b0360405f205416604051908152f35b34610336575f366003190112610336577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163003610e385760206040515f80516020614a3f8339815191528152f35b60405163703e46dd60e11b8152600490fd5b34610336575f36600319011261033657602063ffffffff60135416604051908152f35b34610336575f36600319011261033657602063ffffffff600d5416604051908152f35b3461033657602036600319011261033657600435610ead81610919565b610eb73633612265565b600280546001600160a01b0319166001600160a01b0392909216919091179055005b34610336575f366003190112610336576020600154604051908152f35b34610336575f36600319011261033657602063ffffffff60095416604051908152f35b3461033657602036600319011261033657600435610f3681610919565b5f80516020614a1f833981519152546001600160a01b03919082163303610f8057803b15610f67576107eb9061298a565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b34610336576040366003190112610336576020610feb600435610fb981610328565b63ffffffff60243591610fcb83610919565b165f526005835260405f209060018060a01b03165f5260205260405f2090565b54604051908152f35b34610336575f36600319011261033657602060095460ff60405191831c1615158152f35b3461033657602036600319011261033657604061103f60043561103a81610328565b611b54565b63ffffffff83519216825215156020820152f35b34610336575f3660031901126103365760206001600160401b03600f5416604051908152f35b346103365760203660031901126103365761ffff60043561109981610a74565b165f526003602052602060018060a01b0360405f205416604051908152f35b34610336575f366003190112610336575f80516020614a1f8339815191525460a01c60ff1615611100576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f6110ed565b34610336575f3660031901126103365760206040516180008152f35b34610336575f36600319011261033657602060ff60075460601c166040519015158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b34610336575f3660031901126103365761091560405161118c8161044d565b60058152640352e302e360dc1b6020820152604051918291602083526020830190611149565b34610336576020366003190112610336576004355f52601060205260206001600160401b0360405f205416604051908152f35b346103365760603660031901126103365760043561120281610a74565b6024356001600160401b03811161033657611221903690600401610347565b6044929192359161123183610328565b30330361127b5761ffff1661ee0f810361125a57506107eb9261125591369161051f565b612bc0565b61ee139192501461126757005b6107eb9161127691369161051f565b6129e2565b604051630c86f24360e41b8152336004820152602490fd5b34610336576020366003190112610336576109156004356112b381610328565b60405f8180516112c281610468565b828152826020820152015263ffffffff8092165f52600c6020526001815f206001600160401b038351946112f586610468565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b34610336575f366003190112610336575f80516020614a1f833981519152546040516001600160a01b039091168152602090f35b346103365760203660031901126103365760043561139081610328565b61139a3633612265565b63ffffffff90816113c66113bc8363ffffffff165f52600c60205260405f2090565b5463ffffffff1690565b1615611469576113ea6113e56113e05f5463ffffffff1690565b612d3b565b612d72565b8282169281168311611443575061141261141d9163ffffffff165f52600c60205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b604051631cc9a6c760e21b815263ffffffff928316600482015291166024820152604490fd5b604051630529520560e31b815263ffffffff919091166004820152602490fd5b34610336575f366003190112610336576002546040516001600160a01b039091168152602090f35b634e487b7160e01b5f52603260045260245ffd5b6008548110156114e75760085f525f80516020614a5f83398151915201905f90565b6114b1565b80548210156114e7575f5260205f2001905f90565b3461033657604036600319011261033657600435602435906008548110156103365760085f525f80516020614a5f8339815191520180548210156103365760209161154b916114ec565b905460405160039290921b1c6001600160a01b03168152f35b346103365760203660031901126103365763ffffffff60043561158681610328565b165f526015602052608060405f205460ff604051918181161515835261ffff8160081c166020840152818160181c161515604084015260201c1615156060820152f35b34610336575f3660031901126103365760206007546001600160401b0360405191831c168152f35b3461033657602036600319011261033657606060043561161081610328565b63ffffffff8091165f52600c60205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b34610336576040366003190112610336576020610bd860043561166d81610919565b6024359061167a82610328565b611be8565b346103365760203660031901126103365760043561169c81610328565b6116a63633612265565b6040516116b281610432565b63ffffffff908183168082521561171b57600d80549151831663ffffffff1983161790556040805163ffffffff93909216831682529290911660208201527f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69181908101610a1e565b604051639ca50c6160e01b8152600490fd5b34610336575f366003190112610336576020600454604051908152f35b346103365760203660031901126103365763ffffffff60043561176c81610328565b6117763633612265565b1663ffffffff1960075416176007555f80f35b346103365760403660031901126103365760206004356117a881610328565b63ffffffff8091165f52600e825260405f206024355f52825260405f205416604051908152f35b909291926117ed6117e45f5463ffffffff1690565b63ffffffff1690565b63ffffffff908184169081106119f0576118158463ffffffff165f52601560205260405f2090565b5460ff811690816119e1575b5061195d575f5463ffffffff169082821603611936575060095460ff8160201c166118c5575b505061185a6118568333611be8565b1590565b6118ad57831580156118a2575b6118815761187b906103459394369161051f565b90611e9d565b604051630c6f042160e31b8152600481018590526180006024820152604490fd5b506180008411611867565b604051632af0051560e21b8152336004820152602490fd5b6118ce84611b54565b905061184757610813846118e760135463ffffffff1690565b906118f760085463ffffffff1690565b604051631bf926d360e11b815263ffffffff92831660048201529282166024840152939094168416604482015292909116606483015281906084820190565b6040516307d3e83d60e51b815263ffffffff91821660048201529084166024820152604490fd5b50509261198d61197b8363ffffffff165f52600560205260405f2090565b335f9081526020919091526040902090565b54936004548095036118ad576119a491369161051f565b9081516020830120938085036119c05750610345929350611c9a565b6040516340d6b4cd60e01b8152600481019190915260248101859052604490fd5b60ff915060201c16155f611821565b505050509050565b634e487b7160e01b5f52601160045260245ffd5b6008545f60085580611a1b5750565b60085f525f80516020614a5f833981519152908101905b818110611a3d575050565b80545f825580611a51575b50600101611a32565b815f5260205f20908101905b818110611a6a5750611a48565b5f8155600101611a5d565b90611a9960c892611a84613b5d565b611a8c613b5d565b611a94613b5d565b61298a565b611aa1613b5d565b5f805463ffffffff19908116600117909155600780546bffffffffffffffff0000000019164260201b6bffffffffffffffff000000001617905590611ae590612477565b81604051611af281610432565b52600d541617600d55565b5f19810191908211611b0b57565b6119f8565b91908203918211611b0b57565b9060018201809211611b0b57565b9060048201809211611b0b57565b9060088201809211611b0b57565b91908201809211611b0b57565b60095460ff8160201c1615611bcc57600854908115611bdf57611b7f6117e460135463ffffffff1690565b9063ffffffff80941682811115611bd457611ba6611ba1611bad948793611b10565b611afd565b9116611b47565b90811015611bcc57611bbe816114c5565b505415611bcc571690600190565b50505f905f90565b50505050505f905f90565b5050505f905f90565b90611bf290611b54565b15611c60576008548110156114e75760085f525f80516020614a5f83398151915201908154915f5b838110611c2957505050505f90565b611c3381836114ec565b905460039190911b1c6001600160a01b0390811690841614611c5757600101611c1a565b50505050600190565b50505f90565b6040513d5f823e3d90fd5b90600161ffff80931601918211611b0b57565b91909161ffff80809416911601918211611b0b57565b90611cb38263ffffffff165f52601560205260405f2090565b90611cc3825460ff9060181c1690565b611e5b57600254611ce4906001600160a01b03165b6001600160a01b031690565b90813b1561033657604051905f82636744c79f60e01b94858252818381611d1360048201906001602083019252565b03925af1908115611e4357611d39928692611e48575b50845460081c61ffff16906130c4565b60025490929190611d52906001600160a01b0316611cd8565b91823b15610336575f906040519384918252818381611d7860048201905f602083019252565b03925af1908115611e4357611db792611d9f92611e2a575b50845460081c61ffff16611c84565b835462ffff00191660089190911b62ffff0016178355565b15611de157805463ff00000019166301000000178155611dd961185683612d9a565b611de1575050565b547f1aaaa8b219dd84dddbfc5836221ae6ca620f0ec0711a5fb907846becfacc95009063ffffffff9060081c61ffff1660405161ffff909116815292169180602081015b0390a2565b80611e37611e3d92610483565b8061085e565b5f611d90565b611c66565b80611e37611e5592610483565b5f611d29565b50611dd961185683612d9a565b906020610570928181520190611149565b90816020910312610336575190565b63ffffffff809116908114611b0b5760010190565b9190611ea88161338b565b60209182808301510151611ebd815160ff1690565b90600260ff8316148015906121db575b6121aa5750506060820194611ee6865163ffffffff1690565b9063ffffffff8082169083160361218457505060a082015183815180155f146120c357505050600154806120a357505b611f1f826135ca565b9281519082012091611f4f61197b611f3b885163ffffffff1690565b63ffffffff165f52600560205260405f2090565b548015611fa2578303611f67576103459495506137bd565b610813611f78875163ffffffff1690565b60405163082085ab60e21b815263ffffffff90911660048201523360248201529081906044820190565b5061206f6117e4610345969785611fd933611fc4611f3b855163ffffffff1690565b9060018060a01b03165f5260205260405f2090565b5561202c612002611fee835163ffffffff1690565b63ffffffff165f52600660205260405f2090565b612018612013825463ffffffff1690565b611e88565b63ffffffff1663ffffffff19825416179055565b61206561200287612058612044855163ffffffff1690565b63ffffffff165f52600e60205260405f2090565b905f5260205260405f2090565b5163ffffffff1690565b6040518481523391907fcafc00e7c35f462868d78e8228850e885bd0870978967c446c6bb67122a3fb2890602090a36137bd565b604051631d734b9160e01b815260048101919091525f6024820152604490fd5b03612148576040516347f5343160e01b8152908482806120e68460048301611e68565b038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4918215611e43575f92612165575b5081156121485750600154808203612126575050611f16565b604051631d734b9160e01b815260048101919091526024810191909152604490fd5b604051630ca3ddf160e01b81529081906108139060048301611e68565b61217d919250853d8711610da857610d9981836104cc565b905f61210d565b604051635070175360e01b815263ffffffff918216600482015291166024820152604490fd5b8401516040516385a89b3360e01b815246600482015260ff92909216602483015263ffffffff166044820152606490fd5b508085015163ffffffff1663ffffffff16461415611ecd565b600411610336575f90600490565b6001600160e01b0319903581811693926004811061221f57505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f80516020614a1f833981519152805461229d906001600160a01b031661229461228e866121f4565b90612202565b90843091613adc565b90156122aa575b50505050565b63ffffffff161561235b575f80516020614a1f833981519152805460ff60a01b1916600160a01b1790556122eb90611cd8908190546001600160a01b031690565b91823b1561033657612316925f9283604051809681958294634a63ebf760e11b84526004840161222e565b03925af18015611e4357612348575b505f80516020614a1f833981519152805460ff60a01b191690555f8080806122a4565b80611e3761235592610483565b5f612325565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b80518210156114e75760209160051b010190565b90612399826104ed565b6123a660405191826104cc565b82815280926123b7601f19916104ed565b0190602036910137565b90602082519201516bffffffffffffffffffffffff199081811693601481106123e957505050565b60140360031b82901b16169150565b5f198114611b0b5760010190565b600854600160401b81101561042d5760018101806008558110156114e75760085f525f80516020614a5f83398151915201905f90565b8054600160401b81101561042d57612459916001820181556114ec565b60018060a09493941b038084549260031b9316831b921b1916179055565b9060409182810192835151908115612920576060830163ffffffff93846124a2835163ffffffff1690565b16156128db57836124ba6117e4835163ffffffff1690565b1015612896575f935f5b81811061283a57506124d58561238f565b5f805b8382106126265750505f905f5b8381106125bb57505050507ff485fcf1271cbcd2319ebf860c827016cef79959673d605215dcd74ed0629ea4959661255e6125486125b69594612065612532612595965163ffffffff1690565b63ffffffff1663ffffffff196009541617600955565b63ffffffff1663ffffffff196007541617600755565b61257864010000000064ff00000000196009541617600955565b61258a63ffffffff1960135416601355565b515163ffffffff1690565b905163ffffffff918216815293909216909116602083015281906040820190565b0390a1565b6125c6818c5161237b565b5151516125d1612406565b50506125dc826114c5565b505f5b8281106125f1575050506001016124e5565b60019061262061261a61260d6126068a6123f8565b998961237b565b516001600160a01b031690565b8461243c565b016125df565b97909891966126398a8c95969c5161237b565b515195899b5f5b885181101561282357600261266c61266661266661265e858e61237b565b515160ff1690565b60ff1690565b036127de576020601481612680848d61237b565b5101515103612799576126a26126a89161269a848d61237b565b5101516123c1565b60601c90565b6001600160a01b0381168015612754578f8d8f5b82106126ef575050508c6126cf906123f8565b9c6126da908d61237b565b6001600160a01b039091169052600101612640565b612700611cd861260d84869461237b565b1461270f576001018d8f6126bc565b8c5163098d772360e21b815260206004820152601a60248201527f6475706c6963617465206f70657261746f7220616464726573730000000000006044820152606490fd5b8b5163098d772360e21b815260206004820152601860248201527f6f70657261746f722061646472657373206973207a65726f00000000000000006044820152606490fd5b8a5163098d772360e21b815260206004820181905260248201527f6f70657261746f722061646472657373206973206e6f742032302062797465736044820152606490fd5b895163098d772360e21b815260206004820152601e60248201527f6f70657261746f72206973206e6f7420616e2045564d206164647265737300006044820152606490fd5b5095949b50955096919860019098919801906124d8565b94612846868a5161237b565b5151519081156128635760019161285c91611b47565b95016124c4565b855163098d772360e21b815260206004820152600b60248201526a0656d7074792067726f75760ac1b6044820152606490fd5b825163098d772360e21b815260206004820152601d60248201527f61637469766547726f7570496e646578206f7574206f662072616e67650000006044820152606490fd5b825163098d772360e21b815260206004820152601860248201527f65706f63684475726174696f6e536563206973207a65726f00000000000000006044820152606490fd5b5163098d772360e21b81526020600482015260096024820152686e6f2067726f75707360b81b6044820152606490fd5b61295990611b54565b15612985576008548110156114e75760085f525f80516020614a5f833981519152015463ffffffff1690565b505f90565b5f80516020614a1f83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b6001600160401b0390600182600f54160190828211611b0b5773__$a62ffa76e12530392778037a67b422bd63$__90813b15610336575f91612a4291604051809681948293637401af5d60e01b845260e0600485015260e4840190611149565b90600a6024840152600b60448401526010606484015260116084840152601260a4840152871660c483015203915af4918215611e435761034592612a9e575b506001600160401b03166001600160401b0319600f541617600f55565b80611e37612aab92610483565b5f612a81565b5190811515820361033657565b908160a0910312610336576040519060a082018281106001600160401b0382111761042d5760405280519060068210156103365760809183526020810151612b0581610328565b60208401526040810151612b1881610328565b6040840152612b2960608201612ab1565b60608401520151612b3981610328565b608082015290565b91612b5960809295949560a0855260a0850190611149565b9460086020850152600a604085015263ffffffff809216606085015216910152565b60061115612b8557565b634e487b7160e01b5f52602160045260245ffd5b63ffffffff918216815291166020820152606081019291906006821015612b855760400152565b9060a0612bd260075463ffffffff1690565b60135463ffffffff1693612bfa6040519586938493632b6e75a560e11b855260048501612b41565b038173__$a62ffa76e12530392778037a67b422bd63$__5af4918215611e43575f92612d0a575b508151612c2d81612b7b565b612c3681612b7b565b612cbc5750612ca6608082612c58612532602061034596015163ffffffff1690565b612c6c612548604083015163ffffffff1690565b612c9b612c7c6060830151151590565b64ff0000000060095491151560201b169064ff00000000191617600955565b015163ffffffff1690565b63ffffffff1663ffffffff196013541617601355565b907f06bc5809fcff2cb3c29e300b687d3444b42c7836482312d880b48c4bcfbec922916125b6612cf3608084015163ffffffff1690565b9251612cfe81612b7b565b60405193849384612b99565b612d2d91925060a03d60a011612d34575b612d2581836104cc565b810190612abe565b905f612c21565b503d612d1b565b63ffffffff9060018183161115611c60575f19011690565b63ffffffff91908282168382161115612d6b57031690565b5050505f90565b6105709063ffffffff600d541690612d53565b90600163ffffffff80931601918211611b0b57565b612da381613c13565b156129855760207fe784b22f2061de501b77364cab02ee109528afe4ddc8d423d076f6b49021b58e9163ffffffff612e0181831692835f526015855260405f2064010000000064ff0000000019825416179055600454600155612d85565b1663ffffffff195f5416175f55604051908152a1600190565b6020908181840312610336578051906001600160401b038211610336570182601f8201121561033657805191612e4f836104ed565b936040612e5f60405196876104cc565b848652828601918360c080970286010194818611610336578401925b858410612e8c575050505050505090565b8684830312610336578487918451612ea381610496565b8651612eae81610a74565b815282870151838201528587015186820152606080880151908201526080808801519082015260a08088015190820152815201930192612e7b565b60408051637d3b06bb60e11b81525f94929360049190868280612f0e89878301611e68565b038173__$9d71d8de471125782e5046de17c7aa75b6$__5af4918215611e43575f926130a0575b505f965b825188101561309457620f42405a106130505784612f615a612f5b5a60031c90565b90611b10565b612fe184828b8d6001600160401b03612fd8612fbb8c612f97612f90612f87878461237b565b515161ffff1690565b61ffff1690565b9a612fb1866020612fa8828661237b565b5101519361237b565b5101519086613ccb565b938c60a0612fcf866080612fa8828661237b565b51015191613ccb565b91169187613dbe565b9290612ffd57505050612ff5600191611c71565b970196612f39565b959799509593509650905061ffff8116613045575051632845f55560e21b81526001600160a01b0390941692840192835261ffff166020830152604082015281906060010390fd5b945050505050905f90565b9396505050925061ffff811661308d5750506108135a9251630e76ff3560e41b8152918201928352620f4240602084015290918291604090910190565b92505f9150565b96506001955050505050565b6130bd9192503d805f833e6130b581836104cc565b810190612e1a565b905f612f35565b92905f926040805190637d3b06bb60e11b82526004925f83806130ea8b60048301611e68565b038173__$9d71d8de471125782e5046de17c7aa75b6$__5af4928315611e43575f93613231575b5061ffff809516965b835188101561322457620f42405a106131e95761313b5a612f5b5a60031c90565b61314b612f90612f878b8861237b565b9061318784828d8d6001600160401b03612fd86131738d8d612fb1866020612fa8828661237b565b938d60a0612fcf866080612fa8828661237b565b92906131a35750505061319b600191611c71565b97019661311a565b96989a5096925097508092935016613045575051632845f55560e21b81526001600160a01b0390941692840192835261ffff166020830152604082015281906060010390fd5b9493965050955050811661308d5750506108135a9251630e76ff3560e41b8152918201928352620f4240602084015290918291604090910190565b9750600196505050505050565b6132469193503d805f833e6130b581836104cc565b915f613111565b6040519061325a8261044d565b5f6020838281520152565b604051906132728261044d565b8161327b61324d565b8152602061062a61324d565b60405190613294826104b1565b606060c0838281526132a4613265565b60208201525f60408201525f838201525f60808201528260a08201520152565b604051906132d1826104b1565b606060c0836132de613265565b81528260208201528260408201525f838201528260808201525f60a08201520152565b6040519061330e8261044d565b60606020835f81520152565b604051906133278261044d565b816133306132c4565b8152602061062a613301565b90613346826104ed565b61335360405191826104cc565b8281528092613364601f19916104ed565b01905f5b82811061337457505050565b60209061337f61331a565b82828501015201613368565b90613394613287565b915f8151905f805b83811061356457506133ad9061333c565b9160c086019283525f915b8181106133c6575050505050565b6133d09085613f8e565b90816001600160401b0391828116600a81145f1461340157505050506133f69085614147565b919087525b906133b8565b60128103613449575050506134196134429287613f8e565b91169061343861343361342c8484611b47565b838a613fd2565b614176565b60208a0152611b47565b905b6133fb565b602881036134765750505050906134636134449286613f8e565b6001600160401b03909116604089015291565b603081036134a657505050509061344461349563ffffffff9387613f8e565b9316606089019063ffffffff169052565b603881036134d65750505050906134446134c563ffffffff9387613f8e565b9316608089019063ffffffff169052565b60a281036134f857505050506134ec9085614147565b919060a08801526133fb565b6101420361355457505061350f6134429287613f8e565b91169061354e61353161352c6135258585611b47565b848b613fd2565b61407e565b95875161353d826123f8565b97613548838361237b565b5261237b565b50611b47565b6134429350600716915086613ed4565b6135719085949394613f8e565b6101426001600160401b03839493161461359d575b60076135959293169086613ed4565b92919261339c565b60076135ab613595936123f8565b925050613586565b604090610570939281528160208201520190611149565b90601454918215159060c080910191825151915f9073__$6c94be45b3cad4e4a0bccaa807f5821f2b$__925b84831061360557505050505050565b61361683879993949596975161237b565b5151602092838201519060408093015193858351036137a2578451868115159182613797575b505061377c5783516347f5343160e01b9384825287828c818061366360049c8d8301611e68565b03915af4948515611e435788925f96613755575b509061368f92918751938492839283528a8301611e68565b03818d5af4968715611e43575f97613736575b50508061372c575b61370357506136ba90851c611b1d565b83851c918183036136d9575050505095600180930191909493926135f6565b5163eb9ba7ad60e01b81529283018681526020810191909152604081019190915281906060010390fd5b9151630b296d8760e31b8152928301868152602081019290925260408201528190036060019150fd5b50808214156136aa565b61374d929750803d10610da857610d9981836104cc565b945f806136a2565b83929196506137739061368f943d8511610da857610d9981836104cc565b95909192613677565b8351632d9a492360e21b815280610813878b600484016135b3565b14159050865f61363c565b8351635812b67560e01b815280610813858b600484016135b3565b60609093919301916137db6137d6845163ffffffff1690565b612950565b63ffffffff808216908115613ad357613814637fffffff61380a6113bc876120586120448c5163ffffffff1690565b9460011c16612d85565b90600754926138336001600160401b0394858482169160201c16611b47565b421015918086169182109383613ac6575b505050811591613abe575b5015613ab6576138e9936138816138869261386986600455565b6007805460ff60601b1916600160601b1790556107be565b601455565b7fa77ed82a4a8c2f7b412630e92b1c8f59e6645b05820a8b0ed43ae5fac990491c6138d96138bb6117e4875163ffffffff1690565b6040805186815263ffffffff90951660208601529093918291820190565b0390a2825163ffffffff16614222565b6138ff6138fa825163ffffffff1690565b614288565b613924613910825163ffffffff1690565b63ffffffff165f52601560205260405f2090565b805460ff1916600117815591600254613945906001600160a01b0316611cd8565b90813b1561033657604051905f82636744c79f60e01b9485825281838161397460048201906001602083019252565b03925af1918215611e435761399792613aa3575b50835163ffffffff1690612ee9565b60025491949092916139b1906001600160a01b0316611cd8565b91823b15610336575f9060405193849182528183816139d760048201905f602083019252565b03925af1908115611e4357613a16928492613a90575b50805463ffffff00191662ffff00600888901b161791151560181b63ff00000016919091179055565b158015613a71575b613a26575050565b611e25613a5b6117e47f1aaaa8b219dd84dddbfc5836221ae6ca620f0ec0711a5fb907846becfacc9500935163ffffffff1690565b60405161ffff9094168452929081906020820190565b50613a8b611856613a86835163ffffffff1690565b612d9a565b613a1e565b80611e37613a9d92610483565b5f6139ed565b80611e37613ab092610483565b5f613988565b505050505050565b90505f61384f565b16111590505f8080613844565b50505050505050565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b0384111761042d576040935f9385528380528360205251915afa613b4b57565b9150505f51906020518060201c150290565b60ff5f80516020614a7f8339815191525460401c1615613b7957565b604051631afcd79f60e31b8152600490fd5b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215613bc9575b5050610e3857565b5f80516020614a3f8339815191525416141590505f80613bc1565b3d15613c0e573d90613bf582610504565b91613c0360405193846104cc565b82523d5f602084013e565b606090565b623d09005a1061298557600254613c32906001600160a01b0316611cd8565b803b156103365760405163a3ad9cc360e01b815263ffffffff9290921660048301525f908290602490829084905af19081613c90575b50613c8b57613c75613be4565b8051918215613c845750602001fd5b9150505f90565b600190565b80611e37613c9d92610483565b5f613c68565b90613cad82610504565b613cba60405191826104cc565b82815280926123b7601f1991610504565b91908181118015613d14575b613cfc57808203918211611b0b57602090613cf183613ca3565b930101602083015e90565b6024906040519063d98776dd60e01b82526004820152fd5b5082518211613cd7565b91613d4563ffffffff9161ffff604094979697168552606060208601526060850190611149565b9416910152565b91908260409103126103365761057060208351613d6881610919565b9301612ab1565b9160a093613da5613db39261ffff6001600160401b03959a99989a6003885216602087015260c0604087015260c0860190611149565b908482036060860152611149565b951660808201520152565b9194959390929561ffff831661ee0f8114908115613ec8575b50613e5b5750613e009060409495968551978895869563173d424160e21b875260048701613d6f565b038173__$a62ffa76e12530392778037a67b422bd63$__5af48015611e43575f925f91613e2c57509190565b9050613e5191925060403d604011613e54575b613e4981836104cc565b810190613d4c565b91565b503d613e3f565b5f955085949650613e97859294613e89604051938492602084019763b1635f9f60e01b895260248501613d1e565b03601f1981018352826104cc565b51923090f1613ea4613be4565b90613ec1578051918215613eb85750602001fd5b60019250309150565b505f903090565b61ee139150145f613dd7565b6001600160401b039283169283613ef55750613ef09250613f8e565b905090565b919260018103613f0b5750505061057090611b39565b60028103613f29575061057092613f2191613f8e565b911690611b47565b600514159150613f3e90505761057090611b2b565b60405162461bcd60e51b815260206004820152602260248201527f50726f746f62756652756e74696d653a20756e6b6e6f776e2077697265207479604482015261706560f01b6064820152608490fd5b91908201602001905f805b60806007600186515f1a94607f8616851b1796019201921615613fc557603f8211610336579290613f99565b9293909203601f19019150565b918181101580614073575b156140225781613fec91611b10565b90613ff682613ca3565b9282614003575b50505090565b015f5b82811015613ffd57602081830181015185830182015201614006565b60405162461bcd60e51b8152602060048201526024808201527f50726f746f62756652756e74696d653a20736c696365206f7574206f6620626f604482015263756e647360e01b6064820152608490fd5b508251811115613fdd565b9061408761331a565b915f8151905b81811061409957505050565b6140a39083613f8e565b90816001600160401b0391828116600a81145f146140f9575050506140cb6140f19285613f8e565b9116906140ea6140e56140de8484611b47565b8388613fd2565b614510565b8752611b47565b905b9061408d565b60120361413757505061410f6141319285613f8e565b9116906141276141226140de8484611b47565b6143f3565b6020880152611b47565b906140f3565b6141319350600716915084613ed4565b919061415b6001600160401b039184613f8e565b9116810192838211611b0b57614172918491613fd2565b9190565b9061417f613265565b915f8151905b81811061419157505050565b61419b9083613f8e565b90816001600160401b0391828116600a81145f146141e3575050506141c36141db9285613f8e565b9116906140ea6141d66140de8484611b47565b614673565b905b90614185565b6012036142125750506141f961420c9285613f8e565b9116906141276141d66140de8484611b47565b906141dd565b61420c9350600716915084613ed4565b63ffffffff8082165f52600c60205260405f209081541661428357614259600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b61429f9063ffffffff91829182600d541690612d53565b169081151590816142c8575b506142b35750565b5f52600c6020525f6001604082208281550155565b9050815f52600c60205260405f20541615155f6142ab565b90813b15614364575f80516020614a3f83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115614349576143469161470c565b50565b50503461435257565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b6040519061439282610468565b60606040835f81525f60208201520152565b906143ae826104ed565b6143bb60405191826104cc565b82815280926143cc601f19916104ed565b01905f5b8281106143dc57505050565b6020906143e7614385565b828285010152016143d0565b906143fc613301565b915f8151905f805b8381106144c25750614415906143a4565b91602086019283525f915b81811061442e575050505050565b6144389085613f8e565b90816001600160401b0391828116600881145f1461447b57505050509061447561446763ffffffff9387613f8e565b9316889063ffffffff169052565b90614420565b6012036144b25750506144916144ac9287613f8e565b91169061354e6135316144a76135258585611b47565b614729565b90614475565b6144ac9350600716915086613ed4565b6144cf9085949394613f8e565b60126001600160401b0383949316146144fa575b60076144f29293169086613ed4565b929192614404565b60076145086144f2936123f8565b9250506144e3565b906145196132c4565b915f8151905b81811061452b57505050565b6145359083613f8e565b90816001600160401b0391828116600a81145f146145785750505061455d6145709285613f8e565b9116906140ea6134336140de8484611b47565b905b9061451f565b919250906012810361459d575050506145919083614147565b91906020860152614572565b601a81036145be575050506145b29083614147565b91906040860152614572565b602881036145f257505050906145ed6145dc63ffffffff9385613f8e565b9316606087019063ffffffff169052565b614572565b60328103614613575050506146079083614147565b91906080860152614572565b6038810361463f575050509061462c6145ed9284613f8e565b6001600160401b0390911660a087015291565b60420361465e5750506146529083614147565b919060c0860152614572565b61466d92506007169084613ed4565b90614572565b9061467c61324d565b915f8151905b81811061468e57505050565b6146989083613f8e565b80916001600160401b038116600881145f146146d2575050509060ff6146c16146c89385613f8e565b93906147f5565b1685525b90614682565b6010036146f7575050906146eb63ffffffff9284613f8e565b921660208601526146cc565b61470692506007169084613ed4565b906146cc565b5f8061057093602081519101845af4614723613be4565b91614846565b90614732614385565b915f8151905b81811061474457505050565b61474e9083613f8e565b80916001600160401b038116600881145f1461478d575050509061478761477861477f9385613f8e565b93906148a9565b61ffff168652565b90614738565b601081036147c157505050906147bc6147ab63ffffffff9385613f8e565b9316602087019063ffffffff169052565b614787565b601a036147e05750506147d49083614147565b91906040860152614787565b6147ef92506007169084613ed4565b90614787565b6001600160401b031680156129855760018114614840576002811461483a57600381146148345760249060405190632cd3748760e21b82526004820152fd5b50600390565b50600290565b50600190565b9061486d575080511561485b57602081519101fd5b60405163d6bda27560e01b8152600490fd5b815115806148a0575b61487e575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15614876565b6001600160401b0381168015611c60576107d1808214614a185750610bb9808214614a185750610bba808214614a185750610bbc808214614a185750610bbe808214614a18575061aa00808214614a18575061ee00808214614a18575061ee02808214614a18575061ee04808214614a18575061ee06808214614a18575061ee10808214614a18575061ee11808214614a18575061ee13808214614a18575061ee0f808214614a18575061ee15808214614a18575061ee16808214614a18575061ee17808214614a18575061ee18808214614a18575061ee19808214614a18575061ee1b808214614a18575061ee1c808214614a18575061ee1e808214614a18575061ee1f808214614a18575061ee20808214614a18575061ee21808214614a18575061ee22808214614a18575061ee23808214614a18575061ee24808214614a18575061ee25809114613ef057604051632cd3748760e21b81526001600160401b0383166004820152602490fd5b9150509056fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a264697066735822122062e7b41158989b69b850a966fb57e71e463e33b0d516806bd4e56f9e996f4f9964736f6c63430008190033", + "deployedBytecode": "0x60806040526004361015610011575f80fd5b5f3560e01c80624e356a1461032357806308a9a6de1461031e5780630fa92c5314610319578063151b8f4e146103145780631bdf0a721461030f5780631d7a88271461030a57806320857e651461030557806322fe388b14610300578063239e1d67146102fb5780632945698b146102f65780632f4d9fa9146102f15780633367cca5146102ec57806336051135146102e75780633915ba63146102e25780634ab47f43146102dd5780634cc04c94146102d85780634f1ef286146102d35780635126a52d146102ce57806352d1902d146102c9578063533ae1cc146102c45780635701a93f146102bf5780635c0c8f44146102ba5780635d4e25ce146102b55780635ff8195e146102b05780637a9e5e4b146102ab5780637b778122146102a657806380e643b6146102a15780638b75e9291461029c5780638bdebf21146102975780638d9f616b146102925780638fb360371461028d57806393ec06be1461028857806399b1a19c14610283578063ad3cb1cc1461027e578063af243e8914610279578063b1635f9f14610274578063b41565b81461026f578063bf7e214f1461026a578063c0d0373e14610265578063c268863f14610260578063c45fdcb71461025b578063cd2ad3fb14610256578063d0841f2214610251578063d1ad8d011461024c578063d3f0730614610247578063d5b5272a14610242578063e3dff2af1461023d578063e55ba576146102385763fdfd07df14610233575f80fd5b611789565b61174a565b61172d565b61167f565b61164b565b6115f1565b6115c9565b611564565b611501565b611489565b611373565b61133f565b611293565b6111e5565b6111b2565b61116d565b611124565b611108565b6110b8565b611079565b611053565b611018565b610ff4565b610f97565b610f19565b610ef6565b610ed9565b610e90565b610e6d565b610e4a565b610de1565b610daf565b610ca6565b610c8b565b610c5e565b610c2c565b610be2565b610ba8565b610b8b565b610b5e565b610b3c565b610b21565b610a80565b61092a565b61088b565b610868565b610823565b610737565b610374565b63ffffffff81160361033657565b5f80fd5b359061034582610328565b565b9181601f84011215610336578235916001600160401b038311610336576020838186019501011161033657565b346103365760403660031901126103365760043561039181610328565b6024356001600160401b038111610336576103b0903690600401610347565b90917f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0091825c6103ec575f936103e9926001855d6117cf565b5d005b604051633ee5aeb560e01b8152600490fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b0382111761042d57604052565b6103fe565b602081019081106001600160401b0382111761042d57604052565b604081019081106001600160401b0382111761042d57604052565b606081019081106001600160401b0382111761042d57604052565b6001600160401b03811161042d57604052565b60c081019081106001600160401b0382111761042d57604052565b60e081019081106001600160401b0382111761042d57604052565b90601f801991011681019081106001600160401b0382111761042d57604052565b6001600160401b03811161042d5760051b60200190565b6001600160401b03811161042d57601f01601f191660200190565b92919261052b82610504565b9161053960405193846104cc565b829481845281830111610336578281602093845f960137010152565b9080601f83011215610336578160206105709335910161051f565b90565b91906080838203126103365760409283519161058e83610412565b8294823561059b81610328565b84526020808401356105ac81610328565b858201526001600160401b0392848301358481116103365785019080601f83011215610336578135906105de826104ed565b956105eb865197886104cc565b828752848701918560059460051b8601019481861161033657868101935b86851061062f57505050505050505060609261062a9284928601520161033a565b910152565b843584811161033657820188601f1982860301126103365789519061065382610432565b8981013590868211610336570184603f820112156103365789810135610678816104ed565b916106858d5193846104cc565b8183528c8c8401928b1b82010190878211610336578c94929593918e8201915b8183106106bf575050508394508152815201940193610609565b91809496509690929496358a8111610336578f90830190603f19828c030112610336578f90918f928251906106f38261044d565b838101359060ff82168203610336578e9183526060810135918211610336578594610721928f920101610555565b83820152815201930190918d95939694926106a5565b34610336576020366003190112610336576001600160401b0360043581811161033657610768903690600401610573565b906107733633612265565b63ffffffff805f5416805f52600660205261079660405f2063ffffffff90541690565b916001821490811591610817575b506107ed576107eb836107be866107b9611a0c565b612477565b42166bffffffffffffffff000000006007549160201b16906bffffffffffffffff00000000191617600755565b005b6040516325ea24eb60e11b815263ffffffff918216600482015291166024820152604490fd5b0390fd5b9050821615155f6107a4565b3461033657602036600319011261033657602060043561084281610328565b63ffffffff8091165f526006825260405f205416604051908152f35b5f91031261033657565b34610336575f36600319011261033657602063ffffffff60075416604051908152f35b34610336575f366003190112610336576001600160401b0363ffffffff610915815f5416805f5260066020528260405f205416906108c881612950565b6007549260405196879685169460201c169286936001600160401b039060809497969260a087019863ffffffff8096818094168a5216602089015216604087015216606085015216910152565b0390f35b6001600160a01b0381160361033657565b346103365760403660031901126103365760043561094781610919565b6001600160401b039060243582811161033657610968903690600401610573565b5f80516020614a7f833981519152549260ff8460401c1615931680159081610a6c575b6001149081610a62575b159081610a59575b50610a47575f80516020614a7f833981519152805467ffffffffffffffff191660011790556109d09183610a2357611a75565b6109d657005b5f80516020614a7f833981519152805460ff60401b19169055604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29080602081015b0390a1005b5f80516020614a7f833981519152805460ff60401b1916600160401b179055611a75565b60405163f92ee8a960e01b8152600490fd5b9050155f61099d565b303b159150610995565b84915061098b565b61ffff81160361033657565b3461033657604036600319011261033657600435610a9d81610a74565b61ffff60243591610aad83610919565b610ab73633612265565b16805f526003602052807f46d568db0216a0215fe7f56360f9fa1a002c4bf0773add9cd62cd06eff160189604060018060a01b039485825f2054169582519116958682526020820152a25f90815260036020526040902080546001600160a01b0319169091179055005b34610336575f36600319011261033657602060405160078152f35b34610336575f36600319011261033657602063ffffffff5f5416604051908152f35b34610336576020366003190112610336576004355f526012602052602060ff60405f205416604051908152f35b34610336575f366003190112610336576020601454604051908152f35b34610336576020366003190112610336576020610bd8600435610bca81610919565b63ffffffff5f541690611be8565b6040519015158152f35b34610336576020366003190112610336576001600160401b0363ffffffff610915815f5416805f52600e60205260405f206004355f526020528260405f205416906108c881612950565b34610336576020366003190112610336576004355f52600a602052602060018060a01b0360405f205416604051908152f35b34610336576020366003190112610336576004355f526011602052602060ff60405f205416604051908152f35b34610336575f36600319011261033657602060405160018152f35b604036600319011261033657600435610cbe81610919565b6024356001600160401b03811161033657610cdd903690600401610555565b90610ce6613b8b565b610cf03633612265565b610cf8613b8b565b6040516352d1902d60e01b8152916020836004816001600160a01b0386165afa5f9381610d7e575b50610d4657604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b905f80516020614a3f8339815191528303610d65576107eb92506142e0565b604051632a87526960e21b815260048101849052602490fd5b610da191945060203d602011610da8575b610d9981836104cc565b810190611e79565b925f610d20565b503d610d8f565b34610336576020366003190112610336576004355f52600b602052602060018060a01b0360405f205416604051908152f35b34610336575f366003190112610336577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163003610e385760206040515f80516020614a3f8339815191528152f35b60405163703e46dd60e11b8152600490fd5b34610336575f36600319011261033657602063ffffffff60135416604051908152f35b34610336575f36600319011261033657602063ffffffff600d5416604051908152f35b3461033657602036600319011261033657600435610ead81610919565b610eb73633612265565b600280546001600160a01b0319166001600160a01b0392909216919091179055005b34610336575f366003190112610336576020600154604051908152f35b34610336575f36600319011261033657602063ffffffff60095416604051908152f35b3461033657602036600319011261033657600435610f3681610919565b5f80516020614a1f833981519152546001600160a01b03919082163303610f8057803b15610f67576107eb9061298a565b6040516361798f2f60e11b815291166004820152602490fd5b60405162d1953b60e31b8152336004820152602490fd5b34610336576040366003190112610336576020610feb600435610fb981610328565b63ffffffff60243591610fcb83610919565b165f526005835260405f209060018060a01b03165f5260205260405f2090565b54604051908152f35b34610336575f36600319011261033657602060095460ff60405191831c1615158152f35b3461033657602036600319011261033657604061103f60043561103a81610328565b611b54565b63ffffffff83519216825215156020820152f35b34610336575f3660031901126103365760206001600160401b03600f5416604051908152f35b346103365760203660031901126103365761ffff60043561109981610a74565b165f526003602052602060018060a01b0360405f205416604051908152f35b34610336575f366003190112610336575f80516020614a1f8339815191525460a01c60ff1615611100576020638fb3603760e01b5b6040516001600160e01b03199091168152f35b60205f6110ed565b34610336575f3660031901126103365760206040516180008152f35b34610336575f36600319011261033657602060ff60075460601c166040519015158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b34610336575f3660031901126103365761091560405161118c8161044d565b60058152640352e302e360dc1b6020820152604051918291602083526020830190611149565b34610336576020366003190112610336576004355f52601060205260206001600160401b0360405f205416604051908152f35b346103365760603660031901126103365760043561120281610a74565b6024356001600160401b03811161033657611221903690600401610347565b6044929192359161123183610328565b30330361127b5761ffff1661ee0f810361125a57506107eb9261125591369161051f565b612bc0565b61ee139192501461126757005b6107eb9161127691369161051f565b6129e2565b604051630c86f24360e41b8152336004820152602490fd5b34610336576020366003190112610336576109156004356112b381610328565b60405f8180516112c281610468565b828152826020820152015263ffffffff8092165f52600c6020526001815f206001600160401b038351946112f586610468565b8254908116865260201c1660208501520154818301525191829182919091604080606083019463ffffffff81511684526001600160401b0360208201511660208501520151910152565b34610336575f366003190112610336575f80516020614a1f833981519152546040516001600160a01b039091168152602090f35b346103365760203660031901126103365760043561139081610328565b61139a3633612265565b63ffffffff90816113c66113bc8363ffffffff165f52600c60205260405f2090565b5463ffffffff1690565b1615611469576113ea6113e56113e05f5463ffffffff1690565b612d3b565b612d72565b8282169281168311611443575061141261141d9163ffffffff165f52600c60205260405f2090565b60015f918281550155565b7f7763580cbd840265225211dbf07de735c95fbca2c310158df03a6ffcae688b4c5f80a2005b604051631cc9a6c760e21b815263ffffffff928316600482015291166024820152604490fd5b604051630529520560e31b815263ffffffff919091166004820152602490fd5b34610336575f366003190112610336576002546040516001600160a01b039091168152602090f35b634e487b7160e01b5f52603260045260245ffd5b6008548110156114e75760085f525f80516020614a5f83398151915201905f90565b6114b1565b80548210156114e7575f5260205f2001905f90565b3461033657604036600319011261033657600435602435906008548110156103365760085f525f80516020614a5f8339815191520180548210156103365760209161154b916114ec565b905460405160039290921b1c6001600160a01b03168152f35b346103365760203660031901126103365763ffffffff60043561158681610328565b165f526015602052608060405f205460ff604051918181161515835261ffff8160081c166020840152818160181c161515604084015260201c1615156060820152f35b34610336575f3660031901126103365760206007546001600160401b0360405191831c168152f35b3461033657602036600319011261033657606060043561161081610328565b63ffffffff8091165f52600c60205260405f206001600160401b036001825492015491604051938116845260201c1660208301526040820152f35b34610336576040366003190112610336576020610bd860043561166d81610919565b6024359061167a82610328565b611be8565b346103365760203660031901126103365760043561169c81610328565b6116a63633612265565b6040516116b281610432565b63ffffffff908183168082521561171b57600d80549151831663ffffffff1983161790556040805163ffffffff93909216831682529290911660208201527f0eddf948a07b1e3a53a3d441bc72367af384f1c2d2730bf6b679331c4e8f75e69181908101610a1e565b604051639ca50c6160e01b8152600490fd5b34610336575f366003190112610336576020600454604051908152f35b346103365760203660031901126103365763ffffffff60043561176c81610328565b6117763633612265565b1663ffffffff1960075416176007555f80f35b346103365760403660031901126103365760206004356117a881610328565b63ffffffff8091165f52600e825260405f206024355f52825260405f205416604051908152f35b909291926117ed6117e45f5463ffffffff1690565b63ffffffff1690565b63ffffffff908184169081106119f0576118158463ffffffff165f52601560205260405f2090565b5460ff811690816119e1575b5061195d575f5463ffffffff169082821603611936575060095460ff8160201c166118c5575b505061185a6118568333611be8565b1590565b6118ad57831580156118a2575b6118815761187b906103459394369161051f565b90611e9d565b604051630c6f042160e31b8152600481018590526180006024820152604490fd5b506180008411611867565b604051632af0051560e21b8152336004820152602490fd5b6118ce84611b54565b905061184757610813846118e760135463ffffffff1690565b906118f760085463ffffffff1690565b604051631bf926d360e11b815263ffffffff92831660048201529282166024840152939094168416604482015292909116606483015281906084820190565b6040516307d3e83d60e51b815263ffffffff91821660048201529084166024820152604490fd5b50509261198d61197b8363ffffffff165f52600560205260405f2090565b335f9081526020919091526040902090565b54936004548095036118ad576119a491369161051f565b9081516020830120938085036119c05750610345929350611c9a565b6040516340d6b4cd60e01b8152600481019190915260248101859052604490fd5b60ff915060201c16155f611821565b505050509050565b634e487b7160e01b5f52601160045260245ffd5b6008545f60085580611a1b5750565b60085f525f80516020614a5f833981519152908101905b818110611a3d575050565b80545f825580611a51575b50600101611a32565b815f5260205f20908101905b818110611a6a5750611a48565b5f8155600101611a5d565b90611a9960c892611a84613b5d565b611a8c613b5d565b611a94613b5d565b61298a565b611aa1613b5d565b5f805463ffffffff19908116600117909155600780546bffffffffffffffff0000000019164260201b6bffffffffffffffff000000001617905590611ae590612477565b81604051611af281610432565b52600d541617600d55565b5f19810191908211611b0b57565b6119f8565b91908203918211611b0b57565b9060018201809211611b0b57565b9060048201809211611b0b57565b9060088201809211611b0b57565b91908201809211611b0b57565b60095460ff8160201c1615611bcc57600854908115611bdf57611b7f6117e460135463ffffffff1690565b9063ffffffff80941682811115611bd457611ba6611ba1611bad948793611b10565b611afd565b9116611b47565b90811015611bcc57611bbe816114c5565b505415611bcc571690600190565b50505f905f90565b50505050505f905f90565b5050505f905f90565b90611bf290611b54565b15611c60576008548110156114e75760085f525f80516020614a5f83398151915201908154915f5b838110611c2957505050505f90565b611c3381836114ec565b905460039190911b1c6001600160a01b0390811690841614611c5757600101611c1a565b50505050600190565b50505f90565b6040513d5f823e3d90fd5b90600161ffff80931601918211611b0b57565b91909161ffff80809416911601918211611b0b57565b90611cb38263ffffffff165f52601560205260405f2090565b90611cc3825460ff9060181c1690565b611e5b57600254611ce4906001600160a01b03165b6001600160a01b031690565b90813b1561033657604051905f82636744c79f60e01b94858252818381611d1360048201906001602083019252565b03925af1908115611e4357611d39928692611e48575b50845460081c61ffff16906130c4565b60025490929190611d52906001600160a01b0316611cd8565b91823b15610336575f906040519384918252818381611d7860048201905f602083019252565b03925af1908115611e4357611db792611d9f92611e2a575b50845460081c61ffff16611c84565b835462ffff00191660089190911b62ffff0016178355565b15611de157805463ff00000019166301000000178155611dd961185683612d9a565b611de1575050565b547f1aaaa8b219dd84dddbfc5836221ae6ca620f0ec0711a5fb907846becfacc95009063ffffffff9060081c61ffff1660405161ffff909116815292169180602081015b0390a2565b80611e37611e3d92610483565b8061085e565b5f611d90565b611c66565b80611e37611e5592610483565b5f611d29565b50611dd961185683612d9a565b906020610570928181520190611149565b90816020910312610336575190565b63ffffffff809116908114611b0b5760010190565b9190611ea88161338b565b60209182808301510151611ebd815160ff1690565b90600260ff8316148015906121db575b6121aa5750506060820194611ee6865163ffffffff1690565b9063ffffffff8082169083160361218457505060a082015183815180155f146120c357505050600154806120a357505b611f1f826135ca565b9281519082012091611f4f61197b611f3b885163ffffffff1690565b63ffffffff165f52600560205260405f2090565b548015611fa2578303611f67576103459495506137bd565b610813611f78875163ffffffff1690565b60405163082085ab60e21b815263ffffffff90911660048201523360248201529081906044820190565b5061206f6117e4610345969785611fd933611fc4611f3b855163ffffffff1690565b9060018060a01b03165f5260205260405f2090565b5561202c612002611fee835163ffffffff1690565b63ffffffff165f52600660205260405f2090565b612018612013825463ffffffff1690565b611e88565b63ffffffff1663ffffffff19825416179055565b61206561200287612058612044855163ffffffff1690565b63ffffffff165f52600e60205260405f2090565b905f5260205260405f2090565b5163ffffffff1690565b6040518481523391907fcafc00e7c35f462868d78e8228850e885bd0870978967c446c6bb67122a3fb2890602090a36137bd565b604051631d734b9160e01b815260048101919091525f6024820152604490fd5b03612148576040516347f5343160e01b8152908482806120e68460048301611e68565b038173__$6c94be45b3cad4e4a0bccaa807f5821f2b$__5af4918215611e43575f92612165575b5081156121485750600154808203612126575050611f16565b604051631d734b9160e01b815260048101919091526024810191909152604490fd5b604051630ca3ddf160e01b81529081906108139060048301611e68565b61217d919250853d8711610da857610d9981836104cc565b905f61210d565b604051635070175360e01b815263ffffffff918216600482015291166024820152604490fd5b8401516040516385a89b3360e01b815246600482015260ff92909216602483015263ffffffff166044820152606490fd5b508085015163ffffffff1663ffffffff16461415611ecd565b600411610336575f90600490565b6001600160e01b0319903581811693926004811061221f57505050565b60040360031b82901b16169150565b6001600160a01b0390911681526040602082018190528101829052606091805f848401375f828201840152601f01601f1916010190565b5f80516020614a1f833981519152805461229d906001600160a01b031661229461228e866121f4565b90612202565b90843091613adc565b90156122aa575b50505050565b63ffffffff161561235b575f80516020614a1f833981519152805460ff60a01b1916600160a01b1790556122eb90611cd8908190546001600160a01b031690565b91823b1561033657612316925f9283604051809681958294634a63ebf760e11b84526004840161222e565b03925af18015611e4357612348575b505f80516020614a1f833981519152805460ff60a01b191690555f8080806122a4565b80611e3761235592610483565b5f612325565b60405162d1953b60e31b81526001600160a01b0383166004820152602490fd5b80518210156114e75760209160051b010190565b90612399826104ed565b6123a660405191826104cc565b82815280926123b7601f19916104ed565b0190602036910137565b90602082519201516bffffffffffffffffffffffff199081811693601481106123e957505050565b60140360031b82901b16169150565b5f198114611b0b5760010190565b600854600160401b81101561042d5760018101806008558110156114e75760085f525f80516020614a5f83398151915201905f90565b8054600160401b81101561042d57612459916001820181556114ec565b60018060a09493941b038084549260031b9316831b921b1916179055565b9060409182810192835151908115612920576060830163ffffffff93846124a2835163ffffffff1690565b16156128db57836124ba6117e4835163ffffffff1690565b1015612896575f935f5b81811061283a57506124d58561238f565b5f805b8382106126265750505f905f5b8381106125bb57505050507ff485fcf1271cbcd2319ebf860c827016cef79959673d605215dcd74ed0629ea4959661255e6125486125b69594612065612532612595965163ffffffff1690565b63ffffffff1663ffffffff196009541617600955565b63ffffffff1663ffffffff196007541617600755565b61257864010000000064ff00000000196009541617600955565b61258a63ffffffff1960135416601355565b515163ffffffff1690565b905163ffffffff918216815293909216909116602083015281906040820190565b0390a1565b6125c6818c5161237b565b5151516125d1612406565b50506125dc826114c5565b505f5b8281106125f1575050506001016124e5565b60019061262061261a61260d6126068a6123f8565b998961237b565b516001600160a01b031690565b8461243c565b016125df565b97909891966126398a8c95969c5161237b565b515195899b5f5b885181101561282357600261266c61266661266661265e858e61237b565b515160ff1690565b60ff1690565b036127de576020601481612680848d61237b565b5101515103612799576126a26126a89161269a848d61237b565b5101516123c1565b60601c90565b6001600160a01b0381168015612754578f8d8f5b82106126ef575050508c6126cf906123f8565b9c6126da908d61237b565b6001600160a01b039091169052600101612640565b612700611cd861260d84869461237b565b1461270f576001018d8f6126bc565b8c5163098d772360e21b815260206004820152601a60248201527f6475706c6963617465206f70657261746f7220616464726573730000000000006044820152606490fd5b8b5163098d772360e21b815260206004820152601860248201527f6f70657261746f722061646472657373206973207a65726f00000000000000006044820152606490fd5b8a5163098d772360e21b815260206004820181905260248201527f6f70657261746f722061646472657373206973206e6f742032302062797465736044820152606490fd5b895163098d772360e21b815260206004820152601e60248201527f6f70657261746f72206973206e6f7420616e2045564d206164647265737300006044820152606490fd5b5095949b50955096919860019098919801906124d8565b94612846868a5161237b565b5151519081156128635760019161285c91611b47565b95016124c4565b855163098d772360e21b815260206004820152600b60248201526a0656d7074792067726f75760ac1b6044820152606490fd5b825163098d772360e21b815260206004820152601d60248201527f61637469766547726f7570496e646578206f7574206f662072616e67650000006044820152606490fd5b825163098d772360e21b815260206004820152601860248201527f65706f63684475726174696f6e536563206973207a65726f00000000000000006044820152606490fd5b5163098d772360e21b81526020600482015260096024820152686e6f2067726f75707360b81b6044820152606490fd5b61295990611b54565b15612985576008548110156114e75760085f525f80516020614a5f833981519152015463ffffffff1690565b505f90565b5f80516020614a1f83398151915280546001600160a01b0319166001600160a01b0390921691821790556040519081527f2f658b440c35314f52658ea8a740e05b284cdc84dc9ae01e891f21b8933e7cad90602090a1565b6001600160401b0390600182600f54160190828211611b0b5773__$a62ffa76e12530392778037a67b422bd63$__90813b15610336575f91612a4291604051809681948293637401af5d60e01b845260e0600485015260e4840190611149565b90600a6024840152600b60448401526010606484015260116084840152601260a4840152871660c483015203915af4918215611e435761034592612a9e575b506001600160401b03166001600160401b0319600f541617600f55565b80611e37612aab92610483565b5f612a81565b5190811515820361033657565b908160a0910312610336576040519060a082018281106001600160401b0382111761042d5760405280519060068210156103365760809183526020810151612b0581610328565b60208401526040810151612b1881610328565b6040840152612b2960608201612ab1565b60608401520151612b3981610328565b608082015290565b91612b5960809295949560a0855260a0850190611149565b9460086020850152600a604085015263ffffffff809216606085015216910152565b60061115612b8557565b634e487b7160e01b5f52602160045260245ffd5b63ffffffff918216815291166020820152606081019291906006821015612b855760400152565b9060a0612bd260075463ffffffff1690565b60135463ffffffff1693612bfa6040519586938493632b6e75a560e11b855260048501612b41565b038173__$a62ffa76e12530392778037a67b422bd63$__5af4918215611e43575f92612d0a575b508151612c2d81612b7b565b612c3681612b7b565b612cbc5750612ca6608082612c58612532602061034596015163ffffffff1690565b612c6c612548604083015163ffffffff1690565b612c9b612c7c6060830151151590565b64ff0000000060095491151560201b169064ff00000000191617600955565b015163ffffffff1690565b63ffffffff1663ffffffff196013541617601355565b907f06bc5809fcff2cb3c29e300b687d3444b42c7836482312d880b48c4bcfbec922916125b6612cf3608084015163ffffffff1690565b9251612cfe81612b7b565b60405193849384612b99565b612d2d91925060a03d60a011612d34575b612d2581836104cc565b810190612abe565b905f612c21565b503d612d1b565b63ffffffff9060018183161115611c60575f19011690565b63ffffffff91908282168382161115612d6b57031690565b5050505f90565b6105709063ffffffff600d541690612d53565b90600163ffffffff80931601918211611b0b57565b612da381613c13565b156129855760207fe784b22f2061de501b77364cab02ee109528afe4ddc8d423d076f6b49021b58e9163ffffffff612e0181831692835f526015855260405f2064010000000064ff0000000019825416179055600454600155612d85565b1663ffffffff195f5416175f55604051908152a1600190565b6020908181840312610336578051906001600160401b038211610336570182601f8201121561033657805191612e4f836104ed565b936040612e5f60405196876104cc565b848652828601918360c080970286010194818611610336578401925b858410612e8c575050505050505090565b8684830312610336578487918451612ea381610496565b8651612eae81610a74565b815282870151838201528587015186820152606080880151908201526080808801519082015260a08088015190820152815201930192612e7b565b60408051637d3b06bb60e11b81525f94929360049190868280612f0e89878301611e68565b038173__$9d71d8de471125782e5046de17c7aa75b6$__5af4918215611e43575f926130a0575b505f965b825188101561309457620f42405a106130505784612f615a612f5b5a60031c90565b90611b10565b612fe184828b8d6001600160401b03612fd8612fbb8c612f97612f90612f87878461237b565b515161ffff1690565b61ffff1690565b9a612fb1866020612fa8828661237b565b5101519361237b565b5101519086613ccb565b938c60a0612fcf866080612fa8828661237b565b51015191613ccb565b91169187613dbe565b9290612ffd57505050612ff5600191611c71565b970196612f39565b959799509593509650905061ffff8116613045575051632845f55560e21b81526001600160a01b0390941692840192835261ffff166020830152604082015281906060010390fd5b945050505050905f90565b9396505050925061ffff811661308d5750506108135a9251630e76ff3560e41b8152918201928352620f4240602084015290918291604090910190565b92505f9150565b96506001955050505050565b6130bd9192503d805f833e6130b581836104cc565b810190612e1a565b905f612f35565b92905f926040805190637d3b06bb60e11b82526004925f83806130ea8b60048301611e68565b038173__$9d71d8de471125782e5046de17c7aa75b6$__5af4928315611e43575f93613231575b5061ffff809516965b835188101561322457620f42405a106131e95761313b5a612f5b5a60031c90565b61314b612f90612f878b8861237b565b9061318784828d8d6001600160401b03612fd86131738d8d612fb1866020612fa8828661237b565b938d60a0612fcf866080612fa8828661237b565b92906131a35750505061319b600191611c71565b97019661311a565b96989a5096925097508092935016613045575051632845f55560e21b81526001600160a01b0390941692840192835261ffff166020830152604082015281906060010390fd5b9493965050955050811661308d5750506108135a9251630e76ff3560e41b8152918201928352620f4240602084015290918291604090910190565b9750600196505050505050565b6132469193503d805f833e6130b581836104cc565b915f613111565b6040519061325a8261044d565b5f6020838281520152565b604051906132728261044d565b8161327b61324d565b8152602061062a61324d565b60405190613294826104b1565b606060c0838281526132a4613265565b60208201525f60408201525f838201525f60808201528260a08201520152565b604051906132d1826104b1565b606060c0836132de613265565b81528260208201528260408201525f838201528260808201525f60a08201520152565b6040519061330e8261044d565b60606020835f81520152565b604051906133278261044d565b816133306132c4565b8152602061062a613301565b90613346826104ed565b61335360405191826104cc565b8281528092613364601f19916104ed565b01905f5b82811061337457505050565b60209061337f61331a565b82828501015201613368565b90613394613287565b915f8151905f805b83811061356457506133ad9061333c565b9160c086019283525f915b8181106133c6575050505050565b6133d09085613f8e565b90816001600160401b0391828116600a81145f1461340157505050506133f69085614147565b919087525b906133b8565b60128103613449575050506134196134429287613f8e565b91169061343861343361342c8484611b47565b838a613fd2565b614176565b60208a0152611b47565b905b6133fb565b602881036134765750505050906134636134449286613f8e565b6001600160401b03909116604089015291565b603081036134a657505050509061344461349563ffffffff9387613f8e565b9316606089019063ffffffff169052565b603881036134d65750505050906134446134c563ffffffff9387613f8e565b9316608089019063ffffffff169052565b60a281036134f857505050506134ec9085614147565b919060a08801526133fb565b6101420361355457505061350f6134429287613f8e565b91169061354e61353161352c6135258585611b47565b848b613fd2565b61407e565b95875161353d826123f8565b97613548838361237b565b5261237b565b50611b47565b6134429350600716915086613ed4565b6135719085949394613f8e565b6101426001600160401b03839493161461359d575b60076135959293169086613ed4565b92919261339c565b60076135ab613595936123f8565b925050613586565b604090610570939281528160208201520190611149565b90601454918215159060c080910191825151915f9073__$6c94be45b3cad4e4a0bccaa807f5821f2b$__925b84831061360557505050505050565b61361683879993949596975161237b565b5151602092838201519060408093015193858351036137a2578451868115159182613797575b505061377c5783516347f5343160e01b9384825287828c818061366360049c8d8301611e68565b03915af4948515611e435788925f96613755575b509061368f92918751938492839283528a8301611e68565b03818d5af4968715611e43575f97613736575b50508061372c575b61370357506136ba90851c611b1d565b83851c918183036136d9575050505095600180930191909493926135f6565b5163eb9ba7ad60e01b81529283018681526020810191909152604081019190915281906060010390fd5b9151630b296d8760e31b8152928301868152602081019290925260408201528190036060019150fd5b50808214156136aa565b61374d929750803d10610da857610d9981836104cc565b945f806136a2565b83929196506137739061368f943d8511610da857610d9981836104cc565b95909192613677565b8351632d9a492360e21b815280610813878b600484016135b3565b14159050865f61363c565b8351635812b67560e01b815280610813858b600484016135b3565b60609093919301916137db6137d6845163ffffffff1690565b612950565b63ffffffff808216908115613ad357613814637fffffff61380a6113bc876120586120448c5163ffffffff1690565b9460011c16612d85565b90600754926138336001600160401b0394858482169160201c16611b47565b421015918086169182109383613ac6575b505050811591613abe575b5015613ab6576138e9936138816138869261386986600455565b6007805460ff60601b1916600160601b1790556107be565b601455565b7fa77ed82a4a8c2f7b412630e92b1c8f59e6645b05820a8b0ed43ae5fac990491c6138d96138bb6117e4875163ffffffff1690565b6040805186815263ffffffff90951660208601529093918291820190565b0390a2825163ffffffff16614222565b6138ff6138fa825163ffffffff1690565b614288565b613924613910825163ffffffff1690565b63ffffffff165f52601560205260405f2090565b805460ff1916600117815591600254613945906001600160a01b0316611cd8565b90813b1561033657604051905f82636744c79f60e01b9485825281838161397460048201906001602083019252565b03925af1918215611e435761399792613aa3575b50835163ffffffff1690612ee9565b60025491949092916139b1906001600160a01b0316611cd8565b91823b15610336575f9060405193849182528183816139d760048201905f602083019252565b03925af1908115611e4357613a16928492613a90575b50805463ffffff00191662ffff00600888901b161791151560181b63ff00000016919091179055565b158015613a71575b613a26575050565b611e25613a5b6117e47f1aaaa8b219dd84dddbfc5836221ae6ca620f0ec0711a5fb907846becfacc9500935163ffffffff1690565b60405161ffff9094168452929081906020820190565b50613a8b611856613a86835163ffffffff1690565b612d9a565b613a1e565b80611e37613a9d92610483565b5f6139ed565b80611e37613ab092610483565b5f613988565b505050505050565b90505f61384f565b16111590505f8080613844565b50505050505050565b93915f945f9460405193602085019363b700961360e01b855260018060a01b03809216602487015216604485015263ffffffff60e01b1660648401526064835260a08301918383106001600160401b0384111761042d576040935f9385528380528360205251915afa613b4b57565b9150505f51906020518060201c150290565b60ff5f80516020614a7f8339815191525460401c1615613b7957565b604051631afcd79f60e31b8152600490fd5b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116308114918215613bc9575b5050610e3857565b5f80516020614a3f8339815191525416141590505f80613bc1565b3d15613c0e573d90613bf582610504565b91613c0360405193846104cc565b82523d5f602084013e565b606090565b623d09005a1061298557600254613c32906001600160a01b0316611cd8565b803b156103365760405163a3ad9cc360e01b815263ffffffff9290921660048301525f908290602490829084905af19081613c90575b50613c8b57613c75613be4565b8051918215613c845750602001fd5b9150505f90565b600190565b80611e37613c9d92610483565b5f613c68565b90613cad82610504565b613cba60405191826104cc565b82815280926123b7601f1991610504565b91908181118015613d14575b613cfc57808203918211611b0b57602090613cf183613ca3565b930101602083015e90565b6024906040519063d98776dd60e01b82526004820152fd5b5082518211613cd7565b91613d4563ffffffff9161ffff604094979697168552606060208601526060850190611149565b9416910152565b91908260409103126103365761057060208351613d6881610919565b9301612ab1565b9160a093613da5613db39261ffff6001600160401b03959a99989a6003885216602087015260c0604087015260c0860190611149565b908482036060860152611149565b951660808201520152565b9194959390929561ffff831661ee0f8114908115613ec8575b50613e5b5750613e009060409495968551978895869563173d424160e21b875260048701613d6f565b038173__$a62ffa76e12530392778037a67b422bd63$__5af48015611e43575f925f91613e2c57509190565b9050613e5191925060403d604011613e54575b613e4981836104cc565b810190613d4c565b91565b503d613e3f565b5f955085949650613e97859294613e89604051938492602084019763b1635f9f60e01b895260248501613d1e565b03601f1981018352826104cc565b51923090f1613ea4613be4565b90613ec1578051918215613eb85750602001fd5b60019250309150565b505f903090565b61ee139150145f613dd7565b6001600160401b039283169283613ef55750613ef09250613f8e565b905090565b919260018103613f0b5750505061057090611b39565b60028103613f29575061057092613f2191613f8e565b911690611b47565b600514159150613f3e90505761057090611b2b565b60405162461bcd60e51b815260206004820152602260248201527f50726f746f62756652756e74696d653a20756e6b6e6f776e2077697265207479604482015261706560f01b6064820152608490fd5b91908201602001905f805b60806007600186515f1a94607f8616851b1796019201921615613fc557603f8211610336579290613f99565b9293909203601f19019150565b918181101580614073575b156140225781613fec91611b10565b90613ff682613ca3565b9282614003575b50505090565b015f5b82811015613ffd57602081830181015185830182015201614006565b60405162461bcd60e51b8152602060048201526024808201527f50726f746f62756652756e74696d653a20736c696365206f7574206f6620626f604482015263756e647360e01b6064820152608490fd5b508251811115613fdd565b9061408761331a565b915f8151905b81811061409957505050565b6140a39083613f8e565b90816001600160401b0391828116600a81145f146140f9575050506140cb6140f19285613f8e565b9116906140ea6140e56140de8484611b47565b8388613fd2565b614510565b8752611b47565b905b9061408d565b60120361413757505061410f6141319285613f8e565b9116906141276141226140de8484611b47565b6143f3565b6020880152611b47565b906140f3565b6141319350600716915084613ed4565b919061415b6001600160401b039184613f8e565b9116810192838211611b0b57614172918491613fd2565b9190565b9061417f613265565b915f8151905b81811061419157505050565b61419b9083613f8e565b90816001600160401b0391828116600a81145f146141e3575050506141c36141db9285613f8e565b9116906140ea6141d66140de8484611b47565b614673565b905b90614185565b6012036142125750506141f961420c9285613f8e565b9116906141276141d66140de8484611b47565b906141dd565b61420c9350600716915084613ed4565b63ffffffff8082165f52600c60205260405f209081541661428357614259600192829063ffffffff1663ffffffff19825416179055565b80546bffffffffffffffff0000000019164260201b6bffffffffffffffff00000000161781550155565b505050565b61429f9063ffffffff91829182600d541690612d53565b169081151590816142c8575b506142b35750565b5f52600c6020525f6001604082208281550155565b9050815f52600c60205260405f20541615155f6142ab565b90813b15614364575f80516020614a3f83398151915280546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115614349576143469161470c565b50565b50503461435257565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b6040519061439282610468565b60606040835f81525f60208201520152565b906143ae826104ed565b6143bb60405191826104cc565b82815280926143cc601f19916104ed565b01905f5b8281106143dc57505050565b6020906143e7614385565b828285010152016143d0565b906143fc613301565b915f8151905f805b8381106144c25750614415906143a4565b91602086019283525f915b81811061442e575050505050565b6144389085613f8e565b90816001600160401b0391828116600881145f1461447b57505050509061447561446763ffffffff9387613f8e565b9316889063ffffffff169052565b90614420565b6012036144b25750506144916144ac9287613f8e565b91169061354e6135316144a76135258585611b47565b614729565b90614475565b6144ac9350600716915086613ed4565b6144cf9085949394613f8e565b60126001600160401b0383949316146144fa575b60076144f29293169086613ed4565b929192614404565b60076145086144f2936123f8565b9250506144e3565b906145196132c4565b915f8151905b81811061452b57505050565b6145359083613f8e565b90816001600160401b0391828116600a81145f146145785750505061455d6145709285613f8e565b9116906140ea6134336140de8484611b47565b905b9061451f565b919250906012810361459d575050506145919083614147565b91906020860152614572565b601a81036145be575050506145b29083614147565b91906040860152614572565b602881036145f257505050906145ed6145dc63ffffffff9385613f8e565b9316606087019063ffffffff169052565b614572565b60328103614613575050506146079083614147565b91906080860152614572565b6038810361463f575050509061462c6145ed9284613f8e565b6001600160401b0390911660a087015291565b60420361465e5750506146529083614147565b919060c0860152614572565b61466d92506007169084613ed4565b90614572565b9061467c61324d565b915f8151905b81811061468e57505050565b6146989083613f8e565b80916001600160401b038116600881145f146146d2575050509060ff6146c16146c89385613f8e565b93906147f5565b1685525b90614682565b6010036146f7575050906146eb63ffffffff9284613f8e565b921660208601526146cc565b61470692506007169084613ed4565b906146cc565b5f8061057093602081519101845af4614723613be4565b91614846565b90614732614385565b915f8151905b81811061474457505050565b61474e9083613f8e565b80916001600160401b038116600881145f1461478d575050509061478761477861477f9385613f8e565b93906148a9565b61ffff168652565b90614738565b601081036147c157505050906147bc6147ab63ffffffff9385613f8e565b9316602087019063ffffffff169052565b614787565b601a036147e05750506147d49083614147565b91906040860152614787565b6147ef92506007169084613ed4565b90614787565b6001600160401b031680156129855760018114614840576002811461483a57600381146148345760249060405190632cd3748760e21b82526004820152fd5b50600390565b50600290565b50600190565b9061486d575080511561485b57602081519101fd5b60405163d6bda27560e01b8152600490fd5b815115806148a0575b61487e575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15614876565b6001600160401b0381168015611c60576107d1808214614a185750610bb9808214614a185750610bba808214614a185750610bbc808214614a185750610bbe808214614a18575061aa00808214614a18575061ee00808214614a18575061ee02808214614a18575061ee04808214614a18575061ee06808214614a18575061ee10808214614a18575061ee11808214614a18575061ee13808214614a18575061ee0f808214614a18575061ee15808214614a18575061ee16808214614a18575061ee17808214614a18575061ee18808214614a18575061ee19808214614a18575061ee1b808214614a18575061ee1c808214614a18575061ee1e808214614a18575061ee1f808214614a18575061ee20808214614a18575061ee21808214614a18575061ee22808214614a18575061ee23808214614a18575061ee24808214614a18575061ee25809114613ef057604051632cd3748760e21b81526001600160401b0383166004820152602490fd5b9150509056fef3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a264697066735822122062e7b41158989b69b850a966fb57e71e463e33b0d516806bd4e56f9e996f4f9964736f6c63430008190033", "linkReferences": { "contracts/outpost/OPPCommon.sol": { - "OPPCommon": [ + "OPPCommonV2": [ { "length": 20, - "start": 11450 + "start": 8645 }, { "length": 20, - "start": 11987 + "start": 14012 } ] }, - "contracts/outpost/OPPInboundLib.sol": { - "OPPInboundLib": [ + "contracts/outpost/OPPEnvelopeWalkLib.sol": { + "OPPEnvelopeWalkLib": [ { "length": 20, - "start": 7922 + "start": 12269 }, { "length": 20, - "start": 16213 - }, - { - "length": 20, - "start": 16436 - }, + "start": 12745 + } + ] + }, + "contracts/outpost/OPPInboundLib.sol": { + "OPPInboundLib": [ { "length": 20, - "start": 16523 + "start": 10968 }, { "length": 20, - "start": 17768 + "start": 11481 }, { "length": 20, - "start": 18006 + "start": 16095 } ] } }, "deployedLinkReferences": { "contracts/outpost/OPPCommon.sol": { - "OPPCommon": [ + "OPPCommonV2": [ { "length": 20, - "start": 11227 + "start": 8426 }, { "length": 20, - "start": 11764 + "start": 13793 } ] }, - "contracts/outpost/OPPInboundLib.sol": { - "OPPInboundLib": [ - { - "length": 20, - "start": 7699 - }, + "contracts/outpost/OPPEnvelopeWalkLib.sol": { + "OPPEnvelopeWalkLib": [ { "length": 20, - "start": 15990 + "start": 12050 }, { "length": 20, - "start": 16213 - }, + "start": 12526 + } + ] + }, + "contracts/outpost/OPPInboundLib.sol": { + "OPPInboundLib": [ { "length": 20, - "start": 16300 + "start": 10749 }, { "length": 20, - "start": 17545 + "start": 11262 }, { "length": 20, - "start": 17783 + "start": 15876 } ] }