From c5c628cddceaa6db4e23cad33f02afcd5c0c6714 Mon Sep 17 00:00:00 2001 From: mihaicalinluca Date: Wed, 4 Oct 2023 11:47:53 +0200 Subject: [PATCH 1/5] mvx game sc first draft --- Cargo.toml | 2 + contracts/mvx-game-sc/.gitignore | 10 + contracts/mvx-game-sc/Cargo.toml | 15 + contracts/mvx-game-sc/README.md | 3 + contracts/mvx-game-sc/meta/Cargo.toml | 14 + contracts/mvx-game-sc/meta/src/main.rs | 3 + contracts/mvx-game-sc/multiversx.json | 3 + contracts/mvx-game-sc/mxpy-up.py | 379 ++++++++++++++++++ contracts/mvx-game-sc/mxsc-template.toml | 19 + .../mvx-game-sc/scenarios/adder.scen.json | 99 +++++ .../scenarios/interactor_trace.scen.json | 81 ++++ contracts/mvx-game-sc/src/lib.rs | 87 ++++ contracts/mvx-game-sc/src/owner.rs | 75 ++++ contracts/mvx-game-sc/src/private.rs | 177 ++++++++ contracts/mvx-game-sc/src/storage.rs | 38 ++ contracts/mvx-game-sc/src/types.rs | 39 ++ contracts/mvx-game-sc/wasm/Cargo.lock | 219 ++++++++++ contracts/mvx-game-sc/wasm/Cargo.toml | 27 ++ contracts/mvx-game-sc/wasm/src/lib.rs | 43 ++ 19 files changed, 1333 insertions(+) create mode 100644 contracts/mvx-game-sc/.gitignore create mode 100644 contracts/mvx-game-sc/Cargo.toml create mode 100644 contracts/mvx-game-sc/README.md create mode 100644 contracts/mvx-game-sc/meta/Cargo.toml create mode 100644 contracts/mvx-game-sc/meta/src/main.rs create mode 100644 contracts/mvx-game-sc/multiversx.json create mode 100644 contracts/mvx-game-sc/mxpy-up.py create mode 100644 contracts/mvx-game-sc/mxsc-template.toml create mode 100644 contracts/mvx-game-sc/scenarios/adder.scen.json create mode 100644 contracts/mvx-game-sc/scenarios/interactor_trace.scen.json create mode 100644 contracts/mvx-game-sc/src/lib.rs create mode 100644 contracts/mvx-game-sc/src/owner.rs create mode 100644 contracts/mvx-game-sc/src/private.rs create mode 100644 contracts/mvx-game-sc/src/storage.rs create mode 100644 contracts/mvx-game-sc/src/types.rs create mode 100644 contracts/mvx-game-sc/wasm/Cargo.lock create mode 100644 contracts/mvx-game-sc/wasm/Cargo.toml create mode 100644 contracts/mvx-game-sc/wasm/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 93e74297..05451ba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,4 +67,6 @@ members = [ "contracts/seed-nft-minter/meta", "contracts/token-release", "contracts/token-release/meta", + "contracts/mvx-game-sc", + "contracts/mvx-game-sc/meta" ] diff --git a/contracts/mvx-game-sc/.gitignore b/contracts/mvx-game-sc/.gitignore new file mode 100644 index 00000000..dd49a952 --- /dev/null +++ b/contracts/mvx-game-sc/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +*/target/ + +# The mxpy output +/output*/ + +# Mandos test trace +trace*.scen.json diff --git a/contracts/mvx-game-sc/Cargo.toml b/contracts/mvx-game-sc/Cargo.toml new file mode 100644 index 00000000..94c08f4b --- /dev/null +++ b/contracts/mvx-game-sc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "mvx-game-sc" +version = "0.0.0" +authors = [ "you",] +edition = "2018" +publish = false + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.43.4" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.43.4" diff --git a/contracts/mvx-game-sc/README.md b/contracts/mvx-game-sc/README.md new file mode 100644 index 00000000..064dcfce --- /dev/null +++ b/contracts/mvx-game-sc/README.md @@ -0,0 +1,3 @@ +# lib + +`lib` is a simple Smart Contract. diff --git a/contracts/mvx-game-sc/meta/Cargo.toml b/contracts/mvx-game-sc/meta/Cargo.toml new file mode 100644 index 00000000..77f0a821 --- /dev/null +++ b/contracts/mvx-game-sc/meta/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mvx-game-sc-meta" +version = "0.0.0" +edition = "2018" +publish = false +authors = [ "you",] + +[dev-dependencies] + +[dependencies.mvx-game-sc] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.43.4" diff --git a/contracts/mvx-game-sc/meta/src/main.rs b/contracts/mvx-game-sc/meta/src/main.rs new file mode 100644 index 00000000..22154128 --- /dev/null +++ b/contracts/mvx-game-sc/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/contracts/mvx-game-sc/multiversx.json b/contracts/mvx-game-sc/multiversx.json new file mode 100644 index 00000000..73655396 --- /dev/null +++ b/contracts/mvx-game-sc/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/mvx-game-sc/mxpy-up.py b/contracts/mvx-game-sc/mxpy-up.py new file mode 100644 index 00000000..8cfacc3e --- /dev/null +++ b/contracts/mvx-game-sc/mxpy-up.py @@ -0,0 +1,379 @@ +import logging +import os +import os.path +import shutil +import stat +import subprocess +import sys +from argparse import ArgumentParser +from pathlib import Path +from typing import List, Tuple + +logger = logging.getLogger("installer") + +MIN_REQUIRED_PYTHON_VERSION = (3, 8, 0) +sdk_path = Path("~/multiversx-sdk").expanduser().resolve() + + +def main(): + parser = ArgumentParser() + parser.add_argument("--exact-version", help="the exact version of mxpy to install") + parser.add_argument("--from-branch", help="use a branch of multiversx/mx-sdk-py-cli") + parser.add_argument("--not-interactive", action="store_true", default=False) + parser.add_argument("--verbose", action="store_true", default=False) + parser.set_defaults(modify_path=True) + args = parser.parse_args() + + exact_version = args.exact_version + from_branch = args.from_branch + interactive = not args.not_interactive + verbose = args.verbose + + log_level = logging.DEBUG if verbose else logging.INFO + logging.basicConfig(level=log_level) + + if get_operating_system() == "windows": + print(""" +IMPORTANT NOTE +============== + +Windows support is limited and experimental. +""") + confirm_continuation(interactive) + + guard_non_root_user() + guard_python_version() + migrate_v6(interactive) + + # In case of a fresh install: + sdk_path.mkdir(parents=True, exist_ok=True) + create_venv() + logger.info("Installing the necessary dependencies...") + install_mxpy(exact_version, from_branch, verbose) + + run_post_install_checks() + + if interactive: + guide_system_path_integration() + + +def guard_non_root_user(): + logger.debug("Checking user (should not be root).") + + operating_system = get_operating_system() + + if operating_system == "windows": + return + if os.getuid() == 0: + raise InstallError("You should not install mxpy as root.") + + +def guard_python_version(): + python_version = (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) + + logger.debug("Checking Python version.") + logger.debug(f"Python version: {format_version(python_version)}") + if python_version < MIN_REQUIRED_PYTHON_VERSION: + raise InstallError(f"You need Python {format_version(MIN_REQUIRED_PYTHON_VERSION)} or later.") + + +def format_version(version: Tuple[int, int, int]) -> str: + major, minor, patch = version + return f"{major}.{minor}.{patch}" + + +def get_operating_system(): + aliases = { + "linux": "linux", + "linux1": "linux", + "linux2": "linux", + "darwin": "osx", + "win32": "windows", + "cygwin": "windows", + "msys": "windows" + } + + operating_system = aliases.get(sys.platform) + if operating_system is None: + raise InstallError(f"Unknown platform: {sys.platform}") + + return operating_system + + +def migrate_v6(interactive: bool): + nodejs_folder = sdk_path / "nodejs" + + if nodejs_folder.exists(): + print(f""" +In previous versions of the SDK, the "wasm-opt" tool was installed in the "nodejs" folder. + +This is no longer the case - now, "wasm-opt" is a separate module. + +The following folder will be removed: {nodejs_folder}. + +You may need to reinstall wasm-opt using `mxpy deps install wasm-opt`. +""") + confirm_continuation(interactive) + + shutil.rmtree(nodejs_folder) + + global_testnet_toml = sdk_path / "testnet.toml" + if global_testnet_toml.exists(): + global_testnet_toml.unlink() + + +def create_venv(): + require_python_venv_tools() + venv_folder = get_mxpy_venv_path() + venv_folder.mkdir(parents=True, exist_ok=True) + + logger.debug(f"Creating virtual environment in: {venv_folder}.") + import venv + builder = venv.EnvBuilder(with_pip=True, symlinks=True) + builder.clear_directory(venv_folder) + builder.create(venv_folder) + + logger.debug(f"Virtual environment has been created in: {venv_folder}.") + + +def require_python_venv_tools(): + operating_system = get_operating_system() + + try: + import ensurepip + import venv + logger.debug(f"Packages found: {ensurepip}, {venv}.") + except ModuleNotFoundError: + if operating_system == "linux": + python_venv = f"python{sys.version_info.major}.{sys.version_info.minor}-venv" + raise InstallError(f'Packages [venv] or [ensurepip] not found. Please run "sudo apt install {python_venv}" and then run mxpy-up again.') + else: + raise InstallError("Packages [venv] or [ensurepip] not found, please install them first. See https://docs.python.org/3/tutorial/venv.html.") + + +def get_mxpy_venv_path(): + return sdk_path / "mxpy-venv" + + +def install_mxpy(exact_version: str, from_branch: str, verbose: bool): + logger.debug("Installing mxpy in virtual environment...") + + if from_branch: + package_to_install = f"https://github.com/multiversx/mx-sdk-py-cli/archive/refs/heads/{from_branch}.zip" + else: + package_to_install = "multiversx_sdk_cli" if not exact_version else f"multiversx_sdk_cli=={exact_version}" + + venv_path = get_mxpy_venv_path() + + return_code = run_in_venv(["python3", "-m", "pip", "install", "--upgrade", "pip"], venv_path, verbose) + if return_code != 0: + raise InstallError("Could not upgrade pip.") + return_code = run_in_venv(["pip3", "install", "--no-cache-dir", package_to_install], venv_path, verbose) + if return_code != 0: + raise InstallError("Could not install mxpy.") + + logger.debug("Creating mxpy shortcut...") + + shortcut_path = sdk_path / "mxpy" + + try: + shortcut_path.unlink() + logger.debug(f"Removed existing shortcut: {shortcut_path}") + except FileNotFoundError: + logger.debug(f"Shortcut does not exist yet: {shortcut_path}") + pass + + shortcut_content = get_mxpy_shortcut_content() + shortcut_path.write_text(shortcut_content) + + st = os.stat(shortcut_path) + os.chmod(shortcut_path, st.st_mode | stat.S_IEXEC) + + logger.info("You have successfully installed mxpy.") + + +def get_mxpy_shortcut_content(): + operating_system = get_operating_system() + venv_path = get_mxpy_venv_path() + + if operating_system == "windows": + return f"""#!/bin/sh +. "{venv_path / 'Scripts' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate +""" + + return f"""#!/bin/sh +. "{venv_path / 'bin' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate +""" + + +def run_in_venv(args: List[str], venv_path: Path, verbose: bool): + env = os.environ.copy() + + if "PYTHONHOME" in env: + del env["PYTHONHOME"] + + env["PATH"] = str(venv_path / "bin") + ":" + env["PATH"] + env["VIRTUAL_ENV"] = str(venv_path) + + if verbose: + process = subprocess.Popen(args, env=env) + else: + process = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + + return process.wait() + + +def run_post_install_checks(): + multiversx_sdk_path = Path("~/multiversx-sdk").expanduser() + + logger.debug("Running post-install checks...") + + if multiversx_sdk_path.exists(): + logger.debug("~/multiversx-sdk exists OK") + else: + logger.warning("~/multiversx-sdk exists NOK") + + if (multiversx_sdk_path / "mxpy").exists(): + logger.debug("~/multiversx-sdk/mxpy shortcut created OK") + else: + logger.warning("~/multiversx-sdk/mxpy shortcut created NOK") + + +def guide_system_path_integration(): + interactive = True + operating_system = get_operating_system() + + if operating_system == "windows": + print(f""" +############################################################################### +On Windows, for the "mxpy" command shortcut to be available, you MUST ADD the directory "{sdk_path}" to the system PATH. + +You can do this by following these steps: + +https://superuser.com/questions/949560/how-do-i-set-system-environment-variables-in-windows-10 + +############################################################################### +Do you UNDERSTAND the above? +############################################################################### +""") + confirm_continuation(interactive) + return + + old_export_directive = f'export PATH="{Path("~/elrondsdk").expanduser()}:$PATH"\t# elrond-sdk' + new_export_directive = f'export PATH="${{HOME}}/multiversx-sdk:$PATH"\t# multiversx-sdk' + + profile_files = get_profile_files() + + if not profile_files: + print(f""" +############################################################################### +No shell profile files have been found. + +The "mxpy" command shortcut will not be available until you add the directory "{sdk_path}" to the system PATH. +############################################################################### +Do you UNDERSTAND the above? +""") + confirm_continuation(interactive) + return + + profile_files_formatted = "\n".join(f" - {file}" for file in profile_files) + profile_files_contents = [profile_file.read_text() for profile_file in profile_files] + any_old_export_directive = any(old_export_directive in content for content in profile_files_contents) + any_new_export_directive = any(new_export_directive in content for content in profile_files_contents) + + if any_old_export_directive: + # We don't perform the removal automatically (a bit risky) + print(f""" +############################################################################### +It seems that the old path "~/elrondsdk" is still configured in shell profile. + +Please MANUALLY remove it from the shell profile (now or after the installer script ends). + +Your shell profile files: +{profile_files_formatted} + +The entry (entries) to remove: + {old_export_directive} +############################################################################### +Make sure you UNDERSTAND the above before proceeding further. +############################################################################### +""") + confirm_continuation(interactive) + + if any_new_export_directive: + # Note: in some (rare) cases, here we'll have false positives (e.g. if the export directive is commented out). + print(f""" +############################################################################### +It seems that the path "~/multiversx-sdk" is already configured in shell profile. + +To confirm this, CHECK the shell profile (now or after the installer script ends). + +Your shell profile files: +{profile_files_formatted} + +The entry to check (it should be present): + {new_export_directive}. +############################################################################### +Make sure you UNDERSTAND the above before proceeding further. +############################################################################### +""") + confirm_continuation(interactive) + return + + print(f""" +############################################################################### +In order to use the "mxpy" command shortcut, you have to manually extend the PATH variable to include "~/multiversx-sdk". + +In order to manually extend the PATH variable, add the following line to your shell profile file upon installation: + + export PATH="${{HOME}}/multiversx-sdk:${{PATH}}" + +Your shell profile files: +{profile_files_formatted} + +Upon editing the shell profile file, you may have to RESTART THE USER SESSION for the changes to take effect. +""") + confirm_continuation(interactive) + + +def get_profile_files() -> List[Path]: + files = [ + Path("~/.profile").expanduser().resolve(), + Path("~/.bashrc").expanduser().resolve(), + Path("~/.bash_profile").expanduser().resolve(), + Path("~/.zshrc").expanduser().resolve() + ] + + return [file for file in files if file.exists()] + + +class InstallError(Exception): + def __init__(self, message: str): + super().__init__(message) + + +def confirm_continuation(interactive: bool): + if not interactive: + return + + answer = input("Continue? (y/n)") + if answer.lower() not in ["y", "yes"]: + print("Confirmation not given. Will stop.") + exit(1) + + +if __name__ == "__main__": + try: + main() + except Exception as err: + logger.fatal(err) + sys.exit(1) + + print(""" +############################################################################### +Installer script finished successfully. +############################################################################### +For more information go to https://docs.multiversx.com. +For support, please contact us at http://discord.gg/MultiversXBuilders. +############################################################################### +""") diff --git a/contracts/mvx-game-sc/mxsc-template.toml b/contracts/mvx-game-sc/mxsc-template.toml new file mode 100644 index 00000000..7c73decd --- /dev/null +++ b/contracts/mvx-game-sc/mxsc-template.toml @@ -0,0 +1,19 @@ +name = "lib" +contract_trait = "lib" +src_file = "lib.rs" +rename_pairs = [ + [ + "blockchain.set_current_dir_from_workspace(\"contracts/examples/lib\");", + "// blockchain.set_current_dir_from_workspace(\"relative path to your workspace, if applicable\");", + ], +] +files_include = [ + "meta", + "scenarios", + "src", + "tests", + "wasm/Cargo.toml", + "Cargo.toml", + "README.md", + "multiversx.json", +] diff --git a/contracts/mvx-game-sc/scenarios/adder.scen.json b/contracts/mvx-game-sc/scenarios/adder.scen.json new file mode 100644 index 00000000..b031d1e5 --- /dev/null +++ b/contracts/mvx-game-sc/scenarios/adder.scen.json @@ -0,0 +1,99 @@ +{ + "name": "mvx-game-sc", + "comment": "add then check", + "gasSchedule": "v3", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "1", + "balance": "0" + } + }, + "newAddresses": [ + { + "creatorAddress": "address:owner", + "creatorNonce": "1", + "newAddress": "sc:lib" + } + ] + }, + { + "step": "scDeploy", + "id": "1", + "tx": { + "from": "address:owner", + "contractCode": "file:../output/mvx-game-sc.wasm", + "arguments": [ + "5" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scQuery", + "id": "2", + "tx": { + "to": "sc:lib", + "function": "getSum", + "arguments": [] + }, + "expect": { + "out": [ + "5" + ], + "status": "", + "logs": [] + } + }, + { + "step": "scCall", + "id": "3", + "tx": { + "from": "address:owner", + "to": "sc:lib", + "function": "add", + "arguments": [ + "3" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "*", + "balance": "0", + "storage": {}, + "code": "" + }, + "sc:lib": { + "nonce": "0", + "balance": "0", + "storage": { + "str:sum": "8" + }, + "code": "file:../output/mvx-game-sc.wasm" + } + } + } + ] +} \ No newline at end of file diff --git a/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json b/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json new file mode 100644 index 00000000..3337e6f8 --- /dev/null +++ b/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json @@ -0,0 +1,81 @@ +{ + "steps": [ + { + "step": "setState", + "accounts": { + "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60": { + "nonce": "481", + "balance": "106274669842530000003", + "esdt": { + "str:CAN-14dc0a": "1000", + "str:CAN-2abf4b": "1000", + "str:CAN-6d39e6": "1000", + "str:CAN-ac1592": "1000" + }, + "username": "" + } + } + }, + { + "step": "setState", + "newAddresses": [ + { + "creatorAddress": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", + "creatorNonce": "481", + "newAddress": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60" + } + ] + }, + { + "step": "scDeploy", + "id": "", + "tx": { + "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", + "contractCode": "file:../output/mvx-game-sc.wasm", + "arguments": [ + "0x00" + ], + "gasLimit": "70,000,000", + "gasPrice": "" + }, + "expect": { + "status": "0" + } + }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", + "to": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60", + "function": "add", + "arguments": [ + "0x07" + ], + "gasLimit": "70,000,000", + "gasPrice": "" + }, + "expect": { + "status": "0" + } + }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", + "to": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60", + "function": "add", + "arguments": [ + "0x05" + ], + "gasLimit": "70,000,000", + "gasPrice": "" + }, + "expect": { + "status": "0" + } + } + ], + "name": "" +} \ No newline at end of file diff --git a/contracts/mvx-game-sc/src/lib.rs b/contracts/mvx-game-sc/src/lib.rs new file mode 100644 index 00000000..d5699b0c --- /dev/null +++ b/contracts/mvx-game-sc/src/lib.rs @@ -0,0 +1,87 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub mod owner; +pub mod private; +pub mod storage; +pub mod types; + +#[multiversx_sc::contract] +pub trait MvxGameSc: storage::StorageModule + owner::OwnerModule + private::PrivateModule { + #[init] + fn init( + &self, + enabled_opt: OptionalValue, + game_start_fee_opt: OptionalValue, + token_id_opt: OptionalValue, + ) { + match enabled_opt { + OptionalValue::Some(_) => self.enabled().set(true), + OptionalValue::None => {} + } + + match game_start_fee_opt { + OptionalValue::Some(val) => self.game_start_fee().set(val), + OptionalValue::None => { + require!(!self.game_start_fee().is_empty(), "game start fee not set") + } + } + + match token_id_opt { + OptionalValue::Some(val) => self.token_id().set(val), + OptionalValue::None => require!(!self.token_id().is_empty(), "fee token id not set"), + } + } + + #[payable("*")] + #[endpoint(createGame)] + fn create_game( + &self, + waiting_time: u64, + number_of_players_min: u64, + number_of_players_max: u64, + wager: BigUint, + ) { + self.require_enabled(); + + let (token_id, amount) = self.call_value().single_fungible_esdt(); + self.validate_create_game_payment(&token_id, &amount, &wager, waiting_time); + + let (min, max) = self.get_min_max(number_of_players_min, number_of_players_max); + + let caller = self.blockchain().get_caller(); + self.create_new_game(caller, waiting_time, min, max, wager); + } + + #[payable("*")] + #[endpoint(joinGame)] + fn join_game(&self, game_id: u64) { + self.require_enabled(); + + let (token_id, amount) = self.call_value().single_fungible_esdt(); + let now = self.blockchain().get_block_timestamp(); + let caller = self.blockchain().get_caller(); + + let game_settings = self.validate_join_game(&caller, now, &token_id, &amount, game_id); + + self.add_player(caller, game_id); + + self.refresh_game_status(game_id, game_settings); + } + + //manually claim back wager if the game is invalid + #[endpoint(claimBackWager)] + fn claim_back_wager(&self, game_id: u64) { + self.require_enabled(); + + let caller = self.blockchain().get_caller(); + let wager = self.validate_claim_wager(&caller, game_id); + + let token_id = self.token_id().get(); + self.send().direct(&caller, &token_id, 0u64, &wager); + + self.remove_player(caller, game_id); + } +} diff --git a/contracts/mvx-game-sc/src/owner.rs b/contracts/mvx-game-sc/src/owner.rs new file mode 100644 index 00000000..6d0f69eb --- /dev/null +++ b/contracts/mvx-game-sc/src/owner.rs @@ -0,0 +1,75 @@ +use crate::types::Status; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait OwnerModule: crate::private::PrivateModule + crate::storage::StorageModule { + //u64 is percentage * 100 + //function called by the owner when the winners have been decided + #[only_owner] + #[endpoint(sendReward)] + fn send_reward( + &self, + game_id: u64, + winners: OptionalValue>, + ) { + self.require_enabled(); + + let game_settings = self.validate_send_reward(game_id); + let token_id = self.token_id().get(); + + match game_settings.status { + Status::Invalid => { + self.send_back_wager(game_id, &game_settings.wager, &token_id); + + let game_creation_fee = self.game_start_fee().get(); + self.send() + .direct(&game_settings.creator, &token_id, 0u64, &game_creation_fee); + } + Status::Valid => { + match winners { + OptionalValue::Some(val) => { + let len = self.players(game_id).len(); + let total_wager = &BigUint::from(len) * &game_settings.wager; + + for (winner, percentage) in val.into_iter() { + let reward_per_winner = + &BigUint::from(percentage) * &total_wager / &BigUint::from(100u64); + self.send() + .direct(&winner, &token_id, 0u64, &reward_per_winner); + } + } + //tie/draw + OptionalValue::None => { + self.send_back_wager(game_id, &game_settings.wager, &token_id); + } + } + } + } + } + + #[only_owner] + #[endpoint(enableSC)] + fn enable_sc(&self) { + self.enabled().set(true) + } + + #[only_owner] + #[endpoint(disableSC)] + fn disable_sc(&self) { + self.enabled().clear() + } + + #[only_owner] + #[endpoint(setTokenId)] + fn set_token_id(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.token_id().set(token_id) + } + + #[only_owner] + #[endpoint(setGameStartFee)] + fn set_game_start_fee(&self, amount: BigUint) { + self.game_start_fee().set(amount) + } +} diff --git a/contracts/mvx-game-sc/src/private.rs b/contracts/mvx-game-sc/src/private.rs new file mode 100644 index 00000000..861e48e0 --- /dev/null +++ b/contracts/mvx-game-sc/src/private.rs @@ -0,0 +1,177 @@ +use crate::types::{GameSettings, Status}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait PrivateModule: crate::storage::StorageModule { + //game + fn create_new_game( + &self, + caller: ManagedAddress, + waiting_time: u64, + min: u64, + max: u64, + wager: BigUint, + ) { + let new_id = self.get_new_game_id(); + self.last_game_id().set(new_id); + let now = self.blockchain().get_block_timestamp(); + + let time_limit = now + waiting_time; + let game_settings = GameSettings { + time_limit, + number_of_players_min: min, + number_of_players_max: max, + wager, + creator: caller, + status: Status::Invalid, + }; + self.game_settings(new_id).set(game_settings); + } + + fn add_player(&self, caller: ManagedAddress, game_id: u64) { + self.games_per_user(&caller).insert(game_id); + self.players(game_id).insert(caller); + } + + fn remove_player(&self, caller: ManagedAddress, game_id: u64) { + self.games_per_user(&caller).swap_remove(&game_id); + self.players(game_id).swap_remove(&caller); + } + + fn refresh_game_status(&self, game_id: u64, game_settings: GameSettings) { + let len = self.players(game_id).len() as u64; + if game_settings.number_of_players_min <= len { + self.game_settings(game_id) + .update(|val| val.status = Status::Valid); + } + } + + fn send_back_wager(&self, game_id: u64, wager: &BigUint, token_id: &EgldOrEsdtTokenIdentifier) { + for player in self.players(game_id).iter() { + self.send().direct(&player, token_id, 0u64, wager); + } + } + + //requires + fn validate_create_game_payment( + &self, + token_id: &TokenIdentifier, + amount: &BigUint, + wager: &BigUint, + waiting_time: u64, + ) { + require!(wager > &BigUint::zero(), "wager can't be 0"); + require!(waiting_time > 0u64, "waiting time can't be 0"); + + let approved_token_id = self.token_id().get(); + let start_fee = self.game_start_fee().get(); + + require!( + token_id == &approved_token_id && amount == &start_fee, + "start game payment not right" + ); + } + + fn validate_join_game( + &self, + caller: &ManagedAddress, + now: u64, + token_id: &TokenIdentifier, + amount: &BigUint, + game_id: u64, + ) -> GameSettings { + require!( + !self.game_settings(game_id).is_empty(), + "no settings for game id" + ); + let game_settings = self.game_settings(game_id).get(); + let accepted_token_id = self.token_id().get(); + + require!( + !self.games_per_user(caller).contains(&game_id), + "user already joined this game" + ); + + require!(now <= game_settings.time_limit, "waiting time has passed"); + + let len = self.players(game_id).len() as u64; + require!( + len < game_settings.number_of_players_max, + "max number of players reached" + ); + + require!(token_id == &accepted_token_id, "wrong token sent"); + require!(amount == &game_settings.wager, "wrong amount paid"); + + game_settings + } + + fn validate_claim_wager(&self, caller: &ManagedAddress, game_id: u64) -> BigUint { + require!( + !self.game_settings(game_id).is_empty(), + "no settings for game id" + ); + + require!( + self.games_per_user(caller).contains(&game_id), + "caller has not joined the game" + ); + + let game_settings = self.game_settings(game_id).get(); + let now = self.blockchain().get_block_timestamp(); + + require!( + now > game_settings.time_limit, + "waiting time is not over yet" + ); + + require!( + game_settings.status == Status::Invalid, + "can manually claim back wager only if the game is invalid" + ); + + game_settings.wager + } + + fn validate_send_reward(&self, game_id: u64) -> GameSettings { + require!( + !self.game_settings(game_id).is_empty(), + "no settings for game id" + ); + + let game_settings = self.game_settings(game_id).get(); + let now = self.blockchain().get_block_timestamp(); + + require!( + now > game_settings.time_limit, + "waiting time is not over yet" + ); + + game_settings + } + + fn require_enabled(&self) { + require!(!self.enabled().is_empty(), "maintenance") + } + + //helper + fn get_new_game_id(&self) -> u64 { + if self.last_game_id().is_empty() { + return 1u64; + } + let last_id = self.last_game_id().get(); + return last_id + 1u64; + } + + fn get_min_max(&self, a: u64, b: u64) -> (u64, u64) { + require!(a != 0u64 && b != 0u64, "number of players cannot be 0"); + + if a > b { + return (b, a); + } + + (a, b) + } +} diff --git a/contracts/mvx-game-sc/src/storage.rs b/contracts/mvx-game-sc/src/storage.rs new file mode 100644 index 00000000..35383377 --- /dev/null +++ b/contracts/mvx-game-sc/src/storage.rs @@ -0,0 +1,38 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use crate::types::GameSettings; + +#[multiversx_sc::module] +pub trait StorageModule { + //GENERAL SETTINGS + #[view(getTokenId)] + #[storage_mapper("tokenId")] + fn token_id(&self) -> SingleValueMapper; + + #[view(getGameStartFee)] + #[storage_mapper("gameStartFee")] + fn game_start_fee(&self) -> SingleValueMapper; + + #[view(getEnabled)] + #[storage_mapper("enabled")] + fn enabled(&self) -> SingleValueMapper; + + //GAME + #[view(getLastGameId)] + #[storage_mapper("lastGameId")] + fn last_game_id(&self) -> SingleValueMapper; + + #[view(getGameSettings)] + #[storage_mapper("gameSettings")] + fn game_settings(&self, game_id: u64) -> SingleValueMapper>; + + #[view(getPlayers)] + #[storage_mapper("players")] + fn players(&self, game_id: u64) -> UnorderedSetMapper; + + //USERS + #[view(getGamesPerUser)] + #[storage_mapper("gamesPerUser")] + fn games_per_user(&self, user: &ManagedAddress) -> UnorderedSetMapper; +} diff --git a/contracts/mvx-game-sc/src/types.rs b/contracts/mvx-game-sc/src/types.rs new file mode 100644 index 00000000..e4e61392 --- /dev/null +++ b/contracts/mvx-game-sc/src/types.rs @@ -0,0 +1,39 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + + +#[derive( + TopEncode, + TopDecode, + TypeAbi, + NestedEncode, + NestedDecode, + Clone, + ManagedVecItem, + Debug, + PartialEq, +)] +pub enum Status { + Valid, + Invalid, +} + +#[derive( + TopEncode, + TopDecode, + TypeAbi, + NestedEncode, + NestedDecode, + Clone, + ManagedVecItem, + Debug, + PartialEq, +)] +pub struct GameSettings { + pub time_limit: u64, //start_time + waiting time + pub number_of_players_min: u64, //min and max + pub number_of_players_max: u64, + pub wager: BigUint, + pub creator: ManagedAddress, + pub status: Status +} diff --git a/contracts/mvx-game-sc/wasm/Cargo.lock b/contracts/mvx-game-sc/wasm/Cargo.lock new file mode 100644 index 00000000..abc2a424 --- /dev/null +++ b/contracts/mvx-game-sc/wasm/Cargo.lock @@ -0,0 +1,219 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "multiversx-sc" +version = "0.43.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406939660d0c79dd191c6677f4b048df873a95f4531d8abafc9cdbe282bf1725" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f1e15b46c17b87c0c7cdd79b041a4abd7f3a2b45f3c993f6ce38c0f233e82b6" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7bc0762cd6d88f8bc54805bc652b042a61cd7fbc2d0a325010f088b78fb2ac" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.43.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e006240993963b482fe0682ae49b2d07255495e3c86706925d119137376cdfc" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.43.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e721d1bc80de2ede4099a9040519486c3c1139cb0287d8fc4f9fc3e8a3f19e" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "mvx-game-sc" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "mvx-game-sc-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "mvx-game-sc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[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 = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/contracts/mvx-game-sc/wasm/Cargo.toml b/contracts/mvx-game-sc/wasm/Cargo.toml new file mode 100644 index 00000000..bc6eb69b --- /dev/null +++ b/contracts/mvx-game-sc/wasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "mvx-game-sc-wasm" +version = "0.0.0" +authors = [ "you",] +edition = "2018" +publish = false + +[lib] +crate-type = [ "cdylib",] + +[workspace] +members = [ ".",] + +[dev-dependencies] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" + +[dependencies.mvx-game-sc] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.43.4" diff --git a/contracts/mvx-game-sc/wasm/src/lib.rs b/contracts/mvx-game-sc/wasm/src/lib.rs new file mode 100644 index 00000000..23cdd65d --- /dev/null +++ b/contracts/mvx-game-sc/wasm/src/lib.rs @@ -0,0 +1,43 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 15 +// Async Callback (empty): 1 +// Total number of exported functions: 17 + +#![no_std] + +// Configuration that works with rustc < 1.73.0. +// TODO: Recommended rustc version: 1.73.0 or newer. +#![feature(lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + mvx_game_sc + ( + init => init + createGame => create_game + joinGame => join_game + claimBackWager => claim_back_wager + getTokenId => token_id + getGameStartFee => game_start_fee + getEnabled => enabled + getLastGameId => last_game_id + getGameSettings => game_settings + getPlayers => players + getGamesPerUser => games_per_user + sendReward => send_reward + enableSC => enable_sc + disableSC => disable_sc + setTokenId => set_token_id + setGameStartFee => set_game_start_fee + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} From 103b9a59f4040490a2b3312b2e3165366027c8a1 Mon Sep 17 00:00:00 2001 From: mihaicalinluca Date: Wed, 4 Oct 2023 12:52:06 +0200 Subject: [PATCH 2/5] fixed clippy --- contracts/mvx-game-sc/src/private.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mvx-game-sc/src/private.rs b/contracts/mvx-game-sc/src/private.rs index 861e48e0..d68e3b0e 100644 --- a/contracts/mvx-game-sc/src/private.rs +++ b/contracts/mvx-game-sc/src/private.rs @@ -162,7 +162,7 @@ pub trait PrivateModule: crate::storage::StorageModule { return 1u64; } let last_id = self.last_game_id().get(); - return last_id + 1u64; + last_id + 1u64 } fn get_min_max(&self, a: u64, b: u64) -> (u64, u64) { From dfe544fe40947bbe1e8841467782fa78387a5dd7 Mon Sep 17 00:00:00 2001 From: mihaicalinluca Date: Thu, 5 Oct 2023 00:13:48 +0200 Subject: [PATCH 3/5] added test setup and 2 simple tests --- contracts/mvx-game-sc/src/private.rs | 6 +- .../tests/game_sc_blackbox_tests.rs | 204 ++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs diff --git a/contracts/mvx-game-sc/src/private.rs b/contracts/mvx-game-sc/src/private.rs index d68e3b0e..3edce017 100644 --- a/contracts/mvx-game-sc/src/private.rs +++ b/contracts/mvx-game-sc/src/private.rs @@ -68,10 +68,8 @@ pub trait PrivateModule: crate::storage::StorageModule { let approved_token_id = self.token_id().get(); let start_fee = self.game_start_fee().get(); - require!( - token_id == &approved_token_id && amount == &start_fee, - "start game payment not right" - ); + require!(token_id == &approved_token_id, "wrong token id"); + require!(amount == &start_fee, "start game payment amount not right"); } fn validate_join_game( diff --git a/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs b/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs new file mode 100644 index 00000000..ef6a195b --- /dev/null +++ b/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs @@ -0,0 +1,204 @@ +use multiversx_sc::{ + codec::{multi_types::OptionalValue, CodecFrom}, + storage::mappers::SingleValue, + types::{Address, EgldOrEsdtTokenIdentifier, TokenIdentifier}, +}; +use multiversx_sc_scenario::{api::StaticApi, num_bigint::BigUint, scenario_model::*, *}; +use mvx_game_sc::{ + storage::{ProxyTrait, StorageModule}, + types::{GameSettings, Status}, + ProxyTrait as _, +}; + +const GAME_SC_PATH: &str = "file:output/mvx-game-sc.wasm"; +const BALANCE: u64 = 100_000_000u64; +const TOKEN_ID: &str = "str:GAME-123456"; +const TOKEN_ID_BY: &[u8] = b"GAME-123456"; +const STARTING_FEE: u64 = 20u64; +const USER1_ADDR: &str = "address:user1"; +const USER2_ADDR: &str = "address:user2"; +const OWNER_ADDR: &str = "address:owner"; +const GAME_SC_ADDR: &str = "sc:mvx_game_sc"; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/mvx-game-sc"); + + blockchain.register_contract(GAME_SC_PATH, mvx_game_sc::ContractBuilder); + blockchain +} + +struct GameContractState { + world: ScenarioWorld, + user1: AddressValue, + user2: AddressValue, + owner: AddressValue, +} + +impl GameContractState { + fn new() -> Self { + let mut world = world(); + world.start_trace().set_state_step( + SetStateStep::new() + .put_account( + OWNER_ADDR, + Account::new() + .nonce(1) + .balance(BALANCE) + .esdt_balance(TOKEN_ID, BALANCE), + ) + .put_account( + USER1_ADDR, + Account::new() + .nonce(2) + .balance(BALANCE) + .esdt_balance(TOKEN_ID, BALANCE), + ) + .put_account( + USER2_ADDR, + Account::new() + .nonce(3) + .balance(BALANCE) + .esdt_balance(TOKEN_ID, BALANCE), + ), + ); + + let user1 = AddressValue::from(USER1_ADDR); + let user2 = AddressValue::from(USER2_ADDR); + let owner = AddressValue::from(OWNER_ADDR); + + Self { + world, + user1, + user2, + owner, + } + } + + fn deploy(&mut self, game_sc: &mut ContractInfo>) -> &mut Self { + let game_sc_code = self.world.code_expression(GAME_SC_PATH); + + self.world + .set_state_step(SetStateStep::new().new_address(OWNER_ADDR, 1, GAME_SC_ADDR)) + .sc_deploy( + ScDeployStep::new() + .from(OWNER_ADDR) + .code(game_sc_code) + .call(game_sc.init( + OptionalValue::Some(true), + OptionalValue::Some(BigUint::from(STARTING_FEE)), + OptionalValue::Some(EgldOrEsdtTokenIdentifier::esdt( + TokenIdentifier::from(TOKEN_ID_BY), + )), + )) + .expect(TxExpect::ok().no_result()), + ); + + self + } + + fn create_game( + &mut self, + waiting_time: u64, + number_of_players_min: u64, + number_of_players_max: u64, + wager: BigUint, + caller: &str, + game_sc: &mut ContractInfo>, + ) -> &mut Self { + self.world.sc_call( + ScCallStep::new() + .from(caller) + .to(GAME_SC_ADDR) + .esdt_transfer(TOKEN_ID, 0u64, BigUint::from(STARTING_FEE)) + .call(game_sc.create_game( + waiting_time, + number_of_players_min, + number_of_players_max, + wager, + )) + .expect(TxExpect::ok().no_result()), + ); + + self + } + + fn join_game( + &mut self, + game_id: u64, + caller: &str, + amount: &BigUint, + game_sc: &mut ContractInfo>, + ) -> &mut Self { + self.world.sc_call( + ScCallStep::new() + .from(caller) + .to(GAME_SC_ADDR) + .esdt_transfer(TOKEN_ID, 0u64, amount) + .call(game_sc.join_game(game_id)) + .expect(TxExpect::ok().no_result()), + ); + self + } +} + +#[test] +fn game_sc_deploy_test() { + let mut state = GameContractState::new(); + let mut game_sc = ContractInfo::>::new(GAME_SC_ADDR); + + state.deploy(&mut game_sc); +} + +#[test] +fn game_sc_simple_game_flow() { + let mut state = GameContractState::new(); + let mut game_sc = ContractInfo::>::new(GAME_SC_ADDR); + + let waiting_time = 100u64; + let number_of_players_min = 1u64; + let number_of_players_max = 4u64; + let wager = BigUint::from(100u64); + + //deploy + state.deploy(&mut game_sc); + + //check last game id before creation + state.world.sc_query( + ScQueryStep::new() + .to(GAME_SC_ADDR) + .function("getLastGameId") + .expect(TxExpect::ok().result("")), + ); + + //create first game + state.create_game( + waiting_time, + number_of_players_min, + number_of_players_max, + wager.clone(), + OWNER_ADDR, + &mut game_sc, + ); + + //check last game id, needs to be 1 + state.world.sc_query( + ScQueryStep::new() + .to(GAME_SC_ADDR) + .function("getLastGameId") + .expect(TxExpect::ok().result("1")), + ); + + //user1 tries to join the game, timestamp is ok, max players not reached, should work + state.join_game(1u64, USER1_ADDR, &wager, &mut game_sc); + + //min number of players reached, game should be valid + let game_settings: SingleValue> = game_sc + .game_settings(1u64) + .into_vm_query() + .expect(TxExpect::ok()) + .execute(&mut state.world); + + assert_eq!(game_settings.into().status, Status::Valid); +} + From e4332c2fe9eb15d525759d1bb58aeab4690510d5 Mon Sep 17 00:00:00 2001 From: mihaicalinluca Date: Thu, 5 Oct 2023 00:27:53 +0200 Subject: [PATCH 4/5] fixed clippy for initial tests --- contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs b/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs index ef6a195b..41c0b50e 100644 --- a/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs +++ b/contracts/mvx-game-sc/tests/game_sc_blackbox_tests.rs @@ -1,11 +1,11 @@ use multiversx_sc::{ - codec::{multi_types::OptionalValue, CodecFrom}, + codec::multi_types::OptionalValue, storage::mappers::SingleValue, - types::{Address, EgldOrEsdtTokenIdentifier, TokenIdentifier}, + types::{EgldOrEsdtTokenIdentifier, TokenIdentifier}, }; use multiversx_sc_scenario::{api::StaticApi, num_bigint::BigUint, scenario_model::*, *}; use mvx_game_sc::{ - storage::{ProxyTrait, StorageModule}, + storage::ProxyTrait, types::{GameSettings, Status}, ProxyTrait as _, }; @@ -159,6 +159,9 @@ fn game_sc_simple_game_flow() { let number_of_players_min = 1u64; let number_of_players_max = 4u64; let wager = BigUint::from(100u64); + let _ = state.user1; + let _ = state.user2; + let _ = state.owner; //deploy state.deploy(&mut game_sc); From 4141bbc1621dde87beb761204966b78b680c35df Mon Sep 17 00:00:00 2001 From: mihaicalinluca Date: Thu, 5 Oct 2023 10:31:02 +0200 Subject: [PATCH 5/5] fix after review --- contracts/mvx-game-sc/Cargo.toml | 2 +- contracts/mvx-game-sc/mxpy-up.py | 379 ------------------ contracts/mvx-game-sc/mxsc-template.toml | 19 - .../mvx-game-sc/scenarios/adder.scen.json | 99 ----- .../scenarios/interactor_trace.scen.json | 81 ---- 5 files changed, 1 insertion(+), 579 deletions(-) delete mode 100644 contracts/mvx-game-sc/mxpy-up.py delete mode 100644 contracts/mvx-game-sc/mxsc-template.toml delete mode 100644 contracts/mvx-game-sc/scenarios/adder.scen.json delete mode 100644 contracts/mvx-game-sc/scenarios/interactor_trace.scen.json diff --git a/contracts/mvx-game-sc/Cargo.toml b/contracts/mvx-game-sc/Cargo.toml index 94c08f4b..94d8a570 100644 --- a/contracts/mvx-game-sc/Cargo.toml +++ b/contracts/mvx-game-sc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mvx-game-sc" version = "0.0.0" -authors = [ "you",] +authors = [ "Mihai Calin Luca calin.luca@multiversx.com",] edition = "2018" publish = false diff --git a/contracts/mvx-game-sc/mxpy-up.py b/contracts/mvx-game-sc/mxpy-up.py deleted file mode 100644 index 8cfacc3e..00000000 --- a/contracts/mvx-game-sc/mxpy-up.py +++ /dev/null @@ -1,379 +0,0 @@ -import logging -import os -import os.path -import shutil -import stat -import subprocess -import sys -from argparse import ArgumentParser -from pathlib import Path -from typing import List, Tuple - -logger = logging.getLogger("installer") - -MIN_REQUIRED_PYTHON_VERSION = (3, 8, 0) -sdk_path = Path("~/multiversx-sdk").expanduser().resolve() - - -def main(): - parser = ArgumentParser() - parser.add_argument("--exact-version", help="the exact version of mxpy to install") - parser.add_argument("--from-branch", help="use a branch of multiversx/mx-sdk-py-cli") - parser.add_argument("--not-interactive", action="store_true", default=False) - parser.add_argument("--verbose", action="store_true", default=False) - parser.set_defaults(modify_path=True) - args = parser.parse_args() - - exact_version = args.exact_version - from_branch = args.from_branch - interactive = not args.not_interactive - verbose = args.verbose - - log_level = logging.DEBUG if verbose else logging.INFO - logging.basicConfig(level=log_level) - - if get_operating_system() == "windows": - print(""" -IMPORTANT NOTE -============== - -Windows support is limited and experimental. -""") - confirm_continuation(interactive) - - guard_non_root_user() - guard_python_version() - migrate_v6(interactive) - - # In case of a fresh install: - sdk_path.mkdir(parents=True, exist_ok=True) - create_venv() - logger.info("Installing the necessary dependencies...") - install_mxpy(exact_version, from_branch, verbose) - - run_post_install_checks() - - if interactive: - guide_system_path_integration() - - -def guard_non_root_user(): - logger.debug("Checking user (should not be root).") - - operating_system = get_operating_system() - - if operating_system == "windows": - return - if os.getuid() == 0: - raise InstallError("You should not install mxpy as root.") - - -def guard_python_version(): - python_version = (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) - - logger.debug("Checking Python version.") - logger.debug(f"Python version: {format_version(python_version)}") - if python_version < MIN_REQUIRED_PYTHON_VERSION: - raise InstallError(f"You need Python {format_version(MIN_REQUIRED_PYTHON_VERSION)} or later.") - - -def format_version(version: Tuple[int, int, int]) -> str: - major, minor, patch = version - return f"{major}.{minor}.{patch}" - - -def get_operating_system(): - aliases = { - "linux": "linux", - "linux1": "linux", - "linux2": "linux", - "darwin": "osx", - "win32": "windows", - "cygwin": "windows", - "msys": "windows" - } - - operating_system = aliases.get(sys.platform) - if operating_system is None: - raise InstallError(f"Unknown platform: {sys.platform}") - - return operating_system - - -def migrate_v6(interactive: bool): - nodejs_folder = sdk_path / "nodejs" - - if nodejs_folder.exists(): - print(f""" -In previous versions of the SDK, the "wasm-opt" tool was installed in the "nodejs" folder. - -This is no longer the case - now, "wasm-opt" is a separate module. - -The following folder will be removed: {nodejs_folder}. - -You may need to reinstall wasm-opt using `mxpy deps install wasm-opt`. -""") - confirm_continuation(interactive) - - shutil.rmtree(nodejs_folder) - - global_testnet_toml = sdk_path / "testnet.toml" - if global_testnet_toml.exists(): - global_testnet_toml.unlink() - - -def create_venv(): - require_python_venv_tools() - venv_folder = get_mxpy_venv_path() - venv_folder.mkdir(parents=True, exist_ok=True) - - logger.debug(f"Creating virtual environment in: {venv_folder}.") - import venv - builder = venv.EnvBuilder(with_pip=True, symlinks=True) - builder.clear_directory(venv_folder) - builder.create(venv_folder) - - logger.debug(f"Virtual environment has been created in: {venv_folder}.") - - -def require_python_venv_tools(): - operating_system = get_operating_system() - - try: - import ensurepip - import venv - logger.debug(f"Packages found: {ensurepip}, {venv}.") - except ModuleNotFoundError: - if operating_system == "linux": - python_venv = f"python{sys.version_info.major}.{sys.version_info.minor}-venv" - raise InstallError(f'Packages [venv] or [ensurepip] not found. Please run "sudo apt install {python_venv}" and then run mxpy-up again.') - else: - raise InstallError("Packages [venv] or [ensurepip] not found, please install them first. See https://docs.python.org/3/tutorial/venv.html.") - - -def get_mxpy_venv_path(): - return sdk_path / "mxpy-venv" - - -def install_mxpy(exact_version: str, from_branch: str, verbose: bool): - logger.debug("Installing mxpy in virtual environment...") - - if from_branch: - package_to_install = f"https://github.com/multiversx/mx-sdk-py-cli/archive/refs/heads/{from_branch}.zip" - else: - package_to_install = "multiversx_sdk_cli" if not exact_version else f"multiversx_sdk_cli=={exact_version}" - - venv_path = get_mxpy_venv_path() - - return_code = run_in_venv(["python3", "-m", "pip", "install", "--upgrade", "pip"], venv_path, verbose) - if return_code != 0: - raise InstallError("Could not upgrade pip.") - return_code = run_in_venv(["pip3", "install", "--no-cache-dir", package_to_install], venv_path, verbose) - if return_code != 0: - raise InstallError("Could not install mxpy.") - - logger.debug("Creating mxpy shortcut...") - - shortcut_path = sdk_path / "mxpy" - - try: - shortcut_path.unlink() - logger.debug(f"Removed existing shortcut: {shortcut_path}") - except FileNotFoundError: - logger.debug(f"Shortcut does not exist yet: {shortcut_path}") - pass - - shortcut_content = get_mxpy_shortcut_content() - shortcut_path.write_text(shortcut_content) - - st = os.stat(shortcut_path) - os.chmod(shortcut_path, st.st_mode | stat.S_IEXEC) - - logger.info("You have successfully installed mxpy.") - - -def get_mxpy_shortcut_content(): - operating_system = get_operating_system() - venv_path = get_mxpy_venv_path() - - if operating_system == "windows": - return f"""#!/bin/sh -. "{venv_path / 'Scripts' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate -""" - - return f"""#!/bin/sh -. "{venv_path / 'bin' / 'activate'}" && python3 -m multiversx_sdk_cli.cli "$@" && deactivate -""" - - -def run_in_venv(args: List[str], venv_path: Path, verbose: bool): - env = os.environ.copy() - - if "PYTHONHOME" in env: - del env["PYTHONHOME"] - - env["PATH"] = str(venv_path / "bin") + ":" + env["PATH"] - env["VIRTUAL_ENV"] = str(venv_path) - - if verbose: - process = subprocess.Popen(args, env=env) - else: - process = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - - return process.wait() - - -def run_post_install_checks(): - multiversx_sdk_path = Path("~/multiversx-sdk").expanduser() - - logger.debug("Running post-install checks...") - - if multiversx_sdk_path.exists(): - logger.debug("~/multiversx-sdk exists OK") - else: - logger.warning("~/multiversx-sdk exists NOK") - - if (multiversx_sdk_path / "mxpy").exists(): - logger.debug("~/multiversx-sdk/mxpy shortcut created OK") - else: - logger.warning("~/multiversx-sdk/mxpy shortcut created NOK") - - -def guide_system_path_integration(): - interactive = True - operating_system = get_operating_system() - - if operating_system == "windows": - print(f""" -############################################################################### -On Windows, for the "mxpy" command shortcut to be available, you MUST ADD the directory "{sdk_path}" to the system PATH. - -You can do this by following these steps: - -https://superuser.com/questions/949560/how-do-i-set-system-environment-variables-in-windows-10 - -############################################################################### -Do you UNDERSTAND the above? -############################################################################### -""") - confirm_continuation(interactive) - return - - old_export_directive = f'export PATH="{Path("~/elrondsdk").expanduser()}:$PATH"\t# elrond-sdk' - new_export_directive = f'export PATH="${{HOME}}/multiversx-sdk:$PATH"\t# multiversx-sdk' - - profile_files = get_profile_files() - - if not profile_files: - print(f""" -############################################################################### -No shell profile files have been found. - -The "mxpy" command shortcut will not be available until you add the directory "{sdk_path}" to the system PATH. -############################################################################### -Do you UNDERSTAND the above? -""") - confirm_continuation(interactive) - return - - profile_files_formatted = "\n".join(f" - {file}" for file in profile_files) - profile_files_contents = [profile_file.read_text() for profile_file in profile_files] - any_old_export_directive = any(old_export_directive in content for content in profile_files_contents) - any_new_export_directive = any(new_export_directive in content for content in profile_files_contents) - - if any_old_export_directive: - # We don't perform the removal automatically (a bit risky) - print(f""" -############################################################################### -It seems that the old path "~/elrondsdk" is still configured in shell profile. - -Please MANUALLY remove it from the shell profile (now or after the installer script ends). - -Your shell profile files: -{profile_files_formatted} - -The entry (entries) to remove: - {old_export_directive} -############################################################################### -Make sure you UNDERSTAND the above before proceeding further. -############################################################################### -""") - confirm_continuation(interactive) - - if any_new_export_directive: - # Note: in some (rare) cases, here we'll have false positives (e.g. if the export directive is commented out). - print(f""" -############################################################################### -It seems that the path "~/multiversx-sdk" is already configured in shell profile. - -To confirm this, CHECK the shell profile (now or after the installer script ends). - -Your shell profile files: -{profile_files_formatted} - -The entry to check (it should be present): - {new_export_directive}. -############################################################################### -Make sure you UNDERSTAND the above before proceeding further. -############################################################################### -""") - confirm_continuation(interactive) - return - - print(f""" -############################################################################### -In order to use the "mxpy" command shortcut, you have to manually extend the PATH variable to include "~/multiversx-sdk". - -In order to manually extend the PATH variable, add the following line to your shell profile file upon installation: - - export PATH="${{HOME}}/multiversx-sdk:${{PATH}}" - -Your shell profile files: -{profile_files_formatted} - -Upon editing the shell profile file, you may have to RESTART THE USER SESSION for the changes to take effect. -""") - confirm_continuation(interactive) - - -def get_profile_files() -> List[Path]: - files = [ - Path("~/.profile").expanduser().resolve(), - Path("~/.bashrc").expanduser().resolve(), - Path("~/.bash_profile").expanduser().resolve(), - Path("~/.zshrc").expanduser().resolve() - ] - - return [file for file in files if file.exists()] - - -class InstallError(Exception): - def __init__(self, message: str): - super().__init__(message) - - -def confirm_continuation(interactive: bool): - if not interactive: - return - - answer = input("Continue? (y/n)") - if answer.lower() not in ["y", "yes"]: - print("Confirmation not given. Will stop.") - exit(1) - - -if __name__ == "__main__": - try: - main() - except Exception as err: - logger.fatal(err) - sys.exit(1) - - print(""" -############################################################################### -Installer script finished successfully. -############################################################################### -For more information go to https://docs.multiversx.com. -For support, please contact us at http://discord.gg/MultiversXBuilders. -############################################################################### -""") diff --git a/contracts/mvx-game-sc/mxsc-template.toml b/contracts/mvx-game-sc/mxsc-template.toml deleted file mode 100644 index 7c73decd..00000000 --- a/contracts/mvx-game-sc/mxsc-template.toml +++ /dev/null @@ -1,19 +0,0 @@ -name = "lib" -contract_trait = "lib" -src_file = "lib.rs" -rename_pairs = [ - [ - "blockchain.set_current_dir_from_workspace(\"contracts/examples/lib\");", - "// blockchain.set_current_dir_from_workspace(\"relative path to your workspace, if applicable\");", - ], -] -files_include = [ - "meta", - "scenarios", - "src", - "tests", - "wasm/Cargo.toml", - "Cargo.toml", - "README.md", - "multiversx.json", -] diff --git a/contracts/mvx-game-sc/scenarios/adder.scen.json b/contracts/mvx-game-sc/scenarios/adder.scen.json deleted file mode 100644 index b031d1e5..00000000 --- a/contracts/mvx-game-sc/scenarios/adder.scen.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "mvx-game-sc", - "comment": "add then check", - "gasSchedule": "v3", - "steps": [ - { - "step": "setState", - "accounts": { - "address:owner": { - "nonce": "1", - "balance": "0" - } - }, - "newAddresses": [ - { - "creatorAddress": "address:owner", - "creatorNonce": "1", - "newAddress": "sc:lib" - } - ] - }, - { - "step": "scDeploy", - "id": "1", - "tx": { - "from": "address:owner", - "contractCode": "file:../output/mvx-game-sc.wasm", - "arguments": [ - "5" - ], - "gasLimit": "5,000,000", - "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "", - "logs": "*", - "gas": "*", - "refund": "*" - } - }, - { - "step": "scQuery", - "id": "2", - "tx": { - "to": "sc:lib", - "function": "getSum", - "arguments": [] - }, - "expect": { - "out": [ - "5" - ], - "status": "", - "logs": [] - } - }, - { - "step": "scCall", - "id": "3", - "tx": { - "from": "address:owner", - "to": "sc:lib", - "function": "add", - "arguments": [ - "3" - ], - "gasLimit": "5,000,000", - "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "", - "logs": "*", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "*", - "balance": "0", - "storage": {}, - "code": "" - }, - "sc:lib": { - "nonce": "0", - "balance": "0", - "storage": { - "str:sum": "8" - }, - "code": "file:../output/mvx-game-sc.wasm" - } - } - } - ] -} \ No newline at end of file diff --git a/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json b/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json deleted file mode 100644 index 3337e6f8..00000000 --- a/contracts/mvx-game-sc/scenarios/interactor_trace.scen.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "steps": [ - { - "step": "setState", - "accounts": { - "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60": { - "nonce": "481", - "balance": "106274669842530000003", - "esdt": { - "str:CAN-14dc0a": "1000", - "str:CAN-2abf4b": "1000", - "str:CAN-6d39e6": "1000", - "str:CAN-ac1592": "1000" - }, - "username": "" - } - } - }, - { - "step": "setState", - "newAddresses": [ - { - "creatorAddress": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", - "creatorNonce": "481", - "newAddress": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60" - } - ] - }, - { - "step": "scDeploy", - "id": "", - "tx": { - "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", - "contractCode": "file:../output/mvx-game-sc.wasm", - "arguments": [ - "0x00" - ], - "gasLimit": "70,000,000", - "gasPrice": "" - }, - "expect": { - "status": "0" - } - }, - { - "step": "scCall", - "id": "", - "tx": { - "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", - "to": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60", - "function": "add", - "arguments": [ - "0x07" - ], - "gasLimit": "70,000,000", - "gasPrice": "" - }, - "expect": { - "status": "0" - } - }, - { - "step": "scCall", - "id": "", - "tx": { - "from": "0xe32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60", - "to": "0x0000000000000000050028600ceb73ac22ec0b6f257aff7bed74dffa3ebfed60", - "function": "add", - "arguments": [ - "0x05" - ], - "gasLimit": "70,000,000", - "gasPrice": "" - }, - "expect": { - "status": "0" - } - } - ], - "name": "" -} \ No newline at end of file