diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a41be30..9b01d2d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index ff2c1ceb..401813cd 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,9 @@ clean-test: ## remove test and coverage artifacts test: ## runs tests poetry run pytest -vv -n 4 +test-integration: ## runs integration tests + cd integration-test && ./run_tests.sh + test-single: ## runs tests with "single" markers poetry run pytest -s -vv -m single diff --git a/integration-test/configs/local-chang/shelley-genesis.json b/integration-test/configs/local-chang/shelley-genesis.json index f3afd4c7..6c5a3cc6 100644 --- a/integration-test/configs/local-chang/shelley-genesis.json +++ b/integration-test/configs/local-chang/shelley-genesis.json @@ -21,7 +21,7 @@ "keyDeposit": 1000000, "protocolVersion": { "minor": 0, - "major": 9 + "major": 10 }, "poolDeposit": 1000000, "a0": 0.0, diff --git a/integration-test/docker-compose-chang.yml b/integration-test/docker-compose-chang.yml index d2266442..c9538d77 100644 --- a/integration-test/docker-compose-chang.yml +++ b/integration-test/docker-compose-chang.yml @@ -9,7 +9,7 @@ networks: services: cardano-node: - image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.3} + image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.4} platform: linux/amd64 entrypoint: bash environment: @@ -35,7 +35,7 @@ services: max-file: "10" cardano-pool: - image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.3} + image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-10.1.4} platform: linux/amd64 entrypoint: bash environment: @@ -56,7 +56,7 @@ services: max-file: "10" ogmios: - image: cardanosolutions/ogmios:v6.9.0 + image: cardanosolutions/ogmios:v6.11.0 platform: linux/amd64 environment: NETWORK: "${NETWORK:-local-alonzo}" diff --git a/integration-test/run_tests.sh b/integration-test/run_tests.sh index a4193631..253ff187 100755 --- a/integration-test/run_tests.sh +++ b/integration-test/run_tests.sh @@ -5,7 +5,7 @@ set -o pipefail ROOT=$(pwd) -poetry install +poetry install -C .. #poetry run pip install ogmios ########## diff --git a/poetry.lock b/poetry.lock index 1150e880..58a45ce6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1208,86 +1208,90 @@ testing = ["coverage[toml] (>=6.5)", "pycardano", "pytest"] [[package]] name = "orjson" -version = "3.10.13" +version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.13-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1232c5e873a4d1638ef957c5564b4b0d6f2a6ab9e207a9b3de9de05a09d1d920"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26a0eca3035619fa366cbaf49af704c7cb1d4a0e6c79eced9f6a3f2437964b6"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4b6acd7c9c829895e50d385a357d4b8c3fafc19c5989da2bae11783b0fd4977"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1884e53c6818686891cc6fc5a3a2540f2f35e8c76eac8dc3b40480fb59660b00"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a428afb5720f12892f64920acd2eeb4d996595bf168a26dd9190115dbf1130d"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b13b8739ce5b630c65cb1c85aedbd257bcc2b9c256b06ab2605209af75a2e"}, - {file = "orjson-3.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cab83e67f6aabda1b45882254b2598b48b80ecc112968fc6483fa6dae609e9f0"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62c3cc00c7e776c71c6b7b9c48c5d2701d4c04e7d1d7cdee3572998ee6dc57cc"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dc03db4922e75bbc870b03fc49734cefbd50fe975e0878327d200022210b82d8"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22f1c9a30b43d14a041a6ea190d9eca8a6b80c4beb0e8b67602c82d30d6eec3e"}, - {file = "orjson-3.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b42f56821c29e697c68d7d421410d7c1d8f064ae288b525af6a50cf99a4b1200"}, - {file = "orjson-3.10.13-cp310-cp310-win32.whl", hash = "sha256:0dbf3b97e52e093d7c3e93eb5eb5b31dc7535b33c2ad56872c83f0160f943487"}, - {file = "orjson-3.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:46c249b4e934453be4ff2e518cd1adcd90467da7391c7a79eaf2fbb79c51e8c7"}, - {file = "orjson-3.10.13-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a36c0d48d2f084c800763473020a12976996f1109e2fcb66cfea442fdf88047f"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0065896f85d9497990731dfd4a9991a45b0a524baec42ef0a63c34630ee26fd6"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92b4ec30d6025a9dcdfe0df77063cbce238c08d0404471ed7a79f309364a3d19"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a94542d12271c30044dadad1125ee060e7a2048b6c7034e432e116077e1d13d2"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3723e137772639af8adb68230f2aa4bcb27c48b3335b1b1e2d49328fed5e244c"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f00c7fb18843bad2ac42dc1ce6dd214a083c53f1e324a0fd1c8137c6436269b"}, - {file = "orjson-3.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e2759d3172300b2f892dee85500b22fca5ac49e0c42cfff101aaf9c12ac9617"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee948c6c01f6b337589c88f8e0bb11e78d32a15848b8b53d3f3b6fea48842c12"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa6fe68f0981fba0d4bf9cdc666d297a7cdba0f1b380dcd075a9a3dd5649a69e"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbcd7aad6bcff258f6896abfbc177d54d9b18149c4c561114f47ebfe74ae6bfd"}, - {file = "orjson-3.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2149e2fcd084c3fd584881c7f9d7f9e5ad1e2e006609d8b80649655e0d52cd02"}, - {file = "orjson-3.10.13-cp311-cp311-win32.whl", hash = "sha256:89367767ed27b33c25c026696507c76e3d01958406f51d3a2239fe9e91959df2"}, - {file = "orjson-3.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:dca1d20f1af0daff511f6e26a27354a424f0b5cf00e04280279316df0f604a6f"}, - {file = "orjson-3.10.13-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a3614b00621c77f3f6487792238f9ed1dd8a42f2ec0e6540ee34c2d4e6db813a"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c976bad3996aa027cd3aef78aa57873f3c959b6c38719de9724b71bdc7bd14b"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f74d878d1efb97a930b8a9f9898890067707d683eb5c7e20730030ecb3fb930"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33ef84f7e9513fb13b3999c2a64b9ca9c8143f3da9722fbf9c9ce51ce0d8076e"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd2bcde107221bb9c2fa0c4aaba735a537225104173d7e19cf73f70b3126c993"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064b9dbb0217fd64a8d016a8929f2fae6f3312d55ab3036b00b1d17399ab2f3e"}, - {file = "orjson-3.10.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0044b0b8c85a565e7c3ce0a72acc5d35cda60793edf871ed94711e712cb637d"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7184f608ad563032e398f311910bc536e62b9fbdca2041be889afcbc39500de8"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d36f689e7e1b9b6fb39dbdebc16a6f07cbe994d3644fb1c22953020fc575935f"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54433e421618cd5873e51c0e9d0b9fb35f7bf76eb31c8eab20b3595bb713cd3d"}, - {file = "orjson-3.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e1ba0c5857dd743438acecc1cd0e1adf83f0a81fee558e32b2b36f89e40cee8b"}, - {file = "orjson-3.10.13-cp312-cp312-win32.whl", hash = "sha256:a42b9fe4b0114b51eb5cdf9887d8c94447bc59df6dbb9c5884434eab947888d8"}, - {file = "orjson-3.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:3a7df63076435f39ec024bdfeb4c9767ebe7b49abc4949068d61cf4857fa6d6c"}, - {file = "orjson-3.10.13-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2cdaf8b028a976ebab837a2c27b82810f7fc76ed9fb243755ba650cc83d07730"}, - {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a946796e390cbb803e069472de37f192b7a80f4ac82e16d6eb9909d9e39d56"}, - {file = "orjson-3.10.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d64f1db5ecbc21eb83097e5236d6ab7e86092c1cd4c216c02533332951afc"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:711878da48f89df194edd2ba603ad42e7afed74abcd2bac164685e7ec15f96de"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cf16f06cb77ce8baf844bc222dbcb03838f61d0abda2c3341400c2b7604e436e"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8257c3fb8dd7b0b446b5e87bf85a28e4071ac50f8c04b6ce2d38cb4abd7dff57"}, - {file = "orjson-3.10.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9c3a87abe6f849a4a7ac8a8a1dede6320a4303d5304006b90da7a3cd2b70d2c"}, - {file = "orjson-3.10.13-cp313-cp313-win32.whl", hash = "sha256:527afb6ddb0fa3fe02f5d9fba4920d9d95da58917826a9be93e0242da8abe94a"}, - {file = "orjson-3.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:b5f7c298d4b935b222f52d6c7f2ba5eafb59d690d9a3840b7b5c5cda97f6ec5c"}, - {file = "orjson-3.10.13-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e49333d1038bc03a25fdfe11c86360df9b890354bfe04215f1f54d030f33c342"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:003721c72930dbb973f25c5d8e68d0f023d6ed138b14830cc94e57c6805a2eab"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63664bf12addb318dc8f032160e0f5dc17eb8471c93601e8f5e0d07f95003784"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6066729cf9552d70de297b56556d14b4f49c8f638803ee3c90fd212fa43cc6af"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a1152e2761025c5d13b5e1908d4b1c57f3797ba662e485ae6f26e4e0c466388"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b21d91c5c5ef8a201036d207b1adf3aa596b930b6ca3c71484dd11386cf6c3"}, - {file = "orjson-3.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b12a63f48bb53dba8453d36ca2661f2330126d54e26c1661e550b32864b28ce3"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a5a7624ab4d121c7e035708c8dd1f99c15ff155b69a1c0affc4d9d8b551281ba"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0fee076134398d4e6cb827002468679ad402b22269510cf228301b787fdff5ae"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ae537fcf330b3947e82c6ae4271e092e6cf16b9bc2cef68b14ffd0df1fa8832a"}, - {file = "orjson-3.10.13-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f81b26c03f5fb5f0d0ee48d83cea4d7bc5e67e420d209cc1a990f5d1c62f9be0"}, - {file = "orjson-3.10.13-cp38-cp38-win32.whl", hash = "sha256:0bc858086088b39dc622bc8219e73d3f246fb2bce70a6104abd04b3a080a66a8"}, - {file = "orjson-3.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:3ca6f17467ebbd763f8862f1d89384a5051b461bb0e41074f583a0ebd7120e8e"}, - {file = "orjson-3.10.13-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a11532cbfc2f5752c37e84863ef8435b68b0e6d459b329933294f65fa4bda1a"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c96d2fb80467d1d0dfc4d037b4e1c0f84f1fe6229aa7fea3f070083acef7f3d7"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dda4ba4d3e6f6c53b6b9c35266788053b61656a716a7fef5c884629c2a52e7aa"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f998bbf300690be881772ee9c5281eb9c0044e295bcd4722504f5b5c6092ff"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1cc42ed75b585c0c4dc5eb53a90a34ccb493c09a10750d1a1f9b9eff2bd12"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03b0f29d485411e3c13d79604b740b14e4e5fb58811743f6f4f9693ee6480a8f"}, - {file = "orjson-3.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:233aae4474078d82f425134bb6a10fb2b3fc5a1a1b3420c6463ddd1b6a97eda8"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e384e330a67cf52b3597ee2646de63407da6f8fc9e9beec3eaaaef5514c7a1c9"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4222881d0aab76224d7b003a8e5fdae4082e32c86768e0e8652de8afd6c4e2c1"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e400436950ba42110a20c50c80dff4946c8e3ec09abc1c9cf5473467e83fd1c5"}, - {file = "orjson-3.10.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f47c9e7d224b86ffb086059cdcf634f4b3f32480f9838864aa09022fe2617ce2"}, - {file = "orjson-3.10.13-cp39-cp39-win32.whl", hash = "sha256:a9ecea472f3eb653e1c0a3d68085f031f18fc501ea392b98dcca3e87c24f9ebe"}, - {file = "orjson-3.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:5385935a73adce85cc7faac9d396683fd813566d3857fa95a0b521ef84a5b588"}, - {file = "orjson-3.10.13.tar.gz", hash = "sha256:eb9bfb14ab8f68d9d9492d4817ae497788a15fd7da72e14dfabc289c3bb088ec"}, + {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, + {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, + {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, + {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, + {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, + {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, + {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, + {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, + {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, + {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, + {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, + {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, + {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, + {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, + {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, + {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, + {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, + {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, + {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, + {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, + {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, + {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, + {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, + {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, + {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, + {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, + {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, + {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, + {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, + {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, + {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, ] [[package]] @@ -1428,13 +1432,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] @@ -1571,13 +1575,13 @@ files = [ [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -2212,4 +2216,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "da08ee5525622c2fc94d32e65b140927118146daca26a956eab66ab9786b3393" +content-hash = "eb08a306a62b6c8f50a03d5382e903fdb1ed271889622feff8121cc45b3b9626" diff --git a/pycardano/certificate.py b/pycardano/certificate.py index 67bac613..ec015967 100644 --- a/pycardano/certificate.py +++ b/pycardano/certificate.py @@ -45,7 +45,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeCredential], values: Union[list, tuple] ) -> StakeCredential: @@ -67,7 +67,7 @@ def __post_init__(self): self._CODE = 0 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeRegistration], values: Union[list, tuple] ) -> StakeRegistration: @@ -87,7 +87,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeDeregistration], values: Union[list, tuple] ) -> StakeDeregistration: @@ -109,7 +109,7 @@ def __post_init__(self): self._CODE = 2 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[StakeDelegation], values: Union[list, tuple] ) -> StakeDelegation: @@ -138,7 +138,7 @@ def to_primitive(self): return super().to_primitive() @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[PoolRegistration], values: Union[list, tuple] ) -> PoolRegistration: @@ -166,7 +166,7 @@ def __post_init__(self): self._CODE = 4 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[PoolRetirement], values: Union[list, tuple] ) -> PoolRetirement: diff --git a/pycardano/metadata.py b/pycardano/metadata.py index 17bb4a08..4c761b1e 100644 --- a/pycardano/metadata.py +++ b/pycardano/metadata.py @@ -123,7 +123,9 @@ def to_primitive(self) -> Primitive: return self.data.to_primitive() @classmethod - def from_primitive(cls: Type[AuxiliaryData], value: Primitive) -> AuxiliaryData: + def from_primitive( + cls: Type[AuxiliaryData], value: Primitive, type_args: Optional[tuple] = None + ) -> AuxiliaryData: for t in [AlonzoMetadata, ShelleyMarryMetadata, Metadata]: # The schema of metadata in different eras are mutually exclusive, so we can try deserializing # them one by one without worrying about mismatch. diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index 2dc6254b..cb4ab034 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -35,7 +35,7 @@ class NativeScript(ArrayCBORSerializable): json_field: ClassVar[str] @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[NativeScript], value: list ) -> Union[ diff --git a/pycardano/plutus.py b/pycardano/plutus.py index ea9b172e..4386b99f 100644 --- a/pycardano/plutus.py +++ b/pycardano/plutus.py @@ -989,7 +989,7 @@ class Redeemer(ArrayCBORSerializable): ex_units: Optional[ExecutionUnits] = None @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive(cls: Type[Redeemer], values: list) -> Redeemer: if isinstance(values[2], CBORTag) and cls is Redeemer: values[2] = RawPlutusData.from_primitive(values[2]) @@ -1028,7 +1028,7 @@ class RedeemerValue(ArrayCBORSerializable): ex_units: ExecutionUnits @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive(cls: Type[RedeemerValue], values: list) -> RedeemerValue: if isinstance(values[0], CBORTag) and cls is RedeemerValue: values[0] = RawPlutusData.from_primitive(values[0]) diff --git a/pycardano/pool_params.py b/pycardano/pool_params.py index 2b1ab2aa..3ecaede1 100644 --- a/pycardano/pool_params.py +++ b/pycardano/pool_params.py @@ -165,7 +165,7 @@ def to_primitive(self) -> list: ] @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[SingleHostAddr], values: Union[list, tuple] ) -> SingleHostAddr: @@ -190,7 +190,7 @@ def __post_init__(self): self._CODE = 1 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[SingleHostName], values: Union[list, tuple] ) -> SingleHostName: @@ -213,7 +213,7 @@ def __post_init__(self): self._CODE = 2 @classmethod - @limit_primitive_type(list) + @limit_primitive_type(list, tuple) def from_primitive( cls: Type[MultiHostName], values: Union[list, tuple] ) -> MultiHostName: diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 5ca96447..efbefe93 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -10,20 +10,35 @@ from datetime import datetime from decimal import Decimal from functools import wraps -from inspect import isclass +from inspect import getfullargspec, isclass from typing import ( Any, Callable, ClassVar, Dict, + Generic, + Iterable, List, Optional, + Set, Type, TypeVar, Union, + cast, get_type_hints, ) +import cbor2 + +from pycardano.logging import logger + +# Remove the semantic decoder for 258 (CBOR tag for set) as we care about the order of elements +try: + cbor2._decoder.semantic_decoders.pop(258) +except Exception as e: + logger.warning("Failed to remove semantic decoder for CBOR tag 258", e) + pass + from cbor2 import CBOREncoder, CBORSimpleValue, CBORTag, dumps, loads, undefined from frozendict import frozendict from frozenlist import FrozenList @@ -44,8 +59,12 @@ "RawCBOR", "list_hook", "limit_primitive_type", + "OrderedSet", + "NonEmptyOrderedSet", ] +T = TypeVar("T") + def _identity(x): return x @@ -314,10 +333,12 @@ def validate(self): def _check_recursive(value, type_hint): if type_hint is Any: return True + + if isinstance(value, CBORSerializable): + value.validate() + origin = getattr(type_hint, "__origin__", None) if origin is None: - if isinstance(value, CBORSerializable): - value.validate() return isinstance(value, type_hint) elif origin is ClassVar: return _check_recursive(value, type_hint.__args__[0]) @@ -329,7 +350,7 @@ def _check_recursive(value, type_hint): _check_recursive(k, key_type) and _check_recursive(v, value_type) for k, v in value.items() ) - elif origin in (list, set, tuple, frozenset): + elif origin in (list, set, tuple, frozenset, OrderedSet): if value is None: return True args = type_hint.__args__ @@ -364,12 +385,15 @@ def to_validated_primitive(self) -> Primitive: return self.to_primitive() @classmethod - def from_primitive(cls: Type[CBORBase], value: Any) -> CBORBase: + def from_primitive( + cls: Type[CBORBase], value: Any, type_args: Optional[tuple] = None + ) -> CBORBase: """Turn a CBOR primitive to its original class type. Args: cls (CBORBase): The original class type. value (:const:`Primitive`): A CBOR primitive. + type_args (Optional[tuple]): Type arguments for the class. Returns: CBORBase: A CBOR serializable object. @@ -519,10 +543,26 @@ def _restore_typed_primitive( Union[:const:`Primitive`, CBORSerializable]: A CBOR primitive or a CBORSerializable. """ + is_cbor_serializable = False + try: + is_cbor_serializable = issubclass(t, CBORSerializable) + except TypeError: + # Handle the case when t is a generic alias + origin = typing.get_origin(t) + if origin is not None: + try: + is_cbor_serializable = issubclass(origin, CBORSerializable) + except TypeError: + pass + if t is Any or (t in PRIMITIVE_TYPES and isinstance(v, t)): return v - elif isclass(t) and issubclass(t, CBORSerializable): - return t.from_primitive(v) + elif is_cbor_serializable: + if "type_args" in getfullargspec(t.from_primitive).args: + args = typing.get_args(t) + return t.from_primitive(v, type_args=args) + else: + return t.from_primitive(v) elif hasattr(t, "__origin__") and (t.__origin__ is list): t_args = t.__args__ if len(t_args) != 1: @@ -941,3 +981,82 @@ def list_hook( CBORSerializables. """ return lambda vals: [cls.from_primitive(v) for v in vals] + + +class OrderedSet(list, Generic[T], CBORSerializable): + def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True): + super().__init__() + self._set: Set[str] = set() + self._use_tag = use_tag + if iterable: + self.extend(iterable) + + def append(self, item: T) -> None: + item_key = str(item) + if item_key not in self._set: + super().append(item) + self._set.add(item_key) + + def extend(self, items: Iterable[T]) -> None: + for item in items: + self.append(item) + + def __contains__(self, item: object) -> bool: + return str(item) in self._set + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OrderedSet): + if isinstance(other, list): + return list(self) == other + return False + return list(self) == list(other) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({list(self)})" + + def to_shallow_primitive(self) -> Union[CBORTag, List[T]]: + if self._use_tag: + return CBORTag(258, list(self)) + return list(self) + + @classmethod + def from_primitive( + cls: Type[OrderedSet[T]], value: Primitive, type_args: Optional[tuple] = None + ) -> OrderedSet[T]: + assert ( + type_args is None or len(type_args) == 1 + ), "OrderedSet should have exactly one type argument" + # Retrieve the type arguments from the class + type_arg = type_args[0] if type_args else None + + if isinstance(value, CBORTag) and value.tag == 258: + if isclass(type_arg) and issubclass(type_arg, CBORSerializable): + value.value = [type_arg.from_primitive(v) for v in value.value] + return cls(value.value, use_tag=True) + + if isinstance(value, (list, tuple, set)): + if isclass(type_arg) and issubclass(type_arg, CBORSerializable): + value = [type_arg.from_primitive(v) for v in value] + return cls(list(value), use_tag=False) + + raise ValueError(f"Cannot deserialize {value} to {cls}") + + +class NonEmptyOrderedSet(OrderedSet[T]): + def __init__(self, iterable: Optional[List[T]] = None, use_tag: bool = True): + super().__init__(iterable, use_tag) + + def validate(self): + if not self: + raise ValueError("NonEmptyOrderedSet cannot be empty") + + @classmethod + def from_primitive( + cls: Type[NonEmptyOrderedSet[T]], + value: Primitive, + type_args: Optional[tuple] = None, + ) -> NonEmptyOrderedSet[T]: + result = cast(NonEmptyOrderedSet[T], super().from_primitive(value, type_args)) + if not result: + raise ValueError("NonEmptyOrderedSet cannot be empty") + return result diff --git a/pycardano/transaction.py b/pycardano/transaction.py index d95967ed..e73820b1 100644 --- a/pycardano/transaction.py +++ b/pycardano/transaction.py @@ -35,6 +35,8 @@ DictBase, DictCBORSerializable, MapCBORSerializable, + NonEmptyOrderedSet, + OrderedSet, Primitive, default_encoder, limit_primitive_type, @@ -314,7 +316,9 @@ def __post_init__(self): self._TYPE = self.script.version @classmethod - def from_primitive(cls: Type[_Script], values: List[Primitive]) -> _Script: + def from_primitive( + cls: Type[_Script], values: List[Primitive], type_args: Optional[tuple] = None + ) -> _Script: if values[0] == 0: return cls(NativeScript.from_primitive(values[1])) assert isinstance(values[1], bytes) @@ -344,7 +348,9 @@ def to_shallow_primitive(self) -> Primitive: @classmethod def from_primitive( - cls: Type[_DatumOption], values: List[Primitive] + cls: Type[_DatumOption], + values: List[Primitive], + type_args: Optional[tuple] = None, ) -> _DatumOption: if values[0] == 0: assert isinstance(values[1], bytes) @@ -366,7 +372,9 @@ def to_primitive(self) -> Primitive: return CBORTag(24, cbor2.dumps(self.script, default=default_encoder)) @classmethod - def from_primitive(cls: Type[_ScriptRef], value: Primitive) -> _ScriptRef: + def from_primitive( + cls: Type[_ScriptRef], value: List[Primitive], type_args: Optional[tuple] = None + ) -> _ScriptRef: assert isinstance(value, CBORTag) return cls(_Script.from_primitive(cbor2.loads(value.value))) @@ -459,7 +467,9 @@ def to_primitive(self) -> Primitive: @classmethod def from_primitive( - cls: Type[TransactionOutput], value: Primitive + cls: Type[TransactionOutput], + value: List[Primitive], + type_args: Optional[tuple] = None, ) -> TransactionOutput: if isinstance(value, list): output = _TransactionOutputLegacy.from_primitive(value) @@ -516,9 +526,9 @@ class Withdrawals(DictCBORSerializable): @dataclass(repr=False) class TransactionBody(MapCBORSerializable): - inputs: List[TransactionInput] = field( - default_factory=list, - metadata={"key": 0, "object_hook": list_hook(TransactionInput)}, + inputs: Union[List[TransactionInput], OrderedSet[TransactionInput]] = field( + default_factory=OrderedSet, + metadata={"key": 0}, ) outputs: List[TransactionOutput] = field( @@ -542,7 +552,6 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 5, "optional": True} ) - # TODO: Add proposal update support update: Any = field(default=None, metadata={"key": 6, "optional": True}) auxiliary_data_hash: Optional[AuxiliaryDataHash] = field( @@ -561,21 +570,23 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 11, "optional": True} ) - collateral: Optional[List[TransactionInput]] = field( + collateral: Optional[ + Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]] + ] = field( default=None, metadata={ "key": 13, "optional": True, - "object_hook": list_hook(TransactionInput), }, ) - required_signers: Optional[List[VerificationKeyHash]] = field( + required_signers: Optional[ + Union[List[VerificationKeyHash], NonEmptyOrderedSet[VerificationKeyHash]] + ] = field( default=None, metadata={ "key": 14, "optional": True, - "object_hook": list_hook(VerificationKeyHash), }, ) @@ -591,11 +602,12 @@ class TransactionBody(MapCBORSerializable): default=None, metadata={"key": 17, "optional": True} ) - reference_inputs: Optional[List[TransactionInput]] = field( + reference_inputs: Optional[ + Union[List[TransactionInput], NonEmptyOrderedSet[TransactionInput]] + ] = field( default=None, metadata={ "key": 18, - "object_hook": list_hook(TransactionInput), "optional": True, }, ) diff --git a/pycardano/txbuilder.py b/pycardano/txbuilder.py index 5e42241b..e631b0e4 100644 --- a/pycardano/txbuilder.py +++ b/pycardano/txbuilder.py @@ -57,6 +57,7 @@ datum_hash, script_hash, ) +from pycardano.serialization import NonEmptyOrderedSet, OrderedSet from pycardano.transaction import ( Asset, AssetName, @@ -985,7 +986,7 @@ def _set_redeemer_index(self): def _build_tx_body(self) -> TransactionBody: tx_body = TransactionBody( - [i.input for i in self.inputs], + OrderedSet([i.input for i in self.inputs]), self.outputs, fee=self.fee, ttl=self.ttl, @@ -994,17 +995,28 @@ def _build_tx_body(self) -> TransactionBody: self.auxiliary_data.hash() if self.auxiliary_data else None ), script_data_hash=self.script_data_hash, - required_signers=self.required_signers if self.required_signers else None, + required_signers=( + NonEmptyOrderedSet(self.required_signers) + if self.required_signers + else None + ), validity_start=self.validity_start, collateral=( - [c.input for c in self.collaterals] if self.collaterals else None + NonEmptyOrderedSet([c.input for c in self.collaterals]) + if self.collaterals + else None ), certificates=self.certificates, withdraws=self.withdrawals, collateral_return=self._collateral_return, total_collateral=self._total_collateral, reference_inputs=( - [i.input if isinstance(i, UTxO) else i for i in self.reference_inputs] + NonEmptyOrderedSet( + [ + i.input if isinstance(i, UTxO) else i + for i in self.reference_inputs + ] + ) if self.reference_inputs else None ), @@ -1019,19 +1031,43 @@ def _build_required_vkeys(self) -> Set[VerificationKeyHash]: vkey_hashes.update(self._withdrawal_vkey_hashes()) return vkey_hashes - def _build_fake_vkey_witnesses(self) -> List[VerificationKeyWitness]: - vkey_hashes = self._build_required_vkeys() - - witness_count = self.witness_override or len(vkey_hashes) - - return [ - VerificationKeyWitness(FAKE_VKEY, FAKE_TX_SIGNATURE) - for _ in range(witness_count) - ] + def _witness_count(self) -> int: + return self.witness_override or len(self._build_required_vkeys()) + + def _build_fake_vkey_witnesses(self) -> NonEmptyOrderedSet[VerificationKeyWitness]: + witnesses = [] + for i in range(self._witness_count()): + # Convert index to 32 bytes and use AND operation to create unique keys + i_bytes = i.to_bytes(32, "big") + unique_vkey = VerificationKey.from_primitive( + bytes( + x & y + for x, y in zip( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ), + i_bytes, + ) + ) + ) + unique_sig = bytes( + x & y + for x, y in zip( + bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d" + "763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ), + i_bytes + i_bytes, # 64 bytes for signature + ) + ) + witnesses.append(VerificationKeyWitness(unique_vkey, unique_sig)) + return NonEmptyOrderedSet(witnesses) def _build_fake_witness_set(self) -> TransactionWitnessSet: witness_set = self.build_witness_set() - witness_set.vkey_witnesses = self._build_fake_vkey_witnesses() + if self._witness_count() > 0: + witness_set.vkey_witnesses = self._build_fake_vkey_witnesses() + return witness_set def _build_full_fake_tx(self) -> Transaction: @@ -1051,6 +1087,7 @@ def _build_full_fake_tx(self) -> Transaction: f"({self.context.protocol_param.max_tx_size}). Please try reducing the " f"number of inputs or outputs." ) + return tx def build_witness_set( @@ -1067,10 +1104,10 @@ def build_witness_set( TransactionWitnessSet: A transaction witness set without verification key witnesses. """ - native_scripts: List[NativeScript] = [] - plutus_v1_scripts: List[PlutusV1Script] = [] - plutus_v2_scripts: List[PlutusV2Script] = [] - plutus_v3_scripts: List[PlutusV3Script] = [] + native_scripts: NonEmptyOrderedSet[NativeScript] = NonEmptyOrderedSet() + plutus_v1_scripts: NonEmptyOrderedSet[PlutusV1Script] = NonEmptyOrderedSet() + plutus_v2_scripts: NonEmptyOrderedSet[PlutusV2Script] = NonEmptyOrderedSet() + plutus_v3_scripts: NonEmptyOrderedSet[PlutusV3Script] = NonEmptyOrderedSet() input_scripts = ( { @@ -1586,6 +1623,8 @@ def build_and_sign( """ # The given signers should be required signers if they weren't added yet if auto_required_signers and self.scripts and not self.required_signers: + # Collect all signatories from explicitly defined + # transaction inputs and collateral inputs, and input addresses self.required_signers = [ s.to_verification_key().hash() for s in signing_keys ] @@ -1599,7 +1638,7 @@ def build_and_sign( auto_required_signers=auto_required_signers, ) witness_set = self.build_witness_set(True) - witness_set.vkey_witnesses = [] + witness_set.vkey_witnesses = NonEmptyOrderedSet() required_vkeys = self._build_required_vkeys() diff --git a/pycardano/witness.py b/pycardano/witness.py index 6fc4654e..49e4b22a 100644 --- a/pycardano/witness.py +++ b/pycardano/witness.py @@ -17,6 +17,7 @@ from pycardano.serialization import ( ArrayCBORSerializable, MapCBORSerializable, + NonEmptyOrderedSet, limit_primitive_type, list_hook, ) @@ -48,18 +49,24 @@ def from_primitive( @dataclass(repr=False) class TransactionWitnessSet(MapCBORSerializable): - vkey_witnesses: Optional[List[VerificationKeyWitness]] = field( + vkey_witnesses: Optional[ + Union[List[VerificationKeyWitness], NonEmptyOrderedSet[VerificationKeyWitness]] + ] = field( default=None, metadata={ - "optional": True, "key": 0, - "object_hook": list_hook(VerificationKeyWitness), + "optional": True, }, ) - native_scripts: Optional[List[NativeScript]] = field( + native_scripts: Optional[ + Union[List[NativeScript], NonEmptyOrderedSet[NativeScript]] + ] = field( default=None, - metadata={"optional": True, "key": 1, "object_hook": list_hook(NativeScript)}, + metadata={ + "key": 1, + "optional": True, + }, ) # TODO: Add bootstrap witness (byron) support @@ -67,8 +74,14 @@ class TransactionWitnessSet(MapCBORSerializable): default=None, metadata={"optional": True, "key": 2} ) - plutus_v1_script: Optional[List[PlutusV1Script]] = field( - default=None, metadata={"optional": True, "key": 3} + plutus_v1_script: Optional[ + Union[List[PlutusV1Script], NonEmptyOrderedSet[PlutusV1Script]] + ] = field( + default=None, + metadata={ + "key": 3, + "optional": True, + }, ) plutus_data: Optional[List[Any]] = field( @@ -81,10 +94,35 @@ class TransactionWitnessSet(MapCBORSerializable): metadata={"optional": True, "key": 5}, ) - plutus_v2_script: Optional[List[PlutusV2Script]] = field( - default=None, metadata={"optional": True, "key": 6} + plutus_v2_script: Optional[ + Union[List[PlutusV2Script], NonEmptyOrderedSet[PlutusV2Script]] + ] = field( + default=None, + metadata={ + "key": 6, + "optional": True, + }, ) - plutus_v3_script: Optional[List[PlutusV3Script]] = field( - default=None, metadata={"optional": True, "key": 7} + plutus_v3_script: Optional[ + Union[List[PlutusV3Script], NonEmptyOrderedSet[PlutusV3Script]] + ] = field( + default=None, + metadata={ + "key": 7, + "optional": True, + }, ) + + def __post_init__(self): + # Convert lists to NonEmptyOrderedSet for fields that should use NonEmptyOrderedSet + if isinstance(self.vkey_witnesses, list): + self.vkey_witnesses = NonEmptyOrderedSet(self.vkey_witnesses) + if isinstance(self.native_scripts, list): + self.native_scripts = NonEmptyOrderedSet(self.native_scripts) + if isinstance(self.plutus_v1_script, list): + self.plutus_v1_script = NonEmptyOrderedSet(self.plutus_v1_script) + if isinstance(self.plutus_v2_script, list): + self.plutus_v2_script = NonEmptyOrderedSet(self.plutus_v2_script) + if isinstance(self.plutus_v3_script, list): + self.plutus_v3_script = NonEmptyOrderedSet(self.plutus_v3_script) diff --git a/pyproject.toml b/pyproject.toml index f296bc1c..d448f252 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.8.1" PyNaCl = "^1.5.0" -cbor2 = "^5.4.3" +cbor2 = "^5.6.5" typeguard = "^4.3.0" blockfrost-python = "0.6.0" websocket-client = "^1.4.1" diff --git a/test/pycardano/test_certificate.py b/test/pycardano/test_certificate.py index 58159d42..42f622bf 100644 --- a/test/pycardano/test_certificate.py +++ b/test/pycardano/test_certificate.py @@ -146,6 +146,10 @@ def test_staking_certificate_serdes(): ] ) - after_serdes = TransactionBody.from_cbor(transaction_body.to_cbor()) + primitives = transaction_body.to_validated_primitive() + + cbor_hex = transaction_body.to_cbor_hex() + + after_serdes = TransactionBody.from_cbor(cbor_hex) assert after_serdes == transaction_body diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index 82d59fe6..55c13282 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -1,12 +1,34 @@ from collections import defaultdict, deque from dataclasses import dataclass, field from test.pycardano.util import check_two_way_cbor -from typing import Any, Deque, Dict, List, Optional, Set, Tuple, Union +from typing import ( + Any, + Deque, + Dict, + List, + Optional, + Set, + Tuple, + Type, + Union, + get_args, + get_origin, +) import cbor2 import pytest - -from pycardano import Datum, MultiAsset, RawPlutusData, Transaction +from cbor2 import CBORTag + +from pycardano import ( + CBORBase, + Datum, + MultiAsset, + RawPlutusData, + Transaction, + TransactionWitnessSet, + VerificationKey, + VerificationKeyWitness, +) from pycardano.exception import DeserializeException, SerializeException from pycardano.plutus import PlutusV1Script, PlutusV2Script from pycardano.serialization import ( @@ -16,6 +38,8 @@ DictCBORSerializable, IndefiniteList, MapCBORSerializable, + NonEmptyOrderedSet, + OrderedSet, RawCBOR, default_encoder, limit_primitive_type, @@ -532,3 +556,177 @@ class Test2(DictCBORSerializable): t["x"] = Test1(a=1, b="x") t["y"] = Test1(a=2, b="y") t.copy() + + +def test_ordered_set(): + # Test basic functionality + s = OrderedSet([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert 2 in s + assert 4 not in s + + # Test uniqueness + s.append(2) + assert list(s) == [1, 2, 3] # 2 not added again + + # Test order preservation + s.append(4) + assert list(s) == [1, 2, 3, 4] + + # Test extend + s.extend([5, 2, 6]) # 2 is duplicate + assert list(s) == [1, 2, 3, 4, 5, 6] + + # Test equality + s2 = OrderedSet([1, 2, 3, 4, 5, 6]) + assert s == s2 + assert s == [1, 2, 3, 4, 5, 6] # List comparison + + # Test serialization without tag + s = OrderedSet([1, 2, 3], use_tag=False) + primitive = s.to_primitive() + assert isinstance(primitive, list) + assert primitive == [1, 2, 3] + + # Test serialization with tag + s = OrderedSet([1, 2, 3], use_tag=True) + primitive = s.to_primitive() + assert isinstance(primitive, CBORTag) + assert primitive.tag == 258 + assert primitive.value == [1, 2, 3] + + # Test deserialization from list + s = OrderedSet.from_primitive([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert not s._use_tag + + # Test deserialization from tagged set + s = OrderedSet.from_primitive(CBORTag(258, [1, 2, 3])) + assert list(s) == [1, 2, 3] + assert s._use_tag + + +def test_ordered_set_with_complex_types(): + # Test with VerificationKeyWitness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + witness_set = TransactionWitnessSet( + vkey_witnesses=NonEmptyOrderedSet[VerificationKeyWitness]([witness]) + ) + + # Test serialization/deserialization + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert restored == witness_set + + +def test_non_empty_ordered_set(): + # Test basic functionality + s = NonEmptyOrderedSet([1, 2, 3]) + assert list(s) == [1, 2, 3] + + # Test validation of non-empty constraint + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): + s = NonEmptyOrderedSet() + s.to_validated_primitive() + + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): + s = NonEmptyOrderedSet([]) + s.to_validated_primitive() + + # Test serialization without tag + s = NonEmptyOrderedSet([1, 2, 3], use_tag=False) + primitive = s.to_primitive() + assert isinstance(primitive, list) + assert primitive == [1, 2, 3] + + # Test serialization with tag + s = NonEmptyOrderedSet([1, 2, 3], use_tag=True) + primitive = s.to_primitive() + assert isinstance(primitive, CBORTag) + assert primitive.tag == 258 + assert primitive.value == [1, 2, 3] + + # Test deserialization + s = NonEmptyOrderedSet.from_primitive([1, 2, 3]) + assert list(s) == [1, 2, 3] + assert not s._use_tag + + s = NonEmptyOrderedSet.from_primitive(CBORTag(258, [1, 2, 3])) + assert list(s) == [1, 2, 3] + assert s._use_tag + + +def test_non_empty_ordered_set_with_complex_types(): + # Test with VerificationKeyWitness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + # Create NonEmptyOrderedSet[VerificationKeyWitness] + s = NonEmptyOrderedSet[VerificationKeyWitness]([witness]) + assert len(s) == 1 + assert witness in s + + # Test serialization/deserialization + primitive = s.to_primitive() + restored = NonEmptyOrderedSet[VerificationKeyWitness].from_primitive( + primitive, type_args=(VerificationKeyWitness,) + ) + assert restored == s + assert restored[0].vkey == witness.vkey + assert restored[0].signature == witness.signature + + # Test empty set validation + s = NonEmptyOrderedSet[VerificationKeyWitness]() + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): + s.to_validated_primitive() + + +def test_transaction_witness_set_with_ordered_sets(): + # Create a witness + vkey = VerificationKey.from_primitive( + bytes.fromhex( + "5797dc2cc919dfec0bb849551ebdf30d96e5cbe0f33f734a87fe826db30f7ef9" + ) + ) + sig = bytes.fromhex( + "577ccb5b487b64e396b0976c6f71558e52e44ad254db7d06dfb79843e5441a5d763dd42adcf5e8805d70373722ebbce62a58e3f30dd4560b9a898b8ceeab6a03" + ) + witness = VerificationKeyWitness(vkey, sig) + + # Test conversion from list to NonEmptyOrderedSet + witness_set = TransactionWitnessSet(vkey_witnesses=[witness]) + assert isinstance(witness_set.vkey_witnesses, NonEmptyOrderedSet) + assert witness in witness_set.vkey_witnesses + + # Test serialization/deserialization + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert isinstance(restored.vkey_witnesses, NonEmptyOrderedSet) + assert restored.vkey_witnesses == witness_set.vkey_witnesses + + # Test empty list conversion + witness_set = TransactionWitnessSet(vkey_witnesses=[]) + with pytest.raises(ValueError, match="NonEmptyOrderedSet cannot be empty"): + witness_set.to_validated_primitive() + + # Test None value + witness_set = TransactionWitnessSet(vkey_witnesses=None) + primitive = witness_set.to_primitive() + restored = TransactionWitnessSet.from_primitive(primitive) + assert restored.vkey_witnesses is None diff --git a/test/pycardano/test_txbuilder.py b/test/pycardano/test_txbuilder.py index 78732bba..f31d0385 100644 --- a/test/pycardano/test_txbuilder.py +++ b/test/pycardano/test_txbuilder.py @@ -7,6 +7,7 @@ from unittest.mock import patch import pytest +from cbor2 import CBORTag from pycardano import ( AssetName, @@ -80,14 +81,14 @@ def test_tx_builder(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 4334587], + [sender_address.to_primitive(), 4334323], ], - 2: 165413, + 2: 165677, } assert expected == tx_body.to_primitive() @@ -107,21 +108,24 @@ def test_tx_builder_no_change(chain_context): def test_tx_builder_with_certain_input(chain_context): - tx_builder = TransactionBuilder(chain_context, [RandomImproveMultiAsset([0, 0])]) + tx_builder = TransactionBuilder(chain_context) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" sender_address = Address.from_primitive(sender) - utxos = chain_context.utxos(sender) + tx_in1 = TransactionInput.from_primitive([b"2" * 32, 1]) + tx_out1 = TransactionOutput.from_primitive( + [sender, [6000000, {b"1" * 28: {b"Token1": 1, b"Token2": 2}}]] + ) + utxo1 = UTxO(tx_in1, tx_out1) - # Add sender address as input - tx_builder.add_input_address(sender).add_input(utxos[1]).add_output( + tx_builder.add_input(utxo1).add_output( TransactionOutput.from_primitive([sender, 500000]) ) tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 1]], + 0: CBORTag(258, [[b"2" * 32, 1]]), 1: [ # First output [sender_address.to_primitive(), 500000], @@ -129,12 +133,12 @@ def test_tx_builder_with_certain_input(chain_context): [ sender_address.to_primitive(), [ - 5332431, + 5332167, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 167569, + 2: 167833, } assert expected == tx_body.to_primitive() @@ -184,10 +188,13 @@ def test_tx_builder_multi_asset(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], @@ -199,10 +206,10 @@ def test_tx_builder_multi_asset(chain_context): # Third output as change [ sender_address.to_primitive(), - [5827767, {b"1111111111111111111111111111": {b"Token2": 2}}], + [5827503, {b"1111111111111111111111111111": {b"Token2": 2}}], ], ], - 2: 172233, + 2: 172497, } assert expected == tx_body.to_primitive() @@ -227,9 +234,9 @@ def test_tx_builder_raises_utxo_selection(chain_context): change_address=sender_address, ) - # The unfulfilled amount includes requested (991000000) and estimated fees (161277) - assert "Unfulfilled amount:\n {\n 'coin': 991161277" in e.value.args[0] - assert "{AssetName(b'NewToken'): 1}" in e.value.args[0] + # The unfulfilled amount includes requested amount and estimated fees + assert "'coin': 991161321" in e.value.args[0] + assert "AssetName(b'NewToken'): 1" in e.value.args[0] def test_tx_builder_state_logger_warning_level(chain_context, caplog): @@ -288,16 +295,14 @@ def test_tx_small_utxo_precise_fee(chain_context): tx_body = tx_builder.build(change_address=sender_address) expect = { - 0: [ - [b"11111111111111111111111111111111", 3], - ], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ # First output [sender_address.to_primitive(), 2500000], # Second output as change - [sender_address.to_primitive(), 1334587], + [sender_address.to_primitive(), 1334323], ], - 2: 165413, + 2: 165677, } assert expect == tx_body.to_primitive() @@ -340,17 +345,20 @@ def test_tx_small_utxo_balance_pass(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"11111111111111111111111111111111", 3], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"11111111111111111111111111111111", 3], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], # Second output as change - [sender_address.to_primitive(), 5833003], + [sender_address.to_primitive(), 5832739], ], - 2: 166997, + 2: 167261, } assert expected == tx_body.to_primitive() @@ -386,10 +394,13 @@ def test_tx_builder_mint_multi_asset(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 3000000], @@ -399,16 +410,16 @@ def test_tx_builder_mint_multi_asset(chain_context): [ sender_address.to_primitive(), [ - 5809683, + 5809155, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 190317, + 2: 190845, 3: 123456789, 8: 1000, 9: mint, - 14: [sender_address.payment_part.to_primitive()], + 14: CBORTag(258, [sender_address.payment_part.to_primitive()]), } assert expected == tx_body.to_primitive() @@ -470,10 +481,13 @@ def test_tx_add_change_split_nfts(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [ - [b"11111111111111111111111111111111", 0], - [b"22222222222222222222222222222222", 1], - ], + 0: CBORTag( + 258, + [ + [b"11111111111111111111111111111111", 0], + [b"22222222222222222222222222222222", 1], + ], + ), 1: [ # First output [sender_address.to_primitive(), 7000000], @@ -486,10 +500,10 @@ def test_tx_add_change_split_nfts(chain_context): # Fourth output as change [ sender_address.to_primitive(), - [2793367, {b"1111111111111111111111111111": {b"Token2": 2}}], + [2793103, {b"1111111111111111111111111111": {b"Token2": 2}}], ], ], - 2: 172233, + 2: 172497, } assert expected == tx_body.to_primitive() @@ -631,8 +645,7 @@ def test_add_script_input_payment_script(chain_context): script_address = Address(vk1.hash()) datum = PlutusData() utxo1 = UTxO( - tx_in1, - TransactionOutput(script_address, 10000000, datum_hash=datum.hash()), + tx_in1, TransactionOutput(script_address, 10000000, datum_hash=datum.hash()) ) redeemer = Redeemer(PlutusData(), ExecutionUnits(1000000, 1000000)) pytest.raises( @@ -1280,7 +1293,9 @@ def test_add_minting_script_wrong_redeemer_type(chain_context): def test_excluded_input(chain_context): - tx_builder = TransactionBuilder(chain_context, [RandomImproveMultiAsset([0, 0])]) + tx_builder = TransactionBuilder( + chain_context, [RandomImproveMultiAsset([0, 0, 0, 0, 0])] + ) sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x" sender_address = Address.from_primitive(sender) @@ -1294,7 +1309,7 @@ def test_excluded_input(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 1]], + 0: CBORTag(258, [[b"22222222222222222222222222222222", 1]]), 1: [ # First output [sender_address.to_primitive(), 500000], @@ -1302,12 +1317,12 @@ def test_excluded_input(chain_context): [ sender_address.to_primitive(), [ - 5332431, + 5332167, {b"1111111111111111111111111111": {b"Token1": 1, b"Token2": 2}}, ], ], ], - 2: 167569, + 2: 167833, } assert expected == tx_body.to_primitive() @@ -1342,10 +1357,8 @@ def test_build_and_sign(chain_context): VerificationKeyWitness(SK.to_verification_key(), SK.sign(tx_body.hash())) ] assert ( - "a300818258203131313131313131313131313131313131313131313131313131313131313131" - "00018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a0007" - "a12082581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a004223" - "fb021a00028625" == tx_body.to_cbor_hex() + "a300d9010281825820313131313131313131313131313131313131313131313131313131313131313100018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a0007a12082581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a004222f3021a0002872d" + == tx_body.to_cbor_hex() ) @@ -1434,11 +1447,11 @@ def test_tx_builder_exact_fee_no_change(chain_context): tx = tx_builder.build_and_sign([SK]) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx.transaction_body.to_primitive() @@ -1470,14 +1483,14 @@ def test_tx_builder_certificates(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 2325743], + [sender_address.to_primitive(), 2325479], ], - 2: 174257, + 2: 174521, 4: [ [0, [0, b"1111111111111111111111111111"]], [2, [0, b"1111111111111111111111111111"], b"1111111111111111111111111111"], @@ -1589,14 +1602,14 @@ def test_tx_builder_stake_pool_registration(chain_context, pool_params): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"22222222222222222222222222222222", 2]], + 0: CBORTag(258, [[b"22222222222222222222222222222222", 2]]), 1: [ [ b"`\xf6S(P\xe1\xbc\xce\xe9\xc7*\x91\x13\xad\x98\xbc\xc5\xdb\xb3\r*\xc9`&$D\xf6\xe5\xf4", - 4819407, + 4819143, ] ], - 2: 180593, + 2: 180857, 4: [ [ 3, @@ -1645,14 +1658,14 @@ def test_tx_builder_withdrawal(chain_context): tx_body = tx_builder.build(change_address=sender_address) expected = { - 0: [[b"11111111111111111111111111111111", 0]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 0]]), 1: [ # First output [sender_address.to_primitive(), 500000], # Second output as change - [sender_address.to_primitive(), 4338559], + [sender_address.to_primitive(), 4338295], ], - 2: 171441, + 2: 171705, 5: { b"\xe0H(\xa2\xda\xdb\xa9|\xa9\xfd\x0c\xdc\x99\x97X\x99G\x0c!\x9b\xdc\r\x82\x8c\xfam\xdfmi": 10000 }, @@ -1680,11 +1693,11 @@ def test_tx_builder_no_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1710,11 +1723,11 @@ def test_tx_builder_merge_change_to_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1744,13 +1757,13 @@ def test_tx_builder_merge_change_to_output_2(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ [sender_address.to_primitive(), 10000], [receiver_address.to_primitive(), 10000], - [sender_address.to_primitive(), 9813135], + [sender_address.to_primitive(), 9812871], ], - 2: 166865, + 2: 167129, } assert expected == tx_body.to_primitive() @@ -1776,11 +1789,11 @@ def test_tx_builder_merge_change_to_zero_amount_output(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -1806,11 +1819,11 @@ def test_tx_builder_merge_change_smaller_than_min_utxo(chain_context): ) expected = { - 0: [[b"11111111111111111111111111111111", 3]], + 0: CBORTag(258, [[b"11111111111111111111111111111111", 3]]), 1: [ - [sender_address.to_primitive(), 9836215], + [sender_address.to_primitive(), 9835951], ], - 2: 163785, + 2: 164049, } assert expected == tx_body.to_primitive() @@ -2248,3 +2261,6 @@ def test_burning_all_assets_under_single_policy(chain_context): assert AssetName(b"AssetName2") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {}) + + assert AssetName(b"AssetName3") not in multi_asset.get(policy_id_1, {}) + assert AssetName(b"AseetName4") not in multi_asset.get(policy_id_1, {})