From af44b29a3c2b14f4cd7c6ba1aba42f9cc29efed5 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 09:09:51 -0800 Subject: [PATCH 01/12] Updated setup.py - added eth-brownie and moonworm dependencies --- fullcount_theory/setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fullcount_theory/setup.py b/fullcount_theory/setup.py index 81dfa779..e35f3a8f 100644 --- a/fullcount_theory/setup.py +++ b/fullcount_theory/setup.py @@ -1,3 +1,4 @@ +import os from setuptools import find_packages, setup with open("fullcount_theory/version.txt") as ifp: @@ -7,13 +8,15 @@ with open("README.md") as ifp: long_description = ifp.read() +os.environ["BROWNIE_LIB"] = "1" + setup( name="fullcount_theory", version=VERSION, packages=find_packages(), - install_requires=[], + install_requires=["eth-brownie"], extras_require={ - "dev": ["black"], + "dev": ["black", "moonworm[moonstream]"], "distribute": ["setuptools", "twine", "wheel"], }, description="Fullcount.xyz game design tool", From c7cd13ea9a77c988e8d737c87154880d272258af Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 11:22:03 -0800 Subject: [PATCH 02/12] Added moonworm interface to Fullcount --- .gitignore | 3 + .../fullcount_theory/Fullcount.py | 1290 +++++++++++++++++ 2 files changed, 1293 insertions(+) create mode 100644 fullcount_theory/fullcount_theory/Fullcount.py diff --git a/.gitignore b/.gitignore index 7924ada1..c90c1f77 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ Cargo.lock # Foundry stuff out/ cache/ + +# Brownie stuff +build/ diff --git a/fullcount_theory/fullcount_theory/Fullcount.py b/fullcount_theory/fullcount_theory/Fullcount.py new file mode 100644 index 00000000..4ed8dab6 --- /dev/null +++ b/fullcount_theory/fullcount_theory/Fullcount.py @@ -0,0 +1,1290 @@ +# Code generated by moonworm : https://github.com/moonstream-to/moonworm +# Moonworm version : 0.8.0 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "out", "Fullcount.sol") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> str: + return raw_value + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + foundry_build = json.load(ifp) + + build = { + "type": "contract", + "ast": foundry_build["ast"], + "abi": foundry_build["abi"], + "contractName": abi_name, + "compiler": { + "version": foundry_build["metadata"]["compiler"]["version"], + }, + "language": foundry_build["metadata"]["language"], + "bytecode": foundry_build["bytecode"]["object"], + "sourceMap": foundry_build["bytecode"]["sourceMap"], + "deployedBytecode": foundry_build["deployedBytecode"]["object"], + "deployedSourceMap": foundry_build["deployedBytecode"]["sourceMap"], + "pcMap": {}, + } + + return ContractContainer(PROJECT, build) + + +class Fullcount: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "Fullcount" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("Fullcount") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy( + self, + session_start_price: int, + session_join_price: int, + treasury_address: ChecksumAddress, + seconds_per_phase: int, + transaction_config, + ): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy( + session_start_price, + session_join_price, + treasury_address, + seconds_per_phase, + transaction_config, + ) + self.address = deployed_contract.address + self.contract = deployed_contract + return deployed_contract.tx + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def verify_contract(self): + self.assert_contract_is_instantiated() + contract_class = contract_from_build(self.contract_name) + contract_class.publish_source(self.contract) + + def distance0_distribution( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.Distance0Distribution.call( + arg1, block_identifier=block_number + ) + + def distance1_distribution( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.Distance1Distribution.call( + arg1, block_identifier=block_number + ) + + def distance2_distribution( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.Distance2Distribution.call( + arg1, block_identifier=block_number + ) + + def distance_gt2_distribution( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.DistanceGT2Distribution.call( + arg1, block_identifier=block_number + ) + + def fullcount_version( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.FullcountVersion.call(block_identifier=block_number) + + def num_sessions(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.NumSessions.call(block_identifier=block_number) + + def num_stats(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.NumStats.call(block_identifier=block_number) + + def seconds_per_phase( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.SecondsPerPhase.call(block_identifier=block_number) + + def session_join_price( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.SessionJoinPrice.call(block_identifier=block_number) + + def session_start_price( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.SessionStartPrice.call(block_identifier=block_number) + + def session_state( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.SessionState.call(arg1, block_identifier=block_number) + + def staked_session( + self, + arg1: ChecksumAddress, + arg2: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.StakedSession.call( + arg1, arg2, block_identifier=block_number + ) + + def staker( + self, + arg1: ChecksumAddress, + arg2: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.Staker.call(arg1, arg2, block_identifier=block_number) + + def treasury_address( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.TreasuryAddress.call(block_identifier=block_number) + + def abort_session(self, session_id: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.abortSession(session_id, transaction_config) + + def assign_stats( + self, + token_address: ChecksumAddress, + token_id: int, + stat_i_ds: List, + values: List, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.assignStats( + token_address, token_id, stat_i_ds, values, transaction_config + ) + + def batch_assign_stats( + self, + token_addresses: List, + token_i_ds: List, + stat_i_ds: Any, + values: Any, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.batchAssignStats( + token_addresses, token_i_ds, stat_i_ds, values, transaction_config + ) + + def batch_get_stats( + self, + token_addresses: List, + token_i_ds: List, + stat_i_ds: List, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.batchGetStats.call( + token_addresses, token_i_ds, stat_i_ds, block_identifier=block_number + ) + + def commit_pitch( + self, session_id: int, signature: bytes, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.commitPitch(session_id, signature, transaction_config) + + def commit_swing( + self, session_id: int, signature: bytes, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.commitSwing(session_id, signature, transaction_config) + + def create_stat(self, descriptor: str, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createStat(descriptor, transaction_config) + + def describe_stat( + self, stat_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.describeStat.call(stat_id, block_identifier=block_number) + + def eip712_domain(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.eip712Domain.call(block_identifier=block_number) + + def get_session( + self, session_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getSession.call(session_id, block_identifier=block_number) + + def get_stats( + self, + token_address: ChecksumAddress, + token_id: int, + stat_i_ds: List, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getStats.call( + token_address, token_id, stat_i_ds, block_identifier=block_number + ) + + def is_administrator( + self, + account: ChecksumAddress, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.isAdministrator.call( + account, block_identifier=block_number + ) + + def join_session( + self, + session_id: int, + nft_address: ChecksumAddress, + token_id: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.joinSession( + session_id, nft_address, token_id, transaction_config + ) + + def pitch_hash( + self, + nonce: int, + speed: int, + vertical: int, + horizontal: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.pitchHash.call( + nonce, speed, vertical, horizontal, block_identifier=block_number + ) + + def resolve( + self, + pitch: tuple, + swing: tuple, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.resolve.call(pitch, swing, block_identifier=block_number) + + def reveal_pitch( + self, + session_id: int, + nonce: int, + speed: int, + vertical: int, + horizontal: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.revealPitch( + session_id, nonce, speed, vertical, horizontal, transaction_config + ) + + def reveal_swing( + self, + session_id: int, + nonce: int, + kind: int, + vertical: int, + horizontal: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.revealSwing( + session_id, nonce, kind, vertical, horizontal, transaction_config + ) + + def sample_outcome_from_distribution( + self, + nonce0: int, + nonce1: int, + distribution: List, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.sampleOutcomeFromDistribution.call( + nonce0, nonce1, distribution, block_identifier=block_number + ) + + def session_progress( + self, session_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.sessionProgress.call( + session_id, block_identifier=block_number + ) + + def set_stat_descriptor( + self, stat_id: int, descriptor: str, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setStatDescriptor(stat_id, descriptor, transaction_config) + + def start_session( + self, nft_address: ChecksumAddress, token_id: int, role: int, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.startSession( + nft_address, token_id, role, transaction_config + ) + + def stat_block_version( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.statBlockVersion.call(block_identifier=block_number) + + def swing_hash( + self, + nonce: int, + kind: int, + vertical: int, + horizontal: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.swingHash.call( + nonce, kind, vertical, horizontal, block_identifier=block_number + ) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.max_fee_per_gas is not None: + transaction_config["max_fee"] = args.max_fee_per_gas + if args.max_priority_fee_per_gas is not None: + transaction_config["priority_fee"] = args.max_priority_fee_per_gas + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + if args.nonce is not None: + transaction_config["nonce"] = args.nonce + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + parser.add_argument( + "--block-number", + required=False, + type=int, + help="Call at the given block number, defaults to latest", + ) + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--max-fee-per-gas", + default=None, + help="Max fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--max-priority-fee-per-gas", + default=None, + help="Max priority fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + parser.add_argument( + "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" + ) + parser.add_argument( + "--value", default=None, help="Value of the transaction in wei(optional)" + ) + parser.add_argument("--verbose", action="store_true", help="Print verbose output") + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = Fullcount(None) + result = contract.deploy( + session_start_price=args.session_start_price, + session_join_price=args.session_join_price, + treasury_address=args.treasury_address, + seconds_per_phase=args.seconds_per_phase, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_verify_contract(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.verify_contract() + print(result) + + +def handle_distance0_distribution(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.distance0_distribution( + arg1=args.arg1, block_number=args.block_number + ) + print(result) + + +def handle_distance1_distribution(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.distance1_distribution( + arg1=args.arg1, block_number=args.block_number + ) + print(result) + + +def handle_distance2_distribution(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.distance2_distribution( + arg1=args.arg1, block_number=args.block_number + ) + print(result) + + +def handle_distance_gt2_distribution(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.distance_gt2_distribution( + arg1=args.arg1, block_number=args.block_number + ) + print(result) + + +def handle_fullcount_version(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.fullcount_version(block_number=args.block_number) + print(result) + + +def handle_num_sessions(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.num_sessions(block_number=args.block_number) + print(result) + + +def handle_num_stats(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.num_stats(block_number=args.block_number) + print(result) + + +def handle_seconds_per_phase(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.seconds_per_phase(block_number=args.block_number) + print(result) + + +def handle_session_join_price(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.session_join_price(block_number=args.block_number) + print(result) + + +def handle_session_start_price(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.session_start_price(block_number=args.block_number) + print(result) + + +def handle_session_state(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.session_state(arg1=args.arg1, block_number=args.block_number) + print(result) + + +def handle_staked_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.staked_session( + arg1=args.arg1, arg2=args.arg2, block_number=args.block_number + ) + print(result) + + +def handle_staker(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.staker( + arg1=args.arg1, arg2=args.arg2, block_number=args.block_number + ) + print(result) + + +def handle_treasury_address(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.treasury_address(block_number=args.block_number) + print(result) + + +def handle_abort_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.abort_session( + session_id=args.session_id, transaction_config=transaction_config + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_assign_stats(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.assign_stats( + token_address=args.token_address, + token_id=args.token_id, + stat_i_ds=args.stat_i_ds, + values=args.values, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_batch_assign_stats(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.batch_assign_stats( + token_addresses=args.token_addresses, + token_i_ds=args.token_i_ds, + stat_i_ds=args.stat_i_ds, + values=args.values, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_batch_get_stats(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.batch_get_stats( + token_addresses=args.token_addresses, + token_i_ds=args.token_i_ds, + stat_i_ds=args.stat_i_ds, + block_number=args.block_number, + ) + print(result) + + +def handle_commit_pitch(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.commit_pitch( + session_id=args.session_id, + signature=args.signature, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_commit_swing(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.commit_swing( + session_id=args.session_id, + signature=args.signature, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_create_stat(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.create_stat( + descriptor=args.descriptor, transaction_config=transaction_config + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_describe_stat(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.describe_stat( + stat_id=args.stat_id, block_number=args.block_number + ) + print(result) + + +def handle_eip712_domain(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.eip712_domain(block_number=args.block_number) + print(result) + + +def handle_get_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.get_session( + session_id=args.session_id, block_number=args.block_number + ) + print(result) + + +def handle_get_stats(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.get_stats( + token_address=args.token_address, + token_id=args.token_id, + stat_i_ds=args.stat_i_ds, + block_number=args.block_number, + ) + print(result) + + +def handle_is_administrator(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.is_administrator( + account=args.account, block_number=args.block_number + ) + print(result) + + +def handle_join_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.join_session( + session_id=args.session_id, + nft_address=args.nft_address, + token_id=args.token_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_pitch_hash(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.pitch_hash( + nonce=args.nonce_arg, + speed=args.speed, + vertical=args.vertical, + horizontal=args.horizontal, + block_number=args.block_number, + ) + print(result) + + +def handle_resolve(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.resolve( + pitch=args.pitch, swing=args.swing, block_number=args.block_number + ) + print(result) + + +def handle_reveal_pitch(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.reveal_pitch( + session_id=args.session_id, + nonce=args.nonce_arg, + speed=args.speed, + vertical=args.vertical, + horizontal=args.horizontal, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_reveal_swing(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.reveal_swing( + session_id=args.session_id, + nonce=args.nonce_arg, + kind=args.kind, + vertical=args.vertical, + horizontal=args.horizontal, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_sample_outcome_from_distribution(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.sample_outcome_from_distribution( + nonce0=args.nonce0, + nonce1=args.nonce1, + distribution=args.distribution, + block_number=args.block_number, + ) + print(result) + + +def handle_session_progress(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.session_progress( + session_id=args.session_id, block_number=args.block_number + ) + print(result) + + +def handle_set_stat_descriptor(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_stat_descriptor( + stat_id=args.stat_id, + descriptor=args.descriptor, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_start_session(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + transaction_config = get_transaction_config(args) + result = contract.start_session( + nft_address=args.nft_address, + token_id=args.token_id, + role=args.role, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_stat_block_version(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.stat_block_version(block_number=args.block_number) + print(result) + + +def handle_swing_hash(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.swing_hash( + nonce=args.nonce_arg, + kind=args.kind, + vertical=args.vertical, + horizontal=args.horizontal, + block_number=args.block_number, + ) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for Fullcount") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.add_argument( + "--session-start-price", required=True, help="Type: uint256", type=int + ) + deploy_parser.add_argument( + "--session-join-price", required=True, help="Type: uint256", type=int + ) + deploy_parser.add_argument( + "--treasury-address", required=True, help="Type: address" + ) + deploy_parser.add_argument( + "--seconds-per-phase", required=True, help="Type: uint256", type=int + ) + deploy_parser.set_defaults(func=handle_deploy) + + verify_contract_parser = subcommands.add_parser("verify-contract") + add_default_arguments(verify_contract_parser, False) + verify_contract_parser.set_defaults(func=handle_verify_contract) + + distance0_distribution_parser = subcommands.add_parser("distance0-distribution") + add_default_arguments(distance0_distribution_parser, False) + distance0_distribution_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + distance0_distribution_parser.set_defaults(func=handle_distance0_distribution) + + distance1_distribution_parser = subcommands.add_parser("distance1-distribution") + add_default_arguments(distance1_distribution_parser, False) + distance1_distribution_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + distance1_distribution_parser.set_defaults(func=handle_distance1_distribution) + + distance2_distribution_parser = subcommands.add_parser("distance2-distribution") + add_default_arguments(distance2_distribution_parser, False) + distance2_distribution_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + distance2_distribution_parser.set_defaults(func=handle_distance2_distribution) + + distance_gt2_distribution_parser = subcommands.add_parser( + "distance-gt2-distribution" + ) + add_default_arguments(distance_gt2_distribution_parser, False) + distance_gt2_distribution_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + distance_gt2_distribution_parser.set_defaults(func=handle_distance_gt2_distribution) + + fullcount_version_parser = subcommands.add_parser("fullcount-version") + add_default_arguments(fullcount_version_parser, False) + fullcount_version_parser.set_defaults(func=handle_fullcount_version) + + num_sessions_parser = subcommands.add_parser("num-sessions") + add_default_arguments(num_sessions_parser, False) + num_sessions_parser.set_defaults(func=handle_num_sessions) + + num_stats_parser = subcommands.add_parser("num-stats") + add_default_arguments(num_stats_parser, False) + num_stats_parser.set_defaults(func=handle_num_stats) + + seconds_per_phase_parser = subcommands.add_parser("seconds-per-phase") + add_default_arguments(seconds_per_phase_parser, False) + seconds_per_phase_parser.set_defaults(func=handle_seconds_per_phase) + + session_join_price_parser = subcommands.add_parser("session-join-price") + add_default_arguments(session_join_price_parser, False) + session_join_price_parser.set_defaults(func=handle_session_join_price) + + session_start_price_parser = subcommands.add_parser("session-start-price") + add_default_arguments(session_start_price_parser, False) + session_start_price_parser.set_defaults(func=handle_session_start_price) + + session_state_parser = subcommands.add_parser("session-state") + add_default_arguments(session_state_parser, False) + session_state_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + session_state_parser.set_defaults(func=handle_session_state) + + staked_session_parser = subcommands.add_parser("staked-session") + add_default_arguments(staked_session_parser, False) + staked_session_parser.add_argument("--arg1", required=True, help="Type: address") + staked_session_parser.add_argument( + "--arg2", required=True, help="Type: uint256", type=int + ) + staked_session_parser.set_defaults(func=handle_staked_session) + + staker_parser = subcommands.add_parser("staker") + add_default_arguments(staker_parser, False) + staker_parser.add_argument("--arg1", required=True, help="Type: address") + staker_parser.add_argument("--arg2", required=True, help="Type: uint256", type=int) + staker_parser.set_defaults(func=handle_staker) + + treasury_address_parser = subcommands.add_parser("treasury-address") + add_default_arguments(treasury_address_parser, False) + treasury_address_parser.set_defaults(func=handle_treasury_address) + + abort_session_parser = subcommands.add_parser("abort-session") + add_default_arguments(abort_session_parser, True) + abort_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + abort_session_parser.set_defaults(func=handle_abort_session) + + assign_stats_parser = subcommands.add_parser("assign-stats") + add_default_arguments(assign_stats_parser, True) + assign_stats_parser.add_argument( + "--token-address", required=True, help="Type: address" + ) + assign_stats_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + assign_stats_parser.add_argument( + "--stat-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + assign_stats_parser.add_argument( + "--values", required=True, help="Type: uint256[]", nargs="+" + ) + assign_stats_parser.set_defaults(func=handle_assign_stats) + + batch_assign_stats_parser = subcommands.add_parser("batch-assign-stats") + add_default_arguments(batch_assign_stats_parser, True) + batch_assign_stats_parser.add_argument( + "--token-addresses", required=True, help="Type: address[]", nargs="+" + ) + batch_assign_stats_parser.add_argument( + "--token-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + batch_assign_stats_parser.add_argument( + "--stat-i-ds", required=True, help="Type: uint256[][]", type=eval + ) + batch_assign_stats_parser.add_argument( + "--values", required=True, help="Type: uint256[][]", type=eval + ) + batch_assign_stats_parser.set_defaults(func=handle_batch_assign_stats) + + batch_get_stats_parser = subcommands.add_parser("batch-get-stats") + add_default_arguments(batch_get_stats_parser, False) + batch_get_stats_parser.add_argument( + "--token-addresses", required=True, help="Type: address[]", nargs="+" + ) + batch_get_stats_parser.add_argument( + "--token-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + batch_get_stats_parser.add_argument( + "--stat-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + batch_get_stats_parser.set_defaults(func=handle_batch_get_stats) + + commit_pitch_parser = subcommands.add_parser("commit-pitch") + add_default_arguments(commit_pitch_parser, True) + commit_pitch_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + commit_pitch_parser.add_argument( + "--signature", required=True, help="Type: bytes", type=bytes_argument_type + ) + commit_pitch_parser.set_defaults(func=handle_commit_pitch) + + commit_swing_parser = subcommands.add_parser("commit-swing") + add_default_arguments(commit_swing_parser, True) + commit_swing_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + commit_swing_parser.add_argument( + "--signature", required=True, help="Type: bytes", type=bytes_argument_type + ) + commit_swing_parser.set_defaults(func=handle_commit_swing) + + create_stat_parser = subcommands.add_parser("create-stat") + add_default_arguments(create_stat_parser, True) + create_stat_parser.add_argument( + "--descriptor", required=True, help="Type: string", type=str + ) + create_stat_parser.set_defaults(func=handle_create_stat) + + describe_stat_parser = subcommands.add_parser("describe-stat") + add_default_arguments(describe_stat_parser, False) + describe_stat_parser.add_argument( + "--stat-id", required=True, help="Type: uint256", type=int + ) + describe_stat_parser.set_defaults(func=handle_describe_stat) + + eip712_domain_parser = subcommands.add_parser("eip712-domain") + add_default_arguments(eip712_domain_parser, False) + eip712_domain_parser.set_defaults(func=handle_eip712_domain) + + get_session_parser = subcommands.add_parser("get-session") + add_default_arguments(get_session_parser, False) + get_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + get_session_parser.set_defaults(func=handle_get_session) + + get_stats_parser = subcommands.add_parser("get-stats") + add_default_arguments(get_stats_parser, False) + get_stats_parser.add_argument( + "--token-address", required=True, help="Type: address" + ) + get_stats_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + get_stats_parser.add_argument( + "--stat-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + get_stats_parser.set_defaults(func=handle_get_stats) + + is_administrator_parser = subcommands.add_parser("is-administrator") + add_default_arguments(is_administrator_parser, False) + is_administrator_parser.add_argument( + "--account", required=True, help="Type: address" + ) + is_administrator_parser.set_defaults(func=handle_is_administrator) + + join_session_parser = subcommands.add_parser("join-session") + add_default_arguments(join_session_parser, True) + join_session_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + join_session_parser.add_argument( + "--nft-address", required=True, help="Type: address" + ) + join_session_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + join_session_parser.set_defaults(func=handle_join_session) + + pitch_hash_parser = subcommands.add_parser("pitch-hash") + add_default_arguments(pitch_hash_parser, False) + pitch_hash_parser.add_argument( + "--nonce-arg", required=True, help="Type: uint256", type=int + ) + pitch_hash_parser.add_argument( + "--speed", required=True, help="Type: uint8", type=int + ) + pitch_hash_parser.add_argument( + "--vertical", required=True, help="Type: uint8", type=int + ) + pitch_hash_parser.add_argument( + "--horizontal", required=True, help="Type: uint8", type=int + ) + pitch_hash_parser.set_defaults(func=handle_pitch_hash) + + resolve_parser = subcommands.add_parser("resolve") + add_default_arguments(resolve_parser, False) + resolve_parser.add_argument("--pitch", required=True, help="Type: tuple", type=eval) + resolve_parser.add_argument("--swing", required=True, help="Type: tuple", type=eval) + resolve_parser.set_defaults(func=handle_resolve) + + reveal_pitch_parser = subcommands.add_parser("reveal-pitch") + add_default_arguments(reveal_pitch_parser, True) + reveal_pitch_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + reveal_pitch_parser.add_argument( + "--nonce-arg", required=True, help="Type: uint256", type=int + ) + reveal_pitch_parser.add_argument( + "--speed", required=True, help="Type: uint8", type=int + ) + reveal_pitch_parser.add_argument( + "--vertical", required=True, help="Type: uint8", type=int + ) + reveal_pitch_parser.add_argument( + "--horizontal", required=True, help="Type: uint8", type=int + ) + reveal_pitch_parser.set_defaults(func=handle_reveal_pitch) + + reveal_swing_parser = subcommands.add_parser("reveal-swing") + add_default_arguments(reveal_swing_parser, True) + reveal_swing_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + reveal_swing_parser.add_argument( + "--nonce-arg", required=True, help="Type: uint256", type=int + ) + reveal_swing_parser.add_argument( + "--kind", required=True, help="Type: uint8", type=int + ) + reveal_swing_parser.add_argument( + "--vertical", required=True, help="Type: uint8", type=int + ) + reveal_swing_parser.add_argument( + "--horizontal", required=True, help="Type: uint8", type=int + ) + reveal_swing_parser.set_defaults(func=handle_reveal_swing) + + sample_outcome_from_distribution_parser = subcommands.add_parser( + "sample-outcome-from-distribution" + ) + add_default_arguments(sample_outcome_from_distribution_parser, False) + sample_outcome_from_distribution_parser.add_argument( + "--nonce0", required=True, help="Type: uint256", type=int + ) + sample_outcome_from_distribution_parser.add_argument( + "--nonce1", required=True, help="Type: uint256", type=int + ) + sample_outcome_from_distribution_parser.add_argument( + "--distribution", required=True, help="Type: uint256[7]", nargs="+" + ) + sample_outcome_from_distribution_parser.set_defaults( + func=handle_sample_outcome_from_distribution + ) + + session_progress_parser = subcommands.add_parser("session-progress") + add_default_arguments(session_progress_parser, False) + session_progress_parser.add_argument( + "--session-id", required=True, help="Type: uint256", type=int + ) + session_progress_parser.set_defaults(func=handle_session_progress) + + set_stat_descriptor_parser = subcommands.add_parser("set-stat-descriptor") + add_default_arguments(set_stat_descriptor_parser, True) + set_stat_descriptor_parser.add_argument( + "--stat-id", required=True, help="Type: uint256", type=int + ) + set_stat_descriptor_parser.add_argument( + "--descriptor", required=True, help="Type: string", type=str + ) + set_stat_descriptor_parser.set_defaults(func=handle_set_stat_descriptor) + + start_session_parser = subcommands.add_parser("start-session") + add_default_arguments(start_session_parser, True) + start_session_parser.add_argument( + "--nft-address", required=True, help="Type: address" + ) + start_session_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + start_session_parser.add_argument( + "--role", required=True, help="Type: uint8", type=int + ) + start_session_parser.set_defaults(func=handle_start_session) + + stat_block_version_parser = subcommands.add_parser("stat-block-version") + add_default_arguments(stat_block_version_parser, False) + stat_block_version_parser.set_defaults(func=handle_stat_block_version) + + swing_hash_parser = subcommands.add_parser("swing-hash") + add_default_arguments(swing_hash_parser, False) + swing_hash_parser.add_argument( + "--nonce-arg", required=True, help="Type: uint256", type=int + ) + swing_hash_parser.add_argument( + "--kind", required=True, help="Type: uint8", type=int + ) + swing_hash_parser.add_argument( + "--vertical", required=True, help="Type: uint8", type=int + ) + swing_hash_parser.add_argument( + "--horizontal", required=True, help="Type: uint8", type=int + ) + swing_hash_parser.set_defaults(func=handle_swing_hash) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() From faccbdf1c0235ad8a060b537b5b2af07e8366b04 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 11:27:19 -0800 Subject: [PATCH 03/12] Moved things around a bit And hooked up Moonworm-generated interface to `fullcount` CLI --- {fullcount_theory => python}/.gitignore | 0 {fullcount_theory => python}/README.md | 0 .../fullcount_theory => python/fullcount}/Fullcount.py | 0 .../fullcount_theory => python/fullcount}/__init__.py | 0 .../fullcount_theory => python/fullcount}/cli.py | 5 ++++- .../fullcount_theory => python/fullcount}/data.py | 0 .../fullcount}/generation_1.py | 0 .../fullcount_theory => python/fullcount}/test_data.py | 0 .../fullcount}/test_generation_1.py | 0 .../fullcount_theory => python/fullcount}/version.txt | 0 {fullcount_theory => python}/setup.py | 10 ++++------ 11 files changed, 8 insertions(+), 7 deletions(-) rename {fullcount_theory => python}/.gitignore (100%) rename {fullcount_theory => python}/README.md (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/Fullcount.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/__init__.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/cli.py (81%) rename {fullcount_theory/fullcount_theory => python/fullcount}/data.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/generation_1.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/test_data.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/test_generation_1.py (100%) rename {fullcount_theory/fullcount_theory => python/fullcount}/version.txt (100%) rename {fullcount_theory => python}/setup.py (79%) diff --git a/fullcount_theory/.gitignore b/python/.gitignore similarity index 100% rename from fullcount_theory/.gitignore rename to python/.gitignore diff --git a/fullcount_theory/README.md b/python/README.md similarity index 100% rename from fullcount_theory/README.md rename to python/README.md diff --git a/fullcount_theory/fullcount_theory/Fullcount.py b/python/fullcount/Fullcount.py similarity index 100% rename from fullcount_theory/fullcount_theory/Fullcount.py rename to python/fullcount/Fullcount.py diff --git a/fullcount_theory/fullcount_theory/__init__.py b/python/fullcount/__init__.py similarity index 100% rename from fullcount_theory/fullcount_theory/__init__.py rename to python/fullcount/__init__.py diff --git a/fullcount_theory/fullcount_theory/cli.py b/python/fullcount/cli.py similarity index 81% rename from fullcount_theory/fullcount_theory/cli.py rename to python/fullcount/cli.py index 3dfbd56b..1e45ab51 100644 --- a/fullcount_theory/fullcount_theory/cli.py +++ b/python/fullcount/cli.py @@ -1,6 +1,6 @@ import argparse -from . import generation_1 +from . import Fullcount, generation_1 def generate_cli() -> argparse.ArgumentParser: @@ -13,6 +13,9 @@ def generate_cli() -> argparse.ArgumentParser: subparsers = parser.add_subparsers() + contract_parser = Fullcount.generate_cli() + subparsers.add_parser("contract", parents=[contract_parser], add_help=False) + generation_1_parser = generation_1.generate_cli() subparsers.add_parser("gen-1", parents=[generation_1_parser], add_help=False) diff --git a/fullcount_theory/fullcount_theory/data.py b/python/fullcount/data.py similarity index 100% rename from fullcount_theory/fullcount_theory/data.py rename to python/fullcount/data.py diff --git a/fullcount_theory/fullcount_theory/generation_1.py b/python/fullcount/generation_1.py similarity index 100% rename from fullcount_theory/fullcount_theory/generation_1.py rename to python/fullcount/generation_1.py diff --git a/fullcount_theory/fullcount_theory/test_data.py b/python/fullcount/test_data.py similarity index 100% rename from fullcount_theory/fullcount_theory/test_data.py rename to python/fullcount/test_data.py diff --git a/fullcount_theory/fullcount_theory/test_generation_1.py b/python/fullcount/test_generation_1.py similarity index 100% rename from fullcount_theory/fullcount_theory/test_generation_1.py rename to python/fullcount/test_generation_1.py diff --git a/fullcount_theory/fullcount_theory/version.txt b/python/fullcount/version.txt similarity index 100% rename from fullcount_theory/fullcount_theory/version.txt rename to python/fullcount/version.txt diff --git a/fullcount_theory/setup.py b/python/setup.py similarity index 79% rename from fullcount_theory/setup.py rename to python/setup.py index e35f3a8f..534180b9 100644 --- a/fullcount_theory/setup.py +++ b/python/setup.py @@ -1,7 +1,7 @@ import os from setuptools import find_packages, setup -with open("fullcount_theory/version.txt") as ifp: +with open("fullcount/version.txt") as ifp: VERSION = ifp.read().strip() long_description = "" @@ -11,7 +11,7 @@ os.environ["BROWNIE_LIB"] = "1" setup( - name="fullcount_theory", + name="fullcount", version=VERSION, packages=find_packages(), install_requires=["eth-brownie"], @@ -19,7 +19,7 @@ "dev": ["black", "moonworm[moonstream]"], "distribute": ["setuptools", "twine", "wheel"], }, - description="Fullcount.xyz game design tool", + description="Fullcount.xyz Python client and game design utilities", long_description=long_description, long_description_content_type="text/markdown", author="Moonstream", @@ -31,8 +31,6 @@ "Topic :: Software Development :: Libraries", ], python_requires=">=3.6", - entry_points={ - "console_scripts": ["fullcount-theory=fullcount_theory.cli:main"] - }, + entry_points={"console_scripts": ["fullcount=fullcount.cli:main"]}, include_package_data=True, ) From 5b51f95640a5ec6866c375dea19383c37e0f5555 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 11:28:25 -0800 Subject: [PATCH 04/12] .gitignores --- .gitignore | 5 +++++ python/.gitignore | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c90c1f77..d3dcdf82 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ cache/ # Brownie stuff build/ + +# Python stuff +venv/ +.venv/ +.fullcount/ diff --git a/python/.gitignore b/python/.gitignore index a8943d0b..5a381a07 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -176,4 +176,6 @@ pyrightconfig.json # End of https://www.toptal.com/developers/gitignore/api/python # Virtual environments -.fullcount_theory/ +venv/ +.venv/ +.fullcount/ From fb44189291af5c94210f206cb27e69c1073cb137 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 11:34:54 -0800 Subject: [PATCH 05/12] Modified fullcount program help --- python/fullcount/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/fullcount/cli.py b/python/fullcount/cli.py index 1e45ab51..0d8c10d6 100644 --- a/python/fullcount/cli.py +++ b/python/fullcount/cli.py @@ -5,8 +5,8 @@ def generate_cli() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - prog="fullcount-theory", - description="Fullcount Theory: A tool to experiment with the game design and balance of Fullcount.xyz", + prog="fullcount", + description="Fullcount: A Python client to Fullcount.xyz", epilog="For more information, see https://github.com/moonstream-to/fullcount.xyz", ) parser.set_defaults(func=lambda _: parser.print_help()) From 38825183760f12cc41acbb36c0dd606a08faf432 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 12:25:01 -0800 Subject: [PATCH 06/12] Added some docs --- README.md | 52 +++++++++++---------------------- python/README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 9265b455..83b6a9b3 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,46 @@ -## Foundry +# Fullcount.xyz -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This repository contains all code related to the Fullcount.xyz autonomous, web3, baseball game. -Foundry consists of: +## Smart contracts -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +### Development -## Documentation +Fullcount uses [Foundry](https://book.getfoundry.sh/) to build and test smart contracts. -https://book.getfoundry.sh/ +We use the [`fullcount` CLI](python/README.md) to deploy and run operations related to the `Fullcount` contract. -## Usage - -### Build +#### Build ```shell $ forge build ``` -### Test +#### Test ```shell $ forge test ``` -### Format +#### Format ```shell $ forge fmt ``` -### Gas Snapshots +#### Gas Snapshots ```shell $ forge snapshot ``` -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` +#### Deployment and operations -### Cast +Please see [the README for the Fullcount Python client](python/README.md). -```shell -$ cast -``` +## Frontend -### Help +## Game balance and tuning -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +The [Python client](python/README.md) contains utilities that we use to help tune and balance the +game across versions. diff --git a/python/README.md b/python/README.md index e69de29b..6d916078 100644 --- a/python/README.md +++ b/python/README.md @@ -0,0 +1,75 @@ +# Python client for Fullcount.xyz + +This directory contains the Python client library for Fullcount.xyz. + +All on-chain interactions rely on the existence of `forge` build artifacts in the repository root +directory -- the client uses the latest ABI, bytecode, etc. from the `out/` directory created +by `forge build`. + +## Installation + +To install in a Python environment, run the following commands *from this directory*: + +```bash +export BROWNIE_LIB=1 +pip install -e . +``` + +## Usage + +The Python client is based on [`brownie`](https://github.com/eth-brownie/brownie). + +### In Python + +To import the contract into your own code: + +```python +from fullcount.Fullcount import Fullcount +``` + +To deploy a fresh instance of the contract: + +```python +from brownie import network, ZERO_ADDRESS +from fullcount.Fullcount import Fullcount + +network.connect("") + +sender = network.accounts.load("", "") + +contract = Fullcount(None) +contract.deploy(0, 0, ZERO_ADDRESS, 300, {"from": sender}) +``` + +To interact with an existing deployment: + +```python +from brownie import network +from fullcount.Fullcount import Fullcount + +network.connect("") + +contract = Fullcount("") +print("Number of sessions on contract: ", contract.num_sessions()) +``` + +### CLI + +``` +fullcount -h +``` + +## Development + +If you want to install developer dependencies: + +```bash +export BROWNIE_LIB=1 +pip install -e .[dev] +``` + +To regenerate the Python interface to the `Fullcount` contract: + +``` +moonworm generate-brownie -o fullcount -p ../ -n Fullcount --foundry +``` From 8623c036ae6ab2665126892ca5a75fbec090d976 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 16:29:55 -0800 Subject: [PATCH 07/12] Added BeerLeagueBallers NFT for testing/MVP purposes --- python/README.md | 8 +- python/fullcount/BeerLeagueBallers.py | 760 ++++++++++++++++++++++++++ src/Players.sol | 63 +++ 3 files changed, 830 insertions(+), 1 deletion(-) create mode 100644 python/fullcount/BeerLeagueBallers.py create mode 100644 src/Players.sol diff --git a/python/README.md b/python/README.md index 6d916078..5dae8ac4 100644 --- a/python/README.md +++ b/python/README.md @@ -70,6 +70,12 @@ pip install -e .[dev] To regenerate the Python interface to the `Fullcount` contract: -``` +```bash moonworm generate-brownie -o fullcount -p ../ -n Fullcount --foundry ``` + +To regenerate the Python interface to the `BeerLeagueBallers` NFT contract: + +```bash +moonworm generate-brownie -o fullcount -p ../ --foundry -n BeerLeagueBallers --sol-filename Players.sol +``` diff --git a/python/fullcount/BeerLeagueBallers.py b/python/fullcount/BeerLeagueBallers.py new file mode 100644 index 00000000..e0d62a8d --- /dev/null +++ b/python/fullcount/BeerLeagueBallers.py @@ -0,0 +1,760 @@ +# Code generated by moonworm : https://github.com/moonstream-to/moonworm +# Moonworm version : 0.8.0 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "out", "Players.sol") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> str: + return raw_value + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + foundry_build = json.load(ifp) + + build = { + "type": "contract", + "ast": foundry_build["ast"], + "abi": foundry_build["abi"], + "contractName": abi_name, + "compiler": { + "version": foundry_build["metadata"]["compiler"]["version"], + }, + "language": foundry_build["metadata"]["language"], + "bytecode": foundry_build["bytecode"]["object"], + "sourceMap": foundry_build["bytecode"]["sourceMap"], + "deployedBytecode": foundry_build["deployedBytecode"]["object"], + "deployedSourceMap": foundry_build["deployedBytecode"]["sourceMap"], + "pcMap": {}, + } + + return ContractContainer(PROJECT, build) + + +class BeerLeagueBallers: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "BeerLeagueBallers" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("BeerLeagueBallers") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + return deployed_contract.tx + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def verify_contract(self): + self.assert_contract_is_instantiated() + contract_class = contract_from_build(self.contract_name) + contract_class.publish_source(self.contract) + + def image_index( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.ImageIndex.call(arg1, block_identifier=block_number) + + def name( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.Name.call(arg1, block_identifier=block_number) + + def profile_images( + self, arg1: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.ProfileImages.call(arg1, block_identifier=block_number) + + def approve(self, to: ChecksumAddress, token_id: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.approve(to, token_id, transaction_config) + + def balance_of( + self, owner: ChecksumAddress, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.balanceOf.call(owner, block_identifier=block_number) + + def burn(self, token_id: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.burn(token_id, transaction_config) + + def get_approved( + self, token_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getApproved.call(token_id, block_identifier=block_number) + + def is_approved_for_all( + self, + owner: ChecksumAddress, + operator: ChecksumAddress, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.isApprovedForAll.call( + owner, operator, block_identifier=block_number + ) + + def metadata_json( + self, token_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.metadataJSON.call(token_id, block_identifier=block_number) + + def mint(self, name: str, image_index: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.mint(name, image_index, transaction_config) + + def name(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.name.call(block_identifier=block_number) + + def owner_of( + self, token_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.ownerOf.call(token_id, block_identifier=block_number) + + def safe_transfer_from_0x42842e0e( + self, + from_: ChecksumAddress, + to: ChecksumAddress, + token_id: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.safeTransferFrom(from_, to, token_id, transaction_config) + + def safe_transfer_from_0xb88d4fde( + self, + from_: ChecksumAddress, + to: ChecksumAddress, + token_id: int, + data: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.safeTransferFrom( + from_, to, token_id, data, transaction_config + ) + + def set_approval_for_all( + self, operator: ChecksumAddress, approved: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setApprovalForAll(operator, approved, transaction_config) + + def supports_interface( + self, interface_id: bytes, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.supportsInterface.call( + interface_id, block_identifier=block_number + ) + + def symbol(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.symbol.call(block_identifier=block_number) + + def token_by_index( + self, index: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.tokenByIndex.call(index, block_identifier=block_number) + + def token_of_owner_by_index( + self, + owner: ChecksumAddress, + index: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.tokenOfOwnerByIndex.call( + owner, index, block_identifier=block_number + ) + + def token_uri( + self, token_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.tokenURI.call(token_id, block_identifier=block_number) + + def total_supply(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.totalSupply.call(block_identifier=block_number) + + def transfer_from( + self, + from_: ChecksumAddress, + to: ChecksumAddress, + token_id: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.transferFrom(from_, to, token_id, transaction_config) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.max_fee_per_gas is not None: + transaction_config["max_fee"] = args.max_fee_per_gas + if args.max_priority_fee_per_gas is not None: + transaction_config["priority_fee"] = args.max_priority_fee_per_gas + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + if args.nonce is not None: + transaction_config["nonce"] = args.nonce + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + parser.add_argument( + "--block-number", + required=False, + type=int, + help="Call at the given block number, defaults to latest", + ) + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--max-fee-per-gas", + default=None, + help="Max fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--max-priority-fee-per-gas", + default=None, + help="Max priority fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + parser.add_argument( + "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" + ) + parser.add_argument( + "--value", default=None, help="Value of the transaction in wei(optional)" + ) + parser.add_argument("--verbose", action="store_true", help="Print verbose output") + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = BeerLeagueBallers(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_verify_contract(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.verify_contract() + print(result) + + +def handle_image_index(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.image_index(arg1=args.arg1, block_number=args.block_number) + print(result) + + +def handle_name(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.name(arg1=args.arg1, block_number=args.block_number) + print(result) + + +def handle_profile_images(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.profile_images(arg1=args.arg1, block_number=args.block_number) + print(result) + + +def handle_approve(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.approve( + to=args.to, token_id=args.token_id, transaction_config=transaction_config + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_balance_of(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.balance_of(owner=args.owner, block_number=args.block_number) + print(result) + + +def handle_burn(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.burn( + token_id=args.token_id, transaction_config=transaction_config + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_get_approved(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.get_approved( + token_id=args.token_id, block_number=args.block_number + ) + print(result) + + +def handle_is_approved_for_all(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.is_approved_for_all( + owner=args.owner, operator=args.operator, block_number=args.block_number + ) + print(result) + + +def handle_metadata_json(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.metadata_json( + token_id=args.token_id, block_number=args.block_number + ) + print(result) + + +def handle_mint(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.mint( + name=args.name, + image_index=args.image_index, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_name(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.name(block_number=args.block_number) + print(result) + + +def handle_owner_of(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.owner_of(token_id=args.token_id, block_number=args.block_number) + print(result) + + +def handle_safe_transfer_from_0x42842e0e(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.safe_transfer_from_0x42842e0e( + from_=args.from_arg, + to=args.to, + token_id=args.token_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_safe_transfer_from_0xb88d4fde(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.safe_transfer_from_0xb88d4fde( + from_=args.from_arg, + to=args.to, + token_id=args.token_id, + data=args.data, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_approval_for_all(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_approval_for_all( + operator=args.operator, + approved=args.approved, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_supports_interface(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.supports_interface( + interface_id=args.interface_id, block_number=args.block_number + ) + print(result) + + +def handle_symbol(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.symbol(block_number=args.block_number) + print(result) + + +def handle_token_by_index(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.token_by_index(index=args.index, block_number=args.block_number) + print(result) + + +def handle_token_of_owner_by_index(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.token_of_owner_by_index( + owner=args.owner, index=args.index, block_number=args.block_number + ) + print(result) + + +def handle_token_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.token_uri(token_id=args.token_id, block_number=args.block_number) + print(result) + + +def handle_total_supply(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + result = contract.total_supply(block_number=args.block_number) + print(result) + + +def handle_transfer_from(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = BeerLeagueBallers(args.address) + transaction_config = get_transaction_config(args) + result = contract.transfer_from( + from_=args.from_arg, + to=args.to, + token_id=args.token_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for BeerLeagueBallers") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + verify_contract_parser = subcommands.add_parser("verify-contract") + add_default_arguments(verify_contract_parser, False) + verify_contract_parser.set_defaults(func=handle_verify_contract) + + image_index_parser = subcommands.add_parser("image-index") + add_default_arguments(image_index_parser, False) + image_index_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + image_index_parser.set_defaults(func=handle_image_index) + + name_parser = subcommands.add_parser("name") + add_default_arguments(name_parser, False) + name_parser.add_argument("--arg1", required=True, help="Type: uint256", type=int) + name_parser.set_defaults(func=handle_name) + + profile_images_parser = subcommands.add_parser("profile-images") + add_default_arguments(profile_images_parser, False) + profile_images_parser.add_argument( + "--arg1", required=True, help="Type: uint256", type=int + ) + profile_images_parser.set_defaults(func=handle_profile_images) + + approve_parser = subcommands.add_parser("approve") + add_default_arguments(approve_parser, True) + approve_parser.add_argument("--to", required=True, help="Type: address") + approve_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + approve_parser.set_defaults(func=handle_approve) + + balance_of_parser = subcommands.add_parser("balance-of") + add_default_arguments(balance_of_parser, False) + balance_of_parser.add_argument("--owner", required=True, help="Type: address") + balance_of_parser.set_defaults(func=handle_balance_of) + + burn_parser = subcommands.add_parser("burn") + add_default_arguments(burn_parser, True) + burn_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + burn_parser.set_defaults(func=handle_burn) + + get_approved_parser = subcommands.add_parser("get-approved") + add_default_arguments(get_approved_parser, False) + get_approved_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + get_approved_parser.set_defaults(func=handle_get_approved) + + is_approved_for_all_parser = subcommands.add_parser("is-approved-for-all") + add_default_arguments(is_approved_for_all_parser, False) + is_approved_for_all_parser.add_argument( + "--owner", required=True, help="Type: address" + ) + is_approved_for_all_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + is_approved_for_all_parser.set_defaults(func=handle_is_approved_for_all) + + metadata_json_parser = subcommands.add_parser("metadata-json") + add_default_arguments(metadata_json_parser, False) + metadata_json_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + metadata_json_parser.set_defaults(func=handle_metadata_json) + + mint_parser = subcommands.add_parser("mint") + add_default_arguments(mint_parser, True) + mint_parser.add_argument("--name", required=True, help="Type: string", type=str) + mint_parser.add_argument( + "--image-index", required=True, help="Type: uint256", type=int + ) + mint_parser.set_defaults(func=handle_mint) + + name_parser = subcommands.add_parser("name") + add_default_arguments(name_parser, False) + name_parser.set_defaults(func=handle_name) + + owner_of_parser = subcommands.add_parser("owner-of") + add_default_arguments(owner_of_parser, False) + owner_of_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + owner_of_parser.set_defaults(func=handle_owner_of) + + safe_transfer_from_0x42842e0e_parser = subcommands.add_parser( + "safe-transfer-from-0x42842e0e" + ) + add_default_arguments(safe_transfer_from_0x42842e0e_parser, True) + safe_transfer_from_0x42842e0e_parser.add_argument( + "--from-arg", required=True, help="Type: address" + ) + safe_transfer_from_0x42842e0e_parser.add_argument( + "--to", required=True, help="Type: address" + ) + safe_transfer_from_0x42842e0e_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + safe_transfer_from_0x42842e0e_parser.set_defaults( + func=handle_safe_transfer_from_0x42842e0e + ) + + safe_transfer_from_0xb88d4fde_parser = subcommands.add_parser( + "safe-transfer-from-0xb88d4fde" + ) + add_default_arguments(safe_transfer_from_0xb88d4fde_parser, True) + safe_transfer_from_0xb88d4fde_parser.add_argument( + "--from-arg", required=True, help="Type: address" + ) + safe_transfer_from_0xb88d4fde_parser.add_argument( + "--to", required=True, help="Type: address" + ) + safe_transfer_from_0xb88d4fde_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + safe_transfer_from_0xb88d4fde_parser.add_argument( + "--data", required=True, help="Type: bytes", type=bytes_argument_type + ) + safe_transfer_from_0xb88d4fde_parser.set_defaults( + func=handle_safe_transfer_from_0xb88d4fde + ) + + set_approval_for_all_parser = subcommands.add_parser("set-approval-for-all") + add_default_arguments(set_approval_for_all_parser, True) + set_approval_for_all_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + set_approval_for_all_parser.add_argument( + "--approved", required=True, help="Type: bool", type=boolean_argument_type + ) + set_approval_for_all_parser.set_defaults(func=handle_set_approval_for_all) + + supports_interface_parser = subcommands.add_parser("supports-interface") + add_default_arguments(supports_interface_parser, False) + supports_interface_parser.add_argument( + "--interface-id", required=True, help="Type: bytes4", type=bytes_argument_type + ) + supports_interface_parser.set_defaults(func=handle_supports_interface) + + symbol_parser = subcommands.add_parser("symbol") + add_default_arguments(symbol_parser, False) + symbol_parser.set_defaults(func=handle_symbol) + + token_by_index_parser = subcommands.add_parser("token-by-index") + add_default_arguments(token_by_index_parser, False) + token_by_index_parser.add_argument( + "--index", required=True, help="Type: uint256", type=int + ) + token_by_index_parser.set_defaults(func=handle_token_by_index) + + token_of_owner_by_index_parser = subcommands.add_parser("token-of-owner-by-index") + add_default_arguments(token_of_owner_by_index_parser, False) + token_of_owner_by_index_parser.add_argument( + "--owner", required=True, help="Type: address" + ) + token_of_owner_by_index_parser.add_argument( + "--index", required=True, help="Type: uint256", type=int + ) + token_of_owner_by_index_parser.set_defaults(func=handle_token_of_owner_by_index) + + token_uri_parser = subcommands.add_parser("token-uri") + add_default_arguments(token_uri_parser, False) + token_uri_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + token_uri_parser.set_defaults(func=handle_token_uri) + + total_supply_parser = subcommands.add_parser("total-supply") + add_default_arguments(total_supply_parser, False) + total_supply_parser.set_defaults(func=handle_total_supply) + + transfer_from_parser = subcommands.add_parser("transfer-from") + add_default_arguments(transfer_from_parser, True) + transfer_from_parser.add_argument("--from-arg", required=True, help="Type: address") + transfer_from_parser.add_argument("--to", required=True, help="Type: address") + transfer_from_parser.add_argument( + "--token-id", required=True, help="Type: uint256", type=int + ) + transfer_from_parser.set_defaults(func=handle_transfer_from) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/src/Players.sol b/src/Players.sol new file mode 100644 index 00000000..0d41afc5 --- /dev/null +++ b/src/Players.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.19; + +import { ERC721 } from "../lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import { ERC721Enumerable } from "../lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import { Base64 } from "../lib/openzeppelin-contracts/contracts/utils/Base64.sol"; +import { Strings } from "../lib/openzeppelin-contracts/contracts/utils/Strings.sol"; + +contract BeerLeagueBallers is ERC721Enumerable { + string[11] public ProfileImages = [ + "https://badges.moonstream.to/blb/p0.png", + "https://badges.moonstream.to/blb/p1.png", + "https://badges.moonstream.to/blb/p2.png", + "https://badges.moonstream.to/blb/p3.png", + "https://badges.moonstream.to/blb/p4.png", + "https://badges.moonstream.to/blb/p5.png", + "https://badges.moonstream.to/blb/p6.png", + "https://badges.moonstream.to/blb/p7.png", + "https://badges.moonstream.to/blb/p8.png", + "https://badges.moonstream.to/blb/p9.png", + "https://badges.moonstream.to/blb/p10.png" + ]; + + mapping(uint256 => string) public Name; + mapping(uint256 => uint256) public ImageIndex; + + constructor() ERC721("Beer League Ballers", "BLB") { } + + function mint(string memory name, uint256 imageIndex) public returns (uint256) { + require(imageIndex < ProfileImages.length, "BLB.mint: invalid image index"); + uint256 tokenId = totalSupply() + 1; + Name[tokenId] = name; + ImageIndex[tokenId] = imageIndex; + _safeMint(msg.sender, tokenId); + return tokenId; + } + + function burn(uint256 tokenId) public { + require(msg.sender == ownerOf(tokenId), "BLB.burn: caller is not owner"); + _burn(tokenId); + } + + function _metadata(uint256 tokenId) internal view returns (bytes memory json) { + json = abi.encodePacked( + '{"name":"', + Name[tokenId], + " - ", + Strings.toString(tokenId), + '", "image":"', + ProfileImages[ImageIndex[tokenId]], + '"}' + ); + } + + function metadataJSON(uint256 tokenId) public view returns (string memory) { + return string(_metadata(tokenId)); + } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + return string(abi.encodePacked("data:application/json;base64,", Base64.encode(_metadata(tokenId)))); + } +} From 3043e0a734866b40941f4a6eeb2f8eb8522c972c Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 7 Nov 2023 16:30:42 -0800 Subject: [PATCH 08/12] Hooked up blb command to CLI --- python/fullcount/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/fullcount/cli.py b/python/fullcount/cli.py index 0d8c10d6..6fdf953a 100644 --- a/python/fullcount/cli.py +++ b/python/fullcount/cli.py @@ -1,6 +1,6 @@ import argparse -from . import Fullcount, generation_1 +from . import BeerLeagueBallers, Fullcount, generation_1 def generate_cli() -> argparse.ArgumentParser: @@ -19,6 +19,9 @@ def generate_cli() -> argparse.ArgumentParser: generation_1_parser = generation_1.generate_cli() subparsers.add_parser("gen-1", parents=[generation_1_parser], add_help=False) + blb_parser = BeerLeagueBallers.generate_cli() + subparsers.add_parser("blb", parents=[blb_parser], add_help=False) + return parser From 6594b97e342ef4cb732678c95f4a56c7224e1558 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 8 Nov 2023 06:46:09 -0800 Subject: [PATCH 09/12] Added "fullcount randomness" CLI Also added a public `randomSample` method on the `Fullcount` contract for the benefit of game clients and tooling. Added `test/RandomnessConsistency.t.sol` to verify that the randomness expected from `fullcount randomness` is the same as the randomness generated from player nonces on-chain. --- python/README.md | 20 +++++++++++ python/fullcount/cli.py | 5 ++- python/fullcount/randomness.py | 62 ++++++++++++++++++++++++++++++++ src/Fullcount.sol | 14 ++++++-- test/RandomnessConsistency.t.sol | 62 ++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 python/fullcount/randomness.py create mode 100644 test/RandomnessConsistency.t.sol diff --git a/python/README.md b/python/README.md index 5dae8ac4..ebbd4c4b 100644 --- a/python/README.md +++ b/python/README.md @@ -79,3 +79,23 @@ To regenerate the Python interface to the `BeerLeagueBallers` NFT contract: ```bash moonworm generate-brownie -o fullcount -p ../ --foundry -n BeerLeagueBallers --sol-filename Players.sol ``` + +### Randomness + +The `fulltest randomness` command allows you to see the random `uint256` that would be sampled from any +two nonces. + +For example, to check the `uint256` sampled from the pitcher nonce `2384902384` and the batter nonce `98290483902842390482`, +you would run: + +```bash +fullcount randomness -n 2384902384 -N 98290483902842390482 +``` + +This produces: + +``` +$ fullcount randomness -n 2384902384 -N 98290483902842390482 +Nonce 1: 2384902384, Nonce 2: 98290483902842390482, Denominator: 10000 +Random sample: 6727 +``` diff --git a/python/fullcount/cli.py b/python/fullcount/cli.py index 6fdf953a..c64b61ce 100644 --- a/python/fullcount/cli.py +++ b/python/fullcount/cli.py @@ -1,6 +1,6 @@ import argparse -from . import BeerLeagueBallers, Fullcount, generation_1 +from . import BeerLeagueBallers, Fullcount, generation_1, randomness def generate_cli() -> argparse.ArgumentParser: @@ -22,6 +22,9 @@ def generate_cli() -> argparse.ArgumentParser: blb_parser = BeerLeagueBallers.generate_cli() subparsers.add_parser("blb", parents=[blb_parser], add_help=False) + randomness_parser = randomness.generate_cli() + subparsers.add_parser("randomness", parents=[randomness_parser], add_help=False) + return parser diff --git a/python/fullcount/randomness.py b/python/fullcount/randomness.py new file mode 100644 index 00000000..84e56a1c --- /dev/null +++ b/python/fullcount/randomness.py @@ -0,0 +1,62 @@ +""" +Inspect randomness specified by Fullcount nonces +""" + +import argparse + +from brownie import web3 +from eth_abi import encode + + +def randomness(nonce_1: int, nonce_2: int, denominator: int = 10000) -> int: + assert denominator != 0, "denominator cannot be zero" + combination_raw = web3.keccak(encode(["uint256", "uint256"], [nonce_1, nonce_2])) + combination = web3.toInt(combination_raw) + return combination % denominator + + +def handle_randomness(args: argparse.Namespace) -> None: + sample = randomness(args.nonce_1, args.nonce_2, args.denominator) + if not args.test: + print( + f"Nonce 1: {args.nonce_1}, Nonce 2: {args.nonce_2}, Denominator: {args.denominator}" + ) + print("Random sample:", sample) + else: + test_code = f""" +/** +* $ fullcount randomness -n {args.nonce_1} -N {args.nonce_2} -d {args.denominator} +* Nonce 1: {args.nonce_1}, Nonce 2: {args.nonce_1}, Denominator: {args.denominator} +* Random sample: {sample} +*/ +function test_{args.nonce_1}_{args.nonce_2}_{args.denominator}_{sample}() public {{ + assertEq(game.randomSample({args.nonce_1}, {args.nonce_2}, {args.denominator}), {sample}); +}} +""" + print(test_code) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Inspect randomness specified by Fullcount nonces" + ) + + parser.add_argument("-n", "--nonce-1", required=True, type=int, help="Nonce 1") + parser.add_argument("-N", "--nonce-2", required=True, type=int, help="Nonce 2") + parser.add_argument( + "-d", + "--denominator", + required=False, + type=int, + default=10000, + help="Denominator (default: 10,000)", + ) + parser.add_argument( + "--test", + action="store_true", + help="If this flag is set, the output also generates a test that can be included in test/RandomnessConsistency.t.sol", + ) + + parser.set_defaults(func=handle_randomness) + + return parser diff --git a/src/Fullcount.sol b/src/Fullcount.sol index 34e0576b..4b871d98 100644 --- a/src/Fullcount.sol +++ b/src/Fullcount.sol @@ -444,6 +444,17 @@ contract Fullcount is StatBlockBase, EIP712 { emit SwingCommitted(sessionID); } + // Internal method gets inlined into contract methods + function _randomSample(uint256 nonce0, uint256 nonce1, uint256 totalMass) internal pure returns (uint256 sample) { + // Combining the nonces this way prevents overflow concerns when adding two nonces >= 2^255 + sample = uint256(keccak256(abi.encode(nonce0, nonce1))) % totalMass; + } + + // External method makes it easy to test randomness for the purposes of game clients + function randomSample(uint256 nonce0, uint256 nonce1, uint256 totalMass) external pure returns (uint256) { + return _randomSample(nonce0, nonce1, totalMass); + } + function sampleOutcomeFromDistribution( uint256 nonce0, uint256 nonce1, @@ -456,8 +467,7 @@ contract Fullcount is StatBlockBase, EIP712 { uint256 totalMass = distribution[0] + distribution[1] + distribution[2] + distribution[3] + distribution[4] + distribution[5] + distribution[6]; - // Combining the nonces this way prevents overflow concerns when adding two nonces >= 2^255 - uint256 sample = uint256(keccak256(abi.encode(nonce0, nonce1))) % totalMass; + uint256 sample = _randomSample(nonce0, nonce1, totalMass); uint256 cumulativeMass = distribution[0]; if (sample < cumulativeMass) { diff --git a/test/RandomnessConsistency.t.sol b/test/RandomnessConsistency.t.sol new file mode 100644 index 00000000..d4b81874 --- /dev/null +++ b/test/RandomnessConsistency.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { Test, console2 } from "../lib/forge-std/src/Test.sol"; +import { FullcountTestBase } from "./Fullcount.t.sol"; + +/** + * These tests check that the "fullcount randomness" tool generates the same random numbers from + * nonces as the Fullcount smart contract. + * + * Test names are as follows: + * test____ + * + * The expected output for this test would be generated using: + * $ fullcount randomness -n -N -d + */ +contract RandomnessConsistencyTest is FullcountTestBase { + /** + * $ fullcount randomness -n 1 -N 2 -d 10000 + * Nonce 1: 1, Nonce 2: 2, Denominator: 10000 + * Random sample: 9136 + */ + function test_1_2_10000_9136() public { + assertEq(game.randomSample(1, 2, 10_000), 9136); + } + + /** + * $ fullcount randomness -n 1 -N 2 -d 1 + * Nonce 1: 1, Nonce 2: 2, Denominator: 1 + * Random sample: 0 + */ + function test_1_2_1_0() public { + assertEq(game.randomSample(1, 2, 1), 0); + } + + /** + * $ fullcount randomness -n 29384923 -N 984543 -d 10000 + * Nonce 1: 29384923, Nonce 2: 29384923, Denominator: 10000 + * Random sample: 365 + */ + function test_29384923_984543_10000_365() public { + assertEq(game.randomSample(29_384_923, 984_543, 10_000), 365); + } + + /** + * $ fullcount randomness -n 29384923 -N 984543 -d 123984239 + * Nonce 1: 29384923, Nonce 2: 29384923, Denominator: 123984239 + * Random sample: 72037728 + */ + function test_29384923_984543_123984239_72037728() public { + assertEq(game.randomSample(29_384_923, 984_543, 123_984_239), 72_037_728); + } + + /** + * $ fullcount randomness -n 287349237429034239084 -N 239480239842390842390482390 -d 10000 + * Nonce 1: 287349237429034239084, Nonce 2: 287349237429034239084, Denominator: 10000 + * Random sample: 7575 + */ + function test_287349237429034239084_239480239842390842390482390_10000_7575() public { + assertEq(game.randomSample(287_349_237_429_034_239_084, 239_480_239_842_390_842_390_482_390, 10_000), 7575); + } +} From 224d9410c3edccb31f5ebfc216a63055ccfbd48c Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 8 Nov 2023 07:02:37 -0800 Subject: [PATCH 10/12] Added "fullcount randomness grind" Moved previous `fullcount randomness` functionality to `fullcount randomness inspect` --- python/fullcount/randomness.py | 83 ++++++++++++++++++++++++++++---- python/setup.py | 2 +- test/RandomnessConsistency.t.sol | 14 +++--- 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/python/fullcount/randomness.py b/python/fullcount/randomness.py index 84e56a1c..9046610e 100644 --- a/python/fullcount/randomness.py +++ b/python/fullcount/randomness.py @@ -3,20 +3,45 @@ """ import argparse +from typing import Optional from brownie import web3 from eth_abi import encode +from tqdm import tqdm -def randomness(nonce_1: int, nonce_2: int, denominator: int = 10000) -> int: +def sample(nonce_1: int, nonce_2: int, denominator: int = 10000) -> int: assert denominator != 0, "denominator cannot be zero" combination_raw = web3.keccak(encode(["uint256", "uint256"], [nonce_1, nonce_2])) combination = web3.toInt(combination_raw) return combination % denominator -def handle_randomness(args: argparse.Namespace) -> None: - sample = randomness(args.nonce_1, args.nonce_2, args.denominator) +def grind( + nonce_1: Optional[int], nonce_2: Optional[int], denominator: int, target: int +) -> int: + assert ( + nonce_1 is not None or nonce_2 is not None + ), "at least one nonce should be specified" + + is_valid = lambda x: sample(x, nonce_2, denominator) == target + if nonce_2 is None: + is_valid = lambda x: sample(nonce_1, x, denominator) == target + + candidate = 0 + with tqdm() as pbar: + pbar.set_description( + f"Grinding for nonce which produces the random sample {target}" + ) + while not is_valid(candidate): + candidate += 1 + pbar.update(1) + + return candidate + + +def handle_inspect(args: argparse.Namespace) -> None: + sample = sample(args.nonce_1, args.nonce_2, args.denominator) if not args.test: print( f"Nonce 1: {args.nonce_1}, Nonce 2: {args.nonce_2}, Denominator: {args.denominator}" @@ -25,7 +50,7 @@ def handle_randomness(args: argparse.Namespace) -> None: else: test_code = f""" /** -* $ fullcount randomness -n {args.nonce_1} -N {args.nonce_2} -d {args.denominator} +* $ fullcount randomness inspect -n {args.nonce_1} -N {args.nonce_2} -d {args.denominator} * Nonce 1: {args.nonce_1}, Nonce 2: {args.nonce_1}, Denominator: {args.denominator} * Random sample: {sample} */ @@ -36,14 +61,29 @@ def handle_randomness(args: argparse.Namespace) -> None: print(test_code) +def handle_grind(args: argparse.Namespace) -> None: + result = grind(args.nonce_1, args.nonce_2, args.denominator, args.target) + print(result) + + def generate_cli() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Inspect randomness specified by Fullcount nonces" ) + parser.set_defaults(func=lambda _: parser.print_help()) + + subparsers = parser.add_subparsers() - parser.add_argument("-n", "--nonce-1", required=True, type=int, help="Nonce 1") - parser.add_argument("-N", "--nonce-2", required=True, type=int, help="Nonce 2") - parser.add_argument( + inspect_parser = subparsers.add_parser( + "inspect", help="Inspect randomness specified by Fullcount nonces" + ) + inspect_parser.add_argument( + "-n", "--nonce-1", required=True, type=int, help="Nonce 1" + ) + inspect_parser.add_argument( + "-N", "--nonce-2", required=True, type=int, help="Nonce 2" + ) + inspect_parser.add_argument( "-d", "--denominator", required=False, @@ -51,12 +91,37 @@ def generate_cli() -> argparse.ArgumentParser: default=10000, help="Denominator (default: 10,000)", ) - parser.add_argument( + inspect_parser.add_argument( + "-t", "--test", action="store_true", help="If this flag is set, the output also generates a test that can be included in test/RandomnessConsistency.t.sol", ) - parser.set_defaults(func=handle_randomness) + inspect_parser.set_defaults(func=handle_inspect) + + grind_parser = subparsers.add_parser( + "grind", + help="Find a nonce which, when paired with a given nonce produces the target sample", + ) + grind_parser.add_argument( + "-n", "--nonce-1", required=False, type=int, default=None, help="Nonce 1" + ) + grind_parser.add_argument( + "-N", "--nonce-2", required=False, type=int, default=None, help="Nonce 2" + ) + grind_parser.add_argument( + "-d", + "--denominator", + required=False, + type=int, + default=10000, + help="Denominator (default: 10,000)", + ) + grind_parser.add_argument( + "-t", "--target", required=True, type=int, help="Sample to grind for" + ) + + grind_parser.set_defaults(func=handle_grind) return parser diff --git a/python/setup.py b/python/setup.py index 534180b9..9f8cb852 100644 --- a/python/setup.py +++ b/python/setup.py @@ -14,7 +14,7 @@ name="fullcount", version=VERSION, packages=find_packages(), - install_requires=["eth-brownie"], + install_requires=["eth-brownie", "tqdm"], extras_require={ "dev": ["black", "moonworm[moonstream]"], "distribute": ["setuptools", "twine", "wheel"], diff --git a/test/RandomnessConsistency.t.sol b/test/RandomnessConsistency.t.sol index d4b81874..472f3e34 100644 --- a/test/RandomnessConsistency.t.sol +++ b/test/RandomnessConsistency.t.sol @@ -5,18 +5,18 @@ import { Test, console2 } from "../lib/forge-std/src/Test.sol"; import { FullcountTestBase } from "./Fullcount.t.sol"; /** - * These tests check that the "fullcount randomness" tool generates the same random numbers from + * These tests check that the "fullcount randomness inspect" tool generates the same random numbers from * nonces as the Fullcount smart contract. * * Test names are as follows: * test____ * * The expected output for this test would be generated using: - * $ fullcount randomness -n -N -d + * $ fullcount randomness inspect -n -N -d */ contract RandomnessConsistencyTest is FullcountTestBase { /** - * $ fullcount randomness -n 1 -N 2 -d 10000 + * $ fullcount randomness inspect -n 1 -N 2 -d 10000 * Nonce 1: 1, Nonce 2: 2, Denominator: 10000 * Random sample: 9136 */ @@ -25,7 +25,7 @@ contract RandomnessConsistencyTest is FullcountTestBase { } /** - * $ fullcount randomness -n 1 -N 2 -d 1 + * $ fullcount randomness inspect -n 1 -N 2 -d 1 * Nonce 1: 1, Nonce 2: 2, Denominator: 1 * Random sample: 0 */ @@ -34,7 +34,7 @@ contract RandomnessConsistencyTest is FullcountTestBase { } /** - * $ fullcount randomness -n 29384923 -N 984543 -d 10000 + * $ fullcount randomness inspect -n 29384923 -N 984543 -d 10000 * Nonce 1: 29384923, Nonce 2: 29384923, Denominator: 10000 * Random sample: 365 */ @@ -43,7 +43,7 @@ contract RandomnessConsistencyTest is FullcountTestBase { } /** - * $ fullcount randomness -n 29384923 -N 984543 -d 123984239 + * $ fullcount randomness inspect -n 29384923 -N 984543 -d 123984239 * Nonce 1: 29384923, Nonce 2: 29384923, Denominator: 123984239 * Random sample: 72037728 */ @@ -52,7 +52,7 @@ contract RandomnessConsistencyTest is FullcountTestBase { } /** - * $ fullcount randomness -n 287349237429034239084 -N 239480239842390842390482390 -d 10000 + * $ fullcount randomness inspect -n 287349237429034239084 -N 239480239842390842390482390 -d 10000 * Nonce 1: 287349237429034239084, Nonce 2: 287349237429034239084, Denominator: 10000 * Random sample: 7575 */ From 176a7801f875dbfc7de8bf845a9802e6918143ca Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 8 Nov 2023 08:29:51 -0800 Subject: [PATCH 11/12] Fixed error in randomness.py --- python/fullcount/randomness.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/fullcount/randomness.py b/python/fullcount/randomness.py index 9046610e..861a7cc2 100644 --- a/python/fullcount/randomness.py +++ b/python/fullcount/randomness.py @@ -10,7 +10,7 @@ from tqdm import tqdm -def sample(nonce_1: int, nonce_2: int, denominator: int = 10000) -> int: +def random_sample(nonce_1: int, nonce_2: int, denominator: int = 10000) -> int: assert denominator != 0, "denominator cannot be zero" combination_raw = web3.keccak(encode(["uint256", "uint256"], [nonce_1, nonce_2])) combination = web3.toInt(combination_raw) @@ -24,9 +24,9 @@ def grind( nonce_1 is not None or nonce_2 is not None ), "at least one nonce should be specified" - is_valid = lambda x: sample(x, nonce_2, denominator) == target + is_valid = lambda x: random_sample(x, nonce_2, denominator) == target if nonce_2 is None: - is_valid = lambda x: sample(nonce_1, x, denominator) == target + is_valid = lambda x: random_sample(nonce_1, x, denominator) == target candidate = 0 with tqdm() as pbar: @@ -41,7 +41,7 @@ def grind( def handle_inspect(args: argparse.Namespace) -> None: - sample = sample(args.nonce_1, args.nonce_2, args.denominator) + sample = random_sample(args.nonce_1, args.nonce_2, args.denominator) if not args.test: print( f"Nonce 1: {args.nonce_1}, Nonce 2: {args.nonce_2}, Denominator: {args.denominator}" From 1170c7f4370c79920c2648fbbe9b6921b699a513 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 8 Nov 2023 11:39:57 -0800 Subject: [PATCH 12/12] regenerated Python interface to Fullcount contract --- python/fullcount/Fullcount.py | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/python/fullcount/Fullcount.py b/python/fullcount/Fullcount.py index 4ed8dab6..5560ab9e 100644 --- a/python/fullcount/Fullcount.py +++ b/python/fullcount/Fullcount.py @@ -342,6 +342,18 @@ def pitch_hash( nonce, speed, vertical, horizontal, block_identifier=block_number ) + def random_sample( + self, + nonce0: int, + nonce1: int, + total_mass: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.randomSample.call( + nonce0, nonce1, total_mass, block_identifier=block_number + ) + def resolve( self, pitch: tuple, @@ -803,6 +815,18 @@ def handle_pitch_hash(args: argparse.Namespace) -> None: print(result) +def handle_random_sample(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = Fullcount(args.address) + result = contract.random_sample( + nonce0=args.nonce0, + nonce1=args.nonce1, + total_mass=args.total_mass, + block_number=args.block_number, + ) + print(result) + + def handle_resolve(args: argparse.Namespace) -> None: network.connect(args.network) contract = Fullcount(args.address) @@ -1166,6 +1190,19 @@ def generate_cli() -> argparse.ArgumentParser: ) pitch_hash_parser.set_defaults(func=handle_pitch_hash) + random_sample_parser = subcommands.add_parser("random-sample") + add_default_arguments(random_sample_parser, False) + random_sample_parser.add_argument( + "--nonce0", required=True, help="Type: uint256", type=int + ) + random_sample_parser.add_argument( + "--nonce1", required=True, help="Type: uint256", type=int + ) + random_sample_parser.add_argument( + "--total-mass", required=True, help="Type: uint256", type=int + ) + random_sample_parser.set_defaults(func=handle_random_sample) + resolve_parser = subcommands.add_parser("resolve") add_default_arguments(resolve_parser, False) resolve_parser.add_argument("--pitch", required=True, help="Type: tuple", type=eval)