From 4284f07654d2c9536ba3cecea1ba1787aff04620 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 3 Sep 2026 22:47:15 +0000 Subject: [PATCH 1/3] Fix WIRE-349 creator chain-kind validation Change-Id: I697804f02a44b93e6485a0b4b91ef25427f9de77 --- contracts/sysio.reserv/src/sysio.reserv.cpp | 41 +++++++++------ contracts/sysio.reserv/sysio.reserv.wasm | Bin 84138 -> 84218 bytes contracts/tests/sysio.reserv_tests.cpp | 54 ++++++++++++++++++-- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/contracts/sysio.reserv/src/sysio.reserv.cpp b/contracts/sysio.reserv/src/sysio.reserv.cpp index c260c9da52..9e58579efe 100644 --- a/contracts/sysio.reserv/src/sysio.reserv.cpp +++ b/contracts/sysio.reserv/src/sysio.reserv.cpp @@ -117,14 +117,17 @@ std::optional chain_kind_for_code(sysio::slug_name chain_ return tbl.get(pk).kind; } -/// Soft-gate never-throw msgch handlers before they can emit a queueout to an -/// unregistered destination chain. `sysio.msgch::queueout` fails loudly for -/// direct callers, but dispatch callbacks must log-and-skip instead of -/// aborting the consensus-tipping delivery transaction. -bool registered_chain_or_skip(sysio::slug_name chain_code, const char* handler) { - if (chain_kind_for_code(chain_code).has_value()) return true; +/// Resolve the authoritative kind while soft-gating never-throw msgch handlers +/// before they can emit a queueout to an unregistered destination chain. +/// `sysio.msgch::queueout` fails loudly for direct callers, but dispatch +/// callbacks must log-and-skip instead of aborting the consensus-tipping +/// delivery transaction. +std::optional +registered_chain_kind_or_skip(sysio::slug_name chain_code, const char* handler) { + auto kind = chain_kind_for_code(chain_code); + if (kind.has_value()) return kind; sysio::print(handler, ": chain_code is not registered; skipping\n"); - return false; + return std::nullopt; } /// Reconstruct a `sysio::public_key` variant from the raw creator-key @@ -422,7 +425,9 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, bool is_private, std::vector creator_pub_key) { require_auth(MSGCH_ACCOUNT); - if (!registered_chain_or_skip(chain_code, "oncrtreserve")) return; + const auto expected_chain_kind = + registered_chain_kind_or_skip(chain_code, "oncrtreserve"); + if (!expected_chain_kind.has_value()) return; // Soft-validate; silent skip per feedback_opp_handlers_never_throw. if (connector_weight_bps == 0 || connector_weight_bps > MAX_CONNECTOR_WEIGHT_BPS) { @@ -436,6 +441,11 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, // the SAME cancel/refund path as an unlinked creator below (insert a CANCELLED // row + queue RESERVE_CREATE_CANCELLED), idempotently. const bool invalid_amount = (external_token_amount == 0 || requested_wire_amount == 0); + // `chain_code` is registry-owned and authoritative. The attestation also + // carries a creator kind, but that redundant, externally supplied value + // must not select a different public-key variant or produce a reserve that + // `matchreserve` can never match against the registered chain kind. + const bool creator_chain_kind_mismatch = creator_chain_kind != *expected_chain_kind; // Same `sysio`-billed metadata bound the privileged registrations enforce with // `check_metadata`, asked the non-throwing way: this handler must never abort, so an // over-bound string joins the reject/refund path below rather than reverting dispatch. @@ -466,14 +476,14 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, } opp::types::ChainAddress creator; - creator.kind = creator_chain_kind; + creator.kind = *expected_chain_kind; creator.address = std::move(creator_chain_addr); // Create gating: the creator must already be authex-linked to a WIRE // account ("the only requirement to create a reserve"). Reconstruct the // creator's key variant and probe `sysio.authex::links.bypubkey`. On - // any failure — malformed key bytes, no link, an invalid amount, OR - // over-bound metadata — reject by inserting a + // any failure — a chain-kind mismatch, malformed key bytes, no link, an + // invalid amount, OR over-bound metadata — reject by inserting a // CANCELLED row (for refund idempotency) and queueing // RESERVE_CREATE_CANCELLED so the outpost refunds the creator's escrow. // The CANCELLED row does NOT permanently burn the identity: a later, @@ -481,7 +491,7 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, // (prevents namespace squatting). Never throws. std::vector canonical_creator_key; { - auto pk_variant = pubkey_from_raw(creator_chain_kind, creator_pub_key, creator.address); + auto pk_variant = pubkey_from_raw(*expected_chain_kind, creator_pub_key, creator.address); bool linked = false; if (pk_variant) { sysio::authex::links_t links(AUTHEX_ACCOUNT); @@ -491,7 +501,7 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, canonical_creator_key = sysio::pubkey_to_bytes(*pk_variant); } } - if (!linked || invalid_amount || oversized_metadata) { + if (creator_chain_kind_mismatch || !linked || invalid_amount || oversized_metadata) { // A CANCELLED row already standing means this is a re-relay of the same // rejected create (an unlinked squatter OR an invalid amount). Leave it // and do NOT queue a second refund — the refund was queued when the row @@ -503,7 +513,8 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, return; } sysio::print("oncrtreserve: rejecting with RESERVE_CREATE_CANCELLED " - "(invalid amount, over-bound metadata, or unlinked / malformed creator key)\n"); + "(creator chain-kind mismatch, invalid amount, over-bound metadata, " + "or unlinked / malformed creator key)\n"); const auto now = current_time_ms(); tbl.emplace(ram_payer, pk, reserve_row{ .chain_code = chain_code, @@ -652,7 +663,7 @@ void reserve::oncnclrsv(sysio::slug_name chain_code, opp::types::ChainKind creator_chain_kind, std::vector creator_chain_addr) { require_auth(MSGCH_ACCOUNT); - if (!registered_chain_or_skip(chain_code, "oncnclrsv")) return; + if (!registered_chain_kind_or_skip(chain_code, "oncnclrsv").has_value()) return; reserves_t tbl(get_self()); auto pk = make_key(chain_code, token_code, reserve_code); diff --git a/contracts/sysio.reserv/sysio.reserv.wasm b/contracts/sysio.reserv/sysio.reserv.wasm index 289bf51d6cd48a30bd3d04a201d6c23967fb523b..cdc17701aa98daa404f7a571d9c824c45e1f1381 100755 GIT binary patch delta 10324 zcmb_id0bRg`@he7 zw5x`QzFOvz)L6J=QMOo_QkkNdp;j-Zzr?b_O!+;}xpziTKkxSW%^zn!&v};bdCqgr zooy@4f37qa{9=|o^E|>Ee`HJ2b-2bJGj(R`Oed{de-dmSK`mj=m{Z#pih(UaP{G#d zYd8kqz;QSM6_Bz_TrRdchm1inKKRXV=GPr3xlLGoQUXIfgK1vn3V~))u z0usbRw|7CnJ)uzSeJ3JFgdOaYsMc`Ydo}8U1>Rw2Vx!@6b}hCGe9Tt5qSyds1DwE8 zDcj=g0B4N01Qy|X8cujuy2eZJ8~Z&W9$sNFEj|mr0?`Vxktmwv3G7OXgy8Mi0LYig zE&t+mwHyiIGjISTNmkh?$W&nJ$tK>NiR%G=XZ>1z6MXnKI<7CU0VQ?E3$~d6o;X1* z5OZDTctJ5EZN(%{W;2q9cDoK(FA!1M_fXo+Z~65-()t?an{ArEFhCmJCXW zde!V!K)v{pAdf;VhNK9!W;UsHe@~)#)$9yYLNbMPsoJcBC_<8i#VRUF=~4ydV!BjL zIiyR6FjtEuH>OocF+@FNcFO}WuNB<#K$k;rPZI^PhrY~K$rY|RQUq%jnamlXg!i@+ zKS)EZ0=GwTP$mze#uQ0{Us*XYkfbu{ch{6IsA{ylM(a3x~;T_xsL4}Y<3EI63b)?tlQY-l5A zIP20j3c}cfZJnMT!6KQ$PP(!wIDm?R%bu$$eO$H}#g5c%F)ka`QPdDs=q%72f&hAh zD7VW_Sfw7S%WjukzixHA8X0xUX6eHeo2tmxXsme9hDb!QbC?qqYLMNE+@QhgRvP&m zptCR&2b$8vX}AkQlB8MbQjcJcN@C82<|p>BJo6T#g6fpbv?6ke%RBYu%RE@qIX7Ai z;t~=^BE)kBX64&=9$!J5H(hE+YpU(g)3r9Jah~PJs6A24quoTBfawISSuPub*JY2P zzB#xsJgL}YQjRV@Gmn0RM69E;$CSWU zfd|Qhx@8MQmzf}f3Q5CbT`}v<2Tgbw>h+Z4StTcXIJ8oPLNHt`R~4$%n3N(E-0E4w zvBpt)ceB!2r4XaYl025Yut9z%XDCse<}&+$s>6%6KU%RNQVEST!A48*^9j!j4occy zP81FCOF*IRaf`I0A!(xQxQ()oNfiZAOdL9FTgRZ0cj7ZdF%g)g0x+QJ1eE5#g`<;D zMzIi~Y!w#mdhtI693i-n0A|DB>n^|}+%y254{#nI;)2q|e+D>0CSh%YmbH2Bsq z`@CnB=~iQi`aOnnxMwh^GtjRVHs~+DqyLgrFFL`Xj>km4$yNSNj4F_WYF{tmce8|B zfk!50Ju=C&P}n$7k1pk0mxRHkgcwyo9?#V-r3k^aE3g^k*K|=JC5v*1`omfDwivPF zd1uly^pBDrtyY-86@U2Ri?Wzy_uc}TUpDDGT6haGcxU`07;5!FopRgEd8F> zA)Ebpj~D*tUDtUezywx%Zx>8Eb=iVxWtT@W&FH!f)AL^(|iIhS9}`1lvR<) z!4v1fZcIKzFid3MbsGmctY`NX=3?~6ZuU+0voMjB_GpJ`b&mm%%hJ_ewn;cwQVTFt zpX^<%J_7JKtIAB^Q>L?^o?jsA6+P#}&)SzwAd=lPDjI%f%XPPfI99u1FfgXwjMgVBTBfjC56roJ47qs)SWs;kmw0SV^ z`1LMhS?Yk;rf+}7!+g#e2cUw~g87`^*`xsjU@F@`UXxL6n_Y513Y1qSg&IveWBVlfhS+Nl43NTj-@kF$!6;`s$ z?uREpj<@L}Jppyh0ck=V=M4A4OxA3~?@-PhkB)#DY}%s(;agVr=pwve_8u7r$63zE zJ^!CLORcfdE)>+7z+9K*wyU5;-HKoU^}=EODu{aCA$l*4?ga2FbB=YS)H8HmJwtE) zLJzjcswqN=VQ4mXtgDf6M?=bdyep8c9_xfaRyx+%JTMERCyeCOJ%~ArT8%EvqL!Q7 zs=hUUA3FgDjv6;xn&>pMQ{&p8l>*1Vft%BU9g%)VbN zIKn2(?gz7(cXm9y%_?TMfM;0U>{$588~bzxzy?@9OW z9X>X7S#)L0{-nKksb>YC8;YEt1{>Lc`5WOQ zRx^KhwBK++>YhM4XH-$&p%>V@3r300f61=Q-{CB?G~A*=YC%B#!KLit!j{Nw*rHaL zc3VW+d1TQE5hYJu5=4@B$sdlY((*gNJob5ha~$w(wYFps?#~f2P#2o zeyAJMJvK^?7t!&}*6b*-_Gj-L+cNy*g z*6JlYc)a(#^fhV2zUE2DV~f@dX*M6|gp^ctd6?#7%qOXM<>p9Vzwc(g* zYj-1P`mY;-aM`*p4pn=2-7J6^nzIpvX(vO$&R&TNVKWMc!i((6UI(iz90-ghuWy5s zhp+De=h(~Zop7EVSpO(&W6?$J#r$JzKv6!FuoFduwdEV4+I)M=P+dXyI5ujF> zb;qo0aoIQQs|^pLSrdypqglrlC!jf&7Eb{<$Z9u|os>;2n{b3F0ZI5Q4N86@{rGDv`0PREm49!LgxYAfzU z4_o)z4ya^V+cFxT=Je3*nB_1o#7(wx+focEVz!f{<=dn1B)G7hcx<$Tu+(J-VQJWo zVx;ASN*U z#|m%9Crkkkv&;K>Ap&>pZ^4G|zaM^NTlTA%{$u~n=KOh+n8J`YQCx)AKzf+O6i*wu zn!WkX{ZV%rLY)_wJ`y{Y zSKg&#=|?f*j!HInZlvGcZ($QY?t+*>myhS5z)I$IV!@w`t?&4~vp*RvB5Y56S_8Q8 zKmVMtpLaOQvEa0^1{&0VD(^U)oThJ96Fr=w@thJ_+&AC?&A~gTR3$kH3{s{rgzv4* z%>IS%7;5zeh0Vc72+duOtVM227?$#5$b0rkM}UvL5#_NWdcU-OZkWRUsx_em*v_lw&#{3)ua~rbf;{J0KisC#rbQ zQN$#C-9bQ>9~%Y-y@}uK1>8aBj<}bw-efxG-GB{3SNx z#CV)gb^<*Gt2~k1$bcEWCRi~%l=Hoj6}!ME)R~h_P^j9IBviLk_afwALZiDQIfjKcPz+7?DDq}P{b@} zGBHrN?@Z>P4K&giQI0U@GmSdpv_YM2I>q(I(>1t$J<2U_WR+){LXo%jOdudH&3{f{ zEvw>Tled4BOT>n$=elCL^PC#I1qq>JNECzR3EH<7XdW<7Hy4XN|Jp49@T!hiEG|F^ zUN4H++4GSoz_s%|QGl-B_Z+MXppU?l&tO=u(-Vy@kY2y)XIQV-+*ZGY_08HPjp%2k?j2+YPfs}?8J}pcpdobM+u&zsVk6YT7Ta-DMGzd@OdUc_pC3QzG5 zCJFRqeG-sdiT+kD2wVr^P2zsDvDE5 z8{WdtA@PDL!IXH>ZOg03L=l6_-Cz#txEQCMy&Q2%3lHN2#0;i*5*YLJI^QZRwkBez ze+|^KF_^1}79ll|*7Z3)qZ`%Rb)^sDy2`H^&bnWYVt>{w3!8ggS3`SPg_FL319(9kis{fBZ8C}hFVt4I{5s{b zk&_3edQ-RjF1YZ6wpGEz7NC@mp$#X#_&8YXQt&WxZKzVJa3e{{IzU%3SY zjPB&C5UrYQ2nl+a!uMtouOp=I-?R=<5RW`O90iXdeD*{^AFS0yL0`=91j^TWO2tfy zhQ3WNgA=bYDt_sgv1rC8P=(&JcwL(i4TEie05&GlOLnf79Rm+(O=3W;-&%^TuK?6) z!OftV=XRC*EY5L-{)y)Zn?L=qq2>4?gatb7IVSL*1A2kZBaYEySlC39s<6PRR{IJR z?eLabA;Ku>0TYLCC_(t$X3!*okD<3*PG>U~_*z?eaD|k49H#x%44%*f9u2pCGB-Ru zx1r$=V;vD$+Z?(EULjskpetIpSlHmX9S*#XNWW?f<2lNG(~z6@lia*Oa$lq#lA9Mu z?mB8AxsOtTwhHD;qjRB< znB0W}?-~>rFVsG8!XSKM)*LQe<*L@z1^2>6?WPM_QLCH);o3eIguo*0Qx|l+y{mg1 zc0H>Nh=Xa^^F6dRlw3 z1tj1Kg)MLwAJX1!0X-G$#rr>{NC&Tt^zR0cdZ04!Qw!nJ*%CSh7^Ae2Enyrn->4Nd z(JEWQV1Fem5h`J&7MBFwjka)Yb`rc~R0@sUp&e`m>%)mB{Er7`7oRB%cArRw)JUH@ zL3_O1^VSV@h|YK%t16VQ@PL z!wh|7riSlOMX*)tlL{H&)jX-t8Fpy;qSh}o4&$&Uty20@V2k;ny;{r7c%|d;qgm~mfHcs z%rE1A4n8hj*a0$4&Bjg79y4?5bY?z}gC#Gc2nwC9oX7+d`d;0XL(@RTxq7N^< e)(ieF;&Lxk3$`1e6=s2=eV7I3Ozk=e!v6sv2<+(ACuGc79X-msxD|b8kYu@4ff^zU24Y^Im%m|GoCw``p`e z%U z@I4%Z3itt1XN!emlXt{-#UgQz$i#W#e9?dP3Da$M++3A&D^C$rd9)K;PBBBgm8S}- zkSJE?IVDAKslhHa|5c}ufem7gBC7HzMd%oaL$C}k8qY*)yE<2Jn!-n^LTk*ixra4g zyqo8lY<)BsCV6&5_=`})zKTkL?Vg{bE?VGI_G4@`9ALL%Tf#b40#Y_F~xKDRK^!;0N|gd>p*Zq7%LjxB<}$vXLm7+k? z9F2NGC^D-+l4K*UsUXXvJaZDa0sP9IYJ4={-~$xk5bKt#zz8-nxqI+kP|RwP=!z5U zuK~CiGNm%i)rFD^(+V@CWoDP$8S`qvC3kU# z>g^e#Am-o#L8(HC9?*=g5TCda#8<=v;_^ma+z1jk!ppEBuF+58-rw(&w$pzjt`eq% zk9?@a-0EShI4w3KRIw+DHb<~xS8=P^sAh6RP=?A|k8#?mj@k%Pg=~T55K7P+M7f-S1ZV1@y6kewwd*#Q zGsLJ-rUHuV~)chfvX zkdIs(G9~w;A!8k6m>}{v+#-aUKpl)h>H&T41*&e{D%ZG!Sc0GnRK-#1&C`6lgopxB zc4vqO$OjQ4&Om+71j;gzXLZv<+@uvQNPilVj|#MORjge{Dxo7)XdehT5Ql~%1L}&s zf*gc)cHjp3X;)G5YMaEr%~AP+*dh^az=Q$qBD9Hj0)jegKhaj*?1 zDUvF*XD?>1^)Elep>o~wjKa3 zHner2dD?zK9?E`ieHvR&JL(|#uM8r2~l7O=-VD6o+A>98Jovp*S& zY4Vegz#>oIC)WdBWdb_JVw%#if0I#)g>NG(hU*sE6u2487`;2JSVenaS>Rn*#xd;E zjzi!*me8pY{LVUe>H&YSwVhVLT-NF7tx(9Ib5z(CRE|U_w4@4Uq8|0KRO~qRu#Oiy z$JSf?H4lpUdcf_EQv>+S<7{5%&XCW}cJ2xDS?ez8B=pEG>3Dm2zsqG@v8d}`;Cpty zYc6#pb!!VBct&*V3xHZ{70dc1JDPePUiIR!0YUZ zp8tgJSj(4s!YBp{L)rS5TEb7P>ZNILiVg485I$yedTslEG<+nSW7qmd`=h2oJ$L|b zKAzZqEgk(_TIRTFrHl)7YB=3!1x5m1b%UJn8@q6&~?xJaMj$S zHn|%foOIV!4|%$euK?VKDHGlj-}{zr-0eXo`t6BI-}+RZpz_V?8WHb_f{RXg zQxA^tLu}JTuWAoYB)D#Vvni_9k6HJ&V0GPtQL$z`-ML~tv)sD?eMQH~8SoaHGI=?y zWpPusM(egii~FnF{K(0xBAHiE53(CmdczGIw2}QW)mnRCk+t@~_gL(-Mkqr2X^rb~ zYDj9IA2I4MEB>iMm5tLY^b}4zCZhTV&F}}rDL|*Rq?X}Jf71C#5*8Ik zlD9zTicb=fiYKEYC1E^|uH)-PTM>8e#XVsHJF&PSD)!#uv49?A^CwZPXh|M0c6-SH zn8$iAO~cm0r8#hx9a`$Zyt@1i_VoH%sJE%*Jl^r5{zDeCf%tB- zf$%h91L0}rhVKcteLq6ne#eGxBHVtpi3EntBygk613^@wjf*^=lLNz~D` zv4RU{EnAa zgC&(_*Cn`Mq>R|XK1sdBag^wiXR-;U4a9R7JWERd3jS|XL}OM>@+{W#lV>?p^6{*9 zZhrQvDUq%(qWpKFj2OfUZ^x%OJlUDDqb<^?+>yX$?RW|< zuSJPO!}rBaI{R;KWr-`>G`x z9(^~a=Dmw*du=w$-OO`hZ<>fO^Z&XUU@yD#4K2OtKvdY@PZ&#^YAt>8Kyrq@;Y{i+ ziF;v5sWpImKfGH>l@jil97W0%p5fc0fVDd4^?*GNo(4}1A<-+BX+rw>P9L^|;B6L`w3JN!o0WFn8@xbFVTPa?B5Ro^}lRfU*z z`uLd6dL7aEn99Zc~YZ zXU`l<31mMB@-t3B-GaHsW*r-d6RMBl#%A`4GUtoFMkA-!M@xdD9 z;tw#AI?wCeHMh*A?2>~fBKpvt6TA_ip zi=nzMXV=yUfR7;(e~6LPP54S(%tFpZq5w(f+Moc#&$a2M3qa4z{QNQ)*6X)?0_gRd zK8E!=Jyz%f>GdmEU&Y{jeR(ApQ*UibVx^r&DOmUO1rd2p5ko?uJ-;*I)jPz^aY|Q2Cz%iMU!ZoM3c%Y*SQ zgWqIe1M74p9+qii>q0!!uH?sz#~2Q8rT8r-7mtn@dZ&{pu9a|_^B=q5BbIiz8N9)Y z?$l>f&xF~m-ksO^>T1+`<*o1pABV&XY6Mf_MK>+4A``_d{o3QK&h;o(a@)cRuT90t zA=MEzx_AMnA#gCo!@!uW*ZF>7U8*Cx`&L3#8-ux`Xem+#sa~JsHM~)MP-FTauCZ;^ zJ>X4l_{P^~>PTwgTj$S6GJxeJXjD0+d)LEpK3Smgjpi_o^}8E|V6w0YH&9pvRE@0v z8c;sF>stNy1|RoxR}A-Qse6|#tm@|}v9%TNV^cRv3hqPJ@pcxT*(vgDK8vhz?&c(9 zob0>++ZwQse;EtQxgh?(M&qrAsE_&Q#9D25G-6w;UuWRF{|o|gv~>ElY+O1{e%H5j zuUk`e5|KWg-;W}klgu)IlY@%zVf#Kvs~ky7e*1R>I^J#xbBqP28wP55r(V$9KNH;a zi-m2xV<>}Zzr-^4-H+fiyeM|Zbn5SES<3-$)Hav=5#_RxlS8I@J(s)-ocOiYreI=n zN<-CH?{>NPdkZLIv47;k3fBLRjM{$r^ZCOe2EVPA{V_?reTwDYdmp}J7w(O(J!UtJ zvFfI^Y2R5OZ2n4E2Ya;609lr=(0j{!wb=lfu)lOSfD2$>X%h)V@cSB^80Og=CT*7l zG1?mdcKD{Wp9$~~{F@eHfo^p-7(($Jh5FVIrd)f&0*$4^5IuhcMEha4M6z$tzOg`S zIIP*NkO0d{n_1yyK%cV626=E?ll(w~liGWJa8ay2qfPgR?(m~_+#d>fKpLgJVTUKh zO3Qv1Vd9@1iXIPvO5%CE1+FZhI(kEpcIJL0>fa85dmpS!T>`c?c*@$0@a!&9E_*NUMR{R;gF{72!~v_ zP>N-IK$)v4kuVrO)(RqFLlgZAKfcns?gn|z)dR^0^p_?5) z1_^lBAn{?^{1|vndol*p+O4J7`T@XiTFd%S-~FJAa5xzSbH(7Af|1)(BcyjZs=*BN#x;tBs+qW>17}zRH+HIDx*QdlIxZ+QPN9NwC1E zEHUyh?dQg@G@N+C-!O1?@v+ljcU3Z^M|wR9noJYPsS9)flxzP;MwD@bZ{phFV#e(kkp&;mAVYnx%;PVHPX$Z~%O!FJ>XVFxed(idp{3A$ULpuN`|Ld{D=yZwVwPjkpMX?3!oqbRH|?VSZK zNh^O0>e&Q!YPn#aYAe0+7*t8x$<`2qZPP0R`!rkWudT5yuHS2;lu<(lDT77~6kZ=O zXl(wV0iy;DR0fS4`N~K^eY;Yyzhx_x+M-V`efkL~7PXu0@yaZy?^X%+A3+Q20B1~c H3qklFmB@`v diff --git a/contracts/tests/sysio.reserv_tests.cpp b/contracts/tests/sysio.reserv_tests.cpp index 78a91a8f01..29bb1e3eb7 100644 --- a/contracts/tests/sysio.reserv_tests.cpp +++ b/contracts/tests/sysio.reserv_tests.cpp @@ -31,6 +31,14 @@ std::vector em_pubkey_bytes(const fc::crypto::public_key& pk) { return std::vector(compressed.begin(), compressed.end()); } +/// Extract the raw 32-byte ed25519 pubkey carried by an SVM ChainAddress. +std::vector ed_pubkey_bytes(const fc::crypto::public_key& pk) { + const auto& shim = pk.get(); + const auto raw = shim.serialize(); + return std::vector(reinterpret_cast(raw.data()), + reinterpret_cast(raw.data()) + raw.size()); +} + } // anonymous namespace /// v6 data-model: reserves are keyed by the triple `(chain_code, token_code, @@ -336,8 +344,8 @@ class sysio_reserve_tester : public tester { /// Seed an authex link for `pub` on `chain_kind` via the depot-only /// `recordlink` (signed by sysio.authex itself). After this, a creator /// presenting the matching raw pubkey reads as linked in oncrtreserve. - action_result recordlink_em(name account, ChainKind chain_kind, - const fc::crypto::public_key& pub) { + action_result recordlink(name account, ChainKind chain_kind, + const fc::crypto::public_key& pub) { return push_to(AUTHEX_ACCOUNT, authex_abi_ser, AUTHEX_ACCOUNT, "recordlink"_n, mvo() ("account", account) ("chain_kind", chain_kind) @@ -547,6 +555,42 @@ BOOST_FIXTURE_TEST_CASE(oncrtreserve_unlinked_creator_is_cancelled, sysio_reserv BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); } FC_LOG_AND_RETHROW() } +// WNS-28: `chain_code` determines the authoritative ChainKind. Before the fix, +// a payload could name ETH while supplying SVM plus an authex-linked ED key; +// oncrtreserve trusted the payload kind and created a PENDING row that +// matchreserve could never match using ETH's registered EVM kind. The handler +// must reject through its non-throwing cancel/refund path instead. +BOOST_FIXTURE_TEST_CASE(oncrtreserve_creator_chain_kind_mismatch_is_cancelled, + sysio_reserve_tester) { try { + deploy_authex(); + + auto creator_pub = fc::crypto::private_key::generate( + fc::crypto::private_key::key_type::ed).get_public_key(); + BOOST_REQUIRE_EQUAL(success(), + recordlink("alice"_n, ChainKind::CHAIN_KIND_SVM, creator_pub)); + const auto creator_key = ed_pubkey_bytes(creator_pub); + + BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() + ("chain_code", codename_mvo("ETH")) + ("token_code", codename_mvo("ETH")) + ("reserve_code", codename_mvo("USERRES")) + ("name", "mismatched creator") + ("description", "") + ("external_token_amount", 1000) + ("requested_wire_amount", 1000) + ("source_token_precision", 9u) + ("connector_weight_bps", 5000) + ("creator_chain_kind", ChainKind::CHAIN_KIND_SVM) + ("creator_chain_addr", creator_key) + ("is_private", false) + ("creator_pub_key", creator_key))); + + auto r = find_reserve("ETH", "ETH", "USERRES"); + BOOST_REQUIRE(!r.is_null()); + BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); + BOOST_REQUIRE(r["creator_pub_key"].as_string().empty()); +} FC_LOG_AND_RETHROW() } + // A re-relay of the same unlinked create must be idempotent — it must NOT // re-insert the row or queue a second RESERVE_CREATE_CANCELLED refund. The // CANCELLED marker stays exactly as first written (the outpost refunds per @@ -620,7 +664,7 @@ BOOST_FIXTURE_TEST_CASE(oncrtreserve_cancelled_is_reclaimable_by_linked_creator, auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); auto creator_pub = creator_priv.get_public_key(); BOOST_REQUIRE_EQUAL(success(), - recordlink_em("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); + recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() ("chain_code", codename_mvo("ETH")) @@ -663,7 +707,7 @@ BOOST_FIXTURE_TEST_CASE(oncrtreserve_invalid_amount_is_cancelled, sysio_reserve_ auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); auto creator_pub = creator_priv.get_public_key(); BOOST_REQUIRE_EQUAL(success(), - recordlink_em("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); + recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); // Linked creator, but external_token_amount == 0 (the clamp result for an // invalid inbound amount). The link is valid, so the amount alone forces the @@ -710,7 +754,7 @@ BOOST_FIXTURE_TEST_CASE(oncrtreserve_oversized_metadata_is_cancelled, sysio_rese auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); auto creator_pub = creator_priv.get_public_key(); BOOST_REQUIRE_EQUAL(success(), - recordlink_em("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); + recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); auto create_with_metadata = [&](std::string_view reserve_code, const std::string& name, From ccd1bf796e7e9598a05bae9a8e53aecb10c0288c Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 4 Sep 2026 00:54:27 +0000 Subject: [PATCH 2/3] Refresh sysio.reserv WASM after base update Change-Id: Ifc7cd1309e33b02257218b904a19a5e8c73e16e2 --- contracts/sysio.reserv/sysio.reserv.wasm | Bin 84218 -> 85085 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/contracts/sysio.reserv/sysio.reserv.wasm b/contracts/sysio.reserv/sysio.reserv.wasm index cdc17701aa98daa404f7a571d9c824c45e1f1381..902e3815c60ef703369812d0579717d3c3d7073a 100755 GIT binary patch delta 2419 zcmcIlU2IfE6u#fwyGYr#+(H-1vW2_tQnvILQI~)rz2(2HC`ynZ&?3Z##Q;7ih{!Hr zuz*5gzyYKIqA$`INH$>;ew1Khq(L+(LQ{((Q~@clMbLr;J#+8w(i(g*@nz=B`Ocg< z^PMwi?rm|L-{CkoM^n3Sk5_9)R5dk~dmNif>RC!26cf&jCY-@{IE!;=M$voheO9uG zZDw_B3*&4X+s^tNzNp=cv^e^Baf=%TrKt^Y8!(uu1@+$|-r;@~e?%%qyajYczAVrN zzPy(KpWyv-qwW57tI*PMQXMz{gE7u;ZiZDZtK1&C~`^#!@SX z(pXq|7a#Jxs^PfLr&Z0ygUBaU!}OfBs?Bqlc-Dw#O>#uE>gPceXhq6$9-Ff)q?pH* zpan`nAMGYuPbD-Y*qY{qh6G#Fme7zunz&2i+Lm68lcDt>o=&Yv0v73^cuEMFkwm&P zPC$Zj&Fs!N0SU%6lNh&>EAN$+^kXEovNU{(a~4rT@hN^5mL+4ZW_-ik&;NUF_{~Xy z$c>ui>A1*C?hobbmW;y1$l)dZi*bpcuJ@uX6085%gP-~K!>{8P?mW_n%lzySlg6y0 zjkwA$9i5L~`NU(>a4oX!*gjVxmS>sIKz+oj=gi`T*sEZ|nbWk{1ZbLPsKyf+r!v8WNAN9a>rpYeNzCd8Z{`K+`>LtSd!JLmLUb93EyeH*)&r_eA!kwhp@A|?HzrXJdKq_C z43UQU%oAW9GV)0y-$Nr$+$cpFss4cK7t>47e`Kk&D-GtW@;a6WwJadXkem`ED<*Le z^)>q>`gMp$LhVA6Vc5+MvgPHle5M3=N`>K5OrnnL>}s{`WDA5WXR?#0@;((q$sw^t zY8vCmDe^gxhQ@~uo%9clQI;Bta|6VuUJMd9(lO^rGNah^pWvmwY*A;x>9ww-?*XBy zL3_@M&pw)?Gy38ha9NaPU@}d$8R)>3=!$-L7OWkjqX6Y1EfdS)Eko?i1oaGYD3c6g zqL&NtUuQ0De&9l}IO0M%E=Nf=neVpseJiFB)bGG;YR*^eZ$!6$Yf6Ludvrk->S6mI zNz*7drg&nY5~B+tsFUa*W(~=MwHCsS>F5+22ct$z9SjqlQIZb=cg54$n20~4YqD_( zDYxXU>6W*|;-QF+yAxkpb7@fjORh%;^#(ah>MVhBt+z`JCD9h>pU}fhEHF@s)#89b zUvGP~(ZF!vo#-tudO^IM3#U+Xu>$MFs$A6KUGbO?J5Ve3`tSwTi-p7R88(RAJUot# z(V2PZV%QpeAs?Fnru`M|(KLZ$>c{%{A^I-S4y_TUWX>@n&FjrR`na~8pO#gr1C@68Mwwft1B_e=dJSLI&Tht zjyfKxaq*ZMH{RiOkGDkiM+iGs2hGOPG+-zHqD+I>_e~klV4i=X1(coI@@!h0$|ta! zH&*1~Ha}VM0{%4JPcBeB2c}6yh?GL46#YKK&Vt$xVxah?GXC<5>*H;+IzS0hh!a2c z(uIHbQq!^ePy&A7Z{Nx0-D@8vbncGJ)YeV5axi2rZQAd~B|dcQHC*P;AMeC4 z_ny#cedRG4*Uv`HvYToRwm@~Uk)tgQmulLdh zKi<29B31S+IOW%x9WrXMG5XyRWsOIs%*eX* zDC<&4b}_O10ca_EN1#pVh9>)__S$(QoQ#mTTZGhFI@@knfciZ&$xf_HOTJ8HBWV=y zUZe(PmCm^AGcLo&=q^cDbC?{`WQ`g2-BfW@n;xdSOXDSHGyro4nVa0awwrgx%{}cF z{+pj~)=%~YWgq!ru@oXvY`YiFzDy6;dSF@?TjTLeZ5Jb3l%s?AxH9NfHXvdSwU9#m z&*WD8lihzt&LegvATbdFWL>o{se@1#AYJ4pp^C;2{i|)(EGr2kz_8et zj8YOK$r!_rR(}ef1{(p745bRA2!XrIL$x44@IDsYkRqEn;dRTrkFMTTf~p@gE; zq+_ATO2;w`TZDBs_qXVvlW6LwH^jktNW<^e>3P_T*_azLLGwrK%0!ua6gi3py&Z;1 z&_SFunh;aExRr_J7#G*Fuu8OMLC3g7_%Lu&G-(LqmUUUf5MswbE?z?&6X!gzmfo0F zqcUKqcWk}bP+M$CEi(nq5TF)=-ZcYghN^cVPR~cVdlOAsy`Ekbb!s4R);_zRz=-Ad zA`f`idUgTk0Pl&aTv}GZOpV@Zn8-AUb?#!xphQ z9}nR}>#Ka+X4qjJDa1C0T_RkBKIb^~GkMZXY!rn7v?P)t+6VU-Ypl{m=yYI@l@vsI F%3oac)_4E_ From b4f22e84156e3caa4aba9482761807264279bc5e Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 4 Sep 2026 15:50:15 +0000 Subject: [PATCH 3/3] Address PR review feedback Change-Id: Ia8da58c514a2bdf960b4ede5a00ef9f519286105 --- contracts/sysio.reserv/src/sysio.reserv.cpp | 36 +++++++++++++++---- contracts/sysio.reserv/sysio.reserv.wasm | Bin 85085 -> 85195 bytes contracts/tests/sysio.reserv_tests.cpp | 37 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/contracts/sysio.reserv/src/sysio.reserv.cpp b/contracts/sysio.reserv/src/sysio.reserv.cpp index 9e58579efe..998ed19705 100644 --- a/contracts/sysio.reserv/src/sysio.reserv.cpp +++ b/contracts/sysio.reserv/src/sysio.reserv.cpp @@ -130,6 +130,18 @@ registered_chain_kind_or_skip(sysio::slug_name chain_code, const char* handler) return std::nullopt; } +/// Validate the raw creator address against the authoritative registry kind. +/// User-created reserves originate on supported external outposts only: EVM +/// addresses are 20 bytes and SVM addresses are 32 bytes. Unknown/depot kinds +/// are not valid creator-address domains for this handler. +bool creator_address_matches_kind(opp::types::ChainKind kind, + const std::vector& address) { + using opp::types::ChainKind; + if (kind == ChainKind::CHAIN_KIND_EVM) return address.size() == 20; + if (kind == ChainKind::CHAIN_KIND_SVM) return address.size() == 32; + return false; +} + /// Reconstruct a `sysio::public_key` variant from the raw creator-key /// bytes carried on a ReserveCreate attestation. EVM chains require the /// 33-byte compressed secp256k1 key in `creator_pub_key` (the EM variant, @@ -446,6 +458,8 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, // must not select a different public-key variant or produce a reserve that // `matchreserve` can never match against the registered chain kind. const bool creator_chain_kind_mismatch = creator_chain_kind != *expected_chain_kind; + const bool invalid_creator_address = + !creator_address_matches_kind(*expected_chain_kind, creator_chain_addr); // Same `sysio`-billed metadata bound the privileged registrations enforce with // `check_metadata`, asked the non-throwing way: this handler must never abort, so an // over-bound string joins the reject/refund path below rather than reverting dispatch. @@ -482,8 +496,8 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, // Create gating: the creator must already be authex-linked to a WIRE // account ("the only requirement to create a reserve"). Reconstruct the // creator's key variant and probe `sysio.authex::links.bypubkey`. On - // any failure — a chain-kind mismatch, malformed key bytes, no link, an - // invalid amount, OR over-bound metadata — reject by inserting a + // any failure — a chain-kind mismatch, malformed address/key bytes, no + // link, an invalid amount, OR over-bound metadata — reject by inserting a // CANCELLED row (for refund idempotency) and queueing // RESERVE_CREATE_CANCELLED so the outpost refunds the creator's escrow. // The CANCELLED row does NOT permanently burn the identity: a later, @@ -501,7 +515,8 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, canonical_creator_key = sysio::pubkey_to_bytes(*pk_variant); } } - if (creator_chain_kind_mismatch || !linked || invalid_amount || oversized_metadata) { + if (creator_chain_kind_mismatch || invalid_creator_address || !linked || + invalid_amount || oversized_metadata) { // A CANCELLED row already standing means this is a re-relay of the same // rejected create (an unlinked squatter OR an invalid amount). Leave it // and do NOT queue a second refund — the refund was queued when the row @@ -513,8 +528,12 @@ void reserve::oncrtreserve(sysio::slug_name chain_code, return; } sysio::print("oncrtreserve: rejecting with RESERVE_CREATE_CANCELLED " - "(creator chain-kind mismatch, invalid amount, over-bound metadata, " - "or unlinked / malformed creator key)\n"); + "(creator chain-kind/address mismatch, invalid amount, over-bound " + "metadata, or unlinked / malformed creator key)\n"); + // Do not persist attacker-sized address bytes in the sysio-billed + // cancellation tombstone. The outpost refunds by the reserve triple, + // so a malformed creator address is unnecessary for that handshake. + if (invalid_creator_address) creator.address.clear(); const auto now = current_time_ms(); tbl.emplace(ram_payer, pk, reserve_row{ .chain_code = chain_code, @@ -663,7 +682,9 @@ void reserve::oncnclrsv(sysio::slug_name chain_code, opp::types::ChainKind creator_chain_kind, std::vector creator_chain_addr) { require_auth(MSGCH_ACCOUNT); - if (!registered_chain_kind_or_skip(chain_code, "oncnclrsv").has_value()) return; + const auto expected_chain_kind = + registered_chain_kind_or_skip(chain_code, "oncnclrsv"); + if (!expected_chain_kind.has_value()) return; reserves_t tbl(get_self()); auto pk = make_key(chain_code, token_code, reserve_code); @@ -679,7 +700,8 @@ void reserve::oncnclrsv(sysio::slug_name chain_code, } const bool addr_matches = - it->creator_addr.kind == creator_chain_kind && + creator_chain_kind == *expected_chain_kind && + it->creator_addr.kind == *expected_chain_kind && it->creator_addr.address == creator_chain_addr; if (!addr_matches) { sysio::print("oncnclrsv: creator_addr mismatch; silently skipping\n"); diff --git a/contracts/sysio.reserv/sysio.reserv.wasm b/contracts/sysio.reserv/sysio.reserv.wasm index 902e3815c60ef703369812d0579717d3c3d7073a..cb0b1f0cf5c44cf8259e6bf87d74c9a27e35c166 100755 GIT binary patch delta 8550 zcmcIJ33L?IvR&OXnUG1CCWOgk0eZ#+vJwbn5&c4k4?z)0(65LnL~&tIc2Ph{fPfKM zy?x%Xjw4jYmO9(RX#*F{ z?5?qJ+%wxXP=Y_%_2?*A#UfhmfUC?I-!`xW!WBSEOm6v_$JIJUf;!eCZa?fIDhlo$ zKg#weC}wq*=#CPs*X*K|?T_!#K@D-*RKY2Wo#|g`93x&cyPBxu-Hk;MM60SeZC1q^ zBbw+T3|4I2oHA>f5M~cn%!z{FY^s=$=nPRzG-}44P3WF$QyQz)W($cF4p7#L#N5PHWs4$$w+SGlV z-8;GBUgCBy5&g20O)-Cl+$=h7F;vjb&UW*NP4F+=eUiyFJu3}e0M;IR8>u?6ox*grQ^1UE8 zCWo3SA!;7<7_Kx`g4H~+$M6wO2NjgRGgz^zxJ>+cjXCYS%A7WKw2cFru;1GxHA9pV zL^%!`tSa4H)(9m?v9X7flA1Kl5Yi-pssdN1zuOhU#w2wJZW=2YXiOBMl@PWuDFK2! z=ab9;^VpTP$=N}AkZ~T)Mm9Nz3jbYakm8r6Sh7YaO_YF4MOI1`Z6CVUz+t|U8>_m*p(!n-pj43+Cx@^nQ(l8m zk0)gszy;PhZHt(9l9|(;^omJu4W%qAy#gv&lZP{cj(|d4JCHhjxo5z`^Zj6%=Vqr= z3198HsL<#ND3YpRqhu}mj}b3{wy6c!+3Nn`aEa~cp9Pm$#DEyM!iu|o36of5j)Pfp zA|anm=oX13rsmAx*bYj*7><$hqw*E>2!Z;!8Pkm$4Mm4d$jpVmG3dS;H1<_@1?DgL zxjVq8p1qH+0NmVid$h!7VUHWIkj;OxHQZsFp6m;ES!B<}@HN}mb8VvD8cB&dOK*;V zi9o*&{6IaE2#G?4$Wa?u(Nm6)TYp9P@u`;Z9XtP2II6Jp`VzijYkIXqS894C!(kSh zbq@C_Df=0?$@*kxAbvr1SJ>e>kv#w~WEoGt*$DQq)%{lBruM>%lK?lZ<1dx}|HjSH zZl)iYavu`ZJo8|eUzCuMx3*x*(u!}ZGG)MM)S280sF!~f)tklnOkfb>=L7>1h-=`2x#XrOo|)FvrKN414ux?=w&({+XSD0t-^~} z#8_#&IZc{nGUhtGJPx;@w-pFqfmi&_6QtvL58gi4&T#5@Rh!OL7j0!|Km|6oO@pvzLlG)kr*_pvxYQN{fR|X*PAFp`XmVnxyD&WR7)FBPGuLYq6NTz}O07Zhvto4eES|VG`~Wt1+6^ke7yH(o zg3n9tczi|<4o5=T;K4*+@+PMz3?cf_Bb-23TztfWK z8`d77J$D=)WwYX$GI3FBJA z6;JWFD!?8*_WjAQ@#FDpQP=A49U)ae8SN8Kx!xYZPK=MlrhjL=1D3Hzw=MTnpL#A0z*B`eA=VJ-75;l9Qkpq4kH_^6`Zm@l)=`7gmItT2CVL#A1lhD;mT zTa#O(DRU>s;j>~gIe%pGLDc5)6a)aAVc&h&7b6iiwLRpro>N;O*PBz*kn8iQyCH7I z&xZ8@i9)Tv-u%5CE&y9bBgNd8V}E7~r;(nm(@4+7X{0A$`hL{2qa1vWgBsO46A7U2Q%-r#xr<6E>-44>0o!efNzTWv2TX2$%{ zFoBsr?cIDL&>2HgDIHLH$)cf9!y*fvaEd)v*bi2)1%=5li|sD_5XxDfB1&vfu_N)+KEowTqL634 z*CmTuWu~^~jSgeIi=V|PFE8$hQLZVDrsyml0fSwdkeUeY>@Ct9(@N>yg3O=%$2 zb7Bb(WZ4%G&JK`P#iABqWGrgAg`PzgiqD?E@tCmi6(w+#ZCjDvw1zWdcQDIMxq(v- zvfz>eJi;s}AvdyCI^btEXeC)NdnF}q{YpyQ!IcN7KvtDvf&9+C`HBi8WDS|#YYmzH z_L^7ViJ4X065Jx+bBd_L|G_Z$$4b@M6!pyR(fR$k<^J4qf5URu+W(e^>)tZ<)DA8J_kU%rZU)N!cEhp05=`PzmZ zaF(_EE(gvr_T5u(p8fjWbXdTKZQO?Er}T<_@EHr*^qk#iRTb_*erQ)M?o1!Dv75T1 zNn1Ac2K;umZ;1(>cE~6dF!F$B$Su(r&e2T$9xS3`M6o<)M#Mph9`HKtS&t-%;8^Pcfm92YmB<6-ZfF&-ip zv*)WLq0lqBx)H!NmUrTDSmN1n!X@IMZEZ9Qu5lm~U!%eo?5&z4gg>iU9_F*NlElcHjnCtFDeHB* zxxA#othVIDd#9t&?&8xk$ceVKkK@P7*jfqDl<{ZmkNV8T3ZP>uKGO|TC<$$SSd^v5 zQ)j|XR>&&PoJ28g&)d8D#B|3AHC#-6#Pu;nd?_~$kC&Z!j;ZVnZdAI&V>_piT`2IG ze?uUp%*S?*Bp;T{*R_-R?5k_dP<-k6UP14qNs1yNRdLHJSm1@}P{KaF5DlAI#f7;M zWAUht#8^qr!1f$LS1l4XEeUyt{k9QSv$hwLVI0f363Xse_hWe%BQUmkC!*P=ixcZZ zO4`wDe0qaNA&v22DTaEsI1`#e#x-L@Z`#?HmzuHxR~+oOTYl`wrAcUZ|I7AVpJRPc z2-XBXv5^~6^s%q-Nf%{9NBgotXpFvrejtWAx3>fgF0c9Q+T|u_N#&VlEaFO67>@xc z!ZFrS51(ZZ){#+GULnTN>paj6k)5zv*5mEqH;ga-u%A`qyVqF)mohGwQ33mib{Rz?a0O-Kd6Nk)ven` zG})=^E!nO=SL4Ua`*)tk=bk%loW+1w2mWq(HN9oy?ZAFVV?0`HWXfGTF3PmKDR}es z*4+$P%u4U32I7H~4pA{;pu9<22M__9=Qf5sSTuJgEQKvv3ZAwbGHunF3blg(TjB58 zLJ`vazQ@)sZ_^HokOn`L1xw%t*k1O&2}FP$TAmr&z|OMOW>^jIv)11N*Kic^M}^}Hf#TA1cTtPmK^{Z z9M#SQz!@mg_FCZ?IHtt}!dd_0pkT9-t2Jm0bF@`KkQs<5vz(x#%Mjg4gcD^kGF*o) zg@##6=^aQrpdQU4!@MGJ(N#@jiYt#>qR?0sGCK+}C{D!Rh2+IrPB4UGW5S7if#I9h z->=}rFk@!$*a}v>Jf$yrE*91BHztd}v3!fb+D_--2B8039>$5kk(1*wt^AEmq8O}Q z^`r4Daj?r=_D3+_tzoUUBm^??buI)hN1cIi`mQ1qtYNx%ufJaL#b4VY5`yo~&IMqV zC+(Ie__=ViHarQUwPB&~j%ZwmYIj2c&p29`1A4(_ZIA=>8_{sB!~tDUe@+;*(OQH- z2Go`zj9>1XwMpUdI-J(dg+ocG{*x8{;^vlr=Yho|zFWSot%`t9_(S_X0-|unxd`Zw zCD0=hy5Z>KNa)V@r|3_b!z(^h#x)1Ln$-~3J@PW(1sBF24-*KQ@upS5n+<)e-E0oo za6|j81q{$iT7U{S$`CFExTzH?&|FX9UxdCsNhqqVTfTjNs(CPJf8oi&UsQB}HX>eE z`w&<{ld2Km%(Y&CjxiilC)f>0KMC=&m@gs^DNLw90a&gTelUCa2X2_^FbmNwG~ zZ}T%wv=--rMUAeKQY^2l+8!4a=H8!WKE~R*Zm{t;I*9jMyp}ngYMBG%;059&2RT3v zP9R=#u+r-wXC?{bwm*tAwyp;NSet)N3aay=nG&`z|1VJ)a% zi1_2MOH`eBaco85lA7q23q-{v7imLVLvs`}y*0cY@S(`BKLmFalWW2;f$=rb+LJM; z$ER*_3`(A=ZHwXRZWC>u);bnmf{(N*v5*YYv~Oa$#DlRA6L1*9@lFMQ2{^Acii2pB zlN5)Guu990gDwhE@hh61fOk_){Ao#W0-lM`LIvZxSX&zhDHelEyA%hnpfa@$G}8vf z!?Rv^e>_wJ)4oo?Hf@ltiP)75xI7UGba+h&nZtd=3nKi8=RbdFmLy2_^#RPyJdql# zKwY>}%Sys>;+ZYA-astI4p|mx32kA84G~5^_aSr2GTXsy36^VzIzT$C)q+znTPwAm zDUb@~T7C*H{jaqHDUbqRX@RLoTd!rNLVE5hz~8u0m%PRceCq}N*9(+-fn{Fcb1&fW z0*k!BH(sF33w-GX)^VWz13x=R(C(x{Q}bt{)q0@Jo(9jD{F8D94jl37$dQ`+5sdYb zvQ3Y`%aSaplPU%4B>xekU+w?O8##l@+INCmgq~6*So8grK_iv1uZ|d6_E;CVP3WoB zf_1Y0P-VocIRlkpWlv<{?pe01J7}UqP-oT(*6HG)p+iOrBgc#!G_(!sdv!$Fb3JhT KHg!%Bgnt5H3p|Mc delta 8366 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, '\x01')) + ("is_private", false) + ("creator_pub_key", creator_key))); + + auto r = find_reserve("ETH", "ETH", "BADADDR"); + BOOST_REQUIRE(!r.is_null()); + BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); + BOOST_REQUIRE(r["creator_addr"]["address"].as_string().empty()); } FC_LOG_AND_RETHROW() } // A re-relay of the same unlinked create must be idempotent — it must NOT