From e1a304790be167223edf02a7879e0280458538bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Thu, 1 Sep 2022 16:21:43 +0200 Subject: [PATCH 1/8] Prototype dummy marketplace --- .gitignore | 4 +- contracts/marketplace/Cargo.lock | 1014 ++++++++++++++++++++++++++++++ contracts/marketplace/Cargo.toml | 51 ++ contracts/marketplace/lib.rs | 161 +++++ contracts/scripts/deploy.sh | 48 ++ 5 files changed, 1276 insertions(+), 2 deletions(-) create mode 100644 contracts/marketplace/Cargo.lock create mode 100644 contracts/marketplace/Cargo.toml create mode 100644 contracts/marketplace/lib.rs diff --git a/.gitignore b/.gitignore index e720b4cca6..8203cc4ade 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # IDEs -/.idea +**/.idea /.vscode .projectile @@ -14,7 +14,7 @@ *.out # docker -docker/data/ +**/docker/data/ db/ keystore/ chainspec.json diff --git a/contracts/marketplace/Cargo.lock b/contracts/marketplace/Cargo.lock new file mode 100644 index 0000000000..be94a88db7 --- /dev/null +++ b/contracts/marketplace/Cargo.lock @@ -0,0 +1,1014 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "access_control" +version = "0.1.0" +dependencies = [ + "ink_env", + "ink_lang", + "ink_lang_codegen", + "ink_primitives", + "ink_storage", + "parity-scale-codec", +] + +[[package]] +name = "array-init" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[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 = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081e3f0755c1f380c2d010481b6fa2e02973586d5f2b24eebb7a2a1d98b143d8" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "semver-parser", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const_format" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "game_token" +version = "2.1.0" +dependencies = [ + "access_control", + "ink_env", + "ink_lang", + "ink_prelude", + "ink_primitives", + "ink_storage", + "openbrush", + "parity-scale-codec", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_allocator" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed249de74298ed051ebcf6d3082b8d3dbd19cbc448d9ed3235d8a7b92713049" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_engine" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb9d32ec27d71fefb3f2b6a26bae82a2c6509d7ad61e8a5107b6291a1b03ecb" +dependencies = [ + "blake2 0.10.4", + "derive_more", + "parity-scale-codec", + "rand", + "secp256k1", + "sha2", + "sha3", +] + +[[package]] +name = "ink_env" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1549f5966167387c89fb3dfcdc59973bfb396cc3a7110d7a31ad5fdea56db0cf" +dependencies = [ + "arrayref", + "blake2 0.10.4", + "cfg-if", + "derive_more", + "ink_allocator", + "ink_engine", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "num-traits", + "parity-scale-codec", + "paste", + "rand", + "rlibc", + "scale-info", + "secp256k1", + "sha2", + "sha3", + "static_assertions", +] + +[[package]] +name = "ink_lang" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5282f2722ac6dca469e7f223a7b38b2a6d20fbca6b974497e630d5dc8934e9" +dependencies = [ + "derive_more", + "ink_env", + "ink_lang_macro", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "parity-scale-codec", +] + +[[package]] +name = "ink_lang_codegen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb3a5de33b59450adc3f61c5eb05b768067c7ab8af9d00f33e284310598168dc" +dependencies = [ + "blake2 0.10.4", + "derive_more", + "either", + "heck 0.4.0", + "impl-serde", + "ink_lang_ir", + "itertools", + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_lang_ir" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d4d614462280fa06e15b9ca5725d7c8440dde93c8dae1c6f15422f7756cacb" +dependencies = [ + "blake2 0.10.4", + "either", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_lang_macro" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f85f64141957c5db7cbabbb97a9c16c489e5e9d363e9f147d132a43c71cd29" +dependencies = [ + "ink_lang_codegen", + "ink_lang_ir", + "ink_primitives", + "parity-scale-codec", + "proc-macro2", + "syn", +] + +[[package]] +name = "ink_metadata" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca6c159a2774f07437c6fd9ea710eb73a6b5e9a031a932bddf08742bf2c081a" +dependencies = [ + "derive_more", + "impl-serde", + "ink_prelude", + "ink_primitives", + "scale-info", + "serde", +] + +[[package]] +name = "ink_prelude" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f7f4dec15e573496c9d2af353e78bde84add391251608f25b5adcf175dc777" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_primitives" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3296dd1c4f4fe12ede7c92d60e6fcb94d46a959ec19c701e4ac588b09e0b4a6" +dependencies = [ + "cfg-if", + "ink_prelude", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ff9b503995a7b41fe201a7a2643ce22f5a11e0b67db7b685424b6d5fe0ecf0b" +dependencies = [ + "array-init", + "cfg-if", + "derive_more", + "ink_env", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage_derive", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage_derive" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb68e24e93e8327dda1924868d7ee4dbe01e1ed2b392f28583caa96809b585c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "marketplace" +version = "0.1.0" +dependencies = [ + "access_control", + "game_token", + "ink_engine", + "ink_env", + "ink_lang", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "openbrush", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openbrush" +version = "2.1.0" +source = "git+https://github.com/Supercolony-net/openbrush-contracts.git?rev=8a20f95#8a20f951feaf8c6ea72c4bf113bf115bad9f158f" +dependencies = [ + "ink_env", + "ink_lang", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "openbrush_contracts", + "openbrush_lang", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "openbrush_contracts" +version = "2.1.0" +source = "git+https://github.com/Supercolony-net/openbrush-contracts.git?rev=8a20f95#8a20f951feaf8c6ea72c4bf113bf115bad9f158f" +dependencies = [ + "ink_env", + "ink_lang", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "openbrush_lang", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "openbrush_lang" +version = "2.1.0" +source = "git+https://github.com/Supercolony-net/openbrush-contracts.git?rev=8a20f95#8a20f951feaf8c6ea72c4bf113bf115bad9f158f" +dependencies = [ + "const_format", + "ink_env", + "ink_lang", + "ink_metadata", + "ink_primitives", + "ink_storage", + "openbrush_lang_macro", + "parity-scale-codec", + "scale-info", + "sha2-const", +] + +[[package]] +name = "openbrush_lang_codegen" +version = "2.1.0" +source = "git+https://github.com/Supercolony-net/openbrush-contracts.git?rev=8a20f95#8a20f951feaf8c6ea72c4bf113bf115bad9f158f" +dependencies = [ + "blake2 0.9.2", + "cargo_metadata", + "fs2", + "heck 0.3.3", + "ink_lang_ir", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "synstructure", + "unwrap", +] + +[[package]] +name = "openbrush_lang_macro" +version = "2.1.0" +source = "git+https://github.com/Supercolony-net/openbrush-contracts.git?rev=8a20f95#8a20f951feaf8c6ea72c4bf113bf115bad9f158f" +dependencies = [ + "openbrush_lang_codegen", + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +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.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rlibc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scale-info" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "secp256k1" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +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 = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha2-const" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5edcd790916d95ff81bdc1505b09c74d30d47a755929cc8c71c59cbbfa99f91b" + +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "unwrap" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "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 = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] diff --git a/contracts/marketplace/Cargo.toml b/contracts/marketplace/Cargo.toml new file mode 100644 index 0000000000..3991905c1c --- /dev/null +++ b/contracts/marketplace/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "marketplace" +version = "0.1.0" +authors = ["Cardinal Cryptography"] +edition = "2021" +license = "Apache 2.0" + +[dependencies] +ink_primitives = { version = "~3.3.0", default-features = false } +ink_metadata = { version = "~3.3.0", default-features = false, features = ["derive"], optional = true } +ink_env = { version = "~3.3.0", default-features = false } +ink_storage = { version = "~3.3.0", default-features = false } +ink_lang = { version = "~3.3.0", default-features = false } +ink_prelude = { version = "~3.3.0", default-features = false } +ink_engine = { version = "~3.3.0", default-features = false, optional = true } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } + +openbrush = { git = "https://github.com/Supercolony-net/openbrush-contracts.git", rev = "8a20f95", default-features = false, features = ["psp22"] } +access_control = { path = "../access_control", default-features = false, features = ["ink-as-dependency"] } +game_token = { path = "../game_token", default-features = false, features = ["ink-as-dependency"] } + +[lib] +name = "marketplace" +path = "lib.rs" +crate-type = [ + # Used for normal contract Wasm blobs. + "cdylib", + # Used for ABI generation. + "rlib", +] + +[features] +default = ["std"] +std = [ + "ink_env/std", + "ink_lang/std", + "ink_metadata", + "ink_metadata/std", + "ink_primitives/std", + "ink_storage/std", + "openbrush/std", + "scale-info", + "scale-info/std", + "scale/std", +] +ink-as-dependency = [] + +[profile.dev] +codegen-units = 16 diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs new file mode 100644 index 0000000000..367af48dd1 --- /dev/null +++ b/contracts/marketplace/lib.rs @@ -0,0 +1,161 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(min_specialization)] + +use ink_lang as ink; + +#[ink::contract] +pub mod marketplace { + use access_control::{ + traits::AccessControlled, Role, Role::Initializer, ACCESS_CONTROL_PUBKEY, + }; + + #[ink(storage)] + pub struct Marketplace { + price: Balance, + at_block: BlockNumber, + price_multiplier_numerator: Balance, + price_multiplier_denominator: Balance, + units: u32, + sell_price_multiplier: Balance, + } + + #[derive(Debug)] + pub enum Error { + AccessControlCall(ink_env::Error), + MissingRole(Role), + } + + impl AccessControlled for Marketplace { + type ContractError = Error; + } + + impl Marketplace { + #[ink(constructor)] + pub fn new( + starting_price: Balance, + price_multiplier_numerator: Balance, + price_multiplier_denominator: Balance, + units: u32, + sell_price_multiplier: Balance, + ) -> Self { + match Self::ensure_role(Self::initializer()) { + Err(error) => panic!("Failed to initialize the contract {:?}", error), + Ok(_) => Marketplace { + price: starting_price, + at_block: Self::env().block_number(), + price_multiplier_numerator, + price_multiplier_denominator, + units, + sell_price_multiplier, + }, + } + } + + fn ensure_role(role: Role) -> Result<(), Error> { + ::check_role( + AccountId::from(ACCESS_CONTROL_PUBKEY), + Self::env().caller(), + role, + |reason| Error::AccessControlCall(reason), + |role| Error::MissingRole(role), + ) + } + + fn initializer() -> Role { + let code_hash = Self::env() + .own_code_hash() + .expect("Failure to retrieve code hash."); + Initializer(code_hash) + } + + #[ink(message)] + pub fn price(&self) -> Balance { + self.price + } + + #[ink(message)] + pub fn at_block(&self) -> BlockNumber { + self.at_block + } + + #[ink(message)] + pub fn block_multiplier(&self) -> (Balance, Balance) { + ( + self.price_multiplier_numerator, + self.price_multiplier_denominator, + ) + } + + #[ink(message)] + pub fn available_units(&self) -> u32 { + self.units + } + + #[ink(message)] + pub fn buy(&mut self) { + self.update_price(); + self.units = self.units.saturating_sub(1); + self.price = self.price.saturating_mul(self.sell_price_multiplier); + } + + fn update_price(&mut self) { + let block = self.env().block_number(); + for _ in self.at_block..block { + self.price = self + .price + .saturating_mul(self.price_multiplier_numerator) + .saturating_div(self.price_multiplier_denominator) + } + self.at_block = block; + } + } + + #[cfg(test)] + mod tests { + use ink_env::{block_number, test::advance_block, DefaultEnvironment}; + use ink_lang as ink; + + use super::*; + + type E = DefaultEnvironment; + + #[ink::test] + fn initial_state() { + let mut market = test_marketplace(); + + assert_eq!(market.price(), 10000); + assert_eq!(market.available_units(), 10) + } + + #[ink::test] + fn price_decreases_per_block() { + let mut market = test_marketplace(); + + advance_block::(); + advance_block::(); + + assert_eq!(market.price(), 2500) + } + + #[ink::test] + fn buying_a_unit() { + let mut market = test_marketplace(); + + market.buy(); + + assert_eq!(market.available_units(), 9); + assert_eq!(market.price(), 50000); + } + + fn test_marketplace() -> Marketplace { + Marketplace { + price: 10000, + at_block: block_number::(), + price_multiplier_numerator: 1, + price_multiplier_denominator: 2, + units: 10, + sell_price_multiplier: 5, + } + } + } +} diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index 7d3dab1eda..73f61992ae 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -105,6 +105,47 @@ function deploy_and_instrument_game { eval "$__resultvar='$contract_address'" } +function deploy_and_instrument_marketplace { + local __resultvar=$1 + local contract_name=$2 + local ticket_token=$3 + local game_token=$4 + + # --- UPLOAD CONTRACT CODE + + cd "$CONTRACTS_PATH/$contract_name" + link_bytecode "$contract_name" 4465614444656144446561444465614444656144446561444465614444656144 "$ACCESS_CONTROL_PUBKEY" + rm target/ink/"$contract_name".wasm + node ../scripts/hex-to-wasm.js target/ink/"$contract_name".contract target/ink/"$contract_name".wasm + + local code_hash + code_hash=$(cargo contract upload --url "$NODE" --suri "$AUTHORITY_SEED") + code_hash=$(echo "$code_hash" | grep hash | tail -1 | cut -c 15-) + + # --- GRANT INIT PRIVILEGES ON THE CONTRACT CODE + + cd "$CONTRACTS_PATH"/access_control + cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Initializer('"$code_hash"')' --suri "$AUTHORITY_SEED" + + # --- CREATE AN INSTANCE OF THE CONTRACT + + cd "$CONTRACTS_PATH/$contract_name" + + local contract_address + contract_address=$(cargo contract instantiate --url "$NODE" --constructor new --args 10000000 97 100 10 5 --suri "$AUTHORITY_SEED") + contract_address=$(echo "$contract_address" | grep Contract | tail -1 | cut -c 15-) + + echo "$contract_name contract instance address: $contract_address" + + # --- GRANT PRIVILEGES ON THE CONTRACT + + cd "$CONTRACTS_PATH"/access_control + + cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Owner('"$contract_address"')' --suri "$AUTHORITY_SEED" + + eval "$__resultvar='$contract_address'" +} + function link_bytecode() { local contract=$1 local placeholder=$2 @@ -139,6 +180,9 @@ cargo contract build --release cd "$CONTRACTS_PATH"/the_pressiah_cometh cargo contract build --release +cd "$CONTRACTS_PATH"/marketplace +cargo contract build --release + # --- DEPLOY ACCESS CONTROL CONTRACT cd "$CONTRACTS_PATH"/access_control @@ -208,6 +252,8 @@ instrument_game_token EARLY_BIRD_SPECIAL_TOKEN game_token Ubik UBI 0x4561726C794 deploy_and_instrument_game EARLY_BIRD_SPECIAL early_bird_special $EARLY_BIRD_SPECIAL_TICKET $EARLY_BIRD_SPECIAL_TOKEN +deploy_and_instrument_marketplace EARLY_BIRD_SPECIAL_MARKETPLACE marketplace $EARLY_BIRD_SPECIAL_TICKET $EARLY_BIRD_SPECIAL_TOKEN + # # --- BACK_TO_THE_FUTURE GAME # @@ -240,6 +286,7 @@ deploy_and_instrument_game THE_PRESSIAH_COMETH the_pressiah_cometh $THE_PRESSIAH cd "$CONTRACTS_PATH" jq -n --arg early_bird_special $EARLY_BIRD_SPECIAL \ + --arg early_bird_special_marketplace $EARLY_BIRD_SPECIAL_MARKETPLACE \ --arg early_bird_special_ticket $EARLY_BIRD_SPECIAL_TICKET \ --arg early_bird_special_token $EARLY_BIRD_SPECIAL_TOKEN \ --arg back_to_the_future $BACK_TO_THE_FUTURE \ @@ -249,6 +296,7 @@ jq -n --arg early_bird_special $EARLY_BIRD_SPECIAL \ --arg the_pressiah_cometh_ticket $THE_PRESSIAH_COMETH_TICKET \ --arg the_pressiah_cometh_token $THE_PRESSIAH_COMETH_TOKEN \ '{early_bird_special: $early_bird_special, + early_bird_special_marketplace: $early_bird_special_marketplace, early_bird_special_ticket: $early_bird_special_ticket, early_bird_special_token: $early_bird_special_token, back_to_the_future: $back_to_the_future, From bcc46bf939f464f73d5ba7907d9dc61631b5025f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Mon, 5 Sep 2022 14:42:02 +0200 Subject: [PATCH 2/8] Implement selling tickets --- contracts/button/lib.rs | 3 +- contracts/game_token/lib.rs | 5 +- contracts/marketplace/Cargo.lock | 15 ++ contracts/marketplace/Cargo.toml | 1 + contracts/marketplace/lib.rs | 243 ++++++++++++++++++++----------- contracts/scripts/deploy.sh | 14 +- contracts/ticket_token/lib.rs | 2 +- 7 files changed, 196 insertions(+), 87 deletions(-) diff --git a/contracts/button/lib.rs b/contracts/button/lib.rs index ab1506e0ec..75cb148ca7 100644 --- a/contracts/button/lib.rs +++ b/contracts/button/lib.rs @@ -4,7 +4,7 @@ use access_control::{traits::AccessControlled, Role}; use game_token::MINT_TO_SELECTOR; use ink_env::{ call::{build_call, Call, ExecutionInput, Selector}, - AccountId, DefaultEnvironment, Environment, Error as InkEnvError, + AccountId, CallFlags, DefaultEnvironment, Environment, Error as InkEnvError, }; use ink_lang as ink; use ink_prelude::{format, string::String, vec}; @@ -225,6 +225,7 @@ pub trait ButtonGame { .push_arg(value) .push_arg(vec![0x0]), ) + .call_flags(CallFlags::default().set_allow_reentry(true)) .returns::>() .fire() } diff --git a/contracts/game_token/lib.rs b/contracts/game_token/lib.rs index eac59c2770..aeff1aeab0 100644 --- a/contracts/game_token/lib.rs +++ b/contracts/game_token/lib.rs @@ -1,7 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] #![feature(min_specialization)] -pub use crate::game_token::{BALANCE_OF_SELECTOR, MINT_TO_SELECTOR, TRANSFER_SELECTOR}; +pub use crate::game_token::{ + BALANCE_OF_SELECTOR, MINT_TO_SELECTOR, TRANSFER_FROM_SELECTOR, TRANSFER_SELECTOR, +}; #[openbrush::contract] pub mod game_token { @@ -20,6 +22,7 @@ pub mod game_token { pub const BALANCE_OF_SELECTOR: [u8; 4] = [0x65, 0x68, 0x38, 0x2f]; pub const TRANSFER_SELECTOR: [u8; 4] = [0xdb, 0x20, 0xf9, 0xf5]; + pub const TRANSFER_FROM_SELECTOR: [u8; 4] = [0x54, 0xb3, 0xc7, 0x6e]; // TODO : use correct selector when mint/burn is implemented pub const MINT_TO_SELECTOR: [u8; 4] = [0x0, 0x0, 0x0, 0x0]; diff --git a/contracts/marketplace/Cargo.lock b/contracts/marketplace/Cargo.lock index be94a88db7..6767dd13d4 100644 --- a/contracts/marketplace/Cargo.lock +++ b/contracts/marketplace/Cargo.lock @@ -523,6 +523,7 @@ dependencies = [ "openbrush", "parity-scale-codec", "scale-info", + "ticket_token", ] [[package]] @@ -925,6 +926,20 @@ dependencies = [ "syn", ] +[[package]] +name = "ticket_token" +version = "2.1.0" +dependencies = [ + "access_control", + "ink_env", + "ink_lang", + "ink_prelude", + "ink_primitives", + "ink_storage", + "openbrush", + "parity-scale-codec", +] + [[package]] name = "toml" version = "0.5.9" diff --git a/contracts/marketplace/Cargo.toml b/contracts/marketplace/Cargo.toml index 3991905c1c..62ef428c38 100644 --- a/contracts/marketplace/Cargo.toml +++ b/contracts/marketplace/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { version = "2", default-features = false, features = ["derive"], o openbrush = { git = "https://github.com/Supercolony-net/openbrush-contracts.git", rev = "8a20f95", default-features = false, features = ["psp22"] } access_control = { path = "../access_control", default-features = false, features = ["ink-as-dependency"] } game_token = { path = "../game_token", default-features = false, features = ["ink-as-dependency"] } +ticket_token = { path = "../ticket_token", default-features = false, features = ["ink-as-dependency"] } [lib] name = "marketplace" diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 367af48dd1..701fd0c45d 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -8,21 +8,59 @@ pub mod marketplace { use access_control::{ traits::AccessControlled, Role, Role::Initializer, ACCESS_CONTROL_PUBKEY, }; + use game_token::TRANSFER_FROM_SELECTOR as TRANSFER_FROM_GAME_TOKEN_SELECTOR; + use ink_env::{ + call::{build_call, Call, ExecutionInput, Selector}, + CallFlags, + }; + use ink_prelude::{format, string::String}; + use openbrush::contracts::psp22::PSP22Error; + use ticket_token::{ + BALANCE_OF_SELECTOR as TICKET_BALANCE_SELECTOR, + TRANSFER_SELECTOR as TRANSFER_TICKET_SELECTOR, + }; + + use crate::marketplace::Error::MissingRole; + + const DUMMY_DATA: &[u8] = &[0x0]; #[ink(storage)] pub struct Marketplace { - price: Balance, - at_block: BlockNumber, - price_multiplier_numerator: Balance, - price_multiplier_denominator: Balance, - units: u32, - sell_price_multiplier: Balance, + total_proceeds: Balance, + tickets_sold: Balance, + min_price: Balance, + current_start_block: BlockNumber, + auction_length: BlockNumber, + sale_multiplier: Balance, + ticket_token: AccountId, + game_token: AccountId, } - #[derive(Debug)] + #[derive(Clone, Eq, PartialEq, Debug, scale::Encode, scale::Decode)] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum Error { - AccessControlCall(ink_env::Error), - MissingRole(Role), + MissingRole(String), + ContractCall(String), + PSP22TokenCall(String), + MarketplaceEmpty, + } + + impl Error { + fn missing_role(role: Role) -> Self { + MissingRole(format!("{:?}", role)) + } + } + + impl From for Error { + fn from(inner: ink_env::Error) -> Self { + Error::ContractCall(format!("{:?}", inner)) + } + } + + impl From for Error { + fn from(inner: PSP22Error) -> Self { + Error::PSP22TokenCall(format!("{:?}", inner)) + } } impl AccessControlled for Marketplace { @@ -32,22 +70,25 @@ pub mod marketplace { impl Marketplace { #[ink(constructor)] pub fn new( + ticket_token: AccountId, + game_token: AccountId, starting_price: Balance, - price_multiplier_numerator: Balance, - price_multiplier_denominator: Balance, - units: u32, - sell_price_multiplier: Balance, + min_price: Balance, + sale_multiplier: Balance, + auction_length: BlockNumber, ) -> Self { - match Self::ensure_role(Self::initializer()) { - Err(error) => panic!("Failed to initialize the contract {:?}", error), - Ok(_) => Marketplace { - price: starting_price, - at_block: Self::env().block_number(), - price_multiplier_numerator, - price_multiplier_denominator, - units, - sell_price_multiplier, - }, + Self::ensure_role(Self::initializer()) + .unwrap_or_else(|e| panic!("Failed to initialize the contract {:?}", e)); + + Marketplace { + ticket_token, + game_token, + min_price, + sale_multiplier, + auction_length, + current_start_block: Self::env().block_number(), + total_proceeds: starting_price.saturating_div(sale_multiplier), + tickets_sold: 1, } } @@ -56,8 +97,8 @@ pub mod marketplace { AccountId::from(ACCESS_CONTROL_PUBKEY), Self::env().caller(), role, - |reason| Error::AccessControlCall(reason), - |role| Error::MissingRole(role), + |reason| reason.into(), + |role| Error::missing_role(role), ) } @@ -68,94 +109,132 @@ pub mod marketplace { Initializer(code_hash) } + fn admin(&self) -> Role { + Role::Admin(self.this()) + } + #[ink(message)] pub fn price(&self) -> Balance { - self.price + self.current_price() } #[ink(message)] - pub fn at_block(&self) -> BlockNumber { - self.at_block + pub fn sale_multiplier(&self) -> Balance { + self.sale_multiplier } #[ink(message)] - pub fn block_multiplier(&self) -> (Balance, Balance) { - ( - self.price_multiplier_numerator, - self.price_multiplier_denominator, - ) + pub fn available_tickets(&self) -> Result { + self.ticket_balance() } #[ink(message)] - pub fn available_units(&self) -> u32 { - self.units + pub fn min_price(&self) -> Balance { + self.min_price } #[ink(message)] - pub fn buy(&mut self) { - self.update_price(); - self.units = self.units.saturating_sub(1); - self.price = self.price.saturating_mul(self.sell_price_multiplier); + pub fn game_token(&self) -> AccountId { + self.game_token } - fn update_price(&mut self) { - let block = self.env().block_number(); - for _ in self.at_block..block { - self.price = self - .price - .saturating_mul(self.price_multiplier_numerator) - .saturating_div(self.price_multiplier_denominator) - } - self.at_block = block; + #[ink(message)] + pub fn ticket_token(&self) -> AccountId { + self.ticket_token } - } - #[cfg(test)] - mod tests { - use ink_env::{block_number, test::advance_block, DefaultEnvironment}; - use ink_lang as ink; - - use super::*; + #[ink(message)] + pub fn buy(&mut self) -> Result<(), Error> { + if self.ticket_balance()? > 0 { + let price = self.current_price(); + self.take_payment(self.env().caller(), price)?; + self.give_ticket(self.env().caller())?; + + self.total_proceeds = self.total_proceeds.saturating_add(price); + self.tickets_sold = self.tickets_sold.saturating_add(1); + self.current_start_block = self.env().block_number(); + + Ok(()) + } else { + Err(Error::MarketplaceEmpty) + } + } - type E = DefaultEnvironment; + #[ink(message)] + pub fn reset(&mut self) -> Result<(), Error> { + Self::ensure_role(self.admin())?; - #[ink::test] - fn initial_state() { - let mut market = test_marketplace(); + self.current_start_block = self.env().block_number(); - assert_eq!(market.price(), 10000); - assert_eq!(market.available_units(), 10) + Ok(()) } - #[ink::test] - fn price_decreases_per_block() { - let mut market = test_marketplace(); + fn current_price(&self) -> Balance { + let block = self.env().block_number(); + let elapsed = block.saturating_sub(self.current_start_block.into()); + self.average_price() + .saturating_mul(self.sale_multiplier) + .saturating_sub(self.per_block_reduction().saturating_mul(elapsed.into())) + .max(self.min_price) + } - advance_block::(); - advance_block::(); + fn per_block_reduction(&self) -> Balance { + self.average_price() + .saturating_div(self.auction_length.into()) + .max(1u128) + } - assert_eq!(market.price(), 2500) + fn average_price(&self) -> Balance { + self.total_proceeds.saturating_div(self.tickets_sold) } - #[ink::test] - fn buying_a_unit() { - let mut market = test_marketplace(); + fn take_payment(&self, from: AccountId, amount: Balance) -> Result<(), Error> { + build_call::() + .call_type(Call::new().callee(self.game_token)) + .exec_input( + ExecutionInput::new(Selector::new(TRANSFER_FROM_GAME_TOKEN_SELECTOR)) + .push_arg(from) + .push_arg(self.this()) + .push_arg(amount) + .push_arg(DUMMY_DATA), + ) + .call_flags(CallFlags::default().set_allow_reentry(true)) + .returns::>() + .fire()??; + + Ok(()) + } - market.buy(); + fn give_ticket(&self, to: AccountId) -> Result<(), Error> { + build_call::() + .call_type(Call::new().callee(self.ticket_token)) + .exec_input( + ExecutionInput::new(Selector::new(TRANSFER_TICKET_SELECTOR)) + .push_arg(to) + .push_arg(1u128) + .push_arg(DUMMY_DATA), + ) + .returns::>() + .fire()??; + + Ok(()) + } - assert_eq!(market.available_units(), 9); - assert_eq!(market.price(), 50000); + fn ticket_balance(&self) -> Result { + let balance = build_call::() + .call_type(Call::new().callee(self.ticket_token)) + .exec_input( + ExecutionInput::new(Selector::new(TICKET_BALANCE_SELECTOR)) + .push_arg(self.this()), + ) + .returns::() + .fire()?; + + Ok(balance) } - fn test_marketplace() -> Marketplace { - Marketplace { - price: 10000, - at_block: block_number::(), - price_multiplier_numerator: 1, - price_multiplier_denominator: 2, - units: 10, - sell_price_multiplier: 5, - } + fn this(&self) -> AccountId { + self.env().account_id() } } } diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index 73f61992ae..c813d36c62 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -110,6 +110,7 @@ function deploy_and_instrument_marketplace { local contract_name=$2 local ticket_token=$3 local game_token=$4 + local game=$5 # --- UPLOAD CONTRACT CODE @@ -131,8 +132,15 @@ function deploy_and_instrument_marketplace { cd "$CONTRACTS_PATH/$contract_name" + local blocks_per_hour=3600 + local initial_price="$TOTAL_BALANCE" + local min_price=1 + local sale_price_multiplier=2 + local contract_address - contract_address=$(cargo contract instantiate --url "$NODE" --constructor new --args 10000000 97 100 10 5 --suri "$AUTHORITY_SEED") + contract_address=$(cargo contract instantiate --url "$NODE" --constructor new \ + --args "$ticket_token" "$game_token" "$initial_price" "$min_price" "$sale_price_multiplier" "$blocks_per_hour" \ + --suri "$AUTHORITY_SEED") contract_address=$(echo "$contract_address" | grep Contract | tail -1 | cut -c 15-) echo "$contract_name contract instance address: $contract_address" @@ -142,6 +150,8 @@ function deploy_and_instrument_marketplace { cd "$CONTRACTS_PATH"/access_control cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Owner('"$contract_address"')' --suri "$AUTHORITY_SEED" + cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Admin('"$contract_address"')' --suri "$AUTHORITY_SEED" + cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$game" 'Admin('"$contract_address"')' --suri "$AUTHORITY_SEED" eval "$__resultvar='$contract_address'" } @@ -252,7 +262,7 @@ instrument_game_token EARLY_BIRD_SPECIAL_TOKEN game_token Ubik UBI 0x4561726C794 deploy_and_instrument_game EARLY_BIRD_SPECIAL early_bird_special $EARLY_BIRD_SPECIAL_TICKET $EARLY_BIRD_SPECIAL_TOKEN -deploy_and_instrument_marketplace EARLY_BIRD_SPECIAL_MARKETPLACE marketplace $EARLY_BIRD_SPECIAL_TICKET $EARLY_BIRD_SPECIAL_TOKEN +deploy_and_instrument_marketplace EARLY_BIRD_SPECIAL_MARKETPLACE marketplace "$EARLY_BIRD_SPECIAL_TICKET" "$EARLY_BIRD_SPECIAL_TOKEN" "$EARLY_BIRD_SPECIAL" # # --- BACK_TO_THE_FUTURE GAME diff --git a/contracts/ticket_token/lib.rs b/contracts/ticket_token/lib.rs index 7c45806867..07ffd2b7d6 100644 --- a/contracts/ticket_token/lib.rs +++ b/contracts/ticket_token/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![feature(min_specialization)] -pub use crate::ticket_token::{BALANCE_OF_SELECTOR, TRANSFER_FROM_SELECTOR}; +pub use crate::ticket_token::{BALANCE_OF_SELECTOR, TRANSFER_FROM_SELECTOR, TRANSFER_SELECTOR}; #[openbrush::contract] pub mod ticket_token { From c6a166d295f22e5d480c8069c62ac29fe8b10bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Tue, 6 Sep 2022 16:51:25 +0200 Subject: [PATCH 3/8] Emit events from marketplace --- contracts/marketplace/lib.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 701fd0c45d..5932d7326b 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -13,6 +13,7 @@ pub mod marketplace { call::{build_call, Call, ExecutionInput, Selector}, CallFlags, }; + use ink_lang::{codegen::EmitEvent, reflect::ContractEventBase}; use ink_prelude::{format, string::String}; use openbrush::contracts::psp22::PSP22Error; use ticket_token::{ @@ -22,6 +23,8 @@ pub mod marketplace { use crate::marketplace::Error::MissingRole; + type Event = ::Type; + const DUMMY_DATA: &[u8] = &[0x0]; #[ink(storage)] @@ -45,6 +48,18 @@ pub mod marketplace { MarketplaceEmpty, } + #[ink(event)] + #[derive(Clone, Eq, PartialEq, Debug)] + pub struct Buy { + #[ink(topic)] + pub account_id: AccountId, + pub price: Balance, + } + + #[ink(event)] + #[derive(Clone, Eq, PartialEq, Debug)] + pub struct Reset; + impl Error { fn missing_role(role: Role) -> Self { MissingRole(format!("{:?}", role)) @@ -147,12 +162,15 @@ pub mod marketplace { pub fn buy(&mut self) -> Result<(), Error> { if self.ticket_balance()? > 0 { let price = self.current_price(); - self.take_payment(self.env().caller(), price)?; - self.give_ticket(self.env().caller())?; + let account_id = self.env().caller(); + + self.take_payment(account_id, price)?; + self.give_ticket(account_id)?; self.total_proceeds = self.total_proceeds.saturating_add(price); self.tickets_sold = self.tickets_sold.saturating_add(1); self.current_start_block = self.env().block_number(); + Self::emit_event(self.env(), Event::Buy(Buy { price, account_id })); Ok(()) } else { @@ -165,6 +183,7 @@ pub mod marketplace { Self::ensure_role(self.admin())?; self.current_start_block = self.env().block_number(); + Self::emit_event(self.env(), Event::Reset(Reset {})); Ok(()) } @@ -236,5 +255,9 @@ pub mod marketplace { fn this(&self) -> AccountId { self.env().account_id() } + + fn emit_event>(emitter: EE, event: Event) { + emitter.emit_event(event) + } } } From 2c0745cfdd5a24354f801371079b8114dc9a82c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Thu, 8 Sep 2022 14:43:10 +0200 Subject: [PATCH 4/8] Add docs --- contracts/marketplace/lib.rs | 90 +++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 5932d7326b..c58fb80337 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -36,7 +36,7 @@ pub mod marketplace { auction_length: BlockNumber, sale_multiplier: Balance, ticket_token: AccountId, - game_token: AccountId, + reward_token: AccountId, } #[derive(Clone, Eq, PartialEq, Debug, scale::Encode, scale::Decode)] @@ -86,7 +86,7 @@ pub mod marketplace { #[ink(constructor)] pub fn new( ticket_token: AccountId, - game_token: AccountId, + reward_token: AccountId, starting_price: Balance, min_price: Balance, sale_multiplier: Balance, @@ -97,7 +97,7 @@ pub mod marketplace { Marketplace { ticket_token, - game_token, + reward_token, min_price, sale_multiplier, auction_length, @@ -107,58 +107,74 @@ pub mod marketplace { } } - fn ensure_role(role: Role) -> Result<(), Error> { - ::check_role( - AccountId::from(ACCESS_CONTROL_PUBKEY), - Self::env().caller(), - role, - |reason| reason.into(), - |role| Error::missing_role(role), - ) - } - - fn initializer() -> Role { - let code_hash = Self::env() - .own_code_hash() - .expect("Failure to retrieve code hash."); - Initializer(code_hash) + #[ink(message)] + /// The length of each auction of a single ticket in blocks. + /// + /// The contract will decrease the price linearly from `average_price() * sale_multiplier()` + /// to `min_price()` over this period. The auction doesn't end after the period elapses - + /// the ticket remains available for purchase at `min_price()`. + pub fn auction_length(&self) -> BlockNumber { + self.auction_length } - fn admin(&self) -> Role { - Role::Admin(self.this()) + #[ink(message)] + /// The block at which the auction of the current ticket started. + pub fn current_start_block(&self) -> BlockNumber { + self.current_start_block } #[ink(message)] + /// The price the contract would charge when buying at the current block. pub fn price(&self) -> Balance { self.current_price() } #[ink(message)] + /// The average price over all sales the contract made. + fn average_price(&self) -> Balance { + self.total_proceeds.saturating_div(self.tickets_sold) + } + + #[ink(message)] + /// The multiplier applied to the initial price after each sale. + /// + /// The contract tracks the average price of all sold tickets and starts off each new + /// auction at a price that is this multiplier times the average. pub fn sale_multiplier(&self) -> Balance { self.sale_multiplier } #[ink(message)] + /// Number of tickets available for sale. + /// + /// The tickets will be auctioned off one by one. pub fn available_tickets(&self) -> Result { self.ticket_balance() } #[ink(message)] + /// The minimal price the contract allows. pub fn min_price(&self) -> Balance { self.min_price } #[ink(message)] - pub fn game_token(&self) -> AccountId { - self.game_token + /// Address of the reward token contract this contract will accept as payment. + pub fn reward_token(&self) -> AccountId { + self.reward_token } #[ink(message)] + /// Address of the ticket token contract this contract will auction off. pub fn ticket_token(&self) -> AccountId { self.ticket_token } #[ink(message)] + /// Buy one ticket at the current_price. + /// + /// The caller should make an approval for at least `price()` reward tokens to make sure the + /// call will succeed. pub fn buy(&mut self) -> Result<(), Error> { if self.ticket_balance()? > 0 { let price = self.current_price(); @@ -179,6 +195,9 @@ pub mod marketplace { } #[ink(message)] + /// Re-start the auction from the current block. + /// + /// Note that this will keep the average estimate from previous auctions. pub fn reset(&mut self) -> Result<(), Error> { Self::ensure_role(self.admin())?; @@ -203,13 +222,9 @@ pub mod marketplace { .max(1u128) } - fn average_price(&self) -> Balance { - self.total_proceeds.saturating_div(self.tickets_sold) - } - fn take_payment(&self, from: AccountId, amount: Balance) -> Result<(), Error> { build_call::() - .call_type(Call::new().callee(self.game_token)) + .call_type(Call::new().callee(self.reward_token)) .exec_input( ExecutionInput::new(Selector::new(TRANSFER_FROM_GAME_TOKEN_SELECTOR)) .push_arg(from) @@ -252,6 +267,27 @@ pub mod marketplace { Ok(balance) } + fn ensure_role(role: Role) -> Result<(), Error> { + ::check_role( + AccountId::from(ACCESS_CONTROL_PUBKEY), + Self::env().caller(), + role, + |reason| reason.into(), + |role| Error::missing_role(role), + ) + } + + fn initializer() -> Role { + let code_hash = Self::env() + .own_code_hash() + .expect("Failure to retrieve code hash."); + Initializer(code_hash) + } + + fn admin(&self) -> Role { + Role::Admin(self.this()) + } + fn this(&self) -> AccountId { self.env().account_id() } From dc362aab7b7aa501ff565354594bd01a29243bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Thu, 8 Sep 2022 16:02:04 +0200 Subject: [PATCH 5/8] Deploy markets for all games --- contracts/marketplace/lib.rs | 4 +- contracts/scripts/deploy.sh | 102 ++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index c58fb80337..27cec0056e 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -131,12 +131,12 @@ pub mod marketplace { #[ink(message)] /// The average price over all sales the contract made. - fn average_price(&self) -> Balance { + pub fn average_price(&self) -> Balance { self.total_proceeds.saturating_div(self.tickets_sold) } #[ink(message)] - /// The multiplier applied to the initial price after each sale. + /// The multiplier applied to the average price after each sale. /// /// The contract tracks the average price of all sold tickets and starts off each new /// auction at a price that is this multiplier times the average. diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index c813d36c62..9d77c67924 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -105,32 +105,18 @@ function deploy_and_instrument_game { eval "$__resultvar='$contract_address'" } -function deploy_and_instrument_marketplace { +function instrument_marketplace { local __resultvar=$1 - local contract_name=$2 - local ticket_token=$3 - local game_token=$4 - local game=$5 - - # --- UPLOAD CONTRACT CODE - - cd "$CONTRACTS_PATH/$contract_name" - link_bytecode "$contract_name" 4465614444656144446561444465614444656144446561444465614444656144 "$ACCESS_CONTROL_PUBKEY" - rm target/ink/"$contract_name".wasm - node ../scripts/hex-to-wasm.js target/ink/"$contract_name".contract target/ink/"$contract_name".wasm - - local code_hash - code_hash=$(cargo contract upload --url "$NODE" --suri "$AUTHORITY_SEED") - code_hash=$(echo "$code_hash" | grep hash | tail -1 | cut -c 15-) - - # --- GRANT INIT PRIVILEGES ON THE CONTRACT CODE - - cd "$CONTRACTS_PATH"/access_control - cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Initializer('"$code_hash"')' --suri "$AUTHORITY_SEED" + local code_hash=$2 + local contract_name=$3 + local salt=$4 + local ticket_token=$5 + local game_token=$6 + local game=$7 # --- CREATE AN INSTANCE OF THE CONTRACT - cd "$CONTRACTS_PATH/$contract_name" + cd "$CONTRACTS_PATH"/marketplace local blocks_per_hour=3600 local initial_price="$TOTAL_BALANCE" @@ -140,10 +126,10 @@ function deploy_and_instrument_marketplace { local contract_address contract_address=$(cargo contract instantiate --url "$NODE" --constructor new \ --args "$ticket_token" "$game_token" "$initial_price" "$min_price" "$sale_price_multiplier" "$blocks_per_hour" \ - --suri "$AUTHORITY_SEED") + --suri "$AUTHORITY_SEED" --salt "$salt") contract_address=$(echo "$contract_address" | grep Contract | tail -1 | cut -c 15-) - echo "$contract_name contract instance address: $contract_address" + echo "Marketplace for $contract_name instance address: $contract_address" # --- GRANT PRIVILEGES ON THE CONTRACT @@ -236,7 +222,23 @@ GAME_TOKEN_CODE_HASH=$(echo "$CODE_HASH" | grep hash | tail -1 | cut -c 15-) echo "button token code hash" "$GAME_TOKEN_CODE_HASH" -# --- GRANT INIT PRIVILEGES ON THE TOKEN AND TICKET CONTRACT CODE +# --- UPLOAD MARKETPLACE CONTRACT CODE + +cd "$CONTRACTS_PATH"/marketplace +# replace address placeholder with the on-chain address of the AccessControl contract +link_bytecode marketplace 4465614444656144446561444465614444656144446561444465614444656144 "$ACCESS_CONTROL_PUBKEY" +# remove just in case +rm target/ink/marketplace.wasm +# NOTE : here we go from hex to binary using a nodejs cli tool +# availiable from https://github.com/fbielejec/polkadot-cljs +node ../scripts/hex-to-wasm.js target/ink/marketplace.contract target/ink/marketplace.wasm + +CODE_HASH=$(cargo contract upload --url "$NODE" --suri "$AUTHORITY_SEED") +MARKETPLACE_CODE_HASH=$(echo "$CODE_HASH" | grep hash | tail -1 | cut -c 15-) + +echo "marketplace code hash" "$MARKETPLACE_CODE_HASH" + +# --- GRANT INIT PRIVILEGES ON THE TOKEN, TICKET, AND MARKETPLACE CONTRACT CODE cd "$CONTRACTS_PATH"/access_control @@ -244,9 +246,13 @@ cd "$CONTRACTS_PATH"/access_control cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Initializer('"$GAME_TOKEN_CODE_HASH"')' --suri "$AUTHORITY_SEED" # set the initializer of the ticket contract -cargo contract call --url $NODE --contract $ACCESS_CONTROL --message grant_role --args $AUTHORITY 'Initializer('$TICKET_TOKEN_CODE_HASH')' --suri "$AUTHORITY_SEED" +cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Initializer('"$TICKET_TOKEN_CODE_HASH"')' --suri "$AUTHORITY_SEED" + +# set the initializer of the marketplace contract +cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Initializer('"$MARKETPLACE_CODE_HASH"')' --suri "$AUTHORITY_SEED" + -start=`date +%s.%N` +start=$(date +%s.%N) # # --- EARLY_BIRD_SPECIAL GAME @@ -260,9 +266,9 @@ instrument_game_token EARLY_BIRD_SPECIAL_TOKEN game_token Ubik UBI 0x4561726C794 # --- UPLOAD CODE AND CREATE AN INSTANCE OF THE EARLY_BIRD_SPECIAL GAME -deploy_and_instrument_game EARLY_BIRD_SPECIAL early_bird_special $EARLY_BIRD_SPECIAL_TICKET $EARLY_BIRD_SPECIAL_TOKEN +deploy_and_instrument_game EARLY_BIRD_SPECIAL early_bird_special "$EARLY_BIRD_SPECIAL_TICKET" "$EARLY_BIRD_SPECIAL_TOKEN" -deploy_and_instrument_marketplace EARLY_BIRD_SPECIAL_MARKETPLACE marketplace "$EARLY_BIRD_SPECIAL_TICKET" "$EARLY_BIRD_SPECIAL_TOKEN" "$EARLY_BIRD_SPECIAL" +instrument_marketplace EARLY_BIRD_SPECIAL_MARKETPLACE "$MARKETPLACE_CODE_HASH" early_bird_special 0x4561726C79426972645370656369616C "$EARLY_BIRD_SPECIAL_TICKET" "$EARLY_BIRD_SPECIAL_TOKEN" "$EARLY_BIRD_SPECIAL" # # --- BACK_TO_THE_FUTURE GAME @@ -274,9 +280,11 @@ instrument_ticket_token BACK_TO_THE_FUTURE_TICKET ticket_token 0x4261636B546F546 instrument_game_token BACK_TO_THE_FUTURE_TOKEN game_token Cyberiad CYB 0x4261636B546F546865467574757265 -# --- UPLOAD CODE AND CREATE AN INSTANCE OF THE EARLY_BIRD_SPECIAL GAME +# --- UPLOAD CODE AND CREATE AN INSTANCE OF THE BACK_TO_THE_FUTURE GAME + +deploy_and_instrument_game BACK_TO_THE_FUTURE back_to_the_future "$BACK_TO_THE_FUTURE_TICKET" "$BACK_TO_THE_FUTURE_TOKEN" -deploy_and_instrument_game BACK_TO_THE_FUTURE back_to_the_future $BACK_TO_THE_FUTURE_TICKET $BACK_TO_THE_FUTURE_TOKEN +instrument_marketplace BACK_TO_THE_FUTURE_MARKETPLACE "$MARKETPLACE_CODE_HASH" back_to_the_future 0x4261636B546F546865467574757265 "$BACK_TO_THE_FUTURE_TICKET" "$BACK_TO_THE_FUTURE_TOKEN" "$BACK_TO_THE_FUTURE" # # --- THE_PRESSIAH_COMETH GAME @@ -288,23 +296,27 @@ instrument_ticket_token THE_PRESSIAH_COMETH_TICKET ticket_token 0x7468655F707265 instrument_game_token THE_PRESSIAH_COMETH_TOKEN game_token Dune DUN 0x7468655F70726573736961685F636F6D657468 -# --- UPLOAD CODE AND CREATE AN INSTANCE OF THE EARLY_BIRD_SPECIAL GAME +# --- UPLOAD CODE AND CREATE AN INSTANCE OF THE THE_PRESSIAH_COMETH GAME + +deploy_and_instrument_game THE_PRESSIAH_COMETH the_pressiah_cometh "$THE_PRESSIAH_COMETH_TICKET" "$THE_PRESSIAH_COMETH_TOKEN" -deploy_and_instrument_game THE_PRESSIAH_COMETH the_pressiah_cometh $THE_PRESSIAH_COMETH_TICKET $THE_PRESSIAH_COMETH_TOKEN +instrument_marketplace THE_PRESSIAH_COMETH_MARKETPLACE "$MARKETPLACE_CODE_HASH" the_pressiah_cometh 0x7468655F70726573736961685F636F6D657468 "$THE_PRESSIAH_COMETH_TICKET" "$THE_PRESSIAH_COMETH_TOKEN" "$THE_PRESSIAH_COMETH" # spit adresses to a JSON file cd "$CONTRACTS_PATH" -jq -n --arg early_bird_special $EARLY_BIRD_SPECIAL \ - --arg early_bird_special_marketplace $EARLY_BIRD_SPECIAL_MARKETPLACE \ - --arg early_bird_special_ticket $EARLY_BIRD_SPECIAL_TICKET \ - --arg early_bird_special_token $EARLY_BIRD_SPECIAL_TOKEN \ - --arg back_to_the_future $BACK_TO_THE_FUTURE \ - --arg back_to_the_future_ticket $BACK_TO_THE_FUTURE_TICKET \ - --arg back_to_the_future_token $BACK_TO_THE_FUTURE_TOKEN \ - --arg the_pressiah_cometh $THE_PRESSIAH_COMETH \ - --arg the_pressiah_cometh_ticket $THE_PRESSIAH_COMETH_TICKET \ - --arg the_pressiah_cometh_token $THE_PRESSIAH_COMETH_TOKEN \ +jq -n --arg early_bird_special "$EARLY_BIRD_SPECIAL" \ + --arg early_bird_special_marketplace "$EARLY_BIRD_SPECIAL_MARKETPLACE" \ + --arg early_bird_special_ticket "$EARLY_BIRD_SPECIAL_TICKET" \ + --arg early_bird_special_token "$EARLY_BIRD_SPECIAL_TOKEN" \ + --arg back_to_the_future "$BACK_TO_THE_FUTURE" \ + --arg back_to_the_future_ticket "$BACK_TO_THE_FUTURE_TICKET" \ + --arg back_to_the_future_token "$BACK_TO_THE_FUTURE_TOKEN" \ + --arg back_to_the_future_marketplace "$BACK_TO_THE_FUTURE_MARKETPLACE" \ + --arg the_pressiah_cometh "$THE_PRESSIAH_COMETH" \ + --arg the_pressiah_cometh_ticket "$THE_PRESSIAH_COMETH_TICKET" \ + --arg the_pressiah_cometh_token "$THE_PRESSIAH_COMETH_TOKEN" \ + --arg the_pressiah_cometh_marketplace "$THE_PRESSIAH_COMETH_MARKETPLACE" \ '{early_bird_special: $early_bird_special, early_bird_special_marketplace: $early_bird_special_marketplace, early_bird_special_ticket: $early_bird_special_ticket, @@ -312,9 +324,11 @@ jq -n --arg early_bird_special $EARLY_BIRD_SPECIAL \ back_to_the_future: $back_to_the_future, back_to_the_future_ticket: $back_to_the_future_ticket, back_to_the_future_token: $back_to_the_future_token, + back_to_the_future_marketplace: $back_to_the_future_marketplace, the_pressiah_cometh: $the_pressiah_cometh, the_pressiah_cometh_ticket: $the_pressiah_cometh_ticket, - the_pressiah_cometh_token: $the_pressiah_cometh_token}' > addresses.json + the_pressiah_cometh_token: $the_pressiah_cometh_token, + the_pressiah_cometh_marketplace: $the_pressiah_cometh_marketplace}' > addresses.json end=$( date +%s.%N ) echo "Time elapsed: $( echo "$end - $start" | bc -l )" From 38cff6888175795c917854e534cc634d433f21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Mon, 12 Sep 2022 17:02:13 +0200 Subject: [PATCH 6/8] Improve cosmetics --- contracts/marketplace/lib.rs | 44 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 27cec0056e..5f7dc66d92 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -5,9 +5,7 @@ use ink_lang as ink; #[ink::contract] pub mod marketplace { - use access_control::{ - traits::AccessControlled, Role, Role::Initializer, ACCESS_CONTROL_PUBKEY, - }; + use access_control::{traits::AccessControlled, Role, ACCESS_CONTROL_PUBKEY}; use game_token::TRANSFER_FROM_SELECTOR as TRANSFER_FROM_GAME_TOKEN_SELECTOR; use ink_env::{ call::{build_call, Call, ExecutionInput, Selector}, @@ -50,7 +48,7 @@ pub mod marketplace { #[ink(event)] #[derive(Clone, Eq, PartialEq, Debug)] - pub struct Buy { + pub struct Bought { #[ink(topic)] pub account_id: AccountId, pub price: Balance, @@ -107,74 +105,74 @@ pub mod marketplace { } } - #[ink(message)] /// The length of each auction of a single ticket in blocks. /// /// The contract will decrease the price linearly from `average_price() * sale_multiplier()` /// to `min_price()` over this period. The auction doesn't end after the period elapses - /// the ticket remains available for purchase at `min_price()`. + #[ink(message)] pub fn auction_length(&self) -> BlockNumber { self.auction_length } - #[ink(message)] /// The block at which the auction of the current ticket started. + #[ink(message)] pub fn current_start_block(&self) -> BlockNumber { self.current_start_block } - #[ink(message)] /// The price the contract would charge when buying at the current block. + #[ink(message)] pub fn price(&self) -> Balance { self.current_price() } - #[ink(message)] /// The average price over all sales the contract made. + #[ink(message)] pub fn average_price(&self) -> Balance { self.total_proceeds.saturating_div(self.tickets_sold) } - #[ink(message)] /// The multiplier applied to the average price after each sale. /// /// The contract tracks the average price of all sold tickets and starts off each new - /// auction at a price that is this multiplier times the average. + /// auction at `price() = average_price() * sale_multiplier()`. + #[ink(message)] pub fn sale_multiplier(&self) -> Balance { self.sale_multiplier } - #[ink(message)] /// Number of tickets available for sale. /// /// The tickets will be auctioned off one by one. + #[ink(message)] pub fn available_tickets(&self) -> Result { self.ticket_balance() } - #[ink(message)] /// The minimal price the contract allows. + #[ink(message)] pub fn min_price(&self) -> Balance { self.min_price } - #[ink(message)] /// Address of the reward token contract this contract will accept as payment. + #[ink(message)] pub fn reward_token(&self) -> AccountId { self.reward_token } - #[ink(message)] /// Address of the ticket token contract this contract will auction off. + #[ink(message)] pub fn ticket_token(&self) -> AccountId { self.ticket_token } - #[ink(message)] /// Buy one ticket at the current_price. /// /// The caller should make an approval for at least `price()` reward tokens to make sure the /// call will succeed. + #[ink(message)] pub fn buy(&mut self) -> Result<(), Error> { if self.ticket_balance()? > 0 { let price = self.current_price(); @@ -186,7 +184,7 @@ pub mod marketplace { self.total_proceeds = self.total_proceeds.saturating_add(price); self.tickets_sold = self.tickets_sold.saturating_add(1); self.current_start_block = self.env().block_number(); - Self::emit_event(self.env(), Event::Buy(Buy { price, account_id })); + Self::emit_event(self.env(), Event::Bought(Bought { price, account_id })); Ok(()) } else { @@ -194,10 +192,10 @@ pub mod marketplace { } } - #[ink(message)] /// Re-start the auction from the current block. /// /// Note that this will keep the average estimate from previous auctions. + #[ink(message)] pub fn reset(&mut self) -> Result<(), Error> { Self::ensure_role(self.admin())?; @@ -228,7 +226,7 @@ pub mod marketplace { .exec_input( ExecutionInput::new(Selector::new(TRANSFER_FROM_GAME_TOKEN_SELECTOR)) .push_arg(from) - .push_arg(self.this()) + .push_arg(self.env().account_id()) .push_arg(amount) .push_arg(DUMMY_DATA), ) @@ -259,7 +257,7 @@ pub mod marketplace { .call_type(Call::new().callee(self.ticket_token)) .exec_input( ExecutionInput::new(Selector::new(TICKET_BALANCE_SELECTOR)) - .push_arg(self.this()), + .push_arg(self.env().account_id()), ) .returns::() .fire()?; @@ -281,15 +279,11 @@ pub mod marketplace { let code_hash = Self::env() .own_code_hash() .expect("Failure to retrieve code hash."); - Initializer(code_hash) + Role::Initializer(code_hash) } fn admin(&self) -> Role { - Role::Admin(self.this()) - } - - fn this(&self) -> AccountId { - self.env().account_id() + Role::Admin(self.env().account_id()) } fn emit_event>(emitter: EE, event: Event) { From 6461952c923b8b110ab0918e85a9b2bf5f32ce8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Mon, 12 Sep 2022 17:11:02 +0200 Subject: [PATCH 7/8] Wrap original errors where possible --- contracts/marketplace/Cargo.lock | 6 ++++++ contracts/marketplace/Cargo.toml | 6 ++++++ contracts/marketplace/lib.rs | 28 +++++++++++++++------------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/contracts/marketplace/Cargo.lock b/contracts/marketplace/Cargo.lock index 6767dd13d4..890f67d366 100644 --- a/contracts/marketplace/Cargo.lock +++ b/contracts/marketplace/Cargo.lock @@ -9,9 +9,11 @@ dependencies = [ "ink_env", "ink_lang", "ink_lang_codegen", + "ink_metadata", "ink_primitives", "ink_storage", "parity-scale-codec", + "scale-info", ] [[package]] @@ -238,11 +240,13 @@ dependencies = [ "access_control", "ink_env", "ink_lang", + "ink_metadata", "ink_prelude", "ink_primitives", "ink_storage", "openbrush", "parity-scale-codec", + "scale-info", ] [[package]] @@ -933,11 +937,13 @@ dependencies = [ "access_control", "ink_env", "ink_lang", + "ink_metadata", "ink_prelude", "ink_primitives", "ink_storage", "openbrush", "parity-scale-codec", + "scale-info", ] [[package]] diff --git a/contracts/marketplace/Cargo.toml b/contracts/marketplace/Cargo.toml index 62ef428c38..c8c7f8443a 100644 --- a/contracts/marketplace/Cargo.toml +++ b/contracts/marketplace/Cargo.toml @@ -41,10 +41,16 @@ std = [ "ink_metadata/std", "ink_primitives/std", "ink_storage/std", + "ink_prelude/std", + "ink_engine/std", "openbrush/std", "scale-info", "scale-info/std", "scale/std", + "openbrush/std", + "access_control/std", + "game_token/std", + "ticket_token/std" ] ink-as-dependency = [] diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 5f7dc66d92..3a472e16fa 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -19,8 +19,6 @@ pub mod marketplace { TRANSFER_SELECTOR as TRANSFER_TICKET_SELECTOR, }; - use crate::marketplace::Error::MissingRole; - type Event = ::Type; const DUMMY_DATA: &[u8] = &[0x0]; @@ -37,12 +35,12 @@ pub mod marketplace { reward_token: AccountId, } - #[derive(Clone, Eq, PartialEq, Debug, scale::Encode, scale::Decode)] + #[derive(Eq, PartialEq, Debug, scale::Encode, scale::Decode)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum Error { - MissingRole(String), + MissingRole(Role), ContractCall(String), - PSP22TokenCall(String), + PSP22TokenCall(PSP22Error), MarketplaceEmpty, } @@ -58,12 +56,6 @@ pub mod marketplace { #[derive(Clone, Eq, PartialEq, Debug)] pub struct Reset; - impl Error { - fn missing_role(role: Role) -> Self { - MissingRole(format!("{:?}", role)) - } - } - impl From for Error { fn from(inner: ink_env::Error) -> Self { Error::ContractCall(format!("{:?}", inner)) @@ -72,7 +64,7 @@ pub mod marketplace { impl From for Error { fn from(inner: PSP22Error) -> Self { - Error::PSP22TokenCall(format!("{:?}", inner)) + Error::PSP22TokenCall(inner) } } @@ -156,6 +148,16 @@ pub mod marketplace { self.min_price } + /// Update the minimal price. + #[ink(message)] + pub fn set_min_price(&mut self, value: Balance) -> Result<(), Error> { + Self::ensure_role(self.admin())?; + + self.min_price = value; + + Ok(()) + } + /// Address of the reward token contract this contract will accept as payment. #[ink(message)] pub fn reward_token(&self) -> AccountId { @@ -271,7 +273,7 @@ pub mod marketplace { Self::env().caller(), role, |reason| reason.into(), - |role| Error::missing_role(role), + |role| Error::MissingRole(role), ) } From b5d1ffa699bcd4c1ff36a061b7e4318333e85901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Obrok?= Date: Mon, 12 Sep 2022 17:40:44 +0200 Subject: [PATCH 8/8] Add max_price to buy() --- contracts/marketplace/lib.rs | 62 ++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/contracts/marketplace/lib.rs b/contracts/marketplace/lib.rs index 3a472e16fa..827c88bf96 100644 --- a/contracts/marketplace/lib.rs +++ b/contracts/marketplace/lib.rs @@ -1,3 +1,21 @@ +//! Implement a Dutch auction of one token for another. +//! +//! This contract will auction off units of one token (referred to as `tickets`), accepting payment +//! in another token (`reward token`). The auction keeps track of the average price over all sales +//! made (before the first auction, this number is set on contract initialization) and starts off +//! the auction at `average_price() * sale_multiplier()`. Afterwards the price decreases linearly +//! with each block until reaching `min_price()` after `auction_length()` blocks, at which point +//! the price stays at that level indefinitely. +//! +//! A user can use the `buy(max_price)` call (after issuing a `psp22::approve` for the appropriate +//! amount to the reward token contract) to accept the current price and buy one ticket. This +//! transaction will fail if the price increased to above `max_price`, for example, due to a ticket +//! getting sold. +//! +//! The admin of the contract is expected to transfer a number of reward tokens for sale +//! into this contract and then call `reset()` in the same transaction to begin the auction. Calling +//! `reset()` if an auction is already in progress. + #![cfg_attr(not(feature = "std"), no_std)] #![feature(min_specialization)] @@ -41,6 +59,7 @@ pub mod marketplace { MissingRole(Role), ContractCall(String), PSP22TokenCall(PSP22Error), + MaxPriceExceeded, MarketplaceEmpty, } @@ -173,30 +192,39 @@ pub mod marketplace { /// Buy one ticket at the current_price. /// /// The caller should make an approval for at least `price()` reward tokens to make sure the - /// call will succeed. + /// call will succeed. The caller can specify a `max_price` - the call will fail if the + /// current price is greater than that. #[ink(message)] - pub fn buy(&mut self) -> Result<(), Error> { - if self.ticket_balance()? > 0 { - let price = self.current_price(); - let account_id = self.env().caller(); - - self.take_payment(account_id, price)?; - self.give_ticket(account_id)?; - - self.total_proceeds = self.total_proceeds.saturating_add(price); - self.tickets_sold = self.tickets_sold.saturating_add(1); - self.current_start_block = self.env().block_number(); - Self::emit_event(self.env(), Event::Bought(Bought { price, account_id })); - - Ok(()) - } else { - Err(Error::MarketplaceEmpty) + pub fn buy(&mut self, max_price: Option) -> Result<(), Error> { + if self.ticket_balance()? <= 0 { + return Err(Error::MarketplaceEmpty); + } + + let price = self.current_price(); + if let Some(max_price) = max_price { + if price > max_price { + return Err(Error::MaxPriceExceeded); + } } + + let account_id = self.env().caller(); + + self.take_payment(account_id, price)?; + self.give_ticket(account_id)?; + + self.total_proceeds = self.total_proceeds.saturating_add(price); + self.tickets_sold = self.tickets_sold.saturating_add(1); + self.current_start_block = self.env().block_number(); + Self::emit_event(self.env(), Event::Bought(Bought { price, account_id })); + + Ok(()) } /// Re-start the auction from the current block. /// /// Note that this will keep the average estimate from previous auctions. + /// + /// Requires `Role::Admin`. #[ink(message)] pub fn reset(&mut self) -> Result<(), Error> { Self::ensure_role(self.admin())?;