diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a41a96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store + +# Generated by Cargo +# Compiled files and executables +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# rustc info +.rustc_info.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eaff584 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2636 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom 0.2.11", + "once_cell", + "version_check", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f404657a7ea7b5249e36808dff544bc88a28f26e0ac40009f674b7a009d14be3" +dependencies = [ + "once_cell", + "proc-macro-crate 2.0.0", + "proc-macro2", + "quote", + "syn 2.0.39", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +dependencies = [ + "sha2 0.9.9", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecheck" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concordium-cis2" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c556365e2e5bf2c2669e944a4f235e0039c06bdbd43539e1828de4fa45d8c0" +dependencies = [ + "concordium-std", + "primitive-types", +] + +[[package]] +name = "concordium-contracts-common" +version = "8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1943bb4b1738f7f974ee86fb99fde2ce923e22f478f098f057c7bcd92c424e45" +dependencies = [ + "base64 0.21.5", + "bs58", + "chrono", + "concordium-contracts-common-derive", + "fnv", + "hashbrown 0.11.2", + "hex", + "num-bigint 0.4.4", + "num-integer", + "num-traits", + "rust_decimal", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "concordium-contracts-common-derive" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9db2f3ebe6616b1658ec10acc3e9ca31f65824e3ab5ad88f3cf2532e25aed2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "concordium-rust-sdk" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caefb718c361bfa655f2dced8d35be1c5355a90b93418145e34781fa4d4566c4" +dependencies = [ + "aes-gcm", + "anyhow", + "chrono", + "concordium-smart-contract-engine", + "concordium_base", + "derive_more", + "ed25519-dalek", + "futures", + "hex", + "http", + "num", + "num-bigint 0.4.4", + "num-traits", + "prost", + "rand 0.7.3", + "rust_decimal", + "semver", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "concordium-smart-contract-engine" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc1650c044214b7da9914cceaedabdec884c4b9feaad5b47323292d7ba2e1fb" +dependencies = [ + "anyhow", + "byteorder", + "concordium-contracts-common", + "concordium-wasm", + "derive_more", + "ed25519-zebra", + "futures", + "libc", + "num_enum", + "rand 0.8.5", + "secp256k1", + "serde", + "sha2 0.10.8", + "sha3", + "slab", + "thiserror", + "tinyvec", +] + +[[package]] +name = "concordium-smart-contract-testing" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1a122d7c45c5022eba1553754becdef1944c6672a02926d8ffd06499f4b842" +dependencies = [ + "anyhow", + "concordium-rust-sdk", + "concordium-smart-contract-engine", + "concordium-wasm", + "concordium_base", + "num-bigint 0.4.4", + "num-integer", + "sha2 0.10.8", + "thiserror", + "tokio", +] + +[[package]] +name = "concordium-std" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194f22e2a418e367e584fb3e26238298578ccc944fd2355d53a4473eb4b47a51" +dependencies = [ + "concordium-contracts-common", + "wee_alloc", +] + +[[package]] +name = "concordium-wasm" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3147903254a3c4862db6207bd8f1f43dcbec6c457cc0053940c42b452cc442" +dependencies = [ + "anyhow", + "concordium-contracts-common", + "derive_more", + "leb128", + "num_enum", +] + +[[package]] +name = "concordium_base" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc958a44f995791e61d33867f8345df5c4d8f6c3c6cac5320452d4b1b9418011" +dependencies = [ + "aes", + "anyhow", + "base64 0.21.5", + "bs58", + "byteorder", + "cbc", + "chrono", + "concordium-contracts-common", + "concordium_base_derive", + "curve25519-dalek", + "derive_more", + "ed25519-dalek", + "either", + "ff", + "group", + "hex", + "hmac", + "itertools", + "leb128", + "libc", + "nom", + "num", + "num-bigint 0.4.4", + "num-traits", + "pairing", + "pbkdf2", + "rand 0.7.3", + "rand_core 0.5.1", + "rayon", + "rust_decimal", + "serde", + "serde_json", + "serde_with", + "sha2 0.10.8", + "sha3", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "concordium_base_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9a154e9a64d58d3e9f890c603df847b8685cfd0bac3fadd18498af539650bed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "euroe_stablecoin" +version = "0.1.0" +dependencies = [ + "concordium-cis2", + "concordium-smart-contract-testing", + "concordium-std", + "rand 0.7.3", +] + +[[package]] +name = "ff" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4530da57967e140ee0b44e0143aa66b5cb42bd9c503dbe316a15d5b0be65713e" +dependencies = [ + "byteorder", + "ff_derive", + "rand_core 0.5.1", +] + +[[package]] +name = "ff_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5796e7d62ca01a00ed3a649b0da1ffa1ac8f06bcad40339df09dbdd69a05ba9" +dependencies = [ + "num-bigint 0.2.6", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "group" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cbdfc48f95bef47e3daf3b9d552a1dde6311e3a5fefa43e16c59f651d56fe5b" +dependencies = [ + "ff", + "rand 0.7.3", + "rand_xorshift", +] + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[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.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint 0.4.4", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pairing" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c40534479a28199cd5109da27fe2fc4a4728e4fc701d9e9c1bded78f3271e4" +dependencies = [ + "byteorder", + "ff", + "group", + "rand_core 0.5.1", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac", + "password-hash", + "sha2 0.10.8", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.11", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rend" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +dependencies = [ + "bitvec", + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "secp256k1" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.5", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.5", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bf0b2a0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "euroe_stablecoin" +version = "0.1.0" +authors = ["Membrane Finance Oy ", "Concordium "] +edition = "2021" +license = "MPL-2.0" + +[features] +default = ["std", "wee_alloc"] +std = ["concordium-std/std", "concordium-cis2/std"] +wee_alloc = ["concordium-std/wee_alloc"] + +[dependencies] +concordium-std = {default-features = false,version = "8"} +concordium-cis2 = {default-features = false, version="5"} + +[dev-dependencies] +concordium-smart-contract-testing = {default-features = false, version = "3.2"} +rand = "0.7.0" +[lib] +crate-type=["cdylib", "rlib"] + +[profile.release] +codegen-units = 1 +opt-level = "s" diff --git a/README.md b/README.md new file mode 100644 index 0000000..eeeaf33 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# EUROe on Concordium + +This repository contains the EUROe stablecoin smart contract for the Concordium blockchain. Please find the higher level visual documentation on the [EUROe Developer Portal](https://dev.euroe.com/docs/Stablecoin/overview). + +## Directory Structure + +This directory contains four sub-directories: +1. `dist` - Contains the compiled version of the contract. +2. `interaction` - Holds JSON files used for contract interaction. +3. `src` - The main contract code resides here. +4. `tests` - Contains the tests. + + +The `Cargo.toml` file includes details such as the contract name and other dependencies. + + +### Contract Functions + +The contract has the following functions: + + - 'balanceOf' : 610 B + - 'block' : 596 B + - 'burn' : 606 B + - 'grantRole' : 799 B + - 'mint' : 600 B + - 'operatorOf' : 641 B + - 'permit' : 156 B + - 'removeRole' : 799 B + - 'setImplementors' : 563 B + - 'setPaused' : 547 B + - 'supports' : 588 B + - 'supportsPermit' : 605 B + - 'tokenMetadata' : 591 B + - 'transfer' : 673 B + - 'unblock' : 598 B + - 'updateOperator' : 624 B + - 'upgrade' : 516 B + - 'view' : 142 B + - 'viewMessageHash' : 162 B + - 'viewSupply' : 34 B + + +### Roles in the Contract + +Roles used to invoke various contract functions are as follows: + +- `AdminRole` - Handles other functionalities called via the admin, such as `setImplementors`, `updateOperator`, `upgrade` +- `BlockRole` - Blocks or unblocks an address +- `BurnRole` - Calls the burn function +- `MintRole` - Calls the mint function +- `PauseUnpauseRole` - Pauses or unpauses the contract + +## Contract Tests + +To run the tests, execute `cargo test`. +The tests are under `tests/test.rs`. +Remember to build first before running the tests, since it uses the dist/module.wasm.v1. Build instructions are provided below. + + + +### Test coverage + +The below list provides an overview of existing test coverage and backlog items for tests. + +- Roles + - OK/ Assigning roles as admin + - OK/ Assigning roles as unauthorised + - Removing roles as admin + - Removing roles as unauthorised +- Minting + - OK/ Minting as unauthorised + - OK/ Minting as authorised + - OK/ Minting to blocklisted address + - OK/ Calling mint from a blocklisted address + - OK/ Minting when contract is paused +- Burning + - OK/ Burning as unauthorised + - OK/ Burning as authorised + - Burning with insufficient balance + - OK/ Burning from a blocklisted address + - OK/ Calling burn froma blocklisted address + - OK/ Burning when contract is paused +- Transferring + - OK/ Transferring as unauthorised (not token owner nor operator) + - OK/ Transferring as authorised owner + - Transferring with insufficient balance + - OK/ Transferring to a blocklisted address + - OK/ Transferring from a blocklisted address + - OK/ Transferring when contract is paused +- Pausing and unpausing + - OK/ Pausing as unauthorised + - OK/ Pausing as authorised + - Unpausing as unauthorised + - Unpausing as authorised +- Blocklisting and unblocklisting + - Blocking as unauthorised + - Blocking as authorised + - Unblocking as unauthorised + - Unblocking as unauthorised +- Operators + - OK/ Assigning an operator + - Unassigning an operator + - OK/ Blocklisted address authorising an operator + - Blocklisted address assigned as an operator + - OK/ Operator transfer works + - Operator transfers to a blocklisted address + - Operator transfers from a blocklisted address + - Operator transfers when operator is blocklisted + - Operator transfers during contract pause + - Operator assigned during contract pause + - Operator un-assigned during contract pause +- CIS2 + - Contract supports CIS2 + - OK/ Metadata URL is correct +- CIS3 + - Contract supports permit + - View message hash works + - OK/ Updating operator with permit works + - OK/ Transfering with permit works + - Nonce is incremented correctly +- Contract upgrade + - Upgrade works +- Miscellaneous + - OK/ Circulating supply works + - Circulating supply works correctly (randomised mints & burns) + + +## Compilation and Interaction Instructions + +```bash +# Building the contract +cargo concordium build --schema-embed --out dist/module.wasm.v1 --schema-out dist/schema.bin + +# Deploying the contract +concordium-client module deploy dist/module.wasm.v1 --sender --name euroe_stablecoin --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Initializing the contract +concordium-client contract init --sender --contract euroe_stablecoin --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 +``` + +### Additional Commands + +Additional commands are available for functionalities such as minting, burning, and transferring tokens. Please refer to the sections below for more details. +The commands below use the files from the `interaction` folder. + +```bash +# Mint +concordium-client contract update --entrypoint mint --parameter-json interaction/mint2.json --schema dist/schema.bin --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# View +concordium-client contract invoke --entrypoint view --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Transfer +concordium-client contract update --entrypoint transfer --parameter-json interaction/transfer.json --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Burn +concordium-client contract update --entrypoint burn --parameter-json interaction/burn.json --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Pause +concordium-client contract update --entrypoint setPaused --parameter-json interaction/pause.json --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Unpause +concordium-client contract update --entrypoint setPaused --parameter-json interaction/pause.json --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Add role +concordium-client contract update --entrypoint grantRole --parameter-json interaction/auth.json --schema dist/schema.bin --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Remove role +concordium-client contract update --entrypoint removeRole --parameter-json interaction/auth.json --schema dist/schema.bin --sender --energy 6000 --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +# Upgrade contract +concordium-client contract update --entrypoint upgrade --parameter-json interaction/upgrade.json --energy 5000 --sender --grpc-ip node.testnet.concordium.com --grpc-port 20000 + +``` + + +### Adding another wallet to the concordium client +To add a wallet to invoke this contract, you will have to export a wallet from the web wallet. +Then use the below command. +`concordium-client config account import --name ` \ No newline at end of file diff --git a/dist/module.wasm.v1 b/dist/module.wasm.v1 new file mode 100644 index 0000000..1f52fc1 Binary files /dev/null and b/dist/module.wasm.v1 differ diff --git a/dist/schema.bin b/dist/schema.bin new file mode 100644 index 0000000..424b826 Binary files /dev/null and b/dist/schema.bin differ diff --git a/interaction/auth.json b/interaction/auth.json new file mode 100644 index 0000000..f770d31 --- /dev/null +++ b/interaction/auth.json @@ -0,0 +1,27 @@ +{ + "mintrole": { + "Account": [ + "4gBWupZFEPafw2kNmFi2DGuongg1ybV1u6Xg3LjreSm4AWmBeR" + ] + }, + "pauserole": { + "Account": [ + "4gBWupZFEPafw2kNmFi2DGuongg1ybV1u6Xg3LjreSm4AWmBeR" + ] + }, + "burnrole": { + "Account": [ + "4gBWupZFEPafw2kNmFi2DGuongg1ybV1u6Xg3LjreSm4AWmBeR" + ] + }, + "blockrole": { + "Account": [ + "4gBWupZFEPafw2kNmFi2DGuongg1ybV1u6Xg3LjreSm4AWmBeR" + ] + }, + "adminrole": { + "Account": [ + "4gBWupZFEPafw2kNmFi2DGuongg1ybV1u6Xg3LjreSm4AWmBeR" + ] + } +} \ No newline at end of file diff --git a/interaction/block.json b/interaction/block.json new file mode 100644 index 0000000..7c404e8 --- /dev/null +++ b/interaction/block.json @@ -0,0 +1,7 @@ +{ + "user_to_block": { + "Account": [ + "4EbnQT4cvF7L9xsoyWr7QHabbqzY5B8KM1JUg9H7AEe4uiVjqq" + ] + } +} \ No newline at end of file diff --git a/interaction/burn.json b/interaction/burn.json new file mode 100644 index 0000000..ce9f0fd --- /dev/null +++ b/interaction/burn.json @@ -0,0 +1,4 @@ +{ + "token_id": "01", + "amount": "2" +} \ No newline at end of file diff --git a/interaction/mint2.json b/interaction/mint2.json new file mode 100644 index 0000000..9669945 --- /dev/null +++ b/interaction/mint2.json @@ -0,0 +1,8 @@ +{ + "owner": { + "Account": [ + "4EbnQT4cvF7L9xsoyWr7QHabbqzY5B8KM1JUg9H7AEe4uiVjqq" + ] + }, + "amount": 5 +} \ No newline at end of file diff --git a/interaction/pause.json b/interaction/pause.json new file mode 100644 index 0000000..983ba69 --- /dev/null +++ b/interaction/pause.json @@ -0,0 +1,4 @@ +{ + "paused":false +} + diff --git a/interaction/transfer.json b/interaction/transfer.json new file mode 100644 index 0000000..cec6467 --- /dev/null +++ b/interaction/transfer.json @@ -0,0 +1,17 @@ +[ + { + "token_id": "01", + "amount": "2", + "from": { + "Account": [ + "4XevxM77w7JTKStBVfhnXdTbMyVVwZj9f1hw65NXhFX5TC7DYB" + ] + }, + "to": { + "Account": [ + "32pwnri7uqPdAQjbBQ9fdwkvm7cUJNwRrdacFkt8qCZrU8PLwg" + ] + }, + "data": [] + } +] \ No newline at end of file diff --git a/interaction/unblock.json b/interaction/unblock.json new file mode 100644 index 0000000..92d36cd --- /dev/null +++ b/interaction/unblock.json @@ -0,0 +1,7 @@ +{ + "user_to_unblock": { + "Account": [ + "4EbnQT4cvF7L9xsoyWr7QHabbqzY5B8KM1JUg9H7AEe4uiVjqq" + ] + } +} \ No newline at end of file diff --git a/interaction/upgrade.json b/interaction/upgrade.json new file mode 100644 index 0000000..4437524 --- /dev/null +++ b/interaction/upgrade.json @@ -0,0 +1,8 @@ + { + "migrate": { + "None": [ + + ] + }, + "module": "bb63fe9d0e6b4062e1b055051067669091e156995a912e0a99d11cf817278b71" +} \ No newline at end of file diff --git a/interaction/upgrade_with_state.json b/interaction/upgrade_with_state.json new file mode 100644 index 0000000..ce7e9b1 --- /dev/null +++ b/interaction/upgrade_with_state.json @@ -0,0 +1,11 @@ +{ + "migrate": { + "Some": [ + [ + "migration", + "" + ] + ] + }, + "module": "bb63fe9d0e6b4062e1b055051067669091e156995a912e0a99d11cf817278b71" + } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f1d24eb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1685 @@ +//! SPDX-License-Identifier: MIT +//! +//! EUROe Contract Using Concordium Token Standard CIS2. +//! Copyright (c) 2023 Membrane Finance Oy +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. +//! +//! +//! CIS-2 smart contract for the EUROe Stablecoin. +//! This contract implements the EUROe stablecoin along with its core functionality, such as minting and burning, for the Concordium blockchain as an augmented CIS-2 token. For more information, please see https://dev.euroe.com. + +#![cfg_attr(not(feature = "std"), no_std)] + +use concordium_cis2::*; +use concordium_std::{collections::BTreeMap, EntrypointName,*}; + +/// The ID of the EUROe token in this contract +const TOKEN_ID_EUROE: ContractTokenId = TokenIdUnit(); + +/// The base URL for the token metadata +const TOKEN_METADATA_BASE_URL: &str = "https://dev.euroe.com/persistent/euroe-concordium-offchain-data.json"; + +/// The standard identifier for CIS-3 +pub const CIS3_STANDARD_IDENTIFIER: StandardIdentifier<'static> = + StandardIdentifier::new_unchecked("CIS-3"); + +/// List of standards supported by this contract address +const SUPPORTS_STANDARDS: [StandardIdentifier<'static>; 3] = + [CIS0_STANDARD_IDENTIFIER, CIS2_STANDARD_IDENTIFIER, CIS3_STANDARD_IDENTIFIER]; + +/// List of entrypoints supported by the `permit` function (CIS3) +const SUPPORTS_PERMIT_ENTRYPOINTS: [EntrypointName; 2] = + [EntrypointName::new_unchecked("updateOperator"), EntrypointName::new_unchecked("transfer")]; + +/// Tag for the CIS3 Nonce event +pub const NONCE_EVENT_TAG: u8 = u8::MAX - 5; + +// Event definitions +impl schema::SchemaType for Event { + fn get_type() -> schema::Type { + let mut event_map = BTreeMap::new(); + event_map.insert( + NONCE_EVENT_TAG, + ( + "Nonce".to_string(), + schema::Fields::Named(vec![ + (String::from("account"), AccountAddress::get_type()), + (String::from("nonce"), u64::get_type()), + ]), + ), + ); + event_map.insert( + TRANSFER_EVENT_TAG, + ( + "Transfer".to_string(), + schema::Fields::Named(vec![ + (String::from("token_id"), ContractTokenId::get_type()), + (String::from("amount"), ContractTokenAmount::get_type()), + (String::from("from"), Address::get_type()), + (String::from("to"), Address::get_type()), + ]), + ), + ); + event_map.insert( + MINT_EVENT_TAG, + ( + "Mint".to_string(), + schema::Fields::Named(vec![ + (String::from("token_id"), ContractTokenId::get_type()), + (String::from("amount"), ContractTokenAmount::get_type()), + (String::from("owner"), Address::get_type()), + ]), + ), + ); + event_map.insert( + BURN_EVENT_TAG, + ( + "Burn".to_string(), + schema::Fields::Named(vec![ + (String::from("token_id"), ContractTokenId::get_type()), + (String::from("amount"), ContractTokenAmount::get_type()), + (String::from("owner"), Address::get_type()), + ]), + ), + ); + event_map.insert( + UPDATE_OPERATOR_EVENT_TAG, + ( + "UpdateOperator".to_string(), + schema::Fields::Named(vec![ + (String::from("update"), OperatorUpdate::get_type()), + (String::from("owner"), Address::get_type()), + (String::from("operator"), Address::get_type()), + ]), + ), + ); + event_map.insert( + TOKEN_METADATA_EVENT_TAG, + ( + "TokenMetadata".to_string(), + schema::Fields::Named(vec![ + (String::from("token_id"), ContractTokenId::get_type()), + (String::from("metadata_url"), MetadataUrl::get_type()), + ]), + ), + ); + schema::Type::TaggedEnum(event_map) + } +} + +// Types + +/// Contract token ID type +pub type ContractTokenId = TokenIdUnit; + +/// Contract token amount type +pub type ContractTokenAmount = TokenAmountU64; + +/// The state for each address +#[derive(Serial, DeserialWithState, Deletable)] +#[concordium(state_parameter = "S")] +struct AddressState { + /// The amount of tokens owned by this address + balances: StateMap, + /// The address which are currently enabled as operators for this address + operators: StateSet, +} + +impl AddressState { + fn empty(state_builder: &mut StateBuilder) -> Self { + AddressState { + balances: state_builder.new_map(), + operators: state_builder.new_set(), + } + } +} + +/// The contract state +/// +/// Note: The specification does not specify how to structure the contract state +/// and this could be structured in a more space efficient way. +#[derive(Serial, DeserialWithState)] +#[concordium(state_parameter = "S")] +struct State { + /// The state of addresses. + state: StateMap, S>, + /// Map specifying the `AddressState` (balance and operators) for every address. + token: StateMap, S>, + /// Map specifying the total supply of each token. + token_balance: StateMap, + /// Map with contract addresses providing implementations of additional standards. + implementors: StateMap, S>, + // Paused state for stopping relevant contract operations. + paused: bool, + // State of the roles. + roles: StateMap, S>, + // State of blocklisted addresses. + blocklist: StateSet, + /// A registry to link an account to its next nonce. The nonce is used to + /// prevent replay attacks of the signed message. The nonce is increased + /// sequentially every time a signed message (corresponding to the + /// account) is successfully executed in the `permit` function. This + /// mapping keeps track of the next nonce that needs to be used by the + /// account to generate a signature. + nonces_registry: StateMap, + +} + +/// The errors the contract can produce. +#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)] +pub enum CustomContractError { + /// Failed parsing the parameter. + #[from(ParseError)] + ParseParams, + /// Failed logging: Log is full. + LogFull, + /// Failed logging: Log is malformed. + LogMalformed, + /// Invalid contract name. + InvalidContractName, + /// Only a smart contract can call this function. + ContractOnly, + /// Failed to invoke a contract. + InvokeContractError, + /// Minted token unique ID. + TokenAlreadyMinted, + // Max supply reached. + MaxSupplyReached, + // Not enough balance to burn. + NoBalanceToBurn, + // Contract is paused. + ContractPaused, + // Address is blocklisted. + AddressBlocklisted, + /// Upgrade failed because the new module does not exist. + FailedUpgradeMissingModule, + /// Upgrade failed because the new module does not contain a contract with a + /// matching name. + FailedUpgradeMissingContract, + /// Upgrade failed because the smart contract version of the module is not + /// supported. + FailedUpgradeUnsupportedModuleVersion, + /// Failed to verify signature because signer account does not exist on + /// chain. + MissingAccount, + /// Failed to verify signature because data was malformed. + MalformedData, + /// Failed signature verification: Invalid signature. + WrongSignature, + /// Failed signature verification: A different nonce is expected. + NonceMismatch, + /// Failed signature verification: Signature was intended for a different + /// contract. + WrongContract, + /// Failed signature verification: Signature was intended for a different + /// entry_point. + WrongEntryPoint, + /// Failed signature verification: Signature is expired. + Expired, +} + +pub type ContractError = Cis2Error; + +pub type ContractResult = Result; + +/// Mapping of the logging errors to ContractError. +impl From for CustomContractError { + fn from(le: LogError) -> Self { + match le { + LogError::Full => Self::LogFull, + LogError::Malformed => Self::LogMalformed, + } + } +} + +/// Mapping of errors related to contract invocations to CustomContractError. +impl From> for CustomContractError { + fn from(_cce: CallContractError) -> Self { + Self::InvokeContractError + } +} + +/// Mapping of CustomContractError to ContractError. +impl From for ContractError { + fn from(c: CustomContractError) -> Self { + Cis2Error::Custom(c) + } +} + +/// Mapping of NewReceiveNameError to CustomContractError. +impl From for CustomContractError { + fn from(_: NewReceiveNameError) -> Self { + Self::InvalidContractName + } +} +/// Mapping of NewContractNameError to CustomContractError. +impl From for CustomContractError { + fn from(_: NewContractNameError) -> Self { + Self::InvalidContractName + } +} + +impl State { + /// Construct a state with no tokens. + fn empty(state_builder: &mut StateBuilder, admin: Address) -> Self { + let mut state = State { + state: state_builder.new_map(), + token: state_builder.new_map(), + token_balance: state_builder.new_map(), + implementors: state_builder.new_map(), + paused: false, + roles: state_builder.new_map(), + blocklist: state_builder.new_set(), + nonces_registry: state_builder.new_map(), + }; + state.grant_role(&admin, Roles::AdminRole, state_builder); + state + } + + /// Creates (mints) an amount of tokens to the address passed in `owner`. + fn mint( + &mut self, + token_id: &ContractTokenId, + amount: ContractTokenAmount, + owner: &Address, + state_builder: &mut StateBuilder, + ) { + let mut owner_state = + self.state.entry(*owner).or_insert_with(|| AddressState::empty(state_builder)); + let mut owner_balance = owner_state.balances.entry(*token_id).or_insert(0.into()); + *owner_balance += amount; + // Add the minted amount to the circulating supply. + let mut circulating_supply = self.token_balance.entry(*token_id).or_insert(0.into()); + *circulating_supply += amount; + } + + /// Removes (burns) an amount of tokens from the address passed in `owner`. + fn burn( + &mut self, + token_id: &ContractTokenId, + amount: ContractTokenAmount, + owner: &Address, + ) -> ContractResult<()> { + ensure_eq!(token_id, &TOKEN_ID_EUROE, ContractError::InvalidTokenId); + if amount == 0u64.into() { + return Ok(()); + } + + match self.state.get_mut(owner) { + Some(mut address_state) => match address_state.balances.get_mut(token_id) { + Some(mut b) => { + ensure!( + *b >= amount, + Cis2Error::Custom(CustomContractError::NoBalanceToBurn) + ); + + *b -= amount; + // Deduct the burned amount from the circulating supply. + match self.token_balance.get_mut(token_id) { + Some(mut circulating_supply) => { + ensure!( + circulating_supply.cmp(&amount).is_ge(), + Cis2Error::Custom(CustomContractError::NoBalanceToBurn) + ); + *circulating_supply -= amount; + } + None => return Err(Cis2Error::Custom(CustomContractError::NoBalanceToBurn)), + } + Ok(()) + } + None => Err(Cis2Error::Custom(CustomContractError::NoBalanceToBurn)), + }, + None => Err(Cis2Error::Custom(CustomContractError::NoBalanceToBurn)), + } + } + + /// Returns the current token supply (cumulative mints less cumulative burns). + #[inline(always)] + fn get_circulating_supply( + &self, + token_id: &ContractTokenId, + ) -> ContractResult { + ensure_eq!(token_id, &TOKEN_ID_EUROE, ContractError::InvalidTokenId); + let circulating_supply = self.token_balance.get(token_id).map_or(0.into(), |x| *x); + Ok(circulating_supply) + } + + /// Get the current balance of a given token id for a given address. + /// Results in an error if the token id does not exist in the state. + fn balance( + &self, + token_id: &ContractTokenId, + address: &Address, + ) -> ContractResult { + ensure_eq!(token_id, &TOKEN_ID_EUROE, ContractError::InvalidTokenId); + let balance = self.state.get(address).map_or(0.into(), |address_state| { + address_state.balances.get(token_id).map_or(0.into(), |x| *x) + }); + Ok(balance) + } + + /// Check if an address is an operator of a given owner address. + fn is_operator(&self, address: &Address, owner: &Address) -> bool { + self.state + .get(owner) + .map(|address_state| address_state.operators.contains(address)) + .unwrap_or(false) + } + + /// Update the state with a transfer. + /// Results in an error if the token ID does not exist in the state or if + /// the source address has insufficient amount of tokens to do the transfer. + fn transfer( + &mut self, + token_id: &ContractTokenId, + amount: ContractTokenAmount, + from: &Address, + to: &Address, + state_builder: &mut StateBuilder, + ) -> ContractResult<()> { + ensure_eq!(token_id, &TOKEN_ID_EUROE, ContractError::InvalidTokenId); + // A zero transfer does not modify the state. + if amount == 0.into() { + return Ok(()); + } + + // Get the `from` address state and balance. If not present it will fail since + // the balance is interpreted as 0 and the transfer amount must be more than 0. + { + let mut from_address_state = + self.state.entry(*from).occupied_or(ContractError::InsufficientFunds)?; + let mut from_balance = from_address_state + .balances + .entry(*token_id) + .occupied_or(ContractError::InsufficientFunds)?; + ensure!(*from_balance >= amount, ContractError::InsufficientFunds); + *from_balance -= amount; + } + + let mut to_address_state = + self.state.entry(*to).or_insert_with(|| AddressState::empty(state_builder)); + let mut to_address_balance = to_address_state.balances.entry(*token_id).or_insert(0.into()); + *to_address_balance += amount; + + Ok(()) + } + + /// Update the state adding a new operator for a given address. + /// Succeeds even if the `operator` is already an operator for the `address`. + fn add_operator( + &mut self, + owner: &Address, + operator: &Address, + state_builder: &mut StateBuilder, + ) { + let mut owner_state = + self.state.entry(*owner).or_insert_with(|| AddressState::empty(state_builder)); + owner_state.operators.insert(*operator); + } + + /// Update the state removing an operator for a given address. + /// Succeeds even if the `operator` is not an operator for the `address`. + fn remove_operator(&mut self, owner: &Address, operator: &Address) { + self.state.entry(*owner).and_modify(|address_state| { + address_state.operators.remove(operator); + }); + } + + /// Check if the state contains any implementors for a given standard. + fn have_implementors(&self, std_id: &StandardIdentifierOwned) -> SupportResult { + if let Some(addresses) = self.implementors.get(std_id) { + SupportResult::SupportBy(addresses.to_vec()) + } else { + SupportResult::NoSupport + } + } + + /// Set implementors for a given standard. + fn set_implementors( + &mut self, + std_id: StandardIdentifierOwned, + implementors: Vec, + ) { + self.implementors.insert(std_id, implementors); + } + + /// Check if the contract has a given role for a specific Address. + fn has_role(&self, account: &Address, role: Roles) -> bool { + return match self.roles.get(account) { + None => false, + Some(roles) => roles.roles.contains(&role), + }; + } + + /// Grants a role to a specific ddress. + fn grant_role(&mut self, account: &Address, role: Roles, state_builder: &mut StateBuilder) { + self.roles + .entry(*account) + .or_insert_with(|| AddressRoleState { + roles: state_builder.new_set(), + }); + + self.roles.entry(*account).and_modify(|entry| { + entry.roles.insert(role); + }); + } + + /// Remove a role from a specific Address. + fn remove_role(&mut self, account: &Address, role: Roles) { + self.roles.entry(*account).and_modify(|entry| { + entry.roles.remove(&role); + }); + } + + /// Block a specific Address + fn block_address( + &mut self, + blocklistaddress: &Address, + ) { + self.blocklist.insert(*blocklistaddress); + } + + /// Unblock a specific Address + fn unblock_address( + &mut self, + blocklistaddress: &Address, + ) { + self.blocklist.remove(blocklistaddress); + } + + /// Check if a specific Address is blocked + fn is_blocked( + &self, + blocklistaddress: &Address, + ) -> bool { + self.blocklist.contains(blocklistaddress) + } + +} + +/// Build a string from TOKEN_METADATA_BASE_URL. +fn build_token_metadata_url() -> String { + String::from(TOKEN_METADATA_BASE_URL) +} + +/// Initialize contract instance with no token types. +#[init(contract = "euroe_stablecoin")] +fn contract_init( + ctx: &impl HasInitContext, + state_builder: &mut StateBuilder, +) -> InitResult> { + // Construct the initial contract state. + let invoker: Address = Address::Account(ctx.init_origin()); + Ok(State::empty(state_builder, invoker)) +} + +#[derive(Serialize, SchemaType, PartialEq, Eq, Debug)] +pub struct ViewAddressState { + pub balances: Vec<(ContractTokenId, ContractTokenAmount)>, + pub operators: Vec
, +} + +#[derive(Serialize, SchemaType, PartialEq, Eq)] +pub struct ViewState { + pub state: Vec<(Address, ViewAddressState)>, +} + +/// View function for testing. This reports the entire state of the contract +/// for testing purposes. +#[receive(contract = "euroe_stablecoin", name = "view", return_value = "ViewState")] +fn contract_view( + _ctx: &impl HasReceiveContext, + host: &impl HasHost, StateApiType = S>, +) -> ReceiveResult { + let state = host.state(); + + let mut inner_state = Vec::new(); + for (k, a_state) in state.state.iter() { + let mut balances = Vec::new(); + let mut operators = Vec::new(); + for (token_id, amount) in a_state.balances.iter() { + balances.push((*token_id, *amount)); + } + for o in a_state.operators.iter() { + operators.push(*o); + } + + inner_state.push(( + *k, + ViewAddressState { + balances, + operators, + }, + )); + } + + Ok(ViewState { + state: inner_state, + }) +} + +#[derive(Serialize, SchemaType, Eq, PartialEq, Debug)] +pub struct ViewCirculatingSupply { + pub circulating_supply : ContractTokenAmount, +} + +/// This viewSupply function returns the current circulating supply of EUROe. +#[receive(contract = "euroe_stablecoin", name = "viewSupply", return_value = "ViewCirculatingSupply")] +fn contract_get_circulating_supply( + _ctx: &impl HasReceiveContext, + host: &impl HasHost, StateApiType = S>, +) -> ReceiveResult { + let supply = host.state().get_circulating_supply(&TOKEN_ID_EUROE); + Ok(ViewCirculatingSupply { + circulating_supply: supply.unwrap(), + }) +} + +/// The parameter for the contract function `mint` which mints an amount of EUROe to a given address. +#[derive(Serial, Deserial, SchemaType)] +pub struct MintParams { + pub owner: Address, + pub amount: TokenAmountU64, +} + +/// Mint new EUROe to a given address. +/// Can only be called by an address with the `MintRole` role. +/// Logs a `Mint` and a `TokenMetadata` event for each token. +/// +/// It rejects if: +/// - The sender does not have the role `MintRole`. +/// - Fails to parse parameter. +/// - The sender or the receiving address (owner) is blocked. +#[receive( + contract = "euroe_stablecoin", + name = "mint", + parameter = "MintParams", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_mint( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Check if the contract is paused. + ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused)); + + // Get the sender of the transaction. + let sender = ctx.sender(); + + // Check if the sender is blocked. + ensure!(!host.state().is_blocked(&sender),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::MintRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: MintParams = ctx.parameter_cursor().get()?; + + let (state, builder) = host.state_and_builder(); + + let owner: Address = params.owner; + let amount: TokenAmountU64 = params.amount; + + // If the owner in the parameters is blocked the transaction is rejected. + ensure!(!state.is_blocked(&owner),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Mint the token in the state. + state.mint(&TOKEN_ID_EUROE, amount, &owner, builder); + + // Log the mint event. + logger.log(&Cis2Event::Mint(MintEvent { + token_id: TOKEN_ID_EUROE, + amount, + owner, + }))?; + + + logger.log(&Cis2Event::TokenMetadata::<_, ContractTokenAmount>(TokenMetadataEvent { + token_id: TOKEN_ID_EUROE, + metadata_url: MetadataUrl { + url: build_token_metadata_url(), + hash: None, + }, + }))?; + Ok(()) +} + +#[derive(Serial, Deserial, SchemaType)] +pub struct BurnParams { + pub amount: ContractTokenAmount, + pub burnaddress: Address, +} + +/// Burn EUROe from the sender's account. +/// Logs a `Burn` event for each token. +/// +/// It rejects if: +/// - the sender does not have the role `BurnRole`. +/// - the sender is blocked. +/// - the contract is paused. +#[receive( + contract = "euroe_stablecoin", + name = "burn", + parameter = "BurnParams", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_burn( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + // Check if the contract is paused. + ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused)); + + // Get the sender of the transaction. + let sender = ctx.sender(); + + // Check if the sender is blocked. + ensure!(!host.state().is_blocked(&sender),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::BurnRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: BurnParams = ctx.parameter_cursor().get()?; + + let amount = params.amount; + + let burnaddress = params.burnaddress; + + // Check if the address from which EUROe are burned is blocklisted. + ensure!(!host.state().is_blocked(&burnaddress),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + let (state, _builder) = host.state_and_builder(); + + state.burn(&TOKEN_ID_EUROE, amount, &burnaddress)?; + + // Log the burn event. + logger.log(&Cis2Event::Burn(BurnEvent { + token_id: TOKEN_ID_EUROE, + amount, + owner: burnaddress, + }))?; + Ok(()) +} + +type TransferParameter = TransferParams; + +/// Execute a list of token transfers, in the order of the list. +/// +/// Logs a `Transfer` event and invokes a receive hook function for every +/// transfer in the list. +/// +/// It rejects if: +/// - It fails to parse the parameter. +/// - Any of the transfers fail to be executed, which could be if: +/// - The `token_id` does not exist. +/// - The sender is not the owner of the token, or an operator for this +/// specific `token_id` and `from` address. +/// - The EUROe balance of `from` is not sufficient. +/// - The sender, owner, or receiving address is blocked. +#[receive( + contract = "euroe_stablecoin", + name = "transfer", + parameter = "TransferParameter", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_transfer( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Check if the contract is paused. + ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused)); + + // Get the sender Address. + let sender = ctx.sender(); + + // Check if the sender is blocked. + ensure!(!host.state().is_blocked(&sender),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Parse the parameters. + let TransferParams(transfers): TransferParameter = ctx.parameter_cursor().get()?; + + for transfer in transfers + { + let state = host.state(); + + // Authorize the sender for this transfer. + ensure!(transfer.from == sender || state.is_operator(&sender, &transfer.from), ContractError::Unauthorized); + + // Calls the transfer helper function to execute the transfer. + transfer_helper(transfer, host, logger)?; + } + Ok(()) +} + +/// Enable or disable addresses as operators of the sender address. +/// Logs an `UpdateOperator` event. +/// +/// It rejects if: +/// - It fails to parse the parameter. +/// - Either of the addresses is blocked. +/// - The contract is paused. +#[receive( + contract = "euroe_stablecoin", + name = "updateOperator", + parameter = "UpdateOperatorParams", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_update_operator( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + // Check if the contract is paused. + ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused)); + + // Get the sender who invoked this contract function. + let sender = ctx.sender(); + + // Check if the sender is blocklisted. + ensure!(!host.state().is_blocked(&sender),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Parse the parameters. + let UpdateOperatorParams(params) = ctx.parameter_cursor().get()?; + + let (state, builder) = host.state_and_builder(); + for param in params { + // Check if the operator is blocklisted. + ensure!(!state.is_blocked(¶m.operator),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + update_operator(param.update, sender, param.operator, state, builder, logger)?; + } + Ok(()) +} + +/// Parameter type for the CIS-2 function `balanceOf` specialized to the subset +/// of TokenIDs used by this contract. +pub type ContractBalanceOfQueryParams = BalanceOfQueryParams; + +/// Response type for the CIS-2 function `balanceOf` specialized to the subset +/// of TokenAmounts used by this contract. +pub type ContractBalanceOfQueryResponse = BalanceOfQueryResponse; + +/// Get the balance of given token IDs and addresses. +/// Anyone can call this function. +/// It rejects if: +/// - It fails to parse the parameter. +/// - Any of the queried `token_id` does not exist. +#[receive( + contract = "euroe_stablecoin", + name = "balanceOf", + parameter = "ContractBalanceOfQueryParams", + return_value = "ContractBalanceOfQueryResponse", + error = "ContractError" +)] +fn contract_balance_of( + ctx: &impl HasReceiveContext, + host: &impl HasHost, StateApiType = S>, +) -> ContractResult { + + // Parse the parameters. + let params: ContractBalanceOfQueryParams = ctx.parameter_cursor().get()?; + + // Build the response. + let mut response = Vec::with_capacity(params.queries.len()); + + for query in params.queries { + // Query the state for balance. + let amount = host.state().balance(&query.token_id, &query.address)?; + response.push(amount); + } + let result = ContractBalanceOfQueryResponse::from(response); + Ok(result) +} + +/// Takes a list of queries. Each query is an owner address and some address to +/// check as an operator of the owner address. +/// Anyone can call this function. +/// It rejects if: +/// - It fails to parse the parameter. +#[receive( + contract = "euroe_stablecoin", + name = "operatorOf", + parameter = "OperatorOfQueryParams", + return_value = "OperatorOfQueryResponse", + error = "ContractError" +)] +fn contract_operator_of( + ctx: &impl HasReceiveContext, + host: &impl HasHost, StateApiType = S>, +) -> ContractResult { + // Parse the parameter. + let params: OperatorOfQueryParams = ctx.parameter_cursor().get()?; + + // Build the response. + let mut response = Vec::with_capacity(params.queries.len()); + + for query in params.queries { + // Check if an address is an operator of a given owner address. + let is_operator = host.state().is_operator(&query.address, &query.owner); + response.push(is_operator); + } + let result = OperatorOfQueryResponse::from(response); + Ok(result) +} + +/// Parameter type for the CIS-2 function `tokenMetadata` specialized to the +/// subset of TokenIDs used by this contract. +type ContractTokenMetadataQueryParams = TokenMetadataQueryParams; + +/// Get the token metadata URLs and checksums given a list of token IDs. +/// Anyone can call this function. +/// It rejects if: +/// - It fails to parse the parameter. +/// - Any of the queried `token_id` does not exist. +#[receive( + contract = "euroe_stablecoin", + name = "tokenMetadata", + parameter = "ContractTokenMetadataQueryParams", + return_value = "TokenMetadataQueryResponse", + error = "ContractError" +)] + +fn contract_token_metadata( + ctx: &impl HasReceiveContext, + _host: &impl HasHost, StateApiType = S>, +) -> ContractResult { + + // Parse the parameters. + let params: ContractTokenMetadataQueryParams = ctx.parameter_cursor().get()?; + + // Build the response. + let mut response = Vec::with_capacity(params.queries.len()); + + for token_id in params.queries { + // Check the token exists. + ensure!(token_id == TOKEN_ID_EUROE, ContractError::InvalidTokenId); + + let metadata_url = MetadataUrl { + url: build_token_metadata_url(), + hash: None, + }; + response.push(metadata_url); + } + let result = TokenMetadataQueryResponse::from(response); + Ok(result) +} + +/// Get the supported standards or addresses for a implementation given list of +/// standard identifiers. +/// +/// It rejects if: +/// - It fails to parse the parameter. +#[receive( + contract = "euroe_stablecoin", + name = "supports", + parameter = "SupportsQueryParams", + return_value = "SupportsQueryResponse", + error = "ContractError" +)] +fn contract_supports( + ctx: &impl HasReceiveContext, + host: &impl HasHost, StateApiType = S>, +) -> ContractResult { + // Parse the parameters. + let params: SupportsQueryParams = ctx.parameter_cursor().get()?; + + // Build the response. + let mut response = Vec::with_capacity(params.queries.len()); + for std_id in params.queries { + if SUPPORTS_STANDARDS.contains(&std_id.as_standard_identifier()) { + response.push(SupportResult::Support); + } else { + response.push(host.state().have_implementors(&std_id)); + } + } + let result = SupportsQueryResponse::from(response); + Ok(result) +} + +/// The parameter type for the contract function `setImplementors`. +/// Takes a standard identifier and a list of contract addresses providing +/// implementations of this standard. +#[derive(Debug, Serialize, SchemaType)] +pub struct SetImplementorsParams { + /// The identifier for the standard. + pub id: StandardIdentifierOwned, + /// The addresses of the implementors of the standard. + pub implementors: Vec, +} + +/// Set the addresses for an implementation given a standard identifier and a +/// list of contract addresses. +/// The contract can only be called by the Admin Role +/// It rejects if: +/// - It fails to parse the parameter. +/// - Sender does not have the `AdminRole` role. +#[receive( + contract = "euroe_stablecoin", + name = "setImplementors", + parameter = "SetImplementorsParams", + error = "ContractError", + mutable +)] +fn contract_set_implementor( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, +) -> ContractResult<()> { + + let sender = ctx.sender(); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::AdminRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: SetImplementorsParams = ctx.parameter_cursor().get()?; + + // Update the implementors in the state + host.state_mut().set_implementors(params.id, params.implementors); + Ok(()) +} + +/// The parameter type for the contract function `setPaused`. +#[derive(Serialize, SchemaType, Debug)] +#[repr(transparent)] +pub struct SetPausedParams { + pub paused: bool, +} + +/// Pause or unpause the smart contract. No non-administrative +/// state-mutative functions (mint, burn, transfer, updateOperator, permit) can be +/// executed when the contract is paused. +/// +/// It rejects if: +/// - It fails to parse the parameter. +/// - Sender does not have the `PauseUnpauseRole` role. +#[receive( + contract = "euroe_stablecoin", + name = "setPaused", + parameter = "SetPausedParams", + error = "ContractError", + mutable +)] +fn contract_set_paused( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, +) -> ContractResult<()> { + let sender = ctx.sender(); + + // Check that the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::PauseUnpauseRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: SetPausedParams = ctx.parameter_cursor().get()?; + + // Update the paused variable. + host.state_mut().paused = params.paused; + + Ok(()) +} + +#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType, Clone)] +pub enum Roles { + MintRole, + BurnRole, + PauseUnpauseRole, + BlockUnblockRole, + AdminRole, +} +#[derive(Serial, DeserialWithState, Deletable)] +#[concordium(state_parameter = "S")] +struct AddressRoleState { + roles: StateSet, +} + +#[derive(Serialize, SchemaType)] +pub struct RoleTypes { + pub mintrole: Address, + pub burnrole: Address, + pub blockrole: Address, + pub pauserole: Address, + pub adminrole: Address, +} + + +/// Grant roles to addresses. Roles are used to restrict access to certain +/// functions in the contract. +/// It rejects if: +/// - It fails to parse the parameter. +/// - Sender does not have the `AdminRole` role. +#[receive( + contract = "euroe_stablecoin", + name = "grantRole", + parameter = "RoleTypes", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_grant_role( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + _logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Get the sender of the transaction + let sender = ctx.sender(); + + // Check if the sender has AdminRole role. + ensure!(host.state().has_role(&sender, Roles::AdminRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: RoleTypes = ctx.parameter_cursor().get()?; + + // Build the response + let (state, builder) = host.state_and_builder(); + + // Modify contract state with the updated role assignments. + state.grant_role(¶ms.mintrole, Roles::MintRole, builder); + state.grant_role(¶ms.pauserole, Roles::PauseUnpauseRole, builder); + state.grant_role(¶ms.burnrole, Roles::BurnRole, builder); + state.grant_role(¶ms.blockrole, Roles::BlockUnblockRole, builder); + state.grant_role(¶ms.adminrole, Roles::AdminRole, builder); + Ok(()) +} + + +/// Remove roles from addresses. Roles are used to restrict access to certain +/// functions in the contract. +/// It rejects if: +/// - It fails to parse the parameter. +/// - Sender does not have the `AdminRole` role. +#[receive( + contract = "euroe_stablecoin", + name = "removeRole", + parameter = "RoleTypes", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_remove_role( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + _logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Get the sender of the transaction + let sender = ctx.sender(); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::AdminRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: RoleTypes = ctx.parameter_cursor().get()?; + + // Build the response + let (state, _builder) = host.state_and_builder(); + + // Modify contract state with the updated role assignments. + state.remove_role(¶ms.mintrole, Roles::MintRole); + state.remove_role(¶ms.pauserole, Roles::PauseUnpauseRole); + state.remove_role(¶ms.burnrole, Roles::BurnRole); + state.remove_role(¶ms.blockrole, Roles::BlockUnblockRole); + state.remove_role(¶ms.adminrole, Roles::AdminRole); + Ok(()) +} + +/// Blocklist struct. +#[derive(Debug, Serialize, SchemaType)] +pub struct BlocklistParams { + pub address_to_block: Address, +} + +/// Blocklist function which blocks an address. +/// The contract can only be called by the `BlockRole` role +/// +/// It rejects if: +/// - Sender does not have the correct role. +/// - It fails to parse the parameter. +#[receive( + contract = "euroe_stablecoin", + name = "block", + parameter = "BlocklistParams", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_blocklist( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + _logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Get the sender of the transaction + let sender = ctx.sender(); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::BlockUnblockRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: BlocklistParams = ctx.parameter_cursor().get()?; + + // Blocklist the address. + host.state_mut().block_address(¶ms.address_to_block); + + Ok(()) +} + +/// Unblocklist struct. +#[derive(Debug, Serialize, SchemaType)] +struct UnBlocklistParams { + address_to_unblock: Address, +} + +/// Unblocklisting function which unblocks an address. +/// The contract can only be called by the `BlockRole` Role +/// +/// It rejects if: +/// - Sender is does not have the correct role. +/// - It fails to parse the parameter. +#[receive( + contract = "euroe_stablecoin", + name = "unblock", + parameter = "UnBlocklistParams", + error = "ContractError", + enable_logger, + mutable +)] +fn contract_unblocklist( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + _logger: &mut impl HasLogger, +) -> ContractResult<()> { + + // Get the sender of the transaction + let sender = ctx.sender(); + + // Check if the sender has the correct role. + ensure!(host.state().has_role(&sender, Roles::BlockUnblockRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: UnBlocklistParams = ctx.parameter_cursor().get()?; + + // Remove the address from the blocklist. + host.state_mut().unblock_address(¶ms.address_to_unblock); + + Ok(()) +} + + +/// The parameter type for the contract function `upgrade`. +/// Takes the new module and optionally an entrypoint to call in the new module +/// after triggering the upgrade. The upgrade is reverted if the entrypoint +/// fails. This is useful for doing migration in the same transaction triggering +/// the upgrade. +#[derive(Serialize, SchemaType)] +pub struct UpgradeParams { + /// The new module reference. + pub module: ModuleReference, + /// Optional entrypoint to call in the new module after upgrade. + pub migrate: Option<(OwnedEntrypointName, OwnedParameter)>, +} + +/// Upgrade this smart contract instance to a new module and call optionally a +/// migration function after the upgrade. +/// +/// It rejects if: +/// - Reading the state root fails. +/// - It fails to parse the parameter. +/// - Upgrade fails. +/// - Migration invoke fails. +/// - Sender does not have the `AdminRole` role. +/// +/// This function is marked as `low_level`. This is **necessary** since the +/// high-level mutable functions store the state of the contract at the end of +/// execution. This conflicts with migration since the shape of the state +/// **might** be changed by the migration function. If the state is then written +/// by this function it would overwrite the state stored by the migration +/// function. +#[receive( + contract = "euroe_stablecoin", + name = "upgrade", + parameter = "UpgradeParams", + error = "CustomContractError", + low_level +)] +fn contract_upgrade( + ctx: &impl HasReceiveContext, + host: &mut impl HasHost, +) -> ContractResult<()> { + // Read the top-level contract state. + let state: State = host.state().read_root()?; + + let sender = ctx.sender(); + + // Check if the sender has the correct role. + ensure!(state.has_role(&sender, Roles::AdminRole),ContractError::Unauthorized); + + // Parse the parameters. + let params: UpgradeParams = ctx.parameter_cursor().get()?; + + // Trigger the upgrade. + host.upgrade(params.module)?; + + // Call the migration function if provided. + if let Some((func, parameters)) = params.migrate { + host.invoke_contract_raw( + &ctx.self_address(), + parameters.as_parameter(), + func.as_entrypoint_name(), + Amount::zero(), + )?; + } + Ok(()) +} + +impl From for CustomContractError { + #[inline(always)] + fn from(ue: UpgradeError) -> Self { + match ue { + UpgradeError::MissingModule => Self::FailedUpgradeMissingModule, + UpgradeError::MissingContract => Self::FailedUpgradeMissingContract, + UpgradeError::UnsupportedModuleVersion => Self::FailedUpgradeUnsupportedModuleVersion, + } + } +} + +/// Mapping account signature error to CustomContractError +impl From for CustomContractError { + fn from(e: CheckAccountSignatureError) -> Self { + match e { + CheckAccountSignatureError::MissingAccount => Self::MissingAccount, + CheckAccountSignatureError::MalformedData => Self::MalformedData, + } + } +} + +/// Part of the parameter type for the contract function `permit`. +/// Specifies the message that is signed. +#[derive(SchemaType, Serialize)] +pub struct PermitMessage { + /// The contract_address that the signature is intended for. + pub contract_address: ContractAddress, + /// A nonce to prevent replay attacks. + pub nonce: u64, + /// A timestamp to make signatures expire. + pub timestamp: Timestamp, + /// The entry_point that the signature is intended for. + pub entry_point: OwnedEntrypointName, + /// The serialized payload that should be forwarded to either the `transfer` + /// or the `updateOperator` function. + #[concordium(size_length = 2)] + pub payload: Vec, +} +/// The parameter type for the contract function `permit`. +/// Takes a signature, the signer, and the message that was signed. +#[derive(Serialize, SchemaType)] +pub struct PermitParam { + /// Signature/s. The CIS3 standard supports multi-sig accounts. + pub signature: AccountSignatures, + /// Account that created the above signature. + pub signer: AccountAddress, + /// Message that was signed. + pub message: PermitMessage, +} +#[derive(Serialize)] +pub struct PermitParamPartial { + /// Signature/s. The CIS3 standard supports multi-sig accounts. + signature: AccountSignatures, + /// Account that created the above signature. + signer: AccountAddress, +} + +/// Tagged events to be serialized for the event log. +#[derive(Debug, Serial, Deserial, PartialEq, Eq)] +#[concordium(repr(u8))] +pub enum Event { + /// The event tracks the nonce used by the signer of the `PermitMessage` + /// whenever the `permit` function is invoked. + #[concordium(tag = 250)] + Nonce(NonceEvent), + #[concordium(forward = cis2_events)] + Cis2Event(Cis2Event), +} + +/// The NonceEvent is logged when the `permit` function is invoked. The event +/// tracks the nonce used by the signer of the `PermitMessage`. +#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)] +pub struct NonceEvent { + /// Account that signed the `PermitMessage`. + pub account: AccountAddress, + /// The nonce that was used in the `PermitMessage`. + pub nonce: u64, +} + + +/// Verify an ed25519 signature and allow the transfer of tokens or update of an +/// operator. +/// +/// In case of a `transfer` action: +/// Logs a `Transfer` event and invokes a receive hook function for the +/// transfer. +/// +/// In case of a `updateOperator` action: +/// Logs an `UpdateOperator` event. +/// +/// It rejects if: +/// - It fails to parse the parameter. +/// - The contract is paused. +/// - The sender is blocked. +/// - A different nonce is expected. +/// - The signature was intended for a different contract. +/// - The signature was intended for a different `entry_point`. +/// - The signature is expired. +/// - The signature can not be validated. +/// - Fails to log event. +/// - In case of a `transfer` action: it fails to be executed, which could be +/// if: +/// - The `token_id` does not exist. +/// - The token is not owned by the `from` address. +/// - The receive hook function call rejects. +#[receive( + contract = "euroe_stablecoin", + name = "permit", + parameter = "PermitParam", + crypto_primitives, + mutable, + enable_logger +)] +fn contract_permit( + ctx: &ReceiveContext, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, + crypto_primitives: &impl HasCryptoPrimitives, +) -> ContractResult<()> { + // Check if the contract is paused. + ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused)); + // Get the sender Address. + let sender = ctx.sender(); + // Check if the sender is blocked. + ensure!(!host.state().is_blocked(&sender),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Parse the parameter. + let param: PermitParam = ctx.parameter_cursor().get()?; + + let signer_of_permit = concordium_std::Address::Account(param.signer); + // Check if the signer is blocked. + ensure!(!host.state().is_blocked(&signer_of_permit),ContractError::Custom(CustomContractError::AddressBlocklisted)); + // Update the nonce. + let mut entry = host.state_mut().nonces_registry.entry(param.signer).or_insert_with(|| 0); + + // Get the current nonce. + let nonce = *entry; + // Bump nonce. + *entry += 1; + drop(entry); + + let message = param.message; + + // Check the nonce to prevent replay attacks. + ensure_eq!(message.nonce, nonce, CustomContractError::NonceMismatch.into()); + + // Check that the signature was intended for this contract. + ensure_eq!( + message.contract_address, + ctx.self_address(), + CustomContractError::WrongContract.into() + ); + + // Check signature is not expired. + ensure!(message.timestamp > ctx.metadata().slot_time(), CustomContractError::Expired.into()); + + let message_hash = contract_view_message_hash(ctx, host, crypto_primitives)?; + + // Check signature. + let valid_signature = + host.check_account_signature(param.signer, ¶m.signature, &message_hash)?; + ensure!(valid_signature, CustomContractError::WrongSignature.into()); + + if message.entry_point.as_entrypoint_name() == EntrypointName::new_unchecked("transfer") { + // Transfer the tokens. + + let TransferParams(transfers): TransferParameter = from_bytes(&message.payload)?; + + for transfer_struct in transfers { + ensure!( + transfer_struct.from.matches_account(¶m.signer), + ContractError::Unauthorized + ); + + transfer_helper(transfer_struct, host, logger)? + } + } else if message.entry_point.as_entrypoint_name() + == EntrypointName::new_unchecked("updateOperator") + { + // Update the operator. + let UpdateOperatorParams(updates): UpdateOperatorParams = from_bytes(&message.payload)?; + + let (state, builder) = host.state_and_builder(); + + for update in updates { + + // Check if the operator is blocklisted. + ensure!(!state.is_blocked(&update.operator),ContractError::Custom(CustomContractError::AddressBlocklisted)); + update_operator( + update.update, + concordium_std::Address::Account(param.signer), + update.operator, + state, + builder, + logger, + )?; + } + } else { + bail!(CustomContractError::WrongEntryPoint.into()) + } + + // Log the nonce event. + logger.log(&Event::Nonce(NonceEvent { + account: param.signer, + nonce, + }))?; + + Ok(()) +} + + +/// Calculates the message hash +/// The contract can only be called by any account +/// Returns message hash +/// +/// It rejects if: +/// - It fails to parse the parameter +#[receive( + contract = "euroe_stablecoin", + name = "viewMessageHash", + parameter = "PermitParam", + return_value = "[u8;32]", + crypto_primitives, + mutable +)] +fn contract_view_message_hash( + ctx: &ReceiveContext, + _host: &mut impl HasHost, StateApiType = S>, + crypto_primitives: &impl HasCryptoPrimitives, +) -> ContractResult<[u8; 32]> { + // Parse the parameter. + let mut cursor = ctx.parameter_cursor(); + // The input parameter is `PermitParam` but we only read the initial part of it + // with `PermitParamPartial`. I.e. we read the `signature` and the + // `signer`, but not the `message` here. + let param: PermitParamPartial = cursor.get()?; + + // The input parameter is `PermitParam` but we have only read the initial part + // of it with `PermitParamPartial` so far. We read in the `message` now. + // `(cursor.size() - cursor.cursor_position()` is the length of the message in + // bytes. + let mut message_bytes = vec![0; (cursor.size() - cursor.cursor_position()) as usize]; + + cursor.read_exact(&mut message_bytes)?; + + // The message signed in the Concordium browser wallet is prepended with the + // `account` address and 8 zero bytes. Accounts in the Concordium browser wallet + // can either sign a regular transaction (in that case the prepend is + // `account` address and the nonce of the account which is by design >= 1) + // or sign a message (in that case the prepend is `account` address and 8 zero + // bytes). Hence, the 8 zero bytes ensure that the user does not accidentally + // sign a transaction. The account nonce is of type u64 (8 bytes). + let mut msg_prepend = vec![0; 32 + 8]; + // Prepend the `account` address of the signer. + msg_prepend[0..32].copy_from_slice(param.signer.as_ref()); + // Prepend 8 zero bytes. + msg_prepend[32..40].copy_from_slice(&[0u8; 8]); + // Calculate the message hash. + let message_hash = + crypto_primitives.hash_sha2_256(&[&msg_prepend[0..40], &message_bytes].concat()).0; + + Ok(message_hash) +} + +/// The parameter type for the contract function `supportsPermit`. +#[derive(Debug, Serialize, SchemaType)] +pub struct SupportsPermitQueryParams { + /// The list of supportPermit queries. + #[concordium(size_length = 2)] + pub queries: Vec, +} +/// Get the entrypoints supported by the `permit` function given a +/// list of entrypoints. +/// +/// It rejects if: +/// - It fails to parse the parameter. +#[receive( + contract = "euroe_stablecoin", + name = "supportsPermit", + parameter = "SupportsPermitQueryParams", + return_value = "SupportsQueryResponse", + error = "ContractError" +)] +fn contract_supports_permit( + ctx: &ReceiveContext, + _host: &impl HasHost, StateApiType = S>, +) -> ContractResult { + // Parse the parameter. + let params: SupportsPermitQueryParams = ctx.parameter_cursor().get()?; + + // Build the response. + let mut response = Vec::with_capacity(params.queries.len()); + for entrypoint in params.queries { + if SUPPORTS_PERMIT_ENTRYPOINTS.contains(&entrypoint.as_entrypoint_name()) { + response.push(SupportResult::Support); + } else { + response.push(SupportResult::NoSupport); + } + } + let result = SupportsQueryResponse::from(response); + Ok(result) +} + + +/// ## HELPER FUNCTIONS ## +/// Below are a list of helper functions +/// They are usually used if a function has to call a specific code more than once +/// + +/// Internal `transfer/permit` helper function. Invokes the `transfer` +/// function of the state. Logs a `Transfer` event and invokes a receive hook +/// function. The function assumes that the transfer is authorized. +fn transfer_helper( + transfer: concordium_cis2::Transfer, + host: &mut impl HasHost, StateApiType = S>, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + let (state, builder) = host.state_and_builder(); + + let to_address = transfer.to.address(); + + // Check if the destination address is blocked. + ensure!(!state.is_blocked(&to_address),ContractError::Custom(CustomContractError::AddressBlocklisted)); + // Check if the source address is blocked. + ensure!(!state.is_blocked(&transfer.from),ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Update the contract state + state.transfer(&transfer.token_id, transfer.amount, &transfer.from, &to_address, builder)?; + + // Log transfer event + logger.log(&Cis2Event::Transfer(TransferEvent { + token_id: transfer.token_id, + amount: transfer.amount, + from: transfer.from, + to: to_address, + }))?; + + // If the receiver is a contract: invoke the receive hook function. + if let Receiver::Contract(address, function) = transfer.to { + let parameter = OnReceivingCis2Params { + token_id: transfer.token_id, + amount: transfer.amount, + from: transfer.from, + data: transfer.data, + }; + host.invoke_contract(&address, ¶meter, function.as_entrypoint_name(), Amount::zero())?; + } + + Ok(()) +} + + +// Internal `updateOperator/permit` helper function. Invokes the +/// `add_operator/remove_operator` function of the state. +/// Logs a `UpdateOperator` event. The function assumes that the sender is +/// authorized to do the `updateOperator` action. +fn update_operator( + update: OperatorUpdate, + sender: Address, + operator: Address, + state: &mut State, + builder: &mut StateBuilder, + logger: &mut impl HasLogger, +) -> ContractResult<()> { + // Update the operator in the state. + match update { + OperatorUpdate::Add => state.add_operator(&sender, &operator, builder), + OperatorUpdate::Remove => state.remove_operator(&sender, &operator), + } + + // Log the appropriate event + logger.log(&Cis2Event::::UpdateOperator( + UpdateOperatorEvent { + owner: sender, + operator, + update, + }, + ))?; + + Ok(()) +} diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..fd25e9d --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,1622 @@ +//! Integration tests for the euroe_stablecoin contract. + +use euroe_stablecoin::*; +use concordium_cis2::{TokenIdUnit, *}; +use concordium_smart_contract_testing::{AccountAccessStructure, AccountKeys, *}; +use concordium_std::{AccountSignatures, CredentialSignatures, HashSha2256, SignatureEd25519, + Timestamp, +}; +use std::collections::BTreeMap; +/// The tests accounts. + +const MINT_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const MINT_ADDRESS_ROLE: Address = Address::Account(MINT_ACCOUNT); + +const BURN_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const BURN_ADDRESS_ROLE: Address = Address::Account(BURN_ACCOUNT); + +const PAUSE_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const PAUSE_ADDRESS: Address = Address::Account(PAUSE_ACCOUNT); + +const ADMIN_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const ADMIN_ADDRESS: Address = Address::Account(ADMIN_ACCOUNT); + +const BLOCK_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const BLOCK_ADDRESS: Address = Address::Account(BLOCK_ACCOUNT); + +const RANDOM_BLOCKLIST_ACCOUNT: AccountAddress = AccountAddress([2u8; 32]); +const RANDOM_BLOCKLIST_ADDRESS: Address = Address::Account(RANDOM_BLOCKLIST_ACCOUNT); + +// Alice is considered the admin of the contract for the following tests. +// Alice assigns the role as she gets the admin role in the initialize function. +const ALICE: AccountAddress = AccountAddress([0; 32]); +const ALICE_ADDR: Address = Address::Account(ALICE); +const BOB: AccountAddress = AccountAddress([1; 32]); +const BOB_ADDR: Address = Address::Account(BOB); +const CHARLIE: AccountAddress = AccountAddress([2u8; 32]); + +const PUBLIC_KEY: [u8; 32] = [ + 120, 154, 141, 6, 248, 239, 77, 224, 80, 62, 139, 136, 211, 204, 105, 208, 26, 11, 2, 208, 195, + 253, 29, 192, 126, 199, 208, 39, 69, 4, 246, 32, +]; + +const SIGNATURE_UPDATE_OPERATOR: SignatureEd25519 = SignatureEd25519([ + 199, 250, 51, 48, 15, 210, 20, 180, 70, 191, 98, 217, 109, 67, 115, 94, 195, 81, 16, 157, 59, + 26, 36, 147, 91, 196, 254, 133, 149, 27, 148, 124, 130, 206, 68, 195, 139, 189, 244, 43, 253, + 12, 58, 17, 102, 63, 203, 35, 159, 54, 94, 59, 12, 193, 48, 78, 144, 112, 245, 149, 12, 181, + 74, 10, +]); + +const DUMMY_SIGNATURE: SignatureEd25519 = SignatureEd25519([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]); + +/// Token IDs. +const EUROE_TOKEN: ContractTokenId = TokenIdUnit(); + +/// Initial balance of the accounts. +const ACC_INITIAL_BALANCE: Amount = Amount::from_ccd(10000); + +/// A signer for all the transactions. +const SIGNER: Signer = Signer::with_one_key(); + +const EUROE_URL: &str = "https://dev.euroe.com/persistent/euroe-concordium-offchain-data.json"; + +// Testing that the token supply is the correct when minting tokens to an account. +// The 400 tokens that ALICE has from the initialize_contract_with_euroe_tokens function. +#[test] +fn test_view_circulating_supply(){ + let (chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.viewSupply".to_string()), + address: contract_address, + message: OwnedParameter::empty(), + }) + .expect("Invoke view"); + + let rv: ViewCirculatingSupply = invoke.parse_return_value().expect("ViewCirculatingSupply return value"); + + assert_eq!(rv, ViewCirculatingSupply { + circulating_supply: 400.into(), + }); +} +/// Test minting succeeds and the tokens are owned by the given address and +/// the appropriate events are logged. +#[test] +fn test_minting() { + let (chain, contract_address, update) = initialize_contract_with_euroe_tokens(); + // Invoke the view entrypoint and check that the tokens are owned by Alice. + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.view".to_string()), + address: contract_address, + message: OwnedParameter::empty(), + }) + .expect("Invoke view"); + + let rv: ViewState = invoke.parse_return_value().expect("ViewState return value"); + + assert_eq!(rv.state, vec![(ALICE_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, ContractTokenAmount::from(400))], + operators: Vec::new(), + })]); + + //Check that the events are logged. + let events = update.events().flat_map(|(_addr, events)| events); + + let events: Vec> = + events.map(|e| e.parse().expect("Deserialize event")).collect(); + assert_eq!(events, [ + Cis2Event::Mint(MintEvent { + token_id: TokenIdUnit(), + amount: TokenAmountU64(400), + owner: ALICE_ADDR, + }), + Cis2Event::TokenMetadata(TokenMetadataEvent { + token_id: TokenIdUnit(), + metadata_url: MetadataUrl { + url: EUROE_URL.to_string(), + hash: None, + }, + }), + ]); +} + +// Test that minting fails when the contract is paused +// and that the appropriate error is returned. +#[test] +fn test_minting_pause() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Pause the contract. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + chain + .contract_update(SIGNER, PAUSE_ACCOUNT, PAUSE_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect("Pause contract"); + + // Attempt to mint tokens. + let mint_params: MintParams = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + let update = chain + .contract_update(SIGNER, MINT_ACCOUNT, MINT_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::ContractPaused)); +} + +/// Test that minting fails when the minting EUROe to a blocked address +/// and that the appropriate error is returned. +#[test] +fn test_block_on_mint() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the contract. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Attempt to mint tokens. + let mint_params: MintParams = MintParams { + owner: RANDOM_BLOCKLIST_ADDRESS, + amount: 400.into(), + }; + + let update = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); +} + +// Test that the mint fails if the role is wrong +#[test] +fn test_minting_wrong_role() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Attempt to mint tokens. + let mint_params: MintParams = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Unauthorized); +} + +// Test mint fail if the sender is the blocked address +#[test] +fn test_minting_blocked_sender() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the contract. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Attempt to mint tokens. + let mint_params: MintParams = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + let update = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); +} + +/// Test that burning succeeds and the appropriate events are logged. +#[test] +fn test_burning() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Burn 100 tokens from alice, since she already has 400 + let burn_params: BurnParams = BurnParams { + burnaddress: ALICE_ADDR, + amount: 100.into(), + }; + + let update = chain + .contract_update(SIGNER, BURN_ACCOUNT, BURN_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect("Burn tokens"); + + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.view".to_string()), + address: contract_address, + message: OwnedParameter::empty(), + }) + .expect("Invoke view"); + + let rv: ViewState = invoke.parse_return_value().expect("ViewState return value"); + + assert_eq!(rv.state, vec![(ALICE_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, ContractTokenAmount::from(300))], + operators: Vec::new(), + })]); + + //Check that the events are logged. + let events = update.events().flat_map(|(_addr, events)| events); + + let events: Vec> = + events.map(|e| e.parse().expect("Deserialize event")).collect(); + assert_eq!(events, [ + Cis2Event::Burn(BurnEvent { + token_id: TokenIdUnit(), + amount: TokenAmountU64(100), + owner: ALICE_ADDR, + }) + ]); +} + +// Test burn function paused +#[test] +fn test_burn_pause(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Pause the contract. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + chain + .contract_update(SIGNER, MINT_ACCOUNT, MINT_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect("Pause contract"); + + // Burn 100 tokens from alice, since she already has 400 + let burn_params: BurnParams = BurnParams { + burnaddress: ALICE_ADDR, + amount: 100.into(), + }; + + let update = chain + .contract_update(SIGNER, BURN_ACCOUNT, BURN_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::ContractPaused)); +} + +// Test burn function blocked for the address +#[test] +fn test_burn_block(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the address. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Burn 100 tokens from alice, since she already has 400 + let burn_params: BurnParams = BurnParams { + burnaddress: RANDOM_BLOCKLIST_ADDRESS, + amount: 100.into(), + }; + + let update = chain + .contract_update(SIGNER, BURN_ACCOUNT, BURN_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + +} + +// Test burn fail with wrong role. +#[test] +fn test_burn_wrong_role(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Burn 100 tokens from alice, since she already has 400 + let burn_params: BurnParams = BurnParams { + burnaddress: ALICE_ADDR, + amount: 100.into(), + }; + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Unauthorized); +} +// Test burn function blocked for the sender +#[test] +fn test_burn_block_sender(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the address. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Burn 100 tokens from alice, since she already has 400 + let burn_params: BurnParams = BurnParams { + burnaddress: RANDOM_BLOCKLIST_ADDRESS, + amount: 100.into(), + }; + + let update = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + +} +/// Test regular transfer where sender is the owner. +#[test] +fn test_account_transfer() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Transfer one token from Alice to Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect("Transfer tokens"); + + // Check that Bob has 1 `EUROE_TOKEN` and Alice has 399. + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.view".to_string()), + address: contract_address, + message: OwnedParameter::empty(), + }) + .expect("Invoke view"); + + let rv: ViewState = invoke.parse_return_value().expect("ViewState return value"); + + assert_eq!(rv.state, vec![ + (ALICE_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, 399.into())], + operators: Vec::new(), + }), + (BOB_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, 1.into())], + operators: Vec::new(), + }), + ]); + + + //Check that the events are logged. + let events = update + .events() + .flat_map(|(_addr, events)| events.iter().map(|e| e.parse().expect("Deserialize event"))) + .collect::>>(); + + assert_eq!(events, [Cis2Event::Transfer(TransferEvent { + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + from: ALICE_ADDR, + to: BOB_ADDR, + }),]); +} + +// Test transfer fails if contract is paused +#[test] +fn test_transfer_pause(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Pause the contract. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + chain + .contract_update(SIGNER, MINT_ACCOUNT, MINT_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect("Pause contract"); + + // Transfer one token from Alice to Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + + assert_eq!(rv, ContractError::Custom(CustomContractError::ContractPaused)); +} + +// Test transfer fail if sender is blocked +#[test] +fn test_transfer_block_sender(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the address. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Transfer one token from Alice to Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: RANDOM_BLOCKLIST_ADDRESS, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let update = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); +} + +// Test transfer failed if the reciver is blocked +#[test] +fn test_transfer_block_receiver(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the address. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + // Transfer one token from Alice to Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(RANDOM_BLOCKLIST_ACCOUNT), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + +} + + +/// Test that you can add an operator. +/// Initialize the contract with tokenss owned by Alice. +/// Then add Bob as an operator for Alice. +#[test] +fn test_add_operator() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Add Bob as an operator for Alice. + let params = UpdateOperatorParams(vec![UpdateOperator { + update: OperatorUpdate::Add, + operator: BOB_ADDR, + }]); + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.updateOperator".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("UpdateOperator params"), + }) + .expect("Update operator"); + + // Check that an operator event occurred. + let events = update + .events() + .flat_map(|(_addr, events)| events.iter().map(|e| e.parse().expect("Deserialize event"))) + .collect::>>(); + assert_eq!(events, [Cis2Event::UpdateOperator(UpdateOperatorEvent { + operator: BOB_ADDR, + owner: ALICE_ADDR, + update: OperatorUpdate::Add, + }),]); + + // Construct a query parameter to check whether Bob is an operator for Alice. + let query_params = OperatorOfQueryParams { + queries: vec![OperatorOfQuery { + owner: ALICE_ADDR, + address: BOB_ADDR, + }], + }; + + // Invoke the operatorOf view entrypoint and check that Bob is an operator for + // Alice. + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.operatorOf".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&query_params).expect("OperatorOf params"), + }) + .expect("Invoke view"); + + let rv: OperatorOfQueryResponse = invoke.parse_return_value().expect("OperatorOf return value"); + assert_eq!(rv, OperatorOfQueryResponse(vec![true])); +} + +/// Test that a transfer fails when the sender is neither an operator or the +/// owner. In particular, Bob will attempt to transfer some of Alice's tokens to +/// himself. +#[test] +fn test_unauthorized_sender() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Construct a transfer of `EUROE_TOKEN` from Alice to Bob, which will be submitted + // by Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + // Notice that Bob is the sender/invoker. + let update = chain + .contract_update(SIGNER, BOB, BOB_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Unauthorized); +} + +/// Test that an operator can make a transfer. +#[test] +fn test_operator_can_transfer() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Add Bob as an operator for Alice. + let params = UpdateOperatorParams(vec![UpdateOperator { + update: OperatorUpdate::Add, + operator: BOB_ADDR, + }]); + chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.updateOperator".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("UpdateOperator params"), + }) + .expect("Update operator"); + + // Let Bob make a transfer to himself on behalf of Alice. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + chain + .contract_update(SIGNER, BOB, BOB_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect("Transfer tokens"); + + // Check that Bob now has 1 of `EUROE_TOKEN` and Alice has 399. + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.view".to_string()), + address: contract_address, + message: OwnedParameter::empty(), + }) + .expect("Invoke view"); + let rv: ViewState = invoke.parse_return_value().expect("ViewState return value"); + assert_eq!(rv.state, vec![ + (ALICE_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, 399.into())], + operators: vec![BOB_ADDR], + }), + (BOB_ADDR, ViewAddressState { + balances: vec![(EUROE_TOKEN, 1.into())], + operators: Vec::new(), + }), + ]); +} +// Test contract token metadata function +#[test] +fn test_contract_token_metadata(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Construct the token metadata query parameters + let token_id_queries: Vec = vec![EUROE_TOKEN]; + let metadata_query_params = TokenMetadataQueryParams { + queries: token_id_queries, + }; + + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.tokenMetadata".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&metadata_query_params).expect("Transfer params"), + }) + .expect("TokenMetadata"); + + let expected_metadata_url = MetadataUrl { + url: EUROE_URL.to_string(), + hash: None, + }; + let expected_response = TokenMetadataQueryResponse(vec![expected_metadata_url]); + + let rv: TokenMetadataQueryResponse = update.parse_return_value().expect("TokenMetadata return value"); + // Manually compare the two instances + + assert_eq!(expected_response.0, rv.0); + +} + +// Test when paused, all the pause functionality return contractPaused. +// The pause functionalities are +// contract_mint, contract_burn, contract_transfer, contract_updateOperator +// contract_set_implementor +#[test] +fn test_pause_functionality() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Pause the contract. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + chain + .contract_update(SIGNER, PAUSE_ACCOUNT, PAUSE_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect("Pause contract"); + + // Mint tokens for which Alice is the owner. + let mint_params = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + let mint_update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let mint_rv: ContractError = mint_update.parse_return_value().expect("ContractError return value"); + + assert_eq!(mint_rv, ContractError::Custom(CustomContractError::ContractPaused)); + + // Burn tokens from alice + let burn_params: BurnParams = BurnParams { + burnaddress: ALICE_ADDR, + amount: 100.into(), + }; + + let burn_update = chain + .contract_update(SIGNER, BURN_ACCOUNT, BURN_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let burn_rv: ContractError = burn_update.parse_return_value().expect("ContractError return value"); + + assert_eq!(burn_rv, ContractError::Custom(CustomContractError::ContractPaused)); + + // Transfer tokens from alice to bob + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let transfer_update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let transfer_rv: ContractError = transfer_update.parse_return_value().expect("ContractError return value"); + assert_eq!(transfer_rv, ContractError::Custom(CustomContractError::ContractPaused)); + + // Add Bob as an operator for Alice. + let params = UpdateOperatorParams(vec![UpdateOperator { + update: OperatorUpdate::Add, + operator: BOB_ADDR, + }]); + + let operator_update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.updateOperator".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("UpdateOperator params"), + }) + .expect_err("Update operator"); + + // Check that the correct error is returned. + let operator_rv: ContractError = operator_update.parse_return_value().expect("ContractError return value"); + assert_eq!(operator_rv, ContractError::Custom(CustomContractError::ContractPaused)); + +} + +// Test to check if the pause functionality is only able to be called by the pauseunpause role +#[test] +fn test_pause_functionality_wrong_role() { + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Pause the contract. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect_err("Pause contract"); + + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Unauthorized); + +} + +// Test that removes roles can only be added by the admin role +#[test] +fn test_remove_roles(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + let roles = RoleTypes { + mintrole: MINT_ADDRESS_ROLE, + pauserole: PAUSE_ADDRESS, + burnrole: BURN_ADDRESS_ROLE, + blockrole: BLOCK_ADDRESS, + adminrole: ADMIN_ADDRESS, + }; + + chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.removeRole".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&roles).expect("Remove roles"), + }) + .expect("Remove roles"); + + // Pause the contract. The following steps should fail, due to role has been removed. + let params = SetPausedParams { + paused: true, + }; + + // The role that is allowed to call the pause function is pauseunpause role + let update = chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.setPaused".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Pause params"), + }) + .expect_err("Pause contract"); + + let rv: ContractError = update.parse_return_value().expect("ContractError return value"); + assert_eq!(rv, ContractError::Unauthorized); +} + +// This test is to check when a user is blocked, the user is not able to call any of the functionality which has +// blocklist as a requirement. +// They are contract_mint, contract_burn, contract_transfer, contract_updateOperator +#[test] +fn test_blocklist_functionality(){ + let (mut chain, contract_address, _update) = initialize_contract_with_euroe_tokens(); + + // Block the address. + let params = BlocklistParams { + address_to_block: RANDOM_BLOCKLIST_ADDRESS, + }; + + // The role that is allowed to call the block function is blockunblock role + chain + .contract_update(SIGNER, BLOCK_ACCOUNT, BLOCK_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.block".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(¶ms).expect("Block params"), + }) + .expect("Block contract"); + + + let mint_params: MintParams = MintParams { + owner: RANDOM_BLOCKLIST_ADDRESS, + amount: 400.into(), + }; + + let update_mint = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect_err("Mint tokens"); + + // Check that the correct error is returned. + let mint_rv: ContractError = update_mint.parse_return_value().expect("ContractError return value"); + assert_eq!(mint_rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + + let burn_params: BurnParams = BurnParams { + burnaddress: RANDOM_BLOCKLIST_ADDRESS, + amount: 100.into(), + }; + + let update_burn = chain + .contract_update(SIGNER, BURN_ACCOUNT, BURN_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.burn".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&burn_params).expect("Burn params"), + }) + .expect_err("Burn tokens"); + + // Check that the correct error is returned. + let burn_rv: ContractError = update_burn.parse_return_value().expect("ContractError return value"); + assert_eq!(burn_rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + + // Transfer one token from Alice to Bob. + let transfer_params = TransferParams::from(vec![concordium_cis2::Transfer { + from: RANDOM_BLOCKLIST_ADDRESS, + to: Receiver::Account(BOB), + token_id: EUROE_TOKEN, + amount: TokenAmountU64(1), + data: AdditionalData::empty(), + }]); + + let tranfer_update = chain + .contract_update(SIGNER, RANDOM_BLOCKLIST_ACCOUNT, RANDOM_BLOCKLIST_ADDRESS, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.transfer".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&transfer_params).expect("Transfer params"), + }) + .expect_err("Transfer tokens"); + + // Check that the correct error is returned. + let transfer_rv: ContractError = tranfer_update.parse_return_value().expect("ContractError return value"); + + assert_eq!(transfer_rv, ContractError::Custom(CustomContractError::AddressBlocklisted)); + +} + +/// Test permit update operator function. The signature is generated in the test +/// case. ALICE adds BOB as an operator. +#[test] +fn test_inside_signature_permit_update_operator() { + let (mut chain, contract_address, _update, keypairs) = + initialize_contract_with_alice_tokens_for_permit(true); + + // Check operator in state + let bob_is_operator_of_alice = operator_of(&chain, contract_address); + + assert_eq!(bob_is_operator_of_alice, OperatorOfQueryResponse(vec![false])); + + // Create input parameters for the `permit` updateOperator function. + let update_operator = UpdateOperator { + update: OperatorUpdate::Add, + operator: BOB_ADDR, + }; + let payload = UpdateOperatorParams(vec![update_operator]); + + // The `viewMessageHash` function uses the same input parameter `PermitParam` as + // the `permit` function. The `PermitParam` type includes a `signature` and + // a `signer`. Because these two values (`signature` and `signer`) are not + // read in the `viewMessageHash` function, any value can be used and we choose + // to use `DUMMY_SIGNATURE` and `ALICE` in the test case below. + let signature_map = BTreeMap::from([(0u8, CredentialSignatures { + sigs: BTreeMap::from([(0u8, concordium_std::Signature::Ed25519(DUMMY_SIGNATURE))]), + })]); + + let mut permit_update_operator_param = PermitParam { + signature: AccountSignatures { + sigs: signature_map, + }, + signer: ALICE, + message: PermitMessage { + timestamp: Timestamp::from_timestamp_millis(10_000_000_000), + contract_address: ContractAddress::new(0, 0), + entry_point: OwnedEntrypointName::new_unchecked("updateOperator".into()), + nonce: 0, + payload: to_bytes(&payload), + }, + }; + + // Get the message hash to be signed. + let invoke = chain + .contract_invoke(BOB, BOB_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + address: contract_address, + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.viewMessageHash".to_string()), + message: OwnedParameter::from_serial(&permit_update_operator_param) + .expect("Should be a valid inut parameter"), + }) + .expect("Should be able to query viewMessageHash"); + + let message_hash: HashSha2256 = + from_bytes(&invoke.return_value).expect("Should return a valid result"); + + permit_update_operator_param.signature = keypairs + .expect("Should have a generated private key to sign") + .sign_message(&to_bytes(&message_hash)); + + // Update operator with the permit function. + let update = chain + .contract_update( + Signer::with_one_key(), + CHARLIE, + Address::Account(CHARLIE), + Energy::from(10000), + UpdateContractPayload { + amount: Amount::zero(), + address: contract_address, + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.permit".to_string()), + message: OwnedParameter::from_serial(&permit_update_operator_param) + .expect("Should be a valid inut parameter"), + }, + ) + .expect("Should be able to update operator with permit"); + + // Check that the correct events occurred. + let events = update + .events() + .flat_map(|(_addr, events)| events.iter().map(|e| e.parse().expect("Deserialize event"))) + .collect::>(); + + assert_eq!(events, [ + Event::Cis2Event(Cis2Event::UpdateOperator(UpdateOperatorEvent { + update: OperatorUpdate::Add, + owner: ALICE_ADDR, + operator: BOB_ADDR, + })), + Event::Nonce(NonceEvent { + account: ALICE, + nonce: 0, + }) + ]); + + // Check operator in state + let bob_is_operator_of_alice = operator_of(&chain, contract_address); + + assert_eq!(bob_is_operator_of_alice, OperatorOfQueryResponse(vec![true])); +} + +/// Test permit update operator function. The signature is generated outside +/// this test case (e.g. with https://cyphr.me/ed25519_tool/ed.html). ALICE adds BOB as an operator. +#[test] +fn test_outside_signature_permit_update_operator() { + let (mut chain, contract_address, _update, _keypairs) = + initialize_contract_with_alice_tokens_for_permit(false); + + // Check operator in state + let bob_is_operator_of_alice = operator_of(&chain, contract_address); + + assert_eq!(bob_is_operator_of_alice, OperatorOfQueryResponse(vec![false])); + + // Create input parameters for the `permit` updateOperator function. + let update_operator = UpdateOperator { + update: OperatorUpdate::Add, + operator: BOB_ADDR, + }; + let payload = UpdateOperatorParams(vec![update_operator]); + + let mut inner_signature_map = BTreeMap::new(); + inner_signature_map.insert(0u8, concordium_std::Signature::Ed25519(SIGNATURE_UPDATE_OPERATOR)); + + let mut signature_map = BTreeMap::new(); + signature_map.insert(0u8, CredentialSignatures { + sigs: inner_signature_map, + }); + + let permit_update_operator_param = PermitParam { + signature: AccountSignatures { + sigs: signature_map, + }, + signer: ALICE, + message: PermitMessage { + timestamp: Timestamp::from_timestamp_millis(10_000_000_000), + contract_address: ContractAddress::new(0, 0), + entry_point: OwnedEntrypointName::new_unchecked("updateOperator".into()), + nonce: 0, + payload: to_bytes(&payload), + }, + }; + + // Update operator with the permit function. + let update = chain + .contract_update( + Signer::with_one_key(), + CHARLIE, + Address::Account(CHARLIE), + Energy::from(10000), + UpdateContractPayload { + amount: Amount::zero(), + address: contract_address, + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.permit".to_string()), + message: OwnedParameter::from_serial(&permit_update_operator_param) + .expect("Should be a valid inut parameter"), + }, + ) + .expect("Should be able to update operator with permit"); + + // Check that the correct events occurred. + let events = update + .events() + .flat_map(|(_addr, events)| events.iter().map(|e| e.parse().expect("Deserialize event"))) + .collect::>(); + + assert_eq!(events, [ + Event::Cis2Event(Cis2Event::UpdateOperator(UpdateOperatorEvent { + update: OperatorUpdate::Add, + owner: ALICE_ADDR, + operator: BOB_ADDR, + })), + Event::Nonce(NonceEvent { + account: ALICE, + nonce: 0, + }) + ]); + + // Check operator in state + let bob_is_operator_of_alice = operator_of(&chain, contract_address); + + assert_eq!(bob_is_operator_of_alice, OperatorOfQueryResponse(vec![true])); +} + +/// Test permit transfer function. The signature is generated in the test case. +/// EUROE_TOKEN is transferred from Alice to Bob. +#[test] +fn test_inside_signature_permit_transfer() { + let (mut chain, contract_address, _update, keypairs) = + initialize_contract_with_alice_tokens_for_permit(true); + + // Check balances in state. + let balance_of_alice_and_bob = get_balances(&chain, contract_address); + + assert_eq!(balance_of_alice_and_bob.0, [TokenAmountU64(400), TokenAmountU64(0)]); + + // Create input parameters for the `permit` transfer function. + let transfer = concordium_cis2::Transfer { + from: ALICE_ADDR, + to: Receiver::from_account(BOB), + token_id: EUROE_TOKEN, + amount: ContractTokenAmount::from(1), + data: AdditionalData::empty(), + }; + let payload = TransferParams::from(vec![transfer]); + + // The `viewMessageHash` function uses the same input parameter `PermitParam` as + // the `permit` function. The `PermitParam` type includes a `signature` and + // a `signer`. Because these two values (`signature` and `signer`) are not + // read in the `viewMessageHash` function, any value can be used and we choose + // to use `DUMMY_SIGNATURE` and `ALICE` in the test case below. + let signature_map = BTreeMap::from([(0u8, CredentialSignatures { + sigs: BTreeMap::from([(0u8, concordium_std::Signature::Ed25519(DUMMY_SIGNATURE))]), + })]); + + let mut permit_transfer_param = PermitParam { + signature: AccountSignatures { + sigs: signature_map, + }, + signer: ALICE, + message: PermitMessage { + timestamp: Timestamp::from_timestamp_millis(10_000_000_000), + contract_address: ContractAddress::new(0, 0), + entry_point: OwnedEntrypointName::new_unchecked("transfer".into()), + nonce: 0, + payload: to_bytes(&payload), + }, + }; + + // Get the message hash to be signed. + let invoke = chain + .contract_invoke(BOB, BOB_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + address: contract_address, + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.viewMessageHash".to_string()), + message: OwnedParameter::from_serial(&permit_transfer_param) + .expect("Should be a valid inut parameter"), + }) + .expect("Should be able to query viewMessageHash"); + + let message_hash: HashSha2256 = + from_bytes(&invoke.return_value).expect("Should return a valid result"); + + permit_transfer_param.signature = keypairs + .expect("Should have a generated private key to sign") + .sign_message(&to_bytes(&message_hash)); + + // Transfer token with the permit function. + let update = chain + .contract_update( + Signer::with_one_key(), + BOB, + BOB_ADDR, + Energy::from(10000), + UpdateContractPayload { + amount: Amount::zero(), + address: contract_address, + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.permit".to_string()), + message: OwnedParameter::from_serial(&permit_transfer_param) + .expect("Should be a valid inut parameter"), + }, + ) + .expect("Should be able to transfer token with permit"); + + // Check that the correct events occurred. + let events = update + .events() + .flat_map(|(_addr, events)| events.iter().map(|e| e.parse().expect("Deserialize event"))) + .collect::>(); + + assert_eq!(events, [ + Event::Cis2Event(Cis2Event::Transfer(TransferEvent { + token_id: EUROE_TOKEN, + amount: ContractTokenAmount::from(1), + from: ALICE_ADDR, + to: BOB_ADDR, + })), + Event::Nonce(NonceEvent { + account: ALICE, + nonce: 0, + }) + ]); + + // Check balances in state. + let balance_of_alice_and_bob = get_balances(&chain, contract_address); + + assert_eq!(balance_of_alice_and_bob.0, [TokenAmountU64(399), TokenAmountU64(1)]); +} + +/// Helper function that sets up the contract with tokens minted to Alice. +/// Alice has 400 of `EUROE_TOKEN`. +fn initialize_contract_with_euroe_tokens() -> (Chain, ContractAddress, ContractInvokeSuccess) { + let (mut chain, contract_address) = initialize_chain_and_contract(); + + let mint_params = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + // Mint tokens for which Alice is the owner. + let update = chain + .contract_update(SIGNER, ALICE, MINT_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect("Mint tokens"); + + (chain, contract_address, update) +} + +/// Setup chain and contract. +fn initialize_chain_and_contract() -> (Chain, ContractAddress) { + let mut chain = Chain::new(); + + // Create some accounts accounts on the chain. + chain.create_account(Account::new(ALICE, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(BOB, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(MINT_ACCOUNT, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(BURN_ACCOUNT, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(PAUSE_ACCOUNT, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(ADMIN_ACCOUNT, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(BLOCK_ACCOUNT, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(RANDOM_BLOCKLIST_ACCOUNT, ACC_INITIAL_BALANCE)); + + + // Load and deploy the module. + let module = module_load_v1("dist/module.wasm.v1").expect("Module exists"); + let deployment = chain.module_deploy_v1(SIGNER, ALICE, module).expect("Deploy valid module"); + + let init = chain + .contract_init(SIGNER, ALICE, Energy::from(10000), InitContractPayload { + amount: Amount::zero(), + mod_ref: deployment.module_reference, + init_name: OwnedContractName::new_unchecked("init_euroe_stablecoin".to_string()), + param: OwnedParameter::empty(), + }) + .expect("Initialize contract"); + + // Lets add permissions to the contract + + let roles = RoleTypes { + mintrole: MINT_ADDRESS_ROLE, + pauserole: PAUSE_ADDRESS, + burnrole: BURN_ADDRESS_ROLE, + blockrole: BLOCK_ADDRESS, + adminrole: ADMIN_ADDRESS, + }; + + chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.grantRole".to_string()), + address: init.contract_address, + message: OwnedParameter::from_serial(&roles).expect("Grant roles"), + }) + .expect("Grant roles"); + + (chain, init.contract_address) +} + +/// Get the `EUROE` balances for Alice and Bob. +fn get_balances( + chain: &Chain, + contract_address: ContractAddress, +) -> ContractBalanceOfQueryResponse { + let balance_of_params = ContractBalanceOfQueryParams { + queries: vec![ + BalanceOfQuery { + token_id: EUROE_TOKEN, + address: ALICE_ADDR, + }, + BalanceOfQuery { + token_id: EUROE_TOKEN, + address: BOB_ADDR, + }, + ], + }; + + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.balanceOf".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&balance_of_params) + .expect("BalanceOf params"), + }) + .expect("Invoke balanceOf"); + let rv: ContractBalanceOfQueryResponse = + invoke.parse_return_value().expect("BalanceOf return value"); + rv +} + +/// Check if Bob is an operator of Alice. +fn operator_of(chain: &Chain, contract_address: ContractAddress) -> OperatorOfQueryResponse { + let operator_of_params = OperatorOfQueryParams { + queries: vec![OperatorOfQuery { + address: BOB_ADDR, + owner: ALICE_ADDR, + }], + }; + + // Check operator in state + let invoke = chain + .contract_invoke(ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.operatorOf".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&operator_of_params) + .expect("OperatorOf params"), + }) + .expect("Invoke operatorOf"); + let rv: OperatorOfQueryResponse = invoke.parse_return_value().expect("OperatorOf return value"); + rv +} + +/// Helper function that sets up the contract for permits. +fn initialize_contract_with_alice_tokens_for_permit( + generate_keys: bool, +) -> (Chain, ContractAddress, ContractInvokeSuccess, Option) { + let (mut chain, contract_address, keypairs) = initialize_chain_and_contract_for_permit(generate_keys); + + let mint_params = MintParams { + owner: ALICE_ADDR, + amount: 400.into(), + }; + + // Mint tokens for which Alice + let update = chain + .contract_update(SIGNER, ALICE, MINT_ADDRESS_ROLE, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.mint".to_string()), + address: contract_address, + message: OwnedParameter::from_serial(&mint_params).expect("Mint params"), + }) + .expect("Mint tokens"); + + (chain, contract_address, update, keypairs) +} + +/// Setup chain and contract. +/// +/// Also creates the three accounts, Alice, Bob, and Charlie. +/// +/// Alice is the admin in the beginning and has the admin role. +/// Alice's account is created with keys. +/// Hence, Alice's account signature can be checked in the test cases. +fn initialize_chain_and_contract_for_permit( + generate_keys: bool, +) -> (Chain, ContractAddress, Option) { + let mut chain = Chain::new(); + + let (account_access_structure, keypairs) = match generate_keys { + // If `generate_keys` is true, fresh keys are generated for Alice. + // Since Alice's private key is available, Alice can sign and generate a valid signature in + // the test cases. + true => { + let rng = &mut rand::thread_rng(); + + let keypairs = AccountKeys::singleton(rng); + ((&keypairs).into(), Some(keypairs)) + } + // If `generate_keys` is false, Alice's account is assigned a hardcoded public key. + // Since Alice's private key is NOT available, hardcoded signatures are used in the test + // cases. The signatures are generated outside the test cases (e.g. with https://cyphr.me/ed25519_tool/ed.html). + // NOTE: I am using the true version above here in false since + // this PR https://github.com/Concordium/concordium-rust-smart-contracts/pull/359/files + // has not been merged and i am unable to use the code below + false => ( + AccountAccessStructure::singleton( + ed25519::PublicKey::from_bytes(&PUBLIC_KEY) + .expect("Should be able to construct public key from bytes."), + ), + None, + ), + }; + + let balance = AccountBalance { + total: ACC_INITIAL_BALANCE, + staked: Amount::zero(), + locked: Amount::zero(), + }; + + // Create some accounts accounts on the chain. + chain.create_account(Account::new_with_keys(ALICE, balance, account_access_structure)); + chain.create_account(Account::new(CHARLIE, ACC_INITIAL_BALANCE)); + chain.create_account(Account::new(BOB, ACC_INITIAL_BALANCE)); + + // Load and deploy the module. + let module = module_load_v1("dist/module.wasm.v1").expect("Module exists"); + let deployment = chain.module_deploy_v1(SIGNER, ALICE, module).expect("Deploy valid module"); + + // Initialize the auction contract. + let init = chain + .contract_init(SIGNER, ALICE, Energy::from(10000), InitContractPayload { + amount: Amount::zero(), + mod_ref: deployment.module_reference, + init_name: OwnedContractName::new_unchecked("init_euroe_stablecoin".to_string()), + param: OwnedParameter::empty(), + }) + .expect("Initialize contract"); + + + let roles = RoleTypes { + mintrole: MINT_ADDRESS_ROLE, + pauserole: PAUSE_ADDRESS, + burnrole: BURN_ADDRESS_ROLE, + blockrole: BLOCK_ADDRESS, + adminrole: ADMIN_ADDRESS, + }; + + chain + .contract_update(SIGNER, ALICE, ALICE_ADDR, Energy::from(10000), UpdateContractPayload { + amount: Amount::zero(), + receive_name: OwnedReceiveName::new_unchecked("euroe_stablecoin.grantRole".to_string()), + address: init.contract_address, + message: OwnedParameter::from_serial(&roles).expect("Grant roles"), + }) + .expect("Grant roles"); + + (chain, init.contract_address, keypairs) +} \ No newline at end of file