diff --git a/README.md b/README.md index 922255eb5..5de18b722 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Examples in this guide are simplified Hic Et Nunc demo. Create a new YAML file and adapt the following example to your needs: ```yaml -spec_version: 0.0.1 +spec_version: 0.1 package: demo_hic_et_nunc database: diff --git a/docker-compose.yml b/docker-compose.yml index 491091415..602641ca7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,16 +12,16 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} - ADMIN_SECRET=${ADMIN_SECRET:-changeme} - # quipuswap: - # build: . - # depends_on: - # - db - # volumes: - # - ./src/demo_quipuswap/dipdup-docker.yml:/home/dipdup/dipdup.yml - # restart: always - # environment: - # - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} - # - ADMIN_SECRET=${ADMIN_SECRET:-changeme} + quipuswap: + build: . + depends_on: + - db + volumes: + - ./src/demo_quipuswap/dipdup-docker.yml:/home/dipdup/dipdup.yml + restart: always + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} + - ADMIN_SECRET=${ADMIN_SECRET:-changeme} tzcolors: build: . @@ -34,8 +34,21 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} - ADMIN_SECRET=${ADMIN_SECRET:-changeme} + domains: + build: . + depends_on: + - db + volumes: + - ./src/demo_tezos_domains/dipdup-docker.yml:/home/dipdup/dipdup.yml + restart: always + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} + - ADMIN_SECRET=${ADMIN_SECRET:-changeme} + db: image: postgres:13 + ports: + - 127.0.0.1:6423:5432 restart: always volumes: - db:/var/lib/postgres/data diff --git a/poetry.lock b/poetry.lock index f01d8cc68..eb4f1d8c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,7 +65,7 @@ python-versions = "*" [[package]] name = "argcomplete" -version = "1.12.2" +version = "1.12.3" description = "Bash tab completion for argparse" category = "main" optional = false @@ -204,7 +204,7 @@ toml = ["toml"] [[package]] name = "datamodel-code-generator" -version = "0.10.2" +version = "0.11.1" description = "Datamodel Code Generator" category = "main" optional = false @@ -828,7 +828,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "c2497b978eb38568a92e3df5e157936e966e76dd959954220c0a4fab9a8a5099" +content-hash = "ea4bb57e61832c732eedbc39857942e763f61663bda64bc7fab32e40c92a87e5" [metadata.files] aiohttp = [ @@ -887,8 +887,8 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] argcomplete = [ - {file = "argcomplete-1.12.2-py2.py3-none-any.whl", hash = "sha256:17f01a9b9b9ece3e6b07058eae737ad6e10de8b4e149105f84614783913aba71"}, - {file = "argcomplete-1.12.2.tar.gz", hash = "sha256:de0e1282330940d52ea92a80fea2e4b9e0da1932aaa570f84d268939d1897b04"}, + {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, + {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, ] astroid = [ {file = "astroid-2.5.3-py3-none-any.whl", hash = "sha256:bea3f32799fbb8581f58431c12591bc20ce11cbc90ad82e2ea5717d94f2080d5"}, @@ -1001,8 +1001,8 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] datamodel-code-generator = [ - {file = "datamodel-code-generator-0.10.2.tar.gz", hash = "sha256:266df75190b72d87ff0728e01507c6e5745c5fae691ed7fb7901afe45df4a1dd"}, - {file = "datamodel_code_generator-0.10.2-py3-none-any.whl", hash = "sha256:ecaff537e12701b8eb7930ab3810415ee734ca30ec30aa6db958f4b3938b01eb"}, + {file = "datamodel-code-generator-0.11.1.tar.gz", hash = "sha256:74606826a38313265945384dabb35a1e474df628fcdd61ad93c3bcb30c040caa"}, + {file = "datamodel_code_generator-0.11.1-py3-none-any.whl", hash = "sha256:741d5d772c8efcc72a5442902319ebd835cd04567e28411e6ab022bb7b3ca0d0"}, ] diff-cover = [ {file = "diff_cover-5.0.1-py3-none-any.whl", hash = "sha256:1953185a930f6c973d7ea752989246e7bf6d3181b73e925437664a47e39c63ff"}, @@ -1094,39 +1094,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -1330,26 +1311,18 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, diff --git a/pyproject.toml b/pyproject.toml index df738102b..cc9a178d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ python = "^3.8" aiohttp = "^3.7.4" aiomysql = "^0.0.21" asyncpg = "^0.22.0" -datamodel-code-generator = "^0.10.0" +datamodel-code-generator = "^0.11.1" "ruamel.yaml" = "^0.17.2" tortoise-orm = "^0.17.1" pydantic = "^1.8.1" diff --git a/src/demo_hic_et_nunc/dipdup.yml b/src/demo_hic_et_nunc/dipdup.yml index ba2bb7ded..044fc2c0c 100644 --- a/src/demo_hic_et_nunc/dipdup.yml +++ b/src/demo_hic_et_nunc/dipdup.yml @@ -1,4 +1,4 @@ -spec_version: 0.0.1 +spec_version: 0.1 package: demo_hic_et_nunc database: diff --git a/src/demo_quipuswap/dipdup-docker.yml b/src/demo_quipuswap/dipdup-docker.yml index b70c36ccb..e8ef33a6b 100644 --- a/src/demo_quipuswap/dipdup-docker.yml +++ b/src/demo_quipuswap/dipdup-docker.yml @@ -69,6 +69,10 @@ templates: entrypoint: divestLiquidity - destination: entrypoint: transfer + - callback: on_fa12_withdraw_profit + pattern: + - destination: + entrypoint: withdrawProfit quipuswap_fa2: kind: operation @@ -99,6 +103,10 @@ templates: entrypoint: divestLiquidity - destination: entrypoint: transfer + - callback: on_fa20_withdraw_profit + pattern: + - destination: + entrypoint: withdrawProfit indexes: kusd_mainnet: diff --git a/src/demo_quipuswap/dipdup.yml b/src/demo_quipuswap/dipdup.yml index 7a2d59048..4ea0cda9a 100644 --- a/src/demo_quipuswap/dipdup.yml +++ b/src/demo_quipuswap/dipdup.yml @@ -1,4 +1,4 @@ -spec_version: 0.0.1 +spec_version: 0.1 package: demo_quipuswap database: @@ -60,6 +60,10 @@ templates: entrypoint: divestLiquidity - destination: entrypoint: transfer + - callback: on_fa12_withdraw_profit + pattern: + - destination: + entrypoint: withdrawProfit quipuswap_fa2: kind: operation @@ -90,6 +94,10 @@ templates: entrypoint: divestLiquidity - destination: entrypoint: transfer + - callback: on_fa20_withdraw_profit + pattern: + - destination: + entrypoint: withdrawProfit indexes: kusd_mainnet: diff --git a/src/demo_quipuswap/handlers/on_fa12_divest_liquidity.py b/src/demo_quipuswap/handlers/on_fa12_divest_liquidity.py index 5bed1704f..20feff29a 100644 --- a/src/demo_quipuswap/handlers/on_fa12_divest_liquidity.py +++ b/src/demo_quipuswap/handlers/on_fa12_divest_liquidity.py @@ -1,8 +1,10 @@ from decimal import Decimal +from typing import cast import demo_quipuswap.models as models from demo_quipuswap.types.fa12_token.parameter.transfer import Transfer from demo_quipuswap.types.quipu_fa12.parameter.divest_liquidity import DivestLiquidity +from demo_quipuswap.types.quipu_fa12.storage import Storage as QuipuFA12Storage from dipdup.models import HandlerContext, OperationContext @@ -15,12 +17,24 @@ async def on_fa12_divest_liquidity( if ctx.template_values is None: raise Exception('This index must be templated') + storage = cast(QuipuFA12Storage, divest_liquidity.storage) # FIXME: remove + decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=divest_liquidity.data.sender_address) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) - position, _ = await models.Position.get_or_create(trader=trader, instrument=instrument) + symbol = ctx.template_values['symbol'] + trader = divest_liquidity.data.sender_address + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) transaction = next(op for op in ctx.operations if op.amount) - position.tez_qty -= Decimal(transaction.amount) / (10 ** 6) # type: ignore - position.token_qty -= Decimal(transfer.parameter.value) / (10 ** decimals) + + tez_qty = Decimal(transaction.amount) / (10 ** 6) + token_qty = Decimal(transfer.parameter.value) / (10 ** decimals) + shares_qty = int(divest_liquidity.parameter.shares) + + price = (Decimal(storage.storage.tez_pool) / (10 ** 6)) / (Decimal(storage.storage.token_pool) / (10 ** decimals)) + share_px = (tez_qty + price * token_qty) / shares_qty + + position.realized_pl += shares_qty * (share_px - position.avg_share_px) + position.shares_qty -= shares_qty # type: ignore + assert position.shares_qty >= 0, divest_liquidity.data.hash + await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa12_invest_liquidity.py b/src/demo_quipuswap/handlers/on_fa12_invest_liquidity.py index cbe53bacc..7071d2c54 100644 --- a/src/demo_quipuswap/handlers/on_fa12_invest_liquidity.py +++ b/src/demo_quipuswap/handlers/on_fa12_invest_liquidity.py @@ -1,8 +1,10 @@ from decimal import Decimal +from typing import cast import demo_quipuswap.models as models from demo_quipuswap.types.fa12_token.parameter.transfer import Transfer from demo_quipuswap.types.quipu_fa12.parameter.invest_liquidity import InvestLiquidity +from demo_quipuswap.types.quipu_fa12.storage import Storage as QuipuFA12Storage from dipdup.models import HandlerContext, OperationContext @@ -15,11 +17,24 @@ async def on_fa12_invest_liquidity( if ctx.template_values is None: raise Exception('This index must be templated') + storage = cast(QuipuFA12Storage, invest_liquidity.storage) # FIXME: remove + decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=invest_liquidity.data.sender_address) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) - position, _ = await models.Position.get_or_create(trader=trader, instrument=instrument) + symbol = ctx.template_values['symbol'] + trader = invest_liquidity.data.sender_address + + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) + + tez_qty = Decimal(invest_liquidity.data.amount) / (10 ** 6) + token_qty = Decimal(transfer.parameter.value) / (10 ** decimals) + new_shares_qty = int(storage.storage.ledger[trader].balance) + int(storage.storage.ledger[trader].frozen_balance) + + price = (Decimal(storage.storage.tez_pool) / (10 ** 6)) / (Decimal(storage.storage.token_pool) / (10 ** decimals)) + value = tez_qty + price * token_qty + share_px = value / (new_shares_qty - position.shares_qty) + assert share_px > 0, invest_liquidity.data.hash + + position.avg_share_px = (position.shares_qty * position.avg_share_px + value) / new_shares_qty + position.shares_qty = new_shares_qty # type: ignore - position.tez_qty += Decimal(invest_liquidity.data.amount) / (10 ** 6) # type: ignore - position.token_qty += Decimal(transfer.parameter.value) / (10 ** decimals) await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa12_tez_to_token.py b/src/demo_quipuswap/handlers/on_fa12_tez_to_token.py index d5200e2bb..bbae86937 100644 --- a/src/demo_quipuswap/handlers/on_fa12_tez_to_token.py +++ b/src/demo_quipuswap/handlers/on_fa12_tez_to_token.py @@ -15,19 +15,21 @@ async def on_fa12_tez_to_token( raise Exception('This index must be templated') decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=transfer.parameter.to) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) + symbol = ctx.template_values['symbol'] + trader = tez_to_token_payment.data.sender_address min_token_quantity = Decimal(tez_to_token_payment.parameter.min_out) / (10 ** decimals) token_quantity = Decimal(transfer.parameter.value) / (10 ** decimals) tez_quantity = Decimal(tez_to_token_payment.data.amount) / (10 ** 6) + assert min_token_quantity <= token_quantity, tez_to_token_payment.data.hash + trade = models.Trade( - instrument=instrument, + symbol=symbol, trader=trader, side=models.TradeSide.BUY, quantity=token_quantity, price=token_quantity / tez_quantity, - slippage=((min_token_quantity / token_quantity) - 1) * 100, + slippage=(1 - (min_token_quantity / token_quantity)).quantize(Decimal('0.000001')), level=transfer.data.level, timestamp=transfer.data.timestamp, ) diff --git a/src/demo_quipuswap/handlers/on_fa12_token_to_tez.py b/src/demo_quipuswap/handlers/on_fa12_token_to_tez.py index 96c65ab86..e5fa94405 100644 --- a/src/demo_quipuswap/handlers/on_fa12_token_to_tez.py +++ b/src/demo_quipuswap/handlers/on_fa12_token_to_tez.py @@ -15,20 +15,22 @@ async def on_fa12_token_to_tez( raise Exception('This index must be templated') decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=transfer.parameter.to) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) + symbol = ctx.template_values['symbol'] + trader = token_to_tez_payment.data.sender_address - min_tez_quantity = Decimal(token_to_tez_payment.parameter.min_out) + min_tez_quantity = Decimal(token_to_tez_payment.parameter.min_out) / (10 ** 6) token_quantity = Decimal(token_to_tez_payment.parameter.amount) / (10 ** decimals) transaction = next(op for op in ctx.operations if op.amount) tez_quantity = Decimal(transaction.amount) / (10 ** 6) + assert min_tez_quantity <= tez_quantity, token_to_tez_payment.data.hash + trade = models.Trade( - instrument=instrument, + symbol=symbol, trader=trader, side=models.TradeSide.SELL, quantity=token_quantity, price=token_quantity / tez_quantity, - slippage=((min_tez_quantity / tez_quantity) - 1) * 100, + slippage=(1 - (min_tez_quantity / tez_quantity)).quantize(Decimal('0.000001')), level=transfer.data.level, timestamp=transfer.data.timestamp, ) diff --git a/src/demo_quipuswap/handlers/on_fa12_withdraw_profit.py b/src/demo_quipuswap/handlers/on_fa12_withdraw_profit.py new file mode 100644 index 000000000..07f3e15f7 --- /dev/null +++ b/src/demo_quipuswap/handlers/on_fa12_withdraw_profit.py @@ -0,0 +1,24 @@ +from decimal import Decimal + +import demo_quipuswap.models as models +from demo_quipuswap.types.quipu_fa12.parameter.withdraw_profit import WithdrawProfit +from dipdup.models import HandlerContext, OperationContext + + +async def on_fa12_withdraw_profit( + ctx: HandlerContext, + withdraw_profit: OperationContext[WithdrawProfit], +) -> None: + + if ctx.template_values is None: + raise Exception('This index must be templated') + + symbol = ctx.template_values['symbol'] + trader = withdraw_profit.data.sender_address + + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) + transaction = next(op for op in ctx.operations if op.amount) + + position.realized_pl += Decimal(transaction.amount) / (10 ** 6) # type: ignore + + await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa20_divest_liquidity.py b/src/demo_quipuswap/handlers/on_fa20_divest_liquidity.py index 2c970fccb..c02ed9b4c 100644 --- a/src/demo_quipuswap/handlers/on_fa20_divest_liquidity.py +++ b/src/demo_quipuswap/handlers/on_fa20_divest_liquidity.py @@ -1,8 +1,10 @@ from decimal import Decimal +from typing import cast import demo_quipuswap.models as models from demo_quipuswap.types.fa2_token.parameter.transfer import Transfer from demo_quipuswap.types.quipu_fa2.parameter.divest_liquidity import DivestLiquidity +from demo_quipuswap.types.quipu_fa2.storage import Storage as QuipuFA20Storage from dipdup.models import HandlerContext, OperationContext @@ -15,12 +17,24 @@ async def on_fa20_divest_liquidity( if ctx.template_values is None: raise Exception('This index must be templated') + storage = cast(QuipuFA20Storage, divest_liquidity.storage) # FIXME: remove + decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=divest_liquidity.data.sender_address) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) - position, _ = await models.Position.get_or_create(trader=trader, instrument=instrument) + symbol = ctx.template_values['symbol'] + trader = divest_liquidity.data.sender_address + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) transaction = next(op for op in ctx.operations if op.amount) - position.tez_qty -= Decimal(transaction.amount) / (10 ** 6) # type: ignore - position.token_qty -= Decimal(transfer.parameter.__root__[0].txs[0].amount) / (10 ** decimals) + + tez_qty = Decimal(transaction.amount) / (10 ** 6) + token_qty = Decimal(transfer.parameter.__root__[0].txs[0].amount) / (10 ** decimals) + shares_qty = int(divest_liquidity.parameter.shares) + + price = (Decimal(storage.storage.tez_pool) / (10 ** 6)) / (Decimal(storage.storage.token_pool) / (10 ** decimals)) + share_px = (tez_qty + price * token_qty) / shares_qty + + position.realized_pl += shares_qty * (share_px - position.avg_share_px) + position.shares_qty -= shares_qty # type: ignore + assert position.shares_qty >= 0, divest_liquidity.data.hash + await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa20_invest_liquidity.py b/src/demo_quipuswap/handlers/on_fa20_invest_liquidity.py index a76f0eae3..bc29d37dc 100644 --- a/src/demo_quipuswap/handlers/on_fa20_invest_liquidity.py +++ b/src/demo_quipuswap/handlers/on_fa20_invest_liquidity.py @@ -1,8 +1,10 @@ from decimal import Decimal +from typing import cast import demo_quipuswap.models as models from demo_quipuswap.types.fa2_token.parameter.transfer import Transfer from demo_quipuswap.types.quipu_fa2.parameter.invest_liquidity import InvestLiquidity +from demo_quipuswap.types.quipu_fa2.storage import Storage as QuipuFA20Storage from dipdup.models import HandlerContext, OperationContext @@ -15,11 +17,24 @@ async def on_fa20_invest_liquidity( if ctx.template_values is None: raise Exception('This index must be templated') + storage = cast(QuipuFA20Storage, invest_liquidity.storage) # FIXME: remove + decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=invest_liquidity.data.sender_address) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) - position, _ = await models.Position.get_or_create(trader=trader, instrument=instrument) + symbol = ctx.template_values['symbol'] + trader = invest_liquidity.data.sender_address + + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) + + tez_qty = Decimal(invest_liquidity.data.amount) / (10 ** 6) + token_qty = Decimal(transfer.parameter.__root__[0].txs[0].amount) / (10 ** decimals) + new_shares_qty = int(storage.storage.ledger[trader].balance) + int(storage.storage.ledger[trader].frozen_balance) + + price = (Decimal(storage.storage.tez_pool) / (10 ** 6)) / (Decimal(storage.storage.token_pool) / (10 ** decimals)) + value = tez_qty + price * token_qty + share_px = value / (new_shares_qty - position.shares_qty) + assert share_px > 0, invest_liquidity.data.hash + + position.avg_share_px = (position.shares_qty * position.avg_share_px + value) / new_shares_qty + position.shares_qty = new_shares_qty # type: ignore - position.tez_qty += Decimal(invest_liquidity.data.amount) / (10 ** 6) # type: ignore - position.token_qty += Decimal(transfer.parameter.__root__[0].txs[0].amount) / (10 ** decimals) await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa20_withdraw_profit.py b/src/demo_quipuswap/handlers/on_fa20_withdraw_profit.py new file mode 100644 index 000000000..5eea34bd1 --- /dev/null +++ b/src/demo_quipuswap/handlers/on_fa20_withdraw_profit.py @@ -0,0 +1,24 @@ +from decimal import Decimal + +import demo_quipuswap.models as models +from demo_quipuswap.types.quipu_fa2.parameter.withdraw_profit import WithdrawProfit +from dipdup.models import HandlerContext, OperationContext + + +async def on_fa20_withdraw_profit( + ctx: HandlerContext, + withdraw_profit: OperationContext[WithdrawProfit], +) -> None: + + if ctx.template_values is None: + raise Exception('This index must be templated') + + symbol = ctx.template_values['symbol'] + trader = withdraw_profit.data.sender_address + + position, _ = await models.Position.get_or_create(trader=trader, symbol=symbol) + transaction = next(op for op in ctx.operations if op.amount) + + position.realized_pl += Decimal(transaction.amount) / (10 ** 6) # type: ignore + + await position.save() diff --git a/src/demo_quipuswap/handlers/on_fa2_tez_to_token.py b/src/demo_quipuswap/handlers/on_fa2_tez_to_token.py index 052d96f4f..5682d47c6 100644 --- a/src/demo_quipuswap/handlers/on_fa2_tez_to_token.py +++ b/src/demo_quipuswap/handlers/on_fa2_tez_to_token.py @@ -15,19 +15,21 @@ async def on_fa2_tez_to_token( raise Exception('This index must be templated') decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=transfer.parameter.__root__[0].from_) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) + symbol = ctx.template_values['symbol'] + trader = tez_to_token_payment.data.sender_address min_token_quantity = Decimal(tez_to_token_payment.parameter.min_out) / (10 ** decimals) token_quantity = Decimal(transfer.parameter.__root__[0].txs[0].amount) / (10 ** decimals) tez_quantity = Decimal(tez_to_token_payment.data.amount) / (10 ** 6) + assert min_token_quantity <= token_quantity, tez_to_token_payment.data.hash + trade = models.Trade( - instrument=instrument, + symbol=symbol, trader=trader, side=models.TradeSide.BUY, quantity=token_quantity, price=token_quantity / tez_quantity, - slippage=((min_token_quantity / token_quantity) - 1) * 100, + slippage=1 - (min_token_quantity / token_quantity), level=transfer.data.level, timestamp=transfer.data.timestamp, ) diff --git a/src/demo_quipuswap/handlers/on_fa2_token_to_tez.py b/src/demo_quipuswap/handlers/on_fa2_token_to_tez.py index 4b9ba3288..c97547d58 100644 --- a/src/demo_quipuswap/handlers/on_fa2_token_to_tez.py +++ b/src/demo_quipuswap/handlers/on_fa2_token_to_tez.py @@ -15,20 +15,22 @@ async def on_fa2_token_to_tez( raise Exception('This index must be templated') decimals = int(ctx.template_values['decimals']) - trader, _ = await models.Trader.get_or_create(address=transfer.parameter.__root__[0].from_) - instrument, _ = await models.Instrument.get_or_create(symbol=ctx.template_values['symbol']) + symbol = ctx.template_values['symbol'] + trader = token_to_tez_payment.data.sender_address min_tez_quantity = Decimal(token_to_tez_payment.parameter.min_out) / (10 ** decimals) token_quantity = Decimal(token_to_tez_payment.parameter.amount) / (10 ** decimals) transaction = next(op for op in ctx.operations if op.amount) tez_quantity = Decimal(transaction.amount) / (10 ** 6) + assert min_tez_quantity <= tez_quantity, token_to_tez_payment.data.hash + trade = models.Trade( - instrument=instrument, + symbol=symbol, trader=trader, side=models.TradeSide.SELL, quantity=token_quantity, price=token_quantity / tez_quantity, - slippage=((min_tez_quantity / tez_quantity) - 1) * 100, + slippage=1 - (min_tez_quantity / tez_quantity), level=transfer.data.level, timestamp=transfer.data.timestamp, ) diff --git a/src/demo_quipuswap/models.py b/src/demo_quipuswap/models.py index 1e313748a..0b8d5a644 100644 --- a/src/demo_quipuswap/models.py +++ b/src/demo_quipuswap/models.py @@ -8,29 +8,22 @@ class TradeSide(IntEnum): SELL = 0 -class Trader(Model): - address = fields.CharField(36, pk=True) - - -class Instrument(Model): - symbol = fields.CharField(max_length=5) - - class Trade(Model): id = fields.IntField(pk=True) - instrument = fields.ForeignKeyField('models.Instrument', 'trades') - trader = fields.ForeignKeyField('models.Trader', 'trades') + symbol = fields.CharField(max_length=5) + trader = fields.CharField(36) side = fields.IntEnumField(enum_type=TradeSide) - quantity = fields.DecimalField(decimal_places=6, max_digits=10) - price = fields.DecimalField(decimal_places=6, max_digits=10) - slippage = fields.DecimalField(decimal_places=6, max_digits=10) + quantity = fields.DecimalField(decimal_places=6, max_digits=20) + price = fields.DecimalField(decimal_places=6, max_digits=20) + slippage = fields.DecimalField(decimal_places=6, max_digits=20) level = fields.BigIntField() timestamp = fields.DatetimeField() class Position(Model): id = fields.IntField(pk=True) - instrument = fields.ForeignKeyField('models.Instrument', 'positions') - trader = fields.ForeignKeyField('models.Trader', 'positions') - token_qty = fields.DecimalField(decimal_places=6, max_digits=10, default=0) - tez_qty = fields.DecimalField(decimal_places=6, max_digits=10, default=0) + symbol = fields.CharField(max_length=5) + trader = fields.CharField(36) + shares_qty = fields.BigIntField(default=0) + avg_share_px = fields.DecimalField(decimal_places=6, max_digits=20, default=0) + realized_pl = fields.DecimalField(decimal_places=6, max_digits=20, default=0) diff --git a/src/demo_quipuswap/types/fa12_token/storage.py b/src/demo_quipuswap/types/fa12_token/storage.py index fa1a0811f..1131830b3 100644 --- a/src/demo_quipuswap/types/fa12_token/storage.py +++ b/src/demo_quipuswap/types/fa12_token/storage.py @@ -5,44 +5,17 @@ from typing import Dict -from pydantic import BaseModel, Extra - - -class Approvals(BaseModel): - class Config: - extra = Extra.allow - - __root__: str +from pydantic import BaseModel class Balances(BaseModel): - class Config: - extra = Extra.allow - - approvals: Dict[str, Approvals] + approvals: Dict[str, str] balance: str -class Metadata(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - -class Map(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - class TokenMetadata(BaseModel): - class Config: - extra = Extra.allow - nat: str - map: Dict[str, Map] + map: Dict[str, str] class Storage(BaseModel): @@ -50,7 +23,7 @@ class Storage(BaseModel): balances: Dict[str, Balances] debtCeiling: str governorContractAddress: str - metadata: Dict[str, Metadata] + metadata: Dict[str, str] paused: bool token_metadata: Dict[str, TokenMetadata] totalSupply: str diff --git a/src/demo_quipuswap/types/fa2_token/storage.py b/src/demo_quipuswap/types/fa2_token/storage.py index 4dc607d4e..f8734c504 100644 --- a/src/demo_quipuswap/types/fa2_token/storage.py +++ b/src/demo_quipuswap/types/fa2_token/storage.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List -from pydantic import BaseModel, Extra +from pydantic import BaseModel class Key(BaseModel): @@ -18,13 +18,6 @@ class LedgerItem(BaseModel): value: str -class Metadata(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - class Key1(BaseModel): owner: str operator: str @@ -36,26 +29,16 @@ class Operator(BaseModel): value: Dict[str, Any] -class TokenInfo(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - class TokenMetadata(BaseModel): - class Config: - extra = Extra.allow - token_id: str - token_info: Dict[str, TokenInfo] + token_info: Dict[str, str] class Storage(BaseModel): administrator: str all_tokens: str ledger: List[LedgerItem] - metadata: Dict[str, Metadata] + metadata: Dict[str, str] operators: List[Operator] paused: bool token_metadata: Dict[str, TokenMetadata] diff --git a/src/demo_quipuswap/types/quipu_fa12/parameter/withdraw_profit.py b/src/demo_quipuswap/types/quipu_fa12/parameter/withdraw_profit.py new file mode 100644 index 000000000..b2c1b9146 --- /dev/null +++ b/src/demo_quipuswap/types/quipu_fa12/parameter/withdraw_profit.py @@ -0,0 +1,10 @@ +# generated by datamodel-codegen: +# filename: withdrawProfit.json + +from __future__ import annotations + +from pydantic import BaseModel + + +class WithdrawProfit(BaseModel): + __root__: str diff --git a/src/demo_quipuswap/types/quipu_fa12/storage.py b/src/demo_quipuswap/types/quipu_fa12/storage.py index 3d7e2ab53..c231d9f1e 100644 --- a/src/demo_quipuswap/types/quipu_fa12/storage.py +++ b/src/demo_quipuswap/types/quipu_fa12/storage.py @@ -3,58 +3,27 @@ from __future__ import annotations -from typing import Dict, Optional, Union +from typing import Dict, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel -class DexLambdas(BaseModel): - class Config: - extra = Extra.allow +class Ledger(BaseModel): + allowances: Dict[str, str] + balance: str + frozen_balance: str - __root__: str - - -class Metadata(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - -class LedgerItem(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class UserReward(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class Veto(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class Voter(BaseModel): - pass - - class Config: - extra = Extra.allow +class UserRewards(BaseModel): + reward: str + reward_paid: str -class Vote(BaseModel): - pass - class Config: - extra = Extra.allow +class Voters(BaseModel): + candidate: Optional[str] + last_veto: str + veto: str + vote: str class Storage1(BaseModel): @@ -63,7 +32,7 @@ class Storage1(BaseModel): invariant: str last_update_time: str last_veto: str - ledger: Union[int, LedgerItem] + ledger: Dict[str, Ledger] period_finish: str reward: str reward_paid: str @@ -75,22 +44,15 @@ class Storage1(BaseModel): total_reward: str total_supply: str total_votes: str - user_rewards: Union[int, UserReward] + user_rewards: Dict[str, UserRewards] veto: str - vetos: Union[int, Veto] - voters: Union[int, Voter] - votes: Union[int, Vote] - - -class TokenLambdas(BaseModel): - class Config: - extra = Extra.allow - - __root__: str + vetos: Dict[str, str] + voters: Dict[str, Voters] + votes: Dict[str, str] class Storage(BaseModel): - dex_lambdas: Dict[str, DexLambdas] - metadata: Dict[str, Metadata] + dex_lambdas: Dict[str, str] + metadata: Dict[str, str] storage: Storage1 - token_lambdas: Dict[str, TokenLambdas] + token_lambdas: Dict[str, str] diff --git a/src/demo_quipuswap/types/quipu_fa2/parameter/withdraw_profit.py b/src/demo_quipuswap/types/quipu_fa2/parameter/withdraw_profit.py new file mode 100644 index 000000000..b2c1b9146 --- /dev/null +++ b/src/demo_quipuswap/types/quipu_fa2/parameter/withdraw_profit.py @@ -0,0 +1,10 @@ +# generated by datamodel-codegen: +# filename: withdrawProfit.json + +from __future__ import annotations + +from pydantic import BaseModel + + +class WithdrawProfit(BaseModel): + __root__: str diff --git a/src/demo_quipuswap/types/quipu_fa2/storage.py b/src/demo_quipuswap/types/quipu_fa2/storage.py index 025753e57..0c4c6f8cd 100644 --- a/src/demo_quipuswap/types/quipu_fa2/storage.py +++ b/src/demo_quipuswap/types/quipu_fa2/storage.py @@ -3,58 +3,27 @@ from __future__ import annotations -from typing import Dict, Optional, Union +from typing import Dict, List, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel -class DexLambdas(BaseModel): - class Config: - extra = Extra.allow +class Ledger(BaseModel): + allowances: List[str] + balance: str + frozen_balance: str - __root__: str - - -class Metadata(BaseModel): - class Config: - extra = Extra.allow - - __root__: str - - -class LedgerItem(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class UserReward(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class Veto(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class Voter(BaseModel): - pass - - class Config: - extra = Extra.allow +class UserRewards(BaseModel): + reward: str + reward_paid: str -class Vote(BaseModel): - pass - class Config: - extra = Extra.allow +class Voters(BaseModel): + candidate: Optional[str] + last_veto: str + veto: str + vote: str class Storage1(BaseModel): @@ -63,7 +32,7 @@ class Storage1(BaseModel): invariant: str last_update_time: str last_veto: str - ledger: Union[int, LedgerItem] + ledger: Dict[str, Ledger] period_finish: str reward: str reward_paid: str @@ -76,22 +45,15 @@ class Storage1(BaseModel): total_reward: str total_supply: str total_votes: str - user_rewards: Union[int, UserReward] + user_rewards: Dict[str, UserRewards] veto: str - vetos: Union[int, Veto] - voters: Union[int, Voter] - votes: Union[int, Vote] - - -class TokenLambdas(BaseModel): - class Config: - extra = Extra.allow - - __root__: str + vetos: Dict[str, str] + voters: Dict[str, Voters] + votes: Dict[str, str] class Storage(BaseModel): - dex_lambdas: Dict[str, DexLambdas] - metadata: Dict[str, Metadata] + dex_lambdas: Dict[str, str] + metadata: Dict[str, str] storage: Storage1 - token_lambdas: Dict[str, TokenLambdas] + token_lambdas: Dict[str, str] diff --git a/src/demo_registrydao/dipdup.yml b/src/demo_registrydao/dipdup.yml index 6f6c36967..38c9bc5d8 100644 --- a/src/demo_registrydao/dipdup.yml +++ b/src/demo_registrydao/dipdup.yml @@ -1,4 +1,4 @@ -spec_version: 0.0.1 +spec_version: 0.1 package: demo_registrydao database: diff --git a/src/demo_tezos_domains/__init__.py b/src/demo_tezos_domains/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/dipdup-docker.yml b/src/demo_tezos_domains/dipdup-docker.yml new file mode 100644 index 000000000..bea2a9a96 --- /dev/null +++ b/src/demo_tezos_domains/dipdup-docker.yml @@ -0,0 +1,43 @@ +spec_version: 0.1 +package: demo_tezos_domains + +database: + kind: postgres + host: db + port: 5432 + user: ${POSTGRES_USER:-dipdup} + password: ${POSTGRES_PASSWORD:-changeme} + database: ${POSTGRES_DB:-dipdup} + schema_name: domains + +contracts: + edo_name_registry: + address: KT1JJbWfW8CHUY95hG9iq2CEMma1RiKhMHDR + typename: name_registry + +datasources: + tzkt_staging_edo: + kind: tzkt + url: https://staging.api.edo2net.tzkt.io + +templates: + tezos_domains: + kind: operation + datasource: + contract: + handlers: + - callback: on_admin_update + pattern: + - destination: + entrypoint: admin_update + - callback: on_execute + pattern: + - destination: + entrypoint: execute + +indexes: + tezos_domains_edo: + template: tezos_domains + values: + datasource: tzkt_staging_edo + name_registry: edo_name_registry \ No newline at end of file diff --git a/src/demo_tezos_domains/dipdup.yml b/src/demo_tezos_domains/dipdup.yml new file mode 100644 index 000000000..efcc6a32b --- /dev/null +++ b/src/demo_tezos_domains/dipdup.yml @@ -0,0 +1,38 @@ +spec_version: 0.1 +package: demo_tezos_domains + +database: + kind: sqlite + path: tezos_domains.sqlite3 + +contracts: + edo_name_registry: + address: KT1JJbWfW8CHUY95hG9iq2CEMma1RiKhMHDR + typename: name_registry + +datasources: + tzkt_staging_edo: + kind: tzkt + url: ${TZKT_URL:-https://staging.api.edo2net.tzkt.io} + +templates: + tezos_domains: + kind: operation + datasource: + contract: + handlers: + - callback: on_admin_update + pattern: + - destination: + entrypoint: admin_update + - callback: on_execute + pattern: + - destination: + entrypoint: execute + +indexes: + tezos_domains_edo: + template: tezos_domains + values: + datasource: tzkt_staging_edo + name_registry: edo_name_registry \ No newline at end of file diff --git a/src/demo_tezos_domains/handlers/__init__.py b/src/demo_tezos_domains/handlers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/handlers/on_admin_update.py b/src/demo_tezos_domains/handlers/on_admin_update.py new file mode 100644 index 000000000..f3a056de6 --- /dev/null +++ b/src/demo_tezos_domains/handlers/on_admin_update.py @@ -0,0 +1,14 @@ +from typing import cast + +from demo_tezos_domains.handlers.on_storage_diff import on_storage_diff +from demo_tezos_domains.types.name_registry.parameter.admin_update import AdminUpdate +from demo_tezos_domains.types.name_registry.storage import Storage as NameRegistryStorage +from dipdup.models import HandlerContext, OperationContext + + +async def on_admin_update( + ctx: HandlerContext, + admin_update: OperationContext[AdminUpdate], +) -> None: + storage = cast(NameRegistryStorage, admin_update.storage) # FIXME: remove + await on_storage_diff(storage) diff --git a/src/demo_tezos_domains/handlers/on_execute.py b/src/demo_tezos_domains/handlers/on_execute.py new file mode 100644 index 000000000..3500e599f --- /dev/null +++ b/src/demo_tezos_domains/handlers/on_execute.py @@ -0,0 +1,14 @@ +from typing import cast + +from demo_tezos_domains.handlers.on_storage_diff import on_storage_diff +from demo_tezos_domains.types.name_registry.parameter.execute import Execute +from demo_tezos_domains.types.name_registry.storage import Storage as NameRegistryStorage +from dipdup.models import HandlerContext, OperationContext + + +async def on_execute( + ctx: HandlerContext, + execute: OperationContext[Execute], +) -> None: + storage = cast(NameRegistryStorage, execute.storage) # FIXME: remove + await on_storage_diff(storage) diff --git a/src/demo_tezos_domains/handlers/on_rollback.py b/src/demo_tezos_domains/handlers/on_rollback.py new file mode 100644 index 000000000..121dc532f --- /dev/null +++ b/src/demo_tezos_domains/handlers/on_rollback.py @@ -0,0 +1,16 @@ +import logging +import os +import sys + +from tortoise import Tortoise + +_logger = logging.getLogger(__name__) + + +async def on_rollback( + from_level: int, + to_level: int, +) -> None: + _logger.warning('Rollback event received, reindexing') + await Tortoise._drop_databases() + os.execl(sys.executable, sys.executable, *sys.argv) diff --git a/src/demo_tezos_domains/handlers/on_storage_diff.py b/src/demo_tezos_domains/handlers/on_storage_diff.py new file mode 100644 index 000000000..93e8ef397 --- /dev/null +++ b/src/demo_tezos_domains/handlers/on_storage_diff.py @@ -0,0 +1,35 @@ +import logging +from typing import cast + +import demo_tezos_domains.models as models +from demo_tezos_domains.types.name_registry.storage import Storage as NameRegistryStorage +from dipdup.models import HandlerContext, OperationContext + +_logger = logging.getLogger(__name__) + + +async def on_storage_diff(storage: NameRegistryStorage) -> None: + for name, item in storage.store.records.items(): + record_name = bytes.fromhex(name).decode() + record_path = record_name.split('.') + _logger.info(f'Processing `{record_name}`') + + if len(record_path) != int(item.level): + _logger.error(f'Invalid record `{record_name}`: expected {item.level} chunks, got {len(record_path)}') + return + + if item.level == "1": + await models.TLD.update_or_create(id=record_name, defaults=dict(owner=item.owner)) + else: + if item.level == "2": + await models.Domain.update_or_create( + id=record_name, + defaults=dict( + tld_id=record_path[-1], + owner=item.owner, + expiry=storage.store.expiry_map.get(item.expiry_key) if item.expiry_key else None, + token_id=int(item.tzip12_token_id) if item.tzip12_token_id else None, + ), + ) + + await models.Record.update_or_create(id=record_name, defaults=dict(domain_id='.'.join(record_path[-2:]), address=item.address)) diff --git a/src/demo_tezos_domains/models.py b/src/demo_tezos_domains/models.py new file mode 100644 index 000000000..7cd1cd924 --- /dev/null +++ b/src/demo_tezos_domains/models.py @@ -0,0 +1,20 @@ +from tortoise import Model, fields + + +class TLD(Model): + id = fields.CharField(max_length=255, pk=True) + owner = fields.CharField(max_length=36) + + +class Domain(Model): + id = fields.CharField(max_length=255, pk=True) + tld = fields.ForeignKeyField('models.TLD', 'domains') + expiry = fields.DatetimeField(null=True) + owner = fields.CharField(max_length=36) + token_id = fields.BigIntField(null=True) + + +class Record(Model): + id = fields.CharField(max_length=255, pk=True) + domain = fields.ForeignKeyField('models.Domain', 'records') + address = fields.CharField(max_length=36, null=True) diff --git a/src/demo_tezos_domains/types/__init__.py b/src/demo_tezos_domains/types/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/types/name_registry/__init__.py b/src/demo_tezos_domains/types/name_registry/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/types/name_registry/parameter/__init__.py b/src/demo_tezos_domains/types/name_registry/parameter/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/demo_tezos_domains/types/name_registry/parameter/admin_update.py b/src/demo_tezos_domains/types/name_registry/parameter/admin_update.py new file mode 100644 index 000000000..eca079977 --- /dev/null +++ b/src/demo_tezos_domains/types/name_registry/parameter/admin_update.py @@ -0,0 +1,10 @@ +# generated by datamodel-codegen: +# filename: admin_update.json + +from __future__ import annotations + +from pydantic import BaseModel + + +class AdminUpdate(BaseModel): + __root__: str diff --git a/src/demo_tezos_domains/types/name_registry/parameter/execute.py b/src/demo_tezos_domains/types/name_registry/parameter/execute.py new file mode 100644 index 000000000..4f6b242c2 --- /dev/null +++ b/src/demo_tezos_domains/types/name_registry/parameter/execute.py @@ -0,0 +1,12 @@ +# generated by datamodel-codegen: +# filename: execute.json + +from __future__ import annotations + +from pydantic import BaseModel + + +class Execute(BaseModel): + action_name: str + original_sender: str + payload: str diff --git a/src/demo_tezos_domains/types/name_registry/storage.py b/src/demo_tezos_domains/types/name_registry/storage.py new file mode 100644 index 000000000..1b79c6aba --- /dev/null +++ b/src/demo_tezos_domains/types/name_registry/storage.py @@ -0,0 +1,41 @@ +# generated by datamodel-codegen: +# filename: storage.json + +from __future__ import annotations + +from typing import Dict, List, Optional + +from pydantic import BaseModel + + +class Records(BaseModel): + address: Optional[str] + data: Dict[str, str] + expiry_key: Optional[str] + internal_data: Dict[str, str] + level: str + owner: str + tzip12_token_id: Optional[str] + + +class ReverseRecords(BaseModel): + internal_data: Dict[str, str] + name: Optional[str] + owner: str + + +class Store(BaseModel): + data: Dict[str, str] + expiry_map: Dict[str, str] + metadata: Dict[str, str] + next_tzip12_token_id: str + owner: str + records: Dict[str, Records] + reverse_records: Dict[str, ReverseRecords] + tzip12_tokens: Dict[str, str] + + +class Storage(BaseModel): + actions: Dict[str, str] + store: Store + trusted_senders: List[str] diff --git a/src/demo_tzcolors/dipdup.yml b/src/demo_tzcolors/dipdup.yml index 75528ce98..5600ddb2e 100644 --- a/src/demo_tzcolors/dipdup.yml +++ b/src/demo_tzcolors/dipdup.yml @@ -1,4 +1,4 @@ -spec_version: 0.0.1 +spec_version: 0.1 package: demo_tzcolors database: diff --git a/src/dipdup/codegen.py b/src/dipdup/codegen.py index c6f35e192..70ee5d651 100644 --- a/src/dipdup/codegen.py +++ b/src/dipdup/codegen.py @@ -17,14 +17,13 @@ _logger = logging.getLogger(__name__) -def preprocess_storage_schema(storage_schema: Dict[str, Any]): - if 'properties' in storage_schema: - for property in storage_schema['properties']: - if storage_schema['properties'][property].get('$comment') == 'big_map': - storage_schema['properties'][property] = storage_schema['properties'][property]['oneOf'][1] - elif 'oneOf' in storage_schema: - if storage_schema.get('$comment') == 'big_map': - storage_schema = storage_schema['oneOf'][1] +def resolve_big_maps(schema: Dict[str, Any]) -> Dict[str, Any]: + if 'properties' in schema: + return {**schema, 'properties': {prop: resolve_big_maps(sub_schema) for prop, sub_schema in schema['properties'].items()}} + elif schema.get('$comment') == 'big_map': + return schema['oneOf'][1] + else: + return schema async def create_package(config: DipDupConfig): @@ -72,11 +71,11 @@ async def fetch_schemas(config: DipDupConfig): storage_schema_path = join(contract_schemas_path, 'storage.json') - storage_schema = address_schemas_json['storageSchema'] - preprocess_storage_schema(storage_schema) + storage_schema = resolve_big_maps(address_schemas_json['storageSchema']) if not exists(storage_schema_path): with open(storage_schema_path, 'w') as file: file.write(json.dumps(storage_schema, indent=4)) + # TODO: check contract.typename parameter_schemas_path = join(contract_schemas_path, 'parameter') with suppress(FileExistsError): diff --git a/src/dipdup/config.py b/src/dipdup/config.py index a7c93c9b3..ee2621ebb 100644 --- a/src/dipdup/config.py +++ b/src/dipdup/config.py @@ -349,7 +349,7 @@ def valid_url(cls, v): class DipDupConfig: """Main dapp config - :param spec_version: Version of specification, always 0.0.1 for now + :param spec_version: Version of specification :param package: Name of dapp python package, existing or not :param contracts: Mapping of contract aliases and contract configs :param datasources: Mapping of datasource aliases and datasource configs diff --git a/src/dipdup/models.py b/src/dipdup/models.py index fd5253bb8..63954deab 100644 --- a/src/dipdup/models.py +++ b/src/dipdup/models.py @@ -123,10 +123,10 @@ def get_merged_storage(self, storage_type: Type[StorageType]) -> StorageType: @dataclass -class OperationContext(Generic[ParameterType]): +class OperationContext(Generic[ParameterType]): # TODO: Add StorageType data: OperationData parameter: ParameterType - storage: Optional[Any] = None + storage: Any # TODO: StorageType @dataclass diff --git a/tests/integration_tests/test_demos.py b/tests/integration_tests/test_demos.py index 35bb155d8..c3e33cabe 100644 --- a/tests/integration_tests/test_demos.py +++ b/tests/integration_tests/test_demos.py @@ -49,13 +49,8 @@ async def test_quipuswap(self): self.run_dipdup('quipuswap.yml') async with tortoise_wrapper('sqlite:///tmp/dipdup/db.sqlite3', 'demo_quipuswap.models'): - instruments = await demo_quipuswap.models.Instrument.filter().count() - traders = await demo_quipuswap.models.Trader.filter().count() trades = await demo_quipuswap.models.Trade.filter().count() positions = await demo_quipuswap.models.Position.filter().count() - - self.assertEqual(2, instruments) - self.assertEqual(73, traders) self.assertEqual(94, trades) self.assertEqual(56, positions)