diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed169be --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.dfx +.vscode +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1b16a5d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1601 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "beef" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "lazy_static", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[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 = "blocking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "candid" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7577605c33073dcafc17a5ed6373aa0cb7005e7d4e4b7cd40ca01cb2385533" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive", + "codespan-reporting", + "hex", + "ic-types", + "lalrpop", + "lalrpop-util", + "leb128", + "logos", + "num-bigint", + "num-traits", + "num_enum", + "paste", + "pretty", + "serde", + "serde_bytes", + "thiserror", +] + +[[package]] +name = "candid_derive" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e02c03c4d547674a3f3f3109538fb49871fbe636216daa019f06a62faca9061" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cap-sdk" +version = "0.1.0-alpha1" +source = "git+https://github.com/Psychedelic/cap?branch=cap-sdk#942985cd678ed30e78d69ef72bba66764ab1e528" +dependencies = [ + "async-stream", + "cap-sdk-core", + "futures", + "ic-kit", + "lazy_static", + "thiserror", +] + +[[package]] +name = "cap-sdk-core" +version = "0.1.0-alpha1" +source = "git+https://github.com/Psychedelic/cap?branch=cap-sdk#942985cd678ed30e78d69ef72bba66764ab1e528" +dependencies = [ + "ic-history-common", + "ic-kit", + "serde", + "thiserror", +] + +[[package]] +name = "cap-standards" +version = "0.1.0-alpha1" +source = "git+https://github.com/Psychedelic/cap?branch=cap-sdk#942985cd678ed30e78d69ef72bba66764ab1e528" +dependencies = [ + "bincode", + "candid", + "cap-sdk", + "ic-cdk", + "serde", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "crc32fast", + "hex", + "ic-cdk", + "ic-kit", + "ic-types", + "serde", + "sha2", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + +[[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 = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fastrand" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-executor" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-task" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[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.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "ic-cdk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0584eb389b9182ee3be53a44f0123d478426a7ef4fcc2d98c2e3716f654e1c92" +dependencies = [ + "candid", + "cfg-if", + "serde", +] + +[[package]] +name = "ic-cdk-macros" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0af2789a609a78e5e18a01792c9ee1237d45b4a86824ad7ccde80edf279ae230" +dependencies = [ + "candid", + "ic-cdk", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream", + "syn", +] + +[[package]] +name = "ic-certified-map" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54640ea6dc0dd1c7cddec8586f42c81aa5a02edd0b82ad7b96360ee5b591d7b6" +dependencies = [ + "serde", + "serde_bytes", + "sha2", +] + +[[package]] +name = "ic-history-common" +version = "0.1.0" +source = "git+https://github.com/Psychedelic/cap?branch=cap-sdk#942985cd678ed30e78d69ef72bba66764ab1e528" +dependencies = [ + "async-std", + "ic-cdk", + "ic-certified-map", + "ic-kit", + "serde", + "serde_bytes", + "serde_cbor", + "sha2", +] + +[[package]] +name = "ic-kit" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0d4a05326865a582add1d6bcdec2b5d2af0415a12f6ef2431f3e93e52c347a" +dependencies = [ + "async-std", + "futures", + "ic-cdk", + "ic-cdk-macros", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c021c11ae1d716f45d783f5764f418a11f12aea1fdc4fc8a2b2242e0dae708" +dependencies = [ + "base32", + "crc32fast", + "hex", + "serde", + "serde_bytes", + "sha2", + "thiserror", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" +dependencies = [ + "regex", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", + "value-bag", +] + +[[package]] +name = "logos" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn", + "utf8-ranges", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nft" +version = "0.1.0" +dependencies = [ + "cap-sdk", + "cap-standards", + "common", + "derive-new", + "ic-cdk", + "ic-kit", + "serde", + "serde_bytes", + "serde_cbor", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polling" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9940b913ee56ddd94aec2d3cd179dd47068236f42a1a6415ccf9d880ce2a61" +dependencies = [ + "arrayvec", + "typed-arena", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_tokenstream" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3ce95257fba42a656f558db28d56a9fac5aa6e4f29c5ef607f32f524fab0ab" +dependencies = [ + "proc-macro2", + "serde", + "syn", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "string_cache" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7fd93ca --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [ + "nft", +] + +[profile.release] +lto = true +opt-level = 'z' +overflow-checks = true + +[profile.bench] +overflow-checks = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cca9e43 --- /dev/null +++ b/LICENSE @@ -0,0 +1,208 @@ + 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 + +LLVM EXCEPTION TO THE APACHE 2.0 LICENSE + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you may +redistribute such embedded portions in such Object form without complying with +the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a court +of competent jurisdiction determines that the patent provision (Section 3), the +indemnity provision (Section 9) or other Section of the License conflicts with +the conditions of the GPLv2, you may retroactively and prospectively choose to +deem waived or otherwise exclude such Section(s) of the License, but only in +their entirety and only with respect to the Combined Software. + +END OF LLVM EXCEPTION + +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. + +END OF APPENDIX diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a9de86 --- /dev/null +++ b/README.md @@ -0,0 +1,484 @@ +![Group 5982](https://user-images.githubusercontent.com/73345016/144523337-fe7d6b49-d0a7-4621-852d-daeee344d4e2.png) + +## DIP721 - Introduction + +DIP721 is an ERC-721 style non-fungible token standard built mirroring its Ethereum counterpart and adapting it to the Internet Computer, maintaining the same interface. + +This standard aims to adopt the EIP-721 to the Internet Computer; providing a +simple, non-ambiguous, extendable API for the transfer and tracking ownership of NFTs and expanding/building upon the EXT standard with partial compatibility. + +- [Candid](https://github.com/Psychedelic/DIP721/blob/main/nft/candid/nft.did) and interface. + +> Important: This is an an in-development standard, consider it a work in progress as we finalize details in its design and gather feedback from the community. + +## Contributing + +We'd like to collaborate with the community to provide better token standard implementation for the developers on the IC, if you have some ideas you'd like to discuss, submit an issue, if you want to improve the code or you made a different implementation, make a pull request! + +## Motivation + +DIP-721 tries to improve on existing Internet Computer standards in the following ways: + +- Most NFT projects don't require a multi-token standard, and a simple + NFT standard like DIP-721 would suffice. Users of NFTs based on multi-token standards (such as EXT) will be required + to pay extra cycle cost compared to DIP-721. +- Most NFT projects don't require the generalization of IC Principals into Ledger Accounts, and avoiding that direction can help reduce the complexity of the API. +- Most current NFT standards on the IC don't yet have proper metadata support for NFTs. +- The ability to track the history of NFT transfers is an important requirement of almost every + NFT projects, and it should be a core part of the standard. +- Most NFT projects don't require arbitrarily large token balances, and that can lead to more cycle inefficient implementations. +- DIP-721 closely follows the original EIP-721, and that will make porting existing + Ethereum contracts onto the IC more straightforward. +- NFTs projects that choose to implement DIP-721, will be able to implement other NFT + token standards without worrying about interface function name collision. This is achieved by DIP-721 + postfixing all its interface methods with DIP721. + +## Interface specification + +### Basic interface + +Every DIP-721 compatible smart contract must implement this interface. All other interfaces are optional. +For all interface methods trapping (instead of returning an error) is allowed, but not encouraged. + +#### balanceOfDip721 + +Count of all NFTs assigned to `user`. + +``` +balanceOfDip721: (user: principal) -> (nat64) query; +``` + +#### ownerOfDip721 + +Returns the owner of the NFT associated with `token_id`. Returns ApiError.InvalidTokenId, if the token id is invalid. + +``` +ownerOfDip721: (token_id: nat64) -> (OwnerResult) query; +``` + +#### safeTransferFromDip721 + +Safely transfers token_id token from user `from` to user `to`. +If `to` is zero, then `ApiError.ZeroAddress` should be returned. If the caller is neither +the owner, nor an approved operator, nor someone approved with the `approveDip721` function, then `ApiError.Unauthorized` +should be returned. If `token_id` is not valid, then `ApiError.InvalidTokenId` is returned. + +``` +safeTransferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt); +``` + +#### transferFromDip721 + +Identical to `safeTransferFromDip721` except that this function doesn't check whether the `to` +is a zero address or not. + +``` +transferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt); +``` + +#### supportedInterfacesDip721 + +Returns the interfaces supported by this smart contract. + +``` +supportedInterfacesDip721: () -> (vec InterfaceId) query; +``` + +##### logoDip721 + +Returns the logo of the NFT contract. + +``` +logoDip721: () -> (LogoResult) query; +``` + +##### nameDip721 + +Returns the name of the NFT contract. + +``` +nameDip721: () -> (text) query; +``` + +##### symbolDip721 + +Returns the symbol of the NFT contract. + +``` +symbolDip721: () -> (text) query; +``` + +##### totalSupplyDip721 + +Returns the total current supply of NFT tokens. NFTs that are minted and later +burned explictely or sent to the zero address should also count towards totalSupply. + +``` +totalSupplyDip721: () -> (nat64) query; +``` + +##### getMetadataDip721 + +Returns the metadata for `token_id`. Returns `ApiError.InvalidTokenId`, if the `token_id` +is invalid. + +``` +getMetadataDip721: (token_id: nat64) -> MetadataResult query; +``` + +##### getMetadataForUserDip721 + +Returns all the metadata for the coins `user` owns. + +``` +getMetadataForUserDip721: (user: principal) -> (vec ExtendedMetadataResult); +``` + +### Transfer notification interface + +This interface add notification feature for NFT transfers. Implementing this interface +might open up other smart contracts to re-entrancy attacks. + +#### safeTransferFromNotifyDip721 + +Same as `safeTransferFromDip721`, but `to` is treated as a smart contract that implements +the `Notification` interface. Upon successful transfer onDIP721Received +is called with `data`. + +``` +safeTransferFromNotifyDip721: (from: principal, to: principal, token_id: nat64, data: vec nat8) -> (TxReceipt); +``` + +#### transferFromNotifyDip721 + +Same as `transferFromDip721`, but `to` is treated as a smart contract that implements +the `Notification` interface. Upon successful transfer onDIP721Received +is called with `data`. + +``` +transferFromNotifyDip721: (from: principal, to: principal, token_id: nat64, data: vec nat8) -> (TxReceipt); +``` + +### Approval interface + +This interface adds approve functionality to DIP-721 tokens. + +#### approveDip721 + +Change or reaffirm the approved address for an NFT. The zero address indicates +there is no approved address. Only one user can be approved at a time to manage token_id. +Approvals given by the `approveDip721` function are independent from approvals given by the `setApprovalForAllDip721`. +Returns `ApiError.InvalidTokenId`, if the `token_id` is not valid. +Returns `ApiError.Unauthorized` in case the caller neither owns +`token_id` nor he is an operator approved by a call to +the `setApprovalForAll` function. + +``` +approveDip721: (user: principal, nat64: token_id) -> (TxReceipt) query; +``` + +#### setApprovalForAllDip721 + +Enable or disable an `operator` to manage all of the tokens for the caller of +this function. Multiple operators can be given permission at the same time. +Approvals granted by the `approveDip721` function are independent from the approvals granted +by `setApprovalForAll` function. The zero address indicates +there are no approved operators. + +``` +setApprovalForAllDip721: (operator: principal, isApproved: bool) -> (TxReceipt); +``` + +#### getApprovedDip721 + +Returns the approved user for `token_id`. Returns `ApiError.InvalidTokenId` +if the `token_id` is invalid. + +``` +getApprovedDip721: (token_id: nat64) -> (TxReceipt) query; +``` + +#### isApprovedForAllDip721 + +Returns `true` if the given `operator` is an approved operator for all the tokens owned by the caller, returns `false` otherwise. + +``` +isApprovedForAllDip721: (operator: principal) -> (bool) query; +``` + +### Mint interface + +This interface adds mint functionality to DIP-721 tokens. + +#### mintDip721 + +Mint an NFT for principal `to`. The parameter `blobContent` is non zero, if the NFT +contract embeds the NFTs in the smart contract. Implementations are encouraged +to only allow minting by the owner of the smart contract. Returns `ApiError.Unauthorized`, +if the caller doesn't have the permission to mint the NFT. + +``` +mintDip721: (to: principal, metadata: Metadata, blobContent: blob) -> (MintReceipt); +``` + +### Burn interface + +This interface adds burn functionality to DIP-721 tokens. + +#### burnDip721 + +Burn an NFT identified by `token_id`. Implementations are encouraged to only allow +burning by the owner of the `token_id`. Returns `ApiError.Unauthorized`, +if the caller doesn't have the permission to burn the NFT. Returns `ApiError.InvalidTokenId`, +if the provided token_id doesn't exist. + +``` +burnDip721: (token_id: nat64) -> (TxReceipt); +``` + +## Notification interface + +`transferFromNotifyDip721` and `safeTransferFromNotifyDip721` functions can - upon successfull NFT transfer - notify other smart contracts +that adhere to the following interface. + +`caller` is the entity that called the `transferFromNotifyDip721` or `safeTransferFromNotifyDip721` function, +and `from` is the previous owner of the NFT. + +``` +onDIP721Received: (address caller, address from, uint256 token_id, bytes data) -> (); +``` + +## Datastructure specification + +### OwnerResult + +``` +type ApiError = + variant { + Unauthorized; + InvalidTokenId; + ZeroAddress; + Other; + }; +``` + +``` +type OwnerResult = +variant { + Err: ApiError; + Ok: Principal; + }; +``` + +### TxReceipt + +``` +type TxReceipt = +variant { + Err: ApiError; + Ok: nat; + }; +``` + +### InterfaceId + +``` +type InterfaceId = + variant { + Approval; + TransactionHistory; + Mint; + Burn; + TransferNotification; + }; +``` + +### LogoResult + +``` +type LogoResult = + record { + logo_type: text // MIME type of the logo + data: text // Base64 encoded logo + }; +``` + +``` + type ExtendedMetadataResult = + record { + metadata_desc: MetadataDesc; + token_id: nat64; + }; +``` + +### MetadataResult + +``` +type MetadataResult = + variant { + Err: ApiError; + Ok: MetadataDesc; + }; +``` + +``` +type MetadataDesc = vec MetadataPart; +``` + +``` +type MetadataPart = + record { + purpose: MetadataPurpose; + key_val_data: vec MetadataKeyVal; + data: blob; + }; +``` + +``` +type MetadataPurpose = + variant { + Preview; // used as a preview, can be used as preivew in a wallet + Rendered; // used as a detailed version of the NFT + }; +``` + +``` +type MetadataKeyVal = + record { + text; + MetadataVal; + }; +``` + +``` +type MetadataVal = + variant { + TextContent : Text; + BlobContent : blob; + NatContent : Nat; + Nat8Content: Nat8; + Nat16Content: Nat16; + Nat32Content: Nat32; + Nat64Content: Nat64; + }; +``` + +#### Predefined key value pairs + +##### content hash + +Uniquely identifies the content of the NFT by its hash fingerprint. This field might +be missing unless the NFT is stored on the Web, in which case the content hash +is mandatory. + +``` +{"contentHash", BlobContent()} +``` + +##### contentType + +``` +{"contentType", TextContent()} +``` + +##### locationType + +``` +{"locationType", Nat8Content()} + +1 - IPFS storage +2 - Asset canister storage +3 - URI(Web) storage +4 - Embedded in the token contract +``` + +##### location + +``` +{"location", any()} + +// where any() is one of the followings based on the "locationType" + +BlobContent() - IPFS +TextContent() - Asset canister +TextContent() - URI +location field is missing - Embedded in the token contract +``` + +### TxResult + +``` +type TxResult = + record { + fee: Nat; + transaction_type: TransactionType; + }; + +type TransactionType = + variant { + Transfer: + record { + token_id: nat64; + from: principal; + to: principal; + }; + TransferFrom: + record { + token_id: nat64; + from: principal; + to: principal; + }; + Approve: + record { + token_id: nat64; + from: principal; + to: principal; + }; + SetApprovalForAll: + record { + from: principal; + to: principal; + }; + Mint: + record { + token_id: nat64; + }; + Burn: + record { + token_id: nat64; + }; + }; +``` + +### MintReceipt + +``` +type MintReceipt = + variant { + Err: variant { + Unauthorized; + }; + Ok: record { + token_id: nat64; // minted token id + id: nat // transaction id + }; + }; +``` + +### BurnRequest + +``` +type BurnRequest = + record { + token_id: nat64; + } +``` + +## Fees + +Implementations are encouraged not to charge any fees when an approved entity +transfers NFTs on the user's behalf, as that entity might have no means for payment. +If any fees needs to be taken for such a `transferFromDip721`, `safeTransferFromDip721`, +`transferFromNotifyDip721`, `safeTransferFromNotifyDip721` call, +then it is encouraged to be taken during the call to `approveDip721`, `setApprovalForAllDip721` +from the caller's balance. diff --git a/canister_ids.json b/canister_ids.json new file mode 100644 index 0000000..8d94d78 --- /dev/null +++ b/canister_ids.json @@ -0,0 +1,8 @@ +{ + "cap": { + "ic": "4qyv7-ryaaa-aaaal-aaara-cai" + }, + "nft": { + "ic": "4xztl-4aaaa-aaaal-aaarq-cai" + } +} diff --git a/cap.did b/cap.did new file mode 100644 index 0000000..c4cb67f --- /dev/null +++ b/cap.did @@ -0,0 +1,31 @@ +type GetIndexCanistersResponse = record { + witness : opt Witness; + canisters : vec principal; +}; +type GetTokenContractRootBucketArg = record { + witness : bool; + canister : principal; +}; +type GetTokenContractRootBucketResponse = record { + witness : opt Witness; + canister : opt principal; +}; +type GetUserRootBucketsArg = record { user : principal; witness : bool }; +type GetUserRootBucketsResponse = record { + witness : opt Witness; + contracts : vec principal; +}; +type WithWitnessArg = record { witness : bool }; +type Witness = record { certificate : vec nat8; tree : vec nat8 }; +service : { + deploy_plug_bucket : (principal, nat64) -> (); + get_index_canisters : (WithWitnessArg) -> (GetIndexCanistersResponse) query; + get_token_contract_root_bucket : (GetTokenContractRootBucketArg) -> ( + GetTokenContractRootBucketResponse, + ) query; + get_user_root_buckets : (GetUserRootBucketsArg) -> ( + GetUserRootBucketsResponse, + ) query; + insert_new_users : (principal, vec principal) -> (); + install_bucket_code : (principal) -> (); +} \ No newline at end of file diff --git a/cap.wasm b/cap.wasm new file mode 100644 index 0000000..660a633 Binary files /dev/null and b/cap.wasm differ diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..51ae387 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "common" +version = "0.1.0" +authors = ["Daniel Graczer "] +edition = "2018" + +[dependencies] +crc32fast = "1.2.0" +ic-kit = "0.4.2" +ic-cdk = "0.3.1" +ic-types = "0.2.1" +hex = {version = "0.4.2", features = ["serde"] } +serde = { version="1.0.130", features = ["derive"] } +sha2 = "0.9.1" diff --git a/common/src/account_identifier.rs b/common/src/account_identifier.rs new file mode 100644 index 0000000..bb97119 --- /dev/null +++ b/common/src/account_identifier.rs @@ -0,0 +1,263 @@ +use crate::principal_id::*; +use crate::sha224::Sha224; + +use crc32fast; +use hex; +use ic_kit::candid::types::{Serializer, Type}; +use ic_kit::candid::{decode_one, encode_one, CandidType, Principal}; +use serde::{de, de::Error, Deserialize, Serialize}; + +use std::{ + convert::{TryFrom, TryInto}, + fmt::{Display, Formatter}, + str::FromStr, +}; + +/// While this is backed by an array of length 28, it's canonical representation +/// is a hex string of length 64. The first 8 characters are the CRC-32 encoded +/// hash of the following 56 characters of hex. Both, upper and lower case +/// characters are valid in the input string and can even be mixed. +/// +/// When it is encoded or decoded it will always be as a string to make it +/// easier to use from DFX. +#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AccountIdentifierStruct { + pub hash: [u8; 28], +} + +pub static SUB_ACCOUNT_ZERO: Subaccount = Subaccount([0; 32]); +static ACCOUNT_DOMAIN_SEPERATOR: &[u8] = b"\x0Aaccount-id"; + +#[test] +fn manual_test() { + use ic_kit::candid::Principal; + let principal = + Principal::from_text("a7v4m-eprbc-ecduz-h6rwf-kvcs3-vxowl-dpscn-mh3iq-lwgzg-ur4bd-jae") + .unwrap(); + let principal_id = PrincipalId(principal); + let account_identifier = AccountIdentifierStruct::new(principal_id, Some(Subaccount([1; 32]))); + println!("result here: {}", account_identifier.to_hex()); +} + +impl AccountIdentifierStruct { + pub fn new(account: PrincipalId, sub_account: Option) -> AccountIdentifierStruct { + let mut hash = Sha224::new(); + hash.write(ACCOUNT_DOMAIN_SEPERATOR); + hash.write(account.as_slice()); + + let sub_account = sub_account.unwrap_or(SUB_ACCOUNT_ZERO); + hash.write(&sub_account.0[..]); + + AccountIdentifierStruct { + hash: hash.finish(), + } + } + + pub fn from_hex(hex_str: &str) -> Result { + let hex: Vec = hex::decode(hex_str).map_err(|e| e.to_string())?; + Self::from_slice(&hex[..]) + } + + /// Goes from the canonical format (with checksum) encoded in bytes rather + /// than hex to AccountIdentifier + pub fn from_slice(v: &[u8]) -> Result { + // Trim this down when we reach rust 1.48 + let hex: Box<[u8; 32]> = match v.to_vec().into_boxed_slice().try_into() { + Ok(h) => h, + Err(_) => { + let hex_str = hex::encode(v); + return Err(format!( + "{} has a length of {} but we expected a length of 64", + hex_str, + hex_str.len() + )); + } + }; + check_sum(*hex) + } + + pub fn to_hex(&self) -> String { + hex::encode(self.to_vec()) + } + + pub fn to_vec(&self) -> Vec { + [&self.generate_checksum()[..], &self.hash[..]].concat() + } + + pub fn generate_checksum(&self) -> [u8; 4] { + let mut hasher = crc32fast::Hasher::new(); + hasher.update(&self.hash); + hasher.finalize().to_be_bytes() + } +} + +impl From for AccountIdentifierStruct { + fn from(principal: Principal) -> Self { + AccountIdentifierStruct::new(PrincipalId(principal), Some(Subaccount([0; 32]))) + } +} + +impl Display for AccountIdentifierStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.to_hex().fmt(f) + } +} + +impl FromStr for AccountIdentifierStruct { + type Err = String; + + fn from_str(s: &str) -> Result { + AccountIdentifierStruct::from_hex(s) + } +} + +impl Serialize for AccountIdentifierStruct { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_hex().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for AccountIdentifierStruct { + // This is the canonical way to read a this from string + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + D::Error: de::Error, + { + let hex: [u8; 32] = hex::serde::deserialize(deserializer)?; + check_sum(hex).map_err(D::Error::custom) + } +} + +impl From for AccountIdentifierStruct { + fn from(pid: PrincipalId) -> Self { + AccountIdentifierStruct::new(pid, None) + } +} + +fn check_sum(hex: [u8; 32]) -> Result { + // Get the checksum provided + let found_checksum = &hex[0..4]; + + // Copy the hash into a new array + let mut hash = [0; 28]; + hash.copy_from_slice(&hex[4..32]); + + let account_id = AccountIdentifierStruct { hash }; + let expected_checksum = account_id.generate_checksum(); + + // Check the generated checksum matches + if expected_checksum == found_checksum { + Ok(account_id) + } else { + Err(format!( + "Checksum failed for {}, expected check bytes {} but found {}", + hex::encode(&hex[..]), + hex::encode(expected_checksum), + hex::encode(found_checksum), + )) + } +} + +impl CandidType for AccountIdentifierStruct { + // The type expected for account identifier is + fn _ty() -> Type { + String::_ty() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: Serializer, + { + self.to_hex().idl_serialize(serializer) + } +} + +/// Subaccounts are arbitrary 32-byte values. +#[derive(Serialize, Deserialize, CandidType, Clone, Hash, Debug, PartialEq, Eq, Copy)] +#[serde(transparent)] +pub struct Subaccount(pub [u8; 32]); + +impl Subaccount { + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} + +impl From<&PrincipalId> for Subaccount { + fn from(principal_id: &PrincipalId) -> Self { + let mut subaccount = [0; std::mem::size_of::()]; + let principal_id = principal_id.as_slice(); + subaccount[0] = principal_id.len().try_into().unwrap(); + subaccount[1..1 + principal_id.len()].copy_from_slice(principal_id); + Subaccount(subaccount) + } +} + +impl TryFrom<&Subaccount> for PrincipalId { + type Error = PrincipalIdError; + + fn try_from(subaccount: &Subaccount) -> Result { + let len = subaccount.0[0] as usize; + let bytes = &subaccount.0[1..]; + bytes[0..len.min(bytes.len())].try_into() + } +} + +impl From for Vec { + fn from(val: Subaccount) -> Self { + val.0.to_vec() + } +} + +impl TryFrom<&[u8]> for Subaccount { + type Error = std::array::TryFromSliceError; + + fn try_from(slice: &[u8]) -> Result { + slice.try_into().map(Subaccount) + } +} + +impl Display for Subaccount { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + hex::encode(self.0).fmt(f) + } +} + +#[test] +fn check_round_trip() { + let ai = AccountIdentifierStruct { hash: [7; 28] }; + let res = ai.to_hex(); + assert_eq!( + res.parse(), + Ok(ai), + "The account identifier doesn't change after going back and forth between a string" + ) +} + +#[test] +fn check_encoding() { + let ai = AccountIdentifierStruct { hash: [7; 28] }; + + let en1 = encode_one(ai).unwrap(); + let en2 = encode_one(ai.to_string()).unwrap(); + + assert_eq!( + &en1, &en2, + "Candid encoding of an account identifier and a string should be identical" + ); + + let de1: String = decode_one(&en1[..]).unwrap(); + let de2: AccountIdentifierStruct = decode_one(&en2[..]).unwrap(); + + assert_eq!( + de1.parse(), + Ok(de2), + "The types are the same after decoding, even through a different type" + ); + + assert_eq!(de2, ai, "And the value itself hasn't changed"); +} diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..b1f04e8 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,6 @@ +#![allow(warnings)] + +pub mod account_identifier; +pub mod principal_id; +mod rust_sha224; +mod sha224; diff --git a/common/src/principal_id.rs b/common/src/principal_id.rs new file mode 100644 index 0000000..1fe4126 --- /dev/null +++ b/common/src/principal_id.rs @@ -0,0 +1,539 @@ +use ic_kit::candid::{CandidType, Principal}; +use ic_types::PrincipalError; + +use crate::sha224::Sha224; +use ic_kit::candid::types::{Serializer, Type, TypeId}; + +use serde::{Deserialize, Serialize}; + +use std::{ + convert::TryFrom, + error::Error, + fmt, + hash::{Hash, Hasher}, +}; + +/// The type representing principals as described in the [interface +/// spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_principals). +/// +/// A principal is just a blob that is displayed in a particular way. +/// (see https://sdk.dfinity.org/docs/interface-spec/index.html#textual-ids) +/// +/// Principals have variable length, bounded by 29 bytes. Since we +/// want [`PrincipalId`] to implement the Copy trait, we encode them as +/// a fixed-size array and a length. +#[derive(Clone, Copy, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct PrincipalId(pub Principal); + +impl PartialEq for PrincipalId { + fn eq(&self, other: &PrincipalId) -> bool { + self.0 == other.0 + } +} + +impl Hash for PrincipalId { + fn hash(&self, state: &mut H) { + let slice = self.0.as_slice(); + slice.len().hash(state); + let mut array = [0; Self::MAX_LENGTH_IN_BYTES]; + array[..slice.len()].copy_from_slice(slice); + array.hash(state); + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct PrincipalIdError(pub PrincipalError); + +impl PrincipalIdError { + #[allow(non_snake_case)] + pub fn TooLong(_: usize) -> Self { + PrincipalIdError(PrincipalError::BufferTooLong()) + } +} + +impl Error for PrincipalIdError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.0.source() + } +} + +impl fmt::Display for PrincipalIdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Default for PrincipalId { + fn default() -> Self { + PrincipalId(Principal::management_canister()) + } +} + +impl From for PrincipalId { + fn from(p: Principal) -> PrincipalId { + PrincipalId(p) + } +} +impl From for Principal { + fn from(p: PrincipalId) -> Principal { + p.0 + } +} + +impl PrincipalId { + pub const MAX_LENGTH_IN_BYTES: usize = 29; + const HASH_LEN_IN_BYTES: usize = 28; + + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } + + pub fn into_vec(self) -> Vec { + self.to_vec() + } +} + +impl fmt::Display for PrincipalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Debug for PrincipalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for Vec { + fn from(val: PrincipalId) -> Self { + val.to_vec() + } +} + +/// The [`TryFrom`] trait should only be used when parsing data; fresh ids +/// should always be created with the functions below (PrincipalId::new_*) +impl TryFrom<&[u8]> for PrincipalId { + type Error = PrincipalIdError; + + fn try_from(blob: &[u8]) -> Result { + Principal::try_from(blob) + .map(Self) + .map_err(PrincipalIdError) + } +} + +impl TryFrom> for PrincipalId { + type Error = PrincipalIdError; + + fn try_from(blob: Vec) -> Result { + Principal::try_from(blob) + .map(Self) + .map_err(PrincipalIdError) + } +} +impl TryFrom<&Vec> for PrincipalId { + type Error = PrincipalIdError; + + fn try_from(blob: &Vec) -> Result { + Principal::try_from(blob) + .map(Self) + .map_err(PrincipalIdError) + } +} + +impl AsRef<[u8]> for PrincipalId { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl std::str::FromStr for PrincipalId { + type Err = PrincipalIdError; + + fn from_str(input: &str) -> Result { + Principal::from_str(input) + .map(Self) + .map_err(PrincipalIdError) + } +} + +/// Some principal ids have special classes (system-generated, +/// self-authenticating, derived), see https://sdk.dfinity.org/docs/interface-spec/index.html#id-classes +/// +/// The following functions allow creating and testing for the special forms. +impl PrincipalId { + const TYPE_OPAQUE: u8 = 0x01; + const TYPE_SELF_AUTH: u8 = 0x02; + const TYPE_DERIVED: u8 = 0x03; + const TYPE_ANONYMOUS: u8 = 0x04; + + /// Opaque ids are usually used for system-internal ids (maybe system + /// canisters, maybe test ids). Instead of using this directly, consider + /// adding a separate constructor here for every such use case, so that + /// one can easily check here that all such ids are disjoint. + pub(crate) fn new_opaque(blob: &[u8]) -> Self { + let mut bytes = blob.to_vec(); + bytes.push(Self::TYPE_OPAQUE); + PrincipalId(Principal::from_slice(&bytes[..])) + } + + /// Creates an opaque id from the first `len` bytes of `blob`. + /// + /// `len` must be _strictly_ less than `MAX_LENGTH_IN_BYTES` so that there + /// is enough room for the suffix. + pub(crate) const fn new_opaque_from_array( + mut blob: [u8; Self::MAX_LENGTH_IN_BYTES], + len: usize, + ) -> Self { + blob[len] = Self::TYPE_OPAQUE; + PrincipalId::new(len + 1, blob) + } + + pub fn new_user_test_id(n: u64) -> Self { + let mut bytes = n.to_le_bytes().to_vec(); + bytes.push(0xfe); // internal marker for user test ids + Self::new_opaque(&bytes[..]) + } + pub fn new_node_test_id(n: u64) -> Self { + let mut bytes = n.to_le_bytes().to_vec(); + bytes.push(0xfd); // internal marker for node test ids + Self::new_opaque(&bytes[..]) + } + pub fn new_subnet_test_id(n: u64) -> Self { + let mut bytes = n.to_le_bytes().to_vec(); + bytes.push(0xfc); // internal marker for subnet test ids + Self::new_opaque(&bytes[..]) + } + + pub const fn new(len: usize, data: [u8; Self::MAX_LENGTH_IN_BYTES]) -> Self { + // Calls in constant functions are limited to constant functions, + // tuple structs and tuple variants (E0015) + use std::ops::Range; + const fn get(mut data: &[u8], r: Range) -> Option<&[u8]> { + if r.start > r.end || data.len() < r.end { + return None; + } + + while data.len() > r.end { + match data { + [x @ .., _] => data = x, + [] => {} //unreachable!(), + } + } + + while data.len() > r.end - r.start { + match data { + [_, x @ ..] => data = x, + [] => {} //unreachable!(), + } + } + + Some(data) + } + pub const fn range(data: &[u8], r: Range) -> &[u8] { + let (start, end) = (r.start, r.end); + match get(data, r) { + Some(v) => v, + None => { + // TODO: remove (blocked by rust-lang/rust#85194) + // Give good panic messages + let _ = &data[start]; + let _ = &data[end]; + let _ = &data[end - start]; + const ASSERT: [(); 1] = [()]; + #[allow(unconditional_panic)] + let _ = ASSERT[1]; + + data + } + } + } + + //PrincipalId(Principal::from_slice(&data[0..len])) + PrincipalId(Principal::from_slice(range(&data, 0..len))) + } + + pub fn new_self_authenticating(pubkey: &[u8]) -> Self { + let mut id: [u8; 29] = [0; 29]; + id[..28].copy_from_slice(&Sha224::hash(pubkey)); + id[28] = Self::TYPE_SELF_AUTH; + PrincipalId(Principal::from_slice(&id)) + } + + pub fn new_derived(registerer: &PrincipalId, seed: &[u8]) -> Self { + let mut blob: Vec = registerer.into_vec(); + blob.insert(0, blob.len() as u8); + blob.extend(seed); + let mut bytes = Sha224::hash(&blob[..]).to_vec(); + bytes.push(Self::TYPE_DERIVED); + PrincipalId::try_from(&bytes[..]).unwrap() + } + + pub fn new_anonymous() -> Self { + PrincipalId(Principal::anonymous()) + } + + pub fn authenticates_for_pubkey(&self, pubkey: &[u8]) -> bool { + let blob = self.as_slice(); + if blob.len() != Self::HASH_LEN_IN_BYTES + 1 { + return false; + } + if blob.last() != Some(&Self::TYPE_SELF_AUTH) { + return false; + } + if Sha224::hash(pubkey) != blob[0..Self::HASH_LEN_IN_BYTES] { + return false; + } + true + } + + pub fn is_self_authenticating(&self) -> bool { + let blob = self.as_slice(); + if blob.len() != Self::HASH_LEN_IN_BYTES + 1 { + return false; + } + if blob.last() != Some(&Self::TYPE_SELF_AUTH) { + return false; + } + true + } + + pub fn is_derived(&self, registerer: &PrincipalId, seed: &[u8]) -> bool { + PrincipalId::new_derived(registerer, seed) == *self + } + + pub fn is_anonymous(&self) -> bool { + self.as_slice() == [Self::TYPE_ANONYMOUS] + } +} + +impl CandidType for PrincipalId { + fn id() -> TypeId { + TypeId::of::() + } + fn _ty() -> Type { + Type::Principal + } + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: Serializer, + { + serializer.serialize_principal(self.as_slice()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn parse_bad_checksum() { + let good = PrincipalId::from_str(&"bfozs-kwa73-7nadi".to_string()) + .expect("PrincipalId::from_str failed"); + assert_eq!( + PrincipalId::from_str(&"5h74t-uga73-7nadi".to_string()), + Err(PrincipalIdError(PrincipalError::AbnormalTextualFormat( + good.into() + ))) + ); + } + + #[test] + fn parse_too_short() { + assert_eq!( + PrincipalId::from_str(&"".to_string()), + Err(PrincipalIdError(PrincipalError::TextTooSmall())) + ); + assert_eq!( + PrincipalId::from_str(&"vpgq".to_string()), + Err(PrincipalIdError(PrincipalError::TextTooSmall())) + ); + } + + #[test] + fn parse_too_long() { + assert_eq!( + PrincipalId::from_str( + "fmakz-kp753-o4zo5-ktgeh-ozsvi-qzsee-ia77x-n3tf3-vkmyq-53gkv-cdgiq" + ), + Err(PrincipalIdError(PrincipalError::BufferTooLong())) + ) + } + + #[test] + fn parse_not_normalized() { + let good = PrincipalId::from_str(&"bfozs-kwa73-7nadi".to_string()) + .expect("PrincipalId::from_str failed"); + assert_eq!( + PrincipalId::from_str(&"BFOZS-KWA73-7NADI".to_string()), + Err(PrincipalIdError(PrincipalError::AbnormalTextualFormat( + good.into() + ))) + ); + assert_eq!( + PrincipalId::from_str(&"bfozskwa737nadi".to_string()), + Err(PrincipalIdError(PrincipalError::AbnormalTextualFormat( + good.into() + ))) + ); + assert_eq!( + PrincipalId::from_str(&"bf-oz-sk-wa737-nadi".to_string()), + Err(PrincipalIdError(PrincipalError::AbnormalTextualFormat( + good.into() + ))) + ); + } + + /// Now the tests related to the special classes + + #[test] + fn parse_opaque_id_ok() { + assert_eq!( + PrincipalId::from_str("2chl6-4hpzw-vqaaa-aaaaa-c"), + Ok(PrincipalId::new_opaque( + &[0xef, 0xcd, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00][..] + )) + ); + } + + #[test] + fn parse_self_authenticating_id_ok() { + let key = [ + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, + 0x11, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, + 0x33, 0x22, 0x11, 0x00, + ]; + let id = PrincipalId::new_self_authenticating(&key); + assert_eq!( + PrincipalId::from_str( + "bngem-gzprz-dtr6o-xnali-fgmfi-fjgpb-rya7j-x2idk-3eh6u-4v7tx-hqe" + ), + Ok(id) + ); + assert!(id.authenticates_for_pubkey(&key)); + } + + #[test] + fn parse_derived_id_ok() { + let registerer = PrincipalId::try_from( + &[ + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, + 0x11, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, + 0x33, + ][..], + ) + .unwrap(); + let seed = [0xdd, 0xcc, 0xbb, 0xaa, 0xdd, 0xcc, 0xbb, 0xaa]; + let id = PrincipalId::new_derived(®isterer, &seed); + assert_eq!( + PrincipalId::from_str( + "c2u3y-w273i-ols77-om7wu-jzrdm-gxxz3-b75cc-3ajdg-mauzk-hm5vh-jag" + ), + Ok(id) + ); + + assert!(id.is_derived(®isterer, &seed)); + } + + #[test] + fn parse_anonymous_id_ok() { + assert!(PrincipalId::new_anonymous().is_anonymous()); + assert_eq!( + PrincipalId::from_str("2vxsx-fae"), + Ok(PrincipalId::new_anonymous()) + ); + } + + #[test] + fn sorts_correctly() { + let mut v = vec![ + PrincipalId::from_str("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(), + PrincipalId::from_str( + "c2u3y-w273i-ols77-om7wu-jzrdm-gxxz3-b75cc-3ajdg-mauzk-hm5vh-jag", + ) + .unwrap(), + PrincipalId::try_from(&[3, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::from_str("bfozs-kwa73-7nadi").unwrap(), + PrincipalId::from_str("aaaaa-aa").unwrap(), + PrincipalId::from_str("2vxsx-fae").unwrap(), + PrincipalId::try_from(&[4, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::try_from(&[0, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::try_from(&[1, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + ]; + v.sort_unstable(); + assert_eq!( + v, + vec![ + PrincipalId::from_str("aaaaa-aa").unwrap(), + PrincipalId::from_str("2vxsx-fae").unwrap(), + PrincipalId::from_str("bfozs-kwa73-7nadi").unwrap(), + PrincipalId::from_str("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(), + PrincipalId::try_from(&[0, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::try_from(&[1, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::try_from(&[3, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::try_from(&[4, 0, 0, 0, 0, 0, 0, 0, 253, 1][..]).unwrap(), + PrincipalId::from_str( + "c2u3y-w273i-ols77-om7wu-jzrdm-gxxz3-b75cc-3ajdg-mauzk-hm5vh-jag" + ) + .unwrap(), + ] + ); + } + + #[test] + fn hashes_correctly() { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + fn hash_id_string(s: &str) -> u64 { + calculate_hash(&PrincipalId::from_str(s).unwrap()) + } + fn hash_id_slice(v: &[u8]) -> u64 { + calculate_hash(&PrincipalId::try_from(v).unwrap()) + } + + assert_eq!(hash_id_string("aaaaa-aa"), 7819764810086413800); + assert_eq!(hash_id_string("2vxsx-fae"), 265120109611795366); + assert_eq!(hash_id_string("bfozs-kwa73-7nadi"), 5239847422961869918); + assert_eq!( + hash_id_string("2chl6-4hpzw-vqaaa-aaaaa-c"), + 4991410779248500671 + ); + assert_eq!( + hash_id_string("c2u3y-w273i-ols77-om7wu-jzrdm-gxxz3-b75cc-3ajdg-mauzk-hm5vh-jag"), + 15210277524485168571 + ); + + assert_eq!( + hash_id_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 253, 1]), + 727338461816966860 + ); + assert_eq!( + hash_id_slice(&[1, 0, 0, 0, 0, 0, 0, 0, 253, 1]), + 297900807593556648 + ); + assert_eq!( + hash_id_slice(&[3, 0, 0, 0, 0, 0, 0, 0, 253, 1]), + 11403466979739875017 + ); + assert_eq!( + hash_id_slice(&[4, 0, 0, 0, 0, 0, 0, 0, 253, 1]), + 7553483959829495483 + ); + } +} diff --git a/common/src/rust_sha224.rs b/common/src/rust_sha224.rs new file mode 100644 index 0000000..f87eb4b --- /dev/null +++ b/common/src/rust_sha224.rs @@ -0,0 +1,22 @@ +use sha2::{Digest, Sha224}; + +#[derive(Default)] +pub struct InternalSha224 { + state: Sha224, +} + +pub fn hash(data: &[u8]) -> [u8; 28] { + let mut hasher = Sha224::new(); + hasher.update(data); + hasher.finalize().into() +} + +impl InternalSha224 { + pub fn write(&mut self, data: &[u8]) { + self.state.update(data); + } + + pub fn finish(self) -> [u8; 28] { + self.state.finalize().into() + } +} diff --git a/common/src/sha224.rs b/common/src/sha224.rs new file mode 100644 index 0000000..d185084 --- /dev/null +++ b/common/src/sha224.rs @@ -0,0 +1,63 @@ +pub(crate) use crate::rust_sha224::{hash, InternalSha224}; + +/// Hasher with fixed algorithm that is guaranteed not to change in the future +/// or across registry versions. The algorithm used to generate the hash is +/// SHA224 and therefore has constant output size of 28 bytes. +/// +/// This hasher can be used, e.g., for creating fingerprints of files that are +/// persisted on disk. + +#[derive(Default)] +pub struct Sha224 { + sha224: InternalSha224, +} + +impl Sha224 { + /// Return a new Sha224 object + pub fn new() -> Self { + Self::default() + } + + /// Hashes some data and returns the digest + pub fn hash(data: &[u8]) -> [u8; 28] { + hash(data) + } + + /// Incrementally update the current hash + pub fn write(&mut self, data: &[u8]) { + self.sha224.write(data); + } + + /// Finishes computing a hash, returning the digest + pub fn finish(self) -> [u8; 28] { + self.sha224.finish() + } +} + +impl std::io::Write for Sha224 { + /// Update an incremental hash + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.write(buf); + Ok(buf.len()) + } + + /// This is a no-op + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl std::hash::Hasher for Sha224 { + /// This function will panic; use finish() -> [u8; 28] instead + fn finish(&self) -> u64 { + panic!( + "not supported because the hash values produced by this hasher \ + contain more than just the 64 bits returned by this method" + ) + } + + /// Update an incremental hash + fn write(&mut self, bytes: &[u8]) { + self.write(bytes) + } +} diff --git a/dfx.json b/dfx.json new file mode 100644 index 0000000..bc606c9 --- /dev/null +++ b/dfx.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "dfx": "0.8.4", + "canisters": { + "nft": { + "build": "cargo build --target wasm32-unknown-unknown --release --package nft", + "candid": "nft/candid/nft.did", + "wasm": "target/wasm32-unknown-unknown/release/nft.wasm", + "type": "custom" + }, + "cap": { + "build": "echo", + "candid": "cap.did", + "wasm": "cap.wasm", + "type": "custom" + } + }, + "defaults": { + "build": { + "packtool": "" + } + }, + "networks": { + "local": { + "bind": "127.0.0.1:8000", + "type": "ephemeral" + }, + "ic": { + "bind": "ic0.app" + } + } +} diff --git a/nft/Cargo.toml b/nft/Cargo.toml new file mode 100644 index 0000000..c4cf9fa --- /dev/null +++ b/nft/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nft" +version = "0.1.0" +authors = ["Daniel Graczer "] +edition = "2018" + +[dependencies] +ic-kit = "0.4.2" +ic-cdk = "0.3.1" +common = {path = "../common"} +serde = { version="1.0.130", features = ["derive"] } +derive-new = "0.5" +serde_bytes = "0.11.5" +serde_cbor = "0.11.2" +cap-sdk= { git = "https://github.com/Psychedelic/cap", branch = "cap-sdk", package="cap-sdk" } +cap-std= { git = "https://github.com/Psychedelic/cap", branch = "cap-sdk", package="cap-standards", features = ["alpha-dip721"] } + +[lib] +crate-type = ["cdylib"] +path = "src/lib.rs" diff --git a/nft/candid/nft.did b/nft/candid/nft.did new file mode 100644 index 0000000..c1c76b2 --- /dev/null +++ b/nft/candid/nft.did @@ -0,0 +1,304 @@ +// BEGIN DIP-721 // + + type ApiError = + variant { + Unauthorized; + InvalidTokenId; + ZeroAddress; + Other; + }; + + type OwnerResult = + variant { + Err: ApiError; + Ok: principal; + }; + + type TxReceipt = + variant { + Err: ApiError; + Ok: nat; + }; + + type InterfaceId = + variant { + Approval; + TransactionHistory; + Mint; + Burn; + TransferNotification; + }; + + type LogoResult = + record { + logo_type: text; // MIME type of the logo + data: text; // Base64 encoded logo + }; + + type ExtendedMetadataResult = + record { + metadata_desc: MetadataDesc; + token_id: nat64; + }; + + type MetadataResult = + variant { + Err: ApiError; + Ok: MetadataDesc; + }; + + type MetadataDesc = vec MetadataPart; + + type MetadataPart = record { + purpose: MetadataPurpose; + key_val_data: vec MetadataKeyVal; + data: blob; + }; + + type MetadataPurpose = + variant { + Preview; // used as a preview, can be used as preivew in a wallet + Rendered; // used as a detailed version of the NFT + }; + + type MetadataKeyVal = + record { + key: text; + val: MetadataVal; + }; + + type MetadataVal = + variant { + TextContent : text; + BlobContent : blob; + NatContent : nat; + Nat8Content: nat8; + Nat16Content: nat16; + Nat32Content: nat32; + Nat64Content: nat64; + }; + + type TransactionResult = + record { + fee: nat; + transaction_type: TransactionType; + }; + + type TransactionType = + variant { + Transfer: + record { + token_id: nat64; + from: principal; + to: principal; + }; + TransferFrom: + record { + token_id: nat64; + from: principal; + to: principal; + }; + Approve: + record { + token_id: nat64; + from: principal; + to: principal; + }; + SetApprovalForAll: + record { + from: principal; + to: principal; + }; + Mint: + record { + token_id: nat64; + to: principal; + }; + Burn: + record { + token_id: nat64; + }; + }; + + type MintReceipt = + variant { + Err: ApiError; + Ok: MintReceiptPart; + }; + + type MintReceiptPart = + record { + token_id: nat64; // minted token id + id: nat // transaction id + }; + +// END DIP-721 // + +type Balance = nat; +type Memo = blob; +type SubAccount = vec nat8; +type TokenIdentifier = text; +type TokenIndex = nat32; +type AccountIdentifier = text; + + type User = + variant { + address: AccountIdentifier; + "principal": principal; + }; + + type TransferRequest = + record { + amount: Balance; + from: User; + memo: Memo; + notify: bool; + subaccount: opt SubAccount; + to: User; + token: TokenIdentifier; + }; + + type TransferResponse = + variant { + Err: + variant { + CannotNotify: AccountIdentifier; + InsufficientBalance; + InvalidToken: TokenIdentifier; + Other: text; + Rejected; + Unauthorized: AccountIdentifier; + }; + Ok: Balance; + }; + + type MintRequest = + record { + metadata: opt MetadataContainer; + to: User; + }; + + type CommonError = + variant { + InvalidToken: TokenIdentifier; + Other: text; + }; + + type AccountIdentifierReturn = + variant { + Err: CommonError; + Ok: AccountIdentifier; + }; + + type BalanceReturn = + variant { + Err: CommonError; + Ok: Balance; + }; + + type MetadataReturn = + variant { + Err: CommonError; + Ok: Metadata; + }; + + type TokenMetadata = + record { + account_identifier: AccountIdentifier; + metadata: Metadata; + token_identifier: TokenIdentifier; + "principal": principal; + }; + + type Metadata = + variant { + fungible: + record { + name: text; + symbol: text; + decimals: nat8; + metadata: opt MetadataContainer; + }; + nonfungible: opt MetadataContainer; + }; + + type MetadataContainer = + variant { + data : vec MetadataValue; + "blob" : blob; + json : text; + }; + + type MetadataValue = + record {text; Value}; + + type Value = + variant { + "text" : text; + "blob" : blob; + "nat" : nat; + "nat8": nat8; + }; + + type TransactionId = nat; + type Date = nat64; + + type Transaction = + record { + txid: TransactionId; + request: TransferRequest; + date: Date; + }; + + type TransactionRequestFilter = + variant { + txid: TransactionId; + user: User; + date: record {Date; Date;}; + page: record {nat; nat;}; + }; + + type TransactionRequest = + record { + "query": TransactionRequestFilter; + token: TokenIdentifier; + }; + + type TrasactionsResult = + variant { + Err: CommonError; + Ok: vec Transaction; + }; + + type erc721_token = + service { + // HEALTH-CHECK // + name: () -> (text) query; + + // BEGIN DIP-721 // + balanceOfDip721: (user: principal) -> (nat64) query; + ownerOfDip721: (token_id: nat64) -> (OwnerResult) query; + safeTransferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt); + transferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt); + supportedInterfacesDip721: () -> (vec InterfaceId) query; + logoDip721: () -> (LogoResult) query; + nameDip721: () -> (text) query; + symbolDip721: () -> (text) query; + totalSupplyDip721: () -> (nat64) query; + getMetadataDip721: (token_id: nat64) -> (MetadataResult) query; + getMaxLimitDip721: () -> (nat16) query; + mintDip721: (to: principal, metadata: MetadataDesc) -> (MintReceipt); + getMetadataForUserDip721: (user: principal) -> (vec ExtendedMetadataResult); + getTokenIdsForUserDip721: (user: principal) -> (vec nat64) query; + // END DIP-721 // + + transfer: (TransferRequest) -> (TransferResponse); + mintNFT: (MintRequest) -> (TokenIdentifier); + bearer: (TokenIdentifier) -> (AccountIdentifierReturn) query; + getAllMetadataForUser: (User) -> (vec TokenMetadata) query; + supply: (TokenIdentifier) -> (BalanceReturn) query; + metadata: (TokenIdentifier) -> (MetadataReturn) query; + add: (TransferRequest) -> (TransactionId); + }; + +service : (principal, text, text, principal) -> erc721_token diff --git a/nft/scripts/test.sh b/nft/scripts/test.sh new file mode 100644 index 0000000..5f14431 --- /dev/null +++ b/nft/scripts/test.sh @@ -0,0 +1,192 @@ +dfxDir="/home/piraya/.config/dfx" +candidDir="/home/piraya/repos/rust-ic/tokens/nft/candid" + +NftID=$(dfx canister id nft) +DefaultPem="${dfxDir}/identity/default/identity.pem" +AlicePem="${dfxDir}/identity/Alice/identity.pem" +BobPem="${dfxDir}/identity/Bob/identity.pem" +CharliePem="${dfxDir}/identity/Charlie/identity.pem" +NftCandidFile="${candidDir}/nft.did" +DefaultPrincipalId=$(dfx identity use Default 2>/dev/null;dfx identity get-principal) +AlicePrincipalId=$(dfx identity use Alice 2>/dev/null;dfx identity get-principal) +BobPrincipalId=$(dfx identity use Bob 2>/dev/null;dfx identity get-principal) +CharliePrincipalId=$(dfx identity use Charlie 2>/dev/null;dfx identity get-principal) +DefaultAccountId=$(dfx identity use default 2>/dev/null;dfx ledger account-id) +AliceAccountId=$(dfx identity use Alice 2>/dev/null;dfx ledger account-id) +BobAccountId=$(dfx identity use Bob 2>/dev/null;dfx ledger account-id) +CharlieAccountId=$(dfx identity use Charlie 2>/dev/null;dfx ledger account-id) +IcxPrologueNft="--candid=${NftCandidFile}" +dfx identity use default 2>/dev/null + +# declare -A nameToPrincipal=( ["Alice"]="$AlicePrincipalId" ["Bob"]="$BobPrincipalId" ["Charlie"]="$CharliePrincipalId" ["default"]="$DefaultPrincipalId") +# declare -A nameToPem=( ["Alice"]="$AlicePem" ["Bob"]="$BobPem" ["Charlie"]="$CharliePem" ["Default"]="$DefaultPem") + +help() +{ + printf "\n\nPrincipal ids\n" + printf "Alice: ${AlicePrincipalId}\n" + printf "Bob: ${BobPrincipalId}\n" + printf "Charlie: ${CharliePrincipalId}\n" + + printf "\n\nAccount ids\n" + printf "Alice: ${AliceAccountId}\n" + printf "Bob: ${BobAccountId}\n" + printf "Charlie: ${CharlieAccountId}\n\n\n" + + printf "Principal ids: ${nameToPrincipal}\n\n\n" +} + +deploy() { + eval "dfx deploy cap" + principal=$(dfx identity get-principal) + cap_principal=$(cat .dfx/local/canister_ids.json | jq ".cap.local" -r) + + echo "principal: $principal" + echo "cap_principal: $cap_principal" + #fn init(owner: Principal, symbol: String, name: String, history: Principal) + eval "dfx deploy nft --argument '(principal \"$principal\", \"tkn\", \"token\", principal \"$cap_principal\")'" +} + +# deploy + +### BEGIN OF DIP-721 ### +mintDip721() { + mint_for="${AlicePrincipalId}" + icx --pem=$DefaultPem update $NftID mintDip721 "(principal \"$mint_for\", vec{})" $IcxPrologueNft +} + +balanceOfDip721() { + user="${AlicePrincipalId}" + icx --pem=$DefaultPem query $NftID balanceOfDip721 "(principal \"$user\")" $IcxPrologueNft +} + +ownerOfDip721() { + token_id="0" + icx --pem=$AlicePem query $NftID ownerOfDip721 "($token_id)" $IcxPrologueNft +} + +safeTransferFromDip721() { + from_principal="${BobPrincipalId}" + to_principal="${AlicePrincipalId}" + token_id="0" + icx --pem=$BobPem update $NftID safeTransferFromDip721 "(principal \"$from_principal\", principal \"$to_principal\", $token_id)" $IcxPrologueNft +} + +transferFromDip721() { + from_principal="${AlicePrincipalId}" + to_principal="${BobPrincipalId}" + token_id="0" + icx --pem=$AlicePem update $NftID transferFromDip721 "(principal \"$from_principal\", principal \"$to_principal\", $token_id)" $IcxPrologueNft +} + +supportedInterfacesDip721() { + icx --pem=$DefaultPem query $NftID supportedInterfacesDip721 "()" $IcxPrologueNft +} + +logoDip721() { + icx --pem=$DefaultPem query $NftID logoDip721 "()" $IcxPrologueNft +} + +nameDip721() { + icx --pem=$DefaultPem query $NftID nameDip721 "()" $IcxPrologueNft +} + +symbolDip721() { + icx --pem=$DefaultPem query $NftID symbolDip721 "()" $IcxPrologueNft +} + +totalSupplyDip721() { + icx --pem=$DefaultPem query $NftID totalSupplyDip721 "()" $IcxPrologueNft +} + +getMetadataDip721() { + token_id="0" + icx --pem=$DefaultPem query $NftID getMetadataDip721 "($token_id)" $IcxPrologueNft +} + +getMetadataForUserDip721() { + user="${AlicePrincipalId}" + icx --pem=$DefaultPem query $NftID getMetadataForUserDip721 "(principal \"$user\")" $IcxPrologueNft +} + +### END OF DIP-721 ### + +mintNFT() { + mint_for="${AlicePrincipalId}" + icx --pem=$DefaultPem update $NftID mintNFT "(record {metadata= opt variant {\"blob\" = vec{1;2;3}}; to= variant {\"principal\"= principal \"$mint_for\"}})" $IcxPrologueNft +} + +metadata() { + token_id="0" + icx --pem=$DefaultPem query $NftID metadata \"$token_id\" $IcxPrologueNft +} + +bearer() { + token_id="0" + icx --pem=$DefaultPem query $NftID bearer \"$token_id\" $IcxPrologueNft +} + +supply() { + token_id="0" + icx --pem=$DefaultPem query $NftID supply \"$token_id\" $IcxPrologueNft +} + +getAllMetadataForUser() { + user="${AlicePrincipalId}" + icx --pem=$DefaultPem query $NftID getAllMetadataForUser "(variant {\"principal\" = principal \"$user\"})" $IcxPrologueNft +} + +transfer() { + from_principal="${AlicePrincipalId}" + from_pem="${AlicePem}" + to_principal="${BobPrincipalId}" + token_id="0" + icx --pem=$from_pem update $NftID transfer "(record {amount = 1; from = variant {\"principal\" = principal \"$from_principal\"}; memo = vec{}; notify = true; SubAccount = null; to = variant {\"principal\" = principal \"$to_principal\"}; token = \"$token_id\"})" $IcxPrologueNft +} + +tests() { + printf "Running deploy (run script again is canister_id error)..." + deploy + printf "info..." + help + printf "Running mintDip721..." + mintDip721 + printf "Running supportedInterfacesDip721..." + supportedInterfacesDip721 + printf "Running nameDip721..." + nameDip721 + printf "Running symbolDip721..." + symbolDip721 + printf "Running getMetadataDip721..." + getMetadataDip721 + printf "Running getMetadataForUserDip721 for Alice..." + getMetadataForUserDip721 + printf "Running bearer..." + bearer + printf "Running supply..." + supply + printf "Running totalSupply..." + totalSupplyDip721 + printf "Running balanceOfDip721..." + balanceOfDip721 + printf "Rinning ownerOfDip721..." + ownerOfDip721 + printf "Running transferFromDip721 Alice to Bob..." + transferFromDip721 + printf "Running safeTransferFromDip721 Bob to Alice..." + safeTransferFromDip721 + printf "Running transfer Alice to Bob..." + transfer + + ### not testable + # printf "Running mintNFT" + # mintNFT + # printf "Running logoDip721..." + # logoDip721 + # printf "Running metadata...." + # metadata + # printf "Running getAllMetadataForUser..." + # getAllMetadataForUser +} + +tests \ No newline at end of file diff --git a/nft/src/ledger.rs b/nft/src/ledger.rs new file mode 100644 index 0000000..c6d8412 --- /dev/null +++ b/nft/src/ledger.rs @@ -0,0 +1,188 @@ +use crate::types::*; +use crate::utils::*; + +use ic_kit::candid::CandidType; + +use serde::Deserialize; +use std::collections::HashMap; +use std::convert::Into; +use std::default::Default; + +#[derive(CandidType, Clone, Default, Deserialize)] +pub struct Ledger { + tokens: HashMap, + user_tokens: HashMap>, +} + +impl Ledger { + // BEGIN DIP-721 // + + #[allow(non_snake_case)] + pub fn mintNFT(&mut self, to: &Principal, metadata_desc: &MetadataDesc) -> MintReceipt { + let token_index = ledger().tokens.len() as TokenIndex; + ledger().tokens.insert( + token_index, + TokenMetadata::new( + User::principal(to.clone()).into(), + Metadata::nonfungible(None), + into_token_identifier(&token_index), + to.clone(), + metadata_desc.clone(), + ), + ); + ledger() + .user_tokens + .entry(User::principal(*to)) + .or_default() + .push(token_index); + + Ok(MintReceiptPart { + token_id: token_index as u64, + id: Nat::from(1), + }) + } + + pub fn total_supply(&self) -> u64 { + ledger().tokens.len() as u64 + } + + pub fn get_metadata(&self, token_id: u64) -> MetadataResult { + MetadataResult::Ok( + ledger() + .tokens + .get(&into_token_index(&token_id.to_string())) + .expect("unable to find token index") + .metadata_desc + .clone(), + ) + } + + pub fn get_metadata_for_user(&self, user: &Principal) -> Vec { + ledger() + .user_tokens + .get(&User::principal(*user)) + .unwrap_or(&vec![]) + .iter() + .map(|token_index| { + let user_tokens = ledger() + .tokens + .get(token_index) + .expect("unable to find token index"); + ExtendedMetadataResult { + metadata_desc: user_tokens.metadata_desc.clone(), + token_id: *token_index as u64, + } + }) + .collect() + } + + pub fn get_token_ids_for_user(&self, user: &Principal) -> Vec { + ledger() + .user_tokens + .get(&User::principal(*user)) + .unwrap_or(&vec![]) + .iter() + .map(|token_index| token_index.clone() as u64) + .collect() + } + + // END DIP-721 // + + pub fn owner_of(&self, token_identifier: &TokenIdentifier) -> OwnerResult { + OwnerResult::Ok( + ledger() + .tokens + .get(&into_token_index(&token_identifier)) + .expect("unable to locate token id") + .principal + .clone(), + ) + } + + pub fn balance_of(&self, user: &User) -> u64 { + ledger().user_tokens.get(user).unwrap_or(&vec![]).len() as u64 + } + + pub fn transfer(&mut self, from: &User, to: &User, token_identifier: &TokenIdentifier) { + // changeing token owner in the tokens map + let token_index = into_token_index(token_identifier); + let mut token_metadata = ledger() + .tokens + .get_mut(&token_index) + .expect("unable to find token identifier in tokens"); + + token_metadata.account_identifier = to.clone().into(); + token_metadata.principal = expect_principal(&to); + + // remove the token from the previous owner's tokenlist + let from_token_indexes = ledger() + .user_tokens + .get_mut(&from) + .expect("unable to find previous owner"); + from_token_indexes.remove( + from_token_indexes + .iter() + .position(|token_index_in_vec| &token_index == token_index_in_vec) + .expect("unable to find token index in users_token"), + ); + if from_token_indexes.len() == 0 { + ledger().user_tokens.remove(&from); + } + + // add the token to the new owner's tokenlist + ledger() + .user_tokens + .entry(to.clone()) + .or_default() + .push(token_index); + } + + pub fn bearer(&self, token_identifier: &TokenIdentifier) -> AccountIdentifierReturn { + AccountIdentifierReturn::Ok( + ledger() + .tokens + .get(&into_token_index(&token_identifier)) + .expect("unable to locate token id") + .account_identifier + .clone(), + ) + } + + pub fn supply(&self, _token_identifier: &TokenIdentifier) -> BalanceReturn { + BalanceReturn::Ok(ledger().tokens.len().into()) + } + + pub fn get_all_metadata_for_user(&self, user: &User) -> Vec { + ledger() + .user_tokens + .get(user) + .unwrap_or(&vec![]) + .iter() + .map(|token_index| { + ledger() + .tokens + .get(token_index) + .expect("unable to find token index") + .clone() + }) + .collect() + } + + pub fn metadata(&self, token_identifier: &TokenIdentifier) -> MetadataReturn { + MetadataReturn::Ok( + ledger() + .tokens + .get(&into_token_index(&token_identifier)) + .expect("unable to find token index") + .metadata + .clone(), + ) + } + + #[allow(dead_code)] + #[cfg(test)] + pub fn clear(&mut self) { + self.tokens.clear(); + self.user_tokens.clear(); + } +} diff --git a/nft/src/ledger_tests.rs b/nft/src/ledger_tests.rs new file mode 100644 index 0000000..5992fc7 --- /dev/null +++ b/nft/src/ledger_tests.rs @@ -0,0 +1,76 @@ +#[cfg(test)] +mod tests { + + use crate::ledger::*; + use crate::types::*; + + use ic_kit::mock_principals::*; + use ic_kit::MockContext; + + fn setup_ledger() -> Ledger { + MockContext::new().inject(); + let mut ledger = Ledger::default(); + let mut metadata_desc = vec![MetadataPart { + purpose: MetadataPurpose::Rendered, + key_val_data: vec![MetadataKeyVal { + key: "location".to_owned(), + val: MetadataVal::TextContent("mycanister1".to_owned()), + }], + data: vec![], + }]; + ledger.mintNFT(&alice(), &metadata_desc).unwrap(); + metadata_desc[0].key_val_data[0].val = MetadataVal::TextContent("mycanister2".to_owned()); + ledger.mintNFT(&alice(), &metadata_desc).unwrap(); + metadata_desc[0].key_val_data[0].val = MetadataVal::TextContent("mycanister3".to_owned()); + ledger.mintNFT(&bob(), &metadata_desc).unwrap(); + ledger + } + + // BEGIN DIP-721 // + #[test] + fn basic_interface() { + let mut ledger = setup_ledger(); + assert_eq!(ledger.owner_of(&"0".to_owned()).unwrap(), alice()); + assert_eq!(ledger.owner_of(&"2".to_owned()).unwrap(), bob()); + + assert_eq!(ledger.total_supply(), 3); + assert_eq!(ledger.balance_of(&User::principal(alice())), 2); + assert_eq!(ledger.balance_of(&User::principal(bob())), 1); + assert_eq!(ledger.balance_of(&User::principal(john())), 0); + + assert_eq!(ledger.get_metadata_for_user(&alice()).len(), 2); + + assert_eq!(ledger.get_metadata(0).unwrap().len(), 1); + + ledger.transfer( + &User::principal(alice()), + &User::principal(bob()), + &"0".to_owned(), + ); + assert_eq!(ledger.owner_of(&"0".to_owned()).unwrap(), bob()); + } + + #[should_panic] + #[test] + fn owner_of_non_existent_token() { + let mut ledger = setup_ledger(); + ledger.transfer( + &User::principal(alice()), + &User::principal(bob()), + &"42".to_owned(), + ); + } + + #[should_panic] + #[test] + fn transfer_token_not_owned() { + let mut ledger = setup_ledger(); + ledger.transfer( + &User::principal(john()), + &User::principal(bob()), + &"0".to_owned(), + ); + } + + // END DIP-721 // +} diff --git a/nft/src/lib.rs b/nft/src/lib.rs new file mode 100644 index 0000000..b0a4013 --- /dev/null +++ b/nft/src/lib.rs @@ -0,0 +1,6 @@ +mod ledger; +mod ledger_tests; +mod management; +mod service; +mod types; +mod utils; diff --git a/nft/src/management.rs b/nft/src/management.rs new file mode 100644 index 0000000..aa62af7 --- /dev/null +++ b/nft/src/management.rs @@ -0,0 +1,15 @@ +use ic_kit::{candid::CandidType, ic, Principal}; +use serde::Deserialize; + +#[derive(CandidType, Deserialize)] +pub struct Fleek(pub Vec); + +impl Default for Fleek { + fn default() -> Self { + panic!() + } +} + +pub fn is_fleek(account: &Principal) -> bool { + ic::get::().0.contains(account) +} diff --git a/nft/src/service.rs b/nft/src/service.rs new file mode 100644 index 0000000..8f4101e --- /dev/null +++ b/nft/src/service.rs @@ -0,0 +1,319 @@ +use crate::management::is_fleek; +use crate::management::Fleek; +use crate::types::*; +use crate::utils::*; + +use ic_kit::ic; +use ic_kit::ic::caller; +use ic_kit::ic::trap; +use ic_kit::macros::*; + +use cap_sdk::handshake; +use cap_sdk::DetailValue; +use cap_sdk::IndefiniteEventBuilder; + +/// HEALTH-CHECK /// +#[query] +fn name() -> String { + String::from("NFT Canister") +} + +/// BEGIN DIP-721 /// +#[query(name = "balanceOfDip721")] +fn balance_of_dip721(user: Principal) -> u64 { + ledger().balance_of(&user.into()) +} + +#[query(name = "ownerOfDip721")] +fn owner_of_dip721(token_id: u64) -> Result { + ledger().owner_of(&token_id.to_string()) +} + +#[update(name = "safeTransferFromDip721")] +async fn safe_transfer_from_dip721(_from: Principal, to: Principal, token_id: u64) -> TxReceipt { + if !is_fleek(&ic::caller()) { + return Err(ApiError::Unauthorized); + } + assert_ne!( + to, + Principal::from_slice(&[0; 29]), + "transfer request to cannot be the zero principal" + ); + + ledger().transfer( + &User::principal(caller()), + &User::principal(to), + &token_id.to_string(), + ); + + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("transfer") + .details(vec![ + ("from".into(), DetailValue::Principal(caller())), + ("to".into(), DetailValue::Principal(to)), + ("token_id".into(), DetailValue::U64(token_id)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + + Ok(tx_id.into()) +} + +#[update(name = "transferFromDip721")] +async fn transfer_from_dip721(_from: Principal, to: Principal, token_id: u64) -> TxReceipt { + if !is_fleek(&ic::caller()) { + return Err(ApiError::Unauthorized); + } + assert_ne!( + caller(), + to, + "transfer request caller and to cannot be the same" + ); + + ledger().transfer( + &User::principal(caller()), + &User::principal(to), + &token_id.to_string(), + ); + + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("transfer") + .details(vec![ + ("from".into(), DetailValue::Principal(caller())), + ("to".into(), DetailValue::Principal(to)), + ("token_id".into(), DetailValue::U64(token_id)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + + Ok(tx_id) +} + +#[query(name = "supportedInterfacesDip721")] +fn supported_interfaces_dip721() -> Vec { + vec![InterfaceId::Mint, InterfaceId::TransactionHistory] +} + +#[query(name = "logoDip721")] +fn logo_dip721() -> LogoResult { + unimplemented!(); +} + +#[query(name = "nameDip721")] +fn name_dip721() -> &'static str { + &token_level_metadata().name +} + +#[query(name = "symbolDip721")] +fn symbol_dip721() -> &'static str { + &token_level_metadata().symbol +} + +#[query(name = "totalSupplyDip721")] +fn total_supply_dip721() -> u64 { + ledger().total_supply() +} + +#[query(name = "getMetadataDip721")] +fn get_metadata_dip721(token_id: u64) -> MetadataResult { + ledger().get_metadata(token_id) +} + +#[query(name = "getMaxLimitDip721")] +fn get_max_limit_dip721() -> u16 { + 200 +} + +#[allow(unreachable_code, unused_variables)] +#[query(name = "getMetadataForUserDip721")] +fn get_metadata_for_user_dip721(user: Principal) -> Vec { + ledger().get_metadata_for_user(&user) +} + +#[allow(unreachable_code, unused_variables)] +#[query(name = "getTokenIdsForUserDip721")] +fn get_token_ids_for_user_dip721(user: Principal) -> Vec { + ledger().get_token_ids_for_user(&user) +} + +#[update(name = "mintDip721")] +async fn mint_dip721(to: Principal, metadata_desc: MetadataDesc) -> MintReceipt { + if !is_fleek(&ic::caller()) { + return Err(ApiError::Unauthorized); + } + let response = ledger().mintNFT(&to, &metadata_desc).unwrap(); + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("mint") + .details(vec![ + ("to".into(), DetailValue::Principal(to)), + ("token_id".into(), DetailValue::U64(response.token_id)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + + Ok(MintReceiptPart { + token_id: response.token_id, + id: tx_id.into(), + }) +} + +/// END DIP-721 /// + +#[update] +async fn transfer(transfer_request: TransferRequest) -> TransferResponse { + if !is_fleek(&ic::caller()) { + return Err(TransferError::Unauthorized("Not Admin".to_string())); + } + expect_principal(&transfer_request.from); + expect_principal(&transfer_request.to); + assert_ne!( + transfer_request.from, transfer_request.to, + "transfer request from and to cannot be the same" + ); + assert_eq!(transfer_request.amount, 1, "only amount 1 is supported"); + expect_caller_general(&transfer_request.from, transfer_request.subaccount); + + ledger().transfer( + &User::principal(caller()), + &transfer_request.to, + &transfer_request.token, + ); + + let token_id = &transfer_request.token.parse::().unwrap(); + + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("transfer") + .details(vec![ + ( + "from".into(), + user_to_detail_value(User::principal(caller())), + ), + ("to".into(), user_to_detail_value(transfer_request.to)), + ("token_id".into(), DetailValue::U64(*token_id)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + + Ok(Nat::from(tx_id)) +} + +#[allow(non_snake_case, unreachable_code, unused_variables)] +#[update] +async fn mintNFT(mint_request: MintRequest) -> Option { + trap("Disabled as current EXT metadata doesn't allow multiple assets per token"); + if !is_fleek(&ic::caller()) { + return None; + } + expect_principal(&mint_request.to); + expect_caller(&token_level_metadata().owner.expect("token owner not set")); + + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("mint") + .details(vec![ + ("to".into(), user_to_detail_value(mint_request.to)), + ("token_id".into(), DetailValue::U64(123)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + Some(tx_id.to_string()) +} + +#[query] +fn bearer(token_identifier: TokenIdentifier) -> AccountIdentifierReturn { + ledger().bearer(&token_identifier) +} + +#[allow(unreachable_code, unused_variables)] +#[query(name = "getAllMetadataForUser")] +fn get_all_metadata_for_user(user: User) -> Vec { + trap("Disabled as current EXT metadata doesn't allow multiple assets per token"); + ledger().get_all_metadata_for_user(&user) +} + +#[query] +fn supply(token_identifier: TokenIdentifier) -> BalanceReturn { + ledger().supply(&token_identifier) +} + +#[allow(unreachable_code, unused_variables)] +#[query] +fn metadata(token_identifier: TokenIdentifier) -> MetadataReturn { + trap("Disabled as current EXT metadata doesn't allow multiple assets per token"); + ledger().metadata(&token_identifier) +} + +#[update] +async fn add(transfer_request: TransferRequest) -> Option { + if !is_fleek(&ic::caller()) { + return None; + } + expect_principal(&transfer_request.from); + expect_principal(&transfer_request.to); + + let token_id = &transfer_request.token.parse::().unwrap(); + + let event = IndefiniteEventBuilder::new() + .caller(caller()) + .operation("transfer_from") + .details(vec![ + ("to".into(), user_to_detail_value(transfer_request.to)), + ("from".into(), user_to_detail_value(transfer_request.from)), + ("token_id".into(), DetailValue::U64(*token_id)), + ]) + .build() + .unwrap(); + + let tx_id = insert_into_cap(event).await.unwrap(); + + Some(Nat::from(tx_id)) +} + +fn store_data_in_stable_store() { + let data = StableStorageBorrowed { + ledger: ledger(), + token: token_level_metadata(), + fleek: fleek_db(), + }; + ic::stable_store((data,)).expect("failed"); +} + +fn restore_data_from_stable_store() { + let (data,): (StableStorage,) = ic::stable_restore().expect("failed"); + ic::store(data.ledger); + ic::store(data.token); + ic::store(data.fleek); +} + +#[init] +fn init(owner: Principal, symbol: String, name: String, history: Principal) { + ic::store(Fleek(vec![ic::caller()])); + *token_level_metadata() = TokenLevelMetadata::new(Some(owner), symbol, name, Some(history)); + handshake(1_000_000_000_000, Some(history)); +} + +#[pre_upgrade] +fn pre_upgrade() { + ic_cdk::api::print(format!("Executing preupgrade")); + store_data_in_stable_store(); +} + +#[post_upgrade] +fn post_upgrade() { + ic_cdk::api::print(format!("Executing postupgrade")); + restore_data_from_stable_store(); +} diff --git a/nft/src/types.rs b/nft/src/types.rs new file mode 100644 index 0000000..37fbd50 --- /dev/null +++ b/nft/src/types.rs @@ -0,0 +1,343 @@ +use crate::ledger::Ledger; +use crate::management::Fleek; +use common::account_identifier::AccountIdentifierStruct; + +use derive_new::*; +use ic_kit::candid::CandidType; +pub use ic_kit::candid::Nat; +pub use ic_kit::candid::Principal; +use serde::Deserialize; +use serde::Serialize; +use std::collections::VecDeque; + +use cap_sdk::DetailValue; +use cap_sdk::Event; +use cap_sdk::IndefiniteEvent; +use cap_std::dip721::DIP721TransactionType; + +pub use std::convert::{From, Into}; +pub use std::error::Error; +pub use std::vec::Vec; + +pub type Balance = Nat; +pub type Memo = Vec; +pub type SubAccount = Vec; +pub type TokenIdentifier = String; +pub type TokenIndex = u32; +pub type AccountIdentifier = String; +pub type Date = u64; +pub type TransactionId = Nat; + +pub type AccountIdentifierReturn = Result; +pub type BalanceReturn = Result; +pub type MetadataReturn = Result; +pub type Blob = Vec; + +// BEGIN DIP-721 // + +#[derive(CandidType, Debug, Deserialize)] +pub enum ApiError { + Unauthorized, + InvalidTokenId, + ZeroAddress, + Other, +} + +pub type TxReceipt = Result; + +#[derive(CandidType, Deserialize)] +pub enum InterfaceId { + Approval, + TransactionHistory, + Mint, + Burn, + TransferNotification, +} + +#[derive(CandidType, Deserialize)] +pub struct LogoResult { + logo_type: String, + data: String, +} + +pub type OwnerResult = Result; + +#[derive(CandidType, Deserialize)] +pub struct ExtendedMetadataResult { + pub metadata_desc: MetadataDesc, + pub token_id: u64, +} + +pub type MetadataResult = Result; + +pub type MetadataDesc = Vec; + +#[derive(CandidType, Clone, Deserialize)] +pub struct MetadataPart { + pub purpose: MetadataPurpose, + pub key_val_data: Vec, + pub data: Vec, +} + +#[derive(CandidType, Clone, Deserialize, Serialize)] +pub enum MetadataPurpose { + Preview, + Rendered, +} + +#[derive(CandidType, Clone, Deserialize)] +pub struct MetadataKeyVal { + pub key: String, + pub val: MetadataVal, +} + +#[derive(CandidType, Clone, Deserialize)] +pub enum MetadataVal { + TextContent(String), + BlobContent(Vec), + NatContent(Nat), + Nat8Content(u8), + Nat16Content(u16), + Nat32Content(u32), + Nat64Content(u64), +} + +#[derive(CandidType, Deserialize)] +pub struct TransactionResult { + pub fee: Nat, + pub transaction_type: DIP721TransactionType, +} + +#[derive(CandidType, Deserialize)] +pub struct Transfer { + pub token_id: u64, + pub from: Principal, + pub to: Principal, +} + +#[derive(CandidType, Deserialize)] +pub struct TransferFrom { + pub token_id: u64, + pub from: Principal, + pub to: Principal, +} + +#[derive(CandidType, Deserialize)] +pub struct Approve { + pub token_id: u64, + pub from: Principal, + pub to: Principal, +} + +#[derive(CandidType, Deserialize)] +pub struct SetApprovalForAll { + pub from: Principal, + pub to: Principal, +} + +#[derive(CandidType, Deserialize)] +pub struct Mint { + pub token_id: u64, + pub to: Principal, +} + +#[derive(CandidType, Deserialize)] +pub struct Burn { + pub token_id: u64, +} + +pub type MintReceipt = Result; + +#[derive(CandidType, Deserialize)] +pub struct MintReceiptPart { + pub token_id: u64, + pub id: Nat, +} + +/// END DIP-721 /// + +#[allow(non_camel_case_types)] +#[derive(Clone, CandidType, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum User { + address(AccountIdentifier), + principal(Principal), +} + +impl From for AccountIdentifierStruct { + fn from(user: User) -> Self { + match user { + User::principal(p) => p.into(), + User::address(a) => { + AccountIdentifierStruct::from_hex(&a).expect("unable to decode account identifier") + } + } + } +} + +impl From for String { + fn from(user: User) -> Self { + match user { + User::principal(p) => Into::::into(p).to_hex(), + User::address(a) => a, + } + } +} + +impl From for User { + fn from(principal: Principal) -> Self { + User::principal(principal) + } +} + +impl From for User { + fn from(account_identifier: AccountIdentifier) -> Self { + User::address(account_identifier) + } +} + +pub fn into_token_index(token_identifier: &TokenIdentifier) -> TokenIndex { + token_identifier + .parse::() + .expect("unable to convert token identifier to token index") +} + +pub fn into_token_identifier(token_index: &TokenIndex) -> TokenIdentifier { + token_index.to_string() +} + +#[derive(CandidType, Deserialize)] +pub struct TransferRequest { + pub amount: Balance, + pub from: User, + pub memo: Memo, + pub notify: bool, + pub subaccount: Option, + pub to: User, + pub token: TokenIdentifier, +} + +#[derive(Clone, CandidType, Deserialize)] +pub enum TransferError { + CannotNotify(AccountIdentifier), + InsufficientBalance, + InvalidToken(TokenIdentifier), + Other(String), + Rejected, + Unauthorized(AccountIdentifier), +} + +pub type TransferResponse = Result; + +#[derive(Clone, CandidType, Deserialize)] +pub struct MintRequest { + pub metadata: Option, + pub to: User, +} + +#[allow(non_camel_case_types)] +#[derive(Clone, CandidType, Deserialize)] +pub enum Metadata { + fungible(FungibleMetadata), + nonfungible(Option), +} + +#[derive(Clone, CandidType, Deserialize)] +pub struct FungibleMetadata { + name: String, + symbol: String, + decimals: u8, + metadata: Option, +} + +#[allow(non_camel_case_types)] +#[derive(Clone, CandidType, Deserialize, new)] +pub enum MetadataContainer { + data(Vec), + blob(Blob), + json(String), +} + +#[derive(Clone, CandidType, Deserialize)] +pub struct MetadataValue(String, Value); + +#[allow(non_camel_case_types)] +#[derive(Clone, CandidType, Deserialize)] +pub enum Value { + text(String), + blob(Blob), + nat(Nat), + nat8(u8), +} + +#[derive(Clone, CandidType, Debug, Deserialize)] +pub enum CommonError { + InvalidToken(TokenIdentifier), + Other(String), +} + +#[derive(Clone, CandidType, Deserialize, new)] +pub struct TokenMetadata { + pub account_identifier: AccountIdentifier, + pub metadata: Metadata, + pub token_identifier: TokenIdentifier, + pub principal: Principal, + pub metadata_desc: MetadataDesc, +} + +#[derive(new, CandidType, Clone, Default, Deserialize, Serialize)] +pub struct TokenLevelMetadata { + pub owner: Option, + pub symbol: String, + pub name: String, + pub history: Option, +} + +#[derive(CandidType, Deserialize)] +pub struct Transaction { + pub txid: TransactionId, + request: TransferRequest, + date: Date, +} + +#[allow(non_camel_case_types)] +#[derive(CandidType, Deserialize)] +pub enum TransactionRequestFilter { + txid(TransactionId), + user(User), + date(Date, Date), + page(Nat, Nat), + all, +} + +#[derive(CandidType, Deserialize)] +pub struct TransactionsRequest { + query: TransactionRequestFilter, + token: TokenIdentifier, +} + +fn get_detail_value(key: &str, details: Vec<(String, DetailValue)>) -> Option { + let entry = details.iter().find(|&x| x.0.as_str() == key); + match entry { + Some(x) => Some(x.1.clone()), + None => None, + } +} + +#[derive(Default)] +pub struct TxLog { + pub tx_records: VecDeque, +} + +#[derive(CandidType)] +pub struct StableStorageBorrowed<'a> { + pub ledger: &'a Ledger, + pub token: &'a TokenLevelMetadata, + pub fleek: &'a Fleek, +} + +#[derive(CandidType, Deserialize)] +pub struct StableStorage { + pub ledger: Ledger, + pub token: TokenLevelMetadata, + pub fleek: Fleek, +} diff --git a/nft/src/utils.rs b/nft/src/utils.rs new file mode 100644 index 0000000..1b570b7 --- /dev/null +++ b/nft/src/utils.rs @@ -0,0 +1,122 @@ +use crate::ledger::Ledger; +use crate::management::Fleek; +use crate::types::*; + +use common::account_identifier::{AccountIdentifierStruct, Subaccount}; +use common::principal_id::PrincipalId; + +pub use ic_kit::candid::Principal; +use ic_kit::ic::trap; + +use cap_sdk::insert; +use cap_sdk::DetailValue; +use cap_sdk::IndefiniteEvent; +use std::convert::TryInto; + +pub fn caller() -> Principal { + ic_kit::ic::caller() +} + +pub fn ledger<'a>() -> &'a mut Ledger { + ic_kit::ic::get_mut::() +} + +pub fn token_level_metadata<'a>() -> &'a mut TokenLevelMetadata { + ic_kit::ic::get_mut::() +} + +pub fn fleek_db<'a>() -> &'a mut Fleek { + ic_kit::ic::get_mut::() +} + +pub fn cap_canister_id() -> Principal { + ic_kit::ic::get_mut::().history.unwrap() +} + +pub fn expect_caller(input_principal: &Principal) { + if &caller() != input_principal { + trap("input_principal is different from caller"); + } +} + +pub fn expect_caller_general(user: &User, subaccount: Option) { + match user { + User::address(from_address) => { + if &AccountIdentifierStruct::new( + PrincipalId(caller()), + Some(Subaccount( + subaccount + .expect("SubAccount is missing") + .try_into() + .expect("unable to convert SubAccount to 32 bytes array"), + )), + ) + .to_hex() + != from_address + { + trap("input account identifier is different from caller") + } + } + User::principal(principal) => expect_caller(principal), + } +} + +pub fn expect_principal(user: &User) -> Principal { + match user { + User::address(_) => { + trap("only principals are allowed to preserve compatibility with Dip721") + } + User::principal(principal) => *principal, + } +} + +pub fn user_to_detail_value(user: User) -> DetailValue { + match user { + User::address(address) => DetailValue::Text(address), + User::principal(principal) => DetailValue::Principal(principal), + } +} + +pub fn convert_nat_to_u64(num: Nat) -> Result { + let u64_digits = num.0.to_u64_digits(); + + match u64_digits.len() { + 0 => Ok(0), + 1 => Ok(u64_digits[0]), + _ => Err("Nat -> Nat64 conversion failed".to_string()), + } +} + +pub fn convert_nat_to_u32(num: Nat) -> Result { + let u32_digits = num.0.to_u32_digits(); + match u32_digits.len() { + 0 => Ok(0), + 1 => Ok(u32_digits[0]), + _ => Err("Nat -> Nat32 conversion failed".to_string()), + } +} + +pub async fn insert_into_cap(tx_record: IndefiniteEvent) -> TxReceipt { + let tx_log = tx_log(); + if let Some(failed_tx_record) = tx_log.tx_records.pop_front() { + insert_into_cap_priv(failed_tx_record).await; + } + insert_into_cap_priv(tx_record).await +} + +pub async fn insert_into_cap_priv(tx_record: IndefiniteEvent) -> TxReceipt { + let insert_res = insert(tx_record.clone()) + .await + .map(|tx_id| Nat::from(tx_id)) + .map_err(|err| ApiError::Other); + + if insert_res.is_err() { + tx_log().tx_records.push_back(tx_record); + } + + insert_res +} + +pub fn tx_log<'a>() -> &'a mut TxLog { + ic_kit::ic::get_mut::() +} diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..1987896 --- /dev/null +++ b/roadmap.md @@ -0,0 +1,22 @@ +# Roadmap + +#### Highest priorities + +- Add history support, either local history or cap + +#### High priorities + +1. Write unit tests and javascript based tests (logic is heavily based on rollback, so don't use ic-kit) +2. Follow up with ToniqLabs about https://github.com/Toniq-Labs/extendable-token/issues/9 about including multiple assets support +3. ~~Rename interface methods with attributes follow coding guidelines~~ + +#### Mid priority + +1. Add support for ICP based fees +2. Support allowances +3. ~~Clean up warnings instead of surpressing them~~ +4. Group use declarations (also follow the style guide regarding imports) +5. Remove explicit trapping in the code and support proper return values for errors +6. Change get_mut to thread local variables according to latest dFinity suggestions + +Please don't touch the files in the common's directory, they have been moved from the ic blockchain code with small modifications. diff --git a/style.md b/style.md new file mode 100644 index 0000000..1db07f6 --- /dev/null +++ b/style.md @@ -0,0 +1,12 @@ +# Coding guideline + +## Import organization + +Imports should be grouped into 4 sections separated by a newline in the following order: + +1. From the same crate +2. From the same workspace +3. From other crates +4. From std + +Within each groups, the imports should be in increasing alphabetical order.