From 894495ca632a27944f087cfe997c486f5051b16f Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 2 Jan 2023 17:23:49 +0100 Subject: [PATCH] Add Longevity and Braveness actions. --- README.md | 56 +++++++++++++++++++++++- poetry.lock | 38 +++++++++++++--- pyproject.toml | 5 ++- src/algorealm.py | 111 ++++++++++++++++++++++++++++++++--------------- src/query.py | 105 ++++++++++++++++++++++++++++++-------------- 5 files changed, 237 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 16af3a0..9390033 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ AlgoRealm, only generous heart will ever rule over Algorand. (by cusma) Usage: algorealm.py poem algorealm.py dynasty [--test] + algorealm.py longevity (--crown | --sceptre) [--test] + algorealm.py braveness (--crown | --sceptre) [--test] algorealm.py claim-majesty (--crown | --sceptre) [--test] algorealm.py claim-card algorealm.py buy-order [--notify] @@ -74,6 +76,8 @@ Usage: Commands: poem AlgoRealm's poem. dynasty Print the glorious dynasty of AlgoRealm's Majesties. + longevity Print AlgoRealm's Majesties longevity. + braveness Print AlgoRealm's Majesties braveness. claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. buy-order Place an order for the AlgoRealm Card. @@ -91,7 +95,7 @@ Options: > In case you want to give a try, you can play AlgoRealm on TestNet adding `-t` > to CLI commands. -### 3. AlgoRealm Dynasty +### 3. AlgoRealm Dynasty, Longevity and Braveness Who are the Majesties of the Algorand realm? @@ -141,6 +145,56 @@ on Block: 14989913 donating: 4 microALGOs to the Rewards Pool. on Block: 14989913 donating: 4 microALGOs to the Rewards Pool. ``` +4. Which was the longest lasting Majesty? +```shell +$ python3 algorealm.py longevity --crown +``` + +``` + *** šŸ‘‘ RANDOMIC MAJESTY LONGEVITY *** + ++--------------------+--------------------+ +| Majesty Name | Longevity (blocks) | ++--------------------+--------------------+ +| MillionAlgosFather | 5768768 | +| nullun | 3366046 | +| jkbishbish | 1357847 | +| Matt | 1248429 | +| renangeo | 416539 | +| šŸ‘‘šŸ…æļø | 158346 | +| tmc | 53895 | +| MillionAlgosFather | 32978 | +| nullun | 3369 | ++--------------------+--------------------+ +``` + +5. Who is the bravest Majesty of all time? +```shell +$ python3 algorealm.py braveness --crown +``` + +``` +*** šŸ‘‘ RANDOMIC MAJESTY BRAVENESS *** + ++--------------------+-----------+ +| Majesty Name | Braveness | ++--------------------+-----------+ +| renangeo | 7.824 | +| MillionAlgosFather | 4.605 | +| šŸ‘‘šŸ…æļø | 1.609 | +| jkbishbish | 1 | +| tmc | 0.405 | +| nullun | 0.288 | +| nullun | 0.0 | +| MillionAlgosFather | 0.0 | +| Matt | 0.0 | ++--------------------+-----------+ +``` + +> Braveness is based on the relative gorwth of donation amounts (`d'`, `d`): +> +> `braveness = ln(d') - ln(d)` + ### 4. Claim the Crown of Entropy or the Sceptre of Proof Chose your `` and become part of the Dynasty! Remember that to dethrone the current Majesties you must donate to the Algorand's Rewards Pool more `` than the last donation. diff --git a/poetry.lock b/poetry.lock index 8df619a..60fc535 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -75,7 +75,7 @@ python-versions = "*" [[package]] name = "pathspec" -version = "0.10.2" +version = "0.10.3" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -83,19 +83,33 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.5.4" +version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.4)", "sphinx (>=5.3)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] + +[[package]] +name = "prettytable" +version = "3.5.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] [[package]] name = "py-algorand-sdk" -version = "1.20.1" +version = "1.20.2" description = "Algorand SDK in Python" category = "main" optional = false @@ -145,10 +159,18 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" + [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "05926e82d4f7483312cd284ec6467857bdd935dbdd66ff52b5a3573867adae85" +content-hash = "fe2b504c5c1fda1a1b8ae6b9eb23fbe56a89d2f4b317c984338f9f07789b7694" [metadata.files] black = [] @@ -286,6 +308,7 @@ mypy-extensions = [ ] pathspec = [] platformdirs = [] +prettytable = [] py-algorand-sdk = [] pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, @@ -308,3 +331,4 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +wcwidth = [] diff --git a/pyproject.toml b/pyproject.toml index da8d33d..74a7046 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,10 +9,11 @@ license = "MIT" python = "^3.10" docopt = "^0.6.2" msgpack = "^1.0.4" -py-algorand-sdk = "^1.20.1" +py-algorand-sdk = "^1.20.2" +prettytable = "^3.5.0" [tool.poetry.dev-dependencies] -black = "^22.10.0" +black = "^22.12.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/src/algorealm.py b/src/algorealm.py index d75bd48..9df968b 100644 --- a/src/algorealm.py +++ b/src/algorealm.py @@ -4,6 +4,8 @@ Usage: algorealm.py poem algorealm.py dynasty [--test] + algorealm.py longevity (--crown | --sceptre) [--test] + algorealm.py braveness (--crown | --sceptre) [--test] algorealm.py claim-majesty (--crown | --sceptre) [--test] algorealm.py claim-card algorealm.py buy-order [--notify] @@ -14,6 +16,8 @@ Commands: poem AlgoRealm's poem. dynasty Print the glorious dynasty of AlgoRealm's Majesties. + longevity Print AlgoRealm's Majesties longevity. + braveness Print AlgoRealm's Majesties braveness. claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. buy-order Place an order for the AlgoRealm Card. @@ -35,6 +39,7 @@ from algosdk.v2client.algod import AlgodClient from algosdk.v2client.indexer import IndexerClient from docopt import docopt +from prettytable import PrettyTable import actions import query @@ -135,60 +140,96 @@ def main(): algod_client = build_algod_client(test=args["--test"]) indexer_client = build_indexer_client(test=args["--test"]) + if args["--test"]: + crown_nft_id = TEST_CROWN_ID + sceptre_nft_id = TEST_SCEPTRE_ID + algorealm_app_id = TEST_ALGOREALM_APP_ID + algorealm_contract = TEST_ALGOREALM_LAW_BYTECODE + algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK + else: + crown_nft_id = CROWN_ID + sceptre_nft_id = SCEPTRE_ID + algorealm_app_id = ALGOREALM_APP_ID + algorealm_contract = ALGOREALM_LAW_BYTECODE + algorealm_first_round = ALGOREALM_FIRST_BLOCK + # CLI if args["dynasty"]: - if args["--test"]: - algorealm_app_id = TEST_ALGOREALM_APP_ID - algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK + claims = query.claims_history( + client=indexer_client, + algorealm_app_id=algorealm_app_id, + algorealm_first_round=algorealm_first_round, + ) + + print("\t\t\t\t*** DYNASTY ***\n") + return print(*["\n", *query.dynasty(claims)]) + + if args["longevity"]: + claims = query.claims_history( + client=indexer_client, + algorealm_app_id=algorealm_app_id, + algorealm_first_round=algorealm_first_round, + ) + latest_block = algod_client.status()["last-round"] + + if args["--crown"]: + majesty_title = "šŸ‘‘ RANDOMIC" + claim_select = "Crown" else: - algorealm_app_id = ALGOREALM_APP_ID - algorealm_first_round = ALGOREALM_FIRST_BLOCK - print( - r""" - *** DYNASTY *** - """ + majesty_title = "šŸŖ„ VERIFIABLE" + claim_select = "Sceptre" + + majesty_longevity = query.longevity(claims, latest_block, claim_select) + + longevity_table = PrettyTable() + longevity_table.field_names = ["Majesty Name", "Longevity (blocks)"] + longevity_table.add_rows( + [[claim["name"], claim["longevity"]] for claim in majesty_longevity] ) - return print( - *[ - "\n", - *query.history( - client=indexer_client, - algorealm_app_id=algorealm_app_id, - algorealm_first_round=algorealm_first_round, - ), - ] + + print(f"\t\t*** {majesty_title} MAJESTY LONGEVITY ***\n") + return print(longevity_table) + + if args["braveness"]: + claims = query.claims_history( + client=indexer_client, + algorealm_app_id=algorealm_app_id, + algorealm_first_round=algorealm_first_round, ) + if args["--crown"]: + majesty_title = "šŸ‘‘ RANDOMIC" + claim_select = "Crown" + else: + majesty_title = "šŸŖ„ VERIFIABLE" + claim_select = "Sceptre" + + majesty_braveness = query.braveness(claims, claim_select) + + braveness_table = PrettyTable() + braveness_table.field_names = ["Majesty Name", "Braveness"] + braveness_table.add_rows( + [[claim["name"], claim["braveness"]] for claim in majesty_braveness] + ) + + print(f"\t\t*** {majesty_title} MAJESTY BRAVENESS ***\n") + return print(braveness_table) + if args["claim-majesty"]: majesty_name = args[""] - if args["--test"]: - algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK - algorealm_contract = TEST_ALGOREALM_LAW_BYTECODE - algorealm_app_id = TEST_ALGOREALM_APP_ID - else: - algorealm_first_round = ALGOREALM_FIRST_BLOCK - algorealm_contract = ALGOREALM_LAW_BYTECODE - algorealm_app_id = ALGOREALM_APP_ID - if args["--crown"]: proclaim = ( f"\nšŸ‘‘ Glory to {majesty_name}, the Randomic Majesty of Algorand! šŸŽ‰\n" ) claim_select = "Crown" - if args["--test"]: - nft_id = TEST_CROWN_ID - else: - nft_id = CROWN_ID + nft_id = crown_nft_id else: proclaim = ( f"\nšŸŖ„ Glory to {majesty_name}, the Verifiable Majesty of Algorand! šŸŽ‰\n" ) claim_select = "Sceptre" - if args["--test"]: - nft_id = TEST_SCEPTRE_ID - else: - nft_id = SCEPTRE_ID + nft_id = sceptre_nft_id user = actions.get_user() algorealm_law = actions.get_contract_account(algorealm_contract) diff --git a/src/query.py b/src/query.py index f971fab..1ec6018 100644 --- a/src/query.py +++ b/src/query.py @@ -1,5 +1,7 @@ import base64 +import math import time +from operator import itemgetter from algosdk.error import IndexerHTTPError from algosdk.v2client.indexer import IndexerClient @@ -73,11 +75,11 @@ def algorelm_nft_txns( return nft_txns -def history( +def claims_history( client: IndexerClient, algorealm_app_id: int, algorealm_first_round: int, -) -> list: +) -> list[dict]: """ Retrieve the AlgoRealm Majesties claims history from chain. @@ -111,42 +113,27 @@ def history( if not calls: quit("āŒ Unable to connect to Indexer Client!") - claims_history = [] - name = "" - claim = "" + claims = [] for call in calls: call_args = call["application-transaction"]["application-args"] # Check is an NFT claim call if len(call_args) == 2: - block = call["confirmed-round"] - nft = call_args[0].encode() - donation = call["global-state-delta"][0]["value"]["uint"] - # Check is a different claimer (2 elements in the state delta) - if len(call["global-state-delta"]) == 2: - name = base64.b64decode( - call["global-state-delta"][1]["value"]["bytes"] - ).decode() - if nft == base64.b64encode(b"Crown"): - claim = ( - f"šŸ‘‘ {name} claimed the Crown of Entropy\n" - f"on Block: {block} donating: {donation} microALGOs " - f"to the Rewards Pool.\n\n" - ) - elif nft == base64.b64encode(b"Sceptre"): - claim = ( - f"šŸŖ„ {name} claimed the Sceptre of Proof\n" - f"on Block: {block} donating: {donation} microALGOs " - f"to the Rewards Pool.\n\n" - ) + claim = { + "block": call["confirmed-round"], + "nft": base64.b64decode(call_args[0]).decode(), + "name": base64.b64decode(call_args[1]).decode(), + "donation": call["global-state-delta"][0]["value"]["uint"], + } + if claim["nft"] == "Crown": + claim["symbol"] = "šŸ‘‘" + claim["nft_name"] = "Crown of Entropy" + claim["title"] = "Randomic Majesty" else: - pass - - claims_history += [claim] - - else: - pass - - return claims_history + claim["symbol"] = "šŸŖ„" + claim["nft_name"] = "Sceptre of Proof" + claim["title"] = "Verifiable Majesty" + claims += [claim] + return claims def current_owner( @@ -191,3 +178,55 @@ def current_owner( for txn in nft_txns: if txn["asset-transfer-transaction"]["amount"] == 1: return txn["asset-transfer-transaction"]["receiver"] + + +def dynasty(claims: list[dict]) -> list: + majesty_claims = [] + for claim in claims: + majesty = ( + f"{claim['symbol']} {claim['name']} became the {claim['title']} " + f"on Block: {claim['block']}\n" + f"claiming the {claim['nft_name']} with a donation of:\n" + f"{claim['donation']} microALGOs to the Rewards Pool.\n\n" + ) + majesty_claims += [majesty] + return majesty_claims + + +def longevity( + claims: list[dict], latest_block: int, majesty_selector: str +) -> list[dict]: + assert majesty_selector == "Crown" or majesty_selector == "Sceptre" + + majesty_claims = [] + for claim in claims: + if claim["nft"] == majesty_selector: + majesty_claims += [claim] + + claim_block = [claim["block"] for claim in majesty_claims] + majesty_longevity = [ + new - old for new, old in zip(claim_block[1:], claim_block[:-1]) + ] + majesty_longevity.append(latest_block - claim_block[-1]) + + for claim, blocks in zip(majesty_claims, majesty_longevity): + claim["longevity"] = blocks + return sorted(majesty_claims, key=itemgetter("longevity"), reverse=True) + + +def braveness(claims: list[dict], majesty_selector: str) -> list[dict]: + assert majesty_selector == "Crown" or majesty_selector == "Sceptre" + + majesty_claims = [] + for claim in claims: + if claim["nft"] == majesty_selector: + majesty_claims += [claim] + claim_donation = [claim["donation"] for claim in majesty_claims] + + majesty_braveness = [1] + for new, old in zip(claim_donation[1:], claim_donation[:-1]): + majesty_braveness.append(math.log(new) - math.log(old)) + + for claim, points in zip(majesty_claims, majesty_braveness): + claim["braveness"] = round(points, 3) + return sorted(majesty_claims, key=itemgetter("braveness"), reverse=True)