diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ff67af8 Binary files /dev/null and b/.DS_Store differ diff --git a/rust-contract-building-poc/.DS_Store b/rust-contract-building-poc/.DS_Store new file mode 100644 index 0000000..b6fe95d Binary files /dev/null and b/rust-contract-building-poc/.DS_Store differ diff --git a/rust-contract-building-poc/artifacts/checksums.txt b/rust-contract-building-poc/artifacts/checksums.txt new file mode 100644 index 0000000..c201b69 --- /dev/null +++ b/rust-contract-building-poc/artifacts/checksums.txt @@ -0,0 +1 @@ +2ee2c9dddcdf04fed233af49c0731154813236c4ed0d472ad490afea530eddb4 cw_nameservice.wasm diff --git a/rust-contract-building-poc/artifacts/cw_nameservice.wasm b/rust-contract-building-poc/artifacts/cw_nameservice.wasm new file mode 100644 index 0000000..296fcf3 Binary files /dev/null and b/rust-contract-building-poc/artifacts/cw_nameservice.wasm differ diff --git a/rust-contract-building-poc/kurtosis.yml b/rust-contract-building-poc/kurtosis.yml new file mode 100644 index 0000000..38c92a8 --- /dev/null +++ b/rust-contract-building-poc/kurtosis.yml @@ -0,0 +1 @@ +name : github.com/riyasng12/riya_training/riya \ No newline at end of file diff --git a/rust-contract-building-poc/main.star b/rust-contract-building-poc/main.star new file mode 100644 index 0000000..f5d3fc2 --- /dev/null +++ b/rust-contract-building-poc/main.star @@ -0,0 +1,22 @@ +def run(plan): + + plan.upload_files( + src="nameservice", + name = "contracts" + ) + + service_config = ServiceConfig( + image="cosmwasm/optimizer:0.15.0", + files= { + "/code": "contracts" + }, + entrypoint= ["/bin/sh"] + ) + + plan.add_service(name="rustcontractbuilder", config=service_config) + + plan.exec(service_name = "rustcontractbuilder", recipe= ExecRecipe(["optimize.sh", "."])) + + plan.store_service_files(service_name="rustcontractbuilder", src="/code/artifacts", name="artifacts") + + diff --git a/rust-contract-building-poc/nameservice/.DS_Store b/rust-contract-building-poc/nameservice/.DS_Store new file mode 100644 index 0000000..07c5f31 Binary files /dev/null and b/rust-contract-building-poc/nameservice/.DS_Store differ diff --git a/rust-contract-building-poc/nameservice/.cargo/config b/rust-contract-building-poc/nameservice/.cargo/config new file mode 100644 index 0000000..2a01f1d --- /dev/null +++ b/rust-contract-building-poc/nameservice/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/rust-contract-building-poc/nameservice/.editorconfig b/rust-contract-building-poc/nameservice/.editorconfig new file mode 100644 index 0000000..3d36f20 --- /dev/null +++ b/rust-contract-building-poc/nameservice/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/rust-contract-building-poc/nameservice/.gitignore b/rust-contract-building-poc/nameservice/.gitignore new file mode 100644 index 0000000..6815fd9 --- /dev/null +++ b/rust-contract-building-poc/nameservice/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +*.iml +.idea diff --git a/rust-contract-building-poc/nameservice/Cargo.lock b/rust-contract-building-poc/nameservice/Cargo.lock new file mode 100644 index 0000000..e309249 --- /dev/null +++ b/rust-contract-building-poc/nameservice/Cargo.lock @@ -0,0 +1,698 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" + +[[package]] +name = "cosmwasm-crypto" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7d659bbecbdca16878322becea489c0fcc36e784f41c942ea0171e9cf74179" +dependencies = [ + "digest 0.10.5", + "ed25519-zebra", + "k256", + "rand_core 0.6.3", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81b4676a316d3e4636f658d2d3aba9b4b30c3177e311bbda721b56d4a26342f" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88917b0e7c987fd99251f8bb7e06da7d705e7572e0732428b72f8bc1f17b1af5" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bd1495b8bc0130529dad7e69bef8d800654b50a5d5fc150f1e795c4c24c5b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cosmwasm-std" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39d5725d2bd4b868284c127f7a904c93a9826550f30267b301484138db2eb9b" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc9b8229a8b09eb7673851f3e97cbb8832ae93a9d589800fa67ef1a2daef641" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-nameservice" +version = "0.12.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "der" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + +[[package]] +name = "ecdsa" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85789ce7dfbd0f0624c07ef653a08bb2ebf43d3e16531361f46d36dd54334fed" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.6.3", + "serde", + "sha2 0.9.5", + "thiserror", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.5", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "group" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.5", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "k256" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3636d281d46c3b64182eb3a0a42b7b483191a2ecc3f05301fa67403f7c9bc949" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + +[[package]] +name = "rfc6979" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.1.5", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.5", + "digest 0.10.5", +] + +[[package]] +name = "signature" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" +dependencies = [ + "digest 0.10.5", + "rand_core 0.6.3", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/rust-contract-building-poc/nameservice/Cargo.toml b/rust-contract-building-poc/nameservice/Cargo.toml new file mode 100644 index 0000000..6a11f54 --- /dev/null +++ b/rust-contract-building-poc/nameservice/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cw-nameservice" +description = "A name service" +version = "0.12.0" +authors = ["Cory Levinson "] +edition = "2018" +license = "Apache-2.0" +repository = "https://github.com/InterWasm/cw-contracts" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = "1.1.0" +cosmwasm-storage = "1.1.0" +cw-storage-plus = "0.13.4" +cosmwasm-schema = "1.1.0" +thiserror = "1.0.31" + +[dev-dependencies] + diff --git a/rust-contract-building-poc/nameservice/LICENSE b/rust-contract-building-poc/nameservice/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/rust-contract-building-poc/nameservice/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rust-contract-building-poc/nameservice/NOTICE b/rust-contract-building-poc/nameservice/NOTICE new file mode 100644 index 0000000..8504d3d --- /dev/null +++ b/rust-contract-building-poc/nameservice/NOTICE @@ -0,0 +1,13 @@ +Copyright 2020 Cory Levinson + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rust-contract-building-poc/nameservice/README.md b/rust-contract-building-poc/nameservice/README.md new file mode 100644 index 0000000..8dc54a0 --- /dev/null +++ b/rust-contract-building-poc/nameservice/README.md @@ -0,0 +1,7 @@ +# Name Service + +The goal of the application you are building is to let users buy names and to set a value these names resolve to. +The owner of a given name will be the current highest bidder. In this section, you will learn how these simple + requirements translate to application design. + +Here is the tutorial for this application: [tutorial](https://docs.cosmwasm.com/tutorials/name-service/intro) diff --git a/rust-contract-building-poc/nameservice/examples/schema.rs b/rust-contract-building-poc/nameservice/examples/schema.rs new file mode 100644 index 0000000..3f630fd --- /dev/null +++ b/rust-contract-building-poc/nameservice/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use cw_nameservice::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/rust-contract-building-poc/nameservice/helpers.ts b/rust-contract-building-poc/nameservice/helpers.ts new file mode 100644 index 0000000..542dfb8 --- /dev/null +++ b/rust-contract-building-poc/nameservice/helpers.ts @@ -0,0 +1,234 @@ +/* + * This is a set of helpers meant for use with @cosmjs/cli + * With these you can easily use the cw20 contract without worrying about forming messages and parsing queries. + * + * Usage: npx @cosmjs/cli@^0.23 --init https://raw.githubusercontent.com/CosmWasm/cosmwasm-examples/main/nameservice/helpers.ts + * + * Create a client: + * const client = await useOptions(coralnetOptions).setup(password); + * await client.getAccount() + * + * Get the mnemonic: + * await useOptions(coralnetOptions).recoverMnemonic(password) + * + * If you want to use this code inside an app, you will need several imports from https://github.com/CosmWasm/cosmjs + */ + +const path = require("path"); + +interface Options { + readonly httpUrl: string + readonly networkId: string + readonly feeToken: string + readonly gasPrice: GasPrice + readonly bech32prefix: string + readonly hdPath: HdPath + readonly faucetUrl?: string + readonly defaultKeyFile: string + readonly gasLimits: Partial> // only set the ones you want to override +} + +const coralnetOptions: Options = { + httpUrl: 'https://lcd.coralnet.cosmwasm.com', + networkId: 'cosmwasm-coral', + feeToken: 'ucosm', + gasPrice: GasPrice.fromString("0.025ucosm"), + bech32prefix: 'cosmos', + faucetToken: 'SHELL', + faucetUrl: 'https://faucet.coralnet.cosmwasm.com/credit', + hdPath: makeCosmoshubPath(0), + defaultKeyFile: path.join(process.env.HOME, ".coral.key"), + gasLimits: { + upload: 1500000, + init: 600000, + register:800000, + transfer: 80000, + }, +} + +interface Network { + setup: (password: string, filename?: string) => Promise + recoverMnemonic: (password: string, filename?: string) => Promise +} + +const useOptions = (options: Options): Network => { + + const loadOrCreateWallet = async (options: Options, filename: string, password: string): Promise => { + let encrypted: string; + try { + encrypted = fs.readFileSync(filename, 'utf8'); + } catch (err) { + // generate if no file exists + const wallet = await Secp256k1HdWallet.generate(12, options.hdPath, options.bech32prefix); + const encrypted = await wallet.serialize(password); + fs.writeFileSync(filename, encrypted, 'utf8'); + return wallet; + } + // otherwise, decrypt the file (we cannot put deserialize inside try or it will over-write on a bad password) + const wallet = await Secp256k1HdWallet.deserialize(encrypted, password); + return wallet; + }; + + const connect = async ( + wallet: Secp256k1HdWallet, + options: Options + ): Promise => { + const [{ address }] = await wallet.getAccounts(); + + const client = new SigningCosmWasmClient( + options.httpUrl, + address, + wallet, + coralnetOptions.gasPrice, + coralnetOptions.gasLimits, + ); + return client; + }; + + const hitFaucet = async ( + faucetUrl: string, + address: string, + denom: string + ): Promise => { + await axios.post(faucetUrl, { denom, address }); + } + + const setup = async (password: string, filename?: string): Promise => { + const keyfile = filename || options.defaultKeyFile; + const wallet = await loadOrCreateWallet(coralnetOptions, keyfile, password); + const client = await connect(wallet, coralnetOptions); + + // ensure we have some tokens + if (options.faucetUrl) { + const account = await client.getAccount(); + if (!account) { + console.log(`Getting ${options.feeToken} from faucet`); + await hitFaucet(options.faucetUrl, client.senderAddress, options.feeToken); + } + } + + return client; + } + + const recoverMnemonic = async (password: string, filename?: string): Promise => { + const keyfile = filename || options.defaultKeyFile; + const wallet = await loadOrCreateWallet(coralnetOptions, keyfile, password); + return wallet.mnemonic; + } + + return {setup, recoverMnemonic}; +} + +interface Config { + readonly purchase_price?: Coin + readonly transfer_price?: Coin +} + +interface ResolveRecordResponse { + readonly address?: string +} + +interface InitMsg { + + readonly purchase_price?: Coin + readonly transfer_price?: Coin +} + +interface NameServiceInstance { + readonly contractAddress: string + + // queries + record: (name: string) => Promise + config: () => Promise + + // actions + register: (name: string, amount: Coin[]) => Promise + transfer: (name: string, to: string, amount: Coin[]) => Promise +} + +interface NameServiceContract { + upload: () => Promise + + instantiate: (codeId: number, initMsg: InitMsg, label: string) => Promise + + use: (contractAddress: string) => NameServiceInstance +} + +const NameService = (client: SigningCosmWasmClient): NameServiceContract => { + const use = (contractAddress: string): NameServiceInstance => { + const record = async (name: string): Promise => { + return client.queryContractSmart(contractAddress, {resolve_record: { name }}); + }; + + const config = async (): Promise => { + return client.queryContractSmart(contractAddress, {config: { }}); + }; + + const register = async (name: string, amount: Coin[]): Promise => { + const result = await client.execute(contractAddress, {register: { name }}, "", amount); + return result.transactionHash; + }; + + const transfer = async (name: string, to: string, amount: Coin[]): Promise => { + const result = await client.execute(contractAddress, {transfer: { name, to }}, "", amount); + return result.transactionHash; + }; + + return { + contractAddress, + record, + config, + register, + transfer, + }; + } + + const downloadWasm = async (url: string): Promise => { + const r = await axios.get(url, { responseType: 'arraybuffer' }) + if (r.status !== 200) { + throw new Error(`Download error: ${r.status}`) + } + return r.data + } + + const upload = async (): Promise => { + const meta = { + source: "https://github.com/CosmWasm/cosmwasm-examples/tree/nameservice-0.7.0/nameservice", + builder: "cosmwasm/rust-optimizer:0.10.4" + }; + const sourceUrl = "https://github.com/CosmWasm/cosmwasm-examples/releases/download/nameservice-0.7.0/contract.wasm"; + const wasm = await downloadWasm(sourceUrl); + const result = await client.upload(wasm, meta); + return result.codeId; + } + + const instantiate = async (codeId: number, initMsg: Record, label: string): Promise => { + const result = await client.instantiate(codeId, initMsg, label, { memo: `Init ${label}`}); + return use(result.contractAddress); + } + + return { upload, instantiate, use }; +} + +// Demo: +// const client = await useOptions(coralnetOptions).setup(PASSWORD); +// const { address } = await client.getAccount() +// const factory = NameService(client) +// +// const codeId = await factory.upload(); +// codeId -> 12 +// const initMsg = { purchase_price: { denom: "ushell", amount:"1000" }, transfer_price: { denom: "ushell", amount:"1000" }} +// const contract = await factory.instantiate(12, initMsg, "My Name Service") +// contract.contractAddress -> 'coral1267wq2zk22kt5juypdczw3k4wxhc4z47mug9fd' +// +// OR +// +// const contract = factory.use('coral1267wq2zk22kt5juypdczw3k4wxhc4z47mug9fd') +// +// const randomAddress = 'coral162d3zk45ufaqke5wgcd3kh336k6p3kwwkdj3ma' +// +// contract.config() +// contract.register("name", [{"denom": "ushell", amount: "4000" }]) +// contract.record("name") +// contract.transfer("name", randomAddress, [{"denom": "ushell", amount: "2000" }]) +// diff --git a/rust-contract-building-poc/nameservice/rustfmt.toml b/rust-contract-building-poc/nameservice/rustfmt.toml new file mode 100644 index 0000000..11a85e6 --- /dev/null +++ b/rust-contract-building-poc/nameservice/rustfmt.toml @@ -0,0 +1,15 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" + diff --git a/rust-contract-building-poc/nameservice/schema/cw-nameservice.json b/rust-contract-building-poc/nameservice/schema/cw-nameservice.json new file mode 100644 index 0000000..da12d31 --- /dev/null +++ b/rust-contract-building-poc/nameservice/schema/cw-nameservice.json @@ -0,0 +1,213 @@ +{ + "contract_name": "cw-nameservice", + "contract_version": "0.12.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "properties": { + "purchase_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "transfer_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "name", + "to" + ], + "properties": { + "name": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "resolve_record" + ], + "properties": { + "resolve_record": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "properties": { + "purchase_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + }, + "transfer_price": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "resolve_record": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ResolveRecordResponse", + "type": "object", + "properties": { + "address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/coin_helpers.rs b/rust-contract-building-poc/nameservice/src/coin_helpers.rs new file mode 100644 index 0000000..c7c0892 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/coin_helpers.rs @@ -0,0 +1,62 @@ +use crate::error::ContractError; +use cosmwasm_std::Coin; + +pub fn assert_sent_sufficient_coin( + sent: &[Coin], + required: Option, +) -> Result<(), ContractError> { + if let Some(required_coin) = required { + let required_amount = required_coin.amount.u128(); + if required_amount > 0 { + let sent_sufficient_funds = sent.iter().any(|coin| { + // check if a given sent coin matches denom + // and has sufficient amount + coin.denom == required_coin.denom && coin.amount.u128() >= required_amount + }); + + if sent_sufficient_funds { + return Ok(()); + } else { + return Err(ContractError::InsufficientFundsSend {}); + } + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use cosmwasm_std::{coin, coins}; + + #[test] + fn assert_sent_sufficient_coin_works() { + match assert_sent_sufficient_coin(&[], Some(coin(0, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&[], Some(coin(5, "token"))) { + Ok(()) => panic!("Should have raised insufficient funds error"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&coins(10, "smokin"), Some(coin(5, "token"))) { + Ok(()) => panic!("Should have raised insufficient funds error"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + match assert_sent_sufficient_coin(&coins(10, "token"), Some(coin(5, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + + let sent_coins = vec![coin(2, "smokin"), coin(5, "token"), coin(1, "earth")]; + match assert_sent_sufficient_coin(&sent_coins, Some(coin(5, "token"))) { + Ok(()) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + }; + } +} diff --git a/rust-contract-building-poc/nameservice/src/contract.rs b/rust-contract-building-poc/nameservice/src/contract.rs new file mode 100644 index 0000000..c62b3f7 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/contract.rs @@ -0,0 +1,144 @@ +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, +}; + +use crate::coin_helpers::assert_sent_sufficient_coin; +use crate::error::ContractError; +use crate::msg::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, ResolveRecordResponse}; +use crate::state::{Config, NameRecord, CONFIG, NAME_RESOLVER}; + +const MIN_NAME_LENGTH: u64 = 3; +const MAX_NAME_LENGTH: u64 = 64; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let config = Config { + purchase_price: msg.purchase_price, + transfer_price: msg.transfer_price, + }; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Register { name } => execute_register(deps, env, info, name), + ExecuteMsg::Transfer { name, to } => execute_transfer(deps, env, info, name, to), + } +} + +pub fn execute_register( + deps: DepsMut, + _env: Env, + info: MessageInfo, + name: String, +) -> Result { + // we only need to check here - at point of registration + validate_name(&name)?; + let config = CONFIG.load(deps.storage)?; + assert_sent_sufficient_coin(&info.funds, config.purchase_price)?; + + let key = name.as_bytes(); + let record = NameRecord { owner: info.sender }; + + if (NAME_RESOLVER.may_load(deps.storage, key)?).is_some() { + // name is already taken + return Err(ContractError::NameTaken { name }); + } + + // name is available + NAME_RESOLVER.save(deps.storage, key, &record)?; + + Ok(Response::default()) +} + +pub fn execute_transfer( + deps: DepsMut, + _env: Env, + info: MessageInfo, + name: String, + to: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + assert_sent_sufficient_coin(&info.funds, config.transfer_price)?; + + let new_owner = deps.api.addr_validate(&to)?; + let key = name.as_bytes(); + NAME_RESOLVER.update(deps.storage, key, |record| { + if let Some(mut record) = record { + if info.sender != record.owner { + return Err(ContractError::Unauthorized {}); + } + + record.owner = new_owner.clone(); + Ok(record) + } else { + Err(ContractError::NameNotExists { name: name.clone() }) + } + })?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ResolveRecord { name } => query_resolver(deps, env, name), + QueryMsg::Config {} => to_binary::(&CONFIG.load(deps.storage)?.into()), + } +} + +fn query_resolver(deps: Deps, _env: Env, name: String) -> StdResult { + let key = name.as_bytes(); + + let address = match NAME_RESOLVER.may_load(deps.storage, key)? { + Some(record) => Some(String::from(&record.owner)), + None => None, + }; + let resp = ResolveRecordResponse { address }; + + to_binary(&resp) +} + +// let's not import a regexp library and just do these checks by hand +fn invalid_char(c: char) -> bool { + let is_valid = + c.is_ascii_digit() || c.is_ascii_lowercase() || (c == '.' || c == '-' || c == '_'); + !is_valid +} + +/// validate_name returns an error if the name is invalid +/// (we require 3-64 lowercase ascii letters, numbers, or . - _) +fn validate_name(name: &str) -> Result<(), ContractError> { + let length = name.len() as u64; + if (name.len() as u64) < MIN_NAME_LENGTH { + Err(ContractError::NameTooShort { + length, + min_length: MIN_NAME_LENGTH, + }) + } else if (name.len() as u64) > MAX_NAME_LENGTH { + Err(ContractError::NameTooLong { + length, + max_length: MAX_NAME_LENGTH, + }) + } else { + match name.find(invalid_char) { + None => Ok(()), + Some(bytepos_invalid_char_start) => { + let c = name[bytepos_invalid_char_start..].chars().next().unwrap(); + Err(ContractError::InvalidCharacter { c }) + } + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/error.rs b/rust-contract-building-poc/nameservice/src/error.rs new file mode 100644 index 0000000..c70a2c1 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/error.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Insufficient funds sent")] + InsufficientFundsSend {}, + + #[error("Name does not exist (name {name})")] + NameNotExists { name: String }, + + #[error("Name has been taken (name {name})")] + NameTaken { name: String }, + + #[error("Name too short (length {length} min_length {min_length})")] + NameTooShort { length: u64, min_length: u64 }, + + #[error("Name too long (length {length} min_length {max_length})")] + NameTooLong { length: u64, max_length: u64 }, + + #[error("Invalid character(char {c}")] + InvalidCharacter { c: char }, +} diff --git a/rust-contract-building-poc/nameservice/src/lib.rs b/rust-contract-building-poc/nameservice/src/lib.rs new file mode 100644 index 0000000..9287301 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/lib.rs @@ -0,0 +1,10 @@ +pub mod coin_helpers; +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +mod tests; + +pub use crate::error::ContractError; diff --git a/rust-contract-building-poc/nameservice/src/msg.rs b/rust-contract-building-poc/nameservice/src/msg.rs new file mode 100644 index 0000000..cc190f1 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/msg.rs @@ -0,0 +1,46 @@ +use crate::state::Config; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Coin; + +#[cw_serde] +pub struct InstantiateMsg { + pub purchase_price: Option, + pub transfer_price: Option, +} + +#[cw_serde] +pub enum ExecuteMsg { + Register { name: String }, + Transfer { name: String, to: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // ResolveAddress returns the current address that the name resolves to + #[returns(ResolveRecordResponse)] + ResolveRecord { name: String }, + #[returns(ConfigResponse)] + Config {}, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct ResolveRecordResponse { + pub address: Option, +} + +#[cw_serde] +pub struct ConfigResponse { + pub purchase_price: Option, + pub transfer_price: Option, +} + +impl From for ConfigResponse { + fn from(config: Config) -> ConfigResponse { + ConfigResponse { + purchase_price: config.purchase_price, + transfer_price: config.transfer_price, + } + } +} diff --git a/rust-contract-building-poc/nameservice/src/state.rs b/rust-contract-building-poc/nameservice/src/state.rs new file mode 100644 index 0000000..acaf186 --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/state.rs @@ -0,0 +1,17 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::{Item, Map}; + +#[cw_serde] +pub struct Config { + pub purchase_price: Option, + pub transfer_price: Option, +} + +#[cw_serde] +pub struct NameRecord { + pub owner: Addr, +} + +pub const CONFIG: Item = Item::new("config"); +pub const NAME_RESOLVER: Map<&[u8], NameRecord> = Map::new("name_resolver"); diff --git a/rust-contract-building-poc/nameservice/src/tests.rs b/rust-contract-building-poc/nameservice/src/tests.rs new file mode 100644 index 0000000..698ddde --- /dev/null +++ b/rust-contract-building-poc/nameservice/src/tests.rs @@ -0,0 +1,372 @@ +#[cfg(test)] +mod test_module { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{coin, coins, from_binary, Coin, Deps, DepsMut}; + + use crate::contract::{execute, instantiate, query}; + use crate::error::ContractError; + use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ResolveRecordResponse}; + use crate::state::Config; + + fn assert_name_owner(deps: Deps, name: &str, owner: &str) { + let res = query( + deps, + mock_env(), + QueryMsg::ResolveRecord { + name: name.to_string(), + }, + ) + .unwrap(); + + let value: ResolveRecordResponse = from_binary(&res).unwrap(); + assert_eq!(Some(owner.to_string()), value.address); + } + + fn assert_config_state(deps: Deps, expected: Config) { + let res = query(deps, mock_env(), QueryMsg::Config {}).unwrap(); + let value: Config = from_binary(&res).unwrap(); + assert_eq!(value, expected); + } + + fn mock_init_with_price(deps: DepsMut, purchase_price: Coin, transfer_price: Coin) { + let msg = InstantiateMsg { + purchase_price: Some(purchase_price), + transfer_price: Some(transfer_price), + }; + + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps, mock_env(), info, msg) + .expect("contract successfully handles InstantiateMsg"); + } + + fn mock_init_no_price(deps: DepsMut) { + let msg = InstantiateMsg { + purchase_price: None, + transfer_price: None, + }; + + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps, mock_env(), info, msg) + .expect("contract successfully handles InstantiateMsg"); + } + + fn mock_alice_registers_name(deps: DepsMut, sent: &[Coin]) { + // alice can register an available name + let info = mock_info("alice_key", sent); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let _res = execute(deps, mock_env(), info, msg) + .expect("contract successfully handles Register message"); + } + + #[test] + fn proper_init_no_fees() { + let mut deps = mock_dependencies(); + + mock_init_no_price(deps.as_mut()); + + assert_config_state( + deps.as_ref(), + Config { + purchase_price: None, + transfer_price: None, + }, + ); + } + + #[test] + fn proper_init_with_fees() { + let mut deps = mock_dependencies(); + + mock_init_with_price(deps.as_mut(), coin(3, "token"), coin(4, "token")); + + assert_config_state( + deps.as_ref(), + Config { + purchase_price: Some(coin(3, "token")), + transfer_price: Some(coin(4, "token")), + }, + ); + } + + #[test] + fn register_available_name_and_query_works() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // querying for name resolves to correct address + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn register_available_name_and_query_works_with_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // anyone can register an available name with more fees than needed + let info = mock_info("bob_key", &coins(5, "token")); + let msg = ExecuteMsg::Register { + name: "bob".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Register message"); + + // querying for name resolves to correct address + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + assert_name_owner(deps.as_ref(), "bob", "bob_key"); + } + + #[test] + fn fails_on_register_already_taken_name() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // bob can't register the same name + let info = mock_info("bob_key", &coins(2, "token")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTaken { .. }) => {} + Err(_) => panic!("Unknown error"), + } + // alice can't register the same name again + let info = mock_info("alice_key", &coins(2, "token")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTaken { .. }) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn register_available_name_fails_with_invalid_name() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + let info = mock_info("bob_key", &coins(2, "token")); + + // hi is too short + let msg = ExecuteMsg::Register { + name: "hi".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTooShort { .. }) => {} + Err(_) => panic!("Unknown error"), + } + + // 65 chars is too long + let msg = ExecuteMsg::Register { + name: "01234567890123456789012345678901234567890123456789012345678901234".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameTooLong { .. }) => {} + Err(_) => panic!("Unknown error"), + } + + // no upper case... + let msg = ExecuteMsg::Register { + name: "LOUD".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info.clone(), msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::InvalidCharacter { c }) => assert_eq!(c, 'L'), + Err(_) => panic!("Unknown error"), + } + // ... or spaces + let msg = ExecuteMsg::Register { + name: "two words".to_string(), + }; + match execute(deps.as_mut(), mock_env(), info, msg) { + Ok(_) => panic!("Must return error"), + Err(ContractError::InvalidCharacter { .. }) => {} + Err(_) => panic!("Unknown error"), + } + } + + #[test] + fn fails_on_register_insufficient_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + + // anyone can register an available name with sufficient fees + let info = mock_info("alice_key", &[]); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn fails_on_register_wrong_fee_denom() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + + // anyone can register an available name with sufficient fees + let info = mock_info("alice_key", &coins(2, "earth")); + let msg = ExecuteMsg::Register { + name: "alice".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + #[test] + fn transfer_works() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Transfer message"); + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "bob_key"); + } + + #[test] + fn transfer_works_with_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(2, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[coin(1, "earth"), coin(2, "token")]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let _res = execute(deps.as_mut(), mock_env(), info, msg) + .expect("contract successfully handles Transfer message"); + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "bob_key"); + } + + #[test] + fn fails_on_transfer_non_existent() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("frank_key", &coins(2, "token")); + let msg = ExecuteMsg::Transfer { + name: "alice42".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::NameNotExists { name }) => assert_eq!(name, "alice42"), + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (alice_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn fails_on_transfer_from_nonowner() { + let mut deps = mock_dependencies(); + mock_init_no_price(deps.as_mut()); + mock_alice_registers_name(deps.as_mut(), &[]); + + // alice can transfer her name successfully to bob + let info = mock_info("frank_key", &coins(2, "token")); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("Must return error"), + Err(ContractError::Unauthorized { .. }) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (alice_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn fails_on_transfer_insufficient_fees() { + let mut deps = mock_dependencies(); + mock_init_with_price(deps.as_mut(), coin(2, "token"), coin(5, "token")); + mock_alice_registers_name(deps.as_mut(), &coins(2, "token")); + + // alice can transfer her name successfully to bob + let info = mock_info("alice_key", &[coin(1, "earth"), coin(2, "token")]); + let msg = ExecuteMsg::Transfer { + name: "alice".to_string(), + to: "bob_key".to_string(), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg); + + match res { + Ok(_) => panic!("register call should fail with insufficient fees"), + Err(ContractError::InsufficientFundsSend {}) => {} + Err(e) => panic!("Unexpected error: {:?}", e), + } + + // querying for name resolves to correct address (bob_key) + assert_name_owner(deps.as_ref(), "alice", "alice_key"); + } + + #[test] + fn returns_empty_on_query_unregistered_name() { + let mut deps = mock_dependencies(); + + mock_init_no_price(deps.as_mut()); + + // querying for unregistered name results in NotFound error + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::ResolveRecord { + name: "alice".to_string(), + }, + ) + .unwrap(); + let value: ResolveRecordResponse = from_binary(&res).unwrap(); + assert_eq!(None, value.address); + } +}