From 82e1ef25b43188a5829200f46943b84a18e5ad23 Mon Sep 17 00:00:00 2001 From: abhiyana Date: Tue, 26 Dec 2023 18:42:05 +0530 Subject: [PATCH] feat: implement kurtosis package for building rust contracts for cosmos --- .DS_Store | Bin 0 -> 8196 bytes rust-contract-building-poc/.DS_Store | Bin 0 -> 6148 bytes .../artifacts/checksums.txt | 1 + .../artifacts/cw_nameservice.wasm | Bin 0 -> 147702 bytes rust-contract-building-poc/kurtosis.yml | 1 + rust-contract-building-poc/main.star | 22 + .../nameservice/.DS_Store | Bin 0 -> 6148 bytes .../nameservice/.cargo/config | 5 + .../nameservice/.editorconfig | 11 + .../nameservice/.gitignore | 4 + .../nameservice/Cargo.lock | 698 ++++++++++++++++++ .../nameservice/Cargo.toml | 39 + .../nameservice/LICENSE | 202 +++++ rust-contract-building-poc/nameservice/NOTICE | 13 + .../nameservice/README.md | 7 + .../nameservice/examples/schema.rs | 10 + .../nameservice/helpers.ts | 234 ++++++ .../nameservice/rustfmt.toml | 15 + .../nameservice/schema/cw-nameservice.json | 213 ++++++ .../nameservice/src/coin_helpers.rs | 62 ++ .../nameservice/src/contract.rs | 144 ++++ .../nameservice/src/error.rs | 29 + .../nameservice/src/lib.rs | 10 + .../nameservice/src/msg.rs | 46 ++ .../nameservice/src/state.rs | 17 + .../nameservice/src/tests.rs | 372 ++++++++++ 26 files changed, 2155 insertions(+) create mode 100644 .DS_Store create mode 100644 rust-contract-building-poc/.DS_Store create mode 100644 rust-contract-building-poc/artifacts/checksums.txt create mode 100644 rust-contract-building-poc/artifacts/cw_nameservice.wasm create mode 100644 rust-contract-building-poc/kurtosis.yml create mode 100644 rust-contract-building-poc/main.star create mode 100644 rust-contract-building-poc/nameservice/.DS_Store create mode 100644 rust-contract-building-poc/nameservice/.cargo/config create mode 100644 rust-contract-building-poc/nameservice/.editorconfig create mode 100644 rust-contract-building-poc/nameservice/.gitignore create mode 100644 rust-contract-building-poc/nameservice/Cargo.lock create mode 100644 rust-contract-building-poc/nameservice/Cargo.toml create mode 100644 rust-contract-building-poc/nameservice/LICENSE create mode 100644 rust-contract-building-poc/nameservice/NOTICE create mode 100644 rust-contract-building-poc/nameservice/README.md create mode 100644 rust-contract-building-poc/nameservice/examples/schema.rs create mode 100644 rust-contract-building-poc/nameservice/helpers.ts create mode 100644 rust-contract-building-poc/nameservice/rustfmt.toml create mode 100644 rust-contract-building-poc/nameservice/schema/cw-nameservice.json create mode 100644 rust-contract-building-poc/nameservice/src/coin_helpers.rs create mode 100644 rust-contract-building-poc/nameservice/src/contract.rs create mode 100644 rust-contract-building-poc/nameservice/src/error.rs create mode 100644 rust-contract-building-poc/nameservice/src/lib.rs create mode 100644 rust-contract-building-poc/nameservice/src/msg.rs create mode 100644 rust-contract-building-poc/nameservice/src/state.rs create mode 100644 rust-contract-building-poc/nameservice/src/tests.rs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ff67af8a8beb4d32533ece47e47bdb698a3beacd GIT binary patch literal 8196 zcmeHM%}(1u5S|S=1c4B=AR#1#CC*0T4P2TYdPB+!;5Z?PaN;OGwCc(Cs*li1&qx&y z!6Wnu`V^eto7n|9Yv&SBh16MTcQ)(&KF@wTYik@LGH3J7DbYR=HBi}ZZ=k6te4T5l z)ZEAptO1@VrI5yyQSv!0&DH@`Kow90Q~^~$6?h8@;GNB-vE;pPuWG9br~?0`0{nak zP}xS#=GMxi1C68ruyqVeK_7X5U;<|&XLD->3Qn6n7zZ^j#V{6*>j6)PjhxM`wQw>P zPR8BYxD3U}-GK{aI+@5?ZB+qPpsWDr?(5X1n7&cBn7?O9JI&g-$_MHL>dAu?^oTYo zL6$$IA$1N=T!#(syXgGj$OgAFO0Bj zvgicAu&!#JTF>Lz6OU860;&UVSeYxVa)KVK&TFsqsP@NWIsj%aN>!=N%JG_6%@Osg zIc4u}3VXj2XWBb$CE0Kc|DMW9Jj1NnbaB}j9RK=z{_~Dwb49Mst8tC{ud}}E)?}1s zBaR4u(QRPwrikMZG4&PEFhpb^Mie>16&#g(@(18uazg7r7Vw60{Dvy}{udpzQ3YPAz?vo4=luWE z^6&pIy^Z2k0af4)D`4u)R`U!Fx_4{IRnD~o)C*KDJTJFaAZR2Vhm~|3_W2J(^aD&~ WOyq2CEk;oELx9MjjVka@6?g=83@Cd5 literal 0 HcmV?d00001 diff --git a/rust-contract-building-poc/.DS_Store b/rust-contract-building-poc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b6fe95d025e60c3677c40a091229151996c3cd43 GIT binary patch literal 6148 zcmeHK%TB{E5S*nw6qQ4dT#)<)mH30SDhJN}0n!p7M4Ct`t<+ooh);xBdyz=VAt!`j zS6a{7-m&A6;s*d(9v*Lj8Gs30Q8ef=ZSL*5u;ejO;utmdc&5*B>KW)aHtDxt;ROdY zc*WQ6-=VxO>+PnjH_TCi7WDahG)BNBcF4KjitF8zoufHpyqz(o%+1Lvm|t+lBiUON zU6$MoV;$E`Ro2xLv#GwDE8q&a0OV literal 0 HcmV?d00001 diff --git a/rust-contract-building-poc/artifacts/checksums.txt b/rust-contract-building-poc/artifacts/checksums.txt new file mode 100644 index 0000000..c201b69 --- /dev/null +++ b/rust-contract-building-poc/artifacts/checksums.txt @@ -0,0 +1 @@ +2ee2c9dddcdf04fed233af49c0731154813236c4ed0d472ad490afea530eddb4 cw_nameservice.wasm diff --git a/rust-contract-building-poc/artifacts/cw_nameservice.wasm b/rust-contract-building-poc/artifacts/cw_nameservice.wasm new file mode 100644 index 0000000000000000000000000000000000000000..296fcf3ab8ab2629265458e10ccc5c87a66114f9 GIT binary patch literal 147702 zcmd?S3%Fd@Rp)sg^}JPeReD+SD?U{=^aMA({7T|OX#((b4sZ&>14?FJuX1=jL-*al8{aky!_u6|WJKz1zG)a>5-=*ts%l7R{_uZE7vj;yC zdrxjlmE0hil$8IQdga-+r{0p}wodp_eWy&z)A-Jhm^(J{fYL#-f{a*%3F0SEsX;2 zy6cX&^W|UdN)nayyzh9=J9pkyr48S`>+N^H@#{D5zT(zHEW`YYZvRc1|0eA~{wZ-2+sr{?Xu-u9k<;iKu@^PXLM z-h1oY-?8(KyXfzSvjbV4<^5rwzxvl~=6So*&htELHko|>Yw;hCG;L%&yS2MTos($>BEBEZW=RJ4q z+4b&Q!Mr^?_r7aSvY4*E<1T)D*Uq=^(geQyj(6R4>sFFyZoL)!+Ij1)yKc|Z-mSO3 zW7p2RZ++X&ckjxx?&p|}GVOgRo&U4+v2?>5HvW@0el*>5|Gxj}f%m1~oxVTapMFpJ zf%Jpvf%HS^6hSNx0b`tLT~ zzVq$ha>XB|*S__?-*xMa|HExJecLy0`p*C6TfgZoo4z-F^ZdusO&?DWrPt2?dHR2+ zKbg*N`swti(rbSy-L&~<(w|MQ_)L23|C0W4dNh3^{cO7F-=tLbO#1nB)9EEO;rq8Dz&h+JP)0fgOr{9`NcGG$}Xj9yc#b zigYeZMtQcaNavZeZACtpbw-VT+OxNDOOM8N`cSlnL;u|3W6>(pg>hqZ)=^qgv=+*Z zS0zcWNXz7^q+!L1P_PjTHmug9Xe^XtGE+;VUQ45B>@KsnSYwScAFlM~CZ)z{`Mn=J zo?M@O6S zniTCV8RKo1NzZbXn-(rhdcT^c`99{O*geiJOR{Yl4G1Xdjsd7$B!GWRslDU8NcorF zcp*JX%g$Ra9Ovb}cbAvkJ4z{|xyuUXUEhD4T4~)@U-blDGAGGB9f4WzE8H zYmv=m{ZXn%X9qO^#LZcQhkD%4G@I{RTwJ{Gx^#=IP*~QQ-+fJP<}s)RKl!uB9*iNbD)xzsH82x>TTTB!;rmIbU}9e+~s4@)l{~H zTcjmLcL|lKSkV@FwTgBu*tS|hmp~!Pl^sB0N*?1`UU>!nJxKn3g2g@ z@V(=({oO2W1T<+!$TlaC6;X{^f~?-g&}4vYv*-XI``qCpJpi&Tqgp|>vjnm#R7n+9_g?<%2HXHkQJ*F$LGQ!}X_{5ofn8{Qnzg;j2vfPo(0dAQou5NerP= zZe2i9j54KMITAHD#nQ6!AJco3LDVpA8-XoDkw%0SAw!!k?mB9X`5&&eh6Y)w7Mf*x z9aT4&nh}UqHuu8cM``}58lmU3A^98H$YFU_aVn{i4w7DvR@;TB)kae$*AG9P8o>^- ze%>>3<bO<=jTTW;Hp zsQ6MR%l9>;g`7-wkC7G(XsaaeqjA#U;#)N*40SHMMqt;Vx9v7j-}mei0OkJ0<4M_+ z8Z$&#tvAuMN`<_*xCs(sKIv0gL%$3jdK*K4fwzdo4gGVoCx$1kfB@! zH)PiU1{j@7b%81`KbHj1o?9lMuC|Lzh{hwfP7Br|ij6TSKGnOz&)sEHDvdt0;4|cA zNL8tEv-}VMnM;z*3H>fnv_8u3pfFjX$-t~OlLz`BuC=7L$hGXVaV?X*R~Gs9bW66; zW?_u5DK{>ZPk-omGW<$fq#N>N)RBU+ptlxtd?*D;#p=t~Z_W;q1+kkwP_(u$E*9U( zSW=;PXM3{!uYdNLU-|T>Kl{}8?;CzHBN@F)$-n)@pZfj3eDv2o__v8CpGctZWwLME zZOKcsKbfc}R5ur%TlGYb3Y=SpWE&yxyw%oiSOkzUbL-mH{D;f`9h9w zSN84SZ~%(yZqGhIqu+2~zv%+o=RZJ?S0C8F{j)#%-9PrdKiydDQDK{bo;>;s|N0jm z{u>{`2IOSte(L{IMv|5X(}m&RNwIq3IdD12_6eI0ra+dm`)?W~7m(@8$wIk_JZp9j zKOxwxaZpk@ZMM|rxLvi1*c5b^G>Y6irP0_@RrY3Y?$1JVsW(@29xXTRxhmO(?n=L` z=L_ZQLC4{LNa1%WlZ*~nWax>|-lh?c@**)ter*(vz;dNUq|A3_`ZFmPiNeJtli|Nk z1;AXKC1;|bI_E@C{f4ZNDm|@QNRtM=vMIg8*Usy4s`@6zGJf#(EqkZf{hKE2_wUfmqPK4P8Y>!=4SPql&w0m$zH+RqC`>;Mc?;n0YSVyaE4d3}6tdT@GG>KV{Ns+3aIS9Aa>&S!r z2z7{g=f**+8Yoz1J^E?7LN3&$t-iy#SG4zxXTp5s^x1oy~UUZ=KEA-5O3;XozTx-CWmSb!v@W2Nxf#d{Xpe8BJH+ zRfR*9eZk<5c{rEdO~C=kH<#T%8t8G~Xr&%|N8Ns}SFCw-(5(u1_wTm388BUHB4QJ& zc%YXqGd{rZc7*e$e5&vFIlj+ld@mwG9T=}x7n*>}Y&N5a0$2~v7d!Bum;l4`i?&p? zj&TkO-a^k-NxrOQ(D`kn^R}m>HR^L#{s}Z??trYsy5C6(cHmq4*bH&(7g*Ef+Uo|G zXcvZ9G=% zGt)LKda4c8VJJ9hK7!(9S>Gx=bLtN$e4)B@yHCPxP6lrkClNKwsHf`JU~_gY4=i+$ zqQLaIhR4k2Y-@jp$+`(t>FyZKhKi*IfW%ZR-Gsj&+Sg~vH_)euXezE2r~wiKqAkGN z7fg!t7(WkMyotx!EmB;S17P1YT0?DUYw7SIL+Pz}WsF{_xzdeLimc%8JXd;CKnix<*6T<)m zSq88b1lqx*6tg?_>|qMKj=mf6t-ugyF(z=B5%Po4s~hT+8eEh(?d?ocnaa58Oh+zI zNzr`;MD$_gtJE;SS(3hn#}^JBw?XAnO;OKCnnP_{Z*Lvm=4ePxkKvnmnD^;7aPPW5 z(EU#|4Z=T>~MwL5qizIVSF4%5<($$CnJpe zhC#FZ@_}EG7f*^#I^0gi0keqmK;;i+6t8*FtbaCV!B-3GtG&}I}wh6$7?_wrU)fPFh`d*g(LdK%<)wU3UY%nPV<0$xsf%Om^Jk&^O2Oq@J~Ox-;Rmc`_2pD)Dpy*4+_9Gm^16vRI5}) z{%+`x4a(YA3yEB6)`GP0k;IUOFH0A{Nk=5pw9!E|r8z1w*&-0DwtMmlHw@8%Q9hpx z1#ZC4XpQPbZlXHilmi|k#zc|_(m{Q>DtQCxu+Qz8)7cl0Ezp`KkjFJVp8Tjt4*n^U zyV(d0^F)k5)b|G7Zm~B}-%X@!#2d%#C4^t?vo^98-3&r=JkGd+~fY^*|E&9+Wd zUAk)4CVa!hOQrhu7H6}r)A5PAMLkW(6YQG2X`ow8ek)DC-h{jP50c%pmp0N?dZz!H zX^V+&l;hoKX-WLANt(N6ho53#73<)wJ8FQNjdJ64{Q1M=x;;F;!efAeC3Yfm_cCw? z6%A}#q-pev7Rq!lOTP$fQ57|y;ZoEzQjnKdsiCMW-h||caV&tm%{i8>aa@bTUC80w z#A?HE6dRMBAr@1>l?IeWY$3jcFI8iO)ZYR1pyva->#G6Do-vC-z6&@Cjjl%nBHu+6 z88w_GxG_T3wwn85*%R)u6W~zLKSYKH$!#pibJbf z)lt*ZgH^I}gkEG7svg+#YRZZMYIN^_hDY;SqZbrbQvXG>y%_9^i-vHyHe8cYfd*!=2S-D z4%Hm3Ks*G7q?Hv&OLIP%8#S4-d^DlBU#BKiR7LN`-O2y~99qqo-*CjVm%eobwT`tlh&DZNY3J6%nHICO~VJ8MlMS_JMZ*Oj0B$Sc;BKS{`g z)Nu$LB~IAs7%Bn*X&ebrXK~g=bv83~7S{ozLIUCXLY)=Xqcp538q(kU{92DT$fIAm zQC%*iw6afgk%(yI-QI3SB?_5rm$ZEGf72lPygl|=y{%CrR5N^Errv-=Vx?BlEQPAz z6P8^Ce}`go1n_$<;;sZc4j(ubnP|DZ(QoxTSP&A zE#`$4>9frXDoS(sed((hJ2R%S!xQ4z%QZJ@?7&A89Ki4@AuyZH21BvM+`x8)6?m|j z{t6k(HDv?a%B(M@$}Jb{FDc2eB&61Mt@RH@@K_m|vXl>Qa%YWz?JSb5A2&fqUWPb4 zD^zcm`CjxXHPPQONQ+r)1+B+7NBKm)MUpYqZue|6&GLp6Mp>QGwagvZkn0lH9jWO8 zvQGXxf6seJqVSGik0p3dN0`v;JQG+r7;srJ5M032 zmYGAd#5IeWh5+GCu6gfCckvj2t}h1|)+R^<3;VfiMg!69@@ zx`&bBRiuU1%JRMDHqqK$3$FqG<~H5vQ;08PmH-l5&paBV9*k$WTkMUTa|`1xD@Y?y z6fxHuWj=fKTP%qtXdZ?5MkX6qkqn#cMkqrV&6u?;FjIDRk7op8bMK`%lHvRUAQd)M zA*fONCw(XxYhq>zf&({ds38v=n4lV%lN?b@w{oV2si#tzTk6kS#3|JqdZ6CK$p&IG zES`*Km=p^2%EO;Ho|G4rPw*K2DMoxucxq|F7nUYG9}}j!E+;iuYm7G+cGScGBZ4es zlnWmMpMmi^e}>$Iu3f=x2Ivat0&rd~E;hae#i^?q8rbSwk%mA6^fe(zHpG_F%wYmp z{EN7LCUds4mp_aDkl#~w?%i{B0M+neGx^pt_1mK@zA7L@o!dJWIwhnNLkjI1?b>WOPZB9do)q4F1fK3V>Fnh6yp;NUnhz9lV#)%b(o2T@TRes(L1UYy-|5s%gaWlAfn-!V_p39 z!PVJ{eJxwv%VI>qHZ1+}4`Asf0&!(C$+i_q_P_)W`3)o6HXev_qZ}(QHd-avP2)q3 zFm}|mobi@x7s~$?xB7-8*#bR@50}U5zX8Cp`tPHYAM!D6{}kf&MDC7Wj8KVKtxM!3 z#wgqqQk%7!AcCpGU(mvHyfj*wq~Ei+0+r%*xPfX`EX$N+yK z7)nbnRHbcBu{2L;{%~zRor(j=J;VAyUOqwop-3k$ALn(o`zMbQwvrYdX>@rpdv)?N zD*Qk5eb!A*ezCAH2H@qfG`Kr?XYtF!A#4FG*MiL5P%%qvH{m1Tt&`LQ&3hET=w%af0$%q14(p>#w*&U|uE@XmG=GwuWMC;(KoJyhJ> zQS^4~8DPIa{rxo>;mHI`**&uSVp-h$!Xh3-{f3+sIy|ej{(Q35A9=)Hi~g+k{;Z}y zT3e8#R(AX|dP2QVr_5yMUacAB@(S2op-(ecOll$@8a=(m2_B-fVT2(iMwA+5mC##Y zh09!A1?>6iq)Qd>uytM@SFx$HiFkuK6Hs3k78utTk?1^sh?r z{6kc}itPc+%-#t@b)dxnE>HeXhE;w(PU4=P$YBG69j(^%JuMSg+)A%Aku9tPC@QnOOMfL-B)Ps{9rBufkXIv3VcQ_x z&T=oTMNX-4%>#gqLvtk+>pa6EEAd0q0}G2=PHkJcb2~xC3}r zSBkq4Mns;RTpaWRinh+evGK0A=LUred5U2Z7J3n?ze(=ZA60cxDIj4-niQ^Pajk1=>YWpko_?2e_e- ziQ{T$e35unIGNOX8{KzMZxfUCB^rTL1>o%lTCZ3m?OpwLK%DKDl3oPT)n_OvH% zGzjjBYxNwM!iHqY8&fyhIXg1)r`jPQ(0*BHj;wJIMzDD9H? zZeO})H30s|(zURwSzftd>px!5*5ayy$Ceg^PhUfOc)qss2-VozRw~>?btJ~ca4jOT z7~V9OQ}44?2=xmrqP6LMo2JK|;g6)eNg#Eq2vsRUirl_U1LAo*Xy2BY0_3C+V%#B$ z240}IF-xgfRHAT#XbJYT&-iEgg*`FGBSxSt%5X6gLQNTVTPhuYpRZObEFS#!E!nk# ziYhF+UZwUNSe2+gRt@_MEPj;r)q4FD*j+YcBI`qwh|eXZ6wLJT<1v182K~N#KPo+A z5|Ub>NpGz-iyzeo#g8tjEyj=9XMisV85Eg=mZAX#KY>qyaMei1Cft1V{;Dwe+s9zu zO=u!Gq;33d$;OU_4yvAQDC~mSCb?3raIh^5XxDDZ=ag%!T2m;ZcJSDA*@ghgOXbVw zw&b^w4&HxEct7Hu4i7Iq+RE<0ISLbXQzU-3O<%F5LV&?DNNuB11`Af1oR~CajiQ$Giq*l`pMVkT zc}xyYo{!@)((`C)OPP$vc;#bck{9!cp76*=r2cF81-ql{nU+n{l8j)XW!WVL|HESK zbay|5i!EFD45OYN<}*IY6d8t+ZNRlE+Vv{H(PpVCSd*`+FvK|QJ;bL;4jQ(?R zDpQ<^l|H8v&B-R!1IWyhbYx*AnF*o>+vgVxn>y;PcPG<`cDejC#_{0Ev}wf0Q%$2> z!*sqYI?H^boU?|B$|IC#_*|PPD-{ZK>(f%rtU_UG1sFk?Ssa^DU7#Ovvgo}WFij}~ zvA%V}0@k}vH;>x?;PX2*k0hUN9xM1uBVzKvJ1z6K<(M8An5i!|f8putG5E<+Ak@&% zUX&^_z}U370I?>i5{FFPvn)m_SV2Z92vR6TIBXO~?nq(Y=;q97qz0=I!m=9C8K@DH zyIDR;M@`saTU?#|IG%hh?=~;z)xTG+}4Y-PIX_@IHh0ioD*%LjfieNqltHUj0e1AOsQ@j-EzqXf>dYCUN=x9khv|4Y z^x~n=3%V{UkX`)*O-lbgrYDNq<9aejM|n#AMqzB@9A2#c-NyI^7=jwSl!sNBtT6Hz+83{S}Y=$l{B#!|EJ1sFwsR%=93#i>5QMtZa;{H zuvtOnsQ@J3fUs(y7?rks3|-rT&??Q5zskgLTVAV<8}w4!B!X$Oq)8s8SjY+CH3vzn z6+H=HLewFswzeRcUo;lpT0y3bRPNe$`9*^OwgRC^m>+Gr+YdanfG_Zn#^+!FPzO(_ zZH0TX8o29MSc8&3uyt=A196C+*a07fkBK`v@4R+KqfO0i;=U|w*EcJp@QD`idQ1H(jmAC^)xr zCx$n;UhM&H918|!=5G$xhpVyb2NcXU<=U z-U(=WCe*6#hUr++-OxILW8PVGQlS)c4Jt|nRe1${{ek(MrOKgBdXyA9>i7xE4Tyz0 z*$L#fku_9?FlTXKurY-a8~~2nJ6nJwzl)&jfNy0-{-b$wdb1v>*Adt#wPiG99GO&~ ziqe{{=QcyAA5mXqfoGxbwoTtPznm#4<`p-6hAK^)g8bws3*9F5d>jEvax|&J{u=H^ z)6BJo_=zND@Jz~CwjsxbYhIzlDeI?zXWjPVz@t=go;_C=>mG%M&Uk5sNnY zh(+Sk=Y&t01ACF!Wvw9*ZJu71*dQ}ms3@;E|9#!DuOk9hqQhpzN{$HNiOqW9cqO}N zVQDK_6++?uHs(&; zv>@zQ{lqp@qzzgNQ!e&Hb^*aL20PFdDrRe?n3P9uqENL^s0^I$yQEEvz#|DVgQ7)V z{t2$;pXBfdM=$U`O0}|;1(A{YbVAgDk^Um2f2Xi}Fe2Qe=#AS9J2%xau&N7HO(bdB zGR{d`5NXs`v{&U6ibcpPRTS&QEAHYH+mTM4l50oL7)a!3~b$&kVz6+noiIFae!XNN_Yrs*Gd9&h#EieRR>#D z5pdbW8Zr>>;;53h4o%0s96*nC(y;%+rr2Vn16c?deF3A^=D2m{R#%IRfbafPg#*mw z`X|+#ww>V**aQF(v2wIzpAADrP?Pm@nqcLXs*nQIH(?2({da@G*VD_k6t4 z!KgRFD(1>YG>~fv@~kR zHDg?cb=sz2BQPvAg#?u9?A^mxjr*u6#J~D=*#*96e<+U%@yqZ*^uDMNzXbj(FxMmn z@Ow!PAw&@EBNcob49QjPw!p1PdM-AS zjaJjpFeu)CAxN5xX8EFgFu1utxOZHb;nYqF9QqnU;U{{-{F>$r@U9Nz4DB#GY&0NF8aVDPb7f|KyK; z@y8zhH_!aci^;b^BC@_Q!gC%Im75lqHj+&@qS~_QCQSY>(}1u)?=e)<$ZZLmPOkQ8 zLVw>g9-2lDZeE8&Fq_S@cB@j!hhXe#SxBt+8|+*q2SyW>JU4!yiRPRGz~Y}~$zwkf z+Ej82p<4k!5#QN@R$c?d)}5|qZAWp_GNQ@EsMK24jCQ&*%M-&gRZ5&?m{HVmZ`R~a zOiFXzWg{zWhNRKu3aYfd3RHQcKqakK%aJR#C!>P@$AKbhR2T7~_7sJ<^KZuW-x(n; z<6c!!NUwj16mxBOpDB$(?3+xXb0sjyiEEG_6^j-a=(@3fDv)7egMs{>7Rp zR@-CYiN$Few0*BxoK_&H;fu_pxqP98gSqQu2XZaTA<}ZEk;NSQ&0;4|){rnN&!81` z65+t|P(xv|c>;m3I9UFJ2*J!wQOAs^W7S-elrR4F&;4`@=?J%<5k9XnWB$4FmmRIc7 z5XvswE%EXzGxLm>ugfmo&ust4^!=ktP^@yd_CBRRg+lcdUVm}P=ygV>7<}U{ip!=O z!RV0KDBq-DzVIs`5^CKganmN2n09FnQ0Cd7IW8uR-ao#iD85tSnCAn(%aiXBv_6zfg9Udl$SRgcD|h1$qj!d9hH{hfKjfy)E(*K~&KirK;Wr^7t49OV24 z3M-iAA}Mv#IOuqX+2oyT4bAp_AycG^X`?S^BeQ4dmH9$m%tk8&m6+d*Gv&Wd&T?3_ zq4`T$-0W_~*$dg0{6r?XUp$KMR)lF{B+~1;zJ)0<%7eI2Ulu`xPQMs31O1qp?nHbr zvM>ru0&`N)*KNt4(GzX|X+0SujayV6XhKerR=HpBYjDVb*Jdz|^jfd&;?vh=lTlo_ zIeSjc#Edjgb2i^*eHZg%$D``3#tyY#740qi~{YPYNgX? zK&Q4?tk^LS6bWa^M7Kdq<5Q;r7t_`Nu~JlDem<)ROjsx72M`b+mBIFAcM#6D0+n0s zy>9y)4wD(4a-6IxUiT;%F*`cXvEWSeEp5T^S$%jBcOadq;KV3wdVF9WhX`Kp_jw79 zFAz3LuvXobumsJoIm%mRNE2pf>FhZ1WBlsbb;tr`+zInT#}QIHfD(#bK)OI_Zfl}$Vt&qf{?WOF=< zEqTG?JbA+iCX{U1$#jRiw#|sKN?4Y{vwsea?BI|8&^ZD zOlVjzkGd(eZCn)EHVYrxW>)LW&a544lc6hc1QoN%klJXQjQ)qkYS;ptapoZ01p6p_ zmcO^RLv4@L@Zcu{9vu-r$j=1B z7X}Ul-O&JOMThZn!!f6ogr;c^_-|ekzecv0dfk_LO;tM}Ut6dSfq!#kcLDKI5D+hB zqAgR&3^XRk1NN)*X?5T=xD94 zuCS(J6oFrT{{q~rFD2+(;`>r zlMmrJfz^WuGGEtDTj6emfyx$B)eKy%S8=tbh^`&HQD$Y?H{)tesWIVj1T8njGdSL- z2IX}R@dUH8#%F&~Fi+faW#d2i%z#{WYX zG`;bN576BBS&hFLzY8Yo{6DBsda{? z!tm^hrTGe+oXZiYcL$&*NDZ+DoM(d(=Mt%#nz(yl%d&nONs_`$8cVESLo5S2hOF7e zw%p;nHFT6qtmhqSM^?jpa>sZUF26}zpS$H%>>X9L?4dxJ@R~leeddAjB`6*@?O^NU zC6BVva5mJgoh#jMk(C>A1LsDW^spTbcUWY=#XMSv2wGb-FmkmsK)%TF1#)a-VVaK4 z++3g;3EA7Ogo|;fVV%qvH;#leGH$CIR%a`tFPIdtRS478N5wkBt75GgOJeO|c8#^e z%9<26P+Cu{S()9LUw zra?l%@G-CkDRH$rnU2~|_WZ_KcMWc}8g4%{@;(PE%OzGcRxe=djU*P5>V!$ItIbXWITU37c{eDva%INh zs$d)=RBGJh+Siro-nUkkRtZ_^~4Mi6R8( z7?u(6Wvf7FpAQ;^O{|*HUIE;ki8epTm@u;=8X+4f@)>Dv_~y zWp(@*`vGdlk8R}JBOleai8_FRl5Xy(GvV;rS3 zIeLs5=CGQ{#({T+M(R~ICscNemC$Whq&4d&EZ+RYmTWf6I(u&Y%%jEvngrOvO3PC?yE$K%T>#c4zCU2n(6Ix*%*l3W`QB_%}jPaNZ`Qf1Z_&&0nBrG z`nyr!E4CAgZO>s-hlXmaCt3NFS^OTYm}wuMkvr{gQ2Ss5BP%$H!*(6%$RkR68L@?s zZ%cNiPz@4w2&v0LaJ!me_<9XSM?_f`KzqApmq<|416U0<2El-sQEVcea&CJ@P@@%$ z61z`<-DUeN8a^L7?D8OYz0~%<{Po}b;U9SZ_kQ-TZCkYET2egR!h$(`YVo*9MCCH3 zoMXgn%JXo7zfX=$b(jg?Hb6G1(>`G{L^$k&&Q2zG%AVc9zWuEH94oS{{+EyQYy(hG zN{)jW%^ge`SO^mEb0DEap(4Kw@z|Inn1Gai%)j?(#;(TVnL$T$6s$P&jwNJ}gu2lnZsjR2D;Sx|p!| z)4fdqYv6(>RNz`}Yyy$>u?D+W2aGe`In=4&In)6luic$PHsubDQlZ@zUN-KqCXUvN zFXUS;M7V@=qy>v*XIi4h=qApdu~865j59Bv`T$(Bq1{%-Gm(I22JR`<8Ryh~7m#DD zK%!Xl7ro{qv1X1QcFZ?NZ{Vdw*7FvaILb`|$$UbM;ID5F_{N+(e1}tA|_Q7m;IAw)G-nZDp|G1=w9CCdQJq(R5?O|+sFl(zy zuW~w0s7u=XrfHuH=GDwe7-c2EY->;X#PaOoaq~oG<)UD=j3{Aav=)%E2-MF7NO3a1 zdU+K6%EXtKr;&;sA+jE4ZHn?*t9X@Q`CXr>3QN&i#4%6H0+z*6fzE7^nC~O$pB{rwD~;!qZoUODDqS;~Ge>f{lero)$P*aIxHHRPG#Ms$%Cre_sVm ztBz$doXW>-P{0J`HBW|^mSP@fgzj_=Q?sJeKu-@#R&FCqep$`-D_EYfJr(=U3zMp2 z4wHw~IqiWqhR)L&Yv8{+#rE`L#7P&&_!|b=a~q?Iebr->H|X>|LyMwk(g&BQ*3zDq z>elQtGUXZ-iq*6lqq%-yYYS)QlHv?!WTQq^%g81a?TWqDWfqFpvgzWt!dMIfRKo*O z5d2{E6};J|V2e_T%4S0(tt!(V%i>G$2d+ZX-x}Xbjl}Xv(wwA{>7O5|@SUARUYiOtNRC7ASv2 z7|pIaS!gwGA+s>BTXOMv!lv;!)IObs6Du5WQcrnAt%(e`2DZIzcU=vV-5F4biD!xr zntFq05VMDF1cN|`A#+etOjaXkWrEC-+cF^S0)x}j4LElCj~I@p{|I(@4iVY_s5rh{a60vlmGGazwlSDjr$`qDIfz{ zasLBKY)yxKUVNi9{Bhfv7ob*t$(V@}&DSHQmzRI1#I_PEPMDYf5OW5w8t7j*N4fdj z6dq>fVQWX1cZr-2XI$jLrru(fC9;$3YwhWL$TDG?Y^g9@N=VL!Qe=5#hUAprN&yZg z(}r`EmUbq;7lfd#D$oKxSjw-LtA~BT^8Cyvw+F`J-mf}~(T5r#8fA-mqr0mjEr;64 za|F#Z6VSW{?l!yPPYy9%@u%L`oOCS7{XCA)AV{^fsQe{(?E#mfIgMz8{?zLf|Ls0k{LqWW@XPx z@}7g*_%XSKIqg?<@uyR{5+*o?Euc>vi9C&CD7#q{vz2s;2Je8#C|C^(=Gtk7T3oKb zbAeNSTI>rqf1(Z2cGQPK>Vj+=q)rkbpSxvjkii9f>KGZ}xsQ<}VG=uvrW#|jq9cv5 z?XF**Q=~M|D#cQTv>K*P<>3HwmOn(Vng-Fef^f8I?Kg=s-;j6vzZBn1IU3ZtydiMz z@`(WPQ^}!x_TT^3M_SS;h5DRFir(RV*6Ng47NI6yPIw^%l@uKFP;+hl4acx=gJqG=aKTdnEMbVUwYXoybb^ovtqro4)pr&=+-I=SqYtD`M}bR(r10 zh;_-D7JCq%b_!xsJQTg&4(KIFGw!crS6${L&Y)aC%_+&u!ce_Rtx=@~sfn z^6|kXg^8ey9V$Q#C+zUH->Gf`W5tv8TKdD`1J28}wj|Ywz16TeC5o5xE3i3IjF-Fj zxe3h$twgR)u|>bc;FVS8zGqqiUqwW(vKqsAFuf%_wL_qq4n~KW9qiH2C8N_Y(6F`v#ql5S#EZFg*|CmR7pMrc0haTL9O^DJtR;cRPId_* zMVG?cXDmNfx2362H%BK4+U;n$6K7_ZQ0>;TP1RQPQ_ z|IGrK{;|F%HaewiyfQJY<#})QQLCCW7ctZtoVIdR@@BowWjAZ>+;cVL5h#B`YKARh z8{tUJP(QIt>bZVWxe&@yHgxnnFpL?gR-4HvM8)&To254MV=G#7GSbSt=^0|o(laEo z(KA}CTMC3MSn>!+p#@8WE4)s-oN9VTSg<^0eDw0%##dLPYJNs2Mca;dp-3r-q$e*C?OHj;KjpR=3Sxe~lu* zVjgzr17@*##<(H7c}sSU=v>#4@aNR8egCSX@s|61jfx^(t$HeU9$~c^_q0Z?8FLBU z91U{12mU^7=jV?BBjmTcQIw0w`x=KJKOA$!5CfOcqP5sWKPfmi9lRcXIPHBp;}lK% zJqpn?3a`-jQMezPi~ws7bel;!U_SBEUkI^#31XAD7+PK&>V5{Ex=KLGW>S9RJ$&Ud zXRX)KwR_8x`USr7t95VndB6ca5~c;bg(Epk#Meq|EzR;Dx6(wqOGMT(u$IBERri)J zEL(lmdh8C-$-UvTCZ8Fa&KL;yxf8{(l4zq5bZ4i4-Iq8Ka?-LDkVj7vg<+)k$>4DcloSS*xnn>hGQIQ+K=n1#2vt|9 zxTqmDI9PI6@{V;>(9C?{F``KGFPfZ2BJE*fa#WSg8*RuLllUZvUSRi4droNrhZxA zd-biQ2${vS12*y8GzhRP2)7`|NM3Ac%d{dfqu`Jv0Yz(aEL~QPO-e{KP35Vo zh;>T3r4nB4dxHEbEsl%PNEg$fWDMbTKo?V@g2(t3bTLx+f$^m%F7cpRcfD!^Z4{in z5y)m$T215JZsnmo5L!8@JX9%p#fJy83uq5+E+txqrlLxL<`qk29K}42@^oVEdZ`-l z1Tu~_uBDxj7GI4pjtg4%9-yzP2eiDss^?W#@mNY7E-W?2c8ijqE(gQNwgFxv3cE;J zyxKfD2zPq(Ujt^DqjIraP~GMtzz?_gj7bAfKdc6|(>Ga~SuMnrkxZwhkm`$O3|LO5 z(VVC0DMhgNLhAPcg19=&F>uLvO_qk?|HR`Pvssb~5$uQtMm7>EAJ8TO)lZ{J9plnI(BimyX;uRmQahfRCfcW>K7$9P)Pj1 zcH#$hTok(@El`dL8Jn7I1MuBY;4n)&fsh7`fe;)3kj&9)urX7t%&E?!P~#| z>!13Kpa0%J{Mj!h---v>l3slFd;a?8|M%asBw>mGUM_N!`tU04We7J2gE-jfFm3vW zRU;14IJH7cI;BFO94nOZV7HJ86IG!x^5ySEwoh#7^NZ}ye6P7!SgcnL1*ZeWq?4`$ za8zSsFpuJ@^PnhsIIYdj?e!`ANEZrb(#62xX+J_gXkH9cX2)A9+& z8{;@jp^qUi4M7~raO|{sGxRT}=O41@ed5T)R&+k;c)#F$lFf|^n8x&m^GWKNVI~J} zg|HDuVGAK)VM&_XDb6Pq^Vo78v7kVy%rUl{PjX?|Vw)G(UD}m>`Y?;2A<-7QLMb`W zE6-C)uFlp%_-!SYR24!-JkEScRnhaLaxr8C`C(VWVS*gOwn zzSVCWyb^oyw>FEAvmhsItU)8gf2o_1h6?!cdd8jo?42{`(v?Q8ED%WV0&B(VnQBh1 z!Ix&T5|DDNrkp%m#ru&Mz-?1-&uE!TnBp9<$Z+29!&E-Bd5%}hGz6Hbb%C&+@6^=@ z$#{mXGzB5_-+CeKm*BifH2R9Blk4nHH$TA(@fI4K?q^{1Klz62;(Z-~?r_6I?C{^I zIL`lMsB21WD@E$B_=)I`rP``AS2K@oqvy55%N*nm^ut*hPa@SOl#(?K3oQ{bArc=^ zAgc`c!>kXzfP5}>wA)KLSD%=^QR-{~_P_Ya56oBYM+=nf5eYM7S8FM~mZH@L(rYPf z+8(Pkt8!I2+63S8^2j7jvwb)lbD9H-A{=YP?s%2bIz&>HC{_t2TVB0FF> z*0DZ}hSH_qWLUd)FOhA^=4s6l1jH*E7r@xBEB<&cax|2lFXz%y=dzdbF;dl3F|B^J z{Ng;Eu!q0|zjjY@xpr|LeplZg$*M#B;yew(OS5V_DJ^GPt+Zj%AuVavOCzw@{A-&Q za_LqN$_VDnjl_>1f13QS6VGB_1+Qbrdx(F`iKxCE<-DttUt>V!Z}9!RcnFT6Cc~cM z970R9fr@fpPo!5~ChR7YeBP=G1ud?_QuN2|keQ1H2^fF1uL zyd&8N0^)80-8i1u^#Zo6Xrh3l`S(z9V+UcAdj_jrsnF3!uKZE=}T(PO+` z;uEL)62l4N@MX<_r$0?PJBKyu6Qlzi|M3jzUt`cH)?Ll94}AYIlGeq!Ee2O5hn0F9 zEccA%F7TFesM^tF9ZWLUfD^k5-&5=e$K2NP+}(l*}Zi0VsY!U$)GCjIVv(z9EZM~B*9;G%+L!=O!2ik(u*hCuAkDA^NOC-6WMlLPc}I} zp(ke*>3Ap&^tLSsKVGTn8J>~_#_~Zu@emyhGs>hMaENlE^$}O5IE;uOsjacgi7)2Z z76477TzoK+xyg6Tel13Y;mD`O?@9%;dmw^pW#y%A(d(iBMJfN9n->+jP5<7yJwh1pG82ezVRY`u{7mk=aV|f`N zXR6;+PBtx*%TEy&+FAQ|hD*E>p%$8}RRi>@4EIZep&xf^40G9?L4O4-b{lrgClul4 zI+W|(@uoV12sd_7Y8yf4t}`Uk6*W6u*3jLMQ-*6XcM@H*#wSgiU9i=qNk7Cp-JWQg z)9npAwq3u7e6X_?j`Pl<6NmffNoHMS^+(D!wXV_AR!j5+?4$PZ(N}7!?=l4C3g_Xj za9;Yh^y@2$`lf5aPVb^Ol^UlCfdyejC;Gj82)gf`%Be?oOl$@{LhoS(f)8R=Hwn2@ z(1$(~wGydeLw4;57-%t-vAtQ4m!}K5)tmSSa4=8W|7YP~-Z#v>xqxLT;+JVw(Mldl z0i7N7R^!OW;cgX2mRetN_dyYPy4WW5ME)RlCbj%LTlV5h6ok^cUQicIAe>$^aOJ+f@+* zr(Qe@NKiH7!8a(CI#$!>5H24TSGKN%!-O&v*QFQFPARQ^pp(~w`LR76jZm%|{NJ(T zWrA*NH>)0Kj9JM67?&qs2F~-z-($(^KpM^dp~mR-8u~%5WziEom{SioNdLV|d0)y$ zgIIf^+Upn3qAXh`GTX^oWVU9$V$J-cH6vr<(rM64Up!lZ#$D|6p$;B|UIdTMIG2^u z7zsQ}FP=RMcrFDrI+(`iYzcU{9qCl?_!T3K(bojnrU*Grs~0quau@{eG8{&jBn~5c;#^q_WrlAQQri)?VoN4L z%-x}jX9Wljp{byZ$r+$bpB^#X7V&d;fh-oRB;zIR9lAMf6mG`#tJ5IASWDn2abodH zj0$=T?D--f&QbM<%54&UTr-R{Rhwb@c!boVbrp7$dKefctiAkA`G+p02#g3Hg}Cjp zisN$vpl&$`K-NV8X#91Mzgng)4*v?6da;H%T5GV9DC6eCg;A1AFNs%wAk|&@km?Ae zSV-b>9@8dPi)s4_%R>tL0Ye<`&G~$i0|eZ#WRS}r!{5o+%@ae)Wc)7FUNabIo?ZbA z=%WiZ9ydo3B-zIA8{%)oTVmo(BLeODt9kue>Fdh)P{8*6Mn4_{EM*9)*}#=JTVUkw zB80Rkduj$3;gCW3!S4ofzDQA%yWobnR^{CSwfI6a%uCh4%b4Bf9HVE$kEDjs>fYt* zW&?GlNLa`;kn#kZE;K5f5E}#>egZ2qtgRk~Uh>g)d z0#eUqkEPST+9#LzYH_~QeYHxPdWJoJs;d`3(b;9D4j>u+Ph7nxmv{9-1|#JSj$SrI$(yEBT~ij^yz=Nm zcuKW97I5;)BMfP1nsf)I{(g={aaAP>NPe~Qmc3@ZGhm{?2^1{;wfZysBSi#@#uA3KafO$ z`Y7h7dDzbIyR@h$q8%1~NzZT} zytn5HuBs5Q=aQuStAEcO8yxYdDV7G56)Va^T5V4Kl4Y}bUP2;b1f*u`NFan7v|%sS zAbrDX;LN_N2GUF+SN{f#3bASf`XR&I(D8wmGt_p+FBw2=klQ2(_5^NU^Q&7`=)+nD z!Pg}K3Zj|^TYwl~cxctZSN9BuK=^ROvOFSw!!pDWK#NZx&caBzG;iw$ZX%F>)DYn~ zsf5y(YBr6WKYucXqpF=HQwZkFWlW)+@6Qw(B}Yi)Ha|uRx=(~jT2TWr zv*3VQQOr~zW;(1;$X}n5YEtpz*Yi^g4opodR|6nqqqczMWD`Z--=K5IRu4IokNTrGkTVrLrk*>!IjVm;FpDl_hv-M6DMk~)iDQIIJj$c$f}a@gv?+^;~z@L7`o0Dge#cEIvg01VbZ(( zRoq(TJZO;yV%6^p9A{DN79PSStJLm{gAy6cp-bVR4#dQTT4M~es5uT!u4*%Xm1AZCTD>(IqeE3!}+xu7OJ!U^s|^&Du$3 zQC{rEEnobjaI~)>5M5?D2?hi4Pq>K|)B}d`#8ncNNart|@?r58ZrYn>Dryi8U2jqw zfJ2i~%ImmY(Ew9iq@2TovE3TUtO%!`We-@0eNw-?3#?jBBG) z+2QOKFoIgHCJLQeQ}l>L?6)bRCh$Y&&-;pZobJh~#VCm2jPQZ8inR!-71W|)WnEr{ zo~w$Ej=V0~^Mtn`za)cQtZq7~w&>h5+DHZ`LWH_Jz zcPmBihgZ9rar1Lgcc>#zz>7hhWePP16Ep(q)0itflDlt8YO<;i&T_l6&+4Gwpy5EQ z!;qU!ij7t$$43N2v8@nLmoGX$eaMl2U9n;FXd3 z|153bmr)e0g;!MeD>x$wYGwd`7qL8RRs)tN72EuBbl+EBlcFKbttWn}q2gZqN-lvz z`YC=nsYT`)#9<>8gyfBSfK!y`=v;Fd8l^EOkg0_&NW)@#W{>)2x&sVgt%0UhQWH-E@ETz$xR@;DxXij)zr!vz>}5&(^PG$pDD_KVrTkG)lT(! zs=di))yb!I-j#KV+UO#NY+euy>Y9LhfsoX}Gx0k9mZ1MTe4Th5ft`RR!(nCz73<0i z|7AKLUPpY78P})^GQQYmwSx5(5H&S2Nr9u<)~iW6tUq0*Md~X6bB<;U~>zi3Y7v0}LiyzNEOZ zfZ5OR>X>Z~g)k^N&>V@iy7*KMq^h~6a|7?(pck>I?+FSA(nwzY)`;6HzRiBtlZ3E#k91rln05ooC^;E^RUT}=sgj%!{-mjP@oPA2cSvaIk zB=C2#;$T7q{!~kO5Wr8OhqzR;7Cl6~P%V1sF$j{-6m{YVui@sjM|kCaF{K{n6+KU{ z5A#Zh53h6x&nDhYA`73Zpok1 z@AaFrXZ2jSIXkH*#TPZ$<5X4oR4w$X3Z+sX!|d=Fu}A#%$_o{6MW`2@s%9wZ?a zZoC)|YcdLcJG--19F98$M(4}5&ANyp=<%{sZmqa^!}W6hp5j0)$j}+DEb|+YY%52} z_Iz}{$Z2JHJmH$paurF(saR=^VkKr}(NMQ?>H6&b9%mbq1QQ_nW;)lEJq+1pM9-@L zTUxZ64Slj(UZM}(@>ZSU&?#Gci$nyP5#%0nX5_?(X+e?7e>WW}WD534P{!OccFI+I z*^=}^a*t}yt1^2y0d8^ea|E2G+dBKf)c$?o{W;gC(Ij;QU zwW!&??`r#$S)aUOYY~7+U$<&e$6u0^K zB88>2>>QPr!l;yXfu~8+XvvMCG_XYZi1Xp^qNjmTO2dEBlEWZ);Vq&+d@2@-P!LC1 zs1~22H>y6}tvH{SJjR;u)x7%34#V=%%;>0x8Q)M#QfFcK4ca>J4mN|Yxqh6mL5Lh3 zkctN!RS$(BwG^wyyboU&>Y>cmF}8WP?^L`2 zgrb0Op)v2urzLyvv1oHC2Y(A)k)a1uD6NC791Gx9`OEv>JV3;+EZV!d?@o%8zKnaq zJ2v!vGkO|`vBt ztR%C&*8xDC=cB_)I9LO0Mj@O|P`FIR_Zy~q){B(zGKlZymRF=$yeh$gDpuOzdikVi zUnoUfU_^LJs>zvjHeBUC-gG!x9#iGO=x?_;y1vKJ*)KfiwK%%I#nH)w7)*-QRk*TC zR(Ppm;e6xS3}DrH3*yu~I%5_g3T;cV+oyCQ9^aIkhfz&K3)>3FRGg@X$$5Q#q`|5x zMs8ItMsAhI$WhO#5E&-J@~eb+L}wX_+-dAMecE9odlKDp48xxSfca41BNkJ|(?`im zUu-7q?N_Y^lq83HR6tp}+K)uxI1B>{R}%%uodaB?xJ1B!AV(E4CzOPxiPe;gBIg6_ zk}c3W5YU=0A*HDW63I_WBxC+4ps7Uf)8ePr!$)fvcFO;=h}=VTPWJ}sY6OKJCS%)+ z+!(J4{P7ZmrTtU<+)yWp>L7H-j@7)+X+)#-ylKx&yrM(I)ihZYPy6*0dGwmZk_JKH z$Mf2cTS%u1g7J851!J9PfRhJ~-_-FRoM3>Q^PF_HQ%JWM+oQu27)Pgql}%dr`~#5( zH`wE#4u?AnMs^<6nl1T#Btp)`8miHs4#L8^LFxUhWT}>exDNn%gvWRS54FI3Zq|Y} zvZ7Y1Z@#&;i(m*Xj#=`cZn>g)!bgxHwy{gYhcynrKru34SMiVto? zdWIFLC4&BnHT0@nL50g@R5qW=Q)`w^bo#GT)v;3$xIuzON;Xgp8wgml2U6zF4gX%b zMqr-g8#u=sILEVJ!(ve5cmwBnG;qH#fPibOfBlYP^$znWc-SQfB);cyO<;h22pcRj zv!&mfH2dA+$6DZXmS|zlr9_WiLh%+MB&&5G0ShhNB8nJD02;Ky1Pvs_w4Ietkb`6{ zZ%8Cl<%xck92q!U)7Yeugu!X{V&zORSRUrc4?Z#G5ZarXH82-BdkbY1fm}5x5m?}D zo-`=#qvMzPQcxWQgB@*1=q#+=%%!Ab{31%$m4QSbYM|6{5>#83k++i_CzBM}a26#~3!TP@t5qvE68`8(5tBG{X_BH! zo~wmXPdNneWScd8c;XXB_HYDZPuA#{BqV+l!&wR)*L{SL5G?i`B)tExz_}@ zXuw!XJ4w-cM{p5-`)q*H2EUAh$l%;VYHp+}b(7>b_6i zt7!+ZQ-^UthhtTpxW~>7#8VPSUA0q?4*W#bN`Bv@!<9+v6eSXq9pVWfrwh0e`A4pNb!S zttncgu2HlP4&8>05qOVmE#T~nTn3Lw$@k`gq?zr! zLHs_FkL}*1$MQ-1D58lk$MAxgVy2K)%5o#1q%o3*ohlpHZ^%YU?O8cCi6G4+!hE-X zX0w+`js*$EalQb7^jgXZOJPl>%JZb9Ajhhd;}$`h2@k3?+8oo5^7tf8TR@Lxmvk2Z8L7K@ERe3u#3iA4q$&h-A%FM8zN=GyLazfkt`TdO6EO~w*#lK1V zNxM^BepB^;V*>KAUB-DVS9oM*=7_>0F~ZWoGH?klTA<`0@s#>sISk*IA`F+rOePi< zPI?^R1?VbadsJAMY5aV|M3V4=;_o z{NeJ1M%nQLi5=8vusTU&s#S${sMaF#aNPZ@|NIW+!#MGsV7t+|EB^Z}V2!-`ZD(pWSx8+WUP(job z)kqo&CzTg606eu_OO<~EU$MkN<{RzNUzkhH5<7ya6O0w5B2IWmZXQJn@%g9_jxA(# z;PT|>)T3XpJ+rps3P5ilC|Z|E&nKLIA(tpQj!C!F!Z+m|4K1A08mps(hCvy_PdS+* zGH5Yd{xQZX*pY!(Lk?fu&RLR_1Ckga)IWnhC}WsatmaDdM`>jBqoZ7g7S0>Fq=Z3m zeuuR@lo5m8Y#6j{xB6NI?N-F6acv8|@!;KXC>Y3;X4{tjQ}&Xmy{TYcplPVp)_4@F zlrl8JHsenV{?yT%9(_m^Z}P!zjfe)4Sk#q)%47C~Yy=>w9z!^XmVTWRU;WLO%^+nogSYN1|!e;8W;Z84dJ zkv1ZCbc`A9|(p?HP5Lpi_;5OiOmOn zZyM`5l2yS!>k1Gc+zRJNIX#CA=hQi5u1-6LxJ@Di70e4Dha#sGwn6g)gE9?Gf{&;Q zfwhzZtO@7GOb{3k1w0NyY1Qz({>5|TZX%XiLz*irr94r?MP5G$X7n@BFbjVUyZIF zCzDuUZd0szG1u5xd{b0l zl7vdYGf%Jpwu$gH6-gmAbfXelXX_ijOZ81s5AG54ouj7ju+d7w&8Gc9*euw93^ffI z+~HNIb1)-=6avUh9i$!|nxb1_b!OvngT0;D!Q9x19i}*!zp6-%i>VG2CX$234`leD zO^Qe@?e{CXPG$Dxd>M1FMmIKC2_fRrP(ltpXpfb0V=^b%S8wZJwY z76KQ##X`_Wi-pLFRa!q3<5L7xpg3x7 zXX6x3uj84Ww3gL02$hhfz{tlPAcQ}6qr2wkMllHkq1p-zB$qUEQ3P0GM zv!cS1wpK(~z{t(Kzz496spD~jd^&hDS?cE3wKQN3N%cSpizzCnEn@IvKRO>O3nZj@ zY00T~h|vwvl@Uy~Z7RUkGDCEh=Ky(F#1{ZKDoPxYI0zbYx4L|%zKk)exJnCT)Ru1Q zV-8ebb}hdsOozPl3`naYnB)8H1T}S21Ev337vs6j&3$%-=*3)pTT6Ng4z+$}C{kCT7ge|}0^E^$l9kv5i z`PO-w%oL#DWm(b!I9f3q&+v3A7{lU~ik>}I=O6OCAhc4^h{ptn$G{KJ-|{km@G65m zvoUtG3C$c`C2ck0^i+=o~#^=f^*eB+NnS}p6K}9=2UEPSrmE9?z1w)Rds4u2C|L?;^z@|8Re2H zHjDfXa~L3Pwo;tIFcoIg#QF1?s)O1n*NR)5julcAjZbRJr#Y-tiau&<6XDA19fQ88 zWKP`LLO9PfHlvR-Wq|n`Tr@UQ6vQlmz0M=q0=c>m6n7G|nFqC^%JP^ylmOJjS_|a) zP&o3$v?j68nAe0Fvd5;Xd{$!fQhvZMIJ<~~i*P*!O&JHHU0vVQ(iw{IG%dK*CQn^5 zKDP*;)8{3;&;T1r$a6?tiQFwuP^8u5P|-w;Dx6>x*f1IW1S9`AIl;)jGa+%=XxXVj zqX|=ko+VyZ1a3BUA)t5>u0Xq3xQWqGV{H`JUAFvwqDI+{HwuA*)TUB+0e*P@@dWjM zbCSRo5yhXL{8nwmG(|>fwT1hM*avfJZSj;?m5Zmu27m1$?4=owiFE>N7HP3cX1y*K z_DM71GUz({brJ3?xte>XQ+x(cgWfP@!#QLmN5=*_+lJ98L<9v5&-{q0h&$P}c>F*e zX>yKcMe`=wxUa2mVmmZWJWE)HNK^4nrnMZ05%GMMIofS=wbh_)kR%z z*HsvHWrD_dM8Et94s(c0t1u>kP6sCBh&9XQ4sUtPQ#1zqG}4&mC@;VQyP&O1QI0SU zAvNs8pi2W0ik0uz9aizq!eg0v^Q-|%s2h_8q>^|8G7zi*6cM|$4Rv!;H!_{NIkCeo zNJM0LODghE3WD=IoQ6vhAI4kbDxi{IMf353!`MFRq8y?$i50+KcU(r@`oW2AZPu>{ z^EmJjWgg4~rr<;U&L6)gl9X_2xFvI&A} z#aCmQITq6d?LGl@(%xntCs(iv5Zx{1p!oHLjjm(V~ zS!m_=y&uGUS~Y?~a4qLF9kH-P1%O5eS3tZBN{+M%aP$ah{-4)e2 zD?pnuPoJ}LA@K+hZC3%kDf9h{Hn3!PjOgm0X{Y}Wd2a)4*;Uo~?vHcsIrpCXaq1&U zrEWp?IbBN8q%dfjU^VdWx^@C(j1dQ;hoeV#BVq*Vh6?pz0b^itLyB0m@fn+H4O&Ve zQDPgK&_oFun;R%3U_ku%ydp)Ih!8f6le{+2`JKt12NV?{(ee?7i1s zdwt9`*Zf*@%{6vI-CcpkSVlMn8T&11PFO1fn=`uirI@hS`%MHf{q&Wdcuj)m}sU4bRJl*i_k>}b1c3i%e258sa8G#85e%Gp>Zjr*fixw{`r`_1ly z7&L_$1HI%g+pagV#mgx{>GT|FSa%JXq#D{1#fB$YZQ3ApxKoAbhb-ZG zz>)8J!!F8T4Z=nO@EWdUHUxAa>wdzzRbD>87)_1K&m=`5=3P3a*mL1-FH>M>3gXR- z#erBPKS(E0D&j+z+T)jt#_Mbfv@IioT z4KV=aXrY&1JwD zSZ6x$CDkJ|0NGEze|f|WpGsPqVqU|r2FJqCm+rhO=L?z|)YI=XYFOsR=2{&hW4Swa z6qXtXpVJx@@~nCKCBk&YnS!HYzg zcR{%89xeteJ-?%T;xO66O1Miu%sp!D-aJ&{&!qM82kgySZbnIb8mHyH#89MR>AzFZ zn(m^^*F64_9?#dKW_$QcJ+9Xycp?c^4sB}XKkCIqcZf&L!p$vDRX5r3hAbsdwl_~ULxh4$R9?>r9sE2W)j(UcP zNA>Nx(U>R*kH$o88VD68riJDeBbRnWfvgYM0}~=BfdCxhA)g!tf=EKlWw&-jG^#_ zdCyj@x~KZ$JD#6y0k<7)7ju_{&EUs+yOR&?=0Sty^@BUF1Mb%%J(*+bXbw326n&dV zQBaAO>hOoMO zA$J#BnhrPHY0SAnDiKTdRB#RJSK4Ok`sZBcK_Ivw_^ zvxL1(yx}_U$*XrR7AkC~VdE5OSDGo6L+ zB14=WE>RS6c;FXca$iq9D-$H3W@Kna+T(=E`sF2Zp)}b(r+y2euisdcvh7bI8OL;j zqs*c>*I&3J{3-QlvU6l%dCTP1+&b~4IJe-Kno7)j?&N)b32*-~B0rfm6+|uMkKRPx z5H+Eja+Or2lYN<2n&B2T4eqVh=mXZ4Bw>~wsSqA3zI@i21HP(TVv$>{$SAo`s+37C zbbE4*I)F)IpgX*k;_X0-fp+m$9SVlJHPj3#P^K90gA%Tmp;HnWS>_Z`BYH3Dq}%LE?Q&3?6Bwtyv0>gilIh@g$gae85yUM* zPosHnvV!@sRTIN41}EF1BP2BDeXU!-k;E1HXu}E#LmMyE8EhnO*wKBHlw_@+Th6Ax z9ng>q$nB#XaC8RE z1s4Z0u^AXwIB3irJdfglCIH;=5Xs$Rk~c?uv;QtlpB3LcMzVdguNu{;4rUA%D4Nf4 zqj?6TUt#JRXK;eK(%w{FQWR$5>`2U*yf%V(R@ZUG$Uv;06?MvPNgEvHov`nkcYn}inIuh6zOJE zq%*!56F4>cY&~*C+GaGJHbuG-6=@luR0^3)krv5;1aOIpbVMVmuF|uzfg&AkB-(ZZ z%xj8t!zUjj(e8=SS^GseY-}*35-icZ834^NT+VQEau!ccmdD6E^u&{g=}oB*8!X8-z>A;a=itMpMRz}H zxhc5?9wf$2j`dt_`z0U1rVk*~RtgDxUJ?xJ@s*&`gd0hgQCc~$+F#AUY7;yIdtl|+ zzPPlccD#zHj{k`>b^An%^o2nq+=sw)cZ3u-;&xn{3N$bO3y)O79if`-OWRW5av9U4 zr}w@|oiqt`VslU{**HTAo`4~#Y@uTVsRZ7RB@H&A?UF)n9e#~f&}O`#16ia>yK_q$ zdj~|W$Zy}JE1;#|~uJX(R;Ht6ZZm?QE)j+#$9T8C~=dLc`K#ng_>1^v+_<3 zJ@ViePys7X-;2rV&yt1de}M=N+O7VtnaU8-2>pfWPL`Dan#Nph*nb5(X666N-FOVi zaq$&=_226T-j>89=BhzGbmMA#xdssWvA@TGixIu`0u0!n7f?En@?ko;>swT}R`= zt985*UaEJk?oQ!lL0uLk9iQ#*;LWpiT8izfF{k~DU1HsWEKC)fBjwfvD|@5#aeu9t zfl(}L`jlh(Hj8o16m%sgY6M)yk5Q+%C={s0QEz?*j9O=Q3xGKXuChx)tjosr z7a3utLPSJ<;%G8lFe#2MliQTdSaZQ)aDB>(61cM%N>_-yuRRZ;NJ$kIUWF6=cKSOiTQ+@^)PZu_3EHnYR_ ze&I7d&VrgP1TJ2`OM{2B#y*UuT=`cH^cql&qJ0hs!m-po%Nb!E3$-fwidjB~;@L}< z?1&gI+Lo0=rxG+?BDA~{df9uY-XwBtv=Uq#z#$E?i?kH=m09Ty(sfv2;K1Zp$!4e)rP#N*pW;xYb=XUAismI|0DbcquH zBw3AaGXEnx0u?kzdRB>@?Keqv9h)=8ECr_3iZP>^aW(O`Gfg(JLqTt~lFZO$B{6(w zElGqJKU1WWpI<4R7!%SqjN!i$v3>rTD3bcz&PIqLQkBf9v6pf;LKMjmMgoDj8_D>1 z3Z#kXnS{UoztSsMN0;Y#IPrT+EN3Z-PK#bgxdM%*!Q{0`A3tw4Q^44$K5uHCH)@|t zgsFdSKp!n-NRn|WLsR<9Qih5_PD;Z`?2ov5Ex4W>Yr?^pW|%=m%C@EeqjKilVjm32 zQC3S_C~;#A@EaPEr*$B_Rb3;F)}8JywOTGCwEA2T?#!Ull(n9HDoyG*MRIFldMa)B zzbt7qa)6eGn>6$UeYaRZ;qB!U#IPF44EUN{QyW>H)9`NPhPY<1_bPYpxzlj!cX>O* zTS%tM!2ebG*C~H8$S>lf(hDK)l5#Y=3-#)3c>g&DYLBqn2Wro^ZlDfaOay^(MG!cK zTs(ow3o{UTo^aiX&5S#G9OK#}AWttPo5bh#<~X`VJGAQ_?ew>>--m(4v8ohZBZ#*ARy zx3m3+Z}J_kf0UVXTzPsh6yg%nmhiymmSmL}cFVG^(?-HPPLCy8`h-9QtSAiKytcZG zO*PmE8gw2Kej@~E6acUW$;TJZ8T2ynZVWrqcqp#6tvV(#Own;!UnNNPv+yE$c_G4@ zTzQ&fGREfFek5+gXA8YG8krK*gKG}(DLrLE_RXsaQSmCEBqy+ke+4jp0WgNU{tIy3 z6pkq2i-Kz_Y-H!fl~cqWqyw@^PZ4pz8nWYgG-uuX>GWk&Gn6e?k=R6gvz|9U4B(Y+ zkp=phrJq+zx6>nI`uVkV>sw5}pqAcr5Lh`IYUyLO^b1L+MOJUCmcCK>rPqK&xcQtX zn!%W+I=6srgu3Gh_Y@a+?l`ytE~7g_exhUOj%c4PP=#*p`MnJwwtZrGjcBTT8Ex&bnH9eRS5> z((9vhUM;;oI_KBY>!Wi)EqyRL7YMF4I_K-oM&~@;+32j-osG^q-P!1@)t!wFL7C&| zD6*Q54#uBvkI^yeu%Xsxr(=r3n1>5%=^C4t$*Y$1jkR=*&C8%BN}sKzYiwSIyrT3? zwRDZm%OqWq{)}3>#^z-*NRa-_TDns)cfh--mTqGMRhMnE(>TwErfp5{51tDfZL~LU~!t$ z7YPS26Q%T$J4_5IaNrJ}ngS{AEZ?l|6iowYbBBJAG9KKa8l)=Owt*jpKZRa6Rk10S zjr0n3o3cnC+g~{HF7iA#oZ{jlg0r%4ykd3_iP`nS{<0J?OL%B|I}#y!X=pF>XRg*d zbos}+sf@j>9XGY4MeJzFH!bS!r$uY~R32F@UYVKRuBb;^KpUik57JB8APW;M zhWa)g^ibcX^Hap-rt?$A8@WvR7;jAMx}%Rx=ch{t~Vjp_2C;K|0^uvS9 zD9}yhQ!twIVnTY=Y!4eykuUVdOMG1Y3Z|;b{f>;ZH1kSG3>TR!ah&A;8KEOrGb?dz z@xN?Vgv{q8bz-$^Xve_S9!G}iVtd^6GG@{TGyHGHSE@rqWec)E)--GhFTxbZd?4TV zTN}pBw_BjYy?7=}pzrfqa%?3>dRxnUYT@KY0%?<*vS-4!Yr~6oz4zu~?S4h(BJ^7?SDDk|^YYo4vfSe0h8A zrEN9g%MInryK66XW`>Hzi8`t_wU-2Du$SkTFCVVGlxw}dJgV1nYFWOXVmDh13h4EjsLE#`0rZdzb{S_3>7T+u8lgz zDjc1Cwl5;9vq|tyi_v|s0X`Ywa(mTeQMFUT-$%tphKIZLwelNP zJE$k4YFI79!C&U-^C}BPD%@T@Wq}QF)l(i=J+GcHuZM@LCm2gOUOkNk+E+b+SYf~5 zuNp+Sph==pD+n8F265HE$!0M#J>DK~HuCW-O~yO(m+)!IXRIgjKhyzwm_iS+bzt_N z-!l3&YWIAx@)eTE{Dyim7Pa|qnSe|*-;NInBq#9Gl26jwV%^O@WMmUcnt(NvwpR`M zgPp=~C}+>~3l6-{#uBZRh()6C88ia32^{5W5qgLN*)ECMV7rjHQQ2_o5JGur@M%mX zqq5<8A)AV4Ls8c0NFyp07J{mt4b3{Uvlmyit*LC?m`zd}MwNe+VeG*iBO-~lVfMZA z1^V*Z0&7rnW6_lvs8Mu%13#OaD1rSwvUq^W#SC*%(pIYf#i!Hx49neJ!9UE=Z zaIxeqLuZCh5)Nvf9CO@U8(!4I*h5bX+Umh_QuVdS-isb!HUPL=M?qPl(W@;NPMN@{ zzsUt7*1(06wNM&YqP4Z6&ngrRT$s*MxuvCAb+Hw7?zSda(WP{+kNyuS3q;Xx6T64Z zd}%oJDRl~8n($e(y1B_n{pL>oD@th5+X7Kmk*?&_PN%F?i?hracu~iyqW0e(y(+3N z81*OLwkN6r&%JFr*e>-2P(0h*oX6$aS%DDI)uJOiI?FAsfZaB?cELGNtj)#7x@+s{ zLJ(U#k=ZVKUbe%h*?ltXddWndz^TIn?<7PtQNZ(>2^%eVf`Rl^InCP%1)XuqEW^|b8L8ZL6C zzDw(q>o7y-GD9=}bZV6RNFnRcW!<*>Z8JXe5)VFxH{aRE$ha5DSQ~s%WEbDO9Wt0- zTd=H@uwgK!0gG`IEQEsbt2Bs-b~hkdX+E>tQWLjf6F0I5xsvjYwq5q+ecO=dOvb1* zw1?3*y0KH2O^Kq`fv>S2gV{v!)Zsjc)3KmNc0;)Pb8a)vNSCJA4ztoU+hHTqm~X*K zWc=xzH?)1@4oE+_H1MU#7Re2>F!Qbv_CEkw`2u7m9Aj=_vOV>OB$&v-d6U8l3H(Tu zoo8dET{HiJn~^ED9e9rfyKRXY3%O>m{Xpx-JlU_m*LH9i5;@#x2|b>_^uT-YCmfUk z1BrP`Gs3$4%JGJ+YNF1XO4OnCm^}My-qW89=gT{E&;XW) zrABp!Q(-Zb3niW3w5VuXECs%{YxH6oj*ArEq(qhtIXOMK0?nnr9?3)!3fD{m> zZy4}XW%1_(sA=;774wZHG%?hY+ zR>caaoUgQR3Rf;r2`l=3o|QWaS6~f-%Gy<-a-lMu1y@)-dD2jswQp8Hg%dJXK;;=_ z@lPFBo~a_hmCo|Hb&+1MnY=!?M2qm@(2=a{DM35Z0ykoqR*Bl-xM+}wlBgTc`Rn_W z*dPj929$v-?Lgh6eV}i>*Tcab>sLS@sm1?mPM1!$?yNB`^L7WMVy_C@y zdS~XYW-)+ZHph%rlB@!Ig9p@AHO@V2P?te@WKH#rA%qWSSDa$W^ncF z%bLY!6;NY4a&4lmN^R^7mkdg!Ui0f<@D2(dp9eo|C~yB{d@6_XZY3BC%K_ez^0o21 zhpQ+05RR8m<)^2rPlY7=%X+|2W1@zN&aOkg#~C*kcXKf$rj{AUK%VjNB6uUn3vd;6 z69w=JZ1$Ecu{ZNXE-_KO-cH8GI755{hThhbIacEnbmdX?RCs(!%v${pCrA(fG8|NM zZQa_!kttmGUn7Y49u1D9>%y@m#hkq8Cc73tJmstZrew$`S&|t3e+jy}JS=89$Vy$? zsfuWKiwSF!+--HOWdE;8n3Ve@c~Xf?ykmAHCj3d*XD|6F8Ay-}k*`>aU!u9TZ9f1} z_oh`mIW}pr7$PmnE(DJ>H^gv=D%m-v?u0vZe-A&l5$QsTv) zaU+J$5H_+j<|G%}vaA{8jBH366V40J)Wo76=06;j(bX3S&;qbC_d3AZX@KWB+Y8o1 zLo)D_QUhAOO3o$7H3rTHsLZ%Pv-tUzeY<7uk+_Njwr%t1bJ(SAt-z>(Hyi5{S6Di-tZfFNHsni%(q%0k zG>W?%*w4hHBBxAZIL;R^-kAhS8PFduW0FeBkL96&bW0QV@}uS`cKYt;CrG9Gg8fpi zmSOop)a1l6s1)zmmsKeQF>Z>>!P?ou7PHa-^y`eLCTE#&J8t=&@K9VwDRY!)lN;Ln zRUBo6^=9QLVnRG@0J@4KpiTWu#wCjSv&D8MGtAB5icF*dlEBQ6t;yn-j86EwCm`f}EhQkrq*a)Ux{N*Dx3 zMK!|`EmdipyvOzqY2R9S3iqo6#~1`=GRl9#OirRlzH2YZK%-Agv)@bxO?BUSLP>=y zRknSbBBgnlaxV>6)cojQsy;fyX9b^$I54NJ51F-?gp*{lc9cWyYi@5~Q(DR7ILyP6;95{erk< zbA^;P`R8a)7ljB#^1?#NSgbD7h0_xJBP zwDRtwnH4m$x!85nFiVPK!vPIoEa*_9o6;BcN{~@pX&H9d#cCeF+FIEJB|k`Ci>7;^ z@;wAm&V2O3SwsadQc8@2(>YZ*Tk}7b`j#4|F=%I#miAVih+VMllQhL}R~Ja9VdnCT zc5nrp#fRA#8k0|mr=162fEupNu7-vpMBJ2(27PSCg~$J^Pm?mxf{b%alViUX(X;At zb@QSD++&jsPfJV!e%5xoXckjZ0_HgObRHHEAS$V2xq|5|?--gt@{*6DT{srlMv=i- znLC03_9ARIE@MDp=&*FaXZ3*iRgDKcrQ+!z z6~NNArVI>0={CTb4b1P#7FdW_^MD1M+CeNxgTtC(2pcw=im}UVv<$mbZTH1Q3#JGu zFXnPP%`ro1_?I3@L-S8t#WWK|i3_fVrHLp<^1=2^|w1T9-3B^#OC$ zmj`U{z_rbFT)Q1qTN;UL1rcK2yObbrNKlf)dN{yy9rqUN&*0wU6}plCWc6TTWyBa{ zRBx&_m1|<38DmcT^I;5x(!6DANVNGWr*~L8R7#}F-nU|*#!Z6+3fZp7EEL&k@%P-a#xzf<7;HVGeQNJwo)A_ zPZ!{A60_q*{Z~~%&&K5hD=n_~(8bV8^J-kSIlwQV!1VoO300_$A1`HBqzo*=h< z4>VQE5S8Hbm<`#VkC^)sr$9`b#4nz@CNKcy%T+}&q92YVT2)1+MT*12kzg1v09M#gHHv-n@ z0CkH8Psq5cO%hE7&${&D!auK8RQ|1y-*3sk?dJ%)0>3qLX{bgUZ85o2)Y z`$m3qH_7F^GHiQlv*{agG+aml2W&Yj@4m5`D3X_CACcJ9YnKeeNSTQkMpi_4h(-g} zQv@;@qTaG1HpncXcs*sHo=CbteUUCMB3+O=m4JyJd)TrPHX}Jw!X|u5eC}F678V>; zf?XsmiBJvCT|J5PAxTg6$5sw3-#=O6w}LVFz;2=GuINT-@JKaCBbbQ~?22z5uD&@F znRJEJ9Uxp)aS@471+Z(6Ze+6_h)L-6Q)Wt8cR)>_q8>L@*ue{j|4p{uwVfrRGGlzK zux8~DoLfp9+BGy8he?gBT^d}(J&HBm=|0+`3nc0)l1Za?z6(YdghnrQV%Mf`SRvi(CND&6u=4Cs zIe*vgUMG{`Z8>I3J1~!O4&`R@)TegJ-rzkAWrO9fP;+>v`3%}db{-XW9J3qO!#TEx zvXG2Lo>|qZ(BO7b8*lDclReWX(7+6@O|zASNY+-Y1`6-wJ;~?^=@XQ+NhNu!%0^qb ziwooyI@~7;y&?81Zf5r;(X?2?F9A*Kx89>Yuk4rD`N~u|kc{juE!N?bW9qPN3cq}6 zJ+%=DpzL^IL&Y)MigUpDTsYk5U#_+2B~$0~Ee8mggv{+>zr_a4c_=xiX{THQS+rqi zBdwIc5=o_55LYXjDCJddel8v@fgRS>etPaM)YkT?^VyAt=hfv?vo;6SB_0UK2JaPe z3gDDX)c9eHQ|u!jpx5i0p)q-Vgg1Dm-~v7#?8{m{9+Ms}VI}031cP7T$hzkw*t{C(Ut*G%wZ3Ld7%3taD6{hXevZ*@FwXKK~CkkLn_CxRj$BFc^L@J3f?YOLmS}{eC*^+o#oK%GyGv4byVrs&JvZyc|wMs zsf@4%QFu`c1TBx>c_sR8A>nvY*{DP)WBt9d7 z$OuzC!c|O7_5`Q7h3aGQDaI@?*KT{yd8hvIw0{umJi(+MQ_uq7b4qT~Tu-vkU)RSn zL1IgGBI}>$13JM%$a(fXy`C`q6=+zlX%cfd$x>_SV7IqOTuWkhGkoRumf~LC1Z;1~ zc0_f|&(gj;RLDea&o_lQ%!Z2K%aGw?DI8Ax2ox%ta5#5ZQflpH@sA;3Tykx%Y_Tw# zLk>LH10F;4qzFA< zu=~WDloV@aJQ;2-T#6>L--g9Y8)l--aQL+`38VAJGdBK`bS&fiICjME5ZvGnyQmi*#iLrcI4QPV=IS)Qi!}=d0+VCFcTmfAd)i9Y#_cr}6aVp7VgnB4) zix5hITSO*Or;RP6K~3BCS{1$FJ$+8v>{)Rts;boZ2XB#%3x8V-Ms*|-7`m$e3guZ+ zvhQ_DrU*+~&u#U_RsWM>4H7{%;VwZdWZAMMBPwD1-6&$g`VMK(+!T77Ze994tXtUG zz#2OGLsu_HeORp!ReI`^Z#=s!InaYSC@}&oVW_tzOD^`YgJNrPUlf&$m61DgU7ZY6 zva)-PD%-hf;0Xtx3cw{}QWP|;U z1A+`#Sf;>J1e1k@u>e2J!ZK|FjO^qNqchIJe$!^w*5nTFCo{gR#H7J4&9aK7reI>3 z_LDJ>Yu_I(`_qX1QI4(2yRwpx**By4nDH&9IA0izh2t9;iF9)c=uAo0m~!FE9BnjB zI#`Xvm;d-f?;d*zO@hUwv=S#ANjp>ZckJJ~9C|dx#MiXv+)n|AF7Aeb07^NQ$WKJU zp75=gaB4i_XYYYWG*C;S8`J8u!30f|PI`2sW$Z|zmJzZuvm>YVB~@iOYK(^|Fq*c? z-M-`vHCnGFaU-s)(R!ST&W*~G(J653c1eLt7WmCld27E5s})U+Lrj;Eu_N8&VMykz zwoW{Qp2!yE6o$s$YBSCbE(X_b6$&~b{rPMZCB@%@f!yQ_01_pUn}k_UFtzEn8cM$- zPKn!8uBf34Be-AnZ0N+?CI&o5F^p)Fn44K6Fl>mq3HsDxhdVoaD0Pdfzv5m}J7Dvr z24W%|Y{s^pp+m-wiW=d`Og&qZ13pvnuIAxwHIjq`21AWRY0{yj<#=nf$ohY*!{T(Bjsl?aZ(n5zkHlAX1Km=ldG;1@x=Jbip4 z1{sr>Imwh;CpUki;;HeZYP66eUNkbZVt&cpLaea8F|3pGnnQjpG-PE4NoysYuo6r) z@b>{pVQcbNDTHTgtg2jiXYs!1`C-$Tjg6Z$0dFTtmO_4Imm{HC$S?70(F%4Zsz(;` z%XMa>4qjZP(w1l}0kj%K!v~2Ihltnxn4PoW@~Z;X(F}wbeMhxS-CBPRREy0k9~0_1NhYhZ&P6mQWVR6Yh%NDd%Lq>_ zJcApK3%w{A0T7HG(m-&o(xftw5qSkENbw2K>0-6QUyaT~UNu5BMidS(5d$-BO74ib zI^(e9rVkiIbjFr{bm@x>ku%A5z8i?H=#vIdN0Y zh^xN8tL)D(H+{d5+936<;--(@|MPEet-?*qcTbg@J|T_ke?M*-yZvp)P2qPHH^sUH zSF#yba?`^?LFcCLGUBjYFF{p>d2V#(xv}D=7kYx3KJTcZ^l!!~afizNXXB>CNP&LF zj+WHuHlL;F=UbC^)Tt4hakWw|jjmqqV0l&NC8qAUYr)mk%F5lIGtp=RoOqxdx$F~|VE zafO-zYHup((v%WLOZn$$ty(Kdf4Q})kz{8uS3NsP=D+sIPu=u)pZc>8eKGlAqR5}Y zW>pq7%4jvBAgLAKwB-tQ%sKxUVM2v8PmkrQR%}gyK%5v-AXc$lof0)}quWeRVZ(({ zOV3|!UW;NK;(m zdVSOs|l-JyL+wD6jNSB#I{tOc{ZK70yjCyvNO{Syk0iukR|=; zIZ3lk5Kg#X+lK@Vg@=|uNZ&^KBjG}6D_q&EqCTSv^1$YOJ@9?Zb-wP!W@NR&W;g{= zHyA;{^$0<=M=g7pozq-uT<3R1vu;quffAil0Fp8@ZF;9a*&wXD98*Y`Fd7EK|ayKTJp&T8~mL_FIUUd~uI;Xr0gboZPW3G_XF66ASG0$wwCozgM zbgaP1A0e*5fp5Au?;KZXvEdI&YF8zVTS=*xWC3~f8J|-go(g4o^72tRrEc5oDOQ(= z_mK62XPKLQT?Rd_7z2IjhWhU7%)d|?sN=$GLL`&oz>C=_#NCV8DU`?(forx*@snH_ zxi%Nub)6J5(o@eDF6%HFY1^ZlkY`%$Ys$FR0>1^LcAOM?W7DGJ(_(<*AUqZ|l}pzm z!t3N;!)!oon9X^;TH=J$vNk9af+>y^MML7aYK)%5Iyh6x)JckD9a}|W(IPvSS%ma2hgV2n1rekE4_wiA# z8WFb8n2D~85y6uzgjx4Lr!pJZ;*DKdyfH)HQ5H>r9&e~^y|$xsdosJ+ZypnP0E5PF zk^E}i6MV0sdz|b1fg5qG<=l2_I!#v716?|y>!|jr-7GIHYhS9P*Y(M*CC$Yg)t6r# zbz#6bn)Nquo;yzX=4=Pmq6c5g3s+347;Xn-oob^U4^O#-XXlkT0^YL8EFLLj)sp^EMr zjvY&JYzcSs0r^D9*Wo$td4xiWLpbN93B-jiIJ2ooDR-P^bz`Q0X(LK{xGL%5b1n(l zWkgAjRV97rIhUk0$d$djJ&!l@Tp|q$=~=TP&(EqVK=`7e=dZO7Gca-&AXx zG)JDBY8*cDqQrixpSe7D-r01@$xh>^OgWj`8reyh-U9$Q%!EJF1@o?G#c}RAT-qpO zE$1m^=?>-G|3S|w%2&(jXAg&8Asp1WG#N5uuE1*NeHUDd$-mXt42gj42=~GQ4&o0S{VbsIWuZmM5H@^U(`r*lPQ`!EfJB1iyVrG^ws=a<&nBI+u!34N`WWZ^I3^g+Gpuv(lcj@RjP(7}|l4cu}ABV`{=@;`0j) z6~o&<>aV|7dIR`|loa%9j; z@n(s77DlbkFF5lRVV7MiC4OC?1PT>TfEiTj&jv5lCiFk{>!GI@KDLo_A3Cz6z)*Mp z0aHL5_g}##=>kz!{G#0_8y)FqIvYU@q?hM&VJ%;~o9{So(cywCLs}5c2D$?KtTNlz-{|L6WzBppn*#!C^@NXijjjf@XoMOpG&2R$AnNKg$=#& zBpOJF8vrxZQr684Fk=J2u*+a&t5BI&jKPWo^pZbkp;>KS=gsqWcK#$y8#QS9psj#o zBf`-Gwl<$WIJ&%1gQFJ2&lQekUsm+?Eos`mA>_S09R1m)#CKK@s=*P^c1bjPEx>Ji z_k46_WRJ(3bv2ubQ3oO0tolZ1qlOY9f#e+eCWRKnpisrlz|Jf%Lo20X2uw){e7Ti4tl=Jb-8gfE}wxp(TEzh8iot`FJ<&)X#SIUJZI? zJ69V0b~Wu-{MI~|I?KKikJCGDYbUJZJR&%;sKTx<;3(SH!lu+yL}*Xy<1jmwiUt5k z#ip>O7;R8dAPxzVsOuKNq_LoQHf(WNk1ws5sJ36CvxDaI=V_l8!+|v+9Qgxi(^sHP zWg(P>frlCy%3h0WF7i$)htY)O2SwnNIFO_{mxk@|+9{%{X=vs|5=ShT>}_w4C5jG1 zx4IOdKB=o?;93g=fz}lOi4>h0X8>d;>j0dCJ0v~tHGyH}t%dJ5)0eUHGTkxlk_GeX znjk8E;Dm27-Nylu-#Kpo&R_LkeBYrkVs3c&S-$1G$d{+*)&31V)Y3=ZdghFGd3@jC z+B|QpgOwY1dPO}j187{wEyu+mhZxu1e5PENp}nSmR+kq;)(m`CN2 zy+N2S0BQ6{hsWR##L<@IqVz5(hAk;A5d%^tq_D>^O`k@1w0gu$+X$aY7R)Hg?Pc7F z3@QQ+9)px)2E_n^5ieu7oaS&jO)F>WNjg&R%^amB6Ni+onpfSBkB+)QN(-4!vgm5?Fm<0hNPz}0qB3&dbeN3|D zBzMmcNln7LEa5DXx^6?7418`JLxEY=Z%{@51 z-609g>X2-N`pN8Z-Yvvg#Pn)(*)SJr4%bLdxSJ$C1JKpt`D zRRO|Sw4%s7>z>rMy-5up$n#K4k4rYTElH`I1N@TC_n77~R2ztGicC`2Ne2*)j(@Z43I9Kn0kjX!06~n9lZj{1GMhc7Qh6qW11UOF~--BKE zaTmYYFOH{KqY6)zL!9z(0Y~5|DoPG_!$g>Kr;G44o1b{gd%`8VwTqtB za8_NAeGt<&ddGT0AKZY-iDuJh+X@cCSvWujJi8cCGDl7Y+j6mX`j}CM&DgDpXr)d5 zi}WH7uYh4t$Jk!BpIJ^KWikti)1HwNS~13Qp@_xn#m96g2|Us==9aM;)?&a~Jelm$ zH+l7q=Ygb2vsx*}QcinedX^9nt>##3#6JY#jCRy6s}I#^Gp{eLkiABy$#m;bdv`6eJYIXBahHdUbXvs73#HJfepz9p^uTWj5u zf!8H!&^PlzxFlT0+@0A48JgHW3-vfF48ln^AT{Hu(d#Yq;d#Z_xQ^CZ6`WG{n2Z8Zq3a25uETz@Hs0yqE%wW=Qy$H@u^^caU zh=4C8JFOyAB8!oeO=K|x(gc-9Mr;~?Ap`QEF#=3VE}^Uj*GjGsUR81h#4)lCEkbl& zDxg#=(F4zYNW2W250eDzE3|H2roxr4}MG7OU7yVK6gVzFnCdg6NG za^Xid8kw6ZEQ&#YCggouOZ-PG@f~ceuH=m7W~4&od4qymt48n&o1%44T1gs31SWMn zgN@Q`ZFfC$N$MLzvF%)^8Q_rc>oZaQ*#D4i`DJ?)2#fW@rQ^Y!0^IoXwSg)Amtaqls;S3WOZO`&?-w?#XiVwdH}(Ja?cXqLX*Uw!#!lpyKY=Rsk2@{_SX z#IkZ5oSq1JzE`JiHB1fROI$?C#@t^PsfMylGrCQs%SqJ$HB7nBr5)0h2u%$g018-a zkb{wMO_Kvet9J2`*hLM#IyUHR1ul%CGJheBBWVe51oP-Gqwu|g*`cz7mPIggl=33g zGFy-$rzlEoEGRr13L<{$|1M1v>`NEszs(ZPVwyM(b_T;euC+9wZNh*KF~y?&_l6NEwy3-1(EtT_&)(fWZci9&^NO z;G7GSieF_W(KCNA8A)zHsy6neDOEBTw{{HJm)_Nwk=U2|S^Gj3k)b?Q5oMXOEu}*i z5jdE1J8=aNnqG+>qIjW~C{du~DY6z)cp#D}^vvv?waUF{8aQBlbj*s3#T)nId*HNY zPkMoa)H|OQA))PQ z_rbIb0a+y7kEdc`RLW#YI7i25xHKx7O0Pei_LFe054Ip7OH05Km&@jUH|e(cu3}N{ zKShLSV6&KMNR#f}=H)D+OYwc5mbz=2ULz9-JBwxeun^&QQdrOrS;F-clka=OE+&~Y zGWDN+^xRb3{e#rT#H$4E*<4ui4~tA?L-YKR^%R=*v6P|2mXv05RqHafPP$&zy3Fym z?*O5F0bppHHLVc22=}Oq`k{+7$QcxOY%Udfs1=b~ z5ynldT)RV^R=>O~!QL`0D~qDv=)9(D9R-T1L8+5P|V5X5+3~lO!_3Y+V z=;*#An|@=~uz_$N&}T;w%{9Z|NkIf(9$OCfahID+(U>(}VllQcOjDF*EiYvL>u0>> zQPpH+;ifH|GlYCANz6~h!^|7!8>k8Hn0xi(Ya1ccPXqEL?jz#Iv8 z3E=wSGw_qkCc{sGR_uI(lDZaS%sN18^f70xs`F~7(?lL@1c8yg?z}s*i7AFi3v73{ z1`mVe#&8iGJAL|e>q5cG=eW9^SbFyzingOxd zoXAiR*esSPLn3f(_ftn2D2z}Dx-!5WphMyYpxCsUG@(E(pjl=XzIFaXI(b7l1ojG$ zNLS$?_qKeP39sTBHu9Fq&u-nDWTh0@Rz@TlZOqK|utxFpMO4O0#zj}zIRNd>Z?z16 zIZ3QfwIUb;D*69K8ksT1%cx|zX7+7GDOr>eu4#@z+(g3-2UZYD5P~I9vJ_APS+}beDFO(!EExg}k{QT!XfN!*zlWn?nS)x9JPq%owq3Rq#0l?N zSvm_PHVcw$E}!FLb;3FC|71ej87w5>X$0m|mjt`+pG0;)xe*Wf?(fisB45zK9g!{7 zfg!kr8*FT#J9TyvY#Ql@DrIRFsV$PJloB_7)4T@V#pk}!iyE(iY_vO_xAr-kFxA5c)7l9Vcb zI%9*si$~%Z&}yyvMOw`YrgRTzb>g%dFhyET7>CG$M9XYZfWcvan&2{HJ_cFdYrYqj znA1IYI5Y_gtMz6sNYZkQ0^LN|a6MK|NFM@{3@DM`07da2?AR5xwi;hT-(ZzJR+ea# zOjOd35OTz)>2AdH=0cHNIa_wVlozCm=llA}zJ7)r*ihrLz*;cu2%d`6wzrQMCow~H z>ChBih1Vu~^o`}(_{M>)i5n98jHikjsHpBI8Jq4ya49XxIklj;tETdD$aO7rFh@B$ z;<6JU`bX2(48vJGNrMI~{}?iFOC=)p5t=|M-!54~lNtn=?WL5WoZ=g#G$c1;@<4Em z5Grz=>aI6`C=|*eGNnYuSK26+%>_yc7wzgNd%GW@)FRotOHC8-h5(gEKq-3-P&^J; zqHFl9kS#aqXQ+hp-Wl7S%DDuq^yDpL+IWR_M)BupeNw4fnn)j}K- z3tAnakB87TBZ`cJEL7@3liN{Xz}UkW>fx-lA_bqJRuO(GJWH*S6=*rtIg8Q%+%bq< zX2vK6Z=oj;#gx~51ejcN2V0PKMrt_0wPwJ4c#Y?sLR#FNT4voI%F8IE}N_H&epAb$XSbLjF~M? zpY{0LCRXQzVXn*j0y9&q9NZy!AH;W=PhiHGz=bs~8dX494K9ik`0j(ttpN}K)F)*& zH<+*e1cwJ6Bai8eOGO$@8IeerbVtmt0TYIdbob3kPGxr>%7es2)Q^yWC1QsnJ6H4* zBsfX`w3o=Zg&q^)f?#5wqmi8L>}@|!4yhpU%7kAoDxd5+kH4KCe7E>XkuBe&RAsa zQYEpXnqJuq*p(Yz*q}sQDJjxVuHukJ)Y*#r*Z98_n%E*?0SFygzSS*1%(7Pe0+vw5eX%$ z^OSrAcXr8!jZby}TkY z!*5`POpgaClh3sXyF(^S6L~>P@1#zu(~PEgk!xIMG#!llna3uB2s(miYO;+7 z0F!JC;0L4AqrZaX0?u|$tKbPvm_)`>(jjIz1z-&>5w9>2pfYwS@}8=aRS7Lr?s`T+ zS;`vNksvsw9f^5T@=75)L#b|98#b&blS4vBDc#D}uV8{G!1yg<@ zsDET>04tG%7y*lr!M=Esm^^sP8S;?8l_ z=vrkvs)q;S8(phx$Mo=Ue4}fX?Svj4iEnhRvYph!@%Tp9D%)fAH;?Na zE2dlfK?v8JCt+L&(hkN52U8`&l>ETFxqidDzs2(1OZ*;A)9n-_Z z@r|xkwi9}IB)-wL%63u@$KxAat89nf^>)~L0qidDz4m})-Z*;A)-ClnaMTrrt-bs5DC5B0z$qP>H zOq76vm?)7TN{I43P4rGS7bmJNRC0uN6kwh@_dt}z=E{0b-uVaF+)Fu20(M1;3B2gk zxstF-Mk7bTPzq{6~l+u&}fBW`+w6dj+NHr@fq{r4d6AA6RJeG$^ ztRL_5?gu1$h;^gbQVNms#ya~`Uw&gzK4CfT@dNiITa!ehx1IUAMU`v&AvJOyEc+2L zR&nT*Eq}7$wVFG;L^c5;l`xdJgM&CXi<4n?S-tWGWBt(OPcj4>NZS z7@P>2&FL@P%K8SdkTvRb3l+ZmK6xGBjOpoU4!Seb9=0E%ehlTu7^fdd8&M zZ>?bs*1H%4%WMv_kTB5}1lEiJT!c)eXt`h`7^=uB3=5Ey!^IPo`>lIg0Pu#SMf*y) zE962xBbpSG#zrfXFF`>K8Y16qdU0HQ%~^b<4xRep>mwRnGCn&i`eSlML6nMzU?Lr( zPsk3wUdpCUXj=;P?;XJ5V`NTXW=QV`Bl8(@CsmKm}^?&_qdO zjG7Cspzct2JL?33Woche-9Le8BzGv`Scz!py7D1QG^^xw{8EtZ5%>?2J>HqmmU%;z zE)o6(_7NJw`7PnD zm35Mvh25PAzbTSkowZ50P%rl>FLuqB%<|T?w>R!GU%0JFBV;IY`>Ge|u1<$MdXewC zyz?yX{R=F{>FywNqG6-i4WtK}rqOw(^06?z%InG6YxCP8uWMWCjNul}G{R05c|DE< zR>_JyEh}e7$FB=^1v%q&p>v@;`XHv=z7}ayM!=2ho!CEx^&H&pGZ=uK*25j0<3cUn zW5$97uwDVXpkMC%W5i{B_}Gmg+&JyG^JcMBtw~J}*Dy_ynH?CDf`!vlsOW?GKm_A1 zZn{L!{!=d@5sN-lTEDR#`(QIyz{Z8B-rfQ z;H|JFcu*<)s@|l+z4%5B0rZC%TBMy1XSO{=z2vd`7MbI@-egy${0;sGbbk>>o~!>% zpI(yBF*-;Or`hx*r&%`KXo(A_S;PZqxh4UNT?H-!c*jyQk=t1EP`?*tWa3v=MP0*D z+Sh$Fl`%Jb$a`UBlQB@xLiful1z;mh)ne%VSz7ZMGX2vv=giJLOVAHs_$BoqbC6pI z4HCl>2p$^ABs@xsXPNRzXU;_v7YgV`8C`|}l*&o~v%e^L^Zj3R(qz2(R+5c3-;$@T zxT(3ZIT7hA|BU{+fM+CFQB`twRAdZgkiS*{&xJH&umWS2Z{F2wD>0w5lPnp6_5uy& z+Ktx@c4ZZH2YxA}7y3|po3X((-P5mB`gu?(cd=D=!+7TYs{S*9jzXpcdaVUXi#7#g7uQn+@twG=ea}^P*Za4#c zohGs~JRb%<;8DRG3kc6PMXj5ImO}|?Ins}M35T_Sbxfp4LUa)^q%X{bvOOM=?R1{C zs%GXT$zACsUFF=`~`Q8uE*Arq>B486=V_e!(E)uRHmY z^g=(Jwqd!a5YB`&B%n9=@Xd0$l=G#(tZ4zbB(JT9#d4R1S%!MOr?G8ov)<`+0Bp+7 z&t9N(n1r1_uDw9Tw&aDRjbDabq>qIsmw?tfp)wq2M$?Wp2ob#7l3fxGc~7{%w50zU zSWK(6%yK=%4k!2`ItadgDkGSKg9~8R6d1E9VBi$XZQKyVv#71l&-!qWLn5g|qQkd} zimB1=T(#V<(3%be!l4rk`&3xeMQrd+-MrPGEP~T5+!I`h(ke1FZ3vWX%;)enNID9c9IFCC$3~S@icaH#LhdU;@H)mZU&8XXZkzOi&a22Zx_SK}4N^qjrtj+|d*= zNfnA}B$_;G8I7QMVsc&I8JviCloJlX9XeC)aP(2Y`kZk87XYSq)RqZH%Y;`{s;77< zjS;Fc$yG;=wrLuuDD0o|W;1WJS6A_JxRb-NWN#S8(Z8l1C%7T&o&a!=q+?XLynHV> zRx`5vSh!VFmH#kfdQRm(#r8kGTKoTYj1VW8;YLEh48xUtw>c_+DK@JiK#YTN2~8aw zAbM<>uz<#a&pZqjb5_|fgV`F;I%i~DOKX9t@X%UWU!wIPyQ^Sq{IyLVgJT00a6(*9C# z1>=nIduTCR)~sIU4tR+4MzvuM&WkuEq9sO<&uaegmK)M1G-nwa4hE(~=!N{2n8V#~ z@P($HxpYvT-LDWba;mcZ9$7%bpocw6$>*$!F?3Wl;lnq4zc^}Y?THUr{UNU&XgFI} zMJ2|f%;Bh_I5t*lvn)_Kg*3L6Jc=B20f=k66+2(G(nWg&D$*eX1$0>iMskF;v!PDW zE}apl=IIXCgIvWjpPe>S0r2TM?_J+Z)Cv)J2_yx?Ufi(@3!$BGu$hilpT!vs!SzyV z1S(5z(8+G#dvL{uYGYN^a4}W^E!!^tVJbInSGLCsT=j+RsDe-N7PDYW~9Ycn&HkbEzw+w zrQQvx^JrqHz8Ym_I9o26St?E`G>Ztq3O4mYZ&R|eP)Go++HJNoy4It8CL+`DQXeWc z5ZgtFnp9Va5M&KcOe+#i8`6p7k~C0)D6pmk3Go=%ku^pQ?CGu0Ws6GfVh|VW+S>5r z+nVV_-0I{ww7E1A1;m|;!IZ)gXne@HZCW`fSgankuHyntTaynM?14oG3+ha6hE7*`yHlpoOe}>zl9S?)HKt=~`k>CRLnvhw! ztA_jZlBmfnGQiO)6MY3Y38U&M{LDfoH6o=_!%3$-5UUJCA+*k3f)4d3A=BaS#ajw) z4SjH>N{wX6N2p8nwwEKqL5mDV`vjpuahQLsIB8?CIBu;t7J3Qaq;61-Xaar;>rGlV zfbpSAZxP%SAXq~N1?!~Hj4di{kzwR8cbNtZUK&++x{6R;8q1-PL5t9!&|uI*@t1?xyH)}mwfsQBFdwr+sKm#7>`|1*Esqps zPl9(%fN$DI+1kv85FX+u9LkY?e^r{i`AOJ*~r{I`&qlxfwxtU<|p zkStvjw=NN;h#mGO{9B3mg%^PnimD?-PE{Ri>thR=6U}T+FjB+ul|!crm&k{!&ikyq zaiNcSdcpX}hfj``FUJUl9JAq2p&C2GPc;Vd2!zj*G=V)6EQpLISfEVpql2>l>W<(A zk?WW28P1`enScpybYBUGflTmdg&K#hN$GKdH(sR+s=C(?hN$~9=jtK;?xR9N}dBlZd(<(04Q zu~%{}T~XJuM0G*GGYAb94q@$zOh+se*hwaqQJLZ^^E_yI1`e3y)ow0XjzVWIq`n7c z_p~c0pA+bUA_b)T5I4a8Y;QPqqYGJN?lVPR0!p>6oW<;tY71A>%}jQD3PDxG2y8OY zCnS~yrU{oTqv{dgz=*JyE3nXQ+w`1p*T*Lp!b89Nob4q3;csx?L_15vub%2GgwJ=B`sio4 zedmVlyOqxIlfQa$;tKA5?N>;iCY7DrY537y;n=4pr1VK12?zgniRI?-m@Y?fTmCz) zAIZD7C~P#~OT+Jebz&jN@6g+&NMa{%+Phu418=|f59HqxKJt%T-kF6*zs~i2nLZbf zDXnuqi=jOz2m!5%SrswZQuex*p^Ia+Wzm`c|(|quph9M>n`;t%=?%V zJI9(CQOd5*m}r`hS!kghf{9oHuC~x9NHZw#tUB5%L7p3NDi|oC3KTr3T%?Jkl~K2av&CL)Xe4+3PGI4KUBBNUX2q%b!4(4E=6+* z=835>3&G?GB{d7jB=^weW|)E&Pi9RJ4`E2zgS*QTICje_xrAX5 zNo<6lqXFE=qL={|sJ;Qj!g#qwNL>`95Y!lVLGEf%u?he`FxB)CIVPSM2S>x?pQu!Q zf_hu~q|CU4VYV7a%~1y7z59#t{eDi3G=3x{SzDq_smO8ui5Mg~*;b0wdIpuv?T#uu z3u|o7o9RFlLF*>a28lVHTk@>w>m@8_crcX~V1$-vC4); zK}FOtqJm950t>(vwf@npuYVx^#`O>Fmi6@yOSAP4-U-#4lCNLDg<5R=gFEDEt$(1O zX#GPUW3(rmt$?6T{69#OmKeMfo*={8Ci56k`<0`%G{SwkG-9$DtwsGPT3QBahPegI zJ`5Q@&r6`sG73dG*vExNxFttM1I#$G{Sfa&V<}V43wMNnFtN26{*mjYykW~AW+`<{ zmL9boawNm~35PpTV0V;;^M)DDEIm?C3^zoy1hVHStr|c)yXy>Ay=|mE~n%LRoUKh z#5eFeBlKsZtV=X{LaBMrL~BVT<&?IZ61P3bn!d{sZI*YB2}TR5?kOAHLjKf%K|ru? z@B}FmkWvQov}sus-&E%p#-LEf)Fhx0SS zD(~BRzqJ2FtG%Dl`^ArZ=DTRZ@q{xVhns<+&NAHpzQf$ZX%hjGR4k>?a@5opVxq)| z47)23VACF&p}1i3jyXWKNqxSVYCq4nrdwEh(OA7-AkCzo1hK z-(DzzEWF7}^i=xyqFZ6;7k*n)T;Wu0rGGE?HUD0F6a9M!Z)D(amE7Z_gO(F+W%ZUL zN~`7If?Ceh6TxFMQoZGL2my*9{nm-jP$zhL+B*@qOC_C84?4j~SF3ko+B-4rotXAc zXb*S|{IyQd&B*CWq&Jd{01C{i{)BryvHB@5 zqX`*({8;t&*2Mgtl!)IG%7^*H{d<7@@Z&(|*K@ zrJ>TW$C}QWIWjFW3BX+BzNEDqD2gZvhO&l)RtwDmsq|bZ#W7biQl=lUY-E&vma)?Z zZdm|qU(BO<*b8KQG`1kFk7%t}w4B%6E!Lo6V5Ot6(4$3mml@P`c8Yej)9{Ubdbl;2 zz1q}mEeUU9VsoDL)l({z=yx5dp2p=EJxSmuTCnQ9UR;?Kt(+49k@E_TJtWg&{7iV+ zS`nFGLB#8znUZQ-1gzGAG-o$ySp?TEYVBiVfuIx}Y>i$o!%A(`K|ByzP!-?7erc;8 z3>7E9u-kxhz)g1Mmch~kiy^ZptH=#fSyAd_j)1OpwTh9$80lcRO=jiKOPvBask#{s z{xu5L0huJDW0-h5kRKjHEwaTi{O}DwFomYi`-L7-Kpdg8Hawak0!$AzIrp{9DfpPz zn1=mjjbCZ|^5aQlGIi_9f&!q*VB%c+ADkl+2m0n%M@eT4Nc^|d%w1%HEDyFNs{rM9FcsjnkENiBX&g^gpjf% zcUnms)Ik3$QcTqhG{z?Piei9?@5oS)O%aHJNI#MvC`0+pFQJqzEd%2gGvI`& z45)iU8S>>BXw^^#30Pi*mCy=J5*TawJ!tWUI9_xfaQOVj=o{yEC3(orNBDqLF#xXS zMT^idTR_W4FoVlr2v&rLYxi=!{Q*WmCd21vKVo+>C;pJ#>HNu;^6nDZlMb9(l3kVs zMZV9m0BS8yiu}F2@T1w9D8++%me;AeRR3_DKU&r4waWqBYnS=4s{Nt=MeVYns`iFz z2hZrRCXIjt)riWl4W_Sc1eYH7w z(UB8~OUUIlaUWPao|goZ*s}o(pbm3?wd}i*$tA$af=2H^N(!aAiq>$8o*rq9@`~2< z;*4du7)yoGy_oJkRs-mA;670s(65gK?rSRGI+!(}4rXoMbuep3Xa(l|0<+LTU~US` zEU9V&hv$G;Uvx_6R^*Q66-XzG?}swqiGvvqUyi_meA%2vMGU?Xp2f6}s|9SUH1@7f zkuji0#t{p4A|$}ajHHM#m=hNFc=T@ACEE9d|+?udN$^r{RB2F{G6;8tlF?A`$ zWChraY`q2(q>M%n@QckrmK$J=+F)!3!`OruoYF?I8Lt3ZK?3xLh4`F)X!V^U$OD@} zb*9n+My*`%7_8=0fO`zp56XsSsOzeWy&DX_nK)Xa+;z^Or2zIv0!Nktz9={bT+FgL zdR{Vr3`{#@G!Kj4z`WJo2QM^a?M>xSe4qPRll|!nLXxV! zs6+y7P*Lpfb?E8-R{o_BRVo?&XXz>KVcG(gyA$>Rj8F?Ph4Q<<+x z0bGuo#rhG=!VHwzefuq4WF?Jf%*o)0|J9Hcu*l&9>hh{~LNEft4;s&(mOUHDw7|X` z00UatJ-=YV$TH(6))LO~BAt?>#djx}R<%OXpPY$7Z-z?9m$lRQjtq}10?=8M2dF*9 zdT_`Ao0LERmwkVt#^Dm*Q2EO#yABMH@+a16l5pAl$`imxcUXOel3c>sf<@y!+Nqe? z#>vbqoXp?_`rMfBz8pX_<{JkPwuSXV@I|H?w9)oX>N;xgq*Xx1K*h`VPIi9joEvx9 zKY80YK3v2xEFnNbZ!7v}j&YxCl;{aPHBT}lmVFUPEtA@pcj!xqSxH&-CCfQ-5Cx$oUy+3y^YUCJ*cOv-QW$|M(7k8xpRF*0Q|rMQ^af<#}Fm>XM@Hk?a~w;TzrrDBYJ za&-vII`zo*y1nbMCFZ*piy^kNVBpYVF>d`N-Ax`?0OV0P*sK-tYrF}P`LeIJ zj-JXEI7yVqtgCH-th^}@!lVT%xyP%N{547!A5ws6|Y30iv)5rZ< zSZQ9$@b{NJ4p8@!)g1rH?(Qnl@&9=VmBWxE8DX5^%baGS0u5%fZ99g-unT(F>Aw%s zXcshPYE_ZmX(nqvixHjK<9|d|XYlraiZw~nlJaf)2(5=a)ToP^I(Y0HN@cvt!i;a? zPM|bc5Lu{!R&m%OfB{zKk0s%NqLJ(%;gV1!=*vt_Ucn>zeobD}rf+0X`C7LY+bRzY z(o8Gli^s=kE(oez`5=*qk*%mkVb?pI&OMSCG6n}ZYewwc_3Q%KchFEYL4Q-2`cBKF z4y(;_8%Da#)O2{N*Df`ernl8}gaWZfW`nq(;^<0!|A8qIa8;+$4VYQDCo6;i34(eX z3rfYPE}R>q<*>nGe4o%6_Y#hYx7eMSU&+p#q2mb2s#=7=N(WjlAlNq|VC-besetmfc|BbP@bkrie(QRAsCV4AuYYr&o15RreMdnHU=50slPe=qMW=l(apStxYp8{)4A(F+5#~bmy*uGb>Da0Yj^(mH9N0+ z-78;r-B0e^`>Hp*V&^Mgcg>z_cJF)btFGC$ZO`6U?&b1>r2X(UuexsckFS#9`>xyh z6R&;s@|UmM^XhHew(q2noqJzPL09d0^{cMi^SU>@a?k5tv1jj(Z`-!_O|QRZ&$exs z@7y;s{i!byk_^CROhId-r^s=X3y)d z+qs9X)tj~Znms>x?Oq!8s_S;{-Tlf}?R|x(Y}>|HmcV!9c;PCak=sh$wvBvt_u`Rl z;O%wS?u(Euv#wI+lWWS4y>{m-cfV=dwt2h0_{Xlj?o|skeCHlI^V;inmW93Mn%BPi zHGB8I_I201>NT(Yf4jR9@Hna?-PfENNh4d9Y)i7+#)oW6qq#IQlJ7&Z4aS#z-~*(V zrbilkF6OW-2w`NLgv;CsEQwh=VY$PRcwqwsvYS942S*kH0X7^TS@^P9a5jYG3y1cv z*WFS}HeSH+ExQBd?&|kmz4xl>)vK!4Gm57=Brt4C6Q30 zau)`PdAGI}QvF#aQ$MFmPN!rgC3m%Dvt9BYB~eg0&dzbrM(ygrwW*NHHem!xqNzv8 z_NJS%YHvK3&ki)@vOP_VzNaS({m6OZ>BdmZ7moV<-Q9|>+3WSinhb_CLS-6#9-qh0 z+Npap>1;md$>ySho9jY92LO`+P5^y)KncJFC#R9lW~YDszNRarCUuT1?jh(*xP2)IDl8y}K=$$q%&cQB!$ZkV-zEjdx>R<(3+EyVU_E zTSG_DES1lltX%sc(R3CIyfKp~urI81xA;R|Z%;Vv zjVh5IU&!lIg0Wa2(3FUGXR#Wau!hn-Y<^Jwc&e9X7__*#(?IiESSX~QbpRSS!4$w$ zfDAAe;JOTrc#yNTYod_aol5tohATA4 z`93wP$_gNr)*ch@-_hHNRyX5)SA%SDMK;PBP)59t7BY!=4|LI>if5}(Mr&(qzk8Hy z96N&Cg|dWR$Dk8WLDAw-d2>9K4+Z7fv*kwl3e=%>dI(qIi@A=^ACYI{*%6(NQKyhd zU}a%XqV0^#HyPS2*&AcQY5MgR+7WLq0TA!aILSq`5FEETr4;gg>1_NQHCk`e=8T=K z5R1io;!q@+nM@h0J<+o$B@ zZdFamd1W{L)#`HGg~zL#Ph$mOKp>e=Q@vOLGHgJne$O4z$MADpNj{_cp|*)MRK#e? zaYs>)*4Nnjt95x`f1r&}&DPVMj5B;~jx?Q2A&ZAOwKJ22)4;Zwn3~nF6{tsgy-~kv zreDn(wK8~4`^8@Ux&&8}aUMq2xW5k11U%EF2x*ch!f|)tInjO*GPL5loCzE@>ex+n zxQOk>#B9x-!7@;@L!e_p`)L3g3-v!8a0}PAwCNi7k}>6kQ+Ya-7=Q`QuuW7ZG3GJ~_5`_*A>*p+k#}!9 zJ_fBVdfQKH03kM2+w-AFO-fYB;F@bvQ-!2T8yn3%;&gL6&=2B9jC1MTUk4{%wVj9) zsA%36l5kMs*zZQO3w68zL%c-Y4+sE^ts;o~WcY<yr%rbHcf(4Cf|{q}Wh)^HTNY)C&qdJZcJ%QpOoq071Al%D{^}U~_haxsjltg^ zgIi!r&Ghlr_fa_gq8iE7=-)mD9|Apd`MYEA{~UwAJ_dhr44xvs!sZ>sN-i>E8pK zY^_m#3vlAx>SQJjAFVr~w#rHPk_e(;ulkfds_Z4>8y_`@`sizOx z+n|{>xM{{R*DQ5zf4Q661lsVM)GS=D;gG<$i^8MLstUGgbSjg|waOg#3fjyy zjqf1FM;od!-iv`xFyWU0C(CJ+?*ne0?_S_$&4>Xv^Y;wkYqG4D)^fiKa)yaO)9vy+_eM+EE0uu$B>yc>>_$7w+wIlweFGfs}! zawSC`GZZ!okD9d{mgS9TOYMv{G%wN-1E*b(bjTQpmS}?OsAY6=133hc!}**JhQeh$ zK{m$-7=|0cnPh?Iahr|Fye!(ZX8wwet@0L1jcALVrq->>6viSFP01Zv5R7D-iUKTq zJWHl&!DF|`$ph&E?ox6gr;b$3s!1p>YGO7@)FZDUZ=Ry*fmV5?ii{3WtpgUf=j2#6 zon#ddZp(2kBI#EX3Bv`X;1^MKAI6xbz}O>q;3}tMSW;Q^qZ<`=6JvfN#zZrT#%Zac z1ts_}uY5lRFbYDRR3i&{P$VONfY@4#ui3QOs66sUNKrFHl2rlu*PhL`E0sC77pfs)^`f#Ql)8UWQuTUQrYZ)97|^zamucw zkjg*+*P~98x2kd<21Kr-is%v3C*H#zGYw!V5yF-uS4cUW205whCPwG`z|bUoTN#lD zVpoLXGBPc)-bm1ZIT_!e5g@u46Z6IxYKr_;b@WAHmV6jpa>s<;~IS&dLDRFI?eQ)ZJDGfeM%;yrl1KV0Q|3D`SjBAAT z5`6|fOUIGC#MEe4l>OK!vC?37pd9M3tYsvpnm@WD(5$R>{v4_uPKy$u{=8`ev?;L}7>G($ESFwJOcSnMh)^!T$5IKWXKUM|ky$-sR?D%|i zF$vq09iaWqHPOv=q959C$gb<-(g(uK5C+C^UqU@fJ{a}MhEsiGKSXk&j}|Ss$K8Rt z&(Mdr!p;kY-^ zo+7}_dXkK-3ap9xNm{gVj6C%R9mY5`DeF%u%Rs%ar_Obf&^AVt<#0!>&vx{?Ma{;M zQ_>QXdgq7GH~9iOH91r0CW~qeje8K~#J`nD4I>3JT=3H9G0V$X_yN73>?7v%4$7<1 z&fpbt$qE$z95{zYqHm#$tWQBnEUcXD*8C1!DT+4Q^r?G?39dpJjdQuaHL%!5CU0ig zm4*Er?w*UH{w&mI`LP(zpQ0mv%rz(@Uk%*C+Jx{}P7@lOmMKXsWBArj z3fim&kQ@k31!!U#-8W?mbPB7~z53ned@Qmyoo^?0WBZCiHFGMuhOy{X^PR-p4xNMa zSeq1kY_M(A{MMikjd#liwu5u#xt)%(5&jJ~AJu*>>MV^$>BORtSfB3i=*xEWC3Fqa zf*rOtZ>n{3Hq`wtu9WQiKCWaV45}DvI-5oYjjbF!M;Xi^gY~$wqEytUU;W39i&0=> zBc9TpxdFf|#IQm$9Wj)IYp&B0kjrtUHfBDIj3*V1Ye`f%kY^Xv9Go+@=aN6k(vhij zC(dK~U=K9EQcqG8kX~l#IW=muL?3zdMOlvvaCPH)wSN5$uEd|)a3%Yw534@y*kd3k%Xj_TrEKb#vzpyt*?lUh=x56CPfI1ZWGJ2S-u3egrYc?>w z8|AxEkJjd!7#GRw8p!Qh!1aKGfJ1=q05+k_;5%h~wgAk}wgSHcwCDrb!O=?~BG!}A zu>H_x+WGa9m9};52Id=!P6c zxr@9G)2xr34RcUX)5J+#jbt^~qri0MXvM%0^BB*OD(=P>ZlGkXjQaw-P;%keKg`GQ ztYd;@imS&w*x$6XH)j9PH_k!OjUfrd3w?2{4|u2gfemzu4UYmdJSDf2f;Se!M=2b@ z_SP8jusU>pLvasiAaO(KbeYnc3EGK<#R!KiM!^aBsLZ&Gv{f+nWKuCzNKoR3)gtA_ zpp;h$g=wqsX{LCKCFdNC8SlY3C_39CZX^Wd19>w2*7GYoA!N3P`PhO&dpNIPfz%ngw1Z9gc&o+9BwnKbwV}%Tjy!@BY zR}aSU9Dv3e*R4050~|$}3(rnES8qB8kY;G-5ym-yNxsME!%5EnL`gL6OBe^`&94G9 z0t)28o+!76b7d`QipWE6OFBi~rh%8hbs!!Zcqwqogqm^Mg$>*_rhfSt9Ba%HK(s#w|>LMj!m1l zY&~P!cBQ)~s>XWz;=6VylBsm&>})Py*wepv;2f{d9|$&w!jYDFO()mBvD4EuLhaMg z@|@%dK24t^Bc9hVZTcxQX3m;jKWFZ|hQ^VCC8O!;Yz0?$bTPhaTm?_*Wz3KHS%i9v zHNLmbU$9Vb#qokDS*$j@!&%}gEh~3dj2mA$p{jb~B(1{an%cT4Q{_?P)R_J;yZr)H z0|!3g{1*@Xf13Z0S=hQa%F^7f7%lcPJcBR%g`kKT7*2*JwmP zO8bM&AGgylNVDR7$Cp5pGMh%+@kw5dWk(Y- zdl6U5uw!z1ZaRiQU8OmQJ~#9AC*n#zsqy+QUO#92WvvkwM^H}L=S{kAt7iwm`*>`C zh4)Opa$h>VTRVSMG>PknhIG8$1scKQ^ubzEJ@oS})S(mquK~z6Ht-D)!c-H!5jedv z90L%~NiMwrl9Qp6ePi%A@Ld4P)KPAY+9Uu;Kng$~(w8(K1E6w(vjKFHlmnQb=YfBQ zF{W9SxbOQlUFyTtEPq2jBd=FymHy5>Y7cmaB$9%ahO<~b<&W{bgZA{c#q+olU#q}3 z%1kQy2@*RzXRo9EkMD_d1+=3N@rY<0I~D}Y7)Laq=}I^Hl2LEYu@y03J(8b8FZLGj z?luxl3l>tWD%T^U(52+NWW2u)!@<5Y!5)Ivk3gGZ(_3++Q;th-0dz2cL~feo+7B>vXfN(R!5Y(-ftDhq7?R$!VTbeD@x7LRGb9_0 z_hLR{(zkl-#SWh7RD#lMF*a?y8BM1Y>i%P6A{ z)j1!a2^DWJk;Uz%gm$i#nGsR|$e+C%LRmwtBA#&EA@qR@_aj`%zSD`8F_zc$u@&*$ zkmVrm_W>>d$n-fEQMi1rv%z_ud|rJ$t{hiHnX%6If1Y4TD&Aw)TbD%lc+}s>$ z4mU@dTSDHDFXRseLcvgTC=?2ZBB7SB7vIeAhXdhYxH%jOhr^L@OT-)TMf{OKBp7Lq zgd*WcB+}A?A-16V7Laa1vlcv3a@bqh_d~R;p;eySgFLakNcOcDbN)SqN4BE~FnIPA z+A>+7EvUPe7uUp0ue6auXeFe$IXDk&SJskP z@{M6l_V?mCLIYnr(iP=RJSXim%C+_y^^sQun0BENdtZRFN3;{=DY%mD;QyCD+|m#K zh@^8sIN?Ve^O5Mov0oD^d7x^C$KyGZC7yK7Vg*q;Uty_cZN=ehI`d_+G?eej(SfjB zuf2sihlJ481zs#|(N4|Q;7X_0SzKv(QPihAn^FImg$iqHtQB9uvP zzCsvh8DCM!PY|kwNu{-xI{Q?92ER+%E!-vEBRnWPDLgGa<9gQdYvDQJ1^yMwtHK-7 zo5EZ2QR%P3-^BNL*X;R=)~>t!h8zC=+)J+h#<#zB|5xv^+8m*Ui?;mfsi&pNiJ|b8 zt>@o%=UqQ+er4R(F1qXnskE$O+#Fx9wXJ>iscY9o)t&d=S5s@VJ4-67LoKbh-1_`) z9N{ajyv636zbF>J{Hls{=fiKky}kRL_lG(*UH?r_)9m`q2M-A_Ru4bO7bZ)XVnKHt*cI5v%X{VmNT~RJgcim zjqT3uJ@5RBZ@lfUyMOf5op+_u4_$rM^m8qu)F{S8zR6SES10<)Yo!^EDVBMb6;j#U z;%(L$(hRBI9xPeAd@$^&a@r@(Z)*{I><(`g{xD~agj zgO#_qMN9FL?kTJ6&f-mHO>Zl4T1&^bTAiT=sk-=s1``@Qa}%WvDa^6(G#wbHl~35eeyY`4e0H8?(KJ5OC{C7y!6j2G zS}pwGQ*-zId+{%G)<{lCxS(RinuW!GSzzU*&6b*=Fj&?gMO|B*#XBQ)r45q9CX`u= z*I)3wR3Vm%{ZgmZCGq7hDT2w>+o!J`+~lgmSVHzPRCU;jKcDH`Z{>K=VzF8Uo7HY} zR5)u(Cb=e;y31VUl3N@%ZoH$4ua+kAlf=ok8opMTS|y9~#Ksa2?-hN5pTAkSRk%&M z-TpV>JQm3$e;Epo-2cF%KYQZI*MG+itNVP?_p_qv{wu$E z@bM>}EUlQ+I=^l8)*U;~>Wr!fuDlgZ9(nxb*ME1kw4!ZwR4wlR-h&T4{Oof_|9HVy zF1hihhaP_9r%%4{;;QR@_{&G1cyjgHbz8Tc)p^lnm)~>meGmWhqd$GFqN;kw&UgRv z;ZQMo_HSP}3)^(ou|A7aqs;5kCU%7T2>Hn9{|Hnt3dG@8FfBbVccX__> zjaiESt+>DccEC+=zg7)gz5=L*Nr9XSl<`ap~ME@)Q**jYGt_*pmbQvf*1Rp|xdBJK^p z=tA7H_+mKF&|ZTnWIFR{7MIYs7qOR_+FXQtPK3WI$=sEeF7C|nhq!Uo@>G}HHTCU= zL-Xc(<%aZ4uQUj^bTv+Sugk-ID2Hws>I!|pzZ&A5)50@KUk%@0rnEFoJlx`~RaX6J z%HcIjg39`%yAE$ym!7uq;Dd)Za!)E9>eGiixEH2v;$C@m^PR6LTmSI-v~5qld3c-5 zy|wKqfBtp^xi+p5PLP2A_*Es|32t76ehC6Eox)G4*-_H!aPSi)-hr*qGEZD!pF5G4 z!)PGcp+`2SP{+4YbIFb>PGK^BP^blarX)ab`6+^kpPmD5K?S~2sKSnl+MsRcZK6|{ zf7i=zjH!nC! zY^#MD(Bt`V8IK+`qB+)J5PiP2M9;cWhC)Np5@ZS>rI*6SYN*p{^ z6JHRf^Ls=|aPU^~MKA%QwGl4i|n#0~N#w3eiw(wF|;^BL23b zjXD&CN0)H?kEe0s0lrJ-tZ{*pc>IpqdI5G9W1l2g_-lp9<4XBi_DLlk(TlkW!fbvy zI4|Ir^Xz;R9{^SS3K!-*SFrQ=RaCTt1Lk&9XvV+Bf8D}~n59%NO8h^7J}0af+e>`X zxqPU64(90;eV}RM7m73R7cdv$hdF`{Y$klCNUQ`?_=EWSdJ`CDdA^D-vx$};+i5b@ z#8t?O>hZ#VU~E=gYlO{qda{f7fl^h3P%WH;7yb;%LnHVr(4WNPPghu(q^yGI0mC^P z7{YI?!caiv94k5i&!KJ970*GsL5qd%cxyQa6T|U~r44w_d4y^>=s0G=9|5#Yk**QB zu;jP%Wqg%|F9+2MM$r=G524LM2^_E`ZCqFJD98NQIxI2rV-4sbgD6wMzrnMR!0u2B)Cc#Rb*@lpINo!H^H9JIO`^eBliVO*E*4SF1 z4yXga%>ns#>u6(uIVSvaKfd2ynkBQuja9;xM zHqP1W5jr>_Ho1yXQBiIOie1in#d$ArMfTFpe}hlu=RjAbCb4UuVo)5Ta_}AZu0Nlp z*-Tc+{TOBTGe;8i@EG*KM~#2!fV?A&n(P4@>VP_+4t#b%_J@c`F!q=`v|9%=dj%j? zSZ#x4`CSlAwV(c+@$Qp+J2v{35QwM(3fp?>Glk5Nh literal 0 HcmV?d00001 diff --git a/rust-contract-building-poc/nameservice/.cargo/config b/rust-contract-building-poc/nameservice/.cargo/config new file mode 100644 index 0000000..2a01f1d --- /dev/null +++ b/rust-contract-building-poc/nameservice/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/rust-contract-building-poc/nameservice/.editorconfig b/rust-contract-building-poc/nameservice/.editorconfig new file mode 100644 index 0000000..3d36f20 --- /dev/null +++ b/rust-contract-building-poc/nameservice/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/rust-contract-building-poc/nameservice/.gitignore b/rust-contract-building-poc/nameservice/.gitignore new file mode 100644 index 0000000..6815fd9 --- /dev/null +++ b/rust-contract-building-poc/nameservice/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +*.iml +.idea diff --git a/rust-contract-building-poc/nameservice/Cargo.lock b/rust-contract-building-poc/nameservice/Cargo.lock new file mode 100644 index 0000000..e309249 --- /dev/null +++ b/rust-contract-building-poc/nameservice/Cargo.lock @@ -0,0 +1,698 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" + +[[package]] +name = "cosmwasm-crypto" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7d659bbecbdca16878322becea489c0fcc36e784f41c942ea0171e9cf74179" +dependencies = [ + "digest 0.10.5", + "ed25519-zebra", + "k256", + "rand_core 0.6.3", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81b4676a316d3e4636f658d2d3aba9b4b30c3177e311bbda721b56d4a26342f" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88917b0e7c987fd99251f8bb7e06da7d705e7572e0732428b72f8bc1f17b1af5" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bd1495b8bc0130529dad7e69bef8d800654b50a5d5fc150f1e795c4c24c5b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cosmwasm-std" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39d5725d2bd4b868284c127f7a904c93a9826550f30267b301484138db2eb9b" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc9b8229a8b09eb7673851f3e97cbb8832ae93a9d589800fa67ef1a2daef641" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-nameservice" +version = "0.12.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ecdsa" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.6.3", + "serde", + "sha2 0.9.5", + "thiserror", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.5", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "group" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.5", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "k256" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rfc6979" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.1.5", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.5", + "digest 0.10.5", +] + +[[package]] +name = "signature" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +dependencies = [ + "digest 0.10.5", + "rand_core 0.6.3", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/rust-contract-building-poc/nameservice/Cargo.toml b/rust-contract-building-poc/nameservice/Cargo.toml new file mode 100644 index 0000000..6a11f54 --- /dev/null +++ b/rust-contract-building-poc/nameservice/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cw-nameservice" +description = "A name service" +version = "0.12.0" +authors = ["Cory Levinson "] +edition = "2018" +license = "Apache-2.0" +repository = "https://github.com/InterWasm/cw-contracts" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = "1.1.0" +cosmwasm-storage = "1.1.0" +cw-storage-plus = "0.13.4" +cosmwasm-schema = "1.1.0" +thiserror = "1.0.31" + +[dev-dependencies] + diff --git a/rust-contract-building-poc/nameservice/LICENSE b/rust-contract-building-poc/nameservice/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rust-contract-building-poc/nameservice/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rust-contract-building-poc/nameservice/NOTICE b/rust-contract-building-poc/nameservice/NOTICE new file mode 100644 index 0000000..8504d3d --- /dev/null +++ b/rust-contract-building-poc/nameservice/NOTICE @@ -0,0 +1,13 @@ +Copyright 2020 Cory Levinson + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rust-contract-building-poc/nameservice/README.md b/rust-contract-building-poc/nameservice/README.md new file mode 100644 index 0000000..8dc54a0 --- /dev/null +++ b/rust-contract-building-poc/nameservice/README.md @@ -0,0 +1,7 @@ +# Name Service + +The goal of the application you are building is to let users buy names and to set a value these names resolve to. +The owner of a given name will be the current highest bidder. In this section, you will learn how these simple + requirements translate to application design. + +Here is the tutorial for this application: [tutorial](https://docs.cosmwasm.com/tutorials/name-service/intro) diff --git a/rust-contract-building-poc/nameservice/examples/schema.rs b/rust-contract-building-poc/nameservice/examples/schema.rs new file mode 100644 index 0000000..3f630fd --- /dev/null +++ b/rust-contract-building-poc/nameservice/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use cw_nameservice::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/rust-contract-building-poc/nameservice/helpers.ts b/rust-contract-building-poc/nameservice/helpers.ts new file mode 100644 index 0000000..542dfb8 --- /dev/null +++ b/rust-contract-building-poc/nameservice/helpers.ts @@ -0,0 +1,234 @@ +/* + * This is a set of helpers meant for use with @cosmjs/cli + * With these you can easily use the cw20 contract without worrying about forming messages and parsing queries. + * + * Usage: npx @cosmjs/cli@^0.23 --init https://raw.githubusercontent.com/CosmWasm/cosmwasm-examples/main/nameservice/helpers.ts + * + * Create a client: + * const client = await useOptions(coralnetOptions).setup(password); + * await client.getAccount() + * + * Get the mnemonic: + * await useOptions(coralnetOptions).recoverMnemonic(password) + * + * If you want to use this code inside an app, you will need several imports from https://github.com/CosmWasm/cosmjs + */ + +const path = require("path"); + +interface Options { + readonly httpUrl: string + readonly networkId: string + readonly feeToken: string + readonly gasPrice: GasPrice + readonly bech32prefix: string + readonly hdPath: HdPath + readonly faucetUrl?: string + readonly defaultKeyFile: string + readonly gasLimits: Partial> // only set the ones you want to override +} + +const coralnetOptions: Options = { + httpUrl: 'https://lcd.coralnet.cosmwasm.com', + networkId: 'cosmwasm-coral', + feeToken: 'ucosm', + gasPrice: GasPrice.fromString("0.025ucosm"), + bech32prefix: 'cosmos', + faucetToken: 'SHELL', + faucetUrl: 'https://faucet.coralnet.cosmwasm.com/credit', + hdPath: makeCosmoshubPath(0), + defaultKeyFile: path.join(process.env.HOME, ".coral.key"), + gasLimits: { + upload: 1500000, + init: 600000, + register:800000, + transfer: 80000, + }, +} + +interface Network { + setup: (password: string, filename?: string) => Promise + recoverMnemonic: (password: string, filename?: string) => Promise +} + +const useOptions = (options: Options): Network => { + + const loadOrCreateWallet = async (options: Options, filename: string, password: string): Promise => { + let encrypted: string; + try { + encrypted = fs.readFileSync(filename, 'utf8'); + } catch (err) { + // generate if no file exists + const wallet = await Secp256k1HdWallet.generate(12, options.hdPath, options.bech32prefix); + const encrypted = await wallet.serialize(password); + fs.writeFileSync(filename, encrypted, 'utf8'); + return wallet; + } + // otherwise, decrypt the file (we cannot put deserialize inside try or it will over-write on a bad password) + const wallet = await Secp256k1HdWallet.deserialize(encrypted, password); + return wallet; + }; + + const connect = async ( + wallet: Secp256k1HdWallet, + options: Options + ): Promise => { + const [{ address }] = await wallet.getAccounts(); + + const client = new SigningCosmWasmClient( + options.httpUrl, + address, + wallet, + coralnetOptions.gasPrice, + coralnetOptions.gasLimits, + ); + return client; + }; + + const hitFaucet = async ( + faucetUrl: string, + address: string, + denom: string + ): Promise => { + await axios.post(faucetUrl, { denom, address }); + } + + const setup = async (password: string, filename?: string): Promise => { + const keyfile = filename || options.defaultKeyFile; + const wallet = await loadOrCreateWallet(coralnetOptions, keyfile, password); + const client = await connect(wallet, coralnetOptions); + + // ensure we have some tokens + if (options.faucetUrl) { + const account = await client.getAccount(); + if (!account) { + console.log(`Getting ${options.feeToken} from faucet`); + await hitFaucet(options.faucetUrl, client.senderAddress, options.feeToken); + } + } + + return client; + } + + const recoverMnemonic = async (password: string, filename?: string): Promise => { + const keyfile = filename || options.defaultKeyFile; + const wallet = await loadOrCreateWallet(coralnetOptions, keyfile, password); + return wallet.mnemonic; + } + + return {setup, recoverMnemonic}; +} + +interface Config { + readonly purchase_price?: Coin + readonly transfer_price?: Coin +} + +interface ResolveRecordResponse { + readonly address?: string +} + +interface InitMsg { + + readonly purchase_price?: Coin + readonly transfer_price?: Coin +} + +interface NameServiceInstance { + readonly contractAddress: string + + // queries + record: (name: string) => Promise + config: () => Promise + + // actions + register: (name: string, amount: Coin[]) => Promise + transfer: (name: string, to: string, amount: Coin[]) => Promise +} + +interface NameServiceContract { + upload: () => Promise + + instantiate: (codeId: number, initMsg: InitMsg, label: string) => Promise + + use: (contractAddress: string) => NameServiceInstance +} + +const NameService = (client: SigningCosmWasmClient): NameServiceContract => { + const use = (contractAddress: string): NameServiceInstance => { + const record = async (name: string): Promise => { + return client.queryContractSmart(contractAddress, {resolve_record: { name }}); + }; + + const config = async (): Promise => { + return client.queryContractSmart(contractAddress, {config: { }}); + }; + + const register = async (name: string, amount: Coin[]): Promise => { + const result = await client.execute(contractAddress, {register: { name }}, "", amount); + return result.transactionHash; + }; + + const transfer = async (name: string, to: string, amount: Coin[]): Promise => { + const result = await client.execute(contractAddress, {transfer: { name, to }}, "", amount); + return result.transactionHash; + }; + + return { + contractAddress, + record, + config, + register, + transfer, + }; + } + + const downloadWasm = async (url: string): Promise => { + const r = await axios.get(url, { responseType: 'arraybuffer' }) + if (r.status !== 200) { + throw new Error(`Download error: ${r.status}`) + } + return r.data + } + + const upload = async (): Promise => { + const meta = { + source: "https://github.com/CosmWasm/cosmwasm-examples/tree/nameservice-0.7.0/nameservice", + builder: "cosmwasm/rust-optimizer:0.10.4" + }; + const sourceUrl = "https://github.com/CosmWasm/cosmwasm-examples/releases/download/nameservice-0.7.0/contract.wasm"; + const wasm = await downloadWasm(sourceUrl); + const result = await client.upload(wasm, meta); + return result.codeId; + } + + const instantiate = async (codeId: number, initMsg: Record, label: string): Promise => { + const result = await client.instantiate(codeId, initMsg, label, { memo: `Init ${label}`}); + return use(result.contractAddress); + } + + return { upload, instantiate, use }; +} + +// Demo: +// const client = await useOptions(coralnetOptions).setup(PASSWORD); +// const { address } = await client.getAccount() +// const factory = NameService(client) +// +// const codeId = await factory.upload(); +// codeId -> 12 +// const initMsg = { purchase_price: { denom: "ushell", amount:"1000" }, transfer_price: { denom: "ushell", amount:"1000" }} +// const contract = await factory.instantiate(12, initMsg, "My Name Service") +// contract.contractAddress -> 'coral1267wq2zk22kt5juypdczw3k4wxhc4z47mug9fd' +// +// OR +// +// const contract = factory.use('coral1267wq2zk22kt5juypdczw3k4wxhc4z47mug9fd') +// +// const randomAddress = 'coral162d3zk45ufaqke5wgcd3kh336k6p3kwwkdj3ma' +// +// contract.config() +// contract.register("name", [{"denom": "ushell", amount: "4000" }]) +// contract.record("name") +// contract.transfer("name", randomAddress, [{"denom": "ushell", amount: "2000" }]) +// diff --git a/rust-contract-building-poc/nameservice/rustfmt.toml b/rust-contract-building-poc/nameservice/rustfmt.toml new file mode 100644 index 0000000..11a85e6 --- /dev/null +++ b/rust-contract-building-poc/nameservice/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/rust-contract-building-poc/nameservice/schema/cw-nameservice.json b/rust-contract-building-poc/nameservice/schema/cw-nameservice.json new file mode 100644 index 0000000..da12d31 --- /dev/null +++ b/rust-contract-building-poc/nameservice/schema/cw-nameservice.json @@ -0,0 +1,213 @@ +{ + "contract_name": "cw-nameservice", + "contract_version": "0.12.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "properties": { + "purchase_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "transfer_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "name", + "to" + ], + "properties": { + "name": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "resolve_record" + ], + "properties": { + "resolve_record": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "properties": { + "purchase_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "transfer_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "resolve_record": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ResolveRecordResponse", + "type": "object", + "properties": { + "address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/coin_helpers.rs b/rust-contract-building-poc/nameservice/src/coin_helpers.rs new file mode 100644 index 0000000..c7c0892 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/coin_helpers.rs @@ -0,0 +1,62 @@ +use crate::error::ContractError; +use cosmwasm_std::Coin; + +pub fn assert_sent_sufficient_coin( + sent: &[Coin], + required: Option, +) -> Result<(), ContractError> { + if let Some(required_coin) = required { + let required_amount = required_coin.amount.u128(); + if required_amount > 0 { + let sent_sufficient_funds = sent.iter().any(|coin| { + // check if a given sent coin matches denom + // and has sufficient amount + coin.denom == required_coin.denom && coin.amount.u128() >= required_amount + }); + + if sent_sufficient_funds { + return Ok(()); + } else { + return Err(ContractError::InsufficientFundsSend {}); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use cosmwasm_std::{coin, coins}; + + #[test] + fn assert_sent_sufficient_coin_works() { + match assert_sent_sufficient_coin(&[], Some(coin(0, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&[], Some(coin(5, "token"))) { + Ok(()) => panic!("Should have raised insufficient funds error"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&coins(10, "smokin"), Some(coin(5, "token"))) { + Ok(()) => panic!("Should have raised insufficient funds error"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&coins(10, "token"), Some(coin(5, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + let sent_coins = vec![coin(2, "smokin"), coin(5, "token"), coin(1, "earth")]; + match assert_sent_sufficient_coin(&sent_coins, Some(coin(5, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + } +} diff --git a/rust-contract-building-poc/nameservice/src/contract.rs b/rust-contract-building-poc/nameservice/src/contract.rs new file mode 100644 index 0000000..c62b3f7 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/contract.rs @@ -0,0 +1,144 @@ +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, +}; + +use crate::coin_helpers::assert_sent_sufficient_coin; +use crate::error::ContractError; +use crate::msg::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, ResolveRecordResponse}; +use crate::state::{Config, NameRecord, CONFIG, NAME_RESOLVER}; + +const MIN_NAME_LENGTH: u64 = 3; +const MAX_NAME_LENGTH: u64 = 64; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let config = Config { + purchase_price: msg.purchase_price, + transfer_price: msg.transfer_price, + }; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Register { name } => execute_register(deps, env, info, name), + ExecuteMsg::Transfer { name, to } => execute_transfer(deps, env, info, name, to), + } +} + +pub fn execute_register( + deps: DepsMut, + _env: Env, + info: MessageInfo, + name: String, +) -> Result { + // we only need to check here - at point of registration + validate_name(&name)?; + let config = CONFIG.load(deps.storage)?; + assert_sent_sufficient_coin(&info.funds, config.purchase_price)?; + + let key = name.as_bytes(); + let record = NameRecord { owner: info.sender }; + + if (NAME_RESOLVER.may_load(deps.storage, key)?).is_some() { + // name is already taken + return Err(ContractError::NameTaken { name }); + } + + // name is available + NAME_RESOLVER.save(deps.storage, key, &record)?; + + Ok(Response::default()) +} + +pub fn execute_transfer( + deps: DepsMut, + _env: Env, + info: MessageInfo, + name: String, + to: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + assert_sent_sufficient_coin(&info.funds, config.transfer_price)?; + + let new_owner = deps.api.addr_validate(&to)?; + let key = name.as_bytes(); + NAME_RESOLVER.update(deps.storage, key, |record| { + if let Some(mut record) = record { + if info.sender != record.owner { + return Err(ContractError::Unauthorized {}); + } + + record.owner = new_owner.clone(); + Ok(record) + } else { + Err(ContractError::NameNotExists { name: name.clone() }) + } + })?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ResolveRecord { name } => query_resolver(deps, env, name), + QueryMsg::Config {} => to_binary::(&CONFIG.load(deps.storage)?.into()), + } +} + +fn query_resolver(deps: Deps, _env: Env, name: String) -> StdResult { + let key = name.as_bytes(); + + let address = match NAME_RESOLVER.may_load(deps.storage, key)? { + Some(record) => Some(String::from(&record.owner)), + None => None, + }; + let resp = ResolveRecordResponse { address }; + + to_binary(&resp) +} + +// let's not import a regexp library and just do these checks by hand +fn invalid_char(c: char) -> bool { + let is_valid = + c.is_ascii_digit() || c.is_ascii_lowercase() || (c == '.' || c == '-' || c == '_'); + !is_valid +} + +/// validate_name returns an error if the name is invalid +/// (we require 3-64 lowercase ascii letters, numbers, or . - _) +fn validate_name(name: &str) -> Result<(), ContractError> { + let length = name.len() as u64; + if (name.len() as u64) < MIN_NAME_LENGTH { + Err(ContractError::NameTooShort { + length, + min_length: MIN_NAME_LENGTH, + }) + } else if (name.len() as u64) > MAX_NAME_LENGTH { + Err(ContractError::NameTooLong { + length, + max_length: MAX_NAME_LENGTH, + }) + } else { + match name.find(invalid_char) { + None => Ok(()), + Some(bytepos_invalid_char_start) => { + let c = name[bytepos_invalid_char_start..].chars().next().unwrap(); + Err(ContractError::InvalidCharacter { c }) + } + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/error.rs b/rust-contract-building-poc/nameservice/src/error.rs new file mode 100644 index 0000000..c70a2c1 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/error.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Insufficient funds sent")] + InsufficientFundsSend {}, + + #[error("Name does not exist (name {name})")] + NameNotExists { name: String }, + + #[error("Name has been taken (name {name})")] + NameTaken { name: String }, + + #[error("Name too short (length {length} min_length {min_length})")] + NameTooShort { length: u64, min_length: u64 }, + + #[error("Name too long (length {length} min_length {max_length})")] + NameTooLong { length: u64, max_length: u64 }, + + #[error("Invalid character(char {c}")] + InvalidCharacter { c: char }, +} diff --git a/rust-contract-building-poc/nameservice/src/lib.rs b/rust-contract-building-poc/nameservice/src/lib.rs new file mode 100644 index 0000000..9287301 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/lib.rs @@ -0,0 +1,10 @@ +pub mod coin_helpers; +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +mod tests; + +pub use crate::error::ContractError; diff --git a/rust-contract-building-poc/nameservice/src/msg.rs b/rust-contract-building-poc/nameservice/src/msg.rs new file mode 100644 index 0000000..cc190f1 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/msg.rs @@ -0,0 +1,46 @@ +use crate::state::Config; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Coin; + +#[cw_serde] +pub struct InstantiateMsg { + pub purchase_price: Option, + pub transfer_price: Option, +} + +#[cw_serde] +pub enum ExecuteMsg { + Register { name: String }, + Transfer { name: String, to: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // ResolveAddress returns the current address that the name resolves to + #[returns(ResolveRecordResponse)] + ResolveRecord { name: String }, + #[returns(ConfigResponse)] + Config {}, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct ResolveRecordResponse { + pub address: Option, +} + +#[cw_serde] +pub struct ConfigResponse { + pub purchase_price: Option, + pub transfer_price: Option, +} + +impl From for ConfigResponse { + fn from(config: Config) -> ConfigResponse { + ConfigResponse { + purchase_price: config.purchase_price, + transfer_price: config.transfer_price, + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/state.rs b/rust-contract-building-poc/nameservice/src/state.rs new file mode 100644 index 0000000..acaf186 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/state.rs @@ -0,0 +1,17 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::{Item, Map}; + +#[cw_serde] +pub struct Config { + pub purchase_price: Option, + pub transfer_price: Option, +} + +#[cw_serde] +pub struct NameRecord { + pub owner: Addr, +} + +pub const CONFIG: Item = Item::new("config"); +pub const NAME_RESOLVER: Map<&[u8], NameRecord> = Map::new("name_resolver"); diff --git a/rust-contract-building-poc/nameservice/src/tests.rs b/rust-contract-building-poc/nameservice/src/tests.rs new file mode 100644 index 0000000..698ddde --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/tests.rs @@ -0,0 +1,372 @@ +#[cfg(test)] +mod test_module { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{coin, coins, from_binary, Coin, Deps, DepsMut}; + + use crate::contract::{execute, instantiate, query}; + use crate::error::ContractError; + use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ResolveRecordResponse}; + use crate::state::Config; + + fn assert_name_owner(deps: Deps, name: &str, owner: &str) { + let res = query( + deps, + mock_env(), + QueryMsg::ResolveRecord { + name: name.to_string(), + }, + ) + .unwrap(); + + let value: ResolveRecordResponse = from_binary(&res).unwrap(); + assert_eq!(Some(owner.to_string()), value.address); + } + + fn assert_config_state(deps: Deps, expected: Config) { + let res = query(deps, mock_env(), QueryMsg::Config {}).unwrap(); + let value: Config = from_binary(&res).unwrap(); + assert_eq!(value, expected); + } + + fn mock_init_with_price(deps: DepsMut, purchase_price: Coin, transfer_price: Coin) { + let msg = InstantiateMsg { + purchase_price: Some(purchase_price), + transfer_price: Some(transfer_price), + }; + + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps, mock_env(), info, msg) + .expect("contract successfully handles InstantiateMsg"); + } + + fn mock_init_no_price(deps: DepsMut) { + let msg = InstantiateMsg { + purchase_price: None, + transfer_price: None, + }; + + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps, mock_env(), info, msg) + .expect("contract successfully handles InstantiateMsg"); + } + + fn mock_alice_registers_name(deps: DepsMut, sent: &[Coin]) { + // alice can register an available name + let info = mock_info("alice_key", sent); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let _res = execute(deps, mock_env(), info, msg) + .expect("contract successfully handles Register message"); + } + + #[test] + fn proper_init_no_fees() { + let mut deps = mock_dependencies(); + + mock_init_no_price(deps.as_mut()); + + assert_config_state( + deps.as_ref(), + Config { + purchase_price: None, + transfer_price: None, + }, + ); + } + + #[test] + fn proper_init_with_fees() { + let mut deps = mock_dependencies(); + + mock_init_with_price(deps.as_mut(), coin(3, "token"), coin(4, "token")); + + assert_config_state( + deps.as_ref(), + Config { + purchase_price: Some(coin(3, "token")), + transfer_price: Some(coin(4, "token")), + }, + ); + } + + #[test] + fn register_available_name_and_query_works() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // querying for name resolves to correct address + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn register_available_name_and_query_works_with_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // anyone can register an available name with more fees than needed + let info = mock_info("bob_key", &coins(5, "token")); + let msg = ExecuteMsg::Register { + name: "bob".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Register message"); + + // querying for name resolves to correct address + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + assert_name_owner(deps.as_ref(), "bob", "bob_key"); + } + + #[test] + fn fails_on_register_already_taken_name() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // bob can't register the same name + let info = mock_info("bob_key", &coins(2, "token")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTaken { .. }) => {} + Err(_) => panic!("Unknown error"), + } + // alice can't register the same name again + let info = mock_info("alice_key", &coins(2, "token")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTaken { .. }) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn register_available_name_fails_with_invalid_name() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + let info = mock_info("bob_key", &coins(2, "token")); + + // hi is too short + let msg = ExecuteMsg::Register { + name: "hi".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTooShort { .. }) => {} + Err(_) => panic!("Unknown error"), + } + + // 65 chars is too long + let msg = ExecuteMsg::Register { + name: "01234567890123456789012345678901234567890123456789012345678901234".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTooLong { .. }) => {} + Err(_) => panic!("Unknown error"), + } + + // no upper case... + let msg = ExecuteMsg::Register { + name: "LOUD".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::InvalidCharacter { c }) => assert_eq!(c, 'L'), + Err(_) => panic!("Unknown error"), + } + // ... or spaces + let msg = ExecuteMsg::Register { + name: "two words".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info, msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::InvalidCharacter { .. }) => {} + Err(_) => panic!("Unknown error"), + } + } + + #[test] + fn fails_on_register_insufficient_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + + // anyone can register an available name with sufficient fees + let info = mock_info("alice_key", &[]); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn fails_on_register_wrong_fee_denom() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + + // anyone can register an available name with sufficient fees + let info = mock_info("alice_key", &coins(2, "earth")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn transfer_works() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Transfer message"); + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "bob_key"); + } + + #[test] + fn transfer_works_with_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[coin(1, "earth"), coin(2, "token")]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Transfer message"); + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "bob_key"); + } + + #[test] + fn fails_on_transfer_non_existent() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("frank_key", &coins(2, "token")); + let msg = ExecuteMsg::Transfer { + name: "alice42".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameNotExists { name }) => assert_eq!(name, "alice42"), + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (alice_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn fails_on_transfer_from_nonowner() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("frank_key", &coins(2, "token")); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::Unauthorized { .. }) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (alice_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn fails_on_transfer_insufficient_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(5, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[coin(1, "earth"), coin(2, "token")]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn returns_empty_on_query_unregistered_name() { + let mut deps = mock_dependencies(); + + mock_init_no_price(deps.as_mut()); + + // querying for unregistered name results in NotFound error + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::ResolveRecord { + name: "alice".to_string(), + }, + ) + .unwrap(); + let value: ResolveRecordResponse = from_binary(&res).unwrap(); + assert_eq!(None, value.address); + } +}