diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2f5d4104..b4cea5936b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ Holds are also reflected in the `x/bank` module's `SpendableBalances` query. * Add new MaxSupply param to marker module and deprecate MaxTotalSupply. [#1292](https://github.com/provenance-io/provenance/issues/1292). * Add hidden docgen command to output documentation in different formats. [#1468](https://github.com/provenance-io/provenance/issues/1468). +* Add ics20 marker creation for receiving marker via ibc sends [#1127](https://github.com/provenance-io/provenance/issues/1127). ### Improvements diff --git a/app/app.go b/app/app.go index 5912fb379f..1395856838 100644 --- a/app/app.go +++ b/app/app.go @@ -329,6 +329,8 @@ type App struct { TransferStack *ibchooks.IBCMiddleware Ics20WasmHooks *ibchooks.WasmHooks + Ics20MarkerHooks *ibchooks.MarkerHooks + IbcHooks *ibchooks.IbcHooks HooksICS4Wrapper ibchooks.ICS4Middleware // the module manager @@ -509,9 +511,14 @@ func New( addrPrefix := sdk.GetConfig().GetBech32AccountAddrPrefix() // We use this approach so running tests which use "cosmos" will work while we use "pb" wasmHooks := ibchooks.NewWasmHooks(&hooksKeeper, nil, addrPrefix) // The contract keeper needs to be set later app.Ics20WasmHooks = &wasmHooks + markerHooks := ibchooks.NewMarkerHooks(nil) + app.Ics20MarkerHooks = &markerHooks + ibcHooks := ibchooks.NewIbcHooks(appCodec, &hooksKeeper, app.IBCKeeper, app.Ics20WasmHooks, app.Ics20MarkerHooks, nil) + app.IbcHooks = &ibcHooks + app.HooksICS4Wrapper = ibchooks.NewICS4Middleware( app.IBCKeeper.ChannelKeeper, - app.Ics20WasmHooks, + app.IbcHooks, ) // Create Transfer Keepers @@ -641,6 +648,9 @@ func New( app.ContractKeeper = wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper) app.Ics20WasmHooks.ContractKeeper = app.WasmKeeper // app.ContractKeeper -- this changes in the next version of wasm to a permissioned keeper app.IBCHooksKeeper.ContractKeeper = app.ContractKeeper + app.Ics20MarkerHooks.MarkerKeeper = &app.MarkerKeeper + + app.IbcHooks.SendPacketPreProcessors = []ibchookstypes.PreSendPacketDataProcessingFn{app.Ics20MarkerHooks.SetupMarkerMemoFn, app.Ics20WasmHooks.GetWasmSendPacketPreProcessor} app.ScopedOracleKeeper = scopedOracleKeeper app.OracleKeeper = *oraclekeeper.NewKeeper( diff --git a/testutil/contracts/counter/Cargo.lock b/testutil/contracts/counter/Cargo.lock new file mode 100644 index 0000000000..7f70be5afc --- /dev/null +++ b/testutil/contracts/counter/Cargo.lock @@ -0,0 +1,838 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + +[[package]] +name = "cosmwasm-crypto" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340" +dependencies = [ + "digest 0.10.7", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3737a3aac48f5ed883b5b73bfb731e77feebd8fc6b43419844ec2971072164d" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "counter" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus 0.16.0", + "cw2 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-multi-test" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils", + "derivative", + "itertools", + "k256", + "prost", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.0.1", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "k256" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.17", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45b6ddbb36c5b969c182aec3c4a0bce7df3fbad4b77114706a49aacc80567388" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.17", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/testutil/contracts/counter/Cargo.toml b/testutil/contracts/counter/Cargo.toml new file mode 100644 index 0000000000..f164afc0e6 --- /dev/null +++ b/testutil/contracts/counter/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "counter" +description = "Cosmwasm counter dapp, with permissions for testing Osmosis wasmhooks" +version = "0.1.0" +authors = ["osmosis contributors"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.1.3" +cw-storage-plus = "0.16.0" +cw2 = "0.16.0" +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cw-multi-test = "0.16.0" diff --git a/testutil/contracts/counter/README.md b/testutil/contracts/counter/README.md new file mode 100644 index 0000000000..f4394fe876 --- /dev/null +++ b/testutil/contracts/counter/README.md @@ -0,0 +1,11 @@ +# Counter contract from [Osmosis Labs](https://github.com/osmosis-labs/osmosis/commit/64393a14e18b2562d72a3892eec716197a3716c7) + +This contract is a modification of the standard cosmwasm `counter` contract. +Namely, it tracks a counter, _by sender_. +This is a better way to test wasmhooks. + +This contract tracks any funds sent to it by adding it to the state under the `sender` key. + +This way we can verify that, independently of the sender, the funds will end up under the +`WasmHooksModuleAccount` address when the contract is executed via an IBC send that goes +through the wasmhooks module. diff --git a/testutil/contracts/counter/artifacts/checksums.txt b/testutil/contracts/counter/artifacts/checksums.txt new file mode 100644 index 0000000000..063deca9c2 --- /dev/null +++ b/testutil/contracts/counter/artifacts/checksums.txt @@ -0,0 +1 @@ +4dc0c4ce263c019e3ec53efbcbd594cc1931abbdb705f07c0ded9b3d8e685a27 counter.wasm diff --git a/testutil/contracts/counter/artifacts/checksums_intermediate.txt b/testutil/contracts/counter/artifacts/checksums_intermediate.txt new file mode 100644 index 0000000000..a612f46473 --- /dev/null +++ b/testutil/contracts/counter/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +a8861569ce49bcfa476ebc15f0b85d4f9b1b1734aef63026f489403832eba8a7 ./target/wasm32-unknown-unknown/release/counter.wasm diff --git a/testutil/contracts/counter/artifacts/counter.wasm b/testutil/contracts/counter/artifacts/counter.wasm new file mode 100644 index 0000000000..407509573e Binary files /dev/null and b/testutil/contracts/counter/artifacts/counter.wasm differ diff --git a/testutil/contracts/counter/src/contract.rs b/testutil/contracts/counter/src/contract.rs new file mode 100644 index 0000000000..259d363492 --- /dev/null +++ b/testutil/contracts/counter/src/contract.rs @@ -0,0 +1,395 @@ +use std::collections::HashMap; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::*; +use crate::state::{Counter, COUNTERS}; + +// version info for migration info +const CONTRACT_NAME: &str = "osmosis:permissioned_counter"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let initial_counter = Counter { + count: msg.count, + total_funds: vec![], + owner: info.sender.clone(), + }; + COUNTERS.save(deps.storage, info.sender.clone(), &initial_counter)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender) + .add_attribute("count", msg.count.to_string())) +} + +pub mod utils { + use cosmwasm_std::Addr; + + use super::*; + + pub fn update_counter( + deps: DepsMut, + sender: Addr, + update_counter: &dyn Fn(&Option) -> i32, + update_funds: &dyn Fn(&Option) -> Vec, + ) -> Result { + COUNTERS + .update( + deps.storage, + sender.clone(), + |state| -> Result<_, ContractError> { + match state { + None => Ok(Counter { + count: update_counter(&None), + total_funds: update_funds(&None), + owner: sender, + }), + Some(counter) => Ok(Counter { + count: update_counter(&Some(counter.clone())), + total_funds: update_funds(&Some(counter)), + owner: sender, + }), + } + }, + ) + .map(|_r| true) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Increment {} => execute::increment(deps, info), + ExecuteMsg::Reset { count } => execute::reset(deps, info, count), + } +} + +pub mod execute { + use super::*; + + pub fn increment(deps: DepsMut, info: MessageInfo) -> Result { + utils::update_counter( + deps, + info.sender, + &|counter| match counter { + None => 0, + Some(counter) => counter.count + 1, + }, + &|counter| match counter { + None => info.funds.clone(), + Some(counter) => naive_add_coins(&info.funds, &counter.total_funds), + }, + )?; + Ok(Response::new().add_attribute("action", "increment")) + } + + pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { + utils::update_counter(deps, info.sender, &|_counter| count, &|_counter| vec![])?; + Ok(Response::new().add_attribute("action", "reset")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { + match msg { + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck { + channel: _, + sequence: _, + ack: _, + success, + }) => sudo::receive_ack(deps, env.contract.address, success), + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCTimeout { + channel: _, + sequence: _, + }) => sudo::ibc_timeout(deps, env.contract.address), + } +} + +pub mod sudo { + use cosmwasm_std::Addr; + + use super::*; + + pub fn receive_ack( + deps: DepsMut, + contract: Addr, + _success: bool, + ) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 1, + Some(counter) => counter.count + 1, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "ack")) + } + + pub(crate) fn ibc_timeout(deps: DepsMut, contract: Addr) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 10, + Some(counter) => counter.count + 10, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "timeout")) + } +} + +pub fn naive_add_coins(lhs: &Vec, rhs: &Vec) -> Vec { + // This is a naive, inneficient implementation of Vec addition. + // This shouldn't be used in production but serves our purpose for this + // testing contract + let mut coins: HashMap = HashMap::new(); + for coin in lhs { + coins.insert(coin.denom.clone(), coin.amount); + } + + for coin in rhs { + coins + .entry(coin.denom.clone()) + .and_modify(|e| *e += coin.amount) + .or_insert(coin.amount); + } + coins.iter().map(|(d, &a)| Coin::new(a.into(), d)).collect() +} + +#[test] +fn coin_addition() { + let c1 = vec![Coin::new(1, "a"), Coin::new(2, "b")]; + let c2 = vec![Coin::new(7, "a"), Coin::new(2, "c")]; + + let mut sum = naive_add_coins(&c1, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(2, "a"), Coin::new(4, "b")]); + + let mut sum = naive_add_coins(&c1, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&c2, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(14, "a"), Coin::new(4, "c"),]); + + let mut sum = naive_add_coins(&c2, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&vec![], &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); + + let mut sum = naive_add_coins(&c2, &vec![]); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount { addr } => to_binary(&query::count(deps, addr)?), + QueryMsg::GetTotalFunds { addr } => to_binary(&query::total_funds(deps, addr)?), + } +} + +pub mod query { + use cosmwasm_std::Addr; + + use super::*; + + pub fn count(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetCountResponse { count: state.count }) + } + + pub fn total_funds(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetTotalFundsResponse { + total_funds: state.total_funds, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + use cosmwasm_std::{coins, from_binary}; + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(1000, "earth")); + + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn increment() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("someone-else", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Increment {}; + let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // should increase counter by 1 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(18, value.count); + + // Counter for someone else is not incremented + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("someone-else"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn reset() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // beneficiary can release it + let unauth_info = mock_info("anyone", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 7 }; + let _res = execute(deps.as_mut(), mock_env(), unauth_info, msg); + + // should be 7 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("anyone"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(7, value.count); + + // only the original creator can reset the counter + let auth_info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 5 }; + let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); + + // should now be 5 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(5, value.count); + } + + #[test] + fn acks() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let get_msg = QueryMsg::GetCount { + addr: Addr::unchecked(env.clone().contract.address), + }; + + // No acks + query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap_err(); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(1, value.count); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env, get_msg).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(2, value.count); + } +} diff --git a/testutil/contracts/counter/src/error.rs b/testutil/contracts/counter/src/error.rs new file mode 100644 index 0000000000..3caf0c5c5b --- /dev/null +++ b/testutil/contracts/counter/src/error.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Custom Error val: {val:?}")] + CustomError { val: String }, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. +} diff --git a/testutil/contracts/counter/src/helpers.rs b/testutil/contracts/counter/src/helpers.rs new file mode 100644 index 0000000000..c943c13658 --- /dev/null +++ b/testutil/contracts/counter/src/helpers.rs @@ -0,0 +1,48 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, + WasmQuery, +}; + +use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + /// Get Count + pub fn count(&self, querier: &Q, addr: Addr) -> StdResult + where + Q: Querier, + T: Into, + CQ: CustomQuery, + { + let msg = QueryMsg::GetCount { addr }; + let query = WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_binary(&msg)?, + } + .into(); + let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; + Ok(res) + } +} + diff --git a/testutil/contracts/counter/src/integration_tests.rs b/testutil/contracts/counter/src/integration_tests.rs new file mode 100644 index 0000000000..4c50784652 --- /dev/null +++ b/testutil/contracts/counter/src/integration_tests.rs @@ -0,0 +1,71 @@ +#[cfg(test)] +mod tests { + use crate::helpers::CwTemplateContract; + use crate::msg::InstantiateMsg; + use cosmwasm_std::{Addr, Coin, Empty, Uint128}; + use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; + + pub fn contract_template() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + const USER: &str = "USER"; + const ADMIN: &str = "ADMIN"; + const NATIVE_DENOM: &str = "denom"; + + fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(1), + }], + ) + .unwrap(); + }) + } + + fn proper_instantiate() -> (App, CwTemplateContract) { + let mut app = mock_app(); + let cw_template_id = app.store_code(contract_template()); + + let msg = InstantiateMsg { count: 1i32 }; + let cw_template_contract_addr = app + .instantiate_contract( + cw_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + let cw_template_contract = CwTemplateContract(cw_template_contract_addr); + + (app, cw_template_contract) + } + + mod count { + use super::*; + use crate::msg::ExecuteMsg; + + #[test] + fn count() { + let (mut app, cw_template_contract) = proper_instantiate(); + + let msg = ExecuteMsg::Increment {}; + let cosmos_msg = cw_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + } + } +} diff --git a/testutil/contracts/counter/src/lib.rs b/testutil/contracts/counter/src/lib.rs new file mode 100644 index 0000000000..ffd1f6ac44 --- /dev/null +++ b/testutil/contracts/counter/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(unused_imports)] +pub mod contract; +mod error; +pub mod helpers; +pub mod integration_tests; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/testutil/contracts/counter/src/msg.rs b/testutil/contracts/counter/src/msg.rs new file mode 100644 index 0000000000..1cbdd433ad --- /dev/null +++ b/testutil/contracts/counter/src/msg.rs @@ -0,0 +1,63 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Coin}; + +#[cw_serde] +pub struct InstantiateMsg { + pub count: i32, +} + +#[cw_serde] +pub enum ExecuteMsg { + Increment {}, + Reset { count: i32 }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + #[returns(GetCountResponse)] + GetCount { addr: Addr }, + #[returns(GetTotalFundsResponse)] + GetTotalFunds { addr: Addr }, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetCountResponse { + pub count: i32, +} + +#[cw_serde] +pub struct GetTotalFundsResponse { + pub total_funds: Vec, +} + +#[cw_serde] +#[serde(rename = "ibc_lifecycle_complete")] +pub enum IBCLifecycleComplete { + #[serde(rename = "ibc_ack")] + IBCAck { + /// The source channel (osmosis side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + /// String encoded version of the ack as seen by OnAcknowledgementPacket(..) + ack: String, + /// Weather an ack is a success of failure according to the transfer spec + success: bool, + }, + #[serde(rename = "ibc_timeout")] + IBCTimeout { + /// The source channel (osmosis side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + }, +} + +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_lifecycle_complete")] + IBCLifecycleComplete(IBCLifecycleComplete), +} diff --git a/testutil/contracts/counter/src/state.rs b/testutil/contracts/counter/src/state.rs new file mode 100644 index 0000000000..4b8002fc4f --- /dev/null +++ b/testutil/contracts/counter/src/state.rs @@ -0,0 +1,14 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::{Item, Map}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Counter { + pub count: i32, + pub total_funds: Vec, + pub owner: Addr, +} + +pub const COUNTERS: Map = Map::new("state"); diff --git a/testutil/contracts/echo/Cargo.lock b/testutil/contracts/echo/Cargo.lock new file mode 100644 index 0000000000..8cd39f5c42 --- /dev/null +++ b/testutil/contracts/echo/Cargo.lock @@ -0,0 +1,824 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + +[[package]] +name = "cosmwasm-crypto" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c0e41be7e6c7d7ab3c61cdc32fcfaa14f948491a401cbc1c74bb33b6f4b851" +dependencies = [ + "digest 0.10.7", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7ee2798c92c00dd17bebb4210f81d5f647e5e92d847959b7977e0fd29a3500" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "407aca6f1671a08b60db8167f03bb7cb6b2378f0ddd9a030367b66ba33c2fd41" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d1e00b8fd27ff923c10303023626358e23a6f9079f8ebec23a8b4b0bfcd4b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5fdfd112b070055f068fad079d490117c8e905a588b92a5a7c9276d029930" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9e21c4f58986fd20184d7685e1c43c5732c9309337b09307d5952fd34dba6e" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-multi-test" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils", + "derivative", + "itertools", + "k256", + "prost", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "echo" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "k256" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/testutil/contracts/echo/Cargo.toml b/testutil/contracts/echo/Cargo.toml new file mode 100644 index 0000000000..6980ce8cb2 --- /dev/null +++ b/testutil/contracts/echo/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "echo" +description = "Cosmwasm contract that always returns the same response" +version = "0.1.0" +authors = ["osmosis contributors"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.1.3" +cw-storage-plus = "0.16.0" +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cw-multi-test = "0.16.0" diff --git a/testutil/contracts/echo/artifacts/checksums.txt b/testutil/contracts/echo/artifacts/checksums.txt new file mode 100644 index 0000000000..823ba2ccd2 --- /dev/null +++ b/testutil/contracts/echo/artifacts/checksums.txt @@ -0,0 +1 @@ +20d0515b3b3fac21c70758e8184136fadb821b93da1b491b481fe0ef52a6b8ed echo.wasm diff --git a/testutil/contracts/echo/artifacts/checksums_intermediate.txt b/testutil/contracts/echo/artifacts/checksums_intermediate.txt new file mode 100644 index 0000000000..9d64313db7 --- /dev/null +++ b/testutil/contracts/echo/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +e83bc7e3e532ed1b97846057c5ba737fa9e82668c54bf39307ec78ec79bd4319 ./target/wasm32-unknown-unknown/release/echo.wasm diff --git a/testutil/contracts/echo/artifacts/echo.wasm b/testutil/contracts/echo/artifacts/echo.wasm new file mode 100644 index 0000000000..e8a1bdeced Binary files /dev/null and b/testutil/contracts/echo/artifacts/echo.wasm differ diff --git a/testutil/contracts/echo/src/lib.rs b/testutil/contracts/echo/src/lib.rs new file mode 100644 index 0000000000..ab1d16a3ae --- /dev/null +++ b/testutil/contracts/echo/src/lib.rs @@ -0,0 +1,42 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdError}; +use cosmwasm_schema::{cw_serde}; + +// Messages +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + Echo { msg: String }, +} + +// Instantiate +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + Ok(Response::new()) +} + +// Execute +fn simple_response(msg: String) -> Response { + Response::new() + .add_attribute("echo", msg) + .set_data(b"this should echo") +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Echo { msg } => Ok(simple_response(msg)), + } +} diff --git a/testutil/contracts/wasm.go b/testutil/contracts/wasm.go new file mode 100644 index 0000000000..ae8a0b3816 --- /dev/null +++ b/testutil/contracts/wasm.go @@ -0,0 +1,50 @@ +package contracts + +import ( + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + provenanceapp "github.com/provenance-io/provenance/app" + + _ "embed" +) + +//go:embed counter/artifacts/counter.wasm +var counterWasm []byte + +//go:embed echo/artifacts/echo.wasm +var echoWasm []byte + +// EchoWasm returns the echo contract wasm byte data +func EchoWasm() []byte { + return echoWasm +} + +// CounterWasm returns the counter contract wasm byte data +func CounterWasm() []byte { + return counterWasm +} + +func StoreContractCode(app *provenanceapp.App, ctx sdk.Context, wasmCode []byte) (uint64, error) { + govKeeper := wasmkeeper.NewGovPermissionKeeper(app.WasmKeeper) + creator := app.AccountKeeper.GetModuleAddress(govtypes.ModuleName) + + accessEveryone := wasmtypes.AccessConfig{Permission: wasmtypes.AccessTypeEverybody} + codeID, _, err := govKeeper.Create(ctx, creator, wasmCode, &accessEveryone) + return codeID, err +} + +func InstantiateContract(app *provenanceapp.App, ctx sdk.Context, msg string, codeID uint64) (sdk.AccAddress, error) { + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper) + creator := app.AccountKeeper.GetModuleAddress(govtypes.ModuleName) + addr, _, err := contractKeeper.Instantiate(ctx, codeID, creator, creator, []byte(msg), "contract", nil) + return addr, err +} + +func QueryContract(app *provenanceapp.App, ctx sdk.Context, contract sdk.AccAddress, key []byte) (string, error) { + state, err := app.WasmKeeper.QuerySmart(ctx, contract, key) + return string(state), err +} diff --git a/testutil/ibc/isc4_middleware_mock.go b/testutil/ibc/isc4_middleware_mock.go new file mode 100644 index 0000000000..1c536aec13 --- /dev/null +++ b/testutil/ibc/isc4_middleware_mock.go @@ -0,0 +1,42 @@ +package ibc + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" +) + +var _ porttypes.ICS4Wrapper = &ICS4WrapperMock{} + +type ICS4WrapperMock struct{} + +func (m *ICS4WrapperMock) SendPacket( + _ sdk.Context, + _ *capabilitytypes.Capability, + _ string, + _ string, + _ ibcclienttypes.Height, + _ uint64, + _ []byte, +) (sequence uint64, err error) { + return 1, nil +} + +func (m *ICS4WrapperMock) WriteAcknowledgement( + _ sdk.Context, + _ *capabilitytypes.Capability, + _ exported.PacketI, + _ exported.Acknowledgement, +) error { + return nil +} + +func (m *ICS4WrapperMock) GetAppVersion( + _ sdk.Context, + _, + _ string, +) (string, bool) { + return "", false +} diff --git a/testutil/ibctesting.go b/testutil/ibc/testchain.go similarity index 62% rename from testutil/ibctesting.go rename to testutil/ibc/testchain.go index 8d35f6549c..e0d31fa29b 100644 --- a/testutil/ibctesting.go +++ b/testutil/ibc/testchain.go @@ -1,9 +1,11 @@ -package testutil +package ibc import ( "encoding/json" "testing" + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" @@ -14,6 +16,7 @@ import ( "github.com/cosmos/ibc-go/v6/testing/simapp/helpers" provenanceapp "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/testutil/contracts" ) type TestChain struct { @@ -25,6 +28,34 @@ func SetupTestingApp(t *testing.T) (ibctesting.TestingApp, map[string]json.RawMe return provenanceApp, provenanceapp.NewDefaultGenesisState(provenanceApp.AppCodec()) } +func (chain *TestChain) StoreContractCounterDirect(suite *suite.Suite) uint64 { + codeID, err := contracts.StoreContractCode(chain.GetProvenanceApp(), chain.GetContext(), contracts.CounterWasm()) + suite.Require().NoError(err, "counter contract direct code load failed", err) + println("loaded counter contract with code id: ", codeID) + return codeID +} + +func (chain *TestChain) StoreContractEchoDirect(suite *suite.Suite) uint64 { + codeID, err := contracts.StoreContractCode(chain.GetProvenanceApp(), chain.GetContext(), contracts.EchoWasm()) + suite.Require().NoError(err, "echo contract direct code load failed", err) + println("loaded echo contract with code id: ", codeID) + return codeID +} + +func (chain *TestChain) InstantiateContract(suite *suite.Suite, msg string, codeID uint64) sdk.AccAddress { + addr, err := contracts.InstantiateContract(chain.GetProvenanceApp(), chain.GetContext(), msg, codeID) + suite.Require().NoError(err, "contract instantiation failed", err) + println("instantiated contract '", codeID, "' with address: ", addr) + return addr +} + +func (chain *TestChain) QueryContract(suite *suite.Suite, contract sdk.AccAddress, key []byte) string { + state, err := contracts.QueryContract(chain.GetProvenanceApp(), chain.GetContext(), contract, key) + suite.Require().NoError(err, "contract query failed", err) + println("got query result of ", state) + return state +} + // SendMsgsNoCheck is an alternative to ibctesting.TestChain.SendMsgs so that it doesn't check for errors. That should be handled by the caller func (chain *TestChain) SendMsgsNoCheck(msgs ...sdk.Msg) (*sdk.Result, error) { // ensure the chain has the latest time diff --git a/testutil/wasm.go b/testutil/wasm.go deleted file mode 100644 index a049dd93d6..0000000000 --- a/testutil/wasm.go +++ /dev/null @@ -1,44 +0,0 @@ -package testutil - -import ( - "os" - - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/stretchr/testify/suite" - - sdk "github.com/cosmos/cosmos-sdk/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" -) - -func (chain *TestChain) StoreContractCodeDirect(suite *suite.Suite, path string) uint64 { - provenanceApp := chain.GetProvenanceApp() - govKeeper := wasmkeeper.NewGovPermissionKeeper(provenanceApp.WasmKeeper) - creator := provenanceApp.AccountKeeper.GetModuleAddress(govtypes.ModuleName) - - wasmCode, err := os.ReadFile(path) - suite.Require().NoError(err, "Read of wasm file failed", err) - accessEveryone := wasmtypes.AccessConfig{Permission: wasmtypes.AccessTypeEverybody} - codeID, _, err := govKeeper.Create(chain.GetContext(), creator, wasmCode, &accessEveryone) - suite.Require().NoError(err, "contract direct code load failed", err) - println("loaded contract '", path, "' with code id: ", codeID) - return codeID -} - -func (chain *TestChain) InstantiateContract(suite *suite.Suite, msg string, codeID uint64) sdk.AccAddress { - provenanceApp := chain.GetProvenanceApp() - contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(provenanceApp.WasmKeeper) - creator := provenanceApp.AccountKeeper.GetModuleAddress(govtypes.ModuleName) - addr, _, err := contractKeeper.Instantiate(chain.GetContext(), codeID, creator, creator, []byte(msg), "contract", nil) - suite.Require().NoError(err, "contract instantiation failed", err) - println("instantiated contract '", codeID, "' with address: ", addr) - return addr -} - -func (chain *TestChain) QueryContract(suite *suite.Suite, contract sdk.AccAddress, key []byte) string { - provenanceApp := chain.GetProvenanceApp() - state, err := provenanceApp.WasmKeeper.QuerySmart(chain.GetContext(), contract, key) - suite.Require().NoError(err, "contract query failed", err) - println("got query result of ", string(state)) - return string(state) -} diff --git a/x/ibchooks/bytecode/counter.wasm b/x/ibchooks/bytecode/counter.wasm deleted file mode 100644 index eb667380ce..0000000000 Binary files a/x/ibchooks/bytecode/counter.wasm and /dev/null differ diff --git a/x/ibchooks/bytecode/echo.wasm b/x/ibchooks/bytecode/echo.wasm deleted file mode 100644 index 93e7814b90..0000000000 Binary files a/x/ibchooks/bytecode/echo.wasm and /dev/null differ diff --git a/x/ibchooks/hooks.go b/x/ibchooks/hooks.go index 6e53f3d59b..6a524a924d 100644 --- a/x/ibchooks/hooks.go +++ b/x/ibchooks/hooks.go @@ -6,10 +6,17 @@ import ( clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" + + "github.com/provenance-io/provenance/x/ibchooks/types" ) type Hooks interface{} +// SendPacketPreProcessors returns a list of ordered functions to be executed before ibc's SendPacket function in middleware +type SendPacketPreProcessors interface { + GetSendPacketPreProcessors() []types.PreSendPacketDataProcessingFn +} + type OnChanOpenInitOverrideHooks interface { OnChanOpenInitOverride(im IBCMiddleware, ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string) (string, error) } @@ -140,7 +147,9 @@ type SendPacketAfterHooks interface { timeoutTimestamp uint64, data []byte, sequence uint64, - err error) + err error, + processData map[string]interface{}, + ) } // WriteAcknowledgement Hooks diff --git a/x/ibchooks/ibc_hooks.go b/x/ibchooks/ibc_hooks.go new file mode 100644 index 0000000000..ab2503de73 --- /dev/null +++ b/x/ibchooks/ibc_hooks.go @@ -0,0 +1,87 @@ +package ibchooks + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdktypes "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" + + "github.com/provenance-io/provenance/x/ibchooks/keeper" + "github.com/provenance-io/provenance/x/ibchooks/types" +) + +type IbcHooks struct { + cdc codec.BinaryCodec + ibcKeeper *ibckeeper.Keeper + ibcHooksKeeper *keeper.Keeper + wasmHooks *WasmHooks + markerHooks *MarkerHooks + SendPacketPreProcessors []types.PreSendPacketDataProcessingFn +} + +func NewIbcHooks(cdc codec.BinaryCodec, ibcHooksKeeper *keeper.Keeper, ibcKeeper *ibckeeper.Keeper, wasmHooks *WasmHooks, markerHooks *MarkerHooks, preSendPacketDataProcessingFns []types.PreSendPacketDataProcessingFn) IbcHooks { + return IbcHooks{ + cdc: cdc, + ibcKeeper: ibcKeeper, + ibcHooksKeeper: ibcHooksKeeper, + wasmHooks: wasmHooks, + markerHooks: markerHooks, + SendPacketPreProcessors: preSendPacketDataProcessingFns, + } +} + +// ProperlyConfigured returns false if either wasm or marker hooks are configured incorrectly +func (h IbcHooks) ProperlyConfigured() bool { + return h.wasmHooks.ProperlyConfigured() && h.markerHooks.ProperlyConfigured() && h.ibcHooksKeeper != nil +} + +// GetSendPacketPreProcessors returns a list of ordered functions to be executed before ibc's SendPacket function in middleware +func (h IbcHooks) GetSendPacketPreProcessors() []types.PreSendPacketDataProcessingFn { + return h.SendPacketPreProcessors +} + +// OnRecvPacketOverride executes wasm or marker hooks for Ics20 packets, if not ics20 packet it will continue to process packet with no override +func (h IbcHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, packet channeltypes.Packet, relayer sdktypes.AccAddress) ibcexported.Acknowledgement { + if !h.ProperlyConfigured() { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + + isIcs20, _ := isIcs20Packet(packet.GetData()) + if !isIcs20 { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + + if err := h.markerHooks.AddUpdateMarker(ctx, packet, h.ibcKeeper); err != nil { + return NewEmitErrorAcknowledgement(ctx, types.ErrMarkerError, err.Error()) + } + return h.wasmHooks.OnRecvPacketOverride(im, ctx, packet, relayer) +} + +// SendPacketAfterHook function is executed after ibc's SendPacket +// Note: processData is a JSON object of state data from the PreSendPacketDataProcessingFns +func (h IbcHooks) SendPacketAfterHook(ctx sdktypes.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + sequence uint64, + err error, + processData map[string]interface{}, +) { + h.wasmHooks.SendPacketAfterHook(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, sequence, err, processData) +} + +// OnTimeoutPacketOverride returns impl of wasm hook for OnTimeoutPacketOverride +func (h IbcHooks) OnTimeoutPacketOverride(im IBCMiddleware, ctx sdktypes.Context, packet channeltypes.Packet, relayer sdktypes.AccAddress) error { + return h.wasmHooks.OnTimeoutPacketOverride(im, ctx, packet, relayer) +} + +// OnAcknowledgementPacketOverride returns impl of wasm OnAcknowledgementPacketOverride +func (h IbcHooks) OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdktypes.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdktypes.AccAddress) error { + return h.wasmHooks.OnAcknowledgementPacketOverride(im, ctx, packet, acknowledgement, relayer) +} diff --git a/x/ibchooks/ibc_middleware_test.go b/x/ibchooks/ibc_middleware_test.go index ab02e3c4ea..1c55a7cc39 100644 --- a/x/ibchooks/ibc_middleware_test.go +++ b/x/ibchooks/ibc_middleware_test.go @@ -7,10 +7,13 @@ import ( "github.com/provenance-io/provenance/app" "github.com/provenance-io/provenance/internal/pioconfig" - "github.com/provenance-io/provenance/testutil" + testutil "github.com/provenance-io/provenance/testutil/ibc" "github.com/provenance-io/provenance/x/ibchooks" "github.com/provenance-io/provenance/x/ibchooks/keeper" - "github.com/provenance-io/provenance/x/ibchooks/osmoutils" + "github.com/provenance-io/provenance/x/marker/types" + markertypes "github.com/provenance-io/provenance/x/marker/types" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/stretchr/testify/suite" @@ -237,6 +240,7 @@ func (suite *HooksTestSuite) receivePacketWithSequence(receiver, memo string, pr // recv in chain a res, err := suite.path.EndpointA.RecvPacketWithResult(packet) + suite.Require().NoError(err) // get the ack from the chain a's response ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) @@ -250,10 +254,10 @@ func (suite *HooksTestSuite) receivePacketWithSequence(receiver, memo string, pr func (suite *HooksTestSuite) TestRecvTransferWithMetadata() { // Setup contract - suite.chainA.StoreContractCodeDirect(&suite.Suite, "./bytecode/echo.wasm") - addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1) - - ackBytes := suite.receivePacket(addr.String(), fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"echo": {"msg": "test"} } } }`, addr)) + codeID := suite.chainA.StoreContractEchoDirect(&suite.Suite) + addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", codeID) + memo := fmt.Sprintf(`{"marker":{},"wasm":{"contract":"%s","msg":{"echo":{"msg":"test"}}}}`, addr) + ackBytes := suite.receivePacket(addr.String(), memo) ackStr := string(ackBytes) fmt.Println(ackStr) var ack map[string]string // This can't be unmarshalled to Acknowledgement because it's fetched from the events @@ -266,16 +270,16 @@ func (suite *HooksTestSuite) TestRecvTransferWithMetadata() { // After successfully executing a wasm call, the contract should have the funds sent via IBC func (suite *HooksTestSuite) TestFundsAreTransferredToTheContract() { // Setup contract - suite.chainA.StoreContractCodeDirect(&suite.Suite, "./bytecode/echo.wasm") - addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1) + codeID := suite.chainA.StoreContractEchoDirect(&suite.Suite) + addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", codeID) // Check that the contract has no funds - localDenom := osmoutils.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) + localDenom := ibchooks.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) balance := suite.chainA.GetProvenanceApp().BankKeeper.GetBalance(suite.chainA.GetContext(), addr, localDenom) suite.Require().Equal(sdk.NewInt(0), balance.Amount) // Execute the contract via IBC - ackBytes := suite.receivePacket(addr.String(), fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"echo": {"msg": "test"} } } }`, addr)) + ackBytes := suite.receivePacket(addr.String(), fmt.Sprintf(`{"marker":{},"wasm":{"contract":"%s","msg":{"echo":{"msg":"test"}}}}`, addr)) ackStr := string(ackBytes) fmt.Println(ackStr) var ack map[string]string // This can't be unmarshalled to Acknowledgement because it's fetched from the events @@ -292,16 +296,16 @@ func (suite *HooksTestSuite) TestFundsAreTransferredToTheContract() { // If the wasm call wails, the contract acknowledgement should be an error and the funds returned func (suite *HooksTestSuite) TestFundsAreReturnedOnFailedContractExec() { // Setup contract - suite.chainA.StoreContractCodeDirect(&suite.Suite, "./bytecode/echo.wasm") - addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1) + codeID := suite.chainA.StoreContractEchoDirect(&suite.Suite) + addr := suite.chainA.InstantiateContract(&suite.Suite, "{}", codeID) // Check that the contract has no funds - localDenom := osmoutils.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) + localDenom := ibchooks.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) balance := suite.chainA.GetProvenanceApp().BankKeeper.GetBalance(suite.chainA.GetContext(), addr, localDenom) suite.Require().Equal(sdk.NewInt(0), balance.Amount) // Execute the contract via IBC with a message that the contract will reject - ackBytes := suite.receivePacket(addr.String(), fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"not_echo": {"msg": "test"} } } }`, addr)) + ackBytes := suite.receivePacket(addr.String(), fmt.Sprintf(`{"marker":{},"wasm":{"contract":"%s","msg":{"not_echo":{"msg":"test"}}}}`, addr)) ackStr := string(ackBytes) fmt.Println(ackStr) var ack map[string]string // This can't be unmarshalled to Acknowledgement because it's fetched from the events @@ -315,63 +319,22 @@ func (suite *HooksTestSuite) TestFundsAreReturnedOnFailedContractExec() { suite.Require().Equal(sdk.NewInt(0), balance.Amount) } -func (suite *HooksTestSuite) TestPacketsThatShouldBeSkipped() { - var sequence uint64 - receiver := suite.chainB.SenderAccount.GetAddress().String() - - testCases := []struct { - memo string - expPassthrough bool - }{ - {"", true}, - {"{01]", true}, // bad json - {"{}", true}, - {`{"something": ""}`, true}, - {`{"wasm": "test"}`, false}, - {`{"wasm": []`, true}, // invalid top level JSON - {`{"wasm": {}`, true}, // invalid top level JSON - {`{"wasm": []}`, false}, - {`{"wasm": {}}`, false}, - {`{"wasm": {"contract": "something"}}`, false}, - {`{"wasm": {"contract": "osmo1clpqr4nrk4khgkxj78fcwwh6dl3uw4epasmvnj"}}`, false}, - {`{"wasm": {"msg": "something"}}`, false}, - // invalid receiver - {`{"wasm": {"contract": "osmo1clpqr4nrk4khgkxj78fcwwh6dl3uw4epasmvnj", "msg": {}}}`, false}, - // msg not an object - {fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": 1}}`, receiver), false}, - } - - for _, tc := range testCases { - ackBytes := suite.receivePacketWithSequence(receiver, tc.memo, sequence) - ackStr := string(ackBytes) - fmt.Println(ackStr) - var ack map[string]string // This can't be unmarshalled to Acknowledgement because it's fetched from the events - err := json.Unmarshal(ackBytes, &ack) - suite.Require().NoError(err) - if tc.expPassthrough { - suite.Require().Equal("AQ==", ack["result"], tc.memo) - } else { - suite.Require().Contains(ackStr, "error", tc.memo) - } - sequence += 1 - } -} - // After successfully executing a wasm call, the contract should have the funds sent via IBC func (suite *HooksTestSuite) TestFundTracking() { // Setup contract - suite.chainA.StoreContractCodeDirect(&suite.Suite, "./bytecode/counter.wasm") - addr := suite.chainA.InstantiateContract(&suite.Suite, `{"count": 0}`, 1) + codeID := suite.chainA.StoreContractCounterDirect(&suite.Suite) + addr := suite.chainA.InstantiateContract(&suite.Suite, `{"count": 0}`, codeID) // Check that the contract has no funds - localDenom := osmoutils.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) + localDenom := ibchooks.MustExtractDenomFromPacketOnRecv(suite.makeMockPacket("", "", 0)) balance := suite.chainA.GetProvenanceApp().BankKeeper.GetBalance(suite.chainA.GetContext(), addr, localDenom) suite.Require().Equal(sdk.NewInt(0), balance.Amount) + memo := fmt.Sprintf(`{"marker":{},"wasm":{"contract":"%s","msg":{"increment":{}}}}`, addr) + // Execute the contract via IBC suite.receivePacket( - addr.String(), - fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"increment": {} } } }`, addr)) + addr.String(), memo) prefix := sdk.GetConfig().GetBech32AccountAddrPrefix() senderLocalAcc, err := keeper.DeriveIntermediateSender("channel-0", suite.chainB.SenderAccount.GetAddress().String(), prefix) @@ -389,7 +352,7 @@ func (suite *HooksTestSuite) TestFundTracking() { suite.receivePacketWithSequence( addr.String(), - fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"increment": {} } } }`, addr), 1) + memo, 1) state = suite.chainA.QueryContract( &suite.Suite, addr, @@ -486,8 +449,8 @@ func (suite *HooksTestSuite) FullSend(msg sdk.Msg, direction Direction) (*sdk.Re } func (suite *HooksTestSuite) TestAcks() { - suite.chainA.StoreContractCodeDirect(&suite.Suite, "./bytecode/counter.wasm") - addr := suite.chainA.InstantiateContract(&suite.Suite, `{"count": 0}`, 1) + codeID := suite.chainA.StoreContractCounterDirect(&suite.Suite) + addr := suite.chainA.InstantiateContract(&suite.Suite, `{"count": 0}`, codeID) // Generate swap instructions for the contract callbackMemo := fmt.Sprintf(`{"ibc_callback":"%s"}`, addr) @@ -510,9 +473,61 @@ func (suite *HooksTestSuite) TestAcks() { } func (suite *HooksTestSuite) TestSendWithoutMemo() { + chainA := suite.chainA.GetProvenanceApp() + chainASenderAddress := suite.chainA.SenderAccount.GetAddress() + chainBSenderAddress := suite.chainB.SenderAccount.GetAddress() + // Sending a packet without memo to ensure that the ibc_callback middleware doesn't interfere with a regular send - transferMsg := NewMsgTransfer(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000)), suite.chainA.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String(), "") + transferMsg := NewMsgTransfer(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000)), chainASenderAddress.String(), chainBSenderAddress.String(), "") _, _, ack, err := suite.FullSend(transferMsg, AtoB) - suite.Require().NoError(err) + suite.Require().NoError(err, "FullSend()") + suite.Require().Contains(ack, "result") + prefixedDenom := transfertypes.GetDenomPrefix(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID) + sdk.DefaultBondDenom + denom := transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + marker, err := suite.chainB.GetProvenanceApp().MarkerKeeper.GetMarkerByDenom(suite.chainB.GetContext(), denom) + suite.Require().NoError(err, "GetMarkerByDenom()") + suite.Require().Equal(marker.GetDenom(), denom) + + transferMsg = NewMsgTransfer(sdk.NewCoin(denom, sdk.NewInt(100)), chainBSenderAddress.String(), chainASenderAddress.String(), "") + _, _, ack, err = suite.FullSend(transferMsg, BtoA) + suite.Require().NoError(err, "FullSend()") suite.Require().Contains(ack, "result") + stakeAddr := markertypes.MustGetMarkerAddress(sdk.DefaultBondDenom) + marker, err = chainA.MarkerKeeper.GetMarker(suite.chainA.GetContext(), stakeAddr) + suite.Require().NoError(err, "GetMarker()") + suite.Require().Nil(marker, "Should not create marker on chain A") + + hotdogs := "hotdogs" + rv := markertypes.NewMarkerAccount( + chainA.AccountKeeper.NewAccountWithAddress(suite.chainA.GetContext(), markertypes.MustGetMarkerAddress(hotdogs)).(*authtypes.BaseAccount), + sdk.NewInt64Coin(hotdogs, 10000), + suite.chainA.SenderAccount.GetAddress(), + []types.AccessGrant{ + {Address: chainASenderAddress.String(), Permissions: markertypes.AccessList{markertypes.Access_Transfer, markertypes.Access_Withdraw}}, + }, + types.StatusProposed, + types.MarkerType_RestrictedCoin, + true, // supply fixed + true, // allow gov + false, // no force transfer + []string{}, + ) + + chainA.MarkerKeeper.AddSetNetAssetValues(suite.chainA.GetContext(), rv, []types.NetAssetValue{types.NewNetAssetValue(sdk.NewInt64Coin(types.UsdDenom, int64(1000)), 1)}, markertypes.ModuleName) + suite.Require().NoError(err, "chainA AddSetNetAssetValues()") + err = chainA.MarkerKeeper.AddFinalizeAndActivateMarker(suite.chainA.GetContext(), rv) + suite.Require().NoError(err, "chainA AddFinalizeAndActivateMarker()") + err = chainA.MarkerKeeper.WithdrawCoins(suite.chainA.GetContext(), chainASenderAddress, chainASenderAddress, hotdogs, sdk.NewCoins(sdk.NewInt64Coin(hotdogs, 55))) + suite.Require().NoError(err, "chainA WithdrawCoins()") + + transferMsg = NewMsgTransfer(sdk.NewCoin(hotdogs, sdk.NewInt(55)), chainASenderAddress.String(), chainBSenderAddress.String(), "") + _, _, ack, err = suite.FullSend(transferMsg, AtoB) + suite.Require().NoError(err, "AtoB FullSend()") + suite.Require().Contains(ack, "result", "FullSend() ack check") + prefixedDenom = transfertypes.GetDenomPrefix(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID) + hotdogs + denom = transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + marker, err = suite.chainB.GetProvenanceApp().MarkerKeeper.GetMarkerByDenom(suite.chainB.GetContext(), denom) + suite.Require().NoError(err, "chainB GetMarkerByDenom()") + suite.Require().Equal(marker.GetDenom(), denom) + suite.Require().True(marker.HasAccess(chainASenderAddress.String(), markertypes.Access_Transfer), "ChainB Ibc marker should have transfer rights added") } diff --git a/x/ibchooks/ics4_middleware.go b/x/ibchooks/ics4_middleware.go index ae02231fed..f746767ba4 100644 --- a/x/ibchooks/ics4_middleware.go +++ b/x/ibchooks/ics4_middleware.go @@ -41,10 +41,20 @@ func (i ICS4Middleware) SendPacket( hook.SendPacketBeforeHook(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } + processingStateData := make(map[string]interface{}) + if hook, ok := i.Hooks.(SendPacketPreProcessors); ok { + for _, packetDataProcessingFn := range hook.GetSendPacketPreProcessors() { + data, err = packetDataProcessingFn(ctx, data, processingStateData) + if err != nil { + return 0, err + } + } + } + seq, err := i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) if hook, ok := i.Hooks.(SendPacketAfterHooks); ok { - hook.SendPacketAfterHook(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, seq, err) + hook.SendPacketAfterHook(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, seq, err, processingStateData) } return seq, err diff --git a/x/ibchooks/keeper/keeper.go b/x/ibchooks/keeper/keeper.go index 849c0b9f68..0b43bfa67f 100644 --- a/x/ibchooks/keeper/keeper.go +++ b/x/ibchooks/keeper/keeper.go @@ -19,7 +19,6 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" - "github.com/provenance-io/provenance/x/ibchooks/osmoutils" "github.com/provenance-io/provenance/x/ibchooks/types" ) @@ -237,7 +236,7 @@ func (k Keeper) EmitIBCAck(ctx sdk.Context, sender, channel string, packetSequen newAck = channeltypes.NewResultAcknowledgement(jsonAck) case "ack_error": packet = ack.AckError.Packet - newAck = osmoutils.NewSuccessAckRepresentingAnError(ctx, types.ErrAckFromContract, []byte(ack.AckError.ErrorResponse), ack.AckError.ErrorDescription) + newAck = NewSuccessAckError(ctx, types.ErrAckFromContract, []byte(ack.AckError.ErrorResponse), ack.AckError.ErrorDescription) default: return nil, sdkerrors.Wrap(err, "could not unmarshal into IBCAckResponse or IBCAckError") } @@ -274,3 +273,25 @@ func hashPacket(packet channeltypes.Packet) (string, error) { packetHash := tmhash.Sum(bz) return hex.EncodeToString(packetHash), nil } + +// NewSuccessAckError creates a new success acknowledgement that represents an error. +// This is useful for notifying the sender that an error has occurred in a way that does not allow +// the received tokens to be reverted (which means they shouldn't be released by the sender's ics20 escrow) +func NewSuccessAckError(ctx sdk.Context, err error, errorContent []byte, errorContexts ...string) channeltypes.Acknowledgement { + logger := ctx.Logger().With("module", "ibc-acknowledgement-error") + + attributes := make([]sdk.Attribute, len(errorContexts)+1) + attributes[0] = sdk.NewAttribute("error", err.Error()) + for i, s := range errorContexts { + attributes[i+1] = sdk.NewAttribute("error-context", s) + logger.Error(fmt.Sprintf("error-context: %v", s)) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + "ibc-acknowledgement-error", + attributes..., + ), + }) + return channeltypes.NewResultAcknowledgement(errorContent) +} diff --git a/x/ibchooks/marker_hooks.go b/x/ibchooks/marker_hooks.go new file mode 100644 index 0000000000..d01aa14c38 --- /dev/null +++ b/x/ibchooks/marker_hooks.go @@ -0,0 +1,243 @@ +package ibchooks + +import ( + "encoding/json" + "strconv" + "strings" + + sdkerrors "cosmossdk.io/errors" + + sdktypes "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + "github.com/cosmos/ibc-go/v6/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper" + tendermintclient "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint/types" + + "github.com/provenance-io/provenance/x/ibchooks/types" + markerkeeper "github.com/provenance-io/provenance/x/marker/keeper" + markertypes "github.com/provenance-io/provenance/x/marker/types" +) + +type MarkerHooks struct { + MarkerKeeper *markerkeeper.Keeper +} + +func NewMarkerHooks(markerkeeper *markerkeeper.Keeper) MarkerHooks { + return MarkerHooks{ + MarkerKeeper: markerkeeper, + } +} + +// ProperlyConfigured returns false when marker hooks are configured incorrectly +func (h MarkerHooks) ProperlyConfigured() bool { + return h.MarkerKeeper != nil +} + +// AddUpdateMarker will add or update ibc Marker with transfer authorities +func (h MarkerHooks) AddUpdateMarker(ctx sdktypes.Context, packet exported.PacketI, ibcKeeper *ibckeeper.Keeper) error { + var data transfertypes.FungibleTokenPacketData + if err := json.Unmarshal(packet.GetData(), &data); err != nil { + return err + } + ibcDenom := MustExtractDenomFromPacketOnRecv(packet) + if !strings.HasPrefix(ibcDenom, "ibc/") { + return nil + } + + markerAddress, err := markertypes.MarkerAddress(ibcDenom) + if err != nil { + return err + } + marker, err := h.MarkerKeeper.GetMarker(ctx, markerAddress) + if err != nil { + return err + } + var transferAuthAddrs []sdktypes.AccAddress + transferAuthAddrs, coinType, allowForceTransfer, err := ProcessMarkerMemo(data.GetMemo()) + if err != nil { + return err + } + + if marker != nil { + return h.updateMarkerProperties(ctx, transferAuthAddrs, marker, allowForceTransfer) + } + return h.createNewIbcMarker(ctx, data, ibcDenom, coinType, transferAuthAddrs, allowForceTransfer, packet, ibcKeeper) +} + +func (h MarkerHooks) updateMarkerProperties(ctx sdktypes.Context, transferAuthAddrs []sdktypes.AccAddress, marker markertypes.MarkerAccountI, allowForceTransfer bool) error { + if marker.GetMarkerType() != markertypes.MarkerType_RestrictedCoin { + return nil + } + if err := ResetMarkerAccessGrants(transferAuthAddrs, marker); err != nil { + return err + } + marker.SetAllowForcedTransfer(allowForceTransfer) + h.MarkerKeeper.SetMarker(ctx, marker) + return nil +} + +// createNewIbcMarker creates a new marker account for ibc token +func (h MarkerHooks) createNewIbcMarker(ctx sdktypes.Context, data transfertypes.FungibleTokenPacketData, ibcDenom string, coinType markertypes.MarkerType, transferAuthAddrs []sdktypes.AccAddress, allowForceTransfer bool, packet exported.PacketI, ibcKeeper *ibckeeper.Keeper) error { + amount, err := strconv.ParseInt(data.Amount, 10, 64) + if err != nil { + return err + } + marker := markertypes.NewMarkerAccount( + authtypes.NewBaseAccountWithAddress(markertypes.MustGetMarkerAddress(ibcDenom)), + sdktypes.NewInt64Coin(ibcDenom, amount), + nil, + nil, + markertypes.StatusActive, + coinType, + false, // supply fixed + false, // allow gov + allowForceTransfer, + []string{}, + ) + if err = ResetMarkerAccessGrants(transferAuthAddrs, marker); err != nil { + return err + } + if err = h.MarkerKeeper.AddMarkerAccount(ctx, marker); err != nil { + return err + } + return h.addDenomMetaData(ctx, packet, ibcKeeper, ibcDenom, data) +} + +// addDenomMetaData adds denom metadata for ibc token +func (h MarkerHooks) addDenomMetaData(ctx sdktypes.Context, packet exported.PacketI, ibcKeeper *ibckeeper.Keeper, ibcDenom string, data transfertypes.FungibleTokenPacketData) error { + chainID := h.GetChainID(ctx, packet, ibcKeeper) + markerMetadata := banktypes.Metadata{ + Base: ibcDenom, + Name: chainID + "/" + data.Denom, + Display: chainID + "/" + data.Denom, + Description: data.Denom + " from chain " + chainID, + } + return h.MarkerKeeper.SetDenomMetaData(ctx, markerMetadata, authtypes.NewModuleAddress(types.ModuleName)) +} + +// GetChainID returns the source chain id from packet for `07-tendermint` client connection or returns `unknown` +func (h MarkerHooks) GetChainID(ctx sdktypes.Context, packet exported.PacketI, ibcKeeper *ibckeeper.Keeper) string { + chainID := "unknown" + channel, found := ibcKeeper.ChannelKeeper.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return chainID + } + connectionEnd, found := ibcKeeper.ConnectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return chainID + } + clientState, found := ibcKeeper.ClientKeeper.GetClientState(ctx, connectionEnd.GetClientID()) + if !found { + return chainID + } + if clientState.ClientType() == "07-tendermint" { + tmClientState, ok := clientState.(*tendermintclient.ClientState) + if ok { + chainID = tmClientState.ChainId + } + } + return chainID +} + +// ResetMarkerAccessGrants removes all current access grants from marker and adds new transfer grants for transferAuths +func ResetMarkerAccessGrants(transferAuths []sdktypes.AccAddress, marker markertypes.MarkerAccountI) error { + for _, currentAuth := range marker.GetAccessList() { + if err := marker.RevokeAccess(currentAuth.GetAddress()); err != nil { + return err + } + } + for _, transfAuth := range transferAuths { + if err := marker.GrantAccess(markertypes.NewAccessGrant(transfAuth, markertypes.AccessList{markertypes.Access_Transfer})); err != nil { + return err + } + } + return nil +} + +// ProcessMarkerMemo extracts the list of transfer auth address from marker part of packet memo +func ProcessMarkerMemo(memo string) ([]sdktypes.AccAddress, markertypes.MarkerType, bool, error) { + found, jsonObject := jsonStringHasKey(memo, "marker") + if !found { + return []sdktypes.AccAddress{}, markertypes.MarkerType_Coin, false, nil + } + jsonBytes, err := json.Marshal(jsonObject["marker"]) + if err != nil { + return nil, markertypes.MarkerType_Unknown, false, err + } + + var markerMemo types.MarkerPayload + err = json.Unmarshal(jsonBytes, &markerMemo) + if err != nil { + return nil, markertypes.MarkerType_Unknown, false, err + } + if markerMemo.TransferAuths == nil { + return []sdktypes.AccAddress{}, markertypes.MarkerType_Coin, false, nil + } + + transferAuths := make([]sdktypes.AccAddress, len(markerMemo.TransferAuths)) + for i, addr := range markerMemo.TransferAuths { + accAddr, err := sdktypes.AccAddressFromBech32(addr) + if err != nil { + return nil, markertypes.MarkerType_Unknown, false, err + } + transferAuths[i] = accAddr + } + return transferAuths, markertypes.MarkerType_RestrictedCoin, markerMemo.AllowForceTransfer, nil +} + +// SetupMarkerMemoFn processes a ics20 packets memo part to have `marker` setup information for receiving chain +func (h MarkerHooks) SetupMarkerMemoFn( + ctx sdktypes.Context, + data []byte, + _ map[string]interface{}, +) ([]byte, error) { + isIcs20, ics20Packet := isIcs20Packet(data) + if !isIcs20 { + return data, nil + } + + memoAsJSON := SanitizeMemo(ics20Packet.GetMemo()) + + markerAddress, err := markertypes.MarkerAddress(ics20Packet.Denom) + if err != nil { + return nil, err + } + marker, err := h.MarkerKeeper.GetMarker(ctx, markerAddress) + if err != nil { + return nil, err + } + memoAsJSON["marker"], err = CreateMarkerMemo(marker) + if err != nil { + return nil, sdkerrors.Wrap(err, "ics20data marshall error") + } + memo, err := json.Marshal(memoAsJSON) + if err != nil { + return nil, sdkerrors.Wrap(err, "ics20data marshall error") + } + ics20Packet.Memo = string(memo) + + return ics20Packet.GetBytes(), nil +} + +// SanitizeMemo returns a keyed json object for memo +func SanitizeMemo(memo string) map[string]interface{} { + jsonObject := make(map[string]interface{}) + if len(memo) != 0 { + err := json.Unmarshal([]byte(memo), &jsonObject) + if err != nil { + jsonObject["memo"] = memo + } + } + return jsonObject +} + +// CreateMarkerMemo returns a json memo for marker +func CreateMarkerMemo(marker markertypes.MarkerAccountI) (interface{}, error) { + if marker == nil || marker.GetMarkerType() != markertypes.MarkerType_RestrictedCoin { + return make(map[string]interface{}), nil + } + transferAuthAddrs := marker.AddressListForPermission(markertypes.Access_Transfer) + return types.NewMarkerPayload(transferAuthAddrs, marker.AllowsForcedTransfer()), nil +} diff --git a/x/ibchooks/marker_hooks_test.go b/x/ibchooks/marker_hooks_test.go new file mode 100644 index 0000000000..bfc1f88916 --- /dev/null +++ b/x/ibchooks/marker_hooks_test.go @@ -0,0 +1,379 @@ +package ibchooks_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/provenance-io/provenance/app" + testutil "github.com/provenance-io/provenance/testutil/ibc" + "github.com/provenance-io/provenance/x/ibchooks" + "github.com/provenance-io/provenance/x/marker/types" + markertypes "github.com/provenance-io/provenance/x/marker/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v6/testing" +) + +type MarkerHooksTestSuite struct { + suite.Suite + + App *app.App + Ctx sdk.Context + QueryHelper *baseapp.QueryServiceTestHelper + TestAccs []sdk.AccAddress + + coordinator *ibctesting.Coordinator + + chainA *testutil.TestChain + chainB *testutil.TestChain + + path *ibctesting.Path +} + +func init() { + ibctesting.DefaultTestingAppInit = SetupSimApp +} + +func (suite *MarkerHooksTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = &testutil.TestChain{ + TestChain: suite.coordinator.GetChain(ibctesting.GetChainID(1)), + } + suite.chainB = &testutil.TestChain{ + TestChain: suite.coordinator.GetChain(ibctesting.GetChainID(2)), + } + suite.path = NewTransferPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(suite.path) +} + +func TestMarkerHooksTestSuite(t *testing.T) { + suite.Run(t, new(MarkerHooksTestSuite)) +} + +func (suite *MarkerHooksTestSuite) makeMockPacket(denom, receiver, memo string, prevSequence uint64) channeltypes.Packet { + packetData := transfertypes.FungibleTokenPacketData{ + Denom: denom, + Amount: "1", + Sender: suite.chainB.SenderAccount.GetAddress().String(), + Receiver: receiver, + Memo: memo, + } + + return channeltypes.NewPacket( + packetData.GetBytes(), + prevSequence+1, + suite.path.EndpointB.ChannelConfig.PortID, + suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, + suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) +} + +func (suite *MarkerHooksTestSuite) TestAddUpdateMarker() { + address1 := sdk.AccAddress("address1") + address2 := sdk.AccAddress("address2") + markerHooks := ibchooks.NewMarkerHooks(&suite.chainA.GetProvenanceApp().MarkerKeeper) + testCases := []struct { + name string + denom string + memo string + expErr string + expIbcDenom string + expTransAuths []sdk.AccAddress + }{ + { + name: "successfully process with empty memo", + denom: "fiftyfivehamburgers", + memo: "", + expErr: "", + expIbcDenom: "ibc/F3F4565153F3DD64470F075D6D6B1CB183F06EB55B287CCD0D3506277A03DE8E", + }, + { + name: "successfully process with non json memo", + denom: "fiftyfivehamburgers", + memo: "55 burger 55 fries...", + expErr: "", + expIbcDenom: "ibc/F3F4565153F3DD64470F075D6D6B1CB183F06EB55B287CCD0D3506277A03DE8E", + }, + { + name: "successfully process with non json marker part memo", + denom: "fiftyfivehamburgers", + memo: `{"marker":{random},"wasm":{"contract":"%1234","msg":{"echo":{"msg":"test"}}}}`, + expErr: "", + expIbcDenom: "ibc/F3F4565153F3DD64470F075D6D6B1CB183F06EB55B287CCD0D3506277A03DE8E", + }, + { + name: "successfully process with transfer auths", + denom: "fiftyfivefries", + memo: fmt.Sprintf(`{"marker":{"transfer-auths":["%s", "%s"]}}`, address1.String(), address2.String()), + expErr: "", + expIbcDenom: "ibc/1B3A5773661E8A6B9F6BB407979B5933C2FA792DF24ED2A40B028C90277B0C22", + expTransAuths: []sdk.AccAddress{address1, address2}, + }, + { + name: "fail invalid json", + denom: "fiftyfivetacos", + memo: fmt.Sprintf(`{"marker":{"transfer-auths":"%s"}}`, address1.String()), + expErr: "json: cannot unmarshal string into Go struct field MarkerPayload.transfer-auths of type []string", + }, + } + for _, tc := range testCases { + suite.T().Run(tc.name, func(t *testing.T) { + packet := suite.makeMockPacket(tc.denom, "", tc.memo, 0) + err := markerHooks.AddUpdateMarker(suite.chainA.GetContext(), packet, suite.chainA.GetProvenanceApp().IBCKeeper) + if len(tc.expErr) > 0 { + assert.EqualError(t, err, tc.expErr, "ProcessMarkerMemo() error") + } else { + assert.NoError(t, err) + marker, err := suite.chainA.GetProvenanceApp().MarkerKeeper.GetMarkerByDenom(suite.chainA.GetContext(), tc.expIbcDenom) + require.NoError(t, err, "GetMarkerByDenom should find "+tc.expErr) + assert.Equal(t, tc.expIbcDenom, marker.GetDenom(), "Marker Denom should be ibc denom") + metadata, found := suite.chainA.GetProvenanceApp().BankKeeper.GetDenomMetaData(suite.chainA.GetContext(), tc.expIbcDenom) + require.True(t, found, "GetDenomMetaData() not found for "+tc.expErr) + assert.Equal(t, marker.GetDenom(), metadata.Base, "Metadata Base should equal marker denom") + assert.Equal(t, "testchain2/"+tc.denom, metadata.Name, "Metadata Name should be chainid/denom") + assert.Equal(t, "testchain2/"+tc.denom, metadata.Display, "Metadata Display should be chainid/denom") + assert.Equal(t, tc.denom+" from chain testchain2", metadata.Description, "Metadata Description is incorrect") + assert.Len(t, marker.GetAccessList(), len(tc.expTransAuths), "Resulting access list does not equal expect length") + for _, access := range marker.GetAccessList() { + assert.Len(t, access.GetAccessList(), 1, "Expecting permissions list to only one item") + assert.Equal(t, access.GetAccessList()[0], markertypes.Access_Transfer, "Expecting permissions to be transfer") + assert.Contains(t, tc.expTransAuths, sdk.MustAccAddressFromBech32(access.Address), "Actual list does not contain required address") + } + } + }) + } +} + +func (suite *MarkerHooksTestSuite) TestProcessMarkerMemo() { + address1 := sdk.AccAddress("address1") + address2 := sdk.AccAddress("address2") + testCases := []struct { + name string + memo string + expAddresses []sdk.AccAddress + expMarkerType markertypes.MarkerType + expAllowForceTransfer bool + expErr string + }{ + { + name: "successfully process with non json memo", + memo: `{"marker":{random},"wasm":{"contract":"%1234","msg":{"echo":{"msg":"test"}}}}`, + expAddresses: []sdk.AccAddress{}, + expMarkerType: markertypes.MarkerType_Coin, + expErr: "", + }, + { + name: "successfully process marker ignore unknown property", + memo: `{"marker":{"test":"test"},"wasm":{"contract":"%1234","msg":{"echo":{"msg":"test"}}}}`, + expAddresses: []sdk.AccAddress{}, + expMarkerType: markertypes.MarkerType_Coin, + expErr: "", + }, + { + name: "transfer auth in correct type", + memo: `{"marker":{"transfer-auths":"incorrect data type"}}`, + expAddresses: []sdk.AccAddress{}, + expMarkerType: markertypes.MarkerType_Coin, + expErr: "json: cannot unmarshal string into Go struct field MarkerPayload.transfer-auths of type []string", + }, + { + name: "transfer auth in correct address bech32 value", + memo: `{"marker":{"transfer-auths":["invalidbech32"]}}`, + expAddresses: []sdk.AccAddress{}, + expMarkerType: markertypes.MarkerType_Coin, + expErr: "decoding bech32 failed: invalid separator index -1", + }, + { + name: "transfer auth in correct type", + memo: fmt.Sprintf(`{"marker":{"transfer-auths":["%s", "%s"]}}`, address1.String(), address2.String()), + expAddresses: []sdk.AccAddress{address2, address1}, + expMarkerType: markertypes.MarkerType_RestrictedCoin, + expErr: "", + }, + } + + for _, tc := range testCases { + suite.T().Run(tc.name, func(t *testing.T) { + actualAddrs, actualMarkerType, actualAllowForceTransfer, err := ibchooks.ProcessMarkerMemo(tc.memo) + if len(tc.expErr) > 0 { + assert.EqualError(t, err, tc.expErr, "ProcessMarkerMemo() error") + } else { + require.NoError(t, err) + assert.Equal(t, tc.expMarkerType, actualMarkerType, "Actual Marker type is incorrect") + assert.Len(t, actualAddrs, len(tc.expAddresses), "Actual and expect address list must have same amount of elements") + for _, addr := range tc.expAddresses { + assert.Contains(t, actualAddrs, addr, "Actual list does not contain required address") + + } + assert.Equal(t, tc.expAllowForceTransfer, actualAllowForceTransfer, "Actual allow force transfer is incorrect") + } + }) + } +} + +func (suite *MarkerHooksTestSuite) TestResetMarkerAccessGrants() { + address1 := sdk.AccAddress("address1") + address2 := sdk.AccAddress("address2") + address3 := sdk.AccAddress("address3") + testCases := []struct { + name string + transferAuths []sdk.AccAddress + markerAcct markertypes.MarkerAccount + expErr string + }{ + { + name: "successfully reset marker access grants and remove all others", + transferAuths: []sdk.AccAddress{address1, address2}, + markerAcct: *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{*types.NewAccessGrant(address1, []types.Access{types.Access_Burn}), *types.NewAccessGrant(address1, []types.Access{types.Access_Admin})}), + expErr: "", + }, + { + name: "successfully reset marker access grants and remove all others", + transferAuths: []sdk.AccAddress{address1, address2}, + markerAcct: *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{}), + expErr: "", + }, + { + name: "successfully reset marker access grants and remove other transfer grant", + transferAuths: []sdk.AccAddress{address1, address2}, + markerAcct: *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{*types.NewAccessGrant(address3, []types.Access{types.Access_Transfer})}), + expErr: "", + }, + { + name: "successful with empty transfer auths", + transferAuths: []sdk.AccAddress{}, + markerAcct: *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{}), + expErr: "", + }, + { + name: "successful with nil transfer auths", + transferAuths: nil, + markerAcct: *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{}), + expErr: "", + }, + } + + for _, tc := range testCases { + suite.T().Run(tc.name, func(t *testing.T) { + err := ibchooks.ResetMarkerAccessGrants(tc.transferAuths, &tc.markerAcct) + if len(tc.expErr) > 0 { + assert.EqualError(t, err, tc.expErr, "ResetMarkerAccessGrants() error") + } else { + require.NoError(t, err) + assert.Len(t, tc.markerAcct.GetAccessList(), len(tc.transferAuths), "Resulting access list does not equal expect length") + for _, access := range tc.markerAcct.GetAccessList() { + assert.Len(t, access.GetAccessList(), 1, "Expecting permissions list to only one item") + assert.Equal(t, access.GetAccessList()[0], markertypes.Access_Transfer, "Expecting permissions to be transfer") + assert.Contains(t, tc.transferAuths, sdk.MustAccAddressFromBech32(access.Address), "Actual list does not contain required address") + } + } + }) + } +} + +func (suite *MarkerHooksTestSuite) TestSanitizeMemo() { + testCases := []struct { + name string + memo string + expMemo string + }{ + { + name: "plain text memo", + memo: "plain text user memo", + expMemo: `{"memo":"plain text user memo"}`, + }, + { + name: "mal-formed json should be moved to memo", + memo: `{"marker":{"transfer-auths":["123", "345"]}`, + expMemo: "{\"memo\":\"{\\\"marker\\\":{\\\"transfer-auths\\\":[\\\"123\\\", \\\"345\\\"]}\"}", + }, + { + name: "correct json should not modify memo", + memo: `{"marker":{"transfer-auths":["address"]}}`, + expMemo: `{"marker":{"transfer-auths":["address"]}}`, + }, + { + name: "empty memo", + memo: "", + expMemo: `{}`, + }, + } + for _, tc := range testCases { + suite.T().Run(tc.name, func(t *testing.T) { + actualMemoJSON := ibchooks.SanitizeMemo(tc.memo) + actualMemo, err := json.Marshal(actualMemoJSON) + require.NoError(t, err, "json.Mashal() failed to mashal memo") + assert.Equal(t, tc.expMemo, string(actualMemo), "SanitizeMemo() should have transformed memo to expected memo") + }) + } +} + +func (suite *MarkerHooksTestSuite) TestPreSendPacketDataProcessingFn() { + address1 := sdk.AccAddress("address1") + markerHooks := ibchooks.NewMarkerHooks(&suite.chainA.GetProvenanceApp().MarkerKeeper) + marker1 := *markertypes.NewEmptyMarkerAccount("jackthecat", address1.String(), []types.AccessGrant{*types.NewAccessGrant(address1, []types.Access{types.Access_Transfer}), *types.NewAccessGrant(address1, []types.Access{types.Access_Admin})}) + marker1.MarkerType = markertypes.MarkerType_RestrictedCoin + require.NoError(suite.T(), suite.chainA.GetProvenanceApp().MarkerKeeper.AddMarkerAccount(suite.chainA.GetContext(), &marker1), "AddMarkerAccount() in test setup") + testCases := []struct { + name string + data []byte + expData []byte + expErr string + }{ + { + name: "not a ics20 packet", + data: []byte{0, 12}, + expData: []byte{0, 12}, + }, + { + name: "packet with plain non json memo", + data: suite.makeMockPacket("hotdogs", "recieverAddr", "my memo", 0).Data, + expData: suite.makeMockPacket("hotdogs", "recieverAddr", `{"marker":{},"memo":"my memo"}`, 0).Data, + }, + { + name: "packet with marker json", + data: suite.makeMockPacket("hotdogs", "recieverAddr", `{"marker":{"test":"test"},"wasm":{"contract":"%1234","msg":{"echo":{"msg":"test"}}}}`, 0).Data, + expData: suite.makeMockPacket("hotdogs", "recieverAddr", `{"marker":{},"wasm":{"contract":"%1234","msg":{"echo":{"msg":"test"}}}}`, 0).Data, + }, + { + name: "packet with marker json with non-transfer auth addresses ", + data: suite.makeMockPacket("hotdogs", "recieverAddr", `{"marker":{"transfer-auths":["test"]}}`, 0).Data, + expData: suite.makeMockPacket("hotdogs", "recieverAddr", `{"marker":{}}`, 0).Data, + }, + { + name: "packet with marker json replace transfer auths with marker transfer auths", + data: suite.makeMockPacket("jackthecat", "recieverAddr", `{"marker":{"transfer-auths":["test"]}}`, 0).Data, + expData: suite.makeMockPacket("jackthecat", "recieverAddr", fmt.Sprintf(`{"marker":{"transfer-auths":["%s"],"allow-force-transfer":false}}`, address1.String()), 0).Data, + }, + { + name: "invalid denom should error", + data: suite.makeMockPacket("~~", "recieverAddr", "my memo", 0).Data, + expErr: "invalid denom: ~~", + }, + } + for _, tc := range testCases { + suite.T().Run(tc.name, func(t *testing.T) { + actualData, err := markerHooks.SetupMarkerMemoFn(suite.chainA.GetContext(), tc.data, nil) + if len(tc.expErr) > 0 { + assert.EqualError(t, err, tc.expErr, "PreSendPacketDataProcessingFn() error") + assert.Nil(t, actualData, "PreSendPacketDataProcessingFn() return `data` should be nil") + } else { + require.NoError(t, err, "PreSendPacketDataProcessingFn() was expecting no error") + assert.Equal(t, tc.expData, actualData, "PreSendPacketDataProcessingFn() `data` not expected") + } + }) + } +} diff --git a/x/ibchooks/osmoutils/ibc.go b/x/ibchooks/osmoutils/ibc.go deleted file mode 100644 index 329fd7c05b..0000000000 --- a/x/ibchooks/osmoutils/ibc.go +++ /dev/null @@ -1,91 +0,0 @@ -package osmoutils - -import ( - "encoding/json" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" - ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" -) - -const IbcAcknowledgementErrorType = "ibc-acknowledgement-error" - -// NewEmitErrorAcknowledgement creates a new error acknowledgement after having emitted an event with the -// details of the error. -func NewEmitErrorAcknowledgement(ctx sdk.Context, err error, errorContexts ...string) channeltypes.Acknowledgement { - EmitIBCErrorEvents(ctx, err, errorContexts) - - return channeltypes.NewErrorAcknowledgement(err) -} - -// NewSuccessAckRepresentingAnError creates a new success acknowledgement that represents an error. -// This is useful for notifying the sender that an error has occurred in a way that does not allow -// the received tokens to be reverted (which means they shouldn't be released by the sender's ics20 escrow) -func NewSuccessAckRepresentingAnError(ctx sdk.Context, err error, errorContent []byte, errorContexts ...string) channeltypes.Acknowledgement { - EmitIBCErrorEvents(ctx, err, errorContexts) - - return channeltypes.NewResultAcknowledgement(errorContent) -} - -// EmitIBCErrorEvents Emit and Log errors -func EmitIBCErrorEvents(ctx sdk.Context, err error, errorContexts []string) { - logger := ctx.Logger().With("module", IbcAcknowledgementErrorType) - - attributes := make([]sdk.Attribute, len(errorContexts)+1) - attributes[0] = sdk.NewAttribute("error", err.Error()) - for i, s := range errorContexts { - attributes[i+1] = sdk.NewAttribute("error-context", s) - logger.Error(fmt.Sprintf("error-context: %v", s)) - } - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - IbcAcknowledgementErrorType, - attributes..., - ), - }) -} - -// MustExtractDenomFromPacketOnRecv takes a packet with a valid ICS20 token data in the Data field and returns the -// denom as represented in the local chain. -// If the data cannot be unmarshalled this function will panic -func MustExtractDenomFromPacketOnRecv(packet ibcexported.PacketI) string { - var data transfertypes.FungibleTokenPacketData - if err := json.Unmarshal(packet.GetData(), &data); err != nil { - panic("unable to unmarshal ICS20 packet data") - } - - var denom string - if transfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { - // remove prefix added by sender chain - voucherPrefix := transfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) - - unprefixedDenom := data.Denom[len(voucherPrefix):] - - // coin denomination used in sending from the escrow address - denom = unprefixedDenom - - // The denomination used to send the coins is either the native denom or the hash of the path - // if the denomination is not native. - denomTrace := transfertypes.ParseDenomTrace(unprefixedDenom) - if denomTrace.Path != "" { - denom = denomTrace.IBCDenom() - } - } else { - prefixedDenom := transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + data.Denom - denom = transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() - } - return denom -} - -// IsAckError checks an IBC acknowledgement to see if it's an error. -// This is a replacement for ack.Success() which is currently not working on some circumstances -func IsAckError(acknowledgement []byte) bool { - var ackErr channeltypes.Acknowledgement_Error - if err := json.Unmarshal(acknowledgement, &ackErr); err == nil && len(ackErr.Error) > 0 { - return true - } - return false -} diff --git a/x/ibchooks/types/errors.go b/x/ibchooks/types/errors.go index 31722619aa..5558835334 100644 --- a/x/ibchooks/types/errors.go +++ b/x/ibchooks/types/errors.go @@ -18,4 +18,5 @@ var ( ErrAsyncAckNotAllowed = errorsmod.Register("wasm-hooks", 9, "contract not allowed to send async acks") ErrAckPacketMismatch = errorsmod.Register("wasm-hooks", 10, "packet does not match the expected packet") ErrInvalidContractAddr = errorsmod.Register("wasm-hooks", 11, "invalid contract address") + ErrMarkerError = errorsmod.Register("marker-hooks", 12, "marker error") ) diff --git a/x/ibchooks/types/types.go b/x/ibchooks/types/types.go index 6799968bc1..5fc01c64b7 100644 --- a/x/ibchooks/types/types.go +++ b/x/ibchooks/types/types.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" ) @@ -52,6 +53,7 @@ type IBCAckError struct { ErrorResponse string `json:"error_response"` } +// IBCAck is the parent IBC ack response structure type IBCAck struct { Type string `json:"type"` Content json.RawMessage `json:"content"` @@ -62,6 +64,7 @@ type IBCAck struct { AckError *IBCAckError `json:"error,omitempty"` } +// UnmarshalIBCAck unmashals Ack to either response or error type func UnmarshalIBCAck(bz []byte) (*IBCAck, error) { var ack IBCAck if err := json.Unmarshal(bz, &ack); err != nil { @@ -83,3 +86,93 @@ func UnmarshalIBCAck(bz []byte) (*IBCAck, error) { return &ack, nil } + +// IbcAck ibc ack struct with json fields defined +type IbcAck struct { + Channel string `json:"channel"` + Sequence uint64 `json:"sequence"` + Ack JSONBytes `json:"ack"` + Success bool `json:"success"` +} + +// IbcLifecycleCompleteAck ibc lifcycle complete ack with json fields defined +type IbcLifecycleCompleteAck struct { + IbcAck IbcAck `json:"ibc_ack"` +} + +// IbcTimeout ibc timeout struct with json fields defined +type IbcTimeout struct { + Channel string `json:"channel"` + Sequence uint64 `json:"sequence"` +} + +// IbcLifecycleCompleteTimeout ibc lifecycle complete struct with json fields defined +type IbcLifecycleCompleteTimeout struct { + IbcTimeout IbcTimeout `json:"ibc_timeout"` +} + +// IbcLifecycleComplete ibc lifecycle complete struct with json fields defined +type IbcLifecycleComplete struct { + IbcLifecycleComplete interface{} `json:"ibc_lifecycle_complete"` +} + +// MarkerMemo parent marker struct for memo json +type MarkerMemo struct { + Marker MarkerPayload `json:"marker"` +} + +// MarkerPayload child structure for marker memo +type MarkerPayload struct { + TransferAuths []string `json:"transfer-auths"` + AllowForceTransfer bool `json:"allow-force-transfer"` +} + +// NewMarkerPayload returns a marker payload with transfer authorities and allow force transfer flag +func NewMarkerPayload(transferAuthAddrs []sdk.AccAddress, allowForceTransfer bool) MarkerPayload { + addresses := make([]string, len(transferAuthAddrs)) + for i := 0; i < len(transferAuthAddrs); i++ { + addresses[i] = transferAuthAddrs[i].String() + } + return MarkerPayload{ + TransferAuths: addresses, + AllowForceTransfer: allowForceTransfer, + } +} + +// NewIbcLifecycleCompleteAck returns a new ibc lifecycle complete acknowledgment object for json serialization +func NewIbcLifecycleCompleteAck(sourceChannel string, sequence uint64, ackAsJSON []byte, success bool) IbcLifecycleComplete { + ibcLifecycleCompleteAck := IbcLifecycleCompleteAck{ + IbcAck: IbcAck{ + Channel: sourceChannel, + Sequence: sequence, + Ack: ackAsJSON, + Success: success, + }, + } + return IbcLifecycleComplete{IbcLifecycleComplete: ibcLifecycleCompleteAck} +} + +// NewIbcLifecycleCompleteTimeout return a new ibc lifecycle complete timeout object for json serialization +func NewIbcLifecycleCompleteTimeout(sourceChannel string, sequence uint64) IbcLifecycleComplete { + ibcLifecycleCompleteAck := IbcLifecycleCompleteTimeout{ + IbcTimeout: IbcTimeout{ + Channel: sourceChannel, + Sequence: sequence, + }, + } + return IbcLifecycleComplete{IbcLifecycleComplete: ibcLifecycleCompleteAck} +} + +// JSONBytes is a byte array of a json string +type JSONBytes []byte + +// MarshalJSON returns empty json object bytes when bytes are empty +func (jb JSONBytes) MarshalJSON() ([]byte, error) { + if len(jb) == 0 { + return []byte("{}"), nil + } + return jb, nil +} + +// PreSendPacketDataProcessingFn is function signature used for custom data processing before ibc's PacketSend executed in middleware +type PreSendPacketDataProcessingFn func(ctx sdk.Context, data []byte, processData map[string]interface{}) ([]byte, error) diff --git a/x/ibchooks/types/types_test.go b/x/ibchooks/types/types_test.go new file mode 100644 index 0000000000..89066fed90 --- /dev/null +++ b/x/ibchooks/types/types_test.go @@ -0,0 +1,75 @@ +package types + +import ( + "encoding/json" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" +) + +type IbcHooksTypesTestSuite struct { + suite.Suite +} + +func (s *IbcHooksTypesTestSuite) SetupTest() {} + +func TestSITestSuite(t *testing.T) { + suite.Run(t, new(IbcHooksTypesTestSuite)) +} + +func (s *IbcHooksTypesTestSuite) TestIbcLifecycleCompleteAckJsonSerialization() { + ack := RequestAckI{SourceChannel: "channel", PacketSequence: 100} + ackBz, err := json.Marshal(ack) + s.Require().NoError(err) + ibcLifecycleCompleteAck := NewIbcLifecycleCompleteAck("channel-1", 100, ackBz, true) + actualAck, err := json.Marshal(ibcLifecycleCompleteAck) + s.Require().NoError(err, "Marshal() error") + s.Require().Equal(`{"ibc_lifecycle_complete":{"ibc_ack":{"channel":"channel-1","sequence":100,"ack":{"packet_sequence":100,"source_channel":"channel"},"success":true}}}`, string(actualAck), "Serialized json doesn't match") +} + +func (s *IbcHooksTypesTestSuite) TestIbcLifecycleCompleteTimeoutJsonSerialization() { + ibcLifecycleCompleteAck := NewIbcLifecycleCompleteTimeout("channel-1", 100) + actualAck, err := json.Marshal(ibcLifecycleCompleteAck) + s.Require().NoError(err, "Marshal() error") + s.Require().Equal(`{"ibc_lifecycle_complete":{"ibc_timeout":{"channel":"channel-1","sequence":100}}}`, string(actualAck), "Serialized json doesn't match") +} + +func (s *IbcHooksTypesTestSuite) TestNewMarkerPayloadSerialization() { + testCases := []struct { + name string + addrs []sdk.AccAddress + forceTransfer bool + expJson string + }{ + { + name: "empty address array", + addrs: []sdk.AccAddress{}, + expJson: `{"transfer-auths":[],"allow-force-transfer":false}`, + }, + { + name: "single address array", + addrs: []sdk.AccAddress{sdk.AccAddress("address1")}, + expJson: `{"transfer-auths":["cosmos1v9jxgun9wdenzc33zgq"],"allow-force-transfer":false}`, + }, + { + name: "multiple address array", + addrs: []sdk.AccAddress{sdk.AccAddress("address1"), sdk.AccAddress("address2")}, + expJson: `{"transfer-auths":["cosmos1v9jxgun9wdenzc33zgq","cosmos1v9jxgun9wdenyy7j85h"],"allow-force-transfer":false}`, + }, + { + name: "multiple address array with allow force transfer true", + addrs: []sdk.AccAddress{sdk.AccAddress("address1"), sdk.AccAddress("address2")}, + forceTransfer: true, + expJson: `{"transfer-auths":["cosmos1v9jxgun9wdenzc33zgq","cosmos1v9jxgun9wdenyy7j85h"],"allow-force-transfer":true}`, + }, + } + for _, tc := range testCases { + markerPayload := NewMarkerPayload(tc.addrs, tc.forceTransfer) + s.T().Run(tc.name, func(t *testing.T) { + actualJson, err := json.Marshal(markerPayload) + s.Assert().NoError(err, "Marshal() error") + s.Assert().Equal(tc.expJson, string(actualJson), "Serialized json doesn't match") + }) + } +} diff --git a/x/ibchooks/wasm_hook.go b/x/ibchooks/wasm_hook.go index 4e5e950e55..3bb4186847 100644 --- a/x/ibchooks/wasm_hook.go +++ b/x/ibchooks/wasm_hook.go @@ -3,7 +3,6 @@ package ibchooks import ( "encoding/json" "fmt" - "strings" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" @@ -18,7 +17,6 @@ import ( ibcexported "github.com/cosmos/ibc-go/v6/modules/core/exported" "github.com/provenance-io/provenance/x/ibchooks/keeper" - "github.com/provenance-io/provenance/x/ibchooks/osmoutils" "github.com/provenance-io/provenance/x/ibchooks/types" ) @@ -36,30 +34,29 @@ func NewWasmHooks(ibcHooksKeeper *keeper.Keeper, contractKeeper *wasmkeeper.Keep } } +// ProperlyConfigured returns false when wasm hooks are configured incorrectly func (h WasmHooks) ProperlyConfigured() bool { return h.ContractKeeper != nil && h.ibcHooksKeeper != nil } func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, packet channeltypes.Packet, relayer sdktypes.AccAddress) ibcexported.Acknowledgement { if !h.ProperlyConfigured() { - // Not configured return im.App.OnRecvPacket(ctx, packet, relayer) } - isIcs20, data := isIcs20Packet(packet) + isIcs20, data := isIcs20Packet(packet.GetData()) if !isIcs20 { return im.App.OnRecvPacket(ctx, packet, relayer) } - // Validate the memo isWasmRouted, contractAddr, msgBytes, err := ValidateAndParseMemo(data.GetMemo(), data.Receiver) if !isWasmRouted { return im.App.OnRecvPacket(ctx, packet, relayer) } if err != nil { - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation, err.Error()) + return NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation, err.Error()) } if msgBytes == nil || contractAddr == nil { // This should never happen - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation) + return NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation) } // Calculate the receiver / contract caller based on the packet's channel and sender @@ -67,7 +64,7 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, sender := data.GetSender() senderBech32, err := keeper.DeriveIntermediateSender(channel, sender, h.bech32PrefixAccAddr) if err != nil { - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrBadSender, fmt.Sprintf("cannot convert sender address %s/%s to bech32: %s", channel, sender, err.Error())) + return NewEmitErrorAcknowledgement(ctx, types.ErrBadSender, fmt.Sprintf("cannot convert sender address %s/%s to bech32: %s", channel, sender, err.Error())) } // The funds sent on this packet need to be transferred to the intermediary account for the sender. @@ -79,7 +76,7 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, data.Receiver = senderBech32 bz, err := json.Marshal(data) if err != nil { - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMarshaling, err.Error()) + return NewEmitErrorAcknowledgement(ctx, types.ErrMarshaling, err.Error()) } packet.Data = bz @@ -93,11 +90,11 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, if !ok { // This should never happen, as it should've been caught in the underlaying call to OnRecvPacket, // but returning here for completeness - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrInvalidPacket, "Amount is not an int") + return NewEmitErrorAcknowledgement(ctx, types.ErrInvalidPacket, "Amount is not an int") } // The packet's denom is the denom in the sender chain. This needs to be converted to the local denom. - denom := osmoutils.MustExtractDenomFromPacketOnRecv(packet) + denom := MustExtractDenomFromPacketOnRecv(packet) funds := sdktypes.NewCoins(sdktypes.NewCoin(denom, amount)) // Execute the contract @@ -109,7 +106,7 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, } response, err := h.execWasmMsg(ctx, &execMsg) if err != nil { - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrWasmError, err.Error()) + return NewEmitErrorAcknowledgement(ctx, types.ErrWasmError, err.Error()) } // Check if the contract is requesting for the ack to be async. @@ -120,7 +117,7 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, if asyncAckRequest.IsAsyncAck { // in which case IsAsyncAck is expected to be set to true if !h.ibcHooksKeeper.IsInAllowList(ctx, contractAddr.String()) { // Only allowed contracts can send async acks - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrAsyncAckNotAllowed) + return NewEmitErrorAcknowledgement(ctx, types.ErrAsyncAckNotAllowed) } // Store the contract as the packet's ack actor and return nil h.ibcHooksKeeper.StorePacketAckActor(ctx, packet, contractAddr.String()) @@ -132,7 +129,7 @@ func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdktypes.Context, fullAck := types.ContractAck{ContractResult: response.Data, IbcAck: ack.Acknowledgement()} bz, err = json.Marshal(fullAck) if err != nil { - return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrBadResponse, err.Error()) + return NewEmitErrorAcknowledgement(ctx, types.ErrBadResponse, err.Error()) } return channeltypes.NewResultAcknowledgement(bz) @@ -146,12 +143,12 @@ func (h WasmHooks) execWasmMsg(ctx sdktypes.Context, execMsg *wasmtypes.MsgExecu return wasmMsgServer.ExecuteContract(sdktypes.WrapSDKContext(ctx), execMsg) } -func isIcs20Packet(packet channeltypes.Packet) (isIcs20 bool, ics20data transfertypes.FungibleTokenPacketData) { - var data transfertypes.FungibleTokenPacketData - if err := json.Unmarshal(packet.GetData(), &data); err != nil { - return false, data +func isIcs20Packet(data []byte) (isIcs20 bool, ics20data transfertypes.FungibleTokenPacketData) { + var packetdata transfertypes.FungibleTokenPacketData + if err := json.Unmarshal(data, &packetdata); err != nil { + return false, packetdata } - return true, data + return true, packetdata } // jsonStringHasKey parses the memo as a json object and checks if it contains the key. @@ -249,8 +246,8 @@ func (h WasmHooks) SendPacketOverride( timeoutTimestamp uint64, data []byte, ) (uint64, error) { - var ics20Packet transfertypes.FungibleTokenPacketData - if err := json.Unmarshal(data, &ics20Packet); err != nil { + isIcs20, ics20Packet := isIcs20Packet(data) + if !isIcs20 { return i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) // continue } @@ -269,7 +266,7 @@ func (h WasmHooks) SendPacketOverride( delete(metadata, types.IBCCallbackKey) bzMetadata, err := json.Marshal(metadata) if err != nil { - return 0, sdkerrors.Wrap(err, "Send packet with callback error") + return 0, sdkerrors.Wrap(err, "ibc_callback marshall error") } stringMetadata := string(bzMetadata) if stringMetadata == "{}" { @@ -279,7 +276,7 @@ func (h WasmHooks) SendPacketOverride( } dataBytes, err := json.Marshal(ics20Packet) if err != nil { - return 0, sdkerrors.Wrap(err, "Send packet with callback error") + return 0, sdkerrors.Wrap(err, "ics20data marshall error") } seq, err := i.channel.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, dataBytes) @@ -290,17 +287,102 @@ func (h WasmHooks) SendPacketOverride( // Make sure the callback contract is a string and a valid bech32 addr. If it isn't, ignore this packet contract, ok := callbackRaw.(string) if !ok { - return seq, nil + return 0, nil } if _, err := sdktypes.AccAddressFromBech32(contract); err != nil { - return seq, nil + return 0, nil } h.ibcHooksKeeper.StorePacketCallback(ctx, sourceChannel, seq, contract) return seq, nil } +func (h WasmHooks) GetWasmSendPacketPreProcessor( + _ sdktypes.Context, + data []byte, + processData map[string]interface{}, +) ([]byte, error) { + isIcs20, ics20Packet := isIcs20Packet(data) + if !isIcs20 { + return data, nil + } + + isCallbackRouted, metadata := jsonStringHasKey(ics20Packet.GetMemo(), types.IBCCallbackKey) + if !isCallbackRouted { + return data, nil + } + + // We remove the callback metadata from the memo as it has already been processed. + + // If the only available key in the memo is the callback, we should remove the memo + // from the data completely so the packet is sent without it. + // This way receiver chains that are on old versions of IBC will be able to process the packet + + callbackRaw := metadata[types.IBCCallbackKey] + if callbackRaw != nil { + contract, ok := callbackRaw.(string) + if !ok { + return nil, fmt.Errorf("unable to format callback %v", callbackRaw) + } + + if _, err := sdktypes.AccAddressFromBech32(contract); err != nil { + return nil, fmt.Errorf("invalid bech32 contract address %v: %w", contract, err) + } + } + + processData[types.IBCCallbackKey] = callbackRaw + + delete(metadata, types.IBCCallbackKey) + bzMetadata, err := json.Marshal(metadata) + if err != nil { + return nil, sdkerrors.Wrap(err, "ibc_callback marshall error") + } + stringMetadata := string(bzMetadata) + if stringMetadata == "{}" { + ics20Packet.Memo = "" + } else { + ics20Packet.Memo = stringMetadata + } + dataBytes, err := json.Marshal(ics20Packet) + if err != nil { + return nil, sdkerrors.Wrap(err, "ics20data marshall error") + } + + return dataBytes, nil +} + +func (h WasmHooks) SendPacketAfterHook(ctx sdktypes.Context, + _ *capabilitytypes.Capability, + _ string, + sourceChannel string, + _ clienttypes.Height, + _ uint64, + _ []byte, + sequence uint64, + err error, + processData map[string]interface{}, +) { + if err != nil { + return + } + callbackRaw := processData[types.IBCCallbackKey] + if callbackRaw == nil { + return + } + // Make sure the callback contract is a string and a valid bech32 addr. If it isn't, ignore this packet + contract, ok := callbackRaw.(string) + if !ok { + return + } + + if _, err := sdktypes.AccAddressFromBech32(contract); err != nil { + return + } + + h.ibcHooksKeeper.StorePacketCallback(ctx, sourceChannel, sequence, contract) +} + func (h WasmHooks) OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdktypes.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdktypes.AccAddress) error { err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) if err != nil { @@ -320,27 +402,22 @@ func (h WasmHooks) OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdktype contractAddr, err := sdktypes.AccAddressFromBech32(contract) if err != nil { - return sdkerrors.Wrap(err, "Ack callback error") // The callback configured is not a bech32. Error out + return sdkerrors.Wrap(err, "Ack callback error") } - success := "false" - if !osmoutils.IsAckError(acknowledgement) { - success = "true" - } + success := !IsJSONAckError(acknowledgement) // Notify the sender that the ack has been received - ack, err := json.Marshal(acknowledgement) + ackAsJSON, err := json.Marshal(acknowledgement) if err != nil { - // If the ack is not a json object, error return err } - // This should never match anything, but we want to satisfy the github code scanning flag. - sanitizedSourceChannel := strings.ReplaceAll(packet.SourceChannel, "\"", "") - - sudoMsg := []byte(fmt.Sprintf( - `{"ibc_lifecycle_complete": {"ibc_ack": {"channel": "%s", "sequence": %d, "ack": %s, "success": %s}}}`, - sanitizedSourceChannel, packet.Sequence, ack, success)) + ibcLifecycleComplete := types.NewIbcLifecycleCompleteAck(packet.SourceChannel, packet.Sequence, ackAsJSON, success) + sudoMsg, err := json.Marshal(ibcLifecycleComplete) + if err != nil { + return sdkerrors.Wrap(err, "Ack callback error") + } _, err = h.ContractKeeper.Sudo(ctx, contractAddr, sudoMsg) if err != nil { // error processing the callback @@ -358,25 +435,26 @@ func (h WasmHooks) OnTimeoutPacketOverride(im IBCMiddleware, ctx sdktypes.Contex } if !h.ProperlyConfigured() { - // Not configured. Return from the underlying implementation return nil } contract := h.ibcHooksKeeper.GetPacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) if contract == "" { - // No callback configured return nil } contractAddr, err := sdktypes.AccAddressFromBech32(contract) if err != nil { - return sdkerrors.Wrap(err, "Timeout callback error") // The callback configured is not a bech32. Error out + return sdkerrors.Wrap(err, "Timeout callback error") } - sudoMsg := []byte(fmt.Sprintf( - `{"ibc_lifecycle_complete": {"ibc_timeout": {"channel": "%s", "sequence": %d}}}`, - packet.SourceChannel, packet.Sequence)) - _, err = h.ContractKeeper.Sudo(ctx, contractAddr, sudoMsg) + sudoMsg := types.NewIbcLifecycleCompleteTimeout(packet.SourceChannel, packet.Sequence) + jsonData, err := json.Marshal(sudoMsg) + if err != nil { + return sdkerrors.Wrap(err, "Timeout callback error") + } + + _, err = h.ContractKeeper.Sudo(ctx, contractAddr, jsonData) if err != nil { // error processing the callback. This could be because the contract doesn't implement the message type to // process the callback. Retrying this will not help, so we can delete the callback from storage. @@ -385,7 +463,7 @@ func (h WasmHooks) OnTimeoutPacketOverride(im IBCMiddleware, ctx sdktypes.Contex sdktypes.NewEvent( "ibc-timeout-callback-error", sdktypes.NewAttribute("contract", contractAddr.String()), - sdktypes.NewAttribute("message", string(sudoMsg)), + sdktypes.NewAttribute("message", string(jsonData)), sdktypes.NewAttribute("error", err.Error()), ), }) @@ -393,3 +471,67 @@ func (h WasmHooks) OnTimeoutPacketOverride(im IBCMiddleware, ctx sdktypes.Contex h.ibcHooksKeeper.DeletePacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) return nil } + +// NewEmitErrorAcknowledgement creates a new error acknowledgement after having emitted an event with the +// details of the error. +func NewEmitErrorAcknowledgement(ctx sdktypes.Context, err error, errorContexts ...string) channeltypes.Acknowledgement { + errorType := "ibc-acknowledgement-error" + logger := ctx.Logger().With("module", errorType) + + attributes := make([]sdktypes.Attribute, len(errorContexts)+1) + attributes[0] = sdktypes.NewAttribute("error", err.Error()) + for i, s := range errorContexts { + attributes[i+1] = sdktypes.NewAttribute("error-context", s) + logger.Error(fmt.Sprintf("error-context: %v", s)) + } + + ctx.EventManager().EmitEvents(sdktypes.Events{ + sdktypes.NewEvent( + errorType, + attributes..., + ), + }) + + return channeltypes.NewErrorAcknowledgement(err) +} + +// IsJSONAckError checks an IBC acknowledgement to see if it's an error. +// This is a replacement for ack.Success() which is currently not working on some circumstances +func IsJSONAckError(acknowledgement []byte) bool { + var ackErr channeltypes.Acknowledgement_Error + if err := json.Unmarshal(acknowledgement, &ackErr); err == nil && len(ackErr.Error) > 0 { + return true + } + return false +} + +// MustExtractDenomFromPacketOnRecv takes a packet with a valid ICS20 token data in the Data field and returns the +// denom as represented in the local chain. +// If the data cannot be unmarshalled this function will panic +func MustExtractDenomFromPacketOnRecv(packet ibcexported.PacketI) string { + var data transfertypes.FungibleTokenPacketData + if err := json.Unmarshal(packet.GetData(), &data); err != nil { + panic("unable to unmarshal ICS20 packet data") + } + + if transfertypes.ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { + // remove prefix added by sender chain + voucherPrefix := transfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) + + unprefixedDenom := data.Denom[len(voucherPrefix):] + + // coin denomination used in sending from the escrow address + denom := unprefixedDenom + + // The denomination used to send the coins is either the native denom or the hash of the path + // if the denomination is not native. + denomTrace := transfertypes.ParseDenomTrace(unprefixedDenom) + if denomTrace.Path != "" { + denom = denomTrace.IBCDenom() + } + return denom + } + + prefixedDenom := transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + data.Denom + return transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() +} diff --git a/x/marker/keeper/marker.go b/x/marker/keeper/marker.go index fe3e43b370..7893e50a77 100644 --- a/x/marker/keeper/marker.go +++ b/x/marker/keeper/marker.go @@ -798,6 +798,11 @@ func (k Keeper) SetMarkerDenomMetadata(ctx sdk.Context, metadata banktypes.Metad } // record the metadata with the bank + return k.SetDenomMetaData(ctx, metadata, caller) +} + +// SetDenomMetaData sets denom metadata to keeper and emits event +func (k Keeper) SetDenomMetaData(ctx sdk.Context, metadata banktypes.Metadata, caller sdk.AccAddress) error { k.bankKeeper.SetDenomMetaData(ctx, metadata) markerSetDenomMetaEvent := types.NewEventMarkerSetDenomMetadata(