From f5a5942fcebe64d4a97e2525eca302bd9941e43c Mon Sep 17 00:00:00 2001 From: keruch <53012408+keruch@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:25:00 +0400 Subject: [PATCH] added x/bridge module (#7684) * made x/tokenfactory mint and burn methods public * added x/bridge module * added x/bridge into the app * deleted wiring in app * review * reverter osmoutils * inbound and outbound transfers handlers * code review * reverted tokenfactory * code review round 2 * Generated protofile changes --------- Co-authored-by: github-actions --- proto/osmosis/bridge/v1beta1/events.proto | 7 +- proto/osmosis/bridge/v1beta1/tx.proto | 11 +- x/bridge/README.md | 3 + x/bridge/client/cli/query.go | 20 +++ x/bridge/client/cli/tx.go | 55 ++++++++ x/bridge/images/MintBurn.png | Bin 0 -> 75436 bytes x/bridge/images/MintBurn.puml | 45 ++++++ x/bridge/keeper/assets.go | 63 +++++++++ x/bridge/keeper/genesis.go | 29 ++++ x/bridge/keeper/helpers.go | 27 ++++ x/bridge/keeper/keeper.go | 53 +++++++ x/bridge/keeper/msg_server.go | 132 +++++++++++++++++ x/bridge/keeper/params.go | 69 +++++++++ x/bridge/keeper/query_server.go | 29 ++++ x/bridge/keeper/transfers.go | 63 +++++++++ x/bridge/module.go | 165 ++++++++++++++++++++++ x/bridge/types/assets.go | 88 ++++++++++++ x/bridge/types/codec.go | 47 ++++++ x/bridge/types/constants.go | 7 + x/bridge/types/errors.go | 19 +++ x/bridge/types/events.pb.go | 160 +++++++++++---------- x/bridge/types/expected_keepers.go | 20 +++ x/bridge/types/genesis.go | 18 +++ x/bridge/types/keys.go | 15 ++ x/bridge/types/msgs.go | 150 ++++++++++++++++++++ x/bridge/types/params.go | 114 +++++++++++++++ x/bridge/types/tx.pb.go | 137 +++++++++++------- 27 files changed, 1415 insertions(+), 131 deletions(-) create mode 100644 x/bridge/README.md create mode 100644 x/bridge/client/cli/query.go create mode 100644 x/bridge/client/cli/tx.go create mode 100644 x/bridge/images/MintBurn.png create mode 100644 x/bridge/images/MintBurn.puml create mode 100644 x/bridge/keeper/assets.go create mode 100644 x/bridge/keeper/genesis.go create mode 100644 x/bridge/keeper/helpers.go create mode 100644 x/bridge/keeper/keeper.go create mode 100644 x/bridge/keeper/msg_server.go create mode 100644 x/bridge/keeper/params.go create mode 100644 x/bridge/keeper/query_server.go create mode 100644 x/bridge/keeper/transfers.go create mode 100644 x/bridge/module.go create mode 100644 x/bridge/types/assets.go create mode 100644 x/bridge/types/codec.go create mode 100644 x/bridge/types/constants.go create mode 100644 x/bridge/types/errors.go create mode 100644 x/bridge/types/expected_keepers.go create mode 100644 x/bridge/types/genesis.go create mode 100644 x/bridge/types/keys.go create mode 100644 x/bridge/types/msgs.go create mode 100644 x/bridge/types/params.go diff --git a/proto/osmosis/bridge/v1beta1/events.proto b/proto/osmosis/bridge/v1beta1/events.proto index 1ff2eeedf85..376a19a24fd 100644 --- a/proto/osmosis/bridge/v1beta1/events.proto +++ b/proto/osmosis/bridge/v1beta1/events.proto @@ -45,9 +45,8 @@ message EventUpdateParams { } message EventChangeAssetStatus { - // Sender is a sender's address string sender = 1; - // NewAssetStatus is a pair of the asset and its new status - AssetWithStatus old_asset_status = 2 [ (gogoproto.nullable) = false ]; - AssetWithStatus new_asset_status = 3 [ (gogoproto.nullable) = false ]; + Asset asset = 2 [ (gogoproto.nullable) = false ]; + AssetStatus old_asset_status = 3; + AssetStatus new_asset_status = 4; } \ No newline at end of file diff --git a/proto/osmosis/bridge/v1beta1/tx.proto b/proto/osmosis/bridge/v1beta1/tx.proto index 8d82e393905..404f08066a5 100644 --- a/proto/osmosis/bridge/v1beta1/tx.proto +++ b/proto/osmosis/bridge/v1beta1/tx.proto @@ -100,12 +100,13 @@ message MsgChangeAssetStatus { // Sender is a sender's address string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - // NewAssetStatus is a pair of the asset and its new status. + // Asset is an asset to update. // The asset should be known; otherwise, the method will failed. - AssetWithStatus new_asset_status = 2 [ - (gogoproto.moretags) = "yaml:\"new_asset_status\"", - (gogoproto.nullable) = false - ]; + Asset asset = 2 + [ (gogoproto.moretags) = "yaml:\"asset\"", (gogoproto.nullable) = false ]; + // NewAssetStatus is a new asset's status. + AssetStatus new_asset_status = 3 + [ (gogoproto.moretags) = "yaml:\"new_asset_status\"" ]; } message MsgChangeAssetStatusResponse {} \ No newline at end of file diff --git a/x/bridge/README.md b/x/bridge/README.md new file mode 100644 index 00000000000..06472432bc2 --- /dev/null +++ b/x/bridge/README.md @@ -0,0 +1,3 @@ +# Bridge + +The bridge module allows... \ No newline at end of file diff --git a/x/bridge/client/cli/query.go b/x/bridge/client/cli/query.go new file mode 100644 index 00000000000..57036b5330c --- /dev/null +++ b/x/bridge/client/cli/query.go @@ -0,0 +1,20 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/osmosis-labs/osmosis/osmoutils/osmocli" + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + cmd := osmocli.QueryIndexCmd(types.ModuleName) + + cmd.AddCommand(osmocli.GetParams[*types.QueryParamsRequest]( + types.ModuleName, + types.NewQueryClient, + )) + + return cmd +} diff --git a/x/bridge/client/cli/tx.go b/x/bridge/client/cli/tx.go new file mode 100644 index 00000000000..2c3a2cd3abf --- /dev/null +++ b/x/bridge/client/cli/tx.go @@ -0,0 +1,55 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/osmosis-labs/osmosis/osmoutils/osmocli" + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := osmocli.TxIndexCmd(types.ModuleName) + cmd.AddCommand( + NewInboundTransferCmd(), + NewOutboundTransferCmd(), + NewUpdateParamsCmd(), + NewChangeAssetStatusCmd(), + ) + + return cmd +} + +func NewInboundTransferCmd() *cobra.Command { + return osmocli.BuildTxCli[*types.MsgInboundTransfer](&osmocli.TxCliDesc{ + Use: "inbound-transfer", + Short: "Make an inbound transfer from the external chain to osmosis.", + }) +} + +func NewOutboundTransferCmd() *cobra.Command { + return osmocli.BuildTxCli[*types.MsgOutboundTransfer](&osmocli.TxCliDesc{ + Use: "outbound-transfer", + Short: "Make an outbound transfer from osmosis to the external chain.", + }) +} + +func NewUpdateParamsCmd() *cobra.Command { + return osmocli.BuildTxCli[*types.MsgUpdateParams](&osmocli.TxCliDesc{ + Use: "update-params", + Short: "Update the x/bridge module params.", + }) +} + +func NewChangeAssetStatusCmd() *cobra.Command { + return osmocli.BuildTxCli[*types.MsgChangeAssetStatus](&osmocli.TxCliDesc{ + Use: "change-asset-status", + Short: "Change the asset status to the one specified in the call.", + Long: `Change the asset status to the one specified in the call. +Available statuses: +ASSET_STATUS_OK +ASSET_STATUS_BLOCKED_INBOUND +ASSET_STATUS_BLOCKED_OUTBOUND +ASSET_STATUS_BLOCKED_BOTH`, + }) +} diff --git a/x/bridge/images/MintBurn.png b/x/bridge/images/MintBurn.png new file mode 100644 index 0000000000000000000000000000000000000000..32d661cb3518c21b9d3049b533173bda40c70bbe GIT binary patch literal 75436 zcmc$Gby!vFyDbJSpp*&%7M+sP3WBicPAR1X1f)Si0YMN^Sb)-vDBU3-AR*lyf^;L@ z_g%QRd;8n_oaedcJomZlA92E(YtHX`W4vRGxn9W2-o?Wq!$Cts!;_S_qlkuf+8+(= zR1nrlIN}mqHv#`J*ovv!-nX=JGB-4`MZ0VG$ndegt>J^q22PhvY;CP<_*hx3%=I7H z+F6*h+_$u_Z)u=HLpzyls;qAN=W(KW-D8EaM7?n!F+D>%wz)IH6h=*W`rQp*lfoiH_f#>pSHi^^`PORs2FNO)y)A3f@C@{j{hH z&#PmGP1v4e8DeZw9AarbBNa1^tJN$+Tg!bIeS(Q?Y8I|gXz+}mQ42E z5BU1Rl)-)%Qn}{JThMqkn6JMVxW$3|kQ~c4);EBa-=OW?q_pX+9{AR{!~*k%s|Gqc z*>^%6>vxnAb9s*9xr!Z^ClEYHQ^i`zkx2gr)9VPW9NV5(S9FG0=mcpO1xmB9_0c5= z182D7CVS@jzpIEnYmqr;#v=17{0{X=&Ej}-p*tf~IF^GsUs>NQdDjbLGMlmnG}>rt zUr!k*uHd$v%M&m$nZ7sLA)+yWY&G0#Ws))J(Mz>9onuvL?Kl)Owe-myPDk6GrhZb3 z!)vz|RkE>?UR}BMFq9lrDJf6#I-$k_tQD`Sk(Hq z&em?$PJDc?q%gPMe%`?n7p_hCxLPRncDUx7a7@;BUAaI#Wao8TnwQ;ItwVpv^_|kS zakTI7Bdg0&k9$&dFpTj{Hp5#rCwcfjLE{d_%E9BCB&6Xg&nF{?4Jh-9f>VF6QWles z5@hyW3-kVt-Yx#s=z3W<)jG3*`w8EpYn?CLW-ZasJkTWX+*WqfSs2FFR#rI@w!$J- z$kx7XegE9;JJ*NA(yba3*_#i;4NBj|h3LNvL7Jr&Cy#~VlBLpxPX>_%Vza!DGYw3B z_EOTXRPNp0eno2})mCwIDVxLFjh5*HzDym5mf>l)>81nDrWyUykPq;h{#NPm^Cu6_ z%W}z3Tt8*9hEV>rhtY+QxtPhQb z<1(eO7ZU+3Mh+gg|lwD=PtGR zcum*UuQt5Lv+vkCde&=r;2cO6Hn@dEg2*W$M zv?`w4eRV~gn;a8Dtz7Z$$6H%GialjACtRw~%u0>spGs|^?ra|4Rl-EJ*ZhF2hkA=1 zR(em{j5BF!F7xE@sD#EAH~IqhskFuLbWE0E0$A5H{`D(yy!Uc9%+$k%{$E^t%qhYF z+L$MsV?Lp2CjRydeKA@E6AJ?yX16QFX0_J_{aMR}EOo;3bX`NukFSD>QEbB~eSj|C z)LAT6AaYLAti)cM!)7?)u0OYB%SitGN~%&=qRqWL`Y5ELaIf}(ie3#lJ;%rQUCA4+ zh_d5b{7QN%%{f;UFE{kv&V7S%#^Dmd)td7*Nj4ON=7Lrl0$J)?gua6iNn{ zHhF5M4CK87${4poG&*2sTc2YuPRF>f{`led(!6$~&~lWXyMQEOsc$tqlzC91x4TO- z&t%O*a#j1KEZ7o56^a1d{EE6%Fj&|gEKojpVW9$PD<{mrMGwM z%L|)1xzY|v`VFZF2rj1bHwH3}(RfoTZ`zbctn$m{QhDY_l05|J@v^2ktqsFRIlHr* z6ZY)Lnp`&K_GVLJ541}d?tZWEF>B?=P_e&pOQhW5&a`f#EZ>DqtFJ*Sc@4Dz{0$+D za)sxVQd;6p%bj;wpUG~Hs6+5PGEyq`V#s|Ek*-@^lBn$0B2S*gy~;T5&pK7UGlezF zE+Xo5b3(K6(U5$Bi@#zoGhNYzv+@zIj1P7#g!fl6!*nTgjGAd?4O%*N++8n}m3DeH zbY|EpA`WtzyLHi%MkG>s#+VvjTZpufTWXo#51 z%6_RSxP>AAo%?4}9xVdxBzz|OQs$IJwfFLI&Ku!tQ%MT;;oOTAP79ToR09=9Z*SY> zrBugOL-Bg*Xm+qmt!OME@s^joFfH|r2SaWmv8(2rJ5&0SST;!)4LPn(uX$Fz_w`Pz!A|V<>C$*2$ z1VRWu|H>c|N4(&L?z!R8E#jlVtER^b#Au;y?rKCY27@290XKsM06(2tvx>qRph z=DcVcm1JrbKz>)=mt;#K#y%-naW2dPst?_UIHK%DjIlMzJNKl-1H`gYj!YTDnVxc} zFp6Y)&8FiG%Fe98s;_n>-s_kTrAHw-&zkY2raO@);>sCmN$#TZ9!J{WJgoquD?>|(-Qyk%lJi`qj`t#wG_5Z zoM?XhD%|gtUCjI9w%7*H1CqwKj6yUJ_cJ>#i`U5A-RqQd@uu_~`df@6coYW<7L#3u z)O4IY1ry24I+F$OnXIx8tu=j^$!#^lK=*Dd>kXFMJX|ak?qiw_A*>kt@JX)Stv_w9 z+pLZ01Kt=v145;e>$sA9#m~}YRNB=^W*1-x@Iurib3Sz4Up@Un-JY=(!#?SX0-xJM zKE0)o?o=fKLk#^#;U~Ei=L2Z-c z{i{ve3)$J}rWq?!IS;-vIHx4Lg|;DvZ}uV|-a0Nd6w1Y@Dy)jVSUim(UrZ&adB-7p zU7N9ttT-o@8rJiOSraKcEq5it!e{O>wP6R8q1g}8Z&$o+b@RMwWWqb7!bv#}PRwhK z7k6lR+07Rywz@34G7Tg1HkOa7a9KZvhLTj-%p2RELD5v(LZrpnm1Yq$@rZ%L9T^hvkbT-9xe3Z0F9s z0o>D)OmJyC$|!)G$6JtC2i^Ain>WoSGm-3*jWzyi2#1HaoNkV5k|TLoC@Xi5w!D{z zQ{*aIg=54D@z;}c+zz<8rR=SUdJOAoNA`=v#l&*?9G0RZT$G1cOK&F)Bv7biXulhg z!NhaUzZ;lhLK@|gnv1N}NxJ<~L}a*RqXyITwaWlr18q_lD=MSqi{HdQ^^W#ptAk zagAS*06is~0V1JC*U3?t3_HY6#_Qz*0%^0Y>;Zt(Hu2;ajlT5aKbtaGD<%N2$RoCBR1dz-G&BNpVG zSO$W&*qVipdUK6gL))7Ai;C#GZ@EvNkT(pqm7ff^^AU=;o_D|QFtL|FFr8C|tC=N> zO|5`OEuF0m5SA)rzeOn$6z6EqP^JuYDV?`Y)3bzjHM5!*+hL{ zmvQRLGKA-asM7VL7l;^p&nn5(_2bU3{3!9qT5H{xy!YDumbEXPY_Yxk={T31k{cC1 zs(P$omdQkjcQSjk`Ijn+6r-}6`BhO6E)EM$uJVUxX2*~b3VOb3@^+cDp(7{VOD_gg zYF|VgHmq*1?j1I?TOGZA&xy+IRYtSDO+iZPBjbr)UAdiY{DU#;{)4w(w3N7XBrX&rA(H~wr(^J6j|3nUESaRAU{D$7|u%`X0ci9Q@GS-9PA~| zsz=ayR2ysTMONmnoTjd@eOtO;Q9|zMmFn0dC3V_#S+QFIEZ4U-Zugkru9w6|cw7-3 zi62X_7%Xx>+Iz{S=IoyZC39|Pf~ZE3l*c($iT*lG`H#N)_xdF-N*}rVCfAG>ojsIS zF$^6$v{8Fl;D$FJq)0!I$Nip0tMHLA+krJYUIQm5IWf+2=9gYZB!IsVH&f+3YIV?&62&zunYv|z0v`-Kid~N%<0}NTuWi;}iz?RW zn0{A!$<}{ia#GAexI<%{GGr@teSIUjx5oH!F7d~|_6d)SQE4Alg2{-*MM4ae$nwtw zdC~A+X9$Q1$A9Z$$Yn{1>V;(GGv1=J3bV9~PYpTKK-iml+4bKzrOq0HU6S(yZJ8c^(_LZ z&lO#|IES+-Wq`#kEq5P#FJP!Vl$(4mZW`Lq$H?!SuDNlFBKjs_E{*Fzjm#6psdd~x zH{@_5^I&0cpY_uU8IRPifKAx_Z=b_V#Ws6;I~b|g-+z+JKB{0L4s;Afs8}_Z6w>R- zAx@>;zHjI^+Nk{eu4k3Rdqs0Z0z{ctGUxS%S1}j?m`!ClwSh5+#!Q{EHzPBpb}r8b zb5Pyz=Z>UAT-AJDdo|+-x}lSk)8ur_d56adu4(jf7p3JKp;j)^36=8;5)+0e2QYSf zG@!vcU3Qo~?uEP97Ol~vlxzoP7^U_UMi$>|V`g6@?CRmxhl)H}QYh)S=txX;RNC7- zC=8BvnAQ0r^JMK#=2_>*HM+Ooj9hJ#DzW~KWoc=-B(>O4U^T{*$Fk=@(C;(5!1~Da zLVOUR+qVAlhd8WadXOLiQ_i_T_M&N%ogYYB^kSt?`rgd|WA?FhF{E z!Q_I}5o7{p@!GN!vsFV`=$|u;(9*L@rLN zd5^f@Q!soZ{iZ1)kuU&>Wg(xmsr<+UY4kID_6U)-`m>!aTw{q%3&wx!ldaD=iUTWU z<+Z~D%yy+h6NM7TjCH3e7R+>s!fQFXUj!MJm6S2MKlVyUI6iMSa98?bl&Xy0o8}U6 zax;A{>}$zb`0l)DmfVmYP$BTTpd(O^>tgp9|SAS3uZi+6mg51OC zbT~chGvry*7*x~>(Z>0?^;Z9TeA~6XmEm41!bj^S% zBV($ea@W>GNe;Rk8?D2o(#5-i6>`h3-aqU`~}O?5TrG56isX-tNZQI)6dE zRdl`d;otF!CS$M*tI|cv5OI+t1Eq`kU$A8iQ3xfJBCANjE7p+C^!@(V5guIX43QBM^A+ zQFGk`*0LfEhnZYA5woG2BHOgM-T*9L%Kk_QtnIYb9=Zl7SEIslw{TRplPlIQj@s4L zbvDF^t)_oEn*7#zPrg}K35osjTDS0E<&1^id^3V2a<9&mT&>fG^PZffr_sH$NK)}< zGNg979ID;jyXSZm1ZVs2>de~*af3Ux%AtXfN1$9;GNjc(}TMA+p z^9(Sn*4w?JM$jo-&$v!UawgzMWn~=YQGm?`DKjDko9t~L!}y0-g%=0zMVmP8hL}97CF@gn8F6%_jV+djY8EX0d@J11DBi9tW7a>C zPu={&Z@|N8%h*c!p-GX67Xa^CYKm<&$J4vgq!ltKXv+I&Mz&)dYo!S~p5A z#)~>{4Y!+zD*joMb55n9hkPvyOFa{axE80Fo%&L9VH%)TchYfDhW&0MWj*W>{lW7T zM(58-oLPNG1w(qfB5Ix(w<)PjUJ@RnpA2b_wwEC{?nH)qk4hyXFOUuNMF!Z&r2cG) zIbk|Oz?5kS$Ax6KyPPVM?V}|W;djc}299XVEwY(m@muBo#(ZJwk?XmnC!0sgIJm5x z+1N)23W^7^Ephw%xE(6WMj->oteh4yp7(fiuhrYVe`@wehqyll|7J?wsK*bh95ZGM zm`Z(YPv4iZw^FiH7U=KCf@zcpIn*@NCiTJw3Y?!Mm))Vce3!PqH~UDZ-e~a9j@`RH89zy-TxRHe{$yP-M*S z>xyW2O-CV&XL$3QWU!HZq|%XiHY7zOMyO)M0+HdAU4P*+U0ET{5zCvlCaOi1@Q&Kz zWaU!~r(zqJHc&I#EFoDqim;gwO7i_-6=Sx@un2&9eLjqCvGAVFhT2m@9z}bXXdeP9 z$-Uk6&Q{$g)>eS3=6ljxU60DUjE)IXTnzP6mE=zDl34;gi?nsqAXLxP;LeKG$cF3^ z6|pHXn(xhGDV77CcW7F(6w%mPq1SSBzrf(HPcUwJ8}@odI+#Z0zJcbM3oAkER8N`n zfAWFPPV_tqna#7Pdv$7cbvTT<@pTI!iq#3lV=_@S2VU!STVzK6E})SmB|kcXEfoEW z>OLhEZ*rC_aQcWa)CalX?@OrEBP;&xiW2C#)Z9sC4#gXeaRLyJJA62%1A&9lEP zQb2MSYTNe6>5m6(eUE&gU_U4AJ+0Y^fHCJfXNp-ct=v?M30=S*@@hl1YX(=iBHU_a zku~3caY>A3B8-g$hVhbvtQGr0mLmfHLX77cw+n7+EQ%=aH|FTm3m{ z8TH}aKIQDxdCXdFnxD3{W)?OjULTDi_JDv4a1o&5-~$jc~Xb27}va=4^M zxJHk1IP7YJXzZRDEQakGLy`6OsWc6nMB16T#bCMG
(V_X(=ln5}4@s|j|6<6%H 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 BTCVault : Send tx with right memo\nincluding the osmo address +ValidatorsSet --> BTCVault : Scan the tx and validate it +ValidatorsSet --> Bridge : Send observed inbound tx +Bridge --> Tokenfactory : Mint **osmobtc** tokens\nto the osmo address\nfrom the memo +note over Tokenfactory + Admin of the denom + is the x/bridge module +end note +Tokenfactory --> AliceOsmosis : Update Alice balance +Tokenfactory --> Bridge : Response for the mint +alt failure + Bridge --> AliceBTC : TODO Refund +end + +== OSMO to BTC == + +AliceOsmosis --> Bridge : Call **MsgTransfer** through **MsgServer** +Bridge --> Bridge : Check if Alice has sufficient balance +Bridge --> Tokenfactory : Burn **osmobtc** tokens\nfrom the osmo address\ngot in the tx +note over Tokenfactory + Admin of the denom + is the x/bridge module +end note +Tokenfactory --> AliceOsmosis : Update Alice balance +Tokenfactory --> Bridge : Response for the burn +alt success + Bridge --> ValidatorsSet : Send outbound tx + ValidatorsSet --> BTCVault : Release the AliceBTC for Alice + BTCVault --> AliceBTC : Send a tx with AliceBTC +else failure + Bridge --> AliceOsmosis : TODO Refund +end + +@enduml \ No newline at end of file diff --git a/x/bridge/keeper/assets.go b/x/bridge/keeper/assets.go new file mode 100644 index 00000000000..ea69ef203c4 --- /dev/null +++ b/x/bridge/keeper/assets.go @@ -0,0 +1,63 @@ +package keeper + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +type ChangeAssetStatusResult struct { + OldStatus types.AssetStatus + NewStatus types.AssetStatus +} + +// ChangeAssetStatus changes the status of the provided asset to newStatus. +// Returns error if the provided asset is not found in the module params. +func (k Keeper) ChangeAssetStatus( + ctx sdk.Context, + asset types.Asset, + newStatus types.AssetStatus, +) (ChangeAssetStatusResult, error) { + // get current params + params := k.GetParams(ctx) + + // check if the specified asset is known + const notFoundIdx = -1 + var assetIdx = notFoundIdx + for i := range params.Assets { + if params.Assets[i].Asset == asset { + assetIdx = i + break + } + } + if assetIdx == notFoundIdx { + return ChangeAssetStatusResult{}, errorsmod.Wrapf(types.ErrInvalidAsset, "Asset not found") + } + + // update assetIdx asset status + oldStatus := params.Assets[assetIdx].AssetStatus + params.Assets[assetIdx].AssetStatus = newStatus + k.SetParam(ctx, types.KeyAssets, params.Assets) + + return ChangeAssetStatusResult{ + OldStatus: oldStatus, + NewStatus: newStatus, + }, nil +} + +// createAssets creates tokenfactory denoms for all provided assets +func (k Keeper) createAssets(ctx sdk.Context, assets []types.AssetWithStatus) error { + bridgeModuleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName) + + for _, asset := range assets { + _, err := k.tokenFactoryKeeper.CreateDenom(ctx, bridgeModuleAddr.String(), asset.Asset.Name()) + if err != nil { + return fmt.Errorf("can't create a new denom %s: %s", asset.Asset.Name(), err) + } + } + + return nil +} diff --git a/x/bridge/keeper/genesis.go b/x/bridge/keeper/genesis.go new file mode 100644 index 00000000000..f4e063dc45e --- /dev/null +++ b/x/bridge/keeper/genesis.go @@ -0,0 +1,29 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +// InitGenesis initializes the bridge module's state from a provided genesis state. +func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { + // create denoms for all new assets + err := k.createAssets(ctx, genState.Params.Assets) + if err != nil { + panic(fmt.Errorf("can't create assets on x/bridge genesis: %w", err)) + } + + // don't need to specifically create the signers, just save them + + k.SetParams(ctx, genState.Params) +} + +// ExportGenesis returns the bridge module's exported genesis. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return &types.GenesisState{ + Params: k.GetParams(ctx), + } +} diff --git a/x/bridge/keeper/helpers.go b/x/bridge/keeper/helpers.go new file mode 100644 index 00000000000..ae0623aa81d --- /dev/null +++ b/x/bridge/keeper/helpers.go @@ -0,0 +1,27 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "golang.org/x/exp/slices" +) + +// Difference returns the slice of elements that are elements of a but not elements of b. +// TODO: Placed here temporarily. Delete after releasing the new osmoutils version. +func Difference[T comparable](a, b []T) []T { + mb := make(map[T]struct{}, len(a)) + for _, x := range b { + mb[x] = struct{}{} + } + diff := make([]T, 0) + for _, x := range a { + if _, found := mb[x]; !found { + diff = append(diff, x) + } + } + return diff +} + +// validateSenderIsSigner ensures that the sender is a part of the signers set. +func (k Keeper) validateSenderIsSigner(ctx sdk.Context, sender string) bool { + return slices.Contains(k.GetParams(ctx).Signers, sender) +} diff --git a/x/bridge/keeper/keeper.go b/x/bridge/keeper/keeper.go new file mode 100644 index 00000000000..b7aa3f658d4 --- /dev/null +++ b/x/bridge/keeper/keeper.go @@ -0,0 +1,53 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +type Keeper struct { + storeKey storetypes.StoreKey + paramSpace paramtypes.Subspace + + accountKeeper types.AccountKeeper + tokenFactoryKeeper types.TokenFactoryKeeper + + govModuleAddr string +} + +// NewKeeper returns a new instance of the x/bridge keeper. +func NewKeeper( + storeKey storetypes.StoreKey, + paramSpace paramtypes.Subspace, + accountKeeper types.AccountKeeper, + tokenFactoryKeeper types.TokenFactoryKeeper, + govModuleAddr string, +) Keeper { + // ensure bridge module account is set + if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil { + panic("the bridge module account has not been set") + } + + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + storeKey: storeKey, + paramSpace: paramSpace, + accountKeeper: accountKeeper, + tokenFactoryKeeper: tokenFactoryKeeper, + govModuleAddr: govModuleAddr, + } +} + +// Logger returns a logger for the x/bridge module. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} diff --git a/x/bridge/keeper/msg_server.go b/x/bridge/keeper/msg_server.go new file mode 100644 index 00000000000..d5ddc797587 --- /dev/null +++ b/x/bridge/keeper/msg_server.go @@ -0,0 +1,132 @@ +// Package keeper TODO: upgrade the signatures validation process +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +var _ types.MsgServer = msgServer{} + +type msgServer struct { + k Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{k: keeper} +} + +func (m msgServer) InboundTransfer( + goCtx context.Context, + msg *types.MsgInboundTransfer, +) (*types.MsgInboundTransferResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if !m.k.validateSenderIsSigner(ctx, msg.Sender) { + return nil, errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "Sender is not part of the signer set") + } + + err := m.k.InboundTransfer(ctx, msg.DestAddr, msg.Asset, msg.Amount) + if err != nil { + return nil, err + } + err = ctx.EventManager().EmitTypedEvent(&types.EventInboundTransfer{ + Sender: msg.Sender, + DestAddr: msg.DestAddr, + Asset: msg.Asset, + Amount: msg.Amount, + }) + if err != nil { + return nil, err + } + + return new(types.MsgInboundTransferResponse), nil +} + +func (m msgServer) OutboundTransfer( + goCtx context.Context, + msg *types.MsgOutboundTransfer, +) (*types.MsgOutboundTransferResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Don't need to check the signature here since every user could be the sender + + err := m.k.OutboundTransfer(ctx, msg.Sender, msg.Asset, msg.Amount) + if err != nil { + return nil, err + } + err = ctx.EventManager().EmitTypedEvent(&types.EventOutboundTransfer{ + Sender: msg.Sender, + DestAddr: msg.DestAddr, + Asset: msg.Asset, + Amount: msg.Amount, + }) + if err != nil { + return nil, err + } + + // TODO: How to pass the outbound tx to the TSS valset? + + return new(types.MsgOutboundTransferResponse), nil +} + +func (m msgServer) UpdateParams( + goCtx context.Context, + msg *types.MsgUpdateParams, +) (*types.MsgUpdateParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if msg.Sender != m.k.govModuleAddr { + return nil, errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "Only the gov module can update params") + } + + result, err := m.k.UpdateParams(ctx, msg.NewParams) + if err != nil { + return nil, err + } + + err = ctx.EventManager().EmitTypedEvent(&types.EventUpdateParams{ + NewSigners: msg.NewParams.Signers, + CreatedSigners: result.signersToCreate, + DeletedSigners: result.signersToDelete, + NewAssets: msg.NewParams.Assets, + CreatedAssets: result.assetsToCreate, + DeletedAssets: result.assetsToDelete, + }) + if err != nil { + return nil, err + } + + return new(types.MsgUpdateParamsResponse), nil +} + +func (m msgServer) ChangeAssetStatus( + goCtx context.Context, + msg *types.MsgChangeAssetStatus, +) (*types.MsgChangeAssetStatusResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + result, err := m.k.ChangeAssetStatus(ctx, msg.Asset, msg.NewAssetStatus) + if err != nil { + return nil, err + } + + err = ctx.EventManager().EmitTypedEvent(&types.EventChangeAssetStatus{ + Sender: msg.Sender, + Asset: msg.Asset, + OldAssetStatus: result.OldStatus, + NewAssetStatus: result.NewStatus, + }) + if err != nil { + return nil, err + } + + return new(types.MsgChangeAssetStatusResponse), nil +} diff --git a/x/bridge/keeper/params.go b/x/bridge/keeper/params.go new file mode 100644 index 00000000000..2e4316650c9 --- /dev/null +++ b/x/bridge/keeper/params.go @@ -0,0 +1,69 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +type UpdateParamsResult struct { + signersToCreate []string + signersToDelete []string + assetsToCreate []types.AssetWithStatus + assetsToDelete []types.AssetWithStatus +} + +// UpdateParams properly updates params of the module. +func (k Keeper) UpdateParams(ctx sdk.Context, newParams types.Params) (UpdateParamsResult, error) { + var ( + oldParams = k.GetParams(ctx) + + signersToCreate = Difference(newParams.Signers, oldParams.Signers) + signersToDelete = Difference(oldParams.Signers, newParams.Signers) + assetsToCreate = Difference(newParams.Assets, oldParams.Assets) + assetsToDelete = Difference(oldParams.Assets, newParams.Assets) + ) + + // create denoms for all new assets + err := k.createAssets(ctx, assetsToCreate) + if err != nil { + return UpdateParamsResult{}, err + } + + // disable deleted assets + for _, asset := range assetsToDelete { + _, err = k.ChangeAssetStatus(ctx, asset.Asset, types.AssetStatus_ASSET_STATUS_BLOCKED_BOTH) + if err != nil { + return UpdateParamsResult{}, + errorsmod.Wrapf(types.ErrCantChangeAssetStatus, "Can't disable asset %v: %s", asset.Asset, err) + } + } + + // don't need to specifically update the signers, just save them + + k.SetParams(ctx, newParams) + + return UpdateParamsResult{ + signersToCreate: signersToCreate, + signersToDelete: signersToDelete, + assetsToCreate: assetsToCreate, + assetsToDelete: assetsToDelete, + }, nil +} + +// SetParams sets the total set of params. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) +} + +// SetParam sets a specific bridge module's parameter with the provided parameter. +func (k Keeper) SetParam(ctx sdk.Context, key []byte, value interface{}) { + k.paramSpace.Set(ctx, key, value) +} + +// GetParams returns the total set params. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramSpace.GetParamSet(ctx, ¶ms) + return params +} diff --git a/x/bridge/keeper/query_server.go b/x/bridge/keeper/query_server.go new file mode 100644 index 00000000000..8bcaa73382b --- /dev/null +++ b/x/bridge/keeper/query_server.go @@ -0,0 +1,29 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +var _ types.QueryServer = queryServer{} + +type queryServer struct { + k Keeper +} + +// NewQueryServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewQueryServerImpl(keeper Keeper) types.QueryServer { + return &queryServer{k: keeper} +} + +func (q queryServer) Params(goCtx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{ + Params: q.k.GetParams(ctx), + }, nil +} diff --git a/x/bridge/keeper/transfers.go b/x/bridge/keeper/transfers.go new file mode 100644 index 00000000000..247e6479787 --- /dev/null +++ b/x/bridge/keeper/transfers.go @@ -0,0 +1,63 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +func (k Keeper) InboundTransfer( + ctx sdk.Context, + destAddr string, + asset types.Asset, + amount math.Int, +) error { + params := k.GetParams(ctx) + + assetWithStatus, ok := params.GetAsset(asset) + if !ok { + return errorsmod.Wrapf(types.ErrInvalidAsset, "Asset not found %s", asset.Name()) + } + + if !assetWithStatus.AssetStatus.InboundActive() { + return errorsmod.Wrapf(types.ErrInvalidAssetStatus, "Inbound transfers are disabled for this asset") + } + + moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName) + + return k.tokenFactoryKeeper.Mint( + ctx, + moduleAddr.String(), + sdk.NewCoin(asset.Name(), amount), + destAddr, + ) +} + +func (k Keeper) OutboundTransfer( + ctx sdk.Context, + sourceAddr string, + asset types.Asset, + amount math.Int, +) error { + params := k.GetParams(ctx) + + assetWithStatus, ok := params.GetAsset(asset) + if !ok { + return errorsmod.Wrapf(types.ErrInvalidAsset, "Asset not found %s", asset.Name()) + } + + if !assetWithStatus.AssetStatus.OutboundActive() { + return errorsmod.Wrapf(types.ErrInvalidAssetStatus, "Outbound transfers are disabled for this asset") + } + + moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName) + + return k.tokenFactoryKeeper.Burn( + ctx, + moduleAddr.String(), + sdk.NewCoin(asset.Name(), amount), + sourceAddr, + ) +} diff --git a/x/bridge/module.go b/x/bridge/module.go new file mode 100644 index 00000000000..7d3b33a6356 --- /dev/null +++ b/x/bridge/module.go @@ -0,0 +1,165 @@ +/* +Package bridge contains implementation of the bridge module. +*/ +package bridge + +import ( + "context" + "encoding/json" + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/osmosis-labs/osmosis/v23/simulation/simtypes" + "github.com/osmosis-labs/osmosis/v23/x/bridge/client/cli" + "github.com/osmosis-labs/osmosis/v23/x/bridge/keeper" + "github.com/osmosis-labs/osmosis/v23/x/bridge/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface for the capability module. +type AppModuleBasic struct{} + +func NewAppModuleBasic() AppModuleBasic { + return AppModuleBasic{} +} + +// Name returns the x/bridge module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns the x/bridge module's default genesis state. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis performs genesis state validation for the x/bridge module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + _ = types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck +} + +// GetTxCmd returns the x/bridge module's root tx command. +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the x/bridge module's root query command. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface for the capability module. +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper +} + +func NewAppModule(keeper keeper.Keeper) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(), + keeper: keeper, + } +} + +// Name returns the x/bridge module's name. +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// QuerierRoute returns the x/bridge module's query routing key. +func (AppModule) QuerierRoute() string { return types.QuerierRoute } + +// RegisterServices registers a GRPC query service to respond to the +// module-specific GRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) +} + +// RegisterInvariants registers the x/bridge module's invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the x/bridge module's genesis initialization. It +// returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + cdc.MustUnmarshalJSON(gs, &genState) + + am.keeper.InitGenesis(ctx, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the x/bridge module's exported genesis state as raw +// JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion implements ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock executes all ABCI BeginBlock logic respective to the bridge module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock executes all ABCI EndBlock logic respective to the bridge module. It +// returns no validator updates. +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ___________________________________________________________________________ + +// AppModuleSimulationV2 functions + +// GenerateGenesisState creates a randomized GenState of the bridge module. +func (am AppModule) SimulatorGenesisState(simState *module.SimulationState, s *simtypes.SimCtx) { + tfDefaultGen := types.DefaultGenesis() + tfDefaultGenJson := simState.Cdc.MustMarshalJSON(tfDefaultGen) + simState.GenState[types.ModuleName] = tfDefaultGenJson +} + +// WeightedOperations returns the all the lockup module operations with their respective weights. +func (am AppModule) Actions() []simtypes.Action { + return []simtypes.Action{} +} diff --git a/x/bridge/types/assets.go b/x/bridge/types/assets.go new file mode 100644 index 00000000000..b04b00c3735 --- /dev/null +++ b/x/bridge/types/assets.go @@ -0,0 +1,88 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" +) + +func DefaultAssetsWithStatuses() []AssetWithStatus { + return []AssetWithStatus{ + { + Asset: Asset{ + SourceChain: DefaultBitcoinChainName, + Denom: DefaultBitcoinDenomName, + Precision: DefaultBitcoinPrecision, + }, + AssetStatus: AssetStatus_ASSET_STATUS_BLOCKED_BOTH, + }, + } +} + +func (m AssetWithStatus) Validate() error { + err := m.Asset.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + + err = m.AssetStatus.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAssetStatus, err.Error()) + } + + return nil +} + +func (m Asset) Validate() error { + if len(m.SourceChain) == 0 { + return errorsmod.Wrap(ErrInvalidSourceChain, "Source chain is empty") + } + if len(m.Denom) == 0 { + return errorsmod.Wrap(ErrInvalidDenom, "Denom is empty") + } + return nil +} + +func (m Asset) Name() string { + return fmt.Sprintf("%s-%s", m.SourceChain, m.Denom) +} + +func (m AssetStatus) InboundActive() bool { + switch m { + case AssetStatus_ASSET_STATUS_OK, + AssetStatus_ASSET_STATUS_BLOCKED_OUTBOUND: + return true + case AssetStatus_ASSET_STATUS_BLOCKED_INBOUND, + AssetStatus_ASSET_STATUS_BLOCKED_BOTH: + return false + default: + return false + } +} + +func (m AssetStatus) OutboundActive() bool { + switch m { + case AssetStatus_ASSET_STATUS_OK, + AssetStatus_ASSET_STATUS_BLOCKED_INBOUND: + return true + case AssetStatus_ASSET_STATUS_BLOCKED_OUTBOUND, + AssetStatus_ASSET_STATUS_BLOCKED_BOTH: + return false + default: + return false + } +} + +func (m AssetStatus) Validate() error { + switch m { + case AssetStatus_ASSET_STATUS_OK, + AssetStatus_ASSET_STATUS_BLOCKED_INBOUND, + AssetStatus_ASSET_STATUS_BLOCKED_OUTBOUND, + AssetStatus_ASSET_STATUS_BLOCKED_BOTH: + return nil + case AssetStatus_ASSET_STATUS_UNSPECIFIED: + return fmt.Errorf("invalid asset status: %v", m) + default: + return fmt.Errorf("unknown asset status: %v", m) + } +} diff --git a/x/bridge/types/codec.go b/x/bridge/types/codec.go new file mode 100644 index 00000000000..3f7bff6c983 --- /dev/null +++ b/x/bridge/types/codec.go @@ -0,0 +1,47 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" +) + +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + legacy.RegisterAminoMsg(cdc, &MsgInboundTransfer{}, "osmosis/bridge/inbound-transfer") + legacy.RegisterAminoMsg(cdc, &MsgOutboundTransfer{}, "osmosis/bridge/outbound-transfer") + legacy.RegisterAminoMsg(cdc, &MsgUpdateParams{}, "osmosis/bridge/update-params") + legacy.RegisterAminoMsg(cdc, &MsgChangeAssetStatus{}, "osmosis/bridge/change-asset-status") +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgInboundTransfer{}, + &MsgOutboundTransfer{}, + &MsgUpdateParams{}, + &MsgChangeAssetStatus{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewAminoCodec(amino) +) + +func init() { + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) + sdk.RegisterLegacyAminoCodec(amino) + + // Register all Amino interfaces and concrete types on the authz and gov Amino codec so that this can later be + // used to properly serialize MsgInboundTransfer, MsgOutboundTransfer, + // MsgUpdateParams, MsgChangeAssetStatus instances + RegisterLegacyAminoCodec(authzcodec.Amino) + + amino.Seal() +} diff --git a/x/bridge/types/constants.go b/x/bridge/types/constants.go new file mode 100644 index 00000000000..ad7db8424e0 --- /dev/null +++ b/x/bridge/types/constants.go @@ -0,0 +1,7 @@ +package types + +const ( + DefaultBitcoinChainName = "bitcoin" + DefaultBitcoinDenomName = "btc" + DefaultBitcoinPrecision = 10 // TODO: decide +) diff --git a/x/bridge/types/errors.go b/x/bridge/types/errors.go new file mode 100644 index 00000000000..811afa9247a --- /dev/null +++ b/x/bridge/types/errors.go @@ -0,0 +1,19 @@ +package types + +// DONTCOVER + +import ( + errorsmod "cosmossdk.io/errors" +) + +// x/bridge module sentinel errors +var ( + ErrInvalidAsset = errorsmod.Register(ModuleName, 2, "invalid asset") + ErrInvalidAssets = errorsmod.Register(ModuleName, 3, "invalid assets") + ErrInvalidAssetStatus = errorsmod.Register(ModuleName, 4, "invalid asset status") + ErrInvalidParams = errorsmod.Register(ModuleName, 5, "invalid params") + ErrInvalidDenom = errorsmod.Register(ModuleName, 6, "invalid denom") + ErrInvalidSourceChain = errorsmod.Register(ModuleName, 7, "invalid source chain") + ErrInvalidSigners = errorsmod.Register(ModuleName, 8, "invalid signers") + ErrCantChangeAssetStatus = errorsmod.Register(ModuleName, 9, "can't change asset status") +) diff --git a/x/bridge/types/events.pb.go b/x/bridge/types/events.pb.go index 977ad016e66..4a666f82fcc 100644 --- a/x/bridge/types/events.pb.go +++ b/x/bridge/types/events.pb.go @@ -239,11 +239,10 @@ func (m *EventUpdateParams) GetDeletedAssets() []AssetWithStatus { } type EventChangeAssetStatus struct { - // Sender is a sender's address - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` - // NewAssetStatus is a pair of the asset and its new status - OldAssetStatus AssetWithStatus `protobuf:"bytes,2,opt,name=old_asset_status,json=oldAssetStatus,proto3" json:"old_asset_status"` - NewAssetStatus AssetWithStatus `protobuf:"bytes,3,opt,name=new_asset_status,json=newAssetStatus,proto3" json:"new_asset_status"` + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + Asset Asset `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset"` + OldAssetStatus AssetStatus `protobuf:"varint,3,opt,name=old_asset_status,json=oldAssetStatus,proto3,enum=osmosis.bridge.v1beta1.AssetStatus" json:"old_asset_status,omitempty"` + NewAssetStatus AssetStatus `protobuf:"varint,4,opt,name=new_asset_status,json=newAssetStatus,proto3,enum=osmosis.bridge.v1beta1.AssetStatus" json:"new_asset_status,omitempty"` } func (m *EventChangeAssetStatus) Reset() { *m = EventChangeAssetStatus{} } @@ -286,18 +285,25 @@ func (m *EventChangeAssetStatus) GetSender() string { return "" } -func (m *EventChangeAssetStatus) GetOldAssetStatus() AssetWithStatus { +func (m *EventChangeAssetStatus) GetAsset() Asset { + if m != nil { + return m.Asset + } + return Asset{} +} + +func (m *EventChangeAssetStatus) GetOldAssetStatus() AssetStatus { if m != nil { return m.OldAssetStatus } - return AssetWithStatus{} + return AssetStatus_ASSET_STATUS_UNSPECIFIED } -func (m *EventChangeAssetStatus) GetNewAssetStatus() AssetWithStatus { +func (m *EventChangeAssetStatus) GetNewAssetStatus() AssetStatus { if m != nil { return m.NewAssetStatus } - return AssetWithStatus{} + return AssetStatus_ASSET_STATUS_UNSPECIFIED } func init() { @@ -312,38 +318,39 @@ func init() { } var fileDescriptor_61b63bd2a1c2ae24 = []byte{ - // 493 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x94, 0x41, 0x6f, 0xd3, 0x30, - 0x14, 0xc7, 0xeb, 0xa5, 0xab, 0xa8, 0x2b, 0xc6, 0x88, 0xb6, 0x2a, 0x1a, 0x5a, 0x5a, 0x95, 0xc3, - 0x7a, 0x21, 0xd1, 0x3a, 0x71, 0xe0, 0xb8, 0x21, 0x0e, 0x93, 0x26, 0x81, 0xba, 0xa1, 0x4a, 0x5c, - 0x2a, 0xa7, 0x7e, 0xa4, 0x11, 0x8d, 0x5d, 0xf9, 0x39, 0x2d, 0xdc, 0xf8, 0x08, 0x7c, 0x21, 0x38, - 0xef, 0xb8, 0x23, 0xe2, 0x30, 0xa1, 0xf4, 0x8b, 0xa0, 0x38, 0xce, 0xe8, 0x81, 0x21, 0xd1, 0x1b, - 0xb7, 0xf8, 0xef, 0x9f, 0x7f, 0x7e, 0x2f, 0xb6, 0x4c, 0x9f, 0x4a, 0x4c, 0x25, 0x26, 0x18, 0x46, - 0x2a, 0xe1, 0x31, 0x84, 0x8b, 0xe3, 0x08, 0x34, 0x3b, 0x0e, 0x61, 0x01, 0x42, 0x63, 0x30, 0x57, - 0x52, 0x4b, 0xb7, 0x6d, 0xa1, 0xa0, 0x84, 0x02, 0x0b, 0x1d, 0xec, 0xc5, 0x32, 0x96, 0x06, 0x09, - 0x8b, 0xaf, 0x92, 0x3e, 0xb8, 0x4f, 0x69, 0x17, 0x1b, 0xa8, 0xf7, 0x95, 0xd0, 0xbd, 0x57, 0xc5, - 0x1e, 0xe7, 0x22, 0x92, 0x99, 0xe0, 0x57, 0x8a, 0x09, 0x7c, 0x0f, 0xca, 0x6d, 0xd3, 0x06, 0x82, - 0xe0, 0xa0, 0x3c, 0xd2, 0x25, 0xfd, 0xe6, 0xd0, 0x8e, 0xdc, 0x27, 0xb4, 0xc9, 0x01, 0xf5, 0x98, - 0x71, 0xae, 0xbc, 0x2d, 0x33, 0xf5, 0xa0, 0x08, 0x4e, 0x39, 0x57, 0xee, 0x0b, 0xba, 0xcd, 0x10, - 0x41, 0x7b, 0x4e, 0x97, 0xf4, 0x5b, 0x83, 0xc3, 0xe0, 0xcf, 0x05, 0x07, 0xa7, 0x05, 0x74, 0x56, - 0xbf, 0xbe, 0xed, 0xd4, 0x86, 0xe5, 0x0a, 0xf7, 0x39, 0x6d, 0xb0, 0x54, 0x66, 0x42, 0x7b, 0xf5, - 0x42, 0x7a, 0x76, 0x58, 0x4c, 0xfe, 0xb8, 0xed, 0xec, 0x4f, 0x8c, 0x03, 0xf9, 0x87, 0x20, 0x91, - 0x61, 0xca, 0xf4, 0x34, 0x38, 0x17, 0x7a, 0x68, 0xe1, 0xde, 0x37, 0x42, 0xf7, 0x4d, 0xfd, 0xaf, - 0x33, 0xfd, 0x5f, 0x36, 0xf0, 0xd9, 0xa1, 0x8f, 0x4d, 0x03, 0x6f, 0xe7, 0x9c, 0x69, 0x78, 0xc3, - 0x14, 0x4b, 0xd1, 0xed, 0xd0, 0x96, 0x80, 0xe5, 0x18, 0x93, 0x58, 0x80, 0x42, 0x8f, 0x74, 0x9d, - 0x7e, 0x73, 0x48, 0x05, 0x2c, 0x2f, 0xcb, 0xc4, 0x3d, 0xa2, 0x8f, 0x26, 0x0a, 0x98, 0x06, 0x7e, - 0x07, 0x6d, 0x19, 0x68, 0xc7, 0xc6, 0x6b, 0x20, 0x87, 0x19, 0xac, 0x83, 0x4e, 0x09, 0xda, 0xb8, - 0x02, 0x2f, 0x68, 0xe1, 0x1f, 0x9b, 0x66, 0xd0, 0xab, 0x77, 0x9d, 0x7e, 0x6b, 0x70, 0xf4, 0xd7, - 0xfe, 0x47, 0x89, 0x9e, 0x5e, 0x6a, 0xa6, 0x33, 0xb4, 0x7f, 0xa2, 0x29, 0x60, 0x69, 0x66, 0xd0, - 0xbd, 0xa2, 0x55, 0x21, 0x95, 0x71, 0x7b, 0x13, 0xe3, 0x43, 0x2b, 0xf9, 0x6d, 0xad, 0x9a, 0xb1, - 0xd6, 0xc6, 0x46, 0x56, 0x2b, 0x29, 0xad, 0xbd, 0x9c, 0xd0, 0xb6, 0x39, 0x82, 0x97, 0x53, 0x26, - 0x62, 0x30, 0x69, 0xc9, 0xdf, 0x7b, 0x89, 0x46, 0x74, 0x57, 0xce, 0x6c, 0x11, 0x63, 0x34, 0xac, - 0xb9, 0x4b, 0xff, 0x5c, 0xca, 0x8e, 0x9c, 0xf1, 0xf5, 0x0d, 0x47, 0x74, 0xf7, 0xee, 0x14, 0x2a, - 0xb1, 0xb3, 0x91, 0xb8, 0x3a, 0x0b, 0x9b, 0x5e, 0x5c, 0xe7, 0x3e, 0xb9, 0xc9, 0x7d, 0xf2, 0x33, - 0xf7, 0xc9, 0x97, 0x95, 0x5f, 0xbb, 0x59, 0xf9, 0xb5, 0xef, 0x2b, 0xbf, 0xf6, 0x6e, 0x10, 0x27, - 0x7a, 0x9a, 0x45, 0xc1, 0x44, 0xa6, 0xa1, 0xdd, 0xe2, 0xd9, 0x8c, 0x45, 0x58, 0x0d, 0xc2, 0xc5, - 0xe0, 0x24, 0xfc, 0x58, 0xbd, 0x22, 0xfa, 0xd3, 0x1c, 0x30, 0x6a, 0x98, 0xd7, 0xe3, 0xe4, 0x57, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x95, 0x46, 0x44, 0x09, 0xb7, 0x04, 0x00, 0x00, + // 507 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x94, 0x31, 0x6f, 0xd3, 0x40, + 0x14, 0x80, 0xe3, 0x24, 0x8d, 0xc8, 0x45, 0x84, 0x62, 0xb5, 0x91, 0x55, 0x54, 0x27, 0x4a, 0x87, + 0x66, 0xc1, 0x56, 0x5d, 0x31, 0x30, 0xb6, 0x88, 0xa1, 0x52, 0x2b, 0x90, 0x5b, 0x84, 0xc4, 0x12, + 0x9d, 0x73, 0x0f, 0xc7, 0x22, 0xbe, 0x8b, 0xee, 0x9d, 0x13, 0xd8, 0x58, 0xd9, 0xf8, 0x43, 0x30, + 0x77, 0xec, 0x88, 0x18, 0x2a, 0x94, 0xfc, 0x11, 0xe4, 0xf3, 0x39, 0x64, 0xa0, 0x95, 0xda, 0xad, + 0x9b, 0xfd, 0xee, 0xbb, 0xef, 0xde, 0xbb, 0xf7, 0x74, 0x64, 0x4f, 0x60, 0x2a, 0x30, 0x41, 0x3f, + 0x92, 0x09, 0x8b, 0xc1, 0x9f, 0x1d, 0x44, 0xa0, 0xe8, 0x81, 0x0f, 0x33, 0xe0, 0x0a, 0xbd, 0xa9, + 0x14, 0x4a, 0xd8, 0x1d, 0x03, 0x79, 0x05, 0xe4, 0x19, 0x68, 0x67, 0x2b, 0x16, 0xb1, 0xd0, 0x88, + 0x9f, 0x7f, 0x15, 0xf4, 0xce, 0x4d, 0x4a, 0xb3, 0x59, 0x43, 0xfd, 0x1f, 0x16, 0xd9, 0x7a, 0x9d, + 0x9f, 0x71, 0xc2, 0x23, 0x91, 0x71, 0x76, 0x21, 0x29, 0xc7, 0x8f, 0x20, 0xed, 0x0e, 0x69, 0x20, + 0x70, 0x06, 0xd2, 0xb1, 0x7a, 0xd6, 0xa0, 0x19, 0x9a, 0x3f, 0xfb, 0x19, 0x69, 0x32, 0x40, 0x35, + 0xa4, 0x8c, 0x49, 0xa7, 0xaa, 0x97, 0x1e, 0xe5, 0x81, 0x23, 0xc6, 0xa4, 0xfd, 0x92, 0x6c, 0x50, + 0x44, 0x50, 0x4e, 0xad, 0x67, 0x0d, 0x5a, 0xc1, 0xae, 0xf7, 0xff, 0x84, 0xbd, 0xa3, 0x1c, 0x3a, + 0xae, 0x5f, 0x5e, 0x77, 0x2b, 0x61, 0xb1, 0xc3, 0x7e, 0x41, 0x1a, 0x34, 0x15, 0x19, 0x57, 0x4e, + 0x3d, 0x97, 0x1e, 0xef, 0xe6, 0x8b, 0xbf, 0xaf, 0xbb, 0xdb, 0x23, 0xed, 0x40, 0xf6, 0xc9, 0x4b, + 0x84, 0x9f, 0x52, 0x35, 0xf6, 0x4e, 0xb8, 0x0a, 0x0d, 0xdc, 0xff, 0x69, 0x91, 0x6d, 0x9d, 0xff, + 0x9b, 0x4c, 0x3d, 0xc8, 0x02, 0xbe, 0xd6, 0xc8, 0x53, 0x5d, 0xc0, 0xbb, 0x29, 0xa3, 0x0a, 0xde, + 0x52, 0x49, 0x53, 0xb4, 0xbb, 0xa4, 0xc5, 0x61, 0x3e, 0xc4, 0x24, 0xe6, 0x20, 0xd1, 0xb1, 0x7a, + 0xb5, 0x41, 0x33, 0x24, 0x1c, 0xe6, 0xe7, 0x45, 0xc4, 0xde, 0x27, 0x4f, 0x46, 0x12, 0xa8, 0x02, + 0xb6, 0x82, 0xaa, 0x1a, 0x6a, 0x9b, 0xf0, 0x1a, 0xc8, 0x60, 0x02, 0xeb, 0x60, 0xad, 0x00, 0x4d, + 0xb8, 0x04, 0x4f, 0x49, 0xee, 0x1f, 0xea, 0x62, 0xd0, 0xa9, 0xf7, 0x6a, 0x83, 0x56, 0xb0, 0x7f, + 0x6b, 0xfd, 0xef, 0x13, 0x35, 0x3e, 0x57, 0x54, 0x65, 0x68, 0x6e, 0xa2, 0xc9, 0x61, 0xae, 0x57, + 0xd0, 0xbe, 0x20, 0x65, 0x22, 0xa5, 0x71, 0xe3, 0x3e, 0xc6, 0xc7, 0x46, 0xf2, 0xcf, 0x5a, 0x16, + 0x63, 0xac, 0x8d, 0x7b, 0x59, 0x8d, 0xa4, 0xb0, 0xf6, 0xbf, 0x55, 0x49, 0x47, 0xb7, 0xe0, 0xd5, + 0x98, 0xf2, 0x18, 0x74, 0xb4, 0xe0, 0x6f, 0x1c, 0xa2, 0xd5, 0x9c, 0x54, 0xef, 0x3c, 0x27, 0x67, + 0x64, 0x53, 0x4c, 0x4c, 0xfe, 0x43, 0xd4, 0xc7, 0xe8, 0x69, 0x6b, 0x07, 0x7b, 0xb7, 0x5a, 0x8a, + 0x8c, 0xc2, 0xb6, 0x98, 0xb0, 0xf5, 0x0c, 0xcf, 0xc8, 0xe6, 0xaa, 0x6d, 0xa5, 0xae, 0x7e, 0x07, + 0x5d, 0xd9, 0x32, 0x73, 0x41, 0xa7, 0x97, 0x0b, 0xd7, 0xba, 0x5a, 0xb8, 0xd6, 0x9f, 0x85, 0x6b, + 0x7d, 0x5f, 0xba, 0x95, 0xab, 0xa5, 0x5b, 0xf9, 0xb5, 0x74, 0x2b, 0x1f, 0x82, 0x38, 0x51, 0xe3, + 0x2c, 0xf2, 0x46, 0x22, 0xf5, 0x8d, 0xf8, 0xf9, 0x84, 0x46, 0x58, 0xfe, 0xf8, 0xb3, 0xe0, 0xd0, + 0xff, 0x5c, 0x3e, 0x36, 0xea, 0xcb, 0x14, 0x30, 0x6a, 0xe8, 0x47, 0xe6, 0xf0, 0x6f, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xed, 0xe6, 0x79, 0xac, 0xde, 0x04, 0x00, 0x00, } func (m *EventInboundTransfer) Marshal() (dAtA []byte, err error) { @@ -572,18 +579,18 @@ func (m *EventChangeAssetStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l - { - size, err := m.NewAssetStatus.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEvents(dAtA, i, uint64(size)) + if m.NewAssetStatus != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.NewAssetStatus)) + i-- + dAtA[i] = 0x20 + } + if m.OldAssetStatus != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.OldAssetStatus)) + i-- + dAtA[i] = 0x18 } - i-- - dAtA[i] = 0x1a { - size, err := m.OldAssetStatus.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Asset.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -710,10 +717,14 @@ func (m *EventChangeAssetStatus) Size() (n int) { if l > 0 { n += 1 + l + sovEvents(uint64(l)) } - l = m.OldAssetStatus.Size() - n += 1 + l + sovEvents(uint64(l)) - l = m.NewAssetStatus.Size() + l = m.Asset.Size() n += 1 + l + sovEvents(uint64(l)) + if m.OldAssetStatus != 0 { + n += 1 + sovEvents(uint64(m.OldAssetStatus)) + } + if m.NewAssetStatus != 0 { + n += 1 + sovEvents(uint64(m.NewAssetStatus)) + } return n } @@ -1396,7 +1407,7 @@ func (m *EventChangeAssetStatus) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field OldAssetStatus", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Asset", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1423,15 +1434,15 @@ func (m *EventChangeAssetStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.OldAssetStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Asset.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NewAssetStatus", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OldAssetStatus", wireType) } - var msglen int + m.OldAssetStatus = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -1441,25 +1452,30 @@ func (m *EventChangeAssetStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + m.OldAssetStatus |= AssetStatus(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NewAssetStatus", wireType) } - if err := m.NewAssetStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.NewAssetStatus = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NewAssetStatus |= AssetStatus(b&0x7F) << shift + if b < 0x80 { + break + } } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/bridge/types/expected_keepers.go b/x/bridge/types/expected_keepers.go new file mode 100644 index 00000000000..6010d968d4a --- /dev/null +++ b/x/bridge/types/expected_keepers.go @@ -0,0 +1,20 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type AccountKeeper interface { + // GetModuleAccount is used to create x/bridge module account + GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI + // GetModuleAddress is used to get the module account + // to use it as the admin for denoms in x/tokenfactory. + GetModuleAddress(name string) sdk.AccAddress +} + +type TokenFactoryKeeper interface { + CreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) + Mint(ctx sdk.Context, sender string, amount sdk.Coin, mintTo string) error + Burn(ctx sdk.Context, sender string, amount sdk.Coin, burnFrom string) error +} diff --git a/x/bridge/types/genesis.go b/x/bridge/types/genesis.go new file mode 100644 index 00000000000..0b7f57e93c6 --- /dev/null +++ b/x/bridge/types/genesis.go @@ -0,0 +1,18 @@ +package types + +// DefaultGenesis returns the default Capability genesis state +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any failure. +func (gs GenesisState) Validate() error { + // TODO: validate each asset's source chain and denom + err := gs.Params.Validate() + if err != nil { + return err + } + return nil +} diff --git a/x/bridge/types/keys.go b/x/bridge/types/keys.go new file mode 100644 index 00000000000..8f641a9222d --- /dev/null +++ b/x/bridge/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "bridge" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // RouterKey is the message route for slashing + RouterKey = ModuleName + + // QuerierRoute defines the module's query routing key + QuerierRoute = ModuleName +) diff --git a/x/bridge/types/msgs.go b/x/bridge/types/msgs.go new file mode 100644 index 00000000000..506c09758f4 --- /dev/null +++ b/x/bridge/types/msgs.go @@ -0,0 +1,150 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var _ sdk.Msg = &MsgInboundTransfer{} + +func NewMsgInboundTransfer( + sender string, + destAddr string, + asset Asset, + amount math.Int, +) *MsgInboundTransfer { + return &MsgInboundTransfer{ + Sender: sender, + DestAddr: destAddr, + Asset: asset, + Amount: amount, + } +} + +func (m MsgInboundTransfer) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + _, err = sdk.AccAddressFromBech32(m.DestAddr) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid destination address (%s)", err) + } + + err = m.Asset.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + + // check if amount > 0 + if !m.Amount.IsPositive() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } + + return nil +} + +func (m MsgInboundTransfer) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgOutboundTransfer{} + +func NewMsgOutboundTransfer( + sender string, + destAddr string, + asset Asset, + amount math.Int, +) *MsgOutboundTransfer { + return &MsgOutboundTransfer{ + Sender: sender, + DestAddr: destAddr, + Asset: asset, + Amount: amount, + } +} + +func (m MsgOutboundTransfer) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + _, err = sdk.AccAddressFromBech32(m.DestAddr) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid destination address (%s)", err) + } + + err = m.Asset.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + + // check if amount > 0 + if !m.Amount.IsPositive() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } + + return nil +} + +func (m MsgOutboundTransfer) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgUpdateParams{} + +func NewMsgUpdateParams( + sender string, + newParams Params, +) *MsgUpdateParams { + return &MsgUpdateParams{ + Sender: sender, + NewParams: newParams, + } +} + +func (m MsgUpdateParams) ValidateBasic() error { + err := m.NewParams.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidParams, err.Error()) + } + return nil +} + +func (m MsgUpdateParams) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgChangeAssetStatus{} + +func NewMsgChangeAssetStatus( + sender string, + asset Asset, + newAssetStatus AssetStatus, +) *MsgChangeAssetStatus { + return &MsgChangeAssetStatus{ + Sender: sender, + Asset: asset, + NewAssetStatus: newAssetStatus, + } +} + +func (m MsgChangeAssetStatus) ValidateBasic() error { + err := m.NewAssetStatus.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + return nil +} + +func (m MsgChangeAssetStatus) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} diff --git a/x/bridge/types/params.go b/x/bridge/types/params.go new file mode 100644 index 00000000000..ff7b5f881d4 --- /dev/null +++ b/x/bridge/types/params.go @@ -0,0 +1,114 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/osmosis-labs/osmosis/osmoutils" +) + +var ( + KeySigners = []byte("Signers") + KeyAssets = []byte("Assets") +) + +func NewParams(signers []string, assets []AssetWithStatus) Params { + return Params{ + Signers: signers, + Assets: assets, + } +} + +// DefaultParams creates default x/bridge params. +func DefaultParams() Params { + return Params{ + Signers: []string{}, + Assets: DefaultAssetsWithStatuses(), + } +} + +// Validate x/bridge params. +func (p Params) Validate() error { + for _, signer := range p.Signers { + _, err := sdk.AccAddressFromBech32(signer) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid signer address (%s)", err) + } + } + if osmoutils.ContainsDuplicate(p.Signers) { + return errorsmod.Wrapf(ErrInvalidSigners, "Signers are duplicated") + } + + if len(p.Assets) == 0 { + return errorsmod.Wrapf(ErrInvalidAssets, "Assets are empty") + } + for _, asset := range p.Assets { + err := asset.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + } + if osmoutils.ContainsDuplicate(p.Assets) { + return errorsmod.Wrapf(ErrInvalidAssets, "Assets are duplicated") + } + + return nil +} + +func (p Params) GetAsset(a Asset) (AssetWithStatus, bool) { + for i := range p.Assets { + if p.Assets[i].Asset.Name() == a.Name() { + return p.Assets[i], true + } + } + return AssetWithStatus{}, false +} + +// ParamKeyTable for the x/bridge module. +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeySigners, &p.Signers, validateSigners), + paramtypes.NewParamSetPair(KeyAssets, &p.Assets, validateAssets), + } +} + +func validateSigners(i interface{}) error { + signers, ok := i.([]string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + for _, signer := range signers { + _, err := sdk.AccAddressFromBech32(signer) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid signer address (%s)", err) + } + } + + return nil +} + +func validateAssets(i interface{}) error { + assets, ok := i.([]AssetWithStatus) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + for _, asset := range assets { + err := asset.Validate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidAsset, err.Error()) + } + } + + return nil +} diff --git a/x/bridge/types/tx.pb.go b/x/bridge/types/tx.pb.go index ec9830618c8..a5f55f8c542 100644 --- a/x/bridge/types/tx.pb.go +++ b/x/bridge/types/tx.pb.go @@ -342,9 +342,11 @@ var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo type MsgChangeAssetStatus struct { // Sender is a sender's address Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - // NewAssetStatus is a pair of the asset and its new status. + // Asset is an asset to update. // The asset should be known; otherwise, the method will failed. - NewAssetStatus AssetWithStatus `protobuf:"bytes,2,opt,name=new_asset_status,json=newAssetStatus,proto3" json:"new_asset_status" yaml:"new_asset_status"` + Asset Asset `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset" yaml:"asset"` + // NewAssetStatus is a new asset's status. + NewAssetStatus AssetStatus `protobuf:"varint,3,opt,name=new_asset_status,json=newAssetStatus,proto3,enum=osmosis.bridge.v1beta1.AssetStatus" json:"new_asset_status,omitempty" yaml:"new_asset_status"` } func (m *MsgChangeAssetStatus) Reset() { *m = MsgChangeAssetStatus{} } @@ -387,11 +389,18 @@ func (m *MsgChangeAssetStatus) GetSender() string { return "" } -func (m *MsgChangeAssetStatus) GetNewAssetStatus() AssetWithStatus { +func (m *MsgChangeAssetStatus) GetAsset() Asset { + if m != nil { + return m.Asset + } + return Asset{} +} + +func (m *MsgChangeAssetStatus) GetNewAssetStatus() AssetStatus { if m != nil { return m.NewAssetStatus } - return AssetWithStatus{} + return AssetStatus_ASSET_STATUS_UNSPECIFIED } type MsgChangeAssetStatusResponse struct { @@ -444,48 +453,49 @@ func init() { func init() { proto.RegisterFile("osmosis/bridge/v1beta1/tx.proto", fileDescriptor_8e478e3238c885a8) } var fileDescriptor_8e478e3238c885a8 = []byte{ - // 654 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x95, 0xcd, 0x6e, 0xd3, 0x4e, - 0x14, 0xc5, 0xe3, 0xf6, 0xff, 0xaf, 0xe8, 0xb4, 0xd0, 0xc6, 0x04, 0x9a, 0x9a, 0xd6, 0x2e, 0x06, - 0xd4, 0x52, 0x88, 0xad, 0xba, 0xac, 0xb2, 0x6b, 0x90, 0x90, 0x2a, 0x11, 0x81, 0x0c, 0x08, 0xc4, - 0xa6, 0x1a, 0xc7, 0x83, 0x63, 0x51, 0xcf, 0x04, 0xcf, 0xb8, 0x69, 0x5f, 0x81, 0x15, 0xef, 0x81, - 0x90, 0xd8, 0xf2, 0x06, 0x5d, 0x96, 0x1d, 0x62, 0x61, 0xa1, 0x76, 0xc1, 0x3e, 0x4f, 0x80, 0x3c, - 0x33, 0xf9, 0xc0, 0xf9, 0x50, 0xb3, 0x66, 0x13, 0x39, 0xf6, 0xef, 0x9e, 0x7b, 0xcf, 0xb9, 0xe3, - 0x04, 0x18, 0x84, 0x46, 0x84, 0x86, 0xd4, 0xf6, 0xe2, 0xd0, 0x0f, 0x90, 0x7d, 0xb4, 0xe3, 0x21, - 0x06, 0x77, 0x6c, 0x76, 0x6c, 0xb5, 0x62, 0xc2, 0x88, 0x7a, 0x53, 0x02, 0x96, 0x00, 0x2c, 0x09, - 0x68, 0xa5, 0x80, 0x04, 0x84, 0x23, 0x76, 0x76, 0x25, 0x68, 0xad, 0x08, 0xa3, 0x10, 0x13, 0x9b, - 0x7f, 0xca, 0x5b, 0x7a, 0x83, 0x2b, 0xd8, 0x1e, 0xa4, 0x7d, 0xf9, 0x06, 0x09, 0xb1, 0x7c, 0x7e, - 0x67, 0xcc, 0x04, 0xb2, 0x1f, 0x87, 0xcc, 0xcf, 0x33, 0x40, 0xad, 0xd3, 0x60, 0x1f, 0x7b, 0x24, - 0xc1, 0xfe, 0xcb, 0x18, 0x62, 0xfa, 0x0e, 0xc5, 0xea, 0x7d, 0x30, 0x47, 0x11, 0xf6, 0x51, 0x5c, - 0x56, 0x36, 0x94, 0xad, 0xf9, 0x5a, 0xb1, 0x93, 0x1a, 0x57, 0x4f, 0x60, 0x74, 0x58, 0x35, 0xc5, - 0x7d, 0xd3, 0x95, 0x80, 0xba, 0x03, 0xe6, 0x7d, 0x44, 0xd9, 0x01, 0xf4, 0xfd, 0xb8, 0x3c, 0xc3, - 0xe9, 0x52, 0x27, 0x35, 0x96, 0x05, 0xdd, 0x7b, 0x64, 0xba, 0x57, 0xb2, 0xeb, 0x3d, 0xdf, 0x8f, - 0xd5, 0x7d, 0xf0, 0x3f, 0xa4, 0x14, 0xb1, 0xf2, 0xec, 0x86, 0xb2, 0xb5, 0xe0, 0xac, 0x5b, 0xa3, - 0xa3, 0xb0, 0xf6, 0x32, 0xa8, 0x56, 0x3a, 0x4d, 0x8d, 0x42, 0x27, 0x35, 0x16, 0x85, 0x22, 0xaf, - 0x34, 0x5d, 0xa1, 0xa0, 0x3e, 0x01, 0x73, 0x30, 0x22, 0x09, 0x66, 0xe5, 0xff, 0x78, 0x6b, 0x2b, - 0x83, 0x7f, 0xa6, 0xc6, 0x0d, 0x11, 0x0e, 0xf5, 0xdf, 0x5b, 0x21, 0xb1, 0x23, 0xc8, 0x9a, 0xd6, - 0x3e, 0x66, 0x7d, 0x17, 0xa2, 0xc8, 0x74, 0x65, 0x75, 0xf5, 0xee, 0xc7, 0xdf, 0x5f, 0xb7, 0xf3, - 0x3b, 0x0b, 0x45, 0x2a, 0x15, 0x26, 0x63, 0x31, 0xd7, 0x80, 0x36, 0x1c, 0x96, 0x8b, 0x68, 0x8b, - 0x60, 0x8a, 0xcc, 0x2f, 0x33, 0xe0, 0x7a, 0x9d, 0x06, 0xcf, 0x12, 0xf6, 0x8f, 0x87, 0x79, 0x2f, - 0x0b, 0x73, 0x23, 0x17, 0x26, 0x91, 0xb1, 0xf4, 0xd3, 0x5c, 0x07, 0xb7, 0x46, 0xc4, 0xd5, 0x8b, - 0xf3, 0x9b, 0x02, 0x96, 0xea, 0x34, 0x78, 0xd5, 0xf2, 0x21, 0x43, 0xcf, 0x61, 0x0c, 0x23, 0x3a, - 0x4d, 0x94, 0x6f, 0x00, 0xc0, 0xa8, 0x7d, 0xd0, 0xe2, 0x85, 0x3c, 0xcb, 0x05, 0x47, 0x1f, 0x17, - 0x8e, 0x90, 0xaf, 0xad, 0xca, 0x74, 0x8a, 0x42, 0xb2, 0x5f, 0x6f, 0xba, 0xf3, 0x18, 0xb5, 0x05, - 0x55, 0xbd, 0x9d, 0xd9, 0x5b, 0xcb, 0xd9, 0x4b, 0xf8, 0x98, 0x15, 0x89, 0xaf, 0x82, 0x95, 0xdc, - 0xe8, 0x3d, 0x5b, 0xa9, 0x02, 0x4a, 0x75, 0x1a, 0x3c, 0x6e, 0x42, 0x1c, 0x20, 0xbe, 0x94, 0x17, - 0x0c, 0xb2, 0x64, 0x2a, 0x6f, 0x31, 0x58, 0xce, 0x66, 0xe3, 0x5b, 0x3b, 0xa0, 0xbc, 0x5c, 0x3a, - 0xdc, 0x9c, 0xb8, 0xfe, 0xd7, 0x21, 0x6b, 0x8a, 0x6e, 0x35, 0x43, 0x5a, 0x5d, 0xe9, 0x5b, 0x1d, - 0x94, 0x33, 0xdd, 0x6b, 0x18, 0xb5, 0x07, 0xc6, 0xab, 0x6e, 0x66, 0xae, 0xcd, 0x9c, 0xeb, 0x06, - 0x77, 0x51, 0xe1, 0x95, 0x15, 0x59, 0xa9, 0x83, 0xb5, 0x51, 0xfe, 0xba, 0x01, 0x38, 0xdf, 0x67, - 0xc1, 0x6c, 0x9d, 0x06, 0xea, 0x07, 0xb0, 0x94, 0xff, 0xd9, 0xd9, 0x1e, 0x37, 0xfd, 0xf0, 0x5b, - 0xa7, 0x39, 0x97, 0x67, 0xbb, 0xad, 0x55, 0x06, 0x96, 0x87, 0xde, 0xce, 0x07, 0x13, 0x74, 0xf2, - 0xb0, 0xb6, 0x3b, 0x05, 0xdc, 0xeb, 0xda, 0x04, 0x8b, 0x7f, 0x1d, 0xe2, 0xcd, 0x09, 0x22, 0x83, - 0xa0, 0x66, 0x5f, 0x12, 0xec, 0x75, 0x6a, 0x83, 0xe2, 0xf0, 0xb9, 0x7a, 0x38, 0x41, 0x65, 0x88, - 0xd6, 0x1e, 0x4d, 0x43, 0x77, 0x1b, 0xd7, 0x9e, 0x9e, 0x9e, 0xeb, 0xca, 0xd9, 0xb9, 0xae, 0xfc, - 0x3a, 0xd7, 0x95, 0x4f, 0x17, 0x7a, 0xe1, 0xec, 0x42, 0x2f, 0xfc, 0xb8, 0xd0, 0x0b, 0x6f, 0x9d, - 0x20, 0x64, 0xcd, 0xc4, 0xb3, 0x1a, 0x24, 0xb2, 0xa5, 0x72, 0xe5, 0x10, 0x7a, 0xb4, 0xfb, 0xc5, - 0x3e, 0x72, 0x76, 0xed, 0xe3, 0xee, 0x79, 0x62, 0x27, 0x2d, 0x44, 0xbd, 0x39, 0xfe, 0xdf, 0xb4, - 0xfb, 0x27, 0x00, 0x00, 0xff, 0xff, 0x72, 0x89, 0xa4, 0xf5, 0x44, 0x07, 0x00, 0x00, + // 661 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x95, 0x4f, 0x4f, 0x13, 0x41, + 0x18, 0x87, 0xbb, 0x45, 0x89, 0x0c, 0x08, 0x74, 0xad, 0x52, 0x16, 0xd8, 0xc5, 0x45, 0x03, 0xa2, + 0xdd, 0x0d, 0x8b, 0xa7, 0xde, 0xa8, 0x89, 0x09, 0x89, 0x8d, 0x66, 0xd5, 0xc4, 0x78, 0x21, 0xb3, + 0xdd, 0x71, 0xbb, 0xca, 0xce, 0xd4, 0x9d, 0x29, 0x85, 0xaf, 0xe0, 0xc9, 0xaf, 0xe0, 0xd9, 0x98, + 0x78, 0xf5, 0x1b, 0x70, 0xc4, 0x9b, 0xf1, 0xd0, 0x18, 0x38, 0x78, 0xef, 0x27, 0x30, 0x3b, 0x33, + 0xdd, 0xe2, 0x96, 0x36, 0x34, 0x1e, 0xbd, 0x90, 0xfd, 0xf3, 0xbc, 0xbf, 0x79, 0xdf, 0x67, 0x76, + 0x28, 0x30, 0x08, 0x8d, 0x08, 0x0d, 0xa9, 0xed, 0xc5, 0xa1, 0x1f, 0x20, 0xfb, 0x60, 0xcb, 0x43, + 0x0c, 0x6e, 0xd9, 0xec, 0xd0, 0x6a, 0xc6, 0x84, 0x11, 0xf5, 0x96, 0x04, 0x2c, 0x01, 0x58, 0x12, + 0xd0, 0x8a, 0x01, 0x09, 0x08, 0x47, 0xec, 0xe4, 0x4a, 0xd0, 0x5a, 0x01, 0x46, 0x21, 0x26, 0x36, + 0xff, 0x2b, 0x1f, 0xe9, 0x75, 0x9e, 0x60, 0x7b, 0x90, 0xf6, 0xe3, 0xeb, 0x24, 0xc4, 0xf2, 0xfd, + 0xda, 0x90, 0x0e, 0xe4, 0x7a, 0x1c, 0x32, 0x3f, 0xe7, 0x81, 0x5a, 0xa3, 0xc1, 0x2e, 0xf6, 0x48, + 0x0b, 0xfb, 0x2f, 0x62, 0x88, 0xe9, 0x1b, 0x14, 0xab, 0xf7, 0xc0, 0x24, 0x45, 0xd8, 0x47, 0x71, + 0x49, 0x59, 0x55, 0x36, 0xa6, 0xaa, 0x85, 0x6e, 0xc7, 0xb8, 0x7e, 0x04, 0xa3, 0xfd, 0x8a, 0x29, + 0x9e, 0x9b, 0xae, 0x04, 0xd4, 0x2d, 0x30, 0xe5, 0x23, 0xca, 0xf6, 0xa0, 0xef, 0xc7, 0xa5, 0x3c, + 0xa7, 0x8b, 0xdd, 0x8e, 0x31, 0x2f, 0xe8, 0xf4, 0x95, 0xe9, 0x5e, 0x4b, 0xae, 0x77, 0x7c, 0x3f, + 0x56, 0x77, 0xc1, 0x55, 0x48, 0x29, 0x62, 0xa5, 0x89, 0x55, 0x65, 0x63, 0xda, 0x59, 0xb1, 0x2e, + 0x56, 0x61, 0xed, 0x24, 0x50, 0xb5, 0x78, 0xdc, 0x31, 0x72, 0xdd, 0x8e, 0x31, 0x23, 0x12, 0x79, + 0xa5, 0xe9, 0x8a, 0x04, 0xf5, 0x31, 0x98, 0x84, 0x11, 0x69, 0x61, 0x56, 0xba, 0xc2, 0x97, 0xb6, + 0x12, 0xf8, 0x67, 0xc7, 0xb8, 0x29, 0xe4, 0x50, 0xff, 0x9d, 0x15, 0x12, 0x3b, 0x82, 0xac, 0x61, + 0xed, 0x62, 0xd6, 0x9f, 0x42, 0x14, 0x99, 0xae, 0xac, 0xae, 0xdc, 0xf9, 0xf0, 0xfb, 0xeb, 0x66, + 0x76, 0xcf, 0x42, 0x61, 0xa5, 0xcc, 0xa4, 0x16, 0x73, 0x19, 0x68, 0x83, 0xb2, 0x5c, 0x44, 0x9b, + 0x04, 0x53, 0x64, 0x7e, 0xc9, 0x83, 0x1b, 0x35, 0x1a, 0x3c, 0x6d, 0xb1, 0xff, 0x5c, 0xe6, 0xdd, + 0x44, 0xe6, 0x6a, 0x46, 0x26, 0x91, 0x5a, 0xfa, 0x36, 0x57, 0xc0, 0xd2, 0x05, 0xba, 0x52, 0x9d, + 0xdf, 0x14, 0x30, 0x57, 0xa3, 0xc1, 0xcb, 0xa6, 0x0f, 0x19, 0x7a, 0x06, 0x63, 0x18, 0xd1, 0x71, + 0x54, 0xbe, 0x02, 0x00, 0xa3, 0xf6, 0x5e, 0x93, 0x17, 0x72, 0x97, 0xd3, 0x8e, 0x3e, 0x4c, 0x8e, + 0x88, 0xaf, 0x2e, 0x4a, 0x3b, 0x05, 0x11, 0xd9, 0xaf, 0x37, 0xdd, 0x29, 0x8c, 0xda, 0x82, 0xaa, + 0xdc, 0x4e, 0xc6, 0x5b, 0xce, 0x8c, 0xd7, 0xe2, 0x6d, 0x96, 0x25, 0xbe, 0x08, 0x16, 0x32, 0xad, + 0xa7, 0x63, 0x7d, 0xca, 0x83, 0x62, 0x8d, 0x06, 0x8f, 0x1a, 0x10, 0x07, 0x88, 0x6f, 0xca, 0x73, + 0x06, 0x59, 0x6b, 0xac, 0xd9, 0xd2, 0x3d, 0xcf, 0xff, 0xf3, 0x9e, 0xbf, 0x05, 0xf3, 0xc9, 0x98, + 0xfc, 0x66, 0x8f, 0xf2, 0x4e, 0xf8, 0x97, 0x34, 0xeb, 0xac, 0x8d, 0x4c, 0x15, 0x4d, 0x57, 0x97, + 0xba, 0x1d, 0x63, 0xa1, 0x6f, 0xeb, 0x7c, 0x8c, 0xe9, 0xce, 0x62, 0xd4, 0x3e, 0x07, 0x57, 0xd6, + 0x13, 0x71, 0x66, 0x46, 0x5c, 0x9d, 0x8b, 0x28, 0xf3, 0xca, 0xb2, 0xac, 0xd4, 0xc1, 0xf2, 0x45, + 0x8a, 0x7a, 0x0e, 0x9d, 0xef, 0x13, 0x60, 0xa2, 0x46, 0x03, 0xf5, 0x3d, 0x98, 0xcb, 0xfe, 0xe7, + 0xda, 0x1c, 0xd6, 0xf5, 0xe0, 0xc1, 0xd5, 0x9c, 0xcb, 0xb3, 0xbd, 0xa5, 0x55, 0x06, 0xe6, 0x07, + 0x0e, 0xf8, 0xfd, 0x11, 0x39, 0x59, 0x58, 0xdb, 0x1e, 0x03, 0x4e, 0x57, 0x6d, 0x80, 0x99, 0xbf, + 0xce, 0xc1, 0xfa, 0x88, 0x90, 0xf3, 0xa0, 0x66, 0x5f, 0x12, 0x4c, 0x57, 0x6a, 0x83, 0xc2, 0xe0, + 0xa7, 0xf9, 0x60, 0x44, 0xca, 0x00, 0xad, 0x3d, 0x1c, 0x87, 0xee, 0x2d, 0x5c, 0x7d, 0x72, 0x7c, + 0xaa, 0x2b, 0x27, 0xa7, 0xba, 0xf2, 0xeb, 0x54, 0x57, 0x3e, 0x9e, 0xe9, 0xb9, 0x93, 0x33, 0x3d, + 0xf7, 0xe3, 0x4c, 0xcf, 0xbd, 0x76, 0x82, 0x90, 0x35, 0x5a, 0x9e, 0x55, 0x27, 0x91, 0x2d, 0x93, + 0xcb, 0xfb, 0xd0, 0xa3, 0xbd, 0x1b, 0xfb, 0xc0, 0xd9, 0xb6, 0x0f, 0x7b, 0xdf, 0x13, 0x3b, 0x6a, + 0x22, 0xea, 0x4d, 0xf2, 0x9f, 0xb7, 0xed, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x54, 0x73, 0x51, + 0x3f, 0x87, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -927,8 +937,13 @@ func (m *MsgChangeAssetStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.NewAssetStatus != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.NewAssetStatus)) + i-- + dAtA[i] = 0x18 + } { - size, err := m.NewAssetStatus.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Asset.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1075,8 +1090,11 @@ func (m *MsgChangeAssetStatus) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - l = m.NewAssetStatus.Size() + l = m.Asset.Size() n += 1 + l + sovTx(uint64(l)) + if m.NewAssetStatus != 0 { + n += 1 + sovTx(uint64(m.NewAssetStatus)) + } return n } @@ -1785,7 +1803,7 @@ func (m *MsgChangeAssetStatus) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NewAssetStatus", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Asset", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1812,10 +1830,29 @@ func (m *MsgChangeAssetStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.NewAssetStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Asset.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NewAssetStatus", wireType) + } + m.NewAssetStatus = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NewAssetStatus |= AssetStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:])