From f5cdd2ca3b6aba061415ef1557d3f1f44e6ebc7f Mon Sep 17 00:00:00 2001 From: benluelo Date: Wed, 11 Dec 2024 18:07:10 +0000 Subject: [PATCH 1/5] chore: slight refactor --- .../light-clients/ethereum/src/client.rs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs b/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs index 7aff77e3bc..281be82518 100644 --- a/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs +++ b/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs @@ -12,7 +12,7 @@ use ethereum_sync_protocol::{ use evm_storage_verifier::{ verify_account_storage_root, verify_storage_absence, verify_storage_proof, }; -use union_ibc_light_client::IbcClientCtx; +use union_ibc_light_client::{IbcClientCtx, IbcClientError}; use union_ibc_msg::lightclient::Status; use unionlabs::{ encoding::Bincode, ensure, ethereum::ibc_commitment_key, hash::H256, @@ -46,7 +46,7 @@ impl union_ibc_light_client::IbcClient for EthereumLightClient { key: Vec, storage_proof: Self::StorageProof, value: Vec, - ) -> Result<(), union_ibc_light_client::IbcClientError> { + ) -> Result<(), IbcClientError> { let consensus_state = ctx.read_self_consensus_state(height)?; Ok(verify_membership( key, @@ -61,7 +61,7 @@ impl union_ibc_light_client::IbcClient for EthereumLightClient { height: u64, key: Vec, storage_proof: Self::StorageProof, - ) -> Result<(), union_ibc_light_client::IbcClientError> { + ) -> Result<(), IbcClientError> { let consensus_state = ctx.read_self_consensus_state(height)?; Ok(verify_non_membership( key, @@ -89,23 +89,24 @@ impl union_ibc_light_client::IbcClient for EthereumLightClient { fn verify_creation( _client_state: &Self::ClientState, _consensus_state: &Self::ConsensusState, - ) -> Result<(), union_ibc_light_client::IbcClientError> { + ) -> Result<(), IbcClientError> { Ok(()) } fn verify_header( ctx: IbcClientCtx, header: Header, - ) -> Result< - (u64, Self::ClientState, Self::ConsensusState), - union_ibc_light_client::IbcClientError, - > { + ) -> Result<(u64, Self::ClientState, Self::ConsensusState), IbcClientError> { let client_state = ctx.read_self_client_state()?; let consensus_state = ctx.read_self_consensus_state(header.trusted_height.height())?; - if client_state.chain_spec == PresetBaseKind::Minimal { - verify_header::(&ctx, client_state, consensus_state, header) - } else { - verify_header::(&ctx, client_state, consensus_state, header) + + match client_state.chain_spec { + PresetBaseKind::Minimal => { + verify_header::(&ctx, client_state, consensus_state, header) + } + PresetBaseKind::Mainnet => { + verify_header::(&ctx, client_state, consensus_state, header) + } } .map_err(Into::into) } @@ -113,17 +114,20 @@ impl union_ibc_light_client::IbcClient for EthereumLightClient { fn misbehaviour( ctx: IbcClientCtx, misbehaviour: Self::Misbehaviour, - ) -> Result> { + ) -> Result> { let consensus_state = ctx.read_self_consensus_state(misbehaviour.trusted_height.height())?; let mut client_state = ctx.read_self_client_state()?; - if client_state.chain_spec == PresetBaseKind::Minimal { - verify_misbehaviour::(&ctx, &client_state, consensus_state, misbehaviour)? - } else { - verify_misbehaviour::(&ctx, &client_state, consensus_state, misbehaviour)? - }; + match client_state.chain_spec { + PresetBaseKind::Minimal => { + verify_misbehaviour::(&ctx, &client_state, consensus_state, misbehaviour)? + } + PresetBaseKind::Mainnet => { + verify_misbehaviour::(&ctx, &client_state, consensus_state, misbehaviour)? + } + } client_state.frozen_height = Height::new(1); @@ -185,7 +189,6 @@ pub fn verify_non_membership( pub fn check_commitment_key(path: H256, key: U256) -> Result<(), Error> { let expected_commitment_key = ibc_commitment_key(path); - // Data MUST be stored to the commitment path that is defined in ICS23. if expected_commitment_key != key { Err(Error::InvalidCommitmentKey { expected: expected_commitment_key, From fbe771b34e8ceb307a0dce210cbd0b5fa6a2a115 Mon Sep 17 00:00:00 2001 From: benluelo Date: Wed, 11 Dec 2024 20:45:20 +0000 Subject: [PATCH 2/5] feat(eth-lc): add client update test --- .../light-clients/ethereum/src/client.rs | 2 +- .../light-clients/ethereum/src/lib.rs | 3 +++ .../light-clients/ethereum/src/tests.rs | 25 +++++++++++++++++++ lib/ethereum-sync-protocol/src/lib.rs | 6 ++--- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 cosmwasm/union-ibc/light-clients/ethereum/src/tests.rs diff --git a/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs b/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs index 281be82518..690229a295 100644 --- a/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs +++ b/cosmwasm/union-ibc/light-clients/ethereum/src/client.rs @@ -214,7 +214,7 @@ pub fn verify_header( let (current_sync_committee, next_sync_committee) = header.consensus_update.currently_trusted_sync_committee(); - validate_light_client_update::( + validate_light_client_update::( &header.consensus_update.clone().into(), current_sync_committee, next_sync_committee, diff --git a/cosmwasm/union-ibc/light-clients/ethereum/src/lib.rs b/cosmwasm/union-ibc/light-clients/ethereum/src/lib.rs index 3dc8ddca47..14e8842b41 100644 --- a/cosmwasm/union-ibc/light-clients/ethereum/src/lib.rs +++ b/cosmwasm/union-ibc/light-clients/ethereum/src/lib.rs @@ -3,3 +3,6 @@ pub mod client; pub mod contract; pub mod errors; pub mod verification; + +#[cfg(test)] +pub mod tests; diff --git a/cosmwasm/union-ibc/light-clients/ethereum/src/tests.rs b/cosmwasm/union-ibc/light-clients/ethereum/src/tests.rs new file mode 100644 index 0000000000..c662527e94 --- /dev/null +++ b/cosmwasm/union-ibc/light-clients/ethereum/src/tests.rs @@ -0,0 +1,25 @@ +use alloy::hex; +use beacon_api_types::Mainnet; +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Addr, Timestamp, +}; +use ethereum_light_client_types::{ClientState, ConsensusState, Header}; +use union_ibc_light_client::IbcClientCtx; +use unionlabs::encoding::{Bincode, DecodeAs, EthAbi}; + +use crate::client::verify_header; + +#[test] +fn verify_header_works() { + let deps = mock_dependencies(); + let mut env = mock_env(); + env.block.time = Timestamp::from_seconds(1733949552); + let ctx = IbcClientCtx::new(2, Addr::unchecked(""), deps.as_ref(), env); + + let client_state = ClientState::decode_as::(&hex::decode("05000000000000003137303030010000009143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1c06a15650000000001017000000000000000000002017000000000000000000003017000000000000000000004017000000100000000000005017000007400000000000035692c000000000000000000000000000000000000000000e3e97964d842623d2da087978fbe343523d810b2").unwrap()).unwrap(); + let consensus_state = ConsensusState::decode_as::(&hex::decode("00000000000000000000000000000000000000000000000000000000003043c0217d12a493ccc2d85c34909e7991ff395410acf5de3be9a8577052a868faf1a3d70a9e2d247ebd2fa00d8eed62505794181e019da46463aeb6e308cf683068de000000000000000000000000000000000000000000000000180fe7d0713d800000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000309909f07c99d71f5e5a406e129ef7494a9ff18aa3ad738e98119247f4959d17499fc1d5de972ca324ec3bf1a8cfb32316000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030b007816c54b85471cad25e4a499b1a21c981fd9bc2053c27d2abee6dd375bbef2bf0c31ed300626d0110b115ee86b92e00000000000000000000000000000000").unwrap()).unwrap(); + let header = Header::decode_as::(&hex::decode("000000000000000035692c0000000000010000000002000000000000b71ddd3c8e37198a1dcd56056b97698dec68693fd8fc34152f812ba2c1d2592d66377bf1d3ae9494eb3272cb7c7927cc92c646444300c6597aaef1e7bc1593694250585c7a0c2a240a9390c6de3ccc164960fbbd9f08159269e40982383a37168a9fed04d26bbf1097d5190410d9c117c9366abf9a246bb50dd75e329a8995f203eb01be47f9053c21b7893366fa3220a285f0fa692c3c924e629b5db73ed56d6c9976998b977aee3ef9018d9e380526b7d546baf6dfdea0585f4559a52c1048982b7231a8d3b7e55eeed6159296b8c5bb00784531279d6116a33963044c32774a8260a533b9b8320682a8e58853e322923d81ad5eec1a04a1143fb8d84c65803ca4ac9429a01be9df765efd27fde535513b5e9827681de44971f9863b895fcc8da6568c42f2d8d2ada1e58ae2abf784e9db8a5880591de1035152b242abedaa7b30764a6a2b01bc44e2c05a30482362aee94e4870412772ffc6cfab899ef94adb7e42e64082ff2d2cc21efd4f45432c34007e8681f37b743a3ce5b9c77794e1949769d36defd36b54f194d416b522f4a84a2bc7e2e09c8e2f60dc37d7402f9c89ea43832dee58dd3a1e509dd897c89780903ca86f6cd2fe1e17683a072f13f015cf22078450c3f0c1bdcc2fac17932afcecd59044a30d85ce67c6e4f8465f1694af2c7c5bbab8d9d2e7d0e8898518d0a38c48b6a5566f6e208849d87c408ce3aa6501721ceb0a7f1211a3761568ca7483125bbf0cc233c4388f545e69a6412ca1a6bcf98cba295d8b0e188f70551d88fa614d5e5b75c42383f3b5433a10ba1f99c8708d33e2f38fe18c04889efbe244ee2dcab010f53ff8eedd2830654526fe1d6f056b84ed90debe98da80a699bd1f803f626b841d9f013993fb67851c236a4d3d7b90586ef92375dbca18e291263206fcb1512ee7568fc5237d90bab037deabfb75b715f50fc62242e61fa8c1a87a81fa593c10732905a5a4d81e889bff01856c458104d50e63a97eeb5a889d162d99aaba743684d4a7400ad5a5e273f56e34c2beac292faef0a870753d3db0a5fdc1cd4cc8b4b21daa3e98dfc84db21eb593c7fb4ef710b408f47ec1ac6164868300b6f7808def6e16bab5714f2c92cac090c423ec21282bf4285e786dabb15a80898659aa0239e403cc9b3f95e3ccff1c9f4d177ca98f85228a444989aea02edb281c29abbb588c785d1a76ee3863ecb19555d7284129cf5e73ffe8211f84b51e7854aea3e0953a80f2254ad30a6efb68ebce79eab1450e17937658a0395252a2aae6df254eafb9538140ad6f6b03de6fc3be44275fcbc76117b7bce84c3ebaa056537280af7dc568483a9e5ae14070dab25898d401eea69f3174b5eb19ba4e5a14bec180aa693ac2be3a18447caf477d8cd7f5b9d6c85752d40a6c18d42f849e968ab416c9e92e3d370fce70287f8148a0485b4f736594ecd0004f42fc988eab4f47e76c5ed0a9845e824d1201e0426a984905a5c0419fb6da641bdba9504d50b2724f1e929ef8f6ce56dc222a226a823b56bb6f5d8fa72db8e7833b47d58e71b2af23f5591709f74d1dd035a451691cf8e94065584b1217095562ddb1598bc16ce6b6809688015c64b1ec0d310106328bc75505a10f7ecba6d1ca3aa4153e41f73ee7173cb4d69aa58ee5a5f5049bc772066a0d968a466b53fd075a0eb4d5e998b57d51851e6527522404bfd9dfb4809931b2016a88d44b2a52f18836d4f4fcff04fb196cfddf17d3a15f85a1ce0a3e93587496d32b6c1e2e3f0f0997f2afd7d8fe02c682337c05fdc96cf6b372cb03e110bc8870d41439080bb9632e593dfb8b7726dcdacd9ec06b209ab4c22b32368cea63125070f5ea98a965ae76bb527411ea13046303407f49c47c4bc835950fa34665dbce38df70cc587b032fc77531eb07cd1b63f82df4296d43aa637ac353157a721b99af8bad4448fd8ad7b03915ab730d563bebb6512dc44407969e45406e27d9c38def8db910234f7a5b99743cd2b41ebd8aa256aa95d1cb4e4ef32e91ad7b06de8eaee5946bc956ca5affd80e3b5f705954d2cc83787aafa8bbe96fd0b1c7a8473b4b099aa05bb4cc81179113b8780cc607ec8b504bdeb3fc1996e54cd54bee521ca5c287a29b805d507439ee27e403c5552ed27aecdb475b7906741fa667de4a5ddb1080a15b7e7633bf31461c6d8dd6eb95f07633bf0684828d79c217206145b95bdf24415592575da3a4628e25c79c167c1356b92b8899ee38a54e6fb447aa0ee612ee8ad0acf130b1023cac2eab97d4f36e26fc9fd5a7d2586e98b73af1abbaf50b0ba32afaf6b287c805fe00c4ead5e92c5072277a7ab903b96be8316b2d7c224bfb3d2c118789c9a5e4986f80bc7aaa5e3f6785d9b89a9de20d814063f08c016c8c7508e0b0ff79b04e5cdbd46419ccf7c21f17f7d8b0c8e5c18511f53b83bd101657532e6187350fbb901d0a8ec0d5bec7f3bc819be1557645850e6115bb1d686a5b690782b8280535b2090eb8a2ebe3ae1b2792fb6d887a4dbcf62188f678728a9338bb4e197f047d6acabbb57bb56f7dd19809eff6c494b8a48119d1a355c2be69ad2f6b49348a82cde2202f6cdf60fcba86be94aa5dcc3e4cdaac4df24033e500d0a7ae4f9158fd8e974c1ff8ffdd3c2192034533eb9b844fc417a822dfd55ca562c3a1d854ec58e188a78c631395aeffd4ef8d1104b73f907c01f8cae561693fa06cf5280d3184433cd72ebec7ddec244538f77d594cf1db008420fe366362684be5ddc625067ca14267dd5a00333ff6039dbb93091f7fc51a5432b0d1490bce98d9651de5b72c37aa017470c53384aae291ba2610481390c367031d90612248058a11526249c36182d2e304eba005f7d099db0c6be366d88503dca01b1f3cca2f7ab9c5d4de15b29c9d66cf5d1bdc4d50d62337e5bb58e66e7b15ed6f3e62829ae313294087306d61ebed940c321c28c918b5d0be0f78a65092a593f036c70cb4e543112648caf7654211fa36b4cd4e47117272c12ae0e7ce9ae7c68760ea5a73eaa99571e98a905523f66af5875ff9cebae2aa79b22c296ee8b5ad40fffe1dfe858feb89e53602f80974c23ebee73d310456d98eb3d4aea9d3af60c74360571930eb81c5e6c3630821704e2d4484d1d82664cff850c065ffc25bd70a5843ace0fd1ee8dffeb499d70bb4f9b10a266420fdefe7a46b92e1f87a0d871944e3e996d3dccf579c3ba90db684a0a5b24b407b8b43f1e1c55bb88b15c6a8893c726537166d8318c8997ae7afddf58bab9720c06c116c4ef15118af892c0493862e76f255303b554ff186a263bf747e248eacce31e1168ff67ac32ec8b9ff68ecbb6a6e7003808579c81a12f51fa7d10e707f85f81ee47b94a287a3c6bffc6b517a4368e03705424c982c5bb7705401fb92c106cf83cd89f3fc647cbd077b92efc081493e2813d50c5bb818877f86a6a507fa6c46c74d1ac54c8f91dd18b7e7881550421db62c0b907ba61077a3e872134e4ef7e81ab9b5190aa0dc00551e9d914152937514f9d032789d3c594ead80ec2625ee6d7438239c63b7508971a85480bcf788749c6885ad0db3360994291fb01f84b82da0926678096bb839be0a57fa415a6c79ee188a335180756aadea7b075b817f153f37b0981db571292060bd1fb8f03994dddd6daf801ea9cf7c9f11f3c64dee9d5e4e9319d87e0042eda68e3d9d5500f848d6c8fd90805958eca5009e6604276a34a7939d914c184928a9371e0a997e8679f7e7e3e50642635da5f30bf3da4baee618d2195ab86b7e1cbd5b06e7496dc04bcbd13b5b1b8730732a326932b6584ba9260e5844ab87532a9c34594d547769429f771a3ab28f745ed1e503b70d4890bd1b40767cab412cab7364e6a3aa8eb5b920e89994aa277d2b4cb64509368daa6d20e3b7bab0bd8e467fe1d0fafdf94857ae98e08333ac5c7a3f3ac34a11214cedbfecd85e54f9d262606686f3752bf76370e189efa0cbbb064bbd89d11ca9f7fc318d1dee93ae7a9b3916ba5b3e6563c393bc45f58e1aeaf690110ea43c63b69abab9cbd6b9f9868e330c66520020f2aa8ad0404c9fe36c2cc015c8c577615b28bbd079fea06583dff22cec7a9776dfcb9dbd6d038240b9ea6bd4cb1f778509e34944cb9b4f3af3d7a4b801fc145e34f732927ad0bb744c27a829700a30b13e839a24f411a7b462f1672ccc7cd8491db1cfc8e131037478beef8a7f305d784aadd977e753420085910c5c57ed3bdb39fe22ffd45c8ecfd51ed825fc0b9ebb49b63808e0f12f0eb25190ffca09be340a55ff7f05054fafa531e07f862388bf4d313b76a6cca5d12989b9ba26f03ff146e72cc8a6907b593f646b12f597d3c22e81731a0e5b4f3a029a94af7ad8751b197e47e40373a3e508db82b03bdb9ffd30a82b34b6ca9886806fd052e3c0865439f9c5b1dc1987ff87a707d32ca481586cbe840c6ae88773ab5ca0699ac926dbefb6d589ba9f45606b3420e3d36c68e18dcf2db572646711d422ad976a75ad641f89920538cd849cf184916cdf4b8535a69ab74db83c0ff5ba90f9e08c9f9a7d62cca8bb1fdfcf3f765eb0df7daf3a414a6a9a669b7c82609b0dce974c624d654351783561c9178764c0e4fd0f05d130b61327bc52ea58171a8deac0c784459ee10cab0de767b622e03571f68ec90f98dff025e1e3024bde909bf15dba5a2d89e220ae56dd1c9be64a8f02221e07c62e64532ef1ba268246cbd35abade70020938d7e3615465d67c68338665fbd47d9ec16c2168526df78db9273a38a7c03f848dd020ecee7e93164bfd7dd45889405533c7b38760dcfe52e6dd45bdb1dbc9724b60e5e59fa48f2ec42f5eadc31939dbd8cd02318965a43eb5bb83376d008fa5586581480a7f305d3e7bb90c093c0eb6ab7343255b0e759cb86a413f65d3a192c375f6a87cafa7b683d4d6e8cd563acc37f70528acad37f7f4452da985cd9d9a92e4924c0a604ec36d2b02538a9f7c157f92058a92a58c4ba8b396c04bff96a2c11a7fafeff402c5239fa89d3fbc5b18bb64e6365a1ff5d1f55178fd43ccae82f75a02cb0833abb18a074ed888d5a103c926fca320dea0c6f43a51fcc35dfb3843ff6e5f065d0ca04a8001be9e015112c35afa1021aeb4889e3b3a4098da2411d1199912865c1ec36ff4b98657a25cc20f896af4cce3cda5c3f004d8882667930ebf1f6e7cf58750b5b0e1312b62dfebbba70c124b2455610d8b1275a0c8019b078308c4e7d32de9c721f7f1ba0d9f72ea0f6fdbb8f68b75bc39ad8bf7edf7d292897619aeb555d389c23fd3a93055cc1e5fe6e3f49ec02c1640be4e931729430a7180f611138b13ae88a18ed1873a46db708766559131f55c50a486278401258e369693fb16988d37a7a2dc45ea84ccbe3993091075838db8a5be5f1713579769044d204c9f7c682e3cbb4d3c0ec27d217388a7df630487e5a4884036bbb1503af2c15251cea2ce61373f30c2ec3dd4d15873c776c37e143618328f264b4a49e9848fc9a2341303f6082722395b47793f04049be90aa6cd40bbdb97f7299b5c29d27f9d9ada6d11a258de9b45fa6fbd2ff832e58bf6048da4ebb5765f62f4553978a34b6132b25888561fcf39318191f2bfb3dba5bcde44963974fbce7d65c76355f2c4a2ba5281b760a4522d46d2cf34ff31f60c83a3097e70d8fc5b28839f138fe366be5bf1c83bb29dc303f6807448f1d4dcd9dc21ca02a3bdd82f192e254ad55f7e268c810f448dd2a00b2cb7ddd73f1a2e86d4cae6da58ddd9f1e8d6e3138839b723fe2e3f93bcf6ebd0078735faf56855a40cb9b747fba39aef97f780e709391e5b1bae9772a2f0d8dbffbaa3d843861c4b400f9aed538d5f8904a920b244158691278a5d1211a823d9faa626d5b6a67763bc75de88e6571950a77a8820c8cc7e43a0044d66e01a192d4fa95980eba2c1896eb15778cf88616ae6fe366911e1c6d731e09c057fea658fc8515e4c7414d8d123f38b1ef91e9c538e32139ee56c8b80ac869836fb494e65a69e1718fa1378eb2af25d6674655f187e5bd770a17d77fcd0d2649473af512ebe0b108e71b2d1a0708a78686350dbc9fd814f9e7de8031c0d7f082edfeb2c208ed17053a5b14102265cae8d1c577b9121fdcdeb6fabeb6a6081dde15fb48e4db436cb6338eff548f9651b3d4ffbdeb34e36408dccc79a98efbe630d2defd71d2e473e00802fbd359f975f07b4cc088b25471bebcf1c80bf759bb40d1d24b5905fc368e8b8c8149d996b55a798d347d558dd915b906c6f4aab97c91aa8419022e8155ee2d7b39be93b23e4dbd76640df277580ff2d2d07d1d4a61ae30f3fd0923257c827dc3a619be58b1573fb5e4b353f0ad29b3957b4b9a8eac4b73bee88ac86e4e51e3392a8661b4c91fd44c1e9ea0b75d145f73e1a3c09b9ece18ad1074f5ff071e260850797e167f5e456a24c96cfacc2614c3bcfc8621a8e62d34e0a16c7209f94508451ffe688c8446853e971ea75a7cf87106fe65a54f25f196f0545deafdbbf0e7c8b9f3c66d5e5029d1b1b571a208c4c74e0d5e9882bc631eb568ecd63182eb7976c70cb021b181bd7bdf49ed85d569df55281b8454f8d4a5ab9d004fbef29829a02667ab33e69df01866787397ecc4227fa06f908f29ccbad86de1b0a51de5d12ebf4474296e6faf846e06c68e127263788791aa161d108323eedf508f41ff4824b5f930f95439dd107cdd4bed49815322c6fc58ea72c08627686ad0b37d9f4aca9f9de84d8639b201c8c50ad163074a6448694a348abcba4b9dbd311245936701658f0c158223887b81176a768c4b673deb1ae8d164ece53e9c42b491403e461c05ee8dae5024ce87b3aac250c70e8bf6c1aba4744362f3ab6d18b112c3a001c0ed97899b990cdfaae5716f4464648c3d125674738cfa8d7d4f371d33d648d6e8244e025ec31e1cbdf10f1cfa07832d903738eac2432939dbb5048e934deb2043a21c0a271d66e4f16e44ed7dcef3bafe6e37fd6df1fc77990d2a1df48a4240479257395a6b3ba0b216cd382d634ef1d9d76a2c4ccb60cd0af13b0883569fbff61e49cc42d2272e59cb2fe20733cc832cedb2780794f0865cdebed5b814d514ceccd03b0d50b5f04d9fe38f649b26febe57a98fd310edfd9c9fe52d484ed158918803f818fca74b92fa20686db8b9bb6e3fe007185ceca9c016276124b6998abde20cd475fb6dcf47f7eda329167a3b60a75b9a988ae4ae19e76ad4c52f15f89b264da0204c40d58cd6ca9e1427d2c38f3549b5f68e3d8f1ee6d751cad7cd065423f8582608200f92fabde8b99a9bd9d3a01a82616c1e830814094af6cc6a5c34e7bd1f6fbc201f98b69b652375805846e285b8910cfe6e3ac78ba2df21ebced4dbe5fa18959a0926a37c0e259e22dbfe3045f0be299e968652203bdcdbb4693cdb3c38e2e8573ceeff522ca23bb5234df54c523e846b4894a3580eb91b19013b393c93c42361c9aff3e7161cd606da8f434aea56e7941da4aa6980898779289394e3cb9b3debf44d86f346ca38d6f15b80e639c854b7f04f93a71c1f17247cc34ea5d9220ccc20785626e0dbd30099dbc12033e40713bb0ed1f38bb7991e99ac8cad2eb92a8cfece17c33b79bcc290751b1f596e51127670b9fae3f6562aa3defbc625ae2807c5cd4d07f4110eca4a585898d429f49fbf40734b78a74262d16e8ce7db6a4e6c0aaa58f5c855d73c35285f8c40798c184cbe9e97103e3a71bde0afb141c8e7855f7db2536acd04e08c3024d46a1fe6cdb5817b0fcf6d92a1c291844cb2350d5172647579e81bac84bd4ea69c1f7c3c9abcc35b975a0506eab8d57092c862dbee1d8b8f29f76544b02e7b2e694a927bb066a3c307c5e8dfe6c138c35306cb66409eed03aa32a12b0df5f9dc48bad480ab6c3a4798c98a47e21825ba0f910f484c974c51a5250354f52dfe6ab67b21451ace0c94059d701e3d04e0fae81a0b019e0bdd7efd04ac338f197808fd2354ba927f27987f99a937586ff5fd5ec6d31e0083d4987ed4d0f820e83d18430a706608cf78106272dbecc6ed5d09f6ed7c1b0444b2f00635c05f37c3d3c45d3f11872a5f071de2efdab52aad3f4d1c6a3324051015fca52e155ade7671f1167c863af9206afb22a9ee67c43135696ccb6fbb812e45e20d5f17600c637241d23a1809e0d5e8c073a439758f2fb40d873a105a896d455ed3d1be34f3f9aa3d41ee0a98af7ac1cce942e7da56f9edcf09097d3ab856f38a12cd21853b0f73c51202322b6b1e1cf72af3b6d68b1e33b4c1a123db32b77a777918420f34fdebc01a78e8b4e8151f0d83568df67617f07c555c7696005d86b31880f59805f384da045266c9322cb3705fae561672f633c667c8e3afef4d9b55f3cc15e59b8581607b50657574d732747c4b18b5879222bf17cdb239e57023542ba5ffbe6e148ab9d74b11fa822688145f3ef5d1a88c4daef2bbc36af8ee28344ef2a81d8d84766ac48e416b053894ce5c552a3e3d56adde026ae18d50be95a41c889a0eabe530be31dfdf6d8ce926c5c0d10c0323122d800bc6f8fe7eaa36834dbc7f4d7cdbe74ea89b267b19f802424a05b50ba75743ea498a2fa40cde801a2a9fa1d4b9646404b208c5b29e1d8ede073b0ee1c69ec59e970a28a44b57ab212726a2067c4938373ee2097e701a1b7e7a5c03481565725cba3d6b8e3d0dd74db6d47c8191ed4605740b9e7f77f237e8dfce4993fc032b076ed2aa568a25d516c6016ee86922cb50f22bd7f7aceaa52645479b10f3ea494de94910652adc9d8b3d563b669cd2759b71e969df16abc22b176484fe20c1b144b4afdfbe6753ae733d5b33b5ca9978e7f7d8b0199a0e7f74841ee06c5b7223750ebac7a052692b9a1a016731d02dc2ec1884f6242bc5896f8209a90bcecfe918e4f912aaa07ef08305245dda7aa1aa4b5e370731cf855ef7bc954cf932463de1209b5b8eeb0e3a0843837baaafa9f6f19cbac6164127563fc41688f8f271c9889507d1f3c3c947f523eba49ccef42ddf15fc214916a65ad6de4bd074a37df270e5ca592551762398e4e73f188d3e161e54b3ec1e5f067eb5ea1ef84fe8cc3933d1a1485a6908be0c577a4ce8a32b74a3d8e800b094f41a1ec569b93d3b4ced4349694edb10c280e66c3a5ecc3b2b091714fc7a0a547fad650e943e6f25cba6cfda7ae34f0fc3f03153bc8759c388bd08d13df511546812664fd16fe623423e65f079236daefdec4e4a920a6b7fb22732fa5afb60c8b47815bcab9b7ba2f1dc92027661726de31e9a00d6c4c8d3723c42ac7a20980c91cc9618531d34575a54f43aeb69c5704d4e1714ba68f0d870acf8e707da54340d84725f3a2ab366233485f1740bbe5508c4da6f23f977b429ba3848cb5e213d0cdbb6fb9a1a44a7f13f8de3ca34c097e6677969f66f830b88f025f0fd54b7846e8d7881cd1ff940e322c4169a97b03e5f14c39c478d147880588a896c881ffd8caee664af0b304131f1be818f9f7787b6f3ff8dad3782c1b7c84b4c196ccb250992aa10ec599773e080742e2aa016bbac3563cbc080345b5ef08562f266e13b3557aa889264af8e145eafdf4b7d0e78ba746c497c7c9ed9f3eaea65e89ba84feeb7b22f1aa6258612352fe6730f4dcf807c9b42e5c43af80729eedbbb4259904d9c2f740412b1fdb473862a2b88658a344367be7ecfb7917031e1517385941d2f142be5948815cc47bc10872a5e8dfd7cf8297f67cacbdb8b7d26a30360c7a2d6e34e226a870975cc07e3c59c78664c9aa2e48d10dfcab27386f1e919273ec47aac8001c539d1004dff642f6b3ee4202a8cbb507c7ccc66515a3ed328fdd61729aeab2e8c769b13dcc0819fc8386120091d6ac33414d5e3ba51c8468303d09ffea28c6a7f082e536f8bf78219d52e32f798c0e5dbc1bc5bff5274bdc91ab113a9b8a0b79eaae072727d93d65c66682f73c0d0f36a4b405105819d0bf63bd55e52e820effe229c780415c8f66b8cb1ab100f806d8e0eabe5c6afdccbc921a61fd0fc51924f561a57b64aba51dffa15f0a7d356da32c1939379ed3cbafa26908ed4e3b34c844333f70143773ca6f51e09c6886a5c9137f09faae18076980f95f3ea11f0ba3d6a3125c47851ac2a338c0d56d69cfef3923fb6db20f3edbb7ebb53bc47d144442405a3d53ce045b4e7da94cd184e00a3ff82d634b487b9fa8c3426e82b8c2baaa0a2e4111a09b44e9f3b3857e975431b03d331ec455b76b4f6ce892c423a42d118b11f4e19aefa1b59e9c6424885a524ed104c2b6fcf2ac1c25eb4ebc0e98f9358ca90e60d9c42c25db91b7ae35101e606442a5faf5cff3ad1f1dfe2028796d3d7a44c507a698d054c04a99fa58bd17e87208268d4e397c86ba81f22b84433a4569c0d4d0fc0b37877831b48ca7b15e901ddf0fdd7c18b55a994dbfc39c9c93a8a4f39e9f558f1c349bffa9c2d67ebcf2c502c6749ed0d795ce3167f1e4950fa746b7fe89194cd00a32df6d6951535e4ffa1b415925017d37e5eec1d272496b5f88113c575ab16cb8d29dba60aab062126a2e9c978e667358080fd41bbc095262256becdc25ae503edf8ec6d387c42ec57a8545c2f0a106a247f8528dc7127f5d179f6aedeaeefe74e02ee7b7b7d44fb8f00e6f566af9bcf2eea798218052665b373ad114a3c24d8964fa5f148614fcf34b500cb6b6d3ee71c572daf6801647abcd58c8d769f5121889c1a5d56e1e0600af7c8ba1ff38e08933a2e56d70475137c7d0e7c6ec2835c1c6d2c75d98cba40c085794f768b719d26481f7ce0782a3b516d45e7eb9ff59a2518623857b0c33824a72a6d77e7e4158423deecf321d7cd332643a9982549112ddcf04de19a03d4ec7b90d4867705188f85fb53b00d78b41b31e31f3385defcfd8410140b1bce58e20c2b3ac94072b14b4722a559ebef21c29e6916ae5897193aa0ba41d91aabfec6ecfab8ffcf6d2f991eb625d49bedabc570e6b2be0a38691619938c6c11bd14e681c442d2ae152a3aa1018e7ff038563d21ca1da2e2786f85fdd35d79ff13693dc7e0d6ada4046dbdfb37f6e8ee49a119526de62d60ee093ea440409f924f30b1d989328a43cdeb34240b6661e94f9c17af8d39da3db5c654e70a46e4924a9d76abe785cddaa1eb883c50b9a910bad7af678cfbf783bb8732567d9a67f760cd03230e44ba3730b575b9b529c50a44ea478993bd29e97f1b08710a87c62f18ab8d0dcaa1a5e6a2ea14d6c79a3d6c0c6c33be3cdcaf65cc2100701ad8195dad232e999479746b380890990d23d9cf61e6386f81dd927e524b3b922199e1adfb3dfe129e61e117e1d1c6b66a48c7e43d8718c5050bd0d5600b8497c7b67e82a41771188e019cdbe67111c62cb3bc7d2ac71becd444e8a6ba8ce5e162b4b78369e6905b459fa288a2aa15be85222b909e8f398dc1189709f53b14e9bd7896277171c60e44a30b4458994f55badebcd946d159df58df188d9f8956ceec8a19b97a7f92ec54c4048cf984148cb3f7df7f38453f992b6a74e40f5d00081807aff67d50244f04f3e6a2b9f892e92be9897bb1fbffdee4d90302ee8fd0b88e26bea9ff3a6a705076b979795d985054c50b3cb14c02eab75d79c10b896a4bce24fe758ff9b1498263da53b195480a27f6a1d8da10c5bdb077b67c0d37e05aae110f6f98147c3f48ac7eaed4e92e11e71b5466e7e8d0c1460b04517b57dd8a5fa9385860c26a565f2b8df36d109c51a4f1bceb6fda8fd614ac08b9a56af0f770991cbafaaf96f0b703f3a33f15738e7a83b92fe3f9a88fc7ce2c9c62932d5fa476a1d63d008c1ffe8fb749a058dd84ae6dc84142dcb590ef2cfdcf02025461571a0b05cd86d1fa7cd1b524c586b53296b7e0fa0bda51175fcbba687b4b2f6dd916e0cca0f3730addc2e986c7c6cb37e3f2bfa739ad975677d9e8bb3d6cf694c2cfa0ec21d0be6eab11e94383b97acb20b50b7cdfba2b0a5a4a39949369c60390a2dcb51587c93095d734070098077c0184f056df6a15a3f27636e7dedb6a3638b308fa9a32c02dfddf6889f8b9a6e03aca410e42be1e835ba6a5e40b4c2abd69a144e0e9adec219aee0607e69979669bd22e85373b94fd1e7859e4ac512f96e796351e54a34b6fe9c5ea06e655cedecfdd0f2c634805f5d7c6db6787eb1207879c31d00be787e0bb5ae5876b7d5451ad656fe3cc6a90325a6e0942aacfff3d29c755759d97d1ac9ef5bc13893b3f308ac43792f1e1ed889b66aef63b0653b23c1f37b5df82babfeaa4a58491654e6120250ca033d85f6a4379cb73f5f8e5c850f3b3d525bfc2b973f3990529cc4f7f250612c988fa0870c487413cf35fa375c0e5b8f971b665376c91f3f3f3495a9770146a0f2b8c66a737b1a52b916e04a42f1488da864b58e757029a319a5321d3cc0a4d2356941a05bd2647049f4811e260c6ef2bdbf65ae2c375a1b6f3eb49dc771552d2a936845d68d4f1baea547cbbc8bd5047576b201a6e41facfea2925f6bbc4035d4f2aff85de217a9e2dd130b79ea894bb106eea43b255734ff322cc7359f46e0a063f27d3ccedf472d61a0b5496b7d003daa0b566cfee9ace08e7e206212400b1c31cd1822778d24c85c20ef8afcb2f4a13fa0e8868c862d37b0b3555de7aa6c9446154a63019ec5db29c81e1d1572a1cf6a700c5bd151700de43b8412df6aa15ccadbea570999ca7a0a80c414040516d97e20cf16ddb4396cf97a52d5a4b8f148f4b910bf0f4f3e85e26991f38e0c6cb0dbef75a25c250df75190fdf06e82a57455e9013fe779392daa3943bb37f3d4899ce4843260675828f5223ef0a773ea0a0493d81bc008617a9686027550aec85317e4039875349f120bba89d54a88f8d408a3341417b4795c622f41b4ef0dbe97af3896f4c2079953c0946b4cdc2cd9a47bcca8465a796d1ae7200e753c34a5145a6246f0343093310a5f0c1cf825f2f49202002c59622c756f81a44cdad91dc0053ddc3cdb15b0714f8f2ec39260ed5c3b79798400941e9fb352fa1ff63e1d8e3d537895ae9f0e76b7993e9d1cf1875d13dd76b5d34ce64dc893b45846bb641d682e6d0c5de2a1c078a3d0769141b0b79e94abc385f1c037b9a081936ddafbd6960f5e82696fb55ea679eca446fbc0c72e2abf86e47d51c0d04f304148bf29ee94ccd6937771a1ea0fa89c1b1d3a62448d718dfef579bc8cf40996aebec8b50f5258cd381d492cad8ccbfbe7adc605f808a7d908872e6846b0b9253caffce3798658094900783059fd3739b6c20902adfb4e1c93059fd737a2b7021073cb18b53263236ffd260d95e4a9dff23b13eb48fbd0c59568ad277bdd93f115a4fa8d847f79d6a462472f9b8038c81d3a1c4ed15d3b90df5721f8a3828711cc63b253e69b2cd3904faf8c4a2fd8524e0bed0846b69502f3d6dcac20d9782bfa6ccb7d0afd4edfadba1a872d7281982d36573b6f03836f9401546f0e3d19795b9c6afcef4c642ed269f884ccd8de29909459b7b2d903821a94f0f33c4c8ffd2a4de7ceffaff36a8036bb4b6f52dafa3ec34c6eeb125f21e394b485d62491de88c8eeb29bf61ae11e62091b0e618c58788950401ee29aa3a0612d4ea0d0a530b1c53306c30a905c1bb750a3d774e9c05217fa737c35593b8ec9d1b39bbd894657d4454a728b9653b5f5714adaba5d963d3be9270d28b7e61e3f9e96049144a5a5a8626c691d6c5dbbf0279125e4b3aadd0a064b95517e65d0666d8534ededd6d84a14e1a420f0c1eb0fd25c64197404772446746b4b4d26cffc20b3dee9b9c9c0e29d495391e0d5eabe994f9ff5dc67c3bd109f34d23bbca7d6079e9e6d45852fccb1cfbfef827b5a46cf6211cfa68956c9b61d36fd1e8d7d4c7533f6fee9b379a90d2b4df6983d20f673b09a2ef1348af9fcdf4f83e61db732a69030dfb8df273f964d293364b710c93e49ec315f8bf8fd4ed5472cc333de79cf1cda1b370382ec3fa2a1a3afbb50eaa4205153b7600edef5ebc7c386b2cd837f14a22ad3ef2016d1279779e1c9c931b350a44c6ebe8beb5ae25ad3604b4c8a2ef2d7d9922df84394ec7b6929d3acce17077f31852bda7e48e97b825eed7687e34aa1eb562169b0508747362d69f2c39dcb4cdeb66c5cd40204ab0f48bedd7f51d4b89a202436e4864c0d854d61c109f2798a19c30b4029b42376ad0e20fbf86e4854cca92936ad3170a1dd4ecf9f1e5dbf4ed78ec8fd37885fc1149784c7139a7cde8d627be81d182bedbcd711e1cfc98c704ab76804ba8b00a82826fbacc7353d8f5d60ad4f7fd28479262dccf0a261503f44e98b70867d43e48e3be5320a98e6878c8515ca411b643d9d6543aecdad9e80f995801f7a44438b1c1e336178433677299f07b511eae184ab7a60ae4068352a4a8528c2309d72ed3c827edbbc8a245f5cb5f6896c1267260a8b79eedbd41f8323ff561752bd41a34232381e76c79eda39a716a7a486c109b80b9c314d1d438404fe5794b3c59d5c072d240ba81b5a5269b7cb32ea2b95c516174e271e5e421c20a18c42505e9fac9bc07a8c2008b41a600de2042f19231c8f6379fe21a7a58445c47b612da35d87e63852ee0b24939815a04c82b0c48b01071c8fa236cb1b9b9a3282628e1f86bc31951405906996f1e004a5dd75f14404b58ac4a2e223c8a477a1cbd0bf48b3dbe378897ca19b036befa708e67dc30a45280ca00892de055cf691b3332cae3d592158b485c8c53f5ea9aa30e5ec05f02db9afc05d41366d285d8ffd9dddf2147538ae3aa012d6968e6924711aebe68ed3d6bdb6eae5baf7280594a00b98dbfc3c37f6d3c1d7afe5c8c7ec303c981c1e66d72435c3aed50aebb3ace3800858382a019202456d3eef12bbb7793cb10599ff48bda89eb9c2d0af2fc837963afd93834000360f5db6b7f54faa1d8a221e949f1b5ad9b31a43b8b5f09647778e0ab4127fcacb89178b8336eb7eb45f2a5177aa314fbd6267c0172a1b120f510d6149b1f282f540efd603baefa8d060b1a913e1ace21c782ac6318164a8e0be71fa8d5a4664d0dab6c94ba2fc19abf069517aaec7fed1861f1c2b8da2aba75c01b1bb65e0b09e413d12aecfa6f802da5453b4b69537566bf9556f7123b9aaf0b0de873ef57668093059410b38aa9c5c2aaf2ef0d990ae4529a1c18aef6e5a2abdffda8594c6668aa5a5235aa00a319584d61570e8211ce6a6f4a015a2b17d4705941cce7187d6e263fbebd124643fc3191c89cd645070831eb4856d2007f9f4dcee60e3b1ca65c2c04df51059a483086f2453afd960b0e7b646a4e1b88560678031e5991c0983df8c5c3c720da0e3c664a59a5dac23f100594013dff09672e27cb0aa529433a5cc8b045773d99b0416077c885e60a33b9b14d83acf1d0b6539e326d7c69bfaba66c644e1218fa3c42262bdd16821cbed5b4b95a29fae91041f31e3bea8998ae70b86b14cfc6a0d0dcfbfc270936c6bf382456f5e36caac6887526a5c1632438cde9bbb5d5fcb8347f418f3efb6dc7df98e51ad96f02c02978193a41ce5d77d0cf0e1f906b77ea74beb1be66b391c79aa50862963e5f2e5b007174a9d845571f01ef4f8fbd94c793dcfd83351c24fbbeaa6d9b85e025f8d578d107f69050be8b8b0ba873c6d2239e1e3412d8f5c46631d3a4122843bb5cca95e20605fbef9be6883db3ce8d17ab256f98136da781fc638d81e8ef73350a681462dfff19fcbdfdd7a00fda9da7b74d53061ab2d74a6510db9f11a3fd2e9946aa3284c0ac5bc256dcb86ba8e12a215d5379a787d95fe87ea2363525b16967adfa3a976656f642fea74bd79c693188164b6959a81597cb48552d989a5c11d5571c2a748b87aed16dcbe88bdd5d1c383d208d95ce60d5ad23a9d583bbb183485c2dbf63504aa6e70bfcc687c53ed124a02cb0cdbf4603d79f11242ecb6deaece666d121756825c2f9c5f60205444a888ee7d26c4101c706b05210e4431d4c9bff991222cdc56eb7292d439f8184bef1f57e72f1064b02adebf3a1c33f0e522915b76021c2e7a96e0f0792f171534ea28001c2e137f94d53e253cea69b5fc514c1e398ec496a7c43cadc4184290037e97730f72be266b64e9124a938f764eb7364b60e637631a0eb07b8cadcbeefe1a228bc9cf898a0b01ff78c80ebe080f9e8cef5bf1e7b4d4df4a1c5060023585acdfea31f7cd569330658305f5481c7aa841a058e04e2cf4bacb12ba5b9d0ceedb837ab2f2bbe18f5b5e46d7513890c6e04c54a494577eee4422802198341d9659e5ac88869cb174367db7962fc7b60faab19fde96cfa0df89ccf0f44bf513091488a4335c17f2e6d2602275570d29ea46d8f8acfe56d94bd9ec67a9a2553c24c6b01add8e15f188ee31c84d4b8e347e3ed4a29eb790b3465eb11061f9bb478a3fed550aa971c2752fc91245a915dc04d2b879bcacda7e8da67718f083691ffda4df2a02615bd1d5da8a218c9350cb26d2c656f34f88fe17d599204917fb4f300f819621f4653ebe7f9500151fc17b5b08e1b43d208d0a2e1d4c4c11bcde28b874ba55c674d1e3f372bead6a8b9b4b707e87c4a1700ed877bc08f4dc59657a89c2dc01a20ee7dd92e1ac2e878bc80fd9f4ddbe2f31b2d8ad26304bbc5f62fd958da003f2e5e6d9bdce718d0e65535fd197858736851676399e5726b342d84218788fd40338c1e66b51f34b8761db73ca8bba0095fe237b74611f57f20b3e6af26296da157d2272b9745862f0bc69c35212dc8090306e57bb840667d27ea5f21c94b97af4065f26a1fd1c73727b7d5ee7d6d36772156b73ce8f23609c3f256c046b1696f9216c72c93e23c4436beb383d488efbe96ca12ca7e744922c25d88d062791becda3c65dc6bfaab6fac8d0e8a7391e3362996b49196d16bcb23f427d2d3ab6e049263362c5d809c541cd8aaa05a39f019248355d536dd87c237c115e830288d3aa9cc8e9eb4d7ee65b75cbd7acfd86b6ec2a11953be654a52dfc3ed4b75f230ae5071c3736f9169ab1dbe03c834b8a3824b64105cd4610c23608a8d19432a987e6148333b0fe57f2e3b10cef3b52dd0c43c2d4c162ce8408a9ac75db18473432fc9b92eff7f04b37d32bb74c5f9489a7bb9bdf2f264ce5e20dcd43bed45519b60e92fd8d4d7efdcde854cfca6c07eac1281e6453548c1ce13380e0774c5b8d6e6899bc61a0f7c5d1996fd8b7b93e709573d9b017c906ffceff448b67203a7138b1238b1896b782d60e1f2cd8208eab93012e11932dec34c04391f690547eee3b2397d389f35d9f7621505b1acc9aabdd032f4beef3f162a17a932364824ca9b5ac37715a2442ef7cc15b8833c7e4de1107963085c09d09507b9fc770bf3a92c6c9da7ffa3713c59928d87161cc6d84bc0e1588b2b77e118a917af1a8aad77fd64a40b59eb7485f4f4049ca22ba125335a0d33050f9de5c23c2d45011856091cb2f518e55187249cd51a9217294bd0ddf5c90e0c99e8ed94dc9f8d422924a512635164560078ce0f1db991021837f85e8a9acad2eca576f02d1fa9889d57412da575fb112b4742c7cbf5f93da58610540b64e4e4269a566224dde75ba2d86943b96ad99403eee80e683b27954d502f3442e8913b8d75bbcb4940ade7c1f9091e852c2083d122e774f230bbed2981f9244f8d1b544e46273f2136d71437fc0e44bce69d6184cf45a4a19e65eba5f3e4abd69b6fa5794e3388cfd3d121871d088cdd3cf31c83d8b0e7e791366f35b40828b62c3e1094b993d2a2deb036d64ad9552970b1f98765facff82a16f329d75aa4d45ea339395384ed2e614c2e047c8c6ef682b42893aecc1c7d9aae5c0ff97eb28a16502b9521944c53a6d4c051688a9f8184cb5bb3a6e6fb9f7fce0fccde0da74c9e30ea83e7e7f6584ae0338dd734237178e9503f3d7957a61f464751dd8877390bd194eb42015bb68268fc1168b1c627d153ee5d8622364a2a146f76830630ac080ccb293bbf2e6cd0f5d591ace984cb82e629a2958b9c02c372fd0d5908939c4e9f0818fed0a48fa839dea156cda58eb0fe269ae65ff9c3b69fbbb9743944d80ab7b17761ce373b8fb06e09202a6b5ce61cef986b83ba2f6b414908037fefd5658d1b228d18f4cb6f2f711ecb1b9da609d967e453a124aca0423cccb4f87d5a4abd652fefa3c797187d0ac175173349c35f0effab17b4d54dca1952a0ba05427909f1ccebca3adbb58857a3610daea9bf20c07b03232fcf7d6da59c0d8f597d58a1ff34bd70ae6339f103909be85b13e6530330e7146576386c7632cce15abdeb88a84361584acd4a171c37c12bf52b9d4f94ad596626fa3e458162aae91af2b7c400bbbf0e35696dfcdea0f9a40e416484149fe33e1babeedba3ffbf675b6e3221d9091007ad1121db6f86172a1ad9270b20a84756b593fe40f981fe32ebdafb02c67e82aea32e42c36fd4b773cfcb7e618a62c617076a47e08f07e1893b1862c8aeeacac85538136b29d91b8d2d29d240066dda14dcbda6da97ffcc0301832d21829773c3e420efe6a109bc4b8774a0470eb0d65ba18f004847a810fccc84e7c943adb1e48e6d5d952eb7254e6db3a3192326ce4d8858cfb1600b17c81340a7db8207eb62d0536629ef8341b875d616f9d5c5079c42d5b6a579618fc0b4fb507dbffc0932c3324762da2548686396a251d1982a50d5a01faf1baef50b8d4188ed0636a3d5d348ef56a405ccc0a238933cb3040db2d9f2a60e0382cfd8e81f6cf4393b58457e3d0c8ccdcac042fcf65cbb81d67eb7d706d0a06b33f38edfe64c46aaaced93473d712a88be43b865082bb06b9e98fcdbb4f57fb964729f90c9da182e2f8dcfae333dec92264da577b36690855fa94f02712778a61b43b900e4c1cf05dc4f4972f69f74e689e302dd8156ae8200f403d4cf712faa99e6320a9f9775cf8bc39a1644398337ce0859362dec8cf9229f30d266eeb509fbc32abcb4681d793a43fe1e65e029e3d2129838ad809a44b4a49046c73595813e075829733bacf993fa943320b878a1a0f2a7c605bded90e542a9395e1a1c218a1903d2aa085627981151effe06cb9aa0e88b43448789de5cb382cdd7ad2cb94e4aa9389ffcc044b7dea5062e7c1eb907b75d057253c4466a7a341be33e72ff3afdf80e1e427911a0d7b8d6aa48a4c77efa064dcd57de04c5a0d981543b089fab42647dbaee584536d289249346bdf72e70886ef0697f56fa88272f4eb2fa5d9cba96cb560c266fb648ec83f4352c40098ea9584378b270dc509d69d62366f471ee7b424a6c0a051e4696a58e09786b46d0d6081da9974b7ac7d764c67dc76494f66be5babe401d18d802d664a56b698460d909fbe817da956beaaa630389683270543677f0554f9f26a19abbf52985df3373cb41be374016e0ee256aedf4a7bfb209792a7ab7b8998356c296265e1a8321449396c7f43badc2d772bdc39df418c6b9bb60f1d89c94f7748a4fe09ff849ff084c5e01c9780fd2e163220802da8dfdc7431fefaeb3bf57041307bd3798458c46b56b3929a5f37b852486ce1c178b4059767bef92495dc3505146f0052f61a7a769d112f9877111fe8bc8541d3173b9bc3b167bc0f336b0f690645bc0893d6c3aef1ea54a69a9d764a0f9f1bb5d3a3e6c9e3a56af02c10d661801878362a0e15ff6d5986e57d8c129493b8f5f6bdeea080dc508e93aa368f52ba581d491f45bc1c151d83a40fb5efb64bba5412589c46883e9b18dcfc06aa25c471d2591951e981c829f572b98235faf23f5aa48161ef62c2dee6bdb7514cec40f8843645b908527b661aa5fe9ea2c0e8fe94954270a28b3ee8540e7a5e969cc239cc73bdd8213024cc7e80e7562f7efbb3e82fda5116fe00af23ca2b192829d3dd555b74d8d89572a37695fd38df7dc9028a0369b905afcd0e7d56192ebf8bfb56b89e4ee44b072133c51d4715b6ce392aa226f3cf6781bc86b4be964269e3a17c0140eab50d16e82cb6791d772fac1a9df0d7eea94296b204045fe2081696865848fc9c6fdcadf2be461c3d6a65d5ea7bbdcbad3fb8aabbf7d2611c44509e51c72dd10065ab71f2fb7af6588ae2234d59bdcd5fdaa78c70eef8540def39af5ea330eed7e4935bc017e1fee88a5bc1e35b228ab02a44749d3fbd962a132719d4637f6b85dd37837b1baba61d6b0602d148a1dc7a81246d8124baa0538f409c4ea1b4a957b7f6b33f4cc7a9bf669c1953e42036f9187aa5a021d06b652c3b45c305e2220d1b8d8537acbf090e9cfe24ecec8371e80d35e372badaa60411052d0dd7386b9db303b7d0e6e3c6b75edf52bfe9fed219ab290ef7b3d6405d16ac9b92beb6605d1316609f33ed8f2a58df74cdc08e8a93e18194480881b32ab5b4cf331321cf1b248d778da585df2cfde70e49be9f1f7c697ff3cf912cca7b7ce5e00342af1d2607ce89e16b09aa7d8f5effa6c34be3de69860bd11e3067efdcdd948efdc33a60fa1dd561bdcde4c420a20ff060a04f8e4616a4bc10f20f502c051217ac29c5354f41636561b6f941d372ed706cd9440ec4364a7686ad29dcb3ffc5b47fdb4792ef40a0c4968c6d4d80af974da1b8dd43082245fba6631627bfeded30d62c45de63336c3f0dcdbc15a63181d0f1aa59b40e5fb4568acfa1e1da6db85b0b5a2eb86f25a03d59a347f39b1ed85aaac74360629b9e236f445ef93c74fd45f4ec3a8f93ceafafeb7cfea61a04bd9890c3f05fd44182748b2f15140b6f4c60140f96842dac8250f35a9c79c9426ec8278ad034d803b5be0aa0bbe2677525e05a1ada6ae3e9a7891262357d1aeafbe71663909abf2211f96379fcd097902343d90d2a18d67495c98394d7bbcaf4ebc5b072f7b5812eba37211209c8e1d51a5703e1ca52b25c5032941d8394deccb84c6fea44a786d58c57e62a4a0abd7b5abc4598d00d6fc9379b502131161959f94611809ee572fbf484bc5e106c8f094053c230951611a08d2be255ea609d75e9609f0fb482a5fb39b6db4bc4d84fbf3379e9cb2e871302125ae02c023367ccb5083300357061ccb815bb4a30bac592b6ba1193e00f80b9a9ee9eedbb98bd2facff279c2e9b444ddf009f80913e3e92cf5aaf309cd7f3348e7873bb9b07d71546eda135644b29271c81d690a7765f3fac100410cae44cf17c71b5c791b5e13b2282393a0b6389ccb5c9327ffe44c2eeda6a43f7238b45613c928275736943ad1566a534f775fa133b57cff2103befc7da9e65fe2ef5a226b7b80cb700cd3883497e129018a43875bf6fd327e184131cf8047fa029528244c8d94ea4b35124ee075c35ea4eb87ebf83723443aa22ac384ee5a758fc482090226308eed4cd4e5d394638cc3e754c23cd7b2fd1991a5909ed4553c4df2da49786259dd9ae9d17265408de5e6db42bf256e0c3abf77d9c3f42b0899de36c5f3b9a1c0bcc10a30a1fd762b1489f188e66841c0e68ad08518546cd74cd0dcd801fb92b2a07763dcd48a2ec2f129d6eea9d626f9c6d4ce13032e9389ad5fd9d67aab345f893873d044534dcb038ef8d620390b9b3ac353a8bf9d7324eccfa664f7608d16995be52b6a16f015eb538e4153e832df3fc8467afb10d8838fd941ea93da5e8ea53fc912b23f6a96eefb70ef04543a8cf13d827b23225a4610c004f02c4b534eea097a8a87ce44ce5c41fb40b0358fc9f6f65fd2179eed1cbdedd16259b0b137ec0554451cbe143133cbb640a5397e4ac2f626887a334806ed4247543303d712aec0d2bb170053b666fd4d7fd54f911f8362b36be58ea08c252dfc85ab18d36e91c9dcef3514a9a02894c4285ab8875dbbeaf465c841df275461d972b4cc788dc41c834342d3c6c6ce2002d66008549e2a392149b238fd0994cf9f8a5b3171108aed696fe4751dd39d572a47f248746bc6cd20257a9bc8a5f3c0f6b1e5aa2f7d2f7247af8365bb05913ebb51f934950b25d19988f4574a0532904f3e103678a9ec8b5a6ac4cefe742a26eed767aae0be2a8487482d9b1d2771795fa4a91e8d45c29fea6be3c97095af541b9b38e7d02cc14751c730eadcbf3f0db89a74488c8a43f18f40e877b72c79e981ecf1b7b21b6014e97160a71f5f011c0f4caf58757dd53264dad6bab4f857fcbf8b216947086fdd95a2712d56dd12aa6aaeba6d6c9d0163b71b416e9104e4110867e9ea41451867f627dab77e21ceeddc40a0793bb00b4d1d95019fbb15bef746b99969f1db8236eddfb68333aecd2f97e9ab41156786eaf9e37f665453a283b3948fba1f4424d68c16bb675bfb9f57597269f2ba083238b7546e68596718947553c612b3f791de5c03e1b185770e659227480ac9e758fa196cb12eb50980b85759e68b30271ef648522f95972c7888dbc1f35759f503cb778e93bf2687e9ed8f073bd85bdeb5db10decc1155224fc87daa6c8a78b8e8b315b2885fe6d38f32e3eee372f5564fd8696fa3eb46abe1fa1aa96d5a730f4be7ab2fecfe7fb3cd6db77aeb699987d4389d61bbc554830d5e0a25811ece56ff8349b4cfc7d8b53afca79f9f18498689785dc1bbae3655d9f6451e87dfeb44884b00d88d7e013cebd5c1faac0b61b736aebeac014cf696afaa78e1592a3e14015faf3b0957e26f3ea141dd01a4d8a0051f93d59e0e3a7e1c641c6a6fcb2e9af0e598356dab2ddae0e02dfe54895df7ece38b56d601b8b7facae0001fc57c0962a270d45823eeb75adbec7210acc88c5732994658caf9deb2294e2d92eb03240a6d228f1e058e01749e01669be6f804f9e6fc195fac9e0ff9ba61b08942b3f6b8f241e8deea42eb989d90f89ca92bacc5531912d24bece312eea892388060305dfd61c4e9933ee40072f6ca46651cd138f05fc5b10fb8cf26a68dc8734b03c282d44599e8f503f59b2ba8f2990da9f66527bf2a4d60904e28c341685909b65fa506ceb15ca4acac39b002351d295fabfd95bd764bc9c90486e207f4d1e8353d0acf135dda83b8df3b0f7d7b4dbceb3764537a15c456291d1dc276d6a4f8857ad453ea1def8244b6c1c8000d3f84259019bfbf7f25ab178e92c54845c026760ccd54306e44004fe193aefa7fb19ae06527410b65210b0582a7e3fd072e1214465d95d255d69ec62c3175c82cafa9c5e3e7abee5470c74175c980489aa4d8e649b6aeceaac7a687e5ae6bbe63609d1edbb5d4ff764afcb34fc053bf94dc717627ae8066ce700c8acabae200d1c928f424c8bfda0a2183248a9f560790a9d0643b990c829f15c5f67184f1445949cda7ff5122ed7315ac9ab60bfc5a7af848fcbe77492914f4abe44697dab2110c4c570798be6b4acbd2bb8b63877a9311a2784ff82a371d8fecb4eefdc0f15da739218410a6d285ee32c230b16724b54f453dd2edab5776874ca1881609f9bc752140ff254e40ffc593451c4868d0de50eb812f7fb590322eeaa25ee261cbf3003a40665134e2f414ad8ce827b4c33cc1ff67d41f4282a0c20b253e81f836246e0a9d6a2907598821cbbc4cf5130866d3ae6d4245eb3c7e051e001e946b1bf9574a2fad5a42e366b0c3efd865ab7c01f318810663c94800a62c233dfe13d19a12874cfef32ddf7dfa3db523e8da14e3aee3a4a3e88ecd60f915e508a78330683c2b7a3ad6bc1fb619a2abfb91e2ce38a06ff776c17b8d5913e1fd995b0e8a18bb8b3892e8455aac722c475157c89c7b31f8bce7d53348b5fd4f3028a39873177f4caf5eb9159d0ae885de37f5850d47d7c927b1b9b804bee22d317ef2dbee8298a808376ff3713be0c91d7adc5170ffc370ccd04a7de82af9237cddbb13a0e707613c5b1eeb86b4aa4b5d42122dc2b47edaa31e6c5283280e78265e606003196afebc8c4fba5f33dfd1c63031943925f1768d1b9216311583192709c15bb380e1b964ffabfe637ca0012451e52b23794a7be7d8705ddc79425eb9add8c152631b71da97046e08f57f110ae43bca97b94548f078c767f2cec68e39fe1a6b4c3fa8977ffb988045c0d9b254637d5cba0d9e27db29279d432985585b11b817d17cc678a8684200f6aa6f05225c9087136ccdcf3d657669349870e14ca389639bfaf779833e89406f2d79da9ebe760947b1e448e1128bda709781f3d7c2537969b955b8f812aea66e9e5a283c0948dad7f11f4005c877037745c304e273a14568e20bd8bd7b30f826e457744de08fe6cf366fc9fcdae976f80903c0126535119fa624817fd9e66fc652c7c995a9f6414e4ae018abc71e8fbefc3e2d77fe11ab30d992187b2e5211c032896f1cedb1a8ea8f4333207e325439fdb5425e5fed9a052b6a2b47b40f6f16549df975dedf58b2256b9eebd2d24c5255eabfe902b239de9e53024bc833bd05a25acafb5111041443746a6b839dd1e86f403935bdac8792275f9e26946012942a2aedd9a0d2dd3a8c2fb911f3f90f3dd7fc725dbe11141d2c1259042e2a2acce677cee374815b34e7e6bd73764772b85d4b076ec4326076f495003df47c50309d8d26ea614c9cac13f85917e87f026a87fad08c90135293ceda56de227b11b540eda382d65c94c1a072dd697eecab3ee00c7ede4b395f8588572ae808a98b29ccb187d30803d8fc76712df8e227f7844a0b28bd067d06b9f811a0f38eed0a89c73f533502bbb94451b958789d74c50dcc71fc70077bef8feabbef234b4ef73216735ac6c9cd82a0ef2277beb68ff66247445886e2b2c8c186947a59e2ce10d91c07c85532a7ecd5dfb19ee451a3c45545b7167966744092c8927ff50e0fbfb51c52a97accab69eb6ebd9ac084e74fc78709bc64fba16f7c32382f6913a77e39853def1700e4b3bad7537071844f24bf6a12a390d8b2b3f70c24ea4c3565e34d358523cd7e7db56e2c4968d8b5f0afc36df1e424dbc8e2bc5d8c81854f89fdd1d95e42496c9dc915112978d72fd2870e310643f0e39f77a8cd50979e55e601865c529c93bb87fc02aeb5e5dc9d1f203459df83e59c14ac5234a8984ad4426d927bdc972634af25ea09064bbf9e3baff1fa55abe3b57f07c61b490713e90f531fbec7f4c81e4cf43f3976e92b9459b7f25f6183e2389d800aedf71d3b3a3477b43a30f91ec21f2384bc713b9cd3824f225864799659ce6850810c6a3bb02aa5070910971832306d5718360e54196dde3b6946cfee562f5dafd6521cbe0d628aff25477e2f9c00292bb49e78fd8a79a9b86d5fb2f0206a9a9c7fcf6464e7c584433cf704d19ae0a0df5aeb52982335ea575fcee7931d7827865f5bda0b70b2200e33482f22b8628249eb1635e4cab0b8de59988e8de5b6d9234ec7df8a44192bfe2d8b6af35b989b907fcaaa40f1fb8fbacd6a0b58f0bfff30bb9eefb65da34db63cc086d3f44446614fbcb8b9e7ce80d09188facc519c6539c9bbfa9dc5daaea96799c382ea2d1f50660bd805d5d2e59fcb89b968156244e7911aeb166b1e2503444da7c8c2906f1d3dcdcb971a5f667645c8b33ad46a3aa054b27b422e82af0de65b93cfe6cff34439cf7566da1c98c6e29068dd784446b861053b95f7ed08118f116d2fe68ee3010e29794a3ee5229f08fe9626ce8559689f4c5c53d191df3103b1d747f8432e3655fc8988182fc8996147b4778e13657d4cb02873f5bf71e93890ea07bb13ac14c89af4cc4647b63ff1103208c06983e9eb77090c00e4497de9bb04afeaaba65b03e4176760063b90333ee3ed925e2284886a5de47c8d124d1475f0362b72bc34f739ca7f68bcfe60c2b8e857387582e087ae55a07319613c6f8c557752d4c1b1cf3793c5059b9671618ad1142baa17dd6edaaa02d15e0519df99f2f63d59494cc91c2d434f1d816de11e9c9990cb424d9750241e33c0803466baa54750a819d6c5cf6837c5db909c4aebe413ee7a5a2a55f74bf389ec4d9de675cc025b61ce40016b4047b2f5581178c121517735c798378a0af5ee9b2f012c9722cdcc72126e49179ea29c3e9740a1a7a283bfd71c87190ce7495da9c8fb4553b497c83a656af3a83a63173b59be99f19459a721ef19fb8adc450af9c739fe50fdb198420ab7bac9f9ae30948807ddee466e50e3c717cd9388ca6ac0eae6852de3f019c6417667132e3dae29aef098f901724987b550d43b11ad4d9f3222f980cfc30c3c73d2754b3ae67eaaec5db7e38765a3282815103ce95ce63fb618193d351bf1d4b9230245ca4c8530df74aa359d5992e5397b7102e8fe6b32aea6373746725a739f0d31ea2fa28fd0ff94324e85e1fc26ba3750bf2d372493c54c2b97f830278f738b63bdc81fafe3565ba6d45437efc019e0d02d46f916c5886b518b23fe50d9308e17cb54618ad3a328d07a05f1a82579dbab843b3b16dca087e350724686c3bbc07d08ee920972bcfdf0eb01a2a23e5d45b2b86178c7f2221970a04aee2cefd06bcc4db9359d48de079ef2200a86b41c7123c0a0b67b76911f483a1a6e6dada4f5bcb569ea409d548fc8f5199f2562fea7860d18aeb47c15c90e447383d8b2149cdbf6eda43f8ed76a91b8d3703304b907631460dac121675edeb6b393a3afa15e5460f92a297850c71b44b34b0d5758c63bc2eb72933aea0fa2af3b2c7798f7c0f7b6560f69a150ef418cad06b6c0816b67cc592a82f572e100959a1abe4b4a21186b7a8d33621884e7b3243f9c43050b3c72f6074b55def9b246feaa84bf79ecd12ef9553b935e065655ad60007a923339d7521d57fde66222380b3e602f748de99164c6c22353f50646139d3992e72224ccba2ec10e6baa7ca112e9e58fa87dea1f34ee4ef111bb5c33d66f1731a2daa113d3ab423559c8d9a3e1f9c1663801e93f7ac51f3f568aabac6252be0d5f43d29628f8df5afc1631ad43b76802ffecd66f1b08f7d34b5fe52fd6d8bc7d0b42d061eb79b7a975e7617d9d8a5f0f3e9983ac83d09d8cdb540f05de6241c496c1f83e6d184531f0aa01844343589973a8777f9aed86ba2abeb501352ae30e67685c1358b60f53d5f7a30e1970d4272f8397682a3abeae86797b1c546a921566ffc2861866d81a7fe628c8116b1ffa1a507cfd889e5540c7a3bc7aeab0db89b21806cb0e396d8d9e8790e366bd27de8eb197ace8aede9fef692dc7df20ff4e4da7d5573e24ae0e2e9f136cc5537fe38230a0f889042dd3c1c6443f62c86c12641a8527997e16cd1b3905f5b916d65df9e549278dce8afebecf262adf7fdac5a5834cd5840f9ec6318dd26044fb5141a0d046f9489750d17b0a7cb1f112b31f9a312135b9b29fdc1273108fa57cac508af263fe7c962f8539efe3366c5ac57256587826da6cc8b2aef45231688fee6fd6757b029fc058db8575a2881f7cfa5550552b72964a3c12433393c89686a5e52d958e611917a70cb80adafa71291875eadf191c871594645ff190aa48be1f187461a0aa34ae80dd2000456f8b560a20c8545c45385a032e8b8c4600969e9e225e34a595f39efcae94c8bcc7e1958e45c9c43a775caabc414bba1fb6c145d061e953dab248dea4aa7da5219166b6bd911a265cee3f6439743b3ac2a7ce942bf9334781c3cb07d834aa08abbcde5a36f260e49bc5dae24ed9fc7f54d3884cbc6e3171548461b0b46eebb14c28c873cf93323c95ef586eb251f7b48462c324aa821890e2116978905903fe82e302b1b9b1283d613c7ebdf7a4d573c5da974b81c961dc6210e15da90c8175e3be3904de3782965bff0b62e949b9e2f16b6ac726987961d78e33aabc63d1f77228a3d5ee8d45149acfe7736a633f258fc8078cba1a10138788ea0e32f3a975bcdefdea225978ec34c3a96151575fa3a529cc19356394a02f88b0e9cc56fad3efc04e799a0e6fce196f284c3b1bf80b38cb65690b903005af99758790b47d66678e966a64bfc5c0a97139465fdd7936e81819bb84253ccf8049cad47e646cac458f692119358122cf0ab84fe916c40f5ad992c7343121b6371a57b169b6b953a30fe39f33978750476b6902221be0f5b3790fc479c48c60943e3a107d8029d205c8c18bb8664890b070cb1e4edac0e9ac61bb2218ee25e7907aa95877028513ac28a598d3a6cdf7b1192af255c87f17ddb4f4efb0a10712bb45ea0c21b2ce548d5cd02c8f343a4460e18abf032fa9100bb659586df9f5367ccb859d449b30504be1f95ca20baedd53a66b9b247994f9d927f9cfac01b293803ef8f79cf1a379cb9d710ab69cda346fcfa46dbd24d0c399aab4bf38f66f10cec2a11555330acfe9bd1c0886e65e79ac82486c741104c81b431fcdf9745e991692c2762ef92453f0b5ac5418235bb5e27a00f063a3e6a4ef2ea1c9760c5abde336cb7af019d5de48a61f82dcdda3e75959db0ea6598a00e9ef217668f079f0b8451c499d2ddeeb0ed9ba22ada62e3500ea1811b82ce22e01f480eb6ae9f3971ed28d466b7c6d00ab32f7b9eaf37137cdfce04e7260a3a3160b7505995a17e467162aa2ff0d5c8ba6cddceaf9fdd4fd7bb3c201312e0486f264325354ed947c3a45e2b8c477ce9f4fdfe6f4f240e348cdc28a49b9f87332d3e7523526c352d875ebcfdee784073df9c4f904a141ea7c085b8671781b396d813e253834f5d4e2050c7b5952d9b4180c0b84cb83a62ae6fd3ec04f35e5a6009b2adac9aaa0bd102b702df4598e639312e08bb5a54876a272e5da72524d12ce8c5b4a725d133ecf562563b0048a8ed9f90eb5d811555b1a4b3fefaec1ff8bd215e5fb200efd0c75f83ff852952de1b08547e4e1248ffcceeeec167e4791510d0662ba2647050dec302a275e8496af00307194bc7bc35f20f3ac4b380dbf7bbdcd56cb46ec001eb31b7b5047be96d8b68b400574027c54649dc7182400ea2505efe275f3e9b87cc7357fa910a5fbc2dfdb069a9d2cc7b112d75c516eb2e947a7932d283ef9b0745e3e5fb136976fbe3db0c3bd99539d51c50bf53a796f4fa86957eeabc83bf845e8c1917823aa4ae6ca0a9b8237a3cc9a53c2a33b1e8653de778492d8ba0264d48667feaa775e8589b4fa473f4df16b25ca661f2e5ee42ca9cc470b8df5d61a39c1479fafb6e48f4a5a8eed80325139b47b1695896713aad2d7153df2ab7d142bc917efa2ebb9638b8a6a6da83ffc82466d517d856c662581b4b2a7ec95ef8239058823fb80287d0681f1fcc3e3ebbfd309167e45a0ad128828ba3342338270021bd78c5baf8f9115b082dc6e84e0bb678dfd0f4b1faa83160c3b1bbfb4759f18271377fa5edbc98c4403deee09be8287a685df862e806b67e3c2aa364e3a04e4088b7d380fc2d45b9a7521b14e0ec8fb378eebf7303db79050888e03d40e5c5fd923e8748d0d2ecb9a9fd907a73cb2323e1f24780e93be2eeb8524434c0c5ced317662a6cd7713d655ea64566ac9b16c62a09f3b13af0c24be041ca03f1c5b60d43de1a84112efc04d5c32b9297334fee7f5032b35ab25942b3d2a92ae010b93c490ae33454da0315fc98d7c3381d7ba1b1ee538e4ab8afb64e7be207ce508db5c5508f3c75d561532e3c159e52d357981ec36857c6cbf7bcce49d25d1f0fec33b89a0bb442514598a7acacfeb57b122f8387fee065d2936bf871b4b0ef8dfe2e99b1589dc9f14e09a1400b058d2e0e8c89eb508bf035c6249007483252eb9327126312cc044a1850b80a22b29298d5d637a1222439d3a55fa96a776c59b991ba55053da868eb24e77bcf2f90c1d1ee69af5ac5cdbb45e0d9f66d0a8949b05cc0cced1f99e7f668fbd84b868ef8ac951cce9b89b5cb70545f6d9a4f37bd74316afd51b6f5e75384bf626228dca63af6c420d32ffb3c69d7aee1c7138697fe54352de8ef83a84c2b0167872db838b755dbf971e8984bfeb9a71bda0ec536f880af36f99f10fb32c44a4c2c82ba589c016fd533feb9ae5e7cc772445dd32005ce64d9956ad8d529fe0ba9f8c0a020de589b4d6bcaaf4cc55f81628fe8c9200cfceec7dd998bee570da80a9edb403efe5684f6bdf69f5e5c7c5d5a6d05a614de8a98d854bea083a18a9c1c4b352baf5abbac51641a908c4549aa3a993ff8b501be89ac959ccb19bb85c1e397c21e8cac56ab56d460c580a898921e869751fc911e60520c5e88281de642ef8c5e1b21091efddd541a98b12ca038756229184977ec7fa5061f949fce3e5bd9c7897bbc779fbdf869cc90f5d8f0ba5a7ba49a57e013d32078e11f3ecf265ad9fe570ecdf39a74eed152a87cde2521b3803325b006650f82fcb58cbff24b3c546b910f3c33cfe9df6e61182157da32232eb97493c0c6c659c807b9765fdcf5e4e9c6ec62ae368071eb058d2f7e35999e4dbcde09a844da576443b5f44cc9628677fd058ebf9af73837e6c86620edece036883c02ca615feff9038f24c6363ac924c8272071f9c279b031f8ff59238528ebd6d7aa59e70e3bd4dda516b5b5aea47e9e2ebcb356841d40f3b47e3744f6f24206d7d95ed747d73a78e7d67ce71d9bc3d1ddf7451ca0042c8ab5fd1a47894b46e7e053139cfc5ec0c99877cd944c8d76888e9a3b6ea0fe3176845b45eb81e61532085c405faa7bc262cd5732110857491c5cf005d1b93f8f4db4fd3946bf45f9d84a56c6f16d9561b6427c6a6354563bc44940499ecc892a7eef82e74644e344a1377ee5d49055c6daa5d9d3b20255cf3eb8fd459b9014d7dacea963c93462b2ed7731a78b084ab77627fdc760d9546965bc6bcf2466ad6fe0a32215adef0195f02a223b8ef19437025db2e9269d6f067a3ee0de68e86fd266d67353f8f2f9aacd08483936d7b87f8c872b95e735e4e8dca81e778a27133fa3dad4fb0f70756067646d542728e45f6402f6df79b7eeae40b38e462fa731cf90a3bef4e2b7d70fbde9a746c78c083bff0b52fad585d8940d78034e98979346d5771313d55566a4d2e8dfc27b99d2cdb497ead05887ece8868da4047f73ea87312587f9c1a2d12816c12efb6061ed69ca0d246e52dc21e99c90ac44961042330cb060304c101092bf2e05f1cfad4db2424e4ddb998e89b02fdfbabeaf44515a3af82532cc46b8f062bdc0524a04dda1f1a7d8cebdc5ed4bfdcf27dfde35c1368919b33e9ac24c19198487dc4cf5113c0b01b633a3aff6750187df87c29abec721a096da87074ab67a2bcfc526322fba7097da643785ab4f3a0620b2cd48a9f43a9e89744ac84e471dc62ab9037d86e4b887b26d9d64457dd99f4566547463e8482513967d2c04990119a659f0ffbb20d56112138ce2e6954ef25d32d659e192ed98fd4e80841f6b5e8b1b5f51e3baa45e03d199afbf937398411c5a5e0ed8623b7750a4a186ae37cf3a235e5bcd046b19b5452288298bbadf8f8df69e8d486abbea7d88c0d169f37b0fbefede2c92c092ef85f7efe6b8edd7824f565c8d7a9156973b63eabfd0987c4a1eab63b008ddc00ceeceec27d5229bc9ae072055d83284f1a9c8bdab9f41a4ff870528be7f3662b8c9bee745959ea4ce1508588fa9defced36f3478e89dbb237bddc9e30bd0a9cd438ed69239483adf1fae0250ccb0e6a8fe0d11deb51d1cb5c6e26840ae29bc367e698388f447c7cd343e64aeeec2a3e7383fe08b51cc634107ac6e148e579a5807752a03a041a6d7c6ff8457a5517fa1f7727a986aeac0f6ab6fda9fc23514fc12e5195829e9cc9da72b0aea290789da2e003753e1a5d0ca2e3ad6a9351db9d8afe8556fb1b6bfd05ea18758337f47e820839e0ab6cff548118470a89cd929f95ec7937eefc4696e55ad70adb332e3ae379573d5f52bc70706596160206116df82c9e4d59c79bafa81a0db5e385c283a241c83277d79132afb2a55da68a8c54521b4064a3df83939e0f9adf967640378317b570dc4fa9393400e8f5f0d6ae9db0abae3ec39466fb6ffe3aa44f8a4d37c47da594c395d007ee6bf9d44ae00af0e708d137f88b23b01a5aa564f77756eca56ee51ffe0982abc6de21020a8e5270a6cc9d8c0c7c2f5c39b6f25ca673e6b9162874927ebad760204b2f32b2ffc42cb5bb7b75200b29679fc1007db66c34cfb281e95ffd6d39ce78e0430c923090fb805a0ec321c3c5bbac07083998800a6098804ebae42eb8e83fcf715ae478afdcf16bc6e6771b15f42a4bd6bc7af6d58d7295fd21a172d95703c30caee4f6be5cb6fad227d7ace2d9ce50331f62d2ebe9adce12641150fb6b82a5841bb155559ace612fe5780385cd563034e93f088286826b2fab6cee3860eade355ae7329eea412129f3de24b26c8d9c06a8a8d135d258da69af5d57369cd2618404af67c710b22c5de2846076f481efd658c86a2185d656c4f660fcf0178f0f77882da47e501d48d781b09989d577750773236050e085b05087102522226f357e7d0743ef036caa34d51af4dd6eece648daaa24b700325427cbad0b6d1b821a5440360cf58897edeb4777a04318b51898f3c2b90d1d828ce38498a952e0562720a23afc3d2f5ea17bdd4826b1b6fee200e9884fcb4b805f29675bf889c734bc19ba05d4e6e0738e7dc6f2f7c1e7d820be949cbde5cc6fc6e2d7cebebec5b1939350a8b01939aceee1222956fff4e4ad5c79c4e9128b128082a668d407d5709cb426f8d7163d61abd4694f1a5eed7e0672456ae26838b0a20271339c2d8351b93cebc62c04c126333be6020b87c7430890832c55c45a71806217828d37e77a8b89cb2dd4d160af5dab914e3630e27fa37e723f6649b2afeef9aa3df512a44aa8e832bac9f85c25474fd67551cf4a95160988078467b6863dc1b547d518d1efc8aba583e548ee408e1adff481453833be20b95a7f08db4053eb437ce089d7ab65a3eb42a9d6d4a2a6b46e913b415dee58157a8ecc74308add325676480db593410a5d1cfa5fd530210dbca95e7c7b9ce4b655b3920a18a66f15467b164097541f3b84b04772f9516b88da914c23ef2dc3b8ed645fdffb468925235cc5b4ac2789f0bc5e8036ffb9f2a881be25b81dd7cfc31cf8872e69a33e5b5787683c20c60fea1ea9ffaa5a48b12b5f02e9159744160c369708e0edb6d821ed28131643aef971373c7a9540abe6f485cbf86c01012c36d0af4bf1a484572266e5e27babccb45bdf4894b6aeaba83e4b03a1f9806082105104ba4518aae07daf49622bc124f0a90f27204d8be003c8c4c78415d0257e8b34ce0f719ba8572f3938c4ae75ad8ee18b6b24ff3ee3a2eb82a65a246fad40a0e135b297d51423677154f5b6694b4ba867bc0048f29533205601c55146d52a7f41dbac53aa5955c9088d05ff4da14d79ed509140d86f11d941af5a4cc9f180ff4505304f04b27192b9516e5e323f2c3c094a80984a8cd0b8fa8ff376168e0cb2f73b65ebf8141b084b2f7d75105c491010cdcbc531a25eae649b950758d67aed8cd599bcb594a89a595c88bbf31ab0d7e3c5ce98f7d8b8fc0fc01f9b952af160a85e7c6d938403a0b02dd306cf5fdaa5a52e651ff73ac43ae694bed837808dc2202164663efe074a43fea15e8d0999bcf9e414c4d79645d086aaa9fd46507104c5417fbdf7cca4d161d7485b83b2c37020c12ab4a6e420ef800541e32ae1c011d33fe3030c92d8f5178c8733c37146f921e825bb06f00efa756399741c4e640440aba949c2934741e6fc50f32bb5287b3bd7b88ae18a11ae6a2b0460e7084e05ce967006fa083498cb966c03cbc99f04959bf2f5be99acceb268284b811770ccf4f99e010bac9e9772ad636d2cf56ac2ea4cf21f9ec480f104168f6f65bad892f044a64810ab199ee8f7dc2f35c93094ab99d794d8a4f3565dfffa740bcb3e772dc7b8f550b0055e9daa835560aea854c0386da1fbe4bcefbc3f639fe16b04745a7a66d6a586e0b31fba87ab48e0370afa1d1c9ff0bac9bfb3a3090a11b1dce42e178a7a2bbe3b625ea97ae835f7d24b192c8e874ea3a4a12c7212666cd13c854d8731cb0e80267657736d37fcc57b327e0fa3b1220ba04c1e4a79eaa0e58242dd6641512f87d48920e7a97dccecc9dd814ab6cfcb1b4def2a618a601ff5245b71d50823e3513950c3f25fd960db7d51e52fc7f5cf98fe9894e22298c5ae1b935c50643d5c7a46006ffa4d8a0b22a0fcd97020741a573ffc536eaf9dbe3c65d0f2ce745e49a49bce833bd4d010b25d788e53f707f3c6dfb3256b6580d0b1fcb5fb3e5c626818d0fe7cb329a7899180672724cad158828ebad8cd2d0d123bed53c8a4617cc8080431047a919675daaee8e1b309b7e53d186d28d3bcfe465b90110bf5ffcc89a5f4ea6f6814d512079ec14a68f37eaf6c2511acdf2702b08799119ad2ba32e88e294bc0fb8e0daf6bc061bc816c070ca2ced701a12aa37892a9bd6b1ca163444214aca67791d9de9221025aa7160906012306d6c7ef798ae099d90cd09c6dc48a1f2d2c44838901e16cbd6a96c138d10d6bef1b5846e01f9158a3acb34236583653342b2e8ab70f9e223b7ed01227c2957bdaa3eb7568919b688967f0078a53ddccd12e2b0139f57d1cd141665c585f77720c5297a32b12c1afae384fa90318b083987ef016ddbd526390306a91855babca2392408ddd88e57a2a0b17bf4ce85480e0b5b9cfb00c300448ce93bc7648824e884886bd12b71770607353742e3e343e4285c3cfb8948d41534e9c578942060292f0e054d1bcedee78e197832c80a20b8a9035557ac73f6d2bc2493955eaf37c6ce33231c36dfdfe3293fdeae48b29a39efad7c1b4140077a82962874ef69909f07c99d71f5e5a406e129ef7494a9ff18aa3ad738e98119247f4959d17499fc1d5de972ca324ec3bf1a8cfb32316a04830000000000067ce1700000000007ca819affc83e4bb99d1daa85cf762608d0d8b03471cdd17a1163a4294866bbac3f704afb2ad0615a5e6d86ac539f76fa1909b0762eacf487997f5d9bb95930e4481bf5c47fed1c802e0b9da9116f03bfb1f0179099263229ba830fc111e419e194568750354b791ee0a6c82e0c9178e8ff88a118b75ebc67f63a91fb01d6470e4955e107ea530b4c2db8a323df0a143d8a82b9cb9070cb13292268cb6a9e62841c827bb99bc522b3867e32269621a54dec0bfdba2234af7e86093bf6c46d9ad3c1e63ab01ed5be930ddcceae717868ee931299700010000000000008401080000a004c80000410000200002000102002000210002010000100010200000008000020004000050c04000000210002000860400000420002400800008040080010820004000944c08200000410000010000010070640800224400000084000000000040002012000300000000002404c000020008000082300080000800000000000500003120008000000080081a2010008000010084030410000000000001800000004008004200000000401008000000058102100000080000480002020012030001001000008400400000000048000001100040000800000004004002048000000000001400c0000402000a4000000000008021004060004040804156a1c5881ce7162be69ec1a9efbda4a3a8c3c6e55a802b7f655c47390798e6ca6d2c000000000080c3c901000000008a3d24000000000040d25867000000001d000000000000004275696c6465722b207777772e627463732e636f6d2f6275696c646572010000000000000037eadc1975033636fd107b9c8bcd8a0801dd5e952f8ade7ed39f5ee11ebedd683ec0f5c1aeccec48d31ef875dcade5b6fed40b8a38279a2662ce6c3fd3f621a2263d1ffbd979f2d064ced3efed705857cf91a959ed1ad244f90d259aae0fd93402000004000000000000000000000000006180e0cc9c8e0b288a5dc678d641bab8ba10afb66f6c22860b4d17b606e2ddf093ec1a87dd5d61ead01fe8743ff59efcfb9e8dbb92619108d1cad924b20dad34db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d719889965d5221d3fdd3b5cb064567d3e0268160699dd51d430b4f7958a8eb4ef060483000000000007b1518000000000065d1ccbd1be2b2d9cbe08561717e32259e5ff090d0022c55090140d7a0f38eca964c21ca483b5ccbddc29924ea026b6709e8ae7be6fed1c0cbaf88f416aa00317670c9bcb70dab2437e7b9d22b6bff81e765a3f4a0a3cb55dee2933b1ef002c1730a5a8648b6a15c05e078a7c666a14a889ddd5775637f6588b1613002fb12103b41d9736ed9bfc15a87d8fbb69c616190aed6c6ec791c449881ceefc46c3fd0630c984c29cd9bdbb7de0fe67f8e0fbff381e9f39d47bb5335f6bc92b11fb0a6cdb4a9de0a7379bc89499f02174294ec0cdda4500001000000000000000000000000004000000000001000000000000000000000000000100000000000000000000000000000008000000000100020008000000000000000000020000000000000000000000400080000004000000000000000006000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000002000000000000000000020000000000000000100000000000000000000000000000000000000000000000000000400021000000000000000000000020000010000000000000000000000000000000000000000000000000000000000000000000010000000000000080000000000000000000000000000005306931f0f493ccd9134da4d498ef69534f8dc56a8bb963bfd8557b1176e5d238d6d2c000000000080c3c901000000005cd106000000000040cf5867000000001900000000000000d883010e0c846765746888676f312e32332e33856c696e7578010000000000000037b5f752c74882986b00b92f2c821531191f881952eb0c3b98d4a26f572f68271c806a1093b3ae925837f489aa28276df77ee0ae42dd08fc221f7b14daf6f0badbf18d4105f069b18519034585ae83c276748e1da6b9527b9ad1969e00271620bd0000040000000000000000000000000026b3472433645a7d3731d695734e8f99753be4ba17e741e54a55e44d51b943112c55e5b629ec6581260ec4ed85e5fa9cb2914202c967b4c67974409f81f502b6db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d710c7ac5f531d1256826d786577fd3969f91ff80998de4168b0cc557a88552224d4382010000000000000000000000000000000000000000000000000000000000cd5ea8888b195bbf7bc21594d468ee2ee2780b3381804a1dd3ec6c86c4127e56efbc9361cf6adfa2e58b26663302e7989feff1cfd8a0782824b6e6fb36ce371b1082b11731fb175224f67c675f28ca1a8d84c8aec54dcce51a9a69cd4cbe4a6c2c31cdba898ff612fd6ea7e791fc7d9a8f32f8fa44782c41cd5b7a1caf31126bf6c7992f74e756e23d32e87a640addfcbe503fa32a14746883a99455c3be41b34000000000000000fbdfffbffcffedfffffffffffff7ffffff7bffffffafffffffbff3fefffeffffdfffcf7fffffffdbfef7fffffffeffff5f9fffffffbfbfffffffe77fbf7ffffd8ba41c2149cde0ff37abb2e9419eddd7b7d02627d3c92b34cda125d82cab4b375e25ef59a95a97c705a0f896681da15c018593ed06c5275fd9478f331e444c9cd00dea89f80f5432a34d13a0fbc0e428184afd7cec4f1368a0b2e7763f08e77fa2483000000000004143d954a3785f514650c643916f7fce0927a751400a1ef15207cf8ce0fd785f08000000000000001402000000000000f90211a00a7b7ff923d77ffc74599691896e87d1f573c30837d4a19f1526083d420a3093a095318a79cf5052474f801f2ae42951c73ce15393d9dcbaee687da8a11cc96cefa00ac811982974cbf7aca9069e9163fb952c08fb0d293832eb1c6dc60a2f174cb4a0727cc9af9cd627c6b4dce086bead90368f90bcf816df73ec30889b99b2066a15a0e2de9dda967495831697069e6ced5697f35334348cd4bd82a67b2d3219206c56a0702da00cace2a3c9b9ac21a45dc5da70b2361aaba323ab8101b701ed37185b5fa0edff1dd675a38681889baf110bbc9ae995bd3bafd5305a5f9cd5b6e56c1e97e5a09d2dd8734d2383194e8bcca39f5c0fcb8fa19c39e3906eeb30cc9ab0f9efba89a0e27b2a1d11bfd8b0b1c1ce5b3fc3a0211bacd72e5edaccb9b329d9855b7b20e0a0fe409700021da4946db0a25f69423ad5bf072159821683d76f1421a741b592b7a0da4033b2a0ec1287e2dbecdc008033edb2085749928e06570bbf0c4103d2bb09a007fcf2a6bf993a1b595d2801ec42ef9b4bf0fb78557f8c2fb47f8023ab695f87a04a37bbb3f3a9e89da11c4af8e4108994b1fb91d77d0d01a232295a6df72ce891a093b712b21e5a63778401c346e67f50074326dd711e682d73f6cd596e39566852a0a10447dd9f4b51f20b95209dd419436c7f68182596d377494abe53a405f67512a0bcb102ba58ace82e4978911b8053ba5e0b141a04e5ee5b4122229b7e85790381801402000000000000f90211a00ec4ea4580f196a05edcf694504d41b2721adc450b7a87355529f7b35d9ea469a06dc04797de98289e76c8fe7b7c068c38a52b745c5e7c5d2844c150561928b502a07edca49a5c14ba0b2d846ccca42eb2c0479b3670198128507e85d8296b43d048a0bcc1ef06b880301ccd7062eef2127036c1fb8f91f2c84937878861a69307a173a08ab32e4b84daf6d43d04850415a5bdf7465a15d0d118423b9f36bc9bd4a9e211a0d410c31690efe4ed1952521174e5263a630b1b12bebadaf76c146932ee6362c2a0b9a85d5aa244dedfef6d4b6c6514b482faf427949221b14dfa0720b4dcb378b1a05272b4964374e42164eb5216a8d1a75591d36592086844337c9602b0c73efa1ca005079f1b3653765e72be8f02bb509f067f10c62464d851eeb2088c7ab49540f1a00a187ba6531bc472f77fecc8a0b1e71a061a5bc899569dab2f141512df8bbc4fa0b18cb381db852226f8c001059c86423b41fec96c503a6ba1537a757ac9129e19a0153881bb07d537395463a1ba0bee98b70bf884852c14f8fd2c23b378ee4192f0a016e4876839554c748b9facb1604d257546fbc41495f63c339997f20a0a1ec6cca08bc34427a4f8e5e3dd9029b79ebfa4380f8a3fc5c75f1eff0582d35e9c460eb4a066506856697c871e41ccebe2711a4143036bde044cb267ceac744e2a8570ba70a04d7a733bed47fb92330a4d7658f538ac04c6247c89771babde44715e35c5ea66801402000000000000f90211a0fe2a388030c9891716dbb2e247601880863c59c02d2ee8207a53e61585200276a05a80de596df52bfde44079bd14964a679d27ccfa75fb0cc2501ff3763bf98bc9a0530dfd54dc52eae631aed4ff50ecd14c000385ee4f54d64562215d73cef6f2c9a0e3ba620f89675948cbd9404ba78892079878dd3d0e7dd0809ee5ef2fe3d6e3c2a066f44a42d3fc2a150cd6e6ee1936c5e2058b95355a6e0436b48519dce844cea8a002da12a04093a32feee3a795720b19e7f297b32db8081ac7460b1421a7df1b5ea09107e2708612afba08f9db17a095c54b97c3d696504453de3c74dd8b3730576ea08c31e8401da0b6ad91870d0da29cfd69aa37ef6b5875ad6aa71ea390585f0bd1a040babd2bb690bd441198875864d9fa907def30935f73d8dffab81310e33c5464a01fc32b7e368041074dac98c1add3d8e5f187cb1ae434bd991c09e06120171c47a02da8a33eec8ea4432c40f63e229ec3199bca0bc4e995abc9a28783871b89ca7aa0c42f6455c6bd2e263f390c9fcf05176ec81bb0292f4edc9114377500d94b4e7aa01dac501079141777026261e34a8cf8e61cbeae9a15e2c1d8994ca9ce9d1a636da0975daa67083d8864e643e94846d2ef370d9dc6c75039bfd71d001591e8a92290a0d4724d85308deeeb520693f0ed4b4f483e5bf58d24841a122bed2943cd86d7b9a01db08bc73c0d4c6c5b20b8cf3469fc8a19aa04ea5cfaa91ca352754cf0c798e6801402000000000000f90211a0ded19c46a19bc5444f31d982db12d9bf889eec13f9555aed02894beeec1468a7a0c072192fbe245faac5b86221316529e9be3ee535e0520921eb62155082b08479a0e26cd1c3e4335db42ab4cb96c391d05cfeea2bcc1f83436efa55182c6448dadca07ed21c163c10dd2f20f177db099a922aff4ecad295394f9bcc58c037463ca855a0c5cf26700b440ee92cf65119f670590a3ca792edfaf769ddaeb7d9054d88b104a076d9cd8c76875a086e1ccef6ddb1955c09d0d71c94da3086a224afe8d7fbae99a029d50a51084caeb913a3dbc0b4998074fbe5d678240f44d395e9067a4fc5e610a05e98a78e122d9593c0dd7684411c57b18f84628301c0246609a8efb35fabcc45a09ad47b5f93c03547d1f8fb4c353e7896451c3279414a7e55762f4970a865855ea0774e6ada9ead8cbe80a5b294aa906f0263f6844264acdc4cb8d33edc6ff35d53a017cf18bc2e4b05b602a1f701546f158c66146c1daa60a5da0c0d0df727824fffa0824015d7c5d14a5edb39b799612c1a42c8b26ad440ecda80482415adca038cc1a0dbe4a5f2bec310388f9e2f37b5dfa95228096353b6576c4477a6bee4504af9bda05a5d20093f19a78ff0ee14ca382112b5eb64ebc7b58bc006280772d4927a9fbfa0bccd6e21685b9420139fafcfe7e66f4535c67a582120426719722f1027580362a09c5a8066b52de56e27e5657814b81ff0ae68bb310f310b69f2c60a99ff618877801402000000000000f90211a0578c820bf3ea876e002b6f135faf4d0ef20f386426aa497b6ec9d7fc32662a38a0ffd695b52ae22117f126a2189029f66576971144d72a19bf484eac45bd77ff38a027eb67c18d0c8df797525d29aabcf85ee3d985afe3758cfa06b1ea73b6305112a02bbecbd18a4f26e124f40ec6fa1ca93a998fb52957f6375cf07fb54f4191bc1aa06e00ed0460976f713856356db489bfb885f6a5bd5fb2f61780b52daecf0e0173a051831dc39947913c2e41f3657b9ca4f72160a09dc7a81040c9592b3d34d0d721a0901f296f44ff122b01196579ad8c3cfac5ddd0a1b3a1f8a7442e0712202435afa03bb336292c8cf409eace66adfe0ded2acc9d2bab306f321627b08f9244aeac8ea07393286b4a5e5d899f966941229f09b626247e4b4211bbf729dae483939ae142a022f9fdc7c25b4985dac39e497eb756aa8eb91f4e696d20b3d64c34ceccd34344a04d3cc51de2e49def19cf67199055dc8bd92116a81d9552125f8bb731ae2766eea0a7d3031820b493f379c0d602a0825a2e8ada8a16803cd73476e60e334f37991da0c4fc82f32f7a0bf1b7ece5b7acd2175275094f2e19e4a28c4de544fc8c487cb3a0c828e7046c77fb07750efaf0f934438261fc1fae44dfe26ed9d12e79a7f044e2a0525a45eff8e4374984ab4905e94bab27971d7e83dac95d1563a2118a3b5ee46fa0508daa40f36526f408746ae2b041ed56694eb649aeec1a27f52e3d58c1603370801402000000000000f90211a06b045369c16c8765c57d9acfc34b2df339cc62c390b0f98be7320684f2624f93a090377c487ce3a036428ccb82a569cd56f5d0c7dbb08de68414e3a73158b2db32a0a364a1d089faa5a9d6d35cb0fcb2311aa3b24ea5134e2f9338ad6ba0de072cbba0da1e2ae45e46add90b3ac7585393a41a8e3460a94602722930977a43b456b699a033a66fa6510e11c7ac59073a495457811070fa122f7b0ab2819c6d96b60d95afa00d01a388f4437b8b4749d06f7233838bf3f4439036dec461e98a9a52c22b7535a09613984e41f6b147bcd772399db33a92753daf073304827a892006e1a6f03b3ea0eb7ba351ee8563bf88ac0821510e70f97dd9175deffb2576d04be5a94579e0b6a008f17278efa191d5f85fe701795563371e2bfce44c1c7e602f54494f5965db49a0ca45b460c06a2c538b248f4dc7b3786c15b9eb0e897bc47824e47b5fac355b69a06a13e9857a6140d6a13378593f3afb111eff6cccf209e0c5039cf9a5a2157943a02dc39b7c65fa562c1ec6c57b7df392603ca0a678a8c4eb91c53909f56607fbaba0eb3961a14da4af818695dd0342aca1ee32d240e3152bebc6703e63aeccc93d36a09348f3b8c3b4387e7fbcbe3d2bae95b2607c37bee976ee5b43724cb5d21d7225a0323da327a438a18996e5401bbd40619dbec7cb1d1715fc0c1d9f588afe387fa7a08e85cc5e46747fc430d2e174fa821c8ced735fdfab538fc78d222ce95bd18be180b300000000000000f8b180808080a019966e911f431eadb6bc3fd37e75fd64b45c037a94a3422e5d36c7991da74234a04c01f027e3895a885fde023ee066ea5249bf09dfcecf6a6884400b024650ba63a012ec45fb903896696b419f0a24c9e0571eaa3e357874711b2dd03e14a83e17af808080a036ba631ab4131e36f03029bd430707a0a56447a67cf12ff9b79b93e7851e5811a0b13c7d652de31b8b3e28387d99636389719a0589717213cb0008ebdec26029dc80808080806800000000000000f8669d3254a15ef5066d2370c88cc4c0279900fa840eb287959cb7395f0c1ed9b846f8440180a04143d954a3785f514650c643916f7fce0927a751400a1ef15207cf8ce0fd785fa02471b8fe47f8bedd2ab184a6e0a99fd7b6c91e090ed65ee6baa6b21f2886dd69").unwrap()).unwrap(); + + verify_header::(&ctx, client_state, consensus_state, header).unwrap(); +} diff --git a/lib/ethereum-sync-protocol/src/lib.rs b/lib/ethereum-sync-protocol/src/lib.rs index eed93eeee8..6fe475418d 100644 --- a/lib/ethereum-sync-protocol/src/lib.rs +++ b/lib/ethereum-sync-protocol/src/lib.rs @@ -71,7 +71,7 @@ pub fn validate_light_client_update( fork_parameters: &ForkParameters, bls_verifier: V, ) -> Result<(), Error> { - // Verify sync committee has sufficient participants + // verify that the sync committee has sufficient participants let sync_aggregate = &update.sync_aggregate; let set_bits = sync_aggregate .sync_committee_bits @@ -85,7 +85,7 @@ pub fn validate_light_client_update( is_valid_light_client_header::(fork_parameters, &update.attested_header)?; - // Verify update does not skip a sync committee period + // verify that the update does not skip a sync committee period let update_attested_slot = update.attested_header.beacon.slot; let update_finalized_slot = update.finalized_header.beacon.slot; @@ -139,7 +139,7 @@ pub fn validate_light_client_update( )?; } - // Verify update is relevant + // verify that the update is relevant let update_attested_period = compute_sync_committee_period_at_slot::(update_attested_slot); // There are two options to do a light client update: From ce8cf98532491617af05a183c6d5443ad63b2557 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Thu, 12 Dec 2024 12:11:35 +0100 Subject: [PATCH 3/5] fix(unionlabs): little-endian bitvector --- lib/unionlabs/src/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/unionlabs/src/hash.rs b/lib/unionlabs/src/hash.rs index 2acbccffce..d5853cc9f3 100644 --- a/lib/unionlabs/src/hash.rs +++ b/lib/unionlabs/src/hash.rs @@ -57,7 +57,7 @@ impl<'a> BytesBitIterator<'a> { // debug_assert_eq!(self.hash_bytes.len(), Hash::LENGTH); // invariant // debug_assert_lt!(index, Hash::LENGTH_IN_BITS); // assumed precondition let pos = index / 8; - let bit = 7 - index % 8; + let bit = index % 8; (self.bz[pos] >> bit) & 1 != 0 } } From 05278437ff021885a1be3a37f3269c08b306eeff Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Thu, 12 Dec 2024 12:12:19 +0100 Subject: [PATCH 4/5] fix(eth-lc): sum of bits for participants --- lib/ethereum-sync-protocol/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/ethereum-sync-protocol/src/lib.rs b/lib/ethereum-sync-protocol/src/lib.rs index 6fe475418d..ba2fba8c45 100644 --- a/lib/ethereum-sync-protocol/src/lib.rs +++ b/lib/ethereum-sync-protocol/src/lib.rs @@ -73,11 +73,9 @@ pub fn validate_light_client_update( ) -> Result<(), Error> { // verify that the sync committee has sufficient participants let sync_aggregate = &update.sync_aggregate; - let set_bits = sync_aggregate - .sync_committee_bits - .iter() - .map(|i| *i as usize) - .sum::(); + let set_bits = BytesBitIterator::new(&sync_aggregate.sync_committee_bits) + .filter(|included| *included) + .count(); ensure( set_bits >= C::MIN_SYNC_COMMITTEE_PARTICIPANTS::USIZE, Error::InsufficientSyncCommitteeParticipants(set_bits), @@ -166,11 +164,10 @@ pub fn validate_light_client_update( // to match the finalized checkpoint root saved in the state of `attested_header`. // NOTE(aeryz): We always expect to get `finalized_header` and it's embedded into the type definition. is_valid_light_client_header::(fork_parameters, &update.finalized_header)?; - let finalized_root = update.finalized_header.beacon.tree_hash_root(); // This confirms that the `finalized_header` is really finalized. validate_merkle_branch( - &finalized_root.into(), + &update.finalized_header.beacon.tree_hash_root().into(), &update.finality_branch, floorlog2(FINALIZED_ROOT_INDEX), get_subtree_index(FINALIZED_ROOT_INDEX), @@ -213,7 +210,7 @@ pub fn validate_light_client_update( // It's not mandatory for all of the members of the sync committee to participate. So we are extracting the // public keys of the ones who participated. - let participant_pubkeys = BytesBitIterator::new(&&*update.sync_aggregate.sync_committee_bits) + let participant_pubkeys = BytesBitIterator::new(&sync_aggregate.sync_committee_bits) .zip(sync_committee.pubkeys.iter()) .filter_map(|(included, pubkey)| if included { Some(pubkey) } else { None }) .collect::>(); From 5bcfa1d347e7520d62be9e134391dd0993196a2c Mon Sep 17 00:00:00 2001 From: benluelo Date: Thu, 12 Dec 2024 15:40:20 +0000 Subject: [PATCH 5/5] chore(voyager): improve logs --- voyager/modules/proof/ethereum/src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/voyager/modules/proof/ethereum/src/main.rs b/voyager/modules/proof/ethereum/src/main.rs index cb65a8ce9a..e72b499989 100644 --- a/voyager/modules/proof/ethereum/src/main.rs +++ b/voyager/modules/proof/ethereum/src/main.rs @@ -13,13 +13,14 @@ use jsonrpsee::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tracing::instrument; +use tracing::{debug, instrument}; use unionlabs::{ ethereum::ibc_commitment_key, hash::H160, ibc::core::client::height::Height, uint::U256, ErrorReporter, }; use voyager_message::{ core::ChainId, + into_value, module::{ProofModuleInfo, ProofModuleServer}, ProofModule, }; @@ -78,7 +79,7 @@ impl Module { #[async_trait] impl ProofModuleServer for Module { - #[instrument(skip_all, fields(chain_id = %self.chain_id))] + #[instrument(skip_all, fields(chain_id = %self.chain_id, %at, ?path))] async fn query_ibc_proof( &self, _: &Extensions, @@ -87,6 +88,11 @@ impl ProofModuleServer for Module { ) -> RpcResult { let location = ibc_commitment_key(path.key()); + debug!( + "querying proof for slot {location} for IBC handler contract {}", + self.ibc_handler_address + ); + let execution_height = at.height(); let proof = self @@ -122,6 +128,6 @@ impl ProofModuleServer for Module { .collect(), }; - Ok(serde_json::to_value(proof).expect("serialization is infallible; qed;")) + Ok(into_value(proof)) } }