diff --git a/l1-contracts/slither_output.md b/l1-contracts/slither_output.md index a0a7f05ae3c..1d5dc10c830 100644 --- a/l1-contracts/slither_output.md +++ b/l1-contracts/slither_output.md @@ -321,15 +321,15 @@ src/core/messagebridge/Inbox.sol#L148-L153 Impact: Informational Confidence: Medium - [ ] ID-35 -Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L129) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L122) +Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L131) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L124) -src/core/libraries/ConstantsGen.sol#L129 +src/core/libraries/ConstantsGen.sol#L131 - [ ] ID-36 -Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L109) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L110) +Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L111) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L112) -src/core/libraries/ConstantsGen.sol#L109 +src/core/libraries/ConstantsGen.sol#L111 - [ ] ID-37 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index d7b51b36c73..365932b1417 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -25,6 +25,7 @@ library Constants { uint256 internal constant MAX_PUBLIC_DATA_READS_PER_CALL = 16; uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32; uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2; + uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2; uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1; uint256 internal constant MAX_NEW_NOTE_HASHES_PER_TX = 64; uint256 internal constant MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8; @@ -45,6 +46,7 @@ library Constants { uint256 internal constant MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2; uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128; uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8; + uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8; uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; uint256 internal constant NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; uint256 internal constant NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; @@ -113,7 +115,7 @@ library Constants { uint256 internal constant PARTIAL_STATE_REFERENCE_LENGTH = 6; uint256 internal constant PRIVATE_CALL_STACK_ITEM_LENGTH = 214; uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209; - uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196; + uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; uint256 internal constant STATE_REFERENCE_LENGTH = 8; uint256 internal constant TX_CONTEXT_DATA_LENGTH = 4; uint256 internal constant TX_REQUEST_LENGTH = 10; diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index ac987de4251..4bc91a32835 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -23,7 +23,7 @@ use dep::protocol_types::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, - MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, + MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, @@ -451,6 +451,7 @@ impl PrivateContext { args_hash: reader.read(), return_values: [0; RETURN_VALUES_LENGTH], nullifier_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL], + nullifier_non_existent_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL], public_call_stack_hashes: [0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL], diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 510df6b0d0b..352e8f03f38 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -15,7 +15,8 @@ use dep::protocol_types::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::hash_args, header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader @@ -29,6 +30,7 @@ struct PublicContext { return_values : BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -102,6 +104,7 @@ impl PublicContext { args_hash, return_values: BoundedVec::new(), nullifier_read_requests: BoundedVec::new(), + nullifier_non_existent_read_requests: BoundedVec::new(), contract_storage_update_requests: BoundedVec::new(), contract_storage_reads: BoundedVec::new(), public_call_stack_hashes: BoundedVec::new(), @@ -143,6 +146,7 @@ impl PublicContext { call_context: self.inputs.call_context, // Done args_hash: self.args_hash, // Done nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, return_values: self.return_values.storage, @@ -165,6 +169,12 @@ impl PublicContext { self.side_effect_counter = self.side_effect_counter + 1; } + pub fn push_nullifier_non_existent_read_request(&mut self, nullifier: Field) { + let request = ReadRequest { value: nullifier, counter: self.side_effect_counter }; + self.nullifier_non_existent_read_requests.push(request); + self.side_effect_counter = self.side_effect_counter + 1; + } + pub fn message_portal(&mut self, recipient: EthAddress, content: Field) { let message = L2ToL1Message { recipient, content }; self.new_l2_to_l1_msgs.push(message); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr index 4313375bed7..43c012dc9ba 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr @@ -20,9 +20,10 @@ use dep::types::{ hash::{ compute_constructor_hash, compute_l2_to_l1_hash, compute_logs_hash, compute_new_contract_address_hash, function_tree_root_from_siblings, pedersen_hash, - private_functions_root_from_siblings, root_from_sibling_path, silo_note_hash, silo_nullifier, + private_functions_root_from_siblings, silo_note_hash, silo_nullifier, stdlib_recursion_verification_key_compress_native_vk }, + merkle_tree::check_membership, utils::{arrays::{array_length, array_to_bounded_vec, validate_array}}, traits::{is_empty, is_empty_array} }; @@ -72,8 +73,14 @@ pub fn validate_note_hash_read_requests( // but we use the leaf index as a placeholder to detect a 'pending note read'. if (read_request != 0) & (witness.is_transient == false) { - let root_for_read_request = root_from_sibling_path(read_request, witness.leaf_index, witness.sibling_path); - assert(root_for_read_request == historical_note_hash_tree_root, "note hash tree root mismatch"); + assert( + check_membership( + read_request, + witness.leaf_index, + witness.sibling_path, + historical_note_hash_tree_root + ), "note hash tree root mismatch" + ); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1354): do we need to enforce // that a non-transient read_request was derived from the proper/current contract address? } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr index b6d6de9fab3..46f9cda62a5 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr @@ -1,12 +1,11 @@ use crate::common; use dep::std::{cmp::Eq, option::Option, unsafe}; -use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; +use dep::reset_kernel_lib::{NullifierReadRequestHints, reset_read_requests}; use dep::types::{ abis::{ call_request::CallRequest, nullifier_key_validation_request::NullifierKeyValidationRequestContext, kernel_data::{PrivateKernelInnerData, PrivateKernelTailData}, kernel_circuit_public_inputs::{PrivateKernelCircuitPublicInputsBuilder, PrivateKernelTailCircuitPublicInputs}, - membership_witness::{MembershipWitness, NullifierMembershipWitness}, side_effect::{SideEffect, SideEffectLinkedToNoteHash, Ordered} }, constants::{ @@ -26,7 +25,7 @@ struct PrivateKernelTailCircuitPrivateInputs { read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], sorted_new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], sorted_new_nullifiers_indexes: [u64; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints: NullifierReadRequestResetHints, + nullifier_read_request_hints: NullifierReadRequestHints, nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], master_nullifier_secret_keys: [GrumpkinPrivateKey; MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX], } @@ -43,7 +42,7 @@ impl PrivateKernelTailCircuitPrivateInputs { let pending_nullifiers = self.previous_kernel.public_inputs.end.new_nullifiers; - let hints = self.nullifier_read_request_reset_hints; + let hints = self.nullifier_read_request_hints; let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; @@ -256,7 +255,7 @@ mod tests { use dep::std::{cmp::Eq, unsafe}; use crate::{private_kernel_tail::PrivateKernelTailCircuitPrivateInputs}; use dep::reset_kernel_lib::{ - NullifierReadRequestResetHintsBuilder, + tests::nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::constants::{ @@ -277,7 +276,7 @@ mod tests { previous_kernel: PreviousKernelDataBuilder, read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder, + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder, } impl PrivateKernelTailInputsBuilder { @@ -286,7 +285,7 @@ mod tests { previous_kernel: PreviousKernelDataBuilder::new(false), read_commitment_hints: [0; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_commitment_hints: [0; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) } } @@ -326,10 +325,10 @@ mod tests { pub fn add_nullifier_pending_read(&mut self, nullifier_index_offset_one: u64) { let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; } pub fn nullify_transient_commitment(&mut self, nullifier_index: Field, commitment_index: u64) { @@ -383,7 +382,7 @@ mod tests { read_commitment_hints: sorted_read_commitment_hints, sorted_new_nullifiers, sorted_new_nullifiers_indexes, - nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints(), + nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(), nullifier_commitment_hints: sorted_nullifier_commitment_hints, master_nullifier_secret_keys: unsafe::zeroed() }; @@ -480,10 +479,10 @@ mod tests { builder.append_nullifiers(3); builder.add_nullifier_pending_read(1); - let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop(); + let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop(); assert(hint.pending_value_index == 2); hint.pending_value_index = 1; - builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint); builder.failed(); } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr index 7df1fac5ffa..550649723f8 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr @@ -10,9 +10,10 @@ use dep::types::{ contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, - MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, + MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX }, hash::{silo_note_hash, silo_nullifier, compute_l2_to_l1_hash, accumulate_sha256}, @@ -89,6 +90,7 @@ pub fn initialize_end_values( let start_non_revertible = previous_kernel.public_inputs.end_non_revertible; circuit_outputs.end_non_revertible.public_call_stack = array_to_bounded_vec(start_non_revertible.public_call_stack); circuit_outputs.end_non_revertible.nullifier_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_read_requests); + circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_non_existent_read_requests); } fn perform_static_call_checks(public_call: PublicCallData) { @@ -161,6 +163,7 @@ pub fn update_public_end_non_revertible_values( circuit_outputs.end_non_revertible.public_call_stack.extend_from_bounded_vec(public_call_requests); propagate_nullifier_read_requests_non_revertible(public_call, circuit_outputs); + propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs); propagate_new_nullifiers_non_revertible(public_call, circuit_outputs); propagate_new_note_hashes_non_revertible(public_call, circuit_outputs); propagate_valid_non_revertible_public_data_update_requests(public_call, circuit_outputs); @@ -182,6 +185,8 @@ pub fn update_public_end_values(public_call: PublicCallData, circuit_outputs: &m circuit_outputs.end.public_call_stack.extend_from_bounded_vec(public_call_requests); propagate_nullifier_read_requests_revertible(public_call, circuit_outputs); + propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs); // TODO - Requests are not revertible and should be propagated to "validation_requests". + propagate_new_nullifiers(public_call, circuit_outputs); propagate_new_note_hashes(public_call, circuit_outputs); @@ -224,6 +229,22 @@ fn propagate_nullifier_read_requests_revertible( } } +fn propagate_nullifier_non_existent_read_requests_non_revertible( + public_call: PublicCallData, + circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + let public_call_public_inputs = public_call.call_stack_item.public_inputs; + let nullifier_non_existent_read_requests = public_call_public_inputs.nullifier_non_existent_read_requests; + let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address; + + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL { + let request = nullifier_non_existent_read_requests[i]; + if !is_empty(request) { + circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests.push(request.to_context(storage_contract_address)); + } + } +} + fn propagate_valid_public_data_update_requests( public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr index 546e026f156..defa25ed06e 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr @@ -75,14 +75,14 @@ mod tests { use dep::types::{ abis::{ kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, + public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequest, side_effect::{SideEffect, SideEffectLinkedToNoteHash} }, address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId, hash::{compute_l2_to_l1_hash, compute_logs_hash, silo_note_hash, silo_nullifier}, messaging::l2_to_l1_message::L2ToL1Message, tests::{kernel_data_builder::PreviousKernelDataBuilder, public_call_data_builder::PublicCallDataBuilder}, - utils::{arrays::array_eq} + utils::arrays::{array_eq, array_length} }; use dep::types::constants::{MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL}; @@ -419,4 +419,30 @@ mod tests { ); assert_eq_public_data_reads(public_inputs.end.public_data_reads, read_requests); } + + #[test] + fn propagate_nullifier_non_existent_read_requests() { + let mut builder = PublicKernelAppLogicCircuitPrivateInputsBuilder::new(); + let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address; + + let request_0 = ReadRequest { value: 123, counter: 4567 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0); + let request_1 = ReadRequest { value: 777888, counter: 90 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1); + + let public_inputs = builder.execute(); + + let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests; + assert_eq(array_length(end_requests), 2); + + let request_context = end_requests[0]; + assert_eq(request_context.value, request_0.value); + assert_eq(request_context.counter, request_0.counter); + assert_eq(request_context.contract_address, storage_contract_address); + + let request_context = end_requests[1]; + assert_eq(request_context.value, request_1.value); + assert_eq(request_context.counter, request_1.counter); + assert_eq(request_context.contract_address, storage_contract_address); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr index a08b158dd3f..560b2b848e6 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr @@ -78,7 +78,8 @@ mod tests { abis::{ call_request::CallRequest, function_selector::FunctionSelector, kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData + public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData, + read_request::ReadRequest }, address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId, contrakt::storage_read::StorageRead, hash::compute_logs_hash, @@ -496,4 +497,30 @@ mod tests { let expected_unencrypted_logs_hash = compute_logs_hash(prev_unencrypted_logs_hash, unencrypted_logs_hash); assert_eq(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash); } + + #[test] + fn propagate_nullifier_non_existent_read_requests() { + let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new(); + let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address; + + let request_0 = ReadRequest { value: 123, counter: 4567 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0); + let request_1 = ReadRequest { value: 777888, counter: 90 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1); + + let public_inputs = builder.execute(); + + let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests; + assert_eq(array_length(end_requests), 2); + + let request_context = end_requests[0]; + assert_eq(request_context.value, request_0.value); + assert_eq(request_context.counter, request_0.counter); + assert_eq(request_context.contract_address, storage_contract_address); + + let request_context = end_requests[1]; + assert_eq(request_context.value, request_1.value); + assert_eq(request_context.counter, request_1.counter); + assert_eq(request_context.contract_address, storage_contract_address); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr index d6242fe3292..f0ed14abc3c 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr @@ -1,17 +1,23 @@ use crate::common; -use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; +use dep::reset_kernel_lib::{ + NullifierReadRequestHints, NullifierNonExistentReadRequestHints, reset_non_existent_read_requests, + reset_read_requests +}; use dep::types::{ abis::{ kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, kernel_data::PublicKernelData, side_effect::SideEffectLinkedToNoteHash }, - constants::MAX_NEW_NULLIFIERS_PER_TX, utils::{arrays::{array_length, array_merge, array_concat}} + constants::MAX_NEW_NULLIFIERS_PER_TX, + utils::{arrays::{array_length, array_merge, array_concat, array_to_bounded_vec, assert_sorted_array}}, + hash::silo_nullifier, traits::is_empty }; use dep::std::unsafe; struct PublicKernelTailCircuitPrivateInputs { previous_kernel: PublicKernelData, - nullifier_read_request_reset_hints: NullifierReadRequestResetHints, + nullifier_read_request_hints: NullifierReadRequestHints, + nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, } impl PublicKernelTailCircuitPrivateInputs { @@ -44,7 +50,7 @@ impl PublicKernelTailCircuitPrivateInputs { let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers); - let hints = self.nullifier_read_request_reset_hints; + let hints = self.nullifier_read_request_hints; let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; @@ -62,6 +68,42 @@ impl PublicKernelTailCircuitPrivateInputs { ); } + fn validate_nullifier_non_existent_read_requests(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let end_non_revertible = self.previous_kernel.public_inputs.end_non_revertible; + let end = self.previous_kernel.public_inputs.end; + + // The values of the read requests here need to be siloed. + // Notice that it's not the case for regular read requests, which can be run between two kernel iterations, and will to be verified against unsiloed pending values. + let mut read_requests = end_non_revertible.nullifier_non_existent_read_requests; + for i in 0..read_requests.len() { + let read_request = read_requests[i]; + if !is_empty(read_request) { + read_requests[i].value = silo_nullifier(read_request.contract_address, read_request.value); + } + } + + let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; + + let hints = self.nullifier_non_existent_read_request_hints; + + let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers); + assert_sorted_array( + pending_nullifiers, + hints.sorted_pending_values, + hints.sorted_pending_value_index_hints, + |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) + ); + let sorted_pending_nullifiers = array_to_bounded_vec(hints.sorted_pending_values); + + reset_non_existent_read_requests( + read_requests, + hints.non_membership_hints, + nullifier_tree_root, + sorted_pending_nullifiers, + hints.next_pending_value_indices + ); + } + pub fn public_kernel_tail(self) -> PublicKernelCircuitPublicInputs { let mut public_inputs: PublicKernelCircuitPublicInputsBuilder = unsafe::zeroed(); @@ -73,6 +115,8 @@ impl PublicKernelTailCircuitPrivateInputs { self.validate_nullifier_read_requests(&mut public_inputs); + self.validate_nullifier_non_existent_read_requests(&mut public_inputs); + public_inputs.to_inner() } } @@ -80,57 +124,114 @@ impl PublicKernelTailCircuitPrivateInputs { mod tests { use crate::{public_kernel_tail::PublicKernelTailCircuitPrivateInputs}; use dep::reset_kernel_lib::{ - NullifierReadRequestResetHintsBuilder, + tests::{ + nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder, + nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder + }, read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::{ abis::{ kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, - kernel_data::PublicKernelData + kernel_data::PublicKernelData, nullifier_leaf_preimage::NullifierLeafPreimage }, - constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX, - tests::{kernel_data_builder::PreviousKernelDataBuilder} + constants::{ + MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT + }, + hash::silo_nullifier, + tests::{kernel_data_builder::PreviousKernelDataBuilder, merkle_tree_utils::NonEmptyMerkleTree}, + utils::arrays::array_concat }; + fn build_nullifier_tree() -> NonEmptyMerkleTree { + let mut pre_existing_nullifiers = [NullifierLeafPreimage::empty(); MAX_NEW_NULLIFIERS_PER_TX]; + pre_existing_nullifiers[0] = NullifierLeafPreimage { nullifier: 0, next_nullifier: 100, next_index: 1 }; + pre_existing_nullifiers[1] = NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 }; + NonEmptyMerkleTree::new( + pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT], + [0; NULLIFIER_SUBTREE_HEIGHT] + ) + } + struct PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel: PreviousKernelDataBuilder, - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder, + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder, + nullifier_non_existent_read_request_hints_builder: NullifierNonExistentReadRequestHintsBuilder, } impl PublicKernelTailCircuitPrivateInputsBuilder { pub fn new() -> Self { let previous_kernel = PreviousKernelDataBuilder::new(true); + let mut nullifier_non_existent_read_request_hints_builder = NullifierNonExistentReadRequestHintsBuilder::new(); - PublicKernelTailCircuitPrivateInputsBuilder { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel, - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) - } + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX), + nullifier_non_existent_read_request_hints_builder + }; + builder.set_nullifiers_for_non_existent_read_request_hints(); + builder + } + + pub fn with_nullifier_tree(&mut self) -> Self { + let nullifier_tree = build_nullifier_tree(); + self.previous_kernel.historical_header.state.partial.nullifier_tree.root = nullifier_tree.get_root(); + self.nullifier_non_existent_read_request_hints_builder.set_nullifier_tree(nullifier_tree); + *self + } + + pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) { + self.previous_kernel.add_nullifier(unsiloed_nullifier); + self.set_nullifiers_for_non_existent_read_request_hints(); } pub fn append_nullifiers(&mut self, num_nullifiers: u64) { self.previous_kernel.append_new_nullifiers_from_public(num_nullifiers); + self.set_nullifiers_for_non_existent_read_request_hints(); } pub fn append_nullifiers_non_revertible(&mut self, num_nullifiers: u64) { self.previous_kernel.append_new_nullifiers_non_revertible_from_public(num_nullifiers); + self.set_nullifiers_for_non_existent_read_request_hints(); + } + + fn set_nullifiers_for_non_existent_read_request_hints(&mut self) { + let previous_kernel_public_inputs = self.previous_kernel.to_public_kernel_data().public_inputs; + let nullifiers = array_concat( + previous_kernel_public_inputs.end_non_revertible.new_nullifiers, + previous_kernel_public_inputs.end.new_nullifiers + ); + self.nullifier_non_existent_read_request_hints_builder.set_nullifiers(nullifiers); } pub fn add_nullifier_pending_read(&mut self, nullifier_index: u64) { let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let pending_value_index = nullifier_index + self.previous_kernel.end_non_revertible.new_nullifiers.len(); let hint = PendingReadHint { read_request_index, pending_value_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; } pub fn add_nullifier_pending_read_non_revertible(&mut self, nullifier_index_offset_one: u64) { let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier_non_revertible(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + } + + pub fn read_non_existent_nullifier(&mut self, unsiloed_nullifier: Field) { + self.previous_kernel.add_non_existent_read_request_for_nullifier(unsiloed_nullifier); + let siloed_nullifier = silo_nullifier( + self.previous_kernel.storage_contract_address, + unsiloed_nullifier + ); + self.nullifier_non_existent_read_request_hints_builder.add_value_read(siloed_nullifier); } pub fn execute(&mut self) -> PublicKernelCircuitPublicInputs { @@ -138,7 +239,8 @@ mod tests { let kernel = PublicKernelTailCircuitPrivateInputs { previous_kernel, - nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints() + nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(), + nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints() }; kernel.public_kernel_tail() @@ -154,9 +256,10 @@ mod tests { } #[test] - fn public_kernel_circuit_tail_succeeds() { + unconstrained fn public_kernel_circuit_tail_succeeds() { let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); builder.succeeded(); + // TODO: Check the values in public inputs. } #[test] @@ -194,9 +297,9 @@ mod tests { builder.append_nullifiers(3); builder.add_nullifier_pending_read(1); - let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop(); + let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop(); hint.pending_value_index -= 1; - builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint); builder.failed(); } @@ -214,4 +317,32 @@ mod tests { builder.failed(); } + + // TODO: Add tests for reading (non-existent) settled values. + + #[test] + unconstrained fn nullifier_non_existent_read_request() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree(); + + builder.add_nullifier(3); + builder.add_nullifier(1); + builder.add_nullifier(9); + + builder.read_non_existent_nullifier(8); + + builder.succeeded(); + } + + #[test(should_fail_with="Value exists in pending set")] + unconstrained fn nullifier_non_existent_read_request_failed_read_exist() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree(); + + builder.add_nullifier(3); + builder.add_nullifier(1); + builder.add_nullifier(9); + + builder.read_non_existent_nullifier(1); + + builder.succeeded(); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr index 8e41dc16d8d..07a22b9eb44 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr @@ -1,6 +1,10 @@ +use non_existent_read_request_reset::reset_non_existent_read_requests; +use nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints; +use nullifier_read_request_reset::NullifierReadRequestHints; use read_request_reset::reset_read_requests; -use nullifier_read_request_reset::NullifierReadRequestResetHints; -use nullifier_read_request_reset::NullifierReadRequestResetHintsBuilder; -mod read_request_reset; +mod non_existent_read_request_reset; +mod nullifier_non_existent_read_request_reset; mod nullifier_read_request_reset; +mod read_request_reset; +mod tests; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr new file mode 100644 index 00000000000..4beeee665ae --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr @@ -0,0 +1,354 @@ +use dep::types::{ + abis::{side_effect::OrderedValue, read_request::ReadRequestContext}, + merkle_tree::{assert_check_non_membership, IndexedTreeLeafPreimage, MembershipWitness}, + traits::{Empty, is_empty} +}; + +trait NonMembershipHint where LEAF_PREIMAGE: IndexedTreeLeafPreimage { + fn low_leaf_preimage(self) -> LEAF_PREIMAGE; + fn membership_witness(self) -> MembershipWitness; +} + +fn check_no_matching_pending_value( + read_request: ReadRequestContext, + sorted_pending_values: BoundedVec, + next_value_index: u64 +) -> bool where T: OrderedValue { + if next_value_index == sorted_pending_values.len() { + let highest_value = sorted_pending_values.get_unchecked(sorted_pending_values.len() - 1).value(); + highest_value.lt(read_request.value) + } else { + let next_value = sorted_pending_values.get_unchecked(next_value_index).value(); + let is_less_than_next = read_request.value.lt(next_value); + let is_greater_than_prev = if next_value_index == 0 { + true + } else { + let prev_value = sorted_pending_values.get_unchecked(next_value_index - 1).value(); + prev_value.lt(read_request.value) + }; + is_less_than_next & is_greater_than_prev + } +} + +fn check_is_read_before_pending_value( + read_request: ReadRequestContext, + sorted_pending_values: BoundedVec, + next_value_index: u64 +) -> bool where T: OrderedValue { + if next_value_index == sorted_pending_values.len() { + false + } else { + let pending = sorted_pending_values.get_unchecked(next_value_index); + if pending.value() == read_request.value { + assert(read_request.counter < pending.counter(), "Value exists in pending set"); + true + } else { + false + } + } +} + +// Unlike regular read requests, which can be reset at any time between two function executions. +// Non existent read requests can only be verified at the end, after all pending values are present. +// The values in read_requests and in sorted_pending_values should've been siloed before calling this. +pub fn reset_non_existent_read_requests( + siloed_read_requests: [ReadRequestContext; N], + non_membership_hints: [NON_MEMBERSHIP_HINT; N], + tree_root: Field, + sorted_pending_values: BoundedVec, + next_pending_value_indices: [u64; N] +) where + T: OrderedValue, + NON_MEMBERSHIP_HINT: NonMembershipHint, + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + for i in 0..siloed_read_requests.len() { + let read_request = siloed_read_requests[i]; + if !is_empty(read_request) { + // Verify that it's not in the tree. + let hint = non_membership_hints[i]; + assert_check_non_membership( + read_request.value, + hint.low_leaf_preimage(), + hint.membership_witness(), + tree_root + ); + + // Verify that its value is either not in the pending set, or is created after the read. + let next_value_index = next_pending_value_indices[i]; + assert( + next_value_index <= sorted_pending_values.len(), "Next pending value index out of bounds" + ); + let no_matching_value = check_no_matching_pending_value(read_request, sorted_pending_values, next_value_index); + let is_read_before_value = check_is_read_before_pending_value(read_request, sorted_pending_values, next_value_index); + assert(no_matching_value | is_read_before_value, "Invalid next pending value index"); + } + } +} + +mod tests { + use crate::non_existent_read_request_reset::{NonMembershipHint, reset_non_existent_read_requests}; + + use dep::types::{ + address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect}, + merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, membership::MembershipWitness}, + hash::silo_nullifier, tests::merkle_tree_utils::NonEmptyMerkleTree + }; + + struct TestLeafPreimage { + value: Field, + next_value: Field, + } + + impl IndexedTreeLeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn get_next_key(self) -> Field { + self.next_value + } + + fn as_leaf(self) -> Field { + self.value * 100 + } + } + + struct TestNonMembershipHint { + low_leaf_preimage: TestLeafPreimage, + membership_witness: MembershipWitness<3>, + } + + impl NonMembershipHint<3, TestLeafPreimage> for TestNonMembershipHint { + fn low_leaf_preimage(self) -> TestLeafPreimage { + self.low_leaf_preimage + } + + fn membership_witness(self) -> MembershipWitness<3> { + self.membership_witness + } + } + + global sorted_pending_values = BoundedVec { + storage: [ + SideEffect { value: 5, counter: 17 }, + SideEffect { value: 15, counter: 8 }, + SideEffect { value: 25, counter: 11 }, + SideEffect::empty(), + SideEffect::empty(), + ], + len: 3, + }; + + global leaf_preimages = [ + TestLeafPreimage { value: 0, next_value: 10 }, + TestLeafPreimage { value: 20, next_value: 30 }, + TestLeafPreimage { value: 30, next_value: 0 }, + TestLeafPreimage { value: 10, next_value: 20 }, + ]; + + fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> { + NonEmptyMerkleTree::new( + leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()), + [0; 3], + [0; 1], + [0; 2] + ) + } + + fn get_non_membership_hints(leaf_indices: [Field; N]) -> ([TestNonMembershipHint; N], Field) { + let tree = build_tree(); + let hints = leaf_indices.map( + |leaf_index| TestNonMembershipHint { + low_leaf_preimage: leaf_preimages[leaf_index], + membership_witness: MembershipWitness { leaf_index, sibling_path: tree.get_sibling_path(leaf_index as u64) } + } + ); + let tree_root = tree.get_root(); + (hints, tree_root) + } + + #[test] + fn test_reset_non_existent_read_requests_in_range() { + let read_requests = [ + ReadRequestContext { value: 11, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 22, counter: 51, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 6, counter: 52, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([3, 1, 0]); + let next_pending_value_indices = [1, 2, 1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_less_than_min() { + let read_requests = [ + ReadRequestContext { value: 3, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 2, counter: 51, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([0, 0]); + let next_pending_value_indices = [0, 0]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_greater_than_max() { + let read_requests = [ + ReadRequestContext { value: 35, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 31, counter: 51, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([2, 2]); + let next_pending_value_indices = [3, 3]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_read_before_pending_emitted() { + let read_requests = [ + ReadRequestContext { value: 25, counter: 10, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 5, counter: 11, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([1, 0]); + let next_pending_value_indices = [2, 0]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Cannot check non membership against empty leaf")] + fn test_reset_non_existent_read_requests_empty_leaf_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([0]); + let mut hint = non_membership_hints[0]; + hint.low_leaf_preimage = TestLeafPreimage { value: 0, next_value: 0 }; + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + [hint], + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Low leaf does not exist")] + fn test_reset_non_existent_read_requests_invalid_preimage_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([3]); + let mut hint = non_membership_hints[0]; + hint.low_leaf_preimage = TestLeafPreimage { value: 9, next_value: 20 }; + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + [hint], + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_reset_non_existent_read_requests_read_settled_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([3]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_reset_non_existent_read_requests_invalid_non_membership_hint_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([0]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Value exists in pending set")] + fn test_reset_non_existent_read_requests_read_pending_value_failed() { + let read_requests = [ReadRequestContext { value: 25, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [2]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Invalid next pending value index")] + fn test_reset_non_existent_read_requests_wrong_next_pending_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Invalid next pending value index")] + fn test_reset_non_existent_read_requests_wrong_max_next_pending_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [3]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Next pending value index out of bounds")] + fn test_reset_non_existent_read_requests_overflown_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [4]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr new file mode 100644 index 00000000000..cc76145b18e --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr @@ -0,0 +1,28 @@ +use crate::non_existent_read_request_reset::{NonMembershipHint}; +use dep::types::{ + abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash}, + merkle_tree::{MembershipWitness}, + constants::{MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT} +}; + +struct NullifierNonMembershipHint { + low_leaf_preimage: NullifierLeafPreimage, + membership_witness: MembershipWitness, +} + +impl NonMembershipHint for NullifierNonMembershipHint { + fn low_leaf_preimage(self) -> NullifierLeafPreimage { + self.low_leaf_preimage + } + + fn membership_witness(self) -> MembershipWitness { + self.membership_witness + } +} + +struct NullifierNonExistentReadRequestHints { + non_membership_hints: [NullifierNonMembershipHint; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], + sorted_pending_values: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], + sorted_pending_value_index_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], + next_pending_value_indices: [u64; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr index 6524089d74a..6a122e57a2d 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr @@ -2,8 +2,9 @@ use crate::read_request_reset::{PendingReadHint, ReadRequestStatus, ReadValueHint, SettledReadHint}; use dep::std::unsafe; use dep::types::{ - abis::{membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage}, - constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT} + abis::{nullifier_leaf_preimage::NullifierLeafPreimage}, + constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT}, + merkle_tree::MembershipWitness }; struct NullifierSettledReadHint { @@ -36,49 +37,22 @@ impl SettledReadHint for Nullifier } } -struct NullifierReadRequestResetHints { +struct NullifierReadRequestHints { read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], pending_read_hints: [PendingReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX], settled_read_hints: [NullifierSettledReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX], } -struct NullifierReadRequestResetHintsBuilder { - read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], - pending_read_hints: BoundedVec, - settled_read_hints: BoundedVec, -} - -impl NullifierReadRequestResetHintsBuilder { - pub fn new(read_request_len: u64) -> Self { - NullifierReadRequestResetHintsBuilder { - read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX], - pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 }, - settled_read_hints: BoundedVec { - storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], - len: 0 - } - } - } - - pub fn to_hints(self) -> NullifierReadRequestResetHints { - NullifierReadRequestResetHints { - read_request_statuses: self.read_request_statuses, - pending_read_hints: self.pending_read_hints.storage, - settled_read_hints: self.settled_read_hints.storage - } - } -} - mod tests { use crate::nullifier_read_request_reset::NullifierSettledReadHint; use crate::read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus, reset_read_requests}; use dep::types::{ address::AztecAddress, abis::{ - membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage, - read_request::ReadRequestContext, side_effect::SideEffect + nullifier_leaf_preimage::NullifierLeafPreimage, read_request::ReadRequestContext, + side_effect::SideEffect }, - constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier, + constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier, merkle_tree::MembershipWitness, tests::merkle_tree_utils::NonEmptyMerkleTree }; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr index 2c50bcd46d7..1432df9ef18 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr @@ -1,8 +1,7 @@ // This will be moved to a separate Read Request Reset Circuit. use dep::types::{ - abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::OrderedValue}, - hash::{silo_nullifier, root_from_sibling_path}, merkle_tree::leaf_preimage::LeafPreimage, - traits::{Empty, is_empty} + abis::{read_request::ReadRequestContext, side_effect::OrderedValue}, hash::{silo_nullifier}, + merkle_tree::{assert_check_membership, LeafPreimage, MembershipWitness}, traits::{Empty, is_empty} }; struct ReadRequestStateEnum { @@ -84,7 +83,7 @@ fn validate_pending_read_requests( read_requests: [ReadRequestContext; READ_REQUEST_LEN], hints: [H; NUM_SETTLED_READS], - historical_tree_root: Field + tree_root: Field ) where H: SettledReadHint + ReadValueHint, LEAF_PREIMAGE: LeafPreimage { @@ -99,8 +98,7 @@ fn validate_settled_read_requests BoundedVec where P: OrderedValue, H: SettledReadHint + ReadValueHint, LEAF_PREIMAGE: LeafPreimage { validate_pending_read_requests(read_requests, pending_values, pending_read_hints); - validate_settled_read_requests(read_requests, settled_read_hints, historical_tree_root); + validate_settled_read_requests(read_requests, settled_read_hints, tree_root); propagate_unverified_read_requests( read_requests, @@ -163,9 +161,8 @@ mod tests { }; use dep::std::{hash::pedersen_hash, unsafe}; use dep::types::{ - address::AztecAddress, - abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::SideEffect}, - merkle_tree::leaf_preimage::LeafPreimage, hash::silo_nullifier, + address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect}, + merkle_tree::{LeafPreimage, MembershipWitness}, hash::silo_nullifier, tests::merkle_tree_utils::NonEmptyMerkleTree }; @@ -309,7 +306,7 @@ mod tests { validate_settled_read_requests(read_requests, hints, tree_root); } - #[test(should_fail_with="Tree root mismatch for read request")] + #[test(should_fail_with="membership check failed")] fn test_validate_settled_read_requests_wrong_witness_fails() { let (settled_hints, tree_root) = get_settled_read_hints(); let mut hint = settled_hints[0]; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr new file mode 100644 index 00000000000..9ecd400c180 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr @@ -0,0 +1,2 @@ +mod nullifier_non_existent_read_request_hints_builder; +mod nullifier_read_request_hints_builder; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr new file mode 100644 index 00000000000..d4de21463a7 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr @@ -0,0 +1,80 @@ +use crate::nullifier_non_existent_read_request_reset::{NullifierNonMembershipHint, NullifierNonExistentReadRequestHints}; +use dep::types::{ + abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash}, + constants::{ + MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT +}, + merkle_tree::MembershipWitness, + tests::{merkle_tree_utils::NonEmptyMerkleTree, sort::sort_get_sorted_hints}, + utils::{arrays::find_index, field::full_field_greater_than} +}; +use dep::std::unsafe; + +struct NullifierNonExistentReadRequestHintsBuilder { + nullifier_tree: NonEmptyMerkleTree, + non_membership_hints: BoundedVec, + read_values: BoundedVec, + pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], +} + +impl NullifierNonExistentReadRequestHintsBuilder { + pub fn new() -> Self { + NullifierNonExistentReadRequestHintsBuilder { + nullifier_tree: unsafe::zeroed(), + non_membership_hints: BoundedVec::new(), + read_values: BoundedVec::new(), + pending_nullifiers: [SideEffectLinkedToNoteHash::empty(); MAX_NEW_NULLIFIERS_PER_TX] + } + } + + pub fn set_nullifier_tree( + &mut self, + tree: NonEmptyMerkleTree + ) { + self.nullifier_tree = tree; + } + + pub fn set_nullifiers( + &mut self, + nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] + ) { + self.pending_nullifiers = nullifiers; + } + + pub fn add_value_read(&mut self, siloed_value: Field) { + self.read_values.push(siloed_value); + + // There are only two pre-existing nullifiers in the tree: [0, 100], generated in public_kernel_tail::tests. + // Assuming the siloed_value is always greater than 100. + let hint = NullifierNonMembershipHint { + low_leaf_preimage: NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 }, + membership_witness: MembershipWitness { leaf_index: 1, sibling_path: self.nullifier_tree.get_sibling_path(1) } + }; + self.non_membership_hints.push(hint); + } + + pub fn to_hints(self) -> NullifierNonExistentReadRequestHints { + let sorted_result = sort_get_sorted_hints( + self.pending_nullifiers, + |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) + ); + let sorted_pending_values = sorted_result.sorted_array; + let sorted_pending_value_index_hints = sorted_result.sorted_index_hints; + + let mut next_pending_value_indices = [0; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX]; + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX { + if i < self.read_values.len() { + let value = self.read_values.get_unchecked(i); + next_pending_value_indices[i] = find_index(sorted_pending_values, |v: SideEffectLinkedToNoteHash| !v.value.lt(value)); + } + } + + NullifierNonExistentReadRequestHints { + non_membership_hints: self.non_membership_hints.storage, + sorted_pending_values, + sorted_pending_value_index_hints, + next_pending_value_indices + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr new file mode 100644 index 00000000000..355106e7978 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr @@ -0,0 +1,32 @@ +use crate::{ + nullifier_read_request_reset::{NullifierSettledReadHint, NullifierReadRequestHints}, + read_request_reset::{PendingReadHint, ReadRequestStatus} +}; +use dep::types::constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX; + +struct NullifierReadRequestHintsBuilder { + read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], + pending_read_hints: BoundedVec, + settled_read_hints: BoundedVec, +} + +impl NullifierReadRequestHintsBuilder { + pub fn new(read_request_len: u64) -> Self { + NullifierReadRequestHintsBuilder { + read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX], + pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 }, + settled_read_hints: BoundedVec { + storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], + len: 0 + } + } + } + + pub fn to_hints(self) -> NullifierReadRequestHints { + NullifierReadRequestHints { + read_request_statuses: self.read_request_statuses, + pending_read_hints: self.pending_read_hints.storage, + settled_read_hints: self.settled_read_hints.storage + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr index b554d16bc9e..b06afdce361 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -9,10 +9,7 @@ use crate::{ use dep::types::{ abis::{ append_only_tree_snapshot::AppendOnlyTreeSnapshot, - membership_witness::{ - ArchiveRootMembershipWitness, MembershipWitness, NullifierMembershipWitness, - PublicDataMembershipWitness -}, + membership_witness::{ArchiveRootMembershipWitness, NullifierMembershipWitness, PublicDataMembershipWitness}, nullifier_leaf_preimage::NullifierLeafPreimage, public_data_update_request::PublicDataUpdateRequest, public_data_read::PublicDataRead, kernel_data::RollupKernelData, side_effect::{SideEffect, SideEffectLinkedToNoteHash}, accumulated_data::CombinedAccumulatedData @@ -29,8 +26,10 @@ use dep::types::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX }, - hash::assert_check_membership, - merkle_tree::{append_only_tree, calculate_empty_tree_root, calculate_subtree, indexed_tree}, + merkle_tree::{ + append_only_tree, assert_check_membership, calculate_empty_tree_root, calculate_subtree_root, + indexed_tree, MembershipWitness +}, mocked::{AggregationObject, Proof}, partial_state_reference::PartialStateReference, public_data_tree_leaf::PublicDataTreeLeaf, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, @@ -118,7 +117,7 @@ impl BaseRollupInputs { // TODO(Kev): This should say calculate_commitments_subtree_root // Cpp code says calculate_commitments_subtree, so I'm leaving it as is for now fn calculate_commitments_subtree(self) -> Field { - calculate_subtree(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value)) + calculate_subtree_root(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value)) } fn check_nullifier_tree_non_membership_and_insert_to_tree(self) -> AppendOnlyTreeSnapshot { @@ -137,9 +136,6 @@ impl BaseRollupInputs { } } ), - |a: Field, b: Field| {a == b}, // Nullifier equals - |nullifier: Field| {nullifier == 0}, // Nullifier is zero - |leaf: NullifierLeafPreimage| {leaf.hash()}, // Hash leaf |low_leaf: NullifierLeafPreimage, nullifier: Field| { // Is valid low leaf let is_less_than_nullifier = full_field_less_than(low_leaf.nullifier, nullifier); let is_next_greater_than = full_field_less_than(nullifier, low_leaf.next_nullifier); @@ -169,7 +165,7 @@ impl BaseRollupInputs { } fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field { - calculate_subtree(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) + calculate_subtree_root(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) } fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot { @@ -265,9 +261,6 @@ fn insert_public_data_update_requests( } } ), - |a: PublicDataTreeLeaf, b: PublicDataTreeLeaf| a.eq(b), // PublicDataTreeLeaf equals - |write: PublicDataTreeLeaf| write.is_empty(), // PublicDataTreeLeaf is_empty - |preimage: PublicDataTreeLeafPreimage| preimage.hash(), // Hash preimage |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { // Is valid low preimage let is_update = low_preimage.slot == write.slot; let is_low_empty = low_preimage.is_empty(); @@ -406,12 +399,11 @@ mod tests { MAX_NEW_L2_TO_L1_MSGS_PER_TX }, contract_class_id::ContractClassId, partial_state_reference::PartialStateReference, - hash::assert_check_membership, merkle_tree::{calculate_empty_tree_root, calculate_subtree}, public_data_tree_leaf::PublicDataTreeLeaf, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{ kernel_data_builder::PreviousKernelDataBuilder, - merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes} + merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}, sort::sort_high_to_low }, utils::{field::full_field_less_than, uint256::U256} }; @@ -421,28 +413,10 @@ mod tests { value: Field, } - struct SortedTuple { - value: T, - original_index: u64, - } - global MAX_NEW_NULLIFIERS_PER_TEST = 4; global MAX_PUBLIC_DATA_WRITES_PER_TEST = 2; global MAX_PUBLIC_DATA_READS_PER_TEST = 2; - fn sort_high_to_low(values: [T; N], is_less_than: fn(T, T) -> bool) -> [SortedTuple; N] where T: Eq { - let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; - - for i in 0..N { - sorted_tuples[i] = SortedTuple { - value: values[i], - original_index: i, - }; - } - - sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value)) - } - fn update_public_data_tree( public_data_tree: &mut NonEmptyMerkleTree, kernel_data: &mut RollupKernelData, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr index 623014f668c..508e4d9ed44 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr @@ -9,7 +9,7 @@ use dep::types::{ L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, ARCHIVE_HEIGHT }, header::Header, content_commitment::ContentCommitment, - merkle_tree::{append_only_tree, calculate_subtree, calculate_empty_tree_root}, + merkle_tree::{append_only_tree, calculate_subtree_root, calculate_empty_tree_root}, state_reference::StateReference }; @@ -41,7 +41,7 @@ impl RootRollupInputs { // Check correct l1 to l2 tree given // Compute subtree inserting l1 to l2 messages - let l1_to_l2_subtree_root = calculate_subtree(self.new_l1_to_l2_messages); + let l1_to_l2_subtree_root = calculate_subtree_root(self.new_l1_to_l2_messages); // Insert subtree into the l1 to l2 data tree let empty_l1_to_l2_subtree_root = calculate_empty_tree_root(L1_TO_L2_MSG_SUBTREE_HEIGHT); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr index f138d7e1f26..3499620d06c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr @@ -12,7 +12,8 @@ use crate::{ use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX }; struct AccumulatedNonRevertibleDataBuilder { @@ -20,6 +21,7 @@ struct AccumulatedNonRevertibleDataBuilder { new_nullifiers: BoundedVec, public_call_stack: BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, public_data_update_requests: BoundedVec, public_data_reads: BoundedVec, } @@ -38,6 +40,7 @@ impl AccumulatedNonRevertibleDataBuilder { new_nullifiers: self.new_nullifiers.storage, public_call_stack: self.public_call_stack.storage, nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, public_data_update_requests: self.public_data_update_requests.storage, public_data_reads: self.public_data_reads.storage } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr index 697c307e656..c8175fbd7c0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr @@ -8,7 +8,8 @@ use crate::{ use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX }; use dep::std::unsafe; @@ -21,6 +22,7 @@ struct PublicAccumulatedNonRevertibleData { new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX], public_call_stack: [CallRequest; MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX], nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX], + nullifier_non_existent_read_requests: [ReadRequestContext; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], public_data_update_requests: [PublicDataUpdateRequest; MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], public_data_reads: [PublicDataRead; MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX], } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr index 954cb1b69a0..2b310a2ae87 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr @@ -13,6 +13,7 @@ use crate::constants::{ MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX }; +// TODO - Requests for checking data should not be revertible. struct PublicAccumulatedRevertibleData { note_hash_read_requests: [SideEffect; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr index 488272527c3..d23cfb0f19a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr @@ -3,11 +3,6 @@ use crate::constants::{ ARCHIVE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT }; -struct MembershipWitness { - leaf_index: Field, - sibling_path: [Field; N] -} - // TODO(Kev): Instead of doing `MembershipWitness` we are forced // to do this new struct because the typescript bindings generator // does not have logic to monomorphize these properly. See the file named diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr index 6eb484e5d66..895196567a0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr @@ -1,6 +1,6 @@ global NULLIFIER_LEAF_PREIMAGE_LENGTH: u64 = 3; -use crate::{merkle_tree::leaf_preimage::LeafPreimage, traits::{Empty, Hash}}; +use crate::{merkle_tree::leaf_preimage::{LeafPreimage, IndexedTreeLeafPreimage}, traits::{Empty, Hash}}; struct NullifierLeafPreimage { nullifier : Field, @@ -38,6 +38,20 @@ impl LeafPreimage for NullifierLeafPreimage { } } +impl IndexedTreeLeafPreimage for NullifierLeafPreimage { + fn get_key(self) -> Field { + self.nullifier + } + + fn get_next_key(self) -> Field { + self.next_nullifier + } + + fn as_leaf(self) -> Field { + self.hash() + } +} + impl NullifierLeafPreimage { pub fn is_empty(self) -> bool { (self.nullifier == 0) & (self.next_nullifier == 0) & (self.next_index == 0) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr index 0b9dabb2db7..3a9181be985 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr @@ -69,7 +69,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: true, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item request hash" test - let test_data_call_stack_item_request_hash = 0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307; + let test_data_call_stack_item_request_hash = 0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83; assert_eq(call_stack_item.hash(), test_data_call_stack_item_request_hash); } @@ -87,7 +87,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: false, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item hash" test - let test_data_call_stack_item_hash = 0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9; + let test_data_call_stack_item_hash = 0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d; assert_eq(call_stack_item.hash(), test_data_call_stack_item_hash); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr index b8f44a158ad..e364c3b49d8 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr @@ -6,10 +6,10 @@ use crate::{ address::AztecAddress, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, - RETURN_VALUES_LENGTH, GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, - PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH, + GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::pedersen_hash, header::Header, messaging::l2_to_l1_message::L2ToL1Message, @@ -23,6 +23,7 @@ struct PublicCircuitPublicInputs{ return_values: [Field; RETURN_VALUES_LENGTH], nullifier_read_requests: [ReadRequest; MAX_NULLIFIER_READ_REQUESTS_PER_CALL], + nullifier_non_existent_read_requests: [ReadRequest; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead; MAX_PUBLIC_DATA_READS_PER_CALL], @@ -62,6 +63,9 @@ impl Serialize for PublicCircuitPublicInput for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL { fields.extend_from_array(self.nullifier_read_requests[i].serialize()); } + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL { + fields.extend_from_array(self.nullifier_non_existent_read_requests[i].serialize()); + } for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL { fields.extend_from_array(self.contract_storage_update_requests[i].serialize()); } @@ -97,6 +101,7 @@ impl Deserialize for PublicCircuitPublicInp args_hash: reader.read(), return_values: reader.read_array([0; RETURN_VALUES_LENGTH]), nullifier_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL]), + nullifier_non_existent_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL]), contract_storage_update_requests: reader.read_struct_array(StorageUpdateRequest::deserialize, [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL]), contract_storage_reads: reader.read_struct_array(StorageRead::deserialize, [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL]), public_call_stack_hashes: reader.read_array([0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL]), @@ -135,6 +140,6 @@ fn empty_hash() { let hash = inputs.hash(); // Value from public_circuit_public_inputs.test.ts "computes empty item hash" test - let test_data_empty_hash = 0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9; + let test_data_empty_hash = 0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9; assert_eq(hash, test_data_empty_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 765252a0bb5..60e48560bba 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -32,6 +32,7 @@ global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL: u64 = 16; global MAX_PUBLIC_DATA_READS_PER_CALL: u64 = 16; global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u64 = 32; global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u64 = 2; // Change it to a larger value when there's a seperate reset circuit. +global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL: u64 = 2; global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL: u64 = 1; // "PER TRANSACTION" CONSTANTS @@ -60,6 +61,7 @@ global MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX: u64 = 16; global MAX_NEW_L2_TO_L1_MSGS_PER_TX: u64 = 2; global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u64 = 128; global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u64 = 8; // Change it to a larger value when there's a seperate reset circuit. +global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX: u64 = 8; global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX: u64 = 4; global NUM_ENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; global NUM_UNENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; @@ -167,7 +169,7 @@ global PRIVATE_CALL_STACK_ITEM_LENGTH: u64 = 214; // constant as well PRIVATE_CALL_STACK_ITEM_LENGTH global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 209; // Change this ONLY if you have changed the PublicCircuitPublicInputs structure. -global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 196; +global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 200; global STATE_REFERENCE_LENGTH: u64 = 8; // 2 for snap + 8 for partial global TX_CONTEXT_DATA_LENGTH: u64 = 4; global TX_REQUEST_LENGTH: u64 = 10; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index 0afb6ea78a9..7de5fa7d536 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -14,6 +14,7 @@ use crate::constants::{ GENERATOR_INDEX__NOTE_HASH_NONCE, GENERATOR_INDEX__UNIQUE_NOTE_HASH, GENERATOR_INDEX__FUNCTION_ARGS }; use crate::messaging::l2_to_l1_message::L2ToL1Message; +use crate::merkle_tree::root::root_from_sibling_path; use dep::std::hash::{pedersen_hash_with_separator, sha256}; @@ -61,38 +62,6 @@ pub fn hash_args(args: [Field; N]) -> Field { } } -// Checks that `value` is a member of a merkle tree with root `root` at position `index` -// The witness being the `sibling_path` -pub fn assert_check_membership(value: Field, index: Field, sibling_path: [Field; N], root: Field) { - let calculated_root = root_from_sibling_path(value, index, sibling_path); - assert(calculated_root == root, "membership check failed"); -} - -// Calculate the Merkle tree root from the sibling path and leaf. -// -// The leaf is hashed with its sibling, and then the result is hashed -// with the next sibling etc in the path. The last hash is the root. -// -// TODO(David/Someone): The cpp code is using a uint256, whereas its -// TODO a bit simpler in Noir to just have a bit array. -// TODO: I'd generally like to avoid u256 for algorithms like -// this because it means we never even need to consider cases where -// the index is greater than p. -pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field { - let mut node = leaf; - let indices = leaf_index.to_le_bits(N); - - for i in 0..N { - let (hash_left, hash_right) = if indices[i] == 1 { - (sibling_path[i], node) - } else { - (node, sibling_path[i]) - }; - node = merkle_hash(hash_left, hash_right); - } - node -} - // Calculate the function tree root from the sibling path and leaf preimage. // // TODO: The cpp code passes in components of the FunctionLeafPreimage and then @@ -148,7 +117,7 @@ pub fn silo_nullifier(address: AztecAddress, nullifier: Field) -> Field { ) } -fn merkle_hash(left: Field, right: Field) -> Field { +pub fn merkle_hash(left: Field, right: Field) -> Field { pedersen_hash([left, right], 0) } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr index ecd76abb5ff..67b23d3f449 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr @@ -1,110 +1,14 @@ mod append_only_tree; mod indexed_tree; mod leaf_preimage; - -struct MerkleTree { - leaves: [Field; N], - nodes: [Field; N], -} - -impl MerkleTree { - pub fn new(leaves: [Field; N]) -> Self { - let mut nodes = [0; N]; - - // We need one less node than leaves, but we cannot have computed array lengths - let total_nodes = N - 1; - let half_size = N / 2; - - // hash base layer - for i in 0..half_size { - nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]); - } - - // hash the other layers - for i in 0..(total_nodes - half_size) { - nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]); - } - - MerkleTree { leaves, nodes } - } - - fn get_root(self) -> Field { - self.nodes[N - 2] - } -} - -pub fn calculate_subtree(leaves: [Field; N]) -> Field { - MerkleTree::new(leaves).get_root() -} - -// These values are precomputed and we run tests to ensure that they -// are correct. The values themselves were computed from the cpp code. -// -// Would be good if we could use width since the compute_subtree -// algorithm uses depth. -pub fn calculate_empty_tree_root(depth: u64) -> Field { - if depth == 0 { - 0 - } else if depth == 1 { - 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed - } else if depth == 2 { - 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550 - } else if depth == 3 { - 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb - } else if depth == 4 { - 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d - } else if depth == 5 { - 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0 - } else if depth == 6 { - 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f - } else if depth == 7 { - 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab - } else if depth == 8 { - 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257 - } else if depth == 9 { - 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9 - } else if depth == 10 { - 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02 - } else { - assert(false, "depth should be between 0 and 10"); - 0 - } -} - -#[test] -fn test_merkle_root_interop_test() { - // This is a test to ensure that we match the cpp implementation. - // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)` - // to find the test that matches this. - let root = calculate_subtree([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); - assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root); - - let empty_root = calculate_subtree([0; 16]); - assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root); -} - -#[test] -fn test_empty_subroot() { - assert(calculate_empty_tree_root(0) == 0); - - let expected_empty_root_2 = calculate_subtree([0; 2]); - assert(calculate_empty_tree_root(1) == expected_empty_root_2); - - let expected_empty_root_4 = calculate_subtree([0; 4]); - assert(calculate_empty_tree_root(2) == expected_empty_root_4); - - let expected_empty_root_8 = calculate_subtree([0; 8]); - assert(calculate_empty_tree_root(3) == expected_empty_root_8); - - let expected_empty_root_16 = calculate_subtree([0; 16]); - assert(calculate_empty_tree_root(4) == expected_empty_root_16); - - let expected_empty_root_32 = calculate_subtree([0; 32]); - assert(calculate_empty_tree_root(5) == expected_empty_root_32); - - let expected_empty_root_64 = calculate_subtree([0; 64]); - assert(calculate_empty_tree_root(6) == expected_empty_root_64); - - let expected_empty_root_128 = calculate_subtree([0; 128]); - assert(calculate_empty_tree_root(7) == expected_empty_root_128); -} +mod membership; +mod merkle_tree; +mod root; + +use leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}; +use membership::{ + assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, + MembershipWitness +}; +use merkle_tree::MerkleTree; +use root::{calculate_empty_tree_root, calculate_subtree_root, root_from_sibling_path}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr index 18145d3d233..b1faf2c06c7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr @@ -1,6 +1,6 @@ use crate::{ abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot}, - hash::{assert_check_membership, root_from_sibling_path} + merkle_tree::{membership::assert_check_membership, root::root_from_sibling_path} }; pub fn insert_subtree_to_snapshot_tree( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr index 9c291900061..6cfc75baaf6 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr @@ -1,53 +1,12 @@ use crate::{ - abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, membership_witness::MembershipWitness}, - hash::{assert_check_membership, root_from_sibling_path}, - merkle_tree::{calculate_subtree, calculate_empty_tree_root} + abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot}, + merkle_tree::{ + membership::{assert_check_membership, MembershipWitness}, + root::{calculate_subtree_root, calculate_empty_tree_root, root_from_sibling_path} +}, + traits::{Empty, Hash, is_empty}, utils::arrays::check_permutation }; -fn check_permutation( - original_array: [T; N], - sorted_array: [T; N], - indexes: [u64; N], - is_equal: fn(T, T) -> bool -) { - let mut seen_value = [false; N]; - for i in 0..N { - let index = indexes[i]; - let sorted_value = sorted_array[i]; - let original_value = original_array[index]; - assert(is_equal(sorted_value, original_value), "Invalid index"); - assert(!seen_value[index], "Duplicated index"); - seen_value[index] = true; - } -} - -#[test] -fn check_permutation_basic_test() { - let original_array = [1, 2, 3]; - let sorted_array = [3, 1, 2]; - let indexes = [2, 0, 1]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - -#[test(should_fail_with = "Duplicated index")] -fn check_permutation_duplicated_index() { - let original_array = [0, 1, 0]; - let sorted_array = [1, 0, 0]; - let indexes = [1, 0, 0]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - -#[test(should_fail_with = "Invalid index")] -fn check_permutation_invalid_index() { - let original_array = [0, 1, 2]; - let sorted_array = [1, 0, 0]; - let indexes = [1, 0, 2]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - pub fn batch_insert( start_snapshot: AppendOnlyTreeSnapshot, values_to_insert: [Value; SubtreeWidth], @@ -56,22 +15,14 @@ pub fn batch_insert; SubtreeWidth], - is_equal: fn(Value, Value) -> bool, - is_empty_value: fn(Value) -> bool, - hash_leaf: fn(Leaf) -> Field, is_valid_low_leaf: fn(Leaf, Value) -> bool, update_low_leaf: fn(Leaf, Value, u64) -> Leaf, build_insertion_leaf: fn(Value, Leaf) -> Leaf, _subtree_height: [Field; SubtreeHeight], _tree_height: [Field; TreeHeight] -) -> AppendOnlyTreeSnapshot { +) -> AppendOnlyTreeSnapshot where Value: Eq + Empty, Leaf: Hash { // A permutation to the values is provided to make the insertion use only one insertion strategy - check_permutation( - values_to_insert, - sorted_values, - sorted_values_indexes, - is_equal - ); + check_permutation(values_to_insert, sorted_values, sorted_values_indexes); // Now, update the existing leaves with the new leaves let mut current_tree_root = start_snapshot.root; @@ -80,7 +31,7 @@ pub fn batch_insert Field; fn as_leaf(self) -> Field; } + +trait IndexedTreeLeafPreimage { + fn get_key(self) -> Field; + fn get_next_key(self) -> Field; + fn as_leaf(self) -> Field; +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr new file mode 100644 index 00000000000..6fc8d91d13b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr @@ -0,0 +1,343 @@ +use crate::{merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, root::root_from_sibling_path}}; + +struct MembershipWitness { + leaf_index: Field, + sibling_path: [Field; N] +} + +pub fn check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) -> bool { + let calculated_root = root_from_sibling_path(leaf, index, sibling_path); + calculated_root == root +} + +pub fn assert_check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) { + assert(check_membership(leaf, index, sibling_path, root), "membership check failed"); +} + +struct NonMembershipCheckErrorCodeEnum { + NADA: u64, + IS_EMPTY: u64, + NOT_EXISTS: u64, + NOT_GREATER_THAN_LOW: u64, + NOT_LESS_THAN_NEXT: u64, +} + +global NonMembershipCheckErrorCode = NonMembershipCheckErrorCodeEnum { + NADA: 0, + IS_EMPTY: 1, + NOT_EXISTS: 2, + NOT_GREATER_THAN_LOW: 3, + NOT_LESS_THAN_NEXT: 4, +}; + +fn check_non_membership_internal( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) -> u64 where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let low_key = low_leaf_preimage.get_key(); + let next_key = low_leaf_preimage.get_next_key(); + let is_empty_leaf = (low_key == 0) & (next_key == 0); + + let low_leaf_exists = check_membership( + low_leaf_preimage.as_leaf(), + low_leaf_membership_witness.leaf_index, + low_leaf_membership_witness.sibling_path, + tree_root + ); + + if is_empty_leaf { + NonMembershipCheckErrorCode.IS_EMPTY + } else if !low_leaf_exists { + NonMembershipCheckErrorCode.NOT_EXISTS + } else if !low_key.lt(key) { + NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW + } else if !key.lt(next_key) & (next_key != 0) { + NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT + } else { + NonMembershipCheckErrorCode.NADA + } +} + +pub fn check_non_membership( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) -> bool where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); + error == NonMembershipCheckErrorCode.NADA +} + +pub fn assert_check_non_membership( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); + if error != NonMembershipCheckErrorCode.NADA { + assert( + error != NonMembershipCheckErrorCode.IS_EMPTY, "Cannot check non membership against empty leaf" + ); + assert(error != NonMembershipCheckErrorCode.NOT_EXISTS, "Low leaf does not exist"); + assert( + error != NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW, "Key is not greater than the low leaf" + ); + assert( + error != NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT, "Key is not less than the next leaf" + ); + assert(false, "Unknown error"); + } +} + +mod tests { + use crate::{ + merkle_tree::{ + leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}, + membership::{ + assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, + MembershipWitness + } + }, + tests::merkle_tree_utils::NonEmptyMerkleTree + }; + use dep::std::hash::pedersen_hash; + + struct TestLeafPreimage { + value: Field, + next_value: Field, + } + + impl LeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn as_leaf(self) -> Field { + pedersen_hash([self.value]) + } + } + + impl IndexedTreeLeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn get_next_key(self) -> Field { + self.next_value + } + + fn as_leaf(self) -> Field { + pedersen_hash([self.value]) + } + } + + global leaf_preimages = [ + TestLeafPreimage { value: 20, next_value: 30 }, + TestLeafPreimage { value: 40, next_value: 0 }, + TestLeafPreimage { value: 10, next_value: 20 }, + TestLeafPreimage { value: 30, next_value: 40 }, + ]; + + fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> { + NonEmptyMerkleTree::new( + leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()), + [0; 3], + [0; 1], + [0; 2] + ) + } + + fn check_membership_at_index(leaf_index: Field, leaf: Field) -> bool { + let tree = build_tree(); + let tree_root = tree.get_root(); + + check_membership( + leaf, + leaf_index, + tree.get_sibling_path(leaf_index as u64), + tree_root + ) + } + + fn assert_check_membership_at_index(leaf_index: Field, leaf: Field) { + let tree = build_tree(); + let tree_root = tree.get_root(); + + assert_check_membership( + leaf, + leaf_index, + tree.get_sibling_path(leaf_index as u64), + tree_root + ); + } + + fn check_non_membership_at_index(low_leaf_index: u64, leaf: Field) -> bool { + let tree = build_tree(); + let tree_root = tree.get_root(); + let leaf_preimage = if low_leaf_index < leaf_preimages.len() { + leaf_preimages[low_leaf_index] + } else { + TestLeafPreimage { value: 0, next_value: 0 } + }; + + check_non_membership( + leaf, + leaf_preimage, + MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , + tree_root + ) + } + + fn assert_check_non_membership_at_index(low_leaf_index: u64, leaf: Field) { + let tree = build_tree(); + let tree_root = tree.get_root(); + let leaf_preimage = if low_leaf_index < leaf_preimages.len() { + leaf_preimages[low_leaf_index] + } else { + TestLeafPreimage { value: 0, next_value: 0 } + }; + + assert_check_non_membership( + leaf, + leaf_preimage, + MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , + tree_root + ); + } + + #[test] + fn test_check_membership() { + assert_eq(check_membership_at_index(0, leaf_preimages[0].as_leaf()), true); + assert_eq(check_membership_at_index(2, leaf_preimages[2].as_leaf()), true); + } + + #[test] + fn test_assert_check_membership() { + assert_check_membership_at_index(0, leaf_preimages[0].as_leaf()); + assert_check_membership_at_index(2, leaf_preimages[2].as_leaf()); + } + + #[test] + fn test_check_membership_false_wrong_leaf() { + assert_eq(check_membership_at_index(0, leaf_preimages[1].as_leaf()), false); + assert_eq(check_membership_at_index(2, leaf_preimages[0].as_leaf()), false); + } + + #[test(should_fail_with="membership check failed")] + fn test_assert_check_membership_failed_wrong_leaf() { + assert_check_membership_at_index(0, leaf_preimages[1].as_leaf()); + } + + #[test] + fn test_check_membership_false_wrong_root() { + let tree = build_tree(); + let tree_root = 56; + + let res = check_membership( + leaf_preimages[0].as_leaf(), + 0, + tree.get_sibling_path(0), + tree_root + ); + assert_eq(res, false); + } + + #[test(should_fail_with="membership check failed")] + fn test_assert_check_membership_false_wrong_root() { + let tree = build_tree(); + let tree_root = 56; + + assert_check_membership( + leaf_preimages[0].as_leaf(), + 0, + tree.get_sibling_path(0), + tree_root + ); + } + + #[test] + fn test_check_non_membership() { + assert_eq(check_non_membership_at_index(0, 25), true); + } + + #[test] + fn test_assert_check_non_membership() { + assert_check_non_membership_at_index(0, 25); + } + + #[test] + fn test_check_non_membership_greater_than_max() { + assert_eq(check_non_membership_at_index(1, 45), true); + } + + #[test] + fn test_assert_check_non_membership_greater_than_max() { + assert_check_non_membership_at_index(1, 45); + } + + #[test] + fn test_check_non_membership_false_empty_leaf() { + assert_eq(check_non_membership_at_index(4, 25), false); + } + + #[test(should_fail_with="Cannot check non membership against empty leaf")] + fn test_assert_check_non_membership_failed_empty_leaf() { + assert_check_non_membership_at_index(4, 25); + } + + #[test] + fn test_check_non_membership_false_wrong_low_leaf() { + assert_eq(check_non_membership_at_index(3, 25), false); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_assert_check_non_membership_failed_wrong_low_leaf() { + assert_check_non_membership_at_index(3, 25); + } + + #[test] + fn test_check_non_membership_false_wrong_next_key() { + assert_eq(check_non_membership_at_index(2, 25), false); + } + + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_assert_check_non_membership_failed_wrong_next_key() { + assert_check_non_membership_at_index(2, 25); + } + + #[test] + fn test_check_non_membership_false_invalid_leaf() { + let tree = build_tree(); + let tree_root = tree.get_root(); + + let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; + assert_eq( + check_non_membership( + 55, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root + ), false + ); + } + + #[test(should_fail_with="Low leaf does not exist")] + fn test_assert_check_non_membership_failed_invalid_leaf() { + let tree = build_tree(); + let tree_root = tree.get_root(); + + let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; + assert_check_non_membership( + 55, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr new file mode 100644 index 00000000000..f1cacb956ac --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr @@ -0,0 +1,31 @@ +struct MerkleTree { + leaves: [Field; N], + nodes: [Field; N], +} + +impl MerkleTree { + pub fn new(leaves: [Field; N]) -> Self { + let mut nodes = [0; N]; + + // We need one less node than leaves, but we cannot have computed array lengths + let total_nodes = N - 1; + let half_size = N / 2; + + // hash base layer + for i in 0..half_size { + nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]); + } + + // hash the other layers + for i in 0..(total_nodes - half_size) { + nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]); + } + + MerkleTree { leaves, nodes } + } + + fn get_root(self) -> Field { + self.nodes[N - 2] + } +} + diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr new file mode 100644 index 00000000000..e659261fbdd --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr @@ -0,0 +1,102 @@ +use crate::{hash::merkle_hash, merkle_tree::merkle_tree::MerkleTree}; + +// Calculate the Merkle tree root from the sibling path and leaf. +// +// The leaf is hashed with its sibling, and then the result is hashed +// with the next sibling etc in the path. The last hash is the root. +// +// TODO(David/Someone): The cpp code is using a uint256, whereas its +// TODO a bit simpler in Noir to just have a bit array. +// TODO: I'd generally like to avoid u256 for algorithms like +// this because it means we never even need to consider cases where +// the index is greater than p. +pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field { + let mut node = leaf; + let indices = leaf_index.to_le_bits(N); + + for i in 0..N { + let (hash_left, hash_right) = if indices[i] == 1 { + (sibling_path[i], node) + } else { + (node, sibling_path[i]) + }; + node = merkle_hash(hash_left, hash_right); + } + node +} + +pub fn calculate_subtree_root(leaves: [Field; N]) -> Field { + MerkleTree::new(leaves).get_root() +} + +// These values are precomputed and we run tests to ensure that they +// are correct. The values themselves were computed from the cpp code. +// +// Would be good if we could use width since the compute_subtree +// algorithm uses depth. +pub fn calculate_empty_tree_root(depth: u64) -> Field { + if depth == 0 { + 0 + } else if depth == 1 { + 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed + } else if depth == 2 { + 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550 + } else if depth == 3 { + 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb + } else if depth == 4 { + 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d + } else if depth == 5 { + 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0 + } else if depth == 6 { + 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f + } else if depth == 7 { + 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab + } else if depth == 8 { + 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257 + } else if depth == 9 { + 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9 + } else if depth == 10 { + 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02 + } else { + assert(false, "depth should be between 0 and 10"); + 0 + } +} + +#[test] +fn test_merkle_root_interop_test() { + // This is a test to ensure that we match the cpp implementation. + // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)` + // to find the test that matches this. + let root = calculate_subtree_root([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); + assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root); + + let empty_root = calculate_subtree_root([0; 16]); + assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root); +} + +#[test] +fn test_empty_subroot() { + assert(calculate_empty_tree_root(0) == 0); + + let expected_empty_root_2 = calculate_subtree_root([0; 2]); + assert(calculate_empty_tree_root(1) == expected_empty_root_2); + + let expected_empty_root_4 = calculate_subtree_root([0; 4]); + assert(calculate_empty_tree_root(2) == expected_empty_root_4); + + let expected_empty_root_8 = calculate_subtree_root([0; 8]); + assert(calculate_empty_tree_root(3) == expected_empty_root_8); + + let expected_empty_root_16 = calculate_subtree_root([0; 16]); + assert(calculate_empty_tree_root(4) == expected_empty_root_16); + + let expected_empty_root_32 = calculate_subtree_root([0; 32]); + assert(calculate_empty_tree_root(5) == expected_empty_root_32); + + let expected_empty_root_64 = calculate_subtree_root([0; 64]); + assert(calculate_empty_tree_root(6) == expected_empty_root_64); + + let expected_empty_root_128 = calculate_subtree_root([0; 128]); + assert(calculate_empty_tree_root(7) == expected_empty_root_128); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr index c02eefc2353..77f7f32bc6d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr @@ -6,3 +6,4 @@ mod private_call_data_builder; mod private_circuit_public_inputs_builder; mod public_call_data_builder; mod public_circuit_public_inputs_builder; +mod sort; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr index 6ab1f79d6f1..50769bbfbb4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr @@ -146,6 +146,16 @@ impl PreviousKernelDataBuilder { value_offset + nullifier_index as Field } + pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) { + let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier); + self.end.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() }); + } + + pub fn add_nullifier_non_revertible(&mut self, unsiloed_nullifier: Field) { + let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier); + self.end_non_revertible.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() }); + } + pub fn append_new_nullifiers_from_private(&mut self, num_extra_nullifier: u64) { // in private kernel, the nullifiers have not yet been partitioned // (that is part of the job of the private kernel tail) @@ -154,13 +164,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NEW_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value(index_offset + i); - self.end.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier(mock_value); } } } @@ -170,13 +174,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NEW_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value(index_offset + i); - self.end.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier(mock_value); } } } @@ -186,13 +184,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value_non_revertible(index_offset + i); - self.end_non_revertible.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier_non_revertible(mock_value); } } } @@ -221,6 +213,15 @@ impl PreviousKernelDataBuilder { read_request_index } + pub fn add_non_existent_read_request_for_nullifier(&mut self, unsiloed_nullifier: Field) { + let read_request = ReadRequestContext { + value: unsiloed_nullifier, + counter: self.next_sideffect_counter(), + contract_address: self.storage_contract_address + }; + self.end_non_revertible.nullifier_non_existent_read_requests.push(read_request); + } + // snapshot the side effects // this is useful in the private tail circuit to test side effect splitting pub fn capture_min_revertible_side_effect_counter(&mut self) { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr index ae74ec6bb73..d4bf5c3d294 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr @@ -9,9 +9,9 @@ use crate::{ }; use crate::constants::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, - RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }; struct PublicCircuitPublicInputsBuilder { @@ -19,6 +19,7 @@ struct PublicCircuitPublicInputsBuilder { args_hash: Field, return_values: BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -46,6 +47,7 @@ impl PublicCircuitPublicInputsBuilder { args_hash: self.args_hash, return_values: self.return_values.storage, nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, public_call_stack_hashes: self.public_call_stack_hashes.storage, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr new file mode 100644 index 00000000000..d067eb1a2d9 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr @@ -0,0 +1,87 @@ +use crate::traits::{Empty, is_empty}; + +struct SortedTuple { + value: T, + original_index: u64, +} + +pub fn sort_high_to_low( + values: [T; N], + is_less_than: fn(T, T) -> bool +) -> [SortedTuple; N] where T: Eq { + let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; + + for i in 0..N { + sorted_tuples[i] = SortedTuple { + value: values[i], + original_index: i, + }; + } + + sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value)) +} + +struct SortedResult { + sorted_array: [T; N], + sorted_index_hints: [u64; N], +} + +pub fn sort_get_sorted_hints( + values: [T; N], + ordering: fn(T, T) -> bool +) -> SortedResult where T: Eq + Empty { + let mut tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; + for i in 0..N { + tuples[i] = SortedTuple { + value: values[i], + original_index: i, + }; + } + + let sorted_tuples = tuples.sort_via( + |a: SortedTuple, b: SortedTuple| is_empty(b.value) | (!is_empty(a.value) & !is_empty(b.value) & ordering(a.value, b.value)) + ); + + let sorted_array = sorted_tuples.map(|t: SortedTuple| t.value); + let mut sorted_index_hints = [0; N]; + for i in 0..N { + if !is_empty(sorted_tuples[i].value) { + let original_index = sorted_tuples[i].original_index; + sorted_index_hints[original_index] = i; + } + } + + SortedResult { sorted_array, sorted_index_hints } +} + +#[test] +fn sort_get_sorted_hints_asc_non_padded() { + let values = [40, 60, 20, 50]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + assert_eq(res.sorted_array, [20, 40, 50, 60]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2]); +} + +#[test] +fn sort_get_sorted_hints_desc_non_padded() { + let values = [40, 20, 60, 50]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + assert_eq(res.sorted_array, [60, 50, 40, 20]); + assert_eq(res.sorted_index_hints, [2, 3, 0, 1]); +} + +#[test] +fn sort_get_sorted_hints_asc_padded() { + let values = [40, 60, 20, 50, 0, 0]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + assert_eq(res.sorted_array, [20, 40, 50, 60, 0, 0]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2, 0, 0]); +} + +#[test] +fn sort_get_sorted_hints_desc_padded() { + let values = [40, 20, 60, 50, 0, 0]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + assert_eq(res.sorted_array, [60, 50, 40, 20, 0, 0]); + assert_eq(res.sorted_index_hints, [2, 3, 0, 1, 0, 0]); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index ec5e186d412..73af12d2969 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -57,6 +57,16 @@ pub fn array_eq(array: [T; N], expected: [T; S]) -> bool where T: Empty eq } +pub fn find_index(array: [T; N], find: fn[Env](T) -> bool) -> u64 { + let mut index = N; + for i in 0..N { + if (index == N) & find(array[i]) { + index = i; + } + } + index +} + pub fn array_cp(array: [T; N]) -> [T; S] where T: Empty { let mut result: [T; S] = [T::empty(); S]; for i in 0..S { @@ -102,6 +112,45 @@ pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empt result } +pub fn check_permutation( + original_array: [T; N], + permuted_array: [T; N], + original_indexes: [u64; N] +) where T: Eq + Empty { + let mut seen_value = [false; N]; + for i in 0..N { + let index = original_indexes[i]; + let original_value = original_array[index]; + assert(permuted_array[i].eq(original_value), "Invalid index"); + assert(!seen_value[index], "Duplicated index"); + seen_value[index] = true; + } +} + +pub fn assert_sorted_array( + original_array: [T; N], + sorted_array: [T; N], + sorted_indexes: [u64; N], + ordering: fn[Env](T, T) -> bool +) where T: Eq + Empty { + let mut seen_empty = false; + for i in 0..N { + let original_value = original_array[i]; + if is_empty(original_value) { + seen_empty = true; + assert(is_empty(sorted_array[i]), "Empty values must not be mixed with sorted values"); + } else { + assert(!seen_empty, "Empty values must be padded to the right"); + + let index = sorted_indexes[i]; + assert(sorted_array[index].eq(original_value), "Invalid index"); + if i != 0 { + assert(ordering(sorted_array[i - 1], sorted_array[i]), "Values not sorted"); + } + } + } +} + #[test] fn smoke_validate_array() { let valid_array = []; @@ -147,3 +196,107 @@ fn test_array_length() { assert_eq(array_length([123, 0, 456]), 1); assert_eq(array_length([0, 123, 0, 456]), 0); } + +#[test] +fn find_index_greater_than_min() { + let values = [10, 20, 30, 40]; + let min = 22; + let index = find_index(values, |v: Field| min.lt(v)); + assert_eq(index, 2); +} + +#[test] +fn find_index_not_found() { + let values = [10, 20, 30, 40]; + let min = 100; + let index = find_index(values, |v: Field| min.lt(v)); + assert_eq(index, 4); +} + +#[test] +fn check_permutation_basic_test() { + let original_array = [1, 2, 3]; + let permuted_array = [3, 1, 2]; + let indexes = [2, 0, 1]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test(should_fail_with = "Duplicated index")] +fn check_permutation_duplicated_index() { + let original_array = [0, 1, 0]; + let permuted_array = [1, 0, 0]; + let indexes = [1, 0, 0]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test(should_fail_with = "Invalid index")] +fn check_permutation_invalid_index() { + let original_array = [0, 1, 2]; + let permuted_array = [1, 0, 0]; + let indexes = [1, 0, 2]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test] +fn assert_sorted_array_asc() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 90, 0, 0]; + let indexes = [1, 0, 3, 2, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test] +fn assert_sorted_array_desc() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [90, 50, 30, 20, 0, 0]; + let indexes = [2, 3, 0, 1, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| b.lt(a)); +} + +#[test] +fn assert_sorted_array_all_empty() { + let original = [0, 0, 0, 0, 0, 0]; + let sorted = [0, 0, 0, 0, 0, 0]; + let indexes = [0, 0, 0, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Values not sorted")] +fn assert_sorted_array_failed_ordering() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 90, 50, 0, 0]; + let indexes = [1, 0, 2, 3, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Values not sorted")] +fn assert_sorted_array_failed_misplaced_sorted() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 0, 0, 90]; + let indexes = [1, 0, 5, 2, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Invalid index")] +fn assert_sorted_array_failed_wrong_index() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 90, 0, 0]; + let indexes = [1, 1, 2, 3, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Empty values must be padded to the right")] +fn assert_sorted_array_failed_not_padded() { + let original = [30, 20, 90, 0, 50, 0]; + let sorted = [20, 30, 90, 0, 0, 0]; + let indexes = [1, 0, 2, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Empty values must not be mixed with sorted values")] +fn assert_sorted_array_failed_mixed_empty() { + let original = [30, 20, 90, 0, 0, 0]; + let sorted = [20, 30, 90, 0, 0, 10]; + let indexes = [1, 0, 2, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 7c98dd7f086..0ae6aa96002 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -11,6 +11,7 @@ export const MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL = 16; export const MAX_PUBLIC_DATA_READS_PER_CALL = 16; export const MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32; export const MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2; +export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2; export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1; export const MAX_NEW_NOTE_HASHES_PER_TX = 64; export const MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8; @@ -31,6 +32,7 @@ export const MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX = 16; export const MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2; export const MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128; export const MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8; +export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8; export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; export const NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; export const NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; @@ -98,7 +100,7 @@ export const NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; export const PARTIAL_STATE_REFERENCE_LENGTH = 6; export const PRIVATE_CALL_STACK_ITEM_LENGTH = 214; export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209; -export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196; +export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; export const STATE_REFERENCE_LENGTH = 8; export const TX_CONTEXT_DATA_LENGTH = 4; export const TX_REQUEST_LENGTH = 10; diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap index 5dc051a1f5f..b400f769558 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap @@ -9,18 +9,18 @@ exports[`ContractClass creates a contract class from a contract compilation arti "selector": { "value": 2381782501 }, - "bytecode": "0x1f8b08000000000000ffed9d079454c799ef6fcf0c493dcd30420284080392c8a1a72730e426470990002123230d0c20860c4316306491a364d972daf57a83d7de607bd76b6ff0aec306e7b45edb72ce3ee7f99df7ce79efed396f7775b6aabb3ecf9f9abaedeee156eb6bf8ee39df74dd6fead6f7fbbe5bb76ef5adea5bbf0a82201664b772258f049d37fa7fda7c26ef6cab8db0aca44fce5889709695086779897056940867b712e1ec5e229c3d4a84b3678970f68a9053b39505b76f51f3dee721ae5133c64b2ca6952510d34489c5b47709c4b42a288d36aa4f8970569708e7fd25c2d9b744381f2811ce074b84b35f8970f62f11ce0125c2f95089700e2c11ce874b84735089700e2e11ce2125c239b444386b4a84735889700e2f11ce474a84f3d112e17c2c42ce51c039c27c8e349ff4bfd1e6738cf91c6b3ec799cff1c6c70ab33f41c944cda6a4d6fa5f4a499d927a250de67f35e67f8d4a262969523259c9142553954c53325dc90c25338defb394cc563247c95c25f394cc57b240c942258b942c56b244c952258f2b7942c93225cb95ac50f2a492a794ac54b2ca6259ade469256b943ca3e42d4ad62a7956c95b95ac53f29c92e795342b59af64839216251b956c52b259c90b4ab6286955b255c93625db95ec50b253c92e25bb95ec51b257499b927d56ccf62b39a0e4a0924316e761254794bca8e4a892634a8e2b69577242c94925a7949c567246c95925e794bca4e4bc920b4a2e2ab9a4e4b2922b4aae2ab9a6e4ba921b4a6e2ab9a5e46525af28799b925795bcddb050657f8792d794bc53c9bb94bc5bc97b94bc57c9ef28f95d25ef53f27b4adeafe4f795fc81923f54f2474a3ea0e48f957c50c98794fc89923f55f2674afe5cc987957c44c94795fc8592bf54f231257fa5e4e34a3ea1e4af95fc8d92bf55f2774a3ea9e4ef95fc83924f29f9b492cf28f9ac927f54f24f4afe59c9bf5831ff9c92cf2bf982922f9affd1b3ae2f29f9b2497fc57c7ed57c7ecd7c7edd3ae61b4afed5d27d53c9bf59ba6f29f9b6497fc77cbe6e3ebf6b3ebf673ebf6f3e7f603e7f683e7f643e7f6c3e7f623e7f6a3e7f663e7f6e3e7f613e7f693e7fa5e4fd03b2e99e41c7960e226a77ea3736eb71120af688e0f64dc7a2dcfc8f3e6b8cbec2ecd327c5ae9bd9ef66e9bb9bfdee56393dcd7e4f4b5f6df6ab2d7d5fb3dfd7d23f68f61fb4f4fdcd7e7fd0c703785e6af45a576e5431d0513d2c035db7e0f698685d772a0e743d82db63a175741ebb83ae97d1f500dd7d46d7137471a3eb453153526974e920aa3a916cd6e526a22ed78c21f58e9e77832eb7ca136f9fe87937ea72ab3df0eafa71bf29ab0fd49bbe46570dba078cee7ed03d68747d41d7cfe81e005d7fa37b1074a6990afa81ee21a3eb0fba81463700740f1bdd43a01b6474034137d8e81e06dd10a31b04baa1463718743546370474c38c6e28e8861b5d0de8687eca30d03d6a74c341f798d13d023a6a531f051df5eb1e333add4efc7b00c7187d19e846523b0cba51d406836e34b5bfa01b436d2fe8c6826dd28d83768574e38d8eda28fdbf46934e07515d13a9cc353129ea7255c9badcc9d1979b19739b1274c4350d762641aca69a7484f37a6ad176cc08d9217d05a417425eca47f1a0fb0cb1ebfb4993494fcd715ca3755c02f23439fc4f07d1fa3fd9e2996c31ebfa3f1d38a2afb37529a9b3796f05d7d9d590d7ae7bd4e7b91bebec12e0f050671bfdd4d95452ea6cf6994310b8eb1ef57befc63afb0c70445f671ba4cee6bf155c67f7405ebbeed1779fbbb1ce6e068ee8ebeca446e91be4bd155c67db21af5df7e8fbefdd5867f70187873adb2ced6cde5bc175f60ae4b5eb1e3d8bb91bebec29e088bece4ef65467eba4ce06d9f1a32070d73d7a2e7837d6d9ebc0117d9dddd02c7d83bcb782ebec0720af5df7e819f5dd5867df031c1eeaacafe7b329a9b3d971f32070d73d1a2fb91bebec874c5a8f337cc58c330c02dd578d6e30e8be66744340f775a31b0a7e457f0db4d4cb3590f756f035f039c86bd7e51a93be1baf814f0287873adb287536efade03afb2dc86bd7bde1267d37d6d92f0287873adb247536efade03afb73c86bd73d9ad37037d6d9d74d5af717be63cd77d3bad78d6e24e8be6b74a340f73da31b0dbaef1bdd18d0fdc0e8c682ee8746370e743f32baf1a0fbb1d14d00dd4f8c6e22e87e6a7449d0fdcce86a41f773a34b81ee17465707ba5f1a5d3de87e65740d46a7c704687eca678cae27d84b07d19ddb38f8465bccda4f43bad62f4f32013c68ab2e7a5b75daf75490bfef75c053efc1f738d8c887a71e781aa2e7a9f572ef4966cf71ca8a691c6ca5c02f0ff7a84c7bd618dc1e53da277b09d0e1b53ac9c1d8143d632a06b6a86cda6f0246d2350023b5a574fde8b6b92ad6c1ebe15acadc9fd15e1a38c85e05e49939a0236f5fc35609ffa736a0128ec7b695535dd18c54471a8bcf98ca97b1c162f4747d6762d660f1b86c4ff164db8e05d5a92945b0dd64d9aeb76c631b425bae361efb761efad5b59efa8c99f6609a298bfaeb6407ef5bd3210651f984b6a9bf4e76485f01e954ac232fe5a37850db43ecfa3aa27389ecf67193ace31290678ac3ff7410adfff6f787a916b36e7747c23dc1c3f570db77362a9bf6eb2176534362370562477946828eee118da0a3b696cac0fe2db6c33efa4bb1e0f6be741af61b1cdc4dc0d8e060f4d0c74ce56a9beb819174938127e5296661df2d5362db473dcd5c976483fa6a740d91bd0ac833b8bc23ef6268337cd44fbc1668cbf73b50f4e72995f94e565f000f9e3b0ffdec5a4ff53189dfe7df08a2ad6b769b586fc52aec3b7fd243fcb0ee53d9b44ff684599885599885599885599885599885599885599885599885599885599885993f338e65e13c1ecad7c084d19e0fe5eb397fe6fd55a62c1c037addebfca054661c80e24ff301465b3e57409e5fc73ad87e00f383e8ff38dfc9752e7dcc95cc752ec99e6bbe92cff1e63a8ba7ce110b0eb66b22b39ddae0e7fca692fafd31fa1d6cf5d639b5c7e633ef76b374ba9ed69477f8ed63ecafd0b1486c4b281de5d85e0278d0968f7383d75e59707bfb81f7195ff358a8ada6f1f22996ed0ac8f31f309f682afc3f1d746e2f300f954dfba3e1d8a956d955fefccd3937017f0761ff1e837c6b04eedfccff29ebf0f17d26ede91e972a741e35b6d3d1f701b273085205f02481c7c77dcc535f2789f531ea3904f61c31571fcafe4d8ecfdfc3d8fd60dacff51b1e6116666116666116666116666116e6e49d6dc22cccc22cccc22cccc22cccc22cccc21c31b3e671bd8382f2d531612cd2bc8bcc7806bd8709c7c5ae9775d8f53d0648634e632c9ff177ab5f2beb607bd9a42b83ce732dc2cea5afb1b5b07349f65cef41f135a61c035b5476ad23161c6cd744663b3b8720faf3db318720659dd33aeb7ce2d8395e43ef837a6acfa788079de72478bac6739e1bb287d712ce7fa134fe36db47acf15e62cf29c2f7f2519e0f99d8d2d879f46d752ae9b3dda0f795d03c8194c357caf36168fb3e6ad2387fa40ecafab4e3ffb4e51aa7a6f8699f6744ef73e6fcce3465d1f99de1b09d06d6886cd7a2ed985536e92b20fda9b2ce0c140f8a35b1eb6b04dffb12765cbd755c02f24c77f89f8ed8ff19160f9e63bde9baf331a8679f86fbbfaf36697a488c46438c288fe7f7073ae703da731ab11ded61e5a163f1bd689f87362a6ceeaaeb1e30cd937f61f700b297083adf17f299637aafcf117b1dda8ba9f0ff7470e773c45e873a84f3b402abfc31503e71f508c2ef2d94e707d67dd4c31caebcde8fe3fa3e40bc38078df2fc14daaa7e66ae6621df07deacef76aeef03785c98efd82e447d6fc4fa882cf87e35caf33fadfa383584bbc971ecff0e39966265bf2bac32e81c3f3fef59cbb637332c5fe89a9a0ebe509e7fb7ae9be8fb4cd9fea7af77ca515f87daa05a87af94e73fe15a7b03fa97749ef0be9028effc7fda72f53f297edae759d1fb9c39bfb34d59747e67396ccf01d6886cd7a26dea7f921dd25740bab2bc232fe5a37850ac89bd123891dd3e6e8a755c02f2a41dfea723f67f96c533cb62ceb43df06eb204ccbff7d556a703778cc6408c7e630f74741fc1f780ba9e75f8ba9786f5e3f0775ba4c3b6bd1fc4b458bfa9b19fe9b9fa27632c7eec9f3c6c98ab4c9cedbcf6b34a3a2eca39cbf81b11ec17e26f447c7d474a04b7c7336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ad081c9b2a03461ff73abcafe6c3e8baff9503a3afdff1a50a60ac0346bcdf11a387f7b0d676752d0aeceb7403461fbf412df47935be83197f8b4c8c3ede855fe8fb6cf15dd6745c0f60f4b1c601ae55900fa36b8d8c9ef0e9613d8cdaaebe371fd7c8e8058c3edea51f0f6e7fffff6f63c4b59ce8b8fb80d1c738523cb8fdb9da6f63c477b0d37171cf8cb9eeed9ee783a40a7d0681f32ff01914c5c9358fc5d7b37afbd918ede3b37ae40dfcc531671f69aadf58a430eeb4e53a87aef8f4369f71f8bfdfe789b78ff152d9b43f1d184957e5378e39fb9ad3fcc6228571a72dd73974c5a78ff98c079dd7fff03466ed1ca3a47d1cc7255db5679eb8c5f3dbe2e862bc1f18677a629c5100e34c60a4e3fa0263da13e3cc0218d3c048c73d008c1e9e576718d30530e2735dd23f088cb33d31ce2a80713630d271fd80d1c7b3e738d8cd87710e30d271fd8171ae27c6390530ce05463a6e0030cef3c438b700c679c048c73d048cf33d31ce2b80713e30d2710381718127c6f905302e00463aee61605ce8897141018c0b81918e1b048c8b3c312e2c80711130d271838171b127c64505302e06463a6e08302ef1c4b8b800c625c048c70d05c6a59e189714c0b81418e9b89a12601c56028cc34b80f19112607cb404181f2b01c69e25c03816181f8f9e31f3fd7a69018c8f03cfb2e879eae360231f9e65c0f344f43cb59efccccccf5a6eca8afa1d692bac583d6ec52a01799643fc5678885f0cec52d9b44ff6845998c398350fb54fc41a877c4b993092ee09cf3c718b476fb9dac7157e799209473cb4ad27a3b7952ad4f727816765f43c997bd59305f0ac049ea7a2e7a9f5e467e69eb2caf2e949cba704e4c1766395073f636097caa6fd550edb3541b4b1589d472c563b785617391664af50e6e525c8cc21ced816126b1cf22d63c248baa73cf3c42d1ebde56a1f5d8c2bfc32a6bacaa879d644ce937d47f4ea0278d600cfd391f364ef29d1fb996d479fb17c5a6df994803cd8263de3c1cf18d8a5b269ff19380fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b19c71888350ef956326124ddd39e79e2168fde728d3bb81857f8654c759551f3ac8d9c273b56f34c013c6b81e72d91f364c76aa2f7333b56f3ace5d333964f09c883d7f7b31efc8c815d2a9bf69f85f320ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99718c8158e3906f0d1346d2bdc5334fdce2d15bae710717e30abf8ca9ae326a9e75d1f3647eabf96c013ceb80e7add1f3d47af2333356f39ce5d3b3964f09c883d7f7731efc8c815d2a9bf69f83f320ccc2ec62c6368b58e3906f2d1346d2bdd5334fdce2d15bae76ccc5b8c22f63aaab8c9aa739729eec38fd7305f03403cff391f364dbfee8fdccb6fdeb2d9f9eb37c4a401ebcbed77bf0330676a96cda5f0fe7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c621df3a268ca47bde334fdce2d15bccda4f43dac5b8c22f63aaab8c9aa725729ebacc58cdfa02785a806743e43cd9b19ae8fdcc8ed56cb47c5a6ff994803cd8266df4e0670cec52d9b4bf11cec3ddcebcaa0499a56e148759ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e63e65037708e0eb1c6215f331346d26df0cc13b778f4966bde8e8b71855fc654571935cfe6e87932ef24d95800cf66e0d9143d4fad273f33739d5eb07cda68f994803cd826bde0c1cf18d8a5b269ff05380fc22ccc2e666cb388350ef95a9830926e93679eb8c5a3b75ced988b71855fc6545719354fab279e170ae069059e2dd1f3d47af233d3f66fb57c7ac1f2290179f0fadeeac1cf18d8a5b2697f2b9c875263c66b8958e3906f331346d26d011e0ff5aee036a9d511c71525c0b8bc04187b9500e37d25c01807460ffdf4cc359cb078687fb3dff8a4a2884f25c4896bbb97001e5fe7b0cae2a972c482eb39ec6d3e399fc32ae0f1750eab2d9e6a472cb89ec33ee693f339acf61bc748dafbfb4b80b16f09303e50028c0f960063bf1260ec5f028c034a80f1a112601c58028c0f9700e3a012601c5c028c434a8071680930fa7ea691eb7b6e6b116c877d3f2b86edb0ef15c5b02d3197984bcc25e6127389f99dda96984bcc25e66f7ecc7d3ccbc5e7c6b4e5fa0ee17abeeb7b4c5e18f930ae62ca883cb1e87892e83bdadac6c077622835c65525c0b8bc0418258ed931d4ae306a9e1d9e78b615c0b30378b647cf53ebc9cfcc5cc39d964fdb2c9f129007ebc14e0f7ec6c02e954dfb3be13c941a335e4bc41a877cad4c1849b71d787c5d5f855cef787dedf2c4b3a3009e5dc0e3e37c79f23373bdefb67cda61f994803c78edecf6e0670cec52d9b4bf1bce43a931e3f54eac71c8d7ca8491743b81c7d7f555c8f58ed7d71e4f3cbb0ae0d9033c3ece97273f33d7fb5ecba75d964f09c883d7ce5e0f7ec6c02e954dfb7be13c941a335eefc41a877cad4c1849b71b783cd4bb82bf73ec71c471790930ae2a014689a3c49113a3c4f1de89a3300aa3300ae39bc1580a6db8dc670a7f3680dfc5dba2e7a9c7ef6df9f0b4018f8fef769efccc3c1bd867f9b4c7f2290179b01eecf3e0670cec52d9b4bf0fce83300bb38b19db2c628d43be56268ca4db0b3c1eaeef82dbfe36471c57f8654c759551f3ec8f9ca73e89f5251f9efdc0e3a34ef9f133dbf61fb07c6ab37c4a401ebcbe0f78f0330676a96cda3f00e7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71ee1a33ce7127d638e46b65c248ba7dc0b3df034fa1f3f0f73be2b8c22f63aaab8c9ae760f43cf5585ff2e139083c3eea94273f33bf153864f9b4dff2290179b04d3ae4c1cf18d8a5b269ff109c874298579520b3c4b96bccd866116b1cf2b5326124dd01e0f1707d17dcf61f74c471855fc654571935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f129007dba4231efc8c815d2a9bf68fc0791066617631639b45ac71c8d7ca8491748780c7c3f55d70db7fd811c7157e19535d65d43c2f46ce934a627dc987e745e0f151a7fcf8996dfb8f5a3e1db67c4a401ebcbe8f7af0330676a96cda3f0ae7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c6215f2b1346d21d011e0fcfe30b1eab79d111c7157e19535d65d43cc722e7a94b627dc987e718f0f8a8537efccc8ed51cb77c7ad1f2290179b04d3aeec1cf18d8a5b269ff389c87bb9d795509324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee01c1d628d43be56268ca43b0a3cc73cf0143a8fe898238e2bfc32a6bacaa879daa3e7a9c7fa920f4f3bf0f8a8539efccccc753a61f974ccf2290179b04d3ae1c1cf18d8a5b2699fec09b3308731639b45ac71c8d7ca849174c781a7dd034fa1ed6a3b30bada7e0f8ca9ae326a9e93d1f3d4637dc987e724f0f8a8539efcccb4fda72c9fda2d9f129007afef531efc8c815d2a9bf64fc1791066617631639bd56e3ee390af950923e94e008f87ebbbe0b6ffa4238e2bfc32a6bacaa8794e47cf538ff5251f9ed3c0e3a34e79f233d3f69fb17c3a69f994803c787d9ff1e0670cec52d9b47f06ce83300bb38b19db2c628d43be56268ca43b053c1eaeef82dbfed38e38ae2801c6e525c0b8aa04183dc731d55546cd73d613cfe90278ce028f8ff6c3939f99fbfc39cba7d3964f09c883f5e09c073f636097caa6fd73701e4a8d19af25628d43be56268ca43b033cbeaeaf42ae77bcbe5ef2c473b6009e9780c7c7f9f2e467e67a3f6ff974d6f2290179f0da39efc1cf18d8a5b269ff3c9c875263c6eb9d58e390af950923e9ce018fafebab90eb1dafaf0b9e785e2a80e702f0f8385f9efccc5cef172d9f5eb27c4a401ebc762e7af0330676a96cdabf08e7a1d498f17a27d638e46b65c248baf3c0e3a1de15fc9de382238ecb4b80715509304a1c258e9c18258ef74e1c85511885b130c6ad25c028e75a18b932b67a608c197bc843fbad45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627ea7b625e6127389b9c45c622e31bf53db127389b9c45c622e319798dfa96d89b9c45c622e3197984bccefd4b6c45c626edbf630dfb4e0f9dc1781e782875878f233a9cbbd64ca7a23c2f8e9585db662d56ac52a01792e41fc2e7b889f6bce35ed93bd4299473060f6643bd55b95d10bfc271babac7868fb573cf91ed6e65d2982edb036af18b6c3dabc62d896984bcc25e6f76ecc315d1174fe0d922ee3aa497733fb947f2b1c47793e5199fdac0ae47cfab02dd790c45c622e317f33628e71995f049ec0e20972f0a499f14c61c653cf8c6702339e31cc789a99f1ac61c6b394194f39339e05cc786631e329c6f3ac4278a632e36960c6b39019cf6c663cd398f13432e319c58c27c98c6704339e16663c6b99f12c63c6b38819cf1c663cd399f16c66c63389194f2d339e91cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c0130f3acf018fc3ff2f82aecc3ab6879293033afe7fcde8cbe098eb265dee28fb1ae8686ed575c7b118a76be04bdaa49377b665e284b6d2b04ff62a81e33a139ef1cc789630e319cd8ca79a194f1f663c75cc782633e399c98c671c339e79cc78aa98f1f466c6b39219cf3a663c29663c4dcc782632e399c18c672e339ec5cc7812cc782a99f18c64c653cb8c6712339ecdcc78a633e399c38c6711339e65cc78d632e36961c63382194f9219cf28663c8dcc78a631e399cd8c6721339e06663c5399f15c64c6338b19cf02663ce5cc789632e359c38ca79919cf18663c1398f1d433e399c28c27cd8c67be83c7d7bac334de4e65d3fe4526b63d9c87cc7be16e78f2e9a629abbb2997f8c95e05e499600662f4f8101e4b5cf6fc08ecdbdc84185df5e44bd8bb21ae16c176d8bb218a61bbdab25d5d44db12f3f098df8cde760ae71cd116b3f6d390c6ebcec75c2d4f7eded6e645fd2ecc5b56acae5ab14a409e1b10bf5b1ee2e76a47699fec15ca3c820133d68b9a20da7af172f43efde61d9b14d797adf8a25faf788a69585bfa4a116c87b5a5c5b01dd69616c3b6c45c622e31bfb763fe36938eb09f91441bfa3b14dd3fde0676df6ed2b108edeab25e3565d13b4189e3edc04379b6c2773aa97f72cddf0b31b7d3f48c027fc7efeb1952d83929c6f3abb073520cdb61e7a418b625e6e1317f8707dbf1e0f6b512f496eb19c53b80e7550f3c9efcccdc6b5fb37cba68f994803cd8f6bde6c1cf18d8a5b269ff35e0a10ddf13eba31ee473ce91e716339e34339e29cc78ea99f14c60c63386194f33339e35cc789632e32967c6b38019cf2c663c5399f13430e359c88c6736339e69cc781a99f18c62c69364c63382194f0b339eb5cc789631e3798519cf22663c7398f14c67c6b39919cf24663cb5cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c013f69e58faff2dd0d11839be3bf69d26fd2ae8ca1c3668ace635d055983495a1df3b7b7940e7b2314ebee605a0ad34ec933d7c4fec3b99f08c67c6b38419cf68663cd5cc78fa30e3a963c6339919cf4c663ce398f1cc63c653c58ca737339e95cc78d631e34931e36962c6339119cf0c663c7399f12c66c69360c653c98ca79619cf24663c9b99f14c67c6338719cf22663caf30e359c68c672d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc781632e36960c6339519cf2c663c0b98f19433e359ca8c670d339e66663c6398f14c60c653cf8c670a339e34339e5bcc78e63b787cbd9f2eec9d0fb78a603bec9d0fc5b01df6ce8762d8969887c7dcc3bb6e32ef02c5f742eacd9eeb9286b4e7dff7d4c783dbdfabf3db78f0bd1f3eda254f7e66de41f92e53964ea7232a57c7eadd41c7a6cba578e2fc2aca830cef866322e2c9f93e11b227ccc21cc68cefa42556d7dcbb379b9174af018f8f7643fb3ece9445e5eb77f425fa74d8f5f09ef77a1d07fb3defc4e17acffbe6fe1d6cd586ad32e8fc2e5c5ceb1ecfa5efb508a86cd75a04e40fae45e0ebbe7fc3e2b9e188c59b69fbba07db85f639ae3b78a27ccf26b63168cbc77bdfb5efd782fc7d77bd873e42df6b13c083b63cbce73dd376d17c5c2a5fb70f23fb788d793d5ef7d4768db77cae803c73a0ed1a9ba3ed2a0b3aae197c4f7959d0f9ddef6f18bd5d464d20dfa77cd896efb03c63eea16f52f077585c8f21fa7e462ad3cedd2880e71af0f8b8df7aea4f253ddd9332df61af58b1ba61c52a01793cdfb73275fb9ac543fb644f98855998855998855998855998855998855998855998855998855998855998f933e37830b1e2b8ed2d268ca4c3316b1fcff9b5ef134c5954be1e1bfd429f0ebbd18f5ba492384781c66d27583e57409e3efd3ad8be02e3b6f6bc86b073e96b9df7b07349f62a83cef3507c8ed385cd8129c6186121b66b22b39ddae0e7fca692bd83ec9adf37ac737acb713e7db415387f8ab65c638d5780c7c3586cadafb923daa7cb964fd72d9f129007d7b1b8ecc14fd77d8df62f030f6db84e9eaf7b4660f1048ef8d056c68c27cd8ca727339e29cc78ea99f14c60c6f328339e31cc789a99f10c61c6b38619cf52663c0398f1dccf8ca717339e72663c0b98f1cc62c63395194f03339ec798f10c65c6f310339eb1cc78fa32e3b98f19cf42663c15cc786633e399c68ca79119cf28663c49663c2398f1b430e3a961c6b39619cf40663ccb98f13cc08c27ce8c6711339e6ecc78e630e399ce8c6733339e49cc786a99f18c64c6338c19cfc3cc787c8fa317caf320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680271e74fead471cfe7f1d74974d1a7fcf56e6288fe6e9517eddaf7a7940e7b2cb1c655f7130609c2e812f69934eded976dbef3062a65cda277b95c0718509cf78663cd5cc78fa30e319cd8c6709339efecc786e32e319cc8ce711663c75cc782633e399c98c671c339e79cc787a30e3a962c6d39b194f3f663c2b99f10c62c6b38e19cf70663c29663c1399f13431e399c18c672e339eeecc781633e34930e3a964c6f320339e6bcc781e66c6338c19cf48663cb5cc782631e3d9cc8c673a339e39cc78ba31e359c48c27ce8ce701663ccb98f10c64c6b396194f0d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc782a98f12c64c6731f339ebecc78c632e3798819cf50663c8f31e36960c6339519cf2c663c0b98f19433e3e9c58ce77e663c0398f12c65c6b38619cf10663ccdcc78c630e3799419cf04663cf5cc78a630e3e9c98c27cd8ca78c19cf7c8b07ffafbfdbd3f8ea65d0d1ffffafe91c54193f2e5bf622f02319b6f683af98e19676c4246ced070e3c69663c3d99f14c61c653cf8c6702339e4799f18c61c6d3cc8c6708339e35cc789632e319c08ce77e663cbd98f19433e359c08c6716339ea9cc781a98f13cc68c6728339e8798f18c65c6d39719cf7dcc781632e3a960c6339b19cf34663c8dcc784631e34932e36961c653c38c672d339e81cc789631e37980194f9c19cf22663cdd98f1cc61c6339d19cf66663c9398f1d432e319c98c6718339e8799f15c63c6f320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680276cad05fa7f39e82e9af44dd05d30e96ba03b6fd29741f7924357e660217b174147f3142e808ec636ce838e9e87902ddd9f7b6d4067d632874fe50ed60b0e9f2e3a8ec5f348c7a48368cf23da4ac33ed9ab048e8b4c78c633e3a966c6d38719cf68663c4b98f1f467c6739319cf60663c8f30e3a963c6339919cf4c663ce398f1cc63c6d383194f15339edecc78fa31e359c98c6710339e75cc788633e34931e399c88ca78919cf0c663c7399f17467c6b398194f82194f25339e0799f15c63c6f330339e61cc784632e3a965c6338919cf66663cd399f1cc61c6d38d19cf22663c71663c0f30e359c68c6720339eb5cc786a98f1b430e34932e319c58ca79119cf34663cb399f15430e359c88ce73e663c7d99f18c65c6f310339ea1cc781e63c6d3c08c672a339e59cc781630e32967c6d38b19cffdcc780630e359ca8c670d339e21cc789a99f18c61c6f328339e09cc78ea99f14c61c6d393194f9a194f19339ef9160f8e61264147e95ad0513a053a4ad7818ed2f5a0a37403e828dd083a4a4f021da59b4047e9c9a0a3345d23f1a0c3f762bc879f6c51d9b47f0918693eb3eb9a9e0adc972c9de63ee789fb92c54dfbe780d1e50bb14d03ee73964e739ff5c47dcee2a6fdb3c0e8f285d8a6838ed2334047e999a09b09f64847e959a0a3f46cd0517a0ee8283d1774949e073a4acf071da517808ed20b4147e945a0a3f462d0517a09e828bdd47cea737cd6d2e9737cc6a4d341b4e7986c51d9b47f06185de79dd89601f7194ba7b94f7be23e6371d3fe696074f9426c2b81fbb4a5d3dca73c719fb6b869ff1430ba7c21b635c07dcad269ee939eb84f59dcb47f12185dbe10db5ae03e69e934f7094fdc272d6eda3f018c2e5f886d1d709fb0749abbdd13f7098b9bf6db81d1e50bb1350377bba5d3dcc73d71b75bdcb47f1c185dbe105b0b701fb7749afb9827eee31637ed1f03c676872fc486ef5ddcec89f1a6c578b388b6c3fa5ec5b01dd67f2a86edb03e50316c87dd9b8b613becfe5a0cdb61f7c862d80ebbcf15c376d8bdaa18b6c3ee37c5b0dd6ed96e2fa26db9c68a7f8dbd996deabd7a8dbd99ed5abb65bbbd88b6a5cf247da662d9963e93f4998a65bbddb2dd5e44dbd2a686b7a91e9e4fa4e26083b698b59f86f431e0f1f19cc7939f495dee5153d61b1196ab63f5a215abcd56ac1290e728c4ef450ff18b815d2a9bf6c95e293263bd8845673b19071bf88eb823543ee80e9b742de80e99740a74074dba0e74074cba1e74fb4dba0174fb4c7a31e8da4c7a09e8f69af439d0ed31691cefd96dd26741b7cba4717c65a7499f01dd0e93c6f18ced267d1a74db4c1ac70fb69af429d0b59a343eafdf62d22741f78249e3f3f14d267d02741b4dba19741b4cba1d74eb4d7a33e89e37e9e3a07bcea48f82eead26dd08ba674d7a12e8de62d24da07bc6a42783ee6993c67714ae36691cfb5e65d21740f79449e358f393267d09742b4c1ac776979bf474d03d61d23340f7b849cf04ddcb269d06dd2b263d0b746f33e9d9a07bd5a4e780eeed263d1774ef30e979a07bcda4e783ee9d26bd0074ef32e985a07bb7492f02dd7b4c1ac701de6bd2c7405766d22f828ee69e1e011dfd9ee230e8e83794874047ef4d3808ba1e267d0074341f673fe8688ee93ed0dd67d26da08b9bf45ed0559af41ed0d13b0a76838ede03b40b74f4aea29da0a3f701ee001dbdb3703be8681eea36d0d16f19b6828e7ebfd80a3a7a47c016d0d17b815e001dbdeb6e13e868bee946d0d16f0436808e7e17b81e74f4dbfbe74147efdb790e74f40eb9b7828ee64d3e0b3afa2dc05b405763d2cf806e98493f0dbae126bd1a74f4aeb855a0a3f97f4f818ee6fc3f09ba1126bd0274234d7a39e84699f413a0a3772e3e0e3a9a07f932e8c69af42ba01b67d26f031dbd73f355d0d1bcc1b7838ede55f30ed0d1bdf835d0d1bdf89da0a37bf1bb4047f7e277838eeec5ef011ddd8bdf6b3ef5f5a7afcbeb663f1d44d7efd1f66e04b76fb9fadec4803c51f66513c083b6ae46ee7b2ad36fa6fe56992997eac155b07d3972dbd93efb1553563753ee65cb7605e419d2afe3dc5c82ffa7c1073a0ef350d9b43f018ebd64955d65fcbde2c9dfcb1613715f0126ca33bc5f47decf9a744f382642b6ccf74faa6b01c410b734a4718db4e86395ca7c1fb85600cf15e089fe3ac97e1ff65127f0da8afafbb0fd2cc3ae6b09c87319e2e76b6ee7158b87f6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63c349e80f37928df75268ca4c3b12e1fcff9712c15c7bc3e02e33abec7f7ba9b72275a3e57409eff03634e1f33e94af83f9db7b073e9619c30e7b9247b95e00f8e05f9182f8e812d2afbaa23161c6cd744663bb5c1cff94d257b07d9f1fd6bd639bd6e9d4f1c17c76be8b3504fe9985be6330ef1b905f1f1708de73c37640fafa5aba0a3f40d60f4116bbc97507b30c1ec933d1c17ffb2352e1e7d5b9d4afa6c372e9ab2ecf176f495f27c03dabe6f9a34ce0db90e65fdccf17fda728d5353fcb4cfe7a3f739737e5f3265d1f93defb07d0e5823b25d8bb66346c80ee92b20fd5398cf61ff9e88624decfa1aa13998c86e1f77c33a2e01792e38fc4f07d1fa7fdee2396f31ebbaf36da8673f83fbbfaf36e942488c26408c280ff6833ccc3371b691c441f6741e3aff3dac3cd867a13cbf86364afb42ed3cf989735bf01e70d1937fd72dff689fec61dff80a30da3eeafaf1853e1dbcf7fafcaffff238ffebbfa00ee13cadc02a7f22944f5c3d82f07b0be589f5ef28dfe7fcb26b96cfc47205987e93a77f47de69265d485fffcdfade16d6d7f7f14ec07870fb776fbde5babfe33dc647fbe2c9cfa4ebde75c9f229017946809f1efa31397f07781e6cfb7a0f24d9a03ed4652b161590a73f5cdbb9e288df55af14c59794b33f38d1e10be5196cb5532f7960f279deb09fa5cbbdeaf095f20c87f6ef51938ec379c2b6b4cef17fda72b507143fedb387f7fb65ce2ffd068cceef1987ed53c01a91eddb7e934efd7db243fa0a48a7fa77e4b57f4f4eb1c6f7f8d16fe590dd3eeeb2755c02f29c75f89f0ea2f53fec7d00644fd79d9150cfa81ef96c37cf86c46822c488f2e0b315bab753feb0fbbe8ff9d5b9eefb5781d16e37b1efe293ed9ac5663f4375f507290f1d8bfdc159d0cec61d79fd3f1bcef66d0a99f3eff919797dcc8a671aec8c073dd54f8a33fd0ffbb8946789753ff3d567bd6c71931f388673d1e2be64c5159f452d076ed7b5e97ad6e9a9cf598f7d31bb3f7411d829cf6aab3f64f7e36e3a7cb1efd36541e7ef826f5879f19acc759c9dbe6e1d83cf0c5ccf64f1b72e1780256dd2c93bd96a9349642a73c4221174fe4ee67ace40d701d6392acbbe56e8d80ac8b3d16a93ecbc997b599f8ef8d079c467edae3ae9eb3952d87316b2a7195dbfa38bbeedba377fafd433b27227af2fc531ee9e563a1adba916bc57e48ac545078faf679a61b1b8e8b01d5d2c1a37b8ee1fae585c70f0f8fa8e19168b0b0edb11c66293ebd9822b16e71d3cbebe6b84c5e2bcc37674b19874db738d5cb178c9c113fd338ddcb1c031b142982f3060ee69a5a3b15dbfde3576e78ac539078fafb1bbb0589c73d88e2e16b58daeefecae589c75f0f85a17242c16671db6a38b45d364d73315572cce38787cad9f11168b330edb11d68b8df87c2d572c4e3b787cadc911168bd30edb11f60f1b733d33c4589c72f0f87af617168b530edb11c6a259db3e99472c4e3a784e163916271db6a38bc5fa066dfb441eb138e1e03951e4589c70d88e2e16cd4dda767b1eb16877f0b4173916ed0edb117e87cad48be379c4e2b883c7d7ba2961b138eeb01d5d2c5a327dad6379c4e29883c7d75a2c61b138e6b01d5d2c92997bead13c6271d4c173b4c8b138eab01d61bdc87c9f7c318f5814f3fda361b178d1613bc2fb48a65e1cc92316471c3c478a1c8b230edbd1c56253e6f9d3e13c6271d8c173b8c8b138ecb01de133974cbd3894472c0e39780e153916871cb6a38b455de69e7a308f581c74f01c2c722c0e3a6c47178b8d9931b10379c4e28083e740916371c0613bc27e67a6bdd89f472cf63b78f6173916fb1db623ec77669e5feccb2316fb1c3cfb8a1c8b7d0edb11b69d997e675b1eb16873f0b41539166d0edb11f63b33b1d89b472cf63a78f61639167b1db623ec7766ee237bf288c51e07cf9e22c7628fc37684f522d376eece2316bb1d3cbb8b1c8bdd0edb113ed7cab49dbbf288c52e07cfae22c76297c37684df4732cff876e6118b9d0e9e9d458ec54e87ed08c78a327df01d79c462878367479163b1036cfb986782b1a0b958e3ac5854409e07cccbd8692e56581ca90c9c5786be6c8fdc97ecbcb26d21be6c075f28cf40f0a527e8a364f2e46ba6ce6c3565d1dcf41b0e5f29cfd0011d798799741ccec94d282be9f83f6db9e62051fcb4cf5ba2f73953575f3065d1f9dde2b0bd095823b25d8bb6696e3ad9217d05a4270ee8c84bf9281e146b62d7d708ad9580ecf6713bace31290a7d5e17f3a88d6ff2d16cf168b39f3bb07a867548ffcb45d59a6d690188d8318511e9cb377c3138f3d879038c89ece43e7bf879507e750529e2668a3705e29f9190f3acf9bd4fe6df5e45fd83a6e642f01ba2bc068fba8eb4702e67ed61263d0a1a3b51d700da6464ba77d9de4c957b24565d3fe2460a4b5261a8bcf98ca97b1c162d43c933dc42c0e7669cb75bf980c3c4d1e783cf999b90f4db17c9a64f994803cf8dbc6291efc8c815d2a9bf6a7806d1fe71c6341f7e451562c2a20cf6aabff1816472a43d7df46872fbee2d860f134386c4ff71c472a9bdac4e945b03dd5b25d6fd9d6d736d631bde5bab6a702f3340fccbadc19d1979bb9b6698d34aacf64a71e7c4a430ca2f2096dc7acb2495f01e9ed033a33503ce8de49ecfa3ac2ba1376dc64ebb804e499eef03f1db1ff332c1e3cc77ad3fd86f5d0aff4703d64eac0748b83f6eb2176334262371d6247791a41477d9c29a0a3be02ae2948ffc77e4443f4fe3adb1eda6f0046d2e13a850d0ec6fae81953b9dae67a6024dd34e099ea2966d32c9e51567cf0bedcc3ca43c756409e76b837c61d7975ddaf8a75f8456b1746b8ce4fa65dedee215eb8ae6200f109ac18d2460c3d838eb517a3e4b92f80b515db76ee69debcf1c98dd9a14742abb030f133e670a30c74982e77e882e0f625242b40474b4876035d9915165cba92f2d392763ec285f1a0b22b2cce9ec012a56d5c7e93b65c55a707f0f8a8cabaea549ab24cd5797acf96b68d583fba599c5da93bfa7fe539f2859545f5a0c283efc84465d33ed9d3f14998f4aee60d5b67edd9bc6ffbc61d6d7b11d6beb8301db382607fba8ec14a82171395d3cd0a4eafe883538f17afcd1780bd005802c3d3337a9e4c45a5355b37346fdbb67cdffa6d5b36ccdfb76343db969d3b30a23dacc88545dbbee4f5e66aea30af9dbfbb43676fb8426e0fd0d10ab93d4147f67b818e38ee83ffd967c2cb353102caa76aacff57611cee6e1ca22a48b721dd9ee86ba67790bdfdeb2763d541f6f4e92576f592ba7a095dfd0607fdab6add53d04be0ea256ff512b77a495bbd84ad5eb2562f515b136497a01d1e6497987d34e85842566f9f015ebd64acee6ae82561f512b06383ece3baf141f6d566fa5512bacbaabf8eea6e9efe6aa1bb5cbaaba5bbb5fa2bbb7e7ca3bb31ba8ba8bb3fbacba2bbc1bafb3bd3c47a56905d425a2f193d37c82e093d3fc82ef9bc30c82ee9bc38c82e87be34c82e4dfb44905dce7c79905de6562f7fab97c5d54b97eb2573f552ba7a895dbd44b95e7e572fcbab9721d74bf6eaa57cf552e37a99dfe783ec72e2eb83ec92c12d41763961fde85a2f1fae1fdbebc7d9fa51b17e94a98713f4308a7eccaa87fff470a81e1ed6c3e57afa809e4ea1a797e8e9367afa919e8ea5a7a7e9e97a7afaa29ecea9a7b7eae9be7afa737b909d1e7f32c8fe7c42ff9c44ffbc46ffdc48fffceaa520fb9342fd133dfd9353fd13dccb41f611aafe39b71e56d38f4ef56364fd78553fead5cbf4ea6579f532bc7ad95dbdccae5e56572fa3ab97cdd5cbe4ea6571f532b8ef55f23b4a7e57c9fb94fc9e92f72bf97d257fa0e40f95fc91920f28f963251f54f221257fa2e44f95fc99923f57f261251f51f251257fa1e42f957c4cc95f29f9b8924f28f96b257fa3e46f95fc9d924f2af97b25ffa0e4534a3e1d64ebdf6795fca3927f52f2cf4afe45c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc93794fcab926f2af93725df52f26d25df51f2ba92ef2af99e92ef2bf981921f2af991921f2bf989929f2af999929f2bf985925f2af955d0b10c343616ffdfec0c33fbcd6d6d1bb7ef6aab69db59b37ddfb6b62dbbb61daa39b0a5ed859a9dfb37eed9b46de7013cf883a679a2f5aa67edd9d37ca866cb8e968d076b76ee6babd9b9a966fdce7d3b5a6ebb397ece1c34a8b3c5e696967063df29bb03d2ef77d1e8afcd71b412f8a2dcbefdafae04e4ff75e5a081e55d73e8e3a673475fbb9ecaf6f16af66edbd95693acd9a1feaa9be9ce031b5b26d4e0fff6aa20ef6dabd9dbd6bca7ad66d39e9ddb6b6a2760b9e37b77c189d6de7e60069b770175a9aa7ca95f173cf91ffdba763afef34e48693df9428df6ebdf050f0775e5a074170917f70f0dcbde7debdbf6346f680b3f78d99d1cbcaa2b6eb674d1cdbe03ba60eca1ae1c346940d7085775c5d8f1028c05ff0d3975c98424930400", + "bytecode": "0x1f8b08000000000000ffed9d67941cc775ef7b76177176b058820824181624080220c2ec6c405884418e14b0244082020572810540805880041639e79cb34831283948b22d4759b26c59b26c39c8966c45cb564e94c40fef9df74defd0ae9aa9abfda3503d9a59740def00b7cfb93bd577abebfeeeedeaea50d55d6f0741100bb24bb99287839b17fa7fdafc266f6da98db0aca44fce58897096950867798970569408679712e1ec5a229cdd4a84b37b8970f6889053b39505372e51f3f6f410d7a819e32516d3ca128869a2c462daab04625a1594461bd5bb4438ab4b84f3ae12e1ec53229c77970867df12e1ec57229cfd4b8473408970de53229cf79608e7c012e1bcaf4438ef2f11ce074a84f3c112e1ac2911ce4125c2f95089703e5c229c834b84f39108398701e710f3fba8f91d6a7e29cf70f3fb98f91d617e471a1f2bccfa2825a3359b925aeb7f2925754aea953458ff6b543246c95825e3ccff6accffc62b69523241c9442593944c367198a264aa92694aa62b99a164a692594a662b99a364ae92794ae62b795cc97b942c50b25049b39227943ca9649192c54a9e52f2b492254a9eb158deab64a9926795bc4fc93225cf29795e498b92e54a56286955b252c92a25ab95bca0648d92b54a5e54b24e499b92f54a36287949c9cb4a362ad9a4a45dc966255b946c55b24dc9762b663b94ec54b24bc96e8b738f92bd4af629d9afe48092834a0e2939ace48892a34a8e2939aee48492934a4e2939ade48c92b34ace2939afe482928b4a2e29b9ace48a92ab4aae29b9aee4fd4a5e51f2aa920f18163a105e53f2ba923794bca9e4834a3ea4e4c34a3ea2e4a34a7e47c9ef2af93d25bfafe4634a3eaee4134afe40c91f2af923259f54f2c74afe44c99f2af933257faee42f947c4ac95f2af9b492cf28f92b259f55f2d74afe46c9e794fcad92cf2bf98292bf53f245257fafe41f947c49c93f2af92725fface45f947cd98af9bf2af937255f51f255f33f7a0ef6ef4afec3a4bf667ebf6e7ebf617ebf696df32d25dfb674ffa9e43b96eebf94fcb7497fd7fc7ecffc7edffcfec0fcfed0fcfec8fcfed8fcfec4fcfed4fcfeccfcfedcfcbe657e7f617e7f697e7f657edf56f2eb01d974f7a063490711b549f52b57ea3e140af690e0c645c7a2dcfc8f7e6b8cbec2acd32fc5ae8b59ef62e9bb9af5ae5639ddcd7a774b5f6dd6ab2d7d1fb3dec7d2f735eb7d2d7d7fb3de1ff4f1009ea51abdd6951b550c74540fcb40d725b831265ad7958a035db7e0c658681dedc7aea0eb6174dd40d7d3e8ba832e6e743d28664a2a8d2e1d445527922dbadc44d4e59afea55ed1f3aed0e55679e2ed1d3def4a5d6eb5075e5d3fee3265f5867ad3c7e8aa4177b7d1dd05babe46d70774fd8cee6ed0f537babea033cd54d00f74f7185d7fd0dd6b74034037d0e8ee01dd7d46772fe8ee37ba81a07bc0e8ee03dd8346773fe86a8cee01d00d32ba0741f790d1d5808ec6ae0c02dd60a37b08748f18ddc3a0a3367530e8e89aef11a3d3ed4497186c63f4d44665b6a176187443a90d06dd306a7f41379cda5ed03d06b6493702da15d28d343a6aa3f4ffc69a743a88ea9848658e89715197ab4ad6e536455f6ea63f6e42d011d734d81907b19a68d2118ef9a945db31236487f415909e0d79291fc583ce33c4aecf27e34d7a628eedc65adb2520cf7887ffe9205aff9b2c9e268bb90ba4fdd4d9ba94d4d9bc9782ebec5390d7ae7b74cd733bd6d979c0e1a1ce364a9dcd7b29b8ceb6425ebbeed175efed58679f010e0f75b6c54f9d4d25a5ce669f7f0581bbeed1bdcfed5867570347f475b641ea6cfe4bc175761fe4b5eb1eddffde8e75763370445f67c7b4c8b541de4bc175f60ce4b5eb1e3d8bb91debec41e0f05067574a3b9bf752709d7d15f2da758f9e0bde8e75f63c70445f67c779aab3755267836c5f6610b8eb1e3da3be1debecebc0117d9d5d21cf67f35f0aaeb39f82bc76dda3fe92dbb1ce7e0238a2afb3adbe9ecfa6a4ce66c7700481bbee51dfdded58673f63d2ba6fec6ba66fec01d07ddde81e04dd3760ec01e9be697483c02f0fc7c0583906f25e0a3e06be0579edbafc9049df8ec7c09781c3439d6d913a9bf752709dfd29e4b5eb1e8d69b81debec7780c3439d5d217536efa5e03afbff20af5df7687ccded5867697ca8be5ef8aeb95e180abaef19dd30d07ddfe88683ee0746f718e87e68742340f723a31b09ba1f1bdd28d0fdc4e84683eea7469704ddcf8cae16743f37ba14e8de32ba3ad0fdc2e8ea41f74ba36b00ddaf8cae11746f1bdd18a3d3fd5834a6ea4b46d71db8d24174fb360e31a02566ada7215deb972799001eb45517bdad3aed7b2ac8dff73ae0a9f7e07b1c6ce4c3530f3c0dd1f364c69336465f6e661fa7ac98c6c1560afc1ae3c1af18d8a2b2699dec254087c7f4180763e4e767751e8d812d2a9bd6c70223e9b08da136978e1fdd36df1febe0f5702c65cecf682f0d1c64af02f25c1ed091779061ab84ff531b5009db631bcca9ae6846aa238dc5674ce5cbd860317a3abe33316bb0785cb6c77bb26dc782ead4f822d81e6bd9aeb76c631b424bae367e2c307bb85eaf7d37aed7f1bcc5e17a3d1debc86b5f7753db53e8f5fa186b3b8ed7eb0d704ef0703c64eac0788b83d6eb21764d21b11b0fb1a33c8f828ece118da0a3b696cac0eb5b6c877d5c2fc5821bafa5d3b0dee0e01e0b8c0d0e460fd798a95c6d733d30920eefad539e6236cee219e788c59d6adb433dcd1c976483aed5e818227b1590e7b1f28ebc4ba0cdf0513ff158a025df7ba0e8f7532a734f565f000fee3b0fd7d9b59eea6312efe7df09a2ad6b769b586fc52aec9e3fe9217e58f7a96c5a277bc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f19fbb2701c0fe56b60c2688f87f2f59c3ff3cd355316f601bde5757c502ad30f40f1a7f100c32c9f2b20cfaf631d6c6fc3f820fa3f8e7772ed4b1f632573ed4bb2e71aafe4b3bfb9cee2a973c48283ed9ac86ca756f8d9bfa9a4fee691fe6e60bdb54fedbef9ccf7082d9daea7a3ca3bfcf6d1f757685f24b625948eb26f2f013c68cbc7bec163af2cb8b1fdc0f38caf712cd456537ff978cb7605e4e95116fc66df34c1ffd3c1cded05e6a1b2697d186cdb64955de5cfdf9c6313c60337a5bb5abe350237e5e955d6e1e3274ddad3392e55e8386a6ca7a3bf06c88e214815c093041e1fe7314fd73a49ac8f518f21b0c788b9aea1280f8eaff330b631e7382fb227ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccfc99358feb1b1494af8e096391c65d64fa33e87b4dd82ff67a59875ddf7d80d4e734dcf219df5bfd7e5907db874cba32b879ac45d8bef4d5b716b62fc99eeb3b28befa9463608bcaae75c48283ed9ac86c67c71044bf7f3bc610a4ac7d5a67ed4fec3bc763e893504fedf114f1e0e631099e8ef19cfb86ece1b184e35f288def66fb88359e4bec3145640ffbce3f63624b7de7d1b7d5a9a4cf7683be5742e304520e5f29cfe7a0edfbbc49e3f8913a28ebab8effd392ab9f9ae2a77d9e14bdcf99fd3bd99445fb7792c3761a5823b25d8bb66356d9a4af80f457ca6e66a07850ac895d1f23f8dd97b0edeaaded129067a2c3ff74c4fe4fb278701feb45d79d2f423dfb2a9cff7db549134362340c6244793c7f3fd0391ed01ed388ed68372b0f6d8bdf45fb36b4516163575de780df7ccb2828ce3980ec25829bcf0bf98c31bdd3c788bd05ed4513fc3f1ddcfa18b1b7a00ee138adc02a7f38944f5cdd82f0730be579db3a8f7a18c395d7f7715cf703c48b63d028cfff85b66ab019ab59c8fdc0bb756fe7ba1fc0edc27cc77621ea7323d64764c1efab519eff6fd5c7a610eeb18e6dff27645b8a95fdadb0cae0e6f8f9f9ce5ab6bd9964f942c7d444f085f27429eff0c5cf3553f6fad3d737e5e85a87daa05a87af94a7277c33aad2a4e3b09ff0bc30d0f17f5a725d7f52fcb4cf53a2f739b37fa79ab268ff4e71d89e06ac11d9ae45db74fd4976485f01e97bcb3bf2523e8a07c59ad82b8113d9ededc65bdb25204fdae17f3a62ffa7583c532c665d77aaa09e0d84f1f7bedaea74e08ed17088d16fec818ece23f81d50d7b30e5fe7d2b0eb387c6f8b74d8b60f869816eb9d1afb999eebfa64b8c58fd727c3a09d8d3bf2dacf2a69bb28c72ce33b22785d88ef88f8ba474a0437c6336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ae08ec9b2a03461fe73a3cafe6c3e83aff9503a3aff7f8520530d601239eef88d1c377586b3b3b17055eeb7401461fefa016fabc1abfc18cef2213a38f6fe117fa3d5bfc96356dd70d187dcc71807315e4c3e89a23a33bfc7a980fa3b6b3dfcdc739327a00a38f6fe9c7831bbfffffdb18c701236dd713187df423c5831b9fabfd3646fc063b6d17f7cc98ebdcee793c48aad0671038fe029f41519c5ce3587c3dabb79f8dd13a3eab47dec05f1c735e2335f98d450ae34e4bae7de88a4f2ff31b87fffb7d9e78631f2f954deb13819174557ee398f35a7382df58a430eeb4e4da87aef8f436bff1e0e6f93f3cf5593bfb28691dfb714957ed99276ef1fcb638ba18ef02c6c99e182715c038191869bb3ec098f6c438b900c63430d2767703a387e7d519c674018cf85c97f47d8171aa27c62905304e0546daae1f30fa78f61c07bbf9304e0346daae3f304ef7c438ad00c6e9c048db0d00c6199e18a717c038031869bb7b8071a627c6190530ce0446daee5e609ce5897166018cb38091b61b088cb33d31ce2a80713630d276f701e31c4f8cb30b609c038cb4ddfdc038d713e39c0218e702236df70030cef3c438b700c679c048db3d088cf33d31ce2b80713e30d2763525c038a804181f2a01c6874b80717009303e52028cdd4b80f131607c3c7ac6ccfdf5fc02181f079e05d1f3d4c7c1463e3c0b80e73dd1f3d47af233333e6ba1292bea6fa4355bb17adc8a5502f22c84f8357b885f0cec52d9b44ef6845998c398350fb54fc41a877cf3993092ee3d9e79e2168f5e72b58fcd7e799209473cb4ad27a2b7952ad4f727806751f43c9973d51305f02c029e27a3e7a9f5e467e69cb2d8f2e909cba704e4c17663b1073f636097caa6f5c50edb3541b4b1782a8f583ce5e079aac8b1207b85322f2c41660e71c6b69058e3906f011346d23de999276ef1e82557fbe8626cf6cb98ea2ca3e659123d4fe69cf254013c4b80e7e9e8796a3df99969479fb17c7acaf2290179b04d7ac6839f31b04b65d3fa33b01f0a615e5c82cc12e7ce31639b45ac71c8b7880923e99ef6cc13b778f492ab1d733136fb654c759651f32c8d9c273b3fc03305f02c059ef746ce936dfba3f733dbf63f6bf9f48ce55302f2609bf4ac073f636097caa6f567613f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66ec6320d638e45bc2849174eff5cc13b778f492abdfc1c5d8ec9731d55946cdb32c729e6c5fcdb305f02c039ef745ce93edab89decf6c5fcd73964fcf5a3e25200f1edfcf79f0330676a96c5a7f0ef683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f366c63e06628d43bea54c1849f73ecf3c718b472fb9fa1d5c8ccd7e19539d65d43c2dd1f364dea97cae009e16e0793e7a9e5a4f7e66fa6a965b3e3d67f994803c787c2ff7e0670cec52d9b4be1cf683300bb38b19db2c628d43be654c1849f7bc679eb8c5a3975ced988bb1d92f63aab38c9aa735729e6c3ffdf202785a816745e43cd9b63f7a3fb36dff4acba7e5964f09c883c7f74a0f7ec6c02e954deb2b613f14c2bcb8049925ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689f39d1367ec6320d638e46b61c248ba159e79e2168f5e72f53bb8189bfd32a63acba8795647ce5397e9ab595900cf6ae05915394fb6af267a3fb37d352f583eadb47c4a401e6c935ef0e0670cec52d9b4fe02ec87db9d797109324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee0181d628d43be56268ca45be599276ef1e825d7b81d1763b35fc654671935cfdae87932df2479a1009eb5c0b3267a9e5a4f7e66c63abd68f9f482e55302f2609bf4a2073f636097caa6f517613f08b330bb98b1cd22d638e45bcd8491746b3cf3c42d1ebde46ac75c8ccd7e19539d65d43c6d9e785e2c80a70d78d645cf53ebc9cf4cdbbfdef2e945cba704e4c1e37bbd073f636097caa6f5f5b01f4a8d198f25628d43beb54c1849b70e783cd4bb82dba436471c9b4b8071610930f62801c69e25c01807460fd7e999633861f1d0fa5abff14945119f4a8813d7762f013cbef66195c553e58805d77dd8cbfc72de8755c0e36b1f565b3cd58e5870dd87bdcd2fe77d58ed378e91b4f7779500639f1260bcbb0418fb960063bf1260ec5f028c034a80f19e1260bcb70418079600e37d25c0787f09303e50028c0f9600a3ef671ab9ee73db8a603becfeac18b6c3ee2b8a615b622e3197984bcc25e612f35bb52d3197984bccdffd98fb78968bcf8d69c9750fe17abeebbb4f5e18f9302e66ca883cb1e87892e83bdadac0c077622835c6c525c0b8b00418258ed93ed4ce306a9e973df16c2880e765e079297a9e5a4f7e66c61a6eb47cda60f994803c580f367af0330676a96c5adf08fba1d498f15822d638e46b63c248ba9780c7d7f155c8f18ec7d7264f3c2f17c0b309787cec2f4f7e668ef776cba7972d9f1290078f9d760f7ec6c02e954debedb01f4a8d198f77628d43be36268ca4db083cbe8eaf428e773cbe367be2d95400cf66e0f1b1bf3cf99939deb7583e6db27c4a401e3c76b678f0330676a96c5adf02fba1d498f17827d638e46b63c248ba76e0f150ef0abee7d8ec88e3c212605c5c028c12478923274689e39d13476114466114c67783b114da7039cf14fe6c00efc5b746cf538ff76df9f06c051e1ff7769efccc3c1bd866f9b4d9f2290179b01e6cf3e0670cec52d9b4be0df683300bb38b19db2c628d43be36268ca4db023c1e8eef82dbfead8e3836fb654c759651f36c8f9ca73e89f5251f9eedc0e3a34ef9f133dbf6efb07cda6af994803c787ceff0e0670cec52d9b4be03f64321cc8b4b9059e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e2dc39661ce34eac71c8d7c6849174db8067bb079e42c7e16f77c4b1d92f63aab38c9a6767f43cf5585ff2e1d9093c3eea94273f33ef0aecb27cda6ef994803cd826edf2e0670cec52d9b4be0bf64321cc8b4b9059e2dc39666cb388350ef9da9830926e07f07838be0b6efb773ae2d8ec9731d55946cdb33b7a9e7aac2ff9f0ec061e1f75ca939f99b67f8fe5d34ecba704e4c136698f073f636097caa6f53db01f8459985dccd866116b1cf2b5316124dd2ee0f1707c17dcf6ef76c4b1d92f63aab38c9a676fe43ca924d6977c78f6028f8f3ae5c7cf6cdbbfcff269b7e55302f2e0f1bdcf839f31b04b65d3fa3ed80ffb0a605e5c82cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c4f9ce8933f631106b1cf2b5316124dd1ee0f1f03cbee0be9abd8e3836fb654c759651f3ec8f9ca72e89f5251f9efdc0b32f729e6c5f4df47e66fb6a0e583eedb57c4a401e6c930e78f0330676a96c5a3f00fbe176675e5c82cc52378ac32c754398c398a56e087318b3d40d610e6396ba21cc61cc52376e5a84d9a4a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e0873183387ba81637488350ef9da9830926e1ff0ecf7c053e838a2fd8e3836fb654c759651f31c8c9ea71eeb4b3e3c0781c7479df2e46766acd321cba7fd964f09c8836dd2210f7ec6c02e954deb87603f08b330bb98b1cd22d638e46b63c248ba03c0e3e1f82eb8ed3fe88863b35fc654671935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f1290078fef231efc8c815d2a9bd68fc07e1066617631639b45ac71c8d7c68491748780c7c3f15d70db7fd811c766bf8ca9ce326a9ea3d1f3d4637dc987e728f0f8a8539efcccb4fdc72c9f0e5b3e25200f1edfc73cf81903bb5436ad1f83fd20ccc2ec62c6368b58e390af8d0923e98e008f87e3bbe0b6ffa8238ecd25c0b8b00418179700a3e738a63acba8798e7be2395a00cf71e0f1d17e78f233739e3f61f974d4f2290179b01e9cf0e0670cec52d9b47e02f643a931e3b144ac71c8d7c6849174c780c7d7f155c8f18ec7d7494f3cc70be039093c3ef697273f33c7fb29cba7e3964f09c883c7ce290f7ec6c02e954deba7603f941a331eefc41a877c6d4c18497702787c1d5f851cef787c9df6c473b2009ed3c0e3637f79f23373bc9fb17c3a69f994803c78ec9cf1e0670cec52d9b47e06f643a931e3f14eac71c8d7c6849174a780c743bd2bf89ee3b4238e0b4b80717109304a1c258e9c18258e774e1c85511885b130c6f525c028fb5a18b932b679608c197bc843eb6d45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627eabb625e6127389b9c45c622e31bf55db127389b9c45c622e319798dfaa6d89b9c45c622e3197984bcc6fd5b6c45c626edbf630deb4e0f1dc6780e7b4875878f233a9cb3d6bca7a27c2f8e9589db362d566c52a0179ce42fcce79889f6bcc35ad93bd4299873060f6643bd54b95d103fc271b8bad7868fbe73df91ed6e69d2f82edb036af18b6c3dabc62d896984bcc25e6776ecc315d11dcfc0e922ee382497731eb947f3d6c4779deaaccfe5605b23f7dd8966348622e319798bf1b31c7b88c2f024f60f10439786632e3a967c6d3c28c6709339ee1cc784633e399cf8c6716339e29cc78ca99f13431e36960c6338219cf6c663c5399f14c60c6d3c88c27c98c6728339e91cc785a99f10c61c6b39419cf02663cc5e81f2a84670e339e51cc782632e399c68c670c339e5a663c8f32e399cb8ca792194f8219cf74663c9398f18c65c69362c6b39a19cf32663c8b98f1f462c653c58c6706339ec9cc78d632e319c78ca78e19cf3c663cbd99f15433e319c68c27cd8c27c680271edc3c263d0eff3f03ba326bdb6e4abe35a0e3ff178dbe0cb6b964d2e58eb22f828ec67a5d726c8b71ba08bea44d3a796b4b264e682b0deb64af12382e31e14933e319c68ca79a194f6f663cf398f1d431e319c78c672d339ec9cc786630e3a962c6d38b19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ce751663cb5cc78c630e399c68c6722339e51cc78e630e339c38c6701339ea5cc788630e36965c6339219cf50663c49663c8dcc782630e399ca8c6736339e11cc781a98f13431e32967c6338519cf2c663cf399f18c66c6339c19cf12663c2dcc78ea99f1cc64c633dec1e36b5e66eaffa7b269fd0c13db1ef643e6bb79973df974c594d5d5944bfc64af02f2ac301d43babf0ab7252e7bbc063e2bb80231bae0c997b06f675c2882edb06f6714c376d8b7338a615b621e1ef32bd1db4ee118285a62d67a1ad278dcf9183be6c9cf1bdabca8bf157ad58ad5052b5609c87319e277d543fc5ced28ad93bd4299873060c67a5113445b2fae45efd36fbe414a71bd66c517fdbaee29a6616de9f522d80e6b4b8b613bac2d2d866d89b9c45c627e67c7fcfd261de17546126de87b283a7fbc1fecbe6ad2b108edeab25e3165d1375389e355e0a13c6fc03d9dd43f39e6ef8498db697a4681df15f0f50c296c9f14e3f955d83e2986edb07d520cdb12f3f0987fc083ed7870e35c127ac9f58ce203c0f38a071e4f7e66ceb5af593e9db17c4a401e6cfb5ef3e0670cec52d9b4fe1af0d082dfd1f5510ff2d9e761dfd1e5c053cf8ca78519cf12663cc399f18c66c6339f19cf2c663c5398f19433e36962c6d3c08c6704339eabcc786633e399ca8c6702339e46663c49663c4399f18c64c6d3ca8c6708339ea5cc781630e399c38c6714339e89cc78a631e319c38ca79619cf5c663c95cc7812cc78a633e399c48c672c339e14339ed5cc789631e359c48ce73a339e5ecc78aa98f1cc60c6339919cf5a663ce398f1d431e399c78ca737339e6a663cc398f1a499f1c418f0847d4797fe7f1574d4678fdfd67ddda45f015d99c306f51dbd06ba0a93a632f477797f34e0e6b2314ebec629a0ad34ac933dfc8eeeeb4c78d2cc788631e3a966c6d39b19cf3c663c75cc78c631e359cb8c6732339e19cc78aa98f1f462c6739d19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ca79619cf18663cd398f14c64c6338a19cf1c663c0b98f12c65c63384194f2b339e91cc788632e34932e36964c6338119cf54663cb399f15c65c63382194f03339e26663ce5cc78a630e399c58c673e339ed1cc788633e359c28ca785194f3d339e99cc78c63b78e4db30d1da96eff1f08cb987ef5466be958adff9d18b3df6260d69cfefcbd6c7831bbfe3f9db78f0bb283eda254f7e66bed1f906b04755ae8ed59b56acae5ab14a401e6478d343fc6241f8f756c89e300b7318b3e6a1f609c7fd51beeb4c1849f71af0f86837b4eff44c81cad7df309cddbbc3ae87f344bd8e037def97be833fc2da2f1590e7b3fd3bd8e61bb64af83fed37edcf154ba77df0fd2d7f2a9bd6c91e3e43c2ef82fb3aefdbdfacbeea88c5bb69dbc377cb6b0bbde6b8e2e079273a9e24b63168eb9227df2f07f9fb7ec9c113a1efb509e0f13c574aa6eda2fe182a5fb70fcb7b7b8d793d1ef7d476d9f319e21c1ed7a1ed5a95a3ed2a0b3a8e19fa5f8dd1dbe7ac778cde2ea32690fb291fb6e51e56ee61038b8716bc878dfe3a23952cf41e16e7cff071bef5743d95f4744ecadcc35eb46265cf3592803cc598e3ebb2c543eb644f98855998855998855998855998855998855998855998855998855998855998f933878d39a17cd79930920efbac7d3ce7d7bed37baa54beee1bed56dd6137fa7e8b5412c72850bfed28cb679c377455bf0eb6b861c3f11c38e6c4b52f7df4f7e7da9764af32b8791c8acf7ebab03130c5e8232cc4764d64b6532bfcecdf5492e644bf6aedd3eb8efde9a3adc0f153b4e4ea6bbc083c3ee6bdf7357644fb74c1f2e98ae55302f2e03c16173cf8e93aafd1fa05e0a105e711f475ce082c9ec0111f5aca98f1d433e3e9ce8ca78519cf12663c8399f1cc67c6f300339e01cc784633e399c58ce72e663c5398f1f460c653ce8ca789194f03339ee1cc784630e3b9c68ce711663c0f32e3b98719cf63cc786633e3e9c38c672a339e9ecc782630e3a960c6d3c88c27c98c6728339e91cc785a99f10c61c6b38019cf52663c35cc78ee65c6338719cfddcc78a631e319c58c6722339e38339e31cc78ba30e3a965c6f328339e41cc780632e399cb8ca72f339ee9cc782a99f12498f14c62c6d39519cf58663c29663cab99f12c63c6b38819cf43cc78ee63c6d38f19cf0c663cbd98f15431e399cc8c672d339e71cc78ba31e3a963c6e37b1c73a13c0f33e399c78ce77e663cfd99f1cc64c6338c194f9a194f6f663cd5cc78620c78e2c1cdef9ec4e1ff574047ef48e0fb75658ef2681c23e5d7e7e95f0eb8b9ec3247d9171d0c18a7f3e04bdaa493b7b6dcf05e48cc944beb64af12382e32e1a966c6d39b194f9a19cf30663c3399f1f467c6733f339e79cc781e66c67399194f1d339e6ecc78c631e359cb8c6732339e2a663cbd98f1cc60c6d38f19cf7dcc781e62c6b38819cf32663cab99f1a498f18c65c6d39519cf24663c09663c95cc78a633e3e9cb8c672e339e81cc780631e37994194f2d339e2ecc78c630e38933e399c88c6714339e69cc78ee66c6338719cfbdcc786a98f12c65c6b38019cf10663cadcc784632e319ca8c27c98ca791194f05339e09cc787a32e399ca8ca70f339ed9cc781e63c6730f339e0799f13cc28ce71a339e11cc788633e36960c6d3c48ca79c194f0f663c5398f1dcc58c6716339ed1cc780630e3798019cf7c663c8399f12c61c6d3c28ca73b339e7a663c65cc78c65b3cf87ffdec83c6b75e001dfdbfb14ff6b7caf871c1b217811fc9b0b9317cc50c97b4232661736370e0a967c6d39d194f0b339e25cc780633e399cf8ce701663c0398f18c66c6338b19cf5dcc78a630e3e9c18ca79c194f13339e06663cc399f18c60c6738d19cf23cc781e64c6730f339ec798f1cc66c6d38719cf54663c3d99f14c60c653c18ca791194f9219cf50663c2399f1b432e359c08c6729339e1a663cf732e399c38ce76e663cd398f18c62c63391194f9c19cf18663c5d98f1d432e3799419cf20663c0399f1cc65c6d39719cf74663c95cc7812cc782631e3e9ca8c672c339e14339ed5cc789631e359c48ce721663cf731e3e9c78c6706339e5ecc78aa98f14c66c6b39619cf38663cdd98f1d431e3b9cc8ce761663cf398f1dccf8ca73f339e99cc788631e34933e3e9cd8ca79a194f8c014fd85c14f4ff72d09d33e96ba03b6bd2974177c6a42f80eeb44357e660217be74047e302ce828e9e4d9f011d3d7f205bfafae0ff0cb899b5cce153b983f5acc3a7738e6d713fd236e920dafd88b6d2b04ef670ae8c734c78aa99f1f466c69366c6338c19cf4c663cfd99f1dccf8c671e339e8799f15c66c653c78ca71b339e71cc78d632e399cc8ca78a194f2f663c3398f1f463c6731f339e8798f12c62c6b38c19cf6a663c29663c6399f17465c63389194f82194f25339ee9cc78fa32e399cb8c6720339e41cc781e65c653cb8ca70b339e31cc78e2cc782632e319c58c671a339ebb99f1cc61c6732f339e1a663c4b99f12c60c6d3ca8c6724339ea1cc7892cc781a99f15430e399c08ca727339ea9cc78fa30e399cd8ce731663cf730e3799019cf23cc78ae31e319c18c6738339e06663c4dcc78ca99f1f460c6338519cf5dcc786631e319cd8c6700339e0798f1cc67c6339819cf12663c2dcc78ba33e3a967c653c68c67bcc583634893a0a3742de8289d021da5eb4047e97ad051ba0174946e041da5c7808ed2634147e971a0a334f988ef6714639e0bb24565d3fa7960a4fb17d73e6902eef3964e739ff2c47ddee2a6f553c0e8f285d82600f7294ba7b94f7ae23e6571d3fa496074f9426c134147e949a0a3f464d04d067ba4a3f414d0517a2ae8283d0d74949e0e3a4acf001da5e9fd057c976116e8283d1b74949e033a4acf051da5e7818ed2f3cdafdec7272d9ddec7274c3a1d44bb8fc916954deb2780d1b5df896d01709fb0749afbb827ee131637ad1f0746972fc4b608b88f5b3acd7dcc13f7718b9bd68f01a3cb17625b02dcc72c9de63eea89fb98c54deb4781d1e50bb12d05eea3964e731ff1c47dd4e2a6f523c0e8f285d89601f7114ba7b90f7be23e6271d3fa616074f9426c2dc07dd8d269ee439eb80f5bdcb47e08185dbe105b2b701fb2749afba027ee431637ad1f0446972fc4b61ab80f5a3acd7dc013f7418b9bd60f00a3cb1762c3ef95acf5c478cd62bc5644db61d78cc5b01d76dd570cdb61d76ec5b01d764d510cdb61d705c5b01d766e2f86edb0f373316c879d638b613bec3c590cdb61e7ba62d80e3b5fc9f12dc777d4b6dfcd73c99d7a7cbf9b6deabbd9b6c8b5a25c2b16cbb69c4be45ab158b6efd46b4569cfc3db730fcf845271b0414bcc5a4f43fa00f0f878b6e6c9cfa42e77bf29eb9d08cbd5b1da67c56aad15ab04e4d90ff1dbe7217e31b04b65d33ad92b4566ac17b1e86c27e36003bf6fb897ca07dd1e93ae05dd6e934e816e9749d7816ea749d7836e874937806ebb49cf05dd36939e07baad267d0a745b4c1afb06379bf449d0b59b34f6c56d32e913a0db68d2d8f7f5b2491f07dd4b268d7d4d1b4cfa18e8d69b34f6edb499f451d0ad3369ec4b79d1a48f806e8d49b780ee05933e0cba55268d7d052b4dfa10e85698f45ad02d37e983a07bdea4f783ee39936e04ddfb4c7a0ce89e35e9b1a07baf498f03dd33268ddfe17cdaa4713cc553267d16748b4d1ac72f3c69d2f81dce274c1ac70b349bf444d02d34e949a07b8f494f06dde3269d06ddfb4d7a0ae85e31e9a9a07bd5a4a781ee03263d1d74af99f40cd0bd6ed23341f78649cf02dd9b263d1b741f34e939a0fb9049631fcd874dfa00e8ca4c7a1fe868fcf05ed0d13b4c7b4047ef99ef061d7dab6417e8e87b693b414763bc76808ec6096f075d4f93de06bab8496f055da5496f011d7d176433e8e85b57eda0ab32e94da0eb6dd21b4147df097d19743476f725d0d1fb3a1b4047ef0caf071d7d07a30d74f4ada975a0a3ef39be083a1a83ba0674f4dec90ba0a3777157818ebe2fb11274f44da915a0a3ef362e071d8d357d1e74f47ec973a0ab31e9f7816e90493f0bba874cfabda0a3ef213e033a1ac3f934e8e83d92a74037c4a417838ebe17f124e8869af413a0a3ef8a36836eb8492f04dd6326fd1ed08d30e9c74147ef8dbe1f74f46efd2ba0a3f1cdaf828eced91f001d9db35f031d9db35f071d9db3df001d9db3df041d9db33f083a6afb3f043a6afba9fdd0c7a93e7eaf98f57410dd7594b67735b871c9752d4f0cc813e5b5710278d0d6a5c87d4f65aec3e9faadcc944bf5e512d8be10b9edec3dc04553561753ee05cb7605e469efd7b16fcec3ffd3e0036d8779a86c5a1f05db9eb7caae32fe5ef4e4ef058b89b82f0213e5d9d6af23ef40d32077876d2264cbdccf525d0b2086b8a4218df367441fab54e6fee272013c178127fae3247b7feda34ee0b115f5fdb5fd6cc4ae6b09c87301e2e76b5cf1458b87d6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cd49f80e38328df15268ca4c3be2e1fcff9b1cf15fbbc7a40bf8eeffebdaea6dcd196cf1590676eff0eb68449e3b75568bf85ed4b0ffd8439f725d9ab047fb02fc8477f710c6c51d9971cb1e060bb2632dba9157ef66f2aa9c7c7e8fefdcbd63ebd62ed4fec17c7636820d453dae6baf98d437cae437c3c1ce339f70dd9c363e912e8287d15187dc41acf25d41e507f38d9c37ef1474c6ca95f3cfab63a95f4d96ed0f7a1ecfe76f495f20c87b66f8449e3d8902b505693e3ffb4e4eaa7a6f8699fcf44ef7366ff9e3665d1fe3de3b07d0a5823b25d8bb66346c80ee92b203dbe7f475efbdd288a35b1e3b79b90dddeeeaab55d02f29c75f89f0ea2f5ff8cc573c662d6756734d4b32638fffb6a93ce86c46814c488f2e07590877126ce369238c89ece43fbbf9b9507af5928cf3468a3b42fd4ce939f38b605cf01e73cf977c5f28fd6c91e5e1b5f0446db475d3fba5577f0dee9e3bf9e84f6e23cfc3f1ddcfaf8af27a10ee138adc02a7f34944f5cdd82f0730be579da3a8ffab8c7c07d69c713c797519ea5d00e5de8c4b5febb75df1676adefe37b94f1e0c67b6fbde43abfe339c647fbe2c9cfa4ebdc75def229017986809f1eae6372be5778066cfbfa0629d9a06ba80b562c2a204f9bd57684c511ef552f16c59794f37a70b4c317cab3c96aa74e7b60f2b9dff03a4b977bc9e12be5d906eddf0eb89ea7fd846de971c7ff69c9d51e50fcb4cf27a2f739b37fe99d32dabf271cb68f016b44b66f78bf9eaef7c90ee92b207d0ccedf948fe241b1c66f489ea4ed82f0ed2e58db2520cf4987ffe9205affc3be6d40f674ddd90df5ec385ceffb6a374f86c46834c488f2e0b3153ab753feb0f3be8ff1d5b9cefb9780d16e37f1dac527db658bcd7e86eaba1ea43cb42d5e0f5e857636eec8ebffd970f6daa69031ff9e9f91d7c7ac78a6c1ce48d053fda438d3fff01a97f2bce9fdf955b67e5cb0b8c90fecc33967719fb7e28acfa23e6addd3dac7a6eb59a7a76bce7abc16b3af87ce013be5f9b8753d645fc75d73f8629fa7cb829bef05dfb1f2e231996b3b3b7dc5da069f19b89ec9e2bb2e6781256dd2c95b596a9349642a73c42211dc7c4fe67ace40c701d6392acb3e5668db0ac8f319ab4db2f3ea366979ef8ef8d07ec467edae3ae9eb3952d87316b2a7195defd145df76dd99ef2b758facdc71cb4bb18fbbbb958ec676aa15cf15b96271cec1e3eb9966582cce396c47178bc615aef3872b16671d3cbeee31c36271d6613bc258ac723d5b70c5e28c83c7d7bd46582cce386c47178b31373cd7c8158bd30e9ee89f69e48e05f68915c27c960173772b1d8dedfae5aebe3b572c4e39787cf5dd85c5e294c37674b1a86d74ddb3bb6271d2c173b2c8b138e9b01d5d2cc68e733d5371c5e28483c7c3f3b59cb138e1b01d61bd5889cfd772c5e2b883c7d77c3061b138eeb01de1f56163ae6786188b630e1e5fcffec26271cc613bc258b468db47f388c551078faf796bc26271d4613bba582c6fd0b68fe4118b230e1e5f73e184c5e288c37674b16819ab6d1fce2316871d3cbee6d7098bc56187ed08efa132f5e2501eb138e4e0f135674f582c0e396c47178bd6ccb5d6c13c6271d0c1e36b1ea0b0581c74d88e2e16c9cc39f5401eb138e0e03950e4581c70d88eb05e64ee27f7e7118bfd0e9efd458ec57e87ed08cf23997ab12f8f58ec73f0ec2b722cf6396c47178b5599e74f7bf388c55e07cfde22c762afc37684cf5c32f5624f1eb1d8e3e0d953e458ec71d88e2e16759973eaee3c62b1dbc1b3bbc8b1d8edb01d5d2c5666fac476e5118b5d0e9e5d458ec52e87ed08af3b33edc5ce3c62b1d3c1b3b3c8b1d8e9b01de17567e6f9c58e3c62b1c3c1b3a3c8b1d8e1b01d61db99b9eedc9e472cb63b78b6173916db1db623bceeccc4625b1eb1d8e6e0d956e4586c73d88ef0ba33731ed99a472cb63a78b61639165b1db623ac1799b6734b1eb1d8e2e0d952e4586c71d88ef0b956a6eddc9c472c363b78361739169b1db623bc1fc93ce36bcf2316ed0e9ef622c7a2dd613bc2bea2cc35f8a63c62b1c9c1b3a9c8b1d804b67d8c33c158d058ac11562c2a20cf5af3d1761a8b1516472a03c795a12f1b23f7253baeece5105f36822f94e725f0a53be8a364f2e46ba6ced0f7fa696cfa5587af9467f3808ebc5b4d3a0efbe41a9475c4f17f5a728d41a2f8699fd747ef73a6aeb699b268ffae77d85e07ac11d9ae45db34369dec90be02d2870774e4a57c140f8a35b1eb6364834923bbbddd266bbb04e4d9e0f03f1d44ebff7a8b67bdc59c79ef01ea19d5233f6d57966943488c46408c280f8ed9bbea89c71e43481c644fe7a1fddfcdca83632829cf1968a3705c29f9190f6e1e37a9fd7bc9937f61f3c291bd04e82e02a3eda3ae1fb361ec672d31061dba3a934e41398d964efb3ac693af648bcaa6f531c0586fd28dc5674ce5cbd860316a9e711e6286f36cd092eb7c310e78c67ae0f1e467e63c34def2698ce55302f2e0bb8de33df81903bb5436ad8f07db3ef639c682cec943ad5854409e8f5bd78f6171a43274fd6d74f8e22b8e0d164f83c3f644cf71a4b2a94d9c5804db4d96ed7acbb63eb6b18ee925d7b1dd04cc133c30eb7227455f6ee6d8a6b9d4a83e939d7af0290d3188ca27b41db3ca267d05a4bf30e066068a079d3b895d1f475877c2b61b676d97803c131dfea723f67f92c583fb582ffabae153705de9e178c8d481891607add743ec2685c46e22c48ef234828eae71c6838eae1570ee41fa3f5e473444efafb3eda1f50660241dce67d8e060ac8f9e3195ab6dae0746d24d009e264f319b60f10cb5e283e7e56e561edab602f27c03ce8d71475e5df7ef8f75f845731c4638cf4fa65dedea215e38ff6200f109ac18d2420cdd838e391aa3e4e91974ccc1b8a97dc3c696d52b9f5899ed7a24b40a0b137f630e37ca4087e972872e086e9c6ab2027434d56417d0955961c1292e293f4d69e7235c180f2abbc2e2ec0e2c51dac6693a69c95575ba018f8faaacab0e4dd569aaced31bd7b4afc4fad1c5e2ec4cddd1ff2bcf912fac2caa07151e7c47262a9bd6c99e8e4fc2a45f6a59f1e2948dab37b7ad5cdfbe0961ed830bd3312b08f6af6b1bac24783051395dace0f4883e38f578f0da7c01d80b8025303cdda3e7c954d4bb4c592b5ad6ad5bb879f9ba352b666e5ebfa27dcd86f518d16e56e4c2a26d1ff27a71357598d7cedfd5a1b3179c49b71be86826ddeea023fb3d40471c3de17ff69ef0724c0c81f2a91aebff551887bb1a87a80ad26948b727fa98d19f2ed5a77ffd644c7fa94defbe3e4176eaddbe41766a5dfd56b5be52d053e5eaa971f554b87aea5b3dd5ad9eda564f655b1364a7aa7d28c84e453b38e8986a562f5f025e3db5acbed4d053c7eaa9621f0bb28feb4606d94f9be94f49e84b567d3baa2ff3f4ad85bee4d2975afab256dfb2ebc737fa32465f22eacb1f7dc9a22f83f5e5ef6413eb294176aa693db5f4f4203b75b49e2a5a4f0d3d3bc84efdaca75b9f1764a74e7f3cc84e4fbb20c84e5ddb1c64a7ba7d32c84e85be38c84e9bfb74909df25c4fb5aba7e0d5d39aebe979f5b4bd7aea723da5eff341767af2e541767ae0d6203b75b09e52584f2ffd42909d86584f39fe62907da4ad1fe5eb47dcfaf1b17ebca9bb1874d78a7ef4aabb047517a9ee32d65de87a48811e62a1879ce821387a48921ea2a587ace9217cfb82ec10cf03417608b01e12ad8788eb21f3fa15826341f61513fdca8d7e0549bf92753ac8be66a85fdbd3afa1ead772f56bcafab1aa7ec55b77b5e9c7a9fad1b27ee4aa1fffea697af5b4bc7a1a5e3dedae9e66574fabfb46909d36f78341765adc0f2bf988928f2af91d25bfabe4f794fcbe928f29f9b8924f28f903257fa8e48f947c52c91f2bf913257faae4cf94fcb992bf50f229257fa9e4d34a3ea3e4af947c56c95f2bf91b259f53f2b74a3eafe40b4afe4ec91795fcbd927f08b2f5f11f95fc93927f56f22f4abeace45f95fc9b92af28f9aa927f57f21f4abea6e4eb4abea1e49b4abea5e4db4afe53c97794fc9792ff56f25d25df53f27d253f50f243253f52f263253f51f253253f53f273256f29f985925f2af99592b7838ee9a3b1f1e86e5a9c4166bda5bd7d65db4bed35ed1b6ada36af6b5ff3d2baed355bd7b4bf50b361cbca8dabd66dd88a1b7fda6c4cf35c4fd9b8b1657bcd9af5ad2bb7d56cd8dc5eb36155cdf20d9bd7b7de70b2fc96d9e8be9b2db6b4b6861bfbf9ad90feaa93467f6db6eb63d6e7e4f6ed9dce04a4a2bc131b0d2def6414cdc51edd863d99bde6abd9b46e437b4db266bdfaab4eae1bb6ae6c1d5583ffdba482bca9bd66537bcbc6f69a551b37b4d5d48ec27297f7ea8413aff7f203b3a95ff6b753556570ff4e7832b57fe776c713fd6f81f4a94e1a5dd7190f377666a32b9d247c233c2c9b362f6fdfd8b2a23d7ce38fdccac61feb8c9b9feea49b6b0674c2d886ce6c747a40e7083fd619635f2fc058f0bf713e55c3fca20400", "isInternal": false }, { "selector": { "value": 2603445359 }, - "bytecode": "0x1f8b08000000000000ffed9d79741cc5b5c67b6459963d1acbf2be5bac5e4648a319c9b6b08d65b3181bb3790163360b5b3606db32b60c983d648310080961c90664634908d94312b2ef2464812c2404b213c8bf39efe49d774e4edebb3553f7e853b96bdeb4dc35ae96ef9c7335d557357d7ff7ebdb353dd5dd336f0441900a4a8f1164c706873ef8ffddfa3977788ff618d79573c9994a08674d4238472484b336219c2313c2599710ce5109e1ac4f08e7e81839155b4d30f81137ef1807bac6cd984e98a60d09d03493304dc72640d3c6201963d4b88470362584737c42382724847362423827258473724238a72484736a4238a72584737a42386724847366423867258473764238e72484b339219cc72484f3d884701e9710cee363e49c079c27e8e713f5f35cfdcc7de6ebe705fa39ab9f5b748eb57af924b256b236c567fc4f9d68c89315c83af4ff9af5ff3ac916922d225b4cd6457632d912b2a564cbc84e215baef35f41b692ec54b2d3c84e273b836c15d99964abc9d6909d45b696ec6cb273c8ce253b8fec7cb27564ebc936182c1bc92e20bb906c13d945649bc92e26bb84ec52b2cbc82e27db42d6437605d956b26d64bd64dbc976905d49b693ec2ab2abc97691ed26db43d647b697ec1ab27d64fbc9fa0dcd0e905d4b761dd9f506e741b21bc86e24bb89ec66b25bc86e25bb8dec4d64b793bd99ec2d646f257b1bd9dbc9ee20bb93ec1d647791bd93ec6eb27bc8de45762fd9bbc9de43761fd97bc9ee277b80ec41b287340b17fbfbc8de4ff601b20f927d88ec61b247c81e25fb30d947c83e4af631b28f933d46f638d913644f927d82ec93644f917d8aec69b24f937d86ecb3649f23fb3cd917c8be48f625b267c8be4cf615b2af923d4bf635b2af937d83ec9b64df22fb36d977c8be4bf63db2ef93fd80ec87643f32347f8eecc7643f217b5eff8fe7907e4af633ddfeb97efe857e7e413fbf68bce69764bf327cbf26fb8de17b89ecb7bafd3bfdfcb27efebd7e7e453fbfaa9fffa09fffa89fffa49fffac9fffa29fffaa9fffa69f5fd3cf7fd7cfafebe737c80ae34bedfa60e0d11dc434ee74f476a9f30f2cf609c1e087d26284fe1f3f376b7fad5ee667d66ea45e1e69f8ebf4729db19e7abd5c6ff89bf47293e19fa0972718fe497a7992e19fa297a7803f1dc03ca4f62bdf08ed4a818febb0067c2383c19a285f1daf0e7ca382c15a281f6fc73af08dd6be51e01ba37df5e04b6bdf68d68cac41fbba83b86a22d7a3d69b897bbdfadcccd8f879b7aaf5363ae21d173f6faf5a6f93035e551f7ab808c641dd4cd0be26f04dd4bef1e09ba47d13c03759fb26826f8af64d02df54ed9b0cbe69da37057cd3b56f2af86668df34f0cdd4bee9e09ba57d33c0375bfb66826f8ef6cd025fb3f6cd06df31da37077cc76a5f33f88ed3be63c077bcf61d0b3e1e3f8f031f1fc31daf7d6a4cf85700afd1fe1af09dc8632ef8e6f2780bbe793cd6826f3e8fb3e05b00b1d9978531847d2ddac7e391fadf42ddee0ee2aafffc36b5de4571af97d6acd6db15ff7a8be7ad4e0e0674ed86388b40ab25ba1de3b531ed183ba58de3b0bf16daaba02ff7633df83d85d9d57bc762dd5e52e6750b8dd765a0cfe290fcbb8378f3ef3278ba0c6655ff4b8123fe9a2db44bcd56fc885cb31ba1af597b7c7c331c6b760d7038a8d94e37359bcf49cd96e6178220bcf6f8187738d6ec26e088bf663ba5662b7f44aed96ba0af597bfc396738d6ec76e088bf661775cab141c58fc8357b2bf4356b8f3feb0ec79aed070e0735db25e36cc58fc8357b37f4356b8fe75d8663cdde0e1cf1d76c97a39a2d48cd06a5734541105e7b3c07381c6bf65ee088bf66b776c9b141c58fc835fb24f4356b8fe7a38763cd3e0c1c0e6ad6d5fc6c5e6ab6748e3c08c26b8fcf8d0cc79a7d4ab7d579869febf30c33c1f70bed9b05be17b46f36f85ed4be399057fcfbc0b682ec03153f22ef03cf415fb3969b757b38ee03df000e0735db29355bf12372cdbe047dcdda3b56b78763cd3e0f1c0e6a7691d46cc58fc835fb1af4356b8faf5f188e35fbb26eabe385dfe9e38513c1f7b2f6cd05dfefb56f1ef85ed1bef9e07b55fb1680ef0fda9705df1fb5af057c7fd2be93c0f767ed6b05df5fb4af0d7c7fd5be1cf8fea67dede07b4dfbf2e0fbbbf615c0f7baf67580ef0dedebd43e754e80af4ff99ef6d5434edd417cdbb6784d4a30f8913296bba1dde296279709065f57cdb15ae38f5550b99f14549e7b2bf0b439c83d0d312ae169039e5cfc3cc56b2fdae35f6f711b9f64689a865827415e790779a52016af9b97395e067c381ee443180bf133e653108bd7cdcb0560641f8e4f3cbef2fea3c6e6b1a9015e07fb52f1fd19e3750307c7ab853e2f340df41dafd91ae0ff3c0634401bc7ef9ce17354abed787d3faf9b97db819173cc559f315f29639bc1580f6c31f214356b3378da42b4385a633b18ab8bfb1fc7e0fd8ff7258e570b7dfea766a0ef8929a76cf9a8efb3589ff18ffff91cee9b95f0e481c7c518e3e87d2e87ef4dff09e2adb50e43ab9ca15506fa1440bf0e07fa957bafe478c22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2ec3f339e3fc6f36d61e7668f2423fbda81c7c53c7ff13b49f4ba78fdeabcce33705e27fef316f91c9e77e46bb2e61939d7429fe752036ccf869cf3c5f3bbad86cfd13517c56d89d75c74c332c7c373d0780d88ab73885983271ba2850fb19b638b9ddfea66fbe673ea3b01d4f7eab418db34ecfa9305864fd5e9bf6b06f27671ee2feab9481c4bb81de7b9bd4c70e8b5036eae71298d1f3c5ed50483c70f7c9fe98c3df6e073987cbebcd3885d0b7d5e4a0d6c9b85f0ffeee0d0eb72b00faf9b97e7c16b171aeb6e74976fd96b133a819bdb75466e2dc0cd7d5e81f1fc56d84f1cbcc7e5f1fab000780323277ee039fbf88f014ad710e423f074008f83ebc5da1d1debe4b01ee3be8660a1a155d83114f7e904fd62bf1e5eef1fe671302f733c61166661166661166661166661166661166661166661166661166661166661f69f19efef66d634f46bf784b14ad75d14cf67f0f76de079b1dd3503715d9f03e4734ef38d9c6ba1cfd335036cd7e8764370e8b516b66de9eadc9a6d5b72bc06c807cf05b9ba67bd60f01442b4f02176736cb14bd710c4bf7d07ae21c81bdbb4ddd89e78ee1cf7a15ba14ecdefb848833ed5b86ec9b66d381eee4b05f0711befcd76a135be9798d714713c3c777ea7d696cf9dc73f56e7732ec70dfeee2abe4e201f922bf7b907c6be7b751baf1f6987753d12f27f7e943b4fcdfa1d89eff1c2d83e7c8fd7c335037dcdefe362ada37e8f57ce789d8fdfe3751fd4d923f0feef6a4c5a1c846b340f34e23e781ce4e03a93d031d2bcb652f5e1ed3fcae883c72cdce77118a36cd7ae867df79183efe72bfbdd471c0f8f8d3b80d1ccd1bcc6f468bf46ec19182f16c2ffbb83c3bf46ec19a821bc4e2b30d63f1fd6cf5ca302fb7b0bf779d6781f75b56fd9ae41c37dcbfc3cc0bc780d1af7f9168c55ff84e32cf3d81fafd7aac6673bdbf5e11c0f8fa9a2e48ee342dcef8d588fc8c2f1b01e7f6cd4e3420b776bc86b7f6a792d6bc5d71ce36727533fa5c362784d772c3a94c69b2e2317dea716432edce797c67e13ff3153e9f833fe5c071f0ff1185408c995fbfc16f6b597e1f892b7138e95ff08f93f3fca1d7fb27e2ae7a5f1e75cdcbecbf4ba78fb2e0d897d0ab0c614bb1d63f3f127c7617f2db4df80f713eec77ab0d6ccaef6113e864376f3759dc6eb32d0674948fedd41bcf92f3578961accaa765e853afb071c7fba1aab9758349a0f1a711fbc278adf47b8bfed3dc6c5f5bee5de63c2be1f12c7f67f3a9dd30b6733e7f4c28e4fb80fbf168f4ffe1bc6d974485f73ae92df43e2bc6619ef11c942dc7f87c4ed8e59cf4c3058cf8cc1e13276a311bbb18ab19b8cd84d558c2d9a8be63e69eed3f77fe3f774d72480714402186b13c03832018c7509601c9500c6fa04308e4e00e3980430a681f148beb73bd0271f873e0da0534b08b7abfbf15b0c46f3bb1232062ff31cc963245fb7e158fdecf3366c049e6176ac19cb361ca79f7dde864d6e758c65bc1f9f00c60909609c9800c64909609c9c00c62909609c9a00c66909609c9e00c61909609c9900c65909609c9d00c63909606c4e00e33109603c36018cc72580f1f804309e9000c624cc552e70cb38e4cfd78ac7c16f4276e067f44a785c7fb7a6a33c8bd767f1758c717f479af9bb8e2d865678cf89ebdf834d05f6ef4ae578c22ccc36661c0bf1f7b9b95fd613c6b0ef1e76306e447e4f6975cb33e8b7b75bdd6e8bc8ef556e7f13b4f45e7534fc2668d8fd2d61dff7ce7d70dc3852d73162ece6205e2d3a2ad0a24af7603bf9befeb60432fbf21b033c3e85fdc640d613c62afdc640e4f7aa7c888ead6e19f3436574752f33d64b253cf8bd182e6aca4d9ee5bfcb99e3e1f77fe098e4ea9a7ef3fe385eaee43b4b84599885599885599885599885599885599885599885599885599885599885d90f663cc780dfe5ccfdb29e309adf93e4683e3ef2b99ace101d5bdd32e687ca88df7f161f4fe95c4d94efa95a043c2e6aca4d9ea57335e6f76d761a39e1f76de2feedea3bc816193cbcbc18b683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcd8ce71898350dfdb29e30b26f21f0b8f83da6a8e7411685e8d8ea96313f5446c5e3e037e43ab05e2ae1c1dff57051538ef21cf45b79bcee45464e19e883fbf7c90ef24c05f6df8fc3dfd51366610e63c6318b59d3d02feb0923fb16038f83fd3bf2d8df15a263ab5bc6fc5019154ffcbf61593a4fdf1581077fd3c8454db9c9b334f69bbf9564fe566706fae0feede077b3427f27899797c27688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917d27038f83f9f8c8e76a9684e8d8ea96313f5446c5b32c769e420eeba5129e65c0e3a2a6dce4593a57738a91d31223a70cf4c131e9140779a6202eaf9b974f81ed30dc990b096496daa80eb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc3ed4065ea3c3ac69e897f584917d4b816799039ea8d7112d0bd1b1d52d637ea88c8a6779fc3c1d582f95f02c071e1735e528cfe2b54edd464ecb8c9c32d007c7a46e0779a6202eaf1bf30ef476e81666610e61c6318b59d3d02feb0923fb4e011e07fb77e4b17f79888ead6e19f34365543c2b1cf12c8fc0b30278bae3e76977946771ec5f69e4b4dcc829037d70ff5ee920cf14c4e575f3f24ad80e4963c67d8959d3d02feb0923d618f338a8bbc863d28a101d5b13c0d89600c6d109601c9300c63430661d30a6743ce4e1e5ac5b7df271e8d3003af93aee6580c7d5366c34781a43b4f0751b8ed5cf3e6fc346e071b50d9b0c9ea6102d7cdd86e3f4b3cfdbb0c9ad8eb18cf7e313c03821018c1313c03829018c9313c03825018c5313c0382d018cd313c03823018c3313c0382b018cb313c03827018c38a771243fe71ec9cf6747f27385682e9ae70eef219a8be6a2b9682e9a07a279eef01ea2b9c79a27e13384300e6fc682a78cc8938a8f2787b963ac951ee4ce0c49632c2480d1f57546a2632c8cf9a1322a9ed31cf1ac8cc0731af09c1a3f4fbba33c8bd71a9e6ee4b4d2c829037db00e4e7790670ae2f2ba79f974d80e4963c67d8959d3d02feb0923fb4e051e57fb5794fd1df7af331cf19c1681e70ce071b1bd1ce559dcdf5719399d66e494813eb8efac7290670ae2f2ba7979156c87a431e3feceac69e897f584917da7038fabfd2bcafe8efbd7998e78ce88c07326f0b8d85e8ef22ceeefab8d9cce3072ca401fdc77563bc833057179ddbcbc1ab643d298717f67d634f4cb7ac2c8be55c0e3a0ee227fe6383344c7b604301612c0283a8a8e3e318a8e478f8ec2288cc2288c4782310963b8bccf449f1bc0cfe26be2e7e9c0cf6d95f0ac011e179fed1ce5599c1b38cbc8e94c23a70cf4c13a38cb419e2988cbebe6e5b3603b08b3308731e398c5ac69e897f584917dab81c7c1fe1d79ec5f13a263ab5bc6fc501915cfdad8793a72582f95f0ac051e1735e526cfd2d87fb691d31a23a70cf4c1fdfb6c0779a6202eaf9b97cf86ed1085b9904066d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d87c6ac78f8ba73664d43bfac278cec3b0b78d63ae0897a1dfeda101d5bdd32e687caa878ce899fa703eba5129e7380c7454d39cab378afc0b9464e6b8d9c32d007c7a4731de49982b8bc6e5e3e17b64314e642029945e7a131e398c5ac69e897f584917d67038f83fd3bf2d87f4e888ead6e19f34365543ce7c5cfd381f55209cf79c0e3a2a61ce5591cfbcf37723ac7c829037d704c3adf419e2988cbebe6e5f3613b08b3308731e398c5ac69e897f584917de7028f83fd3bf2d87f5e888ead6e19f34365543ceb62e7c9e7b05e2ae159073c2e6aca4d9ea5b17fbd91d379464e19e883fbf77a0779a6202eaf9b97d7c37688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917de7038f83f9f8c8e76ad685e8d8ea96313f5446c5b321769e420eeba5129e0dc0e3a2a6dce4593a57b3d1c8699d915306fae098b4d1419e2988cbebe6e58db01d863b732181cc521bd56196da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38dd987dac06b7498350dfdb29e30b26f3df06c70c013f53aa20d213ab6ba65cc0f9551f15c103f4f07d64b253c17008f8b9a729467f15aa70b8d9c36183965a00f8e49173ac833057179ddbc7c216c076116e630661cb398350dfdb29e30b26f23f038d8bf238ffd1784e8d8ea96313f5446c5b3297e9e0eac974a7836018f8b9a72946771ecbfc8c8e90223a70cf4c1fdfb220779a6202eaf9b972f82ed20ccc21cc68c6316b3a6a15fd61346f65d083c0ef6efc863ffa6101d5bdd32e687caa87836c7cfd381f55209cf66e07151538ef22c8efd171b396d3272ca401fdcbf2f7690670ae2f2ba79f962d80ec22ccc61cc3866316b1afa653d6164df45c0e360ff8e3cf66f0ed1b135018c6d09602c2480d1b18ef9a1322a9e4b1cf16c8ec07309f0b8183f1ce5597c9fbfd4c869b3915306fa601d5cea20cf14c4e575f3f2a5b01d92c68cfb12b3a6a15fd61346f65d0c3caef6af28fb3bee5f9739e2b92402cf65c0e3627b39cab3b8bf5f6ee47489915306fae0be73b9833c531097d7cdcb97c376481a33eeefcc9a867e594f18d97729f0b8dabfa2ecefb87f6d71c47359049e2dc0e3627b39cab3b8bff718395d66e494813eb8eff438c833057179ddbcccf192c88cfb3bb3a6a15fd61346f65d0e3c5b1cf044dddfb70063d8670e5f190b0960141d45479f1845c7a34747611446618cc6b822018cb2ad85d157c6ac03c6948e873cbc9cad42ec4623766315633719b19baa185b3417cd4573d15c3417cd0f37b6682e9a8be6a2b9682e9a1f6e6cd15c3417cd4573d15c343fdcd8a2b9682e9a8be6a2b9687eb8b14573d1dc8cdd137fecc8d773f700cf16075a38ca33a7d67b855ed77f62d44f69b5d5d02a6b6895813e57807e5b1de81776cd352f73bca8cc277ac0ec28767e2cad6334e4cf310a861e2afe3647b9dbc6bc6d55886d1bf3aa11db36e65523b6682e9a8be647afe6d8ae0d0ebd0749ada357b747ea65eebf025ec77d1e68283d3706b23d5dc4967d483417cd45f323a139ea5253059ec0e009caf0ccf68c67aa673ce33de319ed19cf08cf78e67ac633c7339e699ef14cf08c678c673cb59ef14cf78c67a2673c69cf78467ac633cf339e199ef1ccf78c6792673c0d9ef1643ce3a9f38c67a6673c933de319eb194fa3673c0b3ce319e5194f8f673cb33ce399e219cf38cf789a3ce3a9f78c27e5014f3a38f49a9a34fcbf077c35c66bd5783571fcc0ffb76b7f0dbc66876e8f0859f776f0f1b9aa1d21af459db6432eddba9d3bbc4751278cd50dcb1caf01387678c253ef194f93673ce33ce399e219cf2ccf787a3ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1ccf38c67a4673c69cf78267ac633dd339e5acf78c678c633c1339e699ef1ccf18c67ae673c233ce319ed19cf78cf78a67ac633db339e9a109e1e473c3c7fc9ebe6e51e4f623bd80ec5fb16af7494d34ebdae3abd5ee6e778b5d0e721fdc14dcd47e06b99cb9c6fc6b9a69da051afa35c6cd72ef75621b6eddae56ac4b65dbb5c8dd8a2b9682e9a8be6ea11e33df59d72bf45e53c72bf45791eb9dfa23ccf5ccf78e47e8bf23c72bf45791eb9dfa23c8fdc6f519e47eeb728cfd3e3198fdcff519e47eeff28cf23f77f94e791fb3fcaf3c8fd1fe579e4fe8ff23c72bf45791eb9dfa23c8fdc6f7128cfff77bf05de27c1e7a67ac177a56ef780af262406af6727f86a759bd7a1c69b39e30f65a881d75c15c27565483c8e7355c86baba13bc6ea86658e87f76f5ce5094fbd673c4d9ef18cf38c678a673cb33ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1f478c633cf339e919ef1a43de399e819cf74cf786a3de319e319cf04cf78a679c633c7339eb99ef18cf08c67b4673ce33de399ea19cf6ccf786a42787a1cf1d8aea3eda9426cdb75b4d5886dbb8eb61ab14573d15c343fba35bf3afed81de960f06fb6a947ca58ee86f6d5c0e3e2fdce519ec5fbef76e975c5788f434e69b5dbd0aac7d02a037d76817ebb1de89782b8bc6e5ee678c22ccc3666157b4ffcb18be30cc6667d0283871f7b1c6be128cfe278d017846bccf132d007b7799f833c531097d7cdcb7d21b19b8378b5d85b81167b4378f656590b8e1795795702997dd059c5be26f6d8f95cda88cdfa04060f3fae71ac859b3c4bfbd6be205c638e97813e58a7fb1ce49982b8bc6e5ede07db4198855998855998855998855998855998855998855998855998855998855998fd6656b1f7c71ebb347f8fb1599fc0e0e1c77ec75ab8c9b3347fdf1f846bccf132d007b779bf833c531097d7cdcbfdb01d84599885599885599885599885599885599885599885599885599885599885d96f6615fb40fcb18bf7f9606cd6273078f871c0b1168ef22ccedf5f1b846bccf132d007b7f9b50ef24c415c5e372f5f0bdb419885398c59c5be2ef6d8a5f379189bf5090c1e7e5ce7580b377996c683eb83708d395e06fae036bfde419e2988cbebe6e5eb613b4461ee4b20b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a3476715fb60ecb10bc5f97b8ccdfa04060f3f0e3ad6c24d9ea5f9fb1b82708d395e06fa609ddee020cf14c4e575f3f20db01d863b735f0299a536aac32cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb30fb5a162df187fece2fdec189bf5090c1e7edce8580b477916af7fb92908d798e365a00fd6e94d0ef24c415c5e372fdf04db419885398c59c5be39fed8f9b4119bf5090c1e7edcec580b477916c7835b82708d395e06fae036bfc5419e2988cbebe6e55b603b248d19b75f2abed8c5eb3639468d7e56be5b757b04f86ed3ed5af0bd49b74782ef76ddae03df9b757b14f8de02b9b1efadbabd007c6fd3ed1ef0bd5db7b781ef0eddbe1a7c77eaf66ef0bd43b7f780ef2eddde0bbe77eaf635e0bb5bb7f781ef1eddde0fbe77e9763ff8eed5ed03e07bb76e5f0bbef7e8f675e0bb4fb7af07df7b75fb20f8eed7ed1bc0f7806edf08be0775fb26f03da4dbbbc0f73edd1e0dbef7ebf618f07d00dafcfc41dd6e00df87743b03be87757b2cf81ed1ed46f03daadbe3c0f761dd6e02df47747b3cf83eaadb13c0f731dd9e08be8febf624f03da6db93c1f7b86e4f01df13ba3d157c4feaf634f07d42b7a783ef93ba3d037c4fe9f64cf07d4ab76781ef69dd9e0dbe4febf61cf07d46b771fb7e56b76f061f8f03b7808fc7815bc1c7e3c06de0e371e04de0e371e076f0f138f066f0f138f016f071ddbd157c5c776f031fd7dddbc1c7757707f8b8eeee041fd7dd3bc0c7757717f8b8eede093eaebbbbc1c775770ff8b8eede053eaebb7bc1c775f76ef071ddbd077c5c77f7818febeebde0e3babb1f7c5c770f808febee41f071dd3d043eaebbf7818febeefde0e3bac371a159b73f08be6374fb43e03b56b71f06df71bafd08f88ed7ed47c177826e7f187c27eaf647c03757b73f0abe79bafd31f0cdd7ed8f838fdf9b1e035f56b71f075f8b6e3f01be9374fb49f0b5eaf627c0d7a6db9f045f4eb79f025fbb6e7f0a7c79dd7e1a7c05ddfe34f83a74fb33e0ebd46d1e17d4fed700b9709e0dc07d52080ffbea81a73b88f7988963f1ba79390f8cac637bf519f39532e60c46c5d3e14033ac0d7e94fb4cd2013c05073c8ef22c7e26e93472ca1b3965a0cf899067a7833c531097d7cdcb9d10dbc536472deaf47ae71a5ad4429f37f41b9a7aff2ba723af43d56f7b482eae74cc193cb990d88b1debc8ebe631717115622f3462b719b1713ce747b97d7b21302f72c0acd6db15ff7a8bfbf6c97a5d5ccf1ca70d725a021ac49513c64e69e338ecaf85f6ff360df4e57eac07bf7732bbda8f785b22bbf9ba0ee37519e8b33824ffee20defcbb0c9e2e83591dffff57d3008783fda158038b0d0e5e6e03edba2cda2d06edb84f3bf8f818a7137c7cacc0ebc063253c8e68893fdfd0b187975b80917d0b81b12584b13d7ec6e2b14e8bc1d812a22dfb1601cf42479a2d3278e61afae0fbf228a30fbfb616fa8cd71fec1a83c19f21b8afaafdb1a981bcf833f37f8278c7d53a077ae1e7f900f4090c0df9c10cf5c1c067fe3879c604039fe9f7f7f7edebd9d1bbaeb7675b0ad06a0d4c7c4e85a451033e6c8f08f105c1e0a90b9cf2e4a90b9cf2ac3164c12913eeaf3e4aa9b4787aa077f7cefe8d7b7af76cdd77706f7fefb6b57d3b907aa4418fa4b60c90147dfca80f062659ba83784f76d419b1ca154f3d3c8f8a9fa7dd519ec5379ed1464e75464e19e83312fe37da419e2988cbebe6e5d121b1631c888a5a8ca9408b31213c63aaac054e54b30ff754fe3f9e9ca83172c13d1a7332eb3cd68438e009b0fe948653ff533bfb489dcca8606063f3e8a98e2ad54650339cea5d4bcd60aa83333504a919493503a9661cd50ca39a515433886ac650cd10aa19413503a866fcd40c9f9ad16b0e4a33766a864ecdc81d0f5cdf0356f5895abd3baa19353583a666ccd451957af7574722eae8571da9a94f6feae8407daa54330cea9d561dc5a87768f5aeaa8ed4d4119a3aa25647984bc996919d42b65c6bbd826c25d9a964a7919d4e7606d92ab233c95693ad213b8b6c2dd9d964e7909d4b761ed9f964ebc8d6936d20db487601d985649bc82e22db4c7631d9256497925d467639d996a07456ea0ab2ad41e96c542fd976b21d645792ed24bb2a289d955233efea4c943af3a4ae8651679bd4d9257536499d3d52678bd4d9217536489dfd51677bd4d91d7536479dbd51676bd44cbd9a995733f1b705a5997635b3ae66d2d5ccb99a295733e36a26fc8ea034d3ad66b6ef0a4a33d76aa65acd4cab996835f3ac669ad5ccb29a495633c76aa658cd0cab996035f3ab667ad5ccae9ac95533b76aa656cdccaa99d84783d24cab9a595533a96ae654cd94aa99513513fa44509ae954339b6a2653cd5caa994a3533a96622d5cca39a69fc1cd9e7c9be40f645b22f913d43f665b2af907d95ec59b2af917d9dec1b64df24fb16d9b7c9be43f6dda05483df27fb01d90fc97e44f61cd98fc97e42f63cd94fc97e46f673b25f90bd40f622d92fc97e45f66bb2df90bd44f65bb2df91bd4cf67bb257c85e25fb03d91fc9fe44f667b2bf90fd95ec6f64af91fd9dec75b2378281330e3850fc462ff0ec774f7f7fefeebdfdcdfd7dcdbb0feceadfb977d7c1e6eb76f65fd9dc776defbeedbbfaaec317dfa187269eda5fb16f5fcfc1e69d7bb6f55edfdc77a0bfb96f7bf3157d07f66cdb8f2f7a4cbf68e6a1117bb66db307fb52cd61907e7588419fd3afe39326abcbe7f6fc50047971282ffad71013ba5f9f8a5aaa97d7978e689bf7efeaeb6fce35efa1bf3dbbe835bddb5a9bf17ffb49e4fdfdcdfbfb7bf6f5376fdfd7b7bbb9bd15d7fb60660849bcde348417358daf3cf3e0ff00fdc58975355f0300", + "bytecode": "0x1f8b08000000000000ffed9d77741cd5f5c767654996bd5acb927b17d5658db45a49b6856d9069a677530dd896646370015bf44e1a0921210d924008e924815448ef0909a47712d220014280ff7ee7fcf21fe777dfeebb475f3fcfec6f479ebb7e23df3de76adf5cbd9dfbb9dfb9f376f6cdccee6b41106482f2631cd9c1c1de0ffe7fbf7d2eecdba32bc1751524393329e1ac4b09e7b89470d6a784b321259c8d29e11c9f12cea694704e4890d3b0d5057b3e92e69d28a06bd28cd99469da9c024d7329d374520a346d09d231464d4e09676b4a38db52c23925259c5353c2392d259cd353c23923259c3353c2392b259cb353c23927259c7353c2392f259cf353c2b920259ced29e13c28259c07a784f39094701e9a20e762e03ccc3e1f6e9f17dae745f699fb2eb1cf79fbbcd4e6586f978f20eb20eb347cceffcc8986225937598ff3bf5eb26564cbc956d8ffb5dbfff5911d49b6926c15d96ab2a3c88eb65aac213b86ec58b2e3c88e273b816c2dd9896427919d4c760ad9a964a7919d4e7606d9996467919d4d760ed9b964ebc8ce233b9fec0287e542b28bc82e265b4f7609d9a56497916d20db48b6896c806c906c886c33d916b2cbc9b6925d417625d936b2ed643bc876925d457635d92eb2dd64c364d7905d4b761dd9f58e663790dd487613d9cd0ee72d64b792dd46763bd91d647792bd81ec8d646f227b33d95bc8ee227b2bd9dbc8ee267b3bd93d64ef207b27d9bd64ef227b37d97bc8de4bf63eb2fbc8ee277b3fd907c83e48f600d983968577840f913d44f661b287c93e42f651b28f917d9cec13649f24fb14d923649f26fb0cd967c91e257b8cec73649f27fb02d917c9be44f665b2c7c99e20fb0ad957c9be46f675b26f907d93ec5b64df26fb0ed977c9be47f67db21f90fd90ec47644f92fd98ec27644f913d4df653b29f91fddcd1fc1764bf24fb15d9afedff787ee93764bfb5eddfd9e7dfdbe73fd8e73f3aaf7986ec4f8eefcf64cf3abebf90fdd5b6ff669fff6e9fff619f9fb3cfcfdbe77fdae77fd9e717ecf38bf6f925fbfc6ffbfcb27dfe8f7d7ec53ebf6a9f5f23bba9addc6e0a461efd41426352cfd0a03937c1621f16ecf9305a8cb3ffe3e776ebafb7cbfcccda35d8e506c7df68971b9df534d9e526c7df6a975b1dff14bb3cc5f14fb3cbd31cff0cbb3c03fcd900e628addff8c65957067c5c8775e06b08f6d4c4f81a7975e01b1feca985f1f1766c04df04eb1b0fbe89d6d704beacf54d60cdc89aadaf3f48aa260a1bcd7a7349afd79eb799943cef80596f8b10efe4e47987cc7a5b05784d7dd8e122980c7533c5fa5ac137d5fadac037cdfaa6806fbaf54d05df0ceb9b06be99d6371d7cb3ac6f06f8665bdf4cf0cdb1be59e09b6b7db3c137cffae6806fbef5cd05df02eb9b07be76eb9b0fbe83ac6f01f80eb6be76f01d627d0781ef50eb3b187c3c7e1e023e3ebe3bd4facc98509f81d7583f8f47a5d7f0980bbe853cde826f118fb5e05bcce32cf896406cf6e5610c61df52ebe3f1c8fc6f856df70749d57f71d0acb72fe9f5d29acd7a5726bfded239ad55c188aefd10a70fb45a6ddb095e37d385b133d6380efbeba1bd16fa723fd683df5398ddbc771c69dbab2bbc6e85f3ba1cf4393224fffe20d9fc573a3c2b1de606c85fa666bbbbb466ab7ec4aed975d0d7ad3d3ebe198b357b327008d46cafd66cd58fd8353b007ddddae363dcb158b317008740cdf6c9d46cb1a0355b9eeb0a82f0dae3cf3963b166370347f235dbab355bfd2376cdde067ddddae3cfba63b166878123f99a5ddea7c706553f62d7ec3dd0d7ad3d9e77198b357b277008d4eca08eb3553f62d7ec03d0d7ad3d9e031c8b357b2f70245fb37d4235dbad351b94cf5b064178edf17cf458acd9878023f99a1dd0f9d9ea1fb16bf6abd0d7ad3d3e3732166bf651e048be6607a5e6678b5ab3e5eb358220bcf6f83cdd58acd96fd8b63937f63b7b6e6c3ef87e6f7d0bc0f707b8ce807d7fb4be83202f817d60b9ee03553f62ef03cf405fb7960fb6edb1b80ffc1c38046ab64f6bb6ea47ec9a7d11fabab5c7d72f8cc59a7d1638046a7693d66cd58fd835fb3fd0d7ad3dbe96662cd6eccbb66d8e17fe668f171681efefd6b7187cffb0be25e07bcefaf2e07bdefa9682ef9fd67704f8fe657d1de07bc1fa3ac1f7a2f515c0f792f57581efdfd65704dfcbd6d70dbeff585f0ff85eb1be5ef0bd6a7dcbc0f79af52db73e731e8bafa97acafa9a20f7fe20b96d5bba8e2ad8f3917196fba1bd5496a7900bf6bc4f806375241fabdbe47e44507dee1dc0d329907b166254c3d3093c85e4794a9f47bb925f6f691b1fe1689a855847405e4581bc32108bd7cdcb1c2f073e1c378a218cddc9331633108bd7cdcbddc0c83e1cc7f83e1cde7fccd83c3733c22bb02f95de9f315e3f7070bc7ae833ab6da46fbb656b86fff318d00c6d1ce70b8e4fa8564bb5823767f5c3721730728e85da3316ab65ec74189b802d419e92669d0e4f678816076a6c81b1bab4ff710cdeff785fe278f5d067fcb891be3d1951b662dcf759accfe4c7ff6201f7cd6a788ac02331c608bdcf15f0bde9f520d95aeb71b42a385ae5a04f37e8d723a05fa5f74a8ea7cccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaacccfe33e3f9633cdf16766e767f32b2af0b7824e6f94bdfa363d7c5eb37e7759e84f33ac99fb72816f0bc235f93b5c8c9b91efa3c9319617b3ae49c2f9edfed707c42d75c94b6255e73d10fcb1c0fcf41e3352052e710f30e4f3e440b1f62b72716bb3820b37d8b05f33d16e6bba0963adb34ecfa93258ecfd4e9c47123794b9cfb8b7b2e12c7126e27796e2f17ec7ded80cc352ee5f183c7abba60cff103df677a138fbde7394c3e5fdeebc4ae873e2f6646b6cd32f87f7fb0f77539d887d7cdcb8be0b5cb9c75b7c8e55bf1da845ee0e676a393db52e0e63eafc0787e4f5db92df41e57c4ebc302e00d9c9cf881e7ec933f06285f43508cc1d3033c02d78b75091deb14b01e93be866099a355d83114f7e905fd9609e817761ccccb1c4f99955999955999955999955999955999955999955999955999955999955999fd67c6fbbb99350bfdba3c61acd17517a5f319fcbd1c785eeca6ba91b8d2e700f99cd36227e77ae8f3adba11b6db6cbb39d8fb5a8ba86d29756e2d6a5b72bc66c807cf0549ddb3deedf0748768e143ecf6c46297af21487efb8e5c435074b66997b33df1dc39ee43f7409dbadf7191057d6a71dd52d4b6e178b82f75838fdb786fb684d6f85ee25e53c4f1f0dcf9fd565b3e779efc585d2c488e1bfc9d3f7c9d40312457eef3208c7d0fd9365e3fd205eb7a2ce4fffca8749e9af513fa7eb0d2f6e5efabe2eddb17127b25b02614bb0b6367ac711cf6d743fbd1ba91beeef75fb1d6cc6ef611f73bbac25e57705e97833e2b42f2ef0f92cdbfcfe1e973984ded7c04eaec3178ff971a935604e11a2d028db80f1e07095c67123a46bad7569a3ebcfdc73b7df09885fb3c016354d4b5ab61df7db45c28bfa8ef3ee278786cdc038c6e8eee35a607fa35624fc278b10cfedf1fecfb35624f420de1755a81b3fec5b07ee61a1f44bfb7709fa79df751a97d2bea1a34dcb7dccf03cc8bd7a0719f5fc258c53f36da1cec7dec8fd76bd5e2b35dd4f5e11c0f8fa9e2e48ee342d2ef8d588fc8c2f1b01effe4d4e3b208ee8e90d7fe25e2b5ac155f738c9f9d5cfd8c0e897f2faa1d6ffa9c5c789f5a01b9709fe79dfd46eab75d92cf75cfe3211e83ba4372e53e2fc1bef6321c5ff276c2b1f2bf21ffe747a5e34ffc2ed755c9e75cdabefc9da5bc7d5785c43e0a58138add85b1f9f893e3b0bf1edaff0bef27dc8ff560ad99ddec237c0c87eceeeb7a9dd7e5a0cfca90fcfb8364f35fe5f0ac72984dedbc0a75f65f38fe941aab574668b41834e23e784f14bf8fe0f7bb86bdc7485cef5be93d26ecfb21716c0fc6d59ecd9dd30b3b3ee13efc5a3c3e69b0cc669ccd86f475e72af93d24c96b96f11e913cc4c57b44f2427ae6823df5cc391c92b15b9cd82d358cddeac46ead616cd55c35f749739fbeff1bbfa7bb2e058ce352c0589f02c686143036a680717c0a189b52c03821058c1353c09805c6fdf9de2ea04f31097d9a41a7a521dc52f7e32f7518ddef4ac839bcccb33f8f917cdd8693ecb3cfdbb00578c6d8b16622db70b27df6791bb6caea98c878df9602c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d02c63929609c9b02c67929609c9f02c60529606c4f01e34129603c38058c87a480f1d014301e9602c634cc552e91651cf5e76bc323f09b903df819bd1a1ee9efd614cab3747d165fc798f477a4b9bfebb8d4d10aef3991fe3dd84c10fd5da91c4f9995398a19c742fc1d6fee97f78431ecbb8705c68dd8ef291db23c7bfcf67687ecb688fd5e25fb9ba0e5f7aa03e13741c3ee6f09fbbe77ee83e3c6feba8e1163b707c96ad153851635ba075be4fbfa3b53c8eccb6f0cf0f814f61b03794f186bf41b03b1dfab8a213a76c8321647cb2874ff580fd64b353cf8bd18123525799f5cd4f77f703cfcfe0f1c93a4aee977ef8fe3e56abeb3248cb93b85ccaaf3e89871ccc2ef86e17e794f18ddfbae84f6efd8637f6f888e1db28cc5d132cadc675dfe7d8038f7bd2d071e899a92c9b33cf6bbf7eff73a39e1fdfb382649ddd3b8dce1e1e515b01d94599995599995599995599995599995599995599995599995599995599995d96f663cc780dfc3c5fdf29e30b20fbfbf50e2fb5de39e07591ea263872c6371b48c8647e2fb15b15eaae1c1ef0994a829993cf7fcee6d5ef77227a71cf4c1fdfb48813c3341f4f751e3f7742bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3dfcc788e8159b3d02fef0923fb56008fc07c7cec73357d213a76c8321647cb6878047ec3b207eba51a1efc4d23899a12cab374aec6fdad24f7b73a73d007f76f81dfcd0afd9d245e5e05db419995398c19c72c66cd42bfbc278ceeef010beddfb1c7fe95213a76c8321647cb68785627ce533e4fbf3206cf6ae091a829993ccb63bffbbb7d2b9d9c72d007f76fa9dfed5bedf0f072a5df1aacc4dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f6ad021e81f9f8d8e76a5687e8d821cb581c2da3e1393a719eee02d64b353c47038f444dc9e4593e57d3efe4b4dac929077d704cea17c833037179dd987760b743ff1867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a3987da80dbc468759b3d02fef0923fb8e029ea30578e25e477474888e1db28cc5d1321a9e35c9f3f460bd54c3b30678fa93e7e912cab374add3314e4e473b39e5a00f8e49c708e49981b8bc6e5e3e06b683322b7318338e59cc9a857e794f18715f661e81fd3bf6d8bf2644c70e59c6e268190dcfb1423c6b62f01c0b3c123525946769ec3fcec9698d93530efae0fe7d9c409e1988cbebe6e5e3603ba48d19f72566cd42bfbc278cec3b067804ea2ef698746c888e1d2960ec4c01e38414304e4c01631618f3028c191b0f7978392fab4f31097d9a41275fc7bd1cf0486dc31687a725440b5fb7e124fbecf3366c011ea96dd8eaf0b48668e1eb369c6c9f7dde86adb23a2632deb7a580714a0a18a7a680715a0a18a7a78071460a1867a68071560a1867a780714e0a18e7a680715e0a18e7a78071410a18714e637f7ecedd9f9fcff6e7e70ad55c352fecdb433557cd5573d55c350f54f3c2be3d54738f354fc36708651cdb8cdd9e32224f26399e02e68eb124ceabc4cd3dec7a94343076a78051fa3a23d53111c6e268190dcf09423cc7c5e03901788e4f9ea74b28cfd2b5866b9d9c8e7372ca411fac83b5027966202eaf9b97d7c276481b33ee4bcc9a857e794f18d9773cf048ed5f71f677dcbf4e14e2392106cf89c023b1bd84f22cedef2739399de0e494833eb8ef9c24906706e2f2ba79f924d80e6963c6fd9d59b3d02fef0923fbd6028fd4fe15677fc7fdeb64219e1363f09c0c3c12db4b28cfd2fe7e8a93d3894e4e39e883fbce29027966202eaf9b974f81ed903666dcdf99350bfdf29e30b2ef24e011a8bbd89f394e0ed1b133058cdd2960541d55479f1855c70347476554466554c6fdc19886315cdf67e2cf0de067f15393e7e9c1cf6dd5f09c0a3ca724cfd3259467696ee03427a7939d9c72d007ebe034813c331097d7cdcba7c1765066650e63c6318b59b3d02fef0923fb4e011e81fd3bf6d87f6a888e1db28cc5d1321a9ed313e7e92960bd54c3733af048d4944c9ee5b1ff0c27a7539d9c72d007f7ef3304f2cc405c5e372f9f01db210e73770a995567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da39855e7d131e335eecc9a857e794f18d9771af09c2ec013f73afcd34374ec90652c8e96d1f09c993c4f0fd64b353c67028f444d09e559ba57e02c27a7d39d9c72d007c7a4b304f2cc405c5e372f9f05db210e73770a9955e7d131e398c5ac59e897f784917d67008fc0fe1d7bec3f3344c70e59c6e268190dcfd9c9f3f460bd54c37336f048d494509ea5b1ff1c27a7339d9c72d007c7a47304f2cc405c5e372f9f03db419995398c19c72c66cd42bfbc278cec3b0b7804f6efd863ffd9213a76c8321647cb6878ce4d9ca758c07aa986e75ce091a829993ccb63ff3a27a7b39d9c72d007f7ef75027966202eaf9b97d7c17688c3dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f69d033c02f3f1b1cfd59c1ba263872c6371b48c86e7bcc479ba0b582fd5f09c073c1235259367f95ccdf94e4ee73a39e5a00f8e49e70be49981b8bc6e5e3e1fb6c35867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b63af8732dbb6d6863247316b6d287314b3d6863247316b6d287314b3d6863247316b6d287314b30fb581d7e8306b16fae53d6164df3ae0394f8027ee7544e785e8d821cb581c2da3e1b920799e1eac976a782e001e899a12cab374add3854e4ee73939e5a00f8e49170ae49981b8bc6e5ebe10b683322b7318338e59cc9a857e794f18d9773ef008ecdfb1c7fe0b4274ec90652c8e96d1f05c943c4f0fd64b353c17018f444d09e5591afb2f7672bac0c929077d70ffbe5820cf0cc4e575f3f2c5b01d945999c39871cc62d62cf4cb7bc2c8be0b814760ff8e3df65f14a263872c6371b48c86677df23c3d582fd5f0ac071e899a12cab334f65fe2e4749193530efae0fe7d89409e1988cbebe6e54b603b28b3328731e398c5ac59e897f784917d17038fc0fe1d7bec5f1fa263470a183b53c0d89d0246611d8ba365343c970af1ac8fc17329f0488c1f427996dee72f73725aefe494833e58079709e49981b8bc6e5ebe0cb643da98715f62d62cf4cb7bc2c8be4b80476aff8ab3bfe3feb54188e7d2183c1b8047627b09e559dadf373a395deae494833eb8ef6c14c833037179ddbcccf1d2c88cfb3bb366a15fde1346f65d063c1b92e789bdbf6f009e4d423c1b62f06c021e89ed259467697f1f7072dae0e494833eb8ef0c08e49981b8bc6e5e1e80ed903666dcdf37d8e72cf4cb7bc2c8be8dc0235077b13f736c0ad1b133058cdd2960541d55479f1855c7034747655446658cc7786c0a18755b2ba3af8c7901c68c8d873cbc9caf41ec1627764b0d63b73ab15b6b185b3557cd5573d55c3557cdf735b66aae9aabe6aab96aae9aef6b6cd55c3557cd5573d55c35dfd7d8aab96aae9aabe6aab96abeafb15573d5dc8d2d70bd69ecebb9078067938016427916cc7a07edba5e4f503fa3d590a355ded12a077d0641bf2101fdc2aeb9e6658e1797f9700f9885621727d13a2640fe1ca3dbd1c3c4df2c947bd498b7b906b1a3c6bc5ac48e1af36a115b3557cd55f30357736cd7077bdf8364d6b1c5b61bec32f73f165ec77d9e6c2e3fb704ba3d2562eb3ea49aabe6aaf9fed01c75a9ab014fe0f00415786af1f9240ecf7ccf78667ac6d3e619cf04cf78c679c6b3d0339e059ef1ccf28c678a673c133de3a9f78c67b6673c533de3c97ac6d3e019cf22cf78e678c6b3d8339e699ef1347bc693f38ca7d1339eb99ef14cf78c6792673c2d9ef12cf18c67bc673cf33ce399e119cf64cf785a3de369f28c27e3014f36d8fb9a9a2cfc7f007c75ce6bcdf8b0be6de4ff975b7f1dbc66ab6d8f0b59f7e5e0e373555b435e8b3a5d0eb9f4db7661df1e259d30563f2c73bc66e0d8ea094f93673cad9ef14cf68c6786673cf33ce319ef19cf12cf785a3ce399e419cf74cf78e67ac6d3e8194fce339e66cf78a679c6b3d8339e399ef12cf28ca7c1339eac673c533de399ed194fbd673c133de399e219cf2ccf781678c6b3d0339e719ef14cf08ca7cd339e999ef1ccf78c67c0339eba101ea9dfc5e0f94b5e372f0f78125b603b94ee5bbc4228a72bedba1aed7a999fe3d5439f9fd80f92667e045fcb5cee7c33d6ea95a0d116a15ca2ae5dde5283d851d72ed72276d4b5cbb588ad9aabe6aab96a6e1e09de53dfebf3fd167a7f43651ebdbfa1328fdedf509947ef6fa8cca3f73754e6d1fb1b2af3e8fd0d9579f4fe86ca3c7a7f43651ebdbfa1328fdedf5099c7b7fbb9f57e8bca3c7abf45651ebddfa2328fde6f519947efb7a8ccd3e4194fc6039effef7e0bbc4f82cf4d6d011f9fffaa745f4616d67325f8f8f327afc38c375bdaf666a883d76c0be1ba22241ec7d916f2da5ae88eb1fa6199e3e1fd1bdb3ce169f28ca7d5339ec99ef1ccf08c679e673ce33de359e2194f8b673c933ce399ee19cf5ccf78063ce369f48c27e7194fb3673cd33ce359ec19cf1ccf781679c6d3e0194fd6339ea99ef1ccf68ca7de339e899ef14cf18c6796673c0b3ce359e819cf38cf782678c6d3e619cf4ccf78e67bc65317c223750f45d475b4b5b87f23ea3ada5ac48eba8eb616b15573d55c353fb035df9e7cec9e6cb0e76fb69947a57373db8147e2fd4e28cfd2fd773becba12bcc7a160b4dae96835e06895833e3b40bf9d02fa65202eaf9b97399e322b7314b3897d55f2b14be30cc6667d0287871f57096b219467693cb83a08d798e3e5a00f6ef3ab05f2cc405c5e372f5f1d12bb3d48568b5d5568b12b8467578db5e078719977a490d9079d4decddc9c72e8d33189bf5091c1e7eec16d64228cfd2be351c846bccf172d007eb745820cf0cc4e575f3f2306c8738cc57a79059751e1db3897d4de2b18b85ac139bf5091c1e7e5c23ac854c9ee5f1e0da205c638e97833e58a7d70ae49981b8bc6e5ebe16b683322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb332fbcd6c625f9778ecf2fc3dc6667d0287871fd7096b21936779fefefa205c638e97833eb8cdaf17c833037179ddbc7c3d6c0765566665566665566665566665566665566665566665566665566665566665f69bd9c4be21f9d8a5fb713036eb13383cfcb841580ba13c4bf3f73706e11a73bc1cf4c16d7ea3409e1988cbebe6e51b613b28b33287319bd837251ebb7c3e0f63b33e81c3c38f9b84b590c9b33c1edc1c846bccf172d007b7f9cd027966202eaf9b976f86ed1087f9ea1432abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3ea7ce0e86c62df9278eceed2fc3dc6667d0287871fb7086b21936779fefed6205c638e97833e58a7b70ae49981b8bc6e5ebe15b6c35867be3a85cc5a1bb561d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62f6a1364cecdb928f5dba9f1d63b33e81c3c38fdb84b510cab374fdcbed41b8c61c2f077db04e6f17c833037179ddbc7c3b6c87db95599943984dec3b928f5dcc3ab1599fc0e1e1c71dc25a08e5591a0fee0cc235e67839e883dbfc4e813c331097d7cdcb77c276481b336ebf4c72b14bd76d728c3afb6c7c6fb0ed71e07ba36dd783ef4db6dd00be37db7623f8de62dbe3c17717e4c6beb7daf612f0bdcdb607c077b76d6f06dfdb6d7b3bf8eeb1ed9de07b876d5f05be77daf62ef0dd6bdbbbc1f72edb1e06dfbb6dfb1af0bdc7b6af05df7b6dfb3af0bdcfb6af07df7db67d03f8eeb7ed1bc1f77edbbe097c1fb0ed9bc1f741dbbe057c0fd8f6ade07bd0b66f03df876cfb76f03d64db3bc0f761db9e00be876d7b22f83e026d7efea86d3783ef63b69d03dfc76d7b12f83e61db2de0fba46d4f06dfa76cbb157c8fd8761bf83e6ddb53c0f719db9e0abecfdaf634f03d6adbd3c1f7986dcf00dfe76c7b26f83e6fdbb3c0f705db9e0dbe2fdaf61cf07dc9b6e782efcbb63d0f7c8fdbf67cf03d61db0bc0f715dbc6edfb55dbbe037c3c5edc093e1e2fde003e1e2fde083e1e2fde043e1e2fde0c3e1e2fde023e1e2fee021fd7dd5bc1c775f736f071dddd0d3eaebbb7838febee1ef071ddbd037c5c77ef041fd7ddbde0e3ba7b17f8b8eede0d3eaebbf7808febeebde0e3ba7b1ff8b8eeee031fd7ddfde0e3ba7b3ff8b8ee3e003eaebb0f828febee01f071dd3d083eaebb0f818febee21f071dd7d187cedb6fd30f80eb26d1c3f0eb6ed8f82ef10dbfe18f80eb5ed8f83ef30dbfe04f80eb7ed4f826fa16d7f0a7c8b6cfb11f02db6ed4f838fdfc33e03bebc6d7f167c4b6dfb51f01d61db8f81afc3b63f07be4edbfe3cf80ab6fd05f075d9f617c157b4ed2f81afdbb6bf0cbe1edb7e1c7cbdb6fd04f896d9f657c0b7dcb679fc30fba9d9df3867d6a319f23b22849b7d4dc0dd1f247b0cc6b178ddbc5c0446d6bbabf68cc56a190b0ea3e1e911d00c6b881f953ee3f4004fb7008f509ea5cf38bd4e4e4527a71cf4391cf2ec15c833037179ddbcdc0bb125b6396ad168d7bbd0d1a21efa14ec9b9c799faca423afc3d46f57482e523a161c9e4248ec15c23af2ba794c5c5183d8cb9cd89d4e6c1cf7f95169df5e06cccb0598cd7afb925f6f69df3ed2ae8beb99e374424e2b4183a472c2d8196b1c87fdf5d05edb36d297fbb11efcdec9ec663fe26d89eceeeb7a9cd7e5a0cf8a90fcfb8364f3ef7378fa1c66f3396155db0887c0fe50aa81150e072f7782767d11daad00edb84f17f8f818a7177c7cacc0ebc0632a3c8ee84a3edfd0b1a737849b7dcb8031ec58a7903c63c5639d0230b26f39f02c13d26cb9c3b3d0d107df97c73b7df8b5f5d0e722786fcc86f435b53f373392177fb67e3d48765c6d14d00b3ff707a04fe068c80f66680a46e60692e499188c7cf6df3dbc73d7c62d43670f6d1ccc005abd8389cf999034eac087ed7121be20d8738a03a750798a03a750eb1c59706a85fb9b8f52262d9e4618dabe7578dd8ea11d03bb6eb86a7868f0d49d5b90bac1a147d2a80c90147dfc680a462663fa83644f9e343ab12a154f133c8f4f9ea74b28cfd21bcf0427a74627a71cf46980ff4d10c833037179ddbc3c212476820351498b89556831318467628db5c0096df6e19ecaffc7931d754e2eb847634e6e9d279a10073c0cd69fb170e67f66676fb0c98c0f4636368f9ee6a8d26c0433136adeb5cc4ca799d934439099b93433956666d2cc449a994733d3686616cd4ca299393433856666d0cc049a99bff6a03cb36766f2ccccdda1c0f514b09a4fd4e6ddd1ccbc99993633b3668eaacc27267324628e7ecd919a3932304704e653a5996130efb4e628c6bc439b775573a4668ed0cc11b539c25c45b69aec28b2a3add66bc88e213b96ec38b2e3c94e205b4b7622d9496427939d42762ad96964a7939d417626d9596467939d43762ed93ab2f3c8ce27bb80ec42b28bc82e265b4f7609d9a56497916d20db48b629289fed1a241b0aca67b9b6905d4eb695ec0ab22bc9b605e5b35d66a6de9ce13267b4cc5536e62c96396b65ce5299b352e62c9439eb64ce3299b34ae62c92396b64ce1299b342e62c9039eb737b509ee13733fa6606dfccd89b197a33236f66e0ef0aca33ec6646fdeea03c636e66c8cd8cb899013733de6686dbcc689b196c33636d66a8cd8cb499813633ce6686d9cc289b196433636c6688cd8cb099017e3828cff09a195d33836b666ccd0cad99913533b08f04e5195633a36a6650cd8ca999213533a26606d4cc789a194e33a3696630cd8ca599a13433926606f26b645f27fb06d937c9be45f66db2ef907d97ec7b64df27fb01d90fc97e44f624d98fc97e12946bf269b29f92fd8cece764bf20fb25d9afc87e4df61bb2df92fd8eecf7647f20fb23d933647f22fb33d9b3647f21fb2bd9dfc8fe4ef60fb2e7c89e27fb27d9bfc85e207b91ec25b27f93bd4cf61fb257c85e257b2d1839538103c70b768167cd370e0f0f6dbf6ab87d7867fbf66bb60d6fbd6adb0dedd76d1dbebc7de7b543bb366fdb791dbef83e3b54f1298135bb766dbca17deb8ec1a1ebdb775e33dcbe7373fba69dd7ec18dc8d2f7adcbe68eede11370e0e4607fb51dd3e903e35caa0cfd8d7f1c996932ae7f6ec6804796e342faa1f374a15ed29ac5576f99cf2116efbee6d3b87db0bed3be8efc66df49aa1c18e76fcdf6e1279f770fbeee18dbb86db37efdab9bdbdab03d7fbe3dc2892e86c1bc58b2e6cab3ef3e0ff009826ecb5716c0300", "isInternal": false } ], - "packedBytecode": "0x000000028df71de500000036421f8b08000000000000ffed9d079454c799ef6fcf0c493dcd30420284080392c8a1a72730e426470990002123230d0c20860c4316306491a364d972daf57a83d7de607bd76b6ff0aec306e7b45edb72ce3ee7f99df7ce79efed396f7775b6aabb3ecf9f9abaedeee156eb6bf8ee39df74dd6fead6f7fbbe5bb76ef5adea5bbf0a82201664b772258f049d37fa7fda7c26ef6cab8db0aca44fce5889709695086779897056940867b712e1ec5e229c3d4a84b3678970f68a9053b39505b76f51f3dee721ae5133c64b2ca6952510d34489c5b47709c4b42a288d36aa4f8970569708e7fd25c2d9b744381f2811ce074b84b35f8970f62f11ce0125c2f95089700e2c11ce874b84735089700e2e11ce2125c239b444386b4a84735889700e2f11ce474a84f3d112e17c2c42ce51c039c27c8e349ff4bfd1e6738cf91c6b3ec799cff1c6c70ab33f41c944cda6a4d6fa5f4a499d927a250de67f35e67f8d4a262969523259c9142553954c53325dc90c25338defb394cc563247c95c25f394cc57b240c942258b942c56b244c952258f2b7942c93225cb95ac50f2a492a794ac54b2ca6259ade469256b943ca3e42d4ad62a7956c95b95ac53f29c92e795342b59af64839216251b956c52b259c90b4ab6286955b255c93625db95ec50b253c92e25bb95ec51b257499b927d56ccf62b39a0e4a0924316e761254794bca8e4a892634a8e2b69577242c94925a7949c567246c95925e794bca4e4bc920b4a2e2ab9a4e4b2922b4aae2ab9a6e4ba921b4a6e2ab9a5e46525af28799b925795bcddb050657f8792d794bc53c9bb94bc5bc97b94bc57c9ef28f95d25ef53f27b4adeafe4f795fc81923f54f2474a3ea0e48f957c50c98794fc89923f55f2674afe5cc987957c44c94795fc8592bf54f231257fa5e4e34a3ea1e4af95fc8d92bf55f2774a3ea9e4ef95fc83924f29f9b492cf28f9ac927f54f24f4afe59c9bf5831ff9c92cf2bf982922f9affd1b3ae2f29f9b2497fc57c7ed57c7ecd7c7edd3ae61b4afed5d27d53c9bf59ba6f29f9b6497fc77cbe6e3ebf6b3ebf673ebf6f3e7f603e7f683e7f643e7f6c3e7f623e7f6a3e7f663e7f6e3e7f613e7f693e7fa5e4fd03b2e99e41c7960e226a77ea3736eb71120af688e0f64dc7a2dcfc8f3e6b8cbec2ecd327c5ae9bd9ef66e9bb9bfdee56393dcd7e4f4b5f6df6ab2d7d5fb3dfd7d23f68f61fb4f4fdcd7e7fd0c703785e6af45a576e5431d0513d2c035db7e0f698685d772a0e743d82db63a175741ebb83ae97d1f500dd7d46d7137471a3eb453153526974e920aa3a916cd6e526a22ed78c21f58e9e77832eb7ca136f9fe87937ea72ab3df0eafa71bf29ab0fd49bbe46570dba078cee7ed03d68747d41d7cfe81e005d7fa37b1074a6990afa81ee21a3eb0fba81463700740f1bdd43a01b6474034137d8e81e06dd10a31b04baa1463718743546370474c38c6e28e8861b5d0de8687eca30d03d6a74c341f798d13d023a6a531f051df5eb1e333add4efc7b00c7187d19e846523b0cba51d406836e34b5bfa01b436d2fe8c6826dd28d83768574e38d8eda28fdbf46934e07515d13a9cc353129ea7255c9badcc9d1979b19739b1274c4350d762641aca69a7484f37a6ad176cc08d9217d05a417425eca47f1a0fb0cb1ebfb4993494fcd715ca3755c02f23439fc4f07d1fa3fd9e2996c31ebfa3f1d38a2afb37529a9b3796f05d7d9d590d7ae7bd4e7b91bebec12e0f050671bfdd4d95452ea6cf6994310b8eb1ef57befc63afb0c70445f671ba4cee6bf155c67f7405ebbeed1779fbbb1ce6e068ee8ebeca446e91be4bd155c67db21af5df7e8fbefdd5867f70187873adb2ced6cde5bc175f60ae4b5eb1e3d8bb91bebec29e088bece4ef65467eba4ce06d9f1a32070d73d7a2e7837d6d9ebc0117d9dddd02c7d83bcb782ebec0720af5df7e819f5dd5867df031c1eeaacafe7b329a9b3d971f32070d73d1a2fb91bebec874c5a8f337cc58c330c02dd578d6e30e8be66744340f775a31b0a7e457f0db4d4cb3590f756f035f039c86bd7e51a93be1baf814f0287873adb287536efade03afb2dc86bd7bde1267d37d6d92f0287873adb247536efade03afb73c86bd73d9ad37037d6d9d74d5af717be63cd77d3bad78d6e24e8be6b74a340f73da31b0dbaef1bdd18d0fdc0e8c682ee8746370e743f32baf1a0fbb1d14d00dd4f8c6e22e87e6a7449d0fdcce86a41f773a34b81ee17465707ba5f1a5d3de87e65740d46a7c704687eca678cae27d84b07d19ddb38f8465bccda4f43bad62f4f32013c68ab2e7a5b75daf75490bfef75c053efc1f738d8c887a71e781aa2e7a9f572ef4966cf71ca8a691c6ca5c02f0ff7a84c7bd618dc1e53da277b09d0e1b53ac9c1d8143d632a06b6a86cda6f0246d2350023b5a574fde8b6b92ad6c1ebe15acadc9fd15e1a38c85e05e49939a0236f5fc35609ffa736a0128ec7b695535dd18c54471a8bcf98ca97b1c162f4747d6762d660f1b86c4ff164db8e05d5a92945b0dd64d9aeb76c631b425bae361efb761efad5b59efa8c99f6609a298bfaeb6407ef5bd3210651f984b6a9bf4e76485f01e954ac232fe5a37850db43ecfa3aa27389ecf67193ace31290678ac3ff7410adfff6f787a916b36e7747c23dc1c3f570db77362a9bf6eb2176534362370562477946828eee118da0a3b696cac0fe2db6c33efa4bb1e0f6be741af61b1cdc4dc0d8e060f4d0c74ce56a9beb819174938127e5296661df2d5362db473dcd5c976483fa6a740d91bd0ac833b8bc23ef6268337cd44fbc1668cbf73b50f4e72995f94e565f000f9e3b0ffdec5a4ff53189dfe7df08a2ad6b769b586fc52aec3b7fd243fcb0ee53d9b44ff684599885599885599885599885599885599885599885599885599885599885993f338e65e13c1ecad7c084d19e0fe5eb397fe6fd55a62c1c037addebfca054661c80e24ff301465b3e57409e5fc73ad87e00f383e8ff38dfc9752e7dcc95cc752ec99e6bbe92cff1e63a8ba7ce110b0eb66b22b39ddae0e7fca692fafd31fa1d6cf5d639b5c7e633ef76b374ba9ed69477f8ed63ecafd0b1486c4b281de5d85e0278d0968f7383d75e59707bfb81f7195ff358a8ada6f1f22996ed0ac8f31f309f682afc3f1d746e2f300f954dfba3e1d8a956d955fefccd3937017f0761ff1e837c6b04eedfccff29ebf0f17d26ede91e972a741e35b6d3d1f701b273085205f02481c7c77dcc535f2789f531ea3904f61c31571fcafe4d8ecfdfc3d8fd60dacff51b1e6116666116666116666116666116e6e49d6dc22cccc22cccc22cccc22cccc22cccc21c31b3e671bd8382f2d531612cd2bc8bcc7806bd8709c7c5ae9775d8f53d0648634e632c9ff177ab5f2beb607bd9a42b83ce732dc2cea5afb1b5b07349f65cef41f135a61c035b5476ad23161c6cd744663b3b8720faf3db318720659dd33aeb7ce2d8395e43ef837a6acfa788079de72478bac6739e1bb287d712ce7fa134fe36db47acf15e62cf29c2f7f2519e0f99d8d2d879f46d752ae9b3dda0f795d03c8194c357caf36168fb3e6ad2387fa40ecafab4e3ffb4e51aa7a6f8699f6744ef73e6fcce3465d1f99de1b09d06d6886cd7a2ed985536e92b20fda9b2ce0c140f8a35b1eb6b04dffb12765cbd755c02f24c77f89f8ed8ff19160f9e63bde9baf331a8679f86fbbfaf36697a488c46438c288fe7f7073ae703da731ab11ded61e5a163f1bd689f87362a6ceeaaeb1e30cd937f61f700b297083adf17f299637aafcf117b1dda8ba9f0ff7470e773c45e873a84f3b402abfc31503e71f508c2ef2d94e707d67dd4c31caebcde8fe3fa3e40bc38078df2fc14daaa7e66ae6621df07deacef76aeef03785c98efd82e447d6fc4fa882cf87e35caf33fadfa383584bbc971ecff0e39966265bf2bac32e81c3f3fef59cbb637332c5fe89a9a0ebe509e7fb7ae9be8fb4cd9fea7af77ca515f87daa05a87af94e73fe15a7b03fa97749ef0be9028effc7fda72f53f297edae759d1fb9c39bfb34d59747e67396ccf01d6886cd7a26dea7f921dd25740bab2bc232fe5a37850ac89bd123891dd3e6e8a755c02f2a41dfea723f67f96c533cb62ceb43df06eb204ccbff7d556a703778cc6408c7e630f74741fc1f780ba9e75f8ba9786f5e3f0775ba4c3b6bd1fc4b458bfa9b19fe9b9fa27632c7eec9f3c6c98ab4c9cedbcf6b34a3a2eca39cbf81b11ec17e26f447c7d474a04b7c7336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ad081c9b2a03461ff73abcafe6c3e8baff9503a3afdff1a50a60ac0346bcdf11a387f7b0d676752d0aeceb7403461fbf412df47935be83197f8b4c8c3ede855fe8fb6cf15dd6745c0f60f4b1c601ae55900fa36b8d8c9ef0e9613d8cdaaebe371fd7c8e8058c3edea51f0f6e7fffff6f63c4b59ce8b8fb80d1c738523cb8fdb9da6f63c477b0d37171cf8cb9eeed9ee783a40a7d0681f32ff01914c5c9358fc5d7b37afbd918ede3b37ae40dfcc531671f69aadf58a430eeb4e53a87aef8f4369f71f8bfdfe789b78ff152d9b43f1d184957e5378e39fb9ad3fcc6228571a72dd73974c5a78ff98c079dd7fff03466ed1ca3a47d1cc7255db5679eb8c5f3dbe2e862bc1f18677a629c5100e34c60a4e3fa0263da13e3cc0218d3c048c73d008c1e9e576718d30530e2735dd23f088cb33d31ce2a80713630d271fd80d1c7b3e738d8cd87710e30d271fd8171ae27c6390530ce05463a6e0030cef3c438b700c679c048c73d048cf33d31ce2b80713e30d2710381718127c6f905302e00463aee61605ce8897141018c0b81918e1b048c8b3c312e2c80711130d271838171b127c64505302e06463a6e08302ef1c4b8b800c625c048c70d05c6a59e189714c0b81418e9b89a12601c56028cc34b80f19112607cb404181f2b01c69e25c03816181f8f9e31f3fd7a69018c8f03cfb2e879eae360231f9e65c0f344f43cb59efccccccf5a6eca8afa1d692bac583d6ec52a01799643fc5678885f0cec52d9b44ff6845998c398350fb54fc41a877c4b993092ee09cf3c718b476fb9dac7157e799209473cb4ad27a3b7952ad4f727816765f43c997bd59305f0ac049ea7a2e7a9f5e467e69eb2caf2e949cba704e4c1766395073f636097caa6fd550edb3541b4b1589d472c563b785617391664af50e6e525c8cc21ced816126b1cf22d63c248baa73cf3c42d1ebde56a1f5d8c2bfc32a6bacaa879d644ce937d47f4ea0278d600cfd391f364ef29d1fb996d479fb17c5a6df994803cd8263de3c1cf18d8a5b269ff19380fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b19c71888350ef956326124ddd39e79e2168fde728d3bb81857f8654c759551f3ac8d9c273b56f34c013c6b81e72d91f364c76aa2f7333b56f3ace5d333964f09c883d7f7b31efc8c815d2a9bf69f85f320ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99718c8158e3906f0d1346d2bdc5334fdce2d15bae710717e30abf8ca9ae326a9e75d1f3647eabf96c013ceb80e7add1f3d47af2333356f39ce5d3b3964f09c883d7f7731efc8c815d2a9bf69f83f320ccc2ec62c6368b58e3906f2d1346d2bdd5334fdce2d15bae76ccc5b8c22f63aaab8c9aa739729eec38fd7305f03403cff391f364dbfee8fdccb6fdeb2d9f9eb37c4a401ebcbed77bf0330676a96cda5f0fe7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c621df3a268ca47bde334fdce2d15bccda4f43dac5b8c22f63aaab8c9aa725729ebacc58cdfa02785a806743e43cd9b19ae8fdcc8ed56cb47c5a6ff994803cd8266df4e0670cec52d9b4bf11cec3ddcebcaa0499a56e148759ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e63e65037708e0eb1c6215f331346d26df0cc13b778f4966bde8e8b71855fc654571935cfe6e87932ef24d95800cf66e0d9143d4fad273f33739d5eb07cda68f994803cd826bde0c1cf18d8a5b269ff05380fc22ccc2e666cb388350ef95a9830926e93679eb8c5a3b75ced988b71855fc6545719354fab279e170ae069059e2dd1f3d47af233d3f66fb57c7ac1f2290179f0fadeeac1cf18d8a5b2697f2b9c875263c66b8958e3906f331346d26d011e0ff5aee036a9d511c71525c0b8bc04187b9500e37d25c01807460ffdf4cc359cb078687fb3dff8a4a2884f25c4896bbb97001e5fe7b0cae2a972c482eb39ec6d3e399fc32ae0f1750eab2d9e6a472cb89ec33ee693f339acf61bc748dafbfb4b80b16f09303e50028c0f960063bf1260ec5f028c034a80f1a112601c58028c0f9700e3a012601c5c028c434a8071680930fa7ea691eb7b6e6b116c877d3f2b86edb0ef15c5b02d3197984bcc25e6127389f99dda96984bcc25e66f7ecc7d3ccbc5e7c6b4e5fa0ee17abeeb7b4c5e18f930ae62ca883cb1e87892e83bdadac6c077622835c65525c0b8bc0418258ed931d4ae306a9e1d9e78b615c0b30378b647cf53ebc9cfcc5cc39d964fdb2c9f129007ebc14e0f7ec6c02e954dfb3be13c941a335e4bc41a877cad4c1849b71d787c5d5f855cef787dedf2c4b3a3009e5dc0e3e37c79f23373bdefb67cda61f994803c78edecf6e0670cec52d9b4bf1bce43a931e3f54eac71c8d7ca8491743b81c7d7f555c8f58ed7d71e4f3cbb0ae0d9033c3ece97273f33d7fb5ecba75d964f09c883d7ce5e0f7ec6c02e954dfb7be13c941a335eefc41a877cad4c1849b71b783cd4bb82bf73ec71c471790930ae2a014689a3c49113a3c4f1de89a3300aa3300ae39bc1580a6db8dc670a7f3680dfc5dba2e7a9c7ef6df9f0b4018f8fef769efccc3c1bd867f9b4c7f2290179b01eecf3e0670cec52d9b4bf0fce83300bb38b19db2c628d43be56268ca4db0b3c1eaeef82dbfe36471c57f8654c759551f3ec8f9ca73e89f5251f9efdc0e3a34ef9f133dbf61fb07c6ab37c4a401ebcbe0f78f0330676a96cda3f00e7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71ee1a33ce7127d638e46b65c248ba7dc0b3df034fa1f3f0f73be2b8c22f63aaab8c9ae760f43cf5585ff2e139083c3eea94273f33bf153864f9b4dff2290179b04d3ae4c1cf18d8a5b269ff109c874298579520b3c4b96bccd866116b1cf2b5326124dd01e0f1707d17dcf61f74c471855fc654571935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f129007dba4231efc8c815d2a9bf68fc0791066617631639b45ac71c8d7ca8491748780c7c3f55d70db7fd811c7157e19535d65d43c2f46ce934a627dc987e745e0f151a7fcf8996dfb8f5a3e1db67c4a401ebcbe8f7af0330676a96cda3f0ae7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c6215f2b1346d21d011e0fcfe30b1eab79d111c7157e19535d65d43cc722e7a94b627dc987e718f0f8a8537efccc8ed51cb77c7ad1f2290179b04d3aeec1cf18d8a5b269ff389c87bb9d795509324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee01c1d628d43be56268ca43b0a3cc73cf0143a8fe898238e2bfc32a6bacaa879daa3e7a9c7fa920f4f3bf0f8a8539efccccc753a61f974ccf2290179b04d3ae1c1cf18d8a5b2699fec09b3308731639b45ac71c8d7ca849174c781a7dd034fa1ed6a3b30bada7e0f8ca9ae326a9e93d1f3d4637dc987e724f0f8a8539efcccb4fda72c9fda2d9f129007afef531efc8c815d2a9bf64fc1791066617631639bd56e3ee390af950923e94e008f87ebbbe0b6ffa4238e2bfc32a6bacaa8794e47cf538ff5251f9ed3c0e3a34e79f233d3f69fb17c3a69f994803c787d9ff1e0670cec52d9b47f06ce83300bb38b19db2c628d43be56268ca43b053c1eaeef82dbfed38e38ae2801c6e525c0b8aa04183dc731d55546cd73d613cfe90278ce028f8ff6c3939f99fbfc39cba7d3964f09c883f5e09c073f636097caa6fd73701e4a8d19af25628d43be56268ca43b033cbeaeaf42ae77bcbe5ef2c473b6009e9780c7c7f9f2e467e67a3f6ff974d6f2290179f0da39efc1cf18d8a5b269ff3c9c875263c6eb9d58e390af950923e9ce018fafebab90eb1dafaf0b9e785e2a80e702f0f8385f9efccc5cef172d9f5eb27c4a401ebc762e7af0330676a96cdabf08e7a1d498f17a27d638e46b65c248baf3c0e3a1de15fc9de382238ecb4b80715509304a1c258e9c18258ef74e1c85511885b130c6ad25c028e75a18b932b67a608c197bc843fbad45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627ea7b625e6127389b9c45c622e31bf53db127389b9c45c622e319798dfa96d89b9c45c622e3197984bccefd4b6c45c626edbf630dfb4e0f9dc1781e782875878f233a9cbbd64ca7a23c2f8e9585db662d56ac52a01792e41fc2e7b889f6bce35ed93bd4299473060f6643bd55b95d10bfc271babac7868fb573cf91ed6e65d2982edb036af18b6c3dabc62d896984bcc25e6f76ecc315d1174fe0d922ee3aa497733fb947f2b1c47793e5199fdac0ae47cfab02dd790c45c622e317f33628e71995f049ec0e20972f0a499f14c61c653cf8c6702339e31cc789a99f1ac61c6b394194f39339e05cc786631e329c6f3ac4278a632e36960c6b39019cf6c663cd398f13432e319c58c27c98c6704339e16663c6b99f12c63c6b38819cf1c663cd399f16c66c63389194f2d339e91cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c0130f3acf018fc3ff2f82aecc3ab6879293033afe7fcde8cbe098eb265dee28fb1ae8686ed575c7b118a76be04bdaa49377b665e284b6d2b04ff62a81e33a139ef1cc789630e319cd8ca79a194f1f663c75cc782633e399c98c671c339e79cc78aa98f1f466c6b39219cf3a663c29663c4dcc782632e399c18c672e339ec5cc7812cc782a99f18c64c653cb8c6712339ecdcc78a633e399c38c6711339e65cc78d632e36961c63382194f9219cf28663c8dcc78a631e399cd8c6721339e06663c5399f15c64c6338b19cf02663ce5cc789632e359c38ca79919cf18663c1398f1d433e399c28c27cd8c67be83c7d7bac334de4e65d3fe4526b63d9c87cc7be16e78f2e9a629abbb2997f8c95e05e499600662f4f8101e4b5cf6fc08ecdbdc84185df5e44bd8bb21ae16c176d8bb218a61bbdab25d5d44db12f3f098df8cde760ae71cd116b3f6d390c6ebcec75c2d4f7eded6e645fd2ecc5b56acae5ab14a409e1b10bf5b1ee2e76a47699fec15ca3c820133d68b9a20da7af172f43efde61d9b14d797adf8a25faf788a69585bfa4a116c87b5a5c5b01dd69616c3b6c45c622e31bfb763fe36938eb09f91441bfa3b14dd3fde0676df6ed2b108edeab25e3565d13b4189e3edc04379b6c2773aa97f72cddf0b31b7d3f48c027fc7efeb1952d83929c6f3abb073520cdb61e7a418b625e6e1317f8707dbf1e0f6b512f496eb19c53b80e7550f3c9efcccdc6b5fb37cba68f994803cd8f6bde6c1cf18d8a5b269ff35e0a10ddf13eba31ee473ce91e716339e34339e29cc78ea99f14c60c63386194f33339e35cc789632e32967c6b38019cf2c663c5399f13430e359c88c6736339e69cc781a99f18c62c69364c63382194f0b339eb5cc789631e3798519cf22663c7398f14c67c6b39919cf24663cb5cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c013f69e58faff2dd0d11839be3bf69d26fd2ae8ca1c3668ace635d055983495a1df3b7b7940e7b2314ebee605a0ad34ec933d7c4fec3b99f08c67c6b38419cf68663cd5cc78fa30e3a963c6339919cf4c663ce398f1cc63c653c58ca737339e95cc78d631e34931e36962c6339119cf0c663c7399f12c66c69360c653c98ca79619cf24663c9b99f14c67c6338719cf22663caf30e359c68c672d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc781632e36960c6339519cf2c663c0b98f19433e359ca8c670d339e66663c6398f14c60c653cf8c670a339e34339e5bcc78e63b787cbd9f2eec9d0fb78a603bec9d0fc5b01df6ce8762d8969887c7dcc3bb6e32ef02c5f742eacd9eeb9286b4e7dff7d4c783dbdfabf3db78f0bd1f3eda254f7e66de41f92e53964ea7232a57c7eadd41c7a6cba578e2fc2aca830cef866322e2c9f93e11b227ccc21cc68cefa42556d7dcbb379b9174af018f8f7643fb3ece9445e5eb77f425fa74d8f5f09ef77a1d07fb3defc4e17acffbe6fe1d6cd586ad32e8fc2e5c5ceb1ecfa5efb508a86cd75a04e40fae45e0ebbe7fc3e2b9e188c59b69fbba07db85f639ae3b78a27ccf26b63168cbc77bdfb5efd782fc7d77bd873e42df6b13c083b63cbce73dd376d17c5c2a5fb70f23fb788d793d5ef7d4768db77cae803c73a0ed1a9ba3ed2a0b3aae197c4f7959d0f9ddef6f18bd5d464d20dfa77cd896efb03c63eea16f52f077585c8f21fa7e462ad3cedd2880e71af0f8b8df7aea4f253ddd9332df61af58b1ba61c52a01793cdfb73275fb9ac543fb644f98855998855998855998855998855998855998855998855998855998855998f933e37830b1e2b8ed2d268ca4c3316b1fcff9b5ef134c5954be1e1bfd429f0ebbd18f5ba492384781c66d27583e57409e3efd3ad8be02e3b6f6bc86b073e96b9df7b07349f62a83cef3507c8ed385cd8129c6186121b66b22b39ddae0e7fca692bd83ec9adf37ac737acb713e7db415387f8ab65c638d5780c7c3586cadafb923daa7cb964fd72d9f129007d7b1b8ecc14fd77d8df62f030f6db84e9eaf7b4660f1048ef8d056c68c27cd8ca727339e29cc78ea99f14c60c6f328339e31cc789a99f10c61c6b38619cf52663c0398f1dccf8ca717339e72663c0b98f1cc62c63395194f03339ec798f10c65c6f310339eb1cc78fa32e3b98f19cf42663c15cc786633e399c68ca79119cf28663c49663c2398f1b430e3a961c6b39619cf40663ccb98f13cc08c27ce8c6711339e6ecc78e630e399ce8c6733339e49cc786a99f18c64c6338c19cfc3cc787c8fa317caf320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680271e74fead471cfe7f1d74974d1a7fcf56e6288fe6e9517eddaf7a7940e7b2cb1c655f7130609c2e812f69934eded976dbef3062a65cda277b95c0718509cf78663cd5cc78fa30e319cd8c6709339efecc786e32e319cc8ce711663c75cc782633e399c98c671c339e79cc787a30e3a962c6d39b194f3f663c2b99f10c62c6b38e19cf70663c29663c1399f13431e399c18c672e339eeecc781633e34930e3a964c6f320339e6bcc781e66c6338c19cf48663cb5cc782631e3d9cc8c673a339e39cc78ba31e359c48c27ce8ce701663ccb98f10c64c6b396194f0d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc782a98f12c64c6731f339ebecc78c632e3798819cf50663c8f31e36960c6339519cf2c663c0b98f19433e3e9c58ce77e663c0398f12c65c6b38619cf10663ccdcc78c630e3799419cf04663cf5cc78a630e3e9c98c27cd8ca78c19cf7c8b07ffafbfdbd3f8ea65d0d1ffffafe91c54193f2e5bf622f02319b6f683af98e19676c4246ced070e3c69663c3d99f14c61c653cf8c6702339e4799f18c61c6d3cc8c6708339e35cc789632e319c08ce77e663cbd98f19433e359c08c6716339ea9cc781a98f13cc68c6728339e8798f18c65c6d39719cf7dcc781632e3a960c6339b19cf34663c8dcc784631e34932e36961c653c38c672d339e81cc789631e37980194f9c19cf22663cdd98f1cc61c6339d19cf66663c9398f1d432e319c98c6718339e8799f15c63c6f320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680276cad05fa7f39e82e9af44dd05d30e96ba03b6fd29741f7924357e660217b174147f3142e808ec636ce838e9e87902ddd9f7b6d4067d632874fe50ed60b0e9f2e3a8ec5f348c7a48368cf23da4ac33ed9ab048e8b4c78c633e3a966c6d38719cf68663c4b98f1f467c6739319cf60663c8f30e3a963c6339919cf4c663ce398f1cc63c6d383194f15339edecc78fa31e359c98c6710339e75cc788633e34931e399c88ca78919cf0c663c7399f17467c6b398194f82194f25339e0799f15c63c6f330339e61cc784632e3a965c6338919cf66663cd399f1cc61c6d38d19cf22663c71663c0f30e359c68c6720339eb5cc786a98f1b430e34932e319c58ca79119cf34663cb399f15430e359c88ce73e663c7d99f18c65c6f310339ea1cc781e63c6d3c08c672a339e59cc781630e32967c6d38b19cffdcc780630e359ca8c670d339e21cc789a99f18c61c6f328339e09cc78ea99f14c61c6d393194f9a194f19339ef9160f8e61264147e95ad0513a053a4ad7818ed2f5a0a37403e828dd083a4a4f021da59b4047e9c9a0a3345d23f1a0c3f762bc879f6c51d9b47f0918693eb3eb9a9e0adc972c9de63ee789fb92c54dfbe780d1e50bb14d03ee73964e739ff5c47dcee2a6fdb3c0e8f285d8a6838ed2334047e999a09b09f64847e959a0a3f46cd0517a0ee8283d1774949e073a4acf071da517808ed20b4147e945a0a3f462d0517a09e828bdd47cea737cd6d2e9737cc6a4d341b4e7986c51d9b47f06185de79dd89601f7194ba7b94f7be23e6371d3fe696074f9426c2b81fbb4a5d3dca73c719fb6b869ff1430ba7c21b635c07dcad269ee939eb84f59dcb47f12185dbe10db5ae03e69e934f7094fdc272d6eda3f018c2e5f886d1d709fb0749abbdd13f7098b9bf6db81d1e50bb1350377bba5d3dcc73d71b75bdcb47f1c185dbe105b0b701fb7749afb9827eee31637ed1f03c676872fc486ef5ddcec89f1a6c578b388b6c3fa5ec5b01dd67f2a86edb03e50316c87dd9b8b613becfe5a0cdb61f7c862d80ebbcf15c376d8bdaa18b6c3ee37c5b0dd6ed96e2fa26db9c68a7f8dbd996deabd7a8dbd99ed5abb65bbbd88b6a5cf247da662d9963e93f4998a65bbddb2dd5e44dbd2a686b7a91e9e4fa4e26083b698b59f86f431e0f1f19cc7939f495dee5153d61b1196ab63f5a215abcd56ac1290e728c4ef450ff18b815d2a9bf6c95e293263bd8845673b19071bf88eb823543ee80e9b742de80e99740a74074dba0e74074cba1e74fb4dba0174fb4c7a31e8da4c7a09e8f69af439d0ed31691cefd96dd26741b7cba4717c65a7499f01dd0e93c6f18ced267d1a74db4c1ac70fb69af429d0b59a343eafdf62d22741f78249e3f3f14d267d02741b4dba19741b4cba1d74eb4d7a33e89e37e9e3a07bcea48f82eead26dd08ba674d7a12e8de62d24da07bc6a42783ee6993c67714ae36691cfb5e65d21740f79449e358f393267d09742b4c1ac776979bf474d03d61d23340f7b849cf04ddcb269d06dd2b263d0b746f33e9d9a07bd5a4e780eeed263d1774ef30e979a07bcda4e783ee9d26bd0074ef32e985a07bb7492f02dd7b4c1ac701de6bd2c7405766d22f828ee69e1e011dfd9ee230e8e83794874047ef4d3808ba1e267d0074341f673fe8688ee93ed0dd67d26da08b9bf45ed0559af41ed0d13b0a76838ede03b40b74f4aea29da0a3f701ee001dbdb3703be8681eea36d0d16f19b6828e7ebfd80a3a7a47c016d0d17b815e001dbdeb6e13e868bee946d0d16f0436808e7e17b81e74f4dbfbe74147efdb790e74f40eb9b7828ee64d3e0b3afa2dc05b405763d2cf806e98493f0dbae126bd1a74f4aeb855a0a3f97f4f818ee6fc3f09ba1126bd0274234d7a39e84699f413a0a3772e3e0e3a9a07f932e8c69af42ba01b67d26f031dbd73f355d0d1bcc1b7838ede55f30ed0d1bdf835d0d1bdf89da0a37bf1bb4047f7e277838eeec5ef011ddd8bdf6b3ef5f5a7afcbeb663f1d44d7efd1f66e04b76fb9fadec4803c51f66513c083b6ae46ee7b2ad36fa6fe56992997eac155b07d3972dbd93efb1553563753ee65cb7605e419d2afe3dc5c82ffa7c1073a0ef350d9b43f018ebd64955d65fcbde2c9dfcb1613715f0126ca33bc5f47decf9a744f382642b6ccf74faa6b01c410b734a4718db4e86395ca7c1fb85600cf15e089fe3ac97e1ff65127f0da8afafbb0fd2cc3ae6b09c87319e2e76b6ee7158b87f6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63c349e80f37928df75268ca4c3b12e1fcff9712c15c7bc3e02e33abec7f7ba9b72275a3e57409eff03634e1f33e94af83f9db7b073e9619c30e7b9247b95e00f8e05f9182f8e812d2afbaa23161c6cd744663bb5c1cff94d257b07d9f1fd6bd639bd6e9d4f1c17c76be8b3504fe9985be6330ef1b905f1f1708de73c37640fafa5aba0a3f40d60f4116bbc97507b30c1ec933d1c17ffb2352e1e7d5b9d4afa6c372e9ab2ecf176f495f27c03dabe6f9a34ce0db90e65fdccf17fda728d5353fcb4cfe7a3f739737e5f3265d1f93defb07d0e5823b25d8bb66346c80ee92b20fd5398cf61ff9e88624decfa1aa13998c86e1f77c33a2e01792e38fc4f07d1fa7fdee2396f31ebbaf36da8673f83fbbfaf36e942488c26408c280ff6833ccc3371b691c441f6741e3aff3dac3cd867a13cbf86364afb42ed3cf989735bf01e70d1937fd72dff689fec61dff80a30da3eeafaf1853e1dbcf7fafcaffff238ffebbfa00ee13cadc02a7f22944f5c3d82f07b0be589f5ef28dfe7fcb26b96cfc47205987e93a77f47de69265d485fffcdfade16d6d7f7f14ec07870fb776fbde5babfe33dc647fbe2c9cfa4ebde75c9f229017946809f1efa31397f07781e6cfb7a0f24d9a03ed4652b161590a73f5cdbb9e288df55af14c59794b33f38d1e10be5196cb5532f7960f279deb09fa5cbbdeaf095f20c87f6ef51938ec379c2b6b4cef17fda72b507143fedb387f7fb65ce2ffd068cceef1987ed53c01a91eddb7e934efd7db243fa0a48a7fa77e4b57f4f4eb1c6f7f8d16fe590dd3eeeb2755c02f29c75f89f0ea2f53fec7d00644fd79d9150cfa81ef96c37cf86c46822c488f2e0b315bab753feb0fbbe8ff9d5b9eefb5781d16e37b1efe293ed9ac5663f4375f507290f1d8bfdc159d0cec61d79fd3f1bcef66d0a99f3eff919797dcc8a671aec8c073dd54f8a33fd0ffbb8946789753ff3d567bd6c71931f388673d1e2be64c5159f452d076ed7b5e97ad6e9a9cf598f7d31bb3f7411d829cf6aab3f64f7e36e3a7cb1efd36541e7ef826f5879f19acc759c9dbe6e1d83cf0c5ccf64f1b72e1780256dd2c93bd96a9349642a73c4221174fe4ee67ace40d701d6392acbbe56e8d80ac8b3d16a93ecbc997b599f8ef8d079c467edae3ae9eb3952d87316b2a7195dbfa38bbeedba377fafd433b27227af2fc531ee9e563a1adba916bc57e48ac545078faf679a61b1b8e8b01d5d2c1a37b8ee1fae585c70f0f8fa8e19168b0b0edb11c66293ebd9822b16e71d3cbebe6b84c5e2bcc37674b19874db738d5cb178c9c113fd338ddcb1c031b142982f3060ee69a5a3b15dbfde3576e78ac539078fafb1bbb0589c73d88e2e16b58daeefecae589c75f0f85a17242c16671db6a38b45d364d73315572cce38787cad9f11168b330edb11d68b8df87c2d572c4e3b787cadc911168bd30edb11f60f1b733d33c4589c72f0f87af617168b530edb11c6a259db3e99472c4e3a784e163916271db6a38bc5fa066dfb441eb138e1e03951e4589c70d88e2e16cd4dda767b1eb16877f0b4173916ed0edb117e87cad48be379c4e2b883c7d7ba2961b138eeb01d5d2c5a327dad6379c4e29883c7d75a2c61b138e6b01d5d2c92997bead13c6271d4c173b4c8b138eab01d61bdc87c9f7c318f5814f3fda361b178d1613bc2fb48a65e1cc92316471c3c478a1c8b230edbd1c56253e6f9d3e13c6271d8c173b8c8b138ecb01de133974cbd3894472c0e39780e153916871cb6a38b455de69e7a308f581c74f01c2c722c0e3a6c47178b8d9931b10379c4e28083e740916371c0613bc27e67a6bdd89f472cf63b78f6173916fb1db623ec77669e5feccb2316fb1c3cfb8a1c8b7d0edb11b69d997e675b1eb16873f0b41539166d0edb11f63b33b1d89b472cf63a78f61639167b1db623ec7766ee237bf288c51e07cf9e22c7628fc37684f522d376eece2316bb1d3cbb8b1c8bdd0edb113ed7cab49dbbf288c52e07cfae22c76297c37684df4732cff876e6118b9d0e9e9d458ec54e87ed08c78a327df01d79c462878367479163b1036cfb986782b1a0b958e3ac5854409e07cccbd8692e56581ca90c9c5786be6c8fdc97ecbcb26d21be6c075f28cf40f0a527e8a364f2e46ba6ce6c3565d1dcf41b0e5f29cfd0011d798799741ccec94d282be9f83f6db9e62051fcb4cf5ba2f73953575f3065d1f9dde2b0bd095823b25d8bb6696e3ad9217d05a4270ee8c84bf9281e146b62d7d708ad9580ecf6713bace31290a7d5e17f3a88d6ff2d16cf168b39f3bb07a867548ffcb45d59a6d690188d8318511e9cb377c3138f3d879038c89ece43e7bf879507e750529e2668a3705e29f9190f3acf9bd4fe6df5e45fd83a6e642f01ba2bc068fba8eb4702e67ed61263d0a1a3b51d700da6464ba77d9de4c957b24565d3fe2460a4b5261a8bcf98ca97b1c162d43c933dc42c0e7669cb75bf980c3c4d1e783cf999b90f4db17c9a64f994803cf8dbc6291efc8c815d2a9bf6a7806d1fe71c6341f7e451562c2a20cf6aabff1816472a43d7df46872fbee2d860f134386c4ff71c472a9bdac4e945b03dd5b25d6fd9d6d736d631bde5bab6a702f3340fccbadc19d1979bb9b6698d34aacf64a71e7c4a430ca2f2096dc7acb2495f01e9ed033a33503ce8de49ecfa3ac2ba1376dc64ebb804e499eef03f1db1ff332c1e3cc77ad3fd86f5d0aff4703d64eac0748b83f6eb2176334262371d6247791a41477d9c29a0a3be02ae2948ffc77e4443f4fe3adb1eda6f0046d2e13a850d0ec6fae81953b9dae67a6024dd34e099ea2966d32c9e51567cf0bedcc3ca43c756409e76b837c61d7975ddaf8a75f8456b1746b8ce4fa65dedee215eb8ae6200f109ac18d2460c3d838eb517a3e4b92f80b515db76ee69debcf1c98dd9a14742abb030f133e670a30c74982e77e882e0f625242b40474b4876035d9915165cba92f2d392763ec285f1a0b22b2cce9ec012a56d5c7e93b65c55a707f0f8a8cabaea549ab24cd5797acf96b68d583fba599c5da93bfa7fe539f2859545f5a0c283efc84465d33ed9d3f14998f4aee60d5b67edd9bc6ffbc61d6d7b11d6beb8301db382607fba8ec14a82171395d3cd0a4eafe883538f17afcd1780bd005802c3d3337a9e4c45a5355b37346fdbb67cdffa6d5b36ccdfb76343db969d3b30a23dacc88545dbbee4f5e66aea30af9dbfbb43676fb8426e0fd0d10ab93d4147f67b818e38ee83ffd967c2cb353102caa76aacff57611cee6e1ca22a48b721dd9ee86ba67790bdfdeb2763d541f6f4e92576f592ba7a095dfd0607fdab6add53d04be0ea256ff512b77a495bbd84ad5eb2562f515b136497a01d1e6497987d34e85842566f9f015ebd64acee6ae82561f512b06383ece3baf141f6d566fa5512bacbaabf8eea6e9efe6aa1bb5cbaaba5bbb5fa2bbb7e7ca3bb31ba8ba8bb3fbacba2bbc1bafb3bd3c47a56905d425a2f193d37c82e093d3fc82ef9bc30c82ee9bc38c82e87be34c82e4dfb44905dce7c79905de6562f7fab97c5d54b97eb2573f552ba7a895dbd44b95e7e572fcbab9721d74bf6eaa57cf552e37a99dfe783ec72e2eb83ec92c12d41763961fde85a2f1fae1fdbebc7d9fa51b17e94a98713f4308a7eccaa87fff470a81e1ed6c3e57afa809e4ea1a797e8e9367afa919e8ea5a7a7e9e97a7afaa29ecea9a7b7eae9be7afa737b909d1e7f32c8fe7c42ff9c44ffbc46ffdc48fffceaa520fb9342fd133dfd9353fd13dccb41f611aafe39b71e56d38f4ef56364fd78553fead5cbf4ea6579f532bc7ad95dbdccae5e56572fa3ab97cdd5cbe4ea6571f532b8ef55f23b4a7e57c9fb94fc9e92f72bf97d257fa0e40f95fc91920f28f963251f54f221257fa2e44f95fc99923f57f261251f51f251257fa1e42f957c4cc95f29f9b8924f28f96b257fa3e46f95fc9d924f2af97b25ffa0e4534a3e1d64ebdf6795fca3927f52f2cf4afe45c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc93794fcab926f2af93725df52f26d25df51f2ba92ef2af99e92ef2bf981921f2af991921f2bf989929f2af999929f2bf985925f2af955d0b10c343616ffdfec0c33fbcd6d6d1bb7ef6aab69db59b37ddfb6b62dbbb61daa39b0a5ed859a9dfb37eed9b46de7013cf883a679a2f5aa67edd9d37ca866cb8e968d076b76ee6babd9b9a966fdce7d3b5a6ebb397ece1c34a8b3c5e696967063df29bb03d2ef77d1e8afcd71b412f8a2dcbefdafae04e4ff75e5a081e55d73e8e3a673475fbb9ecaf6f16af66edbd95693acd9a1feaa9be9ce031b5b26d4e0fff6aa20ef6dabd9dbd6bca7ad66d39e9ddb6b6a2760b9e37b77c189d6de7e60069b770175a9aa7ca95f173cf91ffdba763afef34e48693df9428df6ebdf050f0775e5a074170917f70f0dcbde7debdbf6346f680b3f78d99d1cbcaa2b6eb674d1cdbe03ba60eca1ae1c346940d7085775c5d8f1028c05ff0d3975c984249304009b2d6c6f00000023561f8b08000000000000ffed9d79741cc5b5c67b6459963d1acbf2be5bac5e4648a319c9b6b08d65b3181bb3790163360b5b3606db32b60c983d648310080961c90664634908d94312b2ef2464812c2404b213c8bf39efe49d774e4edebb3553f7e853b96bdeb4dc35ae96ef9c7335d557357d7ff7ebdb353dd5dd336f0441900a4a8f1164c706873ef8ffddfa3977788ff618d79573c9994a08674d4238472484b336219c2313c2599710ce5109e1ac4f08e7e81839155b4d30f81137ef1807bac6cd984e98a60d09d03493304dc72640d3c6201963d4b88470362584737c42382724847362423827258473724238a72484736a4238a72584737a42386724847366423867258473764238e72484b339219cc72484f3d884701e9710cee363e49c079c27e8e713f5f35cfdcc7de6ebe705fa39ab9f5b748eb57af924b256b236c567fc4f9d68c89315c83af4ff9af5ff3ac916922d225b4cd6457632d912b2a564cbc84e215baef35f41b692ec54b2d3c84e273b836c15d99964abc9d6909d45b696ec6cb273c8ce253b8fec7cb27564ebc936182c1bc92e20bb906c13d945649bc92e26bb84ec52b2cbc82e27db42d6437605d956b26d64bd64dbc976905d49b693ec2ab2abc97691ed26db43d647b697ec1ab27d64fbc9fa0dcd0e905d4b761dd9f506e741b21bc86e24bb89ec66b25bc86e25bb8dec4d64b793bd99ec2d646f257b1bd9dbc9ee20bb93ec1d647791bd93ec6eb27bc8de45762fd9bbc9de43761fd97bc9ee277b80ec41b287340b17fbfbc8de4ff601b20f927d88ec61b247c81e25fb30d947c83e4af631b28f933d46f638d913644f927d82ec93644f917d8aec69b24f937d86ecb3649f23fb3cd917c8be48f625b267c8be4cf615b2af923d4bf635b2af937d83ec9b64df22fb36d977c8be4bf63db2ef93fd80ec87643f32347f8eecc7643f217b5eff8fe7907e4af633ddfeb97efe857e7e413fbf68bce69764bf327cbf26fb8de17b89ecb7bafd3bfdfcb27efebd7e7e453fbfaa9fffa09fffa89fffa49fffac9fffa29fffaa9fffa69f5fd3cf7fd7cfafebe737c80ae34bedfa60e0d11dc434ee74f476a9f30f2cf609c1e087d26284fe1f3f376b7fad5ee667d66ea45e1e69f8ebf4729db19e7abd5c6ff89bf47293e19fa0972718fe497a7992e19fa297a7803f1dc03ca4f62bdf08ed4a818febb0067c2383c19a285f1daf0e7ca382c15a281f6fc73af08dd6be51e01ba37df5e04b6bdf68d68cac41fbba83b86a22d7a3d69b897bbdfadcccd8f879b7aaf5363ae21d173f6faf5a6f93035e551f7ab808c641dd4cd0be26f04dd4bef1e09ba47d13c03759fb26826f8af64d02df54ed9b0cbe69da37057cd3b56f2af86668df34f0cdd4bee9e09ba57d33c0375bfb66826f8ef6cd025fb3f6cd06df31da37077cc76a5f33f88ed3be63c077bcf61d0b3e1e3f8f031f1fc31daf7d6a4cf85700afd1fe1af09dc8632ef8e6f2780bbe793cd6826f3e8fb3e05b00b1d9978531847d2ddac7e391fadf42ddee0ee2aafffc36b5de4571af97d6acd6db15ff7a8be7ad4e0e0674ed86388b40ab25ba1de3b531ed183ba58de3b0bf16daaba02ff7633df83d85d9d57bc762dd5e52e6750b8dd765a0cfe290fcbb8378f3ef3278ba0c6655ff4b8123fe9a2db44bcd56fc885cb31ba1af597b7c7c331c6b760d7038a8d94e37359bcf49cd96e6178220bcf6f8187738d6ec26e088bf663ba5662b7f44aed96ba0af597bfc396738d6ec76e088bf661775cab141c58fc8357b2bf4356b8f3feb0ec79aed070e0735db25e36cc58fc8357b37f4356b8fe75d8663cdde0e1cf1d76c97a39a2d48cd06a5734541105e7b3c07381c6bf65ee088bf66b776c9b141c58fc835fb24f4356b8fe7a38763cd3e0c1c0e6ad6d5fc6c5e6ab6748e3c08c26b8fcf8d0cc79a7d4ab7d579869febf30c33c1f70bed9b05be17b46f36f85ed4be399057fcfbc0b682ec03153f22ef03cf415fb3969b757b38ee03df000e0735db29355bf12372cdbe047dcdda3b56b78763cd3e0f1c0e6a7691d46cc58fc835fb1af4356b8faf5f188e35fbb26eabe385dfe9e38513c1f7b2f6cd05dfefb56f1ef85ed1bef9e07b55fb1680ef0fda9705df1fb5af057c7fd2be93c0f767ed6b05df5fb4af0d7c7fd5be1cf8fea67dede07b4dfbf2e0fbbbf615c0f7baf67580ef0dedebd43e754e80af4ff99ef6d5434edd417cdbb6784d4a30f8913296bba1dde296279709065f57cdb15ae38f5550b99f14549e7b2bf0b439c83d0d312ae169039e5cfc3cc56b2fdae35f6f711b9f64689a865827415e790779a52016af9b97395e067c381ee443180bf133e653108bd7cdcb0560641f8e4f3cbef2fea3c6e6b1a9015e07fb52f1fd19e3750307c7ab853e2f340df41dafd91ae0ff3c0634401bc7ef9ce17354abed787d3faf9b97db819173cc559f315f29639bc1580f6c31f214356b3378da42b4385a633b18ab8bfb1fc7e0fd8ff7258e570b7dfea766a0ef8929a76cf9a8efb3589ff18ffff91cee9b95f0e481c7c518e3e87d2e87ef4dff09e2adb50e43ab9ca15506fa1440bf0e07fa957bafe478c22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2ec3f339e3fc6f36d61e7668f2423fbda81c7c53c7ff13b49f4ba78fdeabcce33705e27fef316f91c9e77e46bb2e61939d7429fe752036ccf869cf3c5f3bbad86cfd13517c56d89d75c74c332c7c373d0780d88ab73885983271ba2850fb19b638b9ddfea66fbe673ea3b01d4f7eab418db34ecfa9305864fd5e9bf6b06f27671ee2feab9481c4bb81de7b9bd4c70e8b5036eae71298d1f3c5ed50483c70f7c9fe98c3df6e073987cbebcd3885d0b7d5e4a0d6c9b85f0ffeee0d0eb72b00faf9b97e7c16b171aeb6e74976fd96b133a819bdb75466e2dc0cd7d5e81f1fc56d84f1cbcc7e5f1fab000780323277ee039fbf88f014ad710e423f074008f83ebc5da1d1debe4b01ee3be8660a1a155d83114f7e904fd62bf1e5eef1fe671302f733c61166661166661166661166661166661166661166661166661166661166661f69f19efef66d634f46bf784b14ad75d14cf67f0f76de079b1dd3503715d9f03e4734ef38d9c6ba1cfd335036cd7e8764370e8b516b66de9eadc9a6d5b72bc06c807cf05b9ba67bd60f01442b4f02176736cb14bd710c4bf7d07ae21c81bdbb4ddd89e78ee1cf7a15ba14ecdefb848833ed5b86ec9b66d381eee4b05f0711befcd76a135be9798d714713c3c777ea7d696cf9dc73f56e7732ec70dfeee2abe4e201f922bf7b907c6be7b751baf1f6987753d12f27f7e943b4fcdfa1d89eff1c2d83e7c8fd7c335037dcdefe362ada37e8f57ce789d8fdfe3751fd4d923f0feef6a4c5a1c846b340f34e23e781ce4e03a93d031d2bcb652f5e1ed3fcae883c72cdce77118a36cd7ae867df79183efe72bfbdd471c0f8f8d3b80d1ccd1bcc6f468bf46ec19182f16c2ffbb83c3bf46ec19a821bc4e2b30d63f1fd6cf5ca302fb7b0bf779d6781f75b56fd9ae41c37dcbfc3cc0bc780d1af7f9168c55ff84e32cf3d81fafd7aac6673bdbf5e11c0f8fa9a2e48ee342dcef8d588fc8c2f1b01e7f6cd4e3420b776bc86b7f6a792d6bc5d71ce36727533fa5c362784d772c3a94c69b2e2317dea716432edce797c67e13ff3153e9f833fe5c071f0ff1185408c995fbfc16f6b597e1f892b7138e95ff08f93f3fca1d7fb27e2ae7a5f1e75cdcbecbf4ba78fb2e0d897d0ab0c614bb1d63f3f127c7617f2db4df80f713eec77ab0d6ccaef6113e864376f3759dc6eb32d0674948fedd41bcf92f3578961accaa765e853afb071c7fba1aab9758349a0f1a711fbc278adf47b8bfed3dc6c5f5bee5de63c2be1f12c7f67f3a9dd30b6733e7f4c28e4fb80fbf168f4ffe1bc6d974485f73ae92df43e2bc6619ef11c942dc7f87c4ed8e59cf4c3058cf8cc1e13276a311bbb18ab19b8cd84d558c2d9a8be63e69eed3f77fe3f774d72480714402186b13c03832018c7509601c9500c6fa04308e4e00e3980430a681f148beb73bd0271f873e0da0534b08b7abfbf15b0c46f3bb1232062ff31cc963245fb7e158fdecf3366c049e6176ac19cb361ca79f7dde864d6e758c65bc1f9f00c60909609c9800c64909609c9c00c62909609c9a00c66909609c9e00c61909609c9900c65909609c9d00c63909606c4e00e33109603c36018cc72580f1f804309e9000c624cc552e70cb38e4cfd78ac7c16f4276e067f44a785c7fb7a6a33c8bd767f1758c717f479af9bb8e2d865678cf89ebdf834d05f6ef4ae578c22ccc36661c0bf1f7b9b95fd613c6b0ef1e76306e447e4f6975cb33e8b7b75bdd6e8bc8ef556e7f13b4f45e7534fc2668d8fd2d61dff7ce7d70dc3852d73162ece6205e2d3a2ad0a24af7603bf9befeb60432fbf21b033c3e85fdc640d613c62afdc640e4f7aa7c888ead6e19f3436574752f33d64b253cf8bd182e6aca4d9ee5bfcb99e3e1f77fe098e4ea9a7ef3fe385eaee43b4b84599885599885599885599885599885599885599885599885599885599885d90f663cc780dfe5ccfdb29e309adf93e4683e3ef2b99ace101d5bdd32e687ca88df7f161f4fe95c4d94efa95a043c2e6aca4d9ea57335e6f76d761a39e1f76de2feedea3bc816193cbcbc18b683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcd8ce71898350dfdb29e30b26f21f0b8f83da6a8e7411685e8d8ea96313f5446c5e3e037e43ab05e2ae1c1dff57051538ef21cf45b79bcee45464e19e883fbf7c90ef24c05f6df8fc3dfd51366610e63c6318b59d3d02feb0923fb16038f83fd3bf2d8df15a263ab5bc6fc5019154ffcbf61593a4fdf1581077fd3c8454db9c9b334f69bbf9564fe566706fae0feede077b3427f27899797c27688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917d27038f83f9f8c8e76a9684e8d8ea96313f5446c5b32c769e420eeba5129e65c0e3a2a6dce4593a57738a91d31223a70cf4c131e9140779a6202eaf9b974f81ed30dc990b096496daa80eb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc3ed4065ea3c3ac69e897f584917d4b816799039ea8d7112d0bd1b1d52d637ea88c8a6779fc3c1d582f95f02c071e1735e528cfe2b54edd464ecb8c9c32d007c7a46e0779a6202eaf1bf30ef476e81666610e61c6318b59d3d02feb0923fb4e011e07fb77e4b17f79888ead6e19f34365543c2b1cf12c8fc0b30278bae3e76977946771ec5f69e4b4dcc829037d70ff5ee920cf14c4e575f3f24ad80e4963c67d8959d3d02feb0923d618f338a8bbc863d28a101d5b13c0d89600c6d109601c9300c63430661d30a6743ce4e1e5ac5b7df271e8d3003af93aee6580c7d5366c34781a43b4f0751b8ed5cf3e6fc346e071b50d9b0c9ea6102d7cdd86e3f4b3cfdbb0c9ad8eb18cf7e313c03821018c1313c03829018c9313c03825018c5313c0382d018cd313c03823018c3313c0382b018cb313c03827018c38a771243fe71ec9cf6747f27385682e9ae70eef219a8be6a2b9682e9a07a279eef01ea2b9c79a27e13384300e6fc682a78cc8938a8f2787b963ac951ee4ce0c49632c2480d1f57546a2632c8cf9a1322a9ed31cf1ac8cc0731af09c1a3f4fbba33c8bd71a9e6ee4b4d2c829037db00e4e7790670ae2f2ba79f974d80e4963c67d8959d3d02feb0923fb4e051e57fb5794fd1df7af331cf19c1681e70ce071b1bd1ce559dcdf5719399d66e494813eb8efac7290670ae2f2ba7979156c87a431e3feceac69e897f584917da7038fabfd2bcafe8efbd7998e78ce88c07326f0b8d85e8ef22ceeefab8d9cce3072ca401fdc77563bc833057179ddbcbc1ab643d298717f67d634f4cb7ac2c8be55c0e3a0ee227fe6383344c7b604301612c0283a8a8e3e318a8e478f8ec2288cc2288c4782310963b8bccf449f1bc0cfe26be2e7e9c0cf6d95f0ac011e179fed1ce5599c1b38cbc8e94c23a70cf4c13a38cb419e2988cbebe6e5b3603b08b3308731e398c5ac69e897f584917dab81c7c1fe1d79ec5f13a263ab5bc6fc501915cfdad8793a72582f95f0ac051e1735e526cfd2d87fb691d31a23a70cf4c1fdfb6c0779a6202eaf9b97cf86ed1085b9904066d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d87c6ac78f8ba73664d43bfac278cec3b0b78d63ae0897a1dfeda101d5bdd32e687caa878ce899fa703eba5129e7380c7454d39cab378afc0b9464e6b8d9c32d007c7a4731de49982b8bc6e5e3e17b64314e642029945e7a131e398c5ac69e897f584917d67038f83fd3bf2d87f4e888ead6e19f34365543ce7c5cfd381f55209cf79c0e3a2a61ce5591cfbcf37723ac7c829037d704c3adf419e2988cbebe6e5f3613b08b3308731e398c5ac69e897f584917de7028f83fd3bf2d87f5e888ead6e19f34365543ceb62e7c9e7b05e2ae159073c2e6aca4d9ea5b17fbd91d379464e19e883fbf77a0779a6202eaf9b97d7c37688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917de7038f83f9f8c8e76ad685e8d8ea96313f5446c5b321769e420eeba5129e0dc0e3a2a6dce4593a57b3d1c8699d915306fae098b4d1419e2988cbebe6e58db01d863b732181cc521bd56196da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38dd987dac06b7498350dfdb29e30b26f3df06c70c013f53aa20d213ab6ba65cc0f9551f15c103f4f07d64b253c17008f8b9a729467f15aa70b8d9c36183965a00f8e49173ac833057179ddbc7c216c076116e630661cb398350dfdb29e30b26f23f038d8bf238ffd1784e8d8ea96313f5446c5b3297e9e0eac974a7836018f8b9a72946771ecbfc8c8e90223a70cf4c1fdfb220779a6202eaf9b972f82ed20ccc21cc68c6316b3a6a15fd61346f65d083c0ef6efc863ffa6101d5bdd32e687caa87836c7cfd381f55209cf66e07151538ef22c8efd171b396d3272ca401fdcbf2f7690670ae2f2ba79f962d80ec22ccc61cc3866316b1afa653d6164df45c0e360ff8e3cf66f0ed1b135018c6d09602c2480d1b18ef9a1322a9e4b1cf16c8ec07309f0b8183f1ce5597c9fbfd4c869b3915306fa601d5cea20cf14c4e575f3f2a5b01d92c68cfb12b3a6a15fd61346f65d0c3caef6af28fb3bee5f9739e2b92402cf65c0e3627b39cab3b8bf5f6ee47489915306fae0be73b9833c531097d7cdcb97c376481a33eeefcc9a867e594f18d97729f0b8dabfa2ecefb87f6d71c47359049e2dc0e3627b39cab3b8bff718395d66e494813eb8eff438c833057179ddbcccf192c88cfb3bb3a6a15fd61346f65d0e3c5b1cf044dddfb70063d8670e5f190b0960141d45479f1845c7a34747611446618cc6b822018cb2ad85d157c6ac03c6948e873cbc9cad42ec4623766315633719b19baa185b3417cd4573d15c3417cd0f37b6682e9a8be6a2b9682e9a1f6e6cd15c3417cd4573d15c343fdcd8a2b9682e9a8be6a2b9687eb8b14573d1dc8cdd137fecc8d773f700cf16075a38ca33a7d67b855ed77f62d44f69b5d5d02a6b6895813e57807e5b1de81776cd352f73bca8cc277ac0ec28767e2cad6334e4cf310a861e2afe3647b9dbc6bc6d55886d1bf3aa11db36e65523b6682e9a8be647afe6d8ae0d0ebd0749ada357b747ea65eebf025ec77d1e68283d3706b23d5dc4967d483417cd45f323a139ea5253059ec0e009caf0ccf68c67aa673ce33de319ed19cf08cf78e67ac633c7339e699ef14cf08c678c673cb59ef14cf78c67a2673c69cf78467ac633cf339e199ef1ccf78c6792673c0d9ef1643ce3a9f38c67a6673c933de319eb194fa3673c0b3ce319e5194f8f673cb33ce399e219cf38cf789a3ce3a9f78c27e5014f3a38f49a9a34fcbf077c35c66bd5783571fcc0ffb76b7f0dbc66876e8f0859f776f0f1b9aa1d21af459db6432eddba9d3bbc4751278cd50dcb1caf01387678c253ef194f93673ce33ce399e219cf2ccf787a3ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1ccf38c67a4673c69cf78267ac633dd339e5acf78c678c633c1339e699ef1ccf18c67ae673c233ce319ed19cf78cf78a67ac633db339e9a109e1e473c3c7fc9ebe6e51e4f623bd80ec5fb16af7494d34ebdae3abd5ee6e778b5d0e721fdc14dcd47e06b99cb9c6fc6b9a69da051afa35c6cd72ef75621b6eddae56ac4b65dbb5c8dd8a2b9682e9a8be6ea11e33df59d72bf45e53c72bf45791eb9dfa23ccf5ccf78e47e8bf23c72bf45791eb9dfa23c8fdc6f519e47eeb728cfd3e3198fdcff519e47eeff28cf23f77f94e791fb3fcaf3c8fd1fe579e4fe8ff23c72bf45791eb9dfa23c8fdc6f7128cfff77bf05de27c1e7a67ac177a56ef780af262406af6727f86a759bd7a1c69b39e30f65a881d75c15c27565483c8e7355c86baba13bc6ea86658e87f76f5ce5094fbd673c4d9ef18cf38c678a673cb33ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1f478c633cf339e919ef1a43de399e819cf74cf786a3de319e319cf04cf78a679c633c7339eb99ef18cf08c67b4673ce33de399ea19cf6ccf786a42787a1cf1d8aea3eda9426cdb75b4d5886dbb8eb61ab14573d15c343fba35bf3afed81de960f06fb6a947ca58ee86f6d5c0e3e2fdce519ec5fbef76e975c5788f434e69b5dbd0aac7d02a037d76817ebb1de89782b8bc6e5ee678c22ccc3666157b4ffcb18be30cc6667d0283871f7b1c6be128cfe278d017846bccf132d007b7799f833c531097d7cdcb7d21b19b8378b5d85b81167b4378f656590b8e1795795702997dd059c5be26f6d8f95cda88cdfa04060f3fae71ac859b3c4bfbd6be205c638e97813e58a7fb1ce49982b8bc6e5ede07db4198855998855998855998855998855998855998855998855998855998855998fd6656b1f7c71ebb347f8fb1599fc0e0e1c77ec75ab8c9b3347fdf1f846bccf132d007b779bf833c531097d7cdcbfdb01d84599885599885599885599885599885599885599885599885599885599885d96f6615fb40fcb18bf7f9606cd6273078f871c0b1168ef22ccedf5f1b846bccf132d007b7f9b50ef24c415c5e372f5f0bdb419885398c59c5be2ef6d8a5f379189bf5090c1e7e5ce7580b377996c683eb83708d395e06fae036bfde419e2988cbebe6e5eb613b4461ee4b20b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a3476715fb60ecb10bc5f97b8ccdfa04060f3f0e3ad6c24d9ea5f9fb1b82708d395e06fa609ddee020cf14c4e575f3f20db01d863b735f0299a536aac32cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb30fb5a162df187fece2fdec189bf5090c1e7edce8580b477916af7fb92908d798e365a00fd6e94d0ef24c415c5e372fdf04db419885398c59c5be39fed8f9b4119bf5090c1e7edcec580b477916c7835b82708d395e06fae036bfc5419e2988cbebe6e55b603b248d19b75f2abed8c5eb3639468d7e56be5b757b04f86ed3ed5af0bd49b74782ef76ddae03df9b757b14f8de02b9b1efadbabd007c6fd3ed1ef0bd5db7b781ef0eddbe1a7c77eaf66ef0bd43b7f780ef2eddde0bbe77eaf635e0bb5bb7f781ef1eddde0fbe77e9763ff8eed5ed03e07bb76e5f0bbef7e8f675e0bb4fb7af07df7b75fb20f8eed7ed1bc0f7806edf08be0775fb26f03da4dbbbc0f73edd1e0dbef7ebf618f07d00dafcfc41dd6e00df87743b03be87757b2cf81ed1ed46f03daadbe3c0f761dd6e02df47747b3cf83eaadb13c0f731dd9e08be8febf624f03da6db93c1f7b86e4f01df13ba3d157c4feaf634f07d42b7a783ef93ba3d037c4fe9f64cf07d4ab76781ef69dd9e0dbe4febf61cf07d46b771fb7e56b76f061f8f03b7808fc7815bc1c7e3c06de0e371e04de0e371e076f0f138f066f0f138f016f071ddbd157c5c776f031fd7dddbc1c7757707f8b8eeee041fd7dd3bc0c7757717f8b8eede093eaebbbbc1c775770ff8b8eede053eaebb7bc1c775f76ef071ddbd077c5c77f7818febeebde0e3babb1f7c5c770f808febee41f071dd3d043eaebbf7818febeefde0e3bac371a159b73f08be6374fb43e03b56b71f06df71bafd08f88ed7ed47c177826e7f187c27eaf647c03757b73f0abe79bafd31f0cdd7ed8f838fdf9b1e035f56b71f075f8b6e3f01be9374fb49f0b5eaf627c0d7a6db9f045f4eb79f025fbb6e7f0a7c79dd7e1a7c05ddfe34f83a74fb33e0ebd46d1e17d4fed700b9709e0dc07d52080ffbea81a73b88f7988963f1ba79390f8cac637bf519f39532e60c46c5d3e14033ac0d7e94fb4cd2013c05073c8ef22c7e26e93472ca1b3965a0cf899067a7833c531097d7cdcb9d10dbc536472deaf47ae71a5ad4429f37f41b9a7aff2ba723af43d56f7b482eae74cc193cb990d88b1debc8ebe631717115622f3462b719b1713ce747b97d7b21302f72c0acd6db15ff7a8bfbf6c97a5d5ccf1ca70d725a021ac49513c64e69e338ecaf85f6ff360df4e57eac07bf7732bbda8f785b22bbf9ba0ee37519e8b33824ffee20defcbb0c9e2e83591dffff57d3008783fda158038b0d0e5e6e03edba2cda2d06edb84f3bf8f818a7137c7cacc0ebc063253c8e68893fdfd0b187975b80917d0b81b12584b13d7ec6e2b14e8bc1d812a22dfb1601cf42479a2d3278e61afae0fbf228a30fbfb616fa8cd71fec1a83c19f21b8afaafdb1a981bcf833f37f8278c7d53a077ae1e7f900f4090c0df9c10cf5c1c067fe3879c604039fe9f7f7f7edebd9d1bbaeb7675b0ad06a0d4c7c4e85a451033e6c8f08f105c1e0a90b9cf2e4a90b9cf2ac3164c12913eeaf3e4aa9b4787aa077f7cefe8d7b7af76cdd77706f7fefb6b57d3b907aa4418fa4b60c90147dfca80f062659ba83784f76d419b1ca154f3d3c8f8a9fa7dd519ec5379ed1464e75464e19e83312fe37da419e2988cbebe6e5d121b1631c888a5a8ca9408b31213c63aaac054e54b30ff754fe3f9e9ca83172c13d1a7332eb3cd68438e009b0fe948653ff533bfb489dcca8606063f3e8a98e2ad54650339cea5d4bcd60aa83333504a919493503a9661cd50ca39a515433886ac650cd10aa19413503a866fcd40c9f9ad16b0e4a33766a864ecdc81d0f5cdf0356f5895abd3baa19353583a666ccd451957af7574722eae8571da9a94f6feae8407daa54330cea9d561dc5a87768f5aeaa8ed4d4119a3aa25647984bc996919d42b65c6bbd826c25d9a964a7919d4e7606d92ab233c95693ad213b8b6c2dd9d964e7909d4b761ed9f964ebc8d6936d20db487601d985649bc82e22db4c7631d9256497925d467639d996a07456ea0ab2ad41e96c542fd976b21d645792ed24bb2a289d955233efea4c943af3a4ae8651679bd4d9257536499d3d52678bd4d9217536489dfd51677bd4d91d7536479dbd51676bd44cbd9a995733f1b705a5997635b3ae66d2d5ccb99a295733e36a26fc8ea034d3ad66b6ef0a4a33d76aa65acd4cab996835f3ac669ad5ccb29a495633c76aa658cd0cab996035f3ab667ad5ccae9ac95533b76aa656cdccaa99d84783d24cab9a595533a96ae654cd94aa99513513fa44509ae954339b6a2653cd5caa994a3533a96622d5cca39a69fc1cd9e7c9be40f645b22f913d43f665b2af907d95ec59b2af917d9dec1b64df24fb16d9b7c9be43f6dda05483df27fb01d90fc97e44f61cd98fc97e42f63cd94fc97e46f673b25f90bd40f622d92fc97e45f66bb2df90bd44f65bb2df91bd4cf67bb257c85e25fb03d91fc9fe44f667b2bf90fd95ec6f64af91fd9dec75b2378281330e3850fc462ff0ec774f7f7fefeebdfdcdfd7dcdbb0feceadfb977d7c1e6eb76f65fd9dc776defbeedbbfaaec317dfa187269eda5fb16f5fcfc1e69d7bb6f55edfdc77a0bfb96f7bf3157d07f66cdb8f2f7a4cbf68e6a1117bb66db307fb52cd61907e7588419fd3afe39326abcbe7f6fc50047971282ffad71013ba5f9f8a5aaa97d7978e689bf7efeaeb6fce35efa1bf3dbbe835bddb5a9bf17ffb49e4fdfdcdfbfb7bf6f5376fdfd7b7bbb9bd15d7fb60660849bcde348417358daf3cf3e0ff00fdc58975355f0300", + "packedBytecode": "0x000000028df71de500000036cb1f8b08000000000000ffed9d67941cc775ef7b76177176b058820824181624080220c2ec6c405884418e14b0244082020572810540805880041639e79cb34831283948b22d4759b26c59b26c39c8966c45cb564e94c40fef9df74defd0ae9aa9abfda3503d9a59740def00b7cfb93bd577abebfeeeedeaea50d55d6f0741100bb24bb99287839b17fa7fdafc266f6da98db0aca44fce58897096950867798970569408679712e1ec5a229cdd4a84b37b8970f6889053b39505372e51f3f6f410d7a819e32516d3ca128869a2c462daab04625a1594461bd5bb4438ab4b84f3ae12e1ec53229c77970867df12e1ec57229cfd4b8473408970de53229cf79608e7c012e1bcaf4438ef2f11ce074a84f3c112e1ac2911ce4125c2f95089703e5c229c834b84f39108398701e710f3fba8f91d6a7e29cf70f3fb98f91d617e471a1f2bccfa2825a3359b925aeb7f2925754aea953458ff6b543246c95825e3ccff6accffc62b69523241c9442593944c367198a264aa92694aa62b99a164a692594a662b99a364ae92794ae62b795cc97b942c50b25049b39227943ca9649192c54a9e52f2b492254a9eb158deab64a9926795bc4fc93225cf29795e498b92e54a56286955b252c92a25ab95bca0648d92b54a5e54b24e499b92f54a36287949c9cb4a362ad9a4a45dc966255b946c55b24dc9762b663b94ec54b24bc96e8b738f92bd4af629d9afe48092834a0e2939ace48892a34a8e2939aee48492934a4e2939ade48c92b34ace2939afe482928b4a2e29b9ace48a92ab4aae29b9aee4fd4a5e51f2aa920f18163a105e53f2ba923794bca9e4834a3ea4e4c34a3ea2e4a34a7e47c9ef2af93d25bfafe4634a3eaee4134afe40c91f2af923259f54f2c74afe44c99f2af933257faee42f947c4ac95f2af9b492cf28f92b259f55f2d74afe46c9e794fcad92cf2bf98292bf53f245257fafe41f947c49c93f2af92725fface45f947cd98af9bf2af937255f51f255f33f7a0ef6ef4afec3a4bf667ebf6e7ebf617ebf696df32d25dfb674ffa9e43b96eebf94fcb7497fd7fc7ecffc7edffcfec0fcfed0fcfec8fcfed8fcfec4fcfed4fcfeccfcfedcfcbe657e7f617e7f697e7f657edf56f2eb01d974f7a063490711b549f52b57ea3e140af690e0c645c7a2dcfc8f7e6b8cbec2acd32fc5ae8b59ef62e9bb9af5ae5639ddcd7a774b5f6dd6ab2d7d1fb3dec7d2f735eb7d2d7d7fb3de1ff4f1009ea51abdd6951b550c74540fcb40d725b831265ad7958a035db7e0c658681dedc7aea0eb6174dd40d7d3e8ba832e6e743d28664a2a8d2e1d445527922dbadc44d4e59afea55ed1f3aed0e55679e2ed1d3def4a5d6eb5075e5d3fee3265f5867ad3c7e8aa4177b7d1dd05babe46d70774fd8cee6ed0f537babea033cd54d00f74f7185d7fd0dd6b74034037d0e8ee01dd7d46772fe8ee37ba81a07bc0e8ee03dd8346773fe86a8cee01d00d32ba0741f790d1d5808ec6ae0c02dd60a37b08748f18ddc3a0a3367530e8e89aef11a3d3ed4497186c63f4d44665b6a176187443a90d06dd306a7f41379cda5ed03d06b6493702da15d28d343a6aa3f4ffc69a743a88ea9848658e89715197ab4ad6e536455f6ea63f6e42d011d734d81907b19a68d2118ef9a945db31236487f415909e0d79291fc583ce33c4aecf27e34d7a628eedc65adb2520cf7887ffe9205aff9b2c9e268bb90ba4fdd4d9ba94d4d9bc9782ebec5390d7ae7b74cd733bd6d979c0e1a1ce364a9dcd7b29b8ceb6425ebbeed175efed58679f010e0f75b6c54f9d4d25a5ce669f7f0581bbeed1bdcfed5867570347f475b641ea6cfe4bc175761fe4b5eb1eddffde8e75763370445f67c7b4c8b541de4bc175f60ce4b5eb1e3d8bb91debec41e0f05067574a3b9bf752709d7d15f2da758f9e0bde8e75f63c70445f67c779aab3755267836c5f6610b8eb1e3da3be1debecebc0117d9d5d21cf67f35f0aaeb39f82bc76dda3fe92dbb1ce7e0238a2afb3adbe9ecfa6a4ce66c7700481bbee51dfdded58673f63d2ba6fec6ba66fec01d07ddde81e04dd3760ec01e9be697483c02f0fc7c0583906f25e0a3e06be0579edbafc9049df8ec7c09781c3439d6d913a9bf752709dfd29e4b5eb1e8d69b81debec7780c3439d5d217536efa5e03afbff20af5df7687ccded5867697ca8be5ef8aeb95e180abaef19dd30d07ddfe88683ee0746f718e87e68742340f723a31b09ba1f1bdd28d0fdc4e84683eea7469704ddcf8cae16743f37ba14e8de32ba3ad0fdc2e8ea41f74ba36b00ddaf8cae11746f1bdd18a3d3fd5834a6ea4b46d71db8d24174fb360e31a02566ada7215deb972799001eb45517bdad3aed7b2ac8dff73ae0a9f7e07b1c6ce4c3530f3c0dd1f364c69336465f6e661fa7ac98c6c1560afc1ae3c1af18d8a2b2699dec254087c7f4180763e4e767751e8d812d2a9bd6c70223e9b08da136978e1fdd36df1febe0f5702c65cecf682f0d1c64af02f25c1ed091779061ab84ff531b5009db631bcca9ae6846aa238dc5674ce5cbd860317a3abe33316bb0785cb6c77bb26dc782ead4f822d81e6bd9aeb76c631b424bae367e2c307bb85eaf7d37aed7f1bcc5e17a3d1debc86b5f7753db53e8f5fa186b3b8ed7eb0d704ef0703c64eac0788b83d6eb21764d21b11b0fb1a33c8f828ece118da0a3b696cac0eb5b6c877d5c2fc5821bafa5d3b0dee0e01e0b8c0d0e460fd798a95c6d733d30920eefad539e6236cee219e788c59d6adb433dcd1c976483aed5e818227b1590e7b1f28ebc4ba0cdf0513ff158a025df7ba0e8f7532a734f565f000fee3b0fd7d9b59eea6312efe7df09a2ad6b769b586fc52aec9e3fe9217e58f7a96c5a277bc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f19fbb2701c0fe56b60c2688f87f2f59c3ff3cd355316f601bde5757c502ad30f40f1a7f100c32c9f2b20cfaf631d6c6fc3f820fa3f8e7772ed4b1f632573ed4bb2e71aafe4b3bfb9cee2a973c48283ed9ac86ca756f8d9bfa9a4fee691fe6e60bdb54fedbef9ccf7082d9daea7a3ca3bfcf6d1f757685f24b625948eb26f2f013c68cbc7bec163af2cb8b1fdc0f38caf712cd456537ff978cb7605e4e95116fc66df34c1ffd3c1cded05e6a1b2697d186cdb64955de5cfdf9c6313c60337a5bb5abe350237e5e955d6e1e3274ddad3392e55e8386a6ca7a3bf06c88e214815c093041e1fe7314fd73a49ac8f518f21b0c788b9aea1280f8eaff330b631e7382fb227ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccfc99358feb1b1494af8e096391c65d64fa33e87b4dd82ff67a59875ddf7d80d4e734dcf219df5bfd7e5907db874cba32b879ac45d8bef4d5b716b62fc99eeb3b28befa9463608bcaae75c48283ed9ac86c67c71044bf7f3bc610a4ac7d5a67ed4fec3bc763e893504fedf114f1e0e631099e8ef19cfb86ece1b184e35f288def66fb88359e4bec3145640ffbce3f63624b7de7d1b7d5a9a4cf7683be5742e304520e5f29cfe7a0edfbbc49e3f8913a28ebab8effd392ab9f9ae2a77d9e14bdcf99fd3bd99445fb7792c3761a5823b25d8bb66356d9a4af80f457ca6e66a07850ac895d1f23f8dd97b0edeaaded129067a2c3ff74c4fe4fb278701feb45d79d2f423dfb2a9cff7db549134362340c6244793c7f3fd0391ed01ed388ed68372b0f6d8bdf45fb36b4516163575de780df7ccb2828ce3980ec25829bcf0bf98c31bdd3c788bd05ed4513fc3f1ddcfa18b1b7a00ee138adc02a7f38944f5cdd82f0730be579db3a8f7a18c395d7f7715cf703c48b63d028cfff85b66ab019ab59c8fdc0bb756fe7ba1fc0edc27cc77621ea7323d64764c1efab519eff6fd5c7a610eeb18e6dff27645b8a95fdadb0cae0e6f8f9f9ce5ab6bd9964f942c7d444f085f27429eff0c5cf3553f6fad3d737e5e85a87daa05a87af94a7277c33aad2a4e3b09ff0bc30d0f17f5a725d7f52fcb4cf53a2f739b37fa79ab268ff4e71d89e06ac11d9ae45db74fd4976485f01e97bcb3bf2523e8a07c59ad82b8113d9ededc65bdb25204fdae17f3a62ffa7583c532c665d77aaa09e0d84f1f7bedaea74e08ed17088d16fec818ece23f81d50d7b30e5fe7d2b0eb387c6f8b74d8b60f869816eb9d1afb999eebfa64b8c58fd727c3a09d8d3bf2dacf2a69bb28c72ce33b22785d88ef88f8ba474a0437c6336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ae08ec9b2a03461fe73a3cafe6c3e83aff9503a3aff7f8520530d601239eef88d1c377586b3b3b17055eeb7401461fefa016fabc1abfc18cef2213a38f6fe117fa3d5bfc96356dd70d187dcc71807315e4c3e89a23a33bfc7a980fa3b6b3dfcdc739327a00a38f6fe9c7831bbfffffdb18c701236dd713187df423c5831b9fabfd3646fc063b6d17f7cc98ebdcee793c48aad0671038fe029f41519c5ce3587c3dabb79f8dd13a3eab47dec05f1c735e2335f98d450ae34e4bae7de88a4f2ff31b87fffb7d9e78631f2f954deb13819174557ee398f35a7382df58a430eeb4e4da87aef8f436bff1e0e6f93f3cf5593bfb28691dfb714957ed99276ef1fcb638ba18ef02c6c99e182715c038191869bb3ec098f6c438b900c63430d2767703a387e7d519c674018cf85c97f47d8171aa27c62905304e0546daae1f30fa78f61c07bbf9304e0346daae3f304ef7c438ad00c6e9c048db0d00c6199e18a717c038031869bb7b8071a627c6190530ce0446daee5e609ce5897166018cb38091b61b088cb33d31ce2a80713630d276f701e31c4f8cb30b609c038cb4ddfdc038d713e39c0218e702236df70030cef3c438b700c679c048db3d088cf33d31ce2b80713e30d2763525c038a804181f2a01c6874b80717009303e52028cdd4b80f131607c3c7ac6ccfdf5fc02181f079e05d1f3d4c7c1463e3c0b80e73dd1f3d47af233333e6ba1292bea6fa4355bb17adc8a5502f22c84f8357b885f0cec52d9b44ef6845998c398350fb54fc41a877cf3993092ee3d9e79e2168f5e72b58fcd7e799209473cb4ad27a2b7952ad4f727806751f43c9973d51305f02c029e27a3e7a9f5e467e69cb2d8f2e909cba704e4c17663b1073f636097caa6f5c50edb3541b4b1782a8f583ce5e079aac8b1207b85322f2c41660e71c6b69058e3906f011346d23de999276ef1e82557fbe8626cf6cb98ea2ca3e659123d4fe69cf254013c4b80e7e9e8796a3df99969479fb17c7acaf2290179b04d7ac6839f31b04b65d3fa33b01f0a615e5c82cc12e7ce31639b45ac71c8b7880923e99ef6cc13b778f492ab1d733136fb654c759651f32c8d9c273b3fc03305f02c059ef746ce936dfba3f733dbf63f6bf9f48ce55302f2609bf4ac073f636097caa6f567613f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66ec6320d638e45bc2849174eff5cc13b778f492abdfc1c5d8ec9731d55946cdb32c729e6c5fcdb305f02c039ef745ce93edab89decf6c5fcd73964fcf5a3e25200f1edfcf79f0330676a96c5a7f0ef683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f366c63e06628d43bea54c1849f73ecf3c718b472fb9fa1d5c8ccd7e19539d65d43c2dd1f364dea97cae009e16e0793e7a9e5a4f7e66fa6a965b3e3d67f994803c787c2ff7e0670cec52d9b4be1cf683300bb38b19db2c628d43be654c1849f7bc679eb8c5a3975ced988bb1d92f63aab38c9aa735729e6c3ffdf202785a816745e43cd9b63f7a3fb36dff4acba7e5964f09c883c7f74a0f7ec6c02e954deb2b613f14c2bcb8049925ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689f39d1367ec6320d638e46b61c248ba159e79e2168f5e72f53bb8189bfd32a63acba8795647ce5397e9ab595900cf6ae05915394fb6af267a3fb37d352f583eadb47c4a401e6c935ef0e0670cec52d9b4fe02ec87db9d797109324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee0181d628d43be56268ca45be599276ef1e825d7b81d1763b35fc654671935cfdae87932df2479a1009eb5c0b3267a9e5a4f7e66c63abd68f9f482e55302f2609bf4a2073f636097caa6f517613f08b330bb98b1cd22d638e45bcd8491746b3cf3c42d1ebde46ac75c8ccd7e19539d65d43c6d9e785e2c80a70d78d645cf53ebc9cf4cdbbfdef2e945cba704e4c1e37bbd073f636097caa6f5f5b01f4a8d198f25628d43beb54c1849b70e783cd4bb82dba436471c9b4b8071610930f62801c69e25c01807460fd7e999633861f1d0fa5abff14945119f4a8813d7762f013cbef66195c553e58805d77dd8cbfc72de8755c0e36b1f565b3cd58e5870dd87bdcd2fe77d58ed378e91b4f7779500639f1260bcbb0418fb960063bf1260ec5f028c034a80f19e1260bcb70418079600e37d25c0787f09303e50028c0f9600a3ef671ab9ee73db8a603becfeac18b6c3ee2b8a615b622e3197984bcc25e612f35bb52d3197984bccdffd98fb78968bcf8d69c9750fe17abeebbb4f5e18f9302e66ca883cb1e87892e83bdadac0c077622835c6c525c0b8b00418258ed93ed4ce306a9e973df16c2880e765e079297a9e5a4f7e66c61a6eb47cda60f994803c580f367af0330676a96c5adf08fba1d498f15822d638e46b63c248ba9780c7d7f155c8f18ec7d7264f3c2f17c0b309787cec2f4f7e668ef776cba7972d9f1290078f9d760f7ec6c02e954debedb01f4a8d198f77628d43be36268ca4db083cbe8eaf428e773cbe367be2d95400cf66e0f1b1bf3cf99939deb7583e6db27c4a401e3c76b678f0330676a96c5adf02fba1d498f17827d638e46b63c248ba76e0f150ef0abee7d8ec88e3c212605c5c028c12478923274689e39d13476114466114c67783b114da7039cf14fe6c00efc5b746cf538ff76df9f06c051e1ff7769efccc3c1bd866f9b4d9f2290179b01e6cf3e0670cec52d9b4be0df683300bb38b19db2c628d43be36268ca4db023c1e8eef82dbfead8e3836fb654c759651f36c8f9ca73e89f5251f9eedc0e3a34ef9f133dbf6efb07cda6af994803c787ceff0e0670cec52d9b4be03f64321cc8b4b9059e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e2dc39661ce34eac71c8d7c6849174db8067bb079e42c7e16f77c4b1d92f63aab38c9a6767f43cf5585ff2e1d9093c3eea94273f33ef0aecb27cda6ef994803cd826edf2e0670cec52d9b4be0bf64321cc8b4b9059e2dc39666cb388350ef9da9830926e07f07838be0b6efb773ae2d8ec9731d55946cdb33b7a9e7aac2ff9f0ec061e1f75ca939f99b67f8fe5d34ecba704e4c136698f073f636097caa6f53db01f8459985dccd866116b1cf2b5316124dd2ee0f1707c17dcf6ef76c4b1d92f63aab38c9a676fe43ca924d6977c78f6028f8f3ae5c7cf6cdbbfcff269b7e55302f2e0f1bdcf839f31b04b65d3fa3ed80ffb0a605e5c82cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c4f9ce8933f631106b1cf2b5316124dd1ee0f1f03cbee0be9abd8e3836fb654c759651f3ec8f9ca72e89f5251f9efdc0b32f729e6c5f4df47e66fb6a0e583eedb57c4a401e6c930e78f0330676a96c5a3f00fbe176675e5c82cc52378ac32c754398c398a56e087318b3d40d610e6396ba21cc61cc52376e5a84d9a4a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e0873183387ba81637488350ef9da9830926e1ff0ecf7c053e838a2fd8e3836fb654c759651f31c8c9ea71eeb4b3e3c0781c7479df2e46766acd321cba7fd964f09c8836dd2210f7ec6c02e954deb87603f08b330bb98b1cd22d638e46b63c248ba03c0e3e1f82eb8ed3fe88863b35fc654671935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f1290078fef231efc8c815d2a9bd68fc07e1066617631639b45ac71c8d7c68491748780c7c3f15d70db7fd811c766bf8ca9ce326a9ea3d1f3d4637dc987e728f0f8a8539efcccb4fdc72c9f0e5b3e25200f1edfc73cf81903bb5436ad1f83fd20ccc2ec62c6368b58e390af8d0923e98e008f87e3bbe0b6ffa8238ecd25c0b8b00418179700a3e738a63acba8798e7be2395a00cf71e0f1d17e78f233739e3f61f974d4f2290179b01e9cf0e0670cec52d9b47e02f643a931e3b144ac71c8d7c6849174c780c7d7f155c8f18ec7d7494f3cc70be039093c3ef697273f33c7fb29cba7e3964f09c883c7ce290f7ec6c02e954deba7603f941a331eefc41a877c6d4c18497702787c1d5f851cef787c9df6c473b2009ed3c0e3637f79f23373bc9fb17c3a69f994803c78ec9cf1e0670cec52d9b47e06f643a931e3f14eac71c8d7c6849174a780c743bd2bf89ee3b4238e0b4b80717109304a1c258e9c18258e774e1c85511885b130c6f525c028fb5a18b932b679608c197bc843eb6d45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627eabb625e6127389b9c45c622e31bf55db127389b9c45c622e319798dfaa6d89b9c45c622e3197984bcc6fd5b6c45c626edbf630deb4e0f1dc6780e7b4875878f233a9cb3d6bca7a27c2f8e9589db362d566c52a0179ce42fcce79889f6bcc35ad93bd4299873060f6643bd54b95d103fc271b8bad7868fbe73df91ed6e69d2f82edb036af18b6c3dabc62d896984bcc25e6776ecc315d11dcfc0e922ee382497731eb947f3d6c4779deaaccfe5605b23f7dd8966348622e319798bf1b31c7b88c2f024f60f10439786632e3a967c6d3c28c6709339ee1cc784633e399cf8c6716339e29cc78ca99f13431e36960c6338219cf6c663c5399f14c60c6d3c88c27c98c6728339e91cc785a99f10c61c6b39419cf02663cc5e81f2a84670e339e51cc782632e399c68c670c339e5a663c8f32e399cb8ca792194f8219cf74663c9398f18c65c69362c6b39a19cf32663c8b98f1f462c653c58c6706339ec9cc78d632e319c78ca78e19cf3c663cbd99f15433e319c68c27cd8c27c680271edc3c263d0eff3f03ba326bdb6e4abe35a0e3ff178dbe0cb6b964d2e58eb22f828ec67a5d726c8b71ba08bea44d3a796b4b264e682b0deb64af12382e31e14933e319c68ca79a194f6f663cf398f1d431e319c78c672d339ec9cc786630e3a962c6d38b19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ce751663cb5cc78c630e399c68c6722339e51cc78e630e339c38c6701339ea5cc788630e36965c6339219cf50663c49663c8dcc782630e399ca8c6736339e11cc781a98f13431e32967c6338519cf2c663cf399f18c66c6339c19cf12663c2dcc78ea99f1cc64c633dec1e36b5e66eaffa7b269fd0c13db1ef643e6bb79973df974c594d5d5944bfc64af02f2ac301d43babf0ab7252e7bbc063e2bb80231bae0c997b06f675c2882edb06f6714c376d8b7338a615b621e1ef32bd1db4ee118285a62d67a1ad278dcf9183be6c9cf1bdabca8bf157ad58ad5052b5609c87319e277d543fc5ced28ad93bd4299873060c67a5113445b2fae45efd36fbe414a71bd66c517fdbaee29a6616de9f522d80e6b4b8b613bac2d2d866d89b9c45c627e67c7fcfd261de17546126de87b283a7fbc1fecbe6ad2b108edeab25e3165d1375389e355e0a13c6fc03d9dd43f39e6ef8498db697a4681df15f0f50c296c9f14e3f955d83e2986edb07d520cdb12f3f0987fc083ed7870e35c127ac9f58ce203c0f38a071e4f7e66ceb5af593e9db17c4a401e6cfb5ef3e0670cec52d9b4fe1af0d082dfd1f5510ff2d9e761dfd1e5c053cf8ca78519cf12663cc399f18c66c6339f19cf2c663c5398f19433e36962c6d3c08c6704339eabcc786633e399ca8c6702339e46663c49663c4399f18c64c6d3ca8c6708339ea5cc781630e399c38c6714339e89cc78a631e319c38ca79619cf5c663c95cc7812cc78a633e399c48c672c339e14339ed5cc789631e359c48ce73a339e5ecc78aa98f1cc60c6339919cf5a663ce398f1d431e399c78ca737339e6a663cc398f1a499f1c418f0847d4797fe7f1574d4678fdfd67ddda45f015d99c306f51dbd06ba0a93a632f477797f34e0e6b2314ebec629a0ad34ac933dfc8eeeeb4c78d2cc788631e3a966c6d39b19cf3c663c75cc78c631e359cb8c6732339e19cc78aa98f1f462c6739d19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ca79619cf18663cd398f14c64c6338a19cf1c663c0b98f12c65c63384194f2b339e91cc788632e34932e36964c6338119cf54663cb399f15c65c63382194f03339e26663ce5cc78a630e399c58c673e339ed1cc788633e359c28ca785194f3d339e99cc78c63b78e4db30d1da96eff1f08cb987ef5466be958adff9d18b3df6260d69cfefcbd6c7831bbfe3f9db78f0bb283eda254f7e66bed1f906b04755ae8ed59b56acae5ab14a401e6478d343fc6241f8f756c89e300b7318b3e6a1f609c7fd51beeb4c1849f71af0f86837b4eff44c81cad7df309cddbbc3ae87f344bd8e037def97be833fc2da2f1590e7b3fd3bd8e61bb64af83fed37edcf154ba77df0fd2d7f2a9bd6c91e3e43c2ef82fb3aefdbdfacbeea88c5bb69dbc377cb6b0bbde6b8e2e079273a9e24b63168eb9227df2f07f9fb7ec9c113a1efb509e0f13c574aa6eda2fe182a5fb70fcb7b7b8d793d1ef7d476d9f319e21c1ed7a1ed5a95a3ed2a0b3a8e19fa5f8dd1dbe7ac778cde2ea32690fb291fb6e51e56ee61038b8716bc878dfe3a23952cf41e16e7cff071bef5743d95f4744ecadcc35eb46265cf3592803cc598e3ebb2c543eb644f98855998855998855998855998855998855998855998855998855998855998f933878d39a17cd79930920efbac7d3ce7d7bed37baa54beee1bed56dd6137fa7e8b5412c72850bfed28cb679c377455bf0eb6b861c3f11c38e6c4b52f7df4f7e7da9764af32b8791c8acf7ebab03130c5e8232cc4764d64b6532bfcecdf5492e644bf6aedd3eb8efde9a3adc0f153b4e4ea6bbc083c3ee6bdf7357644fb74c1f2e98ae55302f2e03c16173cf8e93aafd1fa05e0a105e711f475ce082c9ec0111f5aca98f1d433e3e9ce8ca78519cf12663c8399f1cc67c6f300339e01cc784633e399c58ce72e663c5398f1f460c653ce8ca789194f03339ee1cc784630e3b9c68ce711663c0f32e3b98719cf63cc786633e3e9c38c672a339e9ecc782630e3a960c6d3c88c27c98c6728339e91cc785a99f10c61c6b38019cf52663c35cc78ee65c6338719cfddcc78a631e319c58c6722339e38339e31cc78ba30e3a965c6f328339e41cc780632e399cb8ca72f339ee9cc782a99f12498f14c62c6d39519cf58663c29663cab99f12c63c6b38819cf43cc78ee63c6d38f19cf0c663cbd98f15431e399cc8c672d339e71cc78ba31e3a963c6e37b1c73a13c0f33e399c78ce77e663cfd99f1cc64c6338c194f9a194f6f663cd5cc78620c78e2c1cdef9ec4e1ff574047ef48e0fb75658ef2681c23e5d7e7e95f0eb8b9ec3247d9171d0c18a7f3e04bdaa493b7b6dcf05e48cc944beb64af12382e32e1a966c6d39b194f9a19cf30663c3399f1f467c6733f339e79cc781e66c67399194f1d339e6ecc78c631e359cb8c6732339e2a663cbd98f1cc60c6d38f19cf7dcc781e62c6b38819cf32663cab99f1a498f18c65c6d39519cf24663c09663c95cc78a633e3e9cb8c672e339e81cc780631e37994194f2d339e2ecc78c630e38933e399c88c6714339e69cc78ee66c6338719cfbdcc786a98f12c65c6b38019cf10663cadcc784632e319ca8c27c98ca791194f05339e09cc787a32e399ca8ca70f339ed9cc781e63c6730f339e0799f13cc28ce71a339e11cc788633e36960c6d3c48ca79c194f0f663c5398f1dcc58c6716339ed1cc780630e3798019cf7c663c8399f12c61c6d3c28ca73b339e7a663c65cc78c65b3cf87ffdec83c6b75e001dfdbfb14ff6b7caf871c1b217811fc9b0b9317cc50c97b4232661736370e0a967c6d39d194f0b339e25cc780633e399cf8ce701663c0398f18c66c6338b19cf5dcc78a630e3e9c18ca79c194f13339e06663cc399f18c60c6738d19cf23cc781e64c6730f339ec798f1cc66c6d38719cf54663c3d99f14c60c653c18ca791194f9219cf50663c2399f1b432e359c08c6729339e1a663cf732e399c38ce76e663cd398f18c62c63391194f9c19cf18663c5d98f1d432e3799419cf20663c0399f1cc65c6d39719cf74663c95cc7812cc782631e3e9ca8c672c339e14339ed5cc789631e359c48ce721663cf731e3e9c78c6706339e5ecc78aa98f14c66c6b39619cf38663cdd98f1d431e3b9cc8ce761663cf398f1dccf8ca73f339e99cc788631e34933e3e9cd8ca79a194f8c014fd85c14f4ff72d09d33e96ba03b6bd2974177c6a42f80eeb44357e660217be74047e302ce828e9e4d9f011d3d7f205bfafae0ff0cb899b5cce153b983f5acc3a7738e6d713fd236e920dafd88b6d2b04ef670ae8c734c78aa99f1f466c69366c6338c19cf4c663cfd99f1dccf8c671e339e8799f15c66c653c78ca71b339e71cc78d632e399cc8ca78a194f2f663c3398f1f463c6731f339e8798f12c62c6b38c19cf6a663c29663c6399f17465c63389194f82194f25339ee9cc78fa32e399cb8c6720339e41cc781e65c653cb8ca70b339e31cc78e2cc782632e319c58c671a339ebb99f1cc61c6732f339e1a663c4b99f12c60c6d3ca8c6724339ea1cc7892cc781a99f15430e399c08ca727339ea9cc78fa30e399cd8ce731663cf730e3799019cf23cc78ae31e319c18c6738339e06663c4dcc78ca99f1f460c6338519cf5dcc786631e319cd8c6700339e0798f1cc67c6339819cf12663c2dcc78ba33e3a967c653c68c67bcc583634893a0a3742de8289d021da5eb4047e97ad051ba0174946e041da5c7808ed2634147e971a0a334f988ef6714639e0bb24565d3fa7960a4fb17d73e6902eef3964e739ff2c47ddee2a6f553c0e8f285d82600f7294ba7b94f7ae23e6571d3fa496074f9426c134147e949a0a3f464d04d067ba4a3f414d0517a2ae8283d0d74949e0e3a4acf001da5e9fd057c976116e8283d1b74949e033a4acf051da5e7818ed2f3cdafdec7272d9ddec7274c3a1d44bb8fc916954deb2780d1b5df896d01709fb0749afbb827ee131637ad1f0746972fc4b608b88f5b3acd7dcc13f7718b9bd68f01a3cb17625b02dcc72c9de63eea89fb98c54deb4781d1e50bb12d05eea3964e731ff1c47dd4e2a6f523c0e8f285d89601f7114ba7b90f7be23e6271d3fa616074f9426c2dc07dd8d269ee439eb80f5bdcb47e08185dbe105b2b701fb2749afba027ee431637ad1f0446972fc4b61ab80f5a3acd7dc013f7418b9bd60f00a3cb1762c3ef95acf5c478cd62bc5644db61d78cc5b01d76dd570cdb61d76ec5b01d764d510cdb61d705c5b01d766e2f86edb0f373316c879d638b613bec3c590cdb61e7ba62d80e3b5fc9f12dc777d4b6dfcd73c99d7a7cbf9b6deabbd9b6c8b5a25c2b16cbb69c4be45ab158b6efd46b4569cfc3db730fcf845271b0414bcc5a4f43fa00f0f878b6e6c9cfa42e77bf29eb9d08cbd5b1da67c56aad15ab04e4d90ff1dbe7217e31b04b65d33ad92b4566ac17b1e86c27e36003bf6fb897ca07dd1e93ae05dd6e934e816e9749d7816ea749d7836e874937806ebb49cf05dd36939e07baad267d0a745b4c1afb06379bf449d0b59b34f6c56d32e913a0db68d2d8f7f5b2491f07dd4b268d7d4d1b4cfa18e8d69b34f6edb499f451d0ad3369ec4b79d1a48f806e8d49b780ee05933e0cba55268d7d052b4dfa10e85698f45ad02d37e983a07bdea4f783ee39936e04ddfb4c7a0ce89e35e9b1a07baf498f03dd33268ddfe17cdaa4713cc553267d16748b4d1ac72f3c69d2f81dce274c1ac70b349bf444d02d34e949a07b8f494f06dde3269d06ddfb4d7a0ae85e31e9a9a07bd5a4a781ee03263d1d74af99f40cd0bd6ed23341f78649cf02dd9b263d1b741f34e939a0fb9049631fcd874dfa00e8ca4c7a1fe868fcf05ed0d13b4c7b4047ef99ef061d7dab6417e8e87b693b414763bc76808ec6096f075d4f93de06bab8496f055da5496f011d7d176433e8e85b57eda0ab32e94da0eb6dd21b4147df097d19743476f725d0d1fb3a1b4047ef0caf071d7d07a30d74f4ada975a0a3ef39be083a1a83ba0674f4dec90ba0a3777157818ebe2fb11274f44da915a0a3ef362e071d8d357d1e74f47ec973a0ab31e9f7816e90493f0bba874cfabda0a3ef213e033a1ac3f934e8e83d92a74037c4a417838ebe17f124e8869af413a0a3ef8a36836eb8492f04dd6326fd1ed08d30e9c74147ef8dbe1f74f46efd2ba0a3f1cdaf828eced91f001d9db35f031d9db35f071d9db3df001d9db3df041d9db33f083a6afb3f043a6afba9fdd0c7a93e7eaf98f57410dd7594b67735b871c9752d4f0cc813e5b5710278d0d6a5c87d4f65aec3e9faadcc944bf5e512d8be10b9edec3dc04553561753ee05cb7605e469efd7b16fcec3ffd3e0036d8779a86c5a1f05db9eb7caae32fe5ef4e4ef058b89b82f0213e5d9d6af23ef40d32077876d2264cbdccf525d0b2086b8a4218df367441fab54e6fee272013c178127fae3247b7feda34ee0b115f5fdb5fd6cc4ae6b09c87301e2e76b5cf1458b87d6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cd49f80e38328df15268ca4c3be2e1fcff9b1cf15fbbc7a40bf8eeffebdaea6dcd196cf1590676eff0eb68449e3b75568bf85ed4b0ffd8439f725d9ab047fb02fc8477f710c6c51d9971cb1e060bb2632dba9157ef66f2aa9c7c7e8fefdcbd63ebd62ed4fec17c7636820d453dae6baf98d437cae437c3c1ce339f70dd9c363e912e8287d15187dc41acf25d41e507f38d9c37ef1474c6ca95f3cfab63a95f4d96ed0f7a1ecfe76f495f20c87b66f8449e3d8902b505693e3ffb4e4eaa7a6f8699fcf44ef7366ff9e3665d1fe3de3b07d0a5823b25d8bb66346c80ee92b203dbe7f475efbdd288a35b1e3b79b90dddeeeaab55d02f29c75f89f0ea2f5ff8cc573c662d6756734d4b32638fffb6a93ce86c46814c488f2e07590877126ce369238c89ece43fbbf9b9507af5928cf3468a3b42fd4ce939f38b605cf01e73cf977c5f28fd6c91e5e1b5f0446db475d3fba5577f0dee9e3bf9e84f6e23cfc3f1ddcfaf8af27a10ee138adc02a7f34944f5cdd82f0730be579da3a8ffab8c7c07d69c713c797519ea5d00e5de8c4b5febb75df1676adefe37b94f1e0c67b6fbde43abfe339c647fbe2c9cfa4ebdc75def229017986809f1eae6372be5778066cfbfa0629d9a06ba80b562c2a204f9bd57684c511ef552f16c59794f37a70b4c317cab3c96aa74e7b60f2b9dff03a4b977bc9e12be5d906eddf0eb89ea7fd846de971c7ff69c9d51e50fcb4cf27a2f739b37fe99d32dabf271cb68f016b44b66f78bf9eaef7c90ee92b207d0ccedf948fe241b1c66f489ea4ed82f0ed2e58db2520cf4987ffe9205affc3be6d40f674ddd90df5ec385ceffb6a374f86c46834c488f2e0b3153ab753feb0f3be8ff1d5b9cefb9780d16e37f1dac527db658bcd7e86eaba1ea43cb42d5e0f5e857636eec8ebffd970f6daa69031ff9e9f91d7c7ac78a6c1ce48d053fda438d3fff01a97f2bce9fdf955b67e5cb0b8c90fecc33967719fb7e28acfa23e6addd3dac7a6eb59a7a76bce7abc16b3af87ce013be5f9b8753d645fc75d73f8629fa7cb829bef05dfb1f2e231996b3b3b7dc5da069f19b89ec9e2bb2e6781256dd2c95b596a9349642a73c42211dc7c4fe67ace40c701d6392acb3e5668db0ac8f319ab4db2f3ea366979ef8ef8d07ec467edae3ae9eb3952d87316b2a7195defd145df76dd99ef2b758facdc71cb4bb18fbbbb958ec676aa15cf15b96271cec1e3eb9966582cce396c47178bc615aef3872b16671d3cbeee31c36271d6613bc258ac723d5b70c5e28c83c7d7bd46582cce386c47178b31373cd7c8158bd30e9ee89f69e48e05f68915c27c960173772b1d8dedfae5aebe3b572c4e39787cf5dd85c5e294c37674b1a86d74ddb3bb6271d2c173b2c8b138e9b01d5d2cc68e733d5371c5e28483c7c3f3b59cb138e1b01d61bd5889cfd772c5e2b883c7d77c3061b138eeb01de1f56163ae6786188b630e1e5fcffec26271cc613bc258b468db47f388c551078faf796bc26271d4613bba582c6fd0b68fe4118b230e1e5f73e184c5e288c37674b16819ab6d1fce2316871d3cbee6d7098bc56187ed08efa132f5e2501eb138e4e0f135674f582c0e396c47178bd6ccb5d6c13c6271d0c1e36b1ea0b0581c74d88e2e16c9cc39f5401eb138e0e03950e4581c70d88eb05e64ee27f7e7118bfd0e9efd458ec57e87ed08cf23997ab12f8f58ec73f0ec2b722cf6396c47178b5599e74f7bf388c55e07cfde22c762afc37684cf5c32f5624f1eb1d8e3e0d953e458ec71d88e2e16759973eaee3c62b1dbc1b3bbc8b1d8edb01d5d2c5666fac476e5118b5d0e9e5d458ec52e87ed08af3b33edc5ce3c62b1d3c1b3b3c8b1d8e9b01de17567e6f9c58e3c62b1c3c1b3a3c8b1d8e1b01d61db99b9eedc9e472cb63b78b6173916db1db623bceeccc4625b1eb1d8e6e0d956e4586c73d88ef0ba33731ed99a472cb63a78b61639165b1db623ac1799b6734b1eb1d8e2e0d952e4586c71d88ef0b956a6eddc9c472c363b78361739169b1db623bc1fc93ce36bcf2316ed0e9ef622c7a2dd613bc2bea2cc35f8a63c62b1c9c1b3a9c8b1d804b67d8c33c158d058ac11562c2a20cf5af3d1761a8b1516472a03c795a12f1b23f7253baeece5105f36822f94e725f0a53be8a364f2e46ba6ced0f7fa696cfa5587af9467f3808ebc5b4d3a0efbe41a9475c4f17f5a728d41a2f8699fd747ef73a6aeb699b268ffae77d85e07ac11d9ae45db34369dec90be02d2870774e4a57c140f8a35b1eb6364834923bbbddd266bbb04e4d9e0f03f1d44ebff7a8b67bdc59c79ef01ea19d5233f6d57966943488c46408c280f8ed9bbea89c71e43481c644fe7a1fddfcdca83632829cf1968a3705c29f9190f6e1e37a9fd7bc9937f61f3c291bd04e82e02a3eda3ae1fb361ec672d31061dba3a934e41398d964efb3ac693af648bcaa6f531c0586fd28dc5674ce5cbd860316a9e711e6286f36cd092eb7c310e78c67ae0f1e467e63c34def2698ce55302f2e0bb8de33df81903bb5436ad8f07db3ef639c682cec943ad5854409e8f5bd78f6171a43274fd6d74f8e22b8e0d164f83c3f644cf71a4b2a94d9c5804db4d96ed7acbb63eb6b18ee925d7b1dd04cc133c30eb7227455f6ee6d8a6b9d4a83e939d7af0290d3188ca27b41db3ca267d05a4bf30e066068a079d3b895d1f475877c2b61b676d97803c131dfea723f67f92c583fb582ffabae153705de9e178c8d481891607add743ec2685c46e22c48ef234828eae71c6838eae1570ee41fa3f5e473444efafb3eda1f50660241dce67d8e060ac8f9e3195ab6dae0746d24d009e264f319b60f10cb5e283e7e56e561edab602f27c03ce8d71475e5df7ef8f75f845731c4638cf4fa65dedea215e38ff6200f109ac18d2420cdd838e391aa3e4e91974ccc1b8a97dc3c696d52b9f5899ed7a24b40a0b137f630e37ca4087e972872e086e9c6ab2027434d56417d0955961c1292e293f4d69e7235c180f2abbc2e2ec0e2c51dac6693a69c95575ba018f8faaacab0e4dd569aaced31bd7b4afc4fad1c5e2ec4cddd1ff2bcf912fac2caa07151e7c47262a9bd6c99e8e4fc2a45f6a59f1e2948dab37b7ad5cdfbe0961ed830bd3312b08f6af6b1bac24783051395dace0f4883e38f578f0da7c01d80b8025303cdda3e7c954d4bb4c592b5ad6ad5bb879f9ba352b666e5ebfa27dcd86f518d16e56e4c2a26d1ff27a71357598d7cedfd5a1b3179c49b71be86826ddeea023fb3d40471c3de17ff69ef0724c0c81f2a91aebff551887bb1a87a80ad26948b727fa98d19f2ed5a77ffd644c7fa94defbe3e4176eaddbe41766a5dfd56b5be52d053e5eaa971f554b87aea5b3dd5ad9eda564f655b1364a7aa7d28c84e453b38e8986a562f5f025e3db5acbed4d053c7eaa9621f0bb28feb4606d94f9be94f49e84b567d3baa2ff3f4ad85bee4d2975afab256dfb2ebc737fa32465f22eacb1f7dc9a22f83f5e5ef6413eb294176aa693db5f4f4203b75b49e2a5a4f0d3d3bc84efdaca75b9f1764a74e7f3cc84e4fbb20c84e5ddb1c64a7ba7d32c84e85be38c84e9bfb74909df25c4fb5aba7e0d5d39aebe979f5b4bd7aea723da5eff341767af2e541767ae0d6203b75b09e52584f2ffd42909d86584f39fe62907da4ad1fe5eb47dcfaf1b17ebca9bb1874d78a7ef4aabb047517a9ee32d65de87a48811e62a1879ce821387a48921ea2a587ace9217cfb82ec10cf03417608b01e12ad8788eb21f3fa15826341f61513fdca8d7e0549bf92753ac8be66a85fdbd3afa1ead772f56bcafab1aa7ec55b77b5e9c7a9fad1b27ee4aa1fffea697af5b4bc7a1a5e3dedae9e66574fabfb46909d36f78341765adc0f2bf988928f2af91d25bfabe4f794fcbe928f29f9b8924f28f903257fa8e48f947c52c91f2bf913257faae4cf94fcb992bf50f229257fa9e4d34a3ea3e4af947c56c95f2bf91b259f53f2b74a3eafe40b4afe4ec91795fcbd927f08b2f5f11f95fc93927f56f22f4abeace45f95fc9b92af28f9aa927f57f21f4abea6e4eb4abea1e49b4abea5e4db4afe53c97794fc9792ff56f25d25df53f27d253f50f243253f52f263253f51f253253f53f273256f29f985925f2af99592b7838ee9a3b1f1e86e5a9c4166bda5bd7d65db4bed35ed1b6ada36af6b5ff3d2baed355bd7b4bf50b361cbca8dabd66dd88a1b7fda6c4cf35c4fd9b8b1657bcd9af5ad2bb7d56cd8dc5eb36155cdf20d9bd7b7de70b2fc96d9e8be9b2db6b4b6861bfbf9ad90feaa93467f6db6eb63d6e7e4f6ed9dce04a4a2bc131b0d2def6414cdc51edd863d99bde6abd9b46e437b4db266bdfaab4eae1bb6ae6c1d5583ffdba482bca9bd66537bcbc6f69a551b37b4d5d48ec27297f7ea8413aff7f203b3a95ff6b753556570ff4e7832b57fe776c713fd6f81f4a94e1a5dd7190f377666a32b9d247c233c2c9b362f6fdfd8b2a23d7ce38fdccac61feb8c9b9feea49b6b0674c2d886ce6c747a40e7083fd619635f2fc058f0bf713e55c3fca204009b2d6c6f000000243c1f8b08000000000000ffed9d77741cd5f5c767654996bd5acb927b17d5658db45a49b6856d9069a677530dd896646370015bf44e1a0921210d924008e924815448ef0909a47712d220014280ff7ee7fcf21fe777dfeebb475f3fcfec6f479ebb7e23df3de76adf5cbd9dfbb9dfb9f376f6cdccee6b41106482f2631cd9c1c1de0ffe7fbf7d2eecdba32bc1751524393329e1ac4b09e7b89470d6a784b321259c8d29e11c9f12cea694704e4890d3b0d5057b3e92e69d28a06bd28cd99469da9c024d7329d374520a346d09d231464d4e09676b4a38db52c23925259c5353c2392d259cd353c23923259c3353c2392b259cb353c23927259c7353c2392f259cf353c2b920259ced29e13c28259c07a784f39094701e9a20e762e03ccc3e1f6e9f17dae745f699fb2eb1cf79fbbcd4e6586f978f20eb20eb347cceffcc8986225937598ff3bf5eb26564cbc956d8ffb5dbfff5911d49b6926c15d96ab2a3c88eb65aac213b86ec58b2e3c88e273b816c2dd9896427919d4c760ad9a964a7919d4e7606d9996467919d4d760ed9b964ebc8ce233b9fec0287e542b28bc82e265b4f7609d9a56497916d20db48b6896c806c906c886c33d916b2cbc9b6925d417625d936b2ed643bc876925d457635d92eb2dd64c364d7905d4b761dd9f58e663790dd487613d9cd0ee72d64b792dd46763bd91d647792bd81ec8d646f227b33d95bc8ee227b2bd9dbc8ee267b3bd93d64ef207b27d9bd64ef227b37d97bc8de4bf63eb2fbc8ee277b3fd907c83e48f600d983968577840f913d44f661b287c93e42f651b28f917d9cec13649f24fb14d923649f26fb0cd967c91e257b8cec73649f27fb02d917c9be44f665b2c7c99e20fb0ad957c9be46f675b26f907d93ec5b64df26fb0ed977c9be47f67db21f90fd90ec47644f92fd98ec27644f913d4df653b29f91fddcd1fc1764bf24fb15d9afedff787ee93764bfb5eddfd9e7dfdbe73fd8e73f3aaf7986ec4f8eefcf64cf3abebf90fdd5b6ff669fff6e9fff619f9fb3cfcfdbe77fdae77fd9e717ecf38bf6f925fbfc6ffbfcb27dfe8f7d7ec53ebf6a9f5f23bba9addc6e0a461efd41426352cfd0a03937c1621f16ecf9305a8cb3ffe3e776ebafb7cbfcccda35d8e506c7df68971b9df534d9e526c7df6a975b1dff14bb3cc5f14fb3cbd31cff0cbb3c03fcd900e628addff8c65957067c5c8775e06b08f6d4c4f81a7975e01b1feca985f1f1766c04df04eb1b0fbe89d6d704beacf54d60cdc89aadaf3f48aa260a1bcd7a7349afd79eb799943cef80596f8b10efe4e47987cc7a5b05784d7dd8e122980c7533c5fa5ac137d5fadac037cdfaa6806fbaf54d05df0ceb9b06be99d6371d7cb3ac6f06f8665bdf4cf0cdb1be59e09b6b7db3c137cffae6806fbef5cd05df02eb9b07be76eb9b0fbe83ac6f01f80eb6be76f01d627d0781ef50eb3b187c3c7e1e023e3ebe3bd4facc98509f81d7583f8f47a5d7f0980bbe853cde826f118fb5e05bcce32cf896406cf6e5610c61df52ebe3f1c8fc6f856df70749d57f71d0acb72fe9f5d29acd7a5726bfded239ad55c188aefd10a70fb45a6ddb095e37d385b133d6380efbeba1bd16fa723fd683df5398ddbc771c69dbab2bbc6e85f3ba1cf4393224fffe20d9fc573a3c2b1de606c85fa666bbbbb466ab7ec4aed975d0d7ad3d3ebe198b357b327008d46cafd66cd58fd8353b007ddddae363dcb158b317008740cdf6c9d46cb1a0355b9eeb0a82f0dae3cf3963b166370347f235dbab355bfd2376cdde067ddddae3cfba63b166878123f99a5ddea7c706553f62d7ec3dd0d7ad3d9e77198b357b277008d4eca08eb3553f62d7ec03d0d7ad3d9e031c8b357b2f70245fb37d4235dbad351b94cf5b064178edf17cf458acd9878023f99a1dd0f9d9ea1fb16bf6abd0d7ad3d3e3732166bf651e048be6607a5e6678b5ab3e5eb358220bcf6f83cdd58acd96fd8b63937f63b7b6e6c3ef87e6f7d0bc0f707b8ce807d7fb4be83202f817d60b9ee03553f62ef03cf405fb7960fb6edb1b80ffc1c38046ab64f6bb6ea47ec9a7d11fabab5c7d72f8cc59a7d1638046a7693d66cd58fd835fb3fd0d7ad3dbe96662cd6eccbb66d8e17fe668f171681efefd6b7187cffb0be25e07bcefaf2e07bdefa9682ef9fd67704f8fe657d1de07bc1fa3ac1f7a2f515c0f792f57581efdfd65704dfcbd6d70dbeff585f0ff85eb1be5ef0bd6a7dcbc0f79af52db73e731e8bafa97acafa9a20f7fe20b96d5bba8e2ad8f3917196fba1bd5496a7900bf6bc4f806375241fabdbe47e44507dee1dc0d329907b166254c3d3093c85e4794a9f47bb925f6f691b1fe1689a855847405e4581bc32108bd7cdcb1c2f073e1c378a218cddc9331633108bd7cdcbddc0c83e1cc7f83e1cde7fccd83c3733c22bb02f95de9f315e3f7070bc7ae833ab6da46fbb656b86fff318d00c6d1ce70b8e4fa8564bb5823767f5c3721730728e85da3316ab65ec74189b802d419e92669d0e4f678816076a6c81b1bab4ff710cdeff785fe278f5d067fcb891be3d1951b662dcf759accfe4c7ff6201f7cd6a788ac02331c608bdcf15f0bde9f520d95aeb71b42a385ae5a04f37e8d723a05fa5f74a8ea7cccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaacccfe33e3f9633cdf16766e767f32b2af0b7824e6f94bdfa363d7c5eb37e7759e84f33ac99fb72816f0bc235f93b5c8c9b91efa3c9319617b3ae49c2f9edfed707c42d75c94b6255e73d10fcb1c0fcf41e3352052e710f30e4f3e440b1f62b72716bb3820b37d8b05f33d16e6bba0963adb34ecfa93258ecfd4e9c47123794b9cfb8b7b2e12c7126e27796e2f17ec7ded80cc352ee5f183c7abba60cff103df677a138fbde7394c3e5fdeebc4ae873e2f6646b6cd32f87f7fb0f77539d887d7cdcb8be0b5cb9c75b7c8e55bf1da845ee0e676a393db52e0e63eafc0787e4f5db92df41e57c4ebc302e00d9c9cf881e7ec933f06285f43508cc1d3033c02d78b75091deb14b01e93be866099a355d83114f7e905fd9609e817761ccccb1c4f99955999955999955999955999955999955999955999955999955999955999fd67c6fbbb99350bfdba3c61acd17517a5f319fcbd1c785eeca6ba91b8d2e700f99cd36227e77ae8f3adba11b6db6cbb39d8fb5a8ba86d29756e2d6a5b72bc66c807cf0549ddb3deedf0748768e143ecf6c46297af21487efb8e5c435074b66997b33df1dc39ee43f7409dbadf7191057d6a71dd52d4b6e178b82f75838fdb786fb684d6f85ee25e53c4f1f0dcf9fd565b3e779efc585d2c488e1bfc9d3f7c9d40312457eef3208c7d0fd9365e3fd205eb7a2ce4fffca8749e9af513fa7eb0d2f6e5efabe2eddb17127b25b02614bb0b6367ac711cf6d743fbd1ba91beeef75fb1d6cc6ef611f73bbac25e57705e97833e2b42f2ef0f92cdbfcfe1e973984ded7c04eaec3178ff971a935604e11a2d028db80f1e07095c67123a46bad7569a3ebcfdc73b7df09885fb3c016354d4b5ab61df7db45c28bfa8ef3ee278786cdc038c6e8eee35a607fa35624fc278b10cfedf1fecfb35624f420de1755a81b3fec5b07ee61a1f44bfb7709fa79df751a97d2bea1a34dcb7dccf03cc8bd7a0719f5fc258c53f36da1cec7dec8fd76bd5e2b35dd4f5e11c0f8fa9e2e48ee342d2ef8d588fc8c2f1b01effe4d4e3b208ee8e90d7fe25e2b5ac155f738c9f9d5cfd8c0e897f2faa1d6ffa9c5c789f5a01b9709fe79dfd46eab75d92cf75cfe3211e83ba4372e53e2fc1bef6321c5ff276c2b1f2bf21ffe747a5e34ffc2ed755c9e75cdabefc9da5bc7d5785c43e0a58138add85b1f9f893e3b0bf1edaff0bef27dc8ff560ad99ddec237c0c87eceeeb7a9dd7e5a0cfca90fcfb8364f35fe5f0ac72984dedbc0a75f65f38fe941aab574668b41834e23e784f14bf8fe0f7bb86bdc7485cef5be93d26ecfb21716c0fc6d59ecd9dd30b3b3ee13efc5a3c3e69b0cc669ccd86f475e72af93d24c96b96f11e913cc4c57b44f2427ae6823df5cc391c92b15b9cd82d358cddeac46ead616cd55c35f749739fbeff1bbfa7bb2e058ce352c0589f02c686143036a680717c0a189b52c03821058c1353c09805c6fdf9de2ea04f31097d9a41a7a521dc52f7e32f7518ddef4ac839bcccb33f8f917cdd8693ecb3cfdbb00578c6d8b16622db70b27df6791bb6caea98c878df9602c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d02c63929609c9b02c67929609c9f02c60529606c4f01e34129603c38058c87a480f1d014301e9602c634cc552e91651cf5e76bc323f09b903df819bd1a1ee9efd614cab3747d165fc798f477a4b9bfebb8d4d10aef3991fe3dd84c10fd5da91c4f9995398a19c742fc1d6fee97f78431ecbb8705c68dd8ef291db23c7bfcf67687ecb688fd5e25fb9ba0e5f7aa03e13741c3ee6f09fbbe77ee83e3c6feba8e1163b707c96ad153851635ba075be4fbfa3b53c8eccb6f0cf0f814f61b03794f186bf41b03b1dfab8a213a76c8321647cb2874ff580fd64b353cf8bd18123525799f5cd4f77f703cfcfe0f1c93a4aee977ef8fe3e56abeb3248cb93b85ccaaf3e89871ccc2ef86e17e794f18ddfbae84f6efd8637f6f888e1db28cc5d132cadc675dfe7d8038f7bd2d071e899a92c9b33cf6bbf7eff73a39e1fdfb382649ddd3b8dce1e1e515b01d94599995599995599995599995599995599995599995599995599995599995d96f663cc780dfc3c5fdf29e30b20fbfbf50e2fb5de39e07591ea263872c6371b48c8647e2fb15b15eaae1c1ef0994a829993cf7fcee6d5ef77227a71cf4c1fdfb48813c3341f4f751e3f7742bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3dfcc788e8159b3d02fef0923fb56008fc07c7cec73357d213a76c8321647cb6878047ec3b207eba51a1efc4d23899a12cab374aec6fdad24f7b73a73d007f76f81dfcd0afd9d245e5e05db419995398c19c72c66cd42bfbc278ceeef010beddfb1c7fe95213a76c8321647cb68785627ce533e4fbf3206cf6ae091a829993ccb63bffbbb7d2b9d9c72d007f76fa9dfed5bedf0f072a5df1aacc4dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f6ad021e81f9f8d8e76a5687e8d821cb581c2da3e1393a719eee02d64b353c47038f444dc9e4593e57d3efe4b4dac929077d704cea17c833037179dd987760b743ff1867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a3987da80dbc468759b3d02fef0923fb8e029ea30578e25e477474888e1db28cc5d1321a9e35c9f3f460bd54c3b30678fa93e7e912cab374add3314e4e473b39e5a00f8e49c708e49981b8bc6e5e3e06b683322b7318338e59cc9a857e794f18715f661e81fd3bf6d8bf2644c70e59c6e268190dcfb1423c6b62f01c0b3c123525946769ec3fcec9698d93530efae0fe7d9c409e1988cbebe6e5e3603ba48d19f72566cd42bfbc278cec3b067804ea2ef698746c888e1d2960ec4c01e38414304e4c01631618f3028c191b0f7978392fab4f31097d9a41275fc7bd1cf0486dc31687a725440b5fb7e124fbecf3366c011ea96dd8eaf0b48668e1eb369c6c9f7dde86adb23a2632deb7a580714a0a18a7a680715a0a18a7a78071460a1867a68071560a1867a780714e0a18e7a680715e0a18e7a78071410a18714e637f7ecedd9f9fcff6e7e70ad55c352fecdb433557cd5573d55c350f54f3c2be3d54738f354fc36708651cdb8cdd9e32224f26399e02e68eb124ceabc4cd3dec7a94343076a78051fa3a23d53111c6e268190dcf09423cc7c5e03901788e4f9ea74b28cfd2b5866b9d9c8e7372ca411fac83b5027966202eaf9b97d7c276481b33ee4bcc9a857e794f18d9773cf048ed5f71f677dcbf4e14e2392106cf89c023b1bd84f22cedef2739399de0e494833eb8ef9c24906706e2f2ba79f924d80e6963c6fd9d59b3d02fef0923fbd6028fd4fe15677fc7fdeb64219e1363f09c0c3c12db4b28cfd2fe7e8a93d3894e4e39e883fbce29027966202eaf9b974f81ed903666dcdf99350bfdf29e30b2ef24e011a8bbd89f394e0ed1b133058cdd2960541d55479f1855c70347476554466554c6fdc19886315cdf67e2cf0de067f15393e7e9c1cf6dd5f09c0a3ca724cfd3259467696ee03427a7939d9c72d007ebe034813c331097d7cdcba7c1765066650e63c6318b59b3d02fef0923fb4e011e81fd3bf6d87f6a888e1db28cc5d1321a9ed313e7e92960bd54c3733af048d4944c9ee5b1ff0c27a7539d9c72d007f7ef3304f2cc405c5e372f9f01db210e73770a995567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da39855e7d131e335eecc9a857e794f18d9771af09c2ec013f73afcd34374ec90652c8e96d1f09c993c4f0fd64b353c67028f444d09e559ba57e02c27a7d39d9c72d007c7a4b304f2cc405c5e372f9f05db210e73770a9955e7d131e398c5ac59e897f784917d67008fc0fe1d7bec3f3344c70e59c6e268190dcfd9c9f3f460bd54c37336f048d494509ea5b1ff1c27a7339d9c72d007c7a47304f2cc405c5e372f9f03db419995398c19c72c66cd42bfbc278cec3b0b7804f6efd863ffd9213a76c8321647cb6878ce4d9ca758c07aa986e75ce091a829993ccb63ff3a27a7b39d9c72d007f7ef75027966202eaf9b97d7c17688c3dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f69d033c02f3f1b1cfd59c1ba263872c6371b48c86e7bcc479ba0b582fd5f09c073c1235259367f95ccdf94e4ee73a39e5a00f8e49e70be49981b8bc6e5e3e1fb6c35867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b63af8732dbb6d6863247316b6d287314b3d6863247316b6d287314b3d6863247316b6d287314b30fb581d7e8306b16fae53d6164df3ae0394f8027ee7544e785e8d821cb581c2da3e1b920799e1eac976a782e001e899a12cab374add3854e4ee73939e5a00f8e49170ae49981b8bc6e5ebe10b683322b7318338e59cc9a857e794f18d9773ef008ecdfb1c7fe0b4274ec90652c8e96d1f05c943c4f0fd64b353c17018f444d09e5591afb2f7672bac0c929077d70ffbe5820cf0cc4e575f3f2c5b01d945999c39871cc62d62cf4cb7bc2c8be0b814760ff8e3df65f14a263872c6371b48c86677df23c3d582fd5f0ac071e899a12cab334f65fe2e4749193530efae0fe7d89409e1988cbebe6e54b603b28b3328731e398c5ac59e897f784917d17038fc0fe1d7bec5f1fa263470a183b53c0d89d0246611d8ba365343c970af1ac8fc17329f0488c1f427996dee72f73725aefe494833e58079709e49981b8bc6e5ebe0cb643da98715f62d62cf4cb7bc2c8be4b80476aff8ab3bfe3feb54188e7d2183c1b8047627b09e559dadf373a395deae494833eb8ef6c14c833037179ddbcccf1d2c88cfb3bb366a15fde1346f65d063c1b92e789bdbf6f009e4d423c1b62f06c021e89ed259467697f1f7072dae0e494833eb8ef0c08e49981b8bc6e5e1e80ed903666dcdf37d8e72cf4cb7bc2c8be8dc0235077b13f736c0ad1b133058cdd2960541d55479f1855c7034747655446658cc7786c0a18755b2ba3af8c7901c68c8d873cbc9caf41ec1627764b0d63b73ab15b6b185b3557cd5573d55c3557cdf735b66aae9aabe6aab96aae9aef6b6cd55c3557cd5573d55c35dfd7d8aab96aae9aabe6aab96abeafb15573d5dc8d2d70bd69ecebb9078067938016427916cc7a07edba5e4f503fa3d590a355ded12a077d0641bf2101fdc2aeb9e6658e1797f9700f9885621727d13a2640fe1ca3dbd1c3c4df2c947bd498b7b906b1a3c6bc5ac48e1af36a115b3557cd55f30357736cd7077bdf8364d6b1c5b61bec32f73f165ec77d9e6c2e3fb704ba3d2562eb3ea49aabe6aaf9fed01c75a9ab014fe0f00415786af1f9240ecf7ccf78667ac6d3e619cf04cf78c679c6b3d0339e059ef1ccf28c678a673c133de3a9f78c67b6673c533de3c97ac6d3e019cf22cf78e678c6b3d8339e699ef1347bc693f38ca7d1339eb99ef14cf78c6792673c2d9ef12cf18c67bc673cf33ce399e119cf64cf785a3de369f28c27e3014f36d8fb9a9a2cfc7f007c75ce6bcdf8b0be6de4ff975b7f1dbc66ab6d8f0b59f7e5e0e373555b435e8b3a5d0eb9f4db7661df1e259d30563f2c73bc66e0d8ea094f93673cad9ef14cf68c6786673cf33ce319ef19cf12cf785a3ce399e419cf74cf78e67ac6d3e8194fce339e66cf78a679c6b3d8339e399ef12cf28ca7c1339eac673c533de399ed194fbd673c133de399e219cf2ccf781678c6b3d0339e719ef14cf08ca7cd339e999ef1ccf78c67c0339eba101ea9dfc5e0f94b5e372f0f78125b603b94ee5bbc4228a72bedba1aed7a999fe3d5439f9fd80f92667e045fcb5cee7c33d6ea95a0d116a15ca2ae5dde5283d851d72ed72276d4b5cbb588ad9aabe6aab96a6e1e09de53dfebf3fd167a7f43651ebdbfa1328fdedf509947ef6fa8cca3f73754e6d1fb1b2af3e8fd0d9579f4fe86ca3c7a7f43651ebdbfa1328fdedf5099c7b7fbb9f57e8bca3c7abf45651ebddfa2328fde6f519947efb7a8ccd3e4194fc6039effef7e0bbc4f82cf4d6d011f9fffaa745f4616d67325f8f8f327afc38c375bdaf666a883d76c0be1ba22241ec7d916f2da5ae88eb1fa6199e3e1fd1bdb3ce169f28ca7d5339ec99ef1ccf08c679e673ce33de359e2194f8b673c933ce399ee19cf5ccf78063ce369f48c27e7194fb3673cd33ce359ec19cf1ccf781679c6d3e0194fd6339ea99ef1ccf68ca7de339e899ef14cf18c6796673c0b3ce359e819cf38cf782678c6d3e619cf4ccf78e67bc65317c223750f45d475b4b5b87f23ea3ada5ac48eba8eb616b15573d55c353fb035df9e7cec9e6cb0e76fb69947a57373db8147e2fd4e28cfd2fd773becba12bcc7a160b4dae96835e06895833e3b40bf9d02fa65202eaf9b97399e322b7314b3897d55f2b14be30cc6667d0287871f57096b219467693cb83a08d798e3e5a00f6ef3ab05f2cc405c5e372f5f1d12bb3d48568b5d5568b12b8467578db5e078719977a490d9079d4decddc9c72e8d33189bf5091c1e7eec16d64228cfd2be351c846bccf172d007eb745820cf0cc4e575f3f2306c8738cc57a79059751e1db3897d4de2b18b85ac139bf5091c1e7e5c23ac854c9ee5f1e0da205c638e97833e58a7d70ae49981b8bc6e5ebe16b683322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb332fbcd6c625f9778ecf2fc3dc6667d0287871fd7096b21936779fefefa205c638e97833eb8cdaf17c833037179ddbc7c3d6c0765566665566665566665566665566665566665566665566665566665566665f69bd9c4be21f9d8a5fb713036eb13383cfcb841580ba13c4bf3f73706e11a73bc1cf4c16d7ea3409e1988cbebe6e51b613b28b33287319bd837251ebb7c3e0f63b33e81c3c38f9b84b590c9b33c1edc1c846bccf172d007b7f9cd027966202eaf9b976f86ed1087f9ea1432abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3ea7ce0e86c62df9278eceed2fc3dc6667d0287871fb7086b21936779fefed6205c638e97833e58a7b70ae49981b8bc6e5ebe15b6c35867be3a85cc5a1bb561d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62f6a1364cecdb928f5dba9f1d63b33e81c3c38fdb84b510cab374fdcbed41b8c61c2f077db04e6f17c833037179ddbc7c3b6c87db95599943984dec3b928f5dcc3ab1599fc0e1e1c71dc25a08e5591a0fee0cc235e67839e883dbfc4e813c331097d7cdcb77c276481b336ebf4c72b14bd76d728c3afb6c7c6fb0ed71e07ba36dd783ef4db6dd00be37db7623f8de62dbe3c17717e4c6beb7daf612f0bdcdb607c077b76d6f06dfdb6d7b3bf8eeb1ed9de07b876d5f05be77daf62ef0dd6bdbbbc1f72edb1e06dfbb6dfb1af0bdc7b6af05df7b6dfb3af0bdcfb6af07df7db67d03f8eeb7ed1bc1f77edbbe097c1fb0ed9bc1f741dbbe057c0fd8f6ade07bd0b66f03df876cfb76f03d64db3bc0f761db9e00be876d7b22f83e026d7efea86d3783ef63b69d03dfc76d7b12f83e61db2de0fba46d4f06dfa76cbb157c8fd8761bf83e6ddb53c0f719db9e0abecfdaf634f03d6adbd3c1f7986dcf00dfe76c7b26f83e6fdbb3c0f705db9e0dbe2fdaf61cf07dc9b6e782efcbb63d0f7c8fdbf67cf03d61db0bc0f715dbc6edfb55dbbe037c3c5edc093e1e2fde003e1e2fde083e1e2fde043e1e2fde0c3e1e2fde023e1e2fee021fd7dd5bc1c775f736f071dddd0d3eaebbb7838febee1ef071ddbd037c5c77ef041fd7ddbde0e3ba7b17f8b8eede0d3eaebbf7808febeebde0e3ba7b1ff8b8eeee031fd7ddfde0e3ba7b3ff8b8ee3e003eaebb0f828febee01f071dd3d083eaebb0f818febee21f071dd7d187cedb6fd30f80eb26d1c3f0eb6ed8f82ef10dbfe18f80eb5ed8f83ef30dbfe04f80eb7ed4f826fa16d7f0a7c8b6cfb11f02db6ed4f838fdfc33e03bebc6d7f167c4b6dfb51f01d61db8f81afc3b63f07be4edbfe3cf80ab6fd05f075d9f617c157b4ed2f81afdbb6bf0cbe1edb7e1c7cbdb6fd04f896d9f657c0b7dcb679fc30fba9d9df3867d6a319f23b22849b7d4dc0dd1f247b0cc6b178ddbc5c0446d6bbabf68cc56a190b0ea3e1e911d00c6b881f953ee3f4004fb7008f509ea5cf38bd4e4e4527a71cf4391cf2ec15c833037179ddbcdc0bb125b6396ad168d7bbd0d1a21efa14ec9b9c799faca423afc3d46f57482e523a161c9e4248ec15c23af2ba794c5c5183d8cb9cd89d4e6c1cf7f95169df5e06cccb0598cd7afb925f6f69df3ed2ae8beb99e374424e2b4183a472c2d8196b1c87fdf5d05edb36d297fbb11efcdec9ec663fe26d89eceeeb7a9cd7e5a0cf8a90fcfb8364f3ef7378fa1c66f3396155db0887c0fe50aa81150e072f7782767d11daad00edb84f17f8f818a7177c7cacc0ebc0632a3c8ee84a3edfd0b1a737849b7dcb8031ec58a7903c63c5639d0230b26f39f02c13d26cb9c3b3d0d107df97c73b7df8b5f5d0e722786fcc86f435b53f373392177fb67e3d48765c6d14d00b3ff707a04fe068c80f66680a46e60692e499188c7cf6df3dbc73d7c62d43670f6d1ccc005abd8389cf999034eac087ed7121be20d8738a03a750798a03a750eb1c59706a85fb9b8f52262d9e4618dabe7578dd8ea11d03bb6eb86a7868f0d49d5b90bac1a147d2a80c90147dfc680a462663fa83644f9e343ab12a154f133c8f4f9ea74b28cfd21bcf0427a74627a71cf46980ff4d10c833037179ddbc3c212476820351498b89556831318467628db5c0096df6e19ecaffc7931d754e2eb847634e6e9d279a10073c0cd69fb170e67f66676fb0c98c0f4636368f9ee6a8d26c0433136adeb5cc4ca799d934439099b93433956666d2cc449a994733d3686616cd4ca299393433856666d0cc049a99bff6a03cb36766f2ccccdda1c0f514b09a4fd4e6ddd1ccbc99993633b3668eaacc27267324628e7ecd919a3932304704e653a5996130efb4e628c6bc439b775573a4668ed0cc11b539c25c45b69aec28b2a3add66bc88e213b96ec38b2e3c94e205b4b7622d9496427939d42762ad96964a7939d417626d9596467939d43762ed93ab2f3c8ce27bb80ec42b28bc82e265b4f7609d9a56497916d20db48b629289fed1a241b0aca67b9b6905d4eb695ec0ab22bc9b605e5b35d66a6de9ce13267b4cc5536e62c96396b65ce5299b352e62c9439eb64ce3299b34ae62c92396b64ce1299b342e62c9039eb737b509ee13733fa6606dfccd89b197a33236f66e0ef0aca33ec6646fdeea03c636e66c8cd8cb899013733de6686dbcc689b196c33636d66a8cd8cb499813633ce6686d9cc289b196433636c6688cd8cb099017e3828cff09a195d33836b666ccd0cad99913533b08f04e5195633a36a6650cd8ca999213533a26606d4cc789a194e33a3696630cd8ca599a13433926606f26b645f27fb06d937c9be45f66db2ef907d97ec7b64df27fb01d90fc97e44f624d98fc97e12946bf269b29f92fd8cece764bf20fb25d9afc87e4df61bb2df92fd8eecf7647f20fb23d933647f22fb33d9b3647f21fb2bd9dfc8fe4ef60fb2e7c89e27fb27d9bfc85e207b91ec25b27f93bd4cf61fb257c85e257b2d1839538103c70b768167cd370e0f0f6dbf6ab87d7867fbf66bb60d6fbd6adb0dedd76d1dbebc7de7b543bb366fdb791dbef83e3b54f1298135bb766dbca17deb8ec1a1ebdb775e33dcbe7373fba69dd7ec18dc8d2f7adcbe68eede11370e0e4607fb51dd3e903e35caa0cfd8d7f1c996932ae7f6ec6804796e342faa1f374a15ed29ac5576f99cf2116efbee6d3b87db0bed3be8efc66df49aa1c18e76fcdf6e1279f770fbeee18dbb86db37efdab9bdbdab03d7fbe3dc2892e86c1bc58b2e6cab3ef3e0ff009826ecb5716c0300", "privateFunctions": [ { "selector": { @@ -44,8 +44,8 @@ exports[`ContractClass creates a contract class from a contract compilation arti "isInternal": false } ], - "id": "0x14984eeda340fba89c656a26cd74e08be8fc849619982aa26f94077a897f56a6", + "id": "0x055aa066c8ac7456e491c363b2817c2e06d092d4c08d1f4d3c55f252980d0720", "privateFunctionsRoot": "0x05fa82a96814b6294d557d507151f7ccc12f70522ec4d9d0395a90e87e8087c6", - "publicBytecodeCommitment": "0x0bf394d6e8a18153fea19434af4dba812bd80872d27c85d18c5e4ec7046172f6" + "publicBytecodeCommitment": "0x1ad0c37ac70788b5d3cdad82d06469037ee016b5fe662ce3d39bdca4e4ff9cbc" }" `; diff --git a/yarn-project/circuits.js/src/hints/build_hints.test.ts b/yarn-project/circuits.js/src/hints/build_hints.test.ts index 6cf3572bbf7..9db861afe55 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.test.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.test.ts @@ -1,34 +1,35 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/fields'; +import { Tuple } from '@aztec/foundation/serialize'; + +import { MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX } from '../constants.gen.js'; +import { siloNullifier } from '../hash/index.js'; import { - AztecAddress, - Fr, - MAX_NEW_NULLIFIERS_PER_TX, - MAX_NULLIFIER_READ_REQUESTS_PER_TX, - NullifierReadRequestResetHints, - NullifierReadRequestResetHintsBuilder, + NullifierNonExistentReadRequestHintsBuilder, + NullifierReadRequestHints, + NullifierReadRequestHintsBuilder, PendingReadHint, ReadRequestContext, ReadRequestState, ReadRequestStatus, SettledReadHint, SideEffectLinkedToNoteHash, -} from '@aztec/circuits.js'; -import { siloNullifier } from '@aztec/circuits.js/hash'; -import { makeTuple } from '@aztec/foundation/array'; -import { Tuple } from '@aztec/foundation/serialize'; - -import { HintsBuildingDataOracle, buildNullifierReadRequestResetHints } from './build_hints.js'; +} from '../structs/index.js'; +import { buildNullifierNonExistentReadRequestHints, buildNullifierReadRequestHints } from './build_hints.js'; -describe('buildNullifierReadRequestResetHints', () => { +describe('buildNullifierReadRequestHints', () => { const contractAddress = AztecAddress.random(); const settledNullifierInnerValue = 99999; const settledNullifierValue = makeNullifier(settledNullifierInnerValue).value; - const oracle: HintsBuildingDataOracle = { - getNullifierMembershipWitness: value => + const oracle = { + getNullifierMembershipWitness: (value: Fr) => value.equals(settledNullifierValue) ? ({ membershipWitness: {}, leafPreimage: {} } as any) : undefined, }; let nullifierReadRequests: Tuple; let nullifiers: Tuple; - let expectedHints: NullifierReadRequestResetHints; + let expectedHints: NullifierReadRequestHints; let numReadRequests = 0; let numPendingReads = 0; let numSettledReads = 0; @@ -73,12 +74,12 @@ describe('buildNullifierReadRequestResetHints', () => { numSettledReads++; }; - const buildHints = () => buildNullifierReadRequestResetHints(oracle, nullifierReadRequests, nullifiers); + const buildHints = () => buildNullifierReadRequestHints(oracle, nullifierReadRequests, nullifiers); beforeEach(() => { nullifierReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty); nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => makeNullifier(innerNullifier(i))); - expectedHints = NullifierReadRequestResetHintsBuilder.empty(); + expectedHints = NullifierReadRequestHintsBuilder.empty(); numReadRequests = 0; numPendingReads = 0; numSettledReads = 0; @@ -118,3 +119,125 @@ describe('buildNullifierReadRequestResetHints', () => { await expect(buildHints()).rejects.toThrow('Read request is reading an unknown nullifier value.'); }); }); + +describe('buildNullifierNonExistentReadRequestHints', () => { + const contractAddress = AztecAddress.random(); + const oracle = { + getLowNullifierMembershipWitness: () => ({ membershipWitness: {}, leafPreimage: {} } as any), + }; + const nonExistentReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty); + let nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty); + + const innerNullifier = (index: number) => index + 1; + + const makeReadRequest = (value: number, counter = 2) => + new ReadRequestContext(new Fr(value), counter, contractAddress); + + const makeNullifier = (value: number, counter = 1) => { + const siloedValue = siloNullifier(contractAddress, new Fr(value)); + return new SideEffectLinkedToNoteHash(siloedValue, new Fr(0), new Fr(counter)); + }; + + interface TestNullifier { + value: number; + siloedValue: Fr; + } + + const populateNullifiers = (numNullifiers = MAX_NEW_NULLIFIERS_PER_TX) => { + nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => + i < numNullifiers ? makeNullifier(innerNullifier(i)) : SideEffectLinkedToNoteHash.empty(), + ); + }; + + const generateSortedNullifiers = (numNullifiers: number) => { + const nullifiers: TestNullifier[] = []; + for (let i = 0; i < numNullifiers; ++i) { + const value = i; + nullifiers.push({ + value, + siloedValue: siloNullifier(contractAddress, new Fr(value)), + }); + } + return nullifiers.sort((a, b) => (b.siloedValue.lt(a.siloedValue) ? 1 : -1)); + }; + + const buildHints = () => buildNullifierNonExistentReadRequestHints(oracle, nonExistentReadRequests, nullifiers); + + it('builds empty hints', async () => { + const hints = await buildHints(); + const emptyHints = NullifierNonExistentReadRequestHintsBuilder.empty(); + expect(hints).toEqual(emptyHints); + }); + + it('builds hints for full sorted nullifiers', async () => { + populateNullifiers(); + + const hints = await buildHints(); + const { sortedPendingValues, sortedPendingValueHints } = hints; + for (let i = 0; i < sortedPendingValues.length - 1; ++i) { + expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true); + } + for (let i = 0; i < nullifiers.length; ++i) { + const index = sortedPendingValueHints[i]; + expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true); + } + }); + + it('builds hints for half-full sorted nullifiers', async () => { + const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2; + populateNullifiers(numNonEmptyNullifiers); + + const hints = await buildHints(); + const { sortedPendingValues, sortedPendingValueHints } = hints; + + // The first half contains sorted values. + for (let i = 0; i < numNonEmptyNullifiers - 1; ++i) { + expect(sortedPendingValues[i]).not.toEqual(SideEffectLinkedToNoteHash.empty()); + expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true); + } + for (let i = 0; i < numNonEmptyNullifiers; ++i) { + const index = sortedPendingValueHints[i]; + expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true); + } + + // The second half is empty. + for (let i = numNonEmptyNullifiers; i < sortedPendingValues.length; ++i) { + expect(sortedPendingValues[i]).toEqual(SideEffectLinkedToNoteHash.empty()); + } + for (let i = numNonEmptyNullifiers; i < sortedPendingValueHints.length; ++i) { + expect(sortedPendingValueHints[i]).toBe(0); + } + }); + + it('builds hints for read requests', async () => { + const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2; + expect(numNonEmptyNullifiers > 1).toBe(true); // Need at least 2 nullifiers to test a value in the middle. + + const sortedNullifiers = generateSortedNullifiers(numNonEmptyNullifiers + 3); + const minNullifier = sortedNullifiers.splice(0, 1)[0]; + const maxNullifier = sortedNullifiers.pop()!; + const midIndex = Math.floor(numNonEmptyNullifiers / 2); + const midNullifier = sortedNullifiers.splice(midIndex, 1)[0]; + + nonExistentReadRequests[0] = makeReadRequest(midNullifier.value); + nonExistentReadRequests[1] = makeReadRequest(maxNullifier.value); + nonExistentReadRequests[2] = makeReadRequest(minNullifier.value); + nullifiers = padArrayEnd( + sortedNullifiers.map(n => makeNullifier(n.value)), + SideEffectLinkedToNoteHash.empty(), + MAX_NEW_NULLIFIERS_PER_TX, + ); + + const hints = await buildHints(); + const { nextPendingValueIndices } = hints; + expect(nextPendingValueIndices.slice(0, 3)).toEqual([midIndex, numNonEmptyNullifiers, 0]); + }); + + it('throws if reading existing value', async () => { + populateNullifiers(); + + nonExistentReadRequests[0] = makeReadRequest(innerNullifier(2)); + + await expect(() => buildHints()).rejects.toThrow('Nullifier exists in the pending set.'); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/build_hints.ts b/yarn-project/circuits.js/src/hints/build_hints.ts index d80f4bd5c37..c22df8b15fe 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.ts @@ -1,34 +1,35 @@ +import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { Tuple } from '@aztec/foundation/serialize'; +import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, } from '../constants.gen.js'; import { siloNullifier } from '../hash/index.js'; import { MembershipWitness } from '../structs/membership_witness.js'; +import { NullifierNonExistentReadRequestHintsBuilder } from '../structs/non_existent_read_request_hints.js'; import { ReadRequestContext } from '../structs/read_request.js'; -import { NullifierReadRequestResetHintsBuilder } from '../structs/read_request_reset_hints.js'; -import { NullifierLeafPreimage } from '../structs/rollup/nullifier_leaf/index.js'; +import { NullifierReadRequestHintsBuilder } from '../structs/read_request_hints.js'; import { SideEffectLinkedToNoteHash } from '../structs/side_effects.js'; import { countAccumulatedItems } from './utils.js'; export interface NullifierMembershipWitnessWithPreimage { membershipWitness: MembershipWitness; - leafPreimage: NullifierLeafPreimage; + leafPreimage: IndexedTreeLeafPreimage; } -export interface HintsBuildingDataOracle { - getNullifierMembershipWitness(nullifier: Fr): Promise; -} - -export async function buildNullifierReadRequestResetHints( - oracle: HintsBuildingDataOracle, +export async function buildNullifierReadRequestHints( + oracle: { + getNullifierMembershipWitness(nullifier: Fr): Promise; + }, nullifierReadRequests: Tuple, nullifiers: Tuple, ) { - const builder = new NullifierReadRequestResetHintsBuilder(); + const builder = new NullifierReadRequestHintsBuilder(); const numReadRequests = countAccumulatedItems(nullifierReadRequests); @@ -58,3 +59,64 @@ export async function buildNullifierReadRequestResetHints( } return builder.toHints(); } + +interface SortedResult { + sortedValues: Tuple; + sortedIndexHints: Tuple; +} + +function sortNullifiersByValues( + nullifiers: Tuple, +): SortedResult { + const numNullifiers = countAccumulatedItems(nullifiers); + const sorted = nullifiers + .slice(0, numNullifiers) + .map((nullifier, originalIndex) => ({ nullifier, originalIndex })) + .sort((a, b) => (b.nullifier.value.lt(a.nullifier.value) ? 1 : -1)); + + const sortedIndexHints: number[] = []; + for (let i = 0; i < numNullifiers; ++i) { + sortedIndexHints[sorted[i].originalIndex] = i; + } + + return { + sortedValues: padArrayEnd( + sorted.map(s => s.nullifier), + SideEffectLinkedToNoteHash.empty(), + MAX_NEW_NULLIFIERS_PER_TX, + ), + sortedIndexHints: padArrayEnd(sortedIndexHints, 0, MAX_NEW_NULLIFIERS_PER_TX), + }; +} + +export async function buildNullifierNonExistentReadRequestHints( + oracle: { + getLowNullifierMembershipWitness(nullifier: Fr): Promise; + }, + nullifierNonExistentReadRequests: Tuple, + pendingNullifiers: Tuple, +) { + const { sortedValues, sortedIndexHints } = sortNullifiersByValues(pendingNullifiers); + + const builder = new NullifierNonExistentReadRequestHintsBuilder(sortedValues, sortedIndexHints); + + const numPendingNullifiers = countAccumulatedItems(pendingNullifiers); + const numReadRequests = countAccumulatedItems(nullifierNonExistentReadRequests); + for (let i = 0; i < numReadRequests; ++i) { + const readRequest = nullifierNonExistentReadRequests[i]; + const siloedValue = siloNullifier(readRequest.contractAddress, readRequest.value); + + const { membershipWitness, leafPreimage } = await oracle.getLowNullifierMembershipWitness(siloedValue); + + let nextPendingValueIndex = sortedValues.findIndex(v => !v.value.lt(siloedValue)); + if (nextPendingValueIndex == -1) { + nextPendingValueIndex = numPendingNullifiers; + } else if (sortedValues[nextPendingValueIndex].value.equals(siloedValue)) { + throw new Error('Nullifier exists in the pending set.'); + } + + builder.addHint(membershipWitness, leafPreimage, nextPendingValueIndex); + } + + return builder.toHints(); +} diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap index 4f72eacc283..f48aceba2c8 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9"`; +exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d"`; -exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307"`; +exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83"`; -exports[`PublicCallStackItem computes hash 1`] = `Fr<0x198bebc3ae39ac7041b6f6cf91cf2055e577494f8f2145d81601b192f71e762a>`; +exports[`PublicCallStackItem computes hash 1`] = `Fr<0x0ef0cbf32ad96d5f6c7577b023a3b4f9a9cd5d53a8c9eb268324183aaa1437ff>`; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap index f42e5c120b2..201d5c667eb 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9>`; +exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9>`; -exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x2ae2a860d511acb274dca33de7a64693fe2948275ed149e2db832dd6ce21fc36>`; +exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x1135d901dacdffe956b9cd85c976a2c5fe311018164a3ec612ff8ed89f8d56cb>`; diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index f18f723e1e0..2a1b8079c16 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -39,7 +39,8 @@ export * from './public_call_stack_item.js'; export * from './public_circuit_public_inputs.js'; export * from './read_request.js'; export * from './note_hash_read_request_membership_witness.js'; -export * from './read_request_reset_hints.js'; +export * from './read_request_hints.js'; +export * from './non_existent_read_request_hints.js'; export * from './rollup/append_only_tree_snapshot.js'; export * from './rollup/base_or_merge_rollup_public_inputs.js'; export * from './rollup/base_rollup.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index 1e05e125508..cc281831036 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -18,6 +18,7 @@ import { MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, @@ -743,6 +744,13 @@ export class PublicAccumulatedNonRevertibleData { * The nullifier read requests made in this transaction. */ public nullifierReadRequests: Tuple, + /** + * The nullifier read requests made in this transaction. + */ + public nullifierNonExistentReadRequests: Tuple< + ReadRequestContext, + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX + >, /** * The new non-revertible commitments made in this transaction. */ @@ -771,6 +779,7 @@ export class PublicAccumulatedNonRevertibleData { toBuffer() { return serializeToBuffer( this.nullifierReadRequests, + this.nullifierNonExistentReadRequests, this.newNoteHashes, this.newNullifiers, this.publicCallStack, @@ -783,6 +792,7 @@ export class PublicAccumulatedNonRevertibleData { const reader = BufferReader.asReader(buffer); return new this( reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext), reader.readArray(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect), reader.readArray(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash), reader.readArray(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest), @@ -802,6 +812,7 @@ export class PublicAccumulatedNonRevertibleData { static empty() { return new this( makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty), makeTuple(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect.empty), makeTuple(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty), makeTuple(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest.empty), @@ -813,6 +824,7 @@ export class PublicAccumulatedNonRevertibleData { static fromPrivateAccumulatedNonRevertibleData(data: PrivateAccumulatedNonRevertibleData) { return new this( makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty), data.newNoteHashes, data.newNullifiers, data.publicCallStack, diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts index da7f5cd14e1..2f90462d3bd 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts @@ -8,10 +8,7 @@ import { } from '../../constants.gen.js'; import { GrumpkinPrivateKey } from '../../index.js'; import { Fr, GrumpkinScalar } from '../index.js'; -import { - NullifierReadRequestResetHints, - nullifierReadRequestResetHintsFromBuffer, -} from '../read_request_reset_hints.js'; +import { NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints.js'; import { SideEffect, SideEffectLinkedToNoteHash } from '../side_effects.js'; import { PrivateKernelInnerData } from './private_kernel_inner_data.js'; @@ -47,7 +44,7 @@ export class PrivateKernelTailCircuitPrivateInputs { /** * Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers. */ - public nullifierReadRequestResetHints: NullifierReadRequestResetHints, + public nullifierReadRequestHints: NullifierReadRequestHints, /** * Contains hints for the transient nullifiers to localize corresponding commitments. */ @@ -70,7 +67,7 @@ export class PrivateKernelTailCircuitPrivateInputs { this.readCommitmentHints, this.sortedNewNullifiers, this.sortedNewNullifiersIndexes, - this.nullifierReadRequestResetHints, + this.nullifierReadRequestHints, this.nullifierCommitmentHints, this.masterNullifierSecretKeys, ); @@ -90,7 +87,7 @@ export class PrivateKernelTailCircuitPrivateInputs { reader.readArray(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, Fr), reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash), reader.readNumbers(MAX_NEW_NULLIFIERS_PER_TX), - reader.readObject({ fromBuffer: nullifierReadRequestResetHintsFromBuffer }), + reader.readObject({ fromBuffer: nullifierReadRequestHintsFromBuffer }), reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, Fr), reader.readArray(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, GrumpkinScalar), ); diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts index 37d79805d11..77f3bc4cf11 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts @@ -1,6 +1,7 @@ import { serializeToBuffer } from '@aztec/foundation/serialize'; -import { NullifierReadRequestResetHints } from '../read_request_reset_hints.js'; +import { NullifierNonExistentReadRequestHints } from '../non_existent_read_request_hints.js'; +import { NullifierReadRequestHints } from '../read_request_hints.js'; import { PublicKernelData } from './public_kernel_data.js'; /** @@ -15,10 +16,18 @@ export class PublicKernelTailCircuitPrivateInputs { /** * Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers. */ - public nullifierReadRequestResetHints: NullifierReadRequestResetHints, + public readonly nullifierReadRequestHints: NullifierReadRequestHints, + /** + * Contains hints for the nullifier non existent read requests. + */ + public readonly nullifierNonExistentReadRequestHints: NullifierNonExistentReadRequestHints, ) {} toBuffer() { - return serializeToBuffer(this.previousKernel, this.nullifierReadRequestResetHints); + return serializeToBuffer( + this.previousKernel, + this.nullifierReadRequestHints, + this.nullifierNonExistentReadRequestHints, + ); } } diff --git a/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts new file mode 100644 index 00000000000..9cd254bc0b9 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts @@ -0,0 +1,152 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; + +import { + MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, +} from '../constants.gen.js'; +import { MembershipWitness } from './membership_witness.js'; +import { NullifierLeafPreimage } from './rollup/nullifier_leaf/index.js'; +import { SideEffectLinkedToNoteHash, SideEffectType } from './side_effects.js'; + +export class NonMembershipHint { + constructor(public membershipWitness: MembershipWitness, public leafPreimage: LEAF_PREIMAGE) {} + + static empty( + treeHeight: TREE_HEIGHT, + makeEmptyLeafPreimage: () => LEAF_PREIMAGE, + ) { + return new NonMembershipHint(MembershipWitness.empty(treeHeight, 0n), makeEmptyLeafPreimage()); + } + + static fromBuffer( + buffer: Buffer | BufferReader, + treeHeight: TREE_HEIGHT, + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + ): NonMembershipHint { + const reader = BufferReader.asReader(buffer); + return new NonMembershipHint( + MembershipWitness.fromBuffer(reader, treeHeight), + reader.readObject(leafPreimageFromBuffer), + ); + } + + toBuffer() { + return serializeToBuffer(this.membershipWitness, this.leafPreimage); + } +} + +export class NonExistentReadRequestHints< + READ_REQUEST_LEN extends number, + TREE_HEIGHT extends number, + LEAF_PREIMAGE extends IndexedTreeLeafPreimage, + PENDING_VALUE_LEN extends number, + PENDING_VALUE extends SideEffectType, +> { + constructor( + /** + * The hints for the low leaves of the read requests. + */ + public nonMembershipHints: Tuple, READ_REQUEST_LEN>, + /** + * Indices of the smallest pending values greater than the read requests. + */ + public nextPendingValueIndices: Tuple, + public sortedPendingValues: Tuple, + public sortedPendingValueHints: Tuple, + ) {} + + static fromBuffer< + READ_REQUEST_LEN extends number, + TREE_HEIGHT extends number, + LEAF_PREIMAGE extends IndexedTreeLeafPreimage, + PENDING_VALUE_LEN extends number, + PENDING_VALUE extends SideEffectType, + >( + buffer: Buffer | BufferReader, + readRequestLen: READ_REQUEST_LEN, + treeHeight: TREE_HEIGHT, + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + pendingValueLen: PENDING_VALUE_LEN, + orderedValueFromBuffer: { fromBuffer: (buffer: BufferReader) => PENDING_VALUE }, + ): NonExistentReadRequestHints { + const reader = BufferReader.asReader(buffer); + return new NonExistentReadRequestHints( + reader.readArray(readRequestLen, { + fromBuffer: buf => NonMembershipHint.fromBuffer(buf, treeHeight, leafPreimageFromBuffer), + }), + reader.readNumbers(readRequestLen), + reader.readArray(pendingValueLen, orderedValueFromBuffer), + reader.readNumbers(pendingValueLen), + ); + } + + toBuffer() { + return serializeToBuffer(this.nonMembershipHints, this.nextPendingValueIndices); + } +} + +export type NullifierNonExistentReadRequestHints = NonExistentReadRequestHints< + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + typeof NULLIFIER_TREE_HEIGHT, + IndexedTreeLeafPreimage, + typeof MAX_NEW_NULLIFIERS_PER_TX, + SideEffectLinkedToNoteHash +>; + +export function nullifierNonExistentReadRequestHintsFromBuffer( + buffer: Buffer | BufferReader, +): NullifierNonExistentReadRequestHints { + return NonExistentReadRequestHints.fromBuffer( + buffer, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, + NullifierLeafPreimage, + MAX_NEW_NULLIFIERS_PER_TX, + SideEffectLinkedToNoteHash, + ); +} + +export class NullifierNonExistentReadRequestHintsBuilder { + private hints: NullifierNonExistentReadRequestHints; + private readRequestIndex = 0; + + constructor( + sortedPendingNullifiers: Tuple, + sortedPendingNullifierIndexHints: Tuple, + ) { + this.hints = new NonExistentReadRequestHints( + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, () => + NonMembershipHint.empty(NULLIFIER_TREE_HEIGHT, NullifierLeafPreimage.empty), + ), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, () => 0), + sortedPendingNullifiers, + sortedPendingNullifierIndexHints, + ); + } + + static empty() { + const emptySortedPendingNullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty); + const emptySortedPendingNullifierIndexHints = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, () => 0); + return new NullifierNonExistentReadRequestHintsBuilder( + emptySortedPendingNullifiers, + emptySortedPendingNullifierIndexHints, + ).toHints(); + } + + addHint( + membershipWitness: MembershipWitness, + lowLeafPreimage: IndexedTreeLeafPreimage, + nextPendingValueIndex: number, + ) { + this.hints.nonMembershipHints[this.readRequestIndex] = new NonMembershipHint(membershipWitness, lowLeafPreimage); + this.hints.nextPendingValueIndices[this.readRequestIndex] = nextPendingValueIndex; + this.readRequestIndex++; + } + + toHints() { + return this.hints; + } +} diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index f4a23e63ac4..8ee7a9375cb 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -11,6 +11,7 @@ import { MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, @@ -48,6 +49,13 @@ export class PublicCircuitPublicInputs { * Nullifier read requests executed during the call. */ public nullifierReadRequests: Tuple, + /** + * Nullifier non existent read requests executed during the call. + */ + public nullifierNonExistentReadRequests: Tuple< + ReadRequest, + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL + >, /** * Contract storage update requests executed during the call. */ @@ -119,6 +127,7 @@ export class PublicCircuitPublicInputs { Fr.ZERO, makeTuple(RETURN_VALUES_LENGTH, Fr.zero), makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest.empty), makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest.empty), makeTuple(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead.empty), makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr.zero), @@ -143,6 +152,7 @@ export class PublicCircuitPublicInputs { this.argsHash.isZero() && isFrArrayEmpty(this.returnValues) && isArrayEmpty(this.nullifierReadRequests, item => item.isEmpty()) && + isArrayEmpty(this.nullifierNonExistentReadRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageUpdateRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageReads, item => item.isEmpty()) && isFrArrayEmpty(this.publicCallStackHashes) && @@ -168,6 +178,7 @@ export class PublicCircuitPublicInputs { fields.argsHash, fields.returnValues, fields.nullifierReadRequests, + fields.nullifierNonExistentReadRequests, fields.contractStorageUpdateRequests, fields.contractStorageReads, fields.publicCallStackHashes, @@ -212,6 +223,7 @@ export class PublicCircuitPublicInputs { reader.readObject(Fr), reader.readArray(RETURN_VALUES_LENGTH, Fr), reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr), @@ -234,6 +246,7 @@ export class PublicCircuitPublicInputs { reader.readField(), reader.readFieldArray(RETURN_VALUES_LENGTH), reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL), diff --git a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts b/yarn-project/circuits.js/src/structs/read_request_hints.ts similarity index 82% rename from yarn-project/circuits.js/src/structs/read_request_reset_hints.ts rename to yarn-project/circuits.js/src/structs/read_request_hints.ts index ef43419940a..880b71a4467 100644 --- a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts +++ b/yarn-project/circuits.js/src/structs/read_request_hints.ts @@ -1,5 +1,6 @@ import { makeTuple } from '@aztec/foundation/array'; import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { TreeLeafPreimage } from '@aztec/foundation/trees'; import { MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT } from '../constants.gen.js'; import { MembershipWitness } from './membership_witness.js'; @@ -45,35 +46,31 @@ export class PendingReadHint { } } -interface LeafPreimageHint { - toBuffer(): Buffer; -} - -export class SettledReadHint { +export class SettledReadHint { constructor( public readRequestIndex: number, public membershipWitness: MembershipWitness, - public leafPreimage: LEAF_PREIMAGE_HINT, + public leafPreimage: LEAF_PREIMAGE, ) {} - static nada( + static nada( readRequestLen: number, treeHeight: TREE_HEIGHT, - emptyLeafPreimage: () => LEAF_PREIMAGE_HINT, + emptyLeafPreimage: () => LEAF_PREIMAGE, ) { return new SettledReadHint(readRequestLen, MembershipWitness.empty(treeHeight, 0n), emptyLeafPreimage()); } - static fromBuffer( + static fromBuffer( buffer: Buffer | BufferReader, treeHeight: TREE_HEIGHT, - leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE_HINT }, - ): SettledReadHint { + leafPreimage: { fromBuffer(buffer: BufferReader): LEAF_PREIMAGE }, + ): SettledReadHint { const reader = BufferReader.asReader(buffer); return new SettledReadHint( reader.readNumber(), MembershipWitness.fromBuffer(reader, treeHeight), - reader.readObject(leafPreimageFromBuffer), + reader.readObject(leafPreimage), ); } @@ -90,7 +87,7 @@ export class ReadRequestResetHints< NUM_PENDING_READS extends number, NUM_SETTLED_READS extends number, TREE_HEIGHT extends number, - LEAF_PREIMAGE_HINT extends LeafPreimageHint, + LEAF_PREIMAGE extends TreeLeafPreimage, > { constructor( public readRequestStatuses: Tuple, @@ -101,7 +98,7 @@ export class ReadRequestResetHints< /** * The hints for read requests reading settled values. */ - public settledReadHints: Tuple, NUM_SETTLED_READS>, + public settledReadHints: Tuple, NUM_SETTLED_READS>, ) {} /** @@ -114,15 +111,15 @@ export class ReadRequestResetHints< NUM_PENDING_READS extends number, NUM_SETTLED_READS extends number, TREE_HEIGHT extends number, - LEAF_PREIMAGE_HINT extends LeafPreimageHint, + LEAF_PREIMAGE extends TreeLeafPreimage, >( buffer: Buffer | BufferReader, readRequestLen: READ_REQUEST_LEN, numPendingReads: NUM_PENDING_READS, numSettledReads: NUM_SETTLED_READS, treeHeight: TREE_HEIGHT, - leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE_HINT }, - ): ReadRequestResetHints { + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + ): ReadRequestResetHints { const reader = BufferReader.asReader(buffer); return new ReadRequestResetHints( reader.readArray(readRequestLen, ReadRequestStatus), @@ -138,17 +135,15 @@ export class ReadRequestResetHints< } } -export type NullifierReadRequestResetHints = ReadRequestResetHints< +export type NullifierReadRequestHints = ReadRequestResetHints< typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof NULLIFIER_TREE_HEIGHT, - NullifierLeafPreimage + TreeLeafPreimage >; -export function nullifierReadRequestResetHintsFromBuffer( - buffer: Buffer | BufferReader, -): NullifierReadRequestResetHints { +export function nullifierReadRequestHintsFromBuffer(buffer: Buffer | BufferReader): NullifierReadRequestHints { return ReadRequestResetHints.fromBuffer( buffer, MAX_NULLIFIER_READ_REQUESTS_PER_TX, @@ -159,8 +154,8 @@ export function nullifierReadRequestResetHintsFromBuffer( ); } -export class NullifierReadRequestResetHintsBuilder { - private hints: NullifierReadRequestResetHints; +export class NullifierReadRequestHintsBuilder { + private hints: NullifierReadRequestHints; private numPendingReadHints = 0; private numSettledReadHints = 0; @@ -175,7 +170,7 @@ export class NullifierReadRequestResetHintsBuilder { } static empty() { - return new NullifierReadRequestResetHintsBuilder().toHints(); + return new NullifierReadRequestHintsBuilder().toHints(); } addPendingReadRequest(readRequestIndex: number, nullifierIndex: number) { @@ -190,7 +185,7 @@ export class NullifierReadRequestResetHintsBuilder { addSettledReadRequest( readRequestIndex: number, membershipWitness: MembershipWitness, - leafPreimage: NullifierLeafPreimage, + leafPreimage: TreeLeafPreimage, ) { this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( ReadRequestState.SETTLED, diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 8b9e7822f70..4a08435fb34 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -47,6 +47,8 @@ import { MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, @@ -357,6 +359,7 @@ export function makeCombinedAccumulatedNonRevertibleData(seed = 1, full = false) return new PublicAccumulatedNonRevertibleData( tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x91), + tupleGenerator(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x95), tupleGenerator(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, sideEffectFromNumber, seed + 0x101), tupleGenerator(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, sideEffectLinkedFromNumber, seed + 0x201), tupleGenerator(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, makeCallRequest, seed + 0x501), @@ -415,6 +418,7 @@ export function makePublicCircuitPublicInputs( fr(seed + 0x100), tupleGenerator(RETURN_VALUES_LENGTH, fr, seed + 0x200), tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x400), + tupleGenerator(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x420), tupleGenerator(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, makeContractStorageUpdateRequest, seed + 0x400), tupleGenerator(MAX_PUBLIC_DATA_READS_PER_CALL, makeContractStorageRead, seed + 0x500), tupleGenerator(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, fr, seed + 0x600), diff --git a/yarn-project/foundation/src/trees/index.ts b/yarn-project/foundation/src/trees/index.ts index 195877aec3e..60b025a9e13 100644 --- a/yarn-project/foundation/src/trees/index.ts +++ b/yarn-project/foundation/src/trees/index.ts @@ -23,22 +23,13 @@ export interface IndexedTreeLeaf { } /** - * Preimage of an indexed merkle tree leaf. + * Preimage of a merkle tree leaf. */ -export interface IndexedTreeLeafPreimage { +export interface TreeLeafPreimage { /** * Returns key of the leaf corresponding to this preimage. */ getKey(): bigint; - /** - * Returns the key of the next leaf. - */ - getNextKey(): bigint; - /** - * Returns the index of the next leaf. - */ - getNextIndex(): bigint; - /** * Returns the preimage as a leaf. */ @@ -52,3 +43,14 @@ export interface IndexedTreeLeafPreimage { */ toHashInputs(): Buffer[]; } + +/** + * Preimage of an indexed merkle tree leaf. + */ +export interface IndexedTreeLeafPreimage extends TreeLeafPreimage { + getNextKey(): bigint; + /** + * Returns the index of the next leaf. + */ + getNextIndex(): bigint; +} diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index a589a7a071f..fef5e799bb2 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -34,6 +34,7 @@ import { MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, @@ -48,11 +49,13 @@ import { MergeRollupInputs, NULLIFIER_TREE_HEIGHT, NUM_FIELDS_PER_SHA256, + NonMembershipHint, NoteHashReadRequestMembershipWitness, NullifierKeyValidationRequest, NullifierKeyValidationRequestContext, NullifierLeafPreimage, - NullifierReadRequestResetHints, + NullifierNonExistentReadRequestHints, + NullifierReadRequestHints, PUBLIC_DATA_TREE_HEIGHT, PartialStateReference, PendingReadHint, @@ -136,7 +139,7 @@ import { PrivateKernelInnerData as PrivateKernelInnerDataNoir, } from './types/private_kernel_inner_types.js'; import { - NullifierReadRequestResetHints as NullifierReadRequestResetHintsNoir, + NullifierReadRequestHints as NullifierReadRequestHintsNoir, NullifierSettledReadHint as NullifierSettledReadHintNoir, PendingReadHint as PendingReadHintNoir, PrivateAccumulatedNonRevertibleData as PrivateAccumulatedNonRevertibleDataNoir, @@ -159,7 +162,11 @@ import { StorageRead as StorageReadNoir, StorageUpdateRequest as StorageUpdateRequestNoir, } from './types/public_kernel_setup_types.js'; -import { PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir } from './types/public_kernel_tail_types.js'; +import { + NullifierNonExistentReadRequestHints as NullifierNonExistentReadRequestHintsNoir, + NullifierNonMembershipHint as NullifierNonMembershipHintNoir, + PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir, +} from './types/public_kernel_tail_types.js'; import { ArchiveRootMembershipWitness as ArchiveRootMembershipWitnessNoir, BaseRollupInputs as BaseRollupInputsNoir, @@ -876,9 +883,7 @@ function mapNullifierSettledReadHintToNoir( }; } -function mapNullifierReadRequestResetHintsToNoir( - hints: NullifierReadRequestResetHints, -): NullifierReadRequestResetHintsNoir { +function mapNullifierReadRequestHintsToNoir(hints: NullifierReadRequestHints): NullifierReadRequestHintsNoir { return { read_request_statuses: mapTuple(hints.readRequestStatuses, mapReadRequestStatusToNoir), pending_read_hints: mapTuple(hints.pendingReadHints, mapPendingReadHintToNoir), @@ -886,6 +891,26 @@ function mapNullifierReadRequestResetHintsToNoir( }; } +function mapNullifierNonMembershipHintToNoir( + hint: NonMembershipHint, +): NullifierNonMembershipHintNoir { + return { + low_leaf_preimage: mapNullifierLeafPreimageToNoir(hint.leafPreimage), + membership_witness: mapNullifierMembershipWitnessToNoir(hint.membershipWitness), + }; +} + +function mapNullifierNonExistentReadRequestHintsToNoir( + hints: NullifierNonExistentReadRequestHints, +): NullifierNonExistentReadRequestHintsNoir { + return { + non_membership_hints: mapTuple(hints.nonMembershipHints, mapNullifierNonMembershipHintToNoir), + sorted_pending_values: mapTuple(hints.sortedPendingValues, mapSideEffectLinkedToNoir), + sorted_pending_value_index_hints: mapTuple(hints.sortedPendingValueHints, mapNumberToNoir), + next_pending_value_indices: mapTuple(hints.nextPendingValueIndices, mapNumberToNoir), + }; +} + /** * Maps combined accumulated data from noir to the parsed type. * @param combinedAccumulatedData - The noir combined accumulated data. @@ -1151,6 +1176,7 @@ export function mapPublicAccumulatedNonRevertibleDataToNoir( ): PublicAccumulatedNonRevertibleDataNoir { return { nullifier_read_requests: mapTuple(data.nullifierReadRequests, mapReadRequestContextToNoir), + nullifier_non_existent_read_requests: mapTuple(data.nullifierNonExistentReadRequests, mapReadRequestContextToNoir), new_note_hashes: mapTuple(data.newNoteHashes, mapSideEffectToNoir), new_nullifiers: mapTuple(data.newNullifiers, mapSideEffectLinkedToNoir), public_call_stack: mapTuple(data.publicCallStack, mapCallRequestToNoir), @@ -1303,7 +1329,7 @@ export function mapPrivateKernelTailCircuitPrivateInputsToNoir( read_commitment_hints: mapTuple(inputs.readCommitmentHints, mapFieldToNoir), sorted_new_nullifiers: mapTuple(inputs.sortedNewNullifiers, mapSideEffectLinkedToNoir), sorted_new_nullifiers_indexes: mapTuple(inputs.sortedNewNullifiersIndexes, mapNumberToNoir), - nullifier_read_request_reset_hints: mapNullifierReadRequestResetHintsToNoir(inputs.nullifierReadRequestResetHints), + nullifier_read_request_hints: mapNullifierReadRequestHintsToNoir(inputs.nullifierReadRequestHints), nullifier_commitment_hints: mapTuple(inputs.nullifierCommitmentHints, mapFieldToNoir), master_nullifier_secret_keys: mapTuple(inputs.masterNullifierSecretKeys, mapGrumpkinPrivateKeyToNoir), }; @@ -1323,7 +1349,10 @@ export function mapPublicKernelTailCircuitPrivateInputsToNoir( ): PublicKernelTailCircuitPrivateInputsNoir { return { previous_kernel: mapPublicKernelDataToNoir(inputs.previousKernel), - nullifier_read_request_reset_hints: mapNullifierReadRequestResetHintsToNoir(inputs.nullifierReadRequestResetHints), + nullifier_read_request_hints: mapNullifierReadRequestHintsToNoir(inputs.nullifierReadRequestHints), + nullifier_non_existent_read_request_hints: mapNullifierNonExistentReadRequestHintsToNoir( + inputs.nullifierNonExistentReadRequestHints, + ), }; } @@ -1347,6 +1376,11 @@ export function mapPublicAccumulatedNonRevertibleDataFromNoir( ): PublicAccumulatedNonRevertibleData { return new PublicAccumulatedNonRevertibleData( mapTupleFromNoir(data.nullifier_read_requests, MAX_NULLIFIER_READ_REQUESTS_PER_TX, mapReadRequestContextFromNoir), + mapTupleFromNoir( + data.nullifier_non_existent_read_requests, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + mapReadRequestContextFromNoir, + ), mapTupleFromNoir(data.new_note_hashes, MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, mapSideEffectFromNoir), mapTupleFromNoir(data.new_nullifiers, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, mapSideEffectLinkedFromNoir), mapTupleFromNoir( @@ -1477,6 +1511,7 @@ export function mapPublicCircuitPublicInputsToNoir( args_hash: mapFieldToNoir(publicInputs.argsHash), return_values: mapTuple(publicInputs.returnValues, mapFieldToNoir), nullifier_read_requests: mapTuple(publicInputs.nullifierReadRequests, mapReadRequestToNoir), + nullifier_non_existent_read_requests: mapTuple(publicInputs.nullifierNonExistentReadRequests, mapReadRequestToNoir), contract_storage_update_requests: mapTuple( publicInputs.contractStorageUpdateRequests, mapStorageUpdateRequestToNoir, diff --git a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap index 387a8dcf251..b0ec87cd621 100644 --- a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap @@ -2,10 +2,10 @@ exports[`GasToken returns canonical protocol contract 1`] = ` { - "address": AztecAddress<0x01ffec73ac535628f98088b70f766f47989801a0dfc754cf4996f505cfd8f082>, + "address": AztecAddress<0x2a0fcbc7f1495cb6ee78f159486bcff67a4ea9dfb70cdb1c5813c0310b8adb43>, "instance": { - "address": AztecAddress<0x01ffec73ac535628f98088b70f766f47989801a0dfc754cf4996f505cfd8f082>, - "contractClassId": Fr<0x2c32fd0ebccda2e20057f37fa2e6085c07d9a1236a72a54f58c724418f7b4438>, + "address": AztecAddress<0x2a0fcbc7f1495cb6ee78f159486bcff67a4ea9dfb70cdb1c5813c0310b8adb43>, + "contractClassId": Fr<0x2ef742aed183f098e02f70f8fc0238512237443d3601ce5ff50d267f74240bb4>, "initializationHash": Fr<0x0bf6e812f14bb029f7cb9c8da8367dd97c068e788d4f21007fd97014eba8cf9f>, "portalContractAddress": EthAddress<0x0000000000000000000000000000000000000000>, "publicKeysHash": Fr<0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed>, @@ -18,7 +18,7 @@ exports[`GasToken returns canonical protocol contract 1`] = ` exports[`GasToken returns canonical protocol contract 2`] = ` { "artifactHash": Fr<0x076fb6d7493b075a880eeed90fec7c4c01e0a24d442522449e4d56c26357205f>, - "id": Fr<0x2c32fd0ebccda2e20057f37fa2e6085c07d9a1236a72a54f58c724418f7b4438>, + "id": Fr<0x2ef742aed183f098e02f70f8fc0238512237443d3601ce5ff50d267f74240bb4>, "privateFunctions": [ { "isInternal": false, @@ -27,7 +27,7 @@ exports[`GasToken returns canonical protocol contract 2`] = ` }, ], "privateFunctionsRoot": Fr<0x13b29c3f4a96eb14d5d3539a6308ff9736ad5d67e3f61ffbb7da908e14980828>, - "publicBytecodeCommitment": Fr<0x1c5e1c199e3affad8f3d3ec7db3e3b40639b3c0ef82351506ceb25cde3b04924>, + "publicBytecodeCommitment": Fr<0x229382af84da08ac5ee5067af6eebdfd943656484f0d7eeca40bb92fb9ac8000>, "version": 1, } `; diff --git a/yarn-project/pxe/src/kernel_prover/hints_builder.ts b/yarn-project/pxe/src/kernel_prover/hints_builder.ts index 7f5dfd881e7..eb7ee451963 100644 --- a/yarn-project/pxe/src/kernel_prover/hints_builder.ts +++ b/yarn-project/pxe/src/kernel_prover/hints_builder.ts @@ -13,7 +13,7 @@ import { SideEffect, SideEffectLinkedToNoteHash, SideEffectType, - buildNullifierReadRequestResetHints, + buildNullifierReadRequestHints, } from '@aztec/circuits.js'; import { makeTuple } from '@aztec/foundation/array'; import { Tuple } from '@aztec/foundation/serialize'; @@ -74,11 +74,11 @@ export class HintsBuilder { return hints; } - getNullifierReadRequestResetHints( + getNullifierReadRequestHints( nullifierReadRequests: Tuple, nullifiers: Tuple, ) { - return buildNullifierReadRequestResetHints(this, nullifierReadRequests, nullifiers); + return buildNullifierReadRequestHints(this, nullifierReadRequests, nullifiers); } async getNullifierMembershipWitness(nullifier: Fr) { diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts index 5ecb98cfba7..b761f93855b 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts @@ -189,7 +189,7 @@ export class KernelProver { sortedNoteHashes, ); - const nullifierReadRequestResetHints = await this.hintsBuilder.getNullifierReadRequestResetHints( + const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( output.publicInputs.end.nullifierReadRequests, output.publicInputs.end.newNullifiers, ); @@ -214,7 +214,7 @@ export class KernelProver { readNoteHashHints, sortedNullifiers, sortedNullifiersIndexes, - nullifierReadRequestResetHints, + nullifierReadRequestHints, nullifierNoteHashHints, masterNullifierSecretKeys, ); diff --git a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts index 063ba0b88fd..86f39110bcc 100644 --- a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts @@ -13,6 +13,7 @@ import { MAX_NEW_NULLIFIERS_PER_CALL, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, @@ -264,13 +265,22 @@ export abstract class AbstractPhaseManager { if (this.phase === PublicKernelPhase.TAIL) { const { endNonRevertibleData, end } = previousOutput; - const nullifierReadRequestResetHints = await this.hintsBuilder.getNullifierReadRequestResetHints( + const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( endNonRevertibleData.nullifierReadRequests, end.nullifierReadRequests, endNonRevertibleData.newNullifiers, end.newNullifiers, ); - const inputs = new PublicKernelTailCircuitPrivateInputs(previousKernel, nullifierReadRequestResetHints); + const nullifierNonExistentReadRequestHints = await this.hintsBuilder.getNullifierNonExistentReadRequestHints( + endNonRevertibleData.nullifierNonExistentReadRequests, + endNonRevertibleData.newNullifiers, + end.newNullifiers, + ); + const inputs = new PublicKernelTailCircuitPrivateInputs( + previousKernel, + nullifierReadRequestHints, + nullifierNonExistentReadRequestHints, + ); return this.publicKernel.publicKernelCircuitTail(inputs); } @@ -329,6 +339,11 @@ export abstract class AbstractPhaseManager { ReadRequest.empty(), MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ), + nullifierNonExistentReadRequests: padArrayEnd( + result.nullifierNonExistentReadRequests, + ReadRequest.empty(), + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + ), contractStorageReads: padArrayEnd( result.contractStorageReads, ContractStorageRead.empty(), diff --git a/yarn-project/sequencer-client/src/sequencer/hints_builder.ts b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts index d7abe9fa0d9..1f5b17c7cf2 100644 --- a/yarn-project/sequencer-client/src/sequencer/hints_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts @@ -3,14 +3,15 @@ import { Fr, MAX_NEW_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_REVERTIBLE_NULLIFIERS_PER_TX, MembershipWitness, NULLIFIER_TREE_HEIGHT, - NullifierLeafPreimage, ReadRequestContext, SideEffectLinkedToNoteHash, - buildNullifierReadRequestResetHints, + buildNullifierNonExistentReadRequestHints, + buildNullifierReadRequestHints, concatAccumulatedData, mergeAccumulatedData, } from '@aztec/circuits.js'; @@ -20,13 +21,13 @@ import { MerkleTreeOperations } from '@aztec/world-state'; export class HintsBuilder { constructor(private db: MerkleTreeOperations) {} - getNullifierReadRequestResetHints( + getNullifierReadRequestHints( nullifierReadRequestsNonRevertible: Tuple, nullifierReadRequestsRevertible: Tuple, nullifiersNonRevertible: Tuple, nullifiersRevertible: Tuple, ) { - return buildNullifierReadRequestResetHints( + return buildNullifierReadRequestHints( this, mergeAccumulatedData( MAX_NULLIFIER_READ_REQUESTS_PER_TX, @@ -37,19 +38,54 @@ export class HintsBuilder { ); } + getNullifierNonExistentReadRequestHints( + nullifierNonExistentReadRequests: Tuple, + nullifiersNonRevertible: Tuple, + nullifiersRevertible: Tuple, + ) { + const pendingNullifiers = concatAccumulatedData( + MAX_NEW_NULLIFIERS_PER_TX, + nullifiersNonRevertible, + nullifiersRevertible, + ); + return buildNullifierNonExistentReadRequestHints(this, nullifierNonExistentReadRequests, pendingNullifiers); + } + async getNullifierMembershipWitness(nullifier: Fr) { const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); if (index === undefined) { return; } - const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); + return this.getNullifierMembershipWitnessWithPreimage(index); + } + + async getLowNullifierMembershipWitness(nullifier: Fr) { + const res = await this.db.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); + if (res === undefined) { + throw new Error(`Cannot find the low leaf for nullifier ${nullifier.toBigInt()}.`); + } + + const { index, alreadyPresent } = res; + if (alreadyPresent) { + throw new Error(`Nullifier ${nullifier.toBigInt()} already exists in the tree.`); + } + + return this.getNullifierMembershipWitnessWithPreimage(index); + } + + private async getNullifierMembershipWitnessWithPreimage(index: bigint) { + const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); const membershipWitness = new MembershipWitness( NULLIFIER_TREE_HEIGHT, index, siblingPath.toTuple(), ); - const leafPreimage = (await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))! as NullifierLeafPreimage; + + const leafPreimage = await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); + if (!leafPreimage) { + throw new Error(`Cannot find the leaf preimage at index ${index}.`); + } return { membershipWitness, leafPreimage }; } diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index c1371204787..1e98b368f0e 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -597,6 +597,7 @@ class PublicExecutionResultBuilder { execution: this._execution, nestedExecutions: this._nestedExecutions, nullifierReadRequests: [], + nullifierNonExistentReadRequests: [], contractStorageUpdateRequests: this._contractStorageUpdateRequests, returnValues: this._returnValues, newNoteHashes: [], diff --git a/yarn-project/simulator/src/avm/temporary_executor_migration.ts b/yarn-project/simulator/src/avm/temporary_executor_migration.ts index 999f97fbb69..f33205ae850 100644 --- a/yarn-project/simulator/src/avm/temporary_executor_migration.ts +++ b/yarn-project/simulator/src/avm/temporary_executor_migration.ts @@ -94,6 +94,7 @@ export function temporaryConvertAvmResults( // Disabled. const nestedExecutions: PublicExecutionResult[] = []; const nullifierReadRequests: ReadRequest[] = []; + const nullifierNonExistentReadRequests: ReadRequest[] = []; const newNullifiers: SideEffectLinkedToNoteHash[] = []; const unencryptedLogs = FunctionL2Logs.empty(); const newL2ToL1Messages = newWorldState.newL1Messages.map(() => L2ToL1Message.empty()); @@ -101,6 +102,7 @@ export function temporaryConvertAvmResults( return { execution, nullifierReadRequests, + nullifierNonExistentReadRequests, newNoteHashes, newL2ToL1Messages, newNullifiers, diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index cb9c42df332..31d306f3952 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -30,6 +30,8 @@ export interface PublicExecutionResult { newNullifiers: SideEffectLinkedToNoteHash[]; /** The nullifier read requests emitted in this call. */ nullifierReadRequests: ReadRequest[]; + /** The nullifier non existent read requests emitted in this call. */ + nullifierNonExistentReadRequests: ReadRequest[]; /** The contract storage reads performed by the function. */ contractStorageReads: ContractStorageRead[]; /** The contract storage update requests performed by the function. */ diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 5e307e3a45f..a513baf77e9 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -85,6 +85,7 @@ export async function executePublicFunction( newL2ToL1Messages: [], newNullifiers: [], nullifierReadRequests: [], + nullifierNonExistentReadRequests: [], contractStorageReads: [], contractStorageUpdateRequests: [], nestedExecutions: [], @@ -102,12 +103,14 @@ export async function executePublicFunction( const { returnValues, nullifierReadRequests: nullifierReadRequestsPadded, + nullifierNonExistentReadRequests: nullifierNonExistentReadRequestsPadded, newL2ToL1Msgs, newNoteHashes: newNoteHashesPadded, newNullifiers: newNullifiersPadded, } = PublicCircuitPublicInputs.fromFields(returnWitness); const nullifierReadRequests = nullifierReadRequestsPadded.filter(v => !v.isEmpty()); + const nullifierNonExistentReadRequests = nullifierNonExistentReadRequestsPadded.filter(v => !v.isEmpty()); const newL2ToL1Messages = newL2ToL1Msgs.filter(v => !v.isEmpty()); const newNoteHashes = newNoteHashesPadded.filter(v => !v.isEmpty()); const newNullifiers = newNullifiersPadded.filter(v => !v.isEmpty()); @@ -134,6 +137,7 @@ export async function executePublicFunction( newL2ToL1Messages, newNullifiers, nullifierReadRequests, + nullifierNonExistentReadRequests, contractStorageReads, contractStorageUpdateRequests, returnValues,